@agentskit/cli 0.5.3 → 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.
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdir, writeFile, readFile } from 'fs/promises';
3
- import path, { resolve, join, basename } from 'path';
3
+ import { homedir } from 'os';
4
+ import path3, { join, resolve, basename } from 'path';
4
5
  import { kimi, grok, deepseek, ollama, gemini, anthropic, openai } from '@agentskit/adapters';
5
- import React3, { useMemo, useState, useEffect } from 'react';
6
+ import React3, { useState, useMemo, useRef, useEffect } from 'react';
6
7
  import { Box, Text, render } from 'ink';
7
- import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ThinkingIndicator, InputBar } from '@agentskit/ink';
8
- import { shell, filesystem, webSearch } from '@agentskit/tools';
8
+ import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ToolConfirmation, ThinkingIndicator, InputBar } from '@agentskit/ink';
9
+ import { shell, filesystem, fetchUrl, webSearch } from '@agentskit/tools';
9
10
  import { summarizer, critic, planner, coder, researcher, composeSkills } from '@agentskit/skills';
10
11
  import { fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
12
+ import { randomBytes, createHash } from 'crypto';
13
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'fs';
11
14
  import { jsxs, jsx } from 'react/jsx-runtime';
12
15
  import { createRuntime } from '@agentskit/runtime';
13
- import { existsSync } from 'fs';
14
16
  import { spawn } from 'child_process';
15
17
  import chokidar from 'chokidar';
16
18
  import kleur3 from 'kleur';
@@ -45,16 +47,39 @@ async function loadPackageJsonConfig(dir) {
45
47
  return void 0;
46
48
  }
47
49
  }
48
- async function loadConfig(options) {
49
- const cwd = resolve(options?.cwd ?? process.cwd());
50
- const tsPath = join(cwd, ".agentskit.config.ts");
51
- const tsConfig = await loadTsConfig(tsPath);
50
+ function mergeConfigs(base, override) {
51
+ if (!base && !override) return void 0;
52
+ if (!base) return override;
53
+ if (!override) return base;
54
+ return {
55
+ ...base,
56
+ ...override,
57
+ tools: { ...base.tools, ...override.tools },
58
+ defaults: { ...base.defaults, ...override.defaults },
59
+ runtime: { ...base.runtime, ...override.runtime },
60
+ observability: { ...base.observability, ...override.observability }
61
+ };
62
+ }
63
+ async function loadLocalConfig(cwd) {
64
+ const tsConfig = await loadTsConfig(join(cwd, ".agentskit.config.ts"));
52
65
  if (tsConfig) return tsConfig;
53
- const jsonPath = join(cwd, ".agentskit.config.json");
54
- const jsonConfig = await loadJsonConfig(jsonPath);
66
+ const jsonConfig = await loadJsonConfig(join(cwd, ".agentskit.config.json"));
55
67
  if (jsonConfig) return jsonConfig;
56
68
  return await loadPackageJsonConfig(cwd);
57
69
  }
70
+ async function loadGlobalConfig(home) {
71
+ if (home === null) return void 0;
72
+ const globalDir = join(home ?? homedir(), ".agentskit");
73
+ const tsConfig = await loadTsConfig(join(globalDir, "config.ts"));
74
+ if (tsConfig) return tsConfig;
75
+ return await loadJsonConfig(join(globalDir, "config.json"));
76
+ }
77
+ async function loadConfig(options) {
78
+ const cwd = resolve(options?.cwd ?? process.cwd());
79
+ const global = await loadGlobalConfig(options?.home);
80
+ const local = await loadLocalConfig(cwd);
81
+ return mergeConfigs(global, local);
82
+ }
58
83
  var providers = {
59
84
  openai: {
60
85
  label: "OpenAI",
@@ -172,19 +197,34 @@ var skillRegistry = {
172
197
  critic,
173
198
  summarizer
174
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
+ }
175
216
  function resolveTools(toolNames) {
176
- if (!toolNames) return [];
217
+ if (!toolNames) {
218
+ return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
219
+ }
177
220
  const tools = [];
178
- for (const name of toolNames.split(",").map((s) => s.trim())) {
221
+ for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
179
222
  switch (name) {
180
223
  case "web_search":
181
- tools.push(webSearch());
182
- break;
224
+ case "fetch_url":
183
225
  case "filesystem":
184
- tools.push(...filesystem({ basePath: process.cwd() }));
185
- break;
186
226
  case "shell":
187
- tools.push(shell({ timeout: 3e4 }));
227
+ tools.push(...instantiate(name));
188
228
  break;
189
229
  default:
190
230
  process.stderr.write(`Unknown tool: ${name}
@@ -224,6 +264,244 @@ function resolveMemory(backend, memoryPath) {
224
264
  return fileChatMemory(memoryPath);
225
265
  }
226
266
  }
267
+ var ROOT = join(homedir(), ".agentskit", "sessions");
268
+ var META_SUFFIX = ".meta.json";
269
+ function cwdHash(cwd = process.cwd()) {
270
+ return createHash("sha256").update(cwd).digest("hex").slice(0, 12);
271
+ }
272
+ function dirFor(cwd = process.cwd()) {
273
+ return join(ROOT, cwdHash(cwd));
274
+ }
275
+ function ensureDir(dir) {
276
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
277
+ }
278
+ function generateSessionId() {
279
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
280
+ const suffix = randomBytes(3).toString("hex");
281
+ return `${ts}-${suffix}`;
282
+ }
283
+ function sessionFilePath(id, cwd = process.cwd()) {
284
+ ensureDir(dirFor(cwd));
285
+ return join(dirFor(cwd), `${id}.json`);
286
+ }
287
+ function metaPath(id, cwd = process.cwd()) {
288
+ return join(dirFor(cwd), `${id}${META_SUFFIX}`);
289
+ }
290
+ function readMeta(id, cwd = process.cwd()) {
291
+ const path4 = metaPath(id, cwd);
292
+ if (!existsSync(path4)) return null;
293
+ try {
294
+ return JSON.parse(readFileSync(path4, "utf8"));
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+ function writeSessionMeta(meta, cwd = process.cwd()) {
300
+ ensureDir(dirFor(cwd));
301
+ writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
302
+ }
303
+ function derivePreview(messages) {
304
+ const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
305
+ if (!firstUser) return "(empty)";
306
+ const single = firstUser.content.replace(/\s+/g, " ").trim();
307
+ return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
308
+ }
309
+ function listSessions(cwd = process.cwd()) {
310
+ const dir = dirFor(cwd);
311
+ if (!existsSync(dir)) return [];
312
+ const entries = readdirSync(dir);
313
+ const records = [];
314
+ for (const entry of entries) {
315
+ if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
316
+ const id = entry.replace(/\.json$/, "");
317
+ const meta = readMeta(id, cwd);
318
+ const file = join(dir, entry);
319
+ if (meta) {
320
+ records.push({ metadata: meta, file });
321
+ } else {
322
+ const stats = statSync(file);
323
+ records.push({
324
+ metadata: {
325
+ id,
326
+ cwd,
327
+ createdAt: stats.birthtime.toISOString(),
328
+ updatedAt: stats.mtime.toISOString(),
329
+ messageCount: 0,
330
+ preview: "(legacy session)"
331
+ },
332
+ file
333
+ });
334
+ }
335
+ }
336
+ records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
337
+ return records;
338
+ }
339
+ function findLatestSession(cwd = process.cwd()) {
340
+ const all = listSessions(cwd);
341
+ return all[0] ?? null;
342
+ }
343
+ function findSession(id, cwd = process.cwd()) {
344
+ const exact = listSessions(cwd).find((s) => s.metadata.id === id);
345
+ if (exact) return exact;
346
+ const prefix = listSessions(cwd).find((s) => s.metadata.id.startsWith(id));
347
+ return prefix ?? null;
348
+ }
349
+ function resolveSession(input2) {
350
+ const cwd = input2.cwd ?? process.cwd();
351
+ if (input2.explicitPath) {
352
+ return { id: "custom", file: input2.explicitPath, isNew: !existsSync(input2.explicitPath) };
353
+ }
354
+ if (input2.forceNew) {
355
+ const id2 = generateSessionId();
356
+ return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
357
+ }
358
+ if (input2.resumeId) {
359
+ const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
360
+ if (target) {
361
+ return { id: target.metadata.id, file: target.file, isNew: false };
362
+ }
363
+ process.stderr.write(
364
+ `No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
365
+ `
366
+ );
367
+ }
368
+ const latest = findLatestSession(cwd);
369
+ if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
370
+ const id = generateSessionId();
371
+ return { id, file: sessionFilePath(id, cwd), isNew: true };
372
+ }
373
+
374
+ // src/slash-commands.ts
375
+ function parseSlashCommand(input2) {
376
+ if (!input2.startsWith("/")) return null;
377
+ const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
378
+ if (!match) return null;
379
+ return { name: match[1], args: match[2] ?? "" };
380
+ }
381
+ function createSlashRegistry(commands) {
382
+ const map = /* @__PURE__ */ new Map();
383
+ for (const cmd of commands) {
384
+ map.set(cmd.name, cmd);
385
+ for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
386
+ }
387
+ return map;
388
+ }
389
+ var builtinSlashCommands = [
390
+ {
391
+ name: "help",
392
+ aliases: ["?"],
393
+ description: "List available slash commands.",
394
+ run(ctx) {
395
+ const seen = /* @__PURE__ */ new Set();
396
+ const lines = [];
397
+ for (const cmd of ctx.commands) {
398
+ if (seen.has(cmd.name)) continue;
399
+ seen.add(cmd.name);
400
+ const suffix = cmd.usage ? ` (${cmd.usage})` : "";
401
+ lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
402
+ }
403
+ ctx.feedback(`Slash commands:
404
+ ${lines.join("\n")}`, "info");
405
+ }
406
+ },
407
+ {
408
+ name: "model",
409
+ description: "Switch the active model.",
410
+ usage: "/model <name>",
411
+ run(ctx, args) {
412
+ const value = args.trim();
413
+ if (!value) {
414
+ ctx.feedback(
415
+ `Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
416
+ "warn"
417
+ );
418
+ return;
419
+ }
420
+ ctx.setModel(value);
421
+ ctx.feedback(`Model \u2192 ${value}`, "success");
422
+ }
423
+ },
424
+ {
425
+ name: "provider",
426
+ description: "Switch the adapter provider.",
427
+ usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
428
+ run(ctx, args) {
429
+ const value = args.trim();
430
+ if (!value) {
431
+ ctx.feedback(
432
+ `Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
433
+ "warn"
434
+ );
435
+ return;
436
+ }
437
+ ctx.setProvider(value);
438
+ ctx.feedback(`Provider \u2192 ${value}`, "success");
439
+ }
440
+ },
441
+ {
442
+ name: "base-url",
443
+ aliases: ["baseurl"],
444
+ description: "Override provider base URL.",
445
+ usage: "/base-url <url|clear>",
446
+ run(ctx, args) {
447
+ const value = args.trim();
448
+ if (!value || value === "clear") {
449
+ ctx.setBaseUrl(void 0);
450
+ ctx.feedback("Base URL cleared.", "success");
451
+ return;
452
+ }
453
+ ctx.setBaseUrl(value);
454
+ ctx.feedback(`Base URL \u2192 ${value}`, "success");
455
+ }
456
+ },
457
+ {
458
+ name: "tools",
459
+ description: "Set active tools (comma-separated) or clear them.",
460
+ usage: "/tools web_search,fetch_url | /tools clear",
461
+ run(ctx, args) {
462
+ const value = args.trim();
463
+ if (!value || value === "clear") {
464
+ ctx.setTools(void 0);
465
+ ctx.feedback("Tools reset to defaults.", "success");
466
+ return;
467
+ }
468
+ ctx.setTools(value);
469
+ ctx.feedback(`Tools \u2192 ${value}`, "success");
470
+ }
471
+ },
472
+ {
473
+ name: "skill",
474
+ description: "Set active skill(s) (comma-separated) or clear them.",
475
+ usage: "/skill researcher,coder | /skill clear",
476
+ run(ctx, args) {
477
+ const value = args.trim();
478
+ if (!value || value === "clear") {
479
+ ctx.setSkill(void 0);
480
+ ctx.feedback("Skills cleared.", "success");
481
+ return;
482
+ }
483
+ ctx.setSkill(value);
484
+ ctx.feedback(`Skills \u2192 ${value}`, "success");
485
+ }
486
+ },
487
+ {
488
+ name: "clear",
489
+ aliases: ["reset"],
490
+ description: "Clear the conversation history in this session.",
491
+ async run(ctx) {
492
+ await ctx.chat.clear();
493
+ ctx.feedback("History cleared.", "success");
494
+ }
495
+ },
496
+ {
497
+ name: "exit",
498
+ aliases: ["quit", "q"],
499
+ description: "Exit the chat.",
500
+ run() {
501
+ process.exit(0);
502
+ }
503
+ }
504
+ ];
227
505
  function groupIntoTurns(messages) {
228
506
  const turns = [];
229
507
  let current = [];
@@ -243,24 +521,28 @@ function groupIntoTurns(messages) {
243
521
  return turns;
244
522
  }
245
523
  function ChatApp(options) {
246
- const runtime = useMemo(() => resolveChatProvider(options), [
247
- options.apiKey,
248
- options.baseUrl,
249
- options.model,
250
- options.provider
251
- ]);
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
+ );
252
534
  const memory = useMemo(
253
535
  () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
254
536
  [options.memoryPath, options.memoryBackend]
255
537
  );
256
- const tools = useMemo(() => resolveTools(options.tools), [options.tools]);
538
+ const tools = useMemo(() => resolveTools(toolsFlag), [toolsFlag]);
257
539
  const skills = useMemo(() => {
258
- if (!options.skill) return void 0;
259
- const names = options.skill.split(",").map((s) => s.trim());
540
+ if (!skillFlag) return void 0;
541
+ const names = skillFlag.split(",").map((s) => s.trim());
260
542
  const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
261
543
  if (resolved.length === 0) return void 0;
262
544
  return resolved;
263
- }, [options.skill]);
545
+ }, [skillFlag]);
264
546
  const chat = useChat({
265
547
  adapter: runtime.adapter,
266
548
  memory,
@@ -268,8 +550,109 @@ function ChatApp(options) {
268
550
  tools: tools.length > 0 ? tools : void 0,
269
551
  skills
270
552
  });
553
+ const [sessionAllowed, setSessionAllowed] = useState(/* @__PURE__ */ new Set());
554
+ const autoApprovedRef = useRef(/* @__PURE__ */ new Set());
555
+ useEffect(() => {
556
+ if (sessionAllowed.size === 0) return;
557
+ for (const message of chat.messages) {
558
+ for (const call of message.toolCalls ?? []) {
559
+ if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
560
+ autoApprovedRef.current.add(call.id);
561
+ void chat.approve(call.id);
562
+ }
563
+ }
564
+ }
565
+ }, [chat.messages, sessionAllowed, chat.approve]);
566
+ const handleApproveAlways = (toolCallId, toolName) => {
567
+ setSessionAllowed((prev) => {
568
+ if (prev.has(toolName)) return prev;
569
+ const next = new Set(prev);
570
+ next.add(toolName);
571
+ return next;
572
+ });
573
+ autoApprovedRef.current.add(toolCallId);
574
+ void chat.approve(toolCallId);
575
+ };
576
+ const sessionCreatedAtRef = useRef(void 0);
577
+ const messageCount = chat.messages.length;
578
+ const firstUserContent = chat.messages.find((m) => m.role === "user")?.content ?? "";
579
+ useEffect(() => {
580
+ const sessionId = options.sessionId;
581
+ if (!sessionId || sessionId === "custom") return;
582
+ if (!sessionCreatedAtRef.current) {
583
+ sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
584
+ }
585
+ try {
586
+ writeSessionMeta({
587
+ id: sessionId,
588
+ cwd: process.cwd(),
589
+ createdAt: sessionCreatedAtRef.current,
590
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
591
+ messageCount,
592
+ preview: derivePreview(chat.messages),
593
+ provider: runtime.provider,
594
+ model: runtime.model
595
+ });
596
+ } catch {
597
+ }
598
+ }, [options.sessionId, messageCount, firstUserContent, runtime.provider, runtime.model]);
271
599
  const turns = useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
272
- const toolNames = options.tools ? options.tools.split(",").map((s) => s.trim()).filter(Boolean) : [];
600
+ const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
601
+ const [feedback, setFeedback] = useState(null);
602
+ const slashCommands = useMemo(
603
+ () => [...builtinSlashCommands, ...options.slashCommands ?? []],
604
+ [options.slashCommands]
605
+ );
606
+ const slashRegistry = useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
607
+ const handleSubmitInput = async (raw) => {
608
+ const parsed = parseSlashCommand(raw);
609
+ if (!parsed) {
610
+ setFeedback(null);
611
+ return false;
612
+ }
613
+ const cmd = slashRegistry.get(parsed.name);
614
+ if (!cmd) {
615
+ setFeedback({
616
+ message: `Unknown command: /${parsed.name}. Type /help for the list.`,
617
+ kind: "error"
618
+ });
619
+ return true;
620
+ }
621
+ const ctx = {
622
+ chat,
623
+ runtime: {
624
+ provider: runtime.provider,
625
+ model: runtime.model,
626
+ mode: runtime.mode,
627
+ baseUrl,
628
+ tools: toolsFlag,
629
+ skill: skillFlag
630
+ },
631
+ setProvider,
632
+ setModel,
633
+ setApiKey,
634
+ setBaseUrl,
635
+ setTools: setToolsFlag,
636
+ setSkill: setSkillFlag,
637
+ feedback: (message, kind = "info") => setFeedback({ message, kind }),
638
+ commands: slashCommands
639
+ };
640
+ try {
641
+ await cmd.run(ctx, parsed.args);
642
+ } catch (err) {
643
+ const message = err instanceof Error ? err.message : String(err);
644
+ setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
645
+ }
646
+ return true;
647
+ };
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
+ );
273
656
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
274
657
  /* @__PURE__ */ jsx(
275
658
  StatusHeader,
@@ -278,7 +661,8 @@ function ChatApp(options) {
278
661
  model: runtime.model,
279
662
  mode: runtime.mode,
280
663
  tools: toolNames,
281
- messageCount: chat.messages.length
664
+ messageCount: chat.messages.length,
665
+ sessionId: options.sessionId
282
666
  }
283
667
  ),
284
668
  /* @__PURE__ */ jsx(ChatContainer, { children: turns.map((turn, turnIdx) => {
@@ -295,7 +679,18 @@ function ChatApp(options) {
295
679
  assistantSteps
296
680
  ] }) : null,
297
681
  /* @__PURE__ */ jsx(Message, { message }),
298
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsx(ToolCallView, { toolCall, expanded: true }, toolCall.id))
682
+ message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
683
+ /* @__PURE__ */ jsx(ToolCallView, { toolCall, expanded: true }),
684
+ toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsx(
685
+ ToolConfirmation,
686
+ {
687
+ toolCall,
688
+ onApprove: chat.approve,
689
+ onDeny: chat.deny,
690
+ onApproveAlways: handleApproveAlways
691
+ }
692
+ ) : null
693
+ ] }, toolCall.id))
299
694
  ] }, message.id);
300
695
  }) }, `turn-${turnIdx}`);
301
696
  }) }),
@@ -306,9 +701,38 @@ function ChatApp(options) {
306
701
  label: toolNames.length > 0 ? "agent working" : "thinking"
307
702
  }
308
703
  ),
309
- /* @__PURE__ */ jsx(InputBar, { chat, placeholder: "Type a message and press Enter\u2026" })
704
+ chat.error ? /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
705
+ /* @__PURE__ */ jsxs(Text, { color: "red", bold: true, children: [
706
+ "\u2717 ",
707
+ chat.error.name || "Error"
708
+ ] }),
709
+ /* @__PURE__ */ jsx(Text, { color: "red", children: chat.error.message })
710
+ ] }) : null,
711
+ feedback ? /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: feedbackBorder(feedback.kind), paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: feedbackBorder(feedback.kind), children: feedback.message }) }) : null,
712
+ /* @__PURE__ */ jsx(
713
+ InputBar,
714
+ {
715
+ chat,
716
+ placeholder: "Type a message or /help for commands",
717
+ disabled: awaitingConfirmation,
718
+ onSubmitInput: handleSubmitInput
719
+ }
720
+ )
310
721
  ] });
311
722
  }
723
+ function feedbackBorder(kind) {
724
+ switch (kind) {
725
+ case "error":
726
+ return "red";
727
+ case "warn":
728
+ return "yellow";
729
+ case "success":
730
+ return "green";
731
+ case "info":
732
+ default:
733
+ return "cyan";
734
+ }
735
+ }
312
736
  function renderChatHeader(options) {
313
737
  const runtime = resolveChatProvider(options);
314
738
  const parts = [`provider=${runtime.provider}`];
@@ -417,7 +841,7 @@ function reactStarter(ctx) {
417
841
  return {
418
842
  "package.json": JSON.stringify(
419
843
  {
420
- name: path.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
844
+ name: path3.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
421
845
  private: true,
422
846
  type: "module",
423
847
  scripts: {
@@ -786,8 +1210,8 @@ async function writeStarterProject(options) {
786
1210
  await mkdir(options.targetDir, { recursive: true });
787
1211
  await Promise.all(
788
1212
  Object.entries(files).map(async ([relativePath, content]) => {
789
- const absolutePath = path.join(options.targetDir, relativePath);
790
- await mkdir(path.dirname(absolutePath), { recursive: true });
1213
+ const absolutePath = path3.join(options.targetDir, relativePath);
1214
+ await mkdir(path3.dirname(absolutePath), { recursive: true });
791
1215
  await writeFile(absolutePath, content, "utf8");
792
1216
  })
793
1217
  );
@@ -1295,7 +1719,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1295
1719
  default: defaults.dir ?? "agentskit-app",
1296
1720
  validate: (value) => {
1297
1721
  if (!value.trim()) return "A directory name is required.";
1298
- const abs = path.resolve(process.cwd(), value);
1722
+ const abs = path3.resolve(process.cwd(), value);
1299
1723
  if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
1300
1724
  return true;
1301
1725
  }
@@ -1373,7 +1797,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1373
1797
  return {
1374
1798
  cancelled: false,
1375
1799
  options: {
1376
- targetDir: path.resolve(process.cwd(), targetDir),
1800
+ targetDir: path3.resolve(process.cwd(), targetDir),
1377
1801
  template,
1378
1802
  provider,
1379
1803
  tools,
@@ -1390,7 +1814,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1390
1814
  }
1391
1815
  }
1392
1816
  function printNextSteps(options) {
1393
- const dir = path.relative(process.cwd(), options.targetDir) || ".";
1817
+ const dir = path3.relative(process.cwd(), options.targetDir) || ".";
1394
1818
  const pm = options.packageManager ?? "pnpm";
1395
1819
  const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
1396
1820
  const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
@@ -1513,34 +1937,87 @@ function RunApp({ task, options }) {
1513
1937
  // src/commands.ts
1514
1938
  function mergeWithConfig(options, config) {
1515
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;
1516
1942
  return {
1517
1943
  ...options,
1518
1944
  // Config defaults — only apply if CLI flag wasn't set
1519
- provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
1520
- model: options.model ?? config.defaults?.model
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
1521
1953
  };
1522
1954
  }
1523
1955
  function createCli() {
1524
1956
  const program = new Command();
1525
1957
  program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1526
- 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) => {
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
+ }
1527
1975
  const config = options.config !== false ? await loadConfig() : void 0;
1528
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
+ }
1529
1988
  const chatOptions = {
1530
1989
  apiKey: merged.apiKey ?? options.apiKey,
1531
1990
  baseUrl: merged.baseUrl ?? options.baseUrl,
1532
1991
  provider: merged.provider,
1533
1992
  model: merged.model,
1534
- system: options.system,
1535
- memoryPath: options.memory,
1536
- tools: options.tools,
1537
- skill: options.skill,
1538
- memoryBackend: options.memoryBackend,
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,
1539
1999
  agentsKitConfig: config
1540
2000
  };
1541
2001
  process.stdout.write(`${renderChatHeader(chatOptions)}
1542
2002
  `);
1543
- render(React3.createElement(ChatApp, chatOptions));
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
+ }
1544
2021
  });
1545
2022
  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) => {
1546
2023
  const task = options.task ?? positionalTask;
@@ -1568,7 +2045,7 @@ function createCli() {
1568
2045
  if (isCi) {
1569
2046
  const template = rawOptions.template ?? "react";
1570
2047
  resolved = {
1571
- targetDir: path.resolve(process.cwd(), rawOptions.dir),
2048
+ targetDir: path3.resolve(process.cwd(), rawOptions.dir),
1572
2049
  template,
1573
2050
  provider: rawOptions.provider ?? "demo",
1574
2051
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
@@ -1588,7 +2065,7 @@ function createCli() {
1588
2065
  await writeStarterProject(resolved);
1589
2066
  if (isCi) {
1590
2067
  process.stdout.write(
1591
- `Created ${resolved.template} starter in ${path.relative(process.cwd(), resolved.targetDir) || "."}
2068
+ `Created ${resolved.template} starter in ${path3.relative(process.cwd(), resolved.targetDir) || "."}
1592
2069
  `
1593
2070
  );
1594
2071
  } else {
@@ -1629,6 +2106,43 @@ function createCli() {
1629
2106
  process.exit(1);
1630
2107
  }
1631
2108
  });
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) => {
2110
+ const isLocal = Boolean(options.local);
2111
+ const targetPath = isLocal ? path3.join(process.cwd(), ".agentskit.config.json") : path3.join(homedir(), ".agentskit", "config.json");
2112
+ if (action === "show") {
2113
+ const config = await loadConfig();
2114
+ process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
2115
+ return;
2116
+ }
2117
+ if (action !== "init") {
2118
+ process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
2119
+ `);
2120
+ process.exit(2);
2121
+ }
2122
+ if (existsSync(targetPath) && !options.force) {
2123
+ process.stderr.write(`Config already exists at ${targetPath}. Re-run with --force to overwrite.
2124
+ `);
2125
+ process.exit(1);
2126
+ }
2127
+ const template = {
2128
+ defaults: {
2129
+ provider: "openai",
2130
+ baseUrl: "https://openrouter.ai/api",
2131
+ apiKeyEnv: "OPENROUTER_API_KEY",
2132
+ model: "openai/gpt-oss-120b:free",
2133
+ tools: "web_search,fetch_url"
2134
+ }
2135
+ };
2136
+ mkdirSync(path3.dirname(targetPath), { recursive: true });
2137
+ writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
2138
+ process.stdout.write(
2139
+ `Wrote ${targetPath}
2140
+ Edit it to taste, then run:
2141
+ agentskit chat
2142
+ (flags on the CLI still win over config values.)
2143
+ `
2144
+ );
2145
+ });
1632
2146
  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) => {
1633
2147
  const portNum = Number(port);
1634
2148
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -1653,5 +2167,5 @@ function createCli() {
1653
2167
  }
1654
2168
 
1655
2169
  export { ChatApp, createCli, loadConfig, renderChatHeader, renderReport, resolveChatProvider, runAgent, runDoctor, startDev, startTunnel, writeStarterProject };
1656
- //# sourceMappingURL=chunk-CCPJYGHP.js.map
1657
- //# sourceMappingURL=chunk-CCPJYGHP.js.map
2170
+ //# sourceMappingURL=chunk-V7E4HWTG.js.map
2171
+ //# sourceMappingURL=chunk-V7E4HWTG.js.map