@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/index.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$1 = require('ink');
6
4
  var commander = require('commander');
7
- var path = require('path');
5
+ var React2 = require('react');
6
+ var ink$1 = require('ink');
8
7
  var promises = require('fs/promises');
8
+ var os = require('os');
9
+ var path = require('path');
9
10
  var ink = 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,39 @@ async function loadPackageJsonConfig(dir) {
54
58
  return void 0;
55
59
  }
56
60
  }
57
- async function loadConfig(options) {
58
- const cwd = path.resolve(options?.cwd ?? 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
+ if (home === null) return void 0;
83
+ const globalDir = path.join(home ?? os.homedir(), ".agentskit");
84
+ const tsConfig = await loadTsConfig(path.join(globalDir, "config.ts"));
85
+ if (tsConfig) return tsConfig;
86
+ return await loadJsonConfig(path.join(globalDir, "config.json"));
87
+ }
88
+ async function loadConfig(options) {
89
+ const cwd = path.resolve(options?.cwd ?? process.cwd());
90
+ const global = await loadGlobalConfig(options?.home);
91
+ const local = await loadLocalConfig(cwd);
92
+ return mergeConfigs(global, local);
93
+ }
67
94
  var providers = {
68
95
  openai: {
69
96
  label: "OpenAI",
@@ -123,7 +150,7 @@ function createDemoAdapter(provider, model) {
123
150
  ].join(" ");
124
151
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
125
152
  if (cancelled) return;
126
- await new Promise((resolve2) => setTimeout(resolve2, 35));
153
+ await new Promise((resolve4) => setTimeout(resolve4, 35));
127
154
  yield { type: "text", content: chunk };
128
155
  }
129
156
  yield { type: "done" };
@@ -174,159 +201,1468 @@ function resolveChatProvider(options) {
174
201
  summary: `${entry.label} live adapter`
175
202
  };
176
203
  }
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
- `);
204
+ var ROOT = path.join(os.homedir(), ".agentskit", "sessions");
205
+ var META_SUFFIX = ".meta.json";
206
+ function cwdHash(cwd = process.cwd()) {
207
+ return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
208
+ }
209
+ function dirFor(cwd = process.cwd()) {
210
+ return path.join(ROOT, cwdHash(cwd));
211
+ }
212
+ function ensureDir(dir) {
213
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
214
+ }
215
+ function generateSessionId() {
216
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
217
+ const suffix = crypto.randomBytes(3).toString("hex");
218
+ return `${ts}-${suffix}`;
219
+ }
220
+ function sessionFilePath(id, cwd = process.cwd()) {
221
+ ensureDir(dirFor(cwd));
222
+ return path.join(dirFor(cwd), `${id}.json`);
223
+ }
224
+ function metaPath(id, cwd = process.cwd()) {
225
+ return path.join(dirFor(cwd), `${id}${META_SUFFIX}`);
226
+ }
227
+ function readMeta(id, cwd = process.cwd()) {
228
+ const path5 = metaPath(id, cwd);
229
+ if (!fs.existsSync(path5)) return null;
230
+ try {
231
+ return JSON.parse(fs.readFileSync(path5, "utf8"));
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+ function writeSessionMeta(meta, cwd = process.cwd()) {
237
+ ensureDir(dirFor(cwd));
238
+ fs.writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
239
+ }
240
+ function derivePreview(messages) {
241
+ const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
242
+ if (!firstUser) return "(empty)";
243
+ const single = firstUser.content.replace(/\s+/g, " ").trim();
244
+ return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
245
+ }
246
+ function listSessions(cwd = process.cwd()) {
247
+ const dir = dirFor(cwd);
248
+ if (!fs.existsSync(dir)) return [];
249
+ const entries = fs.readdirSync(dir);
250
+ const records = [];
251
+ for (const entry of entries) {
252
+ if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
253
+ const id = entry.replace(/\.json$/, "");
254
+ const meta = readMeta(id, cwd);
255
+ const file = path.join(dir, entry);
256
+ if (meta) {
257
+ records.push({ metadata: meta, file });
258
+ } else {
259
+ const stats = fs.statSync(file);
260
+ records.push({
261
+ metadata: {
262
+ id,
263
+ cwd,
264
+ createdAt: stats.birthtime.toISOString(),
265
+ updatedAt: stats.mtime.toISOString(),
266
+ messageCount: 0,
267
+ preview: "(legacy session)"
268
+ },
269
+ file
270
+ });
271
+ }
272
+ }
273
+ records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
274
+ return records;
275
+ }
276
+ function findLatestSession(cwd = process.cwd()) {
277
+ const all = listSessions(cwd);
278
+ return all[0] ?? null;
279
+ }
280
+ function findSession(id, cwd = process.cwd()) {
281
+ const all = listSessions(cwd);
282
+ const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
283
+ if (exact) return exact;
284
+ const prefix = all.find((s) => s.metadata.id.startsWith(id));
285
+ return prefix ?? null;
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
+ }
316
+ function resolveSession(input2) {
317
+ const cwd = input2.cwd ?? process.cwd();
318
+ if (input2.explicitPath) {
319
+ return { id: "custom", file: input2.explicitPath, isNew: !fs.existsSync(input2.explicitPath) };
320
+ }
321
+ if (input2.forceNew) {
322
+ const id2 = generateSessionId();
323
+ return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
324
+ }
325
+ if (input2.resumeId) {
326
+ const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
327
+ if (target) {
328
+ return { id: target.metadata.id, file: target.file, isNew: false };
329
+ }
330
+ process.stderr.write(
331
+ `No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
332
+ `
333
+ );
334
+ }
335
+ const latest = findLatestSession(cwd);
336
+ if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
337
+ const id = generateSessionId();
338
+ return { id, file: sessionFilePath(id, cwd), isNew: true };
339
+ }
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
+
378
+ // src/slash-commands.ts
379
+ function parseSlashCommand(input2) {
380
+ if (!input2.startsWith("/")) return null;
381
+ const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
382
+ if (!match) return null;
383
+ return { name: match[1], args: match[2] ?? "" };
384
+ }
385
+ function createSlashRegistry(commands) {
386
+ const map = /* @__PURE__ */ new Map();
387
+ for (const cmd of commands) {
388
+ map.set(cmd.name, cmd);
389
+ for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
390
+ }
391
+ return map;
392
+ }
393
+ var builtinSlashCommands = [
394
+ {
395
+ name: "help",
396
+ aliases: ["?"],
397
+ description: "List available slash commands.",
398
+ run(ctx) {
399
+ const seen = /* @__PURE__ */ new Set();
400
+ const lines = [];
401
+ for (const cmd of ctx.commands) {
402
+ if (seen.has(cmd.name)) continue;
403
+ seen.add(cmd.name);
404
+ const suffix = cmd.usage ? ` (${cmd.usage})` : "";
405
+ lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
406
+ }
407
+ ctx.feedback(`Slash commands:
408
+ ${lines.join("\n")}`, "info");
409
+ }
410
+ },
411
+ {
412
+ name: "model",
413
+ description: "Switch the active model.",
414
+ usage: "/model <name>",
415
+ run(ctx, args) {
416
+ const value = args.trim();
417
+ if (!value) {
418
+ ctx.feedback(
419
+ `Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
420
+ "warn"
421
+ );
422
+ return;
423
+ }
424
+ ctx.setModel(value);
425
+ ctx.feedback(`Model \u2192 ${value}`, "success");
426
+ }
427
+ },
428
+ {
429
+ name: "provider",
430
+ description: "Switch the adapter provider.",
431
+ usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
432
+ run(ctx, args) {
433
+ const value = args.trim();
434
+ if (!value) {
435
+ ctx.feedback(
436
+ `Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
437
+ "warn"
438
+ );
439
+ return;
440
+ }
441
+ ctx.setProvider(value);
442
+ ctx.feedback(`Provider \u2192 ${value}`, "success");
443
+ }
444
+ },
445
+ {
446
+ name: "base-url",
447
+ aliases: ["baseurl"],
448
+ description: "Override provider base URL.",
449
+ usage: "/base-url <url|clear>",
450
+ run(ctx, args) {
451
+ const value = args.trim();
452
+ if (!value || value === "clear") {
453
+ ctx.setBaseUrl(void 0);
454
+ ctx.feedback("Base URL cleared.", "success");
455
+ return;
456
+ }
457
+ ctx.setBaseUrl(value);
458
+ ctx.feedback(`Base URL \u2192 ${value}`, "success");
459
+ }
460
+ },
461
+ {
462
+ name: "tools",
463
+ description: "Set active tools (comma-separated) or clear them.",
464
+ usage: "/tools web_search,fetch_url | /tools clear",
465
+ run(ctx, args) {
466
+ const value = args.trim();
467
+ if (!value || value === "clear") {
468
+ ctx.setTools(void 0);
469
+ ctx.feedback("Tools reset to defaults.", "success");
470
+ return;
471
+ }
472
+ ctx.setTools(value);
473
+ ctx.feedback(`Tools \u2192 ${value}`, "success");
474
+ }
475
+ },
476
+ {
477
+ name: "skill",
478
+ description: "Set active skill(s) (comma-separated) or clear them.",
479
+ usage: "/skill researcher,coder | /skill clear",
480
+ run(ctx, args) {
481
+ const value = args.trim();
482
+ if (!value || value === "clear") {
483
+ ctx.setSkill(void 0);
484
+ ctx.feedback("Skills cleared.", "success");
485
+ return;
486
+ }
487
+ ctx.setSkill(value);
488
+ ctx.feedback(`Skills \u2192 ${value}`, "success");
489
+ }
490
+ },
491
+ {
492
+ name: "clear",
493
+ aliases: ["reset"],
494
+ description: "Clear the conversation history in this session.",
495
+ async run(ctx) {
496
+ await ctx.chat.clear();
497
+ ctx.feedback("History cleared.", "success");
498
+ }
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
+ },
583
+ {
584
+ name: "exit",
585
+ aliases: ["quit", "q"],
586
+ description: "Exit the chat.",
587
+ run() {
588
+ process.exit(0);
589
+ }
590
+ }
591
+ ];
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
+ `);
631
+ }
632
+ }
633
+ return tools;
634
+ }
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(
713
+ () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
714
+ [provider, model, apiKey, baseUrl]
715
+ );
716
+ const memory = React2.useMemo(
717
+ () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
718
+ [options.memoryPath, options.memoryBackend]
719
+ );
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(() => {
726
+ if (!skillFlag) return void 0;
727
+ const names = skillFlag.split(",").map((s) => s.trim());
728
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
729
+ if (resolved.length === 0) return void 0;
730
+ return resolved;
731
+ }, [skillFlag]);
732
+ return {
733
+ runtime,
734
+ memory,
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(() => {
750
+ if (sessionAllowed.size === 0) return;
751
+ for (const message of chat.messages) {
752
+ for (const call of message.toolCalls ?? []) {
753
+ if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
754
+ autoApprovedRef.current.add(call.id);
755
+ void chat.approve(call.id);
756
+ }
757
+ }
758
+ }
759
+ }, [chat.messages, sessionAllowed, chat.approve]);
760
+ const handleApproveAlways = (toolCallId, toolName) => {
761
+ setSessionAllowed((prev) => {
762
+ if (prev.has(toolName)) return prev;
763
+ const next = new Set(prev);
764
+ next.add(toolName);
765
+ return next;
766
+ });
767
+ autoApprovedRef.current.add(toolCallId);
768
+ void chat.approve(toolCallId);
769
+ };
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(() => {
785
+ const sessionId = options.sessionId;
786
+ if (!sessionId || sessionId === "custom") return;
787
+ if (!sessionCreatedAtRef.current) {
788
+ sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
789
+ }
790
+ try {
791
+ writeSessionMeta({
792
+ id: sessionId,
793
+ cwd: process.cwd(),
794
+ createdAt: sessionCreatedAtRef.current,
795
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
796
+ messageCount,
797
+ preview: derivePreview(options.messages),
798
+ provider: options.provider,
799
+ model: options.model
800
+ });
801
+ } catch {
802
+ }
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]);
988
+ const handleSubmitInput = async (raw) => {
989
+ const parsed = parseSlashCommand(raw);
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
+ }
1002
+ setFeedback(null);
1003
+ return false;
1004
+ }
1005
+ const cmd = slashRegistry.get(parsed.name);
1006
+ if (!cmd) {
1007
+ setFeedback({
1008
+ message: `Unknown command: /${parsed.name}. Type /help for the list.`,
1009
+ kind: "error"
1010
+ });
1011
+ return true;
1012
+ }
1013
+ const ctx = {
1014
+ chat,
1015
+ runtime: {
1016
+ provider: runtime.provider,
1017
+ model: runtime.model,
1018
+ mode: runtime.mode,
1019
+ baseUrl,
1020
+ tools: toolsFlag,
1021
+ skill: skillFlag,
1022
+ sessionId: options.sessionId
1023
+ },
1024
+ setProvider,
1025
+ setModel,
1026
+ setApiKey,
1027
+ setBaseUrl,
1028
+ setTools: setToolsFlag,
1029
+ setSkill: setSkillFlag,
1030
+ feedback: (message, kind = "info") => setFeedback({ message, kind }),
1031
+ commands: slashCommands
1032
+ };
1033
+ try {
1034
+ await cmd.run(ctx, parsed.args);
1035
+ } catch (err) {
1036
+ const message = err instanceof Error ? err.message : String(err);
1037
+ setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
1038
+ }
1039
+ return true;
1040
+ };
1041
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", gap: 1, children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsx(
1043
+ ink.StatusHeader,
1044
+ {
1045
+ provider: runtime.provider,
1046
+ model: runtime.model,
1047
+ mode: runtime.mode,
1048
+ tools: toolNames,
1049
+ messageCount: chat.messages.length,
1050
+ sessionId: options.sessionId
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsxRuntime.jsx(ink.ChatContainer, { children: turns.map((turn, turnIdx) => {
1054
+ const assistantSteps = turn.filter((m) => m.role === "assistant").length;
1055
+ let stepIndex = 0;
1056
+ return /* @__PURE__ */ jsxRuntime.jsx(ink$1.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
1057
+ const showStep = message.role === "assistant" && assistantSteps > 1;
1058
+ if (showStep) stepIndex++;
1059
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
1060
+ showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { dimColor: true, children: [
1061
+ "\u21BB step ",
1062
+ stepIndex,
1063
+ "/",
1064
+ assistantSteps
1065
+ ] }) : null,
1066
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Message, { message }),
1067
+ message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
1068
+ /* @__PURE__ */ jsxRuntime.jsx(ink.ToolCallView, { toolCall, expanded: true }),
1069
+ toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsxRuntime.jsx(
1070
+ ink.ToolConfirmation,
1071
+ {
1072
+ toolCall,
1073
+ onApprove: chat.approve,
1074
+ onDeny: chat.deny,
1075
+ onApproveAlways: handleApproveAlways
1076
+ }
1077
+ ) : null
1078
+ ] }, toolCall.id))
1079
+ ] }, message.id);
1080
+ }) }, `turn-${turnIdx}`);
1081
+ }) }),
1082
+ /* @__PURE__ */ jsxRuntime.jsx(
1083
+ ink.ThinkingIndicator,
1084
+ {
1085
+ visible: chat.status === "streaming",
1086
+ label: toolNames.length > 0 ? "agent working" : "thinking"
1087
+ }
1088
+ ),
1089
+ chat.error ? /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
1090
+ /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "red", bold: true, children: [
1091
+ "\u2717 ",
1092
+ chat.error.name || "Error"
1093
+ ] }),
1094
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { color: "red", children: chat.error.message })
1095
+ ] }) : null,
1096
+ feedback ? /* @__PURE__ */ jsxRuntime.jsx(ink$1.Box, { borderStyle: "round", borderColor: feedbackBorder(feedback.kind), paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { color: feedbackBorder(feedback.kind), children: feedback.message }) }) : null,
1097
+ /* @__PURE__ */ jsxRuntime.jsx(
1098
+ ink.InputBar,
1099
+ {
1100
+ chat,
1101
+ placeholder: "Type a message or /help for commands",
1102
+ disabled: awaitingConfirmation,
1103
+ onSubmitInput: handleSubmitInput
1104
+ }
1105
+ )
1106
+ ] });
1107
+ }
1108
+ function feedbackBorder(kind) {
1109
+ switch (kind) {
1110
+ case "error":
1111
+ return "red";
1112
+ case "warn":
1113
+ return "yellow";
1114
+ case "success":
1115
+ return "green";
1116
+ case "info":
1117
+ default:
1118
+ return "cyan";
1119
+ }
1120
+ }
1121
+ function renderChatHeader(options) {
1122
+ const runtime = resolveChatProvider(options);
1123
+ const parts = [`provider=${runtime.provider}`];
1124
+ if (runtime.model) parts.push(`model=${runtime.model}`);
1125
+ parts.push(`mode=${runtime.mode}`);
1126
+ if (options.tools) parts.push(`tools=${options.tools}`);
1127
+ if (options.skill) parts.push(`skill=${options.skill}`);
1128
+ if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
1129
+ return parts.join(" ");
1130
+ }
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
+ };
1148
+ }
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);
1179
+ }
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
+ }
1196
+ }
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
+ );
1220
+ }
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
+ }
1242
+ }
1243
+ return bundle;
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
+ };
1341
+
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 };
1364
+ }
1365
+ function mcpToolToDefinition(serverName, client, tool) {
1366
+ return {
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
+ }
1373
+ };
1374
+ }
1375
+ function disposeMcpClients(clients) {
1376
+ for (const client of clients) client.dispose();
1377
+ }
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;
1395
+ }
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;
1407
+ }
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
1414
+ });
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
+ );
1420
+ }
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
+ }))
1444
+ };
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)}
1464
+ `);
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}
1475
+ `
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
+ });
1488
+ }
1489
+ function formatEvent(event) {
1490
+ switch (event.type) {
1491
+ case "agent:step":
1492
+ return `[step ${event.step}] ${event.action}`;
1493
+ case "llm:start":
1494
+ return `[llm] start (${event.messageCount} messages)`;
1495
+ case "llm:end": {
1496
+ const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
1497
+ return `[llm] done (${event.durationMs}ms) "${preview}"`;
1498
+ }
1499
+ case "tool:start":
1500
+ return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
1501
+ case "tool:end":
1502
+ return `[tool] ${event.name} done (${event.durationMs}ms)`;
1503
+ case "error":
1504
+ return `[error] ${event.error.message}`;
1505
+ default:
1506
+ return `[${event.type}]`;
1507
+ }
1508
+ }
1509
+ async function runAgent(task, options) {
1510
+ if (options.skill && options.skills) {
1511
+ process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
1512
+ process.exit(1);
1513
+ }
1514
+ const { adapter } = resolveChatProvider({
1515
+ provider: options.provider,
1516
+ model: options.model,
1517
+ apiKey: options.apiKey,
1518
+ baseUrl: options.baseUrl
1519
+ });
1520
+ const tools = resolveTools(options.tools);
1521
+ const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1522
+ const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1523
+ const observers = [];
1524
+ if (options.verbose) {
1525
+ observers.push({
1526
+ name: "cli-verbose",
1527
+ on(event) {
1528
+ process.stderr.write(formatEvent(event) + "\n");
1529
+ }
1530
+ });
1531
+ }
1532
+ const runtime$1 = runtime.createRuntime({
1533
+ adapter,
1534
+ tools,
1535
+ memory,
1536
+ systemPrompt: options.systemPrompt,
1537
+ maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1538
+ observers
1539
+ });
1540
+ const result = await runtime$1.run(task, {
1541
+ skill: skill ?? void 0
1542
+ });
1543
+ process.stdout.write(result.content + "\n");
1544
+ }
1545
+ function RunApp({ task, options }) {
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(() => {
1553
+ async function execute() {
1554
+ if (options.skill && options.skills) {
1555
+ setError("--skill and --skills are mutually exclusive.");
1556
+ setStatus("error");
1557
+ return;
1558
+ }
1559
+ const { adapter } = resolveChatProvider({
1560
+ provider: options.provider,
1561
+ model: options.model,
1562
+ apiKey: options.apiKey,
1563
+ baseUrl: options.baseUrl
1564
+ });
1565
+ const tools = resolveTools(options.tools);
1566
+ const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1567
+ const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1568
+ const observers = [{
1569
+ name: "run-ui",
1570
+ on(event) {
1571
+ switch (event.type) {
1572
+ case "agent:step":
1573
+ setCurrentStep(event.step);
1574
+ break;
1575
+ case "tool:start":
1576
+ setToolCalls((prev) => [...prev, { name: event.name, status: "running" }]);
1577
+ break;
1578
+ case "tool:end":
1579
+ setToolCalls((prev) => prev.map(
1580
+ (tc) => tc.name === event.name && tc.status === "running" ? { ...tc, status: "done", durationMs: event.durationMs } : tc
1581
+ ));
1582
+ break;
1583
+ case "error":
1584
+ setToolCalls((prev) => prev.map(
1585
+ (tc) => tc.status === "running" ? { ...tc, status: "error" } : tc
1586
+ ));
1587
+ break;
1588
+ }
1589
+ }
1590
+ }];
1591
+ const runtime$1 = runtime.createRuntime({
1592
+ adapter,
1593
+ tools,
1594
+ memory,
1595
+ systemPrompt: options.systemPrompt,
1596
+ maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1597
+ observers
1598
+ });
1599
+ try {
1600
+ const runResult = await runtime$1.run(task, { skill: skill ?? void 0 });
1601
+ setResult(runResult.content);
1602
+ setDurationMs(runResult.durationMs);
1603
+ setStatus("done");
1604
+ } catch (err) {
1605
+ setError(err instanceof Error ? err.message : String(err));
1606
+ setStatus("error");
1607
+ }
1608
+ }
1609
+ void execute();
1610
+ }, []);
1611
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", gap: 1, children: [
1612
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { bold: true, color: "cyan", children: "agentskit run" }),
1613
+ /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { dimColor: true, children: [
1614
+ "Task: ",
1615
+ task
1616
+ ] }),
1617
+ status === "running" && currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "yellow", children: [
1618
+ "\u27F3",
1619
+ " Step ",
1620
+ currentStep
1621
+ ] }),
1622
+ toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink$1.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: tc.status === "running" ? "yellow" : tc.status === "done" ? "green" : "red", children: [
1623
+ tc.status === "running" ? "\u27F3" : tc.status === "done" ? "\u2713" : "\u2717",
1624
+ " ",
1625
+ tc.name,
1626
+ tc.durationMs !== void 0 ? ` (${tc.durationMs}ms)` : ""
1627
+ ] }) }, i)),
1628
+ status === "running" && /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { color: "yellow", children: "Running..." }),
1629
+ status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
1630
+ /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "green", bold: true, children: [
1631
+ "Done (",
1632
+ durationMs,
1633
+ "ms)"
1634
+ ] }),
1635
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { children: result })
1636
+ ] }),
1637
+ status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "red", bold: true, children: [
1638
+ "Error: ",
1639
+ error
1640
+ ] })
1641
+ ] });
1642
+ }
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);
201
1651
  }
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 = [];
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 }));
247
1656
  } else {
248
- current.push(message);
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
+ }
249
1664
  }
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.useChat({
274
- adapter: runtime.adapter,
275
- memory,
276
- systemPrompt: options.system,
277
- tools: tools.length > 0 ? tools : void 0,
278
- skills
279
1665
  });
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$1.Box, { flexDirection: "column", gap: 1, children: [
283
- /* @__PURE__ */ jsxRuntime.jsx(
284
- ink.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.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$1.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$1.Box, { flexDirection: "column", children: [
300
- showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { dimColor: true, children: [
301
- "\u21BB step ",
302
- stepIndex,
303
- "/",
304
- assistantSteps
305
- ] }) : null,
306
- /* @__PURE__ */ jsxRuntime.jsx(ink.Message, { message }),
307
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ink.ToolCallView, { toolCall, expanded: true }, toolCall.id))
308
- ] }, message.id);
309
- }) }, `turn-${turnIdx}`);
310
- }) }),
311
- /* @__PURE__ */ jsxRuntime.jsx(
312
- ink.ThinkingIndicator,
313
- {
314
- visible: chat.status === "streaming",
315
- label: toolNames.length > 0 ? "agent working" : "thinking"
316
- }
317
- ),
318
- /* @__PURE__ */ jsxRuntime.jsx(ink.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
1666
  }
331
1667
  var PROVIDER_IMPORT = {
332
1668
  openai: "openai",
@@ -928,159 +2264,42 @@ function printNextSteps(options) {
928
2264
  `);
929
2265
  process.stdout.write(kleur__default.default.dim(" Docs: https://www.agentskit.io/docs\n\n"));
930
2266
  }
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
2267
+
2268
+ // src/commands/init.ts
2269
+ function registerInitCommand(program) {
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) => {
2271
+ const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
2272
+ let resolved;
2273
+ if (isCi) {
2274
+ const template = rawOptions.template ?? "react";
2275
+ resolved = {
2276
+ targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
2277
+ template,
2278
+ provider: rawOptions.provider ?? "demo",
2279
+ tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
2280
+ memory: rawOptions.memory ?? "none",
2281
+ packageManager: rawOptions.pm ?? "pnpm"
2282
+ };
2283
+ } else {
2284
+ const result = await runInteractiveInit({
2285
+ dir: rawOptions.dir,
2286
+ template: rawOptions.template
1040
2287
  });
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");
2288
+ if (result.cancelled) {
2289
+ process.exit(0);
1049
2290
  }
2291
+ resolved = result.options;
1050
2292
  }
1051
- void execute();
1052
- }, []);
1053
- return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", gap: 1, children: [
1054
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { bold: true, color: "cyan", children: "agentskit run" }),
1055
- /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { dimColor: true, children: [
1056
- "Task: ",
1057
- task
1058
- ] }),
1059
- status === "running" && currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "yellow", children: [
1060
- "\u27F3",
1061
- " Step ",
1062
- currentStep
1063
- ] }),
1064
- toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink$1.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink$1.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$1.Text, { color: "yellow", children: "Running..." }),
1071
- status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
1072
- /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "green", bold: true, children: [
1073
- "Done (",
1074
- durationMs,
1075
- "ms)"
1076
- ] }),
1077
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { children: result })
1078
- ] }),
1079
- status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "red", bold: true, children: [
1080
- "Error: ",
1081
- error
1082
- ] })
1083
- ] });
2293
+ await writeStarterProject(resolved);
2294
+ if (isCi) {
2295
+ process.stdout.write(
2296
+ `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
2297
+ `
2298
+ );
2299
+ } else {
2300
+ printNextSteps(resolved);
2301
+ }
2302
+ });
1084
2303
  }
1085
2304
  var PROVIDER_ENV_KEYS = {
1086
2305
  openai: "OPENAI_API_KEY",
@@ -1141,8 +2360,8 @@ async function checkPnpm() {
1141
2360
  };
1142
2361
  }
1143
2362
  async function checkPackageJson() {
1144
- const path4 = path.join(process.cwd(), "package.json");
1145
- if (!fs.existsSync(path4)) {
2363
+ const path5 = path.join(process.cwd(), "package.json");
2364
+ if (!fs.existsSync(path5)) {
1146
2365
  return {
1147
2366
  status: "warn",
1148
2367
  name: "package.json",
@@ -1151,7 +2370,7 @@ async function checkPackageJson() {
1151
2370
  };
1152
2371
  }
1153
2372
  try {
1154
- const pkg = JSON.parse(await promises.readFile(path4, "utf8"));
2373
+ const pkg = JSON.parse(await promises.readFile(path5, "utf8"));
1155
2374
  const deps = {
1156
2375
  ...pkg.dependencies ?? {},
1157
2376
  ...pkg.devDependencies ?? {}
@@ -1347,6 +2566,26 @@ function renderReport(report, opts = {}) {
1347
2566
  lines.push("");
1348
2567
  return lines.join("\n");
1349
2568
  }
2569
+
2570
+ // src/commands/doctor.ts
2571
+ function registerDoctorCommand(program) {
2572
+ program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
2573
+ "--providers <providers>",
2574
+ "Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
2575
+ ).option("--json", "Emit JSON instead of formatted output").action(async (options) => {
2576
+ const providers2 = options.providers ? options.providers.split(",").map((p) => p.trim()).filter(Boolean) : void 0;
2577
+ const report = await runDoctor({
2578
+ providers: providers2,
2579
+ noNetwork: options.network === false
2580
+ });
2581
+ if (options.json) {
2582
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
2583
+ } else {
2584
+ process.stdout.write(renderReport(report, { color: process.stdout.isTTY }));
2585
+ }
2586
+ if (report.fail > 0) process.exit(1);
2587
+ });
2588
+ }
1350
2589
  var DEFAULT_WATCH = [
1351
2590
  "**/*.ts",
1352
2591
  "**/*.tsx",
@@ -1413,11 +2652,11 @@ function startDev(options) {
1413
2652
  }
1414
2653
  });
1415
2654
  };
1416
- const restart = (path4) => {
2655
+ const restart = (path5) => {
1417
2656
  if (restartTimer) clearTimeout(restartTimer);
1418
2657
  restartTimer = setTimeout(() => {
1419
2658
  restartTimer = void 0;
1420
- banner(`\u21BB change detected \u2014 ${path4}`, "yellow");
2659
+ banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
1421
2660
  if (child && !child.killed && child.exitCode === null) {
1422
2661
  child.kill("SIGTERM");
1423
2662
  }
@@ -1454,6 +2693,73 @@ function startDev(options) {
1454
2693
  restarts: () => restartCount
1455
2694
  };
1456
2695
  }
2696
+
2697
+ // src/commands/dev.ts
2698
+ function registerDevCommand(program) {
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) => {
2700
+ const entry = positional ?? "src/index.ts";
2701
+ const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
2702
+ const ignore = options.ignore ? options.ignore.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
2703
+ try {
2704
+ const controller = startDev({
2705
+ entry,
2706
+ watch,
2707
+ ignore,
2708
+ debounceMs: Number(options.debounce) || 200
2709
+ });
2710
+ await controller.done;
2711
+ } catch (err) {
2712
+ process.stderr.write(`Error: ${err.message}
2713
+ `);
2714
+ process.exit(1);
2715
+ }
2716
+ });
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) => {
2724
+ const isLocal = Boolean(options.local);
2725
+ const targetPath = isLocal ? path__default.default.join(process.cwd(), ".agentskit.config.json") : path__default.default.join(os.homedir(), ".agentskit", "config.json");
2726
+ if (action === "show") {
2727
+ const config = await loadConfig();
2728
+ process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
2729
+ return;
2730
+ }
2731
+ if (action !== "init") {
2732
+ process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
2733
+ `);
2734
+ process.exit(2);
2735
+ }
2736
+ if (fs.existsSync(targetPath) && !options.force) {
2737
+ process.stderr.write(
2738
+ `Config already exists at ${targetPath}. Re-run with --force to overwrite.
2739
+ `
2740
+ );
2741
+ process.exit(1);
2742
+ }
2743
+ const template = {
2744
+ defaults: {
2745
+ provider: "openai",
2746
+ baseUrl: "https://openrouter.ai/api",
2747
+ apiKeyEnv: "OPENROUTER_API_KEY",
2748
+ model: "openai/gpt-oss-120b:free",
2749
+ tools: "web_search,fetch_url"
2750
+ }
2751
+ };
2752
+ fs.mkdirSync(path__default.default.dirname(targetPath), { recursive: true });
2753
+ fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2754
+ process.stdout.write(
2755
+ `Wrote ${targetPath}
2756
+ Edit it to taste, then run:
2757
+ agentskit chat
2758
+ (flags on the CLI still win over config values.)
2759
+ `
2760
+ );
2761
+ });
2762
+ }
1457
2763
  async function startTunnel(options) {
1458
2764
  const stdout = options.stdout ?? process.stdout;
1459
2765
  const open = options.open ?? (async (opts) => {
@@ -1519,125 +2825,8 @@ async function startTunnel(options) {
1519
2825
  };
1520
2826
  }
1521
2827
 
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$1.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$1.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
- });
2828
+ // src/commands/tunnel.ts
2829
+ function registerTunnelCommand(program) {
1641
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) => {
1642
2831
  const portNum = Number(port);
1643
2832
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -1658,19 +2847,170 @@ function createCli() {
1658
2847
  process.exit(1);
1659
2848
  }
1660
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);
1661
2974
  return program;
1662
2975
  }
1663
2976
 
1664
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;
1665
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;
1666
2999
  exports.loadConfig = loadConfig;
3000
+ exports.loadPlugins = loadPlugins;
3001
+ exports.mergePluginsIntoBundle = mergePluginsIntoBundle;
3002
+ exports.registerPricing = registerPricing;
3003
+ exports.renameSession = renameSession;
1667
3004
  exports.renderChatHeader = renderChatHeader;
1668
3005
  exports.renderReport = renderReport;
1669
3006
  exports.resolveChatProvider = resolveChatProvider;
3007
+ exports.resolveSession = resolveSession;
1670
3008
  exports.runAgent = runAgent;
1671
3009
  exports.runDoctor = runDoctor;
3010
+ exports.sessionFilePath = sessionFilePath;
1672
3011
  exports.startDev = startDev;
1673
3012
  exports.startTunnel = startTunnel;
3013
+ exports.writeSessionMeta = writeSessionMeta;
1674
3014
  exports.writeStarterProject = writeStarterProject;
1675
3015
  //# sourceMappingURL=index.cjs.map
1676
3016
  //# sourceMappingURL=index.cjs.map