@agentforge-ai/cli 0.3.2 → 0.4.1

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.
Files changed (89) hide show
  1. package/dist/default/.env.example +46 -6
  2. package/dist/default/README.md +89 -9
  3. package/dist/default/convex/schema.ts +248 -4
  4. package/dist/default/dashboard/app/components/DashboardLayout.tsx +245 -0
  5. package/dist/default/dashboard/app/components/ui/badge.tsx +26 -0
  6. package/dist/default/dashboard/app/components/ui/button.tsx +41 -0
  7. package/dist/default/dashboard/app/components/ui/card.tsx +44 -0
  8. package/dist/default/dashboard/app/components/ui/dialog.tsx +66 -0
  9. package/dist/default/dashboard/app/components/ui/input.tsx +21 -0
  10. package/dist/default/dashboard/app/components/ui/label.tsx +18 -0
  11. package/dist/default/dashboard/app/components/ui/select.tsx +75 -0
  12. package/dist/default/dashboard/app/components/ui/sheet.tsx +73 -0
  13. package/dist/default/dashboard/app/components/ui/switch.tsx +34 -0
  14. package/dist/default/dashboard/app/components/ui/table.tsx +60 -0
  15. package/dist/default/dashboard/app/components/ui/tabs.tsx +50 -0
  16. package/dist/default/dashboard/app/components/ui/tooltip.tsx +23 -0
  17. package/dist/default/dashboard/app/lib/utils.ts +6 -0
  18. package/dist/default/dashboard/app/main.tsx +35 -0
  19. package/dist/default/dashboard/app/routeTree.gen.ts +352 -0
  20. package/dist/default/dashboard/app/routes/__root.tsx +10 -0
  21. package/dist/default/dashboard/app/routes/agents.tsx +255 -0
  22. package/dist/default/dashboard/app/routes/chat.tsx +427 -0
  23. package/dist/default/dashboard/app/routes/connections.tsx +413 -0
  24. package/dist/default/dashboard/app/routes/cron.tsx +322 -0
  25. package/dist/default/dashboard/app/routes/files.tsx +203 -0
  26. package/dist/default/dashboard/app/routes/index.tsx +141 -0
  27. package/dist/default/dashboard/app/routes/projects.tsx +254 -0
  28. package/dist/default/dashboard/app/routes/sessions.tsx +272 -0
  29. package/dist/default/dashboard/app/routes/settings.tsx +583 -0
  30. package/dist/default/dashboard/app/routes/skills.tsx +252 -0
  31. package/dist/default/dashboard/app/routes/usage.tsx +181 -0
  32. package/dist/default/dashboard/app/styles/globals.css +93 -0
  33. package/dist/default/dashboard/index.html +13 -0
  34. package/dist/default/dashboard/package.json +36 -0
  35. package/dist/default/dashboard/postcss.config.js +6 -0
  36. package/dist/default/dashboard/tailwind.config.js +50 -0
  37. package/dist/default/dashboard/tsconfig.json +24 -0
  38. package/dist/default/dashboard/vite.config.ts +16 -0
  39. package/dist/default/package.json +8 -3
  40. package/dist/default/skills/skill-creator/SKILL.md +270 -0
  41. package/dist/default/skills/skill-creator/config.json +11 -0
  42. package/dist/default/skills/skill-creator/index.ts +392 -0
  43. package/dist/default/src/agent.ts +85 -5
  44. package/dist/index.js +1574 -10
  45. package/dist/index.js.map +1 -1
  46. package/package.json +2 -1
  47. package/templates/default/.env.example +46 -6
  48. package/templates/default/README.md +89 -9
  49. package/templates/default/convex/schema.ts +248 -4
  50. package/templates/default/dashboard/app/components/DashboardLayout.tsx +245 -0
  51. package/templates/default/dashboard/app/components/ui/badge.tsx +26 -0
  52. package/templates/default/dashboard/app/components/ui/button.tsx +41 -0
  53. package/templates/default/dashboard/app/components/ui/card.tsx +44 -0
  54. package/templates/default/dashboard/app/components/ui/dialog.tsx +66 -0
  55. package/templates/default/dashboard/app/components/ui/input.tsx +21 -0
  56. package/templates/default/dashboard/app/components/ui/label.tsx +18 -0
  57. package/templates/default/dashboard/app/components/ui/select.tsx +75 -0
  58. package/templates/default/dashboard/app/components/ui/sheet.tsx +73 -0
  59. package/templates/default/dashboard/app/components/ui/switch.tsx +34 -0
  60. package/templates/default/dashboard/app/components/ui/table.tsx +60 -0
  61. package/templates/default/dashboard/app/components/ui/tabs.tsx +50 -0
  62. package/templates/default/dashboard/app/components/ui/tooltip.tsx +23 -0
  63. package/templates/default/dashboard/app/lib/utils.ts +6 -0
  64. package/templates/default/dashboard/app/main.tsx +35 -0
  65. package/templates/default/dashboard/app/routeTree.gen.ts +352 -0
  66. package/templates/default/dashboard/app/routes/__root.tsx +10 -0
  67. package/templates/default/dashboard/app/routes/agents.tsx +255 -0
  68. package/templates/default/dashboard/app/routes/chat.tsx +427 -0
  69. package/templates/default/dashboard/app/routes/connections.tsx +413 -0
  70. package/templates/default/dashboard/app/routes/cron.tsx +322 -0
  71. package/templates/default/dashboard/app/routes/files.tsx +203 -0
  72. package/templates/default/dashboard/app/routes/index.tsx +141 -0
  73. package/templates/default/dashboard/app/routes/projects.tsx +254 -0
  74. package/templates/default/dashboard/app/routes/sessions.tsx +272 -0
  75. package/templates/default/dashboard/app/routes/settings.tsx +583 -0
  76. package/templates/default/dashboard/app/routes/skills.tsx +252 -0
  77. package/templates/default/dashboard/app/routes/usage.tsx +181 -0
  78. package/templates/default/dashboard/app/styles/globals.css +93 -0
  79. package/templates/default/dashboard/index.html +13 -0
  80. package/templates/default/dashboard/package.json +36 -0
  81. package/templates/default/dashboard/postcss.config.js +6 -0
  82. package/templates/default/dashboard/tailwind.config.js +50 -0
  83. package/templates/default/dashboard/tsconfig.json +24 -0
  84. package/templates/default/dashboard/vite.config.ts +16 -0
  85. package/templates/default/package.json +8 -3
  86. package/templates/default/skills/skill-creator/SKILL.md +270 -0
  87. package/templates/default/skills/skill-creator/config.json +11 -0
  88. package/templates/default/skills/skill-creator/index.ts +392 -0
  89. package/templates/default/src/agent.ts +85 -5
package/dist/index.js CHANGED
@@ -19,15 +19,25 @@ async function createProject(projectName, options) {
19
19
  console.log(`
20
20
  \u{1F528} Creating AgentForge project: ${projectName}
21
21
  `);
22
- const templateDir = path.resolve(
23
- __dirname,
24
- "..",
25
- // from dist/commands to dist
26
- "templates",
27
- options.template
28
- );
29
- if (!await fs.pathExists(templateDir)) {
22
+ const searchDirs = [
23
+ path.resolve(__dirname, options.template),
24
+ // dist/default (built)
25
+ path.resolve(__dirname, "..", "templates", options.template),
26
+ // packages/cli/templates/default (dev)
27
+ path.resolve(__dirname, "..", "..", "templates", options.template)
28
+ // fallback
29
+ ];
30
+ let templateDir = "";
31
+ for (const dir of searchDirs) {
32
+ if (await fs.pathExists(path.join(dir, "package.json"))) {
33
+ templateDir = dir;
34
+ break;
35
+ }
36
+ }
37
+ if (!templateDir) {
30
38
  console.error(`Error: Template "${options.template}" not found.`);
39
+ console.error(`Searched in:`);
40
+ searchDirs.forEach((d) => console.error(` - ${d}`));
31
41
  process.exit(1);
32
42
  }
33
43
  await fs.copy(templateDir, targetDir);
@@ -37,6 +47,12 @@ async function createProject(projectName, options) {
37
47
  pkg2.name = projectName;
38
48
  await fs.writeJson(pkgPath, pkg2, { spaces: 2 });
39
49
  }
50
+ const dashPkgPath = path.join(targetDir, "dashboard", "package.json");
51
+ if (await fs.pathExists(dashPkgPath)) {
52
+ const dashPkg = await fs.readJson(dashPkgPath);
53
+ dashPkg.name = `${projectName}-dashboard`;
54
+ await fs.writeJson(dashPkgPath, dashPkg, { spaces: 2 });
55
+ }
40
56
  console.log(` \u2705 Project scaffolded at ./${projectName}`);
41
57
  console.log(`
42
58
  \u{1F4E6} Installing dependencies...
@@ -54,12 +70,58 @@ async function createProject(projectName, options) {
54
70
  \u26A0\uFE0F Could not install dependencies. Run "cd ${projectName} && pnpm install" manually.`
55
71
  );
56
72
  }
73
+ const dashDir = path.join(targetDir, "dashboard");
74
+ if (await fs.pathExists(dashDir)) {
75
+ console.log(`
76
+ \u{1F4E6} Installing dashboard dependencies...
77
+ `);
78
+ try {
79
+ execSync("pnpm install", {
80
+ cwd: dashDir,
81
+ stdio: "inherit"
82
+ });
83
+ console.log(`
84
+ \u2705 Dashboard dependencies installed`);
85
+ } catch {
86
+ console.warn(
87
+ `
88
+ \u26A0\uFE0F Could not install dashboard dependencies. Run "cd ${projectName}/dashboard && pnpm install" manually.`
89
+ );
90
+ }
91
+ }
92
+ console.log(`
93
+ \u26A1 Initializing Convex...
94
+ `);
95
+ try {
96
+ execSync("npx convex dev --once", {
97
+ cwd: targetDir,
98
+ stdio: "inherit"
99
+ });
100
+ console.log(`
101
+ \u2705 Convex initialized`);
102
+ } catch {
103
+ console.warn(
104
+ `
105
+ \u26A0\uFE0F Convex initialization skipped. Run "npx convex dev" to set up your backend.`
106
+ );
107
+ }
57
108
  console.log(`
58
109
  \u{1F389} AgentForge project "${projectName}" created successfully!
59
110
 
60
111
  Next steps:
61
112
  cd ${projectName}
62
- agentforge run
113
+
114
+ # Start the Convex backend
115
+ npx convex dev
116
+
117
+ # In another terminal, launch the dashboard
118
+ agentforge dashboard
119
+
120
+ # Or chat with your agent from the CLI
121
+ agentforge chat
122
+
123
+ # Check system status
124
+ agentforge status
63
125
 
64
126
  Documentation: https://github.com/Agentic-Engineering-Agency/agentforge
65
127
  `);
@@ -232,6 +294,1496 @@ async function deployProject(options) {
232
294
  }
233
295
  }
234
296
 
297
+ // src/lib/convex-client.ts
298
+ import { ConvexHttpClient } from "convex/browser";
299
+ import fs4 from "fs-extra";
300
+ import path4 from "path";
301
+ function getConvexUrl() {
302
+ const cwd = process.cwd();
303
+ const envFiles = [".env.local", ".env", ".env.production"];
304
+ for (const envFile of envFiles) {
305
+ const envPath = path4.join(cwd, envFile);
306
+ if (fs4.existsSync(envPath)) {
307
+ const content = fs4.readFileSync(envPath, "utf-8");
308
+ const match = content.match(/CONVEX_URL\s*=\s*(.+)/);
309
+ if (match) {
310
+ return match[1].trim().replace(/["']/g, "");
311
+ }
312
+ }
313
+ }
314
+ const convexEnv = path4.join(cwd, ".convex", "deployment.json");
315
+ if (fs4.existsSync(convexEnv)) {
316
+ try {
317
+ const data = JSON.parse(fs4.readFileSync(convexEnv, "utf-8"));
318
+ if (data.url) return data.url;
319
+ } catch {
320
+ }
321
+ }
322
+ throw new Error(
323
+ "CONVEX_URL not found. Run `npx convex dev` first, or set CONVEX_URL in your .env file."
324
+ );
325
+ }
326
+ function createClient() {
327
+ const url = getConvexUrl();
328
+ return new ConvexHttpClient(url);
329
+ }
330
+ async function safeCall(fn, errorMessage) {
331
+ try {
332
+ return await fn();
333
+ } catch (error2) {
334
+ if (error2.message?.includes("CONVEX_URL not found")) {
335
+ console.error("\n\u274C Not connected to Convex.");
336
+ console.error(" Run `npx convex dev` in your project directory first.\n");
337
+ } else if (error2.message?.includes("fetch failed") || error2.message?.includes("ECONNREFUSED")) {
338
+ console.error("\n\u274C Cannot reach Convex deployment.");
339
+ console.error(" Make sure `npx convex dev` is running.\n");
340
+ } else {
341
+ console.error(`
342
+ \u274C ${errorMessage}`);
343
+ console.error(` ${error2.message}
344
+ `);
345
+ }
346
+ process.exit(1);
347
+ }
348
+ }
349
+
350
+ // src/lib/display.ts
351
+ var colors = {
352
+ reset: "\x1B[0m",
353
+ bold: "\x1B[1m",
354
+ dim: "\x1B[2m",
355
+ red: "\x1B[31m",
356
+ green: "\x1B[32m",
357
+ yellow: "\x1B[33m",
358
+ blue: "\x1B[34m",
359
+ magenta: "\x1B[35m",
360
+ cyan: "\x1B[36m",
361
+ white: "\x1B[37m",
362
+ gray: "\x1B[90m"
363
+ };
364
+ function success(msg) {
365
+ console.log(`${colors.green}\u2714${colors.reset} ${msg}`);
366
+ }
367
+ function error(msg) {
368
+ console.error(`${colors.red}\u2716${colors.reset} ${msg}`);
369
+ }
370
+ function info(msg) {
371
+ console.log(`${colors.blue}\u2139${colors.reset} ${msg}`);
372
+ }
373
+ function header(title) {
374
+ console.log(`
375
+ ${colors.bold}${colors.cyan}${title}${colors.reset}
376
+ `);
377
+ }
378
+ function dim(msg) {
379
+ console.log(`${colors.dim}${msg}${colors.reset}`);
380
+ }
381
+ function table(data, columns) {
382
+ if (data.length === 0) {
383
+ dim(" (no items)");
384
+ return;
385
+ }
386
+ const cols = columns ?? Object.keys(data[0]);
387
+ const widths = {};
388
+ for (const col of cols) {
389
+ widths[col] = Math.max(
390
+ col.length,
391
+ ...data.map((row) => String(row[col] ?? "").length)
392
+ );
393
+ }
394
+ const headerRow = cols.map((c) => c.toUpperCase().padEnd(widths[c])).join(" ");
395
+ console.log(` ${colors.bold}${headerRow}${colors.reset}`);
396
+ console.log(` ${cols.map((c) => "\u2500".repeat(widths[c])).join("\u2500\u2500")}`);
397
+ for (const row of data) {
398
+ const line = cols.map((c) => String(row[c] ?? "").padEnd(widths[c])).join(" ");
399
+ console.log(` ${line}`);
400
+ }
401
+ console.log();
402
+ }
403
+ function details(data) {
404
+ const maxKey = Math.max(...Object.keys(data).map((k) => k.length));
405
+ for (const [key, value] of Object.entries(data)) {
406
+ const label = `${colors.dim}${key.padEnd(maxKey)}${colors.reset}`;
407
+ console.log(` ${label} ${value}`);
408
+ }
409
+ console.log();
410
+ }
411
+ function formatDate(ts) {
412
+ return new Date(ts).toLocaleString();
413
+ }
414
+
415
+ // src/commands/agents.ts
416
+ import readline from "readline";
417
+ function prompt(question) {
418
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
419
+ return new Promise((resolve2) => rl.question(question, (ans) => {
420
+ rl.close();
421
+ resolve2(ans.trim());
422
+ }));
423
+ }
424
+ function registerAgentsCommand(program2) {
425
+ const agents = program2.command("agents").description("Manage agents");
426
+ agents.command("list").description("List all agents").option("--active", "Show only active agents").option("--json", "Output as JSON").action(async (opts) => {
427
+ const client = createClient();
428
+ const result = await safeCall(
429
+ () => client.query("agents:list", {}),
430
+ "Failed to list agents"
431
+ );
432
+ if (opts.json) {
433
+ console.log(JSON.stringify(result, null, 2));
434
+ return;
435
+ }
436
+ header("Agents");
437
+ if (!result || result.length === 0) {
438
+ info("No agents found. Create one with: agentforge agents create");
439
+ return;
440
+ }
441
+ const filtered = opts.active ? result.filter((a) => a.isActive) : result;
442
+ table(
443
+ filtered.map((a) => ({
444
+ ID: a.id,
445
+ Name: a.name,
446
+ Model: a.model,
447
+ Provider: a.provider || "openai",
448
+ Active: a.isActive ? "\u2714" : "\u2716",
449
+ Created: formatDate(a.createdAt)
450
+ }))
451
+ );
452
+ });
453
+ agents.command("create").description("Create a new agent (interactive)").option("--name <name>", "Agent name").option("--model <model>", "Model identifier (e.g., openai:gpt-4o-mini)").option("--instructions <text>", "System instructions").action(async (opts) => {
454
+ const name = opts.name || await prompt("Agent name: ");
455
+ const model = opts.model || await prompt("Model (e.g., openai:gpt-4o-mini): ");
456
+ const instructions = opts.instructions || await prompt("Instructions: ");
457
+ if (!name || !model || !instructions) {
458
+ error("Name, model, and instructions are required.");
459
+ process.exit(1);
460
+ }
461
+ const provider = model.includes(":") ? model.split(":")[0] : "openai";
462
+ const agentId = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
463
+ const client = createClient();
464
+ await safeCall(
465
+ () => client.mutation("agents:create", {
466
+ id: agentId,
467
+ name,
468
+ instructions,
469
+ model,
470
+ provider,
471
+ isActive: true
472
+ }),
473
+ "Failed to create agent"
474
+ );
475
+ success(`Agent "${name}" created with ID: ${agentId}`);
476
+ });
477
+ agents.command("inspect").argument("<id>", "Agent ID").description("Show detailed agent information").action(async (id) => {
478
+ const client = createClient();
479
+ const agent = await safeCall(
480
+ () => client.query("agents:getByAgentId", { id }),
481
+ "Failed to fetch agent"
482
+ );
483
+ if (!agent) {
484
+ error(`Agent "${id}" not found.`);
485
+ process.exit(1);
486
+ }
487
+ header(`Agent: ${agent.name}`);
488
+ const a = agent;
489
+ details({
490
+ "ID": a.id,
491
+ "Name": a.name,
492
+ "Model": a.model,
493
+ "Provider": a.provider || "openai",
494
+ "Active": a.isActive ? "Yes" : "No",
495
+ "Temperature": a.temperature ?? "default",
496
+ "Max Tokens": a.maxTokens ?? "default",
497
+ "Created": formatDate(a.createdAt),
498
+ "Updated": formatDate(a.updatedAt)
499
+ });
500
+ if (a.description) info(`Description: ${a.description}`);
501
+ console.log(` Instructions:
502
+ ${a.instructions.split("\n").join("\n ")}
503
+ `);
504
+ });
505
+ agents.command("edit").argument("<id>", "Agent ID").option("--name <name>", "New name").option("--model <model>", "New model").option("--instructions <text>", "New instructions").description("Edit an agent").action(async (id, opts) => {
506
+ const client = createClient();
507
+ const agent = await safeCall(
508
+ () => client.query("agents:getByAgentId", { id }),
509
+ "Failed to fetch agent"
510
+ );
511
+ if (!agent) {
512
+ error(`Agent "${id}" not found.`);
513
+ process.exit(1);
514
+ }
515
+ const updates = {};
516
+ if (opts.name) updates.name = opts.name;
517
+ if (opts.model) {
518
+ updates.model = opts.model;
519
+ updates.provider = opts.model.includes(":") ? opts.model.split(":")[0] : "openai";
520
+ }
521
+ if (opts.instructions) updates.instructions = opts.instructions;
522
+ if (Object.keys(updates).length === 0) {
523
+ const a = agent;
524
+ const name = await prompt(`Name [${a.name}]: `);
525
+ const model = await prompt(`Model [${a.model}]: `);
526
+ const instr = await prompt(`Instructions [keep current]: `);
527
+ if (name) updates.name = name;
528
+ if (model) {
529
+ updates.model = model;
530
+ updates.provider = model.includes(":") ? model.split(":")[0] : "openai";
531
+ }
532
+ if (instr) updates.instructions = instr;
533
+ }
534
+ if (Object.keys(updates).length === 0) {
535
+ info("No changes made.");
536
+ return;
537
+ }
538
+ await safeCall(
539
+ () => client.mutation("agents:update", { _id: agent._id, ...updates }),
540
+ "Failed to update agent"
541
+ );
542
+ success(`Agent "${id}" updated.`);
543
+ });
544
+ agents.command("delete").argument("<id>", "Agent ID").option("-f, --force", "Skip confirmation").description("Delete an agent").action(async (id, opts) => {
545
+ if (!opts.force) {
546
+ const confirm = await prompt(`Delete agent "${id}"? (y/N): `);
547
+ if (confirm.toLowerCase() !== "y") {
548
+ info("Cancelled.");
549
+ return;
550
+ }
551
+ }
552
+ const client = createClient();
553
+ const agent = await safeCall(
554
+ () => client.query("agents:getByAgentId", { id }),
555
+ "Failed to fetch agent"
556
+ );
557
+ if (!agent) {
558
+ error(`Agent "${id}" not found.`);
559
+ process.exit(1);
560
+ }
561
+ await safeCall(
562
+ () => client.mutation("agents:remove", { _id: agent._id }),
563
+ "Failed to delete agent"
564
+ );
565
+ success(`Agent "${id}" deleted.`);
566
+ });
567
+ agents.command("enable").argument("<id>", "Agent ID").description("Enable an agent").action(async (id) => {
568
+ const client = createClient();
569
+ const agent = await safeCall(() => client.query("agents:getByAgentId", { id }), "Failed to fetch agent");
570
+ if (!agent) {
571
+ error(`Agent "${id}" not found.`);
572
+ process.exit(1);
573
+ }
574
+ await safeCall(() => client.mutation("agents:update", { _id: agent._id, isActive: true }), "Failed");
575
+ success(`Agent "${id}" enabled.`);
576
+ });
577
+ agents.command("disable").argument("<id>", "Agent ID").description("Disable an agent").action(async (id) => {
578
+ const client = createClient();
579
+ const agent = await safeCall(() => client.query("agents:getByAgentId", { id }), "Failed to fetch agent");
580
+ if (!agent) {
581
+ error(`Agent "${id}" not found.`);
582
+ process.exit(1);
583
+ }
584
+ await safeCall(() => client.mutation("agents:update", { _id: agent._id, isActive: false }), "Failed");
585
+ success(`Agent "${id}" disabled.`);
586
+ });
587
+ }
588
+
589
+ // src/commands/chat.ts
590
+ import readline2 from "readline";
591
+ function registerChatCommand(program2) {
592
+ program2.command("chat").argument("[agent-id]", "Agent ID to chat with").option("-s, --session <id>", "Resume an existing session").description("Start an interactive chat session with an agent").action(async (agentId, opts) => {
593
+ const client = createClient();
594
+ if (!agentId && !opts.session) {
595
+ const agents = await safeCall(() => client.query("agents:list", {}), "Failed to list agents");
596
+ if (!agents || agents.length === 0) {
597
+ error("No agents found. Create one first: agentforge agents create");
598
+ process.exit(1);
599
+ }
600
+ header("Available Agents");
601
+ agents.forEach((a2, i) => {
602
+ console.log(` ${colors.cyan}${i + 1}.${colors.reset} ${a2.name} ${colors.dim}(${a2.id})${colors.reset}`);
603
+ });
604
+ console.log();
605
+ const rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout });
606
+ const choice = await new Promise((r) => rl2.question("Select agent (number or ID): ", (a2) => {
607
+ rl2.close();
608
+ r(a2.trim());
609
+ }));
610
+ const idx = parseInt(choice) - 1;
611
+ agentId = idx >= 0 && idx < agents.length ? agents[idx].id : choice;
612
+ }
613
+ const agent = await safeCall(() => client.query("agents:getByAgentId", { id: agentId }), "Failed to fetch agent");
614
+ if (!agent) {
615
+ error(`Agent "${agentId}" not found.`);
616
+ process.exit(1);
617
+ }
618
+ const a = agent;
619
+ header(`Chat with ${a.name}`);
620
+ dim(` Model: ${a.model} | Provider: ${a.provider || "openai"}`);
621
+ dim(` Type "exit" or "quit" to end. "/new" for new thread. "/history" for messages.`);
622
+ console.log();
623
+ let threadId = await safeCall(
624
+ () => client.mutation("threads:create", { agentId: a.id, status: "active" }),
625
+ "Failed to create thread"
626
+ );
627
+ const history = [];
628
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout, prompt: `${colors.green}You${colors.reset} > ` });
629
+ rl.prompt();
630
+ rl.on("line", async (line) => {
631
+ const input = line.trim();
632
+ if (!input) {
633
+ rl.prompt();
634
+ return;
635
+ }
636
+ if (input === "exit" || input === "quit") {
637
+ success("Session ended. Goodbye!");
638
+ process.exit(0);
639
+ }
640
+ if (input === "/new") {
641
+ threadId = await safeCall(() => client.mutation("threads:create", { agentId: a.id, status: "active" }), "Failed");
642
+ history.length = 0;
643
+ info("New thread started.");
644
+ rl.prompt();
645
+ return;
646
+ }
647
+ if (input === "/history") {
648
+ history.forEach((m) => {
649
+ const prefix = m.role === "user" ? `${colors.green}You${colors.reset}` : `${colors.cyan}${a.name}${colors.reset}`;
650
+ console.log(` ${prefix}: ${m.content}`);
651
+ });
652
+ if (history.length === 0) dim(" (no messages yet)");
653
+ console.log();
654
+ rl.prompt();
655
+ return;
656
+ }
657
+ history.push({ role: "user", content: input });
658
+ await safeCall(() => client.mutation("messages:send", { threadId, role: "user", content: input }), "Failed to send");
659
+ process.stdout.write(`${colors.cyan}${a.name}${colors.reset} > `);
660
+ try {
661
+ const response = await safeCall(
662
+ () => client.action("mastraIntegration:generateResponse", { agentId: a.id, threadId, message: input }),
663
+ "Failed to get response"
664
+ );
665
+ const text = response?.text || response?.content || String(response);
666
+ console.log(text);
667
+ history.push({ role: "assistant", content: text });
668
+ } catch {
669
+ console.log(`${colors.yellow}[Configure your LLM API key in .env to get responses]${colors.reset}`);
670
+ }
671
+ console.log();
672
+ rl.prompt();
673
+ });
674
+ rl.on("close", () => {
675
+ console.log();
676
+ info("Session ended.");
677
+ process.exit(0);
678
+ });
679
+ });
680
+ }
681
+
682
+ // src/commands/sessions.ts
683
+ function registerSessionsCommand(program2) {
684
+ const sessions = program2.command("sessions").description("Manage sessions");
685
+ sessions.command("list").option("--status <status>", "Filter by status (active, ended)").option("--json", "Output as JSON").description("List all sessions").action(async (opts) => {
686
+ const client = createClient();
687
+ const result = await safeCall(() => client.query("sessions:list", {}), "Failed to list sessions");
688
+ if (opts.json) {
689
+ console.log(JSON.stringify(result, null, 2));
690
+ return;
691
+ }
692
+ header("Sessions");
693
+ const items = result || [];
694
+ if (items.length === 0) {
695
+ info("No sessions found.");
696
+ return;
697
+ }
698
+ const filtered = opts.status ? items.filter((s) => s.status === opts.status) : items;
699
+ table(filtered.map((s) => ({
700
+ ID: s._id?.slice(-8) || "N/A",
701
+ Name: s.name || "Unnamed",
702
+ Agent: s.agentId,
703
+ Status: s.status,
704
+ Started: formatDate(s.startedAt),
705
+ "Last Activity": formatDate(s.lastActivityAt)
706
+ })));
707
+ });
708
+ sessions.command("inspect").argument("<id>", "Session ID").description("Show session details").action(async (id) => {
709
+ const client = createClient();
710
+ const session = await safeCall(() => client.query("sessions:getById", { id }), "Failed to fetch session");
711
+ if (!session) {
712
+ error(`Session "${id}" not found.`);
713
+ process.exit(1);
714
+ }
715
+ const s = session;
716
+ header(`Session: ${s.name || "Unnamed"}`);
717
+ details({ ID: s._id, Name: s.name, Agent: s.agentId, Status: s.status, Started: formatDate(s.startedAt), "Last Activity": formatDate(s.lastActivityAt) });
718
+ });
719
+ sessions.command("end").argument("<id>", "Session ID").description("End an active session").action(async (id) => {
720
+ const client = createClient();
721
+ await safeCall(() => client.mutation("sessions:update", { _id: id, status: "ended" }), "Failed to end session");
722
+ success(`Session "${id}" ended.`);
723
+ });
724
+ }
725
+ function registerThreadsCommand(program2) {
726
+ const threads = program2.command("threads").description("Manage conversation threads");
727
+ threads.command("list").option("--agent <id>", "Filter by agent ID").option("--json", "Output as JSON").description("List all threads").action(async (opts) => {
728
+ const client = createClient();
729
+ const args = opts.agent ? { agentId: opts.agent } : {};
730
+ const result = await safeCall(() => client.query("threads:list", args), "Failed to list threads");
731
+ if (opts.json) {
732
+ console.log(JSON.stringify(result, null, 2));
733
+ return;
734
+ }
735
+ header("Threads");
736
+ const items = result || [];
737
+ if (items.length === 0) {
738
+ info("No threads found.");
739
+ return;
740
+ }
741
+ table(items.map((t) => ({
742
+ ID: t._id?.slice(-8) || "N/A",
743
+ Name: t.name || "Unnamed",
744
+ Agent: t.agentId,
745
+ Status: t.status,
746
+ Created: formatDate(t.createdAt)
747
+ })));
748
+ });
749
+ threads.command("inspect").argument("<id>", "Thread ID").description("Show thread messages").action(async (id) => {
750
+ const client = createClient();
751
+ const messages = await safeCall(() => client.query("messages:listByThread", { threadId: id }), "Failed to fetch messages");
752
+ header(`Thread: ${id}`);
753
+ const items = messages || [];
754
+ if (items.length === 0) {
755
+ info("No messages in this thread.");
756
+ return;
757
+ }
758
+ items.forEach((m) => {
759
+ const role = m.role === "user" ? "\x1B[32mUser\x1B[0m" : m.role === "assistant" ? "\x1B[36mAssistant\x1B[0m" : `\x1B[33m${m.role}\x1B[0m`;
760
+ console.log(` ${role}: ${m.content}`);
761
+ });
762
+ console.log();
763
+ });
764
+ threads.command("delete").argument("<id>", "Thread ID").description("Delete a thread and its messages").action(async (id) => {
765
+ const client = createClient();
766
+ await safeCall(() => client.mutation("threads:remove", { _id: id }), "Failed to delete thread");
767
+ success(`Thread "${id}" deleted.`);
768
+ });
769
+ }
770
+
771
+ // src/commands/skills.ts
772
+ import fs5 from "fs-extra";
773
+ import path5 from "path";
774
+ import readline3 from "readline";
775
+ function prompt2(q) {
776
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
777
+ return new Promise((r) => rl.question(q, (a) => {
778
+ rl.close();
779
+ r(a.trim());
780
+ }));
781
+ }
782
+ function registerSkillsCommand(program2) {
783
+ const skills = program2.command("skills").description("Manage agent skills");
784
+ skills.command("list").option("--installed", "Show only installed skills").option("--json", "Output as JSON").description("List all skills").action(async (opts) => {
785
+ const client = createClient();
786
+ const result = await safeCall(() => client.query("skills:list", {}), "Failed to list skills");
787
+ if (opts.json) {
788
+ console.log(JSON.stringify(result, null, 2));
789
+ return;
790
+ }
791
+ header("Skills");
792
+ const localSkills = [];
793
+ const skillsDir = path5.join(process.cwd(), "skills");
794
+ if (fs5.existsSync(skillsDir)) {
795
+ const dirs = fs5.readdirSync(skillsDir).filter((d) => fs5.statSync(path5.join(skillsDir, d)).isDirectory());
796
+ localSkills.push(...dirs);
797
+ }
798
+ const items = result || [];
799
+ if (items.length === 0 && localSkills.length === 0) {
800
+ info("No skills found. Install one with: agentforge skills install <name>");
801
+ info("Or create one with: agentforge skills create");
802
+ return;
803
+ }
804
+ if (localSkills.length > 0) {
805
+ dim(" Local Skills:");
806
+ localSkills.forEach((s) => {
807
+ const configPath = path5.join(skillsDir, s, "config.json");
808
+ let desc = "";
809
+ if (fs5.existsSync(configPath)) {
810
+ try {
811
+ desc = JSON.parse(fs5.readFileSync(configPath, "utf-8")).description || "";
812
+ } catch {
813
+ }
814
+ }
815
+ console.log(` ${colors.cyan}\u25CF${colors.reset} ${s} ${colors.dim}${desc}${colors.reset}`);
816
+ });
817
+ console.log();
818
+ }
819
+ if (items.length > 0) {
820
+ const filtered = opts.installed ? items.filter((s) => s.isInstalled) : items;
821
+ table(filtered.map((s) => ({
822
+ Name: s.name,
823
+ Category: s.category,
824
+ Version: s.version,
825
+ Installed: s.isInstalled ? "\u2714" : "\u2716",
826
+ Agent: s.agentId || "all"
827
+ })));
828
+ }
829
+ });
830
+ skills.command("create").description("Create a new skill (interactive)").option("--name <name>", "Skill name").option("--description <desc>", "Skill description").option("--category <cat>", "Category (utility, web, file, data, integration, ai, custom)").action(async (opts) => {
831
+ const name = opts.name || await prompt2("Skill name (kebab-case): ");
832
+ const description = opts.description || await prompt2("Description: ");
833
+ const category = opts.category || await prompt2("Category (utility/web/file/data/integration/ai/custom): ") || "custom";
834
+ if (!name) {
835
+ error("Skill name is required.");
836
+ process.exit(1);
837
+ }
838
+ const skillDir = path5.join(process.cwd(), "skills", name);
839
+ if (fs5.existsSync(skillDir)) {
840
+ error(`Skill "${name}" already exists at ${skillDir}`);
841
+ process.exit(1);
842
+ }
843
+ fs5.mkdirSync(skillDir, { recursive: true });
844
+ fs5.writeFileSync(path5.join(skillDir, "config.json"), JSON.stringify({
845
+ name,
846
+ version: "1.0.0",
847
+ description,
848
+ category,
849
+ author: "User",
850
+ tools: [name],
851
+ dependencies: [],
852
+ agentInstructions: `You have access to the ${name} skill. ${description}`
853
+ }, null, 2));
854
+ fs5.writeFileSync(path5.join(skillDir, "index.ts"), `import { z } from 'zod';
855
+
856
+ /**
857
+ * ${name} \u2014 AgentForge Skill
858
+ * ${description}
859
+ */
860
+ export const tools = [
861
+ {
862
+ name: '${name}',
863
+ description: '${description}',
864
+ inputSchema: z.object({
865
+ input: z.string().describe('Input for ${name}'),
866
+ }),
867
+ outputSchema: z.object({
868
+ result: z.string(),
869
+ success: z.boolean(),
870
+ }),
871
+ handler: async (params: { input: string }) => {
872
+ // TODO: Implement your skill logic here
873
+ return { result: \`Processed: \${params.input}\`, success: true };
874
+ },
875
+ },
876
+ ];
877
+
878
+ export default { tools };
879
+ `);
880
+ fs5.writeFileSync(path5.join(skillDir, "SKILL.md"), `# ${name}
881
+
882
+ ${description}
883
+
884
+ ## Usage
885
+
886
+ Ask your agent: "Use the ${name} tool to [your request]"
887
+
888
+ ## Configuration
889
+
890
+ Edit \`skills/${name}/config.json\` to customize.
891
+ `);
892
+ success(`Skill "${name}" created at skills/${name}/`);
893
+ info("Files created: index.ts, config.json, SKILL.md");
894
+ info(`Edit skills/${name}/index.ts to implement your tool logic.`);
895
+ });
896
+ skills.command("install").argument("<name>", "Skill name to install").description("Install a skill").action(async (name) => {
897
+ const client = createClient();
898
+ await safeCall(
899
+ () => client.mutation("skills:create", {
900
+ name,
901
+ category: "custom",
902
+ version: "1.0.0",
903
+ isInstalled: true
904
+ }),
905
+ "Failed to install skill"
906
+ );
907
+ success(`Skill "${name}" installed.`);
908
+ });
909
+ skills.command("remove").argument("<name>", "Skill name to remove").description("Remove a skill").action(async (name) => {
910
+ const skillDir = path5.join(process.cwd(), "skills", name);
911
+ if (fs5.existsSync(skillDir)) {
912
+ const confirm = await prompt2(`Remove skill "${name}" and delete files? (y/N): `);
913
+ if (confirm.toLowerCase() === "y") {
914
+ fs5.removeSync(skillDir);
915
+ success(`Skill "${name}" removed from disk.`);
916
+ }
917
+ }
918
+ const client = createClient();
919
+ try {
920
+ const skills2 = await client.query("skills:list", {});
921
+ const skill = skills2.find((s) => s.name === name);
922
+ if (skill) {
923
+ await client.mutation("skills:remove", { _id: skill._id });
924
+ success(`Skill "${name}" removed from database.`);
925
+ }
926
+ } catch {
927
+ }
928
+ });
929
+ skills.command("search").argument("<query>", "Search query").description("Search available skills").action(async (query) => {
930
+ header("Skill Search Results");
931
+ const examples = [
932
+ { name: "web-search", desc: "Search the web for information", cat: "web" },
933
+ { name: "calculator", desc: "Evaluate mathematical expressions", cat: "utility" },
934
+ { name: "file-reader", desc: "Read file contents", cat: "file" },
935
+ { name: "http-request", desc: "Make HTTP requests", cat: "web" },
936
+ { name: "json-transformer", desc: "Transform JSON data", cat: "data" },
937
+ { name: "text-summarizer", desc: "Summarize text into key points", cat: "ai" },
938
+ { name: "csv-parser", desc: "Parse CSV into structured JSON", cat: "data" }
939
+ ];
940
+ const q = query.toLowerCase();
941
+ const matches = examples.filter((e) => e.name.includes(q) || e.desc.toLowerCase().includes(q) || e.cat.includes(q));
942
+ if (matches.length === 0) {
943
+ info(`No skills matching "${query}". Try: agentforge skills create`);
944
+ return;
945
+ }
946
+ table(matches.map((e) => ({ Name: e.name, Description: e.desc, Category: e.cat })));
947
+ info("Install with: agentforge skills install <name>");
948
+ info("Or see examples: check skills/skill-creator/SKILL.md");
949
+ });
950
+ }
951
+
952
+ // src/commands/cron.ts
953
+ import readline4 from "readline";
954
+ function prompt3(q) {
955
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
956
+ return new Promise((r) => rl.question(q, (a) => {
957
+ rl.close();
958
+ r(a.trim());
959
+ }));
960
+ }
961
+ function registerCronCommand(program2) {
962
+ const cron = program2.command("cron").description("Manage cron jobs");
963
+ cron.command("list").option("--json", "Output as JSON").description("List all cron jobs").action(async (opts) => {
964
+ const client = createClient();
965
+ const result = await safeCall(() => client.query("cronJobs:list", {}), "Failed to list cron jobs");
966
+ if (opts.json) {
967
+ console.log(JSON.stringify(result, null, 2));
968
+ return;
969
+ }
970
+ header("Cron Jobs");
971
+ const items = result || [];
972
+ if (items.length === 0) {
973
+ info("No cron jobs. Create one with: agentforge cron create");
974
+ return;
975
+ }
976
+ table(items.map((c) => ({
977
+ ID: c._id?.slice(-8) || "N/A",
978
+ Name: c.name,
979
+ Schedule: c.schedule,
980
+ Agent: c.agentId,
981
+ Enabled: c.isEnabled ? "\u2714" : "\u2716",
982
+ "Last Run": c.lastRunAt ? formatDate(c.lastRunAt) : "Never",
983
+ "Next Run": c.nextRunAt ? formatDate(c.nextRunAt) : "N/A"
984
+ })));
985
+ });
986
+ cron.command("create").description("Create a new cron job (interactive)").option("--name <name>", "Job name").option("--schedule <cron>", "Cron expression").option("--agent <id>", "Agent ID").option("--action <action>", "Action to execute").action(async (opts) => {
987
+ const name = opts.name || await prompt3("Job name: ");
988
+ const schedule = opts.schedule || await prompt3('Cron schedule (e.g., "0 */5 * * * *" for every 5 min): ');
989
+ const agentId = opts.agent || await prompt3("Agent ID: ");
990
+ const action = opts.action || await prompt3("Action (message to send to agent): ");
991
+ if (!name || !schedule || !agentId || !action) {
992
+ error("All fields are required.");
993
+ process.exit(1);
994
+ }
995
+ const client = createClient();
996
+ await safeCall(
997
+ () => client.mutation("cronJobs:create", { name, schedule, agentId, action, isEnabled: true }),
998
+ "Failed to create cron job"
999
+ );
1000
+ success(`Cron job "${name}" created.`);
1001
+ });
1002
+ cron.command("delete").argument("<id>", "Cron job ID").description("Delete a cron job").action(async (id) => {
1003
+ const client = createClient();
1004
+ await safeCall(() => client.mutation("cronJobs:remove", { _id: id }), "Failed to delete");
1005
+ success(`Cron job "${id}" deleted.`);
1006
+ });
1007
+ cron.command("enable").argument("<id>", "Cron job ID").description("Enable a cron job").action(async (id) => {
1008
+ const client = createClient();
1009
+ await safeCall(() => client.mutation("cronJobs:update", { _id: id, isEnabled: true }), "Failed");
1010
+ success(`Cron job "${id}" enabled.`);
1011
+ });
1012
+ cron.command("disable").argument("<id>", "Cron job ID").description("Disable a cron job").action(async (id) => {
1013
+ const client = createClient();
1014
+ await safeCall(() => client.mutation("cronJobs:update", { _id: id, isEnabled: false }), "Failed");
1015
+ success(`Cron job "${id}" disabled.`);
1016
+ });
1017
+ }
1018
+
1019
+ // src/commands/mcp.ts
1020
+ import readline5 from "readline";
1021
+ function prompt4(q) {
1022
+ const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
1023
+ return new Promise((r) => rl.question(q, (a) => {
1024
+ rl.close();
1025
+ r(a.trim());
1026
+ }));
1027
+ }
1028
+ function registerMcpCommand(program2) {
1029
+ const mcp = program2.command("mcp").description("Manage MCP connections");
1030
+ mcp.command("list").option("--json", "Output as JSON").description("List all MCP connections").action(async (opts) => {
1031
+ const client = createClient();
1032
+ const result = await safeCall(() => client.query("mcpConnections:list", {}), "Failed to list connections");
1033
+ if (opts.json) {
1034
+ console.log(JSON.stringify(result, null, 2));
1035
+ return;
1036
+ }
1037
+ header("MCP Connections");
1038
+ const items = result || [];
1039
+ if (items.length === 0) {
1040
+ info("No connections. Add one with: agentforge mcp add");
1041
+ return;
1042
+ }
1043
+ table(items.map((c) => ({
1044
+ ID: c._id?.slice(-8) || "N/A",
1045
+ Name: c.name,
1046
+ Type: c.type,
1047
+ Endpoint: c.endpoint,
1048
+ Connected: c.isConnected ? "\u2714" : "\u2716",
1049
+ Enabled: c.isEnabled ? "\u2714" : "\u2716"
1050
+ })));
1051
+ });
1052
+ mcp.command("add").description("Add a new MCP connection (interactive)").option("--name <name>", "Connection name").option("--type <type>", "Connection type (stdio, sse, http)").option("--endpoint <url>", "Endpoint URL or command").action(async (opts) => {
1053
+ const name = opts.name || await prompt4("Connection name: ");
1054
+ const type = opts.type || await prompt4("Type (stdio/sse/http): ");
1055
+ const endpoint = opts.endpoint || await prompt4("Endpoint (URL or command): ");
1056
+ if (!name || !type || !endpoint) {
1057
+ error("All fields required.");
1058
+ process.exit(1);
1059
+ }
1060
+ const client = createClient();
1061
+ await safeCall(
1062
+ () => client.mutation("mcpConnections:create", {
1063
+ name,
1064
+ type,
1065
+ endpoint,
1066
+ isConnected: false,
1067
+ isEnabled: true
1068
+ }),
1069
+ "Failed to add connection"
1070
+ );
1071
+ success(`MCP connection "${name}" added.`);
1072
+ });
1073
+ mcp.command("remove").argument("<id>", "Connection ID").description("Remove an MCP connection").action(async (id) => {
1074
+ const client = createClient();
1075
+ await safeCall(() => client.mutation("mcpConnections:remove", { _id: id }), "Failed");
1076
+ success(`Connection "${id}" removed.`);
1077
+ });
1078
+ mcp.command("test").argument("<id>", "Connection ID").description("Test an MCP connection").action(async (id) => {
1079
+ info(`Testing connection "${id}"...`);
1080
+ const client = createClient();
1081
+ const conns = await safeCall(() => client.query("mcpConnections:list", {}), "Failed");
1082
+ const conn = conns.find((c) => c._id === id || c._id?.endsWith(id));
1083
+ if (!conn) {
1084
+ error(`Connection "${id}" not found.`);
1085
+ process.exit(1);
1086
+ }
1087
+ if (conn.type === "http" || conn.type === "sse") {
1088
+ try {
1089
+ const res = await fetch(conn.endpoint, { method: "HEAD", signal: AbortSignal.timeout(5e3) });
1090
+ if (res.ok) {
1091
+ success(`Connection "${conn.name}" is reachable (HTTP ${res.status}).`);
1092
+ await client.mutation("mcpConnections:update", { _id: conn._id, isConnected: true });
1093
+ } else {
1094
+ error(`Connection "${conn.name}" returned HTTP ${res.status}.`);
1095
+ }
1096
+ } catch (e) {
1097
+ error(`Connection "${conn.name}" failed: ${e.message}`);
1098
+ }
1099
+ } else {
1100
+ info(`Connection type "${conn.type}" \u2014 manual verification required.`);
1101
+ info(`Endpoint: ${conn.endpoint}`);
1102
+ }
1103
+ });
1104
+ mcp.command("enable").argument("<id>", "Connection ID").description("Enable a connection").action(async (id) => {
1105
+ const client = createClient();
1106
+ await safeCall(() => client.mutation("mcpConnections:update", { _id: id, isEnabled: true }), "Failed");
1107
+ success(`Connection "${id}" enabled.`);
1108
+ });
1109
+ mcp.command("disable").argument("<id>", "Connection ID").description("Disable a connection").action(async (id) => {
1110
+ const client = createClient();
1111
+ await safeCall(() => client.mutation("mcpConnections:update", { _id: id, isEnabled: false }), "Failed");
1112
+ success(`Connection "${id}" disabled.`);
1113
+ });
1114
+ }
1115
+
1116
+ // src/commands/files.ts
1117
+ import fs6 from "fs-extra";
1118
+ import path6 from "path";
1119
+ function registerFilesCommand(program2) {
1120
+ const files = program2.command("files").description("Manage files");
1121
+ files.command("list").argument("[folder]", "Folder ID to list files from").option("--json", "Output as JSON").description("List files").action(async (folder, opts) => {
1122
+ const client = createClient();
1123
+ const args = folder ? { folderId: folder } : {};
1124
+ const result = await safeCall(() => client.query("files:list", args), "Failed to list files");
1125
+ if (opts.json) {
1126
+ console.log(JSON.stringify(result, null, 2));
1127
+ return;
1128
+ }
1129
+ header("Files");
1130
+ const items = result || [];
1131
+ if (items.length === 0) {
1132
+ info("No files. Upload one with: agentforge files upload <path>");
1133
+ return;
1134
+ }
1135
+ table(items.map((f) => ({
1136
+ ID: f._id?.slice(-8) || "N/A",
1137
+ Name: f.name,
1138
+ Type: f.mimeType,
1139
+ Size: formatSize(f.size),
1140
+ Folder: f.folderId || "root",
1141
+ Created: formatDate(f.createdAt)
1142
+ })));
1143
+ });
1144
+ files.command("upload").argument("<filepath>", "Path to file to upload").option("--folder <id>", "Folder ID to upload to").option("--project <id>", "Project ID to associate with").description("Upload a file").action(async (filepath, opts) => {
1145
+ const absPath = path6.resolve(filepath);
1146
+ if (!fs6.existsSync(absPath)) {
1147
+ error(`File not found: ${absPath}`);
1148
+ process.exit(1);
1149
+ }
1150
+ const stat = fs6.statSync(absPath);
1151
+ const name = path6.basename(absPath);
1152
+ const ext = path6.extname(absPath).toLowerCase();
1153
+ const mimeTypes = {
1154
+ ".txt": "text/plain",
1155
+ ".md": "text/markdown",
1156
+ ".json": "application/json",
1157
+ ".js": "text/javascript",
1158
+ ".ts": "text/typescript",
1159
+ ".py": "text/x-python",
1160
+ ".pdf": "application/pdf",
1161
+ ".png": "image/png",
1162
+ ".jpg": "image/jpeg",
1163
+ ".csv": "text/csv",
1164
+ ".html": "text/html",
1165
+ ".xml": "text/xml"
1166
+ };
1167
+ const mimeType = mimeTypes[ext] || "application/octet-stream";
1168
+ const client = createClient();
1169
+ await safeCall(
1170
+ () => client.mutation("files:create", {
1171
+ name,
1172
+ mimeType,
1173
+ size: stat.size,
1174
+ folderId: opts.folder,
1175
+ projectId: opts.project
1176
+ }),
1177
+ "Failed to upload file metadata"
1178
+ );
1179
+ success(`File "${name}" registered (${formatSize(stat.size)}, ${mimeType}).`);
1180
+ info("Note: File content storage requires Convex file storage or R2 integration.");
1181
+ });
1182
+ files.command("delete").argument("<id>", "File ID").description("Delete a file").action(async (id) => {
1183
+ const client = createClient();
1184
+ await safeCall(() => client.mutation("files:remove", { _id: id }), "Failed to delete file");
1185
+ success(`File "${id}" deleted.`);
1186
+ });
1187
+ const folders = program2.command("folders").description("Manage folders");
1188
+ folders.command("list").option("--json", "Output as JSON").description("List all folders").action(async (opts) => {
1189
+ const client = createClient();
1190
+ const result = await safeCall(() => client.query("folders:list", {}), "Failed to list folders");
1191
+ if (opts.json) {
1192
+ console.log(JSON.stringify(result, null, 2));
1193
+ return;
1194
+ }
1195
+ header("Folders");
1196
+ const items = result || [];
1197
+ if (items.length === 0) {
1198
+ info("No folders. Create one with: agentforge folders create <name>");
1199
+ return;
1200
+ }
1201
+ table(items.map((f) => ({
1202
+ ID: f._id?.slice(-8) || "N/A",
1203
+ Name: f.name,
1204
+ Parent: f.parentId || "root",
1205
+ Created: formatDate(f.createdAt)
1206
+ })));
1207
+ });
1208
+ folders.command("create").argument("<name>", "Folder name").option("--parent <id>", "Parent folder ID").description("Create a folder").action(async (name, opts) => {
1209
+ const client = createClient();
1210
+ await safeCall(
1211
+ () => client.mutation("folders:create", { name, parentId: opts.parent }),
1212
+ "Failed to create folder"
1213
+ );
1214
+ success(`Folder "${name}" created.`);
1215
+ });
1216
+ folders.command("delete").argument("<id>", "Folder ID").description("Delete a folder").action(async (id) => {
1217
+ const client = createClient();
1218
+ await safeCall(() => client.mutation("folders:remove", { _id: id }), "Failed to delete folder");
1219
+ success(`Folder "${id}" deleted.`);
1220
+ });
1221
+ }
1222
+ function formatSize(bytes) {
1223
+ if (bytes < 1024) return `${bytes} B`;
1224
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1225
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1226
+ }
1227
+
1228
+ // src/commands/projects.ts
1229
+ import readline6 from "readline";
1230
+ function prompt5(q) {
1231
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
1232
+ return new Promise((r) => rl.question(q, (a) => {
1233
+ rl.close();
1234
+ r(a.trim());
1235
+ }));
1236
+ }
1237
+ function registerProjectsCommand(program2) {
1238
+ const projects = program2.command("projects").description("Manage projects and workspaces");
1239
+ projects.command("list").option("--json", "Output as JSON").description("List all projects").action(async (opts) => {
1240
+ const client = createClient();
1241
+ const result = await safeCall(() => client.query("projects:list", {}), "Failed to list projects");
1242
+ if (opts.json) {
1243
+ console.log(JSON.stringify(result, null, 2));
1244
+ return;
1245
+ }
1246
+ header("Projects");
1247
+ const items = result || [];
1248
+ if (items.length === 0) {
1249
+ info("No projects. Create one with: agentforge projects create <name>");
1250
+ return;
1251
+ }
1252
+ table(items.map((p) => ({
1253
+ ID: p._id?.slice(-8) || "N/A",
1254
+ Name: p.name,
1255
+ Status: p.status,
1256
+ Description: (p.description || "").slice(0, 40),
1257
+ Created: formatDate(p.createdAt)
1258
+ })));
1259
+ });
1260
+ projects.command("create").argument("<name>", "Project name").option("-d, --description <desc>", "Project description").description("Create a new project").action(async (name, opts) => {
1261
+ const description = opts.description || await prompt5("Description (optional): ");
1262
+ const client = createClient();
1263
+ await safeCall(
1264
+ () => client.mutation("projects:create", { name, description, status: "active" }),
1265
+ "Failed to create project"
1266
+ );
1267
+ success(`Project "${name}" created.`);
1268
+ });
1269
+ projects.command("inspect").argument("<id>", "Project ID").description("Show project details").action(async (id) => {
1270
+ const client = createClient();
1271
+ const projects2 = await safeCall(() => client.query("projects:list", {}), "Failed");
1272
+ const project = projects2.find((p) => p._id === id || p._id?.endsWith(id));
1273
+ if (!project) {
1274
+ error(`Project "${id}" not found.`);
1275
+ process.exit(1);
1276
+ }
1277
+ header(`Project: ${project.name}`);
1278
+ details({
1279
+ ID: project._id,
1280
+ Name: project.name,
1281
+ Status: project.status,
1282
+ Description: project.description || "N/A",
1283
+ Created: formatDate(project.createdAt),
1284
+ Updated: formatDate(project.updatedAt)
1285
+ });
1286
+ });
1287
+ projects.command("delete").argument("<id>", "Project ID").option("-f, --force", "Skip confirmation").description("Delete a project").action(async (id, opts) => {
1288
+ if (!opts.force) {
1289
+ const confirm = await prompt5(`Delete project "${id}"? (y/N): `);
1290
+ if (confirm.toLowerCase() !== "y") {
1291
+ info("Cancelled.");
1292
+ return;
1293
+ }
1294
+ }
1295
+ const client = createClient();
1296
+ await safeCall(() => client.mutation("projects:remove", { _id: id }), "Failed");
1297
+ success(`Project "${id}" deleted.`);
1298
+ });
1299
+ projects.command("switch").argument("<id>", "Project ID to switch to").description("Set the active project").action(async (id) => {
1300
+ const client = createClient();
1301
+ const projects2 = await safeCall(() => client.query("projects:list", {}), "Failed");
1302
+ const project = projects2.find((p) => p._id === id || p._id?.endsWith(id));
1303
+ if (!project) {
1304
+ error(`Project "${id}" not found.`);
1305
+ process.exit(1);
1306
+ }
1307
+ await safeCall(
1308
+ () => client.mutation("settings:set", { userId: "cli", key: "activeProject", value: project._id }),
1309
+ "Failed to switch project"
1310
+ );
1311
+ success(`Switched to project "${project.name}".`);
1312
+ });
1313
+ }
1314
+
1315
+ // src/commands/config.ts
1316
+ import fs7 from "fs-extra";
1317
+ import path7 from "path";
1318
+ import readline7 from "readline";
1319
+ function prompt6(q) {
1320
+ const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
1321
+ return new Promise((r) => rl.question(q, (a) => {
1322
+ rl.close();
1323
+ r(a.trim());
1324
+ }));
1325
+ }
1326
+ function registerConfigCommand(program2) {
1327
+ const config = program2.command("config").description("Manage configuration");
1328
+ config.command("show").description("Show current configuration").action(async () => {
1329
+ header("Configuration");
1330
+ const cwd = process.cwd();
1331
+ const envFiles = [".env", ".env.local", ".env.production"];
1332
+ for (const envFile of envFiles) {
1333
+ const envPath = path7.join(cwd, envFile);
1334
+ if (fs7.existsSync(envPath)) {
1335
+ console.log(` ${colors.cyan}${envFile}${colors.reset}`);
1336
+ const content = fs7.readFileSync(envPath, "utf-8");
1337
+ content.split("\n").forEach((line) => {
1338
+ if (line.trim() && !line.startsWith("#")) {
1339
+ const [key, ...rest] = line.split("=");
1340
+ const value = rest.join("=").trim();
1341
+ const masked = key.toLowerCase().includes("key") || key.toLowerCase().includes("secret") || key.toLowerCase().includes("token") ? value.slice(0, 4) + "****" + value.slice(-4) : value;
1342
+ console.log(` ${colors.dim}${key.trim()}${colors.reset} = ${masked}`);
1343
+ }
1344
+ });
1345
+ console.log();
1346
+ }
1347
+ }
1348
+ const convexDir = path7.join(cwd, ".convex");
1349
+ if (fs7.existsSync(convexDir)) {
1350
+ info("Convex: Configured");
1351
+ } else {
1352
+ info("Convex: Not configured (run `npx convex dev`)");
1353
+ }
1354
+ const skillsDir = path7.join(cwd, "skills");
1355
+ if (fs7.existsSync(skillsDir)) {
1356
+ const skills = fs7.readdirSync(skillsDir).filter((d) => fs7.statSync(path7.join(skillsDir, d)).isDirectory());
1357
+ info(`Skills: ${skills.length} installed (${skills.join(", ")})`);
1358
+ } else {
1359
+ info("Skills: None installed");
1360
+ }
1361
+ });
1362
+ config.command("set").argument("<key>", "Configuration key").argument("<value>", "Configuration value").option("--env <file>", "Environment file to update", ".env.local").description("Set a configuration value").action(async (key, value, opts) => {
1363
+ const envPath = path7.join(process.cwd(), opts.env);
1364
+ let content = "";
1365
+ if (fs7.existsSync(envPath)) {
1366
+ content = fs7.readFileSync(envPath, "utf-8");
1367
+ }
1368
+ const lines = content.split("\n");
1369
+ const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
1370
+ if (idx >= 0) {
1371
+ lines[idx] = `${key}=${value}`;
1372
+ } else {
1373
+ lines.push(`${key}=${value}`);
1374
+ }
1375
+ fs7.writeFileSync(envPath, lines.join("\n"));
1376
+ success(`Set ${key} in ${opts.env}`);
1377
+ });
1378
+ config.command("get").argument("<key>", "Configuration key").description("Get a configuration value").action(async (key) => {
1379
+ const cwd = process.cwd();
1380
+ const envFiles = [".env.local", ".env", ".env.production"];
1381
+ for (const envFile of envFiles) {
1382
+ const envPath = path7.join(cwd, envFile);
1383
+ if (fs7.existsSync(envPath)) {
1384
+ const content = fs7.readFileSync(envPath, "utf-8");
1385
+ const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
1386
+ if (match) {
1387
+ console.log(match[1].trim());
1388
+ return;
1389
+ }
1390
+ }
1391
+ }
1392
+ error(`Key "${key}" not found in any .env file.`);
1393
+ });
1394
+ config.command("init").description("Initialize configuration for a new project").action(async () => {
1395
+ header("Project Configuration");
1396
+ const convexUrl = await prompt6("Convex URL (from `npx convex dev`): ");
1397
+ const provider = await prompt6("LLM Provider (openai/openrouter/anthropic/google): ") || "openai";
1398
+ const apiKey = await prompt6(`${provider.toUpperCase()} API Key: `);
1399
+ const envContent = [
1400
+ `# AgentForge Configuration`,
1401
+ `CONVEX_URL=${convexUrl}`,
1402
+ ``,
1403
+ `# LLM Provider`,
1404
+ `LLM_PROVIDER=${provider}`
1405
+ ];
1406
+ if (provider === "openai") envContent.push(`OPENAI_API_KEY=${apiKey}`);
1407
+ else if (provider === "openrouter") envContent.push(`OPENROUTER_API_KEY=${apiKey}`);
1408
+ else if (provider === "anthropic") envContent.push(`ANTHROPIC_API_KEY=${apiKey}`);
1409
+ else if (provider === "google") envContent.push(`GOOGLE_API_KEY=${apiKey}`);
1410
+ fs7.writeFileSync(path7.join(process.cwd(), ".env.local"), envContent.join("\n") + "\n");
1411
+ success("Configuration saved to .env.local");
1412
+ info("Run `npx convex dev` to start the Convex backend.");
1413
+ });
1414
+ config.command("provider").argument("<provider>", "LLM provider to configure (openai, openrouter, anthropic, google, xai)").description("Configure an LLM provider").action(async (provider) => {
1415
+ const keyNames = {
1416
+ openai: "OPENAI_API_KEY",
1417
+ openrouter: "OPENROUTER_API_KEY",
1418
+ anthropic: "ANTHROPIC_API_KEY",
1419
+ google: "GOOGLE_API_KEY",
1420
+ xai: "XAI_API_KEY"
1421
+ };
1422
+ const keyName = keyNames[provider.toLowerCase()];
1423
+ if (!keyName) {
1424
+ error(`Unknown provider "${provider}". Supported: ${Object.keys(keyNames).join(", ")}`);
1425
+ process.exit(1);
1426
+ }
1427
+ const apiKey = await prompt6(`${keyName}: `);
1428
+ if (!apiKey) {
1429
+ error("API key is required.");
1430
+ process.exit(1);
1431
+ }
1432
+ const envPath = path7.join(process.cwd(), ".env.local");
1433
+ let content = "";
1434
+ if (fs7.existsSync(envPath)) content = fs7.readFileSync(envPath, "utf-8");
1435
+ const lines = content.split("\n");
1436
+ const idx = lines.findIndex((l) => l.startsWith(`${keyName}=`));
1437
+ if (idx >= 0) lines[idx] = `${keyName}=${apiKey}`;
1438
+ else lines.push(`${keyName}=${apiKey}`);
1439
+ const provIdx = lines.findIndex((l) => l.startsWith("LLM_PROVIDER="));
1440
+ if (provIdx >= 0) lines[provIdx] = `LLM_PROVIDER=${provider}`;
1441
+ else lines.push(`LLM_PROVIDER=${provider}`);
1442
+ fs7.writeFileSync(envPath, lines.join("\n"));
1443
+ success(`Provider "${provider}" configured.`);
1444
+ });
1445
+ }
1446
+
1447
+ // src/commands/vault.ts
1448
+ import readline8 from "readline";
1449
+ function prompt7(q) {
1450
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
1451
+ return new Promise((r) => rl.question(q, (a) => {
1452
+ rl.close();
1453
+ r(a.trim());
1454
+ }));
1455
+ }
1456
+ function promptSecret(q) {
1457
+ return new Promise((resolve2) => {
1458
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
1459
+ if (process.stdin.isTTY) {
1460
+ process.stdout.write(q);
1461
+ let input = "";
1462
+ process.stdin.setRawMode(true);
1463
+ process.stdin.resume();
1464
+ process.stdin.setEncoding("utf-8");
1465
+ const onData = (char) => {
1466
+ if (char === "\n" || char === "\r") {
1467
+ process.stdin.setRawMode(false);
1468
+ process.stdin.pause();
1469
+ process.stdin.removeListener("data", onData);
1470
+ console.log();
1471
+ rl.close();
1472
+ resolve2(input);
1473
+ } else if (char === "") {
1474
+ process.exit();
1475
+ } else if (char === "\x7F") {
1476
+ input = input.slice(0, -1);
1477
+ process.stdout.clearLine(0);
1478
+ process.stdout.cursorTo(0);
1479
+ process.stdout.write(q + "*".repeat(input.length));
1480
+ } else {
1481
+ input += char;
1482
+ process.stdout.write("*");
1483
+ }
1484
+ };
1485
+ process.stdin.on("data", onData);
1486
+ } else {
1487
+ rl.question(q, (ans) => {
1488
+ rl.close();
1489
+ resolve2(ans.trim());
1490
+ });
1491
+ }
1492
+ });
1493
+ }
1494
+ function registerVaultCommand(program2) {
1495
+ const vault = program2.command("vault").description("Manage secrets securely");
1496
+ vault.command("list").option("--json", "Output as JSON").description("List all stored secrets (values hidden)").action(async (opts) => {
1497
+ const client = createClient();
1498
+ const result = await safeCall(() => client.query("vault:list", {}), "Failed to list secrets");
1499
+ if (opts.json) {
1500
+ const safe = (result || []).map((s) => ({ ...s, encryptedValue: void 0 }));
1501
+ console.log(JSON.stringify(safe, null, 2));
1502
+ return;
1503
+ }
1504
+ header("Vault \u2014 Stored Secrets");
1505
+ const items = result || [];
1506
+ if (items.length === 0) {
1507
+ info("No secrets stored. Add one with: agentforge vault set <name> <value>");
1508
+ return;
1509
+ }
1510
+ table(items.map((s) => ({
1511
+ Name: s.name,
1512
+ Category: s.category || "general",
1513
+ Provider: s.provider || "N/A",
1514
+ "Last Rotated": s.lastRotatedAt ? formatDate(s.lastRotatedAt) : "Never",
1515
+ Created: formatDate(s.createdAt)
1516
+ })));
1517
+ });
1518
+ vault.command("set").argument("<name>", "Secret name (e.g., OPENAI_API_KEY)").argument("[value]", "Secret value (omit for secure prompt)").option("--category <cat>", "Category (api_key, token, secret, credential)", "api_key").option("--provider <provider>", "Provider name (openai, anthropic, etc.)").description("Store a secret securely").action(async (name, value, opts) => {
1519
+ if (!value) {
1520
+ value = await promptSecret(`Enter value for ${name}: `);
1521
+ }
1522
+ if (!value) {
1523
+ error("Value is required.");
1524
+ process.exit(1);
1525
+ }
1526
+ const client = createClient();
1527
+ await safeCall(
1528
+ () => client.mutation("vault:store", {
1529
+ name,
1530
+ encryptedValue: value,
1531
+ category: opts.category,
1532
+ provider: opts.provider
1533
+ }),
1534
+ "Failed to store secret"
1535
+ );
1536
+ success(`Secret "${name}" stored securely.`);
1537
+ });
1538
+ vault.command("get").argument("<name>", "Secret name").option("--reveal", "Show the actual value (use with caution)").description("Retrieve a secret").action(async (name, opts) => {
1539
+ const client = createClient();
1540
+ const result = await safeCall(() => client.query("vault:list", {}), "Failed");
1541
+ const secret = (result || []).find((s) => s.name === name);
1542
+ if (!secret) {
1543
+ error(`Secret "${name}" not found.`);
1544
+ process.exit(1);
1545
+ }
1546
+ if (opts.reveal) {
1547
+ const value = await safeCall(
1548
+ () => client.query("vault:getDecrypted", { _id: secret._id }),
1549
+ "Failed to retrieve secret"
1550
+ );
1551
+ console.log(value);
1552
+ } else {
1553
+ const masked = secret.encryptedValue ? secret.encryptedValue.slice(0, 4) + "****" + secret.encryptedValue.slice(-4) : "****";
1554
+ info(`${name} = ${masked}`);
1555
+ dim(" Use --reveal to show the full value.");
1556
+ }
1557
+ });
1558
+ vault.command("delete").argument("<name>", "Secret name").option("-f, --force", "Skip confirmation").description("Delete a secret").action(async (name, opts) => {
1559
+ if (!opts.force) {
1560
+ const confirm = await prompt7(`Delete secret "${name}"? This cannot be undone. (y/N): `);
1561
+ if (confirm.toLowerCase() !== "y") {
1562
+ info("Cancelled.");
1563
+ return;
1564
+ }
1565
+ }
1566
+ const client = createClient();
1567
+ const result = await safeCall(() => client.query("vault:list", {}), "Failed");
1568
+ const secret = (result || []).find((s) => s.name === name);
1569
+ if (!secret) {
1570
+ error(`Secret "${name}" not found.`);
1571
+ process.exit(1);
1572
+ }
1573
+ await safeCall(() => client.mutation("vault:remove", { _id: secret._id }), "Failed");
1574
+ success(`Secret "${name}" deleted.`);
1575
+ });
1576
+ vault.command("rotate").argument("<name>", "Secret name").description("Rotate a secret (set a new value)").action(async (name) => {
1577
+ const client = createClient();
1578
+ const result = await safeCall(() => client.query("vault:list", {}), "Failed");
1579
+ const secret = (result || []).find((s) => s.name === name);
1580
+ if (!secret) {
1581
+ error(`Secret "${name}" not found.`);
1582
+ process.exit(1);
1583
+ }
1584
+ const newValue = await promptSecret(`Enter new value for ${name}: `);
1585
+ if (!newValue) {
1586
+ error("Value is required.");
1587
+ process.exit(1);
1588
+ }
1589
+ await safeCall(
1590
+ () => client.mutation("vault:rotate", { _id: secret._id, newValue }),
1591
+ "Failed to rotate secret"
1592
+ );
1593
+ success(`Secret "${name}" rotated.`);
1594
+ });
1595
+ }
1596
+
1597
+ // src/commands/status.ts
1598
+ import { spawn as spawn2 } from "child_process";
1599
+ import path8 from "path";
1600
+ import fs8 from "fs-extra";
1601
+ import readline9 from "readline";
1602
+ function registerStatusCommand(program2) {
1603
+ program2.command("status").description("Show system health and connection status").action(async () => {
1604
+ header("AgentForge Status");
1605
+ const cwd = process.cwd();
1606
+ const checks = {};
1607
+ checks["Project Root"] = fs8.existsSync(path8.join(cwd, "package.json")) ? "\u2714 Found" : "\u2716 Not found";
1608
+ checks["Convex Dir"] = fs8.existsSync(path8.join(cwd, "convex")) ? "\u2714 Found" : "\u2716 Not found";
1609
+ checks["Skills Dir"] = fs8.existsSync(path8.join(cwd, "skills")) ? "\u2714 Found" : "\u2716 Not configured";
1610
+ checks["Dashboard Dir"] = fs8.existsSync(path8.join(cwd, "dashboard")) ? "\u2714 Found" : "\u2716 Not found";
1611
+ checks["Env Config"] = fs8.existsSync(path8.join(cwd, ".env.local")) || fs8.existsSync(path8.join(cwd, ".env")) ? "\u2714 Found" : "\u2716 Not found";
1612
+ try {
1613
+ const client = createClient();
1614
+ const agents = await client.query("agents:list", {});
1615
+ checks["Convex Connection"] = `\u2714 Connected (${agents?.length || 0} agents)`;
1616
+ } catch {
1617
+ checks["Convex Connection"] = "\u2716 Not connected (run `npx convex dev`)";
1618
+ }
1619
+ const envFiles = [".env.local", ".env"];
1620
+ let provider = "Not configured";
1621
+ for (const envFile of envFiles) {
1622
+ const envPath = path8.join(cwd, envFile);
1623
+ if (fs8.existsSync(envPath)) {
1624
+ const content = fs8.readFileSync(envPath, "utf-8");
1625
+ const match = content.match(/LLM_PROVIDER=(.+)/);
1626
+ if (match) {
1627
+ provider = match[1].trim();
1628
+ break;
1629
+ }
1630
+ if (content.includes("OPENAI_API_KEY=")) {
1631
+ provider = "openai";
1632
+ break;
1633
+ }
1634
+ if (content.includes("OPENROUTER_API_KEY=")) {
1635
+ provider = "openrouter";
1636
+ break;
1637
+ }
1638
+ }
1639
+ }
1640
+ checks["LLM Provider"] = provider !== "Not configured" ? `\u2714 ${provider}` : "\u2716 Not configured";
1641
+ details(checks);
1642
+ });
1643
+ program2.command("dashboard").description("Launch the web dashboard").option("-p, --port <port>", "Port for the dashboard", "3000").option("--install", "Install dashboard dependencies before starting").action(async (opts) => {
1644
+ const cwd = process.cwd();
1645
+ const searchPaths = [
1646
+ path8.join(cwd, "dashboard"),
1647
+ // 1. Bundled in project (agentforge create)
1648
+ path8.join(cwd, "packages", "web"),
1649
+ // 2. Monorepo structure
1650
+ path8.join(cwd, "node_modules", "@agentforge-ai", "web")
1651
+ // 3. Installed as dependency
1652
+ ];
1653
+ let dashDir = "";
1654
+ for (const p of searchPaths) {
1655
+ if (fs8.existsSync(path8.join(p, "package.json"))) {
1656
+ dashDir = p;
1657
+ break;
1658
+ }
1659
+ }
1660
+ if (!dashDir) {
1661
+ error("Dashboard not found!");
1662
+ console.log();
1663
+ info("The dashboard should be in your project's ./dashboard/ directory.");
1664
+ info("If you created this project with an older version of AgentForge,");
1665
+ info("you can add it manually:");
1666
+ console.log();
1667
+ console.log(` ${colors.cyan}# Option 1: Recreate the project${colors.reset}`);
1668
+ console.log(` agentforge create my-project`);
1669
+ console.log();
1670
+ console.log(` ${colors.cyan}# Option 2: Clone the dashboard from the repo${colors.reset}`);
1671
+ console.log(` git clone https://github.com/Agentic-Engineering-Agency/agentforge /tmp/af`);
1672
+ console.log(` cp -r /tmp/af/packages/web ./dashboard`);
1673
+ console.log(` cd dashboard && pnpm install`);
1674
+ console.log();
1675
+ return;
1676
+ }
1677
+ const nodeModulesExists = fs8.existsSync(path8.join(dashDir, "node_modules"));
1678
+ if (!nodeModulesExists || opts.install) {
1679
+ header("AgentForge Dashboard \u2014 Installing Dependencies");
1680
+ info(`Installing in ${path8.relative(cwd, dashDir) || "."}...`);
1681
+ console.log();
1682
+ const installChild = spawn2("pnpm", ["install"], {
1683
+ cwd: dashDir,
1684
+ stdio: "inherit",
1685
+ shell: true
1686
+ });
1687
+ await new Promise((resolve2, reject) => {
1688
+ installChild.on("close", (code) => {
1689
+ if (code === 0) resolve2();
1690
+ else reject(new Error(`pnpm install exited with code ${code}`));
1691
+ });
1692
+ installChild.on("error", reject);
1693
+ });
1694
+ console.log();
1695
+ success("Dependencies installed.");
1696
+ console.log();
1697
+ }
1698
+ const envPath = path8.join(cwd, ".env.local");
1699
+ if (fs8.existsSync(envPath)) {
1700
+ const envContent = fs8.readFileSync(envPath, "utf-8");
1701
+ const convexUrlMatch = envContent.match(/CONVEX_URL=(.+)/);
1702
+ if (convexUrlMatch) {
1703
+ const dashEnvPath = path8.join(dashDir, ".env.local");
1704
+ const dashEnvContent = `VITE_CONVEX_URL=${convexUrlMatch[1].trim()}
1705
+ `;
1706
+ fs8.writeFileSync(dashEnvPath, dashEnvContent);
1707
+ }
1708
+ }
1709
+ header("AgentForge Dashboard");
1710
+ info(`Starting dashboard on port ${opts.port}...`);
1711
+ info(`Open ${colors.cyan}http://localhost:${opts.port}${colors.reset} in your browser.`);
1712
+ console.log();
1713
+ const child = spawn2("pnpm", ["dev", "--port", opts.port], {
1714
+ cwd: dashDir,
1715
+ stdio: "inherit",
1716
+ shell: true
1717
+ });
1718
+ child.on("error", (err) => {
1719
+ error(`Failed to start dashboard: ${err.message}`);
1720
+ });
1721
+ });
1722
+ program2.command("logs").description("Show recent activity logs").option("-n, --lines <count>", "Number of log entries", "20").option("--agent <id>", "Filter by agent ID").option("--json", "Output as JSON").action(async (opts) => {
1723
+ const client = createClient();
1724
+ const args = {};
1725
+ if (opts.agent) args.agentId = opts.agent;
1726
+ const result = await safeCall(
1727
+ () => client.query("usage:list", args),
1728
+ "Failed to fetch logs"
1729
+ );
1730
+ if (opts.json) {
1731
+ console.log(JSON.stringify(result, null, 2));
1732
+ return;
1733
+ }
1734
+ header("Activity Logs");
1735
+ const items = (result || []).slice(0, parseInt(opts.lines));
1736
+ if (items.length === 0) {
1737
+ info("No activity logs found.");
1738
+ return;
1739
+ }
1740
+ items.forEach((log) => {
1741
+ const time = new Date(log.timestamp || log.createdAt).toLocaleString();
1742
+ const agent = log.agentId || "system";
1743
+ const action = log.action || log.type || "unknown";
1744
+ const tokens = log.tokensUsed ? `${log.tokensUsed} tokens` : "";
1745
+ console.log(` ${colors.dim}${time}${colors.reset} ${colors.cyan}${agent}${colors.reset} ${action} ${tokens}`);
1746
+ });
1747
+ console.log();
1748
+ });
1749
+ program2.command("heartbeat").description("Check and resume pending agent tasks").option("--agent <id>", "Check specific agent").action(async (opts) => {
1750
+ const client = createClient();
1751
+ header("Heartbeat Check");
1752
+ const args = {};
1753
+ if (opts.agent) args.agentId = opts.agent;
1754
+ const result = await safeCall(
1755
+ () => client.query("heartbeat:listPending", args),
1756
+ "Failed to check heartbeat"
1757
+ );
1758
+ const items = result || [];
1759
+ if (items.length === 0) {
1760
+ success("All tasks complete. No pending work.");
1761
+ return;
1762
+ }
1763
+ info(`Found ${items.length} pending task(s):`);
1764
+ items.forEach((task, i) => {
1765
+ console.log(` ${colors.yellow}${i + 1}.${colors.reset} [${task.agentId}] ${task.taskDescription || "Unnamed task"}`);
1766
+ console.log(` ${colors.dim}Status: ${task.status} | Thread: ${task.threadId || "N/A"}${colors.reset}`);
1767
+ });
1768
+ console.log();
1769
+ const rl = readline9.createInterface({ input: process.stdin, output: process.stdout });
1770
+ const answer = await new Promise((r) => rl.question("Resume pending tasks? (y/N): ", (a) => {
1771
+ rl.close();
1772
+ r(a.trim());
1773
+ }));
1774
+ if (answer.toLowerCase() === "y") {
1775
+ for (const task of items) {
1776
+ info(`Resuming task for agent "${task.agentId}"...`);
1777
+ await safeCall(
1778
+ () => client.mutation("heartbeat:resume", { _id: task._id }),
1779
+ "Failed to resume task"
1780
+ );
1781
+ }
1782
+ success("All pending tasks resumed.");
1783
+ }
1784
+ });
1785
+ }
1786
+
235
1787
  // src/index.ts
236
1788
  import { readFileSync } from "fs";
237
1789
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -240,7 +1792,7 @@ var __filename2 = fileURLToPath2(import.meta.url);
240
1792
  var __dirname2 = dirname(__filename2);
241
1793
  var pkg = JSON.parse(readFileSync(resolve(__dirname2, "..", "package.json"), "utf-8"));
242
1794
  var program = new Command();
243
- program.name("agentforge").description("CLI tool for creating, running, and managing AgentForge projects").version(pkg.version);
1795
+ program.name("agentforge").description("AgentForge \u2014 NanoClaw: A minimalist agent framework powered by Mastra + Convex").version(pkg.version);
244
1796
  program.command("create").argument("<project-name>", "Name of the project to create").description("Create a new AgentForge project").option("-t, --template <template>", "Project template to use", "default").action(async (projectName, options) => {
245
1797
  await createProject(projectName, options);
246
1798
  });
@@ -250,5 +1802,17 @@ program.command("run").description("Start the local development environment").op
250
1802
  program.command("deploy").description("Deploy the Convex backend to production").option("--env <path>", "Path to environment file", ".env.production").option("--dry-run", "Preview deployment without executing", false).option("--rollback", "Rollback to previous deployment", false).option("--force", "Skip confirmation prompts", false).action(async (options) => {
251
1803
  await deployProject(options);
252
1804
  });
1805
+ registerAgentsCommand(program);
1806
+ registerChatCommand(program);
1807
+ registerSessionsCommand(program);
1808
+ registerThreadsCommand(program);
1809
+ registerSkillsCommand(program);
1810
+ registerCronCommand(program);
1811
+ registerMcpCommand(program);
1812
+ registerFilesCommand(program);
1813
+ registerProjectsCommand(program);
1814
+ registerConfigCommand(program);
1815
+ registerVaultCommand(program);
1816
+ registerStatusCommand(program);
253
1817
  program.parse();
254
1818
  //# sourceMappingURL=index.js.map