@llmist/cli 16.0.4 → 16.2.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/cli.js CHANGED
@@ -110,7 +110,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
110
110
  // package.json
111
111
  var package_default = {
112
112
  name: "@llmist/cli",
113
- version: "16.0.4",
113
+ version: "16.2.0",
114
114
  description: "CLI for llmist - run LLM agents from the command line",
115
115
  type: "module",
116
116
  main: "dist/cli.js",
@@ -167,7 +167,7 @@ var package_default = {
167
167
  node: ">=22.0.0"
168
168
  },
169
169
  dependencies: {
170
- llmist: "^16.0.4",
170
+ llmist: "^16.2.0",
171
171
  "@unblessed/node": "^1.0.0-alpha.23",
172
172
  "diff-match-patch": "^1.0.5",
173
173
  chalk: "^5.6.2",
@@ -182,7 +182,7 @@ var package_default = {
182
182
  zod: "^4.1.12"
183
183
  },
184
184
  devDependencies: {
185
- "@llmist/testing": "^16.0.4",
185
+ "@llmist/testing": "^16.2.0",
186
186
  "@types/diff": "^8.0.0",
187
187
  "@types/diff-match-patch": "^1.0.36",
188
188
  "@types/js-yaml": "^4.0.9",
@@ -211,15 +211,15 @@ var CONVERSION_TIMEOUT_MS = 3e4;
211
211
  var ffmpegCheckPromise = null;
212
212
  async function isFFmpegAvailable() {
213
213
  if (ffmpegCheckPromise !== null) return ffmpegCheckPromise;
214
- ffmpegCheckPromise = new Promise((resolve3) => {
214
+ ffmpegCheckPromise = new Promise((resolve4) => {
215
215
  const proc = spawn("ffmpeg", ["-version"], { stdio: "ignore" });
216
- proc.on("error", () => resolve3(false));
217
- proc.on("close", (code) => resolve3(code === 0));
216
+ proc.on("error", () => resolve4(false));
217
+ proc.on("close", (code) => resolve4(code === 0));
218
218
  });
219
219
  return ffmpegCheckPromise;
220
220
  }
221
221
  async function convertToMp3(input, inputFormat, timeout = CONVERSION_TIMEOUT_MS) {
222
- return new Promise((resolve3) => {
222
+ return new Promise((resolve4) => {
223
223
  let timeoutId;
224
224
  const inputArgs = inputFormat === "pcm16" ? ["-f", "s16le", "-ar", "24000", "-ac", "1"] : ["-f", inputFormat];
225
225
  const proc = spawn(
@@ -244,15 +244,15 @@ async function convertToMp3(input, inputFormat, timeout = CONVERSION_TIMEOUT_MS)
244
244
  proc.stdout.on("data", (chunk) => chunks.push(chunk));
245
245
  proc.on("error", () => {
246
246
  clearTimeout(timeoutId);
247
- resolve3(null);
247
+ resolve4(null);
248
248
  });
249
249
  proc.on("close", (code) => {
250
250
  clearTimeout(timeoutId);
251
- resolve3(code === 0 ? Buffer.concat(chunks) : null);
251
+ resolve4(code === 0 ? Buffer.concat(chunks) : null);
252
252
  });
253
253
  timeoutId = setTimeout(() => {
254
254
  proc.kill();
255
- resolve3(null);
255
+ resolve4(null);
256
256
  }, timeout);
257
257
  proc.stdin.on("error", () => {
258
258
  });
@@ -453,7 +453,6 @@ var finish = createGadget2({
453
453
  throw new TaskCompletionSignal("Task completed");
454
454
  }
455
455
  });
456
- var builtinGadgets = [askUser, tellUser, finish, createTextToSpeech()];
457
456
  function getBuiltinGadgets(speechConfig) {
458
457
  return [askUser, tellUser, finish, createTextToSpeech(speechConfig)];
459
458
  }
@@ -507,8 +506,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
507
506
  // Add to inherited gadgets
508
507
  "gadget-remove",
509
508
  // Remove from inherited gadgets
510
- "gadget",
511
- // DEPRECATED: alias for gadgets
512
509
  "builtins",
513
510
  "builtin-interaction",
514
511
  "gadget-start-prefix",
@@ -559,18 +556,18 @@ var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["none", "low", "medium",
559
556
 
560
557
  // src/paths.ts
561
558
  import { homedir } from "os";
562
- function expandTildePath(path6) {
563
- if (!path6.startsWith("~")) {
564
- return path6;
559
+ function expandTildePath(path7) {
560
+ if (!path7.startsWith("~")) {
561
+ return path7;
565
562
  }
566
- return path6.replace(/^~/, homedir());
563
+ return path7.replace(/^~/, homedir());
567
564
  }
568
565
 
569
566
  // src/config-validators.ts
570
567
  var ConfigError = class extends Error {
571
- constructor(message, path6) {
572
- super(path6 ? `${path6}: ${message}` : message);
573
- this.path = path6;
568
+ constructor(message, path7) {
569
+ super(path7 ? `${path7}: ${message}` : message);
570
+ this.path = path7;
574
571
  this.name = "ConfigError";
575
572
  }
576
573
  };
@@ -935,9 +932,6 @@ function validateAgentFields(rawObj, section, result) {
935
932
  section
936
933
  );
937
934
  }
938
- if ("gadget" in rawObj) {
939
- result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
940
- }
941
935
  if ("builtins" in rawObj) {
942
936
  result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
943
937
  }
@@ -1189,13 +1183,13 @@ function createTemplateEngine(prompts, configPath) {
1189
1183
  functionHeader: "const includeFile = (path) => it.__includeFile(path);"
1190
1184
  });
1191
1185
  const includeStack = [];
1192
- eta.__includeFileImpl = (path6) => {
1193
- if (includeStack.includes(path6)) {
1194
- throw new Error(`Circular include detected: ${[...includeStack, path6].join(" -> ")}`);
1186
+ eta.__includeFileImpl = (path7) => {
1187
+ if (includeStack.includes(path7)) {
1188
+ throw new Error(`Circular include detected: ${[...includeStack, path7].join(" -> ")}`);
1195
1189
  }
1196
- includeStack.push(path6);
1190
+ includeStack.push(path7);
1197
1191
  try {
1198
- const content = loadFileContents(path6, configDir);
1192
+ const content = loadFileContents(path7, configDir);
1199
1193
  if (hasTemplateSyntax(content)) {
1200
1194
  const context = {
1201
1195
  env: process.env,
@@ -1278,15 +1272,9 @@ function hasTemplateSyntax(str) {
1278
1272
  // src/config-resolution.ts
1279
1273
  function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
1280
1274
  const hasGadgets = "gadgets" in section;
1281
- const hasGadgetLegacy = "gadget" in section;
1282
1275
  const hasGadgetAdd = "gadget-add" in section;
1283
1276
  const hasGadgetRemove = "gadget-remove" in section;
1284
- if (hasGadgetLegacy && !hasGadgets) {
1285
- console.warn(
1286
- `[config] Warning: [${sectionName}].gadget is deprecated, use 'gadgets' (plural) instead`
1287
- );
1288
- }
1289
- if ((hasGadgets || hasGadgetLegacy) && (hasGadgetAdd || hasGadgetRemove)) {
1277
+ if (hasGadgets && (hasGadgetAdd || hasGadgetRemove)) {
1290
1278
  throw new ConfigError(
1291
1279
  `[${sectionName}] Cannot use 'gadgets' with 'gadget-add'/'gadget-remove'. Use either full replacement (gadgets) OR modification (gadget-add/gadget-remove).`,
1292
1280
  configPath
@@ -1295,9 +1283,6 @@ function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
1295
1283
  if (hasGadgets) {
1296
1284
  return section.gadgets;
1297
1285
  }
1298
- if (hasGadgetLegacy) {
1299
- return section.gadget;
1300
- }
1301
1286
  let result = [...inheritedGadgets];
1302
1287
  if (hasGadgetRemove) {
1303
1288
  const toRemove = new Set(section["gadget-remove"]);
@@ -1336,7 +1321,6 @@ function resolveInheritance(config, configPath) {
1336
1321
  const {
1337
1322
  inherits: _inherits,
1338
1323
  gadgets: _gadgets,
1339
- gadget: _gadget,
1340
1324
  "gadget-add": _gadgetAdd,
1341
1325
  "gadget-remove": _gadgetRemove,
1342
1326
  ...ownValues
@@ -1346,7 +1330,6 @@ function resolveInheritance(config, configPath) {
1346
1330
  if (resolvedGadgets.length > 0) {
1347
1331
  merged.gadgets = resolvedGadgets;
1348
1332
  }
1349
- delete merged.gadget;
1350
1333
  delete merged["gadget-add"];
1351
1334
  delete merged["gadget-remove"];
1352
1335
  resolving.delete(name);
@@ -1430,6 +1413,48 @@ function resolveTemplatesInConfig(config, configPath) {
1430
1413
  return result;
1431
1414
  }
1432
1415
 
1416
+ // src/skills/config-types.ts
1417
+ var SKILLS_CONFIG_KEYS = /* @__PURE__ */ new Set(["sources", "overrides"]);
1418
+ var SKILL_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["model", "enabled"]);
1419
+ function validateSkillsConfig(value, sectionName) {
1420
+ if (typeof value !== "object" || value === null) {
1421
+ throw new Error(`[${sectionName}] must be a table`);
1422
+ }
1423
+ const raw = value;
1424
+ const result = {};
1425
+ for (const [key, val] of Object.entries(raw)) {
1426
+ if (key === "sources") {
1427
+ if (!Array.isArray(val)) {
1428
+ throw new Error(`[${sectionName}].sources must be an array`);
1429
+ }
1430
+ result.sources = val.map(String);
1431
+ } else if (key === "overrides") {
1432
+ if (typeof val !== "object" || val === null) {
1433
+ throw new Error(`[${sectionName}].overrides must be a table`);
1434
+ }
1435
+ result.overrides = {};
1436
+ for (const [skillName, override] of Object.entries(val)) {
1437
+ if (typeof override !== "object" || override === null) {
1438
+ throw new Error(`[${sectionName}].overrides.${skillName} must be a table`);
1439
+ }
1440
+ const overrideObj = override;
1441
+ const skillOverride = {};
1442
+ for (const [oKey, oVal] of Object.entries(overrideObj)) {
1443
+ if (!SKILL_OVERRIDE_KEYS.has(oKey)) {
1444
+ throw new Error(`[${sectionName}].overrides.${skillName}: unknown key "${oKey}"`);
1445
+ }
1446
+ if (oKey === "model") skillOverride.model = String(oVal);
1447
+ if (oKey === "enabled") skillOverride.enabled = Boolean(oVal);
1448
+ }
1449
+ result.overrides[skillName] = skillOverride;
1450
+ }
1451
+ } else if (!SKILLS_CONFIG_KEYS.has(key)) {
1452
+ throw new Error(`[${sectionName}]: unknown key "${key}"`);
1453
+ }
1454
+ }
1455
+ return result;
1456
+ }
1457
+
1433
1458
  // src/config.ts
1434
1459
  function getConfigPath() {
1435
1460
  return join(homedir2(), ".llmist", "cli.toml");
@@ -1460,6 +1485,8 @@ function validateConfig(raw, configPath) {
1460
1485
  result["rate-limits"] = validateRateLimitsConfig(value, key);
1461
1486
  } else if (key === "retry") {
1462
1487
  result.retry = validateRetryConfig(value, key);
1488
+ } else if (key === "skills") {
1489
+ result.skills = validateSkillsConfig(value, key);
1463
1490
  } else {
1464
1491
  result[key] = validateCustomConfig(value, key);
1465
1492
  }
@@ -1600,18 +1627,99 @@ async function readFileBuffer(filePath, options = {}) {
1600
1627
  }
1601
1628
 
1602
1629
  // src/gadgets.ts
1603
- import fs6 from "fs";
1630
+ import fs7 from "fs";
1604
1631
  import { createRequire } from "module";
1605
- import path5 from "path";
1632
+ import path6 from "path";
1606
1633
  import { pathToFileURL as pathToFileURL2 } from "url";
1607
1634
  import { createJiti } from "jiti";
1608
1635
  import { AbstractGadget } from "llmist";
1609
1636
 
1610
- // src/builtins/filesystem/edit-file.ts
1611
- import { readFileSync as readFileSync3, writeFileSync } from "fs";
1637
+ // src/builtins/filesystem/delete-file.ts
1638
+ import fs2 from "fs";
1612
1639
  import { createGadget as createGadget3 } from "llmist";
1613
1640
  import { z as z3 } from "zod";
1614
1641
 
1642
+ // src/builtins/filesystem/utils.ts
1643
+ import fs from "fs";
1644
+ import path from "path";
1645
+ var PathSandboxException = class extends Error {
1646
+ constructor(inputPath, reason) {
1647
+ super(`Path access denied: ${inputPath}. ${reason}`);
1648
+ this.name = "PathSandboxException";
1649
+ }
1650
+ };
1651
+ function validatePathIsWithinCwd(inputPath) {
1652
+ const cwd = process.cwd();
1653
+ const resolvedPath = path.resolve(cwd, inputPath);
1654
+ let realCwd;
1655
+ try {
1656
+ realCwd = fs.realpathSync(cwd);
1657
+ } catch {
1658
+ realCwd = cwd;
1659
+ }
1660
+ let finalPath;
1661
+ try {
1662
+ finalPath = fs.realpathSync(resolvedPath);
1663
+ } catch (error) {
1664
+ const nodeError = error;
1665
+ if (nodeError.code === "ENOENT") {
1666
+ finalPath = resolvedPath;
1667
+ } else {
1668
+ throw error;
1669
+ }
1670
+ }
1671
+ const cwdWithSep = realCwd + path.sep;
1672
+ if (!finalPath.startsWith(cwdWithSep) && finalPath !== realCwd) {
1673
+ throw new PathSandboxException(inputPath, "Path is outside the current working directory");
1674
+ }
1675
+ return finalPath;
1676
+ }
1677
+
1678
+ // src/builtins/filesystem/delete-file.ts
1679
+ var deleteFile = createGadget3({
1680
+ name: "DeleteFile",
1681
+ description: "Delete a file or directory from the local filesystem. The path must be within the current working directory or its subdirectories.",
1682
+ maxConcurrent: 1,
1683
+ // Sequential execution to prevent race conditions
1684
+ schema: z3.object({
1685
+ filePath: z3.string().describe("Path to the file or directory to delete (relative or absolute)"),
1686
+ recursive: z3.boolean().optional().default(false).describe("If true, perform a recursive deletion (required for directories)")
1687
+ }),
1688
+ examples: [
1689
+ {
1690
+ params: { filePath: "temp.txt", recursive: false },
1691
+ output: "path=temp.txt\n\nDeleted file successfully",
1692
+ comment: "Delete a single file"
1693
+ },
1694
+ {
1695
+ params: { filePath: "tmp-dir", recursive: true },
1696
+ output: "path=tmp-dir\n\nDeleted directory successfully",
1697
+ comment: "Delete a directory and its contents"
1698
+ }
1699
+ ],
1700
+ execute: ({ filePath, recursive }) => {
1701
+ const validatedPath = validatePathIsWithinCwd(filePath);
1702
+ if (!fs2.existsSync(validatedPath)) {
1703
+ return `Error: Path does not exist: ${filePath}`;
1704
+ }
1705
+ const stats = fs2.statSync(validatedPath);
1706
+ const isDirectory = stats.isDirectory();
1707
+ if (isDirectory && !recursive) {
1708
+ return `Error: ${filePath} is a directory. Set recursive=true to delete it.`;
1709
+ }
1710
+ fs2.rmSync(validatedPath, { recursive, force: true });
1711
+ const type = isDirectory ? "directory" : "file";
1712
+ return `path=${filePath}
1713
+
1714
+ Deleted ${type} successfully`;
1715
+ }
1716
+ });
1717
+
1718
+ // src/builtins/filesystem/edit-file.ts
1719
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
1720
+ import { createGadget as createGadget4 } from "llmist";
1721
+ import { z as z4 } from "zod";
1722
+
1615
1723
  // src/builtins/filesystem/editfile/matcher.ts
1616
1724
  import DiffMatchPatch from "diff-match-patch";
1617
1725
  var dmp = new DiffMatchPatch();
@@ -2029,36 +2137,6 @@ function formatMultipleMatches(content, matches, maxMatches = 5) {
2029
2137
  return output.join("\n");
2030
2138
  }
2031
2139
 
2032
- // src/builtins/filesystem/utils.ts
2033
- import fs from "fs";
2034
- import path from "path";
2035
- var PathSandboxException = class extends Error {
2036
- constructor(inputPath, reason) {
2037
- super(`Path access denied: ${inputPath}. ${reason}`);
2038
- this.name = "PathSandboxException";
2039
- }
2040
- };
2041
- function validatePathIsWithinCwd(inputPath) {
2042
- const cwd = process.cwd();
2043
- const resolvedPath = path.resolve(cwd, inputPath);
2044
- let finalPath;
2045
- try {
2046
- finalPath = fs.realpathSync(resolvedPath);
2047
- } catch (error) {
2048
- const nodeError = error;
2049
- if (nodeError.code === "ENOENT") {
2050
- finalPath = resolvedPath;
2051
- } else {
2052
- throw error;
2053
- }
2054
- }
2055
- const cwdWithSep = cwd + path.sep;
2056
- if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
2057
- throw new PathSandboxException(inputPath, "Path is outside the current working directory");
2058
- }
2059
- return finalPath;
2060
- }
2061
-
2062
2140
  // src/builtins/filesystem/edit-file.ts
2063
2141
  function formatFailure(filePath, search, failure, fileContent) {
2064
2142
  const lines = [
@@ -2090,7 +2168,7 @@ function formatFailure(filePath, search, failure, fileContent) {
2090
2168
  lines.push("", "CURRENT FILE CONTENT:", "```", fileContent, "```");
2091
2169
  return lines.join("\n");
2092
2170
  }
2093
- var editFile = createGadget3({
2171
+ var editFile = createGadget4({
2094
2172
  name: "EditFile",
2095
2173
  description: `Edit a file by searching for content and replacing it.
2096
2174
 
@@ -2109,12 +2187,12 @@ Options:
2109
2187
  - expectedCount: Validate exact number of matches before applying`,
2110
2188
  maxConcurrent: 1,
2111
2189
  // Sequential execution to prevent race conditions
2112
- schema: z3.object({
2113
- filePath: z3.string().describe("Path to the file to edit (relative or absolute)"),
2114
- search: z3.string().describe("The content to search for in the file"),
2115
- replace: z3.string().describe("The content to replace it with (empty string to delete)"),
2116
- replaceAll: z3.boolean().optional().default(false).describe("Replace all occurrences instead of just the first match"),
2117
- expectedCount: z3.number().int().positive().optional().describe("Expected number of matches. Edit fails if actual count differs")
2190
+ schema: z4.object({
2191
+ filePath: z4.string().describe("Path to the file to edit (relative or absolute)"),
2192
+ search: z4.string().describe("The content to search for in the file"),
2193
+ replace: z4.string().describe("The content to replace it with (empty string to delete)"),
2194
+ replaceAll: z4.boolean().optional().default(false).describe("Replace all occurrences instead of just the first match"),
2195
+ expectedCount: z4.number().int().positive().optional().describe("Expected number of matches. Edit fails if actual count differs")
2118
2196
  }),
2119
2197
  examples: [
2120
2198
  {
@@ -2266,19 +2344,19 @@ function executeReplaceAll(content, matches, replace) {
2266
2344
  }
2267
2345
 
2268
2346
  // src/builtins/filesystem/list-directory.ts
2269
- import fs2 from "fs";
2347
+ import fs3 from "fs";
2270
2348
  import path2 from "path";
2271
- import { createGadget as createGadget4 } from "llmist";
2272
- import { z as z4 } from "zod";
2349
+ import { createGadget as createGadget5 } from "llmist";
2350
+ import { z as z5 } from "zod";
2273
2351
  function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
2274
2352
  const entries = [];
2275
2353
  try {
2276
- const items = fs2.readdirSync(dirPath);
2354
+ const items = fs3.readdirSync(dirPath);
2277
2355
  for (const item of items) {
2278
2356
  const fullPath = path2.join(dirPath, item);
2279
2357
  const relativePath = path2.relative(basePath, fullPath);
2280
2358
  try {
2281
- const stats = fs2.lstatSync(fullPath);
2359
+ const stats = fs3.lstatSync(fullPath);
2282
2360
  let type;
2283
2361
  let size;
2284
2362
  if (stats.isSymbolicLink()) {
@@ -2353,12 +2431,12 @@ function formatEntriesAsString(entries) {
2353
2431
  );
2354
2432
  return [header, ...rows].join("\n");
2355
2433
  }
2356
- var listDirectory = createGadget4({
2434
+ var listDirectory = createGadget5({
2357
2435
  name: "ListDirectory",
2358
2436
  description: "List files and directories in a directory with full details (names, types, sizes, modification dates). Use maxDepth to explore subdirectories recursively. The directory path must be within the current working directory or its subdirectories.",
2359
- schema: z4.object({
2360
- directoryPath: z4.string().default(".").describe("Path to the directory to list"),
2361
- maxDepth: z4.number().int().min(1).max(10).default(3).describe(
2437
+ schema: z5.object({
2438
+ directoryPath: z5.string().default(".").describe("Path to the directory to list"),
2439
+ maxDepth: z5.number().int().min(1).max(10).default(3).describe(
2362
2440
  "Maximum depth to recurse (1 = immediate children only, 2 = include grandchildren, etc.)"
2363
2441
  )
2364
2442
  }),
@@ -2376,7 +2454,7 @@ var listDirectory = createGadget4({
2376
2454
  ],
2377
2455
  execute: ({ directoryPath, maxDepth }) => {
2378
2456
  const validatedPath = validatePathIsWithinCwd(directoryPath);
2379
- const stats = fs2.statSync(validatedPath);
2457
+ const stats = fs3.statSync(validatedPath);
2380
2458
  if (!stats.isDirectory()) {
2381
2459
  throw new Error(`Path is not a directory: ${directoryPath}`);
2382
2460
  }
@@ -2389,14 +2467,14 @@ ${formattedList}`;
2389
2467
  });
2390
2468
 
2391
2469
  // src/builtins/filesystem/read-file.ts
2392
- import fs3 from "fs";
2393
- import { createGadget as createGadget5 } from "llmist";
2394
- import { z as z5 } from "zod";
2395
- var readFile2 = createGadget5({
2470
+ import fs4 from "fs";
2471
+ import { createGadget as createGadget6 } from "llmist";
2472
+ import { z as z6 } from "zod";
2473
+ var readFile2 = createGadget6({
2396
2474
  name: "ReadFile",
2397
2475
  description: "Read the entire content of a file and return it as text. The file path must be within the current working directory or its subdirectories.",
2398
- schema: z5.object({
2399
- filePath: z5.string().describe("Path to the file to read (relative or absolute)")
2476
+ schema: z6.object({
2477
+ filePath: z6.string().describe("Path to the file to read (relative or absolute)")
2400
2478
  }),
2401
2479
  examples: [
2402
2480
  {
@@ -2412,7 +2490,7 @@ var readFile2 = createGadget5({
2412
2490
  ],
2413
2491
  execute: ({ filePath }) => {
2414
2492
  const validatedPath = validatePathIsWithinCwd(filePath);
2415
- const content = fs3.readFileSync(validatedPath, "utf-8");
2493
+ const content = fs4.readFileSync(validatedPath, "utf-8");
2416
2494
  return `path=${filePath}
2417
2495
 
2418
2496
  ${content}`;
@@ -2420,18 +2498,18 @@ ${content}`;
2420
2498
  });
2421
2499
 
2422
2500
  // src/builtins/filesystem/write-file.ts
2423
- import fs4 from "fs";
2501
+ import fs5 from "fs";
2424
2502
  import path3 from "path";
2425
- import { createGadget as createGadget6 } from "llmist";
2426
- import { z as z6 } from "zod";
2427
- var writeFile = createGadget6({
2503
+ import { createGadget as createGadget7 } from "llmist";
2504
+ import { z as z7 } from "zod";
2505
+ var writeFile = createGadget7({
2428
2506
  name: "WriteFile",
2429
2507
  description: "Write content to a file. Creates parent directories if needed. Overwrites existing files. The file path must be within the current working directory or its subdirectories.",
2430
2508
  maxConcurrent: 1,
2431
2509
  // Sequential execution to prevent race conditions
2432
- schema: z6.object({
2433
- filePath: z6.string().describe("Path to the file to write (relative or absolute)"),
2434
- content: z6.string().describe("Content to write to the file")
2510
+ schema: z7.object({
2511
+ filePath: z7.string().describe("Path to the file to write (relative or absolute)"),
2512
+ content: z7.string().describe("Content to write to the file")
2435
2513
  }),
2436
2514
  examples: [
2437
2515
  {
@@ -2461,12 +2539,12 @@ console.log(\`Server running on http://localhost:\${port}\`);`
2461
2539
  const validatedPath = validatePathIsWithinCwd(filePath);
2462
2540
  const parentDir = path3.dirname(validatedPath);
2463
2541
  let createdDir = false;
2464
- if (!fs4.existsSync(parentDir)) {
2542
+ if (!fs5.existsSync(parentDir)) {
2465
2543
  validatePathIsWithinCwd(parentDir);
2466
- fs4.mkdirSync(parentDir, { recursive: true });
2544
+ fs5.mkdirSync(parentDir, { recursive: true });
2467
2545
  createdDir = true;
2468
2546
  }
2469
- fs4.writeFileSync(validatedPath, content, "utf-8");
2547
+ fs5.writeFileSync(validatedPath, content, "utf-8");
2470
2548
  const bytesWritten = Buffer.byteLength(content, "utf-8");
2471
2549
  const dirNote = createdDir ? ` (created directory: ${path3.dirname(filePath)})` : "";
2472
2550
  return `path=${filePath}
@@ -2475,9 +2553,141 @@ Wrote ${bytesWritten} bytes${dirNote}`;
2475
2553
  }
2476
2554
  });
2477
2555
 
2556
+ // src/builtins/read-image.ts
2557
+ import { readFile as readFile3, stat as stat2 } from "fs/promises";
2558
+ import path4 from "path";
2559
+ import { createGadget as createGadget8, getErrorMessage as getErrorMessage2, resultWithImage } from "llmist";
2560
+ import { z as z8 } from "zod";
2561
+ var MAX_IMAGE_SIZE = 50 * 1024 * 1024;
2562
+ var URL_FETCH_TIMEOUT_MS = 3e4;
2563
+ var SUPPORTED_FORMATS = "JPEG, PNG, GIF, WebP";
2564
+ var USER_AGENT = "llmist-cli";
2565
+ var MIME_TO_EXT = {
2566
+ "image/jpeg": ".jpg",
2567
+ "image/png": ".png",
2568
+ "image/gif": ".gif",
2569
+ "image/webp": ".webp"
2570
+ };
2571
+ function isUrl(source) {
2572
+ return source.startsWith("http://") || source.startsWith("https://");
2573
+ }
2574
+ function fileNameFromUrl(source) {
2575
+ try {
2576
+ const urlPath = new URL(source).pathname;
2577
+ const base = path4.basename(urlPath);
2578
+ if (base && base.includes(".")) {
2579
+ return base;
2580
+ }
2581
+ } catch {
2582
+ }
2583
+ return void 0;
2584
+ }
2585
+ async function readImageFromFile(source) {
2586
+ const resolvedPath = path4.resolve(source);
2587
+ let stats;
2588
+ try {
2589
+ stats = await stat2(resolvedPath);
2590
+ } catch (error) {
2591
+ return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
2592
+ }
2593
+ if (stats.size > MAX_IMAGE_SIZE) {
2594
+ return `error: Image file "${source}" is too large (${formatFileSize(stats.size)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2595
+ }
2596
+ let buffer;
2597
+ try {
2598
+ buffer = await readFile3(resolvedPath);
2599
+ } catch (error) {
2600
+ return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
2601
+ }
2602
+ const fileName = path4.basename(resolvedPath);
2603
+ try {
2604
+ return resultWithImage(`source=${source}
2605
+ size=${formatFileSize(buffer.length)}`, buffer, {
2606
+ description: `Image: ${fileName}`,
2607
+ fileName
2608
+ });
2609
+ } catch (error) {
2610
+ if (getErrorMessage2(error).includes("MIME type")) {
2611
+ return `error: File "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
2612
+ }
2613
+ throw error;
2614
+ }
2615
+ }
2616
+ async function fetchImageFromUrl(source) {
2617
+ const response = await fetch(source, {
2618
+ signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),
2619
+ headers: { "User-Agent": USER_AGENT }
2620
+ });
2621
+ if (!response.ok) {
2622
+ return `error: Failed to fetch image: HTTP ${response.status} ${response.statusText}`;
2623
+ }
2624
+ const contentLength = response.headers.get("content-length");
2625
+ if (contentLength && Number.parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
2626
+ return `error: Image at URL is too large (${formatFileSize(Number.parseInt(contentLength, 10))}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2627
+ }
2628
+ const arrayBuffer = await response.arrayBuffer();
2629
+ const buffer = Buffer.from(arrayBuffer);
2630
+ if (buffer.length > MAX_IMAGE_SIZE) {
2631
+ return `error: Downloaded image is too large (${formatFileSize(buffer.length)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2632
+ }
2633
+ try {
2634
+ const result = resultWithImage(
2635
+ `source=${source}
2636
+ size=${formatFileSize(buffer.length)}`,
2637
+ buffer,
2638
+ { description: `Image from ${source}` }
2639
+ );
2640
+ const detectedMime = result.media?.[0]?.mimeType;
2641
+ const fileName = fileNameFromUrl(source) ?? `image${detectedMime && MIME_TO_EXT[detectedMime] || ".bin"}`;
2642
+ if (result.media?.[0]) {
2643
+ result.media[0].fileName = fileName;
2644
+ }
2645
+ return result;
2646
+ } catch (error) {
2647
+ if (getErrorMessage2(error).includes("MIME type")) {
2648
+ return `error: Content at "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
2649
+ }
2650
+ throw error;
2651
+ }
2652
+ }
2653
+ var readImage = createGadget8({
2654
+ name: "ReadImage",
2655
+ description: "Read an image from a local file path or HTTP/HTTPS URL and return it for visual analysis. Supports JPEG, PNG, GIF, and WebP formats.",
2656
+ schema: z8.object({
2657
+ source: z8.string().min(1).describe("Path to a local image file or an HTTP/HTTPS URL")
2658
+ }),
2659
+ examples: [
2660
+ {
2661
+ params: { source: "./screenshot.png" },
2662
+ output: "source=./screenshot.png\nsize=1.2 MB",
2663
+ comment: "Read a local PNG image"
2664
+ },
2665
+ {
2666
+ params: { source: "https://example.com/photo.jpg" },
2667
+ output: "source=https://example.com/photo.jpg\nsize=245.3 KB",
2668
+ comment: "Fetch an image from a URL"
2669
+ },
2670
+ {
2671
+ params: { source: "/home/user/photos/cat.webp" },
2672
+ output: "source=/home/user/photos/cat.webp\nsize=89.4 KB",
2673
+ comment: "Read an image with absolute path (no CWD restriction)"
2674
+ }
2675
+ ],
2676
+ execute: async ({ source }) => {
2677
+ try {
2678
+ if (isUrl(source)) {
2679
+ return await fetchImageFromUrl(source);
2680
+ }
2681
+ return await readImageFromFile(source);
2682
+ } catch (error) {
2683
+ return `error: ${getErrorMessage2(error)}`;
2684
+ }
2685
+ }
2686
+ });
2687
+
2478
2688
  // src/builtins/run-command.ts
2479
- import { createGadget as createGadget7 } from "llmist";
2480
- import { z as z7 } from "zod";
2689
+ import { createGadget as createGadget9 } from "llmist";
2690
+ import { z as z9 } from "zod";
2481
2691
 
2482
2692
  // src/spawn.ts
2483
2693
  import { spawn as nodeSpawn } from "child_process";
@@ -2510,9 +2720,9 @@ function spawn2(argv, options = {}) {
2510
2720
  options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
2511
2721
  ]
2512
2722
  });
2513
- const exited = new Promise((resolve3, reject) => {
2723
+ const exited = new Promise((resolve4, reject) => {
2514
2724
  proc.on("exit", (code) => {
2515
- resolve3(code ?? 1);
2725
+ resolve4(code ?? 1);
2516
2726
  });
2517
2727
  proc.on("error", (err) => {
2518
2728
  reject(err);
@@ -2538,13 +2748,13 @@ function spawn2(argv, options = {}) {
2538
2748
  }
2539
2749
 
2540
2750
  // src/builtins/run-command.ts
2541
- var runCommand = createGadget7({
2751
+ var runCommand = createGadget9({
2542
2752
  name: "RunCommand",
2543
2753
  description: "Execute a command with arguments and return its output. Uses argv array to bypass shell - arguments are passed directly without interpretation. Returns stdout/stderr combined with exit status.",
2544
- schema: z7.object({
2545
- argv: z7.array(z7.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
2546
- cwd: z7.string().optional().describe("Working directory for the command (default: current directory)"),
2547
- timeout: z7.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
2754
+ schema: z9.object({
2755
+ argv: z9.array(z9.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
2756
+ cwd: z9.string().optional().describe("Working directory for the command (default: current directory)"),
2757
+ timeout: z9.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
2548
2758
  }),
2549
2759
  examples: [
2550
2760
  {
@@ -2649,8 +2859,10 @@ error: ${message}`;
2649
2859
  var builtinGadgetRegistry = {
2650
2860
  ListDirectory: listDirectory,
2651
2861
  ReadFile: readFile2,
2862
+ ReadImage: readImage,
2652
2863
  WriteFile: writeFile,
2653
2864
  EditFile: editFile,
2865
+ DeleteFile: deleteFile,
2654
2866
  RunCommand: runCommand,
2655
2867
  TextToSpeech: textToSpeech
2656
2868
  };
@@ -2660,12 +2872,15 @@ function getBuiltinGadget(name) {
2660
2872
  function isBuiltinGadgetName(name) {
2661
2873
  return name in builtinGadgetRegistry;
2662
2874
  }
2875
+ function getBuiltinGadgetNames() {
2876
+ return Object.keys(builtinGadgetRegistry);
2877
+ }
2663
2878
 
2664
2879
  // src/external-gadgets.ts
2665
2880
  import { execSync } from "child_process";
2666
- import fs5 from "fs";
2881
+ import fs6 from "fs";
2667
2882
  import os from "os";
2668
- import path4 from "path";
2883
+ import path5 from "path";
2669
2884
  import { pathToFileURL } from "url";
2670
2885
  function isCommandAvailable(cmd) {
2671
2886
  try {
@@ -2676,7 +2891,7 @@ function isCommandAvailable(cmd) {
2676
2891
  return false;
2677
2892
  }
2678
2893
  }
2679
- var CACHE_DIR = path4.join(os.homedir(), ".llmist", "gadget-cache");
2894
+ var CACHE_DIR = path5.join(os.homedir(), ".llmist", "gadget-cache");
2680
2895
  function isExternalPackageSpecifier(specifier) {
2681
2896
  if (/^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/[A-Z]\w*)?$/i.test(
2682
2897
  specifier
@@ -2766,21 +2981,21 @@ function parseGadgetSpecifier(specifier) {
2766
2981
  function getCacheDir(spec) {
2767
2982
  const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
2768
2983
  if (spec.type === "npm") {
2769
- return path4.join(CACHE_DIR, "npm", `${spec.package}${versionSuffix}`);
2984
+ return path5.join(CACHE_DIR, "npm", `${spec.package}${versionSuffix}`);
2770
2985
  }
2771
2986
  const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
2772
- return path4.join(CACHE_DIR, "git", `${sanitizedUrl}${versionSuffix}`);
2987
+ return path5.join(CACHE_DIR, "git", `${sanitizedUrl}${versionSuffix}`);
2773
2988
  }
2774
2989
  function isCached(cacheDir) {
2775
- const packageJsonPath = path4.join(cacheDir, "package.json");
2776
- if (!fs5.existsSync(packageJsonPath)) {
2990
+ const packageJsonPath = path5.join(cacheDir, "package.json");
2991
+ if (!fs6.existsSync(packageJsonPath)) {
2777
2992
  return false;
2778
2993
  }
2779
2994
  try {
2780
- const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
2995
+ const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
2781
2996
  const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
2782
- const entryPointPath = path4.join(cacheDir, entryPoint);
2783
- if (!fs5.existsSync(entryPointPath)) {
2997
+ const entryPointPath = path5.join(cacheDir, entryPoint);
2998
+ if (!fs6.existsSync(entryPointPath)) {
2784
2999
  return false;
2785
3000
  }
2786
3001
  } catch {
@@ -2789,13 +3004,13 @@ function isCached(cacheDir) {
2789
3004
  return true;
2790
3005
  }
2791
3006
  async function installNpmPackage(spec, cacheDir) {
2792
- fs5.mkdirSync(cacheDir, { recursive: true });
3007
+ fs6.mkdirSync(cacheDir, { recursive: true });
2793
3008
  const packageJson = {
2794
3009
  name: "llmist-gadget-cache",
2795
3010
  private: true,
2796
3011
  type: "module"
2797
3012
  };
2798
- fs5.writeFileSync(path4.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
3013
+ fs6.writeFileSync(path5.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
2799
3014
  const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
2800
3015
  const quietEnv = { ...process.env, DISABLE_OPENCOLLECTIVE: "1", ADBLOCK: "1" };
2801
3016
  if (!isCommandAvailable("npm")) {
@@ -2813,18 +3028,18 @@ Please install Node.js: https://nodejs.org/`
2813
3028
  }
2814
3029
  }
2815
3030
  async function installGitPackage(spec, cacheDir) {
2816
- fs5.mkdirSync(path4.dirname(cacheDir), { recursive: true });
2817
- if (fs5.existsSync(cacheDir)) {
3031
+ fs6.mkdirSync(path5.dirname(cacheDir), { recursive: true });
3032
+ if (fs6.existsSync(cacheDir)) {
2818
3033
  try {
2819
3034
  execSync("git fetch", { cwd: cacheDir, stdio: "pipe" });
2820
3035
  if (spec.version) {
2821
3036
  execSync(`git checkout ${spec.version}`, { cwd: cacheDir, stdio: "pipe" });
2822
3037
  }
2823
3038
  } catch (error) {
2824
- fs5.rmSync(cacheDir, { recursive: true, force: true });
3039
+ fs6.rmSync(cacheDir, { recursive: true, force: true });
2825
3040
  }
2826
3041
  }
2827
- if (!fs5.existsSync(cacheDir)) {
3042
+ if (!fs6.existsSync(cacheDir)) {
2828
3043
  try {
2829
3044
  const cloneCmd = spec.version ? `git clone --branch ${spec.version} "${spec.package}" "${cacheDir}"` : `git clone "${spec.package}" "${cacheDir}"`;
2830
3045
  execSync(cloneCmd, { stdio: "pipe" });
@@ -2832,7 +3047,7 @@ async function installGitPackage(spec, cacheDir) {
2832
3047
  const message = error instanceof Error ? error.message : String(error);
2833
3048
  throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
2834
3049
  }
2835
- if (fs5.existsSync(path4.join(cacheDir, "package.json"))) {
3050
+ if (fs6.existsSync(path5.join(cacheDir, "package.json"))) {
2836
3051
  const hasNpm = isCommandAvailable("npm");
2837
3052
  if (!hasNpm) {
2838
3053
  throw new Error(
@@ -2851,14 +3066,14 @@ Please install Node.js: https://nodejs.org/`
2851
3066
  `Failed to install dependencies for '${spec.package}' using npm: ${message}`
2852
3067
  );
2853
3068
  }
2854
- const packageJson = JSON.parse(fs5.readFileSync(path4.join(cacheDir, "package.json"), "utf-8"));
3069
+ const packageJson = JSON.parse(fs6.readFileSync(path5.join(cacheDir, "package.json"), "utf-8"));
2855
3070
  if (packageJson.scripts?.build) {
2856
3071
  try {
2857
3072
  execSync(`${runCmd} build`, { cwd: cacheDir, stdio: "inherit", env: quietEnv });
2858
3073
  } catch (error) {
2859
3074
  const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
2860
- const entryPointPath = path4.join(cacheDir, entryPoint);
2861
- if (!fs5.existsSync(entryPointPath)) {
3075
+ const entryPointPath = path5.join(cacheDir, entryPoint);
3076
+ if (!fs6.existsSync(entryPointPath)) {
2862
3077
  const message = error instanceof Error ? error.message : String(error);
2863
3078
  throw new Error(`Failed to build package '${spec.package}': ${message}`);
2864
3079
  }
@@ -2868,20 +3083,20 @@ Please install Node.js: https://nodejs.org/`
2868
3083
  }
2869
3084
  }
2870
3085
  function readManifest(packageDir) {
2871
- const packageJsonPath = path4.join(packageDir, "package.json");
2872
- if (!fs5.existsSync(packageJsonPath)) {
3086
+ const packageJsonPath = path5.join(packageDir, "package.json");
3087
+ if (!fs6.existsSync(packageJsonPath)) {
2873
3088
  return null;
2874
3089
  }
2875
3090
  try {
2876
- const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
3091
+ const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
2877
3092
  return packageJson.llmist || null;
2878
3093
  } catch {
2879
3094
  return null;
2880
3095
  }
2881
3096
  }
2882
3097
  function getPackagePath(cacheDir, packageName) {
2883
- const nodeModulesPath = path4.join(cacheDir, "node_modules", packageName);
2884
- if (fs5.existsSync(nodeModulesPath)) {
3098
+ const nodeModulesPath = path5.join(cacheDir, "node_modules", packageName);
3099
+ if (fs6.existsSync(nodeModulesPath)) {
2885
3100
  return nodeModulesPath;
2886
3101
  }
2887
3102
  return cacheDir;
@@ -2927,8 +3142,8 @@ async function loadExternalGadgets(specifier, forceInstall = false) {
2927
3142
  } else {
2928
3143
  entryPoint = manifest?.gadgets || "./dist/index.js";
2929
3144
  }
2930
- const resolvedEntryPoint = path4.resolve(packagePath, entryPoint);
2931
- if (!fs5.existsSync(resolvedEntryPoint)) {
3145
+ const resolvedEntryPoint = path5.resolve(packagePath, entryPoint);
3146
+ if (!fs6.existsSync(resolvedEntryPoint)) {
2932
3147
  throw new Error(
2933
3148
  `Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
2934
3149
  );
@@ -3071,7 +3286,7 @@ function parseLocalSpecifier(specifier) {
3071
3286
  return { path: specifier };
3072
3287
  }
3073
3288
  function isFileLikeSpecifier(specifier) {
3074
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path5.sep);
3289
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path6.sep);
3075
3290
  }
3076
3291
  function tryResolveBuiltin(specifier) {
3077
3292
  if (specifier.startsWith(BUILTIN_PREFIX)) {
@@ -3079,7 +3294,7 @@ function tryResolveBuiltin(specifier) {
3079
3294
  const gadget = getBuiltinGadget(name);
3080
3295
  if (!gadget) {
3081
3296
  throw new Error(
3082
- `Unknown builtin gadget: ${name}. Available builtins: ListDirectory, ReadFile, WriteFile, EditFile, RunCommand`
3297
+ `Unknown builtin gadget: ${name}. Available builtins: ${getBuiltinGadgetNames().join(", ")}`
3083
3298
  );
3084
3299
  }
3085
3300
  return gadget;
@@ -3094,8 +3309,8 @@ function resolveGadgetSpecifier(specifier, cwd) {
3094
3309
  return specifier;
3095
3310
  }
3096
3311
  const expanded = expandTildePath(specifier);
3097
- const resolvedPath = path5.resolve(cwd, expanded);
3098
- if (!fs6.existsSync(resolvedPath)) {
3312
+ const resolvedPath = path6.resolve(cwd, expanded);
3313
+ if (!fs7.existsSync(resolvedPath)) {
3099
3314
  throw new Error(`Gadget module not found at ${resolvedPath}`);
3100
3315
  }
3101
3316
  return pathToFileURL2(resolvedPath).href;
@@ -3262,7 +3477,7 @@ async function resolvePrompt(promptArg, env) {
3262
3477
  }
3263
3478
 
3264
3479
  // src/ui/formatters.ts
3265
- import chalk3 from "chalk";
3480
+ import chalk4 from "chalk";
3266
3481
  import { format } from "llmist";
3267
3482
 
3268
3483
  // src/ui/call-number.ts
@@ -3376,6 +3591,62 @@ function stripProviderPrefix(model) {
3376
3591
  return model.includes(":") ? model.split(":")[1] : model;
3377
3592
  }
3378
3593
 
3594
+ // src/ui/metric-parts.ts
3595
+ import chalk3 from "chalk";
3596
+ function tokenPart(direction, tokens, opts) {
3597
+ const prefix = opts?.estimated ? "~" : "";
3598
+ const formatted = `${prefix}${formatTokens(tokens)}`;
3599
+ switch (direction) {
3600
+ case "input":
3601
+ return chalk3.dim("\u2191") + chalk3.yellow(` ${formatted}`);
3602
+ case "cached":
3603
+ return chalk3.dim("\u27F3") + chalk3.blue(` ${formatted}`);
3604
+ case "output":
3605
+ return chalk3.dim("\u2193") + chalk3.green(` ${formatted}`);
3606
+ case "reasoning":
3607
+ return chalk3.dim("\u{1F4AD}") + chalk3.magenta(` ${formatted}`);
3608
+ case "cacheCreation":
3609
+ return chalk3.dim("\u270E") + chalk3.magenta(` ${formatted}`);
3610
+ }
3611
+ }
3612
+ function costPart(cost) {
3613
+ return chalk3.cyan(`$${formatCost(cost)}`);
3614
+ }
3615
+ function timePart(seconds) {
3616
+ return chalk3.dim(`${seconds.toFixed(1)}s`);
3617
+ }
3618
+ function finishReasonPart(reason) {
3619
+ const upper = reason.toUpperCase();
3620
+ if (upper === "STOP" || upper === "END_TURN") {
3621
+ return chalk3.green(upper);
3622
+ }
3623
+ return chalk3.yellow(upper);
3624
+ }
3625
+ function buildTokenMetrics(opts) {
3626
+ const parts = [];
3627
+ if (opts.input && opts.input > 0) {
3628
+ parts.push(tokenPart("input", opts.input, { estimated: opts.estimated?.input }));
3629
+ }
3630
+ if (opts.cached && opts.cached > 0) {
3631
+ parts.push(tokenPart("cached", opts.cached));
3632
+ }
3633
+ if (opts.output && opts.output > 0) {
3634
+ parts.push(tokenPart("output", opts.output, { estimated: opts.estimated?.output }));
3635
+ }
3636
+ if (opts.reasoning && opts.reasoning > 0) {
3637
+ parts.push(tokenPart("reasoning", opts.reasoning));
3638
+ }
3639
+ if (opts.cacheCreation && opts.cacheCreation > 0) {
3640
+ parts.push(tokenPart("cacheCreation", opts.cacheCreation));
3641
+ }
3642
+ return parts;
3643
+ }
3644
+ function joinParts(parts) {
3645
+ const filtered = parts.filter((p) => p.length > 0);
3646
+ if (filtered.length === 0) return "";
3647
+ return filtered.join(chalk3.dim(" | "));
3648
+ }
3649
+
3379
3650
  // src/ui/formatters.ts
3380
3651
  function formatLLMCallLine(info) {
3381
3652
  const parts = [];
@@ -3384,85 +3655,81 @@ function formatLLMCallLine(info) {
3384
3655
  info.parentCallNumber,
3385
3656
  info.gadgetInvocationId
3386
3657
  );
3387
- parts.push(`${chalk3.cyan(callNumber)} ${chalk3.magenta(info.model)}`);
3658
+ parts.push(`${chalk4.cyan(callNumber)} ${chalk4.magenta(info.model)}`);
3388
3659
  if (info.contextPercent !== void 0 && info.contextPercent !== null) {
3389
3660
  const formatted = `${Math.round(info.contextPercent)}%`;
3390
3661
  if (info.contextPercent >= 80) {
3391
- parts.push(chalk3.red(formatted));
3662
+ parts.push(chalk4.red(formatted));
3392
3663
  } else if (info.contextPercent >= 50) {
3393
- parts.push(chalk3.yellow(formatted));
3664
+ parts.push(chalk4.yellow(formatted));
3394
3665
  } else {
3395
- parts.push(chalk3.green(formatted));
3666
+ parts.push(chalk4.green(formatted));
3396
3667
  }
3397
3668
  }
3398
- if (info.inputTokens && info.inputTokens > 0) {
3399
- const prefix = info.estimated?.input ? "~" : "";
3400
- parts.push(chalk3.dim("\u2191") + chalk3.yellow(` ${prefix}${formatTokens(info.inputTokens)}`));
3401
- }
3402
- if (info.cachedInputTokens && info.cachedInputTokens > 0) {
3403
- parts.push(chalk3.dim("\u27F3") + chalk3.blue(` ${formatTokens(info.cachedInputTokens)}`));
3404
- }
3669
+ parts.push(
3670
+ ...buildTokenMetrics({
3671
+ input: info.inputTokens,
3672
+ cached: info.cachedInputTokens,
3673
+ estimated: info.estimated
3674
+ })
3675
+ );
3405
3676
  if (info.outputTokens !== void 0 && info.outputTokens > 0 || info.isStreaming) {
3406
3677
  const prefix = info.estimated?.output ? "~" : "";
3407
- parts.push(chalk3.dim("\u2193") + chalk3.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
3678
+ parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
3408
3679
  }
3409
- parts.push(chalk3.dim(`${info.elapsedSeconds.toFixed(1)}s`));
3680
+ parts.push(timePart(info.elapsedSeconds));
3410
3681
  if (info.cost !== void 0 && info.cost > 0) {
3411
- parts.push(chalk3.cyan(`$${formatCost(info.cost)}`));
3682
+ parts.push(costPart(info.cost));
3412
3683
  }
3413
3684
  if (!info.isStreaming && info.finishReason !== void 0) {
3414
3685
  const reason = info.finishReason || "stop";
3415
- if (reason === "stop" || reason === "end_turn") {
3416
- parts.push(chalk3.green(reason.toUpperCase()));
3417
- } else {
3418
- parts.push(chalk3.yellow(reason.toUpperCase()));
3419
- }
3686
+ parts.push(finishReasonPart(reason));
3420
3687
  }
3421
- const line = parts.join(chalk3.dim(" | "));
3688
+ const line = joinParts(parts);
3422
3689
  if (info.isStreaming && info.spinner) {
3423
- return `${chalk3.cyan(info.spinner)} ${line}`;
3690
+ return `${chalk4.cyan(info.spinner)} ${line}`;
3424
3691
  }
3425
3692
  if (!info.isStreaming) {
3426
- return `${chalk3.green("\u2713")} ${line}`;
3693
+ return `${chalk4.green("\u2713")} ${line}`;
3427
3694
  }
3428
3695
  return line;
3429
3696
  }
3430
3697
  function renderSummary(metadata) {
3431
3698
  const parts = [];
3432
3699
  if (metadata.iterations !== void 0) {
3433
- const iterPart = chalk3.cyan(`#${metadata.iterations}`);
3700
+ const iterPart = chalk4.cyan(`#${metadata.iterations}`);
3434
3701
  if (metadata.model) {
3435
- parts.push(`${iterPart} ${chalk3.magenta(metadata.model)}`);
3702
+ parts.push(`${iterPart} ${chalk4.magenta(metadata.model)}`);
3436
3703
  } else {
3437
3704
  parts.push(iterPart);
3438
3705
  }
3439
3706
  } else if (metadata.model) {
3440
- parts.push(chalk3.magenta(metadata.model));
3707
+ parts.push(chalk4.magenta(metadata.model));
3441
3708
  }
3442
3709
  if (metadata.usage) {
3443
3710
  const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
3444
- parts.push(chalk3.dim("\u2191") + chalk3.yellow(` ${formatTokens(inputTokens)}`));
3445
- if (cachedInputTokens && cachedInputTokens > 0) {
3446
- parts.push(chalk3.dim("\u27F3") + chalk3.blue(` ${formatTokens(cachedInputTokens)}`));
3447
- }
3448
- if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
3449
- parts.push(chalk3.dim("\u270E") + chalk3.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
3450
- }
3451
- parts.push(chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(outputTokens)}`));
3711
+ parts.push(
3712
+ ...buildTokenMetrics({
3713
+ input: inputTokens,
3714
+ cached: cachedInputTokens,
3715
+ cacheCreation: cacheCreationInputTokens,
3716
+ output: outputTokens
3717
+ })
3718
+ );
3452
3719
  }
3453
3720
  if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
3454
- parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
3721
+ parts.push(timePart(metadata.elapsedSeconds));
3455
3722
  }
3456
3723
  if (metadata.cost !== void 0 && metadata.cost > 0) {
3457
- parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
3724
+ parts.push(costPart(metadata.cost));
3458
3725
  }
3459
3726
  if (metadata.finishReason) {
3460
- parts.push(chalk3.dim(metadata.finishReason));
3727
+ parts.push(chalk4.dim(metadata.finishReason));
3461
3728
  }
3462
3729
  if (parts.length === 0) {
3463
3730
  return null;
3464
3731
  }
3465
- return parts.join(chalk3.dim(" | "));
3732
+ return joinParts(parts);
3466
3733
  }
3467
3734
  function getRawValue(value) {
3468
3735
  if (typeof value === "string") {
@@ -3516,48 +3783,48 @@ function formatParametersInline(params, maxWidth) {
3516
3783
  }
3517
3784
  return entries.map(([key, _], i) => {
3518
3785
  const formatted = truncateValue(rawValues[i], limits[i]);
3519
- return `${chalk3.dim(key)}${chalk3.dim("=")}${chalk3.cyan(formatted)}`;
3520
- }).join(chalk3.dim(", "));
3786
+ return `${chalk4.dim(key)}${chalk4.dim("=")}${chalk4.cyan(formatted)}`;
3787
+ }).join(chalk4.dim(", "));
3521
3788
  }
3522
3789
  function formatGadgetLine(info, maxWidth) {
3523
3790
  const terminalWidth = maxWidth ?? process.stdout.columns ?? 80;
3524
- const gadgetLabel = chalk3.magenta.bold(info.name);
3791
+ const gadgetLabel = chalk4.magenta.bold(info.name);
3525
3792
  const timeStr = `${info.elapsedSeconds.toFixed(1)}s`;
3526
- const timeLabel = chalk3.dim(timeStr);
3793
+ const timeLabel = chalk4.dim(timeStr);
3527
3794
  const fixedLength = 3 + info.name.length + 2 + 1 + timeStr.length;
3528
3795
  const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
3529
3796
  const paramsStr = formatParametersInline(info.parameters, availableForParams);
3530
- const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
3797
+ const paramsLabel = paramsStr ? `${chalk4.dim("(")}${paramsStr}${chalk4.dim(")")}` : "";
3531
3798
  if (info.error) {
3532
3799
  const errorMsg = info.error.length > 50 ? `${info.error.slice(0, 50)}\u2026` : info.error;
3533
- return `${chalk3.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk3.red("error:")} ${errorMsg} ${timeLabel}`;
3800
+ return `${chalk4.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk4.red("error:")} ${errorMsg} ${timeLabel}`;
3534
3801
  }
3535
3802
  if (!info.isComplete) {
3536
3803
  const parts = [];
3537
- if (info.subagentInputTokens && info.subagentInputTokens > 0) {
3538
- parts.push(chalk3.dim("\u2191") + chalk3.yellow(` ${formatTokens(info.subagentInputTokens)}`));
3539
- }
3540
- if (info.subagentOutputTokens && info.subagentOutputTokens > 0) {
3541
- parts.push(chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(info.subagentOutputTokens)}`));
3542
- }
3804
+ parts.push(
3805
+ ...buildTokenMetrics({
3806
+ input: info.subagentInputTokens,
3807
+ output: info.subagentOutputTokens
3808
+ })
3809
+ );
3543
3810
  if (info.subagentCost && info.subagentCost > 0) {
3544
- parts.push(chalk3.cyan(`$${formatCost(info.subagentCost)}`));
3811
+ parts.push(costPart(info.subagentCost));
3545
3812
  }
3546
- parts.push(chalk3.dim(`${info.elapsedSeconds.toFixed(1)}s`));
3547
- const metricsStr = parts.length > 0 ? ` ${parts.join(chalk3.dim(" | "))}` : "";
3548
- return `${chalk3.blue("\u23F5")} ${gadgetLabel}${metricsStr}`;
3813
+ parts.push(timePart(info.elapsedSeconds));
3814
+ const metricsStr = parts.length > 0 ? ` ${joinParts(parts)}` : "";
3815
+ return `${chalk4.blue("\u23F5")} ${gadgetLabel}${metricsStr}`;
3549
3816
  }
3550
3817
  let outputLabel;
3551
3818
  if (info.tokenCount !== void 0 && info.tokenCount > 0) {
3552
- outputLabel = chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(info.tokenCount)} `);
3819
+ outputLabel = chalk4.dim("\u2193") + chalk4.green(` ${formatTokens(info.tokenCount)} `);
3553
3820
  } else if (info.outputBytes !== void 0 && info.outputBytes > 0) {
3554
- outputLabel = `${chalk3.green(format.bytes(info.outputBytes))} `;
3821
+ outputLabel = `${chalk4.green(format.bytes(info.outputBytes))} `;
3555
3822
  } else {
3556
3823
  outputLabel = "";
3557
3824
  }
3558
- const resultIcon = info.breaksLoop ? chalk3.yellow("\u23F9") : chalk3.green("\u2713");
3559
- const nameRef = chalk3.magenta(info.name);
3560
- const line1 = `${chalk3.dim("\u2192")} ${gadgetLabel}${paramsLabel}`;
3825
+ const resultIcon = info.breaksLoop ? chalk4.yellow("\u23F9") : chalk4.green("\u2713");
3826
+ const nameRef = chalk4.magenta(info.name);
3827
+ const line1 = `${chalk4.dim("\u2192")} ${gadgetLabel}${paramsLabel}`;
3561
3828
  const line2Prefix = ` ${resultIcon} ${nameRef} ${outputLabel}`;
3562
3829
  const line2 = `${line2Prefix}${timeLabel}`;
3563
3830
  return `${line1}
@@ -3979,7 +4246,7 @@ var NestedOperationTracker = class {
3979
4246
  };
3980
4247
 
3981
4248
  // src/progress/progress-renderer.ts
3982
- import chalk4 from "chalk";
4249
+ import chalk5 from "chalk";
3983
4250
  import { FALLBACK_CHARS_PER_TOKEN } from "llmist";
3984
4251
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3985
4252
  var SPINNER_DELAY_MS = 500;
@@ -4163,7 +4430,7 @@ var ProgressRenderer = class {
4163
4430
  const elapsedSeconds2 = (Date.now() - op.startTime) / 1e3;
4164
4431
  const nestedMetrics = op.id ? this.nestedOperationTracker.getAggregatedSubagentMetrics(op.id) : { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0, cost: 0, callCount: 0 };
4165
4432
  const termWidth2 = process.stdout.columns ?? 80;
4166
- const parentPrefix = `${chalk4.dim(`${gadget.name}:`)} `;
4433
+ const parentPrefix = `${chalk5.dim(`${gadget.name}:`)} `;
4167
4434
  const line2 = formatGadgetLine(
4168
4435
  {
4169
4436
  name: op.name ?? "",
@@ -4186,7 +4453,7 @@ var ProgressRenderer = class {
4186
4453
  }
4187
4454
  for (const stream of activeNestedStreams) {
4188
4455
  const indent = " ".repeat(stream.depth + 2);
4189
- const parentPrefix = `${chalk4.dim(`${stream.parentGadgetName}:`)} `;
4456
+ const parentPrefix = `${chalk5.dim(`${stream.parentGadgetName}:`)} `;
4190
4457
  const elapsedSeconds = (Date.now() - stream.startTime) / 1e3;
4191
4458
  const line = formatLLMCallLine({
4192
4459
  iteration: stream.iteration,
@@ -4244,21 +4511,19 @@ var ProgressRenderer = class {
4244
4511
  const elapsed = ((Date.now() - this.callStatsTracker.totalStartTime) / 1e3).toFixed(1);
4245
4512
  const parts = [];
4246
4513
  if (this.callStatsTracker.model) {
4247
- parts.push(chalk4.cyan(this.callStatsTracker.model));
4514
+ parts.push(chalk5.cyan(this.callStatsTracker.model));
4248
4515
  }
4249
4516
  if (this.callStatsTracker.totalTokens > 0) {
4250
- parts.push(chalk4.dim("total:") + chalk4.magenta(` ${this.callStatsTracker.totalTokens}`));
4517
+ parts.push(chalk5.dim("total:") + chalk5.magenta(` ${this.callStatsTracker.totalTokens}`));
4251
4518
  }
4252
4519
  if (this.callStatsTracker.iterations > 0) {
4253
- parts.push(chalk4.dim("iter:") + chalk4.blue(` ${this.callStatsTracker.iterations}`));
4520
+ parts.push(chalk5.dim("iter:") + chalk5.blue(` ${this.callStatsTracker.iterations}`));
4254
4521
  }
4255
4522
  if (this.callStatsTracker.totalCost > 0) {
4256
- parts.push(
4257
- chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.callStatsTracker.totalCost)}`)
4258
- );
4523
+ parts.push(`${chalk5.dim("cost:")} ${costPart(this.callStatsTracker.totalCost)}`);
4259
4524
  }
4260
- parts.push(chalk4.dim(`${elapsed}s`));
4261
- return `${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`;
4525
+ parts.push(chalk5.dim(`${elapsed}s`));
4526
+ return `${parts.join(chalk5.dim(" | "))} ${chalk5.cyan(spinner)}`;
4262
4527
  }
4263
4528
  /**
4264
4529
  * Returns a formatted stats string for cancellation messages.
@@ -4269,15 +4534,21 @@ var ProgressRenderer = class {
4269
4534
  const elapsed = ((Date.now() - this.callStatsTracker.callStartTime) / 1e3).toFixed(1);
4270
4535
  const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
4271
4536
  if (this.callStatsTracker.callInputTokens > 0) {
4272
- const prefix = this.callStatsTracker.callInputTokensEstimated ? "~" : "";
4273
- parts.push(`\u2191 ${prefix}${formatTokens(this.callStatsTracker.callInputTokens)}`);
4537
+ parts.push(
4538
+ tokenPart("input", this.callStatsTracker.callInputTokens, {
4539
+ estimated: this.callStatsTracker.callInputTokensEstimated
4540
+ })
4541
+ );
4274
4542
  }
4275
4543
  if (outTokens > 0) {
4276
- const prefix = this.callStatsTracker.callOutputTokensEstimated ? "~" : "";
4277
- parts.push(`\u2193 ${prefix}${formatTokens(outTokens)}`);
4544
+ parts.push(
4545
+ tokenPart("output", outTokens, {
4546
+ estimated: this.callStatsTracker.callOutputTokensEstimated
4547
+ })
4548
+ );
4278
4549
  }
4279
4550
  parts.push(`${elapsed}s`);
4280
- return parts.join(" | ");
4551
+ return joinParts(parts);
4281
4552
  }
4282
4553
  /**
4283
4554
  * Returns a formatted prompt string with stats (like bash PS1).
@@ -4291,30 +4562,30 @@ var ProgressRenderer = class {
4291
4562
  const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
4292
4563
  const outEstimated = this.callStatsTracker.callOutputTokensEstimated;
4293
4564
  if (this.callStatsTracker.callInputTokens > 0) {
4294
- const prefix = this.callStatsTracker.callInputTokensEstimated ? "~" : "";
4295
4565
  parts.push(
4296
- chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callStatsTracker.callInputTokens)}`)
4566
+ tokenPart("input", this.callStatsTracker.callInputTokens, {
4567
+ estimated: this.callStatsTracker.callInputTokensEstimated
4568
+ })
4297
4569
  );
4298
4570
  }
4299
4571
  if (outTokens > 0) {
4300
- const prefix = outEstimated ? "~" : "";
4301
- parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
4572
+ parts.push(tokenPart("output", outTokens, { estimated: outEstimated }));
4302
4573
  }
4303
- parts.push(chalk4.dim(`${elapsed}s`));
4574
+ parts.push(chalk5.dim(`${elapsed}s`));
4304
4575
  } else {
4305
4576
  const elapsed = Math.round((Date.now() - this.callStatsTracker.totalStartTime) / 1e3);
4306
4577
  if (this.callStatsTracker.totalTokens > 0) {
4307
- parts.push(chalk4.magenta(formatTokens(this.callStatsTracker.totalTokens)));
4578
+ parts.push(chalk5.magenta(formatTokens(this.callStatsTracker.totalTokens)));
4308
4579
  }
4309
4580
  if (this.callStatsTracker.iterations > 0) {
4310
- parts.push(chalk4.blue(`i${this.callStatsTracker.iterations}`));
4581
+ parts.push(chalk5.blue(`i${this.callStatsTracker.iterations}`));
4311
4582
  }
4312
4583
  if (this.callStatsTracker.totalCost > 0) {
4313
- parts.push(chalk4.cyan(`$${formatCost(this.callStatsTracker.totalCost)}`));
4584
+ parts.push(costPart(this.callStatsTracker.totalCost));
4314
4585
  }
4315
- parts.push(chalk4.dim(`${elapsed}s`));
4586
+ parts.push(chalk5.dim(`${elapsed}s`));
4316
4587
  }
4317
- return `${parts.join(chalk4.dim(" | "))} ${chalk4.green(">")} `;
4588
+ return `${parts.join(chalk5.dim(" | "))} ${chalk5.green(">")} `;
4318
4589
  }
4319
4590
  };
4320
4591
 
@@ -4556,7 +4827,7 @@ function addAgentOptions(cmd, defaults) {
4556
4827
  ...previous,
4557
4828
  value
4558
4829
  ];
4559
- const defaultGadgets = defaults?.gadgets ?? defaults?.gadget ?? [];
4830
+ const defaultGadgets = defaults?.gadgets ?? [];
4560
4831
  return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(OPTION_FLAGS.systemPromptFile, OPTION_DESCRIPTIONS.systemPromptFile).option(
4561
4832
  OPTION_FLAGS.temperature,
4562
4833
  OPTION_DESCRIPTIONS.temperature,
@@ -4637,14 +4908,14 @@ function configToCompleteOptions(config) {
4637
4908
  if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
4638
4909
  if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
4639
4910
  if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
4640
- if (rl.enabled === false) result.noRateLimit = true;
4911
+ if (rl.enabled === false) result.rateLimit = false;
4641
4912
  }
4642
4913
  if (config.retry) {
4643
4914
  const r = config.retry;
4644
4915
  if (r.retries !== void 0) result.maxRetries = r.retries;
4645
4916
  if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
4646
4917
  if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
4647
- if (r.enabled === false) result.noRetry = true;
4918
+ if (r.enabled === false) result.retry = false;
4648
4919
  }
4649
4920
  if (config.reasoning) {
4650
4921
  result.profileReasoning = config.reasoning;
@@ -4658,8 +4929,7 @@ function configToAgentOptions(config) {
4658
4929
  if (config.temperature !== void 0) result.temperature = config.temperature;
4659
4930
  if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
4660
4931
  if (config.budget !== void 0) result.budget = config.budget;
4661
- const gadgets = config.gadgets ?? config.gadget;
4662
- if (gadgets !== void 0) result.gadget = gadgets;
4932
+ if (config.gadgets !== void 0) result.gadget = config.gadgets;
4663
4933
  if (config.builtins !== void 0) result.builtins = config.builtins;
4664
4934
  if (config["builtin-interaction"] !== void 0)
4665
4935
  result.builtinInteraction = config["builtin-interaction"];
@@ -4680,14 +4950,14 @@ function configToAgentOptions(config) {
4680
4950
  if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
4681
4951
  if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
4682
4952
  if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
4683
- if (rl.enabled === false) result.noRateLimit = true;
4953
+ if (rl.enabled === false) result.rateLimit = false;
4684
4954
  }
4685
4955
  if (config.retry) {
4686
4956
  const r = config.retry;
4687
4957
  if (r.retries !== void 0) result.maxRetries = r.retries;
4688
4958
  if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
4689
4959
  if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
4690
- if (r.enabled === false) result.noRetry = true;
4960
+ if (r.enabled === false) result.retry = false;
4691
4961
  }
4692
4962
  if (config.reasoning) {
4693
4963
  result.profileReasoning = config.reasoning;
@@ -4731,7 +5001,7 @@ function detectProvider(model) {
4731
5001
  }
4732
5002
  }
4733
5003
  function resolveRateLimitConfig(options, globalConfig, profileConfig, model) {
4734
- if (options.noRateLimit === true) {
5004
+ if (options.rateLimit === false) {
4735
5005
  return { enabled: false, safetyMargin: 0.8 };
4736
5006
  }
4737
5007
  let resolved;
@@ -4856,12 +5126,82 @@ function resolveRetryConfig(options, globalConfig, profileConfig) {
4856
5126
  if (options.retryMaxTimeout !== void 0) {
4857
5127
  resolved.maxTimeout = options.retryMaxTimeout;
4858
5128
  }
4859
- if (options.noRetry === true) {
5129
+ if (options.retry === false) {
4860
5130
  resolved.enabled = false;
4861
5131
  }
4862
5132
  return resolved;
4863
5133
  }
4864
5134
 
5135
+ // src/skills/skill-manager.ts
5136
+ import { homedir as homedir3 } from "os";
5137
+ import { join as join2, resolve as resolve3 } from "path";
5138
+ import { discoverSkills, loadSkillsFromDirectory } from "llmist";
5139
+ var CLISkillManager = class {
5140
+ /**
5141
+ * Load all skills from standard locations and configured sources.
5142
+ *
5143
+ * @param config - Skills configuration from cli.toml [skills] section
5144
+ * @param projectDir - Project directory (cwd)
5145
+ */
5146
+ async loadAll(config, projectDir) {
5147
+ const registry = discoverSkills({
5148
+ projectDir: projectDir ?? process.cwd()
5149
+ });
5150
+ if (config?.sources) {
5151
+ for (const source of config.sources) {
5152
+ const resolvedSource = this.resolveSource(source);
5153
+ const skills = loadSkillsFromDirectory(resolvedSource, {
5154
+ type: "directory",
5155
+ path: resolvedSource
5156
+ });
5157
+ registry.registerMany(skills);
5158
+ }
5159
+ }
5160
+ if (config?.overrides) {
5161
+ for (const [skillName, override] of Object.entries(config.overrides)) {
5162
+ if (override.enabled === false) {
5163
+ registry.remove(skillName);
5164
+ }
5165
+ }
5166
+ }
5167
+ return registry;
5168
+ }
5169
+ /**
5170
+ * Resolve a source string to an absolute directory path.
5171
+ */
5172
+ resolveSource(source) {
5173
+ if (source.startsWith("~")) {
5174
+ return join2(homedir3(), source.slice(1));
5175
+ }
5176
+ return resolve3(source);
5177
+ }
5178
+ };
5179
+
5180
+ // src/skills/slash-handler.ts
5181
+ function parseSlashCommand(input, registry) {
5182
+ const trimmed = input.trim();
5183
+ if (!trimmed.startsWith("/")) {
5184
+ return { isSkillInvocation: false };
5185
+ }
5186
+ const match = trimmed.match(/^\/(\S+)(?:\s+(.*))?$/);
5187
+ if (!match) {
5188
+ return { isSkillInvocation: false };
5189
+ }
5190
+ const [, commandName, args] = match;
5191
+ if (commandName === "skills") {
5192
+ return { isSkillInvocation: true, isListCommand: true };
5193
+ }
5194
+ const skill = registry.get(commandName);
5195
+ if (!skill || !skill.isUserInvocable) {
5196
+ return { isSkillInvocation: false };
5197
+ }
5198
+ return {
5199
+ isSkillInvocation: true,
5200
+ skillName: commandName,
5201
+ arguments: args?.trim()
5202
+ };
5203
+ }
5204
+
4865
5205
  // src/subagent-config.ts
4866
5206
  var INHERIT_MODEL = "inherit";
4867
5207
  function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
@@ -4910,11 +5250,34 @@ function extractSubagentConfig(globalConfig, subagentName) {
4910
5250
  return {};
4911
5251
  }
4912
5252
 
4913
- // src/tui/block-renderer.ts
4914
- import { Box } from "@unblessed/node";
4915
-
4916
- // src/ui/block-formatters.ts
4917
- import chalk5 from "chalk";
5253
+ // src/tui/raw-viewer-data.ts
5254
+ function isRawViewerNode(node) {
5255
+ return node.type === "llm_call" || node.type === "gadget";
5256
+ }
5257
+ function createRawViewerData(node, mode) {
5258
+ if (node.type === "llm_call") {
5259
+ return {
5260
+ mode,
5261
+ request: node.rawRequest,
5262
+ response: node.rawResponse,
5263
+ iteration: node.iteration,
5264
+ model: node.model
5265
+ };
5266
+ }
5267
+ return {
5268
+ mode,
5269
+ gadgetName: node.name,
5270
+ parameters: node.parameters,
5271
+ result: node.result,
5272
+ error: node.error
5273
+ };
5274
+ }
5275
+
5276
+ // src/tui/block-renderer.ts
5277
+ import { Box } from "@unblessed/node";
5278
+
5279
+ // src/ui/block-formatters.ts
5280
+ import chalk6 from "chalk";
4918
5281
  var BOX = {
4919
5282
  topLeft: "\u250C",
4920
5283
  topRight: "\u2510",
@@ -4930,44 +5293,35 @@ var COMPLETE_INDICATOR = "\u2713";
4930
5293
  var ERROR_INDICATOR = "\u2717";
4931
5294
  function formatLLMCallCollapsed(node, selected) {
4932
5295
  const indicator = node.isComplete ? COMPLETE_INDICATOR : PROGRESS_INDICATOR;
4933
- const indicatorColor = node.isComplete ? chalk5.green : chalk5.blue;
5296
+ const indicatorColor = node.isComplete ? chalk6.green : chalk6.blue;
4934
5297
  const parts = [];
4935
- const callNumber = chalk5.cyan(`#${node.iteration}`);
4936
- const model = chalk5.magenta(node.model);
5298
+ const callNumber = chalk6.cyan(`#${node.iteration}`);
5299
+ const model = chalk6.magenta(node.model);
4937
5300
  parts.push(`${callNumber} ${model}`);
4938
5301
  if (node.details) {
4939
5302
  const d = node.details;
4940
- if (d.inputTokens && d.inputTokens > 0) {
4941
- parts.push(chalk5.dim("\u2191") + chalk5.yellow(` ${formatTokens(d.inputTokens)}`));
4942
- }
4943
- if (d.cachedInputTokens && d.cachedInputTokens > 0) {
4944
- parts.push(chalk5.dim("\u27F3") + chalk5.blue(` ${formatTokens(d.cachedInputTokens)}`));
4945
- }
4946
- if (d.outputTokens && d.outputTokens > 0) {
4947
- parts.push(chalk5.dim("\u2193") + chalk5.green(` ${formatTokens(d.outputTokens)}`));
4948
- }
4949
- if (d.reasoningTokens && d.reasoningTokens > 0) {
4950
- parts.push(chalk5.dim("\u{1F4AD}") + chalk5.magenta(` ${formatTokens(d.reasoningTokens)}`));
4951
- }
5303
+ parts.push(
5304
+ ...buildTokenMetrics({
5305
+ input: d.inputTokens,
5306
+ cached: d.cachedInputTokens,
5307
+ output: d.outputTokens,
5308
+ reasoning: d.reasoningTokens
5309
+ })
5310
+ );
4952
5311
  if (d.elapsedSeconds !== void 0) {
4953
- parts.push(chalk5.dim(`${d.elapsedSeconds.toFixed(1)}s`));
5312
+ parts.push(timePart(d.elapsedSeconds));
4954
5313
  }
4955
5314
  if (d.cost !== void 0 && d.cost > 0) {
4956
- parts.push(chalk5.cyan(`$${formatCost(d.cost)}`));
5315
+ parts.push(costPart(d.cost));
4957
5316
  }
4958
5317
  if (node.isComplete && d.finishReason) {
4959
- const reason = d.finishReason.toUpperCase();
4960
- if (reason === "STOP" || reason === "END_TURN") {
4961
- parts.push(chalk5.green(reason));
4962
- } else {
4963
- parts.push(chalk5.yellow(reason));
4964
- }
5318
+ parts.push(finishReasonPart(d.finishReason));
4965
5319
  }
4966
5320
  }
4967
- const line = parts.join(chalk5.dim(" | "));
5321
+ const line = joinParts(parts);
4968
5322
  const prefix = indicatorColor(indicator);
4969
5323
  if (selected) {
4970
- return chalk5.bgBlue.white(`${prefix} ${line}`);
5324
+ return chalk6.bgBlue.white(`${prefix} ${line}`);
4971
5325
  }
4972
5326
  return `${prefix} ${line}`;
4973
5327
  }
@@ -4976,58 +5330,58 @@ function formatLLMCallExpanded(node) {
4976
5330
  const indent = " ";
4977
5331
  const d = node.details;
4978
5332
  if (!d) {
4979
- lines.push(`${indent}${chalk5.dim("No details available")}`);
5333
+ lines.push(`${indent}${chalk6.dim("No details available")}`);
4980
5334
  return lines;
4981
5335
  }
4982
5336
  const width = Math.min(60, (process.stdout.columns || 80) - 4);
4983
5337
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Details ${BOX.horizontal.repeat(width - 11)}`;
4984
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
4985
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} Model: ${chalk5.magenta(node.model)}`);
5338
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5339
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} Model: ${chalk6.magenta(node.model)}`);
4986
5340
  if (d.inputTokens !== void 0) {
4987
- let inputLine = `${indent}${chalk5.dim(BOX.vertical)} Input: ${chalk5.yellow(formatTokens(d.inputTokens))} tokens`;
5341
+ let inputLine = `${indent}${chalk6.dim(BOX.vertical)} Input: ${chalk6.yellow(formatTokens(d.inputTokens))} tokens`;
4988
5342
  if (d.cachedInputTokens && d.cachedInputTokens > 0) {
4989
5343
  const cachePercent = (d.cachedInputTokens / d.inputTokens * 100).toFixed(1);
4990
- inputLine += chalk5.blue(` (${formatTokens(d.cachedInputTokens)} cached, ${cachePercent}%)`);
5344
+ inputLine += chalk6.blue(` (${formatTokens(d.cachedInputTokens)} cached, ${cachePercent}%)`);
4991
5345
  }
4992
5346
  lines.push(inputLine);
4993
5347
  }
4994
5348
  if (d.outputTokens !== void 0) {
4995
5349
  lines.push(
4996
- `${indent}${chalk5.dim(BOX.vertical)} Output: ${chalk5.green(formatTokens(d.outputTokens))} tokens`
5350
+ `${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(formatTokens(d.outputTokens))} tokens`
4997
5351
  );
4998
5352
  }
4999
5353
  if (d.reasoningTokens !== void 0 && d.reasoningTokens > 0) {
5000
5354
  lines.push(
5001
- `${indent}${chalk5.dim(BOX.vertical)} Reason: ${chalk5.magenta(formatTokens(d.reasoningTokens))} tokens`
5355
+ `${indent}${chalk6.dim(BOX.vertical)} Reason: ${chalk6.magenta(formatTokens(d.reasoningTokens))} tokens`
5002
5356
  );
5003
5357
  }
5004
5358
  if (d.contextPercent !== void 0) {
5005
- let contextColor = chalk5.green;
5006
- if (d.contextPercent >= 80) contextColor = chalk5.red;
5007
- else if (d.contextPercent >= 50) contextColor = chalk5.yellow;
5359
+ let contextColor = chalk6.green;
5360
+ if (d.contextPercent >= 80) contextColor = chalk6.red;
5361
+ else if (d.contextPercent >= 50) contextColor = chalk6.yellow;
5008
5362
  lines.push(
5009
- `${indent}${chalk5.dim(BOX.vertical)} Context: ${contextColor(`${Math.round(d.contextPercent)}%`)}`
5363
+ `${indent}${chalk6.dim(BOX.vertical)} Context: ${contextColor(`${Math.round(d.contextPercent)}%`)}`
5010
5364
  );
5011
5365
  }
5012
5366
  if (d.elapsedSeconds !== void 0) {
5013
- let timeLine = `${indent}${chalk5.dim(BOX.vertical)} Time: ${chalk5.dim(`${d.elapsedSeconds.toFixed(1)}s`)}`;
5367
+ let timeLine = `${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(`${d.elapsedSeconds.toFixed(1)}s`)}`;
5014
5368
  if (d.outputTokens && d.elapsedSeconds > 0) {
5015
5369
  const tokensPerSec = Math.round(d.outputTokens / d.elapsedSeconds);
5016
- timeLine += chalk5.dim(` (${tokensPerSec} tok/s)`);
5370
+ timeLine += chalk6.dim(` (${tokensPerSec} tok/s)`);
5017
5371
  }
5018
5372
  lines.push(timeLine);
5019
5373
  }
5020
5374
  if (d.cost !== void 0 && d.cost > 0) {
5021
5375
  lines.push(
5022
- `${indent}${chalk5.dim(BOX.vertical)} Cost: ${chalk5.cyan(`$${formatCost(d.cost)}`)}`
5376
+ `${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(d.cost)}`)}`
5023
5377
  );
5024
5378
  }
5025
5379
  if (d.finishReason) {
5026
5380
  const reason = d.finishReason.toUpperCase();
5027
- const reasonColor = reason === "STOP" || reason === "END_TURN" ? chalk5.green : chalk5.yellow;
5028
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} Finish: ${reasonColor(reason)}`);
5381
+ const reasonColor = reason === "STOP" || reason === "END_TURN" ? chalk6.green : chalk6.yellow;
5382
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} Finish: ${reasonColor(reason)}`);
5029
5383
  }
5030
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5384
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5031
5385
  return lines;
5032
5386
  }
5033
5387
  function formatGadgetCollapsed(node, selected) {
@@ -5035,15 +5389,15 @@ function formatGadgetCollapsed(node, selected) {
5035
5389
  let indicatorColor;
5036
5390
  if (node.error) {
5037
5391
  indicator = ERROR_INDICATOR;
5038
- indicatorColor = chalk5.red;
5392
+ indicatorColor = chalk6.red;
5039
5393
  } else if (node.isComplete) {
5040
5394
  indicator = COMPLETE_INDICATOR;
5041
- indicatorColor = chalk5.green;
5395
+ indicatorColor = chalk6.green;
5042
5396
  } else {
5043
5397
  indicator = PROGRESS_INDICATOR;
5044
- indicatorColor = chalk5.blue;
5398
+ indicatorColor = chalk6.blue;
5045
5399
  }
5046
- const gadgetLabel = chalk5.magenta.bold(node.name);
5400
+ const gadgetLabel = chalk6.magenta.bold(node.name);
5047
5401
  let paramsStr = "";
5048
5402
  if (node.parameters && Object.keys(node.parameters).length > 0) {
5049
5403
  if (node.name === "TellUser") {
@@ -5065,15 +5419,15 @@ function formatGadgetCollapsed(node, selected) {
5065
5419
  const formatted = entries.map(([key, value]) => {
5066
5420
  const strValue = typeof value === "string" ? value : JSON.stringify(value);
5067
5421
  const truncated = strValue.length > maxParamLen ? `${strValue.slice(0, maxParamLen - 3)}...` : strValue;
5068
- return `${chalk5.dim(key)}=${chalk5.cyan(truncated)}`;
5422
+ return `${chalk6.dim(key)}=${chalk6.cyan(truncated)}`;
5069
5423
  });
5070
- paramsStr = `${chalk5.dim("(")}${formatted.join(chalk5.dim(", "))}${chalk5.dim(")")}`;
5424
+ paramsStr = `${chalk6.dim("(")}${formatted.join(chalk6.dim(", "))}${chalk6.dim(")")}`;
5071
5425
  }
5072
5426
  }
5073
5427
  let errorStr = "";
5074
5428
  if (node.error) {
5075
5429
  const truncated = node.error.length > 40 ? `${node.error.slice(0, 37)}...` : node.error;
5076
- errorStr = ` ${chalk5.red("error:")} ${truncated}`;
5430
+ errorStr = ` ${chalk6.red("error:")} ${truncated}`;
5077
5431
  }
5078
5432
  const metrics = [];
5079
5433
  if (node.executionTimeMs !== void 0) {
@@ -5081,24 +5435,23 @@ function formatGadgetCollapsed(node, selected) {
5081
5435
  }
5082
5436
  if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
5083
5437
  const { inputTokens, cachedTokens, outputTokens } = node.subagentStats;
5084
- const tokenParts = [];
5085
- tokenParts.push(chalk5.dim("\u2191") + chalk5.yellow(` ${formatTokens(inputTokens)}`));
5086
- if (cachedTokens > 0) {
5087
- tokenParts.push(chalk5.dim("\u293F") + chalk5.blue(` ${formatTokens(cachedTokens)}`));
5088
- }
5089
- tokenParts.push(chalk5.dim("\u2193") + chalk5.green(` ${formatTokens(outputTokens)}`));
5090
- metrics.push(tokenParts.join(" "));
5438
+ const tokenMetrics = buildTokenMetrics({
5439
+ input: inputTokens,
5440
+ cached: cachedTokens,
5441
+ output: outputTokens
5442
+ });
5443
+ metrics.push(tokenMetrics.join(" "));
5091
5444
  } else if (node.resultTokens && node.resultTokens > 0) {
5092
- metrics.push(chalk5.dim("\u2193") + chalk5.green(` ${formatTokens(node.resultTokens)}`));
5445
+ metrics.push(chalk6.dim("\u2193") + chalk6.green(` ${formatTokens(node.resultTokens)}`));
5093
5446
  }
5094
5447
  if (node.cost && node.cost > 0) {
5095
- metrics.push(chalk5.cyan(`$${formatCost(node.cost)}`));
5448
+ metrics.push(costPart(node.cost));
5096
5449
  }
5097
- const metricsStr = metrics.length > 0 ? ` ${chalk5.dim(metrics.join(" | "))}` : "";
5450
+ const metricsStr = metrics.length > 0 ? ` ${chalk6.dim(metrics.join(" | "))}` : "";
5098
5451
  const line = `${indicatorColor(indicator)} ${gadgetLabel}${paramsStr}${errorStr}${metricsStr}`;
5099
5452
  let result;
5100
5453
  if (selected) {
5101
- result = chalk5.bgBlue.white(line);
5454
+ result = chalk6.bgBlue.white(line);
5102
5455
  } else {
5103
5456
  result = line;
5104
5457
  }
@@ -5120,7 +5473,7 @@ function formatGadgetExpanded(node) {
5120
5473
  const paramsToShow = node.parameters ? Object.entries(node.parameters).filter(([key]) => !(isTellUser && key === "message")) : [];
5121
5474
  if (paramsToShow.length > 0) {
5122
5475
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Parameters ${BOX.horizontal.repeat(width - 14)}`;
5123
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5476
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5124
5477
  for (const [key, value] of paramsToShow) {
5125
5478
  const strValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
5126
5479
  const valueLines = strValue.split("\n");
@@ -5128,30 +5481,30 @@ function formatGadgetExpanded(node) {
5128
5481
  if (valueLines.length === 1) {
5129
5482
  const truncated = strValue.length > maxValueLen ? `${strValue.slice(0, maxValueLen - 3)}...` : strValue;
5130
5483
  lines.push(
5131
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(key)}: ${chalk5.cyan(truncated)}`
5484
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}: ${chalk6.cyan(truncated)}`
5132
5485
  );
5133
5486
  } else {
5134
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(key)}:`);
5487
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}:`);
5135
5488
  for (const line of valueLines.slice(0, 5)) {
5136
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${chalk5.cyan(line)}`);
5489
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.cyan(line)}`);
5137
5490
  }
5138
5491
  if (valueLines.length > 5) {
5139
5492
  lines.push(
5140
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`... (${valueLines.length - 5} more lines)`)}`
5493
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${valueLines.length - 5} more lines)`)}`
5141
5494
  );
5142
5495
  }
5143
5496
  }
5144
5497
  }
5145
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5498
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5146
5499
  }
5147
5500
  if (node.name === "TellUser" && node.parameters?.message) {
5148
5501
  const message = String(node.parameters.message);
5149
5502
  const messageType = node.parameters.type || "info";
5150
5503
  const typeIndicators = {
5151
- info: { emoji: "\u2139\uFE0F", color: chalk5.blue },
5152
- success: { emoji: "\u2705", color: chalk5.green },
5153
- warning: { emoji: "\u26A0\uFE0F", color: chalk5.yellow },
5154
- error: { emoji: "\u274C", color: chalk5.red }
5504
+ info: { emoji: "\u2139\uFE0F", color: chalk6.blue },
5505
+ success: { emoji: "\u2705", color: chalk6.green },
5506
+ warning: { emoji: "\u26A0\uFE0F", color: chalk6.yellow },
5507
+ error: { emoji: "\u274C", color: chalk6.red }
5155
5508
  };
5156
5509
  const typeInfo = typeIndicators[messageType] || typeIndicators.info;
5157
5510
  const headerLine = `${BOX.topLeft}${BOX.horizontal} ${typeInfo.emoji} Message ${BOX.horizontal.repeat(width - 13)}`;
@@ -5159,86 +5512,84 @@ function formatGadgetExpanded(node) {
5159
5512
  const rendered = renderMarkdown(message);
5160
5513
  const renderedLines = rendered.split("\n");
5161
5514
  for (const line of renderedLines) {
5162
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${line}`);
5515
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${line}`);
5163
5516
  }
5164
5517
  lines.push(`${indent}${typeInfo.color(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5165
5518
  } else if (node.result || node.error) {
5166
5519
  const headerText = node.error ? " Error " : " Result ";
5167
5520
  const headerLine = `${BOX.topLeft}${BOX.horizontal}${headerText}${BOX.horizontal.repeat(width - headerText.length - 2)}`;
5168
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5521
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5169
5522
  const content = node.error || node.result || "";
5170
5523
  const contentLines = content.split("\n");
5171
5524
  const maxLines = 10;
5172
5525
  const displayLines = contentLines.slice(0, maxLines);
5173
5526
  for (const line of displayLines) {
5174
5527
  const truncated = line.length > width - 4 ? `${line.slice(0, width - 7)}...` : line;
5175
- const color = node.error ? chalk5.red : chalk5.white;
5176
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${color(truncated)}`);
5528
+ const color = node.error ? chalk6.red : chalk6.white;
5529
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${color(truncated)}`);
5177
5530
  }
5178
5531
  if (contentLines.length > maxLines) {
5179
5532
  lines.push(
5180
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`... (${contentLines.length - maxLines} more lines)`)}`
5533
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${contentLines.length - maxLines} more lines)`)}`
5181
5534
  );
5182
5535
  }
5183
5536
  if (node.executionTimeMs !== void 0) {
5184
5537
  lines.push(
5185
- `${indent}${chalk5.dim(BOX.vertical)} Time: ${chalk5.dim(formatExecutionTime(node.executionTimeMs))}`
5538
+ `${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
5186
5539
  );
5187
5540
  }
5188
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5541
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5189
5542
  }
5190
5543
  if (node.mediaOutputs && node.mediaOutputs.length > 0) {
5191
5544
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Media ${BOX.horizontal.repeat(width - 9)}`;
5192
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5545
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5193
5546
  for (const media of node.mediaOutputs) {
5194
5547
  const kindEmoji = media.kind === "audio" ? "\u{1F50A}" : media.kind === "image" ? "\u{1F5BC}\uFE0F" : media.kind === "video" ? "\u{1F3AC}" : "\u{1F4C4}";
5195
5548
  const maxPathLen = width - 8;
5196
5549
  const displayPath = media.path.length > maxPathLen ? `...${media.path.slice(-(maxPathLen - 3))}` : media.path;
5197
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${kindEmoji} ${chalk5.cyan(displayPath)}`);
5550
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${kindEmoji} ${chalk6.cyan(displayPath)}`);
5198
5551
  }
5199
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5552
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5200
5553
  }
5201
5554
  if (node.children.length > 0) {
5202
5555
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Subagent Activity ${BOX.horizontal.repeat(width - 21)}`;
5203
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5556
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5204
5557
  lines.push(
5205
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`${node.children.length} nested calls (expand children to see details)`)}`
5558
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`${node.children.length} nested calls (expand children to see details)`)}`
5206
5559
  );
5207
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5560
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5208
5561
  }
5209
5562
  if (node.executionTimeMs !== void 0 || node.cost || node.resultTokens || node.subagentStats) {
5210
5563
  const metricsHeaderLine = `${BOX.topLeft}${BOX.horizontal} Metrics ${BOX.horizontal.repeat(width - 11)}`;
5211
- lines.push(`${indent}${chalk5.dim(metricsHeaderLine)}`);
5564
+ lines.push(`${indent}${chalk6.dim(metricsHeaderLine)}`);
5212
5565
  if (node.executionTimeMs !== void 0) {
5213
5566
  lines.push(
5214
- `${indent}${chalk5.dim(BOX.vertical)} Duration: ${chalk5.dim(formatExecutionTime(node.executionTimeMs))}`
5567
+ `${indent}${chalk6.dim(BOX.vertical)} Duration: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
5215
5568
  );
5216
5569
  }
5217
5570
  if (node.resultTokens && node.resultTokens > 0) {
5218
5571
  lines.push(
5219
- `${indent}${chalk5.dim(BOX.vertical)} Output: ${chalk5.green(`~${formatTokens(node.resultTokens)}`)} tokens`
5572
+ `${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(`~${formatTokens(node.resultTokens)}`)} tokens`
5220
5573
  );
5221
5574
  }
5222
5575
  if (node.cost && node.cost > 0) {
5223
5576
  lines.push(
5224
- `${indent}${chalk5.dim(BOX.vertical)} Cost: ${chalk5.cyan(`$${formatCost(node.cost)}`)}`
5577
+ `${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(node.cost)}`)}`
5225
5578
  );
5226
5579
  }
5227
5580
  if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
5228
5581
  const s = node.subagentStats;
5229
- const tokenParts = [];
5230
- tokenParts.push(chalk5.dim("\u2191") + chalk5.yellow(` ${formatTokens(s.inputTokens)}`));
5231
- if (s.cachedTokens > 0) {
5232
- tokenParts.push(chalk5.dim("\u293F") + chalk5.blue(` ${formatTokens(s.cachedTokens)}`));
5233
- }
5234
- tokenParts.push(chalk5.dim("\u2193") + chalk5.green(` ${formatTokens(s.outputTokens)}`));
5235
- const tokenStr = tokenParts.join(" ");
5236
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} LLM calls: ${s.llmCallCount} (${tokenStr})`);
5582
+ const tokenStr = buildTokenMetrics({
5583
+ input: s.inputTokens,
5584
+ cached: s.cachedTokens,
5585
+ output: s.outputTokens
5586
+ }).join(" ");
5587
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} LLM calls: ${s.llmCallCount} (${tokenStr})`);
5237
5588
  }
5238
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5589
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5239
5590
  }
5240
5591
  if (lines.length === 0) {
5241
- lines.push(`${indent}${chalk5.dim("No details available")}`);
5592
+ lines.push(`${indent}${chalk6.dim("No details available")}`);
5242
5593
  }
5243
5594
  return lines;
5244
5595
  }
@@ -5253,6 +5604,163 @@ function getContinuationIndent(depth) {
5253
5604
  return `${" ".repeat(depth)} `;
5254
5605
  }
5255
5606
 
5607
+ // src/tui/block-content-formatter.ts
5608
+ function formatBlockContent(node, selected, expanded) {
5609
+ const indent = getIndent(node.depth);
5610
+ switch (node.type) {
5611
+ case "llm_call": {
5612
+ const collapsed = formatLLMCallCollapsed(node, selected);
5613
+ if (!expanded) {
5614
+ return indent + collapsed;
5615
+ }
5616
+ const expandedLines = formatLLMCallExpanded(node);
5617
+ const contIndent = getContinuationIndent(node.depth);
5618
+ return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
5619
+ }
5620
+ case "gadget": {
5621
+ const collapsed = formatGadgetCollapsed(node, selected);
5622
+ if (!expanded) {
5623
+ return indent + collapsed;
5624
+ }
5625
+ const expandedLines = formatGadgetExpanded(node);
5626
+ const contIndent = getContinuationIndent(node.depth);
5627
+ return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
5628
+ }
5629
+ case "text": {
5630
+ if (node.id.startsWith("user_")) {
5631
+ return formatUserMessage(node.content);
5632
+ }
5633
+ const fullContent = renderMarkdown(node.content);
5634
+ if (expanded) {
5635
+ return `
5636
+ ${fullContent}
5637
+ `;
5638
+ }
5639
+ return abbreviateToLines(fullContent, 2, selected);
5640
+ }
5641
+ case "thinking": {
5642
+ return formatThinkingContent(node, indent, expanded);
5643
+ }
5644
+ case "system_message": {
5645
+ const icon = getSystemMessageIcon(node.category);
5646
+ const color = getSystemMessageColor(node.category);
5647
+ const RESET3 = "\x1B[0m";
5648
+ return `${indent}${color}${icon} ${node.message}${RESET3}`;
5649
+ }
5650
+ }
5651
+ }
5652
+ function formatThinkingContent(node, indent, expanded) {
5653
+ const DIM2 = "\x1B[2m";
5654
+ const RED_DIM = "\x1B[2;31m";
5655
+ const RESET3 = "\x1B[0m";
5656
+ const contIndent = getContinuationIndent(node.depth);
5657
+ if (node.thinkingType === "redacted") {
5658
+ return `${indent}${RED_DIM}\u{1F512} [Redacted thinking block]${RESET3}`;
5659
+ }
5660
+ if (!expanded) {
5661
+ const firstLine = node.content.split("\n")[0]?.slice(0, 60) ?? "";
5662
+ const suffix = node.isComplete ? "" : "...";
5663
+ return `${indent}${DIM2}\u{1F4AD} Thinking${suffix} ${firstLine}${RESET3}`;
5664
+ }
5665
+ const tokenInfo = node.isComplete ? ` (${Math.ceil(node.content.length / 4)} tokens est.)` : "";
5666
+ const header = `${indent}${DIM2}\u25BC \u{1F4AD} Thinking${tokenInfo}${RESET3}`;
5667
+ const contentLines = node.content.split("\n").map((line) => `${contIndent}${DIM2}${line}${RESET3}`);
5668
+ return [header, ...contentLines].join("\n");
5669
+ }
5670
+ function getSystemMessageIcon(category) {
5671
+ switch (category) {
5672
+ case "throttle":
5673
+ return "\u23F8";
5674
+ case "retry":
5675
+ return "\u{1F504}";
5676
+ case "info":
5677
+ return "\u2139\uFE0F";
5678
+ case "warning":
5679
+ return "\u26A0\uFE0F";
5680
+ case "error":
5681
+ return "\u274C";
5682
+ }
5683
+ }
5684
+ function getSystemMessageColor(category) {
5685
+ const YELLOW2 = "\x1B[33m";
5686
+ const BLUE = "\x1B[34m";
5687
+ const GRAY2 = "\x1B[90m";
5688
+ const RED2 = "\x1B[31m";
5689
+ switch (category) {
5690
+ case "throttle":
5691
+ return YELLOW2;
5692
+ case "retry":
5693
+ return BLUE;
5694
+ case "info":
5695
+ return GRAY2;
5696
+ case "warning":
5697
+ return YELLOW2;
5698
+ case "error":
5699
+ return RED2;
5700
+ }
5701
+ }
5702
+ function abbreviateToLines(text3, maxLines, selected) {
5703
+ const lines = text3.split("\n");
5704
+ let startIndex = 0;
5705
+ while (startIndex < lines.length && lines[startIndex].trim() === "") {
5706
+ startIndex++;
5707
+ }
5708
+ const contentLines = lines.slice(startIndex);
5709
+ if (contentLines.length <= maxLines) {
5710
+ return `
5711
+ ${contentLines.join("\n")}`;
5712
+ }
5713
+ const truncatedLines = contentLines.slice(0, maxLines);
5714
+ const indicator = selected ? "\u25B6 ..." : " ...";
5715
+ return `
5716
+ ${truncatedLines.join("\n")}
5717
+ ${indicator}`;
5718
+ }
5719
+ function isNodeVisibleInFilterMode(node, contentFilterMode) {
5720
+ if (contentFilterMode === "full") {
5721
+ return true;
5722
+ }
5723
+ switch (node.type) {
5724
+ case "text":
5725
+ return true;
5726
+ case "gadget":
5727
+ return shouldRenderAsText(node, contentFilterMode);
5728
+ default:
5729
+ return false;
5730
+ }
5731
+ }
5732
+ function shouldRenderAsText(node, contentFilterMode) {
5733
+ if (contentFilterMode !== "focused") return false;
5734
+ if (node.type !== "gadget") return false;
5735
+ const name = node.name;
5736
+ return name === "TellUser" || name === "AskUser" || name === "Finish";
5737
+ }
5738
+ function formatGadgetAsText(node) {
5739
+ if (node.name === "TellUser") {
5740
+ const message = node.parameters?.message;
5741
+ if (typeof message === "string") {
5742
+ return `
5743
+ ${renderMarkdown(message)}
5744
+ `;
5745
+ }
5746
+ } else if (node.name === "AskUser") {
5747
+ const question = node.parameters?.question;
5748
+ if (typeof question === "string") {
5749
+ return `
5750
+ ? ${question}
5751
+ `;
5752
+ }
5753
+ } else if (node.name === "Finish") {
5754
+ const message = node.parameters?.message;
5755
+ if (typeof message === "string" && message.trim()) {
5756
+ return `
5757
+ \x1B[32m\u2713\x1B[0m ${renderMarkdown(message)}
5758
+ `;
5759
+ }
5760
+ }
5761
+ return "";
5762
+ }
5763
+
5256
5764
  // src/tui/node-store.ts
5257
5765
  var NodeStore = class {
5258
5766
  /** All nodes in the tree (flat for easy lookup) */
@@ -5675,6 +6183,28 @@ var NodeStore = class {
5675
6183
  }
5676
6184
  };
5677
6185
 
6186
+ // src/tui/tree-layout.ts
6187
+ function traverseNodeTree(nodeId, getNode, visit, top) {
6188
+ const node = getNode(nodeId);
6189
+ if (!node) {
6190
+ return top;
6191
+ }
6192
+ let nextTop = visit(nodeId, node, top);
6193
+ if ("children" in node) {
6194
+ for (const childId of node.children) {
6195
+ nextTop = traverseNodeTree(childId, getNode, visit, nextTop);
6196
+ }
6197
+ }
6198
+ return nextTop;
6199
+ }
6200
+ function traverseRootTrees(rootIds, getNode, visit) {
6201
+ let top = 0;
6202
+ for (const rootId of rootIds) {
6203
+ top = traverseNodeTree(rootId, getNode, visit, top);
6204
+ }
6205
+ return top;
6206
+ }
6207
+
5678
6208
  // src/tui/scroll-manager.ts
5679
6209
  var ScrollManager = class _ScrollManager {
5680
6210
  container;
@@ -5803,25 +6333,14 @@ var ScrollManager = class _ScrollManager {
5803
6333
  * Calculate total height of all rendered blocks.
5804
6334
  */
5805
6335
  getTotalContentHeight() {
5806
- let totalHeight = 0;
5807
- for (const rootId of this.accessors.getRootIds()) {
5808
- totalHeight = this.sumNodeTreeHeight(rootId, totalHeight);
5809
- }
5810
- return totalHeight;
5811
- }
5812
- sumNodeTreeHeight(nodeId, currentHeight) {
5813
- const node = this.accessors.getNode(nodeId);
5814
- if (!node) return currentHeight;
5815
- const block = this.accessors.getBlock(nodeId);
5816
- if (block) {
5817
- currentHeight += getBlockHeight(block);
5818
- }
5819
- if ("children" in node) {
5820
- for (const childId of node.children) {
5821
- currentHeight = this.sumNodeTreeHeight(childId, currentHeight);
6336
+ return traverseRootTrees(
6337
+ this.accessors.getRootIds(),
6338
+ (id) => this.accessors.getNode(id),
6339
+ (nodeId, _node, currentHeight) => {
6340
+ const block = this.accessors.getBlock(nodeId);
6341
+ return currentHeight + (block ? getBlockHeight(block) : 0);
5822
6342
  }
5823
- }
5824
- return currentHeight;
6343
+ );
5825
6344
  }
5826
6345
  /**
5827
6346
  * Calculate vertical offset to push content to bottom when content < viewport.
@@ -5838,17 +6357,18 @@ var ScrollManager = class _ScrollManager {
5838
6357
  * Apply vertical offset to a node tree (for bottom alignment).
5839
6358
  */
5840
6359
  applyOffsetToNodeTree(nodeId, offset) {
5841
- const node = this.accessors.getNode(nodeId);
5842
- if (!node) return;
5843
- const block = this.accessors.getBlock(nodeId);
5844
- if (block) {
5845
- block.box.top = block.box.top + offset;
5846
- }
5847
- if ("children" in node) {
5848
- for (const childId of node.children) {
5849
- this.applyOffsetToNodeTree(childId, offset);
5850
- }
5851
- }
6360
+ traverseNodeTree(
6361
+ nodeId,
6362
+ (id) => this.accessors.getNode(id),
6363
+ (currentNodeId, _node, top) => {
6364
+ const block = this.accessors.getBlock(currentNodeId);
6365
+ if (block) {
6366
+ block.box.top = block.box.top + offset;
6367
+ }
6368
+ return top;
6369
+ },
6370
+ 0
6371
+ );
5852
6372
  }
5853
6373
  };
5854
6374
  function getBlockHeight(block) {
@@ -5856,6 +6376,167 @@ function getBlockHeight(block) {
5856
6376
  return content.split("\n").length;
5857
6377
  }
5858
6378
 
6379
+ // src/tui/tree-bridge.ts
6380
+ var TreeBridge = class {
6381
+ /** Unsubscribe function for tree events */
6382
+ treeUnsubscribe = null;
6383
+ /** Map tree node IDs to block node IDs */
6384
+ treeNodeToBlockId = /* @__PURE__ */ new Map();
6385
+ callbacks;
6386
+ constructor(callbacks) {
6387
+ this.callbacks = callbacks;
6388
+ }
6389
+ // ───────────────────────────────────────────────────────────────────────────
6390
+ // Public API
6391
+ // ───────────────────────────────────────────────────────────────────────────
6392
+ /**
6393
+ * Check if tree subscription is active.
6394
+ */
6395
+ isSubscribed() {
6396
+ return this.treeUnsubscribe !== null;
6397
+ }
6398
+ /**
6399
+ * Subscribe to an ExecutionTree for automatic block updates.
6400
+ *
6401
+ * When subscribed, the TreeBridge will automatically create and update
6402
+ * blocks based on tree events, calling the provided callbacks.
6403
+ *
6404
+ * @param tree - The ExecutionTree to subscribe to
6405
+ * @returns Unsubscribe function to stop listening
6406
+ *
6407
+ * @example
6408
+ * ```typescript
6409
+ * const agent = builder.ask("Hello");
6410
+ * const unsubscribe = treeBridge.subscribeToTree(agent.getTree());
6411
+ *
6412
+ * for await (const event of agent.run()) {
6413
+ * // Blocks are automatically updated via tree subscription
6414
+ * }
6415
+ *
6416
+ * unsubscribe();
6417
+ * ```
6418
+ */
6419
+ subscribeToTree(tree) {
6420
+ if (this.treeUnsubscribe) {
6421
+ this.treeUnsubscribe();
6422
+ }
6423
+ this.treeNodeToBlockId.clear();
6424
+ this.callbacks.onClearIdempotencyMaps();
6425
+ this.treeUnsubscribe = tree.onAll((event) => {
6426
+ this.handleTreeEvent(event, tree);
6427
+ });
6428
+ return () => {
6429
+ if (this.treeUnsubscribe) {
6430
+ this.treeUnsubscribe();
6431
+ this.treeUnsubscribe = null;
6432
+ }
6433
+ };
6434
+ }
6435
+ /**
6436
+ * Get block ID for a tree node ID.
6437
+ * Useful for external code that needs to correlate tree nodes with blocks.
6438
+ */
6439
+ getBlockIdForTreeNode(treeNodeId) {
6440
+ return this.treeNodeToBlockId.get(treeNodeId);
6441
+ }
6442
+ // ───────────────────────────────────────────────────────────────────────────
6443
+ // Event Handling
6444
+ // ───────────────────────────────────────────────────────────────────────────
6445
+ /**
6446
+ * Handle an ExecutionTree event.
6447
+ */
6448
+ handleTreeEvent(event, tree) {
6449
+ switch (event.type) {
6450
+ case "llm_call_start": {
6451
+ this.callbacks.onResetThinking();
6452
+ let parentBlockId;
6453
+ if (event.parentId) {
6454
+ parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6455
+ }
6456
+ const blockId = this.callbacks.onAddLLMCall(
6457
+ event.iteration + 1,
6458
+ event.model,
6459
+ parentBlockId,
6460
+ event.depth > 0
6461
+ );
6462
+ this.treeNodeToBlockId.set(event.nodeId, blockId);
6463
+ const startNode = tree.getNode(event.nodeId);
6464
+ if (startNode?.type === "llm_call" && startNode.request) {
6465
+ this.callbacks.onSetLLMCallRequest(blockId, startNode.request);
6466
+ }
6467
+ break;
6468
+ }
6469
+ case "llm_call_complete": {
6470
+ this.callbacks.onCompleteThinking();
6471
+ const blockId = this.treeNodeToBlockId.get(event.nodeId);
6472
+ if (blockId) {
6473
+ this.callbacks.onCompleteLLMCall(blockId, {
6474
+ inputTokens: event.usage?.inputTokens,
6475
+ cachedInputTokens: event.usage?.cachedInputTokens,
6476
+ outputTokens: event.usage?.outputTokens,
6477
+ reasoningTokens: event.usage?.reasoningTokens,
6478
+ cost: event.cost,
6479
+ finishReason: event.finishReason ?? void 0
6480
+ });
6481
+ const completeNode = tree.getNode(event.nodeId);
6482
+ if (completeNode?.type === "llm_call" && completeNode.response) {
6483
+ this.callbacks.onSetLLMCallResponse(blockId, completeNode.response);
6484
+ }
6485
+ }
6486
+ break;
6487
+ }
6488
+ case "thinking": {
6489
+ this.callbacks.onAddThinking(event.content, event.thinkingType);
6490
+ break;
6491
+ }
6492
+ case "gadget_call": {
6493
+ let parentBlockId;
6494
+ if (event.parentId) {
6495
+ parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6496
+ }
6497
+ const previousLLMCallId = this.callbacks.onGetCurrentLLMCallId();
6498
+ if (parentBlockId) {
6499
+ this.callbacks.onSetCurrentLLMCall(parentBlockId);
6500
+ }
6501
+ const blockId = this.callbacks.onAddGadget(
6502
+ event.invocationId,
6503
+ event.name,
6504
+ event.parameters
6505
+ );
6506
+ this.treeNodeToBlockId.set(event.nodeId, blockId);
6507
+ this.callbacks.onSetCurrentLLMCall(previousLLMCallId);
6508
+ break;
6509
+ }
6510
+ case "gadget_complete": {
6511
+ const mediaOutputs = event.storedMedia?.map((m) => ({
6512
+ kind: m.kind,
6513
+ path: m.path,
6514
+ mimeType: m.mimeType,
6515
+ description: m.description
6516
+ }));
6517
+ this.callbacks.onCompleteGadget(event.invocationId, {
6518
+ result: event.result,
6519
+ executionTimeMs: event.executionTimeMs,
6520
+ cost: event.cost,
6521
+ mediaOutputs
6522
+ });
6523
+ break;
6524
+ }
6525
+ case "gadget_error": {
6526
+ this.callbacks.onCompleteGadget(event.invocationId, {
6527
+ error: event.error,
6528
+ executionTimeMs: event.executionTimeMs
6529
+ });
6530
+ break;
6531
+ }
6532
+ case "gadget_skipped": {
6533
+ this.callbacks.onSkipGadget(event.invocationId, `Skipped: ${event.failedDependencyError}`);
6534
+ break;
6535
+ }
6536
+ }
6537
+ }
6538
+ };
6539
+
5859
6540
  // src/tui/block-renderer.ts
5860
6541
  var BlockRenderer = class {
5861
6542
  container;
@@ -5865,6 +6546,8 @@ var BlockRenderer = class {
5865
6546
  nodeStore;
5866
6547
  /** Scroll manager — manages scroll position, follow mode, and bottom alignment */
5867
6548
  scrollManager;
6549
+ /** Tree bridge — manages ExecutionTree subscriptions and tree→block mapping */
6550
+ treeBridge;
5868
6551
  /** Rendered blocks with UI state */
5869
6552
  blocks = /* @__PURE__ */ new Map();
5870
6553
  /** IDs of selectable blocks in display order */
@@ -5894,6 +6577,21 @@ var BlockRenderer = class {
5894
6577
  getBlock: (id) => this.blocks.get(id),
5895
6578
  getSelectedBlock: () => this.getSelectedBlock()
5896
6579
  });
6580
+ this.treeBridge = new TreeBridge({
6581
+ onResetThinking: () => this.nodeStore.resetCurrentThinking(),
6582
+ onSetCurrentLLMCall: (llmCallId) => this.setCurrentLLMCall(llmCallId),
6583
+ onClearIdempotencyMaps: () => this.nodeStore.clearIdempotencyMaps(),
6584
+ onAddLLMCall: (iteration, model, parentGadgetId, isNested) => this.addLLMCall(iteration, model, parentGadgetId, isNested),
6585
+ onCompleteLLMCall: (id, details, rawResponse) => this.completeLLMCall(id, details, rawResponse),
6586
+ onSetLLMCallRequest: (id, messages) => this.setLLMCallRequest(id, messages),
6587
+ onSetLLMCallResponse: (id, rawResponse) => this.setLLMCallResponse(id, rawResponse),
6588
+ onCompleteThinking: () => this.completeThinking(),
6589
+ onAddThinking: (content, thinkingType) => this.addThinking(content, thinkingType),
6590
+ onAddGadget: (invocationId, name, parameters) => this.addGadget(invocationId, name, parameters),
6591
+ onCompleteGadget: (invocationId, options) => this.completeGadget(invocationId, options),
6592
+ onSkipGadget: (invocationId, reason) => this.skipGadget(invocationId, reason),
6593
+ onGetCurrentLLMCallId: () => this.getCurrentLLMCallId()
6594
+ });
5897
6595
  }
5898
6596
  // ───────────────────────────────────────────────────────────────────────────
5899
6597
  // Public API - Node Management
@@ -5939,6 +6637,12 @@ var BlockRenderer = class {
5939
6637
  completeGadget(invocationId, options = {}) {
5940
6638
  this.nodeStore.completeGadget(invocationId, options);
5941
6639
  }
6640
+ /**
6641
+ * Mark a gadget as skipped with a rendered reason.
6642
+ */
6643
+ skipGadget(invocationId, reason) {
6644
+ this.nodeStore.completeGadget(invocationId, { error: reason });
6645
+ }
5942
6646
  /**
5943
6647
  * Add a text node (flows between LLM calls).
5944
6648
  */
@@ -6006,9 +6710,8 @@ var BlockRenderer = class {
6006
6710
  /**
6007
6711
  * Get the current LLM call ID.
6008
6712
  *
6009
- * In tree mode, this is only used for attaching raw request/response data
6010
- * to the block for the raw viewer feature. Parent-child relationships are
6011
- * determined by event.parentId in handleTreeEvent(), not by this method.
6713
+ * In tree mode, TreeBridge uses this to restore the current parent context
6714
+ * while translating gadget events into renderer updates.
6012
6715
  */
6013
6716
  getCurrentLLMCallId() {
6014
6717
  return this.nodeStore.currentLLMCallId;
@@ -6018,7 +6721,7 @@ var BlockRenderer = class {
6018
6721
  * When active, external code should skip block creation (tree handles it).
6019
6722
  */
6020
6723
  isTreeSubscribed() {
6021
- return this.treeUnsubscribe !== null;
6724
+ return this.treeBridge.isSubscribed();
6022
6725
  }
6023
6726
  /**
6024
6727
  * Store raw response for an LLM call (enrichment only).
@@ -6189,22 +6892,7 @@ var BlockRenderer = class {
6189
6892
  * Called when nodes are added/removed.
6190
6893
  */
6191
6894
  rebuildBlocks() {
6192
- for (const child of [...this.container.children]) {
6193
- child.detach();
6194
- }
6195
- this.container.setContent("");
6196
- this.blocks.clear();
6197
- this.selectableIds = [];
6198
- let top = 0;
6199
- for (const rootId of this.nodeStore.rootIds) {
6200
- top = this.renderNodeTree(rootId, top);
6201
- }
6202
- if (this.selectedIndex >= this.selectableIds.length) {
6203
- this.selectedIndex = this.selectableIds.length - 1;
6204
- }
6205
- this.applyBottomAlignmentAndScroll();
6206
- this.renderCallback();
6207
- this.notifyHasContentChange();
6895
+ this.rebuildBlocksCore(false);
6208
6896
  }
6209
6897
  /**
6210
6898
  * Render a node and its children recursively.
@@ -6218,64 +6906,29 @@ var BlockRenderer = class {
6218
6906
  * (no headers, just content) for a clean chat-like experience.
6219
6907
  */
6220
6908
  renderNodeTree(nodeId, top) {
6221
- const node = this.getNode(nodeId);
6222
- if (!node) return top;
6223
- const visible = this.isNodeVisible(node);
6224
- if (visible) {
6225
- const renderAsText = this.shouldRenderAsText(node);
6226
- const block = renderAsText ? this.createTextLikeBlock(node, top) : this.createBlock(node, top);
6227
- this.blocks.set(nodeId, block);
6228
- if (block.selectable) {
6229
- this.selectableIds.push(nodeId);
6230
- }
6231
- const height = getBlockHeight(block);
6232
- top += height;
6233
- }
6234
- if ("children" in node && node.children.length > 0) {
6235
- for (const childId of node.children) {
6236
- top = this.renderNodeTree(childId, top);
6237
- }
6238
- }
6239
- return top;
6240
- }
6241
- /**
6242
- * Check if a gadget should render as plain text in focused mode.
6243
- * TellUser, AskUser, and Finish render as text for a chat-like experience.
6244
- */
6245
- shouldRenderAsText(node) {
6246
- if (this.contentFilterMode !== "focused") return false;
6247
- if (node.type !== "gadget") return false;
6248
- const name = node.name;
6249
- return name === "TellUser" || name === "AskUser" || name === "Finish";
6909
+ return traverseNodeTree(
6910
+ nodeId,
6911
+ (id) => this.getNode(id),
6912
+ (currentNodeId, node, currentTop) => {
6913
+ if (!this.isNodeVisible(node)) {
6914
+ return currentTop;
6915
+ }
6916
+ const block = shouldRenderAsText(node, this.contentFilterMode) ? this.createTextLikeBlock(node, currentTop) : this.createBlock(node, currentTop);
6917
+ this.blocks.set(currentNodeId, block);
6918
+ if (block.selectable) {
6919
+ this.selectableIds.push(currentNodeId);
6920
+ }
6921
+ return currentTop + getBlockHeight(block);
6922
+ },
6923
+ top
6924
+ );
6250
6925
  }
6251
6926
  /**
6252
6927
  * Create a text-like block for TellUser/AskUser/Finish gadgets in focused mode.
6253
6928
  * Renders just the content without the gadget header.
6254
6929
  */
6255
6930
  createTextLikeBlock(node, top) {
6256
- let content = "";
6257
- if (node.name === "TellUser") {
6258
- const message = node.parameters?.message;
6259
- if (typeof message === "string") {
6260
- content = `
6261
- ${renderMarkdown(message)}
6262
- `;
6263
- }
6264
- } else if (node.name === "AskUser") {
6265
- const question = node.parameters?.question;
6266
- if (typeof question === "string") {
6267
- content = `
6268
- ? ${question}
6269
- `;
6270
- }
6271
- } else if (node.name === "Finish") {
6272
- const message = node.parameters?.message;
6273
- if (typeof message === "string" && message.trim()) {
6274
- content = `
6275
- \x1B[32m\u2713\x1B[0m ${renderMarkdown(message)}
6276
- `;
6277
- }
6278
- }
6931
+ const content = formatGadgetAsText(node);
6279
6932
  const box = new Box({
6280
6933
  parent: this.container,
6281
6934
  top,
@@ -6300,7 +6953,7 @@ ${renderMarkdown(message)}
6300
6953
  const isSelected = this.selectableIds.length === this.selectedIndex;
6301
6954
  const selectable = node.type !== "text" || !node.id.startsWith("user_");
6302
6955
  const expanded = this.expandedStates.get(node.id) ?? false;
6303
- const content = this.formatBlockContent(node, isSelected, expanded);
6956
+ const content = formatBlockContent(node, isSelected, expanded);
6304
6957
  const box = new Box({
6305
6958
  parent: this.container,
6306
6959
  top,
@@ -6318,133 +6971,6 @@ ${renderMarkdown(message)}
6318
6971
  selectable
6319
6972
  };
6320
6973
  }
6321
- /**
6322
- * Format block content based on type and state.
6323
- */
6324
- formatBlockContent(node, selected, expanded) {
6325
- const indent = getIndent(node.depth);
6326
- switch (node.type) {
6327
- case "llm_call": {
6328
- const collapsed = formatLLMCallCollapsed(node, selected);
6329
- if (!expanded) {
6330
- return indent + collapsed;
6331
- }
6332
- const expandedLines = formatLLMCallExpanded(node);
6333
- const contIndent = getContinuationIndent(node.depth);
6334
- return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
6335
- }
6336
- case "gadget": {
6337
- const collapsed = formatGadgetCollapsed(node, selected);
6338
- if (!expanded) {
6339
- return indent + collapsed;
6340
- }
6341
- const expandedLines = formatGadgetExpanded(node);
6342
- const contIndent = getContinuationIndent(node.depth);
6343
- return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
6344
- }
6345
- case "text": {
6346
- if (node.id.startsWith("user_")) {
6347
- return formatUserMessage(node.content);
6348
- }
6349
- const fullContent = renderMarkdown(node.content);
6350
- if (expanded) {
6351
- return `
6352
- ${fullContent}
6353
- `;
6354
- }
6355
- return this.abbreviateToLines(fullContent, 2, selected);
6356
- }
6357
- case "thinking": {
6358
- const DIM2 = "\x1B[2m";
6359
- const RED_DIM = "\x1B[2;31m";
6360
- const RESET3 = "\x1B[0m";
6361
- const contIndent = getContinuationIndent(node.depth);
6362
- if (node.thinkingType === "redacted") {
6363
- const header2 = `${indent}${RED_DIM}\u{1F512} [Redacted thinking block]${RESET3}`;
6364
- return header2;
6365
- }
6366
- if (!expanded) {
6367
- const firstLine = node.content.split("\n")[0]?.slice(0, 60) ?? "";
6368
- const suffix = node.isComplete ? "" : "...";
6369
- return `${indent}${DIM2}\u{1F4AD} Thinking${suffix} ${firstLine}${RESET3}`;
6370
- }
6371
- const tokenInfo = node.isComplete ? ` (${Math.ceil(node.content.length / 4)} tokens est.)` : "";
6372
- const header = `${indent}${DIM2}\u25BC \u{1F4AD} Thinking${tokenInfo}${RESET3}`;
6373
- const contentLines = node.content.split("\n").map((line) => `${contIndent}${DIM2}${line}${RESET3}`);
6374
- return [header, ...contentLines].join("\n");
6375
- }
6376
- case "system_message": {
6377
- const icon = this.getSystemMessageIcon(node.category);
6378
- const color = this.getSystemMessageColor(node.category);
6379
- const RESET3 = "\x1B[0m";
6380
- return `${indent}${color}${icon} ${node.message}${RESET3}`;
6381
- }
6382
- }
6383
- }
6384
- /**
6385
- * Get icon for system message category.
6386
- */
6387
- getSystemMessageIcon(category) {
6388
- switch (category) {
6389
- case "throttle":
6390
- return "\u23F8";
6391
- case "retry":
6392
- return "\u{1F504}";
6393
- case "info":
6394
- return "\u2139\uFE0F";
6395
- case "warning":
6396
- return "\u26A0\uFE0F";
6397
- case "error":
6398
- return "\u274C";
6399
- }
6400
- }
6401
- /**
6402
- * Get ANSI color code for system message category.
6403
- */
6404
- getSystemMessageColor(category) {
6405
- const YELLOW2 = "\x1B[33m";
6406
- const BLUE = "\x1B[34m";
6407
- const GRAY2 = "\x1B[90m";
6408
- const RED2 = "\x1B[31m";
6409
- switch (category) {
6410
- case "throttle":
6411
- return YELLOW2;
6412
- case "retry":
6413
- return BLUE;
6414
- case "info":
6415
- return GRAY2;
6416
- case "warning":
6417
- return YELLOW2;
6418
- case "error":
6419
- return RED2;
6420
- }
6421
- }
6422
- /**
6423
- * Abbreviate text content to a maximum number of lines.
6424
- * Shows truncation indicator if content exceeds limit.
6425
- *
6426
- * @param text - The text to abbreviate
6427
- * @param maxLines - Maximum number of lines to show
6428
- * @param selected - Whether this block is selected (for indicator styling)
6429
- * @returns Abbreviated text with truncation indicator if needed
6430
- */
6431
- abbreviateToLines(text3, maxLines, selected) {
6432
- const lines = text3.split("\n");
6433
- let startIndex = 0;
6434
- while (startIndex < lines.length && lines[startIndex].trim() === "") {
6435
- startIndex++;
6436
- }
6437
- const contentLines = lines.slice(startIndex);
6438
- if (contentLines.length <= maxLines) {
6439
- return `
6440
- ${contentLines.join("\n")}`;
6441
- }
6442
- const truncatedLines = contentLines.slice(0, maxLines);
6443
- const indicator = selected ? "\u25B6 ..." : " ...";
6444
- return `
6445
- ${truncatedLines.join("\n")}
6446
- ${indicator}`;
6447
- }
6448
6974
  /**
6449
6975
  * Update a single block (after state change).
6450
6976
  */
@@ -6453,7 +6979,7 @@ ${indicator}`;
6453
6979
  const node = this.getNode(nodeId);
6454
6980
  if (!block || !node) return;
6455
6981
  const isSelected = this.selectableIds[this.selectedIndex] === nodeId;
6456
- const content = this.formatBlockContent(node, isSelected, block.expanded);
6982
+ const content = formatBlockContent(node, isSelected, block.expanded);
6457
6983
  const oldHeight = getBlockHeight(block);
6458
6984
  block.box.setContent(content);
6459
6985
  const newHeight = content.split("\n").length;
@@ -6471,7 +6997,7 @@ ${indicator}`;
6471
6997
  const block = this.blocks.get(id);
6472
6998
  if (block) {
6473
6999
  const isSelected = this.selectableIds[this.selectedIndex] === id;
6474
- const content = this.formatBlockContent(block.node, isSelected, block.expanded);
7000
+ const content = formatBlockContent(block.node, isSelected, block.expanded);
6475
7001
  block.box.setContent(content);
6476
7002
  }
6477
7003
  }
@@ -6486,20 +7012,19 @@ ${indicator}`;
6486
7012
  this.scrollManager.repositionBlocks((rootId, top) => this.repositionNodeTree(rootId, top));
6487
7013
  }
6488
7014
  repositionNodeTree(nodeId, top) {
6489
- const node = this.getNode(nodeId);
6490
- if (!node) return top;
6491
- const block = this.blocks.get(nodeId);
6492
- if (block) {
6493
- block.box.top = top;
6494
- const height = getBlockHeight(block);
6495
- top += height;
6496
- }
6497
- if ("children" in node) {
6498
- for (const childId of node.children) {
6499
- top = this.repositionNodeTree(childId, top);
6500
- }
6501
- }
6502
- return top;
7015
+ return traverseNodeTree(
7016
+ nodeId,
7017
+ (id) => this.getNode(id),
7018
+ (currentNodeId, _node, currentTop) => {
7019
+ const block = this.blocks.get(currentNodeId);
7020
+ if (!block) {
7021
+ return currentTop;
7022
+ }
7023
+ block.box.top = currentTop;
7024
+ return currentTop + getBlockHeight(block);
7025
+ },
7026
+ top
7027
+ );
6503
7028
  }
6504
7029
  /**
6505
7030
  * Scroll container to keep selected block visible.
@@ -6566,13 +7091,25 @@ ${indicator}`;
6566
7091
  * Used for mode switches where we need to ensure the screen is fully cleared.
6567
7092
  */
6568
7093
  rebuildBlocksImmediate() {
7094
+ this.rebuildBlocksCore(true);
7095
+ }
7096
+ /**
7097
+ * Shared implementation for rebuildBlocks and rebuildBlocksImmediate.
7098
+ *
7099
+ * @param immediate - When true, forces a synchronous render pass before
7100
+ * and after building blocks to clear visual artifacts (used on mode switch).
7101
+ * When false, defers a single render to the end via renderCallback.
7102
+ */
7103
+ rebuildBlocksCore(immediate) {
6569
7104
  for (const child of [...this.container.children]) {
6570
7105
  child.detach();
6571
7106
  }
6572
7107
  this.container.setContent("");
6573
7108
  this.blocks.clear();
6574
7109
  this.selectableIds = [];
6575
- this.renderNowCallback();
7110
+ if (immediate) {
7111
+ this.renderNowCallback();
7112
+ }
6576
7113
  let top = 0;
6577
7114
  for (const rootId of this.nodeStore.rootIds) {
6578
7115
  top = this.renderNodeTree(rootId, top);
@@ -6581,7 +7118,11 @@ ${indicator}`;
6581
7118
  this.selectedIndex = this.selectableIds.length - 1;
6582
7119
  }
6583
7120
  this.applyBottomAlignmentAndScroll();
6584
- this.renderNowCallback();
7121
+ if (immediate) {
7122
+ this.renderNowCallback();
7123
+ } else {
7124
+ this.renderCallback();
7125
+ }
6585
7126
  this.notifyHasContentChange();
6586
7127
  }
6587
7128
  /**
@@ -6593,38 +7134,15 @@ ${indicator}`;
6593
7134
  /**
6594
7135
  * Check if a node should be visible in the current content filter mode.
6595
7136
  *
6596
- * In focused mode:
6597
- * - Text nodes are always visible
6598
- * - LLM call blocks are hidden
6599
- * - Gadget blocks are hidden EXCEPT TellUser and AskUser
6600
- * (Finish is hidden - status bar indicates completion)
7137
+ * In focused mode, only text and user-facing gadgets remain visible.
7138
+ * TellUser, AskUser, and Finish render as plain text for a chat-like view.
6601
7139
  */
6602
7140
  isNodeVisible(node) {
6603
- if (this.contentFilterMode === "full") {
6604
- return true;
6605
- }
6606
- switch (node.type) {
6607
- case "text":
6608
- return true;
6609
- case "llm_call":
6610
- return false;
6611
- case "thinking":
6612
- return false;
6613
- case "gadget": {
6614
- const name = node.name;
6615
- return name === "TellUser" || name === "AskUser" || name === "Finish";
6616
- }
6617
- default:
6618
- return false;
6619
- }
7141
+ return isNodeVisibleInFilterMode(node, this.contentFilterMode);
6620
7142
  }
6621
7143
  // ───────────────────────────────────────────────────────────────────────────
6622
7144
  // ExecutionTree Integration
6623
7145
  // ───────────────────────────────────────────────────────────────────────────
6624
- /** Unsubscribe function for tree events */
6625
- treeUnsubscribe = null;
6626
- /** Map tree node IDs to block node IDs */
6627
- treeNodeToBlockId = /* @__PURE__ */ new Map();
6628
7146
  /**
6629
7147
  * Subscribe to an ExecutionTree for automatic block updates.
6630
7148
  *
@@ -6648,121 +7166,14 @@ ${indicator}`;
6648
7166
  * ```
6649
7167
  */
6650
7168
  subscribeToTree(tree) {
6651
- if (this.treeUnsubscribe) {
6652
- this.treeUnsubscribe();
6653
- }
6654
- this.treeNodeToBlockId.clear();
6655
- this.nodeStore.clearIdempotencyMaps();
6656
- this.treeUnsubscribe = tree.onAll((event) => {
6657
- this.handleTreeEvent(event, tree);
6658
- });
6659
- return () => {
6660
- if (this.treeUnsubscribe) {
6661
- this.treeUnsubscribe();
6662
- this.treeUnsubscribe = null;
6663
- }
6664
- };
6665
- }
6666
- /**
6667
- * Handle an ExecutionTree event.
6668
- */
6669
- handleTreeEvent(event, tree) {
6670
- switch (event.type) {
6671
- case "llm_call_start": {
6672
- this.nodeStore.resetCurrentThinking();
6673
- let parentBlockId;
6674
- if (event.parentId) {
6675
- parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6676
- }
6677
- const blockId = this.addLLMCall(
6678
- event.iteration + 1,
6679
- event.model,
6680
- parentBlockId,
6681
- event.depth > 0
6682
- );
6683
- this.treeNodeToBlockId.set(event.nodeId, blockId);
6684
- const startNode = tree.getNode(event.nodeId);
6685
- if (startNode?.type === "llm_call" && startNode.request) {
6686
- this.setLLMCallRequest(blockId, startNode.request);
6687
- }
6688
- break;
6689
- }
6690
- case "llm_call_complete": {
6691
- this.completeThinking();
6692
- const blockId = this.treeNodeToBlockId.get(event.nodeId);
6693
- if (blockId) {
6694
- this.completeLLMCall(blockId, {
6695
- inputTokens: event.usage?.inputTokens,
6696
- cachedInputTokens: event.usage?.cachedInputTokens,
6697
- outputTokens: event.usage?.outputTokens,
6698
- reasoningTokens: event.usage?.reasoningTokens,
6699
- cost: event.cost,
6700
- finishReason: event.finishReason ?? void 0
6701
- });
6702
- const completeNode = tree.getNode(event.nodeId);
6703
- if (completeNode?.type === "llm_call" && completeNode.response) {
6704
- this.setLLMCallResponse(blockId, completeNode.response);
6705
- }
6706
- }
6707
- break;
6708
- }
6709
- case "thinking": {
6710
- this.addThinking(event.content, event.thinkingType);
6711
- break;
6712
- }
6713
- case "gadget_call": {
6714
- let parentBlockId;
6715
- if (event.parentId) {
6716
- parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6717
- }
6718
- const previousLLMCallId = this.getCurrentLLMCallId();
6719
- if (parentBlockId) {
6720
- this.setCurrentLLMCall(parentBlockId);
6721
- }
6722
- const blockId = this.addGadget(event.invocationId, event.name, event.parameters);
6723
- this.treeNodeToBlockId.set(event.nodeId, blockId);
6724
- this.setCurrentLLMCall(previousLLMCallId);
6725
- break;
6726
- }
6727
- case "gadget_complete": {
6728
- const mediaOutputs = event.storedMedia?.map((m) => ({
6729
- kind: m.kind,
6730
- path: m.path,
6731
- mimeType: m.mimeType,
6732
- description: m.description
6733
- }));
6734
- this.completeGadget(event.invocationId, {
6735
- result: event.result,
6736
- executionTimeMs: event.executionTimeMs,
6737
- cost: event.cost,
6738
- mediaOutputs
6739
- });
6740
- break;
6741
- }
6742
- case "gadget_error": {
6743
- this.completeGadget(event.invocationId, {
6744
- error: event.error,
6745
- executionTimeMs: event.executionTimeMs
6746
- });
6747
- break;
6748
- }
6749
- case "gadget_skipped": {
6750
- const node = this.findGadgetByInvocationId(event.invocationId);
6751
- if (node) {
6752
- node.isComplete = true;
6753
- node.error = `Skipped: ${event.failedDependencyError}`;
6754
- this.updateBlock(node.id);
6755
- }
6756
- break;
6757
- }
6758
- }
7169
+ return this.treeBridge.subscribeToTree(tree);
6759
7170
  }
6760
7171
  /**
6761
7172
  * Get block ID for a tree node ID.
6762
7173
  * Useful for external code that needs to correlate tree nodes with blocks.
6763
7174
  */
6764
7175
  getBlockIdForTreeNode(treeNodeId) {
6765
- return this.treeNodeToBlockId.get(treeNodeId);
7176
+ return this.treeBridge.getBlockIdForTreeNode(treeNodeId);
6766
7177
  }
6767
7178
  };
6768
7179
 
@@ -7055,6 +7466,26 @@ var TUIController = class {
7055
7466
  }
7056
7467
  };
7057
7468
 
7469
+ // src/tui/event-router.ts
7470
+ var EventRouter = class {
7471
+ constructor(blockRenderer) {
7472
+ this.blockRenderer = blockRenderer;
7473
+ }
7474
+ /**
7475
+ * Route an agent stream event to the appropriate renderer method.
7476
+ *
7477
+ * Only handles text/thinking events - gadgets and LLM calls are managed
7478
+ * via ExecutionTree subscription in TreeSubscriptionManager.
7479
+ */
7480
+ handleEvent(event) {
7481
+ if (event.type === "text") {
7482
+ this.blockRenderer.addText(event.content);
7483
+ } else if (event.type === "thinking") {
7484
+ this.blockRenderer.addThinking(event.content, event.thinkingType);
7485
+ }
7486
+ }
7487
+ };
7488
+
7058
7489
  // src/tui/hints-bar.ts
7059
7490
  var GRAY = "\x1B[90m";
7060
7491
  var RESET = "\x1B[0m";
@@ -7137,10 +7568,10 @@ var HintsBar = class {
7137
7568
  import { spawnSync as spawnSync2 } from "child_process";
7138
7569
  import { readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7139
7570
  import { tmpdir } from "os";
7140
- import { join as join2 } from "path";
7571
+ import { join as join3 } from "path";
7141
7572
  function openEditorSync(initialContent = "") {
7142
7573
  const editor = process.env.VISUAL || process.env.EDITOR || "vi";
7143
- const tmpFile = join2(tmpdir(), `llmist-input-${Date.now()}.txt`);
7574
+ const tmpFile = join3(tmpdir(), `llmist-input-${Date.now()}.txt`);
7144
7575
  writeFileSync2(tmpFile, initialContent, "utf-8");
7145
7576
  try {
7146
7577
  const parts = editor.split(/\s+/);
@@ -7375,11 +7806,11 @@ var InputHandler = class {
7375
7806
  * @returns Promise that resolves with user's response
7376
7807
  */
7377
7808
  async waitForInput(question, gadgetName) {
7378
- return new Promise((resolve3, reject) => {
7809
+ return new Promise((resolve4, reject) => {
7379
7810
  this.pendingInput = {
7380
7811
  question,
7381
7812
  gadgetName,
7382
- resolve: resolve3,
7813
+ resolve: resolve4,
7383
7814
  reject
7384
7815
  };
7385
7816
  this.setActive();
@@ -7394,11 +7825,11 @@ var InputHandler = class {
7394
7825
  * @returns Promise that resolves with user's prompt
7395
7826
  */
7396
7827
  async waitForPrompt() {
7397
- return new Promise((resolve3, reject) => {
7828
+ return new Promise((resolve4, reject) => {
7398
7829
  this.pendingInput = {
7399
7830
  question: "",
7400
7831
  gadgetName: "prompt",
7401
- resolve: resolve3,
7832
+ resolve: resolve4,
7402
7833
  reject
7403
7834
  };
7404
7835
  this.setPendingPrompt();
@@ -7494,10 +7925,10 @@ var InputHandler = class {
7494
7925
  return;
7495
7926
  }
7496
7927
  if (this.pendingInput) {
7497
- const { resolve: resolve3 } = this.pendingInput;
7928
+ const { resolve: resolve4 } = this.pendingInput;
7498
7929
  this.pendingInput = null;
7499
7930
  this.setIdle();
7500
- resolve3(value);
7931
+ resolve4(value);
7501
7932
  } else if (this.midSessionHandler) {
7502
7933
  this.midSessionHandler(value);
7503
7934
  this.setIdle();
@@ -7657,6 +8088,104 @@ var InputHandler = class {
7657
8088
  }
7658
8089
  };
7659
8090
 
8091
+ // src/tui/key-action-handler.ts
8092
+ var KeyActionHandler = class {
8093
+ constructor(controller, blockRenderer, statusBar, screenCtx, modalManager, layout) {
8094
+ this.controller = controller;
8095
+ this.blockRenderer = blockRenderer;
8096
+ this.statusBar = statusBar;
8097
+ this.screenCtx = screenCtx;
8098
+ this.modalManager = modalManager;
8099
+ this.layout = layout;
8100
+ }
8101
+ /**
8102
+ * Handle high-level keyboard actions from KeyboardManager or InputHandler.
8103
+ */
8104
+ handleKeyAction(action) {
8105
+ switch (action.type) {
8106
+ case "ctrl_c": {
8107
+ const result = this.controller.handleCtrlC();
8108
+ if (result === "show_hint") {
8109
+ this.blockRenderer.addText("\n[Press Ctrl+C again to quit]\n");
8110
+ } else if (result === "quit") {
8111
+ process.exit(130);
8112
+ }
8113
+ break;
8114
+ }
8115
+ case "cancel":
8116
+ this.controller.triggerCancel();
8117
+ this.controller.abort();
8118
+ break;
8119
+ case "toggle_focus_mode":
8120
+ this.controller.toggleFocusMode();
8121
+ break;
8122
+ case "toggle_content_filter":
8123
+ this.controller.toggleContentFilterMode();
8124
+ break;
8125
+ case "cycle_profile":
8126
+ this.statusBar.cycleProfile();
8127
+ break;
8128
+ case "scroll_page": {
8129
+ const body = this.layout.body;
8130
+ if (!body.scroll) return;
8131
+ const containerHeight = body.height;
8132
+ const scrollAmount = Math.max(1, containerHeight - 2);
8133
+ if (action.direction < 0) {
8134
+ body.scroll(-scrollAmount);
8135
+ } else {
8136
+ body.scroll(scrollAmount);
8137
+ }
8138
+ this.blockRenderer.handleUserScroll();
8139
+ this.screenCtx.renderNow();
8140
+ break;
8141
+ }
8142
+ case "scroll_line": {
8143
+ const body = this.layout.body;
8144
+ if (!body.scroll) return;
8145
+ body.scroll(action.direction);
8146
+ this.blockRenderer.handleUserScroll();
8147
+ this.screenCtx.renderNow();
8148
+ break;
8149
+ }
8150
+ case "navigation":
8151
+ switch (action.action) {
8152
+ case "select_next":
8153
+ this.blockRenderer.selectNext();
8154
+ break;
8155
+ case "select_previous":
8156
+ this.blockRenderer.selectPrevious();
8157
+ break;
8158
+ case "select_first":
8159
+ this.blockRenderer.selectFirst();
8160
+ break;
8161
+ case "select_last":
8162
+ this.blockRenderer.selectLast();
8163
+ this.blockRenderer.enableFollowMode();
8164
+ break;
8165
+ case "toggle_expand":
8166
+ this.blockRenderer.toggleExpand();
8167
+ break;
8168
+ case "collapse":
8169
+ this.blockRenderer.collapseOrDeselect();
8170
+ break;
8171
+ }
8172
+ this.screenCtx.renderNow();
8173
+ break;
8174
+ case "raw_viewer":
8175
+ void (async () => {
8176
+ const selected = this.blockRenderer.getSelectedBlock();
8177
+ if (!selected) return;
8178
+ if (!isRawViewerNode(selected.node)) return;
8179
+ await this.modalManager.showRawViewer(
8180
+ this.screenCtx.screen,
8181
+ createRawViewerData(selected.node, action.mode)
8182
+ );
8183
+ })();
8184
+ break;
8185
+ }
8186
+ }
8187
+ };
8188
+
7660
8189
  // src/tui/keymap.ts
7661
8190
  var KeyboardManager = class {
7662
8191
  config;
@@ -7857,7 +8386,7 @@ import { Box as Box3 } from "@unblessed/node";
7857
8386
  var MAX_PREVIEW_LINES = 10;
7858
8387
  var MAX_PARAM_VALUE_LENGTH = 60;
7859
8388
  function showApprovalDialog(screen, context) {
7860
- return new Promise((resolve3) => {
8389
+ return new Promise((resolve4) => {
7861
8390
  const content = buildDialogContent(context);
7862
8391
  const dialog = new Box3({
7863
8392
  parent: screen,
@@ -7912,7 +8441,7 @@ function showApprovalDialog(screen, context) {
7912
8441
  if (response) {
7913
8442
  dialog.destroy();
7914
8443
  screen.render();
7915
- resolve3(response);
8444
+ resolve4(response);
7916
8445
  }
7917
8446
  };
7918
8447
  dialog.on("keypress", handleKey);
@@ -7982,7 +8511,7 @@ var WHITE = "\x1B[37m";
7982
8511
  function showRawViewer(options) {
7983
8512
  let closeCallback = () => {
7984
8513
  };
7985
- const closed = new Promise((resolve3) => {
8514
+ const closed = new Promise((resolve4) => {
7986
8515
  const {
7987
8516
  screen,
7988
8517
  mode,
@@ -8078,7 +8607,7 @@ ${error}`;
8078
8607
  helpBar.destroy();
8079
8608
  viewer.destroy();
8080
8609
  screen.render();
8081
- resolve3();
8610
+ resolve4();
8082
8611
  };
8083
8612
  closeCallback = close;
8084
8613
  viewer.key(["escape", "q"], close);
@@ -8354,6 +8883,47 @@ function createScreen(options) {
8354
8883
  };
8355
8884
  }
8356
8885
 
8886
+ // src/tui/session-manager.ts
8887
+ var SessionManager = class {
8888
+ constructor(blockRenderer, statusBar) {
8889
+ this.blockRenderer = blockRenderer;
8890
+ this.statusBar = statusBar;
8891
+ }
8892
+ /**
8893
+ * Start a new session. Called at the start of each REPL turn.
8894
+ */
8895
+ startNewSession() {
8896
+ this.blockRenderer.startNewSession();
8897
+ }
8898
+ /**
8899
+ * Clear blocks from the previous session only.
8900
+ * Keeps current content visible during the session.
8901
+ */
8902
+ clearPreviousSession() {
8903
+ this.blockRenderer.clearPreviousSession();
8904
+ }
8905
+ /**
8906
+ * Clear all blocks and reset BlockRenderer state.
8907
+ * Prevents memory leaks between iterations.
8908
+ */
8909
+ clearAllBlocks() {
8910
+ this.blockRenderer.clear();
8911
+ }
8912
+ /**
8913
+ * Clear status bar activity state.
8914
+ */
8915
+ clearStatusBar() {
8916
+ this.statusBar.clearActivity();
8917
+ }
8918
+ /**
8919
+ * Full cleanup between REPL iterations.
8920
+ */
8921
+ resetAll() {
8922
+ this.clearAllBlocks();
8923
+ this.clearStatusBar();
8924
+ }
8925
+ };
8926
+
8357
8927
  // src/tui/status-bar.ts
8358
8928
  var CHARS_PER_TOKEN = 4;
8359
8929
  var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -8880,6 +9450,195 @@ var StatusBar = class {
8880
9450
  }
8881
9451
  };
8882
9452
 
9453
+ // src/tui/tree-subscription-manager.ts
9454
+ var TreeSubscriptionManager = class {
9455
+ constructor(blockRenderer, statusBar) {
9456
+ this.blockRenderer = blockRenderer;
9457
+ this.statusBar = statusBar;
9458
+ }
9459
+ treeUnsubscribe = null;
9460
+ /**
9461
+ * Subscribe to an ExecutionTree for automatic updates.
9462
+ * Handles previous unsubscription and returns a combined unsubscribe function.
9463
+ */
9464
+ subscribe(tree) {
9465
+ this.unsubscribe();
9466
+ const unsubBlock = this.blockRenderer.subscribeToTree(tree);
9467
+ const unsubStatus = this.statusBar.subscribeToTree(tree);
9468
+ this.treeUnsubscribe = () => {
9469
+ unsubBlock();
9470
+ unsubStatus();
9471
+ };
9472
+ return () => this.unsubscribe();
9473
+ }
9474
+ /**
9475
+ * Unsubscribe from the current tree if any.
9476
+ */
9477
+ unsubscribe() {
9478
+ if (this.treeUnsubscribe) {
9479
+ this.treeUnsubscribe();
9480
+ this.treeUnsubscribe = null;
9481
+ }
9482
+ }
9483
+ };
9484
+
9485
+ // src/tui/tui-app-effects.ts
9486
+ function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
9487
+ statusBar.setFocusMode(mode);
9488
+ if (mode === "input") {
9489
+ inputHandler.activate();
9490
+ } else {
9491
+ inputHandler.deactivate();
9492
+ layout.body.focus();
9493
+ }
9494
+ screenCtx.renderNow();
9495
+ }
9496
+ function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
9497
+ blockRenderer.setContentFilterMode(mode);
9498
+ statusBar.setContentFilterMode(mode);
9499
+ screenCtx.renderNow();
9500
+ }
9501
+
9502
+ // src/tui/tui-app-bootstrap.ts
9503
+ function createTUIAppDependencies(options) {
9504
+ const screenCtx = createScreen({
9505
+ stdin: options.stdin,
9506
+ stdout: options.stdout,
9507
+ title: "llmist"
9508
+ });
9509
+ const { screen } = screenCtx;
9510
+ const showHints = options.showHints ?? true;
9511
+ const layout = createBlockLayout(screen, showHints);
9512
+ const hintsBar = createHintsBar(layout, screenCtx, showHints);
9513
+ const statusBar = createStatusBar(layout, options.model, screenCtx);
9514
+ const inputHandler = createInputHandler(layout, screenCtx, screen, showHints);
9515
+ const blockRenderer = createBlockRenderer(layout, screenCtx);
9516
+ wireHintsBar(blockRenderer, hintsBar);
9517
+ const controller = createController(
9518
+ layout,
9519
+ statusBar,
9520
+ inputHandler,
9521
+ screenCtx,
9522
+ blockRenderer,
9523
+ hintsBar
9524
+ );
9525
+ const modalManager = new ModalManager();
9526
+ const keyActionHandler = new KeyActionHandler(
9527
+ controller,
9528
+ blockRenderer,
9529
+ statusBar,
9530
+ screenCtx,
9531
+ modalManager,
9532
+ layout
9533
+ );
9534
+ const keyboardManager = new KeyboardManager({
9535
+ screen,
9536
+ getFocusMode: () => controller.getFocusMode(),
9537
+ getContentFilterMode: () => controller.getContentFilterMode(),
9538
+ isWaitingForREPLPrompt: () => inputHandler.isWaitingForREPLPrompt(),
9539
+ hasPendingInput: () => inputHandler.hasPendingInput(),
9540
+ isBlockExpanded: () => blockRenderer.getSelectedBlock()?.expanded ?? false,
9541
+ onAction: (action) => {
9542
+ keyActionHandler.handleKeyAction(action);
9543
+ }
9544
+ });
9545
+ const subscriptionManager = new TreeSubscriptionManager(blockRenderer, statusBar);
9546
+ const sessionManager = new SessionManager(blockRenderer, statusBar);
9547
+ const eventRouter = new EventRouter(blockRenderer);
9548
+ wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller);
9549
+ wireScreenEvents(layout, screen, blockRenderer);
9550
+ keyboardManager.setup();
9551
+ applyFocusMode(controller.getFocusMode(), layout, statusBar, inputHandler, screenCtx);
9552
+ screenCtx.requestRender();
9553
+ return {
9554
+ screenCtx,
9555
+ statusBar,
9556
+ inputHandler,
9557
+ blockRenderer,
9558
+ controller,
9559
+ modalManager,
9560
+ subscriptionManager,
9561
+ sessionManager,
9562
+ eventRouter
9563
+ };
9564
+ }
9565
+ function createHintsBar(layout, screenCtx, showHints) {
9566
+ if (!showHints || !layout.hintsBar) {
9567
+ return null;
9568
+ }
9569
+ return new HintsBar(layout.hintsBar, () => screenCtx.requestRender());
9570
+ }
9571
+ function createStatusBar(layout, model, screenCtx) {
9572
+ return new StatusBar(
9573
+ layout.statusBar,
9574
+ model,
9575
+ () => screenCtx.requestRender(),
9576
+ () => screenCtx.renderNow()
9577
+ );
9578
+ }
9579
+ function createInputHandler(layout, screenCtx, screen, showHints) {
9580
+ return new InputHandler(
9581
+ layout.inputBar,
9582
+ layout.promptLabel,
9583
+ layout.body,
9584
+ screen,
9585
+ () => screenCtx.requestRender(),
9586
+ () => screenCtx.renderNow(),
9587
+ showHints
9588
+ );
9589
+ }
9590
+ function createBlockRenderer(layout, screenCtx) {
9591
+ return new BlockRenderer(
9592
+ layout.body,
9593
+ () => screenCtx.requestRender(),
9594
+ () => screenCtx.renderNow()
9595
+ );
9596
+ }
9597
+ function wireHintsBar(blockRenderer, hintsBar) {
9598
+ if (!hintsBar) {
9599
+ return;
9600
+ }
9601
+ blockRenderer.onHasContentChange((hasContent) => {
9602
+ hintsBar.setHasContent(hasContent);
9603
+ });
9604
+ }
9605
+ function createController(layout, statusBar, inputHandler, screenCtx, blockRenderer, hintsBar) {
9606
+ return new TUIController({
9607
+ onFocusModeChange: (mode) => {
9608
+ applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
9609
+ hintsBar?.setFocusMode(mode);
9610
+ },
9611
+ onContentFilterModeChange: (mode) => {
9612
+ applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
9613
+ hintsBar?.setContentFilterMode(mode);
9614
+ }
9615
+ });
9616
+ }
9617
+ function wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller) {
9618
+ inputHandler.onCtrlC(() => keyboardManager.handleForwardedKey("C-c"));
9619
+ inputHandler.onCtrlB(() => keyboardManager.handleForwardedKey("C-b"));
9620
+ inputHandler.onCtrlK(() => keyboardManager.handleForwardedKey("C-k"));
9621
+ inputHandler.onCtrlI(() => keyboardManager.handleForwardedKey("C-i"));
9622
+ inputHandler.onCtrlJ(() => keyboardManager.handleForwardedKey("C-j"));
9623
+ inputHandler.onCtrlP(() => keyboardManager.handleForwardedKey("C-p"));
9624
+ inputHandler.onArrowUp(() => {
9625
+ keyActionHandler.handleKeyAction({ type: "scroll_line", direction: -1 });
9626
+ });
9627
+ inputHandler.onArrowDown(() => {
9628
+ keyActionHandler.handleKeyAction({ type: "scroll_line", direction: 1 });
9629
+ });
9630
+ inputHandler.setGetFocusMode(() => controller.getFocusMode());
9631
+ inputHandler.setGetContentFilterMode(() => controller.getContentFilterMode());
9632
+ }
9633
+ function wireScreenEvents(layout, screen, blockRenderer) {
9634
+ layout.body.on("scroll", () => {
9635
+ blockRenderer.handleUserScroll();
9636
+ });
9637
+ screen.on("resize", () => {
9638
+ blockRenderer.handleResize();
9639
+ });
9640
+ }
9641
+
8883
9642
  // src/tui/index.ts
8884
9643
  var TUIApp = class _TUIApp {
8885
9644
  screenCtx;
@@ -8889,135 +9648,36 @@ var TUIApp = class _TUIApp {
8889
9648
  // New extracted components
8890
9649
  controller;
8891
9650
  modalManager;
8892
- /** Unsubscribe function for tree subscription */
8893
- treeUnsubscribe = null;
8894
- constructor(screenCtx, statusBar, inputHandler, blockRenderer, controller, modalManager) {
9651
+ subscriptionManager;
9652
+ sessionManager;
9653
+ eventRouter;
9654
+ constructor(screenCtx, statusBar, inputHandler, blockRenderer, controller, modalManager, subscriptionManager, sessionManager, eventRouter) {
8895
9655
  this.screenCtx = screenCtx;
8896
9656
  this.statusBar = statusBar;
8897
9657
  this.inputHandler = inputHandler;
8898
9658
  this.blockRenderer = blockRenderer;
8899
9659
  this.controller = controller;
8900
9660
  this.modalManager = modalManager;
9661
+ this.subscriptionManager = subscriptionManager;
9662
+ this.sessionManager = sessionManager;
9663
+ this.eventRouter = eventRouter;
8901
9664
  }
8902
9665
  /**
8903
9666
  * Create a new TUI application instance.
8904
9667
  */
8905
9668
  static async create(options) {
8906
- const screenCtx = createScreen({
8907
- stdin: options.stdin,
8908
- stdout: options.stdout,
8909
- title: "llmist"
8910
- });
8911
- const { screen } = screenCtx;
8912
- const showHints = options.showHints ?? true;
8913
- const layout = createBlockLayout(screen, showHints);
8914
- let hintsBar = null;
8915
- if (layout.hintsBar) {
8916
- hintsBar = new HintsBar(layout.hintsBar, () => screenCtx.requestRender());
8917
- }
8918
- const statusBar = new StatusBar(
8919
- layout.statusBar,
8920
- options.model,
8921
- () => screenCtx.requestRender(),
8922
- () => screenCtx.renderNow()
8923
- );
8924
- const inputHandler = new InputHandler(
8925
- layout.inputBar,
8926
- layout.promptLabel,
8927
- layout.body,
8928
- screen,
8929
- () => screenCtx.requestRender(),
8930
- () => screenCtx.renderNow(),
8931
- showHints
8932
- );
8933
- const blockRenderer = new BlockRenderer(
8934
- layout.body,
8935
- () => screenCtx.requestRender(),
8936
- () => screenCtx.renderNow()
8937
- );
8938
- if (hintsBar) {
8939
- blockRenderer.onHasContentChange((hasContent) => {
8940
- hintsBar.setHasContent(hasContent);
8941
- });
8942
- }
8943
- const controller = new TUIController({
8944
- onFocusModeChange: (mode) => {
8945
- applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
8946
- hintsBar?.setFocusMode(mode);
8947
- },
8948
- onContentFilterModeChange: (mode) => {
8949
- applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
8950
- hintsBar?.setContentFilterMode(mode);
8951
- }
8952
- });
8953
- const modalManager = new ModalManager();
8954
- const keyboardManager = new KeyboardManager({
8955
- screen,
8956
- getFocusMode: () => controller.getFocusMode(),
8957
- getContentFilterMode: () => controller.getContentFilterMode(),
8958
- isWaitingForREPLPrompt: () => inputHandler.isWaitingForREPLPrompt(),
8959
- hasPendingInput: () => inputHandler.hasPendingInput(),
8960
- isBlockExpanded: () => blockRenderer.getSelectedBlock()?.expanded ?? false,
8961
- onAction: (action) => {
8962
- handleKeyAction(
8963
- action,
8964
- controller,
8965
- blockRenderer,
8966
- statusBar,
8967
- screenCtx,
8968
- modalManager,
8969
- layout
8970
- );
8971
- }
8972
- });
8973
- const app = new _TUIApp(
8974
- screenCtx,
8975
- statusBar,
8976
- inputHandler,
8977
- blockRenderer,
8978
- controller,
8979
- modalManager
9669
+ const dependencies = createTUIAppDependencies(options);
9670
+ return new _TUIApp(
9671
+ dependencies.screenCtx,
9672
+ dependencies.statusBar,
9673
+ dependencies.inputHandler,
9674
+ dependencies.blockRenderer,
9675
+ dependencies.controller,
9676
+ dependencies.modalManager,
9677
+ dependencies.subscriptionManager,
9678
+ dependencies.sessionManager,
9679
+ dependencies.eventRouter
8980
9680
  );
8981
- keyboardManager.setup();
8982
- inputHandler.onCtrlC(() => keyboardManager.handleForwardedKey("C-c"));
8983
- inputHandler.onCtrlB(() => keyboardManager.handleForwardedKey("C-b"));
8984
- inputHandler.onCtrlK(() => keyboardManager.handleForwardedKey("C-k"));
8985
- inputHandler.onCtrlI(() => keyboardManager.handleForwardedKey("C-i"));
8986
- inputHandler.onCtrlJ(() => keyboardManager.handleForwardedKey("C-j"));
8987
- inputHandler.onCtrlP(() => keyboardManager.handleForwardedKey("C-p"));
8988
- inputHandler.onArrowUp(() => {
8989
- handleKeyAction(
8990
- { type: "scroll_line", direction: -1 },
8991
- controller,
8992
- blockRenderer,
8993
- statusBar,
8994
- screenCtx,
8995
- modalManager,
8996
- layout
8997
- );
8998
- });
8999
- inputHandler.onArrowDown(() => {
9000
- handleKeyAction(
9001
- { type: "scroll_line", direction: 1 },
9002
- controller,
9003
- blockRenderer,
9004
- statusBar,
9005
- screenCtx,
9006
- modalManager,
9007
- layout
9008
- );
9009
- });
9010
- inputHandler.setGetFocusMode(() => controller.getFocusMode());
9011
- inputHandler.setGetContentFilterMode(() => controller.getContentFilterMode());
9012
- layout.body.on("scroll", () => {
9013
- blockRenderer.handleUserScroll();
9014
- });
9015
- screen.on("resize", () => {
9016
- blockRenderer.handleResize();
9017
- });
9018
- applyFocusMode(controller.getFocusMode(), layout, statusBar, inputHandler, screenCtx);
9019
- screenCtx.requestRender();
9020
- return app;
9021
9681
  }
9022
9682
  // ─────────────────────────────────────────────────────────────────────────────
9023
9683
  // Focus Mode Management (delegated to controller)
@@ -9053,11 +9713,7 @@ var TUIApp = class _TUIApp {
9053
9713
  * automatically by ExecutionTree subscription via subscribeToTree().
9054
9714
  */
9055
9715
  handleEvent(event) {
9056
- if (event.type === "text") {
9057
- this.blockRenderer.addText(event.content);
9058
- } else if (event.type === "thinking") {
9059
- this.blockRenderer.addThinking(event.content, event.thinkingType);
9060
- }
9716
+ this.eventRouter.handleEvent(event);
9061
9717
  }
9062
9718
  /**
9063
9719
  * Show an LLM call starting.
@@ -9082,26 +9738,11 @@ var TUIApp = class _TUIApp {
9082
9738
  async showRawViewer(mode) {
9083
9739
  if (this.controller.getFocusMode() !== "browse") return;
9084
9740
  const selected = this.blockRenderer.getSelectedBlock();
9085
- if (!selected) return;
9086
- if (selected.node.type === "llm_call") {
9087
- const node = selected.node;
9088
- await this.modalManager.showRawViewer(this.screenCtx.screen, {
9089
- mode,
9090
- request: node.rawRequest,
9091
- response: node.rawResponse,
9092
- iteration: node.iteration,
9093
- model: node.model
9094
- });
9095
- } else if (selected.node.type === "gadget") {
9096
- const node = selected.node;
9097
- await this.modalManager.showRawViewer(this.screenCtx.screen, {
9098
- mode,
9099
- gadgetName: node.name,
9100
- parameters: node.parameters,
9101
- result: node.result,
9102
- error: node.error
9103
- });
9104
- }
9741
+ if (!selected || !isRawViewerNode(selected.node)) return;
9742
+ await this.modalManager.showRawViewer(
9743
+ this.screenCtx.screen,
9744
+ createRawViewerData(selected.node, mode)
9745
+ );
9105
9746
  }
9106
9747
  /**
9107
9748
  * Show approval dialog for gadget execution.
@@ -9156,21 +9797,7 @@ var TUIApp = class _TUIApp {
9156
9797
  * Subscribe to an ExecutionTree for automatic block updates.
9157
9798
  */
9158
9799
  subscribeToTree(tree) {
9159
- if (this.treeUnsubscribe) {
9160
- this.treeUnsubscribe();
9161
- }
9162
- const unsubBlock = this.blockRenderer.subscribeToTree(tree);
9163
- const unsubStatus = this.statusBar.subscribeToTree(tree);
9164
- this.treeUnsubscribe = () => {
9165
- unsubBlock();
9166
- unsubStatus();
9167
- };
9168
- return () => {
9169
- if (this.treeUnsubscribe) {
9170
- this.treeUnsubscribe();
9171
- this.treeUnsubscribe = null;
9172
- }
9173
- };
9800
+ return this.subscriptionManager.subscribe(tree);
9174
9801
  }
9175
9802
  // ─────────────────────────────────────────────────────────────────────────────
9176
9803
  // Memory Cleanup (REPL mode)
@@ -9182,21 +9809,21 @@ var TUIApp = class _TUIApp {
9182
9809
  * Call this after each agent run completes (after unsubscribing from tree).
9183
9810
  */
9184
9811
  clearBlockRenderer() {
9185
- this.blockRenderer.clear();
9812
+ this.sessionManager.clearAllBlocks();
9186
9813
  }
9187
9814
  /**
9188
9815
  * Clear status bar activity state.
9189
9816
  * Called between REPL turns to prevent stale state.
9190
9817
  */
9191
9818
  clearStatusBar() {
9192
- this.statusBar.clearActivity();
9819
+ this.sessionManager.clearStatusBar();
9193
9820
  }
9194
9821
  /**
9195
9822
  * Start a new session. Called at the start of each REPL turn.
9196
9823
  * Increments the session counter so new blocks get the new sessionId.
9197
9824
  */
9198
9825
  startNewSession() {
9199
- this.blockRenderer.startNewSession();
9826
+ this.sessionManager.startNewSession();
9200
9827
  }
9201
9828
  /**
9202
9829
  * Clear blocks from the previous session only.
@@ -9204,7 +9831,7 @@ var TUIApp = class _TUIApp {
9204
9831
  * The previous session's content was kept visible during this session for context.
9205
9832
  */
9206
9833
  clearPreviousSession() {
9207
- this.blockRenderer.clearPreviousSession();
9834
+ this.sessionManager.clearPreviousSession();
9208
9835
  }
9209
9836
  // ─────────────────────────────────────────────────────────────────────────────
9210
9837
  // Abort Control (delegated to controller)
@@ -9339,127 +9966,12 @@ var TUIApp = class _TUIApp {
9339
9966
  * Clean up and restore terminal.
9340
9967
  */
9341
9968
  destroy() {
9342
- if (this.treeUnsubscribe) {
9343
- this.treeUnsubscribe();
9344
- this.treeUnsubscribe = null;
9345
- }
9969
+ this.subscriptionManager.unsubscribe();
9346
9970
  this.modalManager.closeAll();
9347
9971
  this.inputHandler.cancelPending();
9348
9972
  this.screenCtx.destroy();
9349
9973
  }
9350
9974
  };
9351
- function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
9352
- statusBar.setFocusMode(mode);
9353
- if (mode === "input") {
9354
- inputHandler.activate();
9355
- } else {
9356
- inputHandler.deactivate();
9357
- layout.body.focus();
9358
- }
9359
- screenCtx.renderNow();
9360
- }
9361
- function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
9362
- blockRenderer.setContentFilterMode(mode);
9363
- statusBar.setContentFilterMode(mode);
9364
- screenCtx.renderNow();
9365
- }
9366
- function handleKeyAction(action, controller, blockRenderer, statusBar, screenCtx, modalManager, layout) {
9367
- switch (action.type) {
9368
- case "ctrl_c": {
9369
- const result = controller.handleCtrlC();
9370
- if (result === "show_hint") {
9371
- blockRenderer.addText("\n[Press Ctrl+C again to quit]\n");
9372
- } else if (result === "quit") {
9373
- process.exit(130);
9374
- }
9375
- break;
9376
- }
9377
- case "cancel":
9378
- controller.triggerCancel();
9379
- controller.abort();
9380
- break;
9381
- case "toggle_focus_mode":
9382
- controller.toggleFocusMode();
9383
- break;
9384
- case "toggle_content_filter":
9385
- controller.toggleContentFilterMode();
9386
- break;
9387
- case "cycle_profile":
9388
- statusBar.cycleProfile();
9389
- break;
9390
- case "scroll_page": {
9391
- const body = layout.body;
9392
- if (!body.scroll) return;
9393
- const containerHeight = body.height;
9394
- const scrollAmount = Math.max(1, containerHeight - 2);
9395
- if (action.direction < 0) {
9396
- body.scroll(-scrollAmount);
9397
- } else {
9398
- body.scroll(scrollAmount);
9399
- }
9400
- blockRenderer.handleUserScroll();
9401
- screenCtx.renderNow();
9402
- break;
9403
- }
9404
- case "scroll_line": {
9405
- const body = layout.body;
9406
- if (!body.scroll) return;
9407
- body.scroll(action.direction);
9408
- blockRenderer.handleUserScroll();
9409
- screenCtx.renderNow();
9410
- break;
9411
- }
9412
- case "navigation":
9413
- switch (action.action) {
9414
- case "select_next":
9415
- blockRenderer.selectNext();
9416
- break;
9417
- case "select_previous":
9418
- blockRenderer.selectPrevious();
9419
- break;
9420
- case "select_first":
9421
- blockRenderer.selectFirst();
9422
- break;
9423
- case "select_last":
9424
- blockRenderer.selectLast();
9425
- blockRenderer.enableFollowMode();
9426
- break;
9427
- case "toggle_expand":
9428
- blockRenderer.toggleExpand();
9429
- break;
9430
- case "collapse":
9431
- blockRenderer.collapseOrDeselect();
9432
- break;
9433
- }
9434
- screenCtx.renderNow();
9435
- break;
9436
- case "raw_viewer":
9437
- void (async () => {
9438
- const selected = blockRenderer.getSelectedBlock();
9439
- if (!selected) return;
9440
- if (selected.node.type === "llm_call") {
9441
- const node = selected.node;
9442
- await modalManager.showRawViewer(screenCtx.screen, {
9443
- mode: action.mode,
9444
- request: node.rawRequest,
9445
- response: node.rawResponse,
9446
- iteration: node.iteration,
9447
- model: node.model
9448
- });
9449
- } else if (selected.node.type === "gadget") {
9450
- const node = selected.node;
9451
- await modalManager.showRawViewer(screenCtx.screen, {
9452
- mode: action.mode,
9453
- gadgetName: node.name,
9454
- parameters: node.parameters,
9455
- result: node.result,
9456
- error: node.error
9457
- });
9458
- }
9459
- })();
9460
- break;
9461
- }
9462
- }
9463
9975
 
9464
9976
  // src/agent-command.ts
9465
9977
  async function executeAgent(promptArg, options, env, commandName) {
@@ -9496,6 +10008,14 @@ async function executeAgent(promptArg, options, env, commandName) {
9496
10008
  registry.registerByClass(gadget);
9497
10009
  }
9498
10010
  }
10011
+ let skillsConfig;
10012
+ try {
10013
+ const fullConfig = loadConfig();
10014
+ skillsConfig = fullConfig.skills;
10015
+ } catch {
10016
+ }
10017
+ const skillManager = new CLISkillManager();
10018
+ const skillRegistry = await skillManager.loadAll(skillsConfig);
9499
10019
  let tui = null;
9500
10020
  if (useTUI) {
9501
10021
  tui = await TUIApp.create({
@@ -9534,7 +10054,7 @@ async function executeAgent(promptArg, options, env, commandName) {
9534
10054
  if (!useTUI) {
9535
10055
  process.once("SIGINT", () => process.exit(130));
9536
10056
  }
9537
- const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile"];
10057
+ const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile", "DeleteFile"];
9538
10058
  const userApprovals = options.gadgetApproval ?? {};
9539
10059
  const gadgetApprovals = {
9540
10060
  ...userApprovals
@@ -9671,6 +10191,11 @@ ${ctx.gadgetName} is denied by configuration.`
9671
10191
  gadgetName: ctx.gadgetName,
9672
10192
  parameters: ctx.parameters
9673
10193
  });
10194
+ if (response === "always") {
10195
+ gadgetApprovals[ctx.gadgetName] = "allowed";
10196
+ } else if (response === "deny") {
10197
+ gadgetApprovals[ctx.gadgetName] = "denied";
10198
+ }
9674
10199
  if (response === "yes" || response === "always") {
9675
10200
  return { action: "proceed" };
9676
10201
  }
@@ -9685,7 +10210,7 @@ Denied by user`
9685
10210
  action: "skip",
9686
10211
  syntheticResult: `status=denied
9687
10212
 
9688
- ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
10213
+ ${ctx.gadgetName} requires interactive approval. Enable TUI mode or adjust 'gadget-approval' in your config to allow this gadget.`
9689
10214
  };
9690
10215
  }
9691
10216
  }
@@ -9724,6 +10249,9 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
9724
10249
  if (options.temperature !== void 0) {
9725
10250
  builder.withTemperature(options.temperature);
9726
10251
  }
10252
+ if (skillRegistry.size > 0) {
10253
+ builder.withSkills(skillRegistry);
10254
+ }
9727
10255
  if (options.reasoning === false) {
9728
10256
  builder.withoutReasoning();
9729
10257
  } else if (options.reasoning !== void 0 || options.reasoningBudget !== void 0) {
@@ -9800,6 +10328,29 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
9800
10328
  );
9801
10329
  let currentAgent = null;
9802
10330
  const runAgentWithPrompt = async (userPrompt) => {
10331
+ builder.clearPreActivatedSkills();
10332
+ if (skillRegistry.size > 0 && userPrompt.startsWith("/")) {
10333
+ const slashResult = parseSlashCommand(userPrompt, skillRegistry);
10334
+ if (slashResult.isSkillInvocation) {
10335
+ if (slashResult.isListCommand) {
10336
+ const skills = skillRegistry.getUserInvocable();
10337
+ const lines = skills.map((s) => ` /${s.name} \u2014 ${s.description}`);
10338
+ const msg = skills.length > 0 ? `Available skills:
10339
+ ${lines.join("\n")}` : "No skills available.";
10340
+ if (tui) {
10341
+ tui.showUserMessage(`/skills`);
10342
+ tui.showUserMessage(msg);
10343
+ } else {
10344
+ env.stdout.write(`${msg}
10345
+ `);
10346
+ }
10347
+ return;
10348
+ }
10349
+ if (slashResult.skillName) {
10350
+ builder.withSkill(slashResult.skillName, slashResult.arguments);
10351
+ }
10352
+ }
10353
+ }
9803
10354
  if (tui) {
9804
10355
  tui.resetAbort();
9805
10356
  tui.startNewSession();
@@ -9924,11 +10475,11 @@ import {
9924
10475
 
9925
10476
  // src/llm-logging.ts
9926
10477
  import { mkdir, writeFile as writeFile2 } from "fs/promises";
9927
- import { join as join3 } from "path";
10478
+ import { join as join4 } from "path";
9928
10479
  import { formatCallNumber as formatCallNumber2, formatLlmRequest } from "llmist";
9929
10480
  async function writeLogFile(dir, filename, content) {
9930
10481
  await mkdir(dir, { recursive: true });
9931
- await writeFile2(join3(dir, filename), content, "utf-8");
10482
+ await writeFile2(join4(dir, filename), content, "utf-8");
9932
10483
  }
9933
10484
 
9934
10485
  // src/complete-command.ts
@@ -10156,7 +10707,7 @@ Profile: ${name}
10156
10707
  ` Temperature: ${section.temperature !== void 0 ? section.temperature : "(default)"}
10157
10708
  `
10158
10709
  );
10159
- const gadgets = section.gadgets ?? section.gadget;
10710
+ const gadgets = section.gadgets;
10160
10711
  if (gadgets && gadgets.length > 0) {
10161
10712
  env.stdout.write("\nGadgets:\n");
10162
10713
  for (const g of gadgets) {
@@ -10206,9 +10757,9 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
10206
10757
  }
10207
10758
 
10208
10759
  // src/environment.ts
10209
- import { join as join4 } from "path";
10760
+ import { join as join5 } from "path";
10210
10761
  import readline from "readline";
10211
- import chalk6 from "chalk";
10762
+ import chalk7 from "chalk";
10212
10763
  import { createLogger, LLMist } from "llmist";
10213
10764
  var LOG_LEVEL_MAP = {
10214
10765
  silly: 0,
@@ -10229,7 +10780,7 @@ function createLoggerFactory(config, sessionLogDir) {
10229
10780
  }
10230
10781
  }
10231
10782
  if (sessionLogDir) {
10232
- const logFile = join4(sessionLogDir, "session.log.jsonl");
10783
+ const logFile = join5(sessionLogDir, "session.log.jsonl");
10233
10784
  const originalLogFile = process.env.LLMIST_LOG_FILE;
10234
10785
  process.env.LLMIST_LOG_FILE = logFile;
10235
10786
  const logger = createLogger(options);
@@ -10248,22 +10799,22 @@ function createLoggerFactory(config, sessionLogDir) {
10248
10799
  }
10249
10800
  function createPromptFunction(stdin, stdout) {
10250
10801
  return (question) => {
10251
- return new Promise((resolve3) => {
10802
+ return new Promise((resolve4) => {
10252
10803
  const rl = readline.createInterface({
10253
10804
  input: stdin,
10254
10805
  output: stdout
10255
10806
  });
10256
10807
  stdout.write("\n");
10257
- stdout.write(`${chalk6.cyan("\u2500".repeat(60))}
10808
+ stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
10258
10809
  `);
10259
- stdout.write(chalk6.cyan.bold("\u{1F916} Agent asks:\n"));
10810
+ stdout.write(chalk7.cyan.bold("\u{1F916} Agent asks:\n"));
10260
10811
  stdout.write(`${question}
10261
10812
  `);
10262
- stdout.write(`${chalk6.cyan("\u2500".repeat(60))}
10813
+ stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
10263
10814
  `);
10264
- rl.question(chalk6.green.bold("You: "), (answer) => {
10815
+ rl.question(chalk7.green.bold("You: "), (answer) => {
10265
10816
  rl.close();
10266
- resolve3(answer);
10817
+ resolve4(answer);
10267
10818
  });
10268
10819
  });
10269
10820
  };
@@ -10342,12 +10893,12 @@ function registerCustomCommand(program, name, config, env, globalSubagents, glob
10342
10893
  }
10343
10894
 
10344
10895
  // src/gadget-command.ts
10345
- import chalk8 from "chalk";
10896
+ import chalk9 from "chalk";
10346
10897
  import { schemaToJSONSchema as schemaToJSONSchema2, validateGadgetSchema } from "llmist";
10347
10898
 
10348
10899
  // src/gadget-prompts.ts
10349
10900
  import { createInterface } from "readline/promises";
10350
- import chalk7 from "chalk";
10901
+ import chalk8 from "chalk";
10351
10902
  import { schemaToJSONSchema } from "llmist";
10352
10903
  async function promptForParameters(schema, ctx) {
10353
10904
  if (!schema) {
@@ -10380,16 +10931,16 @@ ${issues}`);
10380
10931
  async function promptForField(rl, key, prop, required) {
10381
10932
  const isRequired = required.includes(key);
10382
10933
  const typeHint = formatTypeHint(prop);
10383
- const defaultHint = prop.default !== void 0 ? chalk7.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
10384
- const requiredMarker = isRequired ? chalk7.red("*") : "";
10934
+ const defaultHint = prop.default !== void 0 ? chalk8.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
10935
+ const requiredMarker = isRequired ? chalk8.red("*") : "";
10385
10936
  let prompt = `
10386
- ${chalk7.cyan.bold(key)}${requiredMarker}`;
10937
+ ${chalk8.cyan.bold(key)}${requiredMarker}`;
10387
10938
  if (prop.description) {
10388
- prompt += chalk7.dim(` - ${prop.description}`);
10939
+ prompt += chalk8.dim(` - ${prop.description}`);
10389
10940
  }
10390
10941
  prompt += `
10391
10942
  ${typeHint}${defaultHint}
10392
- ${chalk7.green(">")} `;
10943
+ ${chalk8.green(">")} `;
10393
10944
  const answer = await rl.question(prompt);
10394
10945
  const trimmed = answer.trim();
10395
10946
  if (!trimmed) {
@@ -10405,20 +10956,20 @@ ${chalk7.cyan.bold(key)}${requiredMarker}`;
10405
10956
  }
10406
10957
  function formatTypeHint(prop) {
10407
10958
  if (prop.enum) {
10408
- return chalk7.yellow(`(${prop.enum.join(" | ")})`);
10959
+ return chalk8.yellow(`(${prop.enum.join(" | ")})`);
10409
10960
  }
10410
10961
  if (prop.type === "array") {
10411
10962
  const items = prop.items;
10412
10963
  if (items?.enum) {
10413
- return chalk7.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
10964
+ return chalk8.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
10414
10965
  }
10415
10966
  const itemType = items?.type ?? "any";
10416
- return chalk7.yellow(`(${itemType}[]) comma-separated`);
10967
+ return chalk8.yellow(`(${itemType}[]) comma-separated`);
10417
10968
  }
10418
10969
  if (prop.type === "object" && prop.properties) {
10419
- return chalk7.yellow("(object) enter as JSON");
10970
+ return chalk8.yellow("(object) enter as JSON");
10420
10971
  }
10421
- return chalk7.yellow(`(${prop.type ?? "any"})`);
10972
+ return chalk8.yellow(`(${prop.type ?? "any"})`);
10422
10973
  }
10423
10974
  function parseValue(input, prop, key) {
10424
10975
  const type = prop.type;
@@ -10529,7 +11080,7 @@ Available gadgets:
10529
11080
  async function executeGadgetRun(file, options, env) {
10530
11081
  const cwd = process.cwd();
10531
11082
  const { gadget, name } = await selectGadget(file, options.name, cwd);
10532
- env.stderr.write(chalk8.cyan.bold(`
11083
+ env.stderr.write(chalk9.cyan.bold(`
10533
11084
  \u{1F527} Running gadget: ${name}
10534
11085
  `));
10535
11086
  let params;
@@ -10540,7 +11091,7 @@ async function executeGadgetRun(file, options, env) {
10540
11091
  // Prompts go to stderr to keep stdout clean
10541
11092
  });
10542
11093
  } else {
10543
- env.stderr.write(chalk8.dim("Reading parameters from stdin...\n"));
11094
+ env.stderr.write(chalk9.dim("Reading parameters from stdin...\n"));
10544
11095
  const stdinParams = await readStdinJson(env.stdin);
10545
11096
  if (gadget.parameterSchema) {
10546
11097
  const result2 = gadget.parameterSchema.safeParse(stdinParams);
@@ -10554,7 +11105,7 @@ ${issues}`);
10554
11105
  params = stdinParams;
10555
11106
  }
10556
11107
  }
10557
- env.stderr.write(chalk8.dim("\nExecuting...\n"));
11108
+ env.stderr.write(chalk9.dim("\nExecuting...\n"));
10558
11109
  const startTime = Date.now();
10559
11110
  let result;
10560
11111
  let cost;
@@ -10581,7 +11132,7 @@ ${issues}`);
10581
11132
  }
10582
11133
  const elapsed = Date.now() - startTime;
10583
11134
  const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
10584
- env.stderr.write(chalk8.green(`
11135
+ env.stderr.write(chalk9.green(`
10585
11136
  \u2713 Completed in ${elapsed}ms${costInfo}
10586
11137
 
10587
11138
  `));
@@ -10617,37 +11168,37 @@ async function executeGadgetInfo(file, options, env) {
10617
11168
  return;
10618
11169
  }
10619
11170
  env.stdout.write("\n");
10620
- env.stdout.write(chalk8.cyan.bold(`${name}
11171
+ env.stdout.write(chalk9.cyan.bold(`${name}
10621
11172
  `));
10622
- env.stdout.write(chalk8.cyan("\u2550".repeat(name.length)) + "\n\n");
10623
- env.stdout.write(chalk8.bold("Description:\n"));
11173
+ env.stdout.write(chalk9.cyan("\u2550".repeat(name.length)) + "\n\n");
11174
+ env.stdout.write(chalk9.bold("Description:\n"));
10624
11175
  env.stdout.write(` ${gadget.description}
10625
11176
 
10626
11177
  `);
10627
11178
  if (gadget.parameterSchema) {
10628
- env.stdout.write(chalk8.bold("Parameters:\n"));
11179
+ env.stdout.write(chalk9.bold("Parameters:\n"));
10629
11180
  const jsonSchema = schemaToJSONSchema2(gadget.parameterSchema, { target: "draft-7" });
10630
11181
  env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
10631
11182
  } else {
10632
- env.stdout.write(chalk8.dim("No parameters required.\n\n"));
11183
+ env.stdout.write(chalk9.dim("No parameters required.\n\n"));
10633
11184
  }
10634
11185
  if (gadget.timeoutMs) {
10635
- env.stdout.write(chalk8.bold("Timeout:\n"));
11186
+ env.stdout.write(chalk9.bold("Timeout:\n"));
10636
11187
  env.stdout.write(` ${gadget.timeoutMs}ms
10637
11188
 
10638
11189
  `);
10639
11190
  }
10640
11191
  if (gadget.examples && gadget.examples.length > 0) {
10641
- env.stdout.write(chalk8.bold("Examples:\n"));
11192
+ env.stdout.write(chalk9.bold("Examples:\n"));
10642
11193
  for (const example of gadget.examples) {
10643
11194
  if (example.comment) {
10644
- env.stdout.write(chalk8.dim(` # ${example.comment}
11195
+ env.stdout.write(chalk9.dim(` # ${example.comment}
10645
11196
  `));
10646
11197
  }
10647
- env.stdout.write(` Input: ${chalk8.cyan(JSON.stringify(example.params))}
11198
+ env.stdout.write(` Input: ${chalk9.cyan(JSON.stringify(example.params))}
10648
11199
  `);
10649
11200
  if (example.output !== void 0) {
10650
- env.stdout.write(` Output: ${chalk8.green(example.output)}
11201
+ env.stdout.write(` Output: ${chalk9.green(example.output)}
10651
11202
  `);
10652
11203
  }
10653
11204
  env.stdout.write("\n");
@@ -10680,27 +11231,27 @@ function formatSchemaAsText(schema, indent = "") {
10680
11231
  const isRequired = required.includes(key);
10681
11232
  const enumValues = prop.enum;
10682
11233
  const defaultValue = prop.default;
10683
- let line = `${indent}${chalk8.cyan(key)}`;
11234
+ let line = `${indent}${chalk9.cyan(key)}`;
10684
11235
  if (isRequired) {
10685
- line += chalk8.red("*");
11236
+ line += chalk9.red("*");
10686
11237
  }
10687
11238
  if (type === "array") {
10688
11239
  const items = prop.items;
10689
11240
  const itemType = items?.type || "any";
10690
- line += chalk8.dim(` (${itemType}[])`);
11241
+ line += chalk9.dim(` (${itemType}[])`);
10691
11242
  } else if (type === "object" && prop.properties) {
10692
- line += chalk8.dim(" (object)");
11243
+ line += chalk9.dim(" (object)");
10693
11244
  } else {
10694
- line += chalk8.dim(` (${type})`);
11245
+ line += chalk9.dim(` (${type})`);
10695
11246
  }
10696
11247
  if (defaultValue !== void 0) {
10697
- line += chalk8.dim(` [default: ${JSON.stringify(defaultValue)}]`);
11248
+ line += chalk9.dim(` [default: ${JSON.stringify(defaultValue)}]`);
10698
11249
  }
10699
11250
  if (description) {
10700
11251
  line += `: ${description}`;
10701
11252
  }
10702
11253
  if (enumValues) {
10703
- line += chalk8.yellow(` - one of: ${enumValues.join(", ")}`);
11254
+ line += chalk9.yellow(` - one of: ${enumValues.join(", ")}`);
10704
11255
  }
10705
11256
  lines.push(line);
10706
11257
  if (type === "object" && prop.properties) {
@@ -10740,20 +11291,20 @@ async function executeGadgetValidate(file, env) {
10740
11291
  throw new Error(`Validation issues:
10741
11292
  ${issues.map((i) => ` - ${i}`).join("\n")}`);
10742
11293
  }
10743
- env.stdout.write(chalk8.green.bold("\n\u2713 Valid\n\n"));
10744
- env.stdout.write(chalk8.bold("Gadgets found:\n"));
11294
+ env.stdout.write(chalk9.green.bold("\n\u2713 Valid\n\n"));
11295
+ env.stdout.write(chalk9.bold("Gadgets found:\n"));
10745
11296
  for (const gadget of gadgets) {
10746
11297
  const name = gadget.name ?? gadget.constructor.name;
10747
- const schemaInfo = gadget.parameterSchema ? chalk8.cyan("(with schema)") : chalk8.dim("(no schema)");
10748
- env.stdout.write(` ${chalk8.bold(name)} ${schemaInfo}
11298
+ const schemaInfo = gadget.parameterSchema ? chalk9.cyan("(with schema)") : chalk9.dim("(no schema)");
11299
+ env.stdout.write(` ${chalk9.bold(name)} ${schemaInfo}
10749
11300
  `);
10750
- env.stdout.write(chalk8.dim(` ${gadget.description}
11301
+ env.stdout.write(chalk9.dim(` ${gadget.description}
10751
11302
  `));
10752
11303
  }
10753
11304
  env.stdout.write("\n");
10754
11305
  } catch (error) {
10755
11306
  const message = error instanceof Error ? error.message : String(error);
10756
- env.stdout.write(chalk8.red.bold(`
11307
+ env.stdout.write(chalk9.red.bold(`
10757
11308
  \u2717 Invalid
10758
11309
 
10759
11310
  `));
@@ -10937,7 +11488,7 @@ function registerInitCommand(program, env) {
10937
11488
  }
10938
11489
 
10939
11490
  // src/models-command.ts
10940
- import chalk9 from "chalk";
11491
+ import chalk10 from "chalk";
10941
11492
  import { MODEL_ALIASES } from "llmist";
10942
11493
  async function handleModelsCommand(options, env) {
10943
11494
  const client = env.createClient();
@@ -10956,11 +11507,11 @@ async function handleModelsCommand(options, env) {
10956
11507
  function renderAllTables(textModels, imageModels, speechModels, verbose, stream) {
10957
11508
  const hasAnyModels = textModels.length > 0 || imageModels.length > 0 || speechModels.length > 0;
10958
11509
  if (!hasAnyModels) {
10959
- stream.write(chalk9.yellow("\nNo models found matching the specified criteria.\n\n"));
11510
+ stream.write(chalk10.yellow("\nNo models found matching the specified criteria.\n\n"));
10960
11511
  return;
10961
11512
  }
10962
- stream.write(chalk9.bold.cyan("\nAvailable Models\n"));
10963
- stream.write(chalk9.cyan("=".repeat(80)) + "\n\n");
11513
+ stream.write(chalk10.bold.cyan("\nAvailable Models\n"));
11514
+ stream.write(chalk10.cyan("=".repeat(80)) + "\n\n");
10964
11515
  if (textModels.length > 0) {
10965
11516
  renderTextTable(textModels, verbose, stream);
10966
11517
  }
@@ -10971,12 +11522,12 @@ function renderAllTables(textModels, imageModels, speechModels, verbose, stream)
10971
11522
  renderSpeechTable(speechModels, verbose, stream);
10972
11523
  }
10973
11524
  if (textModels.length > 0) {
10974
- stream.write(chalk9.bold.magenta("Model Shortcuts\n"));
10975
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n");
11525
+ stream.write(chalk10.bold.magenta("Model Shortcuts\n"));
11526
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n");
10976
11527
  const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
10977
11528
  for (const [shortcut, fullName] of shortcuts) {
10978
11529
  stream.write(
10979
- chalk9.cyan(` ${shortcut.padEnd(15)}`) + chalk9.dim(" \u2192 ") + chalk9.white(fullName) + "\n"
11530
+ chalk10.cyan(` ${shortcut.padEnd(15)}`) + chalk10.dim(" \u2192 ") + chalk10.white(fullName) + "\n"
10980
11531
  );
10981
11532
  }
10982
11533
  stream.write("\n");
@@ -10991,13 +11542,13 @@ function renderTextTable(models, verbose, stream) {
10991
11542
  }
10992
11543
  grouped.get(provider).push(model);
10993
11544
  }
10994
- stream.write(chalk9.bold.blue("\u{1F4DD} Text/LLM Models\n"));
10995
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n\n");
11545
+ stream.write(chalk10.bold.blue("\u{1F4DD} Text/LLM Models\n"));
11546
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
10996
11547
  const providers = Array.from(grouped.keys()).sort();
10997
11548
  for (const provider of providers) {
10998
11549
  const providerModels = grouped.get(provider);
10999
11550
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11000
- stream.write(chalk9.bold.yellow(`${providerName}
11551
+ stream.write(chalk10.bold.yellow(`${providerName}
11001
11552
  `));
11002
11553
  if (verbose) {
11003
11554
  renderVerboseTable(providerModels, stream);
@@ -11014,58 +11565,58 @@ function renderCompactTable(models, stream) {
11014
11565
  const inputWidth = 10;
11015
11566
  const outputWidth = 10;
11016
11567
  stream.write(
11017
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11568
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11018
11569
  );
11019
11570
  stream.write(
11020
- chalk9.bold(
11571
+ chalk10.bold(
11021
11572
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
11022
11573
  ) + "\n"
11023
11574
  );
11024
11575
  stream.write(
11025
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11576
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11026
11577
  );
11027
11578
  for (const model of models) {
11028
11579
  const contextFormatted = formatTokensLong(model.contextWindow);
11029
11580
  const inputPrice = `$${model.pricing.input.toFixed(2)}`;
11030
11581
  const outputPrice = `$${model.pricing.output.toFixed(2)}`;
11031
11582
  stream.write(
11032
- chalk9.green(model.modelId.padEnd(idWidth)) + " " + chalk9.white(model.displayName.padEnd(nameWidth)) + " " + chalk9.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk9.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk9.cyan(outputPrice.padEnd(outputWidth)) + "\n"
11583
+ chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.padEnd(nameWidth)) + " " + chalk10.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk10.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk10.cyan(outputPrice.padEnd(outputWidth)) + "\n"
11033
11584
  );
11034
11585
  }
11035
11586
  stream.write(
11036
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11587
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11037
11588
  );
11038
- stream.write(chalk9.dim(` * Prices are per 1M tokens
11589
+ stream.write(chalk10.dim(` * Prices are per 1M tokens
11039
11590
  `));
11040
11591
  }
11041
11592
  function renderVerboseTable(models, stream) {
11042
11593
  for (const model of models) {
11043
- stream.write(chalk9.bold.green(`
11594
+ stream.write(chalk10.bold.green(`
11044
11595
  ${model.modelId}
11045
11596
  `));
11046
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11047
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11597
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11598
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11048
11599
  `);
11049
11600
  stream.write(
11050
- ` ${chalk9.dim("Context:")} ${chalk9.yellow(formatTokensLong(model.contextWindow))}
11601
+ ` ${chalk10.dim("Context:")} ${chalk10.yellow(formatTokensLong(model.contextWindow))}
11051
11602
  `
11052
11603
  );
11053
11604
  stream.write(
11054
- ` ${chalk9.dim("Max Output:")} ${chalk9.yellow(formatTokensLong(model.maxOutputTokens))}
11605
+ ` ${chalk10.dim("Max Output:")} ${chalk10.yellow(formatTokensLong(model.maxOutputTokens))}
11055
11606
  `
11056
11607
  );
11057
11608
  stream.write(
11058
- ` ${chalk9.dim("Pricing:")} ${chalk9.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk9.dim("/")} ${chalk9.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk9.dim("(per 1M tokens)")}
11609
+ ` ${chalk10.dim("Pricing:")} ${chalk10.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk10.dim("/")} ${chalk10.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk10.dim("(per 1M tokens)")}
11059
11610
  `
11060
11611
  );
11061
11612
  if (model.pricing.cachedInput !== void 0) {
11062
11613
  stream.write(
11063
- ` ${chalk9.dim("Cached Input:")} ${chalk9.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
11614
+ ` ${chalk10.dim("Cached Input:")} ${chalk10.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
11064
11615
  `
11065
11616
  );
11066
11617
  }
11067
11618
  if (model.knowledgeCutoff) {
11068
- stream.write(` ${chalk9.dim("Knowledge:")} ${model.knowledgeCutoff}
11619
+ stream.write(` ${chalk10.dim("Knowledge:")} ${model.knowledgeCutoff}
11069
11620
  `);
11070
11621
  }
11071
11622
  const features = [];
@@ -11076,20 +11627,20 @@ function renderVerboseTable(models, stream) {
11076
11627
  if (model.features.structuredOutputs) features.push("structured-outputs");
11077
11628
  if (model.features.fineTuning) features.push("fine-tuning");
11078
11629
  if (features.length > 0) {
11079
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11630
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11080
11631
  `);
11081
11632
  }
11082
11633
  if (model.metadata) {
11083
11634
  if (model.metadata.family) {
11084
- stream.write(` ${chalk9.dim("Family:")} ${model.metadata.family}
11635
+ stream.write(` ${chalk10.dim("Family:")} ${model.metadata.family}
11085
11636
  `);
11086
11637
  }
11087
11638
  if (model.metadata.releaseDate) {
11088
- stream.write(` ${chalk9.dim("Released:")} ${model.metadata.releaseDate}
11639
+ stream.write(` ${chalk10.dim("Released:")} ${model.metadata.releaseDate}
11089
11640
  `);
11090
11641
  }
11091
11642
  if (model.metadata.notes) {
11092
- stream.write(` ${chalk9.dim("Notes:")} ${chalk9.italic(model.metadata.notes)}
11643
+ stream.write(` ${chalk10.dim("Notes:")} ${chalk10.italic(model.metadata.notes)}
11093
11644
  `);
11094
11645
  }
11095
11646
  }
@@ -11097,8 +11648,8 @@ function renderVerboseTable(models, stream) {
11097
11648
  stream.write("\n");
11098
11649
  }
11099
11650
  function renderImageTable(models, verbose, stream) {
11100
- stream.write(chalk9.bold.green("\u{1F3A8} Image Generation Models\n"));
11101
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n\n");
11651
+ stream.write(chalk10.bold.green("\u{1F3A8} Image Generation Models\n"));
11652
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
11102
11653
  const grouped = /* @__PURE__ */ new Map();
11103
11654
  for (const model of models) {
11104
11655
  if (!grouped.has(model.provider)) {
@@ -11108,29 +11659,29 @@ function renderImageTable(models, verbose, stream) {
11108
11659
  }
11109
11660
  for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
11110
11661
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11111
- stream.write(chalk9.bold.yellow(`${providerName}
11662
+ stream.write(chalk10.bold.yellow(`${providerName}
11112
11663
  `));
11113
11664
  if (verbose) {
11114
11665
  for (const model of providerModels) {
11115
- stream.write(chalk9.bold.green(`
11666
+ stream.write(chalk10.bold.green(`
11116
11667
  ${model.modelId}
11117
11668
  `));
11118
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11119
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11669
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11670
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11120
11671
  `);
11121
11672
  stream.write(
11122
- ` ${chalk9.dim("Sizes:")} ${chalk9.yellow(model.supportedSizes.join(", "))}
11673
+ ` ${chalk10.dim("Sizes:")} ${chalk10.yellow(model.supportedSizes.join(", "))}
11123
11674
  `
11124
11675
  );
11125
11676
  if (model.supportedQualities) {
11126
11677
  stream.write(
11127
- ` ${chalk9.dim("Qualities:")} ${chalk9.yellow(model.supportedQualities.join(", "))}
11678
+ ` ${chalk10.dim("Qualities:")} ${chalk10.yellow(model.supportedQualities.join(", "))}
11128
11679
  `
11129
11680
  );
11130
11681
  }
11131
- stream.write(` ${chalk9.dim("Max Images:")} ${chalk9.yellow(model.maxImages.toString())}
11682
+ stream.write(` ${chalk10.dim("Max Images:")} ${chalk10.yellow(model.maxImages.toString())}
11132
11683
  `);
11133
- stream.write(` ${chalk9.dim("Pricing:")} ${chalk9.cyan(formatImagePrice(model))}
11684
+ stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatImagePrice(model))}
11134
11685
  `);
11135
11686
  if (model.features) {
11136
11687
  const features = [];
@@ -11138,7 +11689,7 @@ function renderImageTable(models, verbose, stream) {
11138
11689
  if (model.features.transparency) features.push("transparency");
11139
11690
  if (model.features.conversational) features.push("conversational");
11140
11691
  if (features.length > 0) {
11141
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11692
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11142
11693
  `);
11143
11694
  }
11144
11695
  }
@@ -11148,27 +11699,27 @@ function renderImageTable(models, verbose, stream) {
11148
11699
  const nameWidth = 25;
11149
11700
  const sizesWidth = 20;
11150
11701
  const priceWidth = 15;
11151
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11702
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11152
11703
  stream.write(
11153
- chalk9.bold(
11704
+ chalk10.bold(
11154
11705
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Sizes".padEnd(sizesWidth) + " " + "Price".padEnd(priceWidth)
11155
11706
  ) + "\n"
11156
11707
  );
11157
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11708
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11158
11709
  for (const model of providerModels) {
11159
11710
  const sizes = model.supportedSizes.length > 2 ? model.supportedSizes.slice(0, 2).join(", ") + "..." : model.supportedSizes.join(", ");
11160
11711
  stream.write(
11161
- chalk9.green(model.modelId.padEnd(idWidth)) + " " + chalk9.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk9.yellow(sizes.padEnd(sizesWidth)) + " " + chalk9.cyan(formatImagePrice(model).padEnd(priceWidth)) + "\n"
11712
+ chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk10.yellow(sizes.padEnd(sizesWidth)) + " " + chalk10.cyan(formatImagePrice(model).padEnd(priceWidth)) + "\n"
11162
11713
  );
11163
11714
  }
11164
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11715
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11165
11716
  }
11166
11717
  stream.write("\n");
11167
11718
  }
11168
11719
  }
11169
11720
  function renderSpeechTable(models, verbose, stream) {
11170
- stream.write(chalk9.bold.magenta("\u{1F3A4} Speech (TTS) Models\n"));
11171
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n\n");
11721
+ stream.write(chalk10.bold.magenta("\u{1F3A4} Speech (TTS) Models\n"));
11722
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
11172
11723
  const grouped = /* @__PURE__ */ new Map();
11173
11724
  for (const model of models) {
11174
11725
  if (!grouped.has(model.provider)) {
@@ -11178,34 +11729,34 @@ function renderSpeechTable(models, verbose, stream) {
11178
11729
  }
11179
11730
  for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
11180
11731
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11181
- stream.write(chalk9.bold.yellow(`${providerName}
11732
+ stream.write(chalk10.bold.yellow(`${providerName}
11182
11733
  `));
11183
11734
  if (verbose) {
11184
11735
  for (const model of providerModels) {
11185
- stream.write(chalk9.bold.green(`
11736
+ stream.write(chalk10.bold.green(`
11186
11737
  ${model.modelId}
11187
11738
  `));
11188
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11189
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11739
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11740
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11190
11741
  `);
11191
11742
  stream.write(
11192
- ` ${chalk9.dim("Voices:")} ${chalk9.yellow(model.voices.length.toString())} voices
11743
+ ` ${chalk10.dim("Voices:")} ${chalk10.yellow(model.voices.length.toString())} voices
11193
11744
  `
11194
11745
  );
11195
11746
  if (model.voices.length <= 6) {
11196
- stream.write(` ${chalk9.dim(model.voices.join(", "))}
11747
+ stream.write(` ${chalk10.dim(model.voices.join(", "))}
11197
11748
  `);
11198
11749
  } else {
11199
- stream.write(` ${chalk9.dim(model.voices.slice(0, 6).join(", ") + "...")}
11750
+ stream.write(` ${chalk10.dim(model.voices.slice(0, 6).join(", ") + "...")}
11200
11751
  `);
11201
11752
  }
11202
- stream.write(` ${chalk9.dim("Formats:")} ${chalk9.yellow(model.formats.join(", "))}
11753
+ stream.write(` ${chalk10.dim("Formats:")} ${chalk10.yellow(model.formats.join(", "))}
11203
11754
  `);
11204
11755
  stream.write(
11205
- ` ${chalk9.dim("Max Input:")} ${chalk9.yellow(model.maxInputLength.toString())} chars
11756
+ ` ${chalk10.dim("Max Input:")} ${chalk10.yellow(model.maxInputLength.toString())} chars
11206
11757
  `
11207
11758
  );
11208
- stream.write(` ${chalk9.dim("Pricing:")} ${chalk9.cyan(formatSpeechPrice(model))}
11759
+ stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatSpeechPrice(model))}
11209
11760
  `);
11210
11761
  if (model.features) {
11211
11762
  const features = [];
@@ -11213,7 +11764,7 @@ function renderSpeechTable(models, verbose, stream) {
11213
11764
  if (model.features.voiceInstructions) features.push("voice-instructions");
11214
11765
  if (model.features.languages) features.push(`${model.features.languages} languages`);
11215
11766
  if (features.length > 0) {
11216
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11767
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11217
11768
  `);
11218
11769
  }
11219
11770
  }
@@ -11224,23 +11775,23 @@ function renderSpeechTable(models, verbose, stream) {
11224
11775
  const voicesWidth = 12;
11225
11776
  const priceWidth = 18;
11226
11777
  stream.write(
11227
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11778
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11228
11779
  );
11229
11780
  stream.write(
11230
- chalk9.bold(
11781
+ chalk10.bold(
11231
11782
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Voices".padEnd(voicesWidth) + " " + "Price".padEnd(priceWidth)
11232
11783
  ) + "\n"
11233
11784
  );
11234
11785
  stream.write(
11235
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11786
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11236
11787
  );
11237
11788
  for (const model of providerModels) {
11238
11789
  stream.write(
11239
- chalk9.green(model.modelId.padEnd(idWidth)) + " " + chalk9.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk9.yellow(`${model.voices.length} voices`.padEnd(voicesWidth)) + " " + chalk9.cyan(formatSpeechPrice(model).padEnd(priceWidth)) + "\n"
11790
+ chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk10.yellow(`${model.voices.length} voices`.padEnd(voicesWidth)) + " " + chalk10.cyan(formatSpeechPrice(model).padEnd(priceWidth)) + "\n"
11240
11791
  );
11241
11792
  }
11242
11793
  stream.write(
11243
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11794
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11244
11795
  );
11245
11796
  }
11246
11797
  stream.write("\n");
@@ -11332,8 +11883,8 @@ function registerModelsCommand(program, env) {
11332
11883
  // src/session.ts
11333
11884
  import { existsSync as existsSync4 } from "fs";
11334
11885
  import { mkdir as mkdir2 } from "fs/promises";
11335
- import { homedir as homedir3 } from "os";
11336
- import { join as join5 } from "path";
11886
+ import { homedir as homedir4 } from "os";
11887
+ import { join as join6 } from "path";
11337
11888
 
11338
11889
  // src/session-names.ts
11339
11890
  var ADJECTIVES = [
@@ -11468,16 +12019,16 @@ function generateSessionName() {
11468
12019
 
11469
12020
  // src/session.ts
11470
12021
  var currentSession;
11471
- var SESSION_LOGS_BASE = join5(homedir3(), ".llmist", "logs");
12022
+ var SESSION_LOGS_BASE = join6(homedir4(), ".llmist", "logs");
11472
12023
  function findUniqueName(baseName) {
11473
- const baseDir = join5(SESSION_LOGS_BASE, baseName);
12024
+ const baseDir = join6(SESSION_LOGS_BASE, baseName);
11474
12025
  if (!existsSync4(baseDir)) {
11475
12026
  return baseName;
11476
12027
  }
11477
12028
  let suffix = 2;
11478
12029
  while (suffix < 1e3) {
11479
12030
  const name = `${baseName}-${suffix}`;
11480
- const dir = join5(SESSION_LOGS_BASE, name);
12031
+ const dir = join6(SESSION_LOGS_BASE, name);
11481
12032
  if (!existsSync4(dir)) {
11482
12033
  return name;
11483
12034
  }
@@ -11491,12 +12042,101 @@ async function initSession() {
11491
12042
  }
11492
12043
  const baseName = generateSessionName();
11493
12044
  const name = findUniqueName(baseName);
11494
- const logDir = join5(SESSION_LOGS_BASE, name);
12045
+ const logDir = join6(SESSION_LOGS_BASE, name);
11495
12046
  await mkdir2(logDir, { recursive: true });
11496
12047
  currentSession = { name, logDir };
11497
12048
  return currentSession;
11498
12049
  }
11499
12050
 
12051
+ // src/skills/skill-command.ts
12052
+ function registerSkillCommand(program, env) {
12053
+ const skillCmd = program.command("skill").description("Manage Agent Skills (SKILL.md)");
12054
+ skillCmd.command("list").description("List available skills").action(async () => {
12055
+ const config = safeLoadConfig();
12056
+ const manager = new CLISkillManager();
12057
+ const registry = await manager.loadAll(config?.skills);
12058
+ const skills = registry.getAll();
12059
+ if (skills.length === 0) {
12060
+ env.stdout.write("No skills found.\n");
12061
+ env.stdout.write(
12062
+ "Add skills to ~/.llmist/skills/ or configure [skills].sources in ~/.llmist/cli.toml\n"
12063
+ );
12064
+ return;
12065
+ }
12066
+ env.stdout.write(`Found ${skills.length} skill(s):
12067
+
12068
+ `);
12069
+ for (const skill of skills) {
12070
+ const flags = [];
12071
+ if (!skill.isModelInvocable) flags.push("user-only");
12072
+ if (!skill.isUserInvocable) flags.push("background");
12073
+ if (skill.metadata.context === "fork") flags.push("fork");
12074
+ if (skill.metadata.model) flags.push(`model:${skill.metadata.model}`);
12075
+ const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
12076
+ const desc = skill.description.length > 80 ? `${skill.description.slice(0, 77)}...` : skill.description;
12077
+ env.stdout.write(` /${skill.name}${flagStr}
12078
+ `);
12079
+ env.stdout.write(` ${desc}
12080
+
12081
+ `);
12082
+ }
12083
+ });
12084
+ skillCmd.command("info <name>").description("Show detailed skill information").action(async (name) => {
12085
+ const config = safeLoadConfig();
12086
+ const manager = new CLISkillManager();
12087
+ const registry = await manager.loadAll(config?.skills);
12088
+ const skill = registry.get(name);
12089
+ if (!skill) {
12090
+ env.stderr.write(`Skill not found: ${name}
12091
+ `);
12092
+ env.stderr.write(`Available skills: ${registry.getNames().join(", ") || "(none)"}
12093
+ `);
12094
+ process.exitCode = 1;
12095
+ return;
12096
+ }
12097
+ env.stdout.write(`Skill: ${skill.name}
12098
+ `);
12099
+ env.stdout.write(`Description: ${skill.description}
12100
+ `);
12101
+ env.stdout.write(`Source: ${skill.sourcePath}
12102
+ `);
12103
+ if (skill.metadata.model) env.stdout.write(`Model: ${skill.metadata.model}
12104
+ `);
12105
+ if (skill.metadata.context) env.stdout.write(`Context: ${skill.metadata.context}
12106
+ `);
12107
+ if (skill.metadata.agent) env.stdout.write(`Agent: ${skill.metadata.agent}
12108
+ `);
12109
+ if (skill.metadata.allowedTools) {
12110
+ env.stdout.write(`Allowed tools: ${skill.metadata.allowedTools.join(", ")}
12111
+ `);
12112
+ }
12113
+ if (skill.metadata.paths) {
12114
+ env.stdout.write(`Paths: ${skill.metadata.paths.join(", ")}
12115
+ `);
12116
+ }
12117
+ const resources = skill.getResources();
12118
+ if (resources.length > 0) {
12119
+ env.stdout.write(`Resources: ${resources.length}
12120
+ `);
12121
+ for (const r of resources) {
12122
+ env.stdout.write(` ${r.category}/${r.relativePath}
12123
+ `);
12124
+ }
12125
+ }
12126
+ env.stdout.write("\n--- Instructions ---\n\n");
12127
+ const instructions = await skill.getInstructions();
12128
+ env.stdout.write(`${instructions}
12129
+ `);
12130
+ });
12131
+ }
12132
+ function safeLoadConfig() {
12133
+ try {
12134
+ return loadConfig();
12135
+ } catch {
12136
+ return void 0;
12137
+ }
12138
+ }
12139
+
11500
12140
  // src/speech-command.ts
11501
12141
  import { writeFileSync as writeFileSync5 } from "fs";
11502
12142
  var DEFAULT_SPEECH_MODEL = "tts-1";
@@ -11608,6 +12248,7 @@ function createProgram(env, config) {
11608
12248
  registerVisionCommand(program, env);
11609
12249
  registerModelsCommand(program, env);
11610
12250
  registerGadgetCommand(program, env);
12251
+ registerSkillCommand(program, env);
11611
12252
  registerInitCommand(program, env);
11612
12253
  registerConfigCommand(program, env, config);
11613
12254
  if (config) {