// SPDX-License-Identifier: ISC#include <errno.h>#include <fcntl.h>#include <getopt.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <time.h>#include <unistd.h>#include <uv.h> #include "clm/clm.h"#include "clm/host_uv.h"#include "clm/provider.h"#include "frontend.h"#include "version.h"#include "clm/lua_plugin.h"#include "seed_plugins.h"#include "clm/cleanup.h"#include "clm/session.h"#include "mcp_setup.h"#include "model_spec.h"#include "templates.h"#include "xdg.h" static voidusage(const char *prog){ fprintf(stderr, "usage: %s setup\n" " %s [-o|--oneshot PROMPT] [-f|--forever PROMPT] " "[-H|--headless] [-u|--url BASE] " "[-m|--model PROVIDER/MODEL-ID] [--provider NAME] " "[-p|--plugins DIR] [-S|--no-stream] " "[-r|--resume [ID]] " "[-V|--version] [-h|--help]\n", prog, prog); fprintf(stderr, " setup write a starter config.lua and seed " "builtin plugins\n" " -o, --oneshot PROMPT run one prompt headless and exit\n" " -f, --forever PROMPT TUI mode: submit PROMPT, then " "auto-resubmit it\n" " every time a turn completes with nothing " "queued,\n" " so the agent keeps going without a human " "re-prompting it\n" " -H, --headless force the plain stdio REPL\n" " -u, --url BASE base API endpoint " "(default http://127.0.0.1:8081/v1);\n" " \"/messages\" is appended for an anthropic " "provider,\n" " \"/chat/completions\" otherwise\n" " -m, --model PROVIDER/MODEL-ID\n" " a config.lua providers[PROVIDER].models" "[MODEL-ID] entry,\n" " or a literal model id (no \"/\") to request " "on whatever\n" " connection is otherwise active\n" " --provider NAME name of an entry in config.lua's " "`providers` table,\n" " overriding the connection --model's " "provider half names\n" " -p, --plugins DIR plugin directory " "(default $XDG_CONFIG_HOME/clm/plugins)\n" " -S, --no-stream disable streamed (SSE) responses\n" " -r, --resume [ID] resume a saved session in the TUI; with " "no ID,\n" " pick from a list of saved sessions\n" " -V, --version print version and exit\n" " -h, --help show this help\n" "\n" " With no options it runs the interactive ncurses UI on a " "terminal.\n" " Set CLM_API_KEY in the environment to send a bearer token.\n");} struct cli_state { uv_loop_t *loop; struct clm_host *host; struct clm_agent *agent; struct clm_lua_env *lua_env; struct clm_lua_cfg *lua_cfg; struct clm_mcp_client **mcp_clients; size_t mcp_client_count; uv_pipe_t stdin_pipe; char prompt_line[1024]; size_t prompt_len; int oneshot; int turn_done; int turn_status;}; static voidcb_mcp_status(const char *msg, void *user){ (void)user; fprintf(stderr, "%s\n", msg);} static voidcb_assistant_text(const char *text, void *user){ struct cli_state *state = (struct cli_state *)user; if (state->oneshot) printf("%s", text); else printf("assistant> %s", text); fflush(stdout);} static voidcb_reasoning(const char *text, void *user){ (void)user; printf("\033[2m%s\033[0m", text); /* dim "thinking" channel */ fflush(stdout);} static voidcb_finish_reason(enum clm_finish_reason reason, void *user){ (void)user; if (reason == CLM_FINISH_LENGTH) printf("\n\033[33m[truncated: hit token limit]\033[0m\n"); else if (reason == CLM_FINISH_CONTENT_FILTER) printf("\n\033[31m[stopped: content filter]\033[0m\n"); fflush(stdout);} static voidcb_usage(const struct clm_usage *usage, void *user){ (void)user; printf("\033[2m[%d+%d tok", usage->prompt_tokens, usage->completion_tokens); if (usage->tokens_per_sec > 0) printf(", %.1f tok/s", usage->tokens_per_sec); printf("]\033[0m\n"); fflush(stdout);} static voidcb_tool_begin(const char *name, const char *args, void *user){ (void)user; printf("[tool: %s %s]\n", name, args ? args : ""); fflush(stdout);} static voidcb_tool_result(const char *name, const char *content, enum clm_tool_outcome outcome, void *user){ (void)user; switch (outcome) { case CLM_TOOL_OK: printf("[result: %s]\n%s\n", name, content ? content : ""); break; case CLM_TOOL_FAILED: printf("\033[31m[x %s]\033[0m %s\n", name, content ? content : ""); break; case CLM_TOOL_TIMEDOUT: printf("\033[33m[timeout %s]\033[0m %s\n", name, content ? content : ""); break; } fflush(stdout);} static voidcb_tool_batch(size_t completed, size_t total, void *user){ (void)user; printf("[tools %zu/%zu]\n", completed, total); fflush(stdout);} static voidcb_state(enum clm_agent_state state, void *user){} static voidcb_notice(const char *text, void *user){ (void)user; fprintf(stderr, "notice: %s\n", text ? text : "");} static voidcb_turn_done(int status, void *user){ struct cli_state *state = (struct cli_state *)user; if (status != 0) { fprintf(stderr, "error: %s\n", clm_agent_get_last_error(state->agent)); } if (state->oneshot) { state->turn_done = 1; state->turn_status = status; return; } printf("\nuser> "); fflush(stdout);} /* * Auto-allow permission handler: preserves the CLI's pre-gate behaviour under * the library's default-deny. A future refinement could gate this behind an * explicit --yes/--allow flag for unattended runs. */static voidcb_permission(const struct clm_permission_req *req, void *user){ struct cli_state *state = (struct cli_state *)user; clm_tool_permission_respond(state->agent, req, CLM_PERM_ALLOW_ONCE);} static const struct clm_callbacks cli_callbacks = { .on_assistant_text = cb_assistant_text, .on_reasoning = cb_reasoning, .on_tool_begin = cb_tool_begin, .on_permission = cb_permission, .on_tool_result = cb_tool_result, .on_tool_batch = cb_tool_batch, .on_finish_reason = cb_finish_reason, .on_usage = cb_usage, .on_state = cb_state, .on_turn_done = cb_turn_done, .on_notice = cb_notice,}; static voidon_alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf){ buf->base = malloc(suggested_size); if (buf->base == NULL) { buf->len = 0; return; } buf->len = suggested_size;} static voidon_stdin_close(uv_handle_t *handle){ struct cli_state *state = (struct cli_state *)handle->data; uv_stop(state->loop);} static voidon_stdin_read(uv_stream_t *stream, ssize_t n_read, const uv_buf_t *buf){ struct cli_state *state = (struct cli_state *)stream->data; if (n_read < 0) { if (n_read == UV_ENOBUFS) return; if (n_read == UV_EOF) { free(buf->base); printf("\n"); fflush(stdout); uv_close((uv_handle_t *)stream, on_stdin_close); return; } fprintf(stderr, "read error: %s\n", uv_err_name(n_read)); uv_close((uv_handle_t *)stream, on_stdin_close); return; } if (n_read == 0) { free(buf->base); return; } for (ssize_t i = 0; i < n_read; i++) { if (state->prompt_len < sizeof(state->prompt_line) - 1) { state->prompt_line[state->prompt_len++] = buf->base[i]; } if (buf->base[i] == '\n' || buf->base[i] == '\r') { if (state->prompt_len > 0 && state->prompt_line[state->prompt_len - 1] == '\r') state->prompt_len--; if (state->prompt_len > 0 && state->prompt_line[state->prompt_len - 1] == '\n') state->prompt_len--; state->prompt_line[state->prompt_len] = '\0'; if (state->prompt_len > 0) { if (strcmp(state->prompt_line, "quit") == 0 || strcmp(state->prompt_line, "exit") == 0) { free(buf->base); uv_close((uv_handle_t *)stream, on_stdin_close); return; } int r = clm_agent_submit(state->agent, state->prompt_line); if (r < 0) { fprintf(stderr, "error: %s\n", clm_agent_get_last_error(state->agent)); } } state->prompt_len = 0; break; } } free(buf->base);} /* Writes content to path unless it already exists. Returns 0 if written, * 1 if it already existed (left untouched), or -errno on failure. */static intwrite_new_file(const char *path, const char *content, mode_t mode){ int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); if (fd < 0) return errno == EEXIST ? 1 : -errno; size_t len = strlen(content); size_t off = 0; while (off < len) { ssize_t n = write(fd, content + off, len - off); if (n < 0) { int err = -errno; close(fd); (void)unlink(path); return err; } off += (size_t)n; } close(fd); return 0;} /* * --resume with no id: print a numbered listing of saved sessions and read * a choice from stdin. Returns a malloc'd session id, or NULL (no sessions, * bad choice, or EOF). */static char *pick_session(void){ struct clm_session_info *infos = NULL; size_t n = 0, i; char line[32], *end, *id = NULL; unsigned long choice; int r; r = clm_session_list(NULL, &infos, &n); if (r < 0) { fprintf(stderr, "error: listing sessions: %s\n", strerror(-r)); return NULL; } if (n == 0) { fprintf(stderr, "no saved sessions\n"); return NULL; } for (i = 0; i < n; i++) { char when[32] = "?"; time_t created = (time_t)infos[i].created; struct tm tm; if (created > 0 && localtime_r(&created, &tm) != NULL) (void)strftime(when, sizeof(when), "%Y-%m-%d %H:%M", &tm); printf("%3zu) %s %s %s\n %s\n", i + 1, infos[i].id, when, infos[i].model != NULL ? infos[i].model : "", infos[i].first_user != NULL ? infos[i].first_user : "(empty)"); } printf("resume which session? [1-%zu] ", n); fflush(stdout); if (fgets(line, sizeof(line), stdin) == NULL) { clm_session_list_free(infos, n); return NULL; } errno = 0; choice = strtoul(line, &end, 10); if (errno != 0 || end == line || choice < 1 || choice > n) { fprintf(stderr, "no such session\n"); clm_session_list_free(infos, n); return NULL; } id = strdup(infos[choice - 1].id); clm_session_list_free(infos, n); return id;} /* `clm setup`: writes a starter config.lua and secrets.lua, and seeds the * builtin plugins, without silently doing any of that on a normal run. * Safe to re-run: never overwrites a config, secrets, or plugin file * that's already there. */static intrun_setup(void){ autofree char *cfg_path = xdg_config_path("clm/config.lua"); autofree char *secrets_path = xdg_config_path("clm/secrets.lua"); autofree char *agents_dir = xdg_config_path("clm/agents"); autofree char *plugins_dir = xdg_config_path("clm/plugins"); if (cfg_path == NULL || secrets_path == NULL || agents_dir == NULL || plugins_dir == NULL) { fprintf(stderr, "setup: could not determine config path " "($XDG_CONFIG_HOME or $HOME not set)\n"); return 1; } /* Also creates the parent clm/ dir. */ int sr = clm_seed_default_plugins(plugins_dir); if (sr < 0) { fprintf(stderr, "setup: seeding %s: %s\n", plugins_dir, strerror(-sr)); return 1; } printf("plugins in %s\n", plugins_dir); if (mkdir(agents_dir, 0755) != 0 && errno != EEXIST) { fprintf(stderr, "setup: mkdir %s: %s\n", agents_dir, strerror(errno)); return 1; } printf("agent profiles in %s\n", agents_dir); /* 0600: secrets.lua holds API keys, unlike config.lua. */ int xr = write_new_file(secrets_path, (const char *)secrets_lua_tpl_data, 0600); if (xr < 0) { fprintf(stderr, "setup: writing %s: %s\n", secrets_path, strerror(-xr)); return 1; } printf("%s %s\n", xr == 1 ? "kept existing" : "wrote", secrets_path); int cr = write_new_file(cfg_path, (const char *)config_lua_tpl_data, 0644); if (cr < 0) { fprintf(stderr, "setup: writing %s: %s\n", cfg_path, strerror(-cr)); return 1; } printf("%s %s\n", cr == 1 ? "kept existing" : "wrote", cfg_path); return 0;} intmain(int argc, char *argv[]){ const char *api_base = NULL; const char *model_name = NULL; /* -m/--model: a "provider/model-id" spec, or a literal wire id */ const char *provider_name = NULL; /* --provider: a config providers[] name override */ /* Owned copy of a "provider/model-id" spec's provider half (see * split_provider_model() in model_spec.h), process-lifetime like * plenty else in this function -- cfg.provider_name below may alias * it, and cfg is still read as late as tui_run()'s call at the very * end of main(), well past any block-scoped cleanup. */ char *spec_provider = NULL; const char *plugin_dir = NULL; const char *agent_name = NULL; char *oneshot = NULL; char *forever_prompt = NULL; int stream = 1; int headless = 0; struct clm_cfg cfg = {0}; struct cli_state *state; uv_loop_t *loop; char endpoint[256]; int opt, r; struct clm_lua_cfg *lcfg = NULL; if (argc >= 2 && strcmp(argv[1], "setup") == 0) return run_setup(); int resume = 0; const char *resume_id = NULL; enum { OPT_PROVIDER = 256 }; const struct option opts[] = { {"oneshot", required_argument, NULL, 'o'}, {"forever", required_argument, NULL, 'f'}, {"url", required_argument, NULL, 'u'}, {"model", required_argument, NULL, 'm'}, {"provider", required_argument, NULL, OPT_PROVIDER}, {"plugins", required_argument, NULL, 'p'}, {"agent", required_argument, NULL, 'a'}, {"resume", optional_argument, NULL, 'r'}, {"headless", no_argument, NULL, 'H'}, {"no-stream", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, }; while ((opt = getopt_long(argc, argv, "a:o:f:u:m:p:r::HSVh", opts, NULL)) != -1) { switch (opt) { case 'a': agent_name = optarg; break; case 'r': resume = 1; /* getopt's optional_argument only binds --resume=ID / * -rID; also accept a detached "--resume ID". */ if (optarg != NULL) resume_id = optarg; else if (optind < argc && argv[optind][0] != '-') resume_id = argv[optind++]; break;Lines 1–500 of 742. Load more
// SPDX-License-Identifier: ISC #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <time.h> #include <unistd.h> #include <uv.h> #include "clm/clm.h" #include "clm/host_uv.h" #include "clm/provider.h" #include "frontend.h" #include "version.h" #include "clm/lua_plugin.h" #include "seed_plugins.h" #include "clm/cleanup.h" #include "clm/session.h" #include "mcp_setup.h" #include "model_spec.h" #include "templates.h" #include "xdg.h" static void usage(const char *prog) { fprintf(stderr, "usage: %s setup\n" " %s [-o|--oneshot PROMPT] [-f|--forever PROMPT] " "[-H|--headless] [-u|--url BASE] " "[-m|--model PROVIDER/MODEL-ID] [--provider NAME] " "[-p|--plugins DIR] [-S|--no-stream] " "[-r|--resume [ID]] " "[-V|--version] [-h|--help]\n", prog, prog); fprintf(stderr, " setup write a starter config.lua and seed " "builtin plugins\n" " -o, --oneshot PROMPT run one prompt headless and exit\n" " -f, --forever PROMPT TUI mode: submit PROMPT, then " "auto-resubmit it\n" " every time a turn completes with nothing " "queued,\n" " so the agent keeps going without a human " "re-prompting it\n" " -H, --headless force the plain stdio REPL\n" " -u, --url BASE base API endpoint " "(default http://127.0.0.1:8081/v1);\n" " \"/messages\" is appended for an anthropic " "provider,\n" " \"/chat/completions\" otherwise\n" " -m, --model PROVIDER/MODEL-ID\n" " a config.lua providers[PROVIDER].models" "[MODEL-ID] entry,\n" " or a literal model id (no \"/\") to request " "on whatever\n" " connection is otherwise active\n" " --provider NAME name of an entry in config.lua's " "`providers` table,\n" " overriding the connection --model's " "provider half names\n" " -p, --plugins DIR plugin directory " "(default $XDG_CONFIG_HOME/clm/plugins)\n" " -S, --no-stream disable streamed (SSE) responses\n" " -r, --resume [ID] resume a saved session in the TUI; with " "no ID,\n" " pick from a list of saved sessions\n" " -V, --version print version and exit\n" " -h, --help show this help\n" "\n" " With no options it runs the interactive ncurses UI on a " "terminal.\n" " Set CLM_API_KEY in the environment to send a bearer token.\n"); } struct cli_state { uv_loop_t *loop; struct clm_host *host; struct clm_agent *agent; struct clm_lua_env *lua_env; struct clm_lua_cfg *lua_cfg; struct clm_mcp_client **mcp_clients; size_t mcp_client_count; uv_pipe_t stdin_pipe; char prompt_line[1024]; size_t prompt_len; int oneshot; int turn_done; int turn_status; }; static void cb_mcp_status(const char *msg, void *user) { (void)user; fprintf(stderr, "%s\n", msg); } static void cb_assistant_text(const char *text, void *user) { struct cli_state *state = (struct cli_state *)user; if (state->oneshot) printf("%s", text); else printf("assistant> %s", text); fflush(stdout); } static void cb_reasoning(const char *text, void *user) { (void)user; printf("\033[2m%s\033[0m", text); /* dim "thinking" channel */ fflush(stdout); } static void cb_finish_reason(enum clm_finish_reason reason, void *user) { (void)user; if (reason == CLM_FINISH_LENGTH) printf("\n\033[33m[truncated: hit token limit]\033[0m\n"); else if (reason == CLM_FINISH_CONTENT_FILTER) printf("\n\033[31m[stopped: content filter]\033[0m\n"); fflush(stdout); } static void cb_usage(const struct clm_usage *usage, void *user) { (void)user; printf("\033[2m[%d+%d tok", usage->prompt_tokens, usage->completion_tokens); if (usage->tokens_per_sec > 0) printf(", %.1f tok/s", usage->tokens_per_sec); printf("]\033[0m\n"); fflush(stdout); } static void cb_tool_begin(const char *name, const char *args, void *user) { (void)user; printf("[tool: %s %s]\n", name, args ? args : ""); fflush(stdout); } static void cb_tool_result(const char *name, const char *content, enum clm_tool_outcome outcome, void *user) { (void)user; switch (outcome) { case CLM_TOOL_OK: printf("[result: %s]\n%s\n", name, content ? content : ""); break; case CLM_TOOL_FAILED: printf("\033[31m[x %s]\033[0m %s\n", name, content ? content : ""); break; case CLM_TOOL_TIMEDOUT: printf("\033[33m[timeout %s]\033[0m %s\n", name, content ? content : ""); break; } fflush(stdout); } static void cb_tool_batch(size_t completed, size_t total, void *user) { (void)user; printf("[tools %zu/%zu]\n", completed, total); fflush(stdout); } static void cb_state(enum clm_agent_state state, void *user) { } static void cb_notice(const char *text, void *user) { (void)user; fprintf(stderr, "notice: %s\n", text ? text : ""); } static void cb_turn_done(int status, void *user) { struct cli_state *state = (struct cli_state *)user; if (status != 0) { fprintf(stderr, "error: %s\n", clm_agent_get_last_error(state->agent)); } if (state->oneshot) { state->turn_done = 1; state->turn_status = status; return; } printf("\nuser> "); fflush(stdout); } /* * Auto-allow permission handler: preserves the CLI's pre-gate behaviour under * the library's default-deny. A future refinement could gate this behind an * explicit --yes/--allow flag for unattended runs. */ static void cb_permission(const struct clm_permission_req *req, void *user) { struct cli_state *state = (struct cli_state *)user; clm_tool_permission_respond(state->agent, req, CLM_PERM_ALLOW_ONCE); } static const struct clm_callbacks cli_callbacks = { .on_assistant_text = cb_assistant_text, .on_reasoning = cb_reasoning, .on_tool_begin = cb_tool_begin, .on_permission = cb_permission, .on_tool_result = cb_tool_result, .on_tool_batch = cb_tool_batch, .on_finish_reason = cb_finish_reason, .on_usage = cb_usage, .on_state = cb_state, .on_turn_done = cb_turn_done, .on_notice = cb_notice, }; static void on_alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { buf->base = malloc(suggested_size); if (buf->base == NULL) { buf->len = 0; return; } buf->len = suggested_size; } static void on_stdin_close(uv_handle_t *handle) { struct cli_state *state = (struct cli_state *)handle->data; uv_stop(state->loop); } static void on_stdin_read(uv_stream_t *stream, ssize_t n_read, const uv_buf_t *buf) { struct cli_state *state = (struct cli_state *)stream->data; if (n_read < 0) { if (n_read == UV_ENOBUFS) return; if (n_read == UV_EOF) { free(buf->base); printf("\n"); fflush(stdout); uv_close((uv_handle_t *)stream, on_stdin_close); return; } fprintf(stderr, "read error: %s\n", uv_err_name(n_read)); uv_close((uv_handle_t *)stream, on_stdin_close); return; } if (n_read == 0) { free(buf->base); return; } for (ssize_t i = 0; i < n_read; i++) { if (state->prompt_len < sizeof(state->prompt_line) - 1) { state->prompt_line[state->prompt_len++] = buf->base[i]; } if (buf->base[i] == '\n' || buf->base[i] == '\r') { if (state->prompt_len > 0 && state->prompt_line[state->prompt_len - 1] == '\r') state->prompt_len--; if (state->prompt_len > 0 && state->prompt_line[state->prompt_len - 1] == '\n') state->prompt_len--; state->prompt_line[state->prompt_len] = '\0'; if (state->prompt_len > 0) { if (strcmp(state->prompt_line, "quit") == 0 || strcmp(state->prompt_line, "exit") == 0) { free(buf->base); uv_close((uv_handle_t *)stream, on_stdin_close); return; } int r = clm_agent_submit(state->agent, state->prompt_line); if (r < 0) { fprintf(stderr, "error: %s\n", clm_agent_get_last_error(state->agent)); } } state->prompt_len = 0; break; } } free(buf->base); } /* Writes content to path unless it already exists. Returns 0 if written, * 1 if it already existed (left untouched), or -errno on failure. */ static int write_new_file(const char *path, const char *content, mode_t mode) { int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); if (fd < 0) return errno == EEXIST ? 1 : -errno; size_t len = strlen(content); size_t off = 0; while (off < len) { ssize_t n = write(fd, content + off, len - off); if (n < 0) { int err = -errno; close(fd); (void)unlink(path); return err; } off += (size_t)n; } close(fd); return 0; } /* * --resume with no id: print a numbered listing of saved sessions and read * a choice from stdin. Returns a malloc'd session id, or NULL (no sessions, * bad choice, or EOF). */ static char * pick_session(void) { struct clm_session_info *infos = NULL; size_t n = 0, i; char line[32], *end, *id = NULL; unsigned long choice; int r; r = clm_session_list(NULL, &infos, &n); if (r < 0) { fprintf(stderr, "error: listing sessions: %s\n", strerror(-r)); return NULL; } if (n == 0) { fprintf(stderr, "no saved sessions\n"); return NULL; } for (i = 0; i < n; i++) { char when[32] = "?"; time_t created = (time_t)infos[i].created; struct tm tm; if (created > 0 && localtime_r(&created, &tm) != NULL) (void)strftime(when, sizeof(when), "%Y-%m-%d %H:%M", &tm); printf("%3zu) %s %s %s\n %s\n", i + 1, infos[i].id, when, infos[i].model != NULL ? infos[i].model : "", infos[i].first_user != NULL ? infos[i].first_user : "(empty)"); } printf("resume which session? [1-%zu] ", n); fflush(stdout); if (fgets(line, sizeof(line), stdin) == NULL) { clm_session_list_free(infos, n); return NULL; } errno = 0; choice = strtoul(line, &end, 10); if (errno != 0 || end == line || choice < 1 || choice > n) { fprintf(stderr, "no such session\n"); clm_session_list_free(infos, n); return NULL; } id = strdup(infos[choice - 1].id); clm_session_list_free(infos, n); return id; } /* `clm setup`: writes a starter config.lua and secrets.lua, and seeds the * builtin plugins, without silently doing any of that on a normal run. * Safe to re-run: never overwrites a config, secrets, or plugin file * that's already there. */ static int run_setup(void) { autofree char *cfg_path = xdg_config_path("clm/config.lua"); autofree char *secrets_path = xdg_config_path("clm/secrets.lua"); autofree char *agents_dir = xdg_config_path("clm/agents"); autofree char *plugins_dir = xdg_config_path("clm/plugins"); if (cfg_path == NULL || secrets_path == NULL || agents_dir == NULL || plugins_dir == NULL) { fprintf(stderr, "setup: could not determine config path " "($XDG_CONFIG_HOME or $HOME not set)\n"); return 1; } /* Also creates the parent clm/ dir. */ int sr = clm_seed_default_plugins(plugins_dir); if (sr < 0) { fprintf(stderr, "setup: seeding %s: %s\n", plugins_dir, strerror(-sr)); return 1; } printf("plugins in %s\n", plugins_dir); if (mkdir(agents_dir, 0755) != 0 && errno != EEXIST) { fprintf(stderr, "setup: mkdir %s: %s\n", agents_dir, strerror(errno)); return 1; } printf("agent profiles in %s\n", agents_dir); /* 0600: secrets.lua holds API keys, unlike config.lua. */ int xr = write_new_file(secrets_path, (const char *)secrets_lua_tpl_data, 0600); if (xr < 0) { fprintf(stderr, "setup: writing %s: %s\n", secrets_path, strerror(-xr)); return 1; } printf("%s %s\n", xr == 1 ? "kept existing" : "wrote", secrets_path); int cr = write_new_file(cfg_path, (const char *)config_lua_tpl_data, 0644); if (cr < 0) { fprintf(stderr, "setup: writing %s: %s\n", cfg_path, strerror(-cr)); return 1; } printf("%s %s\n", cr == 1 ? "kept existing" : "wrote", cfg_path); return 0; } int main(int argc, char *argv[]) { const char *api_base = NULL; const char *model_name = NULL; /* -m/--model: a "provider/model-id" spec, or a literal wire id */ const char *provider_name = NULL; /* --provider: a config providers[] name override */ /* Owned copy of a "provider/model-id" spec's provider half (see * split_provider_model() in model_spec.h), process-lifetime like * plenty else in this function -- cfg.provider_name below may alias * it, and cfg is still read as late as tui_run()'s call at the very * end of main(), well past any block-scoped cleanup. */ char *spec_provider = NULL; const char *plugin_dir = NULL; const char *agent_name = NULL; char *oneshot = NULL; char *forever_prompt = NULL; int stream = 1; int headless = 0; struct clm_cfg cfg = {0}; struct cli_state *state; uv_loop_t *loop; char endpoint[256]; int opt, r; struct clm_lua_cfg *lcfg = NULL; if (argc >= 2 && strcmp(argv[1], "setup") == 0) return run_setup(); int resume = 0; const char *resume_id = NULL; enum { OPT_PROVIDER = 256 }; const struct option opts[] = { {"oneshot", required_argument, NULL, 'o'}, {"forever", required_argument, NULL, 'f'}, {"url", required_argument, NULL, 'u'}, {"model", required_argument, NULL, 'm'}, {"provider", required_argument, NULL, OPT_PROVIDER}, {"plugins", required_argument, NULL, 'p'}, {"agent", required_argument, NULL, 'a'}, {"resume", optional_argument, NULL, 'r'}, {"headless", no_argument, NULL, 'H'}, {"no-stream", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, }; while ((opt = getopt_long(argc, argv, "a:o:f:u:m:p:r::HSVh", opts, NULL)) != -1) { switch (opt) { case 'a': agent_name = optarg; break; case 'r': resume = 1; /* getopt's optional_argument only binds --resume=ID / * -rID; also accept a detached "--resume ID". */ if (optarg != NULL) resume_id = optarg; else if (optind < argc && argv[optind][0] != '-') resume_id = argv[optind++]; break;