@ayoxx/kundex 0.1.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Kundex CLI & SDK
2
2
 
3
3
  The Kundex terminal AI coding agent — usable as a global CLI (`kundex`) or as
4
- an SDK (`import { ... } from "kundex"`).
4
+ an SDK (`import { ... } from "@ayoxx/kundex"`).
5
5
 
6
6
  ## Install
7
7
 
8
8
  ```bash
9
- npm install -g kundex
9
+ npm install -g @ayoxx/kundex
10
10
  ```
11
11
 
12
12
  ## Usage
@@ -20,15 +20,15 @@ kundex # start an interactive agent session
20
20
 
21
21
  This export ships a GitHub Actions workflow at
22
22
  `.github/workflows/publish-cli.yml` that publishes to npm whenever you push a
23
- git tag matching `cli-v*` (e.g. `v0.2.0`):
23
+ git tag matching `cli-v*` (e.g. `cli-v0.2.0`):
24
24
 
25
25
  1. Push this directory to a GitHub repo.
26
26
  2. In the repo's Settings > Secrets and variables > Actions, add an
27
27
  `NPM_TOKEN` secret (an npm automation token with publish rights).
28
28
  3. Bump `version` in `package.json`, commit, then:
29
29
  ```bash
30
- git tag v0.2.0
31
- git push origin v0.2.0
30
+ git tag cli-v0.2.0
31
+ git push origin cli-v0.2.0
32
32
  ```
33
33
  4. The workflow installs dependencies, type-checks, and runs `npm publish`.
34
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayoxx/kundex",
3
- "version": "0.1.7",
3
+ "version": "1.0.0",
4
4
  "description": "Kundex — a terminal-based AI coding agent CLI and SDK, powered by Groq.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -33,8 +33,24 @@ export function saveConfig(config: Partial<KundexConfig>): KundexConfig {
33
33
  return merged;
34
34
  }
35
35
 
36
- export const KUNDEX_BASE_URL = "https://kundex-api.onrender.com";
36
+ export const KUNDEX_BASE_URL = "https://api.kundex.com.ng";
37
37
 
38
- export function resolveBaseUrl(_config: KundexConfig): string | null {
39
- return KUNDEX_BASE_URL;
38
+ /**
39
+ * Resolves the base URL to use for API calls.
40
+ *
41
+ * Priority:
42
+ * 1. KUNDEX_BASE_URL environment variable (useful for local dev)
43
+ * 2. Stored baseUrl from config (~/.kundex/config.json)
44
+ * 3. Hard-coded default: https://api.kundex.com.ng
45
+ *
46
+ * Previously, this function ignored the stored baseUrl completely.
47
+ * That made `kundex config set baseUrl <url>` a no-op, which broke
48
+ * local development and self-hosted deployments.
49
+ */
50
+ export function resolveBaseUrl(config: KundexConfig): string | null {
51
+ return (
52
+ process.env.KUNDEX_BASE_URL?.trim() ||
53
+ config.baseUrl?.trim() ||
54
+ KUNDEX_BASE_URL
55
+ );
40
56
  }
package/src/index.ts CHANGED
@@ -1,41 +1,575 @@
1
- import { loadConfig, resolveBaseUrl } from "./config";
1
+ import { loadConfig, saveConfig, resolveBaseUrl } from "./config";
2
2
  import { runLogin } from "./login";
3
3
  import { runRepl } from "./repl";
4
+ import { Kundex } from "./sdk";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ const VERSION = "0.2.0";
9
+
10
+ // ─── Help text ────────────────────────────────────────────────────────────────
11
+
12
+ const HELP = `
13
+ \x1b[1m\x1b[36mkundex\x1b[0m — AI coding agent CLI v${VERSION}
14
+
15
+ \x1b[1mUsage:\x1b[0m
16
+ kundex [command] [options]
17
+
18
+ \x1b[1mAuthentication:\x1b[0m
19
+ login Save your Kundex API key
20
+ logout Remove saved credentials
21
+
22
+ \x1b[1mAgent:\x1b[0m
23
+ (no command) Start the interactive AI coding agent REPL
24
+ run <message> Run a single agent turn (non-interactive)
25
+
26
+ \x1b[1mWorkspaces:\x1b[0m
27
+ workspace list List all your agent workspaces
28
+ workspace new [cwd] Create a new workspace (default: current dir)
29
+ workspace delete <id> Delete a workspace
30
+
31
+ \x1b[1mFiles:\x1b[0m
32
+ files list <wsId> List files in a workspace
33
+ files get <wsId> <path> Print a file's content to stdout
34
+ files push <wsId> <localPath> Upload a local file to a workspace
35
+ files push <wsId> <localPath> <remotePath>
36
+ files pull <wsId> <remotePath> Download a workspace file to stdout (or --out)
37
+
38
+ \x1b[1mModels:\x1b[0m
39
+ models List available models
40
+
41
+ \x1b[1mConfiguration:\x1b[0m
42
+ config show Show current configuration
43
+ config set <key> <val> Set a config value
44
+ Keys: model, baseUrl
45
+
46
+ \x1b[1mOther:\x1b[0m
47
+ help, --help, -h Show this help
48
+ version, --version, -v Show version
49
+
50
+ \x1b[1mExamples:\x1b[0m
51
+ kundex login
52
+ kundex # start REPL in current directory
53
+ kundex run "Fix all TypeScript errors"
54
+ kundex models
55
+ kundex workspace list
56
+ kundex workspace new /path/to/project
57
+ kundex workspace delete 42
58
+ kundex files list 17
59
+ kundex files get 17 src/index.ts
60
+ kundex files push 17 ./README.md
61
+ kundex files pull 17 src/index.ts --out ./downloaded.ts
62
+ kundex config set model openai/gpt-oss-120b
63
+ kundex config set baseUrl https://api.kundex.com.ng
64
+ `.trim();
65
+
66
+ // ─── Colour helpers ───────────────────────────────────────────────────────────
67
+
68
+ const c = {
69
+ reset: "\x1b[0m",
70
+ bold: "\x1b[1m",
71
+ dim: "\x1b[2m",
72
+ cyan: "\x1b[36m",
73
+ green: "\x1b[32m",
74
+ yellow: "\x1b[33m",
75
+ red: "\x1b[31m",
76
+ };
77
+
78
+ function ok(msg: string) { console.log(`${c.green}${c.bold} ✓${c.reset} ${msg}`); }
79
+ function errMsg(msg: string) { console.error(`${c.red}${c.bold} ✗${c.reset} ${msg}`); }
80
+ function dim(msg: string) { console.log(`${c.dim} ${msg}${c.reset}`); }
81
+ function header(msg: string) { console.log(`\n${c.bold}${c.cyan} ${msg}${c.reset}`); }
82
+
83
+ // ─── Commands ─────────────────────────────────────────────────────────────────
84
+
85
+ const PROVIDER_DISPLAY: Record<string, string> = {
86
+ heavstal: "Heavstal",
87
+ gemini: "Gemini",
88
+ groq: "Groq",
89
+ mistral: "Mistral",
90
+ };
91
+
92
+ async function cmdModels(config: ReturnType<typeof loadConfig>, baseUrl: string) {
93
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
94
+ header("Available models");
95
+ try {
96
+ const models = await kundex.models.list();
97
+ if (!models.length) { dim("No models found."); return; }
98
+ const nameWidth = Math.max(...models.map((m) => m.name.length), 4);
99
+ const idWidth = Math.max(...models.map((m) => m.id.length), 2);
100
+ const providerWidth = Math.max(...models.map((m) => (m.provider?.length ?? 0)), 8);
101
+ console.log(`\n ${c.dim}${"Name".padEnd(nameWidth)} ${"ID".padEnd(idWidth)} ${"Provider".padEnd(providerWidth)} Context${c.reset}`);
102
+ console.log(` ${c.dim}${"─".repeat(nameWidth + idWidth + providerWidth + 18)}${c.reset}`);
103
+
104
+ // Group by provider for cleaner output
105
+ const grouped: Record<string, typeof models> = {};
106
+ const order: string[] = [];
107
+ for (const m of models) {
108
+ const p = m.provider ?? "other";
109
+ if (!grouped[p]) { grouped[p] = []; order.push(p); }
110
+ grouped[p].push(m);
111
+ }
112
+
113
+ for (const provider of order) {
114
+ for (const m of grouped[provider]) {
115
+ const ctx = m.contextWindow >= 1_000_000
116
+ ? `${(m.contextWindow / 1_000_000).toFixed(1)}M`
117
+ : m.contextWindow >= 1000
118
+ ? `${(m.contextWindow / 1000).toFixed(0)}k`
119
+ : String(m.contextWindow);
120
+ const providerLabel = (PROVIDER_DISPLAY[m.provider ?? ""] ?? m.provider ?? "").padEnd(providerWidth);
121
+ console.log(
122
+ ` ${c.bold}${m.name.padEnd(nameWidth)}${c.reset}` +
123
+ ` ${c.dim}${m.id.padEnd(idWidth)}${c.reset}` +
124
+ ` ${c.cyan}${providerLabel}${c.reset}` +
125
+ ` ${c.dim}${ctx}${c.reset}`,
126
+ );
127
+ }
128
+ }
129
+ console.log();
130
+ } catch (e) {
131
+ errMsg(`Failed to list models: ${(e as Error).message}`);
132
+ process.exitCode = 1;
133
+ }
134
+ }
135
+
136
+ async function cmdWorkspaceList(config: ReturnType<typeof loadConfig>, baseUrl: string) {
137
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
138
+ header("Workspaces");
139
+ try {
140
+ const sessions = await kundex.agent.listSessions();
141
+
142
+ if (!sessions.length) {
143
+ dim("No workspaces yet. Create one with: kundex workspace new <directory>");
144
+ return;
145
+ }
146
+
147
+ console.log(`\n ${c.dim}${"ID".padEnd(6)} ${"Name".padEnd(28)} ${"Directory".padEnd(34)} Model${c.reset}`);
148
+ console.log(` ${c.dim}${"─".repeat(90)}${c.reset}`);
149
+ for (const s of sessions) {
150
+ const cwd = (s.cwd ?? "").length > 32 ? `…${s.cwd.slice(-31)}` : (s.cwd ?? "");
151
+ const name = (s.name ?? `Workspace #${s.id}`).slice(0, 26);
152
+ console.log(` ${c.bold}${String(s.id).padEnd(6)}${c.reset} ${name.padEnd(28)} ${c.dim}${cwd.padEnd(34)} ${s.model}${c.reset}`);
153
+ }
154
+ console.log();
155
+ } catch (e) {
156
+ errMsg(`Failed to list workspaces: ${(e as Error).message}`);
157
+ process.exitCode = 1;
158
+ }
159
+ }
160
+
161
+ async function cmdWorkspaceNew(
162
+ config: ReturnType<typeof loadConfig>,
163
+ baseUrl: string,
164
+ args: string[],
165
+ ) {
166
+ const cwd = args[0] ?? process.cwd();
167
+
168
+ // Parse optional flags: --model <model>, --name <name>
169
+ let model = config.defaultModel ?? "openai/gpt-oss-120b";
170
+ let name: string | undefined;
171
+ for (let i = 0; i < args.length; i++) {
172
+ if (args[i] === "--model" && args[i + 1]) { model = args[++i]; }
173
+ else if (args[i] === "--name" && args[i + 1]) { name = args[++i]; }
174
+ }
175
+
176
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
177
+
178
+ try {
179
+ const session = await kundex.agent.createSession({ cwd, model, name });
180
+ ok(`Created workspace #${session.id}`);
181
+ dim(`Name: ${session.name}`);
182
+ dim(`Directory: ${cwd}`);
183
+ dim(`Model: ${session.model}`);
184
+ console.log();
185
+ } catch (e) {
186
+ errMsg(`Failed to create workspace: ${(e as Error).message}`);
187
+ process.exitCode = 1;
188
+ }
189
+ }
190
+
191
+ async function cmdWorkspaceDelete(
192
+ config: ReturnType<typeof loadConfig>,
193
+ baseUrl: string,
194
+ idStr: string,
195
+ ) {
196
+ const id = Number(idStr);
197
+ if (!Number.isFinite(id) || id <= 0) {
198
+ errMsg("Usage: kundex workspace delete <id>");
199
+ process.exitCode = 1;
200
+ return;
201
+ }
202
+
203
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
204
+
205
+ try {
206
+ await kundex.agent.deleteSession(id);
207
+ ok(`Deleted workspace #${id}`);
208
+ } catch (e) {
209
+ errMsg(`Failed to delete workspace: ${(e as Error).message}`);
210
+ process.exitCode = 1;
211
+ }
212
+ }
213
+
214
+ // ── Files ─────────────────────────────────────────────────────────────────────
215
+
216
+ async function cmdFilesList(
217
+ config: ReturnType<typeof loadConfig>,
218
+ baseUrl: string,
219
+ wsIdStr: string,
220
+ ) {
221
+ const wsId = Number(wsIdStr);
222
+ if (!Number.isFinite(wsId) || wsId <= 0) {
223
+ errMsg("Usage: kundex files list <wsId>");
224
+ process.exitCode = 1;
225
+ return;
226
+ }
227
+
228
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
229
+ header(`Files in workspace #${wsId}`);
230
+
231
+ try {
232
+ const { files } = await kundex.files.list(wsId);
233
+ if (!files.length) {
234
+ dim("No files yet.");
235
+ return;
236
+ }
237
+
238
+ console.log(`\n ${c.dim}${"Path".padEnd(44)} ${"Size".padEnd(10)} MIME${c.reset}`);
239
+ console.log(` ${c.dim}${"─".repeat(80)}${c.reset}`);
240
+ for (const f of files) {
241
+ const size = f.size < 1024 ? `${f.size} B`
242
+ : f.size < 1024 * 1024 ? `${(f.size / 1024).toFixed(1)} KB`
243
+ : `${(f.size / 1024 / 1024).toFixed(1)} MB`;
244
+ const displayPath = f.path.length > 42 ? `…${f.path.slice(-41)}` : f.path;
245
+ console.log(` ${displayPath.padEnd(44)} ${size.padEnd(10)} ${c.dim}${f.mimeType}${c.reset}`);
246
+ }
247
+ console.log();
248
+ } catch (e) {
249
+ errMsg(`Failed to list files: ${(e as Error).message}`);
250
+ process.exitCode = 1;
251
+ }
252
+ }
253
+
254
+ async function cmdFilesGet(
255
+ config: ReturnType<typeof loadConfig>,
256
+ baseUrl: string,
257
+ wsIdStr: string,
258
+ remotePath: string,
259
+ ) {
260
+ const wsId = Number(wsIdStr);
261
+ if (!Number.isFinite(wsId) || wsId <= 0 || !remotePath) {
262
+ errMsg("Usage: kundex files get <wsId> <path>");
263
+ process.exitCode = 1;
264
+ return;
265
+ }
266
+
267
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
268
+
269
+ try {
270
+ const file = await kundex.files.get(wsId, remotePath);
271
+ process.stdout.write(file.content ?? "");
272
+ } catch (e) {
273
+ errMsg(`Failed to get file: ${(e as Error).message}`);
274
+ process.exitCode = 1;
275
+ }
276
+ }
277
+
278
+ async function cmdFilesPush(
279
+ config: ReturnType<typeof loadConfig>,
280
+ baseUrl: string,
281
+ wsIdStr: string,
282
+ localPath: string,
283
+ remotePath?: string,
284
+ ) {
285
+ const wsId = Number(wsIdStr);
286
+ if (!Number.isFinite(wsId) || wsId <= 0 || !localPath) {
287
+ errMsg("Usage: kundex files push <wsId> <localPath> [remotePath]");
288
+ process.exitCode = 1;
289
+ return;
290
+ }
291
+
292
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
293
+ const destPath = remotePath ?? path.basename(localPath);
294
+
295
+ try {
296
+ const content = await fs.readFile(localPath, "utf-8");
297
+ const name = path.basename(destPath);
298
+ await kundex.files.upsert(wsId, { path: destPath, name, content });
299
+ ok(`Pushed ${localPath} → workspace #${wsId}:${destPath}`);
300
+ } catch (e) {
301
+ errMsg(`Failed to push file: ${(e as Error).message}`);
302
+ process.exitCode = 1;
303
+ }
304
+ }
305
+
306
+ async function cmdFilesPull(
307
+ config: ReturnType<typeof loadConfig>,
308
+ baseUrl: string,
309
+ wsIdStr: string,
310
+ remotePath: string,
311
+ outPath?: string,
312
+ ) {
313
+ const wsId = Number(wsIdStr);
314
+ if (!Number.isFinite(wsId) || wsId <= 0 || !remotePath) {
315
+ errMsg("Usage: kundex files pull <wsId> <remotePath> [--out <localPath>]");
316
+ process.exitCode = 1;
317
+ return;
318
+ }
319
+
320
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
321
+
322
+ try {
323
+ const file = await kundex.files.get(wsId, remotePath);
324
+ const content = file.content ?? "";
325
+
326
+ if (outPath) {
327
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
328
+ await fs.writeFile(outPath, content, "utf-8");
329
+ ok(`Saved ${remotePath} → ${outPath} (${content.length} bytes)`);
330
+ } else {
331
+ process.stdout.write(content);
332
+ }
333
+ } catch (e) {
334
+ errMsg(`Failed to pull file: ${(e as Error).message}`);
335
+ process.exitCode = 1;
336
+ }
337
+ }
338
+
339
+ // ── Run (single-turn non-interactive) ─────────────────────────────────────────
340
+
341
+ async function cmdRun(
342
+ config: ReturnType<typeof loadConfig>,
343
+ baseUrl: string,
344
+ message: string,
345
+ args: string[],
346
+ ) {
347
+ if (!message) {
348
+ errMsg("Usage: kundex run <message>");
349
+ process.exitCode = 1;
350
+ return;
351
+ }
352
+
353
+ // Parse optional flags: --model, --workspace, --cwd
354
+ let model = config.defaultModel ?? "openai/gpt-oss-120b";
355
+ let wsId: number | null = null;
356
+ let cwd = process.cwd();
357
+ for (let i = 0; i < args.length; i++) {
358
+ if (args[i] === "--model" && args[i + 1]) model = args[++i];
359
+ else if ((args[i] === "--workspace" || args[i] === "-w") && args[i + 1]) wsId = Number(args[++i]);
360
+ else if (args[i] === "--cwd" && args[i + 1]) cwd = args[++i];
361
+ }
362
+
363
+ const kundex = new Kundex({ apiKey: config.apiKey!, baseUrl });
364
+ let sessionId: number;
365
+
366
+ try {
367
+ if (wsId !== null && Number.isFinite(wsId)) {
368
+ sessionId = wsId;
369
+ } else {
370
+ const session = await kundex.agent.createSession({ cwd, model });
371
+ sessionId = session.id;
372
+ }
373
+
374
+ process.stdout.write(`\n`);
375
+ const { executeToolCall } = await import("./tools");
376
+
377
+ let turn = await kundex.agent.streamMessage(
378
+ { sessionId, message },
379
+ (delta) => process.stdout.write(delta),
380
+ );
381
+
382
+ // Handle tool calls in a loop until the turn is done
383
+ while (!turn.done && turn.toolCalls && turn.toolCalls.length > 0) {
384
+ for (const call of turn.toolCalls) {
385
+ const { result, isError } = await executeToolCall(cwd, call);
386
+ turn = await kundex.agent.submitToolResult(
387
+ { sessionId, toolCallId: call.id, result, isError },
388
+ (delta) => process.stdout.write(delta),
389
+ );
390
+ }
391
+ }
392
+
393
+ process.stdout.write("\n");
394
+ } catch (e) {
395
+ errMsg(`Agent error: ${(e as Error).message}`);
396
+ process.exitCode = 1;
397
+ }
398
+ }
399
+
400
+ // ─── Config commands ──────────────────────────────────────────────────────────
401
+
402
+ function cmdConfigShow(config: ReturnType<typeof loadConfig>, baseUrl: string) {
403
+ header("Current configuration");
404
+ console.log();
405
+ const rows = [
406
+ ["API key", config.apiKey ? `${config.apiKey.slice(0, 8)}…` : "(not set)"],
407
+ ["Base URL", baseUrl],
408
+ ["Model", config.defaultModel ?? "(default)"],
409
+ ];
410
+ for (const [k, v] of rows) {
411
+ console.log(` ${c.dim}${k.padEnd(12)}${c.reset} ${c.bold}${v}${c.reset}`);
412
+ }
413
+ console.log();
414
+ }
415
+
416
+ function cmdConfigSet(key: string, value: string) {
417
+ const allowed = ["model", "baseUrl"];
418
+ if (!allowed.includes(key)) {
419
+ errMsg(`Unknown config key "${key}". Allowed keys: ${allowed.join(", ")}`);
420
+ process.exitCode = 1;
421
+ return;
422
+ }
423
+
424
+ const map: Record<string, keyof ReturnType<typeof loadConfig>> = {
425
+ model: "defaultModel",
426
+ baseUrl: "baseUrl",
427
+ };
428
+
429
+ saveConfig({ [map[key]]: value });
430
+ ok(`Set ${key} = ${value}`);
431
+ }
432
+
433
+ function cmdLogout() {
434
+ saveConfig({ apiKey: null, baseUrl: null, defaultModel: null });
435
+ ok("Logged out. Run `kundex login` to authenticate again.");
436
+ }
437
+
438
+ // ─── Main ─────────────────────────────────────────────────────────────────────
4
439
 
5
440
  async function main(): Promise<void> {
6
- const [, , command] = process.argv;
441
+ const args = process.argv.slice(2);
442
+ const [command, sub, ...rest] = args;
7
443
 
8
- if (command === "login") {
9
- await runLogin();
444
+ // Version
445
+ if (command === "version" || command === "--version" || command === "-v") {
446
+ console.log(`kundex v${VERSION}`);
10
447
  return;
11
448
  }
12
449
 
450
+ // Help
13
451
  if (command === "help" || command === "--help" || command === "-h") {
14
- console.log(
15
- [
16
- "kundeX — terminal AI coding agent.",
17
- "",
18
- "Usage:",
19
- " kundex login Save your API key and API base URL",
20
- " kundex Start the agent REPL in the current directory",
21
- ].join("\n"),
22
- );
452
+ console.log(HELP);
453
+ return;
454
+ }
455
+
456
+ // No command — drop into REPL if logged in, else show help
457
+ if (!command) {
458
+ const config = loadConfig();
459
+ const baseUrl = resolveBaseUrl(config);
460
+ if (config.apiKey && baseUrl) {
461
+ await runRepl(config, config.apiKey, baseUrl);
462
+ return;
463
+ }
464
+ console.log(HELP);
465
+ console.log(`\n${c.yellow} Run \`kundex login\` to get started.${c.reset}\n`);
466
+ return;
467
+ }
468
+
469
+ // Login / logout
470
+ if (command === "login") {
471
+ await runLogin();
472
+ return;
473
+ }
474
+ if (command === "logout") {
475
+ cmdLogout();
23
476
  return;
24
477
  }
25
478
 
479
+ // Load config — required for all remaining commands
26
480
  const config = loadConfig();
27
481
  const baseUrl = resolveBaseUrl(config);
28
482
 
29
483
  if (!config.apiKey || !baseUrl) {
30
- console.error('Not logged in. Run "kundex login" first.');
484
+ errMsg('Not logged in. Run "kundex login" first.');
31
485
  process.exitCode = 1;
32
486
  return;
33
487
  }
34
488
 
35
- await runRepl(config, config.apiKey, baseUrl);
489
+ // Models
490
+ if (command === "models") {
491
+ await cmdModels(config, baseUrl);
492
+ return;
493
+ }
494
+
495
+ // Config
496
+ if (command === "config") {
497
+ if (sub === "show" || !sub) {
498
+ cmdConfigShow(config, baseUrl);
499
+ } else if (sub === "set") {
500
+ const [key, value] = rest;
501
+ if (!key || !value) {
502
+ errMsg("Usage: kundex config set <key> <value>");
503
+ process.exitCode = 1;
504
+ } else {
505
+ cmdConfigSet(key, value);
506
+ }
507
+ } else {
508
+ errMsg(`Unknown config subcommand "${sub}". Use: show, set`);
509
+ process.exitCode = 1;
510
+ }
511
+ return;
512
+ }
513
+
514
+ // Workspace
515
+ if (command === "workspace") {
516
+ if (sub === "list" || !sub) {
517
+ await cmdWorkspaceList(config, baseUrl);
518
+ } else if (sub === "new") {
519
+ await cmdWorkspaceNew(config, baseUrl, rest);
520
+ } else if (sub === "delete") {
521
+ await cmdWorkspaceDelete(config, baseUrl, rest[0]);
522
+ } else {
523
+ errMsg(`Unknown workspace subcommand "${sub}". Use: list, new, delete`);
524
+ process.exitCode = 1;
525
+ }
526
+ return;
527
+ }
528
+
529
+ // Files
530
+ if (command === "files") {
531
+ if (sub === "list") {
532
+ await cmdFilesList(config, baseUrl, rest[0]);
533
+ } else if (sub === "get") {
534
+ await cmdFilesGet(config, baseUrl, rest[0], rest[1]);
535
+ } else if (sub === "push") {
536
+ await cmdFilesPush(config, baseUrl, rest[0], rest[1], rest[2]);
537
+ } else if (sub === "pull") {
538
+ // Support --out flag
539
+ let outPath: string | undefined;
540
+ const remaining: string[] = [];
541
+ for (let i = 0; i < rest.length; i++) {
542
+ if (rest[i] === "--out" && rest[i + 1]) { outPath = rest[++i]; }
543
+ else remaining.push(rest[i]);
544
+ }
545
+ await cmdFilesPull(config, baseUrl, remaining[0], remaining[1], outPath);
546
+ } else {
547
+ errMsg(`Unknown files subcommand "${sub}". Use: list, get, push, pull`);
548
+ process.exitCode = 1;
549
+ }
550
+ return;
551
+ }
552
+
553
+ // Run (single-turn agent)
554
+ if (command === "run") {
555
+ // "kundex run <message>" — join remaining args as the message
556
+ const allArgs = [sub, ...rest].filter(Boolean);
557
+ // Find message (everything before the first -- flag or flag-like arg)
558
+ const flagStart = allArgs.findIndex((a) => a.startsWith("--"));
559
+ const messageParts = flagStart >= 0 ? allArgs.slice(0, flagStart) : allArgs;
560
+ const flagArgs = flagStart >= 0 ? allArgs.slice(flagStart) : [];
561
+ const message = messageParts.join(" ").trim();
562
+
563
+ await cmdRun(config, baseUrl, message, flagArgs);
564
+ return;
565
+ }
566
+
567
+ // Unknown command — suggest help
568
+ errMsg(`Unknown command "${command}". Run \`kundex help\` to see available commands.`);
569
+ process.exitCode = 1;
36
570
  }
37
571
 
38
- main().catch((err) => {
39
- console.error(err instanceof Error ? err.message : err);
572
+ main().catch((e) => {
573
+ console.error(`${c.red} Fatal: ${e instanceof Error ? e.message : e}${c.reset}`);
40
574
  process.exitCode = 1;
41
575
  });