@agentskit/cli 0.6.0 → 0.7.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.
package/dist/bin.cjs CHANGED
@@ -1,44 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var React3 = require('react');
5
- var ink = require('ink');
6
4
  var commander = require('commander');
7
- var fs = require('fs');
8
- var os = require('os');
9
- var path3 = require('path');
5
+ var React2 = require('react');
6
+ var ink = require('ink');
10
7
  var promises = require('fs/promises');
8
+ var os = require('os');
9
+ var path = require('path');
11
10
  var ink$1 = require('@agentskit/ink');
12
11
  var adapters = require('@agentskit/adapters');
12
+ var crypto = require('crypto');
13
+ var fs = require('fs');
13
14
  var tools = require('@agentskit/tools');
14
15
  var skills = require('@agentskit/skills');
15
16
  var memory = require('@agentskit/memory');
16
- var crypto = require('crypto');
17
+ var child_process = require('child_process');
17
18
  var jsxRuntime = require('react/jsx-runtime');
19
+ var url = require('url');
20
+ var runtime = require('@agentskit/runtime');
18
21
  var prompts = require('@inquirer/prompts');
19
22
  var kleur = require('kleur');
20
- var runtime = require('@agentskit/runtime');
21
- var child_process = require('child_process');
22
23
  var chokidar = require('chokidar');
24
+ var rag = require('@agentskit/rag');
23
25
 
24
26
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
25
27
 
26
- var React3__default = /*#__PURE__*/_interopDefault(React3);
27
- var path3__default = /*#__PURE__*/_interopDefault(path3);
28
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
29
+ var path__default = /*#__PURE__*/_interopDefault(path);
28
30
  var kleur__default = /*#__PURE__*/_interopDefault(kleur);
29
31
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
30
32
 
31
- async function loadJsonConfig(path4) {
33
+ async function loadJsonConfig(path5) {
32
34
  try {
33
- const raw = await promises.readFile(path4, "utf8");
35
+ const raw = await promises.readFile(path5, "utf8");
34
36
  return JSON.parse(raw);
35
37
  } catch {
36
38
  return void 0;
37
39
  }
38
40
  }
39
- async function loadTsConfig(path4) {
41
+ async function loadTsConfig(path5) {
40
42
  try {
41
- const mod = await import(path4);
43
+ const mod = await import(path5);
42
44
  return mod.default ?? mod;
43
45
  } catch {
44
46
  return void 0;
@@ -46,7 +48,7 @@ async function loadTsConfig(path4) {
46
48
  }
47
49
  async function loadPackageJsonConfig(dir) {
48
50
  try {
49
- const raw = await promises.readFile(path3.join(dir, "package.json"), "utf8");
51
+ const raw = await promises.readFile(path.join(dir, "package.json"), "utf8");
50
52
  const pkg = JSON.parse(raw);
51
53
  if (pkg.agentskit && typeof pkg.agentskit === "object") {
52
54
  return pkg.agentskit;
@@ -70,20 +72,20 @@ function mergeConfigs(base, override) {
70
72
  };
71
73
  }
72
74
  async function loadLocalConfig(cwd) {
73
- const tsConfig = await loadTsConfig(path3.join(cwd, ".agentskit.config.ts"));
75
+ const tsConfig = await loadTsConfig(path.join(cwd, ".agentskit.config.ts"));
74
76
  if (tsConfig) return tsConfig;
75
- const jsonConfig = await loadJsonConfig(path3.join(cwd, ".agentskit.config.json"));
77
+ const jsonConfig = await loadJsonConfig(path.join(cwd, ".agentskit.config.json"));
76
78
  if (jsonConfig) return jsonConfig;
77
79
  return await loadPackageJsonConfig(cwd);
78
80
  }
79
81
  async function loadGlobalConfig(home) {
80
- const globalDir = path3.join(os.homedir(), ".agentskit");
81
- const tsConfig = await loadTsConfig(path3.join(globalDir, "config.ts"));
82
+ const globalDir = path.join(os.homedir(), ".agentskit");
83
+ const tsConfig = await loadTsConfig(path.join(globalDir, "config.ts"));
82
84
  if (tsConfig) return tsConfig;
83
- return await loadJsonConfig(path3.join(globalDir, "config.json"));
85
+ return await loadJsonConfig(path.join(globalDir, "config.json"));
84
86
  }
85
87
  async function loadConfig(options) {
86
- const cwd = path3.resolve(process.cwd());
88
+ const cwd = path.resolve(process.cwd());
87
89
  const global = await loadGlobalConfig();
88
90
  const local = await loadLocalConfig(cwd);
89
91
  return mergeConfigs(global, local);
@@ -147,7 +149,7 @@ function createDemoAdapter(provider, model) {
147
149
  ].join(" ");
148
150
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
149
151
  if (cancelled) return;
150
- await new Promise((resolve2) => setTimeout(resolve2, 35));
152
+ await new Promise((resolve4) => setTimeout(resolve4, 35));
151
153
  yield { type: "text", content: chunk };
152
154
  }
153
155
  yield { type: "done" };
@@ -198,87 +200,13 @@ function resolveChatProvider(options) {
198
200
  summary: `${entry.label} live adapter`
199
201
  };
200
202
  }
201
- var skillRegistry = {
202
- researcher: skills.researcher,
203
- coder: skills.coder,
204
- planner: skills.planner,
205
- critic: skills.critic,
206
- summarizer: skills.summarizer
207
- };
208
- function instantiate(kind) {
209
- switch (kind) {
210
- case "web_search":
211
- return [tools.webSearch()];
212
- case "fetch_url":
213
- return [tools.fetchUrl()];
214
- case "filesystem":
215
- return tools.filesystem({ basePath: process.cwd() });
216
- case "shell":
217
- return [tools.shell({ timeout: 3e4 })];
218
- }
219
- }
220
- function gateTool(tool) {
221
- if (tool.requiresConfirmation === false) return tool;
222
- return { ...tool, requiresConfirmation: true };
223
- }
224
- function resolveTools(toolNames) {
225
- if (!toolNames) {
226
- return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
227
- }
228
- const tools = [];
229
- for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
230
- switch (name) {
231
- case "web_search":
232
- case "fetch_url":
233
- case "filesystem":
234
- case "shell":
235
- tools.push(...instantiate(name));
236
- break;
237
- default:
238
- process.stderr.write(`Unknown tool: ${name}
239
- `);
240
- }
241
- }
242
- return tools;
243
- }
244
- function resolveSkill(skillName) {
245
- if (!skillName) return void 0;
246
- const skill = skillRegistry[skillName.trim()];
247
- if (!skill) {
248
- process.stderr.write(`Unknown skill: ${skillName}
249
- `);
250
- return void 0;
251
- }
252
- return skill;
253
- }
254
- function resolveSkills(skillNames) {
255
- if (!skillNames) return void 0;
256
- const names = skillNames.split(",").map((s) => s.trim());
257
- const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
258
- if (resolved.length === 0) {
259
- process.stderr.write(`No valid skills found in: ${skillNames}
260
- `);
261
- return void 0;
262
- }
263
- if (resolved.length === 1) return resolved[0];
264
- return skills.composeSkills(...resolved);
265
- }
266
- function resolveMemory(backend, memoryPath) {
267
- switch (backend) {
268
- case "sqlite":
269
- return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
270
- case "file":
271
- default:
272
- return memory.fileChatMemory(memoryPath);
273
- }
274
- }
275
- var ROOT = path3.join(os.homedir(), ".agentskit", "sessions");
203
+ var ROOT = path.join(os.homedir(), ".agentskit", "sessions");
276
204
  var META_SUFFIX = ".meta.json";
277
205
  function cwdHash(cwd = process.cwd()) {
278
206
  return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
279
207
  }
280
208
  function dirFor(cwd = process.cwd()) {
281
- return path3.join(ROOT, cwdHash(cwd));
209
+ return path.join(ROOT, cwdHash(cwd));
282
210
  }
283
211
  function ensureDir(dir) {
284
212
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -290,16 +218,16 @@ function generateSessionId() {
290
218
  }
291
219
  function sessionFilePath(id, cwd = process.cwd()) {
292
220
  ensureDir(dirFor(cwd));
293
- return path3.join(dirFor(cwd), `${id}.json`);
221
+ return path.join(dirFor(cwd), `${id}.json`);
294
222
  }
295
223
  function metaPath(id, cwd = process.cwd()) {
296
- return path3.join(dirFor(cwd), `${id}${META_SUFFIX}`);
224
+ return path.join(dirFor(cwd), `${id}${META_SUFFIX}`);
297
225
  }
298
226
  function readMeta(id, cwd = process.cwd()) {
299
- const path4 = metaPath(id, cwd);
300
- if (!fs.existsSync(path4)) return null;
227
+ const path5 = metaPath(id, cwd);
228
+ if (!fs.existsSync(path5)) return null;
301
229
  try {
302
- return JSON.parse(fs.readFileSync(path4, "utf8"));
230
+ return JSON.parse(fs.readFileSync(path5, "utf8"));
303
231
  } catch {
304
232
  return null;
305
233
  }
@@ -323,7 +251,7 @@ function listSessions(cwd = process.cwd()) {
323
251
  if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
324
252
  const id = entry.replace(/\.json$/, "");
325
253
  const meta = readMeta(id, cwd);
326
- const file = path3.join(dir, entry);
254
+ const file = path.join(dir, entry);
327
255
  if (meta) {
328
256
  records.push({ metadata: meta, file });
329
257
  } else {
@@ -349,11 +277,41 @@ function findLatestSession(cwd = process.cwd()) {
349
277
  return all[0] ?? null;
350
278
  }
351
279
  function findSession(id, cwd = process.cwd()) {
352
- const exact = listSessions(cwd).find((s) => s.metadata.id === id);
280
+ const all = listSessions(cwd);
281
+ const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
353
282
  if (exact) return exact;
354
- const prefix = listSessions(cwd).find((s) => s.metadata.id.startsWith(id));
283
+ const prefix = all.find((s) => s.metadata.id.startsWith(id));
355
284
  return prefix ?? null;
356
285
  }
286
+ function renameSession(id, label, cwd = process.cwd()) {
287
+ const record = findSession(id, cwd);
288
+ if (!record) throw new Error(`No session matching "${id}".`);
289
+ const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
290
+ writeSessionMeta(next, cwd);
291
+ return next;
292
+ }
293
+ function forkSession(id, cwd = process.cwd()) {
294
+ const record = findSession(id, cwd);
295
+ if (!record) throw new Error(`No session matching "${id}".`);
296
+ const newId = generateSessionId();
297
+ const newFile = sessionFilePath(newId, cwd);
298
+ if (fs.existsSync(record.file)) {
299
+ fs.writeFileSync(newFile, fs.readFileSync(record.file, "utf8"));
300
+ }
301
+ const now = (/* @__PURE__ */ new Date()).toISOString();
302
+ writeSessionMeta(
303
+ {
304
+ ...record.metadata,
305
+ id: newId,
306
+ createdAt: now,
307
+ updatedAt: now,
308
+ forkedFrom: record.metadata.id,
309
+ label: void 0
310
+ },
311
+ cwd
312
+ );
313
+ return { id: newId, file: newFile, isNew: true };
314
+ }
357
315
  function resolveSession(input2) {
358
316
  const cwd = input2.cwd ?? process.cwd();
359
317
  if (input2.explicitPath) {
@@ -379,6 +337,40 @@ function resolveSession(input2) {
379
337
  return { id, file: sessionFilePath(id, cwd), isNew: true };
380
338
  }
381
339
 
340
+ // src/extensibility/telemetry/pricing.ts
341
+ var builtinPricing = {
342
+ "gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
343
+ "gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
344
+ "gpt-4.1": { inputPerM: 2, outputPerM: 8 },
345
+ "gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
346
+ "claude-opus-4": { inputPerM: 15, outputPerM: 75 },
347
+ "claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
348
+ "claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
349
+ "gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
350
+ "gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
351
+ };
352
+ var customPricing = {};
353
+ function getPricing(model) {
354
+ if (!model) return void 0;
355
+ if (customPricing[model]) return customPricing[model];
356
+ if (builtinPricing[model]) return builtinPricing[model];
357
+ const short = model.includes("/") ? model.split("/").pop() : model;
358
+ return customPricing[short] ?? builtinPricing[short];
359
+ }
360
+ function computeCost(model, usage) {
361
+ if (!model) return void 0;
362
+ const pricing = getPricing(model);
363
+ if (!pricing) return void 0;
364
+ const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
365
+ const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
366
+ return {
367
+ model,
368
+ inputUsd,
369
+ outputUsd,
370
+ totalUsd: inputUsd + outputUsd
371
+ };
372
+ }
373
+
382
374
  // src/slash-commands.ts
383
375
  function parseSlashCommand(input2) {
384
376
  if (!input2.startsWith("/")) return null;
@@ -501,6 +493,89 @@ ${lines.join("\n")}`, "info");
501
493
  ctx.feedback("History cleared.", "success");
502
494
  }
503
495
  },
496
+ {
497
+ name: "usage",
498
+ description: "Show the cumulative token usage for this session.",
499
+ run(ctx) {
500
+ const usage = ctx.chat.usage;
501
+ if (!usage || usage.totalTokens === 0) {
502
+ ctx.feedback("No usage reported yet for this session.", "info");
503
+ return;
504
+ }
505
+ ctx.feedback(
506
+ `Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
507
+ "info"
508
+ );
509
+ }
510
+ },
511
+ {
512
+ name: "cost",
513
+ description: "Estimate the cost so far for the current model.",
514
+ run(ctx) {
515
+ const usage = ctx.chat.usage;
516
+ const model = ctx.runtime.model;
517
+ if (!usage || usage.totalTokens === 0) {
518
+ ctx.feedback("No usage reported yet for this session.", "info");
519
+ return;
520
+ }
521
+ const cost = computeCost(model, usage);
522
+ if (!cost) {
523
+ ctx.feedback(
524
+ `No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
525
+ "warn"
526
+ );
527
+ return;
528
+ }
529
+ ctx.feedback(
530
+ `$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
531
+ "info"
532
+ );
533
+ }
534
+ },
535
+ {
536
+ name: "rename",
537
+ description: "Attach a human-readable label to the current session.",
538
+ usage: "/rename <label>",
539
+ run(ctx, args) {
540
+ const label = args.trim();
541
+ const sessionId = ctx.runtime.sessionId;
542
+ if (!sessionId || sessionId === "custom") {
543
+ ctx.feedback("Rename is only available for managed sessions.", "warn");
544
+ return;
545
+ }
546
+ if (!label) {
547
+ ctx.feedback("Usage: /rename <label>", "warn");
548
+ return;
549
+ }
550
+ try {
551
+ renameSession(sessionId, label);
552
+ ctx.feedback(`Session labeled "${label}".`, "success");
553
+ } catch (err) {
554
+ ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
555
+ }
556
+ }
557
+ },
558
+ {
559
+ name: "fork",
560
+ description: "Branch a copy of the current session. Does not switch to it.",
561
+ run(ctx) {
562
+ const sessionId = ctx.runtime.sessionId;
563
+ if (!sessionId || sessionId === "custom") {
564
+ ctx.feedback("Fork is only available for managed sessions.", "warn");
565
+ return;
566
+ }
567
+ try {
568
+ const result = forkSession(sessionId);
569
+ ctx.feedback(
570
+ `Forked into ${result.id}. Resume with:
571
+ agentskit chat --resume ${result.id}`,
572
+ "success"
573
+ );
574
+ } catch (err) {
575
+ ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
576
+ }
577
+ }
578
+ },
504
579
  {
505
580
  name: "exit",
506
581
  aliases: ["quit", "q"],
@@ -510,57 +585,160 @@ ${lines.join("\n")}`, "info");
510
585
  }
511
586
  }
512
587
  ];
513
- function groupIntoTurns(messages) {
514
- const turns = [];
515
- let current = [];
516
- for (const message of messages) {
517
- if (message.role === "user") {
518
- if (current.length > 0) turns.push(current);
519
- current = [message];
520
- } else if (message.role === "system") {
521
- if (current.length > 0) turns.push(current);
522
- turns.push([message]);
523
- current = [];
524
- } else {
525
- current.push(message);
588
+ var skillRegistry = {
589
+ researcher: skills.researcher,
590
+ coder: skills.coder,
591
+ planner: skills.planner,
592
+ critic: skills.critic,
593
+ summarizer: skills.summarizer
594
+ };
595
+ function instantiate(kind) {
596
+ switch (kind) {
597
+ case "web_search":
598
+ return [tools.webSearch()];
599
+ case "fetch_url":
600
+ return [tools.fetchUrl()];
601
+ case "filesystem":
602
+ return tools.filesystem({ basePath: process.cwd() });
603
+ case "shell":
604
+ return [tools.shell({ timeout: 3e4 })];
605
+ }
606
+ }
607
+ function gateTool(tool) {
608
+ if (tool.requiresConfirmation === false) return tool;
609
+ return { ...tool, requiresConfirmation: true };
610
+ }
611
+ function resolveTools(toolNames) {
612
+ if (!toolNames) {
613
+ return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
614
+ }
615
+ const tools = [];
616
+ for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
617
+ switch (name) {
618
+ case "web_search":
619
+ case "fetch_url":
620
+ case "filesystem":
621
+ case "shell":
622
+ tools.push(...instantiate(name));
623
+ break;
624
+ default:
625
+ process.stderr.write(`Unknown tool: ${name}
626
+ `);
526
627
  }
527
628
  }
528
- if (current.length > 0) turns.push(current);
529
- return turns;
629
+ return tools;
530
630
  }
531
- function ChatApp(options) {
532
- const [provider, setProvider] = React3.useState(options.provider);
533
- const [model, setModel] = React3.useState(options.model);
534
- const [apiKey, setApiKey] = React3.useState(options.apiKey);
535
- const [baseUrl, setBaseUrl] = React3.useState(options.baseUrl);
536
- const [toolsFlag, setToolsFlag] = React3.useState(options.tools);
537
- const [skillFlag, setSkillFlag] = React3.useState(options.skill);
538
- const runtime = React3.useMemo(
631
+ function resolveSkill(skillName) {
632
+ if (!skillName) return void 0;
633
+ const skill = skillRegistry[skillName.trim()];
634
+ if (!skill) {
635
+ process.stderr.write(`Unknown skill: ${skillName}
636
+ `);
637
+ return void 0;
638
+ }
639
+ return skill;
640
+ }
641
+ function resolveSkills(skillNames) {
642
+ if (!skillNames) return void 0;
643
+ const names = skillNames.split(",").map((s) => s.trim());
644
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
645
+ if (resolved.length === 0) {
646
+ process.stderr.write(`No valid skills found in: ${skillNames}
647
+ `);
648
+ return void 0;
649
+ }
650
+ if (resolved.length === 1) return resolved[0];
651
+ return skills.composeSkills(...resolved);
652
+ }
653
+ function resolveMemory(backend, memoryPath) {
654
+ switch (backend) {
655
+ case "sqlite":
656
+ return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
657
+ case "file":
658
+ default:
659
+ return memory.fileChatMemory(memoryPath);
660
+ }
661
+ }
662
+
663
+ // src/extensibility/permissions/policy.ts
664
+ function evaluatePolicy(policy, toolName) {
665
+ if (policy.mode === "bypassPermissions") return "allow";
666
+ if (policy.mode === "plan") return "ask";
667
+ for (const rule of policy.rules) {
668
+ if (matchesRule(rule, toolName)) return rule.action;
669
+ }
670
+ if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
671
+ return "allow";
672
+ }
673
+ return "ask";
674
+ }
675
+ function matchesRule(rule, toolName) {
676
+ if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
677
+ const str = rule.tool;
678
+ if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
679
+ return str === toolName;
680
+ }
681
+ function applyPolicyToTool(policy, tool) {
682
+ const action = evaluatePolicy(policy, tool.name);
683
+ if (action === "deny") return null;
684
+ if (action === "allow") return { ...tool, requiresConfirmation: false };
685
+ return { ...tool, requiresConfirmation: true };
686
+ }
687
+ function applyPolicyToTools(policy, tools) {
688
+ const out = [];
689
+ for (const tool of tools) {
690
+ const gated = applyPolicyToTool(policy, tool);
691
+ if (gated) out.push(gated);
692
+ }
693
+ return out;
694
+ }
695
+
696
+ // src/runtime/use-runtime.ts
697
+ function useRuntime(options) {
698
+ const [provider, setProvider] = React2.useState(options.provider);
699
+ const [model, setModel] = React2.useState(options.model);
700
+ const [apiKey, setApiKey] = React2.useState(options.apiKey);
701
+ const [baseUrl, setBaseUrl] = React2.useState(options.baseUrl);
702
+ const [toolsFlag, setToolsFlag] = React2.useState(options.tools);
703
+ const [skillFlag, setSkillFlag] = React2.useState(options.skill);
704
+ const runtime = React2.useMemo(
539
705
  () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
540
706
  [provider, model, apiKey, baseUrl]
541
707
  );
542
- const memory = React3.useMemo(
708
+ const memory = React2.useMemo(
543
709
  () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
544
710
  [options.memoryPath, options.memoryBackend]
545
711
  );
546
- const tools = React3.useMemo(() => resolveTools(toolsFlag), [toolsFlag]);
547
- const skills = React3.useMemo(() => {
712
+ const tools = React2.useMemo(() => {
713
+ const resolved = resolveTools(toolsFlag);
714
+ if (!options.permissionPolicy) return resolved;
715
+ return applyPolicyToTools(options.permissionPolicy, resolved);
716
+ }, [toolsFlag, options.permissionPolicy]);
717
+ const skills = React2.useMemo(() => {
548
718
  if (!skillFlag) return void 0;
549
719
  const names = skillFlag.split(",").map((s) => s.trim());
550
720
  const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
551
721
  if (resolved.length === 0) return void 0;
552
722
  return resolved;
553
723
  }, [skillFlag]);
554
- const chat = ink$1.useChat({
555
- adapter: runtime.adapter,
724
+ return {
725
+ runtime,
556
726
  memory,
557
- systemPrompt: options.system,
558
- tools: tools.length > 0 ? tools : void 0,
559
- skills
560
- });
561
- const [sessionAllowed, setSessionAllowed] = React3.useState(/* @__PURE__ */ new Set());
562
- const autoApprovedRef = React3.useRef(/* @__PURE__ */ new Set());
563
- React3.useEffect(() => {
727
+ tools,
728
+ skills,
729
+ state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
730
+ setProvider,
731
+ setModel,
732
+ setApiKey,
733
+ setBaseUrl,
734
+ setToolsFlag,
735
+ setSkillFlag
736
+ };
737
+ }
738
+ function useToolPermissions(chat) {
739
+ const [sessionAllowed, setSessionAllowed] = React2.useState(/* @__PURE__ */ new Set());
740
+ const autoApprovedRef = React2.useRef(/* @__PURE__ */ new Set());
741
+ React2.useEffect(() => {
564
742
  if (sessionAllowed.size === 0) return;
565
743
  for (const message of chat.messages) {
566
744
  for (const call of message.toolCalls ?? []) {
@@ -581,10 +759,21 @@ function ChatApp(options) {
581
759
  autoApprovedRef.current.add(toolCallId);
582
760
  void chat.approve(toolCallId);
583
761
  };
584
- const sessionCreatedAtRef = React3.useRef(void 0);
585
- const messageCount = chat.messages.length;
586
- const firstUserContent = chat.messages.find((m) => m.role === "user")?.content ?? "";
587
- React3.useEffect(() => {
762
+ const awaitingConfirmation = React2.useMemo(
763
+ () => chat.messages.some(
764
+ (message) => message.toolCalls?.some(
765
+ (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
766
+ )
767
+ ),
768
+ [chat.messages, sessionAllowed]
769
+ );
770
+ return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
771
+ }
772
+ function useSessionMeta(options) {
773
+ const sessionCreatedAtRef = React2.useRef(void 0);
774
+ const messageCount = options.messages.length;
775
+ const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
776
+ React2.useEffect(() => {
588
777
  const sessionId = options.sessionId;
589
778
  if (!sessionId || sessionId === "custom") return;
590
779
  if (!sessionCreatedAtRef.current) {
@@ -597,24 +786,211 @@ function ChatApp(options) {
597
786
  createdAt: sessionCreatedAtRef.current,
598
787
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
599
788
  messageCount,
600
- preview: derivePreview(chat.messages),
601
- provider: runtime.provider,
602
- model: runtime.model
789
+ preview: derivePreview(options.messages),
790
+ provider: options.provider,
791
+ model: options.model
603
792
  });
604
793
  } catch {
605
794
  }
606
- }, [options.sessionId, messageCount, firstUserContent, runtime.provider, runtime.model]);
607
- const turns = React3.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
608
- const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
609
- const [feedback, setFeedback] = React3.useState(null);
610
- const slashCommands = React3.useMemo(
611
- () => [...builtinSlashCommands, ...options.slashCommands ?? []],
612
- [options.slashCommands]
613
- );
614
- const slashRegistry = React3.useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
615
- const handleSubmitInput = async (raw) => {
795
+ }, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
796
+ }
797
+
798
+ // src/extensibility/hooks/runner.ts
799
+ var HookDispatcher = class {
800
+ constructor(handlers = [], onError = (_h, err) => process.stderr.write(
801
+ `[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
802
+ `
803
+ )) {
804
+ this.onError = onError;
805
+ this.handlers = /* @__PURE__ */ new Map();
806
+ for (const handler of handlers) this.register(handler);
807
+ }
808
+ register(handler) {
809
+ const list = this.handlers.get(handler.event) ?? [];
810
+ list.push(handler);
811
+ this.handlers.set(handler.event, list);
812
+ }
813
+ async dispatch(event, payload) {
814
+ const list = this.handlers.get(event) ?? [];
815
+ let current = { ...payload, event };
816
+ for (const handler of list) {
817
+ if (!this.matches(handler, current)) continue;
818
+ let result;
819
+ try {
820
+ result = await handler.run(current);
821
+ } catch (err) {
822
+ this.onError(handler, err);
823
+ continue;
824
+ }
825
+ if (result.decision === "block") {
826
+ return { payload: current, blocked: true, reason: result.reason };
827
+ }
828
+ if (result.decision === "modify") {
829
+ current = { ...result.payload, event };
830
+ }
831
+ }
832
+ return { payload: current, blocked: false };
833
+ }
834
+ matches(handler, payload) {
835
+ if (!handler.matcher) return true;
836
+ if (typeof handler.matcher === "function") return handler.matcher(payload);
837
+ return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
838
+ }
839
+ };
840
+ function configHooksToHandlers(config) {
841
+ if (!config) return [];
842
+ const handlers = [];
843
+ for (const [event, entries] of Object.entries(config)) {
844
+ for (const entry of entries) {
845
+ handlers.push({
846
+ event,
847
+ matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
848
+ run: (payload) => runShellHook(entry, payload)
849
+ });
850
+ }
851
+ }
852
+ return handlers;
853
+ }
854
+ function runShellHook(entry, payload) {
855
+ return new Promise((resolvePromise) => {
856
+ const timeoutMs = entry.timeout ?? 5e3;
857
+ const child = child_process.spawn("sh", ["-c", entry.run], {
858
+ stdio: ["pipe", "pipe", "inherit"]
859
+ });
860
+ let stdout = "";
861
+ child.stdout.on("data", (chunk) => {
862
+ stdout += chunk.toString();
863
+ });
864
+ const timer = setTimeout(() => {
865
+ child.kill("SIGTERM");
866
+ }, timeoutMs);
867
+ child.on("close", (code) => {
868
+ clearTimeout(timer);
869
+ if (code !== 0) {
870
+ resolvePromise({
871
+ decision: "block",
872
+ reason: `shell hook exited with code ${code}`
873
+ });
874
+ return;
875
+ }
876
+ const trimmed = stdout.trim();
877
+ if (!trimmed) {
878
+ resolvePromise({ decision: "continue" });
879
+ return;
880
+ }
881
+ try {
882
+ const parsed = JSON.parse(trimmed);
883
+ resolvePromise(parsed);
884
+ } catch {
885
+ resolvePromise({ decision: "continue" });
886
+ }
887
+ });
888
+ child.on("error", (err) => {
889
+ clearTimeout(timer);
890
+ resolvePromise({ decision: "block", reason: err.message });
891
+ });
892
+ try {
893
+ child.stdin.write(JSON.stringify(payload));
894
+ child.stdin.end();
895
+ } catch {
896
+ }
897
+ });
898
+ }
899
+ function groupIntoTurns(messages) {
900
+ const turns = [];
901
+ let current = [];
902
+ for (const message of messages) {
903
+ if (message.role === "user") {
904
+ if (current.length > 0) turns.push(current);
905
+ current = [message];
906
+ } else if (message.role === "system") {
907
+ if (current.length > 0) turns.push(current);
908
+ turns.push([message]);
909
+ current = [];
910
+ } else {
911
+ current.push(message);
912
+ }
913
+ }
914
+ if (current.length > 0) turns.push(current);
915
+ return turns;
916
+ }
917
+ function ChatApp(options) {
918
+ const {
919
+ runtime,
920
+ memory,
921
+ tools,
922
+ skills,
923
+ state: { baseUrl, toolsFlag, skillFlag },
924
+ setProvider,
925
+ setModel,
926
+ setApiKey,
927
+ setBaseUrl,
928
+ setToolsFlag,
929
+ setSkillFlag
930
+ } = useRuntime(options);
931
+ const mergedTools = React2.useMemo(() => {
932
+ const extra = options.extraTools ?? [];
933
+ return [...tools, ...extra];
934
+ }, [tools, options.extraTools]);
935
+ const mergedSkills = React2.useMemo(() => {
936
+ const extra = options.extraSkills ?? [];
937
+ if (!skills && extra.length === 0) return void 0;
938
+ return [...skills ?? [], ...extra];
939
+ }, [skills, options.extraSkills]);
940
+ const chat = ink$1.useChat({
941
+ adapter: runtime.adapter,
942
+ memory,
943
+ systemPrompt: options.system,
944
+ tools: mergedTools.length > 0 ? mergedTools : void 0,
945
+ skills: mergedSkills
946
+ });
947
+ const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
948
+ useSessionMeta({
949
+ sessionId: options.sessionId,
950
+ messages: chat.messages,
951
+ provider: runtime.provider,
952
+ model: runtime.model
953
+ });
954
+ const turns = React2.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
955
+ const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
956
+ const [feedback, setFeedback] = React2.useState(null);
957
+ const hookDispatcher = React2.useMemo(
958
+ () => new HookDispatcher(options.hookHandlers ?? []),
959
+ [options.hookHandlers]
960
+ );
961
+ React2.useEffect(() => {
962
+ void hookDispatcher.dispatch("SessionStart", {
963
+ event: "SessionStart",
964
+ sessionId: options.sessionId,
965
+ provider: runtime.provider,
966
+ model: runtime.model
967
+ });
968
+ return () => {
969
+ void hookDispatcher.dispatch("SessionEnd", {
970
+ event: "SessionEnd",
971
+ sessionId: options.sessionId
972
+ });
973
+ };
974
+ }, [hookDispatcher]);
975
+ const slashCommands = React2.useMemo(
976
+ () => [...builtinSlashCommands, ...options.slashCommands ?? []],
977
+ [options.slashCommands]
978
+ );
979
+ const slashRegistry = React2.useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
980
+ const handleSubmitInput = async (raw) => {
616
981
  const parsed = parseSlashCommand(raw);
617
982
  if (!parsed) {
983
+ const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
984
+ event: "UserPromptSubmit",
985
+ prompt: raw
986
+ });
987
+ if (hookResult.blocked) {
988
+ setFeedback({
989
+ message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
990
+ kind: "warn"
991
+ });
992
+ return true;
993
+ }
618
994
  setFeedback(null);
619
995
  return false;
620
996
  }
@@ -634,7 +1010,8 @@ function ChatApp(options) {
634
1010
  mode: runtime.mode,
635
1011
  baseUrl,
636
1012
  tools: toolsFlag,
637
- skill: skillFlag
1013
+ skill: skillFlag,
1014
+ sessionId: options.sessionId
638
1015
  },
639
1016
  setProvider,
640
1017
  setModel,
@@ -653,14 +1030,6 @@ function ChatApp(options) {
653
1030
  }
654
1031
  return true;
655
1032
  };
656
- const awaitingConfirmation = React3.useMemo(
657
- () => chat.messages.some(
658
- (message) => message.toolCalls?.some(
659
- (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
660
- )
661
- ),
662
- [chat.messages, sessionAllowed]
663
- );
664
1033
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
665
1034
  /* @__PURE__ */ jsxRuntime.jsx(
666
1035
  ink$1.StatusHeader,
@@ -751,605 +1120,363 @@ function renderChatHeader(options) {
751
1120
  if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
752
1121
  return parts.join(" ");
753
1122
  }
754
- var PROVIDER_IMPORT = {
755
- openai: "openai",
756
- anthropic: "anthropic",
757
- gemini: "gemini",
758
- ollama: "ollama"
759
- };
760
- var PROVIDER_DEFAULT_MODEL = {
761
- openai: "gpt-4o-mini",
762
- anthropic: "claude-sonnet-4-6",
763
- gemini: "gemini-2.5-flash",
764
- ollama: "llama3.1",
765
- demo: "demo"
766
- };
767
- var PROVIDER_ENV_KEY = {
768
- openai: "OPENAI_API_KEY",
769
- anthropic: "ANTHROPIC_API_KEY",
770
- gemini: "GEMINI_API_KEY",
771
- ollama: null,
772
- demo: null
773
- };
774
- function adapterCall(provider, prefix = "process.env") {
775
- const model = PROVIDER_DEFAULT_MODEL[provider];
776
- if (provider === "demo") return `demoAdapter()`;
777
- if (provider === "ollama") return `ollama({ model: '${model}' })`;
778
- const envKey = PROVIDER_ENV_KEY[provider];
779
- return `${PROVIDER_IMPORT[provider]}({ apiKey: ${prefix}.${envKey} ?? '', model: '${model}' })`;
780
- }
781
- function viteAdapterCall(provider) {
782
- if (provider === "demo") return `demoAdapter()`;
783
- if (provider === "ollama") return `ollama({ model: '${PROVIDER_DEFAULT_MODEL[provider]}' })`;
784
- const envKey = PROVIDER_ENV_KEY[provider];
785
- return `${PROVIDER_IMPORT[provider]}({ apiKey: import.meta.env.VITE_${envKey} ?? '', model: '${PROVIDER_DEFAULT_MODEL[provider]}' })`;
786
- }
787
- function adapterImport(provider) {
788
- if (provider === "demo") return "";
789
- return `import { ${PROVIDER_IMPORT[provider]} } from '@agentskit/adapters'
790
- `;
791
- }
792
- function toolImports(tools) {
793
- if (tools.length === 0) return "";
794
- return `import { ${tools.map((t) => t === "web_search" ? "webSearch" : t).join(", ")} } from '@agentskit/tools'
795
- `;
1123
+
1124
+ // src/commands/shared.ts
1125
+ function mergeWithConfig(options, config) {
1126
+ if (!config) return options;
1127
+ const d = config.defaults ?? {};
1128
+ const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
1129
+ return {
1130
+ ...options,
1131
+ provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
1132
+ model: options.model ?? d.model,
1133
+ apiKey: resolvedApiKey,
1134
+ baseUrl: options.baseUrl ?? d.baseUrl,
1135
+ tools: options.tools ?? d.tools,
1136
+ skill: options.skill ?? d.skill,
1137
+ system: options.system ?? d.system,
1138
+ memoryBackend: options.memoryBackend ?? d.memoryBackend
1139
+ };
796
1140
  }
797
- function toolList(tools) {
798
- if (tools.length === 0) return "[]";
799
- const calls = tools.map((t) => {
800
- if (t === "web_search") return "webSearch()";
801
- if (t === "filesystem") return `...filesystem({ basePath: './workspace' })`;
802
- if (t === "shell") return `shell({ allowedCommands: ['ls', 'cat'] })`;
803
- return "";
804
- });
805
- return `[${calls.join(", ")}]`;
1141
+ async function loadPlugins(options = {}) {
1142
+ const {
1143
+ specs = [],
1144
+ pluginDirs = [],
1145
+ cwd = process.cwd(),
1146
+ autoDiscoverUserDir = true,
1147
+ onError = (spec, err) => process.stderr.write(
1148
+ `[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
1149
+ `
1150
+ ),
1151
+ log = () => {
1152
+ }
1153
+ } = options;
1154
+ const resolvedSpecs = [...specs];
1155
+ const discoveryDirs = [...pluginDirs];
1156
+ if (autoDiscoverUserDir) discoveryDirs.push(path.join(os.homedir(), ".agentskit", "plugins"));
1157
+ for (const dir of discoveryDirs) {
1158
+ const discovered = await discoverPluginsInDir(dir);
1159
+ resolvedSpecs.push(...discovered);
1160
+ }
1161
+ const plugins = [];
1162
+ for (const spec of resolvedSpecs) {
1163
+ try {
1164
+ const plugin = await loadPluginFromSpec(spec, cwd, log);
1165
+ if (plugin) plugins.push(plugin);
1166
+ } catch (err) {
1167
+ onError(spec, err);
1168
+ }
1169
+ }
1170
+ return mergePluginsIntoBundle(plugins);
806
1171
  }
807
- function memoryImport(memory) {
808
- if (memory === "file") return `import { fileChatMemory } from '@agentskit/memory'
809
- `;
810
- if (memory === "sqlite") return `import { sqliteChatMemory } from '@agentskit/memory'
811
- `;
812
- return "";
1172
+ async function discoverPluginsInDir(dir) {
1173
+ try {
1174
+ const entries = await promises.readdir(dir);
1175
+ const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => path.join(dir, name));
1176
+ const validated = [];
1177
+ for (const p of absolutes) {
1178
+ try {
1179
+ const s = await promises.stat(p);
1180
+ if (s.isFile()) validated.push(p);
1181
+ } catch {
1182
+ }
1183
+ }
1184
+ return validated;
1185
+ } catch {
1186
+ return [];
1187
+ }
813
1188
  }
814
- function memoryCall(memory) {
815
- if (memory === "file") return `fileChatMemory('./.agentskit-history.json')`;
816
- if (memory === "sqlite") return `sqliteChatMemory({ path: './.agentskit-history.db' })`;
817
- return "undefined";
1189
+ async function loadPluginFromSpec(spec, cwd, log) {
1190
+ const isPath = spec.startsWith("./") || spec.startsWith("../") || path.isAbsolute(spec);
1191
+ const importTarget = isPath ? url.pathToFileURL(path.resolve(cwd, spec)).href : spec;
1192
+ const mod = await import(importTarget);
1193
+ const exported = mod.default ?? mod.plugin ?? mod;
1194
+ const sourcePath = isPath ? path.resolve(cwd, spec) : void 0;
1195
+ const ctx = {
1196
+ cwd,
1197
+ sourcePath,
1198
+ log: (msg) => log(`[${spec}] ${msg}`)
1199
+ };
1200
+ if (typeof exported === "function") {
1201
+ const factory = exported;
1202
+ return await factory(ctx);
1203
+ }
1204
+ if (exported && typeof exported === "object" && "name" in exported) {
1205
+ const plugin = exported;
1206
+ if (plugin.init) await plugin.init(ctx);
1207
+ return plugin;
1208
+ }
1209
+ throw new Error(
1210
+ "Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
1211
+ );
818
1212
  }
819
- function demoAdapterSnippet() {
820
- return `function demoAdapter() {
821
- return {
822
- createSource: () => ({
823
- stream: async function* () {
824
- yield { type: 'text' as const, content: 'Hello from your AgentsKit starter. ' }
825
- yield { type: 'text' as const, content: 'Configure a real adapter to talk to a model.' }
826
- yield { type: 'done' as const }
827
- },
828
- abort: () => {},
829
- }),
1213
+ function mergePluginsIntoBundle(plugins) {
1214
+ const bundle = {
1215
+ plugins,
1216
+ slashCommands: [],
1217
+ tools: [],
1218
+ skills: [],
1219
+ providers: {},
1220
+ hooks: [],
1221
+ mcpServers: []
1222
+ };
1223
+ for (const plugin of plugins) {
1224
+ if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
1225
+ if (plugin.tools) bundle.tools.push(...plugin.tools);
1226
+ if (plugin.skills) bundle.skills.push(...plugin.skills);
1227
+ if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
1228
+ if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
1229
+ if (plugin.providers) {
1230
+ for (const [name, factory] of Object.entries(plugin.providers)) {
1231
+ bundle.providers[name] = factory;
1232
+ }
1233
+ }
830
1234
  }
1235
+ return bundle;
831
1236
  }
1237
+ var McpClient = class {
1238
+ constructor(spec, onError = (err) => process.stderr.write(
1239
+ `[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
1240
+ `
1241
+ )) {
1242
+ this.spec = spec;
1243
+ this.onError = onError;
1244
+ this.buffer = "";
1245
+ this.nextId = 1;
1246
+ this.pending = /* @__PURE__ */ new Map();
1247
+ this.disposed = false;
1248
+ }
1249
+ async start() {
1250
+ if (this.child) return;
1251
+ const child = child_process.spawn(this.spec.command, this.spec.args ?? [], {
1252
+ env: { ...process.env, ...this.spec.env ?? {} },
1253
+ stdio: ["pipe", "pipe", "pipe"]
1254
+ });
1255
+ this.child = child;
1256
+ child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
1257
+ child.stderr.on("data", (chunk) => {
1258
+ process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
1259
+ });
1260
+ child.on("error", (err) => this.onError(err));
1261
+ child.on("close", () => {
1262
+ this.disposed = true;
1263
+ for (const pending of this.pending.values()) {
1264
+ pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
1265
+ }
1266
+ this.pending.clear();
1267
+ });
1268
+ await this.request("initialize", {
1269
+ protocolVersion: "2024-11-05",
1270
+ capabilities: {},
1271
+ clientInfo: { name: "agentskit", version: "0" }
1272
+ });
1273
+ }
1274
+ async listTools() {
1275
+ const res = await this.request("tools/list", {});
1276
+ const tools = res.tools ?? [];
1277
+ return tools;
1278
+ }
1279
+ async callTool(name, args) {
1280
+ return await this.request("tools/call", { name, arguments: args });
1281
+ }
1282
+ dispose() {
1283
+ if (this.disposed) return;
1284
+ this.disposed = true;
1285
+ this.child?.kill("SIGTERM");
1286
+ }
1287
+ request(method, params) {
1288
+ return new Promise((resolvePromise, rejectPromise) => {
1289
+ if (!this.child || this.disposed) {
1290
+ rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
1291
+ return;
1292
+ }
1293
+ const id = this.nextId++;
1294
+ const timeoutMs = this.spec.timeout ?? 1e4;
1295
+ const timer = setTimeout(() => {
1296
+ this.pending.delete(id);
1297
+ rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
1298
+ }, timeoutMs);
1299
+ this.pending.set(id, (res) => {
1300
+ clearTimeout(timer);
1301
+ if (res.error) {
1302
+ rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
1303
+ return;
1304
+ }
1305
+ resolvePromise(res.result);
1306
+ });
1307
+ const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
1308
+ this.child.stdin.write(message);
1309
+ });
1310
+ }
1311
+ onStdout(chunk) {
1312
+ this.buffer += chunk;
1313
+ let newlineIndex = this.buffer.indexOf("\n");
1314
+ while (newlineIndex !== -1) {
1315
+ const line = this.buffer.slice(0, newlineIndex).trim();
1316
+ this.buffer = this.buffer.slice(newlineIndex + 1);
1317
+ if (line) {
1318
+ try {
1319
+ const parsed = JSON.parse(line);
1320
+ const pending = this.pending.get(Number(parsed.id));
1321
+ if (pending) {
1322
+ this.pending.delete(Number(parsed.id));
1323
+ pending(parsed);
1324
+ }
1325
+ } catch (err) {
1326
+ this.onError(err);
1327
+ }
1328
+ }
1329
+ newlineIndex = this.buffer.indexOf("\n");
1330
+ }
1331
+ }
1332
+ };
832
1333
 
833
- `;
1334
+ // src/extensibility/mcp/bridge.ts
1335
+ async function bridgeMcpServers(specs) {
1336
+ const clients = [];
1337
+ const tools = [];
1338
+ for (const spec of specs) {
1339
+ const client = new McpClient(spec);
1340
+ try {
1341
+ await client.start();
1342
+ const mcpTools = await client.listTools();
1343
+ for (const mcpTool of mcpTools) {
1344
+ tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
1345
+ }
1346
+ clients.push(client);
1347
+ } catch (err) {
1348
+ process.stderr.write(
1349
+ `[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
1350
+ `
1351
+ );
1352
+ client.dispose();
1353
+ }
1354
+ }
1355
+ return { clients, tools };
834
1356
  }
835
- function reactStarter(ctx) {
836
- const deps = {
837
- "@agentskit/react": "^0.4.0",
838
- react: "^19.0.0",
839
- "react-dom": "^19.0.0"
840
- };
841
- if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
842
- if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
843
- if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
844
- const includesDemo = ctx.provider === "demo";
845
- const adapter = ctx.provider === "demo" ? viteAdapterCall(ctx.provider) : viteAdapterCall(ctx.provider);
846
- const envKey = PROVIDER_ENV_KEY[ctx.provider];
847
- const envContent = envKey ? `VITE_${envKey}=
848
- ` : "# No API key required for the local provider\n";
1357
+ function mcpToolToDefinition(serverName, client, tool) {
849
1358
  return {
850
- "package.json": JSON.stringify(
851
- {
852
- name: path3__default.default.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
853
- private: true,
854
- type: "module",
855
- scripts: {
856
- dev: "vite",
857
- build: "vite build",
858
- preview: "vite preview"
859
- },
860
- dependencies: deps,
861
- devDependencies: {
862
- "@types/react": "^19.0.0",
863
- "@types/react-dom": "^19.0.0",
864
- "@vitejs/plugin-react": "^5.0.0",
865
- typescript: "^5.5.0",
866
- vite: "^7.0.0"
867
- }
868
- },
869
- null,
870
- 2
871
- ) + "\n",
872
- "index.html": `<!doctype html>
873
- <html lang="en">
874
- <head>
875
- <meta charset="UTF-8" />
876
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
877
- <title>AgentsKit React Starter</title>
878
- </head>
879
- <body>
880
- <div id="root"></div>
881
- <script type="module" src="/src/main.tsx"></script>
882
- </body>
883
- </html>
884
- `,
885
- "vite.config.ts": `import { defineConfig } from 'vite'
886
- import react from '@vitejs/plugin-react'
887
-
888
- export default defineConfig({ plugins: [react()] })
889
- `,
890
- "tsconfig.json": JSON.stringify(
891
- {
892
- compilerOptions: {
893
- target: "ES2022",
894
- lib: ["ES2022", "DOM"],
895
- module: "ESNext",
896
- moduleResolution: "bundler",
897
- jsx: "react-jsx",
898
- strict: true,
899
- noEmit: true,
900
- skipLibCheck: true
901
- },
902
- include: ["src"]
903
- },
904
- null,
905
- 2
906
- ) + "\n",
907
- "src/main.tsx": `import React from 'react'
908
- import ReactDOM from 'react-dom/client'
909
- import App from './App'
910
-
911
- ReactDOM.createRoot(document.getElementById('root')!).render(
912
- <React.StrictMode>
913
- <App />
914
- </React.StrictMode>,
915
- )
916
- `,
917
- "src/App.tsx": `import { ChatContainer, InputBar, Message, useChat } from '@agentskit/react'
918
- ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}import '@agentskit/react/theme'
919
-
920
- ${includesDemo ? demoAdapterSnippet() : ""}export default function App() {
921
- const chat = useChat({
922
- adapter: ${adapter},${ctx.tools.length > 0 ? `
923
- tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
924
- memory: ${memoryCall(ctx.memory)},` : ""}
925
- })
926
-
927
- return (
928
- <ChatContainer>
929
- {chat.messages.map(message => (
930
- <Message key={message.id} message={message} />
931
- ))}
932
- <InputBar chat={chat} />
933
- </ChatContainer>
934
- )
935
- }
936
- `,
937
- ".env.example": envContent,
938
- ".gitignore": `node_modules
939
- dist
940
- .env
941
- .env.local
942
- .agentskit-history.*
943
- `,
944
- "README.md": readmeFor(ctx)
945
- };
946
- }
947
- function inkStarter(ctx) {
948
- const deps = {
949
- "@agentskit/ink": "^0.4.0",
950
- ink: "^7.0.0",
951
- react: "^19.0.0"
952
- };
953
- if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
954
- if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
955
- if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
956
- return {
957
- "package.json": JSON.stringify(
958
- {
959
- name: "agentskit-ink-app",
960
- private: true,
961
- type: "module",
962
- scripts: {
963
- dev: "tsx src/index.tsx",
964
- start: "tsx src/index.tsx"
965
- },
966
- dependencies: deps,
967
- devDependencies: {
968
- "@types/react": "^19.0.0",
969
- "@types/react-dom": "^19.0.0",
970
- tsx: "^4.20.0",
971
- typescript: "^5.5.0"
972
- }
973
- },
974
- null,
975
- 2
976
- ) + "\n",
977
- "tsconfig.json": JSON.stringify(
978
- {
979
- compilerOptions: {
980
- target: "ES2022",
981
- module: "ESNext",
982
- moduleResolution: "bundler",
983
- jsx: "react-jsx",
984
- strict: true,
985
- noEmit: true,
986
- skipLibCheck: true
987
- },
988
- include: ["src"]
989
- },
990
- null,
991
- 2
992
- ) + "\n",
993
- "src/index.tsx": `import React from 'react'
994
- import { render } from 'ink'
995
- import { ChatContainer, InputBar, Message, useChat } from '@agentskit/ink'
996
- ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}
997
- ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}function App() {
998
- const chat = useChat({
999
- adapter: ${adapterCall(ctx.provider)},${ctx.tools.length > 0 ? `
1000
- tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
1001
- memory: ${memoryCall(ctx.memory)},` : ""}
1002
- })
1003
-
1004
- return (
1005
- <ChatContainer>
1006
- {chat.messages.map(message => (
1007
- <Message key={message.id} message={message} />
1008
- ))}
1009
- <InputBar chat={chat} />
1010
- </ChatContainer>
1011
- )
1012
- }
1013
-
1014
- render(<App />)
1015
- `,
1016
- ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
1017
- ` : "# No API key required for the local provider\n",
1018
- ".gitignore": `node_modules
1019
- .env
1020
- .env.local
1021
- .agentskit-history.*
1022
- `,
1023
- "README.md": readmeFor(ctx)
1024
- };
1025
- }
1026
- function runtimeStarter(ctx) {
1027
- const deps = {
1028
- "@agentskit/runtime": "^0.4.0"
1029
- };
1030
- if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
1031
- if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
1032
- if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
1033
- return {
1034
- "package.json": JSON.stringify(
1035
- {
1036
- name: "agentskit-runtime-app",
1037
- private: true,
1038
- type: "module",
1039
- scripts: {
1040
- start: "tsx src/index.ts",
1041
- dev: "tsx src/index.ts"
1042
- },
1043
- dependencies: deps,
1044
- devDependencies: {
1045
- tsx: "^4.20.0",
1046
- typescript: "^5.5.0"
1047
- }
1048
- },
1049
- null,
1050
- 2
1051
- ) + "\n",
1052
- "tsconfig.json": JSON.stringify(
1053
- {
1054
- compilerOptions: {
1055
- target: "ES2022",
1056
- module: "ESNext",
1057
- moduleResolution: "bundler",
1058
- strict: true,
1059
- noEmit: true,
1060
- skipLibCheck: true
1061
- },
1062
- include: ["src"]
1063
- },
1064
- null,
1065
- 2
1066
- ) + "\n",
1067
- "src/index.ts": `import { createRuntime } from '@agentskit/runtime'
1068
- ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}
1069
- ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}const runtime = createRuntime({
1070
- adapter: ${adapterCall(ctx.provider)},${ctx.tools.length > 0 ? `
1071
- tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
1072
- memory: ${memoryCall(ctx.memory)},` : ""}
1073
- maxSteps: 10,
1074
- })
1075
-
1076
- const task = process.argv.slice(2).join(' ') || 'Say hello and tell me one fact about TypeScript.'
1077
- const result = await runtime.run(task)
1078
-
1079
- console.log(result.content)
1080
- console.log(\`\\n\u2014 \${result.steps} steps \xB7 \${result.toolCalls.length} tool calls \xB7 \${result.durationMs}ms\`)
1081
- `,
1082
- ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
1083
- ` : "# No API key required for the local provider\n",
1084
- ".gitignore": `node_modules
1085
- .env
1086
- .env.local
1087
- .agentskit-history.*
1088
- `,
1089
- "README.md": readmeFor(ctx)
1090
- };
1091
- }
1092
- function multiAgentStarter(ctx) {
1093
- const deps = {
1094
- "@agentskit/runtime": "^0.4.0",
1095
- "@agentskit/skills": "^0.4.0"
1096
- };
1097
- if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
1098
- if (ctx.tools.length === 0) ctx.tools = ["web_search"];
1099
- deps["@agentskit/tools"] = "^0.4.0";
1100
- return {
1101
- "package.json": JSON.stringify(
1102
- {
1103
- name: "agentskit-multi-agent",
1104
- private: true,
1105
- type: "module",
1106
- scripts: {
1107
- start: "tsx src/index.ts",
1108
- dev: "tsx src/index.ts"
1109
- },
1110
- dependencies: deps,
1111
- devDependencies: {
1112
- tsx: "^4.20.0",
1113
- typescript: "^5.5.0"
1114
- }
1115
- },
1116
- null,
1117
- 2
1118
- ) + "\n",
1119
- "tsconfig.json": JSON.stringify(
1120
- {
1121
- compilerOptions: {
1122
- target: "ES2022",
1123
- module: "ESNext",
1124
- moduleResolution: "bundler",
1125
- strict: true,
1126
- noEmit: true,
1127
- skipLibCheck: true
1128
- },
1129
- include: ["src"]
1130
- },
1131
- null,
1132
- 2
1133
- ) + "\n",
1134
- "src/index.ts": `import { createRuntime } from '@agentskit/runtime'
1135
- import { planner, researcher } from '@agentskit/skills'
1136
- ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}
1137
- ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}const runtime = createRuntime({
1138
- adapter: ${adapterCall(ctx.provider)},
1139
- maxSteps: 10,
1140
- maxDelegationDepth: 2,
1141
- })
1142
-
1143
- const task = process.argv.slice(2).join(' ') || 'Research the current state of WebGPU and summarize.'
1144
-
1145
- const result = await runtime.run(task, {
1146
- skill: planner,
1147
- delegates: {
1148
- researcher: {
1149
- skill: researcher,
1150
- tools: ${toolList(ctx.tools)},
1151
- maxSteps: 5,
1152
- },
1153
- },
1154
- })
1155
-
1156
- console.log(result.content)
1157
- console.log(\`\\n\u2014 \${result.steps} steps \xB7 \${result.toolCalls.length} tool calls\`)
1158
- `,
1159
- ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
1160
- ` : "# No API key required for the local provider\n",
1161
- ".gitignore": `node_modules
1162
- .env
1163
- .env.local
1164
- `,
1165
- "README.md": readmeFor(ctx)
1359
+ name: `${serverName}__${tool.name}`,
1360
+ description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
1361
+ schema: tool.inputSchema ?? { type: "object", properties: {} },
1362
+ execute: async (args) => {
1363
+ return await client.callTool(tool.name, args);
1364
+ }
1166
1365
  };
1167
1366
  }
1168
- function readmeFor(ctx) {
1169
- const installCmd = ctx.pm === "npm" ? "npm install" : `${ctx.pm} install`;
1170
- const runCmd = ctx.pm === "npm" ? "npm run dev" : `${ctx.pm} dev`;
1171
- const envKey = PROVIDER_ENV_KEY[ctx.provider];
1172
- return `# AgentsKit ${ctx.template} starter
1173
-
1174
- Generated by \`agentskit init\`.
1175
-
1176
- ## Stack
1177
-
1178
- - **Template**: \`${ctx.template}\`
1179
- - **Provider**: \`${ctx.provider}\`${ctx.tools.length ? `
1180
- - **Tools**: ${ctx.tools.map((t) => `\`${t}\``).join(", ")}` : ""}${ctx.memory !== "none" ? `
1181
- - **Memory**: \`${ctx.memory}\`` : ""}
1182
-
1183
- ## Run
1184
-
1185
- \`\`\`bash
1186
- ${installCmd}
1187
- ${envKey ? `cp .env.example .env
1188
- # add ${envKey}=...` : "# No API key required"}
1189
- ${runCmd}
1190
- \`\`\`
1191
-
1192
- ## Next steps
1193
-
1194
- - Open the AgentsKit docs at https://www.agentskit.io/docs
1195
- - Add a custom skill: https://www.agentskit.io/docs/concepts/skill
1196
- - Wire up RAG: https://www.agentskit.io/docs/recipes/rag-chat
1197
-
1198
- ## License
1199
-
1200
- ISC
1201
- `;
1202
- }
1203
- var TEMPLATE_FN = {
1204
- react: reactStarter,
1205
- ink: inkStarter,
1206
- runtime: runtimeStarter,
1207
- "multi-agent": multiAgentStarter
1208
- };
1209
- async function writeStarterProject(options) {
1210
- const ctx = {
1211
- template: options.template,
1212
- provider: options.provider ?? "demo",
1213
- tools: options.tools ?? [],
1214
- memory: options.memory ?? "none",
1215
- pm: options.packageManager ?? "pnpm"
1216
- };
1217
- const files = TEMPLATE_FN[ctx.template](ctx);
1218
- await promises.mkdir(options.targetDir, { recursive: true });
1219
- await Promise.all(
1220
- Object.entries(files).map(async ([relativePath, content]) => {
1221
- const absolutePath = path3__default.default.join(options.targetDir, relativePath);
1222
- await promises.mkdir(path3__default.default.dirname(absolutePath), { recursive: true });
1223
- await promises.writeFile(absolutePath, content, "utf8");
1224
- })
1225
- );
1367
+ function disposeMcpClients(clients) {
1368
+ for (const client of clients) client.dispose();
1226
1369
  }
1227
- async function runInteractiveInit(defaults = {}) {
1228
- process.stdout.write(`
1229
- ${kleur__default.default.bold().green("\u25B2")} ${kleur__default.default.bold("agentskit init")}
1230
- `);
1231
- process.stdout.write(kleur__default.default.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
1232
- try {
1233
- const targetDir = await prompts.input({
1234
- message: "Project directory:",
1235
- default: defaults.dir ?? "agentskit-app",
1236
- validate: (value) => {
1237
- if (!value.trim()) return "A directory name is required.";
1238
- const abs = path3__default.default.resolve(process.cwd(), value);
1239
- if (fs.existsSync(abs)) return `${value} already exists. Pick a different name.`;
1240
- return true;
1370
+
1371
+ // src/commands/chat.ts
1372
+ function registerChatCommand(program) {
1373
+ program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").option(
1374
+ "--plugin-dir <dir>",
1375
+ "Extra directory to auto-discover plugin modules from (repeatable)",
1376
+ (value, prev = []) => [...prev, value],
1377
+ []
1378
+ ).option(
1379
+ "--mode <mode>",
1380
+ "Permission mode: default | plan | acceptEdits | bypassPermissions"
1381
+ ).action(async (options) => {
1382
+ if (options.listSessions) {
1383
+ const sessions = listSessions();
1384
+ if (sessions.length === 0) {
1385
+ process.stdout.write("No saved sessions for this directory.\n");
1386
+ return;
1241
1387
  }
1242
- });
1243
- const template = await prompts.select({
1244
- message: "Template:",
1245
- default: defaults.template ?? "react",
1246
- choices: [
1247
- { name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
1248
- { name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
1249
- { name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
1250
- { name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
1251
- ]
1252
- });
1253
- const provider = await prompts.select({
1254
- message: "LLM provider:",
1255
- default: "demo",
1256
- choices: [
1257
- { name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
1258
- { name: "OpenAI", value: "openai" },
1259
- { name: "Anthropic", value: "anthropic" },
1260
- { name: "Gemini", value: "gemini" },
1261
- { name: "Ollama (local, no key)", value: "ollama" }
1262
- ]
1263
- });
1264
- let tools = [];
1265
- if (template !== "react") {
1266
- tools = await prompts.checkbox({
1267
- message: "Tools (space to toggle, enter to confirm):",
1268
- choices: [
1269
- { name: "web_search", value: "web_search" },
1270
- { name: "filesystem", value: "filesystem" },
1271
- { name: "shell", value: "shell" }
1272
- ]
1273
- });
1388
+ for (const s of sessions) {
1389
+ const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
1390
+ const display = label ? `${label} (${id})` : id;
1391
+ const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
1392
+ process.stdout.write(
1393
+ `${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
1394
+ ${preview}
1395
+ `
1396
+ );
1397
+ }
1398
+ return;
1274
1399
  }
1275
- const memory = await prompts.select({
1276
- message: "Memory backend:",
1277
- default: "none",
1278
- choices: [
1279
- { name: "None (stateless)", value: "none" },
1280
- { name: "File (JSON on disk)", value: "file" },
1281
- { name: "SQLite (better-sqlite3)", value: "sqlite" }
1282
- ]
1283
- });
1284
- const packageManager = await prompts.select({
1285
- message: "Package manager:",
1286
- default: "pnpm",
1287
- choices: [
1288
- { name: "pnpm", value: "pnpm" },
1289
- { name: "npm", value: "npm" },
1290
- { name: "yarn", value: "yarn" },
1291
- { name: "bun", value: "bun" }
1292
- ]
1400
+ const config = options.config !== false ? await loadConfig() : void 0;
1401
+ const merged = mergeWithConfig(options, config);
1402
+ const session = resolveSession({
1403
+ explicitPath: options.memory,
1404
+ forceNew: Boolean(options.new),
1405
+ resumeId: options.resume
1293
1406
  });
1294
- process.stdout.write("\n" + kleur__default.default.dim(" Summary:\n"));
1295
- process.stdout.write(kleur__default.default.dim(` dir ${targetDir}
1296
- `));
1297
- process.stdout.write(kleur__default.default.dim(` template ${template}
1298
- `));
1299
- process.stdout.write(kleur__default.default.dim(` provider ${provider}
1300
- `));
1301
- if (tools.length) process.stdout.write(kleur__default.default.dim(` tools ${tools.join(", ")}
1302
- `));
1303
- process.stdout.write(kleur__default.default.dim(` memory ${memory}
1304
- `));
1305
- process.stdout.write(kleur__default.default.dim(` pm ${packageManager}
1306
-
1307
- `));
1308
- const proceed = await prompts.confirm({ message: "Generate?", default: true });
1309
- if (!proceed) {
1310
- process.stdout.write(kleur__default.default.yellow("Cancelled.\n"));
1311
- return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
1407
+ if (!session.isNew && !options.memory) {
1408
+ process.stdout.write(
1409
+ `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
1410
+ `
1411
+ );
1312
1412
  }
1313
- return {
1314
- cancelled: false,
1315
- options: {
1316
- targetDir: path3__default.default.resolve(process.cwd(), targetDir),
1317
- template,
1318
- provider,
1319
- tools,
1320
- memory,
1321
- packageManager
1322
- }
1413
+ const pluginBundle = await loadPlugins({
1414
+ specs: config?.plugins ?? [],
1415
+ pluginDirs: options.pluginDir ?? []
1416
+ });
1417
+ const configHooks = configHooksToHandlers(config?.hooks);
1418
+ const hookHandlers = [...configHooks, ...pluginBundle.hooks];
1419
+ const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
1420
+ name,
1421
+ command: spec.command,
1422
+ args: spec.args,
1423
+ env: spec.env,
1424
+ timeout: spec.timeout
1425
+ }));
1426
+ const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
1427
+ const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
1428
+ const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
1429
+ const permissionPolicy = {
1430
+ mode: policyMode,
1431
+ rules: (config?.permissions?.rules ?? []).map((r) => ({
1432
+ tool: r.tool,
1433
+ action: r.action,
1434
+ scope: r.scope
1435
+ }))
1323
1436
  };
1324
- } catch (err) {
1325
- if (err.name === "ExitPromptError") {
1326
- process.stdout.write(kleur__default.default.yellow("\nCancelled.\n"));
1327
- return { cancelled: true, options: { targetDir: "", template: "react" } };
1328
- }
1329
- throw err;
1330
- }
1331
- }
1332
- function printNextSteps(options) {
1333
- const dir = path3__default.default.relative(process.cwd(), options.targetDir) || ".";
1334
- const pm = options.packageManager ?? "pnpm";
1335
- const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
1336
- const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
1337
- process.stdout.write("\n" + kleur__default.default.green("\u2713 Created starter at ") + kleur__default.default.bold(dir) + "\n\n");
1338
- process.stdout.write(kleur__default.default.bold("Next steps:\n\n"));
1339
- process.stdout.write(` ${kleur__default.default.cyan("cd")} ${dir}
1340
- `);
1341
- process.stdout.write(` ${kleur__default.default.cyan(installCmd)}
1437
+ const chatOptions = {
1438
+ apiKey: merged.apiKey ?? options.apiKey,
1439
+ baseUrl: merged.baseUrl ?? options.baseUrl,
1440
+ provider: merged.provider,
1441
+ model: merged.model,
1442
+ system: merged.system ?? options.system,
1443
+ memoryPath: session.file,
1444
+ sessionId: session.id,
1445
+ tools: merged.tools ?? options.tools,
1446
+ skill: merged.skill ?? options.skill,
1447
+ memoryBackend: merged.memoryBackend ?? options.memoryBackend,
1448
+ agentsKitConfig: config,
1449
+ slashCommands: pluginBundle.slashCommands,
1450
+ extraTools: [...pluginBundle.tools, ...mcpTools],
1451
+ extraSkills: pluginBundle.skills,
1452
+ hookHandlers,
1453
+ permissionPolicy
1454
+ };
1455
+ process.stdout.write(`${renderChatHeader(chatOptions)}
1342
1456
  `);
1343
- if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
1344
- process.stdout.write(
1345
- ` ${kleur__default.default.cyan("cp")} .env.example .env ${kleur__default.default.dim("# add your API key")}
1457
+ const instance = ink.render(React2__default.default.createElement(ChatApp, chatOptions));
1458
+ try {
1459
+ await instance.waitUntilExit();
1460
+ } finally {
1461
+ disposeMcpClients(mcpClients);
1462
+ }
1463
+ if (options.memory) {
1464
+ process.stdout.write(
1465
+ `
1466
+ Session saved to ${session.file}. Resume with --memory ${session.file}
1346
1467
  `
1347
- );
1348
- }
1349
- process.stdout.write(` ${kleur__default.default.cyan(runCmd)}
1350
-
1351
- `);
1352
- process.stdout.write(kleur__default.default.dim(" Docs: https://www.agentskit.io/docs\n\n"));
1468
+ );
1469
+ } else {
1470
+ process.stdout.write(
1471
+ `
1472
+ Session saved. Resume with:
1473
+ agentskit chat --resume ${session.id}
1474
+ Or start fresh with:
1475
+ agentskit chat --new
1476
+ `
1477
+ );
1478
+ }
1479
+ });
1353
1480
  }
1354
1481
  function formatEvent(event) {
1355
1482
  switch (event.type) {
@@ -1408,13 +1535,13 @@ async function runAgent(task, options) {
1408
1535
  process.stdout.write(result.content + "\n");
1409
1536
  }
1410
1537
  function RunApp({ task, options }) {
1411
- const [status, setStatus] = React3.useState("running");
1412
- const [currentStep, setCurrentStep] = React3.useState(0);
1413
- const [toolCalls, setToolCalls] = React3.useState([]);
1414
- const [result, setResult] = React3.useState("");
1415
- const [error, setError] = React3.useState("");
1416
- const [durationMs, setDurationMs] = React3.useState(0);
1417
- React3.useEffect(() => {
1538
+ const [status, setStatus] = React2.useState("running");
1539
+ const [currentStep, setCurrentStep] = React2.useState(0);
1540
+ const [toolCalls, setToolCalls] = React2.useState([]);
1541
+ const [result, setResult] = React2.useState("");
1542
+ const [error, setError] = React2.useState("");
1543
+ const [durationMs, setDurationMs] = React2.useState(0);
1544
+ React2.useEffect(() => {
1418
1545
  async function execute() {
1419
1546
  if (options.skill && options.skills) {
1420
1547
  setError("--skill and --skills are mutually exclusive.");
@@ -1505,555 +1632,640 @@ function RunApp({ task, options }) {
1505
1632
  ] })
1506
1633
  ] });
1507
1634
  }
1508
- var PROVIDER_ENV_KEYS = {
1635
+
1636
+ // src/commands/run.ts
1637
+ function registerRunCommand(program) {
1638
+ program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
1639
+ const task = options.task ?? positionalTask;
1640
+ if (!task) {
1641
+ process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
1642
+ process.exit(1);
1643
+ }
1644
+ const config = options.config !== false ? await loadConfig() : void 0;
1645
+ const merged = mergeWithConfig(options, config);
1646
+ if (options.pretty) {
1647
+ ink.render(React2__default.default.createElement(RunApp, { task, options }));
1648
+ } else {
1649
+ try {
1650
+ await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
1651
+ } catch (err) {
1652
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1653
+ `);
1654
+ process.exit(1);
1655
+ }
1656
+ }
1657
+ });
1658
+ }
1659
+ var PROVIDER_IMPORT = {
1660
+ openai: "openai",
1661
+ anthropic: "anthropic",
1662
+ gemini: "gemini",
1663
+ ollama: "ollama"
1664
+ };
1665
+ var PROVIDER_DEFAULT_MODEL = {
1666
+ openai: "gpt-4o-mini",
1667
+ anthropic: "claude-sonnet-4-6",
1668
+ gemini: "gemini-2.5-flash",
1669
+ ollama: "llama3.1",
1670
+ demo: "demo"
1671
+ };
1672
+ var PROVIDER_ENV_KEY = {
1509
1673
  openai: "OPENAI_API_KEY",
1510
1674
  anthropic: "ANTHROPIC_API_KEY",
1511
1675
  gemini: "GEMINI_API_KEY",
1512
- deepseek: "DEEPSEEK_API_KEY",
1513
- grok: "XAI_API_KEY",
1514
- kimi: "KIMI_API_KEY"
1515
- };
1516
- var PROVIDER_REACH_URLS = {
1517
- openai: "https://api.openai.com/v1/models",
1518
- anthropic: "https://api.anthropic.com/v1/messages",
1519
- gemini: "https://generativelanguage.googleapis.com/v1beta/models",
1520
- ollama: "http://localhost:11434/api/tags"
1676
+ ollama: null,
1677
+ demo: null
1521
1678
  };
1522
- async function checkNodeVersion() {
1523
- const major = Number(process.versions.node.split(".")[0]);
1524
- if (Number.isNaN(major)) {
1525
- return { status: "fail", name: "Node version", detail: "Could not parse process.versions.node" };
1526
- }
1527
- if (major < 22) {
1528
- return {
1529
- status: "fail",
1530
- name: "Node version",
1531
- detail: `Node ${process.versions.node} (need 22+)`,
1532
- fix: "Install Node 22 LTS or newer (https://nodejs.org)"
1533
- };
1534
- }
1535
- if (major === 25) {
1536
- return {
1537
- status: "warn",
1538
- name: "Node version",
1539
- detail: `Node ${process.versions.node} \u2014 Docusaurus apps may break here`,
1540
- fix: "Use Node 22 LTS for the legacy docs app, or stay on 25 for everything else"
1541
- };
1542
- }
1543
- return { status: "pass", name: "Node version", detail: `Node ${process.versions.node}` };
1679
+ function adapterCall(provider, prefix = "process.env") {
1680
+ const model = PROVIDER_DEFAULT_MODEL[provider];
1681
+ if (provider === "demo") return `demoAdapter()`;
1682
+ if (provider === "ollama") return `ollama({ model: '${model}' })`;
1683
+ const envKey = PROVIDER_ENV_KEY[provider];
1684
+ return `${PROVIDER_IMPORT[provider]}({ apiKey: ${prefix}.${envKey} ?? '', model: '${model}' })`;
1544
1685
  }
1545
- async function checkPnpm() {
1546
- const cwd = process.cwd();
1547
- const hasPnpm = fs.existsSync(path3.join(cwd, "pnpm-lock.yaml")) || fs.existsSync(path3.join(cwd, "pnpm-workspace.yaml"));
1548
- if (hasPnpm) {
1549
- return { status: "pass", name: "Package manager", detail: "pnpm detected (lockfile)" };
1550
- }
1551
- if (fs.existsSync(path3.join(cwd, "package-lock.json"))) {
1552
- return { status: "warn", name: "Package manager", detail: "npm detected \u2014 pnpm recommended for monorepo workflows" };
1553
- }
1554
- if (fs.existsSync(path3.join(cwd, "yarn.lock"))) {
1555
- return { status: "pass", name: "Package manager", detail: "yarn detected" };
1556
- }
1557
- if (fs.existsSync(path3.join(cwd, "bun.lock")) || fs.existsSync(path3.join(cwd, "bun.lockb"))) {
1558
- return { status: "pass", name: "Package manager", detail: "bun detected" };
1559
- }
1560
- return {
1561
- status: "skip",
1562
- name: "Package manager",
1563
- detail: "No lockfile found in cwd"
1564
- };
1686
+ function viteAdapterCall(provider) {
1687
+ if (provider === "demo") return `demoAdapter()`;
1688
+ if (provider === "ollama") return `ollama({ model: '${PROVIDER_DEFAULT_MODEL[provider]}' })`;
1689
+ const envKey = PROVIDER_ENV_KEY[provider];
1690
+ return `${PROVIDER_IMPORT[provider]}({ apiKey: import.meta.env.VITE_${envKey} ?? '', model: '${PROVIDER_DEFAULT_MODEL[provider]}' })`;
1565
1691
  }
1566
- async function checkPackageJson() {
1567
- const path4 = path3.join(process.cwd(), "package.json");
1568
- if (!fs.existsSync(path4)) {
1569
- return {
1570
- status: "warn",
1571
- name: "package.json",
1572
- detail: "No package.json in cwd",
1573
- fix: "Run from a project directory (or use `agentskit init` to create one)"
1574
- };
1575
- }
1576
- try {
1577
- const pkg = JSON.parse(await promises.readFile(path4, "utf8"));
1578
- const deps = {
1579
- ...pkg.dependencies ?? {},
1580
- ...pkg.devDependencies ?? {}
1581
- };
1582
- const akDeps = Object.entries(deps).filter(([name]) => name.startsWith("@agentskit/"));
1583
- if (akDeps.length === 0) {
1584
- return { status: "skip", name: "AgentsKit packages", detail: "No @agentskit/* deps found in package.json" };
1585
- }
1586
- return {
1587
- status: "pass",
1588
- name: "AgentsKit packages",
1589
- detail: `${akDeps.length} installed: ${akDeps.map(([n]) => n.replace("@agentskit/", "")).join(", ")}`
1590
- };
1591
- } catch (err) {
1592
- return {
1593
- status: "fail",
1594
- name: "package.json",
1595
- detail: `Could not parse: ${err.message}`
1596
- };
1597
- }
1692
+ function adapterImport(provider) {
1693
+ if (provider === "demo") return "";
1694
+ return `import { ${PROVIDER_IMPORT[provider]} } from '@agentskit/adapters'
1695
+ `;
1598
1696
  }
1599
- async function checkProviderEnv(provider) {
1600
- const envKey = PROVIDER_ENV_KEYS[provider];
1601
- if (!envKey) {
1602
- return { status: "skip", name: `${provider} API key`, detail: "No env-key requirement for this provider" };
1603
- }
1604
- const value = process.env[envKey];
1605
- if (!value) {
1606
- return {
1607
- status: "skip",
1608
- name: `${provider} API key`,
1609
- detail: `${envKey} not set`,
1610
- fix: `export ${envKey}=... (only needed if you use ${provider})`
1611
- };
1612
- }
1613
- if (value.length < 16) {
1614
- return {
1615
- status: "warn",
1616
- name: `${provider} API key`,
1617
- detail: `${envKey} looks too short (${value.length} chars)`,
1618
- fix: "Verify the key is complete and not truncated"
1619
- };
1620
- }
1621
- return { status: "pass", name: `${provider} API key`, detail: `${envKey} set (${value.length} chars)` };
1697
+ function toolImports(tools) {
1698
+ if (tools.length === 0) return "";
1699
+ return `import { ${tools.map((t) => t === "web_search" ? "webSearch" : t).join(", ")} } from '@agentskit/tools'
1700
+ `;
1622
1701
  }
1623
- async function checkProviderReachable(provider, fetchImpl = fetch, timeoutMs = 4e3) {
1624
- const url = PROVIDER_REACH_URLS[provider];
1625
- if (!url) {
1626
- return { status: "skip", name: `${provider} reachable`, detail: "No reachability check configured" };
1627
- }
1628
- const envKey = PROVIDER_ENV_KEYS[provider];
1629
- if (envKey && !process.env[envKey]) {
1630
- return { status: "skip", name: `${provider} reachable`, detail: "Skipped \u2014 no API key configured" };
1631
- }
1632
- const controller = new AbortController();
1633
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1634
- try {
1635
- const res = await fetchImpl(url, {
1636
- method: "GET",
1637
- signal: controller.signal
1638
- });
1639
- if (res.status >= 200 && res.status < 400) {
1640
- return { status: "pass", name: `${provider} reachable`, detail: `${url} \u2192 ${res.status} OK` };
1641
- }
1642
- if (res.status === 401 || res.status === 403 || res.status === 405) {
1643
- return { status: "pass", name: `${provider} reachable`, detail: `${url} \u2192 ${res.status} (host reachable)` };
1644
- }
1645
- return {
1646
- status: "warn",
1647
- name: `${provider} reachable`,
1648
- detail: `${url} \u2192 HTTP ${res.status}`,
1649
- fix: "Host reachable but returned unexpected status \u2014 check provider docs"
1650
- };
1651
- } catch (err) {
1652
- const reason = err.name === "AbortError" ? `timeout after ${timeoutMs}ms` : err.message;
1653
- return {
1654
- status: "fail",
1655
- name: `${provider} reachable`,
1656
- detail: `${url} \u2192 ${reason}`,
1657
- fix: provider === "ollama" ? "Start Ollama: `ollama serve` (or install from https://ollama.com)" : "Check network / firewall / VPN settings"
1658
- };
1659
- } finally {
1660
- clearTimeout(timer);
1661
- }
1702
+ function toolList(tools) {
1703
+ if (tools.length === 0) return "[]";
1704
+ const calls = tools.map((t) => {
1705
+ if (t === "web_search") return "webSearch()";
1706
+ if (t === "filesystem") return `...filesystem({ basePath: './workspace' })`;
1707
+ if (t === "shell") return `shell({ allowedCommands: ['ls', 'cat'] })`;
1708
+ return "";
1709
+ });
1710
+ return `[${calls.join(", ")}]`;
1662
1711
  }
1663
- async function checkConfig() {
1664
- try {
1665
- const config = await loadConfig();
1666
- if (!config) {
1667
- return { status: "skip", name: "AgentsKit config", detail: "No .agentskit.config or package.json#agentskit found" };
1668
- }
1669
- return {
1670
- status: "pass",
1671
- name: "AgentsKit config",
1672
- detail: `loaded \u2014 defaults: ${JSON.stringify(config.defaults ?? {})}`
1673
- };
1674
- } catch (err) {
1675
- return {
1676
- status: "warn",
1677
- name: "AgentsKit config",
1678
- detail: `Could not load: ${err.message}`
1679
- };
1680
- }
1712
+ function memoryImport(memory) {
1713
+ if (memory === "file") return `import { fileChatMemory } from '@agentskit/memory'
1714
+ `;
1715
+ if (memory === "sqlite") return `import { sqliteChatMemory } from '@agentskit/memory'
1716
+ `;
1717
+ return "";
1681
1718
  }
1682
- async function runDoctor(options = {}) {
1683
- const providers2 = options.providers ?? ["openai", "anthropic", "gemini", "ollama"];
1684
- const fetchImpl = options.fetchImpl ?? fetch;
1685
- const checks = [
1686
- checkNodeVersion(),
1687
- checkPnpm(),
1688
- checkPackageJson(),
1689
- checkConfig()
1690
- ];
1691
- for (const provider of providers2) {
1692
- checks.push(checkProviderEnv(provider));
1693
- if (!options.noNetwork) {
1694
- checks.push(checkProviderReachable(provider, fetchImpl));
1695
- }
1696
- }
1697
- const results = await Promise.all(checks);
1719
+ function memoryCall(memory) {
1720
+ if (memory === "file") return `fileChatMemory('./.agentskit-history.json')`;
1721
+ if (memory === "sqlite") return `sqliteChatMemory({ path: './.agentskit-history.db' })`;
1722
+ return "undefined";
1723
+ }
1724
+ function demoAdapterSnippet() {
1725
+ return `function demoAdapter() {
1698
1726
  return {
1699
- results,
1700
- pass: results.filter((r) => r.status === "pass").length,
1701
- warn: results.filter((r) => r.status === "warn").length,
1702
- fail: results.filter((r) => r.status === "fail").length,
1703
- skip: results.filter((r) => r.status === "skip").length
1704
- };
1727
+ createSource: () => ({
1728
+ stream: async function* () {
1729
+ yield { type: 'text' as const, content: 'Hello from your AgentsKit starter. ' }
1730
+ yield { type: 'text' as const, content: 'Configure a real adapter to talk to a model.' }
1731
+ yield { type: 'done' as const }
1732
+ },
1733
+ abort: () => {},
1734
+ }),
1735
+ }
1736
+ }
1737
+
1738
+ `;
1705
1739
  }
1706
- var ICON = {
1707
- pass: "\u2714",
1708
- warn: "\u26A0",
1709
- fail: "\u2718",
1710
- skip: "\u25CB"
1711
- };
1712
- function renderReport(report, opts = {}) {
1713
- const color = opts.color ?? true;
1714
- const c = (code, text) => color ? `\x1B[${code}m${text}\x1B[0m` : text;
1715
- const colorFor = {
1716
- pass: (t) => c("32", t),
1717
- // green
1718
- warn: (t) => c("33", t),
1719
- // yellow
1720
- fail: (t) => c("31", t),
1721
- // red
1722
- skip: (t) => c("90", t)
1723
- // dim
1724
- };
1725
- const lines = [];
1726
- lines.push("");
1727
- lines.push(` ${c("1;36", "\u26A1 AgentsKit Doctor")}`);
1728
- lines.push(` ${c("90", "\u2500".repeat(50))}`);
1729
- lines.push("");
1730
- const groups = {
1731
- "Environment": [],
1732
- "Providers": [],
1733
- "Network": []
1740
+ function reactStarter(ctx) {
1741
+ const deps = {
1742
+ "@agentskit/react": "^0.4.0",
1743
+ react: "^19.0.0",
1744
+ "react-dom": "^19.0.0"
1734
1745
  };
1735
- for (const r of report.results) {
1736
- if (r.name.includes("reachable")) {
1737
- groups["Network"].push(r);
1738
- } else if (r.name.includes("API key")) {
1739
- groups["Providers"].push(r);
1740
- } else {
1741
- groups["Environment"].push(r);
1742
- }
1743
- }
1744
- for (const [group, results] of Object.entries(groups)) {
1745
- if (results.length === 0) continue;
1746
- lines.push(` ${c("1", group)}`);
1747
- for (const r of results) {
1748
- const icon = colorFor[r.status](ICON[r.status]);
1749
- const name = r.name.padEnd(28);
1750
- const detail = r.detail ? c("90", r.detail) : "";
1751
- lines.push(` ${icon} ${name} ${detail}`);
1752
- if (r.fix && r.status !== "pass") {
1753
- lines.push(` ${c("90", "\u21B3 " + r.fix)}`);
1754
- }
1755
- }
1756
- lines.push("");
1757
- }
1758
- lines.push(` ${c("90", "\u2500".repeat(50))}`);
1759
- const parts = [];
1760
- if (report.pass > 0) parts.push(colorFor.pass(`${report.pass} passed`));
1761
- if (report.warn > 0) parts.push(colorFor.warn(`${report.warn} warnings`));
1762
- if (report.fail > 0) parts.push(colorFor.fail(`${report.fail} failed`));
1763
- if (report.skip > 0) parts.push(colorFor.skip(`${report.skip} skipped`));
1764
- lines.push(` ${parts.join(" \xB7 ")}`);
1765
- if (report.fail === 0) {
1766
- lines.push(` ${c("32", "\u2714 Ready to build agents.")}`);
1767
- } else {
1768
- lines.push(` ${c("31", "\u2718 Fix the issues above before continuing.")}`);
1769
- }
1770
- lines.push("");
1771
- return lines.join("\n");
1746
+ if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
1747
+ if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
1748
+ if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
1749
+ const includesDemo = ctx.provider === "demo";
1750
+ const adapter = ctx.provider === "demo" ? viteAdapterCall(ctx.provider) : viteAdapterCall(ctx.provider);
1751
+ const envKey = PROVIDER_ENV_KEY[ctx.provider];
1752
+ const envContent = envKey ? `VITE_${envKey}=
1753
+ ` : "# No API key required for the local provider\n";
1754
+ return {
1755
+ "package.json": JSON.stringify(
1756
+ {
1757
+ name: path__default.default.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
1758
+ private: true,
1759
+ type: "module",
1760
+ scripts: {
1761
+ dev: "vite",
1762
+ build: "vite build",
1763
+ preview: "vite preview"
1764
+ },
1765
+ dependencies: deps,
1766
+ devDependencies: {
1767
+ "@types/react": "^19.0.0",
1768
+ "@types/react-dom": "^19.0.0",
1769
+ "@vitejs/plugin-react": "^5.0.0",
1770
+ typescript: "^5.5.0",
1771
+ vite: "^7.0.0"
1772
+ }
1773
+ },
1774
+ null,
1775
+ 2
1776
+ ) + "\n",
1777
+ "index.html": `<!doctype html>
1778
+ <html lang="en">
1779
+ <head>
1780
+ <meta charset="UTF-8" />
1781
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1782
+ <title>AgentsKit React Starter</title>
1783
+ </head>
1784
+ <body>
1785
+ <div id="root"></div>
1786
+ <script type="module" src="/src/main.tsx"></script>
1787
+ </body>
1788
+ </html>
1789
+ `,
1790
+ "vite.config.ts": `import { defineConfig } from 'vite'
1791
+ import react from '@vitejs/plugin-react'
1792
+
1793
+ export default defineConfig({ plugins: [react()] })
1794
+ `,
1795
+ "tsconfig.json": JSON.stringify(
1796
+ {
1797
+ compilerOptions: {
1798
+ target: "ES2022",
1799
+ lib: ["ES2022", "DOM"],
1800
+ module: "ESNext",
1801
+ moduleResolution: "bundler",
1802
+ jsx: "react-jsx",
1803
+ strict: true,
1804
+ noEmit: true,
1805
+ skipLibCheck: true
1806
+ },
1807
+ include: ["src"]
1808
+ },
1809
+ null,
1810
+ 2
1811
+ ) + "\n",
1812
+ "src/main.tsx": `import React from 'react'
1813
+ import ReactDOM from 'react-dom/client'
1814
+ import App from './App'
1815
+
1816
+ ReactDOM.createRoot(document.getElementById('root')!).render(
1817
+ <React.StrictMode>
1818
+ <App />
1819
+ </React.StrictMode>,
1820
+ )
1821
+ `,
1822
+ "src/App.tsx": `import { ChatContainer, InputBar, Message, useChat } from '@agentskit/react'
1823
+ ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}import '@agentskit/react/theme'
1824
+
1825
+ ${includesDemo ? demoAdapterSnippet() : ""}export default function App() {
1826
+ const chat = useChat({
1827
+ adapter: ${adapter},${ctx.tools.length > 0 ? `
1828
+ tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
1829
+ memory: ${memoryCall(ctx.memory)},` : ""}
1830
+ })
1831
+
1832
+ return (
1833
+ <ChatContainer>
1834
+ {chat.messages.map(message => (
1835
+ <Message key={message.id} message={message} />
1836
+ ))}
1837
+ <InputBar chat={chat} />
1838
+ </ChatContainer>
1839
+ )
1772
1840
  }
1773
- var DEFAULT_WATCH = [
1774
- "**/*.ts",
1775
- "**/*.tsx",
1776
- "**/*.mjs",
1777
- "**/*.json",
1778
- ".agentskit.config.*"
1779
- ];
1780
- var DEFAULT_IGNORE = [
1781
- "**/node_modules/**",
1782
- "**/dist/**",
1783
- "**/build/**",
1784
- "**/.next/**",
1785
- "**/.turbo/**",
1786
- "**/.git/**",
1787
- "**/coverage/**",
1788
- "**/*.test.ts",
1789
- "**/*.spec.ts"
1790
- ];
1791
- function startDev(options) {
1792
- const entry = path3.resolve(process.cwd(), options.entry);
1793
- if (!fs.existsSync(entry)) {
1794
- throw new Error(`Entry file not found: ${entry}`);
1795
- }
1796
- const stdout = options.stdout ?? process.stdout;
1797
- const stderr = options.stderr ?? process.stderr;
1798
- const debounceMs = options.debounceMs ?? 200;
1799
- const isTs = entry.endsWith(".ts") || entry.endsWith(".tsx");
1800
- const cmd = isTs ? "tsx" : "node";
1801
- const baseArgs = [entry, ...options.scriptArgs ?? []];
1802
- const spawnFn = options.spawn ?? ((c, a) => child_process.spawn(c, a, {
1803
- stdio: ["inherit", "pipe", "pipe"],
1804
- env: { ...process.env, FORCE_COLOR: "1" }
1805
- }));
1806
- const watchPaths = options.watch ?? DEFAULT_WATCH;
1807
- const ignorePaths = [...DEFAULT_IGNORE, ...options.ignore ?? []];
1808
- const watcherFactory = options.watcher ?? ((paths, opts) => chokidar__default.default.watch(paths, { ignored: opts.ignored, ignoreInitial: true }));
1809
- const watcher = watcherFactory(watchPaths, { ignored: ignorePaths });
1810
- let child;
1811
- let restartCount = 0;
1812
- let restartTimer;
1813
- let stopped = false;
1814
- let resolveDone;
1815
- const done = new Promise((r) => {
1816
- resolveDone = r;
1817
- });
1818
- const banner = (msg, color = "green") => {
1819
- const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
1820
- stdout.write(kleur__default.default[color](`[agentskit dev ${time}] `) + msg + "\n");
1841
+ `,
1842
+ ".env.example": envContent,
1843
+ ".gitignore": `node_modules
1844
+ dist
1845
+ .env
1846
+ .env.local
1847
+ .agentskit-history.*
1848
+ `,
1849
+ "README.md": readmeFor(ctx)
1821
1850
  };
1822
- const startChild = () => {
1823
- restartCount++;
1824
- banner(`\u25B8 starting ${kleur__default.default.bold(path3.basename(entry))} (restart #${restartCount - 1})`, "cyan");
1825
- const c = spawnFn(cmd, baseArgs);
1826
- child = c;
1827
- c.stdout?.on("data", (d) => stdout.write(d));
1828
- c.stderr?.on("data", (d) => stderr.write(d));
1829
- c.on("exit", (code, signal) => {
1830
- if (stopped) return;
1831
- if (signal === "SIGTERM" || signal === "SIGINT") return;
1832
- if (code === 0) {
1833
- banner(`\u2713 exited cleanly \u2014 waiting for changes`, "green");
1834
- } else {
1835
- banner(`\u2717 exited with code ${code} \u2014 waiting for changes`, "red");
1836
- }
1837
- });
1851
+ }
1852
+ function inkStarter(ctx) {
1853
+ const deps = {
1854
+ "@agentskit/ink": "^0.4.0",
1855
+ ink: "^7.0.0",
1856
+ react: "^19.0.0"
1838
1857
  };
1839
- const restart = (path4) => {
1840
- if (restartTimer) clearTimeout(restartTimer);
1841
- restartTimer = setTimeout(() => {
1842
- restartTimer = void 0;
1843
- banner(`\u21BB change detected \u2014 ${path4}`, "yellow");
1844
- if (child && !child.killed && child.exitCode === null) {
1845
- child.kill("SIGTERM");
1846
- }
1847
- setTimeout(startChild, 80);
1848
- }, debounceMs);
1858
+ if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
1859
+ if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
1860
+ if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
1861
+ return {
1862
+ "package.json": JSON.stringify(
1863
+ {
1864
+ name: "agentskit-ink-app",
1865
+ private: true,
1866
+ type: "module",
1867
+ scripts: {
1868
+ dev: "tsx src/index.tsx",
1869
+ start: "tsx src/index.tsx"
1870
+ },
1871
+ dependencies: deps,
1872
+ devDependencies: {
1873
+ "@types/react": "^19.0.0",
1874
+ "@types/react-dom": "^19.0.0",
1875
+ tsx: "^4.20.0",
1876
+ typescript: "^5.5.0"
1877
+ }
1878
+ },
1879
+ null,
1880
+ 2
1881
+ ) + "\n",
1882
+ "tsconfig.json": JSON.stringify(
1883
+ {
1884
+ compilerOptions: {
1885
+ target: "ES2022",
1886
+ module: "ESNext",
1887
+ moduleResolution: "bundler",
1888
+ jsx: "react-jsx",
1889
+ strict: true,
1890
+ noEmit: true,
1891
+ skipLibCheck: true
1892
+ },
1893
+ include: ["src"]
1894
+ },
1895
+ null,
1896
+ 2
1897
+ ) + "\n",
1898
+ "src/index.tsx": `import React from 'react'
1899
+ import { render } from 'ink'
1900
+ import { ChatContainer, InputBar, Message, useChat } from '@agentskit/ink'
1901
+ ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}
1902
+ ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}function App() {
1903
+ const chat = useChat({
1904
+ adapter: ${adapterCall(ctx.provider)},${ctx.tools.length > 0 ? `
1905
+ tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
1906
+ memory: ${memoryCall(ctx.memory)},` : ""}
1907
+ })
1908
+
1909
+ return (
1910
+ <ChatContainer>
1911
+ {chat.messages.map(message => (
1912
+ <Message key={message.id} message={message} />
1913
+ ))}
1914
+ <InputBar chat={chat} />
1915
+ </ChatContainer>
1916
+ )
1917
+ }
1918
+
1919
+ render(<App />)
1920
+ `,
1921
+ ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
1922
+ ` : "# No API key required for the local provider\n",
1923
+ ".gitignore": `node_modules
1924
+ .env
1925
+ .env.local
1926
+ .agentskit-history.*
1927
+ `,
1928
+ "README.md": readmeFor(ctx)
1849
1929
  };
1850
- watcher.on("change", restart);
1851
- watcher.on("add", restart);
1852
- watcher.on("unlink", restart);
1853
- startChild();
1854
- const stop = async () => {
1855
- if (stopped) return;
1856
- stopped = true;
1857
- if (restartTimer) clearTimeout(restartTimer);
1858
- if (child && !child.killed && child.exitCode === null) {
1859
- child.kill("SIGTERM");
1860
- }
1861
- await watcher.close();
1862
- banner(`stopped`, "cyan");
1863
- resolveDone();
1930
+ }
1931
+ function runtimeStarter(ctx) {
1932
+ const deps = {
1933
+ "@agentskit/runtime": "^0.4.0"
1864
1934
  };
1865
- if (process.stdin.isTTY && process.stdin.setRawMode) {
1866
- process.stdin.setRawMode(true);
1867
- process.stdin.resume();
1868
- process.stdin.on("data", (data) => {
1869
- const key = data.toString();
1870
- if (key === "r") restart("manual");
1871
- if (key === "q" || key === "") void stop();
1872
- });
1873
- }
1935
+ if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
1936
+ if (ctx.tools.length > 0) deps["@agentskit/tools"] = "^0.4.0";
1937
+ if (ctx.memory !== "none") deps["@agentskit/memory"] = "^0.4.0";
1874
1938
  return {
1875
- done,
1876
- stop,
1877
- restarts: () => restartCount
1939
+ "package.json": JSON.stringify(
1940
+ {
1941
+ name: "agentskit-runtime-app",
1942
+ private: true,
1943
+ type: "module",
1944
+ scripts: {
1945
+ start: "tsx src/index.ts",
1946
+ dev: "tsx src/index.ts"
1947
+ },
1948
+ dependencies: deps,
1949
+ devDependencies: {
1950
+ tsx: "^4.20.0",
1951
+ typescript: "^5.5.0"
1952
+ }
1953
+ },
1954
+ null,
1955
+ 2
1956
+ ) + "\n",
1957
+ "tsconfig.json": JSON.stringify(
1958
+ {
1959
+ compilerOptions: {
1960
+ target: "ES2022",
1961
+ module: "ESNext",
1962
+ moduleResolution: "bundler",
1963
+ strict: true,
1964
+ noEmit: true,
1965
+ skipLibCheck: true
1966
+ },
1967
+ include: ["src"]
1968
+ },
1969
+ null,
1970
+ 2
1971
+ ) + "\n",
1972
+ "src/index.ts": `import { createRuntime } from '@agentskit/runtime'
1973
+ ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}${memoryImport(ctx.memory)}
1974
+ ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}const runtime = createRuntime({
1975
+ adapter: ${adapterCall(ctx.provider)},${ctx.tools.length > 0 ? `
1976
+ tools: ${toolList(ctx.tools)},` : ""}${ctx.memory !== "none" ? `
1977
+ memory: ${memoryCall(ctx.memory)},` : ""}
1978
+ maxSteps: 10,
1979
+ })
1980
+
1981
+ const task = process.argv.slice(2).join(' ') || 'Say hello and tell me one fact about TypeScript.'
1982
+ const result = await runtime.run(task)
1983
+
1984
+ console.log(result.content)
1985
+ console.log(\`\\n\u2014 \${result.steps} steps \xB7 \${result.toolCalls.length} tool calls \xB7 \${result.durationMs}ms\`)
1986
+ `,
1987
+ ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
1988
+ ` : "# No API key required for the local provider\n",
1989
+ ".gitignore": `node_modules
1990
+ .env
1991
+ .env.local
1992
+ .agentskit-history.*
1993
+ `,
1994
+ "README.md": readmeFor(ctx)
1878
1995
  };
1879
1996
  }
1880
- async function startTunnel(options) {
1881
- const stdout = options.stdout ?? process.stdout;
1882
- const open = options.open ?? (async (opts) => {
1883
- const lt = (await import('localtunnel')).default;
1884
- return await lt(opts);
1885
- });
1886
- const banner = (msg, color = "green") => {
1887
- const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
1888
- stdout.write(kleur__default.default[color](`[agentskit tunnel ${time}] `) + msg + "\n");
1997
+ function multiAgentStarter(ctx) {
1998
+ const deps = {
1999
+ "@agentskit/runtime": "^0.4.0",
2000
+ "@agentskit/skills": "^0.4.0"
1889
2001
  };
1890
- banner(`opening tunnel to ${options.host ?? "localhost"}:${options.port}...`, "cyan");
1891
- const tunnel = await open({
1892
- port: options.port,
1893
- subdomain: options.subdomain,
1894
- local_host: options.host
1895
- });
1896
- let requests = 0;
1897
- let stopped = false;
1898
- let resolveDone;
1899
- const done = new Promise((r) => {
1900
- resolveDone = r;
1901
- });
1902
- tunnel.on("request", () => {
1903
- requests++;
1904
- });
1905
- tunnel.on("close", () => {
1906
- if (stopped) return;
1907
- banner(`tunnel closed by remote`, "yellow");
1908
- resolveDone();
1909
- });
1910
- tunnel.on("error", (...args) => {
1911
- const err = args[0];
1912
- banner(`error: ${err?.message ?? "unknown"}`, "red");
1913
- });
1914
- banner(`\u2713 ready`, "green");
1915
- stdout.write("\n");
1916
- stdout.write(` ${kleur__default.default.bold("Public URL:")} ${kleur__default.default.cyan(tunnel.url)}
1917
- `);
1918
- stdout.write(` ${kleur__default.default.bold("Local:")} http://${options.host ?? "localhost"}:${options.port}
1919
- `);
1920
- stdout.write("\n");
1921
- stdout.write(kleur__default.default.dim(` Forward webhooks here, then ${kleur__default.default.bold("Ctrl+C")} to stop.
2002
+ if (ctx.provider !== "demo") deps["@agentskit/adapters"] = "^0.4.0";
2003
+ if (ctx.tools.length === 0) ctx.tools = ["web_search"];
2004
+ deps["@agentskit/tools"] = "^0.4.0";
2005
+ return {
2006
+ "package.json": JSON.stringify(
2007
+ {
2008
+ name: "agentskit-multi-agent",
2009
+ private: true,
2010
+ type: "module",
2011
+ scripts: {
2012
+ start: "tsx src/index.ts",
2013
+ dev: "tsx src/index.ts"
2014
+ },
2015
+ dependencies: deps,
2016
+ devDependencies: {
2017
+ tsx: "^4.20.0",
2018
+ typescript: "^5.5.0"
2019
+ }
2020
+ },
2021
+ null,
2022
+ 2
2023
+ ) + "\n",
2024
+ "tsconfig.json": JSON.stringify(
2025
+ {
2026
+ compilerOptions: {
2027
+ target: "ES2022",
2028
+ module: "ESNext",
2029
+ moduleResolution: "bundler",
2030
+ strict: true,
2031
+ noEmit: true,
2032
+ skipLibCheck: true
2033
+ },
2034
+ include: ["src"]
2035
+ },
2036
+ null,
2037
+ 2
2038
+ ) + "\n",
2039
+ "src/index.ts": `import { createRuntime } from '@agentskit/runtime'
2040
+ import { planner, researcher } from '@agentskit/skills'
2041
+ ${adapterImport(ctx.provider)}${toolImports(ctx.tools)}
2042
+ ${ctx.provider === "demo" ? demoAdapterSnippet() : ""}const runtime = createRuntime({
2043
+ adapter: ${adapterCall(ctx.provider)},
2044
+ maxSteps: 10,
2045
+ maxDelegationDepth: 2,
2046
+ })
2047
+
2048
+ const task = process.argv.slice(2).join(' ') || 'Research the current state of WebGPU and summarize.'
2049
+
2050
+ const result = await runtime.run(task, {
2051
+ skill: planner,
2052
+ delegates: {
2053
+ researcher: {
2054
+ skill: researcher,
2055
+ tools: ${toolList(ctx.tools)},
2056
+ maxSteps: 5,
2057
+ },
2058
+ },
2059
+ })
1922
2060
 
1923
- `));
1924
- options.onReady?.(tunnel.url);
1925
- const stop = async () => {
1926
- if (stopped) return;
1927
- stopped = true;
1928
- tunnel.close();
1929
- banner(`stopped \u2014 proxied ${requests} request${requests === 1 ? "" : "s"}`, "cyan");
1930
- resolveDone();
1931
- };
1932
- if (process.stdin.isTTY) {
1933
- process.on("SIGINT", () => {
1934
- void stop();
1935
- });
1936
- }
1937
- return {
1938
- url: tunnel.url,
1939
- done,
1940
- stop,
1941
- requests: () => requests
2061
+ console.log(result.content)
2062
+ console.log(\`\\n\u2014 \${result.steps} steps \xB7 \${result.toolCalls.length} tool calls\`)
2063
+ `,
2064
+ ".env.example": PROVIDER_ENV_KEY[ctx.provider] ? `${PROVIDER_ENV_KEY[ctx.provider]}=
2065
+ ` : "# No API key required for the local provider\n",
2066
+ ".gitignore": `node_modules
2067
+ .env
2068
+ .env.local
2069
+ `,
2070
+ "README.md": readmeFor(ctx)
1942
2071
  };
1943
2072
  }
2073
+ function readmeFor(ctx) {
2074
+ const installCmd = ctx.pm === "npm" ? "npm install" : `${ctx.pm} install`;
2075
+ const runCmd = ctx.pm === "npm" ? "npm run dev" : `${ctx.pm} dev`;
2076
+ const envKey = PROVIDER_ENV_KEY[ctx.provider];
2077
+ return `# AgentsKit ${ctx.template} starter
1944
2078
 
1945
- // src/commands.ts
1946
- function mergeWithConfig(options, config) {
1947
- if (!config) return options;
1948
- const d = config.defaults ?? {};
1949
- const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
1950
- return {
1951
- ...options,
1952
- // Config defaults only apply if CLI flag wasn't set
1953
- provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
1954
- model: options.model ?? d.model,
1955
- apiKey: resolvedApiKey,
1956
- baseUrl: options.baseUrl ?? d.baseUrl,
1957
- tools: options.tools ?? d.tools,
1958
- skill: options.skill ?? d.skill,
1959
- system: options.system ?? d.system,
1960
- memoryBackend: options.memoryBackend ?? d.memoryBackend
2079
+ Generated by \`agentskit init\`.
2080
+
2081
+ ## Stack
2082
+
2083
+ - **Template**: \`${ctx.template}\`
2084
+ - **Provider**: \`${ctx.provider}\`${ctx.tools.length ? `
2085
+ - **Tools**: ${ctx.tools.map((t) => `\`${t}\``).join(", ")}` : ""}${ctx.memory !== "none" ? `
2086
+ - **Memory**: \`${ctx.memory}\`` : ""}
2087
+
2088
+ ## Run
2089
+
2090
+ \`\`\`bash
2091
+ ${installCmd}
2092
+ ${envKey ? `cp .env.example .env
2093
+ # add ${envKey}=...` : "# No API key required"}
2094
+ ${runCmd}
2095
+ \`\`\`
2096
+
2097
+ ## Next steps
2098
+
2099
+ - Open the AgentsKit docs at https://www.agentskit.io/docs
2100
+ - Add a custom skill: https://www.agentskit.io/docs/concepts/skill
2101
+ - Wire up RAG: https://www.agentskit.io/docs/recipes/rag-chat
2102
+
2103
+ ## License
2104
+
2105
+ ISC
2106
+ `;
2107
+ }
2108
+ var TEMPLATE_FN = {
2109
+ react: reactStarter,
2110
+ ink: inkStarter,
2111
+ runtime: runtimeStarter,
2112
+ "multi-agent": multiAgentStarter
2113
+ };
2114
+ async function writeStarterProject(options) {
2115
+ const ctx = {
2116
+ template: options.template,
2117
+ provider: options.provider ?? "demo",
2118
+ tools: options.tools ?? [],
2119
+ memory: options.memory ?? "none",
2120
+ pm: options.packageManager ?? "pnpm"
1961
2121
  };
2122
+ const files = TEMPLATE_FN[ctx.template](ctx);
2123
+ await promises.mkdir(options.targetDir, { recursive: true });
2124
+ await Promise.all(
2125
+ Object.entries(files).map(async ([relativePath, content]) => {
2126
+ const absolutePath = path__default.default.join(options.targetDir, relativePath);
2127
+ await promises.mkdir(path__default.default.dirname(absolutePath), { recursive: true });
2128
+ await promises.writeFile(absolutePath, content, "utf8");
2129
+ })
2130
+ );
1962
2131
  }
1963
- function createCli() {
1964
- const program = new commander.Command();
1965
- program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1966
- program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
1967
- if (options.listSessions) {
1968
- const sessions = listSessions();
1969
- if (sessions.length === 0) {
1970
- process.stdout.write("No saved sessions for this directory.\n");
1971
- return;
1972
- }
1973
- for (const s of sessions) {
1974
- const { id, updatedAt, messageCount, preview, model } = s.metadata;
1975
- process.stdout.write(
1976
- `${id} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}
1977
- ${preview}
1978
- `
1979
- );
2132
+ async function runInteractiveInit(defaults = {}) {
2133
+ process.stdout.write(`
2134
+ ${kleur__default.default.bold().green("\u25B2")} ${kleur__default.default.bold("agentskit init")}
2135
+ `);
2136
+ process.stdout.write(kleur__default.default.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
2137
+ try {
2138
+ const targetDir = await prompts.input({
2139
+ message: "Project directory:",
2140
+ default: defaults.dir ?? "agentskit-app",
2141
+ validate: (value) => {
2142
+ if (!value.trim()) return "A directory name is required.";
2143
+ const abs = path__default.default.resolve(process.cwd(), value);
2144
+ if (fs.existsSync(abs)) return `${value} already exists. Pick a different name.`;
2145
+ return true;
1980
2146
  }
1981
- return;
1982
- }
1983
- const config = options.config !== false ? await loadConfig() : void 0;
1984
- const merged = mergeWithConfig(options, config);
1985
- const session = resolveSession({
1986
- explicitPath: options.memory,
1987
- forceNew: Boolean(options.new),
1988
- resumeId: options.resume
1989
2147
  });
1990
- if (!session.isNew && !options.memory) {
1991
- process.stdout.write(
1992
- `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
1993
- `
1994
- );
1995
- }
1996
- const chatOptions = {
1997
- apiKey: merged.apiKey ?? options.apiKey,
1998
- baseUrl: merged.baseUrl ?? options.baseUrl,
1999
- provider: merged.provider,
2000
- model: merged.model,
2001
- system: merged.system ?? options.system,
2002
- memoryPath: session.file,
2003
- sessionId: session.id,
2004
- tools: merged.tools ?? options.tools,
2005
- skill: merged.skill ?? options.skill,
2006
- memoryBackend: merged.memoryBackend ?? options.memoryBackend,
2007
- agentsKitConfig: config
2008
- };
2009
- process.stdout.write(`${renderChatHeader(chatOptions)}
2010
- `);
2011
- const instance = ink.render(React3__default.default.createElement(ChatApp, chatOptions));
2012
- await instance.waitUntilExit();
2013
- if (options.memory) {
2014
- process.stdout.write(
2015
- `
2016
- Session saved to ${session.file}. Resume with --memory ${session.file}
2017
- `
2018
- );
2019
- } else {
2020
- process.stdout.write(
2021
- `
2022
- Session saved. Resume with:
2023
- agentskit chat --resume ${session.id}
2024
- Or start fresh with:
2025
- agentskit chat --new
2026
- `
2027
- );
2148
+ const template = await prompts.select({
2149
+ message: "Template:",
2150
+ default: defaults.template ?? "react",
2151
+ choices: [
2152
+ { name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
2153
+ { name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
2154
+ { name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
2155
+ { name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
2156
+ ]
2157
+ });
2158
+ const provider = await prompts.select({
2159
+ message: "LLM provider:",
2160
+ default: "demo",
2161
+ choices: [
2162
+ { name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
2163
+ { name: "OpenAI", value: "openai" },
2164
+ { name: "Anthropic", value: "anthropic" },
2165
+ { name: "Gemini", value: "gemini" },
2166
+ { name: "Ollama (local, no key)", value: "ollama" }
2167
+ ]
2168
+ });
2169
+ let tools = [];
2170
+ if (template !== "react") {
2171
+ tools = await prompts.checkbox({
2172
+ message: "Tools (space to toggle, enter to confirm):",
2173
+ choices: [
2174
+ { name: "web_search", value: "web_search" },
2175
+ { name: "filesystem", value: "filesystem" },
2176
+ { name: "shell", value: "shell" }
2177
+ ]
2178
+ });
2028
2179
  }
2029
- });
2030
- program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
2031
- const task = options.task ?? positionalTask;
2032
- if (!task) {
2033
- process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
2034
- process.exit(1);
2180
+ const memory = await prompts.select({
2181
+ message: "Memory backend:",
2182
+ default: "none",
2183
+ choices: [
2184
+ { name: "None (stateless)", value: "none" },
2185
+ { name: "File (JSON on disk)", value: "file" },
2186
+ { name: "SQLite (better-sqlite3)", value: "sqlite" }
2187
+ ]
2188
+ });
2189
+ const packageManager = await prompts.select({
2190
+ message: "Package manager:",
2191
+ default: "pnpm",
2192
+ choices: [
2193
+ { name: "pnpm", value: "pnpm" },
2194
+ { name: "npm", value: "npm" },
2195
+ { name: "yarn", value: "yarn" },
2196
+ { name: "bun", value: "bun" }
2197
+ ]
2198
+ });
2199
+ process.stdout.write("\n" + kleur__default.default.dim(" Summary:\n"));
2200
+ process.stdout.write(kleur__default.default.dim(` dir ${targetDir}
2201
+ `));
2202
+ process.stdout.write(kleur__default.default.dim(` template ${template}
2203
+ `));
2204
+ process.stdout.write(kleur__default.default.dim(` provider ${provider}
2205
+ `));
2206
+ if (tools.length) process.stdout.write(kleur__default.default.dim(` tools ${tools.join(", ")}
2207
+ `));
2208
+ process.stdout.write(kleur__default.default.dim(` memory ${memory}
2209
+ `));
2210
+ process.stdout.write(kleur__default.default.dim(` pm ${packageManager}
2211
+
2212
+ `));
2213
+ const proceed = await prompts.confirm({ message: "Generate?", default: true });
2214
+ if (!proceed) {
2215
+ process.stdout.write(kleur__default.default.yellow("Cancelled.\n"));
2216
+ return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
2035
2217
  }
2036
- const config = options.config !== false ? await loadConfig() : void 0;
2037
- const merged = mergeWithConfig(options, config);
2038
- if (options.pretty) {
2039
- ink.render(React3__default.default.createElement(RunApp, { task, options }));
2040
- } else {
2041
- try {
2042
- await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
2043
- } catch (err) {
2044
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2045
- `);
2046
- process.exit(1);
2218
+ return {
2219
+ cancelled: false,
2220
+ options: {
2221
+ targetDir: path__default.default.resolve(process.cwd(), targetDir),
2222
+ template,
2223
+ provider,
2224
+ tools,
2225
+ memory,
2226
+ packageManager
2047
2227
  }
2228
+ };
2229
+ } catch (err) {
2230
+ if (err.name === "ExitPromptError") {
2231
+ process.stdout.write(kleur__default.default.yellow("\nCancelled.\n"));
2232
+ return { cancelled: true, options: { targetDir: "", template: "react" } };
2048
2233
  }
2049
- });
2234
+ throw err;
2235
+ }
2236
+ }
2237
+ function printNextSteps(options) {
2238
+ const dir = path__default.default.relative(process.cwd(), options.targetDir) || ".";
2239
+ const pm = options.packageManager ?? "pnpm";
2240
+ const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
2241
+ const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
2242
+ process.stdout.write("\n" + kleur__default.default.green("\u2713 Created starter at ") + kleur__default.default.bold(dir) + "\n\n");
2243
+ process.stdout.write(kleur__default.default.bold("Next steps:\n\n"));
2244
+ process.stdout.write(` ${kleur__default.default.cyan("cd")} ${dir}
2245
+ `);
2246
+ process.stdout.write(` ${kleur__default.default.cyan(installCmd)}
2247
+ `);
2248
+ if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
2249
+ process.stdout.write(
2250
+ ` ${kleur__default.default.cyan("cp")} .env.example .env ${kleur__default.default.dim("# add your API key")}
2251
+ `
2252
+ );
2253
+ }
2254
+ process.stdout.write(` ${kleur__default.default.cyan(runCmd)}
2255
+
2256
+ `);
2257
+ process.stdout.write(kleur__default.default.dim(" Docs: https://www.agentskit.io/docs\n\n"));
2258
+ }
2259
+
2260
+ // src/commands/init.ts
2261
+ function registerInitCommand(program) {
2050
2262
  program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|ink|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
2051
2263
  const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
2052
2264
  let resolved;
2053
2265
  if (isCi) {
2054
2266
  const template = rawOptions.template ?? "react";
2055
2267
  resolved = {
2056
- targetDir: path3__default.default.resolve(process.cwd(), rawOptions.dir),
2268
+ targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
2057
2269
  template,
2058
2270
  provider: rawOptions.provider ?? "demo",
2059
2271
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
@@ -2073,13 +2285,282 @@ Or start fresh with:
2073
2285
  await writeStarterProject(resolved);
2074
2286
  if (isCi) {
2075
2287
  process.stdout.write(
2076
- `Created ${resolved.template} starter in ${path3__default.default.relative(process.cwd(), resolved.targetDir) || "."}
2288
+ `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
2077
2289
  `
2078
2290
  );
2079
2291
  } else {
2080
- printNextSteps(resolved);
2292
+ printNextSteps(resolved);
2293
+ }
2294
+ });
2295
+ }
2296
+ var PROVIDER_ENV_KEYS = {
2297
+ openai: "OPENAI_API_KEY",
2298
+ anthropic: "ANTHROPIC_API_KEY",
2299
+ gemini: "GEMINI_API_KEY",
2300
+ deepseek: "DEEPSEEK_API_KEY",
2301
+ grok: "XAI_API_KEY",
2302
+ kimi: "KIMI_API_KEY"
2303
+ };
2304
+ var PROVIDER_REACH_URLS = {
2305
+ openai: "https://api.openai.com/v1/models",
2306
+ anthropic: "https://api.anthropic.com/v1/messages",
2307
+ gemini: "https://generativelanguage.googleapis.com/v1beta/models",
2308
+ ollama: "http://localhost:11434/api/tags"
2309
+ };
2310
+ async function checkNodeVersion() {
2311
+ const major = Number(process.versions.node.split(".")[0]);
2312
+ if (Number.isNaN(major)) {
2313
+ return { status: "fail", name: "Node version", detail: "Could not parse process.versions.node" };
2314
+ }
2315
+ if (major < 22) {
2316
+ return {
2317
+ status: "fail",
2318
+ name: "Node version",
2319
+ detail: `Node ${process.versions.node} (need 22+)`,
2320
+ fix: "Install Node 22 LTS or newer (https://nodejs.org)"
2321
+ };
2322
+ }
2323
+ if (major === 25) {
2324
+ return {
2325
+ status: "warn",
2326
+ name: "Node version",
2327
+ detail: `Node ${process.versions.node} \u2014 Docusaurus apps may break here`,
2328
+ fix: "Use Node 22 LTS for the legacy docs app, or stay on 25 for everything else"
2329
+ };
2330
+ }
2331
+ return { status: "pass", name: "Node version", detail: `Node ${process.versions.node}` };
2332
+ }
2333
+ async function checkPnpm() {
2334
+ const cwd = process.cwd();
2335
+ const hasPnpm = fs.existsSync(path.join(cwd, "pnpm-lock.yaml")) || fs.existsSync(path.join(cwd, "pnpm-workspace.yaml"));
2336
+ if (hasPnpm) {
2337
+ return { status: "pass", name: "Package manager", detail: "pnpm detected (lockfile)" };
2338
+ }
2339
+ if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
2340
+ return { status: "warn", name: "Package manager", detail: "npm detected \u2014 pnpm recommended for monorepo workflows" };
2341
+ }
2342
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
2343
+ return { status: "pass", name: "Package manager", detail: "yarn detected" };
2344
+ }
2345
+ if (fs.existsSync(path.join(cwd, "bun.lock")) || fs.existsSync(path.join(cwd, "bun.lockb"))) {
2346
+ return { status: "pass", name: "Package manager", detail: "bun detected" };
2347
+ }
2348
+ return {
2349
+ status: "skip",
2350
+ name: "Package manager",
2351
+ detail: "No lockfile found in cwd"
2352
+ };
2353
+ }
2354
+ async function checkPackageJson() {
2355
+ const path5 = path.join(process.cwd(), "package.json");
2356
+ if (!fs.existsSync(path5)) {
2357
+ return {
2358
+ status: "warn",
2359
+ name: "package.json",
2360
+ detail: "No package.json in cwd",
2361
+ fix: "Run from a project directory (or use `agentskit init` to create one)"
2362
+ };
2363
+ }
2364
+ try {
2365
+ const pkg = JSON.parse(await promises.readFile(path5, "utf8"));
2366
+ const deps = {
2367
+ ...pkg.dependencies ?? {},
2368
+ ...pkg.devDependencies ?? {}
2369
+ };
2370
+ const akDeps = Object.entries(deps).filter(([name]) => name.startsWith("@agentskit/"));
2371
+ if (akDeps.length === 0) {
2372
+ return { status: "skip", name: "AgentsKit packages", detail: "No @agentskit/* deps found in package.json" };
2373
+ }
2374
+ return {
2375
+ status: "pass",
2376
+ name: "AgentsKit packages",
2377
+ detail: `${akDeps.length} installed: ${akDeps.map(([n]) => n.replace("@agentskit/", "")).join(", ")}`
2378
+ };
2379
+ } catch (err) {
2380
+ return {
2381
+ status: "fail",
2382
+ name: "package.json",
2383
+ detail: `Could not parse: ${err.message}`
2384
+ };
2385
+ }
2386
+ }
2387
+ async function checkProviderEnv(provider) {
2388
+ const envKey = PROVIDER_ENV_KEYS[provider];
2389
+ if (!envKey) {
2390
+ return { status: "skip", name: `${provider} API key`, detail: "No env-key requirement for this provider" };
2391
+ }
2392
+ const value = process.env[envKey];
2393
+ if (!value) {
2394
+ return {
2395
+ status: "skip",
2396
+ name: `${provider} API key`,
2397
+ detail: `${envKey} not set`,
2398
+ fix: `export ${envKey}=... (only needed if you use ${provider})`
2399
+ };
2400
+ }
2401
+ if (value.length < 16) {
2402
+ return {
2403
+ status: "warn",
2404
+ name: `${provider} API key`,
2405
+ detail: `${envKey} looks too short (${value.length} chars)`,
2406
+ fix: "Verify the key is complete and not truncated"
2407
+ };
2408
+ }
2409
+ return { status: "pass", name: `${provider} API key`, detail: `${envKey} set (${value.length} chars)` };
2410
+ }
2411
+ async function checkProviderReachable(provider, fetchImpl = fetch, timeoutMs = 4e3) {
2412
+ const url = PROVIDER_REACH_URLS[provider];
2413
+ if (!url) {
2414
+ return { status: "skip", name: `${provider} reachable`, detail: "No reachability check configured" };
2415
+ }
2416
+ const envKey = PROVIDER_ENV_KEYS[provider];
2417
+ if (envKey && !process.env[envKey]) {
2418
+ return { status: "skip", name: `${provider} reachable`, detail: "Skipped \u2014 no API key configured" };
2419
+ }
2420
+ const controller = new AbortController();
2421
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2422
+ try {
2423
+ const res = await fetchImpl(url, {
2424
+ method: "GET",
2425
+ signal: controller.signal
2426
+ });
2427
+ if (res.status >= 200 && res.status < 400) {
2428
+ return { status: "pass", name: `${provider} reachable`, detail: `${url} \u2192 ${res.status} OK` };
2429
+ }
2430
+ if (res.status === 401 || res.status === 403 || res.status === 405) {
2431
+ return { status: "pass", name: `${provider} reachable`, detail: `${url} \u2192 ${res.status} (host reachable)` };
2432
+ }
2433
+ return {
2434
+ status: "warn",
2435
+ name: `${provider} reachable`,
2436
+ detail: `${url} \u2192 HTTP ${res.status}`,
2437
+ fix: "Host reachable but returned unexpected status \u2014 check provider docs"
2438
+ };
2439
+ } catch (err) {
2440
+ const reason = err.name === "AbortError" ? `timeout after ${timeoutMs}ms` : err.message;
2441
+ return {
2442
+ status: "fail",
2443
+ name: `${provider} reachable`,
2444
+ detail: `${url} \u2192 ${reason}`,
2445
+ fix: provider === "ollama" ? "Start Ollama: `ollama serve` (or install from https://ollama.com)" : "Check network / firewall / VPN settings"
2446
+ };
2447
+ } finally {
2448
+ clearTimeout(timer);
2449
+ }
2450
+ }
2451
+ async function checkConfig() {
2452
+ try {
2453
+ const config = await loadConfig();
2454
+ if (!config) {
2455
+ return { status: "skip", name: "AgentsKit config", detail: "No .agentskit.config or package.json#agentskit found" };
2456
+ }
2457
+ return {
2458
+ status: "pass",
2459
+ name: "AgentsKit config",
2460
+ detail: `loaded \u2014 defaults: ${JSON.stringify(config.defaults ?? {})}`
2461
+ };
2462
+ } catch (err) {
2463
+ return {
2464
+ status: "warn",
2465
+ name: "AgentsKit config",
2466
+ detail: `Could not load: ${err.message}`
2467
+ };
2468
+ }
2469
+ }
2470
+ async function runDoctor(options = {}) {
2471
+ const providers2 = options.providers ?? ["openai", "anthropic", "gemini", "ollama"];
2472
+ const fetchImpl = options.fetchImpl ?? fetch;
2473
+ const checks = [
2474
+ checkNodeVersion(),
2475
+ checkPnpm(),
2476
+ checkPackageJson(),
2477
+ checkConfig()
2478
+ ];
2479
+ for (const provider of providers2) {
2480
+ checks.push(checkProviderEnv(provider));
2481
+ if (!options.noNetwork) {
2482
+ checks.push(checkProviderReachable(provider, fetchImpl));
2483
+ }
2484
+ }
2485
+ const results = await Promise.all(checks);
2486
+ return {
2487
+ results,
2488
+ pass: results.filter((r) => r.status === "pass").length,
2489
+ warn: results.filter((r) => r.status === "warn").length,
2490
+ fail: results.filter((r) => r.status === "fail").length,
2491
+ skip: results.filter((r) => r.status === "skip").length
2492
+ };
2493
+ }
2494
+ var ICON = {
2495
+ pass: "\u2714",
2496
+ warn: "\u26A0",
2497
+ fail: "\u2718",
2498
+ skip: "\u25CB"
2499
+ };
2500
+ function renderReport(report, opts = {}) {
2501
+ const color = opts.color ?? true;
2502
+ const c = (code, text) => color ? `\x1B[${code}m${text}\x1B[0m` : text;
2503
+ const colorFor = {
2504
+ pass: (t) => c("32", t),
2505
+ // green
2506
+ warn: (t) => c("33", t),
2507
+ // yellow
2508
+ fail: (t) => c("31", t),
2509
+ // red
2510
+ skip: (t) => c("90", t)
2511
+ // dim
2512
+ };
2513
+ const lines = [];
2514
+ lines.push("");
2515
+ lines.push(` ${c("1;36", "\u26A1 AgentsKit Doctor")}`);
2516
+ lines.push(` ${c("90", "\u2500".repeat(50))}`);
2517
+ lines.push("");
2518
+ const groups = {
2519
+ "Environment": [],
2520
+ "Providers": [],
2521
+ "Network": []
2522
+ };
2523
+ for (const r of report.results) {
2524
+ if (r.name.includes("reachable")) {
2525
+ groups["Network"].push(r);
2526
+ } else if (r.name.includes("API key")) {
2527
+ groups["Providers"].push(r);
2528
+ } else {
2529
+ groups["Environment"].push(r);
2081
2530
  }
2082
- });
2531
+ }
2532
+ for (const [group, results] of Object.entries(groups)) {
2533
+ if (results.length === 0) continue;
2534
+ lines.push(` ${c("1", group)}`);
2535
+ for (const r of results) {
2536
+ const icon = colorFor[r.status](ICON[r.status]);
2537
+ const name = r.name.padEnd(28);
2538
+ const detail = r.detail ? c("90", r.detail) : "";
2539
+ lines.push(` ${icon} ${name} ${detail}`);
2540
+ if (r.fix && r.status !== "pass") {
2541
+ lines.push(` ${c("90", "\u21B3 " + r.fix)}`);
2542
+ }
2543
+ }
2544
+ lines.push("");
2545
+ }
2546
+ lines.push(` ${c("90", "\u2500".repeat(50))}`);
2547
+ const parts = [];
2548
+ if (report.pass > 0) parts.push(colorFor.pass(`${report.pass} passed`));
2549
+ if (report.warn > 0) parts.push(colorFor.warn(`${report.warn} warnings`));
2550
+ if (report.fail > 0) parts.push(colorFor.fail(`${report.fail} failed`));
2551
+ if (report.skip > 0) parts.push(colorFor.skip(`${report.skip} skipped`));
2552
+ lines.push(` ${parts.join(" \xB7 ")}`);
2553
+ if (report.fail === 0) {
2554
+ lines.push(` ${c("32", "\u2714 Ready to build agents.")}`);
2555
+ } else {
2556
+ lines.push(` ${c("31", "\u2718 Fix the issues above before continuing.")}`);
2557
+ }
2558
+ lines.push("");
2559
+ return lines.join("\n");
2560
+ }
2561
+
2562
+ // src/commands/doctor.ts
2563
+ function registerDoctorCommand(program) {
2083
2564
  program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
2084
2565
  "--providers <providers>",
2085
2566
  "Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
@@ -2096,6 +2577,117 @@ Or start fresh with:
2096
2577
  }
2097
2578
  if (report.fail > 0) process.exit(1);
2098
2579
  });
2580
+ }
2581
+ var DEFAULT_WATCH = [
2582
+ "**/*.ts",
2583
+ "**/*.tsx",
2584
+ "**/*.mjs",
2585
+ "**/*.json",
2586
+ ".agentskit.config.*"
2587
+ ];
2588
+ var DEFAULT_IGNORE = [
2589
+ "**/node_modules/**",
2590
+ "**/dist/**",
2591
+ "**/build/**",
2592
+ "**/.next/**",
2593
+ "**/.turbo/**",
2594
+ "**/.git/**",
2595
+ "**/coverage/**",
2596
+ "**/*.test.ts",
2597
+ "**/*.spec.ts"
2598
+ ];
2599
+ function startDev(options) {
2600
+ const entry = path.resolve(process.cwd(), options.entry);
2601
+ if (!fs.existsSync(entry)) {
2602
+ throw new Error(`Entry file not found: ${entry}`);
2603
+ }
2604
+ const stdout = options.stdout ?? process.stdout;
2605
+ const stderr = options.stderr ?? process.stderr;
2606
+ const debounceMs = options.debounceMs ?? 200;
2607
+ const isTs = entry.endsWith(".ts") || entry.endsWith(".tsx");
2608
+ const cmd = isTs ? "tsx" : "node";
2609
+ const baseArgs = [entry, ...options.scriptArgs ?? []];
2610
+ const spawnFn = options.spawn ?? ((c, a) => child_process.spawn(c, a, {
2611
+ stdio: ["inherit", "pipe", "pipe"],
2612
+ env: { ...process.env, FORCE_COLOR: "1" }
2613
+ }));
2614
+ const watchPaths = options.watch ?? DEFAULT_WATCH;
2615
+ const ignorePaths = [...DEFAULT_IGNORE, ...options.ignore ?? []];
2616
+ const watcherFactory = options.watcher ?? ((paths, opts) => chokidar__default.default.watch(paths, { ignored: opts.ignored, ignoreInitial: true }));
2617
+ const watcher = watcherFactory(watchPaths, { ignored: ignorePaths });
2618
+ let child;
2619
+ let restartCount = 0;
2620
+ let restartTimer;
2621
+ let stopped = false;
2622
+ let resolveDone;
2623
+ const done = new Promise((r) => {
2624
+ resolveDone = r;
2625
+ });
2626
+ const banner = (msg, color = "green") => {
2627
+ const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
2628
+ stdout.write(kleur__default.default[color](`[agentskit dev ${time}] `) + msg + "\n");
2629
+ };
2630
+ const startChild = () => {
2631
+ restartCount++;
2632
+ banner(`\u25B8 starting ${kleur__default.default.bold(path.basename(entry))} (restart #${restartCount - 1})`, "cyan");
2633
+ const c = spawnFn(cmd, baseArgs);
2634
+ child = c;
2635
+ c.stdout?.on("data", (d) => stdout.write(d));
2636
+ c.stderr?.on("data", (d) => stderr.write(d));
2637
+ c.on("exit", (code, signal) => {
2638
+ if (stopped) return;
2639
+ if (signal === "SIGTERM" || signal === "SIGINT") return;
2640
+ if (code === 0) {
2641
+ banner(`\u2713 exited cleanly \u2014 waiting for changes`, "green");
2642
+ } else {
2643
+ banner(`\u2717 exited with code ${code} \u2014 waiting for changes`, "red");
2644
+ }
2645
+ });
2646
+ };
2647
+ const restart = (path5) => {
2648
+ if (restartTimer) clearTimeout(restartTimer);
2649
+ restartTimer = setTimeout(() => {
2650
+ restartTimer = void 0;
2651
+ banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
2652
+ if (child && !child.killed && child.exitCode === null) {
2653
+ child.kill("SIGTERM");
2654
+ }
2655
+ setTimeout(startChild, 80);
2656
+ }, debounceMs);
2657
+ };
2658
+ watcher.on("change", restart);
2659
+ watcher.on("add", restart);
2660
+ watcher.on("unlink", restart);
2661
+ startChild();
2662
+ const stop = async () => {
2663
+ if (stopped) return;
2664
+ stopped = true;
2665
+ if (restartTimer) clearTimeout(restartTimer);
2666
+ if (child && !child.killed && child.exitCode === null) {
2667
+ child.kill("SIGTERM");
2668
+ }
2669
+ await watcher.close();
2670
+ banner(`stopped`, "cyan");
2671
+ resolveDone();
2672
+ };
2673
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
2674
+ process.stdin.setRawMode(true);
2675
+ process.stdin.resume();
2676
+ process.stdin.on("data", (data) => {
2677
+ const key = data.toString();
2678
+ if (key === "r") restart("manual");
2679
+ if (key === "q" || key === "") void stop();
2680
+ });
2681
+ }
2682
+ return {
2683
+ done,
2684
+ stop,
2685
+ restarts: () => restartCount
2686
+ };
2687
+ }
2688
+
2689
+ // src/commands/dev.ts
2690
+ function registerDevCommand(program) {
2099
2691
  program.command("dev [entry]").description("Run an entry file with hot-reload on file changes.").option("--watch <globs>", "Comma-separated glob patterns to watch").option("--ignore <globs>", "Comma-separated glob patterns to ignore").option("--debounce <ms>", "Debounce window before restart", "200").action(async (positional, options) => {
2100
2692
  const entry = positional ?? "src/index.ts";
2101
2693
  const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
@@ -2114,9 +2706,15 @@ Or start fresh with:
2114
2706
  process.exit(1);
2115
2707
  }
2116
2708
  });
2117
- program.command("config").description("Show or scaffold the AgentsKit config.").argument("[action]", 'Action: "init" to create a template, "show" to print the merged config.', "show").option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
2709
+ }
2710
+ function registerConfigCommand(program) {
2711
+ program.command("config").description("Show or scaffold the AgentsKit config.").argument(
2712
+ "[action]",
2713
+ 'Action: "init" to create a template, "show" to print the merged config.',
2714
+ "show"
2715
+ ).option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
2118
2716
  const isLocal = Boolean(options.local);
2119
- const targetPath = isLocal ? path3__default.default.join(process.cwd(), ".agentskit.config.json") : path3__default.default.join(os.homedir(), ".agentskit", "config.json");
2717
+ const targetPath = isLocal ? path__default.default.join(process.cwd(), ".agentskit.config.json") : path__default.default.join(os.homedir(), ".agentskit", "config.json");
2120
2718
  if (action === "show") {
2121
2719
  const config = await loadConfig();
2122
2720
  process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
@@ -2128,8 +2726,10 @@ Or start fresh with:
2128
2726
  process.exit(2);
2129
2727
  }
2130
2728
  if (fs.existsSync(targetPath) && !options.force) {
2131
- process.stderr.write(`Config already exists at ${targetPath}. Re-run with --force to overwrite.
2132
- `);
2729
+ process.stderr.write(
2730
+ `Config already exists at ${targetPath}. Re-run with --force to overwrite.
2731
+ `
2732
+ );
2133
2733
  process.exit(1);
2134
2734
  }
2135
2735
  const template = {
@@ -2141,7 +2741,7 @@ Or start fresh with:
2141
2741
  tools: "web_search,fetch_url"
2142
2742
  }
2143
2743
  };
2144
- fs.mkdirSync(path3__default.default.dirname(targetPath), { recursive: true });
2744
+ fs.mkdirSync(path__default.default.dirname(targetPath), { recursive: true });
2145
2745
  fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2146
2746
  process.stdout.write(
2147
2747
  `Wrote ${targetPath}
@@ -2151,6 +2751,74 @@ Edit it to taste, then run:
2151
2751
  `
2152
2752
  );
2153
2753
  });
2754
+ }
2755
+ async function startTunnel(options) {
2756
+ const stdout = options.stdout ?? process.stdout;
2757
+ const open = options.open ?? (async (opts) => {
2758
+ const lt = (await import('localtunnel')).default;
2759
+ return await lt(opts);
2760
+ });
2761
+ const banner = (msg, color = "green") => {
2762
+ const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
2763
+ stdout.write(kleur__default.default[color](`[agentskit tunnel ${time}] `) + msg + "\n");
2764
+ };
2765
+ banner(`opening tunnel to ${options.host ?? "localhost"}:${options.port}...`, "cyan");
2766
+ const tunnel = await open({
2767
+ port: options.port,
2768
+ subdomain: options.subdomain,
2769
+ local_host: options.host
2770
+ });
2771
+ let requests = 0;
2772
+ let stopped = false;
2773
+ let resolveDone;
2774
+ const done = new Promise((r) => {
2775
+ resolveDone = r;
2776
+ });
2777
+ tunnel.on("request", () => {
2778
+ requests++;
2779
+ });
2780
+ tunnel.on("close", () => {
2781
+ if (stopped) return;
2782
+ banner(`tunnel closed by remote`, "yellow");
2783
+ resolveDone();
2784
+ });
2785
+ tunnel.on("error", (...args) => {
2786
+ const err = args[0];
2787
+ banner(`error: ${err?.message ?? "unknown"}`, "red");
2788
+ });
2789
+ banner(`\u2713 ready`, "green");
2790
+ stdout.write("\n");
2791
+ stdout.write(` ${kleur__default.default.bold("Public URL:")} ${kleur__default.default.cyan(tunnel.url)}
2792
+ `);
2793
+ stdout.write(` ${kleur__default.default.bold("Local:")} http://${options.host ?? "localhost"}:${options.port}
2794
+ `);
2795
+ stdout.write("\n");
2796
+ stdout.write(kleur__default.default.dim(` Forward webhooks here, then ${kleur__default.default.bold("Ctrl+C")} to stop.
2797
+
2798
+ `));
2799
+ options.onReady?.(tunnel.url);
2800
+ const stop = async () => {
2801
+ if (stopped) return;
2802
+ stopped = true;
2803
+ tunnel.close();
2804
+ banner(`stopped \u2014 proxied ${requests} request${requests === 1 ? "" : "s"}`, "cyan");
2805
+ resolveDone();
2806
+ };
2807
+ if (process.stdin.isTTY) {
2808
+ process.on("SIGINT", () => {
2809
+ void stop();
2810
+ });
2811
+ }
2812
+ return {
2813
+ url: tunnel.url,
2814
+ done,
2815
+ stop,
2816
+ requests: () => requests
2817
+ };
2818
+ }
2819
+
2820
+ // src/commands/tunnel.ts
2821
+ function registerTunnelCommand(program) {
2154
2822
  program.command("tunnel <port>").description("Open a public URL pointing to a local port (great for webhooks).").option("--subdomain <name>", "Hint for a stable subdomain (provider may decline)").option("--host <host>", "Local hostname", "localhost").action(async (port, options) => {
2155
2823
  const portNum = Number(port);
2156
2824
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -2171,6 +2839,130 @@ Edit it to taste, then run:
2171
2839
  process.exit(1);
2172
2840
  }
2173
2841
  });
2842
+ }
2843
+
2844
+ // src/extensibility/rag/embedders.ts
2845
+ function createOpenAiEmbedder(config) {
2846
+ const model = config.model ?? "text-embedding-3-small";
2847
+ const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
2848
+ return async (text) => {
2849
+ const res = await fetch(`${baseUrl}/v1/embeddings`, {
2850
+ method: "POST",
2851
+ headers: {
2852
+ "content-type": "application/json",
2853
+ authorization: `Bearer ${config.apiKey}`
2854
+ },
2855
+ body: JSON.stringify({ model, input: text })
2856
+ });
2857
+ if (!res.ok) {
2858
+ const body = await res.text().catch(() => "");
2859
+ throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
2860
+ }
2861
+ const json = await res.json();
2862
+ const first = json.data?.[0]?.embedding;
2863
+ if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
2864
+ return first;
2865
+ };
2866
+ }
2867
+ function resolveEmbedder(config) {
2868
+ const embedder = config.embedder;
2869
+ const provider = embedder?.provider ?? "openai";
2870
+ if (provider !== "openai") {
2871
+ throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
2872
+ }
2873
+ const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
2874
+ if (!apiKey) {
2875
+ throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
2876
+ }
2877
+ return createOpenAiEmbedder({
2878
+ apiKey,
2879
+ model: embedder?.model,
2880
+ baseUrl: embedder?.baseUrl
2881
+ });
2882
+ }
2883
+ function buildRagFromConfig(options) {
2884
+ const cwd = options.cwd ?? process.cwd();
2885
+ const dir = path.resolve(cwd, options.config.dir ?? "./.agentskit-rag");
2886
+ const store = memory.fileVectorMemory({ path: `${dir}/store.json` });
2887
+ const embed = options.embedder ?? resolveEmbedder(options.config);
2888
+ return rag.createRAG({
2889
+ embed,
2890
+ store,
2891
+ chunkSize: options.config.chunkSize,
2892
+ topK: options.config.topK
2893
+ });
2894
+ }
2895
+ async function indexSources(rag, config, cwd) {
2896
+ const root = process.cwd();
2897
+ const sources = config.sources ?? [];
2898
+ const absolutePaths = [];
2899
+ for (const pattern of sources) {
2900
+ for await (const match of promises.glob(pattern, { cwd: root })) {
2901
+ absolutePaths.push(path.resolve(root, match));
2902
+ }
2903
+ }
2904
+ const documents = await Promise.all(
2905
+ absolutePaths.map(async (path5) => ({
2906
+ id: path5,
2907
+ content: await promises.readFile(path5, "utf8"),
2908
+ source: path5
2909
+ }))
2910
+ );
2911
+ if (documents.length > 0) await rag.ingest(documents);
2912
+ return { documentCount: documents.length, sources: absolutePaths };
2913
+ }
2914
+
2915
+ // src/commands/rag.ts
2916
+ function registerRagCommand(program) {
2917
+ const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
2918
+ rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
2919
+ "--source <glob>",
2920
+ "Glob to index (overrides config.rag.sources; repeatable)",
2921
+ (value, prev = []) => [...prev, value],
2922
+ []
2923
+ ).action(async (options) => {
2924
+ const config = await loadConfig();
2925
+ const rawConfig = config?.rag;
2926
+ const overrideSources = options.source;
2927
+ const ragConfig = {
2928
+ ...rawConfig ?? {},
2929
+ sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
2930
+ };
2931
+ if (!ragConfig.sources || ragConfig.sources.length === 0) {
2932
+ process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
2933
+ process.exit(1);
2934
+ }
2935
+ try {
2936
+ const instance = buildRagFromConfig({ config: ragConfig });
2937
+ const result = await indexSources(instance, ragConfig);
2938
+ process.stdout.write(
2939
+ `Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
2940
+ `
2941
+ );
2942
+ for (const source of result.sources) {
2943
+ process.stdout.write(` \u2022 ${source}
2944
+ `);
2945
+ }
2946
+ } catch (err) {
2947
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2948
+ `);
2949
+ process.exit(1);
2950
+ }
2951
+ });
2952
+ }
2953
+
2954
+ // src/commands/index.ts
2955
+ function createCli() {
2956
+ const program = new commander.Command();
2957
+ program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
2958
+ registerChatCommand(program);
2959
+ registerRunCommand(program);
2960
+ registerInitCommand(program);
2961
+ registerDoctorCommand(program);
2962
+ registerDevCommand(program);
2963
+ registerConfigCommand(program);
2964
+ registerTunnelCommand(program);
2965
+ registerRagCommand(program);
2174
2966
  return program;
2175
2967
  }
2176
2968