@agentskit/cli 0.5.2 → 0.6.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
@@ -4,17 +4,19 @@
4
4
  var React3 = require('react');
5
5
  var ink$1 = require('ink');
6
6
  var commander = require('commander');
7
- var path = require('path');
7
+ var fs = require('fs');
8
+ var os = require('os');
9
+ var path3 = require('path');
8
10
  var promises = require('fs/promises');
9
11
  var ink = require('@agentskit/ink');
10
12
  var adapters = require('@agentskit/adapters');
11
13
  var tools = require('@agentskit/tools');
12
14
  var skills = require('@agentskit/skills');
13
15
  var memory = require('@agentskit/memory');
16
+ var crypto = require('crypto');
14
17
  var jsxRuntime = require('react/jsx-runtime');
15
18
  var prompts = require('@inquirer/prompts');
16
19
  var kleur = require('kleur');
17
- var fs = require('fs');
18
20
  var runtime = require('@agentskit/runtime');
19
21
  var child_process = require('child_process');
20
22
  var chokidar = require('chokidar');
@@ -22,7 +24,7 @@ var chokidar = require('chokidar');
22
24
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
25
 
24
26
  var React3__default = /*#__PURE__*/_interopDefault(React3);
25
- var path__default = /*#__PURE__*/_interopDefault(path);
27
+ var path3__default = /*#__PURE__*/_interopDefault(path3);
26
28
  var kleur__default = /*#__PURE__*/_interopDefault(kleur);
27
29
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
28
30
 
@@ -44,7 +46,7 @@ async function loadTsConfig(path4) {
44
46
  }
45
47
  async function loadPackageJsonConfig(dir) {
46
48
  try {
47
- const raw = await promises.readFile(path.join(dir, "package.json"), "utf8");
49
+ const raw = await promises.readFile(path3.join(dir, "package.json"), "utf8");
48
50
  const pkg = JSON.parse(raw);
49
51
  if (pkg.agentskit && typeof pkg.agentskit === "object") {
50
52
  return pkg.agentskit;
@@ -54,16 +56,39 @@ async function loadPackageJsonConfig(dir) {
54
56
  return void 0;
55
57
  }
56
58
  }
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);
59
+ function mergeConfigs(base, override) {
60
+ if (!base && !override) return void 0;
61
+ if (!base) return override;
62
+ if (!override) return base;
63
+ return {
64
+ ...base,
65
+ ...override,
66
+ tools: { ...base.tools, ...override.tools },
67
+ defaults: { ...base.defaults, ...override.defaults },
68
+ runtime: { ...base.runtime, ...override.runtime },
69
+ observability: { ...base.observability, ...override.observability }
70
+ };
71
+ }
72
+ async function loadLocalConfig(cwd) {
73
+ const tsConfig = await loadTsConfig(path3.join(cwd, ".agentskit.config.ts"));
61
74
  if (tsConfig) return tsConfig;
62
- const jsonPath = path.join(cwd, ".agentskit.config.json");
63
- const jsonConfig = await loadJsonConfig(jsonPath);
75
+ const jsonConfig = await loadJsonConfig(path3.join(cwd, ".agentskit.config.json"));
64
76
  if (jsonConfig) return jsonConfig;
65
77
  return await loadPackageJsonConfig(cwd);
66
78
  }
79
+ async function loadGlobalConfig(home) {
80
+ if (home === null) return void 0;
81
+ const globalDir = path3.join(home ?? os.homedir(), ".agentskit");
82
+ const tsConfig = await loadTsConfig(path3.join(globalDir, "config.ts"));
83
+ if (tsConfig) return tsConfig;
84
+ return await loadJsonConfig(path3.join(globalDir, "config.json"));
85
+ }
86
+ async function loadConfig(options) {
87
+ const cwd = path3.resolve(options?.cwd ?? process.cwd());
88
+ const global = await loadGlobalConfig(options?.home);
89
+ const local = await loadLocalConfig(cwd);
90
+ return mergeConfigs(global, local);
91
+ }
67
92
  var providers = {
68
93
  openai: {
69
94
  label: "OpenAI",
@@ -181,26 +206,41 @@ var skillRegistry = {
181
206
  critic: skills.critic,
182
207
  summarizer: skills.summarizer
183
208
  };
209
+ function instantiate(kind) {
210
+ switch (kind) {
211
+ case "web_search":
212
+ return [tools.webSearch()];
213
+ case "fetch_url":
214
+ return [tools.fetchUrl()];
215
+ case "filesystem":
216
+ return tools.filesystem({ basePath: process.cwd() });
217
+ case "shell":
218
+ return [tools.shell({ timeout: 3e4 })];
219
+ }
220
+ }
221
+ function gateTool(tool) {
222
+ if (tool.requiresConfirmation === false) return tool;
223
+ return { ...tool, requiresConfirmation: true };
224
+ }
184
225
  function resolveTools(toolNames) {
185
- if (!toolNames) return [];
186
- const tools$1 = [];
187
- for (const name of toolNames.split(",").map((s) => s.trim())) {
226
+ if (!toolNames) {
227
+ return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
228
+ }
229
+ const tools = [];
230
+ for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
188
231
  switch (name) {
189
232
  case "web_search":
190
- tools$1.push(tools.webSearch());
191
- break;
233
+ case "fetch_url":
192
234
  case "filesystem":
193
- tools$1.push(...tools.filesystem({ basePath: process.cwd() }));
194
- break;
195
235
  case "shell":
196
- tools$1.push(tools.shell({ timeout: 3e4 }));
236
+ tools.push(...instantiate(name));
197
237
  break;
198
238
  default:
199
239
  process.stderr.write(`Unknown tool: ${name}
200
240
  `);
201
241
  }
202
242
  }
203
- return tools$1;
243
+ return tools;
204
244
  }
205
245
  function resolveSkill(skillName) {
206
246
  if (!skillName) return void 0;
@@ -233,41 +273,475 @@ function resolveMemory(backend, memoryPath) {
233
273
  return memory.fileChatMemory(memoryPath);
234
274
  }
235
275
  }
276
+ var ROOT = path3.join(os.homedir(), ".agentskit", "sessions");
277
+ var META_SUFFIX = ".meta.json";
278
+ function cwdHash(cwd = process.cwd()) {
279
+ return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
280
+ }
281
+ function dirFor(cwd = process.cwd()) {
282
+ return path3.join(ROOT, cwdHash(cwd));
283
+ }
284
+ function ensureDir(dir) {
285
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
286
+ }
287
+ function generateSessionId() {
288
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
289
+ const suffix = crypto.randomBytes(3).toString("hex");
290
+ return `${ts}-${suffix}`;
291
+ }
292
+ function sessionFilePath(id, cwd = process.cwd()) {
293
+ ensureDir(dirFor(cwd));
294
+ return path3.join(dirFor(cwd), `${id}.json`);
295
+ }
296
+ function metaPath(id, cwd = process.cwd()) {
297
+ return path3.join(dirFor(cwd), `${id}${META_SUFFIX}`);
298
+ }
299
+ function readMeta(id, cwd = process.cwd()) {
300
+ const path4 = metaPath(id, cwd);
301
+ if (!fs.existsSync(path4)) return null;
302
+ try {
303
+ return JSON.parse(fs.readFileSync(path4, "utf8"));
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+ function writeSessionMeta(meta, cwd = process.cwd()) {
309
+ ensureDir(dirFor(cwd));
310
+ fs.writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
311
+ }
312
+ function derivePreview(messages) {
313
+ const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
314
+ if (!firstUser) return "(empty)";
315
+ const single = firstUser.content.replace(/\s+/g, " ").trim();
316
+ return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
317
+ }
318
+ function listSessions(cwd = process.cwd()) {
319
+ const dir = dirFor(cwd);
320
+ if (!fs.existsSync(dir)) return [];
321
+ const entries = fs.readdirSync(dir);
322
+ const records = [];
323
+ for (const entry of entries) {
324
+ if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
325
+ const id = entry.replace(/\.json$/, "");
326
+ const meta = readMeta(id, cwd);
327
+ const file = path3.join(dir, entry);
328
+ if (meta) {
329
+ records.push({ metadata: meta, file });
330
+ } else {
331
+ const stats = fs.statSync(file);
332
+ records.push({
333
+ metadata: {
334
+ id,
335
+ cwd,
336
+ createdAt: stats.birthtime.toISOString(),
337
+ updatedAt: stats.mtime.toISOString(),
338
+ messageCount: 0,
339
+ preview: "(legacy session)"
340
+ },
341
+ file
342
+ });
343
+ }
344
+ }
345
+ records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
346
+ return records;
347
+ }
348
+ function findLatestSession(cwd = process.cwd()) {
349
+ const all = listSessions(cwd);
350
+ return all[0] ?? null;
351
+ }
352
+ function findSession(id, cwd = process.cwd()) {
353
+ const exact = listSessions(cwd).find((s) => s.metadata.id === id);
354
+ if (exact) return exact;
355
+ const prefix = listSessions(cwd).find((s) => s.metadata.id.startsWith(id));
356
+ return prefix ?? null;
357
+ }
358
+ function resolveSession(input2) {
359
+ const cwd = input2.cwd ?? process.cwd();
360
+ if (input2.explicitPath) {
361
+ return { id: "custom", file: input2.explicitPath, isNew: !fs.existsSync(input2.explicitPath) };
362
+ }
363
+ if (input2.forceNew) {
364
+ const id2 = generateSessionId();
365
+ return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
366
+ }
367
+ if (input2.resumeId) {
368
+ const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
369
+ if (target) {
370
+ return { id: target.metadata.id, file: target.file, isNew: false };
371
+ }
372
+ process.stderr.write(
373
+ `No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
374
+ `
375
+ );
376
+ }
377
+ const latest = findLatestSession(cwd);
378
+ if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
379
+ const id = generateSessionId();
380
+ return { id, file: sessionFilePath(id, cwd), isNew: true };
381
+ }
382
+
383
+ // src/slash-commands.ts
384
+ function parseSlashCommand(input2) {
385
+ if (!input2.startsWith("/")) return null;
386
+ const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
387
+ if (!match) return null;
388
+ return { name: match[1], args: match[2] ?? "" };
389
+ }
390
+ function createSlashRegistry(commands) {
391
+ const map = /* @__PURE__ */ new Map();
392
+ for (const cmd of commands) {
393
+ map.set(cmd.name, cmd);
394
+ for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
395
+ }
396
+ return map;
397
+ }
398
+ var builtinSlashCommands = [
399
+ {
400
+ name: "help",
401
+ aliases: ["?"],
402
+ description: "List available slash commands.",
403
+ run(ctx) {
404
+ const seen = /* @__PURE__ */ new Set();
405
+ const lines = [];
406
+ for (const cmd of ctx.commands) {
407
+ if (seen.has(cmd.name)) continue;
408
+ seen.add(cmd.name);
409
+ const suffix = cmd.usage ? ` (${cmd.usage})` : "";
410
+ lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
411
+ }
412
+ ctx.feedback(`Slash commands:
413
+ ${lines.join("\n")}`, "info");
414
+ }
415
+ },
416
+ {
417
+ name: "model",
418
+ description: "Switch the active model.",
419
+ usage: "/model <name>",
420
+ run(ctx, args) {
421
+ const value = args.trim();
422
+ if (!value) {
423
+ ctx.feedback(
424
+ `Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
425
+ "warn"
426
+ );
427
+ return;
428
+ }
429
+ ctx.setModel(value);
430
+ ctx.feedback(`Model \u2192 ${value}`, "success");
431
+ }
432
+ },
433
+ {
434
+ name: "provider",
435
+ description: "Switch the adapter provider.",
436
+ usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
437
+ run(ctx, args) {
438
+ const value = args.trim();
439
+ if (!value) {
440
+ ctx.feedback(
441
+ `Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
442
+ "warn"
443
+ );
444
+ return;
445
+ }
446
+ ctx.setProvider(value);
447
+ ctx.feedback(`Provider \u2192 ${value}`, "success");
448
+ }
449
+ },
450
+ {
451
+ name: "base-url",
452
+ aliases: ["baseurl"],
453
+ description: "Override provider base URL.",
454
+ usage: "/base-url <url|clear>",
455
+ run(ctx, args) {
456
+ const value = args.trim();
457
+ if (!value || value === "clear") {
458
+ ctx.setBaseUrl(void 0);
459
+ ctx.feedback("Base URL cleared.", "success");
460
+ return;
461
+ }
462
+ ctx.setBaseUrl(value);
463
+ ctx.feedback(`Base URL \u2192 ${value}`, "success");
464
+ }
465
+ },
466
+ {
467
+ name: "tools",
468
+ description: "Set active tools (comma-separated) or clear them.",
469
+ usage: "/tools web_search,fetch_url | /tools clear",
470
+ run(ctx, args) {
471
+ const value = args.trim();
472
+ if (!value || value === "clear") {
473
+ ctx.setTools(void 0);
474
+ ctx.feedback("Tools reset to defaults.", "success");
475
+ return;
476
+ }
477
+ ctx.setTools(value);
478
+ ctx.feedback(`Tools \u2192 ${value}`, "success");
479
+ }
480
+ },
481
+ {
482
+ name: "skill",
483
+ description: "Set active skill(s) (comma-separated) or clear them.",
484
+ usage: "/skill researcher,coder | /skill clear",
485
+ run(ctx, args) {
486
+ const value = args.trim();
487
+ if (!value || value === "clear") {
488
+ ctx.setSkill(void 0);
489
+ ctx.feedback("Skills cleared.", "success");
490
+ return;
491
+ }
492
+ ctx.setSkill(value);
493
+ ctx.feedback(`Skills \u2192 ${value}`, "success");
494
+ }
495
+ },
496
+ {
497
+ name: "clear",
498
+ aliases: ["reset"],
499
+ description: "Clear the conversation history in this session.",
500
+ async run(ctx) {
501
+ await ctx.chat.clear();
502
+ ctx.feedback("History cleared.", "success");
503
+ }
504
+ },
505
+ {
506
+ name: "exit",
507
+ aliases: ["quit", "q"],
508
+ description: "Exit the chat.",
509
+ run() {
510
+ process.exit(0);
511
+ }
512
+ }
513
+ ];
514
+ function groupIntoTurns(messages) {
515
+ const turns = [];
516
+ let current = [];
517
+ for (const message of messages) {
518
+ if (message.role === "user") {
519
+ if (current.length > 0) turns.push(current);
520
+ current = [message];
521
+ } else if (message.role === "system") {
522
+ if (current.length > 0) turns.push(current);
523
+ turns.push([message]);
524
+ current = [];
525
+ } else {
526
+ current.push(message);
527
+ }
528
+ }
529
+ if (current.length > 0) turns.push(current);
530
+ return turns;
531
+ }
236
532
  function ChatApp(options) {
237
- const adapter = React3.useMemo(
238
- () => resolveChatProvider(options).adapter,
239
- [options.apiKey, options.baseUrl, options.model, options.provider]
533
+ const [provider, setProvider] = React3.useState(options.provider);
534
+ const [model, setModel] = React3.useState(options.model);
535
+ const [apiKey, setApiKey] = React3.useState(options.apiKey);
536
+ const [baseUrl, setBaseUrl] = React3.useState(options.baseUrl);
537
+ const [toolsFlag, setToolsFlag] = React3.useState(options.tools);
538
+ const [skillFlag, setSkillFlag] = React3.useState(options.skill);
539
+ const runtime = React3.useMemo(
540
+ () => resolveChatProvider({ provider, model, apiKey, baseUrl }),
541
+ [provider, model, apiKey, baseUrl]
240
542
  );
241
543
  const memory = React3.useMemo(
242
544
  () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
243
545
  [options.memoryPath, options.memoryBackend]
244
546
  );
245
- const tools = React3.useMemo(() => resolveTools(options.tools), [options.tools]);
547
+ const tools = React3.useMemo(() => resolveTools(toolsFlag), [toolsFlag]);
246
548
  const skills = React3.useMemo(() => {
247
- if (!options.skill) return void 0;
248
- const names = options.skill.split(",").map((s) => s.trim());
549
+ if (!skillFlag) return void 0;
550
+ const names = skillFlag.split(",").map((s) => s.trim());
249
551
  const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
250
552
  if (resolved.length === 0) return void 0;
251
553
  return resolved;
252
- }, [options.skill]);
554
+ }, [skillFlag]);
253
555
  const chat = ink.useChat({
254
- adapter,
556
+ adapter: runtime.adapter,
255
557
  memory,
256
558
  systemPrompt: options.system,
257
559
  tools: tools.length > 0 ? tools : void 0,
258
560
  skills
259
561
  });
562
+ const [sessionAllowed, setSessionAllowed] = React3.useState(/* @__PURE__ */ new Set());
563
+ const autoApprovedRef = React3.useRef(/* @__PURE__ */ new Set());
564
+ React3.useEffect(() => {
565
+ if (sessionAllowed.size === 0) return;
566
+ for (const message of chat.messages) {
567
+ for (const call of message.toolCalls ?? []) {
568
+ if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
569
+ autoApprovedRef.current.add(call.id);
570
+ void chat.approve(call.id);
571
+ }
572
+ }
573
+ }
574
+ }, [chat.messages, sessionAllowed, chat.approve]);
575
+ const handleApproveAlways = (toolCallId, toolName) => {
576
+ setSessionAllowed((prev) => {
577
+ if (prev.has(toolName)) return prev;
578
+ const next = new Set(prev);
579
+ next.add(toolName);
580
+ return next;
581
+ });
582
+ autoApprovedRef.current.add(toolCallId);
583
+ void chat.approve(toolCallId);
584
+ };
585
+ const sessionCreatedAtRef = React3.useRef(void 0);
586
+ const messageCount = chat.messages.length;
587
+ const firstUserContent = chat.messages.find((m) => m.role === "user")?.content ?? "";
588
+ React3.useEffect(() => {
589
+ const sessionId = options.sessionId;
590
+ if (!sessionId || sessionId === "custom") return;
591
+ if (!sessionCreatedAtRef.current) {
592
+ sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
593
+ }
594
+ try {
595
+ writeSessionMeta({
596
+ id: sessionId,
597
+ cwd: process.cwd(),
598
+ createdAt: sessionCreatedAtRef.current,
599
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
600
+ messageCount,
601
+ preview: derivePreview(chat.messages),
602
+ provider: runtime.provider,
603
+ model: runtime.model
604
+ });
605
+ } catch {
606
+ }
607
+ }, [options.sessionId, messageCount, firstUserContent, runtime.provider, runtime.model]);
608
+ const turns = React3.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
609
+ const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
610
+ const [feedback, setFeedback] = React3.useState(null);
611
+ const slashCommands = React3.useMemo(
612
+ () => [...builtinSlashCommands, ...options.slashCommands ?? []],
613
+ [options.slashCommands]
614
+ );
615
+ const slashRegistry = React3.useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
616
+ const handleSubmitInput = async (raw) => {
617
+ const parsed = parseSlashCommand(raw);
618
+ if (!parsed) {
619
+ setFeedback(null);
620
+ return false;
621
+ }
622
+ const cmd = slashRegistry.get(parsed.name);
623
+ if (!cmd) {
624
+ setFeedback({
625
+ message: `Unknown command: /${parsed.name}. Type /help for the list.`,
626
+ kind: "error"
627
+ });
628
+ return true;
629
+ }
630
+ const ctx = {
631
+ chat,
632
+ runtime: {
633
+ provider: runtime.provider,
634
+ model: runtime.model,
635
+ mode: runtime.mode,
636
+ baseUrl,
637
+ tools: toolsFlag,
638
+ skill: skillFlag
639
+ },
640
+ setProvider,
641
+ setModel,
642
+ setApiKey,
643
+ setBaseUrl,
644
+ setTools: setToolsFlag,
645
+ setSkill: setSkillFlag,
646
+ feedback: (message, kind = "info") => setFeedback({ message, kind }),
647
+ commands: slashCommands
648
+ };
649
+ try {
650
+ await cmd.run(ctx, parsed.args);
651
+ } catch (err) {
652
+ const message = err instanceof Error ? err.message : String(err);
653
+ setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
654
+ }
655
+ return true;
656
+ };
657
+ const awaitingConfirmation = React3.useMemo(
658
+ () => chat.messages.some(
659
+ (message) => message.toolCalls?.some(
660
+ (call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
661
+ )
662
+ ),
663
+ [chat.messages, sessionAllowed]
664
+ );
260
665
  return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", gap: 1, children: [
261
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { bold: true, color: "cyan", children: "AgentsKit CLI" }),
262
- /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { dimColor: true, children: "Press Enter to send. Live providers use env vars or --api-key, and demo mode stays available for zero-config usage." }),
263
- /* @__PURE__ */ jsxRuntime.jsx(ink.ChatContainer, { children: chat.messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", marginBottom: 1, children: [
264
- /* @__PURE__ */ jsxRuntime.jsx(ink.Message, { message }),
265
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ink.ToolCallView, { toolCall }, toolCall.id))
266
- ] }, message.id)) }),
267
- /* @__PURE__ */ jsxRuntime.jsx(ink.ThinkingIndicator, { visible: chat.status === "streaming" }),
268
- /* @__PURE__ */ jsxRuntime.jsx(ink.InputBar, { chat, placeholder: "Type a message and press Enter..." })
666
+ /* @__PURE__ */ jsxRuntime.jsx(
667
+ ink.StatusHeader,
668
+ {
669
+ provider: runtime.provider,
670
+ model: runtime.model,
671
+ mode: runtime.mode,
672
+ tools: toolNames,
673
+ messageCount: chat.messages.length,
674
+ sessionId: options.sessionId
675
+ }
676
+ ),
677
+ /* @__PURE__ */ jsxRuntime.jsx(ink.ChatContainer, { children: turns.map((turn, turnIdx) => {
678
+ const assistantSteps = turn.filter((m) => m.role === "assistant").length;
679
+ let stepIndex = 0;
680
+ return /* @__PURE__ */ jsxRuntime.jsx(ink$1.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
681
+ const showStep = message.role === "assistant" && assistantSteps > 1;
682
+ if (showStep) stepIndex++;
683
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
684
+ showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { dimColor: true, children: [
685
+ "\u21BB step ",
686
+ stepIndex,
687
+ "/",
688
+ assistantSteps
689
+ ] }) : null,
690
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Message, { message }),
691
+ message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { flexDirection: "column", children: [
692
+ /* @__PURE__ */ jsxRuntime.jsx(ink.ToolCallView, { toolCall, expanded: true }),
693
+ toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsxRuntime.jsx(
694
+ ink.ToolConfirmation,
695
+ {
696
+ toolCall,
697
+ onApprove: chat.approve,
698
+ onDeny: chat.deny,
699
+ onApproveAlways: handleApproveAlways
700
+ }
701
+ ) : null
702
+ ] }, toolCall.id))
703
+ ] }, message.id);
704
+ }) }, `turn-${turnIdx}`);
705
+ }) }),
706
+ /* @__PURE__ */ jsxRuntime.jsx(
707
+ ink.ThinkingIndicator,
708
+ {
709
+ visible: chat.status === "streaming",
710
+ label: toolNames.length > 0 ? "agent working" : "thinking"
711
+ }
712
+ ),
713
+ chat.error ? /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
714
+ /* @__PURE__ */ jsxRuntime.jsxs(ink$1.Text, { color: "red", bold: true, children: [
715
+ "\u2717 ",
716
+ chat.error.name || "Error"
717
+ ] }),
718
+ /* @__PURE__ */ jsxRuntime.jsx(ink$1.Text, { color: "red", children: chat.error.message })
719
+ ] }) : null,
720
+ 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,
721
+ /* @__PURE__ */ jsxRuntime.jsx(
722
+ ink.InputBar,
723
+ {
724
+ chat,
725
+ placeholder: "Type a message or /help for commands",
726
+ disabled: awaitingConfirmation,
727
+ onSubmitInput: handleSubmitInput
728
+ }
729
+ )
269
730
  ] });
270
731
  }
732
+ function feedbackBorder(kind) {
733
+ switch (kind) {
734
+ case "error":
735
+ return "red";
736
+ case "warn":
737
+ return "yellow";
738
+ case "success":
739
+ return "green";
740
+ case "info":
741
+ default:
742
+ return "cyan";
743
+ }
744
+ }
271
745
  function renderChatHeader(options) {
272
746
  const runtime = resolveChatProvider(options);
273
747
  const parts = [`provider=${runtime.provider}`];
@@ -376,7 +850,7 @@ function reactStarter(ctx) {
376
850
  return {
377
851
  "package.json": JSON.stringify(
378
852
  {
379
- name: path__default.default.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
853
+ name: path3__default.default.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
380
854
  private: true,
381
855
  type: "module",
382
856
  scripts: {
@@ -745,8 +1219,8 @@ async function writeStarterProject(options) {
745
1219
  await promises.mkdir(options.targetDir, { recursive: true });
746
1220
  await Promise.all(
747
1221
  Object.entries(files).map(async ([relativePath, content]) => {
748
- const absolutePath = path__default.default.join(options.targetDir, relativePath);
749
- await promises.mkdir(path__default.default.dirname(absolutePath), { recursive: true });
1222
+ const absolutePath = path3__default.default.join(options.targetDir, relativePath);
1223
+ await promises.mkdir(path3__default.default.dirname(absolutePath), { recursive: true });
750
1224
  await promises.writeFile(absolutePath, content, "utf8");
751
1225
  })
752
1226
  );
@@ -762,7 +1236,7 @@ ${kleur__default.default.bold().green("\u25B2")} ${kleur__default.default.bold("
762
1236
  default: defaults.dir ?? "agentskit-app",
763
1237
  validate: (value) => {
764
1238
  if (!value.trim()) return "A directory name is required.";
765
- const abs = path__default.default.resolve(process.cwd(), value);
1239
+ const abs = path3__default.default.resolve(process.cwd(), value);
766
1240
  if (fs.existsSync(abs)) return `${value} already exists. Pick a different name.`;
767
1241
  return true;
768
1242
  }
@@ -840,7 +1314,7 @@ ${kleur__default.default.bold().green("\u25B2")} ${kleur__default.default.bold("
840
1314
  return {
841
1315
  cancelled: false,
842
1316
  options: {
843
- targetDir: path__default.default.resolve(process.cwd(), targetDir),
1317
+ targetDir: path3__default.default.resolve(process.cwd(), targetDir),
844
1318
  template,
845
1319
  provider,
846
1320
  tools,
@@ -857,7 +1331,7 @@ ${kleur__default.default.bold().green("\u25B2")} ${kleur__default.default.bold("
857
1331
  }
858
1332
  }
859
1333
  function printNextSteps(options) {
860
- const dir = path__default.default.relative(process.cwd(), options.targetDir) || ".";
1334
+ const dir = path3__default.default.relative(process.cwd(), options.targetDir) || ".";
861
1335
  const pm = options.packageManager ?? "pnpm";
862
1336
  const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
863
1337
  const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
@@ -1071,17 +1545,17 @@ async function checkNodeVersion() {
1071
1545
  }
1072
1546
  async function checkPnpm() {
1073
1547
  const cwd = process.cwd();
1074
- const hasPnpm = fs.existsSync(path.join(cwd, "pnpm-lock.yaml")) || fs.existsSync(path.join(cwd, "pnpm-workspace.yaml"));
1548
+ const hasPnpm = fs.existsSync(path3.join(cwd, "pnpm-lock.yaml")) || fs.existsSync(path3.join(cwd, "pnpm-workspace.yaml"));
1075
1549
  if (hasPnpm) {
1076
1550
  return { status: "pass", name: "Package manager", detail: "pnpm detected (lockfile)" };
1077
1551
  }
1078
- if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
1552
+ if (fs.existsSync(path3.join(cwd, "package-lock.json"))) {
1079
1553
  return { status: "warn", name: "Package manager", detail: "npm detected \u2014 pnpm recommended for monorepo workflows" };
1080
1554
  }
1081
- if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
1555
+ if (fs.existsSync(path3.join(cwd, "yarn.lock"))) {
1082
1556
  return { status: "pass", name: "Package manager", detail: "yarn detected" };
1083
1557
  }
1084
- if (fs.existsSync(path.join(cwd, "bun.lock")) || fs.existsSync(path.join(cwd, "bun.lockb"))) {
1558
+ if (fs.existsSync(path3.join(cwd, "bun.lock")) || fs.existsSync(path3.join(cwd, "bun.lockb"))) {
1085
1559
  return { status: "pass", name: "Package manager", detail: "bun detected" };
1086
1560
  }
1087
1561
  return {
@@ -1091,7 +1565,7 @@ async function checkPnpm() {
1091
1565
  };
1092
1566
  }
1093
1567
  async function checkPackageJson() {
1094
- const path4 = path.join(process.cwd(), "package.json");
1568
+ const path4 = path3.join(process.cwd(), "package.json");
1095
1569
  if (!fs.existsSync(path4)) {
1096
1570
  return {
1097
1571
  status: "warn",
@@ -1316,7 +1790,7 @@ var DEFAULT_IGNORE = [
1316
1790
  "**/*.spec.ts"
1317
1791
  ];
1318
1792
  function startDev(options) {
1319
- const entry = path.resolve(process.cwd(), options.entry);
1793
+ const entry = path3.resolve(process.cwd(), options.entry);
1320
1794
  if (!fs.existsSync(entry)) {
1321
1795
  throw new Error(`Entry file not found: ${entry}`);
1322
1796
  }
@@ -1348,7 +1822,7 @@ function startDev(options) {
1348
1822
  };
1349
1823
  const startChild = () => {
1350
1824
  restartCount++;
1351
- banner(`\u25B8 starting ${kleur__default.default.bold(path.basename(entry))} (restart #${restartCount - 1})`, "cyan");
1825
+ banner(`\u25B8 starting ${kleur__default.default.bold(path3.basename(entry))} (restart #${restartCount - 1})`, "cyan");
1352
1826
  const c = spawnFn(cmd, baseArgs);
1353
1827
  child = c;
1354
1828
  c.stdout?.on("data", (d) => stdout.write(d));
@@ -1472,34 +1946,87 @@ async function startTunnel(options) {
1472
1946
  // src/commands.ts
1473
1947
  function mergeWithConfig(options, config) {
1474
1948
  if (!config) return options;
1949
+ const d = config.defaults ?? {};
1950
+ const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
1475
1951
  return {
1476
1952
  ...options,
1477
1953
  // Config defaults — only apply if CLI flag wasn't set
1478
- provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
1479
- model: options.model ?? config.defaults?.model
1954
+ provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
1955
+ model: options.model ?? d.model,
1956
+ apiKey: resolvedApiKey,
1957
+ baseUrl: options.baseUrl ?? d.baseUrl,
1958
+ tools: options.tools ?? d.tools,
1959
+ skill: options.skill ?? d.skill,
1960
+ system: options.system ?? d.system,
1961
+ memoryBackend: options.memoryBackend ?? d.memoryBackend
1480
1962
  };
1481
1963
  }
1482
1964
  function createCli() {
1483
1965
  const program = new commander.Command();
1484
1966
  program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1485
- 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) => {
1967
+ program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
1968
+ if (options.listSessions) {
1969
+ const sessions = listSessions();
1970
+ if (sessions.length === 0) {
1971
+ process.stdout.write("No saved sessions for this directory.\n");
1972
+ return;
1973
+ }
1974
+ for (const s of sessions) {
1975
+ const { id, updatedAt, messageCount, preview, model } = s.metadata;
1976
+ process.stdout.write(
1977
+ `${id} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}
1978
+ ${preview}
1979
+ `
1980
+ );
1981
+ }
1982
+ return;
1983
+ }
1486
1984
  const config = options.config !== false ? await loadConfig() : void 0;
1487
1985
  const merged = mergeWithConfig(options, config);
1986
+ const session = resolveSession({
1987
+ explicitPath: options.memory,
1988
+ forceNew: Boolean(options.new),
1989
+ resumeId: options.resume
1990
+ });
1991
+ if (!session.isNew && !options.memory) {
1992
+ process.stdout.write(
1993
+ `Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
1994
+ `
1995
+ );
1996
+ }
1488
1997
  const chatOptions = {
1489
1998
  apiKey: merged.apiKey ?? options.apiKey,
1490
1999
  baseUrl: merged.baseUrl ?? options.baseUrl,
1491
2000
  provider: merged.provider,
1492
2001
  model: merged.model,
1493
- system: options.system,
1494
- memoryPath: options.memory,
1495
- tools: options.tools,
1496
- skill: options.skill,
1497
- memoryBackend: options.memoryBackend,
2002
+ system: merged.system ?? options.system,
2003
+ memoryPath: session.file,
2004
+ sessionId: session.id,
2005
+ tools: merged.tools ?? options.tools,
2006
+ skill: merged.skill ?? options.skill,
2007
+ memoryBackend: merged.memoryBackend ?? options.memoryBackend,
1498
2008
  agentsKitConfig: config
1499
2009
  };
1500
2010
  process.stdout.write(`${renderChatHeader(chatOptions)}
1501
2011
  `);
1502
- ink$1.render(React3__default.default.createElement(ChatApp, chatOptions));
2012
+ const instance = ink$1.render(React3__default.default.createElement(ChatApp, chatOptions));
2013
+ await instance.waitUntilExit();
2014
+ if (options.memory) {
2015
+ process.stdout.write(
2016
+ `
2017
+ Session saved to ${session.file}. Resume with --memory ${session.file}
2018
+ `
2019
+ );
2020
+ } else {
2021
+ process.stdout.write(
2022
+ `
2023
+ Session saved. Resume with:
2024
+ agentskit chat --resume ${session.id}
2025
+ Or start fresh with:
2026
+ agentskit chat --new
2027
+ `
2028
+ );
2029
+ }
1503
2030
  });
1504
2031
  program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
1505
2032
  const task = options.task ?? positionalTask;
@@ -1527,7 +2054,7 @@ function createCli() {
1527
2054
  if (isCi) {
1528
2055
  const template = rawOptions.template ?? "react";
1529
2056
  resolved = {
1530
- targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
2057
+ targetDir: path3__default.default.resolve(process.cwd(), rawOptions.dir),
1531
2058
  template,
1532
2059
  provider: rawOptions.provider ?? "demo",
1533
2060
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
@@ -1547,7 +2074,7 @@ function createCli() {
1547
2074
  await writeStarterProject(resolved);
1548
2075
  if (isCi) {
1549
2076
  process.stdout.write(
1550
- `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
2077
+ `Created ${resolved.template} starter in ${path3__default.default.relative(process.cwd(), resolved.targetDir) || "."}
1551
2078
  `
1552
2079
  );
1553
2080
  } else {
@@ -1588,6 +2115,43 @@ function createCli() {
1588
2115
  process.exit(1);
1589
2116
  }
1590
2117
  });
2118
+ program.command("config").description("Show or scaffold the AgentsKit config.").argument("[action]", 'Action: "init" to create a template, "show" to print the merged config.', "show").option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
2119
+ const isLocal = Boolean(options.local);
2120
+ const targetPath = isLocal ? path3__default.default.join(process.cwd(), ".agentskit.config.json") : path3__default.default.join(os.homedir(), ".agentskit", "config.json");
2121
+ if (action === "show") {
2122
+ const config = await loadConfig();
2123
+ process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
2124
+ return;
2125
+ }
2126
+ if (action !== "init") {
2127
+ process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
2128
+ `);
2129
+ process.exit(2);
2130
+ }
2131
+ if (fs.existsSync(targetPath) && !options.force) {
2132
+ process.stderr.write(`Config already exists at ${targetPath}. Re-run with --force to overwrite.
2133
+ `);
2134
+ process.exit(1);
2135
+ }
2136
+ const template = {
2137
+ defaults: {
2138
+ provider: "openai",
2139
+ baseUrl: "https://openrouter.ai/api",
2140
+ apiKeyEnv: "OPENROUTER_API_KEY",
2141
+ model: "openai/gpt-oss-120b:free",
2142
+ tools: "web_search,fetch_url"
2143
+ }
2144
+ };
2145
+ fs.mkdirSync(path3__default.default.dirname(targetPath), { recursive: true });
2146
+ fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2147
+ process.stdout.write(
2148
+ `Wrote ${targetPath}
2149
+ Edit it to taste, then run:
2150
+ agentskit chat
2151
+ (flags on the CLI still win over config values.)
2152
+ `
2153
+ );
2154
+ });
1591
2155
  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) => {
1592
2156
  const portNum = Number(port);
1593
2157
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {