Files
cproof-context/skills/command-system.md
2026-04-30 18:21:04 +00:00

10 KiB

name, description
name description
command-system 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)

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)

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)

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())

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

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)

// 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)

Macros (cmd_defs.c:91-98)

#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

{ 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)

Initialization

// 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

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

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

if (connection_get_status() != JABBER_CONNECTED) {
    cons_show("You are not currently connected.");
    return TRUE;
}

Options Parsing

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

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)

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)

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

gboolean cmd_mycommand(ProfWin* window, const char* const command, gchar** args);

Step 2: Implement Function in cmd_funcs.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:

    { 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:
static char* _mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous);
  1. Register in cmd_ac_init():
g_hash_table_insert(ac_funcs, "/mycommand", _mycommand_autocomplete);
  1. Implement the function:
static char*
_mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
    return NULL;  // No autocomplete
}

Key Files