@agentskit/cli 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, writeFile, readFile } from 'fs/promises';
2
+ import { readdir, stat, mkdir, writeFile, glob, readFile } from 'fs/promises';
3
3
  import { homedir } from 'os';
4
- import path3, { join, resolve, basename } from 'path';
4
+ import path, { join, resolve, isAbsolute, basename } from 'path';
5
5
  import { kimi, grok, deepseek, ollama, gemini, anthropic, openai } from '@agentskit/adapters';
6
- import React3, { useState, useMemo, useRef, useEffect } from 'react';
6
+ import { randomBytes, createHash } from 'crypto';
7
+ import { writeFileSync, existsSync, readdirSync, statSync, readFileSync, mkdirSync } from 'fs';
8
+ import { spawn } from 'child_process';
9
+ import React2, { useMemo, useState, useEffect, useRef } from 'react';
7
10
  import { Box, Text, render } from 'ink';
8
11
  import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ToolConfirmation, ThinkingIndicator, InputBar } from '@agentskit/ink';
9
12
  import { shell, filesystem, fetchUrl, webSearch } from '@agentskit/tools';
10
13
  import { summarizer, critic, planner, coder, researcher, composeSkills } from '@agentskit/skills';
11
- import { fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
12
- import { randomBytes, createHash } from 'crypto';
13
- import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'fs';
14
+ import { fileVectorMemory, fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
14
15
  import { jsxs, jsx } from 'react/jsx-runtime';
16
+ import { pathToFileURL } from 'url';
15
17
  import { createRuntime } from '@agentskit/runtime';
16
- import { spawn } from 'child_process';
17
18
  import chokidar from 'chokidar';
18
19
  import kleur3 from 'kleur';
20
+ import { createRAG } from '@agentskit/rag';
19
21
  import { Command } from 'commander';
20
22
  import { input, select, checkbox, confirm } from '@inquirer/prompts';
21
23
 
22
- async function loadJsonConfig(path4) {
24
+ async function loadJsonConfig(path5) {
23
25
  try {
24
- const raw = await readFile(path4, "utf8");
26
+ const raw = await readFile(path5, "utf8");
25
27
  return JSON.parse(raw);
26
28
  } catch {
27
29
  return void 0;
28
30
  }
29
31
  }
30
- async function loadTsConfig(path4) {
32
+ async function loadTsConfig(path5) {
31
33
  try {
32
- const mod = await import(path4);
34
+ const mod = await import(path5);
33
35
  return mod.default ?? mod;
34
36
  } catch {
35
37
  return void 0;
@@ -139,7 +141,7 @@ function createDemoAdapter(provider, model) {
139
141
  ].join(" ");
140
142
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
141
143
  if (cancelled) return;
142
- await new Promise((resolve2) => setTimeout(resolve2, 35));
144
+ await new Promise((resolve4) => setTimeout(resolve4, 35));
143
145
  yield { type: "text", content: chunk };
144
146
  }
145
147
  yield { type: "done" };
@@ -190,80 +192,6 @@ function resolveChatProvider(options) {
190
192
  summary: `${entry.label} live adapter`
191
193
  };
192
194
  }
193
- var skillRegistry = {
194
- researcher,
195
- coder,
196
- planner,
197
- critic,
198
- summarizer
199
- };
200
- function instantiate(kind) {
201
- switch (kind) {
202
- case "web_search":
203
- return [webSearch()];
204
- case "fetch_url":
205
- return [fetchUrl()];
206
- case "filesystem":
207
- return filesystem({ basePath: process.cwd() });
208
- case "shell":
209
- return [shell({ timeout: 3e4 })];
210
- }
211
- }
212
- function gateTool(tool) {
213
- if (tool.requiresConfirmation === false) return tool;
214
- return { ...tool, requiresConfirmation: true };
215
- }
216
- function resolveTools(toolNames) {
217
- if (!toolNames) {
218
- return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
219
- }
220
- const tools = [];
221
- for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
222
- switch (name) {
223
- case "web_search":
224
- case "fetch_url":
225
- case "filesystem":
226
- case "shell":
227
- tools.push(...instantiate(name));
228
- break;
229
- default:
230
- process.stderr.write(`Unknown tool: ${name}
231
- `);
232
- }
233
- }
234
- return tools;
235
- }
236
- function resolveSkill(skillName) {
237
- if (!skillName) return void 0;
238
- const skill = skillRegistry[skillName.trim()];
239
- if (!skill) {
240
- process.stderr.write(`Unknown skill: ${skillName}
241
- `);
242
- return void 0;
243
- }
244
- return skill;
245
- }
246
- function resolveSkills(skillNames) {
247
- if (!skillNames) return void 0;
248
- const names = skillNames.split(",").map((s) => s.trim());
249
- const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
250
- if (resolved.length === 0) {
251
- process.stderr.write(`No valid skills found in: ${skillNames}
252
- `);
253
- return void 0;
254
- }
255
- if (resolved.length === 1) return resolved[0];
256
- return composeSkills(...resolved);
257
- }
258
- function resolveMemory(backend, memoryPath) {
259
- switch (backend) {
260
- case "sqlite":
261
- return sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
262
- case "file":
263
- default:
264
- return fileChatMemory(memoryPath);
265
- }
266
- }
267
195
  var ROOT = join(homedir(), ".agentskit", "sessions");
268
196
  var META_SUFFIX = ".meta.json";
269
197
  function cwdHash(cwd = process.cwd()) {
@@ -288,10 +216,10 @@ function metaPath(id, cwd = process.cwd()) {
288
216
  return join(dirFor(cwd), `${id}${META_SUFFIX}`);
289
217
  }
290
218
  function readMeta(id, cwd = process.cwd()) {
291
- const path4 = metaPath(id, cwd);
292
- if (!existsSync(path4)) return null;
219
+ const path5 = metaPath(id, cwd);
220
+ if (!existsSync(path5)) return null;
293
221
  try {
294
- return JSON.parse(readFileSync(path4, "utf8"));
222
+ return JSON.parse(readFileSync(path5, "utf8"));
295
223
  } catch {
296
224
  return null;
297
225
  }
@@ -341,11 +269,41 @@ function findLatestSession(cwd = process.cwd()) {
341
269
  return all[0] ?? null;
342
270
  }
343
271
  function findSession(id, cwd = process.cwd()) {
344
- const exact = listSessions(cwd).find((s) => s.metadata.id === id);
272
+ const all = listSessions(cwd);
273
+ const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
345
274
  if (exact) return exact;
346
- const prefix = listSessions(cwd).find((s) => s.metadata.id.startsWith(id));
275
+ const prefix = all.find((s) => s.metadata.id.startsWith(id));
347
276
  return prefix ?? null;
348
277
  }
278
+ function renameSession(id, label, cwd = process.cwd()) {
279
+ const record = findSession(id, cwd);
280
+ if (!record) throw new Error(`No session matching "${id}".`);
281
+ const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
282
+ writeSessionMeta(next, cwd);
283
+ return next;
284
+ }
285
+ function forkSession(id, cwd = process.cwd()) {
286
+ const record = findSession(id, cwd);
287
+ if (!record) throw new Error(`No session matching "${id}".`);
288
+ const newId = generateSessionId();
289
+ const newFile = sessionFilePath(newId, cwd);
290
+ if (existsSync(record.file)) {
291
+ writeFileSync(newFile, readFileSync(record.file, "utf8"));
292
+ }
293
+ const now = (/* @__PURE__ */ new Date()).toISOString();
294
+ writeSessionMeta(
295
+ {
296
+ ...record.metadata,
297
+ id: newId,
298
+ createdAt: now,
299
+ updatedAt: now,
300
+ forkedFrom: record.metadata.id,
301
+ label: void 0
302
+ },
303
+ cwd
304
+ );
305
+ return { id: newId, file: newFile, isNew: true };
306
+ }
349
307
  function resolveSession(input2) {
350
308
  const cwd = input2.cwd ?? process.cwd();
351
309
  if (input2.explicitPath) {
@@ -371,6 +329,182 @@ function resolveSession(input2) {
371
329
  return { id, file: sessionFilePath(id, cwd), isNew: true };
372
330
  }
373
331
 
332
+ // src/extensibility/telemetry/pricing.ts
333
+ var builtinPricing = {
334
+ "gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
335
+ "gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
336
+ "gpt-4.1": { inputPerM: 2, outputPerM: 8 },
337
+ "gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
338
+ "claude-opus-4": { inputPerM: 15, outputPerM: 75 },
339
+ "claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
340
+ "claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
341
+ "gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
342
+ "gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
343
+ };
344
+ var customPricing = {};
345
+ function registerPricing(model, pricing) {
346
+ customPricing[model] = pricing;
347
+ }
348
+ function getPricing(model) {
349
+ if (!model) return void 0;
350
+ if (customPricing[model]) return customPricing[model];
351
+ if (builtinPricing[model]) return builtinPricing[model];
352
+ const short = model.includes("/") ? model.split("/").pop() : model;
353
+ return customPricing[short] ?? builtinPricing[short];
354
+ }
355
+ function computeCost(model, usage) {
356
+ if (!model) return void 0;
357
+ const pricing = getPricing(model);
358
+ if (!pricing) return void 0;
359
+ const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
360
+ const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
361
+ return {
362
+ model,
363
+ inputUsd,
364
+ outputUsd,
365
+ totalUsd: inputUsd + outputUsd
366
+ };
367
+ }
368
+
369
+ // src/extensibility/permissions/policy.ts
370
+ var defaultPolicy = {
371
+ mode: "default",
372
+ rules: []
373
+ };
374
+ function evaluatePolicy(policy, toolName) {
375
+ if (policy.mode === "bypassPermissions") return "allow";
376
+ if (policy.mode === "plan") return "ask";
377
+ for (const rule of policy.rules) {
378
+ if (matchesRule(rule, toolName)) return rule.action;
379
+ }
380
+ if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
381
+ return "allow";
382
+ }
383
+ return "ask";
384
+ }
385
+ function matchesRule(rule, toolName) {
386
+ if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
387
+ const str = rule.tool;
388
+ if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
389
+ return str === toolName;
390
+ }
391
+ function applyPolicyToTool(policy, tool) {
392
+ const action = evaluatePolicy(policy, tool.name);
393
+ if (action === "deny") return null;
394
+ if (action === "allow") return { ...tool, requiresConfirmation: false };
395
+ return { ...tool, requiresConfirmation: true };
396
+ }
397
+ function applyPolicyToTools(policy, tools) {
398
+ const out = [];
399
+ for (const tool of tools) {
400
+ const gated = applyPolicyToTool(policy, tool);
401
+ if (gated) out.push(gated);
402
+ }
403
+ return out;
404
+ }
405
+
406
+ // src/extensibility/hooks/runner.ts
407
+ var HookDispatcher = class {
408
+ constructor(handlers = [], onError = (_h, err) => process.stderr.write(
409
+ `[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
410
+ `
411
+ )) {
412
+ this.onError = onError;
413
+ this.handlers = /* @__PURE__ */ new Map();
414
+ for (const handler of handlers) this.register(handler);
415
+ }
416
+ register(handler) {
417
+ const list = this.handlers.get(handler.event) ?? [];
418
+ list.push(handler);
419
+ this.handlers.set(handler.event, list);
420
+ }
421
+ async dispatch(event, payload) {
422
+ const list = this.handlers.get(event) ?? [];
423
+ let current = { ...payload, event };
424
+ for (const handler of list) {
425
+ if (!this.matches(handler, current)) continue;
426
+ let result;
427
+ try {
428
+ result = await handler.run(current);
429
+ } catch (err) {
430
+ this.onError(handler, err);
431
+ continue;
432
+ }
433
+ if (result.decision === "block") {
434
+ return { payload: current, blocked: true, reason: result.reason };
435
+ }
436
+ if (result.decision === "modify") {
437
+ current = { ...result.payload, event };
438
+ }
439
+ }
440
+ return { payload: current, blocked: false };
441
+ }
442
+ matches(handler, payload) {
443
+ if (!handler.matcher) return true;
444
+ if (typeof handler.matcher === "function") return handler.matcher(payload);
445
+ return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
446
+ }
447
+ };
448
+ function configHooksToHandlers(config) {
449
+ if (!config) return [];
450
+ const handlers = [];
451
+ for (const [event, entries] of Object.entries(config)) {
452
+ for (const entry of entries) {
453
+ handlers.push({
454
+ event,
455
+ matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
456
+ run: (payload) => runShellHook(entry, payload)
457
+ });
458
+ }
459
+ }
460
+ return handlers;
461
+ }
462
+ function runShellHook(entry, payload) {
463
+ return new Promise((resolvePromise) => {
464
+ const timeoutMs = entry.timeout ?? 5e3;
465
+ const child = spawn("sh", ["-c", entry.run], {
466
+ stdio: ["pipe", "pipe", "inherit"]
467
+ });
468
+ let stdout = "";
469
+ child.stdout.on("data", (chunk) => {
470
+ stdout += chunk.toString();
471
+ });
472
+ const timer = setTimeout(() => {
473
+ child.kill("SIGTERM");
474
+ }, timeoutMs);
475
+ child.on("close", (code) => {
476
+ clearTimeout(timer);
477
+ if (code !== 0) {
478
+ resolvePromise({
479
+ decision: "block",
480
+ reason: `shell hook exited with code ${code}`
481
+ });
482
+ return;
483
+ }
484
+ const trimmed = stdout.trim();
485
+ if (!trimmed) {
486
+ resolvePromise({ decision: "continue" });
487
+ return;
488
+ }
489
+ try {
490
+ const parsed = JSON.parse(trimmed);
491
+ resolvePromise(parsed);
492
+ } catch {
493
+ resolvePromise({ decision: "continue" });
494
+ }
495
+ });
496
+ child.on("error", (err) => {
497
+ clearTimeout(timer);
498
+ resolvePromise({ decision: "block", reason: err.message });
499
+ });
500
+ try {
501
+ child.stdin.write(JSON.stringify(payload));
502
+ child.stdin.end();
503
+ } catch {
504
+ }
505
+ });
506
+ }
507
+
374
508
  // src/slash-commands.ts
375
509
  function parseSlashCommand(input2) {
376
510
  if (!input2.startsWith("/")) return null;
@@ -493,6 +627,89 @@ ${lines.join("\n")}`, "info");
493
627
  ctx.feedback("History cleared.", "success");
494
628
  }
495
629
  },
630
+ {
631
+ name: "usage",
632
+ description: "Show the cumulative token usage for this session.",
633
+ run(ctx) {
634
+ const usage = ctx.chat.usage;
635
+ if (!usage || usage.totalTokens === 0) {
636
+ ctx.feedback("No usage reported yet for this session.", "info");
637
+ return;
638
+ }
639
+ ctx.feedback(
640
+ `Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
641
+ "info"
642
+ );
643
+ }
644
+ },
645
+ {
646
+ name: "cost",
647
+ description: "Estimate the cost so far for the current model.",
648
+ run(ctx) {
649
+ const usage = ctx.chat.usage;
650
+ const model = ctx.runtime.model;
651
+ if (!usage || usage.totalTokens === 0) {
652
+ ctx.feedback("No usage reported yet for this session.", "info");
653
+ return;
654
+ }
655
+ const cost = computeCost(model, usage);
656
+ if (!cost) {
657
+ ctx.feedback(
658
+ `No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
659
+ "warn"
660
+ );
661
+ return;
662
+ }
663
+ ctx.feedback(
664
+ `$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
665
+ "info"
666
+ );
667
+ }
668
+ },
669
+ {
670
+ name: "rename",
671
+ description: "Attach a human-readable label to the current session.",
672
+ usage: "/rename <label>",
673
+ run(ctx, args) {
674
+ const label = args.trim();
675
+ const sessionId = ctx.runtime.sessionId;
676
+ if (!sessionId || sessionId === "custom") {
677
+ ctx.feedback("Rename is only available for managed sessions.", "warn");
678
+ return;
679
+ }
680
+ if (!label) {
681
+ ctx.feedback("Usage: /rename <label>", "warn");
682
+ return;
683
+ }
684
+ try {
685
+ renameSession(sessionId, label);
686
+ ctx.feedback(`Session labeled "${label}".`, "success");
687
+ } catch (err) {
688
+ ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
689
+ }
690
+ }
691
+ },
692
+ {
693
+ name: "fork",
694
+ description: "Branch a copy of the current session. Does not switch to it.",
695
+ run(ctx) {
696
+ const sessionId = ctx.runtime.sessionId;
697
+ if (!sessionId || sessionId === "custom") {
698
+ ctx.feedback("Fork is only available for managed sessions.", "warn");
699
+ return;
700
+ }
701
+ try {
702
+ const result = forkSession(sessionId);
703
+ ctx.feedback(
704
+ `Forked into ${result.id}. Resume with:
705
+ agentskit chat --resume ${result.id}`,
706
+ "success"
707
+ );
708
+ } catch (err) {
709
+ ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
710
+ }
711
+ }
712
+ },
496
713
  {
497
714
  name: "exit",
498
715
  aliases: ["quit", "q"],
@@ -502,57 +719,127 @@ ${lines.join("\n")}`, "info");
502
719
  }
503
720
  }
504
721
  ];
505
- function groupIntoTurns(messages) {
506
- const turns = [];
507
- let current = [];
508
- for (const message of messages) {
509
- if (message.role === "user") {
510
- if (current.length > 0) turns.push(current);
511
- current = [message];
512
- } else if (message.role === "system") {
513
- if (current.length > 0) turns.push(current);
514
- turns.push([message]);
515
- current = [];
516
- } else {
517
- current.push(message);
722
+ var skillRegistry = {
723
+ researcher,
724
+ coder,
725
+ planner,
726
+ critic,
727
+ summarizer
728
+ };
729
+ function instantiate(kind) {
730
+ switch (kind) {
731
+ case "web_search":
732
+ return [webSearch()];
733
+ case "fetch_url":
734
+ return [fetchUrl()];
735
+ case "filesystem":
736
+ return filesystem({ basePath: process.cwd() });
737
+ case "shell":
738
+ return [shell({ timeout: 3e4 })];
739
+ }
740
+ }
741
+ function gateTool(tool) {
742
+ if (tool.requiresConfirmation === false) return tool;
743
+ return { ...tool, requiresConfirmation: true };
744
+ }
745
+ function resolveTools(toolNames) {
746
+ if (!toolNames) {
747
+ return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
748
+ }
749
+ const tools = [];
750
+ for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
751
+ switch (name) {
752
+ case "web_search":
753
+ case "fetch_url":
754
+ case "filesystem":
755
+ case "shell":
756
+ tools.push(...instantiate(name));
757
+ break;
758
+ default:
759
+ process.stderr.write(`Unknown tool: ${name}
760
+ `);
518
761
  }
519
762
  }
520
- if (current.length > 0) turns.push(current);
521
- return turns;
763
+ return tools;
522
764
  }
523
- function ChatApp(options) {
524
- const [provider, setProvider] = useState(options.provider);
525
- const [model, setModel] = useState(options.model);
526
- const [apiKey, setApiKey] = useState(options.apiKey);
527
- const [baseUrl, setBaseUrl] = useState(options.baseUrl);
528
- const [toolsFlag, setToolsFlag] = useState(options.tools);
529
- const [skillFlag, setSkillFlag] = useState(options.skill);
530
- const runtime = useMemo(
531
- () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
532
- [provider, model, apiKey, baseUrl]
533
- );
534
- const memory = useMemo(
535
- () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
536
- [options.memoryPath, options.memoryBackend]
537
- );
538
- const tools = useMemo(() => resolveTools(toolsFlag), [toolsFlag]);
539
- const skills = useMemo(() => {
540
- if (!skillFlag) return void 0;
541
- const names = skillFlag.split(",").map((s) => s.trim());
542
- const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
543
- if (resolved.length === 0) return void 0;
544
- return resolved;
545
- }, [skillFlag]);
546
- const chat = useChat({
547
- adapter: runtime.adapter,
548
- memory,
549
- systemPrompt: options.system,
550
- tools: tools.length > 0 ? tools : void 0,
551
- skills
552
- });
553
- const [sessionAllowed, setSessionAllowed] = useState(/* @__PURE__ */ new Set());
554
- const autoApprovedRef = useRef(/* @__PURE__ */ new Set());
555
- useEffect(() => {
765
+ function resolveSkill(skillName) {
766
+ if (!skillName) return void 0;
767
+ const skill = skillRegistry[skillName.trim()];
768
+ if (!skill) {
769
+ process.stderr.write(`Unknown skill: ${skillName}
770
+ `);
771
+ return void 0;
772
+ }
773
+ return skill;
774
+ }
775
+ function resolveSkills(skillNames) {
776
+ if (!skillNames) return void 0;
777
+ const names = skillNames.split(",").map((s) => s.trim());
778
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
779
+ if (resolved.length === 0) {
780
+ process.stderr.write(`No valid skills found in: ${skillNames}
781
+ `);
782
+ return void 0;
783
+ }
784
+ if (resolved.length === 1) return resolved[0];
785
+ return composeSkills(...resolved);
786
+ }
787
+ function resolveMemory(backend, memoryPath) {
788
+ switch (backend) {
789
+ case "sqlite":
790
+ return sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
791
+ case "file":
792
+ default:
793
+ return fileChatMemory(memoryPath);
794
+ }
795
+ }
796
+
797
+ // src/runtime/use-runtime.ts
798
+ function useRuntime(options) {
799
+ const [provider, setProvider] = useState(options.provider);
800
+ const [model, setModel] = useState(options.model);
801
+ const [apiKey, setApiKey] = useState(options.apiKey);
802
+ const [baseUrl, setBaseUrl] = useState(options.baseUrl);
803
+ const [toolsFlag, setToolsFlag] = useState(options.tools);
804
+ const [skillFlag, setSkillFlag] = useState(options.skill);
805
+ const runtime = useMemo(
806
+ () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
807
+ [provider, model, apiKey, baseUrl]
808
+ );
809
+ const memory = useMemo(
810
+ () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
811
+ [options.memoryPath, options.memoryBackend]
812
+ );
813
+ const tools = useMemo(() => {
814
+ const resolved = resolveTools(toolsFlag);
815
+ if (!options.permissionPolicy) return resolved;
816
+ return applyPolicyToTools(options.permissionPolicy, resolved);
817
+ }, [toolsFlag, options.permissionPolicy]);
818
+ const skills = useMemo(() => {
819
+ if (!skillFlag) return void 0;
820
+ const names = skillFlag.split(",").map((s) => s.trim());
821
+ const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
822
+ if (resolved.length === 0) return void 0;
823
+ return resolved;
824
+ }, [skillFlag]);
825
+ return {
826
+ runtime,
827
+ memory,
828
+ tools,
829
+ skills,
830
+ state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
831
+ setProvider,
832
+ setModel,
833
+ setApiKey,
834
+ setBaseUrl,
835
+ setToolsFlag,
836
+ setSkillFlag
837
+ };
838
+ }
839
+ function useToolPermissions(chat) {
840
+ const [sessionAllowed, setSessionAllowed] = useState(/* @__PURE__ */ new Set());
841
+ const autoApprovedRef = useRef(/* @__PURE__ */ new Set());
842
+ useEffect(() => {
556
843
  if (sessionAllowed.size === 0) return;
557
844
  for (const message of chat.messages) {
558
845
  for (const call of message.toolCalls ?? []) {
@@ -573,9 +860,20 @@ function ChatApp(options) {
573
860
  autoApprovedRef.current.add(toolCallId);
574
861
  void chat.approve(toolCallId);
575
862
  };
863
+ const awaitingConfirmation = useMemo(
864
+ () => chat.messages.some(
865
+ (message) => message.toolCalls?.some(
866
+ (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
867
+ )
868
+ ),
869
+ [chat.messages, sessionAllowed]
870
+ );
871
+ return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
872
+ }
873
+ function useSessionMeta(options) {
576
874
  const sessionCreatedAtRef = useRef(void 0);
577
- const messageCount = chat.messages.length;
578
- const firstUserContent = chat.messages.find((m) => m.role === "user")?.content ?? "";
875
+ const messageCount = options.messages.length;
876
+ const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
579
877
  useEffect(() => {
580
878
  const sessionId = options.sessionId;
581
879
  if (!sessionId || sessionId === "custom") return;
@@ -589,16 +887,90 @@ function ChatApp(options) {
589
887
  createdAt: sessionCreatedAtRef.current,
590
888
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
591
889
  messageCount,
592
- preview: derivePreview(chat.messages),
593
- provider: runtime.provider,
594
- model: runtime.model
890
+ preview: derivePreview(options.messages),
891
+ provider: options.provider,
892
+ model: options.model
595
893
  });
596
894
  } catch {
597
895
  }
598
- }, [options.sessionId, messageCount, firstUserContent, runtime.provider, runtime.model]);
896
+ }, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
897
+ }
898
+ function groupIntoTurns(messages) {
899
+ const turns = [];
900
+ let current = [];
901
+ for (const message of messages) {
902
+ if (message.role === "user") {
903
+ if (current.length > 0) turns.push(current);
904
+ current = [message];
905
+ } else if (message.role === "system") {
906
+ if (current.length > 0) turns.push(current);
907
+ turns.push([message]);
908
+ current = [];
909
+ } else {
910
+ current.push(message);
911
+ }
912
+ }
913
+ if (current.length > 0) turns.push(current);
914
+ return turns;
915
+ }
916
+ function ChatApp(options) {
917
+ const {
918
+ runtime,
919
+ memory,
920
+ tools,
921
+ skills,
922
+ state: { baseUrl, toolsFlag, skillFlag },
923
+ setProvider,
924
+ setModel,
925
+ setApiKey,
926
+ setBaseUrl,
927
+ setToolsFlag,
928
+ setSkillFlag
929
+ } = useRuntime(options);
930
+ const mergedTools = useMemo(() => {
931
+ const extra = options.extraTools ?? [];
932
+ return [...tools, ...extra];
933
+ }, [tools, options.extraTools]);
934
+ const mergedSkills = useMemo(() => {
935
+ const extra = options.extraSkills ?? [];
936
+ if (!skills && extra.length === 0) return void 0;
937
+ return [...skills ?? [], ...extra];
938
+ }, [skills, options.extraSkills]);
939
+ const chat = useChat({
940
+ adapter: runtime.adapter,
941
+ memory,
942
+ systemPrompt: options.system,
943
+ tools: mergedTools.length > 0 ? mergedTools : void 0,
944
+ skills: mergedSkills
945
+ });
946
+ const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
947
+ useSessionMeta({
948
+ sessionId: options.sessionId,
949
+ messages: chat.messages,
950
+ provider: runtime.provider,
951
+ model: runtime.model
952
+ });
599
953
  const turns = useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
600
954
  const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
601
955
  const [feedback, setFeedback] = useState(null);
956
+ const hookDispatcher = useMemo(
957
+ () => new HookDispatcher(options.hookHandlers ?? []),
958
+ [options.hookHandlers]
959
+ );
960
+ useEffect(() => {
961
+ void hookDispatcher.dispatch("SessionStart", {
962
+ event: "SessionStart",
963
+ sessionId: options.sessionId,
964
+ provider: runtime.provider,
965
+ model: runtime.model
966
+ });
967
+ return () => {
968
+ void hookDispatcher.dispatch("SessionEnd", {
969
+ event: "SessionEnd",
970
+ sessionId: options.sessionId
971
+ });
972
+ };
973
+ }, [hookDispatcher]);
602
974
  const slashCommands = useMemo(
603
975
  () => [...builtinSlashCommands, ...options.slashCommands ?? []],
604
976
  [options.slashCommands]
@@ -607,6 +979,17 @@ function ChatApp(options) {
607
979
  const handleSubmitInput = async (raw) => {
608
980
  const parsed = parseSlashCommand(raw);
609
981
  if (!parsed) {
982
+ const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
983
+ event: "UserPromptSubmit",
984
+ prompt: raw
985
+ });
986
+ if (hookResult.blocked) {
987
+ setFeedback({
988
+ message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
989
+ kind: "warn"
990
+ });
991
+ return true;
992
+ }
610
993
  setFeedback(null);
611
994
  return false;
612
995
  }
@@ -626,7 +1009,8 @@ function ChatApp(options) {
626
1009
  mode: runtime.mode,
627
1010
  baseUrl,
628
1011
  tools: toolsFlag,
629
- skill: skillFlag
1012
+ skill: skillFlag,
1013
+ sessionId: options.sessionId
630
1014
  },
631
1015
  setProvider,
632
1016
  setModel,
@@ -645,14 +1029,6 @@ function ChatApp(options) {
645
1029
  }
646
1030
  return true;
647
1031
  };
648
- const awaitingConfirmation = useMemo(
649
- () => chat.messages.some(
650
- (message) => message.toolCalls?.some(
651
- (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
652
- )
653
- ),
654
- [chat.messages, sessionAllowed]
655
- );
656
1032
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
657
1033
  /* @__PURE__ */ jsx(
658
1034
  StatusHeader,
@@ -743,6 +1119,291 @@ function renderChatHeader(options) {
743
1119
  if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
744
1120
  return parts.join(" ");
745
1121
  }
1122
+ async function loadPlugins(options = {}) {
1123
+ const {
1124
+ specs = [],
1125
+ pluginDirs = [],
1126
+ cwd = process.cwd(),
1127
+ autoDiscoverUserDir = true,
1128
+ onError = (spec, err) => process.stderr.write(
1129
+ `[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
1130
+ `
1131
+ ),
1132
+ log = () => {
1133
+ }
1134
+ } = options;
1135
+ const resolvedSpecs = [...specs];
1136
+ const discoveryDirs = [...pluginDirs];
1137
+ if (autoDiscoverUserDir) discoveryDirs.push(join(homedir(), ".agentskit", "plugins"));
1138
+ for (const dir of discoveryDirs) {
1139
+ const discovered = await discoverPluginsInDir(dir);
1140
+ resolvedSpecs.push(...discovered);
1141
+ }
1142
+ const plugins = [];
1143
+ for (const spec of resolvedSpecs) {
1144
+ try {
1145
+ const plugin = await loadPluginFromSpec(spec, cwd, log);
1146
+ if (plugin) plugins.push(plugin);
1147
+ } catch (err) {
1148
+ onError(spec, err);
1149
+ }
1150
+ }
1151
+ return mergePluginsIntoBundle(plugins);
1152
+ }
1153
+ async function discoverPluginsInDir(dir) {
1154
+ try {
1155
+ const entries = await readdir(dir);
1156
+ const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => join(dir, name));
1157
+ const validated = [];
1158
+ for (const p of absolutes) {
1159
+ try {
1160
+ const s = await stat(p);
1161
+ if (s.isFile()) validated.push(p);
1162
+ } catch {
1163
+ }
1164
+ }
1165
+ return validated;
1166
+ } catch {
1167
+ return [];
1168
+ }
1169
+ }
1170
+ async function loadPluginFromSpec(spec, cwd, log) {
1171
+ const isPath = spec.startsWith("./") || spec.startsWith("../") || isAbsolute(spec);
1172
+ const importTarget = isPath ? pathToFileURL(resolve(cwd, spec)).href : spec;
1173
+ const mod = await import(importTarget);
1174
+ const exported = mod.default ?? mod.plugin ?? mod;
1175
+ const sourcePath = isPath ? resolve(cwd, spec) : void 0;
1176
+ const ctx = {
1177
+ cwd,
1178
+ sourcePath,
1179
+ log: (msg) => log(`[${spec}] ${msg}`)
1180
+ };
1181
+ if (typeof exported === "function") {
1182
+ const factory = exported;
1183
+ return await factory(ctx);
1184
+ }
1185
+ if (exported && typeof exported === "object" && "name" in exported) {
1186
+ const plugin = exported;
1187
+ if (plugin.init) await plugin.init(ctx);
1188
+ return plugin;
1189
+ }
1190
+ throw new Error(
1191
+ "Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
1192
+ );
1193
+ }
1194
+ function mergePluginsIntoBundle(plugins) {
1195
+ const bundle = {
1196
+ plugins,
1197
+ slashCommands: [],
1198
+ tools: [],
1199
+ skills: [],
1200
+ providers: {},
1201
+ hooks: [],
1202
+ mcpServers: []
1203
+ };
1204
+ for (const plugin of plugins) {
1205
+ if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
1206
+ if (plugin.tools) bundle.tools.push(...plugin.tools);
1207
+ if (plugin.skills) bundle.skills.push(...plugin.skills);
1208
+ if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
1209
+ if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
1210
+ if (plugin.providers) {
1211
+ for (const [name, factory] of Object.entries(plugin.providers)) {
1212
+ bundle.providers[name] = factory;
1213
+ }
1214
+ }
1215
+ }
1216
+ return bundle;
1217
+ }
1218
+ var McpClient = class {
1219
+ constructor(spec, onError = (err) => process.stderr.write(
1220
+ `[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
1221
+ `
1222
+ )) {
1223
+ this.spec = spec;
1224
+ this.onError = onError;
1225
+ this.buffer = "";
1226
+ this.nextId = 1;
1227
+ this.pending = /* @__PURE__ */ new Map();
1228
+ this.disposed = false;
1229
+ }
1230
+ async start() {
1231
+ if (this.child) return;
1232
+ const child = spawn(this.spec.command, this.spec.args ?? [], {
1233
+ env: { ...process.env, ...this.spec.env ?? {} },
1234
+ stdio: ["pipe", "pipe", "pipe"]
1235
+ });
1236
+ this.child = child;
1237
+ child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
1238
+ child.stderr.on("data", (chunk) => {
1239
+ process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
1240
+ });
1241
+ child.on("error", (err) => this.onError(err));
1242
+ child.on("close", () => {
1243
+ this.disposed = true;
1244
+ for (const pending of this.pending.values()) {
1245
+ pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
1246
+ }
1247
+ this.pending.clear();
1248
+ });
1249
+ await this.request("initialize", {
1250
+ protocolVersion: "2024-11-05",
1251
+ capabilities: {},
1252
+ clientInfo: { name: "agentskit", version: "0" }
1253
+ });
1254
+ }
1255
+ async listTools() {
1256
+ const res = await this.request("tools/list", {});
1257
+ const tools = res.tools ?? [];
1258
+ return tools;
1259
+ }
1260
+ async callTool(name, args) {
1261
+ return await this.request("tools/call", { name, arguments: args });
1262
+ }
1263
+ dispose() {
1264
+ if (this.disposed) return;
1265
+ this.disposed = true;
1266
+ this.child?.kill("SIGTERM");
1267
+ }
1268
+ request(method, params) {
1269
+ return new Promise((resolvePromise, rejectPromise) => {
1270
+ if (!this.child || this.disposed) {
1271
+ rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
1272
+ return;
1273
+ }
1274
+ const id = this.nextId++;
1275
+ const timeoutMs = this.spec.timeout ?? 1e4;
1276
+ const timer = setTimeout(() => {
1277
+ this.pending.delete(id);
1278
+ rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
1279
+ }, timeoutMs);
1280
+ this.pending.set(id, (res) => {
1281
+ clearTimeout(timer);
1282
+ if (res.error) {
1283
+ rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
1284
+ return;
1285
+ }
1286
+ resolvePromise(res.result);
1287
+ });
1288
+ const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
1289
+ this.child.stdin.write(message);
1290
+ });
1291
+ }
1292
+ onStdout(chunk) {
1293
+ this.buffer += chunk;
1294
+ let newlineIndex = this.buffer.indexOf("\n");
1295
+ while (newlineIndex !== -1) {
1296
+ const line = this.buffer.slice(0, newlineIndex).trim();
1297
+ this.buffer = this.buffer.slice(newlineIndex + 1);
1298
+ if (line) {
1299
+ try {
1300
+ const parsed = JSON.parse(line);
1301
+ const pending = this.pending.get(Number(parsed.id));
1302
+ if (pending) {
1303
+ this.pending.delete(Number(parsed.id));
1304
+ pending(parsed);
1305
+ }
1306
+ } catch (err) {
1307
+ this.onError(err);
1308
+ }
1309
+ }
1310
+ newlineIndex = this.buffer.indexOf("\n");
1311
+ }
1312
+ }
1313
+ };
1314
+
1315
+ // src/extensibility/mcp/bridge.ts
1316
+ async function bridgeMcpServers(specs) {
1317
+ const clients = [];
1318
+ const tools = [];
1319
+ for (const spec of specs) {
1320
+ const client = new McpClient(spec);
1321
+ try {
1322
+ await client.start();
1323
+ const mcpTools = await client.listTools();
1324
+ for (const mcpTool of mcpTools) {
1325
+ tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
1326
+ }
1327
+ clients.push(client);
1328
+ } catch (err) {
1329
+ process.stderr.write(
1330
+ `[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
1331
+ `
1332
+ );
1333
+ client.dispose();
1334
+ }
1335
+ }
1336
+ return { clients, tools };
1337
+ }
1338
+ function mcpToolToDefinition(serverName, client, tool) {
1339
+ return {
1340
+ name: `${serverName}__${tool.name}`,
1341
+ description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
1342
+ schema: tool.inputSchema ?? { type: "object", properties: {} },
1343
+ execute: async (args) => {
1344
+ return await client.callTool(tool.name, args);
1345
+ }
1346
+ };
1347
+ }
1348
+ function disposeMcpClients(clients) {
1349
+ for (const client of clients) client.dispose();
1350
+ }
1351
+ function formatEvent(event) {
1352
+ switch (event.type) {
1353
+ case "agent:step":
1354
+ return `[step ${event.step}] ${event.action}`;
1355
+ case "llm:start":
1356
+ return `[llm] start (${event.messageCount} messages)`;
1357
+ case "llm:end": {
1358
+ const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
1359
+ return `[llm] done (${event.durationMs}ms) "${preview}"`;
1360
+ }
1361
+ case "tool:start":
1362
+ return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
1363
+ case "tool:end":
1364
+ return `[tool] ${event.name} done (${event.durationMs}ms)`;
1365
+ case "error":
1366
+ return `[error] ${event.error.message}`;
1367
+ default:
1368
+ return `[${event.type}]`;
1369
+ }
1370
+ }
1371
+ async function runAgent(task, options) {
1372
+ if (options.skill && options.skills) {
1373
+ process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
1374
+ process.exit(1);
1375
+ }
1376
+ const { adapter } = resolveChatProvider({
1377
+ provider: options.provider,
1378
+ model: options.model,
1379
+ apiKey: options.apiKey,
1380
+ baseUrl: options.baseUrl
1381
+ });
1382
+ const tools = resolveTools(options.tools);
1383
+ const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1384
+ const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1385
+ const observers = [];
1386
+ if (options.verbose) {
1387
+ observers.push({
1388
+ name: "cli-verbose",
1389
+ on(event) {
1390
+ process.stderr.write(formatEvent(event) + "\n");
1391
+ }
1392
+ });
1393
+ }
1394
+ const runtime = createRuntime({
1395
+ adapter,
1396
+ tools,
1397
+ memory,
1398
+ systemPrompt: options.systemPrompt,
1399
+ maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1400
+ observers
1401
+ });
1402
+ const result = await runtime.run(task, {
1403
+ skill: skill ?? void 0
1404
+ });
1405
+ process.stdout.write(result.content + "\n");
1406
+ }
746
1407
  var PROVIDER_IMPORT = {
747
1408
  openai: "openai",
748
1409
  anthropic: "anthropic",
@@ -841,7 +1502,7 @@ function reactStarter(ctx) {
841
1502
  return {
842
1503
  "package.json": JSON.stringify(
843
1504
  {
844
- name: path3.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
1505
+ name: path.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
845
1506
  private: true,
846
1507
  type: "module",
847
1508
  scripts: {
@@ -1210,68 +1871,12 @@ async function writeStarterProject(options) {
1210
1871
  await mkdir(options.targetDir, { recursive: true });
1211
1872
  await Promise.all(
1212
1873
  Object.entries(files).map(async ([relativePath, content]) => {
1213
- const absolutePath = path3.join(options.targetDir, relativePath);
1214
- await mkdir(path3.dirname(absolutePath), { recursive: true });
1874
+ const absolutePath = path.join(options.targetDir, relativePath);
1875
+ await mkdir(path.dirname(absolutePath), { recursive: true });
1215
1876
  await writeFile(absolutePath, content, "utf8");
1216
1877
  })
1217
1878
  );
1218
1879
  }
1219
- function formatEvent(event) {
1220
- switch (event.type) {
1221
- case "agent:step":
1222
- return `[step ${event.step}] ${event.action}`;
1223
- case "llm:start":
1224
- return `[llm] start (${event.messageCount} messages)`;
1225
- case "llm:end": {
1226
- const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
1227
- return `[llm] done (${event.durationMs}ms) "${preview}"`;
1228
- }
1229
- case "tool:start":
1230
- return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
1231
- case "tool:end":
1232
- return `[tool] ${event.name} done (${event.durationMs}ms)`;
1233
- case "error":
1234
- return `[error] ${event.error.message}`;
1235
- default:
1236
- return `[${event.type}]`;
1237
- }
1238
- }
1239
- async function runAgent(task, options) {
1240
- if (options.skill && options.skills) {
1241
- process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
1242
- process.exit(1);
1243
- }
1244
- const { adapter } = resolveChatProvider({
1245
- provider: options.provider,
1246
- model: options.model,
1247
- apiKey: options.apiKey,
1248
- baseUrl: options.baseUrl
1249
- });
1250
- const tools = resolveTools(options.tools);
1251
- const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
1252
- const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
1253
- const observers = [];
1254
- if (options.verbose) {
1255
- observers.push({
1256
- name: "cli-verbose",
1257
- on(event) {
1258
- process.stderr.write(formatEvent(event) + "\n");
1259
- }
1260
- });
1261
- }
1262
- const runtime = createRuntime({
1263
- adapter,
1264
- tools,
1265
- memory,
1266
- systemPrompt: options.systemPrompt,
1267
- maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
1268
- observers
1269
- });
1270
- const result = await runtime.run(task, {
1271
- skill: skill ?? void 0
1272
- });
1273
- process.stdout.write(result.content + "\n");
1274
- }
1275
1880
  var PROVIDER_ENV_KEYS = {
1276
1881
  openai: "OPENAI_API_KEY",
1277
1882
  anthropic: "ANTHROPIC_API_KEY",
@@ -1331,8 +1936,8 @@ async function checkPnpm() {
1331
1936
  };
1332
1937
  }
1333
1938
  async function checkPackageJson() {
1334
- const path4 = join(process.cwd(), "package.json");
1335
- if (!existsSync(path4)) {
1939
+ const path5 = join(process.cwd(), "package.json");
1940
+ if (!existsSync(path5)) {
1336
1941
  return {
1337
1942
  status: "warn",
1338
1943
  name: "package.json",
@@ -1341,7 +1946,7 @@ async function checkPackageJson() {
1341
1946
  };
1342
1947
  }
1343
1948
  try {
1344
- const pkg = JSON.parse(await readFile(path4, "utf8"));
1949
+ const pkg = JSON.parse(await readFile(path5, "utf8"));
1345
1950
  const deps = {
1346
1951
  ...pkg.dependencies ?? {},
1347
1952
  ...pkg.devDependencies ?? {}
@@ -1603,11 +2208,11 @@ function startDev(options) {
1603
2208
  }
1604
2209
  });
1605
2210
  };
1606
- const restart = (path4) => {
2211
+ const restart = (path5) => {
1607
2212
  if (restartTimer) clearTimeout(restartTimer);
1608
2213
  restartTimer = setTimeout(() => {
1609
2214
  restartTimer = void 0;
1610
- banner(`\u21BB change detected \u2014 ${path4}`, "yellow");
2215
+ banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
1611
2216
  if (child && !child.killed && child.exitCode === null) {
1612
2217
  child.kill("SIGTERM");
1613
2218
  }
@@ -1708,132 +2313,205 @@ async function startTunnel(options) {
1708
2313
  requests: () => requests
1709
2314
  };
1710
2315
  }
1711
- async function runInteractiveInit(defaults = {}) {
1712
- process.stdout.write(`
1713
- ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1714
- `);
1715
- process.stdout.write(kleur3.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
1716
- try {
1717
- const targetDir = await input({
1718
- message: "Project directory:",
1719
- default: defaults.dir ?? "agentskit-app",
1720
- validate: (value) => {
1721
- if (!value.trim()) return "A directory name is required.";
1722
- const abs = path3.resolve(process.cwd(), value);
1723
- if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
1724
- return true;
1725
- }
1726
- });
1727
- const template = await select({
1728
- message: "Template:",
1729
- default: defaults.template ?? "react",
1730
- choices: [
1731
- { name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
1732
- { name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
1733
- { name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
1734
- { name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
1735
- ]
1736
- });
1737
- const provider = await select({
1738
- message: "LLM provider:",
1739
- default: "demo",
1740
- choices: [
1741
- { name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
1742
- { name: "OpenAI", value: "openai" },
1743
- { name: "Anthropic", value: "anthropic" },
1744
- { name: "Gemini", value: "gemini" },
1745
- { name: "Ollama (local, no key)", value: "ollama" }
1746
- ]
1747
- });
1748
- let tools = [];
1749
- if (template !== "react") {
1750
- tools = await checkbox({
1751
- message: "Tools (space to toggle, enter to confirm):",
1752
- choices: [
1753
- { name: "web_search", value: "web_search" },
1754
- { name: "filesystem", value: "filesystem" },
1755
- { name: "shell", value: "shell" }
1756
- ]
1757
- });
1758
- }
1759
- const memory = await select({
1760
- message: "Memory backend:",
1761
- default: "none",
1762
- choices: [
1763
- { name: "None (stateless)", value: "none" },
1764
- { name: "File (JSON on disk)", value: "file" },
1765
- { name: "SQLite (better-sqlite3)", value: "sqlite" }
1766
- ]
1767
- });
1768
- const packageManager = await select({
1769
- message: "Package manager:",
1770
- default: "pnpm",
1771
- choices: [
1772
- { name: "pnpm", value: "pnpm" },
1773
- { name: "npm", value: "npm" },
1774
- { name: "yarn", value: "yarn" },
1775
- { name: "bun", value: "bun" }
1776
- ]
1777
- });
1778
- process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
1779
- process.stdout.write(kleur3.dim(` dir ${targetDir}
1780
- `));
1781
- process.stdout.write(kleur3.dim(` template ${template}
1782
- `));
1783
- process.stdout.write(kleur3.dim(` provider ${provider}
1784
- `));
1785
- if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
1786
- `));
1787
- process.stdout.write(kleur3.dim(` memory ${memory}
1788
- `));
1789
- process.stdout.write(kleur3.dim(` pm ${packageManager}
1790
2316
 
1791
- `));
1792
- const proceed = await confirm({ message: "Generate?", default: true });
1793
- if (!proceed) {
1794
- process.stdout.write(kleur3.yellow("Cancelled.\n"));
1795
- return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
1796
- }
1797
- return {
1798
- cancelled: false,
1799
- options: {
1800
- targetDir: path3.resolve(process.cwd(), targetDir),
1801
- template,
1802
- provider,
1803
- tools,
1804
- memory,
1805
- packageManager
1806
- }
1807
- };
1808
- } catch (err) {
1809
- if (err.name === "ExitPromptError") {
1810
- process.stdout.write(kleur3.yellow("\nCancelled.\n"));
1811
- return { cancelled: true, options: { targetDir: "", template: "react" } };
2317
+ // src/extensibility/rag/embedders.ts
2318
+ function createOpenAiEmbedder(config) {
2319
+ const model = config.model ?? "text-embedding-3-small";
2320
+ const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
2321
+ return async (text) => {
2322
+ const res = await fetch(`${baseUrl}/v1/embeddings`, {
2323
+ method: "POST",
2324
+ headers: {
2325
+ "content-type": "application/json",
2326
+ authorization: `Bearer ${config.apiKey}`
2327
+ },
2328
+ body: JSON.stringify({ model, input: text })
2329
+ });
2330
+ if (!res.ok) {
2331
+ const body = await res.text().catch(() => "");
2332
+ throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
1812
2333
  }
1813
- throw err;
2334
+ const json = await res.json();
2335
+ const first = json.data?.[0]?.embedding;
2336
+ if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
2337
+ return first;
2338
+ };
2339
+ }
2340
+ function resolveEmbedder(config) {
2341
+ const embedder = config.embedder;
2342
+ const provider = embedder?.provider ?? "openai";
2343
+ if (provider !== "openai") {
2344
+ throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
1814
2345
  }
2346
+ const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
2347
+ if (!apiKey) {
2348
+ throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
2349
+ }
2350
+ return createOpenAiEmbedder({
2351
+ apiKey,
2352
+ model: embedder?.model,
2353
+ baseUrl: embedder?.baseUrl
2354
+ });
1815
2355
  }
1816
- function printNextSteps(options) {
1817
- const dir = path3.relative(process.cwd(), options.targetDir) || ".";
1818
- const pm = options.packageManager ?? "pnpm";
1819
- const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
1820
- const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
1821
- process.stdout.write("\n" + kleur3.green("\u2713 Created starter at ") + kleur3.bold(dir) + "\n\n");
1822
- process.stdout.write(kleur3.bold("Next steps:\n\n"));
1823
- process.stdout.write(` ${kleur3.cyan("cd")} ${dir}
1824
- `);
1825
- process.stdout.write(` ${kleur3.cyan(installCmd)}
1826
- `);
1827
- if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
1828
- process.stdout.write(
1829
- ` ${kleur3.cyan("cp")} .env.example .env ${kleur3.dim("# add your API key")}
1830
- `
1831
- );
2356
+ function buildRagFromConfig(options) {
2357
+ const cwd = options.cwd ?? process.cwd();
2358
+ const dir = resolve(cwd, options.config.dir ?? "./.agentskit-rag");
2359
+ const store = fileVectorMemory({ path: `${dir}/store.json` });
2360
+ const embed = options.embedder ?? resolveEmbedder(options.config);
2361
+ return createRAG({
2362
+ embed,
2363
+ store,
2364
+ chunkSize: options.config.chunkSize,
2365
+ topK: options.config.topK
2366
+ });
2367
+ }
2368
+ async function indexSources(rag, config, cwd) {
2369
+ const root = cwd ?? process.cwd();
2370
+ const sources = config.sources ?? [];
2371
+ const absolutePaths = [];
2372
+ for (const pattern of sources) {
2373
+ for await (const match of glob(pattern, { cwd: root })) {
2374
+ absolutePaths.push(resolve(root, match));
2375
+ }
1832
2376
  }
1833
- process.stdout.write(` ${kleur3.cyan(runCmd)}
2377
+ const documents = await Promise.all(
2378
+ absolutePaths.map(async (path5) => ({
2379
+ id: path5,
2380
+ content: await readFile(path5, "utf8"),
2381
+ source: path5
2382
+ }))
2383
+ );
2384
+ if (documents.length > 0) await rag.ingest(documents);
2385
+ return { documentCount: documents.length, sources: absolutePaths };
2386
+ }
2387
+
2388
+ // src/commands/shared.ts
2389
+ function mergeWithConfig(options, config) {
2390
+ if (!config) return options;
2391
+ const d = config.defaults ?? {};
2392
+ const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
2393
+ return {
2394
+ ...options,
2395
+ provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
2396
+ model: options.model ?? d.model,
2397
+ apiKey: resolvedApiKey,
2398
+ baseUrl: options.baseUrl ?? d.baseUrl,
2399
+ tools: options.tools ?? d.tools,
2400
+ skill: options.skill ?? d.skill,
2401
+ system: options.system ?? d.system,
2402
+ memoryBackend: options.memoryBackend ?? d.memoryBackend
2403
+ };
2404
+ }
1834
2405
 
2406
+ // src/commands/chat.ts
2407
+ function registerChatCommand(program) {
2408
+ 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(
2409
+ "--plugin-dir <dir>",
2410
+ "Extra directory to auto-discover plugin modules from (repeatable)",
2411
+ (value, prev = []) => [...prev, value],
2412
+ []
2413
+ ).option(
2414
+ "--mode <mode>",
2415
+ "Permission mode: default | plan | acceptEdits | bypassPermissions"
2416
+ ).action(async (options) => {
2417
+ if (options.listSessions) {
2418
+ const sessions = listSessions();
2419
+ if (sessions.length === 0) {
2420
+ process.stdout.write("No saved sessions for this directory.\n");
2421
+ return;
2422
+ }
2423
+ for (const s of sessions) {
2424
+ const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
2425
+ const display = label ? `${label} (${id})` : id;
2426
+ const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
2427
+ process.stdout.write(
2428
+ `${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
2429
+ ${preview}
2430
+ `
2431
+ );
2432
+ }
2433
+ return;
2434
+ }
2435
+ const config = options.config !== false ? await loadConfig() : void 0;
2436
+ const merged = mergeWithConfig(options, config);
2437
+ const session = resolveSession({
2438
+ explicitPath: options.memory,
2439
+ forceNew: Boolean(options.new),
2440
+ resumeId: options.resume
2441
+ });
2442
+ if (!session.isNew && !options.memory) {
2443
+ process.stdout.write(
2444
+ `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
2445
+ `
2446
+ );
2447
+ }
2448
+ const pluginBundle = await loadPlugins({
2449
+ specs: config?.plugins ?? [],
2450
+ pluginDirs: options.pluginDir ?? []
2451
+ });
2452
+ const configHooks = configHooksToHandlers(config?.hooks);
2453
+ const hookHandlers = [...configHooks, ...pluginBundle.hooks];
2454
+ const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
2455
+ name,
2456
+ command: spec.command,
2457
+ args: spec.args,
2458
+ env: spec.env,
2459
+ timeout: spec.timeout
2460
+ }));
2461
+ const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
2462
+ const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
2463
+ const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
2464
+ const permissionPolicy = {
2465
+ mode: policyMode,
2466
+ rules: (config?.permissions?.rules ?? []).map((r) => ({
2467
+ tool: r.tool,
2468
+ action: r.action,
2469
+ scope: r.scope
2470
+ }))
2471
+ };
2472
+ const chatOptions = {
2473
+ apiKey: merged.apiKey ?? options.apiKey,
2474
+ baseUrl: merged.baseUrl ?? options.baseUrl,
2475
+ provider: merged.provider,
2476
+ model: merged.model,
2477
+ system: merged.system ?? options.system,
2478
+ memoryPath: session.file,
2479
+ sessionId: session.id,
2480
+ tools: merged.tools ?? options.tools,
2481
+ skill: merged.skill ?? options.skill,
2482
+ memoryBackend: merged.memoryBackend ?? options.memoryBackend,
2483
+ agentsKitConfig: config,
2484
+ slashCommands: pluginBundle.slashCommands,
2485
+ extraTools: [...pluginBundle.tools, ...mcpTools],
2486
+ extraSkills: pluginBundle.skills,
2487
+ hookHandlers,
2488
+ permissionPolicy
2489
+ };
2490
+ process.stdout.write(`${renderChatHeader(chatOptions)}
1835
2491
  `);
1836
- process.stdout.write(kleur3.dim(" Docs: https://www.agentskit.io/docs\n\n"));
2492
+ const instance = render(React2.createElement(ChatApp, chatOptions));
2493
+ try {
2494
+ await instance.waitUntilExit();
2495
+ } finally {
2496
+ disposeMcpClients(mcpClients);
2497
+ }
2498
+ if (options.memory) {
2499
+ process.stdout.write(
2500
+ `
2501
+ Session saved to ${session.file}. Resume with --memory ${session.file}
2502
+ `
2503
+ );
2504
+ } else {
2505
+ process.stdout.write(
2506
+ `
2507
+ Session saved. Resume with:
2508
+ agentskit chat --resume ${session.id}
2509
+ Or start fresh with:
2510
+ agentskit chat --new
2511
+ `
2512
+ );
2513
+ }
2514
+ });
1837
2515
  }
1838
2516
  function RunApp({ task, options }) {
1839
2517
  const [status, setStatus] = useState("running");
@@ -1934,91 +2612,8 @@ function RunApp({ task, options }) {
1934
2612
  ] });
1935
2613
  }
1936
2614
 
1937
- // src/commands.ts
1938
- function mergeWithConfig(options, config) {
1939
- if (!config) return options;
1940
- const d = config.defaults ?? {};
1941
- const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
1942
- return {
1943
- ...options,
1944
- // Config defaults — only apply if CLI flag wasn't set
1945
- provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
1946
- model: options.model ?? d.model,
1947
- apiKey: resolvedApiKey,
1948
- baseUrl: options.baseUrl ?? d.baseUrl,
1949
- tools: options.tools ?? d.tools,
1950
- skill: options.skill ?? d.skill,
1951
- system: options.system ?? d.system,
1952
- memoryBackend: options.memoryBackend ?? d.memoryBackend
1953
- };
1954
- }
1955
- function createCli() {
1956
- const program = new Command();
1957
- program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1958
- program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
1959
- if (options.listSessions) {
1960
- const sessions = listSessions();
1961
- if (sessions.length === 0) {
1962
- process.stdout.write("No saved sessions for this directory.\n");
1963
- return;
1964
- }
1965
- for (const s of sessions) {
1966
- const { id, updatedAt, messageCount, preview, model } = s.metadata;
1967
- process.stdout.write(
1968
- `${id} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}
1969
- ${preview}
1970
- `
1971
- );
1972
- }
1973
- return;
1974
- }
1975
- const config = options.config !== false ? await loadConfig() : void 0;
1976
- const merged = mergeWithConfig(options, config);
1977
- const session = resolveSession({
1978
- explicitPath: options.memory,
1979
- forceNew: Boolean(options.new),
1980
- resumeId: options.resume
1981
- });
1982
- if (!session.isNew && !options.memory) {
1983
- process.stdout.write(
1984
- `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
1985
- `
1986
- );
1987
- }
1988
- const chatOptions = {
1989
- apiKey: merged.apiKey ?? options.apiKey,
1990
- baseUrl: merged.baseUrl ?? options.baseUrl,
1991
- provider: merged.provider,
1992
- model: merged.model,
1993
- system: merged.system ?? options.system,
1994
- memoryPath: session.file,
1995
- sessionId: session.id,
1996
- tools: merged.tools ?? options.tools,
1997
- skill: merged.skill ?? options.skill,
1998
- memoryBackend: merged.memoryBackend ?? options.memoryBackend,
1999
- agentsKitConfig: config
2000
- };
2001
- process.stdout.write(`${renderChatHeader(chatOptions)}
2002
- `);
2003
- const instance = render(React3.createElement(ChatApp, chatOptions));
2004
- await instance.waitUntilExit();
2005
- if (options.memory) {
2006
- process.stdout.write(
2007
- `
2008
- Session saved to ${session.file}. Resume with --memory ${session.file}
2009
- `
2010
- );
2011
- } else {
2012
- process.stdout.write(
2013
- `
2014
- Session saved. Resume with:
2015
- agentskit chat --resume ${session.id}
2016
- Or start fresh with:
2017
- agentskit chat --new
2018
- `
2019
- );
2020
- }
2021
- });
2615
+ // src/commands/run.ts
2616
+ function registerRunCommand(program) {
2022
2617
  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) => {
2023
2618
  const task = options.task ?? positionalTask;
2024
2619
  if (!task) {
@@ -2028,7 +2623,7 @@ Or start fresh with:
2028
2623
  const config = options.config !== false ? await loadConfig() : void 0;
2029
2624
  const merged = mergeWithConfig(options, config);
2030
2625
  if (options.pretty) {
2031
- render(React3.createElement(RunApp, { task, options }));
2626
+ render(React2.createElement(RunApp, { task, options }));
2032
2627
  } else {
2033
2628
  try {
2034
2629
  await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
@@ -2039,13 +2634,144 @@ Or start fresh with:
2039
2634
  }
2040
2635
  }
2041
2636
  });
2637
+ }
2638
+ async function runInteractiveInit(defaults = {}) {
2639
+ process.stdout.write(`
2640
+ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
2641
+ `);
2642
+ process.stdout.write(kleur3.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
2643
+ try {
2644
+ const targetDir = await input({
2645
+ message: "Project directory:",
2646
+ default: defaults.dir ?? "agentskit-app",
2647
+ validate: (value) => {
2648
+ if (!value.trim()) return "A directory name is required.";
2649
+ const abs = path.resolve(process.cwd(), value);
2650
+ if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
2651
+ return true;
2652
+ }
2653
+ });
2654
+ const template = await select({
2655
+ message: "Template:",
2656
+ default: defaults.template ?? "react",
2657
+ choices: [
2658
+ { name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
2659
+ { name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
2660
+ { name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
2661
+ { name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
2662
+ ]
2663
+ });
2664
+ const provider = await select({
2665
+ message: "LLM provider:",
2666
+ default: "demo",
2667
+ choices: [
2668
+ { name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
2669
+ { name: "OpenAI", value: "openai" },
2670
+ { name: "Anthropic", value: "anthropic" },
2671
+ { name: "Gemini", value: "gemini" },
2672
+ { name: "Ollama (local, no key)", value: "ollama" }
2673
+ ]
2674
+ });
2675
+ let tools = [];
2676
+ if (template !== "react") {
2677
+ tools = await checkbox({
2678
+ message: "Tools (space to toggle, enter to confirm):",
2679
+ choices: [
2680
+ { name: "web_search", value: "web_search" },
2681
+ { name: "filesystem", value: "filesystem" },
2682
+ { name: "shell", value: "shell" }
2683
+ ]
2684
+ });
2685
+ }
2686
+ const memory = await select({
2687
+ message: "Memory backend:",
2688
+ default: "none",
2689
+ choices: [
2690
+ { name: "None (stateless)", value: "none" },
2691
+ { name: "File (JSON on disk)", value: "file" },
2692
+ { name: "SQLite (better-sqlite3)", value: "sqlite" }
2693
+ ]
2694
+ });
2695
+ const packageManager = await select({
2696
+ message: "Package manager:",
2697
+ default: "pnpm",
2698
+ choices: [
2699
+ { name: "pnpm", value: "pnpm" },
2700
+ { name: "npm", value: "npm" },
2701
+ { name: "yarn", value: "yarn" },
2702
+ { name: "bun", value: "bun" }
2703
+ ]
2704
+ });
2705
+ process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
2706
+ process.stdout.write(kleur3.dim(` dir ${targetDir}
2707
+ `));
2708
+ process.stdout.write(kleur3.dim(` template ${template}
2709
+ `));
2710
+ process.stdout.write(kleur3.dim(` provider ${provider}
2711
+ `));
2712
+ if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
2713
+ `));
2714
+ process.stdout.write(kleur3.dim(` memory ${memory}
2715
+ `));
2716
+ process.stdout.write(kleur3.dim(` pm ${packageManager}
2717
+
2718
+ `));
2719
+ const proceed = await confirm({ message: "Generate?", default: true });
2720
+ if (!proceed) {
2721
+ process.stdout.write(kleur3.yellow("Cancelled.\n"));
2722
+ return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
2723
+ }
2724
+ return {
2725
+ cancelled: false,
2726
+ options: {
2727
+ targetDir: path.resolve(process.cwd(), targetDir),
2728
+ template,
2729
+ provider,
2730
+ tools,
2731
+ memory,
2732
+ packageManager
2733
+ }
2734
+ };
2735
+ } catch (err) {
2736
+ if (err.name === "ExitPromptError") {
2737
+ process.stdout.write(kleur3.yellow("\nCancelled.\n"));
2738
+ return { cancelled: true, options: { targetDir: "", template: "react" } };
2739
+ }
2740
+ throw err;
2741
+ }
2742
+ }
2743
+ function printNextSteps(options) {
2744
+ const dir = path.relative(process.cwd(), options.targetDir) || ".";
2745
+ const pm = options.packageManager ?? "pnpm";
2746
+ const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
2747
+ const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
2748
+ process.stdout.write("\n" + kleur3.green("\u2713 Created starter at ") + kleur3.bold(dir) + "\n\n");
2749
+ process.stdout.write(kleur3.bold("Next steps:\n\n"));
2750
+ process.stdout.write(` ${kleur3.cyan("cd")} ${dir}
2751
+ `);
2752
+ process.stdout.write(` ${kleur3.cyan(installCmd)}
2753
+ `);
2754
+ if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
2755
+ process.stdout.write(
2756
+ ` ${kleur3.cyan("cp")} .env.example .env ${kleur3.dim("# add your API key")}
2757
+ `
2758
+ );
2759
+ }
2760
+ process.stdout.write(` ${kleur3.cyan(runCmd)}
2761
+
2762
+ `);
2763
+ process.stdout.write(kleur3.dim(" Docs: https://www.agentskit.io/docs\n\n"));
2764
+ }
2765
+
2766
+ // src/commands/init.ts
2767
+ function registerInitCommand(program) {
2042
2768
  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) => {
2043
2769
  const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
2044
2770
  let resolved;
2045
2771
  if (isCi) {
2046
2772
  const template = rawOptions.template ?? "react";
2047
2773
  resolved = {
2048
- targetDir: path3.resolve(process.cwd(), rawOptions.dir),
2774
+ targetDir: path.resolve(process.cwd(), rawOptions.dir),
2049
2775
  template,
2050
2776
  provider: rawOptions.provider ?? "demo",
2051
2777
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
@@ -2065,13 +2791,17 @@ Or start fresh with:
2065
2791
  await writeStarterProject(resolved);
2066
2792
  if (isCi) {
2067
2793
  process.stdout.write(
2068
- `Created ${resolved.template} starter in ${path3.relative(process.cwd(), resolved.targetDir) || "."}
2794
+ `Created ${resolved.template} starter in ${path.relative(process.cwd(), resolved.targetDir) || "."}
2069
2795
  `
2070
2796
  );
2071
2797
  } else {
2072
2798
  printNextSteps(resolved);
2073
2799
  }
2074
2800
  });
2801
+ }
2802
+
2803
+ // src/commands/doctor.ts
2804
+ function registerDoctorCommand(program) {
2075
2805
  program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
2076
2806
  "--providers <providers>",
2077
2807
  "Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
@@ -2088,6 +2818,10 @@ Or start fresh with:
2088
2818
  }
2089
2819
  if (report.fail > 0) process.exit(1);
2090
2820
  });
2821
+ }
2822
+
2823
+ // src/commands/dev.ts
2824
+ function registerDevCommand(program) {
2091
2825
  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) => {
2092
2826
  const entry = positional ?? "src/index.ts";
2093
2827
  const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
@@ -2106,9 +2840,15 @@ Or start fresh with:
2106
2840
  process.exit(1);
2107
2841
  }
2108
2842
  });
2109
- program.command("config").description("Show or scaffold the AgentsKit config.").argument("[action]", 'Action: "init" to create a template, "show" to print the merged config.', "show").option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
2843
+ }
2844
+ function registerConfigCommand(program) {
2845
+ program.command("config").description("Show or scaffold the AgentsKit config.").argument(
2846
+ "[action]",
2847
+ 'Action: "init" to create a template, "show" to print the merged config.',
2848
+ "show"
2849
+ ).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) => {
2110
2850
  const isLocal = Boolean(options.local);
2111
- const targetPath = isLocal ? path3.join(process.cwd(), ".agentskit.config.json") : path3.join(homedir(), ".agentskit", "config.json");
2851
+ const targetPath = isLocal ? path.join(process.cwd(), ".agentskit.config.json") : path.join(homedir(), ".agentskit", "config.json");
2112
2852
  if (action === "show") {
2113
2853
  const config = await loadConfig();
2114
2854
  process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
@@ -2120,8 +2860,10 @@ Or start fresh with:
2120
2860
  process.exit(2);
2121
2861
  }
2122
2862
  if (existsSync(targetPath) && !options.force) {
2123
- process.stderr.write(`Config already exists at ${targetPath}. Re-run with --force to overwrite.
2124
- `);
2863
+ process.stderr.write(
2864
+ `Config already exists at ${targetPath}. Re-run with --force to overwrite.
2865
+ `
2866
+ );
2125
2867
  process.exit(1);
2126
2868
  }
2127
2869
  const template = {
@@ -2133,7 +2875,7 @@ Or start fresh with:
2133
2875
  tools: "web_search,fetch_url"
2134
2876
  }
2135
2877
  };
2136
- mkdirSync(path3.dirname(targetPath), { recursive: true });
2878
+ mkdirSync(path.dirname(targetPath), { recursive: true });
2137
2879
  writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2138
2880
  process.stdout.write(
2139
2881
  `Wrote ${targetPath}
@@ -2143,6 +2885,10 @@ Edit it to taste, then run:
2143
2885
  `
2144
2886
  );
2145
2887
  });
2888
+ }
2889
+
2890
+ // src/commands/tunnel.ts
2891
+ function registerTunnelCommand(program) {
2146
2892
  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) => {
2147
2893
  const portNum = Number(port);
2148
2894
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -2163,9 +2909,62 @@ Edit it to taste, then run:
2163
2909
  process.exit(1);
2164
2910
  }
2165
2911
  });
2912
+ }
2913
+
2914
+ // src/commands/rag.ts
2915
+ function registerRagCommand(program) {
2916
+ const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
2917
+ rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
2918
+ "--source <glob>",
2919
+ "Glob to index (overrides config.rag.sources; repeatable)",
2920
+ (value, prev = []) => [...prev, value],
2921
+ []
2922
+ ).action(async (options) => {
2923
+ const config = await loadConfig();
2924
+ const rawConfig = config?.rag;
2925
+ const overrideSources = options.source;
2926
+ const ragConfig = {
2927
+ ...rawConfig ?? {},
2928
+ sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
2929
+ };
2930
+ if (!ragConfig.sources || ragConfig.sources.length === 0) {
2931
+ process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
2932
+ process.exit(1);
2933
+ }
2934
+ try {
2935
+ const instance = buildRagFromConfig({ config: ragConfig });
2936
+ const result = await indexSources(instance, ragConfig);
2937
+ process.stdout.write(
2938
+ `Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
2939
+ `
2940
+ );
2941
+ for (const source of result.sources) {
2942
+ process.stdout.write(` \u2022 ${source}
2943
+ `);
2944
+ }
2945
+ } catch (err) {
2946
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2947
+ `);
2948
+ process.exit(1);
2949
+ }
2950
+ });
2951
+ }
2952
+
2953
+ // src/commands/index.ts
2954
+ function createCli() {
2955
+ const program = new Command();
2956
+ program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
2957
+ registerChatCommand(program);
2958
+ registerRunCommand(program);
2959
+ registerInitCommand(program);
2960
+ registerDoctorCommand(program);
2961
+ registerDevCommand(program);
2962
+ registerConfigCommand(program);
2963
+ registerTunnelCommand(program);
2964
+ registerRagCommand(program);
2166
2965
  return program;
2167
2966
  }
2168
2967
 
2169
- export { ChatApp, createCli, loadConfig, renderChatHeader, renderReport, resolveChatProvider, runAgent, runDoctor, startDev, startTunnel, writeStarterProject };
2170
- //# sourceMappingURL=chunk-V7E4HWTG.js.map
2171
- //# sourceMappingURL=chunk-V7E4HWTG.js.map
2968
+ export { ChatApp, HookDispatcher, McpClient, applyPolicyToTool, applyPolicyToTools, bridgeMcpServers, buildRagFromConfig, computeCost, configHooksToHandlers, createCli, createOpenAiEmbedder, defaultPolicy, derivePreview, disposeMcpClients, evaluatePolicy, findLatestSession, findSession, forkSession, generateSessionId, getPricing, indexSources, listSessions, loadConfig, loadPlugins, mergePluginsIntoBundle, registerPricing, renameSession, renderChatHeader, renderReport, resolveChatProvider, resolveSession, runAgent, runDoctor, sessionFilePath, startDev, startTunnel, writeSessionMeta, writeStarterProject };
2969
+ //# sourceMappingURL=chunk-72XFU2X2.js.map
2970
+ //# sourceMappingURL=chunk-72XFU2X2.js.map