@agentskit/cli 0.6.0 → 0.7.0

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