Usability pass: tool registry, TUI polish, saved keys and settings
Tools - Table-driven tool registry with permission classes and default/minimal profiles; new tools: edit_file, list_dir, glob, grep, delete_file, move_file, todo_write, web_fetch, ask_user (plus read_file paging). - Built-in Myers diff; write tools return unified diffs. - Persistent permission rules (.agent/permissions.json), per-turn undo snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80% of the model context, cancellable run_shell with process-group kill. TUI - Table-driven slash commands: /provider /key /effort /status /tools /permissions /yolo /undo /retry /diff /export /copy /rename /delete /init /max-steps /todo, with / and @ completion. - Permission modal with diff and prefix rules, ask_user modal, todo panel, header with model/effort/context, bracketed paste, line editing, wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits. Startup and configuration - API keys are entered in the TUI (hidden) and saved per provider in <config>/credentials (0600); env variables are only a fallback. - Last provider, per-provider model and reasoning effort are remembered in <config>/settings.json; --effort/-e and /effort send reasoning_effort (low|medium|high|max) to models that accept it. - DeepSeek preset uses deepseek-flash / deepseek-v4-pro. - Session flags --new/--continue/--resume/--list-sessions, --yolo, --confirm. Compatibility and tests - ncurses detection, macOS/Homebrew support, make install, XDG config dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS). - Unit tests for diff, fs tools, registry, undo/perms and config; e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON.
This commit is contained in:
+316
-56
@@ -1,4 +1,5 @@
|
||||
#include "cst_agent.h"
|
||||
#include "cst_tools.h"
|
||||
#include "os_compat.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
@@ -6,30 +7,120 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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"
|
||||
"Usage: %s [options] (works in the current directory)\n"
|
||||
" -P, --provider NAME minimax | deepseek | openai (last used)\n"
|
||||
" -m, --model NAME model (last used for the provider)\n"
|
||||
" -e, --effort LEVEL low | medium | high | max (last used)\n"
|
||||
" -w, --workspace DIR project directory (default: current)\n"
|
||||
" -c, --continue reopen the last session (default)\n"
|
||||
" -N, --new start a new session\n"
|
||||
" -R, --resume ID open a session by id or unique prefix\n"
|
||||
" -L, --list-sessions list sessions and exit\n"
|
||||
" -y, --yolo never ask before shell / writes / web\n"
|
||||
" -C, --confirm LIST shell,write,net (default) | none\n"
|
||||
" -r, --protocol P native-eos (default) | native-inc | ctl |\n"
|
||||
" ctl-heredoc | xml\n"
|
||||
" -T, --tools PROFILE default (all tools) | minimal (paper set)\n"
|
||||
" -n, --max-steps N model calls per turn (30)\n"
|
||||
" -H, --headless -p TEXT run one prompt without the TUI\n"
|
||||
" -a, --ask-key enter and save the provider's API key\n"
|
||||
" -k, --api-key KEY use KEY for this run only (not saved)\n"
|
||||
" -b, --api-base URL OpenAI-compatible base URL (…/v1)\n"
|
||||
" -s, --session FILE explicit session file\n"
|
||||
" -d, --demo-ctl local CTL parser demo (no API)\n"
|
||||
"\n"
|
||||
"Defaults: api-base=https://api.minimax.cn/v1 model=MiniMax-M2.7\n"
|
||||
"Session: <workspace>/.agent/sessions/<id>.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",
|
||||
"API keys: the TUI asks for a missing key and saves it per provider\n"
|
||||
" in the config dir (credentials, mode 0600); /key changes it.\n"
|
||||
" Fallbacks: <provider env> (DEEPSEEK_API_KEY, ...), CST_API_KEY.\n"
|
||||
"Settings: provider, models and effort are remembered in\n"
|
||||
" settings.json next to the credentials.\n"
|
||||
"Env: CST_PROVIDER CST_MODEL CST_EFFORT CST_PROTOCOL CST_TOOLS\n"
|
||||
" CST_CONFIRM CST_MAX_STEPS CST_TOOL_OUT_CAP CST_CTX_WINDOW\n"
|
||||
" CST_CTX_TOKENS CST_AUTO_COMPACT=0 CST_SHELL_TIMEOUT\n"
|
||||
" CST_METRICS=0 CST_RECORD_SSE CST_REPLAY_SSE CST_MODELS\n"
|
||||
" CST_AUTO_APPROVE=1 CST_SHELL CST_NO_PROJECT_NOTES=1 NO_COLOR\n"
|
||||
"TUI: type /help inside for commands and keys\n",
|
||||
argv0);
|
||||
}
|
||||
|
||||
static int parse_confirm(const char *s, cst_agent_config_t *cfg) {
|
||||
cfg->confirm_shell = cfg->confirm_write = cfg->confirm_net = 0;
|
||||
if (!s || strcmp(s, "none") == 0 || !s[0])
|
||||
return 0;
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "%s", s);
|
||||
for (char *tok = strtok(buf, ", "); tok; tok = strtok(NULL, ", ")) {
|
||||
if (strcmp(tok, "shell") == 0)
|
||||
cfg->confirm_shell = 1;
|
||||
else if (strcmp(tok, "write") == 0)
|
||||
cfg->confirm_write = 1;
|
||||
else if (strcmp(tok, "net") == 0)
|
||||
cfg->confirm_net = 1;
|
||||
else if (strcmp(tok, "all") == 0)
|
||||
cfg->confirm_shell = cfg->confirm_write = cfg->confirm_net = 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int list_sessions(const char *ws) {
|
||||
static char ids[200][128];
|
||||
int n = cst_agent_store_list(ws, ids, 200);
|
||||
char cur[128] = "";
|
||||
cst_agent_store_get_current(ws, cur, sizeof(cur));
|
||||
if (n == 0) {
|
||||
printf("no sessions under %s/.agent/sessions\n", ws);
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
cst_session_info_t in;
|
||||
if (cst_agent_store_info(ws, ids[i], &in) != 0)
|
||||
continue;
|
||||
char when[32] = "?";
|
||||
time_t t = (time_t)in.mtime;
|
||||
struct tm tm;
|
||||
os_localtime(&t, &tm);
|
||||
strftime(when, sizeof(when), "%Y-%m-%d %H:%M", &tm);
|
||||
printf("%c %-24s %s %4d msgs %s\n", strcmp(cur, ids[i]) == 0 ? '*' : ' ',
|
||||
in.id, when, in.messages,
|
||||
in.title[0] ? in.title : in.first_user);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Exact id or unique prefix → id. */
|
||||
static int resolve_session_id(const char *ws, const char *want, char *out,
|
||||
size_t out_sz) {
|
||||
static char ids[500][128];
|
||||
int n = cst_agent_store_list(ws, ids, 500), hit = -1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (strcmp(ids[i], want) == 0) {
|
||||
hit = i;
|
||||
break;
|
||||
}
|
||||
if (strncmp(ids[i], want, strlen(want)) == 0) {
|
||||
if (hit >= 0)
|
||||
return -2;
|
||||
hit = i;
|
||||
}
|
||||
}
|
||||
if (hit < 0)
|
||||
return -1;
|
||||
snprintf(out, out_sz, "%s", ids[hit]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int g_ready;
|
||||
static ctl_tool_call_t g_call;
|
||||
|
||||
@@ -104,32 +195,52 @@ int main(int argc, char **argv) {
|
||||
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_base[0] = 0;
|
||||
cfg.api_key[0] = 0;
|
||||
snprintf(cfg.model, sizeof(cfg.model), "%s",
|
||||
env_model && env_model[0] ? env_model : "MiniMax-M2.7");
|
||||
cfg.model[0] = 0;
|
||||
cfg.protocol = CST_PROTO_NATIVE_EOS;
|
||||
const char *env_proto = getenv("CST_PROTOCOL");
|
||||
if (env_proto && env_proto[0] &&
|
||||
cst_protocol_parse(env_proto, &cfg.protocol) != 0) {
|
||||
fprintf(stderr, "unknown CST_PROTOCOL=%s\n", env_proto);
|
||||
return 1;
|
||||
}
|
||||
cfg.max_steps = CST_DEFAULT_MAX_STEPS;
|
||||
const char *env_steps = getenv("CST_MAX_STEPS");
|
||||
if (env_steps && atoi(env_steps) > 0)
|
||||
cfg.max_steps = atoi(env_steps);
|
||||
cfg.tool_out_cap = CST_DEFAULT_TOOL_OUT_CAP;
|
||||
const char *env_cap = getenv("CST_TOOL_OUT_CAP");
|
||||
if (env_cap && atol(env_cap) > 0)
|
||||
cfg.tool_out_cap = (size_t)atol(env_cap);
|
||||
const char *provider_name = getenv("CST_PROVIDER");
|
||||
cfg.headless = 0;
|
||||
cfg.confirm_shell = 1;
|
||||
cfg.confirm_write = 1;
|
||||
cfg.confirm_net = 1;
|
||||
cfg.auto_approve = 0;
|
||||
cfg.tool_profile = CST_TOOLS_DEFAULT;
|
||||
const char *env_tools = getenv("CST_TOOLS");
|
||||
if (env_tools && env_tools[0] &&
|
||||
cst_tool_profile_parse(env_tools, &cfg.tool_profile) != 0) {
|
||||
fprintf(stderr, "unknown CST_TOOLS=%s (default | minimal)\n", env_tools);
|
||||
return 1;
|
||||
}
|
||||
if (getenv("CST_AUTO_APPROVE"))
|
||||
cfg.auto_approve = 1;
|
||||
const char *env_confirm = getenv("CST_CONFIRM");
|
||||
if (env_confirm && parse_confirm(env_confirm, &cfg) != 0) {
|
||||
fprintf(stderr, "bad CST_CONFIRM=%s (shell,write,net | none)\n",
|
||||
env_confirm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int do_demo = 0;
|
||||
int force_ask_key = 0;
|
||||
int session_explicit = 0;
|
||||
const char *prompt = NULL;
|
||||
int key_from_cli = 0;
|
||||
int new_session = 0, do_list = 0;
|
||||
const char *resume_id = NULL;
|
||||
|
||||
static struct option opts[] = {
|
||||
{"workspace", required_argument, 0, 'w'},
|
||||
@@ -137,14 +248,26 @@ int main(int argc, char **argv) {
|
||||
{"api-base", required_argument, 0, 'b'},
|
||||
{"api-key", required_argument, 0, 'k'},
|
||||
{"model", required_argument, 0, 'm'},
|
||||
{"provider", required_argument, 0, 'P'},
|
||||
{"protocol", required_argument, 0, 'r'},
|
||||
{"max-steps", required_argument, 0, 'n'},
|
||||
{"tools", required_argument, 0, 'T'},
|
||||
{"headless", no_argument, 0, 'H'},
|
||||
{"prompt", required_argument, 0, 'p'},
|
||||
{"demo-ctl", no_argument, 0, 'd'},
|
||||
{"ask-key", no_argument, 0, 'a'},
|
||||
{"new", no_argument, 0, 'N'},
|
||||
{"continue", no_argument, 0, 'c'},
|
||||
{"resume", required_argument, 0, 'R'},
|
||||
{"list-sessions", no_argument, 0, 'L'},
|
||||
{"yolo", no_argument, 0, 'y'},
|
||||
{"confirm", required_argument, 0, 'C'},
|
||||
{"effort", required_argument, 0, 'e'},
|
||||
{"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) {
|
||||
while ((c = getopt_long(argc, argv, "w:s:b:k:m:P:r:n:T:Hp:dahNcR:LyC:e:", opts, NULL)) !=
|
||||
-1) {
|
||||
switch (c) {
|
||||
case 'w':
|
||||
snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", optarg);
|
||||
@@ -158,11 +281,33 @@ int main(int argc, char **argv) {
|
||||
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 'P':
|
||||
provider_name = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
if (cst_protocol_parse(optarg, &cfg.protocol) != 0) {
|
||||
fprintf(stderr, "unknown protocol: %s\n", optarg);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
cfg.max_steps = atoi(optarg);
|
||||
if (cfg.max_steps <= 0)
|
||||
cfg.max_steps = CST_DEFAULT_MAX_STEPS;
|
||||
break;
|
||||
case 'T':
|
||||
if (cst_tool_profile_parse(optarg, &cfg.tool_profile) != 0) {
|
||||
fprintf(stderr, "unknown tool profile: %s (default | minimal)\n",
|
||||
optarg);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'H':
|
||||
cfg.headless = 1;
|
||||
break;
|
||||
@@ -175,6 +320,38 @@ int main(int argc, char **argv) {
|
||||
case 'a':
|
||||
force_ask_key = 1;
|
||||
break;
|
||||
case 'N':
|
||||
new_session = 1;
|
||||
break;
|
||||
case 'c':
|
||||
new_session = 0;
|
||||
resume_id = NULL;
|
||||
break;
|
||||
case 'R':
|
||||
resume_id = optarg;
|
||||
break;
|
||||
case 'L':
|
||||
do_list = 1;
|
||||
break;
|
||||
case 'y':
|
||||
cfg.auto_approve = 1;
|
||||
break;
|
||||
case 'C':
|
||||
if (parse_confirm(optarg, &cfg) != 0) {
|
||||
fprintf(stderr, "bad --confirm %s (shell,write,net | none)\n", optarg);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (!cst_effort_valid(optarg)) {
|
||||
fprintf(stderr, "bad --effort %s (low | medium | high | max)\n",
|
||||
optarg);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
snprintf(cfg.effort, sizeof(cfg.effort), "%s", optarg);
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
curl_global_cleanup();
|
||||
@@ -186,6 +363,63 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (do_list) {
|
||||
int rc = list_sessions(cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Explicit option > environment > last used (settings.json) > preset. */
|
||||
cst_settings_t saved;
|
||||
cst_settings_load(&saved);
|
||||
if ((!provider_name || !provider_name[0]) &&
|
||||
cst_provider_find(saved.provider))
|
||||
provider_name = saved.provider;
|
||||
const cst_provider_t *prov = cst_provider_default();
|
||||
if (provider_name && provider_name[0]) {
|
||||
prov = cst_provider_find(provider_name);
|
||||
if (!prov) {
|
||||
fprintf(stderr, "unknown provider: %s\n", provider_name);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
snprintf(cfg.provider, sizeof(cfg.provider), "%s", prov->name);
|
||||
if (!cfg.effort[0]) {
|
||||
const char *e = getenv("CST_EFFORT");
|
||||
if (!cst_effort_valid(e))
|
||||
e = saved.effort;
|
||||
snprintf(cfg.effort, sizeof(cfg.effort), "%s",
|
||||
cst_effort_valid(e) ? e : CST_DEFAULT_EFFORT);
|
||||
}
|
||||
if (!cfg.api_base[0]) {
|
||||
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");
|
||||
snprintf(cfg.api_base, sizeof(cfg.api_base), "%s",
|
||||
env_base && env_base[0] ? env_base : prov->api_base);
|
||||
}
|
||||
if (!cfg.model[0]) {
|
||||
const char *env_model = getenv("CST_MODEL");
|
||||
if (!env_model || !env_model[0])
|
||||
env_model = getenv("OPENAI_MODEL");
|
||||
if (!env_model || !env_model[0])
|
||||
env_model = cst_settings_model(&saved, prov->name);
|
||||
snprintf(cfg.model, sizeof(cfg.model), "%s",
|
||||
env_model && env_model[0] ? env_model : prov->model);
|
||||
}
|
||||
/* --api-key > saved key > provider env > CST_API_KEY; the TUI asks for
|
||||
* a missing key itself. */
|
||||
if (!cfg.api_key[0] && !force_ask_key &&
|
||||
cst_creds_resolve(prov->name, cfg.api_key, sizeof(cfg.api_key), NULL) !=
|
||||
0) {
|
||||
const char *k = getenv("CST_API_KEY");
|
||||
if (k && k[0])
|
||||
snprintf(cfg.api_key, sizeof(cfg.api_key), "%s", k);
|
||||
}
|
||||
|
||||
/* trim trailing slash on api_base */
|
||||
size_t alen = strlen(cfg.api_base);
|
||||
while (alen > 0 && cfg.api_base[alen - 1] == '/')
|
||||
@@ -196,7 +430,29 @@ int main(int argc, char **argv) {
|
||||
cfg.auto_approve = 1;
|
||||
|
||||
/* Default session under <workspace>/.agent/sessions/ (OpenCode-like). */
|
||||
if (!session_explicit) {
|
||||
if (!session_explicit && (new_session || resume_id)) {
|
||||
char id[128];
|
||||
int rc = 0;
|
||||
if (new_session) {
|
||||
rc = cst_agent_store_ensure(cfg.workspace_root) != 0 ||
|
||||
cst_agent_store_new_id(id, sizeof(id)) != 0
|
||||
? -1
|
||||
: 0;
|
||||
} else {
|
||||
rc = resolve_session_id(cfg.workspace_root, resume_id, id, sizeof(id));
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "%s session: %s (see --list-sessions)\n",
|
||||
rc == -2 ? "ambiguous" : "no such", resume_id);
|
||||
}
|
||||
if (rc != 0 || cst_agent_store_path_for_id(cfg.workspace_root, id,
|
||||
cfg.session_path,
|
||||
sizeof(cfg.session_path)) != 0) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
cst_agent_store_set_current(cfg.workspace_root, id);
|
||||
fprintf(stderr, "session %s%s\n", id, new_session ? " (new)" : "");
|
||||
} else if (!session_explicit) {
|
||||
char id[128];
|
||||
if (cst_agent_store_resolve_default(cfg.workspace_root, cfg.session_path,
|
||||
sizeof(cfg.session_path), id,
|
||||
@@ -215,37 +471,41 @@ int main(int argc, char **argv) {
|
||||
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;
|
||||
}
|
||||
int need_console_key =
|
||||
force_ask_key || (!cfg.api_key[0] && cfg.headless && isatty(0));
|
||||
if (need_console_key) {
|
||||
if (cst_creds_prompt_console(cfg.provider, cfg.api_key,
|
||||
sizeof(cfg.api_key)) != 0) {
|
||||
fprintf(stderr, "no API key entered\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)
|
||||
char dir[1024] = "the config directory";
|
||||
os_config_dir(dir, sizeof(dir));
|
||||
if (cst_creds_save(cfg.provider, cfg.api_key) != 0)
|
||||
fprintf(stderr, "warning: failed to save credentials\n");
|
||||
else
|
||||
fprintf(stderr, "saved the %s API key to %s/credentials\n", cfg.provider,
|
||||
dir);
|
||||
/* --ask-key alone: save and exit */
|
||||
if (force_ask_key && !cfg.headless && !prompt) {
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!cfg.api_key[0] && cfg.headless) {
|
||||
fprintf(stderr,
|
||||
"no API key for %s: start cst-agent once to enter it, or run "
|
||||
"cst-agent --ask-key --provider %s\n",
|
||||
cfg.provider, cfg.provider);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "using %s model=%s\n", cfg.api_base, cfg.model);
|
||||
fprintf(stderr,
|
||||
"using %s model=%s effort=%s protocol=%s tools=%s max_steps=%d\n",
|
||||
cfg.api_base, cfg.model, cfg.effort, cst_protocol_name(cfg.protocol),
|
||||
cst_tool_profile_name(cfg.tool_profile), cfg.max_steps);
|
||||
|
||||
cst_agent_t *agent = cst_agent_create(&cfg);
|
||||
if (!agent) {
|
||||
|
||||
Reference in New Issue
Block a user