delete incorrectly uploaded skill
This commit is contained in:
304
skills/SKILL.md
304
skills/SKILL.md
@@ -1,304 +0,0 @@
|
||||
---
|
||||
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 <name> <url>", "/ai set token <provider> <token>")
|
||||
CMD_DESC("Interact with AI models via OpenAI-compatible APIs.")
|
||||
CMD_ARGS({ "", "Display current AI settings" },
|
||||
{ "set provider <name> <url>", "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 [<arg>]")
|
||||
CMD_DESC("My new command description.")
|
||||
CMD_ARGS({ "<arg>", "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
|
||||
Reference in New Issue
Block a user