#include "cst_agent.h" #include "os_compat.h" #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif static void usage(const char *argv0) { fprintf(stderr, "Usage: %s [--workspace DIR] [--session FILE] [--api-base URL]\n" " [--api-key KEY] [--model NAME] [--headless]\n" " [--prompt TEXT] [--demo-ctl] [--ask-key]\n" "\n" "Defaults: api-base=https://api.minimax.cn/v1 model=MiniMax-M2.7\n" "Session: /.agent/sessions/.jsonl (CURRENT tracked)\n" "API key: CST_API_KEY / OPENAI_API_KEY / MINIMAX_API_KEY\n" "Base: CST_API_BASE / OPENAI_BASE_URL (OpenAI-compatible …/v1)\n" "Model: CST_MODEL / OPENAI_MODEL\n" "Also: CST_MODELS, CST_AUTO_APPROVE=1, CST_SHELL, CST_EXE_DIR\n" "TUI: /new /session /model /compact /details /help\n" " Enter send · Ctrl+N newline · ↑↓ history · Esc cancel\n" " busy Enter queues · tool perm y/n/a=always · wheel scroll\n", argv0); } static int g_ready; static ctl_tool_call_t g_call; static void demo_on_ev(const ctl_event_t *ev, void *ud) { (void)ud; if (ev->type == CTL_EV_TEXT) fwrite(ev->text, 1, ev->text_len, stdout); if (ev->type == CTL_EV_CALL_READY) { snprintf(g_call.id, sizeof(g_call.id), "%s", ev->call->id); snprintf(g_call.name, sizeof(g_call.name), "%s", ev->call->name); for (int i = 0; i < g_call.nargs; i++) { free(g_call.args[i].value); g_call.args[i].value = NULL; } g_call.nargs = 0; for (int i = 0; i < ev->call->nargs; i++) { snprintf(g_call.args[i].key, sizeof(g_call.args[i].key), "%s", ev->call->args[i].key); g_call.args[i].value_len = ev->call->args[i].value_len; g_call.args[i].value = malloc(ev->call->args[i].value_len + 1); memcpy(g_call.args[i].value, ev->call->args[i].value, ev->call->args[i].value_len); g_call.args[i].value[ev->call->args[i].value_len] = 0; g_call.nargs++; } g_ready = 1; } } static int demo_ctl_impl(const char *workspace) { printf("=== demo CTL (byte-at-a-time feed) ===\n"); g_ready = 0; memset(&g_call, 0, sizeof(g_call)); ctl_parser_t *p = ctl_parser_create(demo_on_ev, NULL); const char *stream = "Reading README.\n" "call t1 read_file\n" "arg path 9\n" "README.md" "end\n"; for (size_t i = 0; stream[i]; i++) ctl_parser_feed(p, &stream[i], 1); if (ctl_parser_finish(p) != CTL_OK || !g_ready) { fprintf(stderr, "demo: CTL failed\n"); ctl_parser_destroy(p); return 1; } printf("\npending_peak=%zu\n", ctl_parser_pending_peak(p)); ctl_parser_destroy(p); cst_tool_result_t r = {0}; cst_tool_dispatch(workspace, &g_call, &r); printf("[tool result exit=%d]\n%.*s\n", r.exit_code, (int)r.out_len, r.out ? r.out : ""); cst_tool_result_free(&r); for (int i = 0; i < g_call.nargs; i++) free(g_call.args[i].value); return 0; } int main(int argc, char **argv) { os_set_prog_path(argv[0]); curl_global_init(CURL_GLOBAL_DEFAULT); cst_agent_config_t cfg = {0}; char cwd[4096]; #ifdef _WIN32 if (!_getcwd(cwd, (int)sizeof(cwd))) #else if (!getcwd(cwd, sizeof(cwd))) #endif snprintf(cwd, sizeof(cwd), "."); snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", cwd); cfg.session_path[0] = 0; /* filled after workspace opts via .agent/store */ /* OpenAI-compatible base URL aliases */ const char *env_base = getenv("CST_API_BASE"); if (!env_base || !env_base[0]) env_base = getenv("OPENAI_BASE_URL"); if (!env_base || !env_base[0]) env_base = getenv("OPENAI_API_BASE"); const char *env_model = getenv("CST_MODEL"); if (!env_model || !env_model[0]) env_model = getenv("OPENAI_MODEL"); snprintf(cfg.api_base, sizeof(cfg.api_base), "%s", env_base && env_base[0] ? env_base : "https://api.minimax.cn/v1"); cfg.api_key[0] = 0; snprintf(cfg.model, sizeof(cfg.model), "%s", env_model && env_model[0] ? env_model : "MiniMax-M2.7"); cfg.headless = 0; cfg.confirm_shell = 1; cfg.confirm_write = 1; cfg.auto_approve = 0; if (getenv("CST_AUTO_APPROVE")) cfg.auto_approve = 1; int do_demo = 0; int force_ask_key = 0; int session_explicit = 0; const char *prompt = NULL; int key_from_cli = 0; static struct option opts[] = { {"workspace", required_argument, 0, 'w'}, {"session", required_argument, 0, 's'}, {"api-base", required_argument, 0, 'b'}, {"api-key", required_argument, 0, 'k'}, {"model", required_argument, 0, 'm'}, {"headless", no_argument, 0, 'H'}, {"prompt", required_argument, 0, 'p'}, {"demo-ctl", no_argument, 0, 'd'}, {"ask-key", no_argument, 0, 'a'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; int c; while ((c = getopt_long(argc, argv, "w:s:b:k:m:Hp:dah", opts, NULL)) != -1) { switch (c) { case 'w': snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", optarg); break; case 's': snprintf(cfg.session_path, sizeof(cfg.session_path), "%s", optarg); session_explicit = 1; break; case 'b': snprintf(cfg.api_base, sizeof(cfg.api_base), "%s", optarg); break; case 'k': snprintf(cfg.api_key, sizeof(cfg.api_key), "%s", optarg); key_from_cli = 1; break; case 'm': snprintf(cfg.model, sizeof(cfg.model), "%s", optarg); break; case 'H': cfg.headless = 1; break; case 'p': prompt = optarg; break; case 'd': do_demo = 1; break; case 'a': force_ask_key = 1; break; case 'h': usage(argv[0]); curl_global_cleanup(); return 0; default: usage(argv[0]); curl_global_cleanup(); return 1; } } /* trim trailing slash on api_base */ size_t alen = strlen(cfg.api_base); while (alen > 0 && cfg.api_base[alen - 1] == '/') cfg.api_base[--alen] = 0; /* Headless defaults to auto-approve shell/write (no interactive confirm). */ if (cfg.headless) cfg.auto_approve = 1; /* Default session under /.agent/sessions/ (OpenCode-like). */ if (!session_explicit) { char id[128]; if (cst_agent_store_resolve_default(cfg.workspace_root, cfg.session_path, sizeof(cfg.session_path), id, sizeof(id)) != 0) { fprintf(stderr, "failed to init .agent/sessions under %s\n", cfg.workspace_root); curl_global_cleanup(); return 1; } fprintf(stderr, "session %s\n", id); } if (do_demo) { int rc = demo_ctl_impl(cfg.workspace_root); curl_global_cleanup(); return rc; } if (force_ask_key || !key_from_cli) { if (force_ask_key) { if (cst_creds_prompt_api_key(cfg.api_key, sizeof(cfg.api_key)) != 0) { fprintf(stderr, "API key prompt cancelled\n"); curl_global_cleanup(); return 1; } if (cst_creds_save_api_key(cfg.api_key) != 0) fprintf(stderr, "warning: failed to save credentials\n"); else fprintf(stderr, "saved API key to ~/.config/cst-agent/credentials\n"); /* --ask-key alone: save and exit unless running a turn/TUI */ if (!cfg.headless && !prompt) { fprintf(stderr, "using %s model=%s\n", cfg.api_base, cfg.model); curl_global_cleanup(); return 0; } } else if (!cfg.api_key[0]) { if (cst_creds_ensure_api_key(cfg.api_key, sizeof(cfg.api_key)) != 0) { fprintf(stderr, "no API key (set CST_API_KEY or use --ask-key)\n"); curl_global_cleanup(); return 1; } } } else if (key_from_cli) { /* persist CLI-provided key so next launch can reuse */ if (cst_creds_save_api_key(cfg.api_key) != 0) fprintf(stderr, "warning: failed to save credentials\n"); } fprintf(stderr, "using %s model=%s\n", cfg.api_base, cfg.model); cst_agent_t *agent = cst_agent_create(&cfg); if (!agent) { fprintf(stderr, "failed to create agent (session path?)\n"); curl_global_cleanup(); return 1; } int rc = 0; if (cfg.headless) { if (!prompt) { fprintf(stderr, "--headless requires --prompt\n"); rc = 1; } else { char err[512]; if (cst_agent_run_turn(agent, prompt, err, sizeof(err)) != 0) { fprintf(stderr, "turn failed: %s\n", err); rc = 1; } else { cst_session_t *s = cst_agent_session(agent); for (int i = 0; i < cst_session_count(s); i++) { const cst_message_t *m = cst_session_get(s, i); printf("[%d role=%d] %s\n", i, (int)m->role, m->content ? m->content : ""); } } } } else { rc = cst_tui_run(agent); } cst_agent_destroy(agent); curl_global_cleanup(); return rc; }