From a9e62fc2cb1f4bcba6d721a030f54e7d61ea46da68ddd7a8d66c10466d56a62f Mon Sep 17 00:00:00 2001 From: "jabber.developer" Date: Thu, 30 Apr 2026 18:18:50 +0000 Subject: [PATCH] Add skills --- skills/SKILL.md | 304 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 skills/SKILL.md diff --git a/skills/SKILL.md b/skills/SKILL.md new file mode 100644 index 0000000..fb27087 --- /dev/null +++ b/skills/SKILL.md @@ -0,0 +1,304 @@ +--- +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