@llmist/cli 16.0.4 → 16.1.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.1.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.1.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.1.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",
@@ -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);
@@ -1600,18 +1583,93 @@ async function readFileBuffer(filePath, options = {}) {
1600
1583
  }
1601
1584
 
1602
1585
  // src/gadgets.ts
1603
- import fs6 from "fs";
1586
+ import fs7 from "fs";
1604
1587
  import { createRequire } from "module";
1605
- import path5 from "path";
1588
+ import path6 from "path";
1606
1589
  import { pathToFileURL as pathToFileURL2 } from "url";
1607
1590
  import { createJiti } from "jiti";
1608
1591
  import { AbstractGadget } from "llmist";
1609
1592
 
1610
- // src/builtins/filesystem/edit-file.ts
1611
- import { readFileSync as readFileSync3, writeFileSync } from "fs";
1593
+ // src/builtins/filesystem/delete-file.ts
1594
+ import fs2 from "fs";
1612
1595
  import { createGadget as createGadget3 } from "llmist";
1613
1596
  import { z as z3 } from "zod";
1614
1597
 
1598
+ // src/builtins/filesystem/utils.ts
1599
+ import fs from "fs";
1600
+ import path from "path";
1601
+ var PathSandboxException = class extends Error {
1602
+ constructor(inputPath, reason) {
1603
+ super(`Path access denied: ${inputPath}. ${reason}`);
1604
+ this.name = "PathSandboxException";
1605
+ }
1606
+ };
1607
+ function validatePathIsWithinCwd(inputPath) {
1608
+ const cwd = process.cwd();
1609
+ const resolvedPath = path.resolve(cwd, inputPath);
1610
+ let finalPath;
1611
+ try {
1612
+ finalPath = fs.realpathSync(resolvedPath);
1613
+ } catch (error) {
1614
+ const nodeError = error;
1615
+ if (nodeError.code === "ENOENT") {
1616
+ finalPath = resolvedPath;
1617
+ } else {
1618
+ throw error;
1619
+ }
1620
+ }
1621
+ const cwdWithSep = cwd + path.sep;
1622
+ if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
1623
+ throw new PathSandboxException(inputPath, "Path is outside the current working directory");
1624
+ }
1625
+ return finalPath;
1626
+ }
1627
+
1628
+ // src/builtins/filesystem/delete-file.ts
1629
+ var deleteFile = createGadget3({
1630
+ name: "DeleteFile",
1631
+ description: "Delete a file or directory from the local filesystem. The path must be within the current working directory or its subdirectories.",
1632
+ maxConcurrent: 1,
1633
+ // Sequential execution to prevent race conditions
1634
+ schema: z3.object({
1635
+ filePath: z3.string().describe("Path to the file or directory to delete (relative or absolute)"),
1636
+ recursive: z3.boolean().optional().default(false).describe("If true, perform a recursive deletion (required for directories)")
1637
+ }),
1638
+ examples: [
1639
+ {
1640
+ params: { filePath: "temp.txt", recursive: false },
1641
+ output: "path=temp.txt\n\nDeleted file successfully",
1642
+ comment: "Delete a single file"
1643
+ },
1644
+ {
1645
+ params: { filePath: "tmp-dir", recursive: true },
1646
+ output: "path=tmp-dir\n\nDeleted directory successfully",
1647
+ comment: "Delete a directory and its contents"
1648
+ }
1649
+ ],
1650
+ execute: ({ filePath, recursive }) => {
1651
+ const validatedPath = validatePathIsWithinCwd(filePath);
1652
+ if (!fs2.existsSync(validatedPath)) {
1653
+ return `Error: Path does not exist: ${filePath}`;
1654
+ }
1655
+ const stats = fs2.statSync(validatedPath);
1656
+ const isDirectory = stats.isDirectory();
1657
+ if (isDirectory && !recursive) {
1658
+ return `Error: ${filePath} is a directory. Set recursive=true to delete it.`;
1659
+ }
1660
+ fs2.rmSync(validatedPath, { recursive, force: true });
1661
+ const type = isDirectory ? "directory" : "file";
1662
+ return `path=${filePath}
1663
+
1664
+ Deleted ${type} successfully`;
1665
+ }
1666
+ });
1667
+
1668
+ // src/builtins/filesystem/edit-file.ts
1669
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
1670
+ import { createGadget as createGadget4 } from "llmist";
1671
+ import { z as z4 } from "zod";
1672
+
1615
1673
  // src/builtins/filesystem/editfile/matcher.ts
1616
1674
  import DiffMatchPatch from "diff-match-patch";
1617
1675
  var dmp = new DiffMatchPatch();
@@ -2029,36 +2087,6 @@ function formatMultipleMatches(content, matches, maxMatches = 5) {
2029
2087
  return output.join("\n");
2030
2088
  }
2031
2089
 
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
2090
  // src/builtins/filesystem/edit-file.ts
2063
2091
  function formatFailure(filePath, search, failure, fileContent) {
2064
2092
  const lines = [
@@ -2090,7 +2118,7 @@ function formatFailure(filePath, search, failure, fileContent) {
2090
2118
  lines.push("", "CURRENT FILE CONTENT:", "```", fileContent, "```");
2091
2119
  return lines.join("\n");
2092
2120
  }
2093
- var editFile = createGadget3({
2121
+ var editFile = createGadget4({
2094
2122
  name: "EditFile",
2095
2123
  description: `Edit a file by searching for content and replacing it.
2096
2124
 
@@ -2109,12 +2137,12 @@ Options:
2109
2137
  - expectedCount: Validate exact number of matches before applying`,
2110
2138
  maxConcurrent: 1,
2111
2139
  // 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")
2140
+ schema: z4.object({
2141
+ filePath: z4.string().describe("Path to the file to edit (relative or absolute)"),
2142
+ search: z4.string().describe("The content to search for in the file"),
2143
+ replace: z4.string().describe("The content to replace it with (empty string to delete)"),
2144
+ replaceAll: z4.boolean().optional().default(false).describe("Replace all occurrences instead of just the first match"),
2145
+ expectedCount: z4.number().int().positive().optional().describe("Expected number of matches. Edit fails if actual count differs")
2118
2146
  }),
2119
2147
  examples: [
2120
2148
  {
@@ -2266,19 +2294,19 @@ function executeReplaceAll(content, matches, replace) {
2266
2294
  }
2267
2295
 
2268
2296
  // src/builtins/filesystem/list-directory.ts
2269
- import fs2 from "fs";
2297
+ import fs3 from "fs";
2270
2298
  import path2 from "path";
2271
- import { createGadget as createGadget4 } from "llmist";
2272
- import { z as z4 } from "zod";
2299
+ import { createGadget as createGadget5 } from "llmist";
2300
+ import { z as z5 } from "zod";
2273
2301
  function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
2274
2302
  const entries = [];
2275
2303
  try {
2276
- const items = fs2.readdirSync(dirPath);
2304
+ const items = fs3.readdirSync(dirPath);
2277
2305
  for (const item of items) {
2278
2306
  const fullPath = path2.join(dirPath, item);
2279
2307
  const relativePath = path2.relative(basePath, fullPath);
2280
2308
  try {
2281
- const stats = fs2.lstatSync(fullPath);
2309
+ const stats = fs3.lstatSync(fullPath);
2282
2310
  let type;
2283
2311
  let size;
2284
2312
  if (stats.isSymbolicLink()) {
@@ -2353,12 +2381,12 @@ function formatEntriesAsString(entries) {
2353
2381
  );
2354
2382
  return [header, ...rows].join("\n");
2355
2383
  }
2356
- var listDirectory = createGadget4({
2384
+ var listDirectory = createGadget5({
2357
2385
  name: "ListDirectory",
2358
2386
  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(
2387
+ schema: z5.object({
2388
+ directoryPath: z5.string().default(".").describe("Path to the directory to list"),
2389
+ maxDepth: z5.number().int().min(1).max(10).default(3).describe(
2362
2390
  "Maximum depth to recurse (1 = immediate children only, 2 = include grandchildren, etc.)"
2363
2391
  )
2364
2392
  }),
@@ -2376,7 +2404,7 @@ var listDirectory = createGadget4({
2376
2404
  ],
2377
2405
  execute: ({ directoryPath, maxDepth }) => {
2378
2406
  const validatedPath = validatePathIsWithinCwd(directoryPath);
2379
- const stats = fs2.statSync(validatedPath);
2407
+ const stats = fs3.statSync(validatedPath);
2380
2408
  if (!stats.isDirectory()) {
2381
2409
  throw new Error(`Path is not a directory: ${directoryPath}`);
2382
2410
  }
@@ -2389,14 +2417,14 @@ ${formattedList}`;
2389
2417
  });
2390
2418
 
2391
2419
  // 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({
2420
+ import fs4 from "fs";
2421
+ import { createGadget as createGadget6 } from "llmist";
2422
+ import { z as z6 } from "zod";
2423
+ var readFile2 = createGadget6({
2396
2424
  name: "ReadFile",
2397
2425
  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)")
2426
+ schema: z6.object({
2427
+ filePath: z6.string().describe("Path to the file to read (relative or absolute)")
2400
2428
  }),
2401
2429
  examples: [
2402
2430
  {
@@ -2412,7 +2440,7 @@ var readFile2 = createGadget5({
2412
2440
  ],
2413
2441
  execute: ({ filePath }) => {
2414
2442
  const validatedPath = validatePathIsWithinCwd(filePath);
2415
- const content = fs3.readFileSync(validatedPath, "utf-8");
2443
+ const content = fs4.readFileSync(validatedPath, "utf-8");
2416
2444
  return `path=${filePath}
2417
2445
 
2418
2446
  ${content}`;
@@ -2420,18 +2448,18 @@ ${content}`;
2420
2448
  });
2421
2449
 
2422
2450
  // src/builtins/filesystem/write-file.ts
2423
- import fs4 from "fs";
2451
+ import fs5 from "fs";
2424
2452
  import path3 from "path";
2425
- import { createGadget as createGadget6 } from "llmist";
2426
- import { z as z6 } from "zod";
2427
- var writeFile = createGadget6({
2453
+ import { createGadget as createGadget7 } from "llmist";
2454
+ import { z as z7 } from "zod";
2455
+ var writeFile = createGadget7({
2428
2456
  name: "WriteFile",
2429
2457
  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
2458
  maxConcurrent: 1,
2431
2459
  // 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")
2460
+ schema: z7.object({
2461
+ filePath: z7.string().describe("Path to the file to write (relative or absolute)"),
2462
+ content: z7.string().describe("Content to write to the file")
2435
2463
  }),
2436
2464
  examples: [
2437
2465
  {
@@ -2461,12 +2489,12 @@ console.log(\`Server running on http://localhost:\${port}\`);`
2461
2489
  const validatedPath = validatePathIsWithinCwd(filePath);
2462
2490
  const parentDir = path3.dirname(validatedPath);
2463
2491
  let createdDir = false;
2464
- if (!fs4.existsSync(parentDir)) {
2492
+ if (!fs5.existsSync(parentDir)) {
2465
2493
  validatePathIsWithinCwd(parentDir);
2466
- fs4.mkdirSync(parentDir, { recursive: true });
2494
+ fs5.mkdirSync(parentDir, { recursive: true });
2467
2495
  createdDir = true;
2468
2496
  }
2469
- fs4.writeFileSync(validatedPath, content, "utf-8");
2497
+ fs5.writeFileSync(validatedPath, content, "utf-8");
2470
2498
  const bytesWritten = Buffer.byteLength(content, "utf-8");
2471
2499
  const dirNote = createdDir ? ` (created directory: ${path3.dirname(filePath)})` : "";
2472
2500
  return `path=${filePath}
@@ -2475,9 +2503,141 @@ Wrote ${bytesWritten} bytes${dirNote}`;
2475
2503
  }
2476
2504
  });
2477
2505
 
2506
+ // src/builtins/read-image.ts
2507
+ import { readFile as readFile3, stat as stat2 } from "fs/promises";
2508
+ import path4 from "path";
2509
+ import { createGadget as createGadget8, getErrorMessage as getErrorMessage2, resultWithImage } from "llmist";
2510
+ import { z as z8 } from "zod";
2511
+ var MAX_IMAGE_SIZE = 50 * 1024 * 1024;
2512
+ var URL_FETCH_TIMEOUT_MS = 3e4;
2513
+ var SUPPORTED_FORMATS = "JPEG, PNG, GIF, WebP";
2514
+ var USER_AGENT = "llmist-cli";
2515
+ var MIME_TO_EXT = {
2516
+ "image/jpeg": ".jpg",
2517
+ "image/png": ".png",
2518
+ "image/gif": ".gif",
2519
+ "image/webp": ".webp"
2520
+ };
2521
+ function isUrl(source) {
2522
+ return source.startsWith("http://") || source.startsWith("https://");
2523
+ }
2524
+ function fileNameFromUrl(source) {
2525
+ try {
2526
+ const urlPath = new URL(source).pathname;
2527
+ const base = path4.basename(urlPath);
2528
+ if (base && base.includes(".")) {
2529
+ return base;
2530
+ }
2531
+ } catch {
2532
+ }
2533
+ return void 0;
2534
+ }
2535
+ async function readImageFromFile(source) {
2536
+ const resolvedPath = path4.resolve(source);
2537
+ let stats;
2538
+ try {
2539
+ stats = await stat2(resolvedPath);
2540
+ } catch (error) {
2541
+ return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
2542
+ }
2543
+ if (stats.size > MAX_IMAGE_SIZE) {
2544
+ return `error: Image file "${source}" is too large (${formatFileSize(stats.size)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2545
+ }
2546
+ let buffer;
2547
+ try {
2548
+ buffer = await readFile3(resolvedPath);
2549
+ } catch (error) {
2550
+ return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
2551
+ }
2552
+ const fileName = path4.basename(resolvedPath);
2553
+ try {
2554
+ return resultWithImage(`source=${source}
2555
+ size=${formatFileSize(buffer.length)}`, buffer, {
2556
+ description: `Image: ${fileName}`,
2557
+ fileName
2558
+ });
2559
+ } catch (error) {
2560
+ if (getErrorMessage2(error).includes("MIME type")) {
2561
+ return `error: File "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
2562
+ }
2563
+ throw error;
2564
+ }
2565
+ }
2566
+ async function fetchImageFromUrl(source) {
2567
+ const response = await fetch(source, {
2568
+ signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),
2569
+ headers: { "User-Agent": USER_AGENT }
2570
+ });
2571
+ if (!response.ok) {
2572
+ return `error: Failed to fetch image: HTTP ${response.status} ${response.statusText}`;
2573
+ }
2574
+ const contentLength = response.headers.get("content-length");
2575
+ if (contentLength && Number.parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
2576
+ return `error: Image at URL is too large (${formatFileSize(Number.parseInt(contentLength, 10))}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2577
+ }
2578
+ const arrayBuffer = await response.arrayBuffer();
2579
+ const buffer = Buffer.from(arrayBuffer);
2580
+ if (buffer.length > MAX_IMAGE_SIZE) {
2581
+ return `error: Downloaded image is too large (${formatFileSize(buffer.length)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
2582
+ }
2583
+ try {
2584
+ const result = resultWithImage(
2585
+ `source=${source}
2586
+ size=${formatFileSize(buffer.length)}`,
2587
+ buffer,
2588
+ { description: `Image from ${source}` }
2589
+ );
2590
+ const detectedMime = result.media?.[0]?.mimeType;
2591
+ const fileName = fileNameFromUrl(source) ?? `image${detectedMime && MIME_TO_EXT[detectedMime] || ".bin"}`;
2592
+ if (result.media?.[0]) {
2593
+ result.media[0].fileName = fileName;
2594
+ }
2595
+ return result;
2596
+ } catch (error) {
2597
+ if (getErrorMessage2(error).includes("MIME type")) {
2598
+ return `error: Content at "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
2599
+ }
2600
+ throw error;
2601
+ }
2602
+ }
2603
+ var readImage = createGadget8({
2604
+ name: "ReadImage",
2605
+ 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.",
2606
+ schema: z8.object({
2607
+ source: z8.string().min(1).describe("Path to a local image file or an HTTP/HTTPS URL")
2608
+ }),
2609
+ examples: [
2610
+ {
2611
+ params: { source: "./screenshot.png" },
2612
+ output: "source=./screenshot.png\nsize=1.2 MB",
2613
+ comment: "Read a local PNG image"
2614
+ },
2615
+ {
2616
+ params: { source: "https://example.com/photo.jpg" },
2617
+ output: "source=https://example.com/photo.jpg\nsize=245.3 KB",
2618
+ comment: "Fetch an image from a URL"
2619
+ },
2620
+ {
2621
+ params: { source: "/home/user/photos/cat.webp" },
2622
+ output: "source=/home/user/photos/cat.webp\nsize=89.4 KB",
2623
+ comment: "Read an image with absolute path (no CWD restriction)"
2624
+ }
2625
+ ],
2626
+ execute: async ({ source }) => {
2627
+ try {
2628
+ if (isUrl(source)) {
2629
+ return await fetchImageFromUrl(source);
2630
+ }
2631
+ return await readImageFromFile(source);
2632
+ } catch (error) {
2633
+ return `error: ${getErrorMessage2(error)}`;
2634
+ }
2635
+ }
2636
+ });
2637
+
2478
2638
  // src/builtins/run-command.ts
2479
- import { createGadget as createGadget7 } from "llmist";
2480
- import { z as z7 } from "zod";
2639
+ import { createGadget as createGadget9 } from "llmist";
2640
+ import { z as z9 } from "zod";
2481
2641
 
2482
2642
  // src/spawn.ts
2483
2643
  import { spawn as nodeSpawn } from "child_process";
@@ -2538,13 +2698,13 @@ function spawn2(argv, options = {}) {
2538
2698
  }
2539
2699
 
2540
2700
  // src/builtins/run-command.ts
2541
- var runCommand = createGadget7({
2701
+ var runCommand = createGadget9({
2542
2702
  name: "RunCommand",
2543
2703
  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)")
2704
+ schema: z9.object({
2705
+ argv: z9.array(z9.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
2706
+ cwd: z9.string().optional().describe("Working directory for the command (default: current directory)"),
2707
+ timeout: z9.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
2548
2708
  }),
2549
2709
  examples: [
2550
2710
  {
@@ -2649,8 +2809,10 @@ error: ${message}`;
2649
2809
  var builtinGadgetRegistry = {
2650
2810
  ListDirectory: listDirectory,
2651
2811
  ReadFile: readFile2,
2812
+ ReadImage: readImage,
2652
2813
  WriteFile: writeFile,
2653
2814
  EditFile: editFile,
2815
+ DeleteFile: deleteFile,
2654
2816
  RunCommand: runCommand,
2655
2817
  TextToSpeech: textToSpeech
2656
2818
  };
@@ -2660,12 +2822,15 @@ function getBuiltinGadget(name) {
2660
2822
  function isBuiltinGadgetName(name) {
2661
2823
  return name in builtinGadgetRegistry;
2662
2824
  }
2825
+ function getBuiltinGadgetNames() {
2826
+ return Object.keys(builtinGadgetRegistry);
2827
+ }
2663
2828
 
2664
2829
  // src/external-gadgets.ts
2665
2830
  import { execSync } from "child_process";
2666
- import fs5 from "fs";
2831
+ import fs6 from "fs";
2667
2832
  import os from "os";
2668
- import path4 from "path";
2833
+ import path5 from "path";
2669
2834
  import { pathToFileURL } from "url";
2670
2835
  function isCommandAvailable(cmd) {
2671
2836
  try {
@@ -2676,7 +2841,7 @@ function isCommandAvailable(cmd) {
2676
2841
  return false;
2677
2842
  }
2678
2843
  }
2679
- var CACHE_DIR = path4.join(os.homedir(), ".llmist", "gadget-cache");
2844
+ var CACHE_DIR = path5.join(os.homedir(), ".llmist", "gadget-cache");
2680
2845
  function isExternalPackageSpecifier(specifier) {
2681
2846
  if (/^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/[A-Z]\w*)?$/i.test(
2682
2847
  specifier
@@ -2766,21 +2931,21 @@ function parseGadgetSpecifier(specifier) {
2766
2931
  function getCacheDir(spec) {
2767
2932
  const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
2768
2933
  if (spec.type === "npm") {
2769
- return path4.join(CACHE_DIR, "npm", `${spec.package}${versionSuffix}`);
2934
+ return path5.join(CACHE_DIR, "npm", `${spec.package}${versionSuffix}`);
2770
2935
  }
2771
2936
  const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
2772
- return path4.join(CACHE_DIR, "git", `${sanitizedUrl}${versionSuffix}`);
2937
+ return path5.join(CACHE_DIR, "git", `${sanitizedUrl}${versionSuffix}`);
2773
2938
  }
2774
2939
  function isCached(cacheDir) {
2775
- const packageJsonPath = path4.join(cacheDir, "package.json");
2776
- if (!fs5.existsSync(packageJsonPath)) {
2940
+ const packageJsonPath = path5.join(cacheDir, "package.json");
2941
+ if (!fs6.existsSync(packageJsonPath)) {
2777
2942
  return false;
2778
2943
  }
2779
2944
  try {
2780
- const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
2945
+ const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
2781
2946
  const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
2782
- const entryPointPath = path4.join(cacheDir, entryPoint);
2783
- if (!fs5.existsSync(entryPointPath)) {
2947
+ const entryPointPath = path5.join(cacheDir, entryPoint);
2948
+ if (!fs6.existsSync(entryPointPath)) {
2784
2949
  return false;
2785
2950
  }
2786
2951
  } catch {
@@ -2789,13 +2954,13 @@ function isCached(cacheDir) {
2789
2954
  return true;
2790
2955
  }
2791
2956
  async function installNpmPackage(spec, cacheDir) {
2792
- fs5.mkdirSync(cacheDir, { recursive: true });
2957
+ fs6.mkdirSync(cacheDir, { recursive: true });
2793
2958
  const packageJson = {
2794
2959
  name: "llmist-gadget-cache",
2795
2960
  private: true,
2796
2961
  type: "module"
2797
2962
  };
2798
- fs5.writeFileSync(path4.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
2963
+ fs6.writeFileSync(path5.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
2799
2964
  const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
2800
2965
  const quietEnv = { ...process.env, DISABLE_OPENCOLLECTIVE: "1", ADBLOCK: "1" };
2801
2966
  if (!isCommandAvailable("npm")) {
@@ -2813,18 +2978,18 @@ Please install Node.js: https://nodejs.org/`
2813
2978
  }
2814
2979
  }
2815
2980
  async function installGitPackage(spec, cacheDir) {
2816
- fs5.mkdirSync(path4.dirname(cacheDir), { recursive: true });
2817
- if (fs5.existsSync(cacheDir)) {
2981
+ fs6.mkdirSync(path5.dirname(cacheDir), { recursive: true });
2982
+ if (fs6.existsSync(cacheDir)) {
2818
2983
  try {
2819
2984
  execSync("git fetch", { cwd: cacheDir, stdio: "pipe" });
2820
2985
  if (spec.version) {
2821
2986
  execSync(`git checkout ${spec.version}`, { cwd: cacheDir, stdio: "pipe" });
2822
2987
  }
2823
2988
  } catch (error) {
2824
- fs5.rmSync(cacheDir, { recursive: true, force: true });
2989
+ fs6.rmSync(cacheDir, { recursive: true, force: true });
2825
2990
  }
2826
2991
  }
2827
- if (!fs5.existsSync(cacheDir)) {
2992
+ if (!fs6.existsSync(cacheDir)) {
2828
2993
  try {
2829
2994
  const cloneCmd = spec.version ? `git clone --branch ${spec.version} "${spec.package}" "${cacheDir}"` : `git clone "${spec.package}" "${cacheDir}"`;
2830
2995
  execSync(cloneCmd, { stdio: "pipe" });
@@ -2832,7 +2997,7 @@ async function installGitPackage(spec, cacheDir) {
2832
2997
  const message = error instanceof Error ? error.message : String(error);
2833
2998
  throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
2834
2999
  }
2835
- if (fs5.existsSync(path4.join(cacheDir, "package.json"))) {
3000
+ if (fs6.existsSync(path5.join(cacheDir, "package.json"))) {
2836
3001
  const hasNpm = isCommandAvailable("npm");
2837
3002
  if (!hasNpm) {
2838
3003
  throw new Error(
@@ -2851,14 +3016,14 @@ Please install Node.js: https://nodejs.org/`
2851
3016
  `Failed to install dependencies for '${spec.package}' using npm: ${message}`
2852
3017
  );
2853
3018
  }
2854
- const packageJson = JSON.parse(fs5.readFileSync(path4.join(cacheDir, "package.json"), "utf-8"));
3019
+ const packageJson = JSON.parse(fs6.readFileSync(path5.join(cacheDir, "package.json"), "utf-8"));
2855
3020
  if (packageJson.scripts?.build) {
2856
3021
  try {
2857
3022
  execSync(`${runCmd} build`, { cwd: cacheDir, stdio: "inherit", env: quietEnv });
2858
3023
  } catch (error) {
2859
3024
  const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
2860
- const entryPointPath = path4.join(cacheDir, entryPoint);
2861
- if (!fs5.existsSync(entryPointPath)) {
3025
+ const entryPointPath = path5.join(cacheDir, entryPoint);
3026
+ if (!fs6.existsSync(entryPointPath)) {
2862
3027
  const message = error instanceof Error ? error.message : String(error);
2863
3028
  throw new Error(`Failed to build package '${spec.package}': ${message}`);
2864
3029
  }
@@ -2868,20 +3033,20 @@ Please install Node.js: https://nodejs.org/`
2868
3033
  }
2869
3034
  }
2870
3035
  function readManifest(packageDir) {
2871
- const packageJsonPath = path4.join(packageDir, "package.json");
2872
- if (!fs5.existsSync(packageJsonPath)) {
3036
+ const packageJsonPath = path5.join(packageDir, "package.json");
3037
+ if (!fs6.existsSync(packageJsonPath)) {
2873
3038
  return null;
2874
3039
  }
2875
3040
  try {
2876
- const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
3041
+ const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
2877
3042
  return packageJson.llmist || null;
2878
3043
  } catch {
2879
3044
  return null;
2880
3045
  }
2881
3046
  }
2882
3047
  function getPackagePath(cacheDir, packageName) {
2883
- const nodeModulesPath = path4.join(cacheDir, "node_modules", packageName);
2884
- if (fs5.existsSync(nodeModulesPath)) {
3048
+ const nodeModulesPath = path5.join(cacheDir, "node_modules", packageName);
3049
+ if (fs6.existsSync(nodeModulesPath)) {
2885
3050
  return nodeModulesPath;
2886
3051
  }
2887
3052
  return cacheDir;
@@ -2927,8 +3092,8 @@ async function loadExternalGadgets(specifier, forceInstall = false) {
2927
3092
  } else {
2928
3093
  entryPoint = manifest?.gadgets || "./dist/index.js";
2929
3094
  }
2930
- const resolvedEntryPoint = path4.resolve(packagePath, entryPoint);
2931
- if (!fs5.existsSync(resolvedEntryPoint)) {
3095
+ const resolvedEntryPoint = path5.resolve(packagePath, entryPoint);
3096
+ if (!fs6.existsSync(resolvedEntryPoint)) {
2932
3097
  throw new Error(
2933
3098
  `Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
2934
3099
  );
@@ -3071,7 +3236,7 @@ function parseLocalSpecifier(specifier) {
3071
3236
  return { path: specifier };
3072
3237
  }
3073
3238
  function isFileLikeSpecifier(specifier) {
3074
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path5.sep);
3239
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path6.sep);
3075
3240
  }
3076
3241
  function tryResolveBuiltin(specifier) {
3077
3242
  if (specifier.startsWith(BUILTIN_PREFIX)) {
@@ -3079,7 +3244,7 @@ function tryResolveBuiltin(specifier) {
3079
3244
  const gadget = getBuiltinGadget(name);
3080
3245
  if (!gadget) {
3081
3246
  throw new Error(
3082
- `Unknown builtin gadget: ${name}. Available builtins: ListDirectory, ReadFile, WriteFile, EditFile, RunCommand`
3247
+ `Unknown builtin gadget: ${name}. Available builtins: ${getBuiltinGadgetNames().join(", ")}`
3083
3248
  );
3084
3249
  }
3085
3250
  return gadget;
@@ -3094,8 +3259,8 @@ function resolveGadgetSpecifier(specifier, cwd) {
3094
3259
  return specifier;
3095
3260
  }
3096
3261
  const expanded = expandTildePath(specifier);
3097
- const resolvedPath = path5.resolve(cwd, expanded);
3098
- if (!fs6.existsSync(resolvedPath)) {
3262
+ const resolvedPath = path6.resolve(cwd, expanded);
3263
+ if (!fs7.existsSync(resolvedPath)) {
3099
3264
  throw new Error(`Gadget module not found at ${resolvedPath}`);
3100
3265
  }
3101
3266
  return pathToFileURL2(resolvedPath).href;
@@ -3262,7 +3427,7 @@ async function resolvePrompt(promptArg, env) {
3262
3427
  }
3263
3428
 
3264
3429
  // src/ui/formatters.ts
3265
- import chalk3 from "chalk";
3430
+ import chalk4 from "chalk";
3266
3431
  import { format } from "llmist";
3267
3432
 
3268
3433
  // src/ui/call-number.ts
@@ -3376,6 +3541,62 @@ function stripProviderPrefix(model) {
3376
3541
  return model.includes(":") ? model.split(":")[1] : model;
3377
3542
  }
3378
3543
 
3544
+ // src/ui/metric-parts.ts
3545
+ import chalk3 from "chalk";
3546
+ function tokenPart(direction, tokens, opts) {
3547
+ const prefix = opts?.estimated ? "~" : "";
3548
+ const formatted = `${prefix}${formatTokens(tokens)}`;
3549
+ switch (direction) {
3550
+ case "input":
3551
+ return chalk3.dim("\u2191") + chalk3.yellow(` ${formatted}`);
3552
+ case "cached":
3553
+ return chalk3.dim("\u27F3") + chalk3.blue(` ${formatted}`);
3554
+ case "output":
3555
+ return chalk3.dim("\u2193") + chalk3.green(` ${formatted}`);
3556
+ case "reasoning":
3557
+ return chalk3.dim("\u{1F4AD}") + chalk3.magenta(` ${formatted}`);
3558
+ case "cacheCreation":
3559
+ return chalk3.dim("\u270E") + chalk3.magenta(` ${formatted}`);
3560
+ }
3561
+ }
3562
+ function costPart(cost) {
3563
+ return chalk3.cyan(`$${formatCost(cost)}`);
3564
+ }
3565
+ function timePart(seconds) {
3566
+ return chalk3.dim(`${seconds.toFixed(1)}s`);
3567
+ }
3568
+ function finishReasonPart(reason) {
3569
+ const upper = reason.toUpperCase();
3570
+ if (upper === "STOP" || upper === "END_TURN") {
3571
+ return chalk3.green(upper);
3572
+ }
3573
+ return chalk3.yellow(upper);
3574
+ }
3575
+ function buildTokenMetrics(opts) {
3576
+ const parts = [];
3577
+ if (opts.input && opts.input > 0) {
3578
+ parts.push(tokenPart("input", opts.input, { estimated: opts.estimated?.input }));
3579
+ }
3580
+ if (opts.cached && opts.cached > 0) {
3581
+ parts.push(tokenPart("cached", opts.cached));
3582
+ }
3583
+ if (opts.output && opts.output > 0) {
3584
+ parts.push(tokenPart("output", opts.output, { estimated: opts.estimated?.output }));
3585
+ }
3586
+ if (opts.reasoning && opts.reasoning > 0) {
3587
+ parts.push(tokenPart("reasoning", opts.reasoning));
3588
+ }
3589
+ if (opts.cacheCreation && opts.cacheCreation > 0) {
3590
+ parts.push(tokenPart("cacheCreation", opts.cacheCreation));
3591
+ }
3592
+ return parts;
3593
+ }
3594
+ function joinParts(parts) {
3595
+ const filtered = parts.filter((p) => p.length > 0);
3596
+ if (filtered.length === 0) return "";
3597
+ return filtered.join(chalk3.dim(" | "));
3598
+ }
3599
+
3379
3600
  // src/ui/formatters.ts
3380
3601
  function formatLLMCallLine(info) {
3381
3602
  const parts = [];
@@ -3384,85 +3605,81 @@ function formatLLMCallLine(info) {
3384
3605
  info.parentCallNumber,
3385
3606
  info.gadgetInvocationId
3386
3607
  );
3387
- parts.push(`${chalk3.cyan(callNumber)} ${chalk3.magenta(info.model)}`);
3608
+ parts.push(`${chalk4.cyan(callNumber)} ${chalk4.magenta(info.model)}`);
3388
3609
  if (info.contextPercent !== void 0 && info.contextPercent !== null) {
3389
3610
  const formatted = `${Math.round(info.contextPercent)}%`;
3390
3611
  if (info.contextPercent >= 80) {
3391
- parts.push(chalk3.red(formatted));
3612
+ parts.push(chalk4.red(formatted));
3392
3613
  } else if (info.contextPercent >= 50) {
3393
- parts.push(chalk3.yellow(formatted));
3614
+ parts.push(chalk4.yellow(formatted));
3394
3615
  } else {
3395
- parts.push(chalk3.green(formatted));
3616
+ parts.push(chalk4.green(formatted));
3396
3617
  }
3397
3618
  }
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
- }
3619
+ parts.push(
3620
+ ...buildTokenMetrics({
3621
+ input: info.inputTokens,
3622
+ cached: info.cachedInputTokens,
3623
+ estimated: info.estimated
3624
+ })
3625
+ );
3405
3626
  if (info.outputTokens !== void 0 && info.outputTokens > 0 || info.isStreaming) {
3406
3627
  const prefix = info.estimated?.output ? "~" : "";
3407
- parts.push(chalk3.dim("\u2193") + chalk3.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
3628
+ parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
3408
3629
  }
3409
- parts.push(chalk3.dim(`${info.elapsedSeconds.toFixed(1)}s`));
3630
+ parts.push(timePart(info.elapsedSeconds));
3410
3631
  if (info.cost !== void 0 && info.cost > 0) {
3411
- parts.push(chalk3.cyan(`$${formatCost(info.cost)}`));
3632
+ parts.push(costPart(info.cost));
3412
3633
  }
3413
3634
  if (!info.isStreaming && info.finishReason !== void 0) {
3414
3635
  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
- }
3636
+ parts.push(finishReasonPart(reason));
3420
3637
  }
3421
- const line = parts.join(chalk3.dim(" | "));
3638
+ const line = joinParts(parts);
3422
3639
  if (info.isStreaming && info.spinner) {
3423
- return `${chalk3.cyan(info.spinner)} ${line}`;
3640
+ return `${chalk4.cyan(info.spinner)} ${line}`;
3424
3641
  }
3425
3642
  if (!info.isStreaming) {
3426
- return `${chalk3.green("\u2713")} ${line}`;
3643
+ return `${chalk4.green("\u2713")} ${line}`;
3427
3644
  }
3428
3645
  return line;
3429
3646
  }
3430
3647
  function renderSummary(metadata) {
3431
3648
  const parts = [];
3432
3649
  if (metadata.iterations !== void 0) {
3433
- const iterPart = chalk3.cyan(`#${metadata.iterations}`);
3650
+ const iterPart = chalk4.cyan(`#${metadata.iterations}`);
3434
3651
  if (metadata.model) {
3435
- parts.push(`${iterPart} ${chalk3.magenta(metadata.model)}`);
3652
+ parts.push(`${iterPart} ${chalk4.magenta(metadata.model)}`);
3436
3653
  } else {
3437
3654
  parts.push(iterPart);
3438
3655
  }
3439
3656
  } else if (metadata.model) {
3440
- parts.push(chalk3.magenta(metadata.model));
3657
+ parts.push(chalk4.magenta(metadata.model));
3441
3658
  }
3442
3659
  if (metadata.usage) {
3443
3660
  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)}`));
3661
+ parts.push(
3662
+ ...buildTokenMetrics({
3663
+ input: inputTokens,
3664
+ cached: cachedInputTokens,
3665
+ cacheCreation: cacheCreationInputTokens,
3666
+ output: outputTokens
3667
+ })
3668
+ );
3452
3669
  }
3453
3670
  if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
3454
- parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
3671
+ parts.push(timePart(metadata.elapsedSeconds));
3455
3672
  }
3456
3673
  if (metadata.cost !== void 0 && metadata.cost > 0) {
3457
- parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
3674
+ parts.push(costPart(metadata.cost));
3458
3675
  }
3459
3676
  if (metadata.finishReason) {
3460
- parts.push(chalk3.dim(metadata.finishReason));
3677
+ parts.push(chalk4.dim(metadata.finishReason));
3461
3678
  }
3462
3679
  if (parts.length === 0) {
3463
3680
  return null;
3464
3681
  }
3465
- return parts.join(chalk3.dim(" | "));
3682
+ return joinParts(parts);
3466
3683
  }
3467
3684
  function getRawValue(value) {
3468
3685
  if (typeof value === "string") {
@@ -3516,48 +3733,48 @@ function formatParametersInline(params, maxWidth) {
3516
3733
  }
3517
3734
  return entries.map(([key, _], i) => {
3518
3735
  const formatted = truncateValue(rawValues[i], limits[i]);
3519
- return `${chalk3.dim(key)}${chalk3.dim("=")}${chalk3.cyan(formatted)}`;
3520
- }).join(chalk3.dim(", "));
3736
+ return `${chalk4.dim(key)}${chalk4.dim("=")}${chalk4.cyan(formatted)}`;
3737
+ }).join(chalk4.dim(", "));
3521
3738
  }
3522
3739
  function formatGadgetLine(info, maxWidth) {
3523
3740
  const terminalWidth = maxWidth ?? process.stdout.columns ?? 80;
3524
- const gadgetLabel = chalk3.magenta.bold(info.name);
3741
+ const gadgetLabel = chalk4.magenta.bold(info.name);
3525
3742
  const timeStr = `${info.elapsedSeconds.toFixed(1)}s`;
3526
- const timeLabel = chalk3.dim(timeStr);
3743
+ const timeLabel = chalk4.dim(timeStr);
3527
3744
  const fixedLength = 3 + info.name.length + 2 + 1 + timeStr.length;
3528
3745
  const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
3529
3746
  const paramsStr = formatParametersInline(info.parameters, availableForParams);
3530
- const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
3747
+ const paramsLabel = paramsStr ? `${chalk4.dim("(")}${paramsStr}${chalk4.dim(")")}` : "";
3531
3748
  if (info.error) {
3532
3749
  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}`;
3750
+ return `${chalk4.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk4.red("error:")} ${errorMsg} ${timeLabel}`;
3534
3751
  }
3535
3752
  if (!info.isComplete) {
3536
3753
  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
- }
3754
+ parts.push(
3755
+ ...buildTokenMetrics({
3756
+ input: info.subagentInputTokens,
3757
+ output: info.subagentOutputTokens
3758
+ })
3759
+ );
3543
3760
  if (info.subagentCost && info.subagentCost > 0) {
3544
- parts.push(chalk3.cyan(`$${formatCost(info.subagentCost)}`));
3761
+ parts.push(costPart(info.subagentCost));
3545
3762
  }
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}`;
3763
+ parts.push(timePart(info.elapsedSeconds));
3764
+ const metricsStr = parts.length > 0 ? ` ${joinParts(parts)}` : "";
3765
+ return `${chalk4.blue("\u23F5")} ${gadgetLabel}${metricsStr}`;
3549
3766
  }
3550
3767
  let outputLabel;
3551
3768
  if (info.tokenCount !== void 0 && info.tokenCount > 0) {
3552
- outputLabel = chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(info.tokenCount)} `);
3769
+ outputLabel = chalk4.dim("\u2193") + chalk4.green(` ${formatTokens(info.tokenCount)} `);
3553
3770
  } else if (info.outputBytes !== void 0 && info.outputBytes > 0) {
3554
- outputLabel = `${chalk3.green(format.bytes(info.outputBytes))} `;
3771
+ outputLabel = `${chalk4.green(format.bytes(info.outputBytes))} `;
3555
3772
  } else {
3556
3773
  outputLabel = "";
3557
3774
  }
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}`;
3775
+ const resultIcon = info.breaksLoop ? chalk4.yellow("\u23F9") : chalk4.green("\u2713");
3776
+ const nameRef = chalk4.magenta(info.name);
3777
+ const line1 = `${chalk4.dim("\u2192")} ${gadgetLabel}${paramsLabel}`;
3561
3778
  const line2Prefix = ` ${resultIcon} ${nameRef} ${outputLabel}`;
3562
3779
  const line2 = `${line2Prefix}${timeLabel}`;
3563
3780
  return `${line1}
@@ -3979,7 +4196,7 @@ var NestedOperationTracker = class {
3979
4196
  };
3980
4197
 
3981
4198
  // src/progress/progress-renderer.ts
3982
- import chalk4 from "chalk";
4199
+ import chalk5 from "chalk";
3983
4200
  import { FALLBACK_CHARS_PER_TOKEN } from "llmist";
3984
4201
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3985
4202
  var SPINNER_DELAY_MS = 500;
@@ -4163,7 +4380,7 @@ var ProgressRenderer = class {
4163
4380
  const elapsedSeconds2 = (Date.now() - op.startTime) / 1e3;
4164
4381
  const nestedMetrics = op.id ? this.nestedOperationTracker.getAggregatedSubagentMetrics(op.id) : { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0, cost: 0, callCount: 0 };
4165
4382
  const termWidth2 = process.stdout.columns ?? 80;
4166
- const parentPrefix = `${chalk4.dim(`${gadget.name}:`)} `;
4383
+ const parentPrefix = `${chalk5.dim(`${gadget.name}:`)} `;
4167
4384
  const line2 = formatGadgetLine(
4168
4385
  {
4169
4386
  name: op.name ?? "",
@@ -4186,7 +4403,7 @@ var ProgressRenderer = class {
4186
4403
  }
4187
4404
  for (const stream of activeNestedStreams) {
4188
4405
  const indent = " ".repeat(stream.depth + 2);
4189
- const parentPrefix = `${chalk4.dim(`${stream.parentGadgetName}:`)} `;
4406
+ const parentPrefix = `${chalk5.dim(`${stream.parentGadgetName}:`)} `;
4190
4407
  const elapsedSeconds = (Date.now() - stream.startTime) / 1e3;
4191
4408
  const line = formatLLMCallLine({
4192
4409
  iteration: stream.iteration,
@@ -4244,21 +4461,19 @@ var ProgressRenderer = class {
4244
4461
  const elapsed = ((Date.now() - this.callStatsTracker.totalStartTime) / 1e3).toFixed(1);
4245
4462
  const parts = [];
4246
4463
  if (this.callStatsTracker.model) {
4247
- parts.push(chalk4.cyan(this.callStatsTracker.model));
4464
+ parts.push(chalk5.cyan(this.callStatsTracker.model));
4248
4465
  }
4249
4466
  if (this.callStatsTracker.totalTokens > 0) {
4250
- parts.push(chalk4.dim("total:") + chalk4.magenta(` ${this.callStatsTracker.totalTokens}`));
4467
+ parts.push(chalk5.dim("total:") + chalk5.magenta(` ${this.callStatsTracker.totalTokens}`));
4251
4468
  }
4252
4469
  if (this.callStatsTracker.iterations > 0) {
4253
- parts.push(chalk4.dim("iter:") + chalk4.blue(` ${this.callStatsTracker.iterations}`));
4470
+ parts.push(chalk5.dim("iter:") + chalk5.blue(` ${this.callStatsTracker.iterations}`));
4254
4471
  }
4255
4472
  if (this.callStatsTracker.totalCost > 0) {
4256
- parts.push(
4257
- chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.callStatsTracker.totalCost)}`)
4258
- );
4473
+ parts.push(`${chalk5.dim("cost:")} ${costPart(this.callStatsTracker.totalCost)}`);
4259
4474
  }
4260
- parts.push(chalk4.dim(`${elapsed}s`));
4261
- return `${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`;
4475
+ parts.push(chalk5.dim(`${elapsed}s`));
4476
+ return `${parts.join(chalk5.dim(" | "))} ${chalk5.cyan(spinner)}`;
4262
4477
  }
4263
4478
  /**
4264
4479
  * Returns a formatted stats string for cancellation messages.
@@ -4269,15 +4484,21 @@ var ProgressRenderer = class {
4269
4484
  const elapsed = ((Date.now() - this.callStatsTracker.callStartTime) / 1e3).toFixed(1);
4270
4485
  const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
4271
4486
  if (this.callStatsTracker.callInputTokens > 0) {
4272
- const prefix = this.callStatsTracker.callInputTokensEstimated ? "~" : "";
4273
- parts.push(`\u2191 ${prefix}${formatTokens(this.callStatsTracker.callInputTokens)}`);
4487
+ parts.push(
4488
+ tokenPart("input", this.callStatsTracker.callInputTokens, {
4489
+ estimated: this.callStatsTracker.callInputTokensEstimated
4490
+ })
4491
+ );
4274
4492
  }
4275
4493
  if (outTokens > 0) {
4276
- const prefix = this.callStatsTracker.callOutputTokensEstimated ? "~" : "";
4277
- parts.push(`\u2193 ${prefix}${formatTokens(outTokens)}`);
4494
+ parts.push(
4495
+ tokenPart("output", outTokens, {
4496
+ estimated: this.callStatsTracker.callOutputTokensEstimated
4497
+ })
4498
+ );
4278
4499
  }
4279
4500
  parts.push(`${elapsed}s`);
4280
- return parts.join(" | ");
4501
+ return joinParts(parts);
4281
4502
  }
4282
4503
  /**
4283
4504
  * Returns a formatted prompt string with stats (like bash PS1).
@@ -4291,30 +4512,30 @@ var ProgressRenderer = class {
4291
4512
  const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
4292
4513
  const outEstimated = this.callStatsTracker.callOutputTokensEstimated;
4293
4514
  if (this.callStatsTracker.callInputTokens > 0) {
4294
- const prefix = this.callStatsTracker.callInputTokensEstimated ? "~" : "";
4295
4515
  parts.push(
4296
- chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callStatsTracker.callInputTokens)}`)
4516
+ tokenPart("input", this.callStatsTracker.callInputTokens, {
4517
+ estimated: this.callStatsTracker.callInputTokensEstimated
4518
+ })
4297
4519
  );
4298
4520
  }
4299
4521
  if (outTokens > 0) {
4300
- const prefix = outEstimated ? "~" : "";
4301
- parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
4522
+ parts.push(tokenPart("output", outTokens, { estimated: outEstimated }));
4302
4523
  }
4303
- parts.push(chalk4.dim(`${elapsed}s`));
4524
+ parts.push(chalk5.dim(`${elapsed}s`));
4304
4525
  } else {
4305
4526
  const elapsed = Math.round((Date.now() - this.callStatsTracker.totalStartTime) / 1e3);
4306
4527
  if (this.callStatsTracker.totalTokens > 0) {
4307
- parts.push(chalk4.magenta(formatTokens(this.callStatsTracker.totalTokens)));
4528
+ parts.push(chalk5.magenta(formatTokens(this.callStatsTracker.totalTokens)));
4308
4529
  }
4309
4530
  if (this.callStatsTracker.iterations > 0) {
4310
- parts.push(chalk4.blue(`i${this.callStatsTracker.iterations}`));
4531
+ parts.push(chalk5.blue(`i${this.callStatsTracker.iterations}`));
4311
4532
  }
4312
4533
  if (this.callStatsTracker.totalCost > 0) {
4313
- parts.push(chalk4.cyan(`$${formatCost(this.callStatsTracker.totalCost)}`));
4534
+ parts.push(costPart(this.callStatsTracker.totalCost));
4314
4535
  }
4315
- parts.push(chalk4.dim(`${elapsed}s`));
4536
+ parts.push(chalk5.dim(`${elapsed}s`));
4316
4537
  }
4317
- return `${parts.join(chalk4.dim(" | "))} ${chalk4.green(">")} `;
4538
+ return `${parts.join(chalk5.dim(" | "))} ${chalk5.green(">")} `;
4318
4539
  }
4319
4540
  };
4320
4541
 
@@ -4556,7 +4777,7 @@ function addAgentOptions(cmd, defaults) {
4556
4777
  ...previous,
4557
4778
  value
4558
4779
  ];
4559
- const defaultGadgets = defaults?.gadgets ?? defaults?.gadget ?? [];
4780
+ const defaultGadgets = defaults?.gadgets ?? [];
4560
4781
  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
4782
  OPTION_FLAGS.temperature,
4562
4783
  OPTION_DESCRIPTIONS.temperature,
@@ -4658,8 +4879,7 @@ function configToAgentOptions(config) {
4658
4879
  if (config.temperature !== void 0) result.temperature = config.temperature;
4659
4880
  if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
4660
4881
  if (config.budget !== void 0) result.budget = config.budget;
4661
- const gadgets = config.gadgets ?? config.gadget;
4662
- if (gadgets !== void 0) result.gadget = gadgets;
4882
+ if (config.gadgets !== void 0) result.gadget = config.gadgets;
4663
4883
  if (config.builtins !== void 0) result.builtins = config.builtins;
4664
4884
  if (config["builtin-interaction"] !== void 0)
4665
4885
  result.builtinInteraction = config["builtin-interaction"];
@@ -4910,11 +5130,34 @@ function extractSubagentConfig(globalConfig, subagentName) {
4910
5130
  return {};
4911
5131
  }
4912
5132
 
5133
+ // src/tui/raw-viewer-data.ts
5134
+ function isRawViewerNode(node) {
5135
+ return node.type === "llm_call" || node.type === "gadget";
5136
+ }
5137
+ function createRawViewerData(node, mode) {
5138
+ if (node.type === "llm_call") {
5139
+ return {
5140
+ mode,
5141
+ request: node.rawRequest,
5142
+ response: node.rawResponse,
5143
+ iteration: node.iteration,
5144
+ model: node.model
5145
+ };
5146
+ }
5147
+ return {
5148
+ mode,
5149
+ gadgetName: node.name,
5150
+ parameters: node.parameters,
5151
+ result: node.result,
5152
+ error: node.error
5153
+ };
5154
+ }
5155
+
4913
5156
  // src/tui/block-renderer.ts
4914
5157
  import { Box } from "@unblessed/node";
4915
5158
 
4916
5159
  // src/ui/block-formatters.ts
4917
- import chalk5 from "chalk";
5160
+ import chalk6 from "chalk";
4918
5161
  var BOX = {
4919
5162
  topLeft: "\u250C",
4920
5163
  topRight: "\u2510",
@@ -4930,44 +5173,35 @@ var COMPLETE_INDICATOR = "\u2713";
4930
5173
  var ERROR_INDICATOR = "\u2717";
4931
5174
  function formatLLMCallCollapsed(node, selected) {
4932
5175
  const indicator = node.isComplete ? COMPLETE_INDICATOR : PROGRESS_INDICATOR;
4933
- const indicatorColor = node.isComplete ? chalk5.green : chalk5.blue;
5176
+ const indicatorColor = node.isComplete ? chalk6.green : chalk6.blue;
4934
5177
  const parts = [];
4935
- const callNumber = chalk5.cyan(`#${node.iteration}`);
4936
- const model = chalk5.magenta(node.model);
5178
+ const callNumber = chalk6.cyan(`#${node.iteration}`);
5179
+ const model = chalk6.magenta(node.model);
4937
5180
  parts.push(`${callNumber} ${model}`);
4938
5181
  if (node.details) {
4939
5182
  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
- }
5183
+ parts.push(
5184
+ ...buildTokenMetrics({
5185
+ input: d.inputTokens,
5186
+ cached: d.cachedInputTokens,
5187
+ output: d.outputTokens,
5188
+ reasoning: d.reasoningTokens
5189
+ })
5190
+ );
4952
5191
  if (d.elapsedSeconds !== void 0) {
4953
- parts.push(chalk5.dim(`${d.elapsedSeconds.toFixed(1)}s`));
5192
+ parts.push(timePart(d.elapsedSeconds));
4954
5193
  }
4955
5194
  if (d.cost !== void 0 && d.cost > 0) {
4956
- parts.push(chalk5.cyan(`$${formatCost(d.cost)}`));
5195
+ parts.push(costPart(d.cost));
4957
5196
  }
4958
5197
  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
- }
5198
+ parts.push(finishReasonPart(d.finishReason));
4965
5199
  }
4966
5200
  }
4967
- const line = parts.join(chalk5.dim(" | "));
5201
+ const line = joinParts(parts);
4968
5202
  const prefix = indicatorColor(indicator);
4969
5203
  if (selected) {
4970
- return chalk5.bgBlue.white(`${prefix} ${line}`);
5204
+ return chalk6.bgBlue.white(`${prefix} ${line}`);
4971
5205
  }
4972
5206
  return `${prefix} ${line}`;
4973
5207
  }
@@ -4976,58 +5210,58 @@ function formatLLMCallExpanded(node) {
4976
5210
  const indent = " ";
4977
5211
  const d = node.details;
4978
5212
  if (!d) {
4979
- lines.push(`${indent}${chalk5.dim("No details available")}`);
5213
+ lines.push(`${indent}${chalk6.dim("No details available")}`);
4980
5214
  return lines;
4981
5215
  }
4982
5216
  const width = Math.min(60, (process.stdout.columns || 80) - 4);
4983
5217
  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)}`);
5218
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5219
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} Model: ${chalk6.magenta(node.model)}`);
4986
5220
  if (d.inputTokens !== void 0) {
4987
- let inputLine = `${indent}${chalk5.dim(BOX.vertical)} Input: ${chalk5.yellow(formatTokens(d.inputTokens))} tokens`;
5221
+ let inputLine = `${indent}${chalk6.dim(BOX.vertical)} Input: ${chalk6.yellow(formatTokens(d.inputTokens))} tokens`;
4988
5222
  if (d.cachedInputTokens && d.cachedInputTokens > 0) {
4989
5223
  const cachePercent = (d.cachedInputTokens / d.inputTokens * 100).toFixed(1);
4990
- inputLine += chalk5.blue(` (${formatTokens(d.cachedInputTokens)} cached, ${cachePercent}%)`);
5224
+ inputLine += chalk6.blue(` (${formatTokens(d.cachedInputTokens)} cached, ${cachePercent}%)`);
4991
5225
  }
4992
5226
  lines.push(inputLine);
4993
5227
  }
4994
5228
  if (d.outputTokens !== void 0) {
4995
5229
  lines.push(
4996
- `${indent}${chalk5.dim(BOX.vertical)} Output: ${chalk5.green(formatTokens(d.outputTokens))} tokens`
5230
+ `${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(formatTokens(d.outputTokens))} tokens`
4997
5231
  );
4998
5232
  }
4999
5233
  if (d.reasoningTokens !== void 0 && d.reasoningTokens > 0) {
5000
5234
  lines.push(
5001
- `${indent}${chalk5.dim(BOX.vertical)} Reason: ${chalk5.magenta(formatTokens(d.reasoningTokens))} tokens`
5235
+ `${indent}${chalk6.dim(BOX.vertical)} Reason: ${chalk6.magenta(formatTokens(d.reasoningTokens))} tokens`
5002
5236
  );
5003
5237
  }
5004
5238
  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;
5239
+ let contextColor = chalk6.green;
5240
+ if (d.contextPercent >= 80) contextColor = chalk6.red;
5241
+ else if (d.contextPercent >= 50) contextColor = chalk6.yellow;
5008
5242
  lines.push(
5009
- `${indent}${chalk5.dim(BOX.vertical)} Context: ${contextColor(`${Math.round(d.contextPercent)}%`)}`
5243
+ `${indent}${chalk6.dim(BOX.vertical)} Context: ${contextColor(`${Math.round(d.contextPercent)}%`)}`
5010
5244
  );
5011
5245
  }
5012
5246
  if (d.elapsedSeconds !== void 0) {
5013
- let timeLine = `${indent}${chalk5.dim(BOX.vertical)} Time: ${chalk5.dim(`${d.elapsedSeconds.toFixed(1)}s`)}`;
5247
+ let timeLine = `${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(`${d.elapsedSeconds.toFixed(1)}s`)}`;
5014
5248
  if (d.outputTokens && d.elapsedSeconds > 0) {
5015
5249
  const tokensPerSec = Math.round(d.outputTokens / d.elapsedSeconds);
5016
- timeLine += chalk5.dim(` (${tokensPerSec} tok/s)`);
5250
+ timeLine += chalk6.dim(` (${tokensPerSec} tok/s)`);
5017
5251
  }
5018
5252
  lines.push(timeLine);
5019
5253
  }
5020
5254
  if (d.cost !== void 0 && d.cost > 0) {
5021
5255
  lines.push(
5022
- `${indent}${chalk5.dim(BOX.vertical)} Cost: ${chalk5.cyan(`$${formatCost(d.cost)}`)}`
5256
+ `${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(d.cost)}`)}`
5023
5257
  );
5024
5258
  }
5025
5259
  if (d.finishReason) {
5026
5260
  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)}`);
5261
+ const reasonColor = reason === "STOP" || reason === "END_TURN" ? chalk6.green : chalk6.yellow;
5262
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} Finish: ${reasonColor(reason)}`);
5029
5263
  }
5030
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5264
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5031
5265
  return lines;
5032
5266
  }
5033
5267
  function formatGadgetCollapsed(node, selected) {
@@ -5035,15 +5269,15 @@ function formatGadgetCollapsed(node, selected) {
5035
5269
  let indicatorColor;
5036
5270
  if (node.error) {
5037
5271
  indicator = ERROR_INDICATOR;
5038
- indicatorColor = chalk5.red;
5272
+ indicatorColor = chalk6.red;
5039
5273
  } else if (node.isComplete) {
5040
5274
  indicator = COMPLETE_INDICATOR;
5041
- indicatorColor = chalk5.green;
5275
+ indicatorColor = chalk6.green;
5042
5276
  } else {
5043
5277
  indicator = PROGRESS_INDICATOR;
5044
- indicatorColor = chalk5.blue;
5278
+ indicatorColor = chalk6.blue;
5045
5279
  }
5046
- const gadgetLabel = chalk5.magenta.bold(node.name);
5280
+ const gadgetLabel = chalk6.magenta.bold(node.name);
5047
5281
  let paramsStr = "";
5048
5282
  if (node.parameters && Object.keys(node.parameters).length > 0) {
5049
5283
  if (node.name === "TellUser") {
@@ -5065,15 +5299,15 @@ function formatGadgetCollapsed(node, selected) {
5065
5299
  const formatted = entries.map(([key, value]) => {
5066
5300
  const strValue = typeof value === "string" ? value : JSON.stringify(value);
5067
5301
  const truncated = strValue.length > maxParamLen ? `${strValue.slice(0, maxParamLen - 3)}...` : strValue;
5068
- return `${chalk5.dim(key)}=${chalk5.cyan(truncated)}`;
5302
+ return `${chalk6.dim(key)}=${chalk6.cyan(truncated)}`;
5069
5303
  });
5070
- paramsStr = `${chalk5.dim("(")}${formatted.join(chalk5.dim(", "))}${chalk5.dim(")")}`;
5304
+ paramsStr = `${chalk6.dim("(")}${formatted.join(chalk6.dim(", "))}${chalk6.dim(")")}`;
5071
5305
  }
5072
5306
  }
5073
5307
  let errorStr = "";
5074
5308
  if (node.error) {
5075
5309
  const truncated = node.error.length > 40 ? `${node.error.slice(0, 37)}...` : node.error;
5076
- errorStr = ` ${chalk5.red("error:")} ${truncated}`;
5310
+ errorStr = ` ${chalk6.red("error:")} ${truncated}`;
5077
5311
  }
5078
5312
  const metrics = [];
5079
5313
  if (node.executionTimeMs !== void 0) {
@@ -5081,24 +5315,23 @@ function formatGadgetCollapsed(node, selected) {
5081
5315
  }
5082
5316
  if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
5083
5317
  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(" "));
5318
+ const tokenMetrics = buildTokenMetrics({
5319
+ input: inputTokens,
5320
+ cached: cachedTokens,
5321
+ output: outputTokens
5322
+ });
5323
+ metrics.push(tokenMetrics.join(" "));
5091
5324
  } else if (node.resultTokens && node.resultTokens > 0) {
5092
- metrics.push(chalk5.dim("\u2193") + chalk5.green(` ${formatTokens(node.resultTokens)}`));
5325
+ metrics.push(chalk6.dim("\u2193") + chalk6.green(` ${formatTokens(node.resultTokens)}`));
5093
5326
  }
5094
5327
  if (node.cost && node.cost > 0) {
5095
- metrics.push(chalk5.cyan(`$${formatCost(node.cost)}`));
5328
+ metrics.push(costPart(node.cost));
5096
5329
  }
5097
- const metricsStr = metrics.length > 0 ? ` ${chalk5.dim(metrics.join(" | "))}` : "";
5330
+ const metricsStr = metrics.length > 0 ? ` ${chalk6.dim(metrics.join(" | "))}` : "";
5098
5331
  const line = `${indicatorColor(indicator)} ${gadgetLabel}${paramsStr}${errorStr}${metricsStr}`;
5099
5332
  let result;
5100
5333
  if (selected) {
5101
- result = chalk5.bgBlue.white(line);
5334
+ result = chalk6.bgBlue.white(line);
5102
5335
  } else {
5103
5336
  result = line;
5104
5337
  }
@@ -5120,7 +5353,7 @@ function formatGadgetExpanded(node) {
5120
5353
  const paramsToShow = node.parameters ? Object.entries(node.parameters).filter(([key]) => !(isTellUser && key === "message")) : [];
5121
5354
  if (paramsToShow.length > 0) {
5122
5355
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Parameters ${BOX.horizontal.repeat(width - 14)}`;
5123
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5356
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5124
5357
  for (const [key, value] of paramsToShow) {
5125
5358
  const strValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
5126
5359
  const valueLines = strValue.split("\n");
@@ -5128,30 +5361,30 @@ function formatGadgetExpanded(node) {
5128
5361
  if (valueLines.length === 1) {
5129
5362
  const truncated = strValue.length > maxValueLen ? `${strValue.slice(0, maxValueLen - 3)}...` : strValue;
5130
5363
  lines.push(
5131
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(key)}: ${chalk5.cyan(truncated)}`
5364
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}: ${chalk6.cyan(truncated)}`
5132
5365
  );
5133
5366
  } else {
5134
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(key)}:`);
5367
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}:`);
5135
5368
  for (const line of valueLines.slice(0, 5)) {
5136
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${chalk5.cyan(line)}`);
5369
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.cyan(line)}`);
5137
5370
  }
5138
5371
  if (valueLines.length > 5) {
5139
5372
  lines.push(
5140
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`... (${valueLines.length - 5} more lines)`)}`
5373
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${valueLines.length - 5} more lines)`)}`
5141
5374
  );
5142
5375
  }
5143
5376
  }
5144
5377
  }
5145
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5378
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5146
5379
  }
5147
5380
  if (node.name === "TellUser" && node.parameters?.message) {
5148
5381
  const message = String(node.parameters.message);
5149
5382
  const messageType = node.parameters.type || "info";
5150
5383
  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 }
5384
+ info: { emoji: "\u2139\uFE0F", color: chalk6.blue },
5385
+ success: { emoji: "\u2705", color: chalk6.green },
5386
+ warning: { emoji: "\u26A0\uFE0F", color: chalk6.yellow },
5387
+ error: { emoji: "\u274C", color: chalk6.red }
5155
5388
  };
5156
5389
  const typeInfo = typeIndicators[messageType] || typeIndicators.info;
5157
5390
  const headerLine = `${BOX.topLeft}${BOX.horizontal} ${typeInfo.emoji} Message ${BOX.horizontal.repeat(width - 13)}`;
@@ -5159,86 +5392,84 @@ function formatGadgetExpanded(node) {
5159
5392
  const rendered = renderMarkdown(message);
5160
5393
  const renderedLines = rendered.split("\n");
5161
5394
  for (const line of renderedLines) {
5162
- lines.push(`${indent}${chalk5.dim(BOX.vertical)} ${line}`);
5395
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${line}`);
5163
5396
  }
5164
5397
  lines.push(`${indent}${typeInfo.color(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5165
5398
  } else if (node.result || node.error) {
5166
5399
  const headerText = node.error ? " Error " : " Result ";
5167
5400
  const headerLine = `${BOX.topLeft}${BOX.horizontal}${headerText}${BOX.horizontal.repeat(width - headerText.length - 2)}`;
5168
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5401
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5169
5402
  const content = node.error || node.result || "";
5170
5403
  const contentLines = content.split("\n");
5171
5404
  const maxLines = 10;
5172
5405
  const displayLines = contentLines.slice(0, maxLines);
5173
5406
  for (const line of displayLines) {
5174
5407
  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)}`);
5408
+ const color = node.error ? chalk6.red : chalk6.white;
5409
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${color(truncated)}`);
5177
5410
  }
5178
5411
  if (contentLines.length > maxLines) {
5179
5412
  lines.push(
5180
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`... (${contentLines.length - maxLines} more lines)`)}`
5413
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${contentLines.length - maxLines} more lines)`)}`
5181
5414
  );
5182
5415
  }
5183
5416
  if (node.executionTimeMs !== void 0) {
5184
5417
  lines.push(
5185
- `${indent}${chalk5.dim(BOX.vertical)} Time: ${chalk5.dim(formatExecutionTime(node.executionTimeMs))}`
5418
+ `${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
5186
5419
  );
5187
5420
  }
5188
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5421
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5189
5422
  }
5190
5423
  if (node.mediaOutputs && node.mediaOutputs.length > 0) {
5191
5424
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Media ${BOX.horizontal.repeat(width - 9)}`;
5192
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5425
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5193
5426
  for (const media of node.mediaOutputs) {
5194
5427
  const kindEmoji = media.kind === "audio" ? "\u{1F50A}" : media.kind === "image" ? "\u{1F5BC}\uFE0F" : media.kind === "video" ? "\u{1F3AC}" : "\u{1F4C4}";
5195
5428
  const maxPathLen = width - 8;
5196
5429
  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)}`);
5430
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${kindEmoji} ${chalk6.cyan(displayPath)}`);
5198
5431
  }
5199
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5432
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5200
5433
  }
5201
5434
  if (node.children.length > 0) {
5202
5435
  const headerLine = `${BOX.topLeft}${BOX.horizontal} Subagent Activity ${BOX.horizontal.repeat(width - 21)}`;
5203
- lines.push(`${indent}${chalk5.dim(headerLine)}`);
5436
+ lines.push(`${indent}${chalk6.dim(headerLine)}`);
5204
5437
  lines.push(
5205
- `${indent}${chalk5.dim(BOX.vertical)} ${chalk5.dim(`${node.children.length} nested calls (expand children to see details)`)}`
5438
+ `${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`${node.children.length} nested calls (expand children to see details)`)}`
5206
5439
  );
5207
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5440
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5208
5441
  }
5209
5442
  if (node.executionTimeMs !== void 0 || node.cost || node.resultTokens || node.subagentStats) {
5210
5443
  const metricsHeaderLine = `${BOX.topLeft}${BOX.horizontal} Metrics ${BOX.horizontal.repeat(width - 11)}`;
5211
- lines.push(`${indent}${chalk5.dim(metricsHeaderLine)}`);
5444
+ lines.push(`${indent}${chalk6.dim(metricsHeaderLine)}`);
5212
5445
  if (node.executionTimeMs !== void 0) {
5213
5446
  lines.push(
5214
- `${indent}${chalk5.dim(BOX.vertical)} Duration: ${chalk5.dim(formatExecutionTime(node.executionTimeMs))}`
5447
+ `${indent}${chalk6.dim(BOX.vertical)} Duration: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
5215
5448
  );
5216
5449
  }
5217
5450
  if (node.resultTokens && node.resultTokens > 0) {
5218
5451
  lines.push(
5219
- `${indent}${chalk5.dim(BOX.vertical)} Output: ${chalk5.green(`~${formatTokens(node.resultTokens)}`)} tokens`
5452
+ `${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(`~${formatTokens(node.resultTokens)}`)} tokens`
5220
5453
  );
5221
5454
  }
5222
5455
  if (node.cost && node.cost > 0) {
5223
5456
  lines.push(
5224
- `${indent}${chalk5.dim(BOX.vertical)} Cost: ${chalk5.cyan(`$${formatCost(node.cost)}`)}`
5457
+ `${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(node.cost)}`)}`
5225
5458
  );
5226
5459
  }
5227
5460
  if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
5228
5461
  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})`);
5462
+ const tokenStr = buildTokenMetrics({
5463
+ input: s.inputTokens,
5464
+ cached: s.cachedTokens,
5465
+ output: s.outputTokens
5466
+ }).join(" ");
5467
+ lines.push(`${indent}${chalk6.dim(BOX.vertical)} LLM calls: ${s.llmCallCount} (${tokenStr})`);
5237
5468
  }
5238
- lines.push(`${indent}${chalk5.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5469
+ lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
5239
5470
  }
5240
5471
  if (lines.length === 0) {
5241
- lines.push(`${indent}${chalk5.dim("No details available")}`);
5472
+ lines.push(`${indent}${chalk6.dim("No details available")}`);
5242
5473
  }
5243
5474
  return lines;
5244
5475
  }
@@ -5253,26 +5484,183 @@ function getContinuationIndent(depth) {
5253
5484
  return `${" ".repeat(depth)} `;
5254
5485
  }
5255
5486
 
5256
- // src/tui/node-store.ts
5257
- var NodeStore = class {
5258
- /** All nodes in the tree (flat for easy lookup) */
5259
- nodes = /* @__PURE__ */ new Map();
5260
- /** Root node IDs (top-level LLM calls and text) */
5261
- rootIds = [];
5262
- /** Counter for generating unique node IDs */
5263
- nodeIdCounter = 0;
5264
- /** Current LLM call node (for adding gadget children) */
5265
- currentLLMCallId = null;
5266
- /** Current thinking block (accumulates chunks during streaming) */
5267
- currentThinkingId = null;
5268
- /** Current session ID (increments each new REPL turn) */
5269
- currentSessionId = 0;
5270
- /** Previous session ID (for deferred cleanup) */
5271
- previousSessionId = null;
5272
- /** Track main agent LLM calls by iteration for idempotency */
5273
- llmCallByIteration = /* @__PURE__ */ new Map();
5274
- /** Track gadgets by invocationId for idempotency */
5275
- gadgetByInvocationId = /* @__PURE__ */ new Map();
5487
+ // src/tui/block-content-formatter.ts
5488
+ function formatBlockContent(node, selected, expanded) {
5489
+ const indent = getIndent(node.depth);
5490
+ switch (node.type) {
5491
+ case "llm_call": {
5492
+ const collapsed = formatLLMCallCollapsed(node, selected);
5493
+ if (!expanded) {
5494
+ return indent + collapsed;
5495
+ }
5496
+ const expandedLines = formatLLMCallExpanded(node);
5497
+ const contIndent = getContinuationIndent(node.depth);
5498
+ return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
5499
+ }
5500
+ case "gadget": {
5501
+ const collapsed = formatGadgetCollapsed(node, selected);
5502
+ if (!expanded) {
5503
+ return indent + collapsed;
5504
+ }
5505
+ const expandedLines = formatGadgetExpanded(node);
5506
+ const contIndent = getContinuationIndent(node.depth);
5507
+ return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
5508
+ }
5509
+ case "text": {
5510
+ if (node.id.startsWith("user_")) {
5511
+ return formatUserMessage(node.content);
5512
+ }
5513
+ const fullContent = renderMarkdown(node.content);
5514
+ if (expanded) {
5515
+ return `
5516
+ ${fullContent}
5517
+ `;
5518
+ }
5519
+ return abbreviateToLines(fullContent, 2, selected);
5520
+ }
5521
+ case "thinking": {
5522
+ return formatThinkingContent(node, indent, expanded);
5523
+ }
5524
+ case "system_message": {
5525
+ const icon = getSystemMessageIcon(node.category);
5526
+ const color = getSystemMessageColor(node.category);
5527
+ const RESET3 = "\x1B[0m";
5528
+ return `${indent}${color}${icon} ${node.message}${RESET3}`;
5529
+ }
5530
+ }
5531
+ }
5532
+ function formatThinkingContent(node, indent, expanded) {
5533
+ const DIM2 = "\x1B[2m";
5534
+ const RED_DIM = "\x1B[2;31m";
5535
+ const RESET3 = "\x1B[0m";
5536
+ const contIndent = getContinuationIndent(node.depth);
5537
+ if (node.thinkingType === "redacted") {
5538
+ return `${indent}${RED_DIM}\u{1F512} [Redacted thinking block]${RESET3}`;
5539
+ }
5540
+ if (!expanded) {
5541
+ const firstLine = node.content.split("\n")[0]?.slice(0, 60) ?? "";
5542
+ const suffix = node.isComplete ? "" : "...";
5543
+ return `${indent}${DIM2}\u{1F4AD} Thinking${suffix} ${firstLine}${RESET3}`;
5544
+ }
5545
+ const tokenInfo = node.isComplete ? ` (${Math.ceil(node.content.length / 4)} tokens est.)` : "";
5546
+ const header = `${indent}${DIM2}\u25BC \u{1F4AD} Thinking${tokenInfo}${RESET3}`;
5547
+ const contentLines = node.content.split("\n").map((line) => `${contIndent}${DIM2}${line}${RESET3}`);
5548
+ return [header, ...contentLines].join("\n");
5549
+ }
5550
+ function getSystemMessageIcon(category) {
5551
+ switch (category) {
5552
+ case "throttle":
5553
+ return "\u23F8";
5554
+ case "retry":
5555
+ return "\u{1F504}";
5556
+ case "info":
5557
+ return "\u2139\uFE0F";
5558
+ case "warning":
5559
+ return "\u26A0\uFE0F";
5560
+ case "error":
5561
+ return "\u274C";
5562
+ }
5563
+ }
5564
+ function getSystemMessageColor(category) {
5565
+ const YELLOW2 = "\x1B[33m";
5566
+ const BLUE = "\x1B[34m";
5567
+ const GRAY2 = "\x1B[90m";
5568
+ const RED2 = "\x1B[31m";
5569
+ switch (category) {
5570
+ case "throttle":
5571
+ return YELLOW2;
5572
+ case "retry":
5573
+ return BLUE;
5574
+ case "info":
5575
+ return GRAY2;
5576
+ case "warning":
5577
+ return YELLOW2;
5578
+ case "error":
5579
+ return RED2;
5580
+ }
5581
+ }
5582
+ function abbreviateToLines(text3, maxLines, selected) {
5583
+ const lines = text3.split("\n");
5584
+ let startIndex = 0;
5585
+ while (startIndex < lines.length && lines[startIndex].trim() === "") {
5586
+ startIndex++;
5587
+ }
5588
+ const contentLines = lines.slice(startIndex);
5589
+ if (contentLines.length <= maxLines) {
5590
+ return `
5591
+ ${contentLines.join("\n")}`;
5592
+ }
5593
+ const truncatedLines = contentLines.slice(0, maxLines);
5594
+ const indicator = selected ? "\u25B6 ..." : " ...";
5595
+ return `
5596
+ ${truncatedLines.join("\n")}
5597
+ ${indicator}`;
5598
+ }
5599
+ function isNodeVisibleInFilterMode(node, contentFilterMode) {
5600
+ if (contentFilterMode === "full") {
5601
+ return true;
5602
+ }
5603
+ switch (node.type) {
5604
+ case "text":
5605
+ return true;
5606
+ case "gadget":
5607
+ return shouldRenderAsText(node, contentFilterMode);
5608
+ default:
5609
+ return false;
5610
+ }
5611
+ }
5612
+ function shouldRenderAsText(node, contentFilterMode) {
5613
+ if (contentFilterMode !== "focused") return false;
5614
+ if (node.type !== "gadget") return false;
5615
+ const name = node.name;
5616
+ return name === "TellUser" || name === "AskUser" || name === "Finish";
5617
+ }
5618
+ function formatGadgetAsText(node) {
5619
+ if (node.name === "TellUser") {
5620
+ const message = node.parameters?.message;
5621
+ if (typeof message === "string") {
5622
+ return `
5623
+ ${renderMarkdown(message)}
5624
+ `;
5625
+ }
5626
+ } else if (node.name === "AskUser") {
5627
+ const question = node.parameters?.question;
5628
+ if (typeof question === "string") {
5629
+ return `
5630
+ ? ${question}
5631
+ `;
5632
+ }
5633
+ } else if (node.name === "Finish") {
5634
+ const message = node.parameters?.message;
5635
+ if (typeof message === "string" && message.trim()) {
5636
+ return `
5637
+ \x1B[32m\u2713\x1B[0m ${renderMarkdown(message)}
5638
+ `;
5639
+ }
5640
+ }
5641
+ return "";
5642
+ }
5643
+
5644
+ // src/tui/node-store.ts
5645
+ var NodeStore = class {
5646
+ /** All nodes in the tree (flat for easy lookup) */
5647
+ nodes = /* @__PURE__ */ new Map();
5648
+ /** Root node IDs (top-level LLM calls and text) */
5649
+ rootIds = [];
5650
+ /** Counter for generating unique node IDs */
5651
+ nodeIdCounter = 0;
5652
+ /** Current LLM call node (for adding gadget children) */
5653
+ currentLLMCallId = null;
5654
+ /** Current thinking block (accumulates chunks during streaming) */
5655
+ currentThinkingId = null;
5656
+ /** Current session ID (increments each new REPL turn) */
5657
+ currentSessionId = 0;
5658
+ /** Previous session ID (for deferred cleanup) */
5659
+ previousSessionId = null;
5660
+ /** Track main agent LLM calls by iteration for idempotency */
5661
+ llmCallByIteration = /* @__PURE__ */ new Map();
5662
+ /** Track gadgets by invocationId for idempotency */
5663
+ gadgetByInvocationId = /* @__PURE__ */ new Map();
5276
5664
  /** Track nested LLM calls by parentId_iteration for idempotency */
5277
5665
  nestedLLMCallByKey = /* @__PURE__ */ new Map();
5278
5666
  /** Callbacks for change notifications */
@@ -5675,6 +6063,28 @@ var NodeStore = class {
5675
6063
  }
5676
6064
  };
5677
6065
 
6066
+ // src/tui/tree-layout.ts
6067
+ function traverseNodeTree(nodeId, getNode, visit, top) {
6068
+ const node = getNode(nodeId);
6069
+ if (!node) {
6070
+ return top;
6071
+ }
6072
+ let nextTop = visit(nodeId, node, top);
6073
+ if ("children" in node) {
6074
+ for (const childId of node.children) {
6075
+ nextTop = traverseNodeTree(childId, getNode, visit, nextTop);
6076
+ }
6077
+ }
6078
+ return nextTop;
6079
+ }
6080
+ function traverseRootTrees(rootIds, getNode, visit) {
6081
+ let top = 0;
6082
+ for (const rootId of rootIds) {
6083
+ top = traverseNodeTree(rootId, getNode, visit, top);
6084
+ }
6085
+ return top;
6086
+ }
6087
+
5678
6088
  // src/tui/scroll-manager.ts
5679
6089
  var ScrollManager = class _ScrollManager {
5680
6090
  container;
@@ -5803,25 +6213,14 @@ var ScrollManager = class _ScrollManager {
5803
6213
  * Calculate total height of all rendered blocks.
5804
6214
  */
5805
6215
  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);
6216
+ return traverseRootTrees(
6217
+ this.accessors.getRootIds(),
6218
+ (id) => this.accessors.getNode(id),
6219
+ (nodeId, _node, currentHeight) => {
6220
+ const block = this.accessors.getBlock(nodeId);
6221
+ return currentHeight + (block ? getBlockHeight(block) : 0);
5822
6222
  }
5823
- }
5824
- return currentHeight;
6223
+ );
5825
6224
  }
5826
6225
  /**
5827
6226
  * Calculate vertical offset to push content to bottom when content < viewport.
@@ -5838,17 +6237,18 @@ var ScrollManager = class _ScrollManager {
5838
6237
  * Apply vertical offset to a node tree (for bottom alignment).
5839
6238
  */
5840
6239
  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
- }
6240
+ traverseNodeTree(
6241
+ nodeId,
6242
+ (id) => this.accessors.getNode(id),
6243
+ (currentNodeId, _node, top) => {
6244
+ const block = this.accessors.getBlock(currentNodeId);
6245
+ if (block) {
6246
+ block.box.top = block.box.top + offset;
6247
+ }
6248
+ return top;
6249
+ },
6250
+ 0
6251
+ );
5852
6252
  }
5853
6253
  };
5854
6254
  function getBlockHeight(block) {
@@ -5856,6 +6256,167 @@ function getBlockHeight(block) {
5856
6256
  return content.split("\n").length;
5857
6257
  }
5858
6258
 
6259
+ // src/tui/tree-bridge.ts
6260
+ var TreeBridge = class {
6261
+ /** Unsubscribe function for tree events */
6262
+ treeUnsubscribe = null;
6263
+ /** Map tree node IDs to block node IDs */
6264
+ treeNodeToBlockId = /* @__PURE__ */ new Map();
6265
+ callbacks;
6266
+ constructor(callbacks) {
6267
+ this.callbacks = callbacks;
6268
+ }
6269
+ // ───────────────────────────────────────────────────────────────────────────
6270
+ // Public API
6271
+ // ───────────────────────────────────────────────────────────────────────────
6272
+ /**
6273
+ * Check if tree subscription is active.
6274
+ */
6275
+ isSubscribed() {
6276
+ return this.treeUnsubscribe !== null;
6277
+ }
6278
+ /**
6279
+ * Subscribe to an ExecutionTree for automatic block updates.
6280
+ *
6281
+ * When subscribed, the TreeBridge will automatically create and update
6282
+ * blocks based on tree events, calling the provided callbacks.
6283
+ *
6284
+ * @param tree - The ExecutionTree to subscribe to
6285
+ * @returns Unsubscribe function to stop listening
6286
+ *
6287
+ * @example
6288
+ * ```typescript
6289
+ * const agent = builder.ask("Hello");
6290
+ * const unsubscribe = treeBridge.subscribeToTree(agent.getTree());
6291
+ *
6292
+ * for await (const event of agent.run()) {
6293
+ * // Blocks are automatically updated via tree subscription
6294
+ * }
6295
+ *
6296
+ * unsubscribe();
6297
+ * ```
6298
+ */
6299
+ subscribeToTree(tree) {
6300
+ if (this.treeUnsubscribe) {
6301
+ this.treeUnsubscribe();
6302
+ }
6303
+ this.treeNodeToBlockId.clear();
6304
+ this.callbacks.onClearIdempotencyMaps();
6305
+ this.treeUnsubscribe = tree.onAll((event) => {
6306
+ this.handleTreeEvent(event, tree);
6307
+ });
6308
+ return () => {
6309
+ if (this.treeUnsubscribe) {
6310
+ this.treeUnsubscribe();
6311
+ this.treeUnsubscribe = null;
6312
+ }
6313
+ };
6314
+ }
6315
+ /**
6316
+ * Get block ID for a tree node ID.
6317
+ * Useful for external code that needs to correlate tree nodes with blocks.
6318
+ */
6319
+ getBlockIdForTreeNode(treeNodeId) {
6320
+ return this.treeNodeToBlockId.get(treeNodeId);
6321
+ }
6322
+ // ───────────────────────────────────────────────────────────────────────────
6323
+ // Event Handling
6324
+ // ───────────────────────────────────────────────────────────────────────────
6325
+ /**
6326
+ * Handle an ExecutionTree event.
6327
+ */
6328
+ handleTreeEvent(event, tree) {
6329
+ switch (event.type) {
6330
+ case "llm_call_start": {
6331
+ this.callbacks.onResetThinking();
6332
+ let parentBlockId;
6333
+ if (event.parentId) {
6334
+ parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6335
+ }
6336
+ const blockId = this.callbacks.onAddLLMCall(
6337
+ event.iteration + 1,
6338
+ event.model,
6339
+ parentBlockId,
6340
+ event.depth > 0
6341
+ );
6342
+ this.treeNodeToBlockId.set(event.nodeId, blockId);
6343
+ const startNode = tree.getNode(event.nodeId);
6344
+ if (startNode?.type === "llm_call" && startNode.request) {
6345
+ this.callbacks.onSetLLMCallRequest(blockId, startNode.request);
6346
+ }
6347
+ break;
6348
+ }
6349
+ case "llm_call_complete": {
6350
+ this.callbacks.onCompleteThinking();
6351
+ const blockId = this.treeNodeToBlockId.get(event.nodeId);
6352
+ if (blockId) {
6353
+ this.callbacks.onCompleteLLMCall(blockId, {
6354
+ inputTokens: event.usage?.inputTokens,
6355
+ cachedInputTokens: event.usage?.cachedInputTokens,
6356
+ outputTokens: event.usage?.outputTokens,
6357
+ reasoningTokens: event.usage?.reasoningTokens,
6358
+ cost: event.cost,
6359
+ finishReason: event.finishReason ?? void 0
6360
+ });
6361
+ const completeNode = tree.getNode(event.nodeId);
6362
+ if (completeNode?.type === "llm_call" && completeNode.response) {
6363
+ this.callbacks.onSetLLMCallResponse(blockId, completeNode.response);
6364
+ }
6365
+ }
6366
+ break;
6367
+ }
6368
+ case "thinking": {
6369
+ this.callbacks.onAddThinking(event.content, event.thinkingType);
6370
+ break;
6371
+ }
6372
+ case "gadget_call": {
6373
+ let parentBlockId;
6374
+ if (event.parentId) {
6375
+ parentBlockId = this.treeNodeToBlockId.get(event.parentId);
6376
+ }
6377
+ const previousLLMCallId = this.callbacks.onGetCurrentLLMCallId();
6378
+ if (parentBlockId) {
6379
+ this.callbacks.onSetCurrentLLMCall(parentBlockId);
6380
+ }
6381
+ const blockId = this.callbacks.onAddGadget(
6382
+ event.invocationId,
6383
+ event.name,
6384
+ event.parameters
6385
+ );
6386
+ this.treeNodeToBlockId.set(event.nodeId, blockId);
6387
+ this.callbacks.onSetCurrentLLMCall(previousLLMCallId);
6388
+ break;
6389
+ }
6390
+ case "gadget_complete": {
6391
+ const mediaOutputs = event.storedMedia?.map((m) => ({
6392
+ kind: m.kind,
6393
+ path: m.path,
6394
+ mimeType: m.mimeType,
6395
+ description: m.description
6396
+ }));
6397
+ this.callbacks.onCompleteGadget(event.invocationId, {
6398
+ result: event.result,
6399
+ executionTimeMs: event.executionTimeMs,
6400
+ cost: event.cost,
6401
+ mediaOutputs
6402
+ });
6403
+ break;
6404
+ }
6405
+ case "gadget_error": {
6406
+ this.callbacks.onCompleteGadget(event.invocationId, {
6407
+ error: event.error,
6408
+ executionTimeMs: event.executionTimeMs
6409
+ });
6410
+ break;
6411
+ }
6412
+ case "gadget_skipped": {
6413
+ this.callbacks.onSkipGadget(event.invocationId, `Skipped: ${event.failedDependencyError}`);
6414
+ break;
6415
+ }
6416
+ }
6417
+ }
6418
+ };
6419
+
5859
6420
  // src/tui/block-renderer.ts
5860
6421
  var BlockRenderer = class {
5861
6422
  container;
@@ -5865,6 +6426,8 @@ var BlockRenderer = class {
5865
6426
  nodeStore;
5866
6427
  /** Scroll manager — manages scroll position, follow mode, and bottom alignment */
5867
6428
  scrollManager;
6429
+ /** Tree bridge — manages ExecutionTree subscriptions and tree→block mapping */
6430
+ treeBridge;
5868
6431
  /** Rendered blocks with UI state */
5869
6432
  blocks = /* @__PURE__ */ new Map();
5870
6433
  /** IDs of selectable blocks in display order */
@@ -5894,6 +6457,21 @@ var BlockRenderer = class {
5894
6457
  getBlock: (id) => this.blocks.get(id),
5895
6458
  getSelectedBlock: () => this.getSelectedBlock()
5896
6459
  });
6460
+ this.treeBridge = new TreeBridge({
6461
+ onResetThinking: () => this.nodeStore.resetCurrentThinking(),
6462
+ onSetCurrentLLMCall: (llmCallId) => this.setCurrentLLMCall(llmCallId),
6463
+ onClearIdempotencyMaps: () => this.nodeStore.clearIdempotencyMaps(),
6464
+ onAddLLMCall: (iteration, model, parentGadgetId, isNested) => this.addLLMCall(iteration, model, parentGadgetId, isNested),
6465
+ onCompleteLLMCall: (id, details, rawResponse) => this.completeLLMCall(id, details, rawResponse),
6466
+ onSetLLMCallRequest: (id, messages) => this.setLLMCallRequest(id, messages),
6467
+ onSetLLMCallResponse: (id, rawResponse) => this.setLLMCallResponse(id, rawResponse),
6468
+ onCompleteThinking: () => this.completeThinking(),
6469
+ onAddThinking: (content, thinkingType) => this.addThinking(content, thinkingType),
6470
+ onAddGadget: (invocationId, name, parameters) => this.addGadget(invocationId, name, parameters),
6471
+ onCompleteGadget: (invocationId, options) => this.completeGadget(invocationId, options),
6472
+ onSkipGadget: (invocationId, reason) => this.skipGadget(invocationId, reason),
6473
+ onGetCurrentLLMCallId: () => this.getCurrentLLMCallId()
6474
+ });
5897
6475
  }
5898
6476
  // ───────────────────────────────────────────────────────────────────────────
5899
6477
  // Public API - Node Management
@@ -5939,6 +6517,12 @@ var BlockRenderer = class {
5939
6517
  completeGadget(invocationId, options = {}) {
5940
6518
  this.nodeStore.completeGadget(invocationId, options);
5941
6519
  }
6520
+ /**
6521
+ * Mark a gadget as skipped with a rendered reason.
6522
+ */
6523
+ skipGadget(invocationId, reason) {
6524
+ this.nodeStore.completeGadget(invocationId, { error: reason });
6525
+ }
5942
6526
  /**
5943
6527
  * Add a text node (flows between LLM calls).
5944
6528
  */
@@ -6006,9 +6590,8 @@ var BlockRenderer = class {
6006
6590
  /**
6007
6591
  * Get the current LLM call ID.
6008
6592
  *
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.
6593
+ * In tree mode, TreeBridge uses this to restore the current parent context
6594
+ * while translating gadget events into renderer updates.
6012
6595
  */
6013
6596
  getCurrentLLMCallId() {
6014
6597
  return this.nodeStore.currentLLMCallId;
@@ -6018,7 +6601,7 @@ var BlockRenderer = class {
6018
6601
  * When active, external code should skip block creation (tree handles it).
6019
6602
  */
6020
6603
  isTreeSubscribed() {
6021
- return this.treeUnsubscribe !== null;
6604
+ return this.treeBridge.isSubscribed();
6022
6605
  }
6023
6606
  /**
6024
6607
  * Store raw response for an LLM call (enrichment only).
@@ -6189,22 +6772,7 @@ var BlockRenderer = class {
6189
6772
  * Called when nodes are added/removed.
6190
6773
  */
6191
6774
  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();
6775
+ this.rebuildBlocksCore(false);
6208
6776
  }
6209
6777
  /**
6210
6778
  * Render a node and its children recursively.
@@ -6218,64 +6786,29 @@ var BlockRenderer = class {
6218
6786
  * (no headers, just content) for a clean chat-like experience.
6219
6787
  */
6220
6788
  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";
6789
+ return traverseNodeTree(
6790
+ nodeId,
6791
+ (id) => this.getNode(id),
6792
+ (currentNodeId, node, currentTop) => {
6793
+ if (!this.isNodeVisible(node)) {
6794
+ return currentTop;
6795
+ }
6796
+ const block = shouldRenderAsText(node, this.contentFilterMode) ? this.createTextLikeBlock(node, currentTop) : this.createBlock(node, currentTop);
6797
+ this.blocks.set(currentNodeId, block);
6798
+ if (block.selectable) {
6799
+ this.selectableIds.push(currentNodeId);
6800
+ }
6801
+ return currentTop + getBlockHeight(block);
6802
+ },
6803
+ top
6804
+ );
6250
6805
  }
6251
6806
  /**
6252
6807
  * Create a text-like block for TellUser/AskUser/Finish gadgets in focused mode.
6253
6808
  * Renders just the content without the gadget header.
6254
6809
  */
6255
6810
  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
- }
6811
+ const content = formatGadgetAsText(node);
6279
6812
  const box = new Box({
6280
6813
  parent: this.container,
6281
6814
  top,
@@ -6300,7 +6833,7 @@ ${renderMarkdown(message)}
6300
6833
  const isSelected = this.selectableIds.length === this.selectedIndex;
6301
6834
  const selectable = node.type !== "text" || !node.id.startsWith("user_");
6302
6835
  const expanded = this.expandedStates.get(node.id) ?? false;
6303
- const content = this.formatBlockContent(node, isSelected, expanded);
6836
+ const content = formatBlockContent(node, isSelected, expanded);
6304
6837
  const box = new Box({
6305
6838
  parent: this.container,
6306
6839
  top,
@@ -6318,133 +6851,6 @@ ${renderMarkdown(message)}
6318
6851
  selectable
6319
6852
  };
6320
6853
  }
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
6854
  /**
6449
6855
  * Update a single block (after state change).
6450
6856
  */
@@ -6453,7 +6859,7 @@ ${indicator}`;
6453
6859
  const node = this.getNode(nodeId);
6454
6860
  if (!block || !node) return;
6455
6861
  const isSelected = this.selectableIds[this.selectedIndex] === nodeId;
6456
- const content = this.formatBlockContent(node, isSelected, block.expanded);
6862
+ const content = formatBlockContent(node, isSelected, block.expanded);
6457
6863
  const oldHeight = getBlockHeight(block);
6458
6864
  block.box.setContent(content);
6459
6865
  const newHeight = content.split("\n").length;
@@ -6471,7 +6877,7 @@ ${indicator}`;
6471
6877
  const block = this.blocks.get(id);
6472
6878
  if (block) {
6473
6879
  const isSelected = this.selectableIds[this.selectedIndex] === id;
6474
- const content = this.formatBlockContent(block.node, isSelected, block.expanded);
6880
+ const content = formatBlockContent(block.node, isSelected, block.expanded);
6475
6881
  block.box.setContent(content);
6476
6882
  }
6477
6883
  }
@@ -6486,20 +6892,19 @@ ${indicator}`;
6486
6892
  this.scrollManager.repositionBlocks((rootId, top) => this.repositionNodeTree(rootId, top));
6487
6893
  }
6488
6894
  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;
6895
+ return traverseNodeTree(
6896
+ nodeId,
6897
+ (id) => this.getNode(id),
6898
+ (currentNodeId, _node, currentTop) => {
6899
+ const block = this.blocks.get(currentNodeId);
6900
+ if (!block) {
6901
+ return currentTop;
6902
+ }
6903
+ block.box.top = currentTop;
6904
+ return currentTop + getBlockHeight(block);
6905
+ },
6906
+ top
6907
+ );
6503
6908
  }
6504
6909
  /**
6505
6910
  * Scroll container to keep selected block visible.
@@ -6566,13 +6971,25 @@ ${indicator}`;
6566
6971
  * Used for mode switches where we need to ensure the screen is fully cleared.
6567
6972
  */
6568
6973
  rebuildBlocksImmediate() {
6974
+ this.rebuildBlocksCore(true);
6975
+ }
6976
+ /**
6977
+ * Shared implementation for rebuildBlocks and rebuildBlocksImmediate.
6978
+ *
6979
+ * @param immediate - When true, forces a synchronous render pass before
6980
+ * and after building blocks to clear visual artifacts (used on mode switch).
6981
+ * When false, defers a single render to the end via renderCallback.
6982
+ */
6983
+ rebuildBlocksCore(immediate) {
6569
6984
  for (const child of [...this.container.children]) {
6570
6985
  child.detach();
6571
6986
  }
6572
6987
  this.container.setContent("");
6573
6988
  this.blocks.clear();
6574
6989
  this.selectableIds = [];
6575
- this.renderNowCallback();
6990
+ if (immediate) {
6991
+ this.renderNowCallback();
6992
+ }
6576
6993
  let top = 0;
6577
6994
  for (const rootId of this.nodeStore.rootIds) {
6578
6995
  top = this.renderNodeTree(rootId, top);
@@ -6581,7 +6998,11 @@ ${indicator}`;
6581
6998
  this.selectedIndex = this.selectableIds.length - 1;
6582
6999
  }
6583
7000
  this.applyBottomAlignmentAndScroll();
6584
- this.renderNowCallback();
7001
+ if (immediate) {
7002
+ this.renderNowCallback();
7003
+ } else {
7004
+ this.renderCallback();
7005
+ }
6585
7006
  this.notifyHasContentChange();
6586
7007
  }
6587
7008
  /**
@@ -6593,38 +7014,15 @@ ${indicator}`;
6593
7014
  /**
6594
7015
  * Check if a node should be visible in the current content filter mode.
6595
7016
  *
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)
7017
+ * In focused mode, only text and user-facing gadgets remain visible.
7018
+ * TellUser, AskUser, and Finish render as plain text for a chat-like view.
6601
7019
  */
6602
7020
  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
- }
7021
+ return isNodeVisibleInFilterMode(node, this.contentFilterMode);
6620
7022
  }
6621
7023
  // ───────────────────────────────────────────────────────────────────────────
6622
7024
  // ExecutionTree Integration
6623
7025
  // ───────────────────────────────────────────────────────────────────────────
6624
- /** Unsubscribe function for tree events */
6625
- treeUnsubscribe = null;
6626
- /** Map tree node IDs to block node IDs */
6627
- treeNodeToBlockId = /* @__PURE__ */ new Map();
6628
7026
  /**
6629
7027
  * Subscribe to an ExecutionTree for automatic block updates.
6630
7028
  *
@@ -6648,121 +7046,14 @@ ${indicator}`;
6648
7046
  * ```
6649
7047
  */
6650
7048
  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
- }
7049
+ return this.treeBridge.subscribeToTree(tree);
6759
7050
  }
6760
7051
  /**
6761
7052
  * Get block ID for a tree node ID.
6762
7053
  * Useful for external code that needs to correlate tree nodes with blocks.
6763
7054
  */
6764
7055
  getBlockIdForTreeNode(treeNodeId) {
6765
- return this.treeNodeToBlockId.get(treeNodeId);
7056
+ return this.treeBridge.getBlockIdForTreeNode(treeNodeId);
6766
7057
  }
6767
7058
  };
6768
7059
 
@@ -7047,11 +7338,31 @@ var TUIController = class {
7047
7338
  this.callbacks.onCancel?.();
7048
7339
  }
7049
7340
  /**
7050
- * Trigger the mid-session input callback.
7051
- * Called when user submits input during a running session.
7341
+ * Trigger the mid-session input callback.
7342
+ * Called when user submits input during a running session.
7343
+ */
7344
+ triggerMidSessionInput(message) {
7345
+ this.callbacks.onMidSessionInput?.(message);
7346
+ }
7347
+ };
7348
+
7349
+ // src/tui/event-router.ts
7350
+ var EventRouter = class {
7351
+ constructor(blockRenderer) {
7352
+ this.blockRenderer = blockRenderer;
7353
+ }
7354
+ /**
7355
+ * Route an agent stream event to the appropriate renderer method.
7356
+ *
7357
+ * Only handles text/thinking events - gadgets and LLM calls are managed
7358
+ * via ExecutionTree subscription in TreeSubscriptionManager.
7052
7359
  */
7053
- triggerMidSessionInput(message) {
7054
- this.callbacks.onMidSessionInput?.(message);
7360
+ handleEvent(event) {
7361
+ if (event.type === "text") {
7362
+ this.blockRenderer.addText(event.content);
7363
+ } else if (event.type === "thinking") {
7364
+ this.blockRenderer.addThinking(event.content, event.thinkingType);
7365
+ }
7055
7366
  }
7056
7367
  };
7057
7368
 
@@ -7657,6 +7968,104 @@ var InputHandler = class {
7657
7968
  }
7658
7969
  };
7659
7970
 
7971
+ // src/tui/key-action-handler.ts
7972
+ var KeyActionHandler = class {
7973
+ constructor(controller, blockRenderer, statusBar, screenCtx, modalManager, layout) {
7974
+ this.controller = controller;
7975
+ this.blockRenderer = blockRenderer;
7976
+ this.statusBar = statusBar;
7977
+ this.screenCtx = screenCtx;
7978
+ this.modalManager = modalManager;
7979
+ this.layout = layout;
7980
+ }
7981
+ /**
7982
+ * Handle high-level keyboard actions from KeyboardManager or InputHandler.
7983
+ */
7984
+ handleKeyAction(action) {
7985
+ switch (action.type) {
7986
+ case "ctrl_c": {
7987
+ const result = this.controller.handleCtrlC();
7988
+ if (result === "show_hint") {
7989
+ this.blockRenderer.addText("\n[Press Ctrl+C again to quit]\n");
7990
+ } else if (result === "quit") {
7991
+ process.exit(130);
7992
+ }
7993
+ break;
7994
+ }
7995
+ case "cancel":
7996
+ this.controller.triggerCancel();
7997
+ this.controller.abort();
7998
+ break;
7999
+ case "toggle_focus_mode":
8000
+ this.controller.toggleFocusMode();
8001
+ break;
8002
+ case "toggle_content_filter":
8003
+ this.controller.toggleContentFilterMode();
8004
+ break;
8005
+ case "cycle_profile":
8006
+ this.statusBar.cycleProfile();
8007
+ break;
8008
+ case "scroll_page": {
8009
+ const body = this.layout.body;
8010
+ if (!body.scroll) return;
8011
+ const containerHeight = body.height;
8012
+ const scrollAmount = Math.max(1, containerHeight - 2);
8013
+ if (action.direction < 0) {
8014
+ body.scroll(-scrollAmount);
8015
+ } else {
8016
+ body.scroll(scrollAmount);
8017
+ }
8018
+ this.blockRenderer.handleUserScroll();
8019
+ this.screenCtx.renderNow();
8020
+ break;
8021
+ }
8022
+ case "scroll_line": {
8023
+ const body = this.layout.body;
8024
+ if (!body.scroll) return;
8025
+ body.scroll(action.direction);
8026
+ this.blockRenderer.handleUserScroll();
8027
+ this.screenCtx.renderNow();
8028
+ break;
8029
+ }
8030
+ case "navigation":
8031
+ switch (action.action) {
8032
+ case "select_next":
8033
+ this.blockRenderer.selectNext();
8034
+ break;
8035
+ case "select_previous":
8036
+ this.blockRenderer.selectPrevious();
8037
+ break;
8038
+ case "select_first":
8039
+ this.blockRenderer.selectFirst();
8040
+ break;
8041
+ case "select_last":
8042
+ this.blockRenderer.selectLast();
8043
+ this.blockRenderer.enableFollowMode();
8044
+ break;
8045
+ case "toggle_expand":
8046
+ this.blockRenderer.toggleExpand();
8047
+ break;
8048
+ case "collapse":
8049
+ this.blockRenderer.collapseOrDeselect();
8050
+ break;
8051
+ }
8052
+ this.screenCtx.renderNow();
8053
+ break;
8054
+ case "raw_viewer":
8055
+ void (async () => {
8056
+ const selected = this.blockRenderer.getSelectedBlock();
8057
+ if (!selected) return;
8058
+ if (!isRawViewerNode(selected.node)) return;
8059
+ await this.modalManager.showRawViewer(
8060
+ this.screenCtx.screen,
8061
+ createRawViewerData(selected.node, action.mode)
8062
+ );
8063
+ })();
8064
+ break;
8065
+ }
8066
+ }
8067
+ };
8068
+
7660
8069
  // src/tui/keymap.ts
7661
8070
  var KeyboardManager = class {
7662
8071
  config;
@@ -8354,6 +8763,47 @@ function createScreen(options) {
8354
8763
  };
8355
8764
  }
8356
8765
 
8766
+ // src/tui/session-manager.ts
8767
+ var SessionManager = class {
8768
+ constructor(blockRenderer, statusBar) {
8769
+ this.blockRenderer = blockRenderer;
8770
+ this.statusBar = statusBar;
8771
+ }
8772
+ /**
8773
+ * Start a new session. Called at the start of each REPL turn.
8774
+ */
8775
+ startNewSession() {
8776
+ this.blockRenderer.startNewSession();
8777
+ }
8778
+ /**
8779
+ * Clear blocks from the previous session only.
8780
+ * Keeps current content visible during the session.
8781
+ */
8782
+ clearPreviousSession() {
8783
+ this.blockRenderer.clearPreviousSession();
8784
+ }
8785
+ /**
8786
+ * Clear all blocks and reset BlockRenderer state.
8787
+ * Prevents memory leaks between iterations.
8788
+ */
8789
+ clearAllBlocks() {
8790
+ this.blockRenderer.clear();
8791
+ }
8792
+ /**
8793
+ * Clear status bar activity state.
8794
+ */
8795
+ clearStatusBar() {
8796
+ this.statusBar.clearActivity();
8797
+ }
8798
+ /**
8799
+ * Full cleanup between REPL iterations.
8800
+ */
8801
+ resetAll() {
8802
+ this.clearAllBlocks();
8803
+ this.clearStatusBar();
8804
+ }
8805
+ };
8806
+
8357
8807
  // src/tui/status-bar.ts
8358
8808
  var CHARS_PER_TOKEN = 4;
8359
8809
  var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -8880,6 +9330,195 @@ var StatusBar = class {
8880
9330
  }
8881
9331
  };
8882
9332
 
9333
+ // src/tui/tree-subscription-manager.ts
9334
+ var TreeSubscriptionManager = class {
9335
+ constructor(blockRenderer, statusBar) {
9336
+ this.blockRenderer = blockRenderer;
9337
+ this.statusBar = statusBar;
9338
+ }
9339
+ treeUnsubscribe = null;
9340
+ /**
9341
+ * Subscribe to an ExecutionTree for automatic updates.
9342
+ * Handles previous unsubscription and returns a combined unsubscribe function.
9343
+ */
9344
+ subscribe(tree) {
9345
+ this.unsubscribe();
9346
+ const unsubBlock = this.blockRenderer.subscribeToTree(tree);
9347
+ const unsubStatus = this.statusBar.subscribeToTree(tree);
9348
+ this.treeUnsubscribe = () => {
9349
+ unsubBlock();
9350
+ unsubStatus();
9351
+ };
9352
+ return () => this.unsubscribe();
9353
+ }
9354
+ /**
9355
+ * Unsubscribe from the current tree if any.
9356
+ */
9357
+ unsubscribe() {
9358
+ if (this.treeUnsubscribe) {
9359
+ this.treeUnsubscribe();
9360
+ this.treeUnsubscribe = null;
9361
+ }
9362
+ }
9363
+ };
9364
+
9365
+ // src/tui/tui-app-effects.ts
9366
+ function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
9367
+ statusBar.setFocusMode(mode);
9368
+ if (mode === "input") {
9369
+ inputHandler.activate();
9370
+ } else {
9371
+ inputHandler.deactivate();
9372
+ layout.body.focus();
9373
+ }
9374
+ screenCtx.renderNow();
9375
+ }
9376
+ function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
9377
+ blockRenderer.setContentFilterMode(mode);
9378
+ statusBar.setContentFilterMode(mode);
9379
+ screenCtx.renderNow();
9380
+ }
9381
+
9382
+ // src/tui/tui-app-bootstrap.ts
9383
+ function createTUIAppDependencies(options) {
9384
+ const screenCtx = createScreen({
9385
+ stdin: options.stdin,
9386
+ stdout: options.stdout,
9387
+ title: "llmist"
9388
+ });
9389
+ const { screen } = screenCtx;
9390
+ const showHints = options.showHints ?? true;
9391
+ const layout = createBlockLayout(screen, showHints);
9392
+ const hintsBar = createHintsBar(layout, screenCtx, showHints);
9393
+ const statusBar = createStatusBar(layout, options.model, screenCtx);
9394
+ const inputHandler = createInputHandler(layout, screenCtx, screen, showHints);
9395
+ const blockRenderer = createBlockRenderer(layout, screenCtx);
9396
+ wireHintsBar(blockRenderer, hintsBar);
9397
+ const controller = createController(
9398
+ layout,
9399
+ statusBar,
9400
+ inputHandler,
9401
+ screenCtx,
9402
+ blockRenderer,
9403
+ hintsBar
9404
+ );
9405
+ const modalManager = new ModalManager();
9406
+ const keyActionHandler = new KeyActionHandler(
9407
+ controller,
9408
+ blockRenderer,
9409
+ statusBar,
9410
+ screenCtx,
9411
+ modalManager,
9412
+ layout
9413
+ );
9414
+ const keyboardManager = new KeyboardManager({
9415
+ screen,
9416
+ getFocusMode: () => controller.getFocusMode(),
9417
+ getContentFilterMode: () => controller.getContentFilterMode(),
9418
+ isWaitingForREPLPrompt: () => inputHandler.isWaitingForREPLPrompt(),
9419
+ hasPendingInput: () => inputHandler.hasPendingInput(),
9420
+ isBlockExpanded: () => blockRenderer.getSelectedBlock()?.expanded ?? false,
9421
+ onAction: (action) => {
9422
+ keyActionHandler.handleKeyAction(action);
9423
+ }
9424
+ });
9425
+ const subscriptionManager = new TreeSubscriptionManager(blockRenderer, statusBar);
9426
+ const sessionManager = new SessionManager(blockRenderer, statusBar);
9427
+ const eventRouter = new EventRouter(blockRenderer);
9428
+ wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller);
9429
+ wireScreenEvents(layout, screen, blockRenderer);
9430
+ keyboardManager.setup();
9431
+ applyFocusMode(controller.getFocusMode(), layout, statusBar, inputHandler, screenCtx);
9432
+ screenCtx.requestRender();
9433
+ return {
9434
+ screenCtx,
9435
+ statusBar,
9436
+ inputHandler,
9437
+ blockRenderer,
9438
+ controller,
9439
+ modalManager,
9440
+ subscriptionManager,
9441
+ sessionManager,
9442
+ eventRouter
9443
+ };
9444
+ }
9445
+ function createHintsBar(layout, screenCtx, showHints) {
9446
+ if (!showHints || !layout.hintsBar) {
9447
+ return null;
9448
+ }
9449
+ return new HintsBar(layout.hintsBar, () => screenCtx.requestRender());
9450
+ }
9451
+ function createStatusBar(layout, model, screenCtx) {
9452
+ return new StatusBar(
9453
+ layout.statusBar,
9454
+ model,
9455
+ () => screenCtx.requestRender(),
9456
+ () => screenCtx.renderNow()
9457
+ );
9458
+ }
9459
+ function createInputHandler(layout, screenCtx, screen, showHints) {
9460
+ return new InputHandler(
9461
+ layout.inputBar,
9462
+ layout.promptLabel,
9463
+ layout.body,
9464
+ screen,
9465
+ () => screenCtx.requestRender(),
9466
+ () => screenCtx.renderNow(),
9467
+ showHints
9468
+ );
9469
+ }
9470
+ function createBlockRenderer(layout, screenCtx) {
9471
+ return new BlockRenderer(
9472
+ layout.body,
9473
+ () => screenCtx.requestRender(),
9474
+ () => screenCtx.renderNow()
9475
+ );
9476
+ }
9477
+ function wireHintsBar(blockRenderer, hintsBar) {
9478
+ if (!hintsBar) {
9479
+ return;
9480
+ }
9481
+ blockRenderer.onHasContentChange((hasContent) => {
9482
+ hintsBar.setHasContent(hasContent);
9483
+ });
9484
+ }
9485
+ function createController(layout, statusBar, inputHandler, screenCtx, blockRenderer, hintsBar) {
9486
+ return new TUIController({
9487
+ onFocusModeChange: (mode) => {
9488
+ applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
9489
+ hintsBar?.setFocusMode(mode);
9490
+ },
9491
+ onContentFilterModeChange: (mode) => {
9492
+ applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
9493
+ hintsBar?.setContentFilterMode(mode);
9494
+ }
9495
+ });
9496
+ }
9497
+ function wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller) {
9498
+ inputHandler.onCtrlC(() => keyboardManager.handleForwardedKey("C-c"));
9499
+ inputHandler.onCtrlB(() => keyboardManager.handleForwardedKey("C-b"));
9500
+ inputHandler.onCtrlK(() => keyboardManager.handleForwardedKey("C-k"));
9501
+ inputHandler.onCtrlI(() => keyboardManager.handleForwardedKey("C-i"));
9502
+ inputHandler.onCtrlJ(() => keyboardManager.handleForwardedKey("C-j"));
9503
+ inputHandler.onCtrlP(() => keyboardManager.handleForwardedKey("C-p"));
9504
+ inputHandler.onArrowUp(() => {
9505
+ keyActionHandler.handleKeyAction({ type: "scroll_line", direction: -1 });
9506
+ });
9507
+ inputHandler.onArrowDown(() => {
9508
+ keyActionHandler.handleKeyAction({ type: "scroll_line", direction: 1 });
9509
+ });
9510
+ inputHandler.setGetFocusMode(() => controller.getFocusMode());
9511
+ inputHandler.setGetContentFilterMode(() => controller.getContentFilterMode());
9512
+ }
9513
+ function wireScreenEvents(layout, screen, blockRenderer) {
9514
+ layout.body.on("scroll", () => {
9515
+ blockRenderer.handleUserScroll();
9516
+ });
9517
+ screen.on("resize", () => {
9518
+ blockRenderer.handleResize();
9519
+ });
9520
+ }
9521
+
8883
9522
  // src/tui/index.ts
8884
9523
  var TUIApp = class _TUIApp {
8885
9524
  screenCtx;
@@ -8889,135 +9528,36 @@ var TUIApp = class _TUIApp {
8889
9528
  // New extracted components
8890
9529
  controller;
8891
9530
  modalManager;
8892
- /** Unsubscribe function for tree subscription */
8893
- treeUnsubscribe = null;
8894
- constructor(screenCtx, statusBar, inputHandler, blockRenderer, controller, modalManager) {
9531
+ subscriptionManager;
9532
+ sessionManager;
9533
+ eventRouter;
9534
+ constructor(screenCtx, statusBar, inputHandler, blockRenderer, controller, modalManager, subscriptionManager, sessionManager, eventRouter) {
8895
9535
  this.screenCtx = screenCtx;
8896
9536
  this.statusBar = statusBar;
8897
9537
  this.inputHandler = inputHandler;
8898
9538
  this.blockRenderer = blockRenderer;
8899
9539
  this.controller = controller;
8900
9540
  this.modalManager = modalManager;
9541
+ this.subscriptionManager = subscriptionManager;
9542
+ this.sessionManager = sessionManager;
9543
+ this.eventRouter = eventRouter;
8901
9544
  }
8902
9545
  /**
8903
9546
  * Create a new TUI application instance.
8904
9547
  */
8905
9548
  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
9549
+ const dependencies = createTUIAppDependencies(options);
9550
+ return new _TUIApp(
9551
+ dependencies.screenCtx,
9552
+ dependencies.statusBar,
9553
+ dependencies.inputHandler,
9554
+ dependencies.blockRenderer,
9555
+ dependencies.controller,
9556
+ dependencies.modalManager,
9557
+ dependencies.subscriptionManager,
9558
+ dependencies.sessionManager,
9559
+ dependencies.eventRouter
8980
9560
  );
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
9561
  }
9022
9562
  // ─────────────────────────────────────────────────────────────────────────────
9023
9563
  // Focus Mode Management (delegated to controller)
@@ -9053,11 +9593,7 @@ var TUIApp = class _TUIApp {
9053
9593
  * automatically by ExecutionTree subscription via subscribeToTree().
9054
9594
  */
9055
9595
  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
- }
9596
+ this.eventRouter.handleEvent(event);
9061
9597
  }
9062
9598
  /**
9063
9599
  * Show an LLM call starting.
@@ -9082,26 +9618,11 @@ var TUIApp = class _TUIApp {
9082
9618
  async showRawViewer(mode) {
9083
9619
  if (this.controller.getFocusMode() !== "browse") return;
9084
9620
  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
- }
9621
+ if (!selected || !isRawViewerNode(selected.node)) return;
9622
+ await this.modalManager.showRawViewer(
9623
+ this.screenCtx.screen,
9624
+ createRawViewerData(selected.node, mode)
9625
+ );
9105
9626
  }
9106
9627
  /**
9107
9628
  * Show approval dialog for gadget execution.
@@ -9156,21 +9677,7 @@ var TUIApp = class _TUIApp {
9156
9677
  * Subscribe to an ExecutionTree for automatic block updates.
9157
9678
  */
9158
9679
  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
- };
9680
+ return this.subscriptionManager.subscribe(tree);
9174
9681
  }
9175
9682
  // ─────────────────────────────────────────────────────────────────────────────
9176
9683
  // Memory Cleanup (REPL mode)
@@ -9182,21 +9689,21 @@ var TUIApp = class _TUIApp {
9182
9689
  * Call this after each agent run completes (after unsubscribing from tree).
9183
9690
  */
9184
9691
  clearBlockRenderer() {
9185
- this.blockRenderer.clear();
9692
+ this.sessionManager.clearAllBlocks();
9186
9693
  }
9187
9694
  /**
9188
9695
  * Clear status bar activity state.
9189
9696
  * Called between REPL turns to prevent stale state.
9190
9697
  */
9191
9698
  clearStatusBar() {
9192
- this.statusBar.clearActivity();
9699
+ this.sessionManager.clearStatusBar();
9193
9700
  }
9194
9701
  /**
9195
9702
  * Start a new session. Called at the start of each REPL turn.
9196
9703
  * Increments the session counter so new blocks get the new sessionId.
9197
9704
  */
9198
9705
  startNewSession() {
9199
- this.blockRenderer.startNewSession();
9706
+ this.sessionManager.startNewSession();
9200
9707
  }
9201
9708
  /**
9202
9709
  * Clear blocks from the previous session only.
@@ -9204,7 +9711,7 @@ var TUIApp = class _TUIApp {
9204
9711
  * The previous session's content was kept visible during this session for context.
9205
9712
  */
9206
9713
  clearPreviousSession() {
9207
- this.blockRenderer.clearPreviousSession();
9714
+ this.sessionManager.clearPreviousSession();
9208
9715
  }
9209
9716
  // ─────────────────────────────────────────────────────────────────────────────
9210
9717
  // Abort Control (delegated to controller)
@@ -9339,127 +9846,12 @@ var TUIApp = class _TUIApp {
9339
9846
  * Clean up and restore terminal.
9340
9847
  */
9341
9848
  destroy() {
9342
- if (this.treeUnsubscribe) {
9343
- this.treeUnsubscribe();
9344
- this.treeUnsubscribe = null;
9345
- }
9849
+ this.subscriptionManager.unsubscribe();
9346
9850
  this.modalManager.closeAll();
9347
9851
  this.inputHandler.cancelPending();
9348
9852
  this.screenCtx.destroy();
9349
9853
  }
9350
9854
  };
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
9855
 
9464
9856
  // src/agent-command.ts
9465
9857
  async function executeAgent(promptArg, options, env, commandName) {
@@ -9534,7 +9926,7 @@ async function executeAgent(promptArg, options, env, commandName) {
9534
9926
  if (!useTUI) {
9535
9927
  process.once("SIGINT", () => process.exit(130));
9536
9928
  }
9537
- const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile"];
9929
+ const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile", "DeleteFile"];
9538
9930
  const userApprovals = options.gadgetApproval ?? {};
9539
9931
  const gadgetApprovals = {
9540
9932
  ...userApprovals
@@ -9671,6 +10063,11 @@ ${ctx.gadgetName} is denied by configuration.`
9671
10063
  gadgetName: ctx.gadgetName,
9672
10064
  parameters: ctx.parameters
9673
10065
  });
10066
+ if (response === "always") {
10067
+ gadgetApprovals[ctx.gadgetName] = "allowed";
10068
+ } else if (response === "deny") {
10069
+ gadgetApprovals[ctx.gadgetName] = "denied";
10070
+ }
9674
10071
  if (response === "yes" || response === "always") {
9675
10072
  return { action: "proceed" };
9676
10073
  }
@@ -9685,7 +10082,7 @@ Denied by user`
9685
10082
  action: "skip",
9686
10083
  syntheticResult: `status=denied
9687
10084
 
9688
- ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
10085
+ ${ctx.gadgetName} requires interactive approval. Enable TUI mode or adjust 'gadget-approval' in your config to allow this gadget.`
9689
10086
  };
9690
10087
  }
9691
10088
  }
@@ -10156,7 +10553,7 @@ Profile: ${name}
10156
10553
  ` Temperature: ${section.temperature !== void 0 ? section.temperature : "(default)"}
10157
10554
  `
10158
10555
  );
10159
- const gadgets = section.gadgets ?? section.gadget;
10556
+ const gadgets = section.gadgets;
10160
10557
  if (gadgets && gadgets.length > 0) {
10161
10558
  env.stdout.write("\nGadgets:\n");
10162
10559
  for (const g of gadgets) {
@@ -10208,7 +10605,7 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
10208
10605
  // src/environment.ts
10209
10606
  import { join as join4 } from "path";
10210
10607
  import readline from "readline";
10211
- import chalk6 from "chalk";
10608
+ import chalk7 from "chalk";
10212
10609
  import { createLogger, LLMist } from "llmist";
10213
10610
  var LOG_LEVEL_MAP = {
10214
10611
  silly: 0,
@@ -10254,14 +10651,14 @@ function createPromptFunction(stdin, stdout) {
10254
10651
  output: stdout
10255
10652
  });
10256
10653
  stdout.write("\n");
10257
- stdout.write(`${chalk6.cyan("\u2500".repeat(60))}
10654
+ stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
10258
10655
  `);
10259
- stdout.write(chalk6.cyan.bold("\u{1F916} Agent asks:\n"));
10656
+ stdout.write(chalk7.cyan.bold("\u{1F916} Agent asks:\n"));
10260
10657
  stdout.write(`${question}
10261
10658
  `);
10262
- stdout.write(`${chalk6.cyan("\u2500".repeat(60))}
10659
+ stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
10263
10660
  `);
10264
- rl.question(chalk6.green.bold("You: "), (answer) => {
10661
+ rl.question(chalk7.green.bold("You: "), (answer) => {
10265
10662
  rl.close();
10266
10663
  resolve3(answer);
10267
10664
  });
@@ -10342,12 +10739,12 @@ function registerCustomCommand(program, name, config, env, globalSubagents, glob
10342
10739
  }
10343
10740
 
10344
10741
  // src/gadget-command.ts
10345
- import chalk8 from "chalk";
10742
+ import chalk9 from "chalk";
10346
10743
  import { schemaToJSONSchema as schemaToJSONSchema2, validateGadgetSchema } from "llmist";
10347
10744
 
10348
10745
  // src/gadget-prompts.ts
10349
10746
  import { createInterface } from "readline/promises";
10350
- import chalk7 from "chalk";
10747
+ import chalk8 from "chalk";
10351
10748
  import { schemaToJSONSchema } from "llmist";
10352
10749
  async function promptForParameters(schema, ctx) {
10353
10750
  if (!schema) {
@@ -10380,16 +10777,16 @@ ${issues}`);
10380
10777
  async function promptForField(rl, key, prop, required) {
10381
10778
  const isRequired = required.includes(key);
10382
10779
  const typeHint = formatTypeHint(prop);
10383
- const defaultHint = prop.default !== void 0 ? chalk7.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
10384
- const requiredMarker = isRequired ? chalk7.red("*") : "";
10780
+ const defaultHint = prop.default !== void 0 ? chalk8.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
10781
+ const requiredMarker = isRequired ? chalk8.red("*") : "";
10385
10782
  let prompt = `
10386
- ${chalk7.cyan.bold(key)}${requiredMarker}`;
10783
+ ${chalk8.cyan.bold(key)}${requiredMarker}`;
10387
10784
  if (prop.description) {
10388
- prompt += chalk7.dim(` - ${prop.description}`);
10785
+ prompt += chalk8.dim(` - ${prop.description}`);
10389
10786
  }
10390
10787
  prompt += `
10391
10788
  ${typeHint}${defaultHint}
10392
- ${chalk7.green(">")} `;
10789
+ ${chalk8.green(">")} `;
10393
10790
  const answer = await rl.question(prompt);
10394
10791
  const trimmed = answer.trim();
10395
10792
  if (!trimmed) {
@@ -10405,20 +10802,20 @@ ${chalk7.cyan.bold(key)}${requiredMarker}`;
10405
10802
  }
10406
10803
  function formatTypeHint(prop) {
10407
10804
  if (prop.enum) {
10408
- return chalk7.yellow(`(${prop.enum.join(" | ")})`);
10805
+ return chalk8.yellow(`(${prop.enum.join(" | ")})`);
10409
10806
  }
10410
10807
  if (prop.type === "array") {
10411
10808
  const items = prop.items;
10412
10809
  if (items?.enum) {
10413
- return chalk7.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
10810
+ return chalk8.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
10414
10811
  }
10415
10812
  const itemType = items?.type ?? "any";
10416
- return chalk7.yellow(`(${itemType}[]) comma-separated`);
10813
+ return chalk8.yellow(`(${itemType}[]) comma-separated`);
10417
10814
  }
10418
10815
  if (prop.type === "object" && prop.properties) {
10419
- return chalk7.yellow("(object) enter as JSON");
10816
+ return chalk8.yellow("(object) enter as JSON");
10420
10817
  }
10421
- return chalk7.yellow(`(${prop.type ?? "any"})`);
10818
+ return chalk8.yellow(`(${prop.type ?? "any"})`);
10422
10819
  }
10423
10820
  function parseValue(input, prop, key) {
10424
10821
  const type = prop.type;
@@ -10529,7 +10926,7 @@ Available gadgets:
10529
10926
  async function executeGadgetRun(file, options, env) {
10530
10927
  const cwd = process.cwd();
10531
10928
  const { gadget, name } = await selectGadget(file, options.name, cwd);
10532
- env.stderr.write(chalk8.cyan.bold(`
10929
+ env.stderr.write(chalk9.cyan.bold(`
10533
10930
  \u{1F527} Running gadget: ${name}
10534
10931
  `));
10535
10932
  let params;
@@ -10540,7 +10937,7 @@ async function executeGadgetRun(file, options, env) {
10540
10937
  // Prompts go to stderr to keep stdout clean
10541
10938
  });
10542
10939
  } else {
10543
- env.stderr.write(chalk8.dim("Reading parameters from stdin...\n"));
10940
+ env.stderr.write(chalk9.dim("Reading parameters from stdin...\n"));
10544
10941
  const stdinParams = await readStdinJson(env.stdin);
10545
10942
  if (gadget.parameterSchema) {
10546
10943
  const result2 = gadget.parameterSchema.safeParse(stdinParams);
@@ -10554,7 +10951,7 @@ ${issues}`);
10554
10951
  params = stdinParams;
10555
10952
  }
10556
10953
  }
10557
- env.stderr.write(chalk8.dim("\nExecuting...\n"));
10954
+ env.stderr.write(chalk9.dim("\nExecuting...\n"));
10558
10955
  const startTime = Date.now();
10559
10956
  let result;
10560
10957
  let cost;
@@ -10581,7 +10978,7 @@ ${issues}`);
10581
10978
  }
10582
10979
  const elapsed = Date.now() - startTime;
10583
10980
  const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
10584
- env.stderr.write(chalk8.green(`
10981
+ env.stderr.write(chalk9.green(`
10585
10982
  \u2713 Completed in ${elapsed}ms${costInfo}
10586
10983
 
10587
10984
  `));
@@ -10617,37 +11014,37 @@ async function executeGadgetInfo(file, options, env) {
10617
11014
  return;
10618
11015
  }
10619
11016
  env.stdout.write("\n");
10620
- env.stdout.write(chalk8.cyan.bold(`${name}
11017
+ env.stdout.write(chalk9.cyan.bold(`${name}
10621
11018
  `));
10622
- env.stdout.write(chalk8.cyan("\u2550".repeat(name.length)) + "\n\n");
10623
- env.stdout.write(chalk8.bold("Description:\n"));
11019
+ env.stdout.write(chalk9.cyan("\u2550".repeat(name.length)) + "\n\n");
11020
+ env.stdout.write(chalk9.bold("Description:\n"));
10624
11021
  env.stdout.write(` ${gadget.description}
10625
11022
 
10626
11023
  `);
10627
11024
  if (gadget.parameterSchema) {
10628
- env.stdout.write(chalk8.bold("Parameters:\n"));
11025
+ env.stdout.write(chalk9.bold("Parameters:\n"));
10629
11026
  const jsonSchema = schemaToJSONSchema2(gadget.parameterSchema, { target: "draft-7" });
10630
11027
  env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
10631
11028
  } else {
10632
- env.stdout.write(chalk8.dim("No parameters required.\n\n"));
11029
+ env.stdout.write(chalk9.dim("No parameters required.\n\n"));
10633
11030
  }
10634
11031
  if (gadget.timeoutMs) {
10635
- env.stdout.write(chalk8.bold("Timeout:\n"));
11032
+ env.stdout.write(chalk9.bold("Timeout:\n"));
10636
11033
  env.stdout.write(` ${gadget.timeoutMs}ms
10637
11034
 
10638
11035
  `);
10639
11036
  }
10640
11037
  if (gadget.examples && gadget.examples.length > 0) {
10641
- env.stdout.write(chalk8.bold("Examples:\n"));
11038
+ env.stdout.write(chalk9.bold("Examples:\n"));
10642
11039
  for (const example of gadget.examples) {
10643
11040
  if (example.comment) {
10644
- env.stdout.write(chalk8.dim(` # ${example.comment}
11041
+ env.stdout.write(chalk9.dim(` # ${example.comment}
10645
11042
  `));
10646
11043
  }
10647
- env.stdout.write(` Input: ${chalk8.cyan(JSON.stringify(example.params))}
11044
+ env.stdout.write(` Input: ${chalk9.cyan(JSON.stringify(example.params))}
10648
11045
  `);
10649
11046
  if (example.output !== void 0) {
10650
- env.stdout.write(` Output: ${chalk8.green(example.output)}
11047
+ env.stdout.write(` Output: ${chalk9.green(example.output)}
10651
11048
  `);
10652
11049
  }
10653
11050
  env.stdout.write("\n");
@@ -10680,27 +11077,27 @@ function formatSchemaAsText(schema, indent = "") {
10680
11077
  const isRequired = required.includes(key);
10681
11078
  const enumValues = prop.enum;
10682
11079
  const defaultValue = prop.default;
10683
- let line = `${indent}${chalk8.cyan(key)}`;
11080
+ let line = `${indent}${chalk9.cyan(key)}`;
10684
11081
  if (isRequired) {
10685
- line += chalk8.red("*");
11082
+ line += chalk9.red("*");
10686
11083
  }
10687
11084
  if (type === "array") {
10688
11085
  const items = prop.items;
10689
11086
  const itemType = items?.type || "any";
10690
- line += chalk8.dim(` (${itemType}[])`);
11087
+ line += chalk9.dim(` (${itemType}[])`);
10691
11088
  } else if (type === "object" && prop.properties) {
10692
- line += chalk8.dim(" (object)");
11089
+ line += chalk9.dim(" (object)");
10693
11090
  } else {
10694
- line += chalk8.dim(` (${type})`);
11091
+ line += chalk9.dim(` (${type})`);
10695
11092
  }
10696
11093
  if (defaultValue !== void 0) {
10697
- line += chalk8.dim(` [default: ${JSON.stringify(defaultValue)}]`);
11094
+ line += chalk9.dim(` [default: ${JSON.stringify(defaultValue)}]`);
10698
11095
  }
10699
11096
  if (description) {
10700
11097
  line += `: ${description}`;
10701
11098
  }
10702
11099
  if (enumValues) {
10703
- line += chalk8.yellow(` - one of: ${enumValues.join(", ")}`);
11100
+ line += chalk9.yellow(` - one of: ${enumValues.join(", ")}`);
10704
11101
  }
10705
11102
  lines.push(line);
10706
11103
  if (type === "object" && prop.properties) {
@@ -10740,20 +11137,20 @@ async function executeGadgetValidate(file, env) {
10740
11137
  throw new Error(`Validation issues:
10741
11138
  ${issues.map((i) => ` - ${i}`).join("\n")}`);
10742
11139
  }
10743
- env.stdout.write(chalk8.green.bold("\n\u2713 Valid\n\n"));
10744
- env.stdout.write(chalk8.bold("Gadgets found:\n"));
11140
+ env.stdout.write(chalk9.green.bold("\n\u2713 Valid\n\n"));
11141
+ env.stdout.write(chalk9.bold("Gadgets found:\n"));
10745
11142
  for (const gadget of gadgets) {
10746
11143
  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}
11144
+ const schemaInfo = gadget.parameterSchema ? chalk9.cyan("(with schema)") : chalk9.dim("(no schema)");
11145
+ env.stdout.write(` ${chalk9.bold(name)} ${schemaInfo}
10749
11146
  `);
10750
- env.stdout.write(chalk8.dim(` ${gadget.description}
11147
+ env.stdout.write(chalk9.dim(` ${gadget.description}
10751
11148
  `));
10752
11149
  }
10753
11150
  env.stdout.write("\n");
10754
11151
  } catch (error) {
10755
11152
  const message = error instanceof Error ? error.message : String(error);
10756
- env.stdout.write(chalk8.red.bold(`
11153
+ env.stdout.write(chalk9.red.bold(`
10757
11154
  \u2717 Invalid
10758
11155
 
10759
11156
  `));
@@ -10937,7 +11334,7 @@ function registerInitCommand(program, env) {
10937
11334
  }
10938
11335
 
10939
11336
  // src/models-command.ts
10940
- import chalk9 from "chalk";
11337
+ import chalk10 from "chalk";
10941
11338
  import { MODEL_ALIASES } from "llmist";
10942
11339
  async function handleModelsCommand(options, env) {
10943
11340
  const client = env.createClient();
@@ -10956,11 +11353,11 @@ async function handleModelsCommand(options, env) {
10956
11353
  function renderAllTables(textModels, imageModels, speechModels, verbose, stream) {
10957
11354
  const hasAnyModels = textModels.length > 0 || imageModels.length > 0 || speechModels.length > 0;
10958
11355
  if (!hasAnyModels) {
10959
- stream.write(chalk9.yellow("\nNo models found matching the specified criteria.\n\n"));
11356
+ stream.write(chalk10.yellow("\nNo models found matching the specified criteria.\n\n"));
10960
11357
  return;
10961
11358
  }
10962
- stream.write(chalk9.bold.cyan("\nAvailable Models\n"));
10963
- stream.write(chalk9.cyan("=".repeat(80)) + "\n\n");
11359
+ stream.write(chalk10.bold.cyan("\nAvailable Models\n"));
11360
+ stream.write(chalk10.cyan("=".repeat(80)) + "\n\n");
10964
11361
  if (textModels.length > 0) {
10965
11362
  renderTextTable(textModels, verbose, stream);
10966
11363
  }
@@ -10971,12 +11368,12 @@ function renderAllTables(textModels, imageModels, speechModels, verbose, stream)
10971
11368
  renderSpeechTable(speechModels, verbose, stream);
10972
11369
  }
10973
11370
  if (textModels.length > 0) {
10974
- stream.write(chalk9.bold.magenta("Model Shortcuts\n"));
10975
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n");
11371
+ stream.write(chalk10.bold.magenta("Model Shortcuts\n"));
11372
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n");
10976
11373
  const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
10977
11374
  for (const [shortcut, fullName] of shortcuts) {
10978
11375
  stream.write(
10979
- chalk9.cyan(` ${shortcut.padEnd(15)}`) + chalk9.dim(" \u2192 ") + chalk9.white(fullName) + "\n"
11376
+ chalk10.cyan(` ${shortcut.padEnd(15)}`) + chalk10.dim(" \u2192 ") + chalk10.white(fullName) + "\n"
10980
11377
  );
10981
11378
  }
10982
11379
  stream.write("\n");
@@ -10991,13 +11388,13 @@ function renderTextTable(models, verbose, stream) {
10991
11388
  }
10992
11389
  grouped.get(provider).push(model);
10993
11390
  }
10994
- stream.write(chalk9.bold.blue("\u{1F4DD} Text/LLM Models\n"));
10995
- stream.write(chalk9.dim("\u2500".repeat(80)) + "\n\n");
11391
+ stream.write(chalk10.bold.blue("\u{1F4DD} Text/LLM Models\n"));
11392
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
10996
11393
  const providers = Array.from(grouped.keys()).sort();
10997
11394
  for (const provider of providers) {
10998
11395
  const providerModels = grouped.get(provider);
10999
11396
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11000
- stream.write(chalk9.bold.yellow(`${providerName}
11397
+ stream.write(chalk10.bold.yellow(`${providerName}
11001
11398
  `));
11002
11399
  if (verbose) {
11003
11400
  renderVerboseTable(providerModels, stream);
@@ -11014,58 +11411,58 @@ function renderCompactTable(models, stream) {
11014
11411
  const inputWidth = 10;
11015
11412
  const outputWidth = 10;
11016
11413
  stream.write(
11017
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11414
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11018
11415
  );
11019
11416
  stream.write(
11020
- chalk9.bold(
11417
+ chalk10.bold(
11021
11418
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
11022
11419
  ) + "\n"
11023
11420
  );
11024
11421
  stream.write(
11025
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11422
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11026
11423
  );
11027
11424
  for (const model of models) {
11028
11425
  const contextFormatted = formatTokensLong(model.contextWindow);
11029
11426
  const inputPrice = `$${model.pricing.input.toFixed(2)}`;
11030
11427
  const outputPrice = `$${model.pricing.output.toFixed(2)}`;
11031
11428
  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"
11429
+ 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
11430
  );
11034
11431
  }
11035
11432
  stream.write(
11036
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11433
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
11037
11434
  );
11038
- stream.write(chalk9.dim(` * Prices are per 1M tokens
11435
+ stream.write(chalk10.dim(` * Prices are per 1M tokens
11039
11436
  `));
11040
11437
  }
11041
11438
  function renderVerboseTable(models, stream) {
11042
11439
  for (const model of models) {
11043
- stream.write(chalk9.bold.green(`
11440
+ stream.write(chalk10.bold.green(`
11044
11441
  ${model.modelId}
11045
11442
  `));
11046
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11047
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11443
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11444
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11048
11445
  `);
11049
11446
  stream.write(
11050
- ` ${chalk9.dim("Context:")} ${chalk9.yellow(formatTokensLong(model.contextWindow))}
11447
+ ` ${chalk10.dim("Context:")} ${chalk10.yellow(formatTokensLong(model.contextWindow))}
11051
11448
  `
11052
11449
  );
11053
11450
  stream.write(
11054
- ` ${chalk9.dim("Max Output:")} ${chalk9.yellow(formatTokensLong(model.maxOutputTokens))}
11451
+ ` ${chalk10.dim("Max Output:")} ${chalk10.yellow(formatTokensLong(model.maxOutputTokens))}
11055
11452
  `
11056
11453
  );
11057
11454
  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)")}
11455
+ ` ${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
11456
  `
11060
11457
  );
11061
11458
  if (model.pricing.cachedInput !== void 0) {
11062
11459
  stream.write(
11063
- ` ${chalk9.dim("Cached Input:")} ${chalk9.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
11460
+ ` ${chalk10.dim("Cached Input:")} ${chalk10.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
11064
11461
  `
11065
11462
  );
11066
11463
  }
11067
11464
  if (model.knowledgeCutoff) {
11068
- stream.write(` ${chalk9.dim("Knowledge:")} ${model.knowledgeCutoff}
11465
+ stream.write(` ${chalk10.dim("Knowledge:")} ${model.knowledgeCutoff}
11069
11466
  `);
11070
11467
  }
11071
11468
  const features = [];
@@ -11076,20 +11473,20 @@ function renderVerboseTable(models, stream) {
11076
11473
  if (model.features.structuredOutputs) features.push("structured-outputs");
11077
11474
  if (model.features.fineTuning) features.push("fine-tuning");
11078
11475
  if (features.length > 0) {
11079
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11476
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11080
11477
  `);
11081
11478
  }
11082
11479
  if (model.metadata) {
11083
11480
  if (model.metadata.family) {
11084
- stream.write(` ${chalk9.dim("Family:")} ${model.metadata.family}
11481
+ stream.write(` ${chalk10.dim("Family:")} ${model.metadata.family}
11085
11482
  `);
11086
11483
  }
11087
11484
  if (model.metadata.releaseDate) {
11088
- stream.write(` ${chalk9.dim("Released:")} ${model.metadata.releaseDate}
11485
+ stream.write(` ${chalk10.dim("Released:")} ${model.metadata.releaseDate}
11089
11486
  `);
11090
11487
  }
11091
11488
  if (model.metadata.notes) {
11092
- stream.write(` ${chalk9.dim("Notes:")} ${chalk9.italic(model.metadata.notes)}
11489
+ stream.write(` ${chalk10.dim("Notes:")} ${chalk10.italic(model.metadata.notes)}
11093
11490
  `);
11094
11491
  }
11095
11492
  }
@@ -11097,8 +11494,8 @@ function renderVerboseTable(models, stream) {
11097
11494
  stream.write("\n");
11098
11495
  }
11099
11496
  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");
11497
+ stream.write(chalk10.bold.green("\u{1F3A8} Image Generation Models\n"));
11498
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
11102
11499
  const grouped = /* @__PURE__ */ new Map();
11103
11500
  for (const model of models) {
11104
11501
  if (!grouped.has(model.provider)) {
@@ -11108,29 +11505,29 @@ function renderImageTable(models, verbose, stream) {
11108
11505
  }
11109
11506
  for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
11110
11507
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11111
- stream.write(chalk9.bold.yellow(`${providerName}
11508
+ stream.write(chalk10.bold.yellow(`${providerName}
11112
11509
  `));
11113
11510
  if (verbose) {
11114
11511
  for (const model of providerModels) {
11115
- stream.write(chalk9.bold.green(`
11512
+ stream.write(chalk10.bold.green(`
11116
11513
  ${model.modelId}
11117
11514
  `));
11118
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11119
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11515
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11516
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11120
11517
  `);
11121
11518
  stream.write(
11122
- ` ${chalk9.dim("Sizes:")} ${chalk9.yellow(model.supportedSizes.join(", "))}
11519
+ ` ${chalk10.dim("Sizes:")} ${chalk10.yellow(model.supportedSizes.join(", "))}
11123
11520
  `
11124
11521
  );
11125
11522
  if (model.supportedQualities) {
11126
11523
  stream.write(
11127
- ` ${chalk9.dim("Qualities:")} ${chalk9.yellow(model.supportedQualities.join(", "))}
11524
+ ` ${chalk10.dim("Qualities:")} ${chalk10.yellow(model.supportedQualities.join(", "))}
11128
11525
  `
11129
11526
  );
11130
11527
  }
11131
- stream.write(` ${chalk9.dim("Max Images:")} ${chalk9.yellow(model.maxImages.toString())}
11528
+ stream.write(` ${chalk10.dim("Max Images:")} ${chalk10.yellow(model.maxImages.toString())}
11132
11529
  `);
11133
- stream.write(` ${chalk9.dim("Pricing:")} ${chalk9.cyan(formatImagePrice(model))}
11530
+ stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatImagePrice(model))}
11134
11531
  `);
11135
11532
  if (model.features) {
11136
11533
  const features = [];
@@ -11138,7 +11535,7 @@ function renderImageTable(models, verbose, stream) {
11138
11535
  if (model.features.transparency) features.push("transparency");
11139
11536
  if (model.features.conversational) features.push("conversational");
11140
11537
  if (features.length > 0) {
11141
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11538
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11142
11539
  `);
11143
11540
  }
11144
11541
  }
@@ -11148,27 +11545,27 @@ function renderImageTable(models, verbose, stream) {
11148
11545
  const nameWidth = 25;
11149
11546
  const sizesWidth = 20;
11150
11547
  const priceWidth = 15;
11151
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11548
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11152
11549
  stream.write(
11153
- chalk9.bold(
11550
+ chalk10.bold(
11154
11551
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Sizes".padEnd(sizesWidth) + " " + "Price".padEnd(priceWidth)
11155
11552
  ) + "\n"
11156
11553
  );
11157
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11554
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11158
11555
  for (const model of providerModels) {
11159
11556
  const sizes = model.supportedSizes.length > 2 ? model.supportedSizes.slice(0, 2).join(", ") + "..." : model.supportedSizes.join(", ");
11160
11557
  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"
11558
+ 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
11559
  );
11163
11560
  }
11164
- stream.write(chalk9.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11561
+ stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
11165
11562
  }
11166
11563
  stream.write("\n");
11167
11564
  }
11168
11565
  }
11169
11566
  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");
11567
+ stream.write(chalk10.bold.magenta("\u{1F3A4} Speech (TTS) Models\n"));
11568
+ stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
11172
11569
  const grouped = /* @__PURE__ */ new Map();
11173
11570
  for (const model of models) {
11174
11571
  if (!grouped.has(model.provider)) {
@@ -11178,34 +11575,34 @@ function renderSpeechTable(models, verbose, stream) {
11178
11575
  }
11179
11576
  for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
11180
11577
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
11181
- stream.write(chalk9.bold.yellow(`${providerName}
11578
+ stream.write(chalk10.bold.yellow(`${providerName}
11182
11579
  `));
11183
11580
  if (verbose) {
11184
11581
  for (const model of providerModels) {
11185
- stream.write(chalk9.bold.green(`
11582
+ stream.write(chalk10.bold.green(`
11186
11583
  ${model.modelId}
11187
11584
  `));
11188
- stream.write(chalk9.dim(" " + "\u2500".repeat(60)) + "\n");
11189
- stream.write(` ${chalk9.dim("Name:")} ${chalk9.white(model.displayName)}
11585
+ stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
11586
+ stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
11190
11587
  `);
11191
11588
  stream.write(
11192
- ` ${chalk9.dim("Voices:")} ${chalk9.yellow(model.voices.length.toString())} voices
11589
+ ` ${chalk10.dim("Voices:")} ${chalk10.yellow(model.voices.length.toString())} voices
11193
11590
  `
11194
11591
  );
11195
11592
  if (model.voices.length <= 6) {
11196
- stream.write(` ${chalk9.dim(model.voices.join(", "))}
11593
+ stream.write(` ${chalk10.dim(model.voices.join(", "))}
11197
11594
  `);
11198
11595
  } else {
11199
- stream.write(` ${chalk9.dim(model.voices.slice(0, 6).join(", ") + "...")}
11596
+ stream.write(` ${chalk10.dim(model.voices.slice(0, 6).join(", ") + "...")}
11200
11597
  `);
11201
11598
  }
11202
- stream.write(` ${chalk9.dim("Formats:")} ${chalk9.yellow(model.formats.join(", "))}
11599
+ stream.write(` ${chalk10.dim("Formats:")} ${chalk10.yellow(model.formats.join(", "))}
11203
11600
  `);
11204
11601
  stream.write(
11205
- ` ${chalk9.dim("Max Input:")} ${chalk9.yellow(model.maxInputLength.toString())} chars
11602
+ ` ${chalk10.dim("Max Input:")} ${chalk10.yellow(model.maxInputLength.toString())} chars
11206
11603
  `
11207
11604
  );
11208
- stream.write(` ${chalk9.dim("Pricing:")} ${chalk9.cyan(formatSpeechPrice(model))}
11605
+ stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatSpeechPrice(model))}
11209
11606
  `);
11210
11607
  if (model.features) {
11211
11608
  const features = [];
@@ -11213,7 +11610,7 @@ function renderSpeechTable(models, verbose, stream) {
11213
11610
  if (model.features.voiceInstructions) features.push("voice-instructions");
11214
11611
  if (model.features.languages) features.push(`${model.features.languages} languages`);
11215
11612
  if (features.length > 0) {
11216
- stream.write(` ${chalk9.dim("Features:")} ${chalk9.blue(features.join(", "))}
11613
+ stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
11217
11614
  `);
11218
11615
  }
11219
11616
  }
@@ -11224,23 +11621,23 @@ function renderSpeechTable(models, verbose, stream) {
11224
11621
  const voicesWidth = 12;
11225
11622
  const priceWidth = 18;
11226
11623
  stream.write(
11227
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11624
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11228
11625
  );
11229
11626
  stream.write(
11230
- chalk9.bold(
11627
+ chalk10.bold(
11231
11628
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Voices".padEnd(voicesWidth) + " " + "Price".padEnd(priceWidth)
11232
11629
  ) + "\n"
11233
11630
  );
11234
11631
  stream.write(
11235
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11632
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11236
11633
  );
11237
11634
  for (const model of providerModels) {
11238
11635
  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"
11636
+ 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
11637
  );
11241
11638
  }
11242
11639
  stream.write(
11243
- chalk9.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11640
+ chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
11244
11641
  );
11245
11642
  }
11246
11643
  stream.write("\n");