@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.
@@ -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, 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,41 +264,475 @@ 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
+ ];
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);
518
+ }
519
+ }
520
+ if (current.length > 0) turns.push(current);
521
+ return turns;
522
+ }
227
523
  function ChatApp(options) {
228
- const adapter = useMemo(
229
- () => resolveChatProvider(options).adapter,
230
- [options.apiKey, options.baseUrl, options.model, options.provider]
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]
231
533
  );
232
534
  const memory = useMemo(
233
535
  () => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
234
536
  [options.memoryPath, options.memoryBackend]
235
537
  );
236
- const tools = useMemo(() => resolveTools(options.tools), [options.tools]);
538
+ const tools = useMemo(() => resolveTools(toolsFlag), [toolsFlag]);
237
539
  const skills = useMemo(() => {
238
- if (!options.skill) return void 0;
239
- const names = options.skill.split(",").map((s) => s.trim());
540
+ if (!skillFlag) return void 0;
541
+ const names = skillFlag.split(",").map((s) => s.trim());
240
542
  const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
241
543
  if (resolved.length === 0) return void 0;
242
544
  return resolved;
243
- }, [options.skill]);
545
+ }, [skillFlag]);
244
546
  const chat = useChat({
245
- adapter,
547
+ adapter: runtime.adapter,
246
548
  memory,
247
549
  systemPrompt: options.system,
248
550
  tools: tools.length > 0 ? tools : void 0,
249
551
  skills
250
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]);
599
+ const turns = useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
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
+ );
251
656
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
252
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "AgentsKit CLI" }),
253
- /* @__PURE__ */ jsx(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." }),
254
- /* @__PURE__ */ jsx(ChatContainer, { children: chat.messages.map((message) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
255
- /* @__PURE__ */ jsx(Message, { message }),
256
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsx(ToolCallView, { toolCall }, toolCall.id))
257
- ] }, message.id)) }),
258
- /* @__PURE__ */ jsx(ThinkingIndicator, { visible: chat.status === "streaming" }),
259
- /* @__PURE__ */ jsx(InputBar, { chat, placeholder: "Type a message and press Enter..." })
657
+ /* @__PURE__ */ jsx(
658
+ StatusHeader,
659
+ {
660
+ provider: runtime.provider,
661
+ model: runtime.model,
662
+ mode: runtime.mode,
663
+ tools: toolNames,
664
+ messageCount: chat.messages.length,
665
+ sessionId: options.sessionId
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsx(ChatContainer, { children: turns.map((turn, turnIdx) => {
669
+ const assistantSteps = turn.filter((m) => m.role === "assistant").length;
670
+ let stepIndex = 0;
671
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
672
+ const showStep = message.role === "assistant" && assistantSteps > 1;
673
+ if (showStep) stepIndex++;
674
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
675
+ showStep ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
676
+ "\u21BB step ",
677
+ stepIndex,
678
+ "/",
679
+ assistantSteps
680
+ ] }) : null,
681
+ /* @__PURE__ */ jsx(Message, { message }),
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))
694
+ ] }, message.id);
695
+ }) }, `turn-${turnIdx}`);
696
+ }) }),
697
+ /* @__PURE__ */ jsx(
698
+ ThinkingIndicator,
699
+ {
700
+ visible: chat.status === "streaming",
701
+ label: toolNames.length > 0 ? "agent working" : "thinking"
702
+ }
703
+ ),
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
+ )
260
721
  ] });
261
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
+ }
262
736
  function renderChatHeader(options) {
263
737
  const runtime = resolveChatProvider(options);
264
738
  const parts = [`provider=${runtime.provider}`];
@@ -367,7 +841,7 @@ function reactStarter(ctx) {
367
841
  return {
368
842
  "package.json": JSON.stringify(
369
843
  {
370
- name: path.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
844
+ name: path3.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
371
845
  private: true,
372
846
  type: "module",
373
847
  scripts: {
@@ -736,8 +1210,8 @@ async function writeStarterProject(options) {
736
1210
  await mkdir(options.targetDir, { recursive: true });
737
1211
  await Promise.all(
738
1212
  Object.entries(files).map(async ([relativePath, content]) => {
739
- const absolutePath = path.join(options.targetDir, relativePath);
740
- await mkdir(path.dirname(absolutePath), { recursive: true });
1213
+ const absolutePath = path3.join(options.targetDir, relativePath);
1214
+ await mkdir(path3.dirname(absolutePath), { recursive: true });
741
1215
  await writeFile(absolutePath, content, "utf8");
742
1216
  })
743
1217
  );
@@ -1245,7 +1719,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1245
1719
  default: defaults.dir ?? "agentskit-app",
1246
1720
  validate: (value) => {
1247
1721
  if (!value.trim()) return "A directory name is required.";
1248
- const abs = path.resolve(process.cwd(), value);
1722
+ const abs = path3.resolve(process.cwd(), value);
1249
1723
  if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
1250
1724
  return true;
1251
1725
  }
@@ -1323,7 +1797,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1323
1797
  return {
1324
1798
  cancelled: false,
1325
1799
  options: {
1326
- targetDir: path.resolve(process.cwd(), targetDir),
1800
+ targetDir: path3.resolve(process.cwd(), targetDir),
1327
1801
  template,
1328
1802
  provider,
1329
1803
  tools,
@@ -1340,7 +1814,7 @@ ${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
1340
1814
  }
1341
1815
  }
1342
1816
  function printNextSteps(options) {
1343
- const dir = path.relative(process.cwd(), options.targetDir) || ".";
1817
+ const dir = path3.relative(process.cwd(), options.targetDir) || ".";
1344
1818
  const pm = options.packageManager ?? "pnpm";
1345
1819
  const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
1346
1820
  const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
@@ -1463,34 +1937,87 @@ function RunApp({ task, options }) {
1463
1937
  // src/commands.ts
1464
1938
  function mergeWithConfig(options, config) {
1465
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;
1466
1942
  return {
1467
1943
  ...options,
1468
1944
  // Config defaults — only apply if CLI flag wasn't set
1469
- provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
1470
- 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
1471
1953
  };
1472
1954
  }
1473
1955
  function createCli() {
1474
1956
  const program = new Command();
1475
1957
  program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
1476
- 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
+ }
1477
1975
  const config = options.config !== false ? await loadConfig() : void 0;
1478
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
+ }
1479
1988
  const chatOptions = {
1480
1989
  apiKey: merged.apiKey ?? options.apiKey,
1481
1990
  baseUrl: merged.baseUrl ?? options.baseUrl,
1482
1991
  provider: merged.provider,
1483
1992
  model: merged.model,
1484
- system: options.system,
1485
- memoryPath: options.memory,
1486
- tools: options.tools,
1487
- skill: options.skill,
1488
- 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,
1489
1999
  agentsKitConfig: config
1490
2000
  };
1491
2001
  process.stdout.write(`${renderChatHeader(chatOptions)}
1492
2002
  `);
1493
- 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
+ }
1494
2021
  });
1495
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) => {
1496
2023
  const task = options.task ?? positionalTask;
@@ -1518,7 +2045,7 @@ function createCli() {
1518
2045
  if (isCi) {
1519
2046
  const template = rawOptions.template ?? "react";
1520
2047
  resolved = {
1521
- targetDir: path.resolve(process.cwd(), rawOptions.dir),
2048
+ targetDir: path3.resolve(process.cwd(), rawOptions.dir),
1522
2049
  template,
1523
2050
  provider: rawOptions.provider ?? "demo",
1524
2051
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
@@ -1538,7 +2065,7 @@ function createCli() {
1538
2065
  await writeStarterProject(resolved);
1539
2066
  if (isCi) {
1540
2067
  process.stdout.write(
1541
- `Created ${resolved.template} starter in ${path.relative(process.cwd(), resolved.targetDir) || "."}
2068
+ `Created ${resolved.template} starter in ${path3.relative(process.cwd(), resolved.targetDir) || "."}
1542
2069
  `
1543
2070
  );
1544
2071
  } else {
@@ -1579,6 +2106,43 @@ function createCli() {
1579
2106
  process.exit(1);
1580
2107
  }
1581
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
+ });
1582
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) => {
1583
2147
  const portNum = Number(port);
1584
2148
  if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
@@ -1603,5 +2167,5 @@ function createCli() {
1603
2167
  }
1604
2168
 
1605
2169
  export { ChatApp, createCli, loadConfig, renderChatHeader, renderReport, resolveChatProvider, runAgent, runDoctor, startDev, startTunnel, writeStarterProject };
1606
- //# sourceMappingURL=chunk-KXI7545I.js.map
1607
- //# sourceMappingURL=chunk-KXI7545I.js.map
2170
+ //# sourceMappingURL=chunk-V7E4HWTG.js.map
2171
+ //# sourceMappingURL=chunk-V7E4HWTG.js.map