@agentskit/cli 0.5.3 → 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/bin.cjs CHANGED
@@ -1,42 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var React3 = require('react');
5
- var ink = require('ink');
6
4
  var commander = require('commander');
7
- var path = require('path');
5
+ var React2 = require('react');
6
+ var ink = require('ink');
8
7
  var promises = require('fs/promises');
8
+ var os = require('os');
9
+ var path = require('path');
9
10
  var ink$1 = require('@agentskit/ink');
10
11
  var adapters = require('@agentskit/adapters');
12
+ var crypto = require('crypto');
13
+ var fs = require('fs');
11
14
  var tools = require('@agentskit/tools');
12
15
  var skills = require('@agentskit/skills');
13
16
  var memory = require('@agentskit/memory');
17
+ var child_process = require('child_process');
14
18
  var jsxRuntime = require('react/jsx-runtime');
19
+ var url = require('url');
20
+ var runtime = require('@agentskit/runtime');
15
21
  var prompts = require('@inquirer/prompts');
16
22
  var kleur = require('kleur');
17
- var fs = require('fs');
18
- var runtime = require('@agentskit/runtime');
19
- var child_process = require('child_process');
20
23
  var chokidar = require('chokidar');
24
+ var rag = require('@agentskit/rag');
21
25
 
22
26
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
27
 
24
- var React3__default = /*#__PURE__*/_interopDefault(React3);
28
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
25
29
  var path__default = /*#__PURE__*/_interopDefault(path);
26
30
  var kleur__default = /*#__PURE__*/_interopDefault(kleur);
27
31
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
28
32
 
29
- async function loadJsonConfig(path4) {
33
+ async function loadJsonConfig(path5) {
30
34
  try {
31
- const raw = await promises.readFile(path4, "utf8");
35
+ const raw = await promises.readFile(path5, "utf8");
32
36
  return JSON.parse(raw);
33
37
  } catch {
34
38
  return void 0;
35
39
  }
36
40
  }
37
- async function loadTsConfig(path4) {
41
+ async function loadTsConfig(path5) {
38
42
  try {
39
- const mod = await import(path4);
43
+ const mod = await import(path5);
40
44
  return mod.default ?? mod;
41
45
  } catch {
42
46
  return void 0;
@@ -54,16 +58,38 @@ async function loadPackageJsonConfig(dir) {
54
58
  return void 0;
55
59
  }
56
60
  }
57
- async function loadConfig(options) {
58
- const cwd = path.resolve(process.cwd());
59
- const tsPath = path.join(cwd, ".agentskit.config.ts");
60
- const tsConfig = await loadTsConfig(tsPath);
61
+ function mergeConfigs(base, override) {
62
+ if (!base && !override) return void 0;
63
+ if (!base) return override;
64
+ if (!override) return base;
65
+ return {
66
+ ...base,
67
+ ...override,
68
+ tools: { ...base.tools, ...override.tools },
69
+ defaults: { ...base.defaults, ...override.defaults },
70
+ runtime: { ...base.runtime, ...override.runtime },
71
+ observability: { ...base.observability, ...override.observability }
72
+ };
73
+ }
74
+ async function loadLocalConfig(cwd) {
75
+ const tsConfig = await loadTsConfig(path.join(cwd, ".agentskit.config.ts"));
61
76
  if (tsConfig) return tsConfig;
62
- const jsonPath = path.join(cwd, ".agentskit.config.json");
63
- const jsonConfig = await loadJsonConfig(jsonPath);
77
+ const jsonConfig = await loadJsonConfig(path.join(cwd, ".agentskit.config.json"));
64
78
  if (jsonConfig) return jsonConfig;
65
79
  return await loadPackageJsonConfig(cwd);
66
80
  }
81
+ async function loadGlobalConfig(home) {
82
+ const globalDir = path.join(os.homedir(), ".agentskit");
83
+ const tsConfig = await loadTsConfig(path.join(globalDir, "config.ts"));
84
+ if (tsConfig) return tsConfig;
85
+ return await loadJsonConfig(path.join(globalDir, "config.json"));
86
+ }
87
+ async function loadConfig(options) {
88
+ const cwd = path.resolve(process.cwd());
89
+ const global = await loadGlobalConfig();
90
+ const local = await loadLocalConfig(cwd);
91
+ return mergeConfigs(global, local);
92
+ }
67
93
  var providers = {
68
94
  openai: {
69
95
  label: "OpenAI",
@@ -123,7 +149,7 @@ function createDemoAdapter(provider, model) {
123
149
  ].join(" ");
124
150
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
125
151
  if (cancelled) return;
126
- await new Promise((resolve2) => setTimeout(resolve2, 35));
152
+ await new Promise((resolve4) => setTimeout(resolve4, 35));
127
153
  yield { type: "text", content: chunk };
128
154
  }
129
155
  yield { type: "done" };
@@ -174,159 +200,1461 @@ function resolveChatProvider(options) {
174
200
  summary: `${entry.label} live adapter`
175
201
  };
176
202
  }
177
- var skillRegistry = {
178
- researcher: skills.researcher,
179
- coder: skills.coder,
180
- planner: skills.planner,
181
- critic: skills.critic,
182
- summarizer: skills.summarizer
183
- };
184
- function resolveTools(toolNames) {
185
- if (!toolNames) return [];
186
- const tools$1 = [];
187
- for (const name of toolNames.split(",").map((s) => s.trim())) {
188
- switch (name) {
189
- case "web_search":
190
- tools$1.push(tools.webSearch());
191
- break;
192
- case "filesystem":
193
- tools$1.push(...tools.filesystem({ basePath: process.cwd() }));
194
- break;
195
- case "shell":
196
- tools$1.push(tools.shell({ timeout: 3e4 }));
197
- break;
198
- default:
199
- process.stderr.write(`Unknown tool: ${name}
200
- `);
203
+ var ROOT = path.join(os.homedir(), ".agentskit", "sessions");
204
+ var META_SUFFIX = ".meta.json";
205
+ function cwdHash(cwd = process.cwd()) {
206
+ return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
207
+ }
208
+ function dirFor(cwd = process.cwd()) {
209
+ return path.join(ROOT, cwdHash(cwd));
210
+ }
211
+ function ensureDir(dir) {
212
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
213
+ }
214
+ function generateSessionId() {
215
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
216
+ const suffix = crypto.randomBytes(3).toString("hex");
217
+ return `${ts}-${suffix}`;
218
+ }
219
+ function sessionFilePath(id, cwd = process.cwd()) {
220
+ ensureDir(dirFor(cwd));
221
+ return path.join(dirFor(cwd), `${id}.json`);
222
+ }
223
+ function metaPath(id, cwd = process.cwd()) {
224
+ return path.join(dirFor(cwd), `${id}${META_SUFFIX}`);
225
+ }
226
+ function readMeta(id, cwd = process.cwd()) {
227
+ const path5 = metaPath(id, cwd);
228
+ if (!fs.existsSync(path5)) return null;
229
+ try {
230
+ return JSON.parse(fs.readFileSync(path5, "utf8"));
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ function writeSessionMeta(meta, cwd = process.cwd()) {
236
+ ensureDir(dirFor(cwd));
237
+ fs.writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
238
+ }
239
+ function derivePreview(messages) {
240
+ const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
241
+ if (!firstUser) return "(empty)";
242
+ const single = firstUser.content.replace(/\s+/g, " ").trim();
243
+ return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
244
+ }
245
+ function listSessions(cwd = process.cwd()) {
246
+ const dir = dirFor(cwd);
247
+ if (!fs.existsSync(dir)) return [];
248
+ const entries = fs.readdirSync(dir);
249
+ const records = [];
250
+ for (const entry of entries) {
251
+ if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
252
+ const id = entry.replace(/\.json$/, "");
253
+ const meta = readMeta(id, cwd);
254
+ const file = path.join(dir, entry);
255
+ if (meta) {
256
+ records.push({ metadata: meta, file });
257
+ } else {
258
+ const stats = fs.statSync(file);
259
+ records.push({
260
+ metadata: {
261
+ id,
262
+ cwd,
263
+ createdAt: stats.birthtime.toISOString(),
264
+ updatedAt: stats.mtime.toISOString(),
265
+ messageCount: 0,
266
+ preview: "(legacy session)"
267
+ },
268
+ file
269
+ });
270
+ }
271
+ }
272
+ records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
273
+ return records;
274
+ }
275
+ function findLatestSession(cwd = process.cwd()) {
276
+ const all = listSessions(cwd);
277
+ return all[0] ?? null;
278
+ }
279
+ function findSession(id, cwd = process.cwd()) {
280
+ const all = listSessions(cwd);
281
+ const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
282
+ if (exact) return exact;
283
+ const prefix = all.find((s) => s.metadata.id.startsWith(id));
284
+ return prefix ?? null;
285
+ }
286
+ function renameSession(id, label, cwd = process.cwd()) {
287
+ const record = findSession(id, cwd);
288
+ if (!record) throw new Error(`No session matching "${id}".`);
289
+ const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
290
+ writeSessionMeta(next, cwd);
291
+ return next;
292
+ }
293
+ function forkSession(id, cwd = process.cwd()) {
294
+ const record = findSession(id, cwd);
295
+ if (!record) throw new Error(`No session matching "${id}".`);
296
+ const newId = generateSessionId();
297
+ const newFile = sessionFilePath(newId, cwd);
298
+ if (fs.existsSync(record.file)) {
299
+ fs.writeFileSync(newFile, fs.readFileSync(record.file, "utf8"));
300
+ }
301
+ const now = (/* @__PURE__ */ new Date()).toISOString();
302
+ writeSessionMeta(
303
+ {
304
+ ...record.metadata,
305
+ id: newId,
306
+ createdAt: now,
307
+ updatedAt: now,
308
+ forkedFrom: record.metadata.id,
309
+ label: void 0
310
+ },
311
+ cwd
312
+ );
313
+ return { id: newId, file: newFile, isNew: true };
314
+ }
315
+ function resolveSession(input2) {
316
+ const cwd = input2.cwd ?? process.cwd();
317
+ if (input2.explicitPath) {
318
+ return { id: "custom", file: input2.explicitPath, isNew: !fs.existsSync(input2.explicitPath) };
319
+ }
320
+ if (input2.forceNew) {
321
+ const id2 = generateSessionId();
322
+ return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
323
+ }
324
+ if (input2.resumeId) {
325
+ const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
326
+ if (target) {
327
+ return { id: target.metadata.id, file: target.file, isNew: false };
328
+ }
329
+ process.stderr.write(
330
+ `No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
331
+ `
332
+ );
333
+ }
334
+ const latest = findLatestSession(cwd);
335
+ if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
336
+ const id = generateSessionId();
337
+ return { id, file: sessionFilePath(id, cwd), isNew: true };
338
+ }
339
+
340
+ // src/extensibility/telemetry/pricing.ts
341
+ var builtinPricing = {
342
+ "gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
343
+ "gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
344
+ "gpt-4.1": { inputPerM: 2, outputPerM: 8 },
345
+ "gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
346
+ "claude-opus-4": { inputPerM: 15, outputPerM: 75 },
347
+ "claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
348
+ "claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
349
+ "gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
350
+ "gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
351
+ };
352
+ var customPricing = {};
353
+ function getPricing(model) {
354
+ if (!model) return void 0;
355
+ if (customPricing[model]) return customPricing[model];
356
+ if (builtinPricing[model]) return builtinPricing[model];
357
+ const short = model.includes("/") ? model.split("/").pop() : model;
358
+ return customPricing[short] ?? builtinPricing[short];
359
+ }
360
+ function computeCost(model, usage) {
361
+ if (!model) return void 0;
362
+ const pricing = getPricing(model);
363
+ if (!pricing) return void 0;
364
+ const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
365
+ const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
366
+ return {
367
+ model,
368
+ inputUsd,
369
+ outputUsd,
370
+ totalUsd: inputUsd + outputUsd
371
+ };
372
+ }
373
+
374
+ // src/slash-commands.ts
375
+ function parseSlashCommand(input2) {
376
+ if (!input2.startsWith("/")) return null;
377
+ const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
378
+ if (!match) return null;
379
+ return { name: match[1], args: match[2] ?? "" };
380
+ }
381
+ function createSlashRegistry(commands) {
382
+ const map = /* @__PURE__ */ new Map();
383
+ for (const cmd of commands) {
384
+ map.set(cmd.name, cmd);
385
+ for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
386
+ }
387
+ return map;
388
+ }
389
+ var builtinSlashCommands = [
390
+ {
391
+ name: "help",
392
+ aliases: ["?"],
393
+ description: "List available slash commands.",
394
+ run(ctx) {
395
+ const seen = /* @__PURE__ */ new Set();
396
+ const lines = [];
397
+ for (const cmd of ctx.commands) {
398
+ if (seen.has(cmd.name)) continue;
399
+ seen.add(cmd.name);
400
+ const suffix = cmd.usage ? ` (${cmd.usage})` : "";
401
+ lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
402
+ }
403
+ ctx.feedback(`Slash commands:
404
+ ${lines.join("\n")}`, "info");
405
+ }
406
+ },
407
+ {
408
+ name: "model",
409
+ description: "Switch the active model.",
410
+ usage: "/model <name>",
411
+ run(ctx, args) {
412
+ const value = args.trim();
413
+ if (!value) {
414
+ ctx.feedback(
415
+ `Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
416
+ "warn"
417
+ );
418
+ return;
419
+ }
420
+ ctx.setModel(value);
421
+ ctx.feedback(`Model \u2192 ${value}`, "success");
422
+ }
423
+ },
424
+ {
425
+ name: "provider",
426
+ description: "Switch the adapter provider.",
427
+ usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
428
+ run(ctx, args) {
429
+ const value = args.trim();
430
+ if (!value) {
431
+ ctx.feedback(
432
+ `Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
433
+ "warn"
434
+ );
435
+ return;
436
+ }
437
+ ctx.setProvider(value);
438
+ ctx.feedback(`Provider \u2192 ${value}`, "success");
439
+ }
440
+ },
441
+ {
442
+ name: "base-url",
443
+ aliases: ["baseurl"],
444
+ description: "Override provider base URL.",
445
+ usage: "/base-url <url|clear>",
446
+ run(ctx, args) {
447
+ const value = args.trim();
448
+ if (!value || value === "clear") {
449
+ ctx.setBaseUrl(void 0);
450
+ ctx.feedback("Base URL cleared.", "success");
451
+ return;
452
+ }
453
+ ctx.setBaseUrl(value);
454
+ ctx.feedback(`Base URL \u2192 ${value}`, "success");
455
+ }
456
+ },
457
+ {
458
+ name: "tools",
459
+ description: "Set active tools (comma-separated) or clear them.",
460
+ usage: "/tools web_search,fetch_url | /tools clear",
461
+ run(ctx, args) {
462
+ const value = args.trim();
463
+ if (!value || value === "clear") {
464
+ ctx.setTools(void 0);
465
+ ctx.feedback("Tools reset to defaults.", "success");
466
+ return;
467
+ }
468
+ ctx.setTools(value);
469
+ ctx.feedback(`Tools \u2192 ${value}`, "success");
470
+ }
471
+ },
472
+ {
473
+ name: "skill",
474
+ description: "Set active skill(s) (comma-separated) or clear them.",
475
+ usage: "/skill researcher,coder | /skill clear",
476
+ run(ctx, args) {
477
+ const value = args.trim();
478
+ if (!value || value === "clear") {
479
+ ctx.setSkill(void 0);
480
+ ctx.feedback("Skills cleared.", "success");
481
+ return;
482
+ }
483
+ ctx.setSkill(value);
484
+ ctx.feedback(`Skills \u2192 ${value}`, "success");
485
+ }
486
+ },
487
+ {
488
+ name: "clear",
489
+ aliases: ["reset"],
490
+ description: "Clear the conversation history in this session.",
491
+ async run(ctx) {
492
+ await ctx.chat.clear();
493
+ ctx.feedback("History cleared.", "success");
494
+ }
495
+ },
496
+ {
497
+ name: "usage",
498
+ description: "Show the cumulative token usage for this session.",
499
+ run(ctx) {
500
+ const usage = ctx.chat.usage;
501
+ if (!usage || usage.totalTokens === 0) {
502
+ ctx.feedback("No usage reported yet for this session.", "info");
503
+ return;
504
+ }
505
+ ctx.feedback(
506
+ `Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
507
+ "info"
508
+ );
509
+ }
510
+ },
511
+ {
512
+ name: "cost",
513
+ description: "Estimate the cost so far for the current model.",
514
+ run(ctx) {
515
+ const usage = ctx.chat.usage;
516
+ const model = ctx.runtime.model;
517
+ if (!usage || usage.totalTokens === 0) {
518
+ ctx.feedback("No usage reported yet for this session.", "info");
519
+ return;
520
+ }
521
+ const cost = computeCost(model, usage);
522
+ if (!cost) {
523
+ ctx.feedback(
524
+ `No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
525
+ "warn"
526
+ );
527
+ return;
528
+ }
529
+ ctx.feedback(
530
+ `$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
531
+ "info"
532
+ );
533
+ }
534
+ },
535
+ {
536
+ name: "rename",
537
+ description: "Attach a human-readable label to the current session.",
538
+ usage: "/rename <label>",
539
+ run(ctx, args) {
540
+ const label = args.trim();
541
+ const sessionId = ctx.runtime.sessionId;
542
+ if (!sessionId || sessionId === "custom") {
543
+ ctx.feedback("Rename is only available for managed sessions.", "warn");
544
+ return;
545
+ }
546
+ if (!label) {
547
+ ctx.feedback("Usage: /rename <label>", "warn");
548
+ return;
549
+ }
550
+ try {
551
+ renameSession(sessionId, label);
552
+ ctx.feedback(`Session labeled "${label}".`, "success");
553
+ } catch (err) {
554
+ ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
555
+ }
556
+ }
557
+ },
558
+ {
559
+ name: "fork",
560
+ description: "Branch a copy of the current session. Does not switch to it.",
561
+ run(ctx) {
562
+ const sessionId = ctx.runtime.sessionId;
563
+ if (!sessionId || sessionId === "custom") {
564
+ ctx.feedback("Fork is only available for managed sessions.", "warn");
565
+ return;
566
+ }
567
+ try {
568
+ const result = forkSession(sessionId);
569
+ ctx.feedback(
570
+ `Forked into ${result.id}. Resume with:
571
+ agentskit chat --resume ${result.id}`,
572
+ "success"
573
+ );
574
+ } catch (err) {
575
+ ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
576
+ }
577
+ }
578
+ },
579
+ {
580
+ name: "exit",
581
+ aliases: ["quit", "q"],
582
+ description: "Exit the chat.",
583
+ run() {
584
+ process.exit(0);
585
+ }
586
+ }
587
+ ];
588
+ var skillRegistry = {
589
+ researcher: skills.researcher,
590
+ coder: skills.coder,
591
+ planner: skills.planner,
592
+ critic: skills.critic,
593
+ summarizer: skills.summarizer
594
+ };
595
+ function instantiate(kind) {
596
+ switch (kind) {
597
+ case "web_search":
598
+ return [tools.webSearch()];
599
+ case "fetch_url":
600
+ return [tools.fetchUrl()];
601
+ case "filesystem":
602
+ return tools.filesystem({ basePath: process.cwd() });
603
+ case "shell":
604
+ return [tools.shell({ timeout: 3e4 })];
605
+ }
606
+ }
607
+ function gateTool(tool) {
608
+ if (tool.requiresConfirmation === false) return tool;
609
+ return { ...tool, requiresConfirmation: true };
610
+ }
611
+ function resolveTools(toolNames) {
612
+ if (!toolNames) {
613
+ return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
614
+ }
615
+ const tools = [];
616
+ for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
617
+ switch (name) {
618
+ case "web_search":
619
+ case "fetch_url":
620
+ case "filesystem":
621
+ case "shell":
622
+ tools.push(...instantiate(name));
623
+ break;
624
+ default:
625
+ process.stderr.write(`Unknown tool: ${name}
626
+ `);
627
+ }
628
+ }
629
+ return tools;
630
+ }
631
+ function resolveSkill(skillName) {
632
+ if (!skillName) return void 0;
633
+ const skill = skillRegistry[skillName.trim()];
634
+ if (!skill) {
635
+ process.stderr.write(`Unknown skill: ${skillName}
636
+ `);
637
+ return void 0;
638
+ }
639
+ return skill;
640
+ }
641
+ function resolveSkills(skillNames) {
642
+ if (!skillNames) return void 0;
643
+ const names = skillNames.split(",").map((s) => s.trim());
644
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
645
+ if (resolved.length === 0) {
646
+ process.stderr.write(`No valid skills found in: ${skillNames}
647
+ `);
648
+ return void 0;
649
+ }
650
+ if (resolved.length === 1) return resolved[0];
651
+ return skills.composeSkills(...resolved);
652
+ }
653
+ function resolveMemory(backend, memoryPath) {
654
+ switch (backend) {
655
+ case "sqlite":
656
+ return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
657
+ case "file":
658
+ default:
659
+ return memory.fileChatMemory(memoryPath);
660
+ }
661
+ }
662
+
663
+ // src/extensibility/permissions/policy.ts
664
+ function evaluatePolicy(policy, toolName) {
665
+ if (policy.mode === "bypassPermissions") return "allow";
666
+ if (policy.mode === "plan") return "ask";
667
+ for (const rule of policy.rules) {
668
+ if (matchesRule(rule, toolName)) return rule.action;
669
+ }
670
+ if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
671
+ return "allow";
672
+ }
673
+ return "ask";
674
+ }
675
+ function matchesRule(rule, toolName) {
676
+ if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
677
+ const str = rule.tool;
678
+ if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
679
+ return str === toolName;
680
+ }
681
+ function applyPolicyToTool(policy, tool) {
682
+ const action = evaluatePolicy(policy, tool.name);
683
+ if (action === "deny") return null;
684
+ if (action === "allow") return { ...tool, requiresConfirmation: false };
685
+ return { ...tool, requiresConfirmation: true };
686
+ }
687
+ function applyPolicyToTools(policy, tools) {
688
+ const out = [];
689
+ for (const tool of tools) {
690
+ const gated = applyPolicyToTool(policy, tool);
691
+ if (gated) out.push(gated);
692
+ }
693
+ return out;
694
+ }
695
+
696
+ // src/runtime/use-runtime.ts
697
+ function useRuntime(options) {
698
+ const [provider, setProvider] = React2.useState(options.provider);
699
+ const [model, setModel] = React2.useState(options.model);
700
+ const [apiKey, setApiKey] = React2.useState(options.apiKey);
701
+ const [baseUrl, setBaseUrl] = React2.useState(options.baseUrl);
702
+ const [toolsFlag, setToolsFlag] = React2.useState(options.tools);
703
+ const [skillFlag, setSkillFlag] = React2.useState(options.skill);
704
+ const runtime = React2.useMemo(
705
+ () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
706
+ [provider, model, apiKey, baseUrl]
707
+ );
708
+ const memory = React2.useMemo(
709
+ () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
710
+ [options.memoryPath, options.memoryBackend]
711
+ );
712
+ const tools = React2.useMemo(() => {
713
+ const resolved = resolveTools(toolsFlag);
714
+ if (!options.permissionPolicy) return resolved;
715
+ return applyPolicyToTools(options.permissionPolicy, resolved);
716
+ }, [toolsFlag, options.permissionPolicy]);
717
+ const skills = React2.useMemo(() => {
718
+ if (!skillFlag) return void 0;
719
+ const names = skillFlag.split(",").map((s) => s.trim());
720
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
721
+ if (resolved.length === 0) return void 0;
722
+ return resolved;
723
+ }, [skillFlag]);
724
+ return {
725
+ runtime,
726
+ memory,
727
+ tools,
728
+ skills,
729
+ state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
730
+ setProvider,
731
+ setModel,
732
+ setApiKey,
733
+ setBaseUrl,
734
+ setToolsFlag,
735
+ setSkillFlag
736
+ };
737
+ }
738
+ function useToolPermissions(chat) {
739
+ const [sessionAllowed, setSessionAllowed] = React2.useState(/* @__PURE__ */ new Set());
740
+ const autoApprovedRef = React2.useRef(/* @__PURE__ */ new Set());
741
+ React2.useEffect(() => {
742
+ if (sessionAllowed.size === 0) return;
743
+ for (const message of chat.messages) {
744
+ for (const call of message.toolCalls ?? []) {
745
+ if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
746
+ autoApprovedRef.current.add(call.id);
747
+ void chat.approve(call.id);
748
+ }
749
+ }
750
+ }
751
+ }, [chat.messages, sessionAllowed, chat.approve]);
752
+ const handleApproveAlways = (toolCallId, toolName) => {
753
+ setSessionAllowed((prev) => {
754
+ if (prev.has(toolName)) return prev;
755
+ const next = new Set(prev);
756
+ next.add(toolName);
757
+ return next;
758
+ });
759
+ autoApprovedRef.current.add(toolCallId);
760
+ void chat.approve(toolCallId);
761
+ };
762
+ const awaitingConfirmation = React2.useMemo(
763
+ () => chat.messages.some(
764
+ (message) => message.toolCalls?.some(
765
+ (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
766
+ )
767
+ ),
768
+ [chat.messages, sessionAllowed]
769
+ );
770
+ return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
771
+ }
772
+ function useSessionMeta(options) {
773
+ const sessionCreatedAtRef = React2.useRef(void 0);
774
+ const messageCount = options.messages.length;
775
+ const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
776
+ React2.useEffect(() => {
777
+ const sessionId = options.sessionId;
778
+ if (!sessionId || sessionId === "custom") return;
779
+ if (!sessionCreatedAtRef.current) {
780
+ sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
781
+ }
782
+ try {
783
+ writeSessionMeta({
784
+ id: sessionId,
785
+ cwd: process.cwd(),
786
+ createdAt: sessionCreatedAtRef.current,
787
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
788
+ messageCount,
789
+ preview: derivePreview(options.messages),
790
+ provider: options.provider,
791
+ model: options.model
792
+ });
793
+ } catch {
794
+ }
795
+ }, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
796
+ }
797
+
798
+ // src/extensibility/hooks/runner.ts
799
+ var HookDispatcher = class {
800
+ constructor(handlers = [], onError = (_h, err) => process.stderr.write(
801
+ `[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
802
+ `
803
+ )) {
804
+ this.onError = onError;
805
+ this.handlers = /* @__PURE__ */ new Map();
806
+ for (const handler of handlers) this.register(handler);
807
+ }
808
+ register(handler) {
809
+ const list = this.handlers.get(handler.event) ?? [];
810
+ list.push(handler);
811
+ this.handlers.set(handler.event, list);
812
+ }
813
+ async dispatch(event, payload) {
814
+ const list = this.handlers.get(event) ?? [];
815
+ let current = { ...payload, event };
816
+ for (const handler of list) {
817
+ if (!this.matches(handler, current)) continue;
818
+ let result;
819
+ try {
820
+ result = await handler.run(current);
821
+ } catch (err) {
822
+ this.onError(handler, err);
823
+ continue;
824
+ }
825
+ if (result.decision === "block") {
826
+ return { payload: current, blocked: true, reason: result.reason };
827
+ }
828
+ if (result.decision === "modify") {
829
+ current = { ...result.payload, event };
830
+ }
831
+ }
832
+ return { payload: current, blocked: false };
833
+ }
834
+ matches(handler, payload) {
835
+ if (!handler.matcher) return true;
836
+ if (typeof handler.matcher === "function") return handler.matcher(payload);
837
+ return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
838
+ }
839
+ };
840
+ function configHooksToHandlers(config) {
841
+ if (!config) return [];
842
+ const handlers = [];
843
+ for (const [event, entries] of Object.entries(config)) {
844
+ for (const entry of entries) {
845
+ handlers.push({
846
+ event,
847
+ matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
848
+ run: (payload) => runShellHook(entry, payload)
849
+ });
850
+ }
851
+ }
852
+ return handlers;
853
+ }
854
+ function runShellHook(entry, payload) {
855
+ return new Promise((resolvePromise) => {
856
+ const timeoutMs = entry.timeout ?? 5e3;
857
+ const child = child_process.spawn("sh", ["-c", entry.run], {
858
+ stdio: ["pipe", "pipe", "inherit"]
859
+ });
860
+ let stdout = "";
861
+ child.stdout.on("data", (chunk) => {
862
+ stdout += chunk.toString();
863
+ });
864
+ const timer = setTimeout(() => {
865
+ child.kill("SIGTERM");
866
+ }, timeoutMs);
867
+ child.on("close", (code) => {
868
+ clearTimeout(timer);
869
+ if (code !== 0) {
870
+ resolvePromise({
871
+ decision: "block",
872
+ reason: `shell hook exited with code ${code}`
873
+ });
874
+ return;
875
+ }
876
+ const trimmed = stdout.trim();
877
+ if (!trimmed) {
878
+ resolvePromise({ decision: "continue" });
879
+ return;
880
+ }
881
+ try {
882
+ const parsed = JSON.parse(trimmed);
883
+ resolvePromise(parsed);
884
+ } catch {
885
+ resolvePromise({ decision: "continue" });
886
+ }
887
+ });
888
+ child.on("error", (err) => {
889
+ clearTimeout(timer);
890
+ resolvePromise({ decision: "block", reason: err.message });
891
+ });
892
+ try {
893
+ child.stdin.write(JSON.stringify(payload));
894
+ child.stdin.end();
895
+ } catch {
896
+ }
897
+ });
898
+ }
899
+ function groupIntoTurns(messages) {
900
+ const turns = [];
901
+ let current = [];
902
+ for (const message of messages) {
903
+ if (message.role === "user") {
904
+ if (current.length > 0) turns.push(current);
905
+ current = [message];
906
+ } else if (message.role === "system") {
907
+ if (current.length > 0) turns.push(current);
908
+ turns.push([message]);
909
+ current = [];
910
+ } else {
911
+ current.push(message);
912
+ }
913
+ }
914
+ if (current.length > 0) turns.push(current);
915
+ return turns;
916
+ }
917
+ function ChatApp(options) {
918
+ const {
919
+ runtime,
920
+ memory,
921
+ tools,
922
+ skills,
923
+ state: { baseUrl, toolsFlag, skillFlag },
924
+ setProvider,
925
+ setModel,
926
+ setApiKey,
927
+ setBaseUrl,
928
+ setToolsFlag,
929
+ setSkillFlag
930
+ } = useRuntime(options);
931
+ const mergedTools = React2.useMemo(() => {
932
+ const extra = options.extraTools ?? [];
933
+ return [...tools, ...extra];
934
+ }, [tools, options.extraTools]);
935
+ const mergedSkills = React2.useMemo(() => {
936
+ const extra = options.extraSkills ?? [];
937
+ if (!skills && extra.length === 0) return void 0;
938
+ return [...skills ?? [], ...extra];
939
+ }, [skills, options.extraSkills]);
940
+ const chat = ink$1.useChat({
941
+ adapter: runtime.adapter,
942
+ memory,
943
+ systemPrompt: options.system,
944
+ tools: mergedTools.length > 0 ? mergedTools : void 0,
945
+ skills: mergedSkills
946
+ });
947
+ const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
948
+ useSessionMeta({
949
+ sessionId: options.sessionId,
950
+ messages: chat.messages,
951
+ provider: runtime.provider,
952
+ model: runtime.model
953
+ });
954
+ const turns = React2.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
955
+ const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
956
+ const [feedback, setFeedback] = React2.useState(null);
957
+ const hookDispatcher = React2.useMemo(
958
+ () => new HookDispatcher(options.hookHandlers ?? []),
959
+ [options.hookHandlers]
960
+ );
961
+ React2.useEffect(() => {
962
+ void hookDispatcher.dispatch("SessionStart", {
963
+ event: "SessionStart",
964
+ sessionId: options.sessionId,
965
+ provider: runtime.provider,
966
+ model: runtime.model
967
+ });
968
+ return () => {
969
+ void hookDispatcher.dispatch("SessionEnd", {
970
+ event: "SessionEnd",
971
+ sessionId: options.sessionId
972
+ });
973
+ };
974
+ }, [hookDispatcher]);
975
+ const slashCommands = React2.useMemo(
976
+ () => [...builtinSlashCommands, ...options.slashCommands ?? []],
977
+ [options.slashCommands]
978
+ );
979
+ const slashRegistry = React2.useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
980
+ const handleSubmitInput = async (raw) => {
981
+ const parsed = parseSlashCommand(raw);
982
+ if (!parsed) {
983
+ const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
984
+ event: "UserPromptSubmit",
985
+ prompt: raw
986
+ });
987
+ if (hookResult.blocked) {
988
+ setFeedback({
989
+ message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
990
+ kind: "warn"
991
+ });
992
+ return true;
993
+ }
994
+ setFeedback(null);
995
+ return false;
996
+ }
997
+ const cmd = slashRegistry.get(parsed.name);
998
+ if (!cmd) {
999
+ setFeedback({
1000
+ message: `Unknown command: /${parsed.name}. Type /help for the list.`,
1001
+ kind: "error"
1002
+ });
1003
+ return true;
1004
+ }
1005
+ const ctx = {
1006
+ chat,
1007
+ runtime: {
1008
+ provider: runtime.provider,
1009
+ model: runtime.model,
1010
+ mode: runtime.mode,
1011
+ baseUrl,
1012
+ tools: toolsFlag,
1013
+ skill: skillFlag,
1014
+ sessionId: options.sessionId
1015
+ },
1016
+ setProvider,
1017
+ setModel,
1018
+ setApiKey,
1019
+ setBaseUrl,
1020
+ setTools: setToolsFlag,
1021
+ setSkill: setSkillFlag,
1022
+ feedback: (message, kind = "info") => setFeedback({ message, kind }),
1023
+ commands: slashCommands
1024
+ };
1025
+ try {
1026
+ await cmd.run(ctx, parsed.args);
1027
+ } catch (err) {
1028
+ const message = err instanceof Error ? err.message : String(err);
1029
+ setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
1030
+ }
1031
+ return true;
1032
+ };
1033
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
1034
+ /* @__PURE__ */ jsxRuntime.jsx(
1035
+ ink$1.StatusHeader,
1036
+ {
1037
+ provider: runtime.provider,
1038
+ model: runtime.model,
1039
+ mode: runtime.mode,
1040
+ tools: toolNames,
1041
+ messageCount: chat.messages.length,
1042
+ sessionId: options.sessionId
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.ChatContainer, { children: turns.map((turn, turnIdx) => {
1046
+ const assistantSteps = turn.filter((m) => m.role === "assistant").length;
1047
+ let stepIndex = 0;
1048
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
1049
+ const showStep = message.role === "assistant" && assistantSteps > 1;
1050
+ if (showStep) stepIndex++;
1051
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1052
+ showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1053
+ "\u21BB step ",
1054
+ stepIndex,
1055
+ "/",
1056
+ assistantSteps
1057
+ ] }) : null,
1058
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.Message, { message }),
1059
+ message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1060
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.ToolCallView, { toolCall, expanded: true }),
1061
+ toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsxRuntime.jsx(
1062
+ ink$1.ToolConfirmation,
1063
+ {
1064
+ toolCall,
1065
+ onApprove: chat.approve,
1066
+ onDeny: chat.deny,
1067
+ onApproveAlways: handleApproveAlways
1068
+ }
1069
+ ) : null
1070
+ ] }, toolCall.id))
1071
+ ] }, message.id);
1072
+ }) }, `turn-${turnIdx}`);
1073
+ }) }),
1074
+ /* @__PURE__ */ jsxRuntime.jsx(
1075
+ ink$1.ThinkingIndicator,
1076
+ {
1077
+ visible: chat.status === "streaming",
1078
+ label: toolNames.length > 0 ? "agent working" : "thinking"
1079
+ }
1080
+ ),
1081
+ chat.error ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
1082
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
1083
+ "\u2717 ",
1084
+ chat.error.name || "Error"
1085
+ ] }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: chat.error.message })
1087
+ ] }) : null,
1088
+ feedback ? /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { borderStyle: "round", borderColor: feedbackBorder(feedback.kind), paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: feedbackBorder(feedback.kind), children: feedback.message }) }) : null,
1089
+ /* @__PURE__ */ jsxRuntime.jsx(
1090
+ ink$1.InputBar,
1091
+ {
1092
+ chat,
1093
+ placeholder: "Type a message or /help for commands",
1094
+ disabled: awaitingConfirmation,
1095
+ onSubmitInput: handleSubmitInput
1096
+ }
1097
+ )
1098
+ ] });
1099
+ }
1100
+ function feedbackBorder(kind) {
1101
+ switch (kind) {
1102
+ case "error":
1103
+ return "red";
1104
+ case "warn":
1105
+ return "yellow";
1106
+ case "success":
1107
+ return "green";
1108
+ case "info":
1109
+ default:
1110
+ return "cyan";
1111
+ }
1112
+ }
1113
+ function renderChatHeader(options) {
1114
+ const runtime = resolveChatProvider(options);
1115
+ const parts = [`provider=${runtime.provider}`];
1116
+ if (runtime.model) parts.push(`model=${runtime.model}`);
1117
+ parts.push(`mode=${runtime.mode}`);
1118
+ if (options.tools) parts.push(`tools=${options.tools}`);
1119
+ if (options.skill) parts.push(`skill=${options.skill}`);
1120
+ if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
1121
+ return parts.join(" ");
1122
+ }
1123
+
1124
+ // src/commands/shared.ts
1125
+ function mergeWithConfig(options, config) {
1126
+ if (!config) return options;
1127
+ const d = config.defaults ?? {};
1128
+ const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
1129
+ return {
1130
+ ...options,
1131
+ provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
1132
+ model: options.model ?? d.model,
1133
+ apiKey: resolvedApiKey,
1134
+ baseUrl: options.baseUrl ?? d.baseUrl,
1135
+ tools: options.tools ?? d.tools,
1136
+ skill: options.skill ?? d.skill,
1137
+ system: options.system ?? d.system,
1138
+ memoryBackend: options.memoryBackend ?? d.memoryBackend
1139
+ };
1140
+ }
1141
+ async function loadPlugins(options = {}) {
1142
+ const {
1143
+ specs = [],
1144
+ pluginDirs = [],
1145
+ cwd = process.cwd(),
1146
+ autoDiscoverUserDir = true,
1147
+ onError = (spec, err) => process.stderr.write(
1148
+ `[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
1149
+ `
1150
+ ),
1151
+ log = () => {
1152
+ }
1153
+ } = options;
1154
+ const resolvedSpecs = [...specs];
1155
+ const discoveryDirs = [...pluginDirs];
1156
+ if (autoDiscoverUserDir) discoveryDirs.push(path.join(os.homedir(), ".agentskit", "plugins"));
1157
+ for (const dir of discoveryDirs) {
1158
+ const discovered = await discoverPluginsInDir(dir);
1159
+ resolvedSpecs.push(...discovered);
1160
+ }
1161
+ const plugins = [];
1162
+ for (const spec of resolvedSpecs) {
1163
+ try {
1164
+ const plugin = await loadPluginFromSpec(spec, cwd, log);
1165
+ if (plugin) plugins.push(plugin);
1166
+ } catch (err) {
1167
+ onError(spec, err);
1168
+ }
1169
+ }
1170
+ return mergePluginsIntoBundle(plugins);
1171
+ }
1172
+ async function discoverPluginsInDir(dir) {
1173
+ try {
1174
+ const entries = await promises.readdir(dir);
1175
+ const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => path.join(dir, name));
1176
+ const validated = [];
1177
+ for (const p of absolutes) {
1178
+ try {
1179
+ const s = await promises.stat(p);
1180
+ if (s.isFile()) validated.push(p);
1181
+ } catch {
1182
+ }
1183
+ }
1184
+ return validated;
1185
+ } catch {
1186
+ return [];
1187
+ }
1188
+ }
1189
+ async function loadPluginFromSpec(spec, cwd, log) {
1190
+ const isPath = spec.startsWith("./") || spec.startsWith("../") || path.isAbsolute(spec);
1191
+ const importTarget = isPath ? url.pathToFileURL(path.resolve(cwd, spec)).href : spec;
1192
+ const mod = await import(importTarget);
1193
+ const exported = mod.default ?? mod.plugin ?? mod;
1194
+ const sourcePath = isPath ? path.resolve(cwd, spec) : void 0;
1195
+ const ctx = {
1196
+ cwd,
1197
+ sourcePath,
1198
+ log: (msg) => log(`[${spec}] ${msg}`)
1199
+ };
1200
+ if (typeof exported === "function") {
1201
+ const factory = exported;
1202
+ return await factory(ctx);
1203
+ }
1204
+ if (exported && typeof exported === "object" && "name" in exported) {
1205
+ const plugin = exported;
1206
+ if (plugin.init) await plugin.init(ctx);
1207
+ return plugin;
1208
+ }
1209
+ throw new Error(
1210
+ "Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
1211
+ );
1212
+ }
1213
+ function mergePluginsIntoBundle(plugins) {
1214
+ const bundle = {
1215
+ plugins,
1216
+ slashCommands: [],
1217
+ tools: [],
1218
+ skills: [],
1219
+ providers: {},
1220
+ hooks: [],
1221
+ mcpServers: []
1222
+ };
1223
+ for (const plugin of plugins) {
1224
+ if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
1225
+ if (plugin.tools) bundle.tools.push(...plugin.tools);
1226
+ if (plugin.skills) bundle.skills.push(...plugin.skills);
1227
+ if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
1228
+ if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
1229
+ if (plugin.providers) {
1230
+ for (const [name, factory] of Object.entries(plugin.providers)) {
1231
+ bundle.providers[name] = factory;
1232
+ }
1233
+ }
1234
+ }
1235
+ return bundle;
1236
+ }
1237
+ var McpClient = class {
1238
+ constructor(spec, onError = (err) => process.stderr.write(
1239
+ `[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
1240
+ `
1241
+ )) {
1242
+ this.spec = spec;
1243
+ this.onError = onError;
1244
+ this.buffer = "";
1245
+ this.nextId = 1;
1246
+ this.pending = /* @__PURE__ */ new Map();
1247
+ this.disposed = false;
1248
+ }
1249
+ async start() {
1250
+ if (this.child) return;
1251
+ const child = child_process.spawn(this.spec.command, this.spec.args ?? [], {
1252
+ env: { ...process.env, ...this.spec.env ?? {} },
1253
+ stdio: ["pipe", "pipe", "pipe"]
1254
+ });
1255
+ this.child = child;
1256
+ child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
1257
+ child.stderr.on("data", (chunk) => {
1258
+ process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
1259
+ });
1260
+ child.on("error", (err) => this.onError(err));
1261
+ child.on("close", () => {
1262
+ this.disposed = true;
1263
+ for (const pending of this.pending.values()) {
1264
+ pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
1265
+ }
1266
+ this.pending.clear();
1267
+ });
1268
+ await this.request("initialize", {
1269
+ protocolVersion: "2024-11-05",
1270
+ capabilities: {},
1271
+ clientInfo: { name: "agentskit", version: "0" }
1272
+ });
1273
+ }
1274
+ async listTools() {
1275
+ const res = await this.request("tools/list", {});
1276
+ const tools = res.tools ?? [];
1277
+ return tools;
1278
+ }
1279
+ async callTool(name, args) {
1280
+ return await this.request("tools/call", { name, arguments: args });
1281
+ }
1282
+ dispose() {
1283
+ if (this.disposed) return;
1284
+ this.disposed = true;
1285
+ this.child?.kill("SIGTERM");
1286
+ }
1287
+ request(method, params) {
1288
+ return new Promise((resolvePromise, rejectPromise) => {
1289
+ if (!this.child || this.disposed) {
1290
+ rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
1291
+ return;
1292
+ }
1293
+ const id = this.nextId++;
1294
+ const timeoutMs = this.spec.timeout ?? 1e4;
1295
+ const timer = setTimeout(() => {
1296
+ this.pending.delete(id);
1297
+ rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
1298
+ }, timeoutMs);
1299
+ this.pending.set(id, (res) => {
1300
+ clearTimeout(timer);
1301
+ if (res.error) {
1302
+ rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
1303
+ return;
1304
+ }
1305
+ resolvePromise(res.result);
1306
+ });
1307
+ const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
1308
+ this.child.stdin.write(message);
1309
+ });
1310
+ }
1311
+ onStdout(chunk) {
1312
+ this.buffer += chunk;
1313
+ let newlineIndex = this.buffer.indexOf("\n");
1314
+ while (newlineIndex !== -1) {
1315
+ const line = this.buffer.slice(0, newlineIndex).trim();
1316
+ this.buffer = this.buffer.slice(newlineIndex + 1);
1317
+ if (line) {
1318
+ try {
1319
+ const parsed = JSON.parse(line);
1320
+ const pending = this.pending.get(Number(parsed.id));
1321
+ if (pending) {
1322
+ this.pending.delete(Number(parsed.id));
1323
+ pending(parsed);
1324
+ }
1325
+ } catch (err) {
1326
+ this.onError(err);
1327
+ }
1328
+ }
1329
+ newlineIndex = this.buffer.indexOf("\n");
1330
+ }
1331
+ }
1332
+ };
1333
+
1334
+ // src/extensibility/mcp/bridge.ts
1335
+ async function bridgeMcpServers(specs) {
1336
+ const clients = [];
1337
+ const tools = [];
1338
+ for (const spec of specs) {
1339
+ const client = new McpClient(spec);
1340
+ try {
1341
+ await client.start();
1342
+ const mcpTools = await client.listTools();
1343
+ for (const mcpTool of mcpTools) {
1344
+ tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
1345
+ }
1346
+ clients.push(client);
1347
+ } catch (err) {
1348
+ process.stderr.write(
1349
+ `[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
1350
+ `
1351
+ );
1352
+ client.dispose();
1353
+ }
1354
+ }
1355
+ return { clients, tools };
1356
+ }
1357
+ function mcpToolToDefinition(serverName, client, tool) {
1358
+ return {
1359
+ name: `${serverName}__${tool.name}`,
1360
+ description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
1361
+ schema: tool.inputSchema ?? { type: "object", properties: {} },
1362
+ execute: async (args) => {
1363
+ return await client.callTool(tool.name, args);
1364
+ }
1365
+ };
1366
+ }
1367
+ function disposeMcpClients(clients) {
1368
+ for (const client of clients) client.dispose();
1369
+ }
1370
+
1371
+ // src/commands/chat.ts
1372
+ function registerChatCommand(program) {
1373
+ program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").option(
1374
+ "--plugin-dir <dir>",
1375
+ "Extra directory to auto-discover plugin modules from (repeatable)",
1376
+ (value, prev = []) => [...prev, value],
1377
+ []
1378
+ ).option(
1379
+ "--mode <mode>",
1380
+ "Permission mode: default | plan | acceptEdits | bypassPermissions"
1381
+ ).action(async (options) => {
1382
+ if (options.listSessions) {
1383
+ const sessions = listSessions();
1384
+ if (sessions.length === 0) {
1385
+ process.stdout.write("No saved sessions for this directory.\n");
1386
+ return;
1387
+ }
1388
+ for (const s of sessions) {
1389
+ const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
1390
+ const display = label ? `${label} (${id})` : id;
1391
+ const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
1392
+ process.stdout.write(
1393
+ `${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
1394
+ ${preview}
1395
+ `
1396
+ );
1397
+ }
1398
+ return;
1399
+ }
1400
+ const config = options.config !== false ? await loadConfig() : void 0;
1401
+ const merged = mergeWithConfig(options, config);
1402
+ const session = resolveSession({
1403
+ explicitPath: options.memory,
1404
+ forceNew: Boolean(options.new),
1405
+ resumeId: options.resume
1406
+ });
1407
+ if (!session.isNew && !options.memory) {
1408
+ process.stdout.write(
1409
+ `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
1410
+ `
1411
+ );
1412
+ }
1413
+ const pluginBundle = await loadPlugins({
1414
+ specs: config?.plugins ?? [],
1415
+ pluginDirs: options.pluginDir ?? []
1416
+ });
1417
+ const configHooks = configHooksToHandlers(config?.hooks);
1418
+ const hookHandlers = [...configHooks, ...pluginBundle.hooks];
1419
+ const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
1420
+ name,
1421
+ command: spec.command,
1422
+ args: spec.args,
1423
+ env: spec.env,
1424
+ timeout: spec.timeout
1425
+ }));
1426
+ const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
1427
+ const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
1428
+ const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
1429
+ const permissionPolicy = {
1430
+ mode: policyMode,
1431
+ rules: (config?.permissions?.rules ?? []).map((r) => ({
1432
+ tool: r.tool,
1433
+ action: r.action,
1434
+ scope: r.scope
1435
+ }))
1436
+ };
1437
+ const chatOptions = {
1438
+ apiKey: merged.apiKey ?? options.apiKey,
1439
+ baseUrl: merged.baseUrl ?? options.baseUrl,
1440
+ provider: merged.provider,
1441
+ model: merged.model,
1442
+ system: merged.system ?? options.system,
1443
+ memoryPath: session.file,
1444
+ sessionId: session.id,
1445
+ tools: merged.tools ?? options.tools,
1446
+ skill: merged.skill ?? options.skill,
1447
+ memoryBackend: merged.memoryBackend ?? options.memoryBackend,
1448
+ agentsKitConfig: config,
1449
+ slashCommands: pluginBundle.slashCommands,
1450
+ extraTools: [...pluginBundle.tools, ...mcpTools],
1451
+ extraSkills: pluginBundle.skills,
1452
+ hookHandlers,
1453
+ permissionPolicy
1454
+ };
1455
+ process.stdout.write(`${renderChatHeader(chatOptions)}
1456
+ `);
1457
+ const instance = ink.render(React2__default.default.createElement(ChatApp, chatOptions));
1458
+ try {
1459
+ await instance.waitUntilExit();
1460
+ } finally {
1461
+ disposeMcpClients(mcpClients);
1462
+ }
1463
+ if (options.memory) {
1464
+ process.stdout.write(
1465
+ `
1466
+ Session saved to ${session.file}. Resume with --memory ${session.file}
1467
+ `
1468
+ );
1469
+ } else {
1470
+ process.stdout.write(
1471
+ `
1472
+ Session saved. Resume with:
1473
+ agentskit chat --resume ${session.id}
1474
+ Or start fresh with:
1475
+ agentskit chat --new
1476
+ `
1477
+ );
1478
+ }
1479
+ });
1480
+ }
1481
+ function formatEvent(event) {
1482
+ switch (event.type) {
1483
+ case "agent:step":
1484
+ return `[step ${event.step}] ${event.action}`;
1485
+ case "llm:start":
1486
+ return `[llm] start (${event.messageCount} messages)`;
1487
+ case "llm:end": {
1488
+ const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
1489
+ return `[llm] done (${event.durationMs}ms) "${preview}"`;
1490
+ }
1491
+ case "tool:start":
1492
+ return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
1493
+ case "tool:end":
1494
+ return `[tool] ${event.name} done (${event.durationMs}ms)`;
1495
+ case "error":
1496
+ return `[error] ${event.error.message}`;
1497
+ default:
1498
+ return `[${event.type}]`;
1499
+ }
1500
+ }
1501
+ async function runAgent(task, options) {
1502
+ if (options.skill && options.skills) {
1503
+ process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
1504
+ process.exit(1);
1505
+ }
1506
+ const { adapter } = resolveChatProvider({
1507
+ provider: options.provider,
1508
+ model: options.model,
1509
+ apiKey: options.apiKey,
1510
+ baseUrl: options.baseUrl
1511
+ });
1512
+ const tools = resolveTools(options.tools);
1513
+ const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1514
+ const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1515
+ const observers = [];
1516
+ if (options.verbose) {
1517
+ observers.push({
1518
+ name: "cli-verbose",
1519
+ on(event) {
1520
+ process.stderr.write(formatEvent(event) + "\n");
1521
+ }
1522
+ });
1523
+ }
1524
+ const runtime$1 = runtime.createRuntime({
1525
+ adapter,
1526
+ tools,
1527
+ memory,
1528
+ systemPrompt: options.systemPrompt,
1529
+ maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1530
+ observers
1531
+ });
1532
+ const result = await runtime$1.run(task, {
1533
+ skill: skill ?? void 0
1534
+ });
1535
+ process.stdout.write(result.content + "\n");
1536
+ }
1537
+ function RunApp({ task, options }) {
1538
+ const [status, setStatus] = React2.useState("running");
1539
+ const [currentStep, setCurrentStep] = React2.useState(0);
1540
+ const [toolCalls, setToolCalls] = React2.useState([]);
1541
+ const [result, setResult] = React2.useState("");
1542
+ const [error, setError] = React2.useState("");
1543
+ const [durationMs, setDurationMs] = React2.useState(0);
1544
+ React2.useEffect(() => {
1545
+ async function execute() {
1546
+ if (options.skill && options.skills) {
1547
+ setError("--skill and --skills are mutually exclusive.");
1548
+ setStatus("error");
1549
+ return;
1550
+ }
1551
+ const { adapter } = resolveChatProvider({
1552
+ provider: options.provider,
1553
+ model: options.model,
1554
+ apiKey: options.apiKey,
1555
+ baseUrl: options.baseUrl
1556
+ });
1557
+ const tools = resolveTools(options.tools);
1558
+ const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1559
+ const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1560
+ const observers = [{
1561
+ name: "run-ui",
1562
+ on(event) {
1563
+ switch (event.type) {
1564
+ case "agent:step":
1565
+ setCurrentStep(event.step);
1566
+ break;
1567
+ case "tool:start":
1568
+ setToolCalls((prev) => [...prev, { name: event.name, status: "running" }]);
1569
+ break;
1570
+ case "tool:end":
1571
+ setToolCalls((prev) => prev.map(
1572
+ (tc) => tc.name === event.name && tc.status === "running" ? { ...tc, status: "done", durationMs: event.durationMs } : tc
1573
+ ));
1574
+ break;
1575
+ case "error":
1576
+ setToolCalls((prev) => prev.map(
1577
+ (tc) => tc.status === "running" ? { ...tc, status: "error" } : tc
1578
+ ));
1579
+ break;
1580
+ }
1581
+ }
1582
+ }];
1583
+ const runtime$1 = runtime.createRuntime({
1584
+ adapter,
1585
+ tools,
1586
+ memory,
1587
+ systemPrompt: options.systemPrompt,
1588
+ maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1589
+ observers
1590
+ });
1591
+ try {
1592
+ const runResult = await runtime$1.run(task, { skill: skill ?? void 0 });
1593
+ setResult(runResult.content);
1594
+ setDurationMs(runResult.durationMs);
1595
+ setStatus("done");
1596
+ } catch (err) {
1597
+ setError(err instanceof Error ? err.message : String(err));
1598
+ setStatus("error");
1599
+ }
1600
+ }
1601
+ void execute();
1602
+ }, []);
1603
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
1604
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: "agentskit run" }),
1605
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1606
+ "Task: ",
1607
+ task
1608
+ ] }),
1609
+ status === "running" && currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "yellow", children: [
1610
+ "\u27F3",
1611
+ " Step ",
1612
+ currentStep
1613
+ ] }),
1614
+ toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: tc.status === "running" ? "yellow" : tc.status === "done" ? "green" : "red", children: [
1615
+ tc.status === "running" ? "\u27F3" : tc.status === "done" ? "\u2713" : "\u2717",
1616
+ " ",
1617
+ tc.name,
1618
+ tc.durationMs !== void 0 ? ` (${tc.durationMs}ms)` : ""
1619
+ ] }) }, i)),
1620
+ status === "running" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "yellow", children: "Running..." }),
1621
+ status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1622
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "green", bold: true, children: [
1623
+ "Done (",
1624
+ durationMs,
1625
+ "ms)"
1626
+ ] }),
1627
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: result })
1628
+ ] }),
1629
+ status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
1630
+ "Error: ",
1631
+ error
1632
+ ] })
1633
+ ] });
1634
+ }
1635
+
1636
+ // src/commands/run.ts
1637
+ function registerRunCommand(program) {
1638
+ program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
1639
+ const task = options.task ?? positionalTask;
1640
+ if (!task) {
1641
+ process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
1642
+ process.exit(1);
201
1643
  }
202
- }
203
- return tools$1;
204
- }
205
- function resolveSkill(skillName) {
206
- if (!skillName) return void 0;
207
- const skill = skillRegistry[skillName.trim()];
208
- if (!skill) {
209
- process.stderr.write(`Unknown skill: ${skillName}
210
- `);
211
- return void 0;
212
- }
213
- return skill;
214
- }
215
- function resolveSkills(skillNames) {
216
- if (!skillNames) return void 0;
217
- const names = skillNames.split(",").map((s) => s.trim());
218
- const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
219
- if (resolved.length === 0) {
220
- process.stderr.write(`No valid skills found in: ${skillNames}
221
- `);
222
- return void 0;
223
- }
224
- if (resolved.length === 1) return resolved[0];
225
- return skills.composeSkills(...resolved);
226
- }
227
- function resolveMemory(backend, memoryPath) {
228
- switch (backend) {
229
- case "sqlite":
230
- return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
231
- case "file":
232
- default:
233
- return memory.fileChatMemory(memoryPath);
234
- }
235
- }
236
- function groupIntoTurns(messages) {
237
- const turns = [];
238
- let current = [];
239
- for (const message of messages) {
240
- if (message.role === "user") {
241
- if (current.length > 0) turns.push(current);
242
- current = [message];
243
- } else if (message.role === "system") {
244
- if (current.length > 0) turns.push(current);
245
- turns.push([message]);
246
- current = [];
1644
+ const config = options.config !== false ? await loadConfig() : void 0;
1645
+ const merged = mergeWithConfig(options, config);
1646
+ if (options.pretty) {
1647
+ ink.render(React2__default.default.createElement(RunApp, { task, options }));
247
1648
  } else {
248
- current.push(message);
1649
+ try {
1650
+ await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
1651
+ } catch (err) {
1652
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1653
+ `);
1654
+ process.exit(1);
1655
+ }
249
1656
  }
250
- }
251
- if (current.length > 0) turns.push(current);
252
- return turns;
253
- }
254
- function ChatApp(options) {
255
- const runtime = React3.useMemo(() => resolveChatProvider(options), [
256
- options.apiKey,
257
- options.baseUrl,
258
- options.model,
259
- options.provider
260
- ]);
261
- const memory = React3.useMemo(
262
- () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
263
- [options.memoryPath, options.memoryBackend]
264
- );
265
- const tools = React3.useMemo(() => resolveTools(options.tools), [options.tools]);
266
- const skills = React3.useMemo(() => {
267
- if (!options.skill) return void 0;
268
- const names = options.skill.split(",").map((s) => s.trim());
269
- const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
270
- if (resolved.length === 0) return void 0;
271
- return resolved;
272
- }, [options.skill]);
273
- const chat = ink$1.useChat({
274
- adapter: runtime.adapter,
275
- memory,
276
- systemPrompt: options.system,
277
- tools: tools.length > 0 ? tools : void 0,
278
- skills
279
1657
  });
280
- const turns = React3.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
281
- const toolNames = options.tools ? options.tools.split(",").map((s) => s.trim()).filter(Boolean) : [];
282
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
283
- /* @__PURE__ */ jsxRuntime.jsx(
284
- ink$1.StatusHeader,
285
- {
286
- provider: runtime.provider,
287
- model: runtime.model,
288
- mode: runtime.mode,
289
- tools: toolNames,
290
- messageCount: chat.messages.length
291
- }
292
- ),
293
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.ChatContainer, { children: turns.map((turn, turnIdx) => {
294
- const assistantSteps = turn.filter((m) => m.role === "assistant").length;
295
- let stepIndex = 0;
296
- return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
297
- const showStep = message.role === "assistant" && assistantSteps > 1;
298
- if (showStep) stepIndex++;
299
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
300
- showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
301
- "\u21BB step ",
302
- stepIndex,
303
- "/",
304
- assistantSteps
305
- ] }) : null,
306
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.Message, { message }),
307
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ink$1.ToolCallView, { toolCall, expanded: true }, toolCall.id))
308
- ] }, message.id);
309
- }) }, `turn-${turnIdx}`);
310
- }) }),
311
- /* @__PURE__ */ jsxRuntime.jsx(
312
- ink$1.ThinkingIndicator,
313
- {
314
- visible: chat.status === "streaming",
315
- label: toolNames.length > 0 ? "agent working" : "thinking"
316
- }
317
- ),
318
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.InputBar, { chat, placeholder: "Type a message and press Enter\u2026" })
319
- ] });
320
- }
321
- function renderChatHeader(options) {
322
- const runtime = resolveChatProvider(options);
323
- const parts = [`provider=${runtime.provider}`];
324
- if (runtime.model) parts.push(`model=${runtime.model}`);
325
- parts.push(`mode=${runtime.mode}`);
326
- if (options.tools) parts.push(`tools=${options.tools}`);
327
- if (options.skill) parts.push(`skill=${options.skill}`);
328
- if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
329
- return parts.join(" ");
330
1658
  }
331
1659
  var PROVIDER_IMPORT = {
332
1660
  openai: "openai",
@@ -928,159 +2256,42 @@ function printNextSteps(options) {
928
2256
  `);
929
2257
  process.stdout.write(kleur__default.default.dim(" Docs: https://www.agentskit.io/docs\n\n"));
930
2258
  }
931
- function formatEvent(event) {
932
- switch (event.type) {
933
- case "agent:step":
934
- return `[step ${event.step}] ${event.action}`;
935
- case "llm:start":
936
- return `[llm] start (${event.messageCount} messages)`;
937
- case "llm:end": {
938
- const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
939
- return `[llm] done (${event.durationMs}ms) "${preview}"`;
940
- }
941
- case "tool:start":
942
- return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
943
- case "tool:end":
944
- return `[tool] ${event.name} done (${event.durationMs}ms)`;
945
- case "error":
946
- return `[error] ${event.error.message}`;
947
- default:
948
- return `[${event.type}]`;
949
- }
950
- }
951
- async function runAgent(task, options) {
952
- if (options.skill && options.skills) {
953
- process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
954
- process.exit(1);
955
- }
956
- const { adapter } = resolveChatProvider({
957
- provider: options.provider,
958
- model: options.model,
959
- apiKey: options.apiKey,
960
- baseUrl: options.baseUrl
961
- });
962
- const tools = resolveTools(options.tools);
963
- const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
964
- const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
965
- const observers = [];
966
- if (options.verbose) {
967
- observers.push({
968
- name: "cli-verbose",
969
- on(event) {
970
- process.stderr.write(formatEvent(event) + "\n");
971
- }
972
- });
973
- }
974
- const runtime$1 = runtime.createRuntime({
975
- adapter,
976
- tools,
977
- memory,
978
- systemPrompt: options.systemPrompt,
979
- maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
980
- observers
981
- });
982
- const result = await runtime$1.run(task, {
983
- skill: skill ?? void 0
984
- });
985
- process.stdout.write(result.content + "\n");
986
- }
987
- function RunApp({ task, options }) {
988
- const [status, setStatus] = React3.useState("running");
989
- const [currentStep, setCurrentStep] = React3.useState(0);
990
- const [toolCalls, setToolCalls] = React3.useState([]);
991
- const [result, setResult] = React3.useState("");
992
- const [error, setError] = React3.useState("");
993
- const [durationMs, setDurationMs] = React3.useState(0);
994
- React3.useEffect(() => {
995
- async function execute() {
996
- if (options.skill && options.skills) {
997
- setError("--skill and --skills are mutually exclusive.");
998
- setStatus("error");
999
- return;
1000
- }
1001
- const { adapter } = resolveChatProvider({
1002
- provider: options.provider,
1003
- model: options.model,
1004
- apiKey: options.apiKey,
1005
- baseUrl: options.baseUrl
1006
- });
1007
- const tools = resolveTools(options.tools);
1008
- const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1009
- const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1010
- const observers = [{
1011
- name: "run-ui",
1012
- on(event) {
1013
- switch (event.type) {
1014
- case "agent:step":
1015
- setCurrentStep(event.step);
1016
- break;
1017
- case "tool:start":
1018
- setToolCalls((prev) => [...prev, { name: event.name, status: "running" }]);
1019
- break;
1020
- case "tool:end":
1021
- setToolCalls((prev) => prev.map(
1022
- (tc) => tc.name === event.name && tc.status === "running" ? { ...tc, status: "done", durationMs: event.durationMs } : tc
1023
- ));
1024
- break;
1025
- case "error":
1026
- setToolCalls((prev) => prev.map(
1027
- (tc) => tc.status === "running" ? { ...tc, status: "error" } : tc
1028
- ));
1029
- break;
1030
- }
1031
- }
1032
- }];
1033
- const runtime$1 = runtime.createRuntime({
1034
- adapter,
1035
- tools,
1036
- memory,
1037
- systemPrompt: options.systemPrompt,
1038
- maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1039
- observers
2259
+
2260
+ // src/commands/init.ts
2261
+ function registerInitCommand(program) {
2262
+ program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|ink|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
2263
+ const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
2264
+ let resolved;
2265
+ if (isCi) {
2266
+ const template = rawOptions.template ?? "react";
2267
+ resolved = {
2268
+ targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
2269
+ template,
2270
+ provider: rawOptions.provider ?? "demo",
2271
+ tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
2272
+ memory: rawOptions.memory ?? "none",
2273
+ packageManager: rawOptions.pm ?? "pnpm"
2274
+ };
2275
+ } else {
2276
+ const result = await runInteractiveInit({
2277
+ dir: rawOptions.dir,
2278
+ template: rawOptions.template
1040
2279
  });
1041
- try {
1042
- const runResult = await runtime$1.run(task, { skill: skill ?? void 0 });
1043
- setResult(runResult.content);
1044
- setDurationMs(runResult.durationMs);
1045
- setStatus("done");
1046
- } catch (err) {
1047
- setError(err instanceof Error ? err.message : String(err));
1048
- setStatus("error");
2280
+ if (result.cancelled) {
2281
+ process.exit(0);
1049
2282
  }
2283
+ resolved = result.options;
1050
2284
  }
1051
- void execute();
1052
- }, []);
1053
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
1054
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: "agentskit run" }),
1055
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1056
- "Task: ",
1057
- task
1058
- ] }),
1059
- status === "running" && currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "yellow", children: [
1060
- "\u27F3",
1061
- " Step ",
1062
- currentStep
1063
- ] }),
1064
- toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: tc.status === "running" ? "yellow" : tc.status === "done" ? "green" : "red", children: [
1065
- tc.status === "running" ? "\u27F3" : tc.status === "done" ? "\u2713" : "\u2717",
1066
- " ",
1067
- tc.name,
1068
- tc.durationMs !== void 0 ? ` (${tc.durationMs}ms)` : ""
1069
- ] }) }, i)),
1070
- status === "running" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "yellow", children: "Running..." }),
1071
- status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1072
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "green", bold: true, children: [
1073
- "Done (",
1074
- durationMs,
1075
- "ms)"
1076
- ] }),
1077
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: result })
1078
- ] }),
1079
- status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
1080
- "Error: ",
1081
- error
1082
- ] })
1083
- ] });
2285
+ await writeStarterProject(resolved);
2286
+ if (isCi) {
2287
+ process.stdout.write(
2288
+ `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
2289
+ `
2290
+ );
2291
+ } else {
2292
+ printNextSteps(resolved);
2293
+ }
2294
+ });
1084
2295
  }
1085
2296
  var PROVIDER_ENV_KEYS = {
1086
2297
  openai: "OPENAI_API_KEY",
@@ -1141,8 +2352,8 @@ async function checkPnpm() {
1141
2352
  };
1142
2353
  }
1143
2354
  async function checkPackageJson() {
1144
- const path4 = path.join(process.cwd(), "package.json");
1145
- if (!fs.existsSync(path4)) {
2355
+ const path5 = path.join(process.cwd(), "package.json");
2356
+ if (!fs.existsSync(path5)) {
1146
2357
  return {
1147
2358
  status: "warn",
1148
2359
  name: "package.json",
@@ -1151,7 +2362,7 @@ async function checkPackageJson() {
1151
2362
  };
1152
2363
  }
1153
2364
  try {
1154
- const pkg = JSON.parse(await promises.readFile(path4, "utf8"));
2365
+ const pkg = JSON.parse(await promises.readFile(path5, "utf8"));
1155
2366
  const deps = {
1156
2367
  ...pkg.dependencies ?? {},
1157
2368
  ...pkg.devDependencies ?? {}
@@ -1347,6 +2558,26 @@ function renderReport(report, opts = {}) {
1347
2558
  lines.push("");
1348
2559
  return lines.join("\n");
1349
2560
  }
2561
+
2562
+ // src/commands/doctor.ts
2563
+ function registerDoctorCommand(program) {
2564
+ program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
2565
+ "--providers <providers>",
2566
+ "Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
2567
+ ).option("--json", "Emit JSON instead of formatted output").action(async (options) => {
2568
+ const providers2 = options.providers ? options.providers.split(",").map((p) => p.trim()).filter(Boolean) : void 0;
2569
+ const report = await runDoctor({
2570
+ providers: providers2,
2571
+ noNetwork: options.network === false
2572
+ });
2573
+ if (options.json) {
2574
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
2575
+ } else {
2576
+ process.stdout.write(renderReport(report, { color: process.stdout.isTTY }));
2577
+ }
2578
+ if (report.fail > 0) process.exit(1);
2579
+ });
2580
+ }
1350
2581
  var DEFAULT_WATCH = [
1351
2582
  "**/*.ts",
1352
2583
  "**/*.tsx",
@@ -1413,11 +2644,11 @@ function startDev(options) {
1413
2644
  }
1414
2645
  });
1415
2646
  };
1416
- const restart = (path4) => {
2647
+ const restart = (path5) => {
1417
2648
  if (restartTimer) clearTimeout(restartTimer);
1418
2649
  restartTimer = setTimeout(() => {
1419
2650
  restartTimer = void 0;
1420
- banner(`\u21BB change detected \u2014 ${path4}`, "yellow");
2651
+ banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
1421
2652
  if (child && !child.killed && child.exitCode === null) {
1422
2653
  child.kill("SIGTERM");
1423
2654
  }
@@ -1454,6 +2685,73 @@ function startDev(options) {
1454
2685
  restarts: () => restartCount
1455
2686
  };
1456
2687
  }
2688
+
2689
+ // src/commands/dev.ts
2690
+ function registerDevCommand(program) {
2691
+ program.command("dev [entry]").description("Run an entry file with hot-reload on file changes.").option("--watch <globs>", "Comma-separated glob patterns to watch").option("--ignore <globs>", "Comma-separated glob patterns to ignore").option("--debounce <ms>", "Debounce window before restart", "200").action(async (positional, options) => {
2692
+ const entry = positional ?? "src/index.ts";
2693
+ const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
2694
+ const ignore = options.ignore ? options.ignore.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
2695
+ try {
2696
+ const controller = startDev({
2697
+ entry,
2698
+ watch,
2699
+ ignore,
2700
+ debounceMs: Number(options.debounce) || 200
2701
+ });
2702
+ await controller.done;
2703
+ } catch (err) {
2704
+ process.stderr.write(`Error: ${err.message}
2705
+ `);
2706
+ process.exit(1);
2707
+ }
2708
+ });
2709
+ }
2710
+ function registerConfigCommand(program) {
2711
+ program.command("config").description("Show or scaffold the AgentsKit config.").argument(
2712
+ "[action]",
2713
+ 'Action: "init" to create a template, "show" to print the merged config.',
2714
+ "show"
2715
+ ).option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
2716
+ const isLocal = Boolean(options.local);
2717
+ const targetPath = isLocal ? path__default.default.join(process.cwd(), ".agentskit.config.json") : path__default.default.join(os.homedir(), ".agentskit", "config.json");
2718
+ if (action === "show") {
2719
+ const config = await loadConfig();
2720
+ process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
2721
+ return;
2722
+ }
2723
+ if (action !== "init") {
2724
+ process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
2725
+ `);
2726
+ process.exit(2);
2727
+ }
2728
+ if (fs.existsSync(targetPath) && !options.force) {
2729
+ process.stderr.write(
2730
+ `Config already exists at ${targetPath}. Re-run with --force to overwrite.
2731
+ `
2732
+ );
2733
+ process.exit(1);
2734
+ }
2735
+ const template = {
2736
+ defaults: {
2737
+ provider: "openai",
2738
+ baseUrl: "https://openrouter.ai/api",
2739
+ apiKeyEnv: "OPENROUTER_API_KEY",
2740
+ model: "openai/gpt-oss-120b:free",
2741
+ tools: "web_search,fetch_url"
2742
+ }
2743
+ };
2744
+ fs.mkdirSync(path__default.default.dirname(targetPath), { recursive: true });
2745
+ fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2746
+ process.stdout.write(
2747
+ `Wrote ${targetPath}
2748
+ Edit it to taste, then run:
2749
+ agentskit chat
2750
+ (flags on the CLI still win over config values.)
2751
+ `
2752
+ );
2753
+ });
2754
+ }
1457
2755
  async function startTunnel(options) {
1458
2756
  const stdout = options.stdout ?? process.stdout;
1459
2757
  const open = options.open ?? (async (opts) => {
@@ -1519,125 +2817,8 @@ async function startTunnel(options) {
1519
2817
  };
1520
2818
  }
1521
2819
 
1522
- // src/commands.ts
1523
- function mergeWithConfig(options, config) {
1524
- if (!config) return options;
1525
- return {
1526
- ...options,
1527
- // Config defaults — only apply if CLI flag wasn't set
1528
- provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
1529
- model: options.model ?? config.defaults?.model
1530
- };
1531
- }
1532
- function createCli() {
1533
- const program = new commander.Command();
1534
- program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1535
- 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>", "Path for file-based memory", ".agentskit-history.json").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
1536
- const config = options.config !== false ? await loadConfig() : void 0;
1537
- const merged = mergeWithConfig(options, config);
1538
- const chatOptions = {
1539
- apiKey: merged.apiKey ?? options.apiKey,
1540
- baseUrl: merged.baseUrl ?? options.baseUrl,
1541
- provider: merged.provider,
1542
- model: merged.model,
1543
- system: options.system,
1544
- memoryPath: options.memory,
1545
- tools: options.tools,
1546
- skill: options.skill,
1547
- memoryBackend: options.memoryBackend,
1548
- agentsKitConfig: config
1549
- };
1550
- process.stdout.write(`${renderChatHeader(chatOptions)}
1551
- `);
1552
- ink.render(React3__default.default.createElement(ChatApp, chatOptions));
1553
- });
1554
- 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) => {
1555
- const task = options.task ?? positionalTask;
1556
- if (!task) {
1557
- process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
1558
- process.exit(1);
1559
- }
1560
- const config = options.config !== false ? await loadConfig() : void 0;
1561
- const merged = mergeWithConfig(options, config);
1562
- if (options.pretty) {
1563
- ink.render(React3__default.default.createElement(RunApp, { task, options }));
1564
- } else {
1565
- try {
1566
- await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
1567
- } catch (err) {
1568
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1569
- `);
1570
- process.exit(1);
1571
- }
1572
- }
1573
- });
1574
- 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) => {
1575
- const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
1576
- let resolved;
1577
- if (isCi) {
1578
- const template = rawOptions.template ?? "react";
1579
- resolved = {
1580
- targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
1581
- template,
1582
- provider: rawOptions.provider ?? "demo",
1583
- tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
1584
- memory: rawOptions.memory ?? "none",
1585
- packageManager: rawOptions.pm ?? "pnpm"
1586
- };
1587
- } else {
1588
- const result = await runInteractiveInit({
1589
- dir: rawOptions.dir,
1590
- template: rawOptions.template
1591
- });
1592
- if (result.cancelled) {
1593
- process.exit(0);
1594
- }
1595
- resolved = result.options;
1596
- }
1597
- await writeStarterProject(resolved);
1598
- if (isCi) {
1599
- process.stdout.write(
1600
- `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
1601
- `
1602
- );
1603
- } else {
1604
- printNextSteps(resolved);
1605
- }
1606
- });
1607
- program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
1608
- "--providers <providers>",
1609
- "Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
1610
- ).option("--json", "Emit JSON instead of formatted output").action(async (options) => {
1611
- const providers2 = options.providers ? options.providers.split(",").map((p) => p.trim()).filter(Boolean) : void 0;
1612
- const report = await runDoctor({
1613
- providers: providers2,
1614
- noNetwork: options.network === false
1615
- });
1616
- if (options.json) {
1617
- process.stdout.write(JSON.stringify(report, null, 2) + "\n");
1618
- } else {
1619
- process.stdout.write(renderReport(report, { color: process.stdout.isTTY }));
1620
- }
1621
- if (report.fail > 0) process.exit(1);
1622
- });
1623
- 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) => {
1624
- const entry = positional ?? "src/index.ts";
1625
- const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1626
- const ignore = options.ignore ? options.ignore.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1627
- try {
1628
- const controller = startDev({
1629
- entry,
1630
- watch,
1631
- ignore,
1632
- debounceMs: Number(options.debounce) || 200
1633
- });
1634
- await controller.done;
1635
- } catch (err) {
1636
- process.stderr.write(`Error: ${err.message}
1637
- `);
1638
- process.exit(1);
1639
- }
1640
- });
2820
+ // src/commands/tunnel.ts
2821
+ function registerTunnelCommand(program) {
1641
2822
  program.command("tunnel <port>").description("Open a public URL pointing to a local port (great for webhooks).").option("--subdomain <name>", "Hint for a stable subdomain (provider may decline)").option("--host <host>", "Local hostname", "localhost").action(async (port, options) => {
1642
2823
  const portNum = Number(port);
1643
2824
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -1658,6 +2839,130 @@ function createCli() {
1658
2839
  process.exit(1);
1659
2840
  }
1660
2841
  });
2842
+ }
2843
+
2844
+ // src/extensibility/rag/embedders.ts
2845
+ function createOpenAiEmbedder(config) {
2846
+ const model = config.model ?? "text-embedding-3-small";
2847
+ const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
2848
+ return async (text) => {
2849
+ const res = await fetch(`${baseUrl}/v1/embeddings`, {
2850
+ method: "POST",
2851
+ headers: {
2852
+ "content-type": "application/json",
2853
+ authorization: `Bearer ${config.apiKey}`
2854
+ },
2855
+ body: JSON.stringify({ model, input: text })
2856
+ });
2857
+ if (!res.ok) {
2858
+ const body = await res.text().catch(() => "");
2859
+ throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
2860
+ }
2861
+ const json = await res.json();
2862
+ const first = json.data?.[0]?.embedding;
2863
+ if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
2864
+ return first;
2865
+ };
2866
+ }
2867
+ function resolveEmbedder(config) {
2868
+ const embedder = config.embedder;
2869
+ const provider = embedder?.provider ?? "openai";
2870
+ if (provider !== "openai") {
2871
+ throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
2872
+ }
2873
+ const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
2874
+ if (!apiKey) {
2875
+ throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
2876
+ }
2877
+ return createOpenAiEmbedder({
2878
+ apiKey,
2879
+ model: embedder?.model,
2880
+ baseUrl: embedder?.baseUrl
2881
+ });
2882
+ }
2883
+ function buildRagFromConfig(options) {
2884
+ const cwd = options.cwd ?? process.cwd();
2885
+ const dir = path.resolve(cwd, options.config.dir ?? "./.agentskit-rag");
2886
+ const store = memory.fileVectorMemory({ path: `${dir}/store.json` });
2887
+ const embed = options.embedder ?? resolveEmbedder(options.config);
2888
+ return rag.createRAG({
2889
+ embed,
2890
+ store,
2891
+ chunkSize: options.config.chunkSize,
2892
+ topK: options.config.topK
2893
+ });
2894
+ }
2895
+ async function indexSources(rag, config, cwd) {
2896
+ const root = process.cwd();
2897
+ const sources = config.sources ?? [];
2898
+ const absolutePaths = [];
2899
+ for (const pattern of sources) {
2900
+ for await (const match of promises.glob(pattern, { cwd: root })) {
2901
+ absolutePaths.push(path.resolve(root, match));
2902
+ }
2903
+ }
2904
+ const documents = await Promise.all(
2905
+ absolutePaths.map(async (path5) => ({
2906
+ id: path5,
2907
+ content: await promises.readFile(path5, "utf8"),
2908
+ source: path5
2909
+ }))
2910
+ );
2911
+ if (documents.length > 0) await rag.ingest(documents);
2912
+ return { documentCount: documents.length, sources: absolutePaths };
2913
+ }
2914
+
2915
+ // src/commands/rag.ts
2916
+ function registerRagCommand(program) {
2917
+ const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
2918
+ rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
2919
+ "--source <glob>",
2920
+ "Glob to index (overrides config.rag.sources; repeatable)",
2921
+ (value, prev = []) => [...prev, value],
2922
+ []
2923
+ ).action(async (options) => {
2924
+ const config = await loadConfig();
2925
+ const rawConfig = config?.rag;
2926
+ const overrideSources = options.source;
2927
+ const ragConfig = {
2928
+ ...rawConfig ?? {},
2929
+ sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
2930
+ };
2931
+ if (!ragConfig.sources || ragConfig.sources.length === 0) {
2932
+ process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
2933
+ process.exit(1);
2934
+ }
2935
+ try {
2936
+ const instance = buildRagFromConfig({ config: ragConfig });
2937
+ const result = await indexSources(instance, ragConfig);
2938
+ process.stdout.write(
2939
+ `Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
2940
+ `
2941
+ );
2942
+ for (const source of result.sources) {
2943
+ process.stdout.write(` \u2022 ${source}
2944
+ `);
2945
+ }
2946
+ } catch (err) {
2947
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2948
+ `);
2949
+ process.exit(1);
2950
+ }
2951
+ });
2952
+ }
2953
+
2954
+ // src/commands/index.ts
2955
+ function createCli() {
2956
+ const program = new commander.Command();
2957
+ program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
2958
+ registerChatCommand(program);
2959
+ registerRunCommand(program);
2960
+ registerInitCommand(program);
2961
+ registerDoctorCommand(program);
2962
+ registerDevCommand(program);
2963
+ registerConfigCommand(program);
2964
+ registerTunnelCommand(program);
2965
+ registerRagCommand(program);
1661
2966
  return program;
1662
2967
  }
1663
2968