--- name: command-system description: Use when writing, modifying, or debugging CProof command functions (cmd_funcs.c), command definitions (cmd_defs.c), or autocomplete (cmd_ac.c). Documents the command dispatch architecture, critical argument indexing rules (args[0] contains subcommand name), and common patterns. --- # CProof Command System ## When to Use This Skill Use this skill when: - Writing new command handler functions in `cmd_funcs.c` - Adding/modifying command definitions in `cmd_defs.c` - Implementing autocomplete for commands in `cmd_ac.c` - Debugging command argument parsing issues ## Core Architecture ### Command Structure ([`Command`](src/command/cmd_defs.h:64)) ```c typedef struct cmd_t { gchar* cmd; // e.g., "/ai" gchar** (*parser)(const char*, int, int, gboolean*); // parse_args, parse_args_with_freetext, parse_args_as_one int min_args; int max_args; void (*setting_func)(void); // Shows current settings in console (or NULL) struct { const char* cmd; gboolean (*func)(ProfWin*, const char*, gchar**); } sub_funcs[50]; gboolean (*func)(ProfWin*, const char*, gchar**); // Main handler (or NULL) CommandHelp help; // Tags, synopsis, desc, args, examples } Command; ``` ### Help Structure ([`CommandHelp`](src/command/cmd_funcs.h:43)) ```c typedef struct cmd_help_t { gchar* tags[20]; // Command categories (NULL-terminated) gchar* synopsis[50]; // Usage examples (NULL-terminated) gchar* desc; // Description text gchar* args[128][2]; // Argument names and descriptions ({NULL,NULL}-terminated) gchar* examples[20]; // Example commands (NULL-terminated) } CommandHelp; ``` ### Available Tags ([`cmd_defs.c:82-89`](src/command/cmd_defs.c:82)) | Tag | Description | |-----|-------------| | `CMD_TAG_CHAT` | Chat commands | | `CMD_TAG_GROUPCHAT` | Group chat commands | | `CMD_TAG_ROSTER` | Roster management | | `CMD_TAG_PRESENCE` | Presence commands | | `CMD_TAG_CONNECTION` | Connection management | | `CMD_TAG_DISCOVERY` | Discovery commands | | `CMD_TAG_UI` | UI display commands | | `CMD_TAG_PLUGINS` | Plugin commands | ### Dispatch Flow ([`_cmd_execute()`](src/command/cmd_funcs.c:8261)) ``` User: /ai set provider openai https://api.openai.com | v parse_args("set provider openai https://...", 0, 5) → {"set","provider","openai","https://...",NULL} | v args[0]="set" matches cmd->sub_funcs[0].cmd="set" | v cmd_ai_set(window, "/ai", args) ← args[0] is "set", NOT "provider"! ``` **CRITICAL: Subcommand functions receive the FULL args array. args[0] contains the subcommand name itself. All parameter accesses must be shifted +1.** ## Argument Indexing Reference | User Input | args[0] | args[1] | args[2] | args[3] | |------------|---------|---------|---------|---------| | `/ai` | NULL | - | - | - | | `/ai set provider openai url` | `"set"` | `"provider"` | `"openai"` | `"url"` | | `/ai set token openai sk-xxx` | `"set"` | `"token"` | `"openai"` | `"sk-xxx"` | | `/ai start openai/gpt-4o` | `"start"` | `"openai/gpt-4o"` | - | - | | `/ai remove provider foo` | `"remove"` | `"provider"` | `"foo"` | - | | `/tls trust` | `"trust"` | - | - | - | | `/connect account server foo` | `"account"` | `"server"` | `"foo"` | - | ## Correct Subcommand Function Pattern ```c gboolean cmd_ai_set(ProfWin* window, const char* const command, gchar** args) { // args[0] = "set" (subcommand name) // args[1] = next subcommand (provider/token/org) if (args[1] == NULL) { cons_bad_cmd_usage(command); return TRUE; } if (g_strcmp0(args[1], "provider") == 0) { // args[2] = name, args[3] = url if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } ai_add_provider(args[2], args[3], NULL); cons_show("Provider '%s' configured with URL: %s", args[2], args[3]); } else if (g_strcmp0(args[1], "token") == 0) { // args[2] = provider, args[3] = token if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } ai_set_provider_key(args[2], args[3]); } else if (g_strcmp0(args[1], "org") == 0) { // args[2] = provider, args[3] = org_id if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } AIProvider* p = ai_get_provider(args[2]); if (p) { g_free(p->org_id); p->org_id = g_strdup(args[3]); } } else { cons_bad_cmd_usage(command); return TRUE; } cons_show(""); return TRUE; } ``` ### Common Bug (WRONG) ```c // BUG: args[0] is "set", not "provider" if (g_strcmp0(args[0], "provider") == 0) { ... } // BUG: args[1] is "provider", not the name ai_add_provider(args[1], args[2], NULL); ``` ## Command Definition Pattern ([`cmd_defs.c`](src/command/cmd_defs.c)) ### Macros ([`cmd_defs.c:91-98`](src/command/cmd_defs.c:91)) ```c #define CMD_PREAMBLE(c, p, min, max, set) .cmd = c, .parser = p, .min_args = min, .max_args = max, .setting_func = set, #define CMD_MAINFUNC(f) .func = f, #define CMD_SUBFUNCS(...) .sub_funcs = { __VA_ARGS__, { NULL, NULL } }, #define CMD_TAGS(...) .help.tags = { __VA_ARGS__, NULL }, #define CMD_SYN(...) .help.synopsis = { __VA_ARGS__, NULL }, #define CMD_DESC(d) .help.desc = d, #define CMD_ARGS(...) .help.args = { __VA_ARGS__, { NULL, NULL } }, #define CMD_EXAMPLES(...) .help.examples = { __VA_ARGS__, NULL } ``` ### Example Definition ```c { CMD_PREAMBLE("/ai", parse_args, 0, 5, NULL) CMD_SUBFUNCS({ "set", cmd_ai_set }, { "remove", cmd_ai_remove }, { "start", cmd_ai_start }, { "clear", cmd_ai_clear }, { "correct", cmd_ai_correct }, { "providers", cmd_ai_providers }) CMD_MAINFUNC(cmd_ai) CMD_TAGS(CMD_TAG_CHAT) CMD_SYN("/ai", "/ai set provider ", "/ai set token ") CMD_DESC("Interact with AI models via OpenAI-compatible APIs.") CMD_ARGS({ "", "Display current AI settings" }, { "set provider ", "Add or update a provider" }) CMD_EXAMPLES("/ai", "/ai set token openai sk-xxx", "/ai start openai/gpt-4o") }, ``` ## Autocomplete Pattern ([`cmd_ac.c`](src/command/cmd_ac.c)) ### Initialization ```c // In cmd_ac_init(): autocomplete_add(ai_subcommands_ac, "set"); autocomplete_add(ai_subcommands_ac, "remove"); autocomplete_add(ai_set_subcommands_ac, "provider"); autocomplete_add(ai_set_subcommands_ac, "token"); autocomplete_add(ai_set_subcommands_ac, "org"); g_hash_table_insert(ac_funcs, "/ai", _ai_autocomplete); ``` ### Callback ```c static char* _ai_autocomplete(ProfWin* window, const char* const input, gboolean previous) { char* result = NULL; result = autocomplete_param_with_ac(input, "/ai", ai_subcommands_ac, TRUE, previous); if (result) return result; result = autocomplete_param_with_ac(input, "/ai set", ai_set_subcommands_ac, TRUE, previous); if (result) return result; // Dynamic: provider names from ai_list_providers() return NULL; } ``` ## Common Patterns ### Window Type Access ```c ProfAiWin* aiwin = (window && window->type == WIN_AI) ? (ProfAiWin*)window : wins_get_ai(); // Or switch: switch (window->type) { case WIN_CHAT: { ProfChatWin* w = (ProfChatWin*)window; /* w->barejid */ break; } case WIN_MUC: { ProfMucWin* w = (ProfMucWin*)window; /* w->roomjid */ break; } } ``` ### Connection Check ```c if (connection_get_status() != JABBER_CONNECTED) { cons_show("You are not currently connected."); return TRUE; } ``` ### Options Parsing ```c gchar* opt_keys[] = { "server", "port", "tls", NULL }; GHashTable* opts = parse_options(&args[args[0] ? 1 : 0], opt_keys, &parsed); // &args[args[0] ? 1 : 0] skips args[0] if present ``` ### Memory Management ```c auto_gcharv gchar** args = cmd->parser(inp, min, max, &result); // Auto-freed auto_gchar gchar* path = prefs_get_string(PREF_SOME_PREF); // Auto-freed ``` ## Argument Parsers ([`parser.c`](src/tools/parser.c)) | Function | Description | |----------|-------------| | `parse_args()` | Standard argument parsing, splits on spaces | | `parse_args_with_freetext()` | Last argument captures everything after `max` tokens | | `parse_args_as_one()` | Everything after first space becomes single argument | ## Console Output Functions ([`ui.h`](src/ui/ui.h)) | Function | Description | |----------|-------------| | `cons_show(const char* msg, ...)` | Print formatted message to console | | `cons_show_error(const char* cmd, ...)` | Print error message | | `cons_bad_cmd_usage(const char* cmd)` | Print usage error | ## Adding a New Command: Step-by-Step ### Step 1: Declare Function in `cmd_funcs.h` ```c gboolean cmd_mycommand(ProfWin* window, const char* const command, gchar** args); ``` ### Step 2: Implement Function in `cmd_funcs.c` ```c gboolean cmd_mycommand(ProfWin* window, const char* const command, gchar** args) { // args[0] = first argument (or NULL if none) if (args[0] == NULL) { cons_show("No argument provided."); return TRUE; } cons_show("Got: %s", args[0]); return TRUE; } ``` ### Step 3: Add Definition to `command_defs[]` in `cmd_defs.c` Find the end of the array (look for `};` after the last entry) and add: ```c { CMD_PREAMBLE("/mycommand", parse_args, 0, 1, NULL) CMD_MAINFUNC(cmd_mycommand) CMD_TAGS(CMD_TAG_CHAT) CMD_SYN("/mycommand []") CMD_DESC("My new command description.") CMD_ARGS({ "", "An argument" }) CMD_EXAMPLES("/mycommand hello") }, ``` ### Step 4: Add Autocomplete In `cmd_ac.c`: 1. Add static function declaration at top: ```c static char* _mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous); ``` 2. Register in `cmd_ac_init()`: ```c g_hash_table_insert(ac_funcs, "/mycommand", _mycommand_autocomplete); ``` 3. Implement the function: ```c static char* _mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous) { return NULL; // No autocomplete } ``` ## Key Files - [`src/command/cmd_defs.c`](src/command/cmd_defs.c) - Command definitions - [`src/command/cmd_defs.h`](src/command/cmd_defs.h) - Type definitions - [`src/command/cmd_funcs.c`](src/command/cmd_funcs.c) - Handler implementations - [`src/command/cmd_funcs.h`](src/command/cmd_funcs.h) - Function declarations - [`src/command/cmd_ac.c`](src/command/cmd_ac.c) - Autocomplete - [`src/tools/parser.c`](src/tools/parser.c) - Argument parsing - [`src/ui/ui.h`](src/ui/ui.h) - Console output functions