llmist 1.3.0 → 1.4.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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import "./chunk-UEEESLOA.js";
2
3
  import {
3
4
  AgentBuilder,
4
5
  BaseGadget,
@@ -13,7 +14,7 @@ import {
13
14
  createLogger,
14
15
  init_builder,
15
16
  init_client,
16
- init_constants2 as init_constants,
17
+ init_constants,
17
18
  init_create_gadget,
18
19
  init_exceptions,
19
20
  init_gadget,
@@ -26,7 +27,7 @@ import {
26
27
  resolveModel,
27
28
  schemaToJSONSchema,
28
29
  validateGadgetSchema
29
- } from "./chunk-RZTAKIDE.js";
30
+ } from "./chunk-VGZCFUPX.js";
30
31
 
31
32
  // src/cli/constants.ts
32
33
  var CLI_NAME = "llmist";
@@ -79,7 +80,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
79
80
  // package.json
80
81
  var package_default = {
81
82
  name: "llmist",
82
- version: "1.2.0",
83
+ version: "1.3.1",
83
84
  description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
84
85
  type: "module",
85
86
  main: "dist/index.cjs",
@@ -163,6 +164,7 @@ var package_default = {
163
164
  "@google/genai": "^1.27.0",
164
165
  chalk: "^5.6.2",
165
166
  commander: "^12.1.0",
167
+ diff: "^8.0.2",
166
168
  eta: "^4.4.1",
167
169
  "js-toml": "^1.0.2",
168
170
  "js-yaml": "^4.1.0",
@@ -179,6 +181,7 @@ var package_default = {
179
181
  "@commitlint/config-conventional": "^20.0.0",
180
182
  "@semantic-release/changelog": "^6.0.3",
181
183
  "@semantic-release/git": "^10.0.1",
184
+ "@types/diff": "^8.0.0",
182
185
  "@types/js-yaml": "^4.0.9",
183
186
  "@types/marked-terminal": "^6.1.1",
184
187
  "@types/node": "^20.12.7",
@@ -193,10 +196,272 @@ var package_default = {
193
196
 
194
197
  // src/cli/agent-command.ts
195
198
  init_builder();
199
+ import { createInterface as createInterface2 } from "node:readline/promises";
200
+ import chalk5 from "chalk";
201
+
202
+ // src/core/errors.ts
203
+ function isAbortError(error) {
204
+ if (!(error instanceof Error)) return false;
205
+ if (error.name === "AbortError") return true;
206
+ if (error.name === "APIConnectionAbortedError") return true;
207
+ if (error.name === "APIUserAbortError") return true;
208
+ const message = error.message.toLowerCase();
209
+ if (message.includes("abort")) return true;
210
+ if (message.includes("cancelled")) return true;
211
+ if (message.includes("canceled")) return true;
212
+ return false;
213
+ }
214
+
215
+ // src/cli/agent-command.ts
196
216
  init_registry();
197
217
  init_constants();
218
+
219
+ // src/cli/approval/manager.ts
198
220
  import { createInterface } from "node:readline/promises";
199
- import chalk3 from "chalk";
221
+ import chalk2 from "chalk";
222
+
223
+ // src/cli/approval/context-providers.ts
224
+ import { existsSync, readFileSync } from "node:fs";
225
+ import { resolve } from "node:path";
226
+ import { createPatch } from "diff";
227
+
228
+ // src/cli/approval/diff-renderer.ts
229
+ import chalk from "chalk";
230
+ function renderColoredDiff(diff) {
231
+ return diff.split("\n").map((line) => {
232
+ if (line.startsWith("---") || line.startsWith("+++")) {
233
+ return chalk.bold(line);
234
+ }
235
+ if (line.startsWith("+")) {
236
+ return chalk.green(line);
237
+ }
238
+ if (line.startsWith("-")) {
239
+ return chalk.red(line);
240
+ }
241
+ if (line.startsWith("@@")) {
242
+ return chalk.cyan(line);
243
+ }
244
+ return chalk.dim(line);
245
+ }).join("\n");
246
+ }
247
+ function formatNewFileDiff(filePath, content) {
248
+ const lines = content.split("\n");
249
+ const header = `+++ ${filePath} (new file)`;
250
+ const addedLines = lines.map((line) => `+ ${line}`).join("\n");
251
+ return `${header}
252
+ ${addedLines}`;
253
+ }
254
+
255
+ // src/cli/approval/context-providers.ts
256
+ var WriteFileContextProvider = class {
257
+ gadgetName = "WriteFile";
258
+ async getContext(params) {
259
+ const filePath = String(params.filePath ?? params.path ?? "");
260
+ const newContent = String(params.content ?? "");
261
+ const resolvedPath = resolve(process.cwd(), filePath);
262
+ if (!existsSync(resolvedPath)) {
263
+ return {
264
+ summary: `Create new file: ${filePath}`,
265
+ details: formatNewFileDiff(filePath, newContent)
266
+ };
267
+ }
268
+ const oldContent = readFileSync(resolvedPath, "utf-8");
269
+ const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
270
+ return {
271
+ summary: `Modify: ${filePath}`,
272
+ details: diff
273
+ };
274
+ }
275
+ };
276
+ var EditFileContextProvider = class {
277
+ gadgetName = "EditFile";
278
+ async getContext(params) {
279
+ const filePath = String(params.filePath ?? params.path ?? "");
280
+ const resolvedPath = resolve(process.cwd(), filePath);
281
+ if ("content" in params) {
282
+ const newContent = String(params.content);
283
+ if (!existsSync(resolvedPath)) {
284
+ return {
285
+ summary: `Create new file: ${filePath}`,
286
+ details: formatNewFileDiff(filePath, newContent)
287
+ };
288
+ }
289
+ const oldContent = readFileSync(resolvedPath, "utf-8");
290
+ const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
291
+ return {
292
+ summary: `Modify: ${filePath}`,
293
+ details: diff
294
+ };
295
+ }
296
+ if ("commands" in params) {
297
+ const commands = String(params.commands);
298
+ return {
299
+ summary: `Edit: ${filePath}`,
300
+ details: `Commands:
301
+ ${commands}`
302
+ };
303
+ }
304
+ return {
305
+ summary: `Edit: ${filePath}`
306
+ };
307
+ }
308
+ };
309
+ var RunCommandContextProvider = class {
310
+ gadgetName = "RunCommand";
311
+ async getContext(params) {
312
+ const command = String(params.command ?? "");
313
+ const cwd = params.cwd ? ` (in ${params.cwd})` : "";
314
+ return {
315
+ summary: `Execute: ${command}${cwd}`
316
+ };
317
+ }
318
+ };
319
+ var DefaultContextProvider = class {
320
+ constructor(gadgetName) {
321
+ this.gadgetName = gadgetName;
322
+ }
323
+ async getContext(params) {
324
+ const paramEntries = Object.entries(params);
325
+ if (paramEntries.length === 0) {
326
+ return {
327
+ summary: `${this.gadgetName}()`
328
+ };
329
+ }
330
+ const formatValue = (value) => {
331
+ const MAX_LEN = 50;
332
+ const str = JSON.stringify(value);
333
+ return str.length > MAX_LEN ? `${str.slice(0, MAX_LEN - 3)}...` : str;
334
+ };
335
+ const paramStr = paramEntries.map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
336
+ return {
337
+ summary: `${this.gadgetName}(${paramStr})`
338
+ };
339
+ }
340
+ };
341
+ var builtinContextProviders = [
342
+ new WriteFileContextProvider(),
343
+ new EditFileContextProvider(),
344
+ new RunCommandContextProvider()
345
+ ];
346
+
347
+ // src/cli/approval/manager.ts
348
+ var ApprovalManager = class {
349
+ /**
350
+ * Creates a new ApprovalManager.
351
+ *
352
+ * @param config - Approval configuration with per-gadget modes
353
+ * @param env - CLI environment for I/O operations
354
+ * @param progress - Optional progress indicator to pause during prompts
355
+ */
356
+ constructor(config, env, progress) {
357
+ this.config = config;
358
+ this.env = env;
359
+ this.progress = progress;
360
+ for (const provider of builtinContextProviders) {
361
+ this.registerProvider(provider);
362
+ }
363
+ }
364
+ providers = /* @__PURE__ */ new Map();
365
+ /**
366
+ * Registers a custom context provider for a gadget.
367
+ *
368
+ * @param provider - The context provider to register
369
+ */
370
+ registerProvider(provider) {
371
+ this.providers.set(provider.gadgetName.toLowerCase(), provider);
372
+ }
373
+ /**
374
+ * Gets the approval mode for a gadget.
375
+ *
376
+ * Resolution order:
377
+ * 1. Explicit configuration for the gadget name
378
+ * 2. Wildcard "*" configuration
379
+ * 3. Default mode from config
380
+ *
381
+ * @param gadgetName - Name of the gadget
382
+ * @returns The approval mode to use
383
+ */
384
+ getApprovalMode(gadgetName) {
385
+ const normalizedName = gadgetName.toLowerCase();
386
+ for (const [configName, mode] of Object.entries(this.config.gadgetApprovals)) {
387
+ if (configName.toLowerCase() === normalizedName) {
388
+ return mode;
389
+ }
390
+ }
391
+ if ("*" in this.config.gadgetApprovals) {
392
+ return this.config.gadgetApprovals["*"];
393
+ }
394
+ return this.config.defaultMode;
395
+ }
396
+ /**
397
+ * Requests approval for a gadget execution.
398
+ *
399
+ * Behavior depends on the gadget's approval mode:
400
+ * - "allowed": Returns approved immediately
401
+ * - "denied": Returns denied with configuration message
402
+ * - "approval-required": Prompts user interactively
403
+ *
404
+ * @param gadgetName - Name of the gadget
405
+ * @param params - The gadget's execution parameters
406
+ * @returns Approval result indicating whether to proceed
407
+ */
408
+ async requestApproval(gadgetName, params) {
409
+ const mode = this.getApprovalMode(gadgetName);
410
+ if (mode === "allowed") {
411
+ return { approved: true };
412
+ }
413
+ if (mode === "denied") {
414
+ return {
415
+ approved: false,
416
+ reason: `${gadgetName} is denied by configuration`
417
+ };
418
+ }
419
+ return this.promptForApproval(gadgetName, params);
420
+ }
421
+ /**
422
+ * Prompts the user for approval interactively.
423
+ */
424
+ async promptForApproval(gadgetName, params) {
425
+ const provider = this.providers.get(gadgetName.toLowerCase()) ?? new DefaultContextProvider(gadgetName);
426
+ const context = await provider.getContext(params);
427
+ this.progress?.pause();
428
+ this.env.stderr.write(`
429
+ ${chalk2.yellow("\u{1F512} Approval required:")} ${context.summary}
430
+ `);
431
+ if (context.details) {
432
+ this.env.stderr.write(`
433
+ ${renderColoredDiff(context.details)}
434
+ `);
435
+ }
436
+ const response = await this.prompt(" \u23CE approve, or type to reject: ");
437
+ const isApproved = response === "" || response.toLowerCase() === "y";
438
+ if (isApproved) {
439
+ this.env.stderr.write(` ${chalk2.green("\u2713 Approved")}
440
+
441
+ `);
442
+ return { approved: true };
443
+ }
444
+ this.env.stderr.write(` ${chalk2.red("\u2717 Denied")}
445
+
446
+ `);
447
+ return { approved: false, reason: response || "Rejected by user" };
448
+ }
449
+ /**
450
+ * Prompts for user input.
451
+ */
452
+ async prompt(message) {
453
+ const rl = createInterface({
454
+ input: this.env.stdin,
455
+ output: this.env.stderr
456
+ });
457
+ try {
458
+ const answer = await rl.question(message);
459
+ return answer.trim();
460
+ } finally {
461
+ rl.close();
462
+ }
463
+ }
464
+ };
200
465
 
201
466
  // src/cli/builtin-gadgets.ts
202
467
  init_create_gadget();
@@ -278,10 +543,422 @@ var builtinGadgets = [askUser, tellUser, finish];
278
543
 
279
544
  // src/cli/gadgets.ts
280
545
  init_gadget();
546
+ import fs5 from "node:fs";
547
+ import path4 from "node:path";
548
+ import { pathToFileURL } from "node:url";
549
+
550
+ // src/cli/builtins/filesystem/list-directory.ts
551
+ import fs2 from "node:fs";
552
+ import path2 from "node:path";
553
+ import { z as z2 } from "zod";
554
+
555
+ // src/cli/builtins/filesystem/utils.ts
281
556
  import fs from "node:fs";
282
557
  import path from "node:path";
283
- import { pathToFileURL } from "node:url";
558
+ var PathSandboxException = class extends Error {
559
+ constructor(inputPath, reason) {
560
+ super(`Path access denied: ${inputPath}. ${reason}`);
561
+ this.name = "PathSandboxException";
562
+ }
563
+ };
564
+ function validatePathIsWithinCwd(inputPath) {
565
+ const cwd = process.cwd();
566
+ const resolvedPath = path.resolve(cwd, inputPath);
567
+ let finalPath;
568
+ try {
569
+ finalPath = fs.realpathSync(resolvedPath);
570
+ } catch (error) {
571
+ const nodeError = error;
572
+ if (nodeError.code === "ENOENT") {
573
+ finalPath = resolvedPath;
574
+ } else {
575
+ throw error;
576
+ }
577
+ }
578
+ const cwdWithSep = cwd + path.sep;
579
+ if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
580
+ throw new PathSandboxException(inputPath, "Path is outside the current working directory");
581
+ }
582
+ return finalPath;
583
+ }
584
+
585
+ // src/cli/builtins/filesystem/list-directory.ts
586
+ function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
587
+ const entries = [];
588
+ try {
589
+ const items = fs2.readdirSync(dirPath);
590
+ for (const item of items) {
591
+ const fullPath = path2.join(dirPath, item);
592
+ const relativePath = path2.relative(basePath, fullPath);
593
+ try {
594
+ const stats = fs2.lstatSync(fullPath);
595
+ let type;
596
+ let size;
597
+ if (stats.isSymbolicLink()) {
598
+ type = "symlink";
599
+ size = 0;
600
+ } else if (stats.isDirectory()) {
601
+ type = "directory";
602
+ size = 0;
603
+ } else {
604
+ type = "file";
605
+ size = stats.size;
606
+ }
607
+ entries.push({
608
+ name: item,
609
+ relativePath,
610
+ type,
611
+ size,
612
+ modified: Math.floor(stats.mtime.getTime() / 1e3)
613
+ });
614
+ if (type === "directory" && currentDepth < maxDepth) {
615
+ try {
616
+ validatePathIsWithinCwd(fullPath);
617
+ const subEntries = listFiles(fullPath, basePath, maxDepth, currentDepth + 1);
618
+ entries.push(...subEntries);
619
+ } catch {
620
+ }
621
+ }
622
+ } catch {
623
+ }
624
+ }
625
+ } catch {
626
+ return [];
627
+ }
628
+ return entries;
629
+ }
630
+ function formatAge(epochSeconds) {
631
+ const now = Math.floor(Date.now() / 1e3);
632
+ const seconds = now - epochSeconds;
633
+ if (seconds < 60) return `${seconds}s`;
634
+ const minutes = Math.floor(seconds / 60);
635
+ if (minutes < 60) return `${minutes}m`;
636
+ const hours = Math.floor(minutes / 60);
637
+ if (hours < 24) return `${hours}h`;
638
+ const days = Math.floor(hours / 24);
639
+ if (days < 7) return `${days}d`;
640
+ const weeks = Math.floor(days / 7);
641
+ if (weeks < 4) return `${weeks}w`;
642
+ const months = Math.floor(days / 30);
643
+ if (months < 12) return `${months}mo`;
644
+ const years = Math.floor(days / 365);
645
+ return `${years}y`;
646
+ }
647
+ function formatEntriesAsString(entries) {
648
+ if (entries.length === 0) {
649
+ return "#empty";
650
+ }
651
+ const sortedEntries = [...entries].sort((a, b) => {
652
+ const typeOrder = { directory: 0, file: 1, symlink: 2 };
653
+ const typeCompare = typeOrder[a.type] - typeOrder[b.type];
654
+ if (typeCompare !== 0) return typeCompare;
655
+ return a.relativePath.localeCompare(b.relativePath);
656
+ });
657
+ const typeCode = {
658
+ directory: "D",
659
+ file: "F",
660
+ symlink: "L"
661
+ };
662
+ const encodeName = (name) => name.replace(/\|/g, "%7C").replace(/\n/g, "%0A");
663
+ const header = "#T|N|S|A";
664
+ const rows = sortedEntries.map(
665
+ (e) => `${typeCode[e.type]}|${encodeName(e.relativePath)}|${e.size}|${formatAge(e.modified)}`
666
+ );
667
+ return [header, ...rows].join("\n");
668
+ }
669
+ var listDirectory = createGadget({
670
+ name: "ListDirectory",
671
+ 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.",
672
+ schema: z2.object({
673
+ directoryPath: z2.string().default(".").describe("Path to the directory to list"),
674
+ maxDepth: z2.number().int().min(1).max(10).default(1).describe(
675
+ "Maximum depth to recurse (1 = immediate children only, 2 = include grandchildren, etc.)"
676
+ )
677
+ }),
678
+ examples: [
679
+ {
680
+ params: { directoryPath: ".", maxDepth: 1 },
681
+ output: "path=. maxDepth=1\n\n#T|N|S|A\nD|src|0|2h\nD|tests|0|1d\nF|package.json|2841|3h",
682
+ comment: "List current directory"
683
+ },
684
+ {
685
+ params: { directoryPath: "src", maxDepth: 2 },
686
+ output: "path=src maxDepth=2\n\n#T|N|S|A\nD|components|0|1d\nD|utils|0|2d\nF|index.ts|512|1h\nF|components/Button.tsx|1024|3h",
687
+ comment: "List src directory recursively"
688
+ }
689
+ ],
690
+ execute: ({ directoryPath, maxDepth }) => {
691
+ const validatedPath = validatePathIsWithinCwd(directoryPath);
692
+ const stats = fs2.statSync(validatedPath);
693
+ if (!stats.isDirectory()) {
694
+ throw new Error(`Path is not a directory: ${directoryPath}`);
695
+ }
696
+ const entries = listFiles(validatedPath, validatedPath, maxDepth);
697
+ const formattedList = formatEntriesAsString(entries);
698
+ return `path=${directoryPath} maxDepth=${maxDepth}
699
+
700
+ ${formattedList}`;
701
+ }
702
+ });
703
+
704
+ // src/cli/builtins/filesystem/read-file.ts
705
+ import fs3 from "node:fs";
706
+ import { z as z3 } from "zod";
707
+ var readFile = createGadget({
708
+ name: "ReadFile",
709
+ 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.",
710
+ schema: z3.object({
711
+ filePath: z3.string().describe("Path to the file to read (relative or absolute)")
712
+ }),
713
+ examples: [
714
+ {
715
+ params: { filePath: "package.json" },
716
+ output: 'path=package.json\n\n{\n "name": "my-project",\n "version": "1.0.0"\n ...\n}',
717
+ comment: "Read a JSON config file"
718
+ },
719
+ {
720
+ params: { filePath: "src/index.ts" },
721
+ output: "path=src/index.ts\n\nexport function main() { ... }",
722
+ comment: "Read a source file"
723
+ }
724
+ ],
725
+ execute: ({ filePath }) => {
726
+ const validatedPath = validatePathIsWithinCwd(filePath);
727
+ const content = fs3.readFileSync(validatedPath, "utf-8");
728
+ return `path=${filePath}
729
+
730
+ ${content}`;
731
+ }
732
+ });
733
+
734
+ // src/cli/builtins/filesystem/write-file.ts
735
+ import fs4 from "node:fs";
736
+ import path3 from "node:path";
737
+ import { z as z4 } from "zod";
738
+ var writeFile = createGadget({
739
+ name: "WriteFile",
740
+ 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.",
741
+ schema: z4.object({
742
+ filePath: z4.string().describe("Path to the file to write (relative or absolute)"),
743
+ content: z4.string().describe("Content to write to the file")
744
+ }),
745
+ examples: [
746
+ {
747
+ params: { filePath: "output.txt", content: "Hello, World!" },
748
+ output: "path=output.txt\n\nWrote 13 bytes",
749
+ comment: "Write a simple text file"
750
+ },
751
+ {
752
+ params: {
753
+ filePath: "src/server.ts",
754
+ content: `import { serve } from "bun";
755
+
756
+ const port = 3000;
757
+
758
+ serve({
759
+ port,
760
+ fetch: (req) => new Response(\`Hello from \${req.url}\`),
761
+ });
762
+
763
+ console.log(\`Server running on http://localhost:\${port}\`);`
764
+ },
765
+ output: "path=src/server.ts\n\nWrote 198 bytes (created directory: src)",
766
+ comment: "Write code with template literals - NO escaping needed inside heredoc (use <<<EOF...EOF)"
767
+ }
768
+ ],
769
+ execute: ({ filePath, content }) => {
770
+ const validatedPath = validatePathIsWithinCwd(filePath);
771
+ const parentDir = path3.dirname(validatedPath);
772
+ let createdDir = false;
773
+ if (!fs4.existsSync(parentDir)) {
774
+ validatePathIsWithinCwd(parentDir);
775
+ fs4.mkdirSync(parentDir, { recursive: true });
776
+ createdDir = true;
777
+ }
778
+ fs4.writeFileSync(validatedPath, content, "utf-8");
779
+ const bytesWritten = Buffer.byteLength(content, "utf-8");
780
+ const dirNote = createdDir ? ` (created directory: ${path3.dirname(filePath)})` : "";
781
+ return `path=${filePath}
782
+
783
+ Wrote ${bytesWritten} bytes${dirNote}`;
784
+ }
785
+ });
786
+
787
+ // src/cli/builtins/filesystem/edit-file.ts
788
+ import { z as z5 } from "zod";
789
+ function filterDangerousCommands(commands) {
790
+ return commands.split("\n").filter((line) => !line.trimStart().startsWith("!")).join("\n");
791
+ }
792
+ var editFile = createGadget({
793
+ name: "EditFile",
794
+ description: "Edit a file using ed commands. Ed is a line-oriented text editor - pipe commands to it for precise file modifications. Commands are executed in sequence. Remember to end with 'w' (write) and 'q' (quit). Shell escape commands (!) are filtered for security.",
795
+ schema: z5.object({
796
+ filePath: z5.string().describe("Path to the file to edit (relative or absolute)"),
797
+ commands: z5.string().describe("Ed commands to execute, one per line")
798
+ }),
799
+ examples: [
800
+ {
801
+ params: {
802
+ filePath: "config.txt",
803
+ commands: `1,$p
804
+ q`
805
+ },
806
+ output: "path=config.txt\n\n32\nkey=value\noption=true",
807
+ comment: "Print entire file contents (ed shows byte count, then content)"
808
+ },
809
+ {
810
+ params: {
811
+ filePath: "data.txt",
812
+ commands: `1,$s/foo/bar/g
813
+ w
814
+ q`
815
+ },
816
+ output: "path=data.txt\n\n42\n42",
817
+ comment: "Replace all 'foo' with 'bar' (ed shows bytes read, then bytes written)"
818
+ },
819
+ {
820
+ params: {
821
+ filePath: "list.txt",
822
+ commands: `3d
823
+ w
824
+ q`
825
+ },
826
+ output: "path=list.txt\n\n45\n28",
827
+ comment: "Delete line 3, save and quit"
828
+ },
829
+ {
830
+ params: {
831
+ filePath: "readme.txt",
832
+ commands: `$a
833
+ New last line
834
+ .
835
+ w
836
+ q`
837
+ },
838
+ output: "path=readme.txt\n\n40\n56",
839
+ comment: "Append text after last line ($ = last line, . = end input mode)"
840
+ }
841
+ ],
842
+ timeoutMs: 3e4,
843
+ execute: async ({ filePath, commands }) => {
844
+ const validatedPath = validatePathIsWithinCwd(filePath);
845
+ const safeCommands = filterDangerousCommands(commands);
846
+ try {
847
+ const proc = Bun.spawn(["ed", validatedPath], {
848
+ stdin: "pipe",
849
+ stdout: "pipe",
850
+ stderr: "pipe"
851
+ });
852
+ proc.stdin.write(`${safeCommands}
853
+ `);
854
+ proc.stdin.end();
855
+ const timeoutPromise = new Promise((_, reject) => {
856
+ setTimeout(() => {
857
+ proc.kill();
858
+ reject(new Error("ed command timed out after 30000ms"));
859
+ }, 3e4);
860
+ });
861
+ const exitCode = await Promise.race([proc.exited, timeoutPromise]);
862
+ const stdout = await new Response(proc.stdout).text();
863
+ const stderr = await new Response(proc.stderr).text();
864
+ const output = [stdout, stderr].filter(Boolean).join("\n").trim();
865
+ if (exitCode !== 0) {
866
+ return `path=${filePath}
867
+
868
+ ${output || "ed exited with non-zero status"}`;
869
+ }
870
+ return `path=${filePath}
871
+
872
+ ${output || "(no output)"}`;
873
+ } catch (error) {
874
+ const message = error instanceof Error ? error.message : String(error);
875
+ return `path=${filePath}
876
+
877
+ error: ${message}`;
878
+ }
879
+ }
880
+ });
881
+
882
+ // src/cli/builtins/run-command.ts
883
+ import { z as z6 } from "zod";
884
+ var runCommand = createGadget({
885
+ name: "RunCommand",
886
+ description: "Execute a shell command and return its output. Returns both stdout and stderr combined with the exit status.",
887
+ schema: z6.object({
888
+ command: z6.string().describe("The shell command to execute"),
889
+ cwd: z6.string().optional().describe("Working directory for the command (default: current directory)"),
890
+ timeout: z6.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
891
+ }),
892
+ examples: [
893
+ {
894
+ params: { command: "ls -la", timeout: 3e4 },
895
+ output: "status=0\n\ntotal 24\ndrwxr-xr-x 5 user staff 160 Nov 27 10:00 .\ndrwxr-xr-x 3 user staff 96 Nov 27 09:00 ..\n-rw-r--r-- 1 user staff 1024 Nov 27 10:00 package.json",
896
+ comment: "List directory contents with details"
897
+ },
898
+ {
899
+ params: { command: "echo 'Hello World'", timeout: 3e4 },
900
+ output: "status=0\n\nHello World",
901
+ comment: "Simple echo command"
902
+ },
903
+ {
904
+ params: { command: "cat nonexistent.txt", timeout: 3e4 },
905
+ output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
906
+ comment: "Command that fails returns non-zero status"
907
+ },
908
+ {
909
+ params: { command: "pwd", cwd: "/tmp", timeout: 3e4 },
910
+ output: "status=0\n\n/tmp",
911
+ comment: "Execute command in a specific directory"
912
+ }
913
+ ],
914
+ execute: async ({ command, cwd, timeout }) => {
915
+ const workingDir = cwd ?? process.cwd();
916
+ try {
917
+ const proc = Bun.spawn(["sh", "-c", command], {
918
+ cwd: workingDir,
919
+ stdout: "pipe",
920
+ stderr: "pipe"
921
+ });
922
+ const timeoutPromise = new Promise((_, reject) => {
923
+ setTimeout(() => {
924
+ proc.kill();
925
+ reject(new Error(`Command timed out after ${timeout}ms`));
926
+ }, timeout);
927
+ });
928
+ const exitCode = await Promise.race([proc.exited, timeoutPromise]);
929
+ const stdout = await new Response(proc.stdout).text();
930
+ const stderr = await new Response(proc.stderr).text();
931
+ const output = [stdout, stderr].filter(Boolean).join("\n").trim();
932
+ return `status=${exitCode}
933
+
934
+ ${output || "(no output)"}`;
935
+ } catch (error) {
936
+ const message = error instanceof Error ? error.message : String(error);
937
+ return `status=1
938
+
939
+ error: ${message}`;
940
+ }
941
+ }
942
+ });
943
+
944
+ // src/cli/builtins/index.ts
945
+ var builtinGadgetRegistry = {
946
+ ListDirectory: listDirectory,
947
+ ReadFile: readFile,
948
+ WriteFile: writeFile,
949
+ EditFile: editFile,
950
+ RunCommand: runCommand
951
+ };
952
+ function getBuiltinGadget(name) {
953
+ return builtinGadgetRegistry[name];
954
+ }
955
+ function isBuiltinGadgetName(name) {
956
+ return name in builtinGadgetRegistry;
957
+ }
958
+
959
+ // src/cli/gadgets.ts
284
960
  var PATH_PREFIXES = [".", "/", "~"];
961
+ var BUILTIN_PREFIX = "builtin:";
285
962
  function isGadgetLike(value) {
286
963
  if (typeof value !== "object" || value === null) {
287
964
  return false;
@@ -304,18 +981,34 @@ function expandHomePath(input) {
304
981
  if (!home) {
305
982
  return input;
306
983
  }
307
- return path.join(home, input.slice(1));
984
+ return path4.join(home, input.slice(1));
308
985
  }
309
986
  function isFileLikeSpecifier(specifier) {
310
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path.sep);
987
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path4.sep);
988
+ }
989
+ function tryResolveBuiltin(specifier) {
990
+ if (specifier.startsWith(BUILTIN_PREFIX)) {
991
+ const name = specifier.slice(BUILTIN_PREFIX.length);
992
+ const gadget = getBuiltinGadget(name);
993
+ if (!gadget) {
994
+ throw new Error(
995
+ `Unknown builtin gadget: ${name}. Available builtins: ListDirectory, ReadFile, WriteFile, EditFile, RunCommand`
996
+ );
997
+ }
998
+ return gadget;
999
+ }
1000
+ if (!isFileLikeSpecifier(specifier) && isBuiltinGadgetName(specifier)) {
1001
+ return getBuiltinGadget(specifier);
1002
+ }
1003
+ return null;
311
1004
  }
312
1005
  function resolveGadgetSpecifier(specifier, cwd) {
313
1006
  if (!isFileLikeSpecifier(specifier)) {
314
1007
  return specifier;
315
1008
  }
316
1009
  const expanded = expandHomePath(specifier);
317
- const resolvedPath = path.resolve(cwd, expanded);
318
- if (!fs.existsSync(resolvedPath)) {
1010
+ const resolvedPath = path4.resolve(cwd, expanded);
1011
+ if (!fs5.existsSync(resolvedPath)) {
319
1012
  throw new Error(`Gadget module not found at ${resolvedPath}`);
320
1013
  }
321
1014
  return pathToFileURL(resolvedPath).href;
@@ -357,6 +1050,11 @@ function extractGadgetsFromModule(moduleExports) {
357
1050
  async function loadGadgets(specifiers, cwd, importer = (specifier) => import(specifier)) {
358
1051
  const gadgets = [];
359
1052
  for (const specifier of specifiers) {
1053
+ const builtin = tryResolveBuiltin(specifier);
1054
+ if (builtin) {
1055
+ gadgets.push(builtin);
1056
+ continue;
1057
+ }
360
1058
  const resolved = resolveGadgetSpecifier(specifier, cwd);
361
1059
  let exports;
362
1060
  try {
@@ -381,7 +1079,7 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
381
1079
  }
382
1080
 
383
1081
  // src/cli/llm-logging.ts
384
- import { mkdir, writeFile } from "node:fs/promises";
1082
+ import { mkdir, writeFile as writeFile2 } from "node:fs/promises";
385
1083
  import { homedir } from "node:os";
386
1084
  import { join } from "node:path";
387
1085
  var DEFAULT_LLM_LOG_DIR = join(homedir(), ".llmist", "logs");
@@ -405,43 +1103,43 @@ function formatLlmRequest(messages) {
405
1103
  }
406
1104
  async function writeLogFile(dir, filename, content) {
407
1105
  await mkdir(dir, { recursive: true });
408
- await writeFile(join(dir, filename), content, "utf-8");
1106
+ await writeFile2(join(dir, filename), content, "utf-8");
409
1107
  }
410
1108
 
411
1109
  // src/cli/utils.ts
412
1110
  init_constants();
413
- import chalk2 from "chalk";
1111
+ import chalk4 from "chalk";
414
1112
  import { InvalidArgumentError } from "commander";
415
1113
 
416
1114
  // src/cli/ui/formatters.ts
417
- import chalk from "chalk";
1115
+ import chalk3 from "chalk";
418
1116
  import { marked } from "marked";
419
1117
  import { markedTerminal } from "marked-terminal";
420
1118
  var markedConfigured = false;
421
1119
  function ensureMarkedConfigured() {
422
1120
  if (!markedConfigured) {
423
- chalk.level = process.env.NO_COLOR ? 0 : 3;
1121
+ chalk3.level = process.env.NO_COLOR ? 0 : 3;
424
1122
  marked.use(
425
1123
  markedTerminal({
426
1124
  // Text styling
427
- strong: chalk.bold,
428
- em: chalk.italic,
429
- del: chalk.dim.gray.strikethrough,
1125
+ strong: chalk3.bold,
1126
+ em: chalk3.italic,
1127
+ del: chalk3.dim.gray.strikethrough,
430
1128
  // Code styling
431
- code: chalk.yellow,
432
- codespan: chalk.yellow,
1129
+ code: chalk3.yellow,
1130
+ codespan: chalk3.yellow,
433
1131
  // Headings
434
- heading: chalk.green.bold,
435
- firstHeading: chalk.magenta.underline.bold,
1132
+ heading: chalk3.green.bold,
1133
+ firstHeading: chalk3.magenta.underline.bold,
436
1134
  // Links
437
- link: chalk.blue,
438
- href: chalk.blue.underline,
1135
+ link: chalk3.blue,
1136
+ href: chalk3.blue.underline,
439
1137
  // Block elements
440
- blockquote: chalk.gray.italic,
1138
+ blockquote: chalk3.gray.italic,
441
1139
  // List formatting - reduce indentation and add bullet styling
442
1140
  tab: 2,
443
1141
  // Reduce from default 4 to 2 spaces
444
- listitem: chalk.reset
1142
+ listitem: chalk3.reset
445
1143
  // Keep items readable (no dim)
446
1144
  })
447
1145
  );
@@ -451,11 +1149,11 @@ function ensureMarkedConfigured() {
451
1149
  function renderMarkdown(text) {
452
1150
  ensureMarkedConfigured();
453
1151
  let rendered = marked.parse(text);
454
- rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk.bold(content)).replace(/(?<!\*)\*(\S[^*]*)\*(?!\*)/g, (_, content) => chalk.italic(content));
1152
+ rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk3.bold(content)).replace(/(?<!\*)\*(\S[^*]*)\*(?!\*)/g, (_, content) => chalk3.italic(content));
455
1153
  return rendered.trimEnd();
456
1154
  }
457
1155
  function createRainbowSeparator() {
458
- const colors = [chalk.red, chalk.yellow, chalk.green, chalk.cyan, chalk.blue, chalk.magenta];
1156
+ const colors = [chalk3.red, chalk3.yellow, chalk3.green, chalk3.cyan, chalk3.blue, chalk3.magenta];
459
1157
  const char = "\u2500";
460
1158
  const width = process.stdout.columns || 80;
461
1159
  let result = "";
@@ -491,58 +1189,58 @@ function formatCost(cost) {
491
1189
  function renderSummary(metadata) {
492
1190
  const parts = [];
493
1191
  if (metadata.iterations !== void 0) {
494
- const iterPart = chalk.cyan(`#${metadata.iterations}`);
1192
+ const iterPart = chalk3.cyan(`#${metadata.iterations}`);
495
1193
  if (metadata.model) {
496
- parts.push(`${iterPart} ${chalk.magenta(metadata.model)}`);
1194
+ parts.push(`${iterPart} ${chalk3.magenta(metadata.model)}`);
497
1195
  } else {
498
1196
  parts.push(iterPart);
499
1197
  }
500
1198
  } else if (metadata.model) {
501
- parts.push(chalk.magenta(metadata.model));
1199
+ parts.push(chalk3.magenta(metadata.model));
502
1200
  }
503
1201
  if (metadata.usage) {
504
1202
  const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
505
- parts.push(chalk.dim("\u2191") + chalk.yellow(` ${formatTokens(inputTokens)}`));
1203
+ parts.push(chalk3.dim("\u2191") + chalk3.yellow(` ${formatTokens(inputTokens)}`));
506
1204
  if (cachedInputTokens && cachedInputTokens > 0) {
507
- parts.push(chalk.dim("\u27F3") + chalk.blue(` ${formatTokens(cachedInputTokens)}`));
1205
+ parts.push(chalk3.dim("\u27F3") + chalk3.blue(` ${formatTokens(cachedInputTokens)}`));
508
1206
  }
509
1207
  if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
510
- parts.push(chalk.dim("\u270E") + chalk.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
1208
+ parts.push(chalk3.dim("\u270E") + chalk3.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
511
1209
  }
512
- parts.push(chalk.dim("\u2193") + chalk.green(` ${formatTokens(outputTokens)}`));
1210
+ parts.push(chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(outputTokens)}`));
513
1211
  }
514
1212
  if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
515
- parts.push(chalk.dim(`${metadata.elapsedSeconds}s`));
1213
+ parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
516
1214
  }
517
1215
  if (metadata.cost !== void 0 && metadata.cost > 0) {
518
- parts.push(chalk.cyan(`$${formatCost(metadata.cost)}`));
1216
+ parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
519
1217
  }
520
1218
  if (metadata.finishReason) {
521
- parts.push(chalk.dim(metadata.finishReason));
1219
+ parts.push(chalk3.dim(metadata.finishReason));
522
1220
  }
523
1221
  if (parts.length === 0) {
524
1222
  return null;
525
1223
  }
526
- return parts.join(chalk.dim(" | "));
1224
+ return parts.join(chalk3.dim(" | "));
527
1225
  }
528
1226
  function renderOverallSummary(metadata) {
529
1227
  const parts = [];
530
1228
  if (metadata.totalTokens !== void 0 && metadata.totalTokens > 0) {
531
- parts.push(chalk.dim("total:") + chalk.magenta(` ${formatTokens(metadata.totalTokens)}`));
1229
+ parts.push(chalk3.dim("total:") + chalk3.magenta(` ${formatTokens(metadata.totalTokens)}`));
532
1230
  }
533
1231
  if (metadata.iterations !== void 0 && metadata.iterations > 0) {
534
- parts.push(chalk.cyan(`#${metadata.iterations}`));
1232
+ parts.push(chalk3.cyan(`#${metadata.iterations}`));
535
1233
  }
536
1234
  if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
537
- parts.push(chalk.dim(`${metadata.elapsedSeconds}s`));
1235
+ parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
538
1236
  }
539
1237
  if (metadata.cost !== void 0 && metadata.cost > 0) {
540
- parts.push(chalk.cyan(`$${formatCost(metadata.cost)}`));
1238
+ parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
541
1239
  }
542
1240
  if (parts.length === 0) {
543
1241
  return null;
544
1242
  }
545
- return parts.join(chalk.dim(" | "));
1243
+ return parts.join(chalk3.dim(" | "));
546
1244
  }
547
1245
  function formatParametersInline(params) {
548
1246
  if (!params || Object.keys(params).length === 0) {
@@ -558,8 +1256,8 @@ function formatParametersInline(params) {
558
1256
  const json = JSON.stringify(value);
559
1257
  formatted = json.length > 30 ? `${json.slice(0, 30)}\u2026` : json;
560
1258
  }
561
- return `${chalk.dim(key)}${chalk.dim("=")}${chalk.cyan(formatted)}`;
562
- }).join(chalk.dim(", "));
1259
+ return `${chalk3.dim(key)}${chalk3.dim("=")}${chalk3.cyan(formatted)}`;
1260
+ }).join(chalk3.dim(", "));
563
1261
  }
564
1262
  function formatBytes(bytes) {
565
1263
  if (bytes < 1024) {
@@ -571,25 +1269,25 @@ function formatBytes(bytes) {
571
1269
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
572
1270
  }
573
1271
  function formatGadgetSummary(result) {
574
- const gadgetLabel = chalk.magenta.bold(result.gadgetName);
575
- const timeLabel = chalk.dim(`${Math.round(result.executionTimeMs)}ms`);
1272
+ const gadgetLabel = chalk3.magenta.bold(result.gadgetName);
1273
+ const timeLabel = chalk3.dim(`${Math.round(result.executionTimeMs)}ms`);
576
1274
  const paramsStr = formatParametersInline(result.parameters);
577
- const paramsLabel = paramsStr ? `${chalk.dim("(")}${paramsStr}${chalk.dim(")")}` : "";
1275
+ const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
578
1276
  if (result.error) {
579
1277
  const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
580
- return `${chalk.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk.red("error:")} ${errorMsg} ${timeLabel}`;
1278
+ return `${chalk3.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk3.red("error:")} ${errorMsg} ${timeLabel}`;
581
1279
  }
582
1280
  let outputLabel;
583
1281
  if (result.tokenCount !== void 0 && result.tokenCount > 0) {
584
- outputLabel = chalk.green(`${formatTokens(result.tokenCount)} tokens`);
1282
+ outputLabel = chalk3.green(`${formatTokens(result.tokenCount)} tokens`);
585
1283
  } else if (result.result) {
586
1284
  const outputBytes = Buffer.byteLength(result.result, "utf-8");
587
- outputLabel = outputBytes > 0 ? chalk.green(formatBytes(outputBytes)) : chalk.dim("no output");
1285
+ outputLabel = outputBytes > 0 ? chalk3.green(formatBytes(outputBytes)) : chalk3.dim("no output");
588
1286
  } else {
589
- outputLabel = chalk.dim("no output");
1287
+ outputLabel = chalk3.dim("no output");
590
1288
  }
591
- const icon = result.breaksLoop ? chalk.yellow("\u23F9") : chalk.green("\u2713");
592
- const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk.dim("\u2192")} ${outputLabel} ${timeLabel}`;
1289
+ const icon = result.breaksLoop ? chalk3.yellow("\u23F9") : chalk3.green("\u2713");
1290
+ const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk3.dim("\u2192")} ${outputLabel} ${timeLabel}`;
593
1291
  if (result.gadgetName === "TellUser" && result.parameters?.message) {
594
1292
  const message = String(result.parameters.message);
595
1293
  const rendered = renderMarkdownWithSeparators(message);
@@ -653,6 +1351,66 @@ var StreamPrinter = class {
653
1351
  function isInteractive(stream) {
654
1352
  return Boolean(stream.isTTY);
655
1353
  }
1354
+ var ESC_KEY = 27;
1355
+ var ESC_TIMEOUT_MS = 50;
1356
+ function createEscKeyListener(stdin, onEsc) {
1357
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
1358
+ return null;
1359
+ }
1360
+ let escTimeout = null;
1361
+ const handleData = (data) => {
1362
+ if (data[0] === ESC_KEY) {
1363
+ if (data.length === 1) {
1364
+ escTimeout = setTimeout(() => {
1365
+ onEsc();
1366
+ }, ESC_TIMEOUT_MS);
1367
+ } else {
1368
+ if (escTimeout) {
1369
+ clearTimeout(escTimeout);
1370
+ escTimeout = null;
1371
+ }
1372
+ }
1373
+ } else {
1374
+ if (escTimeout) {
1375
+ clearTimeout(escTimeout);
1376
+ escTimeout = null;
1377
+ }
1378
+ }
1379
+ };
1380
+ stdin.setRawMode(true);
1381
+ stdin.resume();
1382
+ stdin.on("data", handleData);
1383
+ return () => {
1384
+ if (escTimeout) {
1385
+ clearTimeout(escTimeout);
1386
+ }
1387
+ stdin.removeListener("data", handleData);
1388
+ stdin.setRawMode(false);
1389
+ stdin.pause();
1390
+ };
1391
+ }
1392
+ var SIGINT_DOUBLE_PRESS_MS = 1e3;
1393
+ function createSigintListener(onCancel, onQuit, isOperationActive, stderr = process.stderr) {
1394
+ let lastSigintTime = 0;
1395
+ const handler = () => {
1396
+ const now = Date.now();
1397
+ if (isOperationActive()) {
1398
+ onCancel();
1399
+ lastSigintTime = now;
1400
+ return;
1401
+ }
1402
+ if (now - lastSigintTime < SIGINT_DOUBLE_PRESS_MS) {
1403
+ onQuit();
1404
+ return;
1405
+ }
1406
+ lastSigintTime = now;
1407
+ stderr.write(chalk4.dim("\n[Press Ctrl+C again to quit]\n"));
1408
+ };
1409
+ process.on("SIGINT", handler);
1410
+ return () => {
1411
+ process.removeListener("SIGINT", handler);
1412
+ };
1413
+ }
656
1414
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
657
1415
  var SPINNER_DELAY_MS = 500;
658
1416
  var StreamProgress = class {
@@ -824,9 +1582,9 @@ var StreamProgress = class {
824
1582
  const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
825
1583
  const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
826
1584
  const parts = [];
827
- const iterPart = chalk2.cyan(`#${this.currentIteration}`);
1585
+ const iterPart = chalk4.cyan(`#${this.currentIteration}`);
828
1586
  if (this.model) {
829
- parts.push(`${iterPart} ${chalk2.magenta(this.model)}`);
1587
+ parts.push(`${iterPart} ${chalk4.magenta(this.model)}`);
830
1588
  } else {
831
1589
  parts.push(iterPart);
832
1590
  }
@@ -834,27 +1592,27 @@ var StreamProgress = class {
834
1592
  if (usagePercent !== null) {
835
1593
  const formatted = `${Math.round(usagePercent)}%`;
836
1594
  if (usagePercent >= 80) {
837
- parts.push(chalk2.red(formatted));
1595
+ parts.push(chalk4.red(formatted));
838
1596
  } else if (usagePercent >= 50) {
839
- parts.push(chalk2.yellow(formatted));
1597
+ parts.push(chalk4.yellow(formatted));
840
1598
  } else {
841
- parts.push(chalk2.green(formatted));
1599
+ parts.push(chalk4.green(formatted));
842
1600
  }
843
1601
  }
844
1602
  if (this.callInputTokens > 0) {
845
1603
  const prefix = this.callInputTokensEstimated ? "~" : "";
846
- parts.push(chalk2.dim("\u2191") + chalk2.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
1604
+ parts.push(chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
847
1605
  }
848
1606
  if (this.isStreaming || outTokens > 0) {
849
1607
  const prefix = this.callOutputTokensEstimated ? "~" : "";
850
- parts.push(chalk2.dim("\u2193") + chalk2.green(` ${prefix}${formatTokens(outTokens)}`));
1608
+ parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
851
1609
  }
852
- parts.push(chalk2.dim(`${elapsed}s`));
1610
+ parts.push(chalk4.dim(`${elapsed}s`));
853
1611
  const callCost = this.calculateCurrentCallCost(outTokens);
854
1612
  if (callCost > 0) {
855
- parts.push(chalk2.cyan(`$${formatCost(callCost)}`));
1613
+ parts.push(chalk4.cyan(`$${formatCost(callCost)}`));
856
1614
  }
857
- this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
1615
+ this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
858
1616
  }
859
1617
  /**
860
1618
  * Calculates live cost estimate for the current streaming call.
@@ -895,19 +1653,19 @@ var StreamProgress = class {
895
1653
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
896
1654
  const parts = [];
897
1655
  if (this.model) {
898
- parts.push(chalk2.cyan(this.model));
1656
+ parts.push(chalk4.cyan(this.model));
899
1657
  }
900
1658
  if (this.totalTokens > 0) {
901
- parts.push(chalk2.dim("total:") + chalk2.magenta(` ${this.totalTokens}`));
1659
+ parts.push(chalk4.dim("total:") + chalk4.magenta(` ${this.totalTokens}`));
902
1660
  }
903
1661
  if (this.iterations > 0) {
904
- parts.push(chalk2.dim("iter:") + chalk2.blue(` ${this.iterations}`));
1662
+ parts.push(chalk4.dim("iter:") + chalk4.blue(` ${this.iterations}`));
905
1663
  }
906
1664
  if (this.totalCost > 0) {
907
- parts.push(chalk2.dim("cost:") + chalk2.cyan(` $${formatCost(this.totalCost)}`));
1665
+ parts.push(chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.totalCost)}`));
908
1666
  }
909
- parts.push(chalk2.dim(`${elapsed}s`));
910
- this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
1667
+ parts.push(chalk4.dim(`${elapsed}s`));
1668
+ this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
911
1669
  }
912
1670
  /**
913
1671
  * Pauses the progress indicator and clears the line.
@@ -941,6 +1699,25 @@ var StreamProgress = class {
941
1699
  getTotalCost() {
942
1700
  return this.totalCost;
943
1701
  }
1702
+ /**
1703
+ * Returns a formatted stats string for cancellation messages.
1704
+ * Format: "↑ 1.2k | ↓ 300 | 5.0s"
1705
+ */
1706
+ formatStats() {
1707
+ const parts = [];
1708
+ const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
1709
+ const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
1710
+ if (this.callInputTokens > 0) {
1711
+ const prefix = this.callInputTokensEstimated ? "~" : "";
1712
+ parts.push(`\u2191 ${prefix}${formatTokens(this.callInputTokens)}`);
1713
+ }
1714
+ if (outTokens > 0) {
1715
+ const prefix = this.callOutputTokensEstimated ? "~" : "";
1716
+ parts.push(`\u2193 ${prefix}${formatTokens(outTokens)}`);
1717
+ }
1718
+ parts.push(`${elapsed}s`);
1719
+ return parts.join(" | ");
1720
+ }
944
1721
  /**
945
1722
  * Returns a formatted prompt string with stats (like bash PS1).
946
1723
  * Shows current call stats during streaming, cumulative stats otherwise.
@@ -955,28 +1732,28 @@ var StreamProgress = class {
955
1732
  if (this.callInputTokens > 0) {
956
1733
  const prefix = this.callInputTokensEstimated ? "~" : "";
957
1734
  parts.push(
958
- chalk2.dim("\u2191") + chalk2.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`)
1735
+ chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`)
959
1736
  );
960
1737
  }
961
1738
  if (outTokens > 0) {
962
1739
  const prefix = outEstimated ? "~" : "";
963
- parts.push(chalk2.dim("\u2193") + chalk2.green(` ${prefix}${formatTokens(outTokens)}`));
1740
+ parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
964
1741
  }
965
- parts.push(chalk2.dim(`${elapsed}s`));
1742
+ parts.push(chalk4.dim(`${elapsed}s`));
966
1743
  } else {
967
1744
  const elapsed = Math.round((Date.now() - this.totalStartTime) / 1e3);
968
1745
  if (this.totalTokens > 0) {
969
- parts.push(chalk2.magenta(formatTokens(this.totalTokens)));
1746
+ parts.push(chalk4.magenta(formatTokens(this.totalTokens)));
970
1747
  }
971
1748
  if (this.iterations > 0) {
972
- parts.push(chalk2.blue(`i${this.iterations}`));
1749
+ parts.push(chalk4.blue(`i${this.iterations}`));
973
1750
  }
974
1751
  if (this.totalCost > 0) {
975
- parts.push(chalk2.cyan(`$${formatCost(this.totalCost)}`));
1752
+ parts.push(chalk4.cyan(`$${formatCost(this.totalCost)}`));
976
1753
  }
977
- parts.push(chalk2.dim(`${elapsed}s`));
1754
+ parts.push(chalk4.dim(`${elapsed}s`));
978
1755
  }
979
- return `${parts.join(chalk2.dim(" | "))} ${chalk2.green(">")} `;
1756
+ return `${parts.join(chalk4.dim(" | "))} ${chalk4.green(">")} `;
980
1757
  }
981
1758
  };
982
1759
  async function readStream(stream) {
@@ -1011,7 +1788,7 @@ async function executeAction(action, env) {
1011
1788
  await action();
1012
1789
  } catch (error) {
1013
1790
  const message = error instanceof Error ? error.message : String(error);
1014
- env.stderr.write(`${chalk2.red.bold("Error:")} ${message}
1791
+ env.stderr.write(`${chalk4.red.bold("Error:")} ${message}
1015
1792
  `);
1016
1793
  env.setExitCode(1);
1017
1794
  }
@@ -1036,7 +1813,7 @@ function addAgentOptions(cmd, defaults) {
1036
1813
  ...previous,
1037
1814
  value
1038
1815
  ];
1039
- const defaultGadgets = defaults?.gadget ?? [];
1816
+ const defaultGadgets = defaults?.gadgets ?? defaults?.gadget ?? [];
1040
1817
  return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
1041
1818
  OPTION_FLAGS.temperature,
1042
1819
  OPTION_DESCRIPTIONS.temperature,
@@ -1072,7 +1849,8 @@ function configToAgentOptions(config) {
1072
1849
  if (config.system !== void 0) result.system = config.system;
1073
1850
  if (config.temperature !== void 0) result.temperature = config.temperature;
1074
1851
  if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
1075
- if (config.gadget !== void 0) result.gadget = config.gadget;
1852
+ const gadgets = config.gadgets ?? config.gadget;
1853
+ if (gadgets !== void 0) result.gadget = gadgets;
1076
1854
  if (config.builtins !== void 0) result.builtins = config.builtins;
1077
1855
  if (config["builtin-interaction"] !== void 0)
1078
1856
  result.builtinInteraction = config["builtin-interaction"];
@@ -1082,6 +1860,8 @@ function configToAgentOptions(config) {
1082
1860
  result.gadgetEndPrefix = config["gadget-end-prefix"];
1083
1861
  if (config["gadget-arg-prefix"] !== void 0)
1084
1862
  result.gadgetArgPrefix = config["gadget-arg-prefix"];
1863
+ if (config["gadget-approval"] !== void 0)
1864
+ result.gadgetApproval = config["gadget-approval"];
1085
1865
  if (config.quiet !== void 0) result.quiet = config.quiet;
1086
1866
  if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
1087
1867
  if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
@@ -1089,23 +1869,18 @@ function configToAgentOptions(config) {
1089
1869
  }
1090
1870
 
1091
1871
  // src/cli/agent-command.ts
1092
- async function promptApproval(env, prompt) {
1093
- const rl = createInterface({ input: env.stdin, output: env.stderr });
1094
- try {
1095
- const answer = await rl.question(prompt);
1096
- return answer.trim();
1097
- } finally {
1098
- rl.close();
1099
- }
1100
- }
1101
- function createHumanInputHandler(env, progress) {
1872
+ function createHumanInputHandler(env, progress, keyboard) {
1102
1873
  const stdout = env.stdout;
1103
1874
  if (!isInteractive(env.stdin) || typeof stdout.isTTY !== "boolean" || !stdout.isTTY) {
1104
1875
  return void 0;
1105
1876
  }
1106
1877
  return async (question) => {
1107
1878
  progress.pause();
1108
- const rl = createInterface({ input: env.stdin, output: env.stdout });
1879
+ if (keyboard.cleanupEsc) {
1880
+ keyboard.cleanupEsc();
1881
+ keyboard.cleanupEsc = null;
1882
+ }
1883
+ const rl = createInterface2({ input: env.stdin, output: env.stdout });
1109
1884
  try {
1110
1885
  const questionLine = question.trim() ? `
1111
1886
  ${renderMarkdownWithSeparators(question.trim())}` : "";
@@ -1123,6 +1898,7 @@ ${statsPrompt}` : statsPrompt;
1123
1898
  }
1124
1899
  } finally {
1125
1900
  rl.close();
1901
+ keyboard.restore();
1126
1902
  }
1127
1903
  };
1128
1904
  }
@@ -1149,6 +1925,77 @@ async function executeAgent(promptArg, options, env) {
1149
1925
  const printer = new StreamPrinter(env.stdout);
1150
1926
  const stderrTTY = env.stderr.isTTY === true;
1151
1927
  const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
1928
+ const abortController = new AbortController();
1929
+ let wasCancelled = false;
1930
+ let isStreaming = false;
1931
+ const stdinStream = env.stdin;
1932
+ const handleCancel = () => {
1933
+ if (!abortController.signal.aborted) {
1934
+ wasCancelled = true;
1935
+ abortController.abort();
1936
+ progress.pause();
1937
+ env.stderr.write(chalk5.yellow(`
1938
+ [Cancelled] ${progress.formatStats()}
1939
+ `));
1940
+ }
1941
+ };
1942
+ const keyboard = {
1943
+ cleanupEsc: null,
1944
+ cleanupSigint: null,
1945
+ restore: () => {
1946
+ if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
1947
+ keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
1948
+ }
1949
+ }
1950
+ };
1951
+ const handleQuit = () => {
1952
+ keyboard.cleanupEsc?.();
1953
+ keyboard.cleanupSigint?.();
1954
+ progress.complete();
1955
+ printer.ensureNewline();
1956
+ const summary = renderOverallSummary({
1957
+ totalTokens: usage?.totalTokens,
1958
+ iterations,
1959
+ elapsedSeconds: progress.getTotalElapsedSeconds(),
1960
+ cost: progress.getTotalCost()
1961
+ });
1962
+ if (summary) {
1963
+ env.stderr.write(`${chalk5.dim("\u2500".repeat(40))}
1964
+ `);
1965
+ env.stderr.write(`${summary}
1966
+ `);
1967
+ }
1968
+ env.stderr.write(chalk5.dim("[Quit]\n"));
1969
+ process.exit(130);
1970
+ };
1971
+ if (stdinIsInteractive && stdinStream.isTTY) {
1972
+ keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
1973
+ }
1974
+ keyboard.cleanupSigint = createSigintListener(
1975
+ handleCancel,
1976
+ handleQuit,
1977
+ () => isStreaming && !abortController.signal.aborted,
1978
+ env.stderr
1979
+ );
1980
+ const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile"];
1981
+ const userApprovals = options.gadgetApproval ?? {};
1982
+ const gadgetApprovals = {
1983
+ ...userApprovals
1984
+ };
1985
+ for (const gadget of DEFAULT_APPROVAL_REQUIRED) {
1986
+ const normalizedGadget = gadget.toLowerCase();
1987
+ const isConfigured = Object.keys(userApprovals).some(
1988
+ (key) => key.toLowerCase() === normalizedGadget
1989
+ );
1990
+ if (!isConfigured) {
1991
+ gadgetApprovals[gadget] = "approval-required";
1992
+ }
1993
+ }
1994
+ const approvalConfig = {
1995
+ gadgetApprovals,
1996
+ defaultMode: "allowed"
1997
+ };
1998
+ const approvalManager = new ApprovalManager(approvalConfig, env, progress);
1152
1999
  let usage;
1153
2000
  let iterations = 0;
1154
2001
  const llmRequestsDir = resolveLogDir(options.logLlmRequests, "requests");
@@ -1176,6 +2023,7 @@ async function executeAgent(promptArg, options, env) {
1176
2023
  // onLLMCallStart: Start progress indicator for each LLM call
1177
2024
  // This showcases how to react to agent lifecycle events
1178
2025
  onLLMCallStart: async (context) => {
2026
+ isStreaming = true;
1179
2027
  llmCallCounter++;
1180
2028
  const inputTokens = await countMessagesTokens(
1181
2029
  context.options.model,
@@ -1209,6 +2057,7 @@ async function executeAgent(promptArg, options, env) {
1209
2057
  // onLLMCallComplete: Finalize metrics after each LLM call
1210
2058
  // This is where you'd typically log metrics or update dashboards
1211
2059
  onLLMCallComplete: async (context) => {
2060
+ isStreaming = false;
1212
2061
  usage = context.usage;
1213
2062
  iterations = Math.max(iterations, context.iteration + 1);
1214
2063
  if (context.usage) {
@@ -1256,47 +2105,53 @@ async function executeAgent(promptArg, options, env) {
1256
2105
  }
1257
2106
  }
1258
2107
  },
1259
- // SHOWCASE: Controller-based approval gating for dangerous gadgets
2108
+ // SHOWCASE: Controller-based approval gating for gadgets
1260
2109
  //
1261
2110
  // This demonstrates how to add safety layers WITHOUT modifying gadgets.
1262
- // The RunCommand gadget is simple - it just executes commands. The CLI
1263
- // adds the approval flow externally via beforeGadgetExecution controller.
2111
+ // The ApprovalManager handles approval flows externally via beforeGadgetExecution.
2112
+ // Approval modes are configurable via cli.toml:
2113
+ // - "allowed": auto-proceed
2114
+ // - "denied": auto-reject, return message to LLM
2115
+ // - "approval-required": prompt user interactively
1264
2116
  //
1265
- // This pattern is composable: you can apply the same gating logic to
1266
- // any gadget (DeleteFile, SendEmail, etc.) without changing the gadgets.
2117
+ // Default: RunCommand, WriteFile, EditFile require approval unless overridden.
1267
2118
  controllers: {
1268
2119
  beforeGadgetExecution: async (ctx) => {
1269
- if (ctx.gadgetName !== "RunCommand") {
2120
+ const mode = approvalManager.getApprovalMode(ctx.gadgetName);
2121
+ if (mode === "allowed") {
1270
2122
  return { action: "proceed" };
1271
2123
  }
1272
2124
  const stdinTTY = isInteractive(env.stdin);
1273
2125
  const stderrTTY2 = env.stderr.isTTY === true;
1274
- if (!stdinTTY || !stderrTTY2) {
1275
- return {
1276
- action: "skip",
1277
- syntheticResult: "status=denied\n\nRunCommand requires interactive approval. Run in a terminal to approve commands."
1278
- };
1279
- }
1280
- const command = ctx.parameters.command;
1281
- progress.pause();
1282
- env.stderr.write(`
1283
- \u{1F512} Execute: ${chalk3.cyan(command)}
1284
- `);
1285
- const response = await promptApproval(env, " \u23CE approve, or type to reject: ");
1286
- const isApproved = response === "" || response.toLowerCase() === "y";
1287
- if (!isApproved) {
1288
- env.stderr.write(` ${chalk3.red("\u2717 Denied")}
2126
+ const canPrompt = stdinTTY && stderrTTY2;
2127
+ if (!canPrompt) {
2128
+ if (mode === "approval-required") {
2129
+ return {
2130
+ action: "skip",
2131
+ syntheticResult: `status=denied
1289
2132
 
1290
- `);
2133
+ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
2134
+ };
2135
+ }
2136
+ if (mode === "denied") {
2137
+ return {
2138
+ action: "skip",
2139
+ syntheticResult: `status=denied
2140
+
2141
+ ${ctx.gadgetName} is denied by configuration.`
2142
+ };
2143
+ }
2144
+ return { action: "proceed" };
2145
+ }
2146
+ const result = await approvalManager.requestApproval(ctx.gadgetName, ctx.parameters);
2147
+ if (!result.approved) {
1291
2148
  return {
1292
2149
  action: "skip",
1293
2150
  syntheticResult: `status=denied
1294
2151
 
1295
- Command rejected by user with message: "${response}"`
2152
+ Denied: ${result.reason ?? "by user"}`
1296
2153
  };
1297
2154
  }
1298
- env.stderr.write(` ${chalk3.green("\u2713 Approved")}
1299
- `);
1300
2155
  return { action: "proceed" };
1301
2156
  }
1302
2157
  }
@@ -1310,10 +2165,11 @@ Command rejected by user with message: "${response}"`
1310
2165
  if (options.temperature !== void 0) {
1311
2166
  builder.withTemperature(options.temperature);
1312
2167
  }
1313
- const humanInputHandler = createHumanInputHandler(env, progress);
2168
+ const humanInputHandler = createHumanInputHandler(env, progress, keyboard);
1314
2169
  if (humanInputHandler) {
1315
2170
  builder.onHumanInput(humanInputHandler);
1316
2171
  }
2172
+ builder.withSignal(abortController.signal);
1317
2173
  const gadgets = registry.getAll();
1318
2174
  if (gadgets.length > 0) {
1319
2175
  builder.withGadgets(...gadgets);
@@ -1351,31 +2207,41 @@ Command rejected by user with message: "${response}"`
1351
2207
  textBuffer = "";
1352
2208
  }
1353
2209
  };
1354
- for await (const event of agent.run()) {
1355
- if (event.type === "text") {
1356
- progress.pause();
1357
- textBuffer += event.content;
1358
- } else if (event.type === "gadget_result") {
1359
- flushTextBuffer();
1360
- progress.pause();
1361
- if (options.quiet) {
1362
- if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
1363
- const message = String(event.result.parameters.message);
1364
- env.stdout.write(`${message}
2210
+ try {
2211
+ for await (const event of agent.run()) {
2212
+ if (event.type === "text") {
2213
+ progress.pause();
2214
+ textBuffer += event.content;
2215
+ } else if (event.type === "gadget_result") {
2216
+ flushTextBuffer();
2217
+ progress.pause();
2218
+ if (options.quiet) {
2219
+ if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
2220
+ const message = String(event.result.parameters.message);
2221
+ env.stdout.write(`${message}
1365
2222
  `);
1366
- }
1367
- } else {
1368
- const tokenCount = await countGadgetOutputTokens(event.result.result);
1369
- env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
2223
+ }
2224
+ } else {
2225
+ const tokenCount = await countGadgetOutputTokens(event.result.result);
2226
+ env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
1370
2227
  `);
2228
+ }
1371
2229
  }
1372
2230
  }
2231
+ } catch (error) {
2232
+ if (!isAbortError(error)) {
2233
+ throw error;
2234
+ }
2235
+ } finally {
2236
+ isStreaming = false;
2237
+ keyboard.cleanupEsc?.();
2238
+ keyboard.cleanupSigint?.();
1373
2239
  }
1374
2240
  flushTextBuffer();
1375
2241
  progress.complete();
1376
2242
  printer.ensureNewline();
1377
2243
  if (!options.quiet && iterations > 1) {
1378
- env.stderr.write(`${chalk3.dim("\u2500".repeat(40))}
2244
+ env.stderr.write(`${chalk5.dim("\u2500".repeat(40))}
1379
2245
  `);
1380
2246
  const summary = renderOverallSummary({
1381
2247
  totalTokens: usage?.totalTokens,
@@ -1393,7 +2259,13 @@ function registerAgentCommand(program, env, config) {
1393
2259
  const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
1394
2260
  addAgentOptions(cmd, config);
1395
2261
  cmd.action(
1396
- (prompt, options) => executeAction(() => executeAgent(prompt, options, env), env)
2262
+ (prompt, options) => executeAction(() => {
2263
+ const mergedOptions = {
2264
+ ...options,
2265
+ gadgetApproval: config?.["gadget-approval"]
2266
+ };
2267
+ return executeAgent(prompt, mergedOptions, env);
2268
+ }, env)
1397
2269
  );
1398
2270
  }
1399
2271
 
@@ -1477,7 +2349,7 @@ function registerCompleteCommand(program, env, config) {
1477
2349
  }
1478
2350
 
1479
2351
  // src/cli/config.ts
1480
- import { existsSync, readFileSync } from "node:fs";
2352
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1481
2353
  import { homedir as homedir2 } from "node:os";
1482
2354
  import { join as join2 } from "node:path";
1483
2355
  import { load as parseToml } from "js-toml";
@@ -1564,6 +2436,7 @@ function hasTemplateSyntax(str) {
1564
2436
  }
1565
2437
 
1566
2438
  // src/cli/config.ts
2439
+ var VALID_APPROVAL_MODES = ["allowed", "denied", "approval-required"];
1567
2440
  var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
1568
2441
  var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
1569
2442
  var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
@@ -1586,12 +2459,20 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
1586
2459
  "system",
1587
2460
  "temperature",
1588
2461
  "max-iterations",
2462
+ "gadgets",
2463
+ // Full replacement (preferred)
2464
+ "gadget-add",
2465
+ // Add to inherited gadgets
2466
+ "gadget-remove",
2467
+ // Remove from inherited gadgets
1589
2468
  "gadget",
2469
+ // DEPRECATED: alias for gadgets
1590
2470
  "builtins",
1591
2471
  "builtin-interaction",
1592
2472
  "gadget-start-prefix",
1593
2473
  "gadget-end-prefix",
1594
2474
  "gadget-arg-prefix",
2475
+ "gadget-approval",
1595
2476
  "quiet",
1596
2477
  "inherits",
1597
2478
  "log-level",
@@ -1612,9 +2493,9 @@ function getConfigPath() {
1612
2493
  return join2(homedir2(), ".llmist", "cli.toml");
1613
2494
  }
1614
2495
  var ConfigError = class extends Error {
1615
- constructor(message, path2) {
1616
- super(path2 ? `${path2}: ${message}` : message);
1617
- this.path = path2;
2496
+ constructor(message, path5) {
2497
+ super(path5 ? `${path5}: ${message}` : message);
2498
+ this.path = path5;
1618
2499
  this.name = "ConfigError";
1619
2500
  }
1620
2501
  };
@@ -1670,6 +2551,28 @@ function validateInherits(value, section) {
1670
2551
  }
1671
2552
  throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
1672
2553
  }
2554
+ function validateGadgetApproval(value, section) {
2555
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2556
+ throw new ConfigError(
2557
+ `[${section}].gadget-approval must be a table (e.g., { WriteFile = "approval-required" })`
2558
+ );
2559
+ }
2560
+ const result = {};
2561
+ for (const [gadgetName, mode] of Object.entries(value)) {
2562
+ if (typeof mode !== "string") {
2563
+ throw new ConfigError(
2564
+ `[${section}].gadget-approval.${gadgetName} must be a string`
2565
+ );
2566
+ }
2567
+ if (!VALID_APPROVAL_MODES.includes(mode)) {
2568
+ throw new ConfigError(
2569
+ `[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_APPROVAL_MODES.join(", ")}`
2570
+ );
2571
+ }
2572
+ result[gadgetName] = mode;
2573
+ }
2574
+ return result;
2575
+ }
1673
2576
  function validateLoggingConfig(raw, section) {
1674
2577
  const result = {};
1675
2578
  if ("log-level" in raw) {
@@ -1779,6 +2682,15 @@ function validateAgentConfig(raw, section) {
1779
2682
  min: 1
1780
2683
  });
1781
2684
  }
2685
+ if ("gadgets" in rawObj) {
2686
+ result.gadgets = validateStringArray(rawObj.gadgets, "gadgets", section);
2687
+ }
2688
+ if ("gadget-add" in rawObj) {
2689
+ result["gadget-add"] = validateStringArray(rawObj["gadget-add"], "gadget-add", section);
2690
+ }
2691
+ if ("gadget-remove" in rawObj) {
2692
+ result["gadget-remove"] = validateStringArray(rawObj["gadget-remove"], "gadget-remove", section);
2693
+ }
1782
2694
  if ("gadget" in rawObj) {
1783
2695
  result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
1784
2696
  }
@@ -1813,6 +2725,9 @@ function validateAgentConfig(raw, section) {
1813
2725
  section
1814
2726
  );
1815
2727
  }
2728
+ if ("gadget-approval" in rawObj) {
2729
+ result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
2730
+ }
1816
2731
  if ("quiet" in rawObj) {
1817
2732
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
1818
2733
  }
@@ -1869,6 +2784,15 @@ function validateCustomConfig(raw, section) {
1869
2784
  min: 1
1870
2785
  });
1871
2786
  }
2787
+ if ("gadgets" in rawObj) {
2788
+ result.gadgets = validateStringArray(rawObj.gadgets, "gadgets", section);
2789
+ }
2790
+ if ("gadget-add" in rawObj) {
2791
+ result["gadget-add"] = validateStringArray(rawObj["gadget-add"], "gadget-add", section);
2792
+ }
2793
+ if ("gadget-remove" in rawObj) {
2794
+ result["gadget-remove"] = validateStringArray(rawObj["gadget-remove"], "gadget-remove", section);
2795
+ }
1872
2796
  if ("gadget" in rawObj) {
1873
2797
  result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
1874
2798
  }
@@ -1903,6 +2827,9 @@ function validateCustomConfig(raw, section) {
1903
2827
  section
1904
2828
  );
1905
2829
  }
2830
+ if ("gadget-approval" in rawObj) {
2831
+ result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
2832
+ }
1906
2833
  if ("max-tokens" in rawObj) {
1907
2834
  result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
1908
2835
  integer: true,
@@ -1958,12 +2885,12 @@ function validateConfig(raw, configPath) {
1958
2885
  }
1959
2886
  function loadConfig() {
1960
2887
  const configPath = getConfigPath();
1961
- if (!existsSync(configPath)) {
2888
+ if (!existsSync2(configPath)) {
1962
2889
  return {};
1963
2890
  }
1964
2891
  let content;
1965
2892
  try {
1966
- content = readFileSync(configPath, "utf-8");
2893
+ content = readFileSync2(configPath, "utf-8");
1967
2894
  } catch (error) {
1968
2895
  throw new ConfigError(
1969
2896
  `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -2058,6 +2985,39 @@ function resolveTemplatesInConfig(config, configPath) {
2058
2985
  }
2059
2986
  return result;
2060
2987
  }
2988
+ function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
2989
+ const hasGadgets = "gadgets" in section;
2990
+ const hasGadgetLegacy = "gadget" in section;
2991
+ const hasGadgetAdd = "gadget-add" in section;
2992
+ const hasGadgetRemove = "gadget-remove" in section;
2993
+ if (hasGadgetLegacy && !hasGadgets) {
2994
+ console.warn(
2995
+ `[config] Warning: [${sectionName}].gadget is deprecated, use 'gadgets' (plural) instead`
2996
+ );
2997
+ }
2998
+ if ((hasGadgets || hasGadgetLegacy) && (hasGadgetAdd || hasGadgetRemove)) {
2999
+ throw new ConfigError(
3000
+ `[${sectionName}] Cannot use 'gadgets' with 'gadget-add'/'gadget-remove'. Use either full replacement (gadgets) OR modification (gadget-add/gadget-remove).`,
3001
+ configPath
3002
+ );
3003
+ }
3004
+ if (hasGadgets) {
3005
+ return section.gadgets;
3006
+ }
3007
+ if (hasGadgetLegacy) {
3008
+ return section.gadget;
3009
+ }
3010
+ let result = [...inheritedGadgets];
3011
+ if (hasGadgetRemove) {
3012
+ const toRemove = new Set(section["gadget-remove"]);
3013
+ result = result.filter((g) => !toRemove.has(g));
3014
+ }
3015
+ if (hasGadgetAdd) {
3016
+ const toAdd = section["gadget-add"];
3017
+ result.push(...toAdd);
3018
+ }
3019
+ return result;
3020
+ }
2061
3021
  function resolveInheritance(config, configPath) {
2062
3022
  const resolved = {};
2063
3023
  const resolving = /* @__PURE__ */ new Set();
@@ -2081,8 +3041,23 @@ function resolveInheritance(config, configPath) {
2081
3041
  const parentResolved = resolveSection(parent);
2082
3042
  merged = { ...merged, ...parentResolved };
2083
3043
  }
2084
- const { inherits: _inherits, ...ownValues } = sectionObj;
3044
+ const inheritedGadgets = merged.gadgets ?? [];
3045
+ const {
3046
+ inherits: _inherits,
3047
+ gadgets: _gadgets,
3048
+ gadget: _gadget,
3049
+ "gadget-add": _gadgetAdd,
3050
+ "gadget-remove": _gadgetRemove,
3051
+ ...ownValues
3052
+ } = sectionObj;
2085
3053
  merged = { ...merged, ...ownValues };
3054
+ const resolvedGadgets = resolveGadgets(sectionObj, inheritedGadgets, name, configPath);
3055
+ if (resolvedGadgets.length > 0) {
3056
+ merged.gadgets = resolvedGadgets;
3057
+ }
3058
+ delete merged["gadget"];
3059
+ delete merged["gadget-add"];
3060
+ delete merged["gadget-remove"];
2086
3061
  resolving.delete(name);
2087
3062
  resolved[name] = merged;
2088
3063
  return merged;
@@ -2096,12 +3071,12 @@ function resolveInheritance(config, configPath) {
2096
3071
  // src/cli/gadget-command.ts
2097
3072
  init_schema_to_json();
2098
3073
  init_schema_validator();
2099
- import chalk5 from "chalk";
3074
+ import chalk7 from "chalk";
2100
3075
 
2101
3076
  // src/cli/gadget-prompts.ts
2102
3077
  init_schema_to_json();
2103
- import { createInterface as createInterface2 } from "node:readline/promises";
2104
- import chalk4 from "chalk";
3078
+ import { createInterface as createInterface3 } from "node:readline/promises";
3079
+ import chalk6 from "chalk";
2105
3080
  async function promptForParameters(schema, ctx) {
2106
3081
  if (!schema) {
2107
3082
  return {};
@@ -2110,7 +3085,7 @@ async function promptForParameters(schema, ctx) {
2110
3085
  if (!jsonSchema.properties || Object.keys(jsonSchema.properties).length === 0) {
2111
3086
  return {};
2112
3087
  }
2113
- const rl = createInterface2({ input: ctx.stdin, output: ctx.stdout });
3088
+ const rl = createInterface3({ input: ctx.stdin, output: ctx.stdout });
2114
3089
  const params = {};
2115
3090
  try {
2116
3091
  for (const [key, prop] of Object.entries(jsonSchema.properties)) {
@@ -2133,16 +3108,16 @@ ${issues}`);
2133
3108
  async function promptForField(rl, key, prop, required) {
2134
3109
  const isRequired = required.includes(key);
2135
3110
  const typeHint = formatTypeHint(prop);
2136
- const defaultHint = prop.default !== void 0 ? chalk4.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
2137
- const requiredMarker = isRequired ? chalk4.red("*") : "";
3111
+ const defaultHint = prop.default !== void 0 ? chalk6.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
3112
+ const requiredMarker = isRequired ? chalk6.red("*") : "";
2138
3113
  let prompt = `
2139
- ${chalk4.cyan.bold(key)}${requiredMarker}`;
3114
+ ${chalk6.cyan.bold(key)}${requiredMarker}`;
2140
3115
  if (prop.description) {
2141
- prompt += chalk4.dim(` - ${prop.description}`);
3116
+ prompt += chalk6.dim(` - ${prop.description}`);
2142
3117
  }
2143
3118
  prompt += `
2144
3119
  ${typeHint}${defaultHint}
2145
- ${chalk4.green(">")} `;
3120
+ ${chalk6.green(">")} `;
2146
3121
  const answer = await rl.question(prompt);
2147
3122
  const trimmed = answer.trim();
2148
3123
  if (!trimmed) {
@@ -2158,20 +3133,20 @@ ${chalk4.cyan.bold(key)}${requiredMarker}`;
2158
3133
  }
2159
3134
  function formatTypeHint(prop) {
2160
3135
  if (prop.enum) {
2161
- return chalk4.yellow(`(${prop.enum.join(" | ")})`);
3136
+ return chalk6.yellow(`(${prop.enum.join(" | ")})`);
2162
3137
  }
2163
3138
  if (prop.type === "array") {
2164
3139
  const items = prop.items;
2165
3140
  if (items?.enum) {
2166
- return chalk4.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
3141
+ return chalk6.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
2167
3142
  }
2168
3143
  const itemType = items?.type ?? "any";
2169
- return chalk4.yellow(`(${itemType}[]) comma-separated`);
3144
+ return chalk6.yellow(`(${itemType}[]) comma-separated`);
2170
3145
  }
2171
3146
  if (prop.type === "object" && prop.properties) {
2172
- return chalk4.yellow("(object) enter as JSON");
3147
+ return chalk6.yellow("(object) enter as JSON");
2173
3148
  }
2174
- return chalk4.yellow(`(${prop.type ?? "any"})`);
3149
+ return chalk6.yellow(`(${prop.type ?? "any"})`);
2175
3150
  }
2176
3151
  function parseValue(input, prop, key) {
2177
3152
  const type = prop.type;
@@ -2282,7 +3257,7 @@ Available gadgets:
2282
3257
  async function executeGadgetRun(file, options, env) {
2283
3258
  const cwd = process.cwd();
2284
3259
  const { gadget, name } = await selectGadget(file, options.name, cwd);
2285
- env.stderr.write(chalk5.cyan.bold(`
3260
+ env.stderr.write(chalk7.cyan.bold(`
2286
3261
  \u{1F527} Running gadget: ${name}
2287
3262
  `));
2288
3263
  let params;
@@ -2293,7 +3268,7 @@ async function executeGadgetRun(file, options, env) {
2293
3268
  // Prompts go to stderr to keep stdout clean
2294
3269
  });
2295
3270
  } else {
2296
- env.stderr.write(chalk5.dim("Reading parameters from stdin...\n"));
3271
+ env.stderr.write(chalk7.dim("Reading parameters from stdin...\n"));
2297
3272
  const stdinParams = await readStdinJson(env.stdin);
2298
3273
  if (gadget.parameterSchema) {
2299
3274
  const result2 = gadget.parameterSchema.safeParse(stdinParams);
@@ -2307,7 +3282,7 @@ ${issues}`);
2307
3282
  params = stdinParams;
2308
3283
  }
2309
3284
  }
2310
- env.stderr.write(chalk5.dim("\nExecuting...\n"));
3285
+ env.stderr.write(chalk7.dim("\nExecuting...\n"));
2311
3286
  const startTime = Date.now();
2312
3287
  let result;
2313
3288
  try {
@@ -2329,7 +3304,7 @@ ${issues}`);
2329
3304
  throw new Error(`Execution failed: ${message}`);
2330
3305
  }
2331
3306
  const elapsed = Date.now() - startTime;
2332
- env.stderr.write(chalk5.green(`
3307
+ env.stderr.write(chalk7.green(`
2333
3308
  \u2713 Completed in ${elapsed}ms
2334
3309
 
2335
3310
  `));
@@ -2365,37 +3340,37 @@ async function executeGadgetInfo(file, options, env) {
2365
3340
  return;
2366
3341
  }
2367
3342
  env.stdout.write("\n");
2368
- env.stdout.write(chalk5.cyan.bold(`${name}
3343
+ env.stdout.write(chalk7.cyan.bold(`${name}
2369
3344
  `));
2370
- env.stdout.write(chalk5.cyan("\u2550".repeat(name.length)) + "\n\n");
2371
- env.stdout.write(chalk5.bold("Description:\n"));
3345
+ env.stdout.write(chalk7.cyan("\u2550".repeat(name.length)) + "\n\n");
3346
+ env.stdout.write(chalk7.bold("Description:\n"));
2372
3347
  env.stdout.write(` ${gadget.description}
2373
3348
 
2374
3349
  `);
2375
3350
  if (gadget.parameterSchema) {
2376
- env.stdout.write(chalk5.bold("Parameters:\n"));
3351
+ env.stdout.write(chalk7.bold("Parameters:\n"));
2377
3352
  const jsonSchema = schemaToJSONSchema(gadget.parameterSchema, { target: "draft-7" });
2378
3353
  env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
2379
3354
  } else {
2380
- env.stdout.write(chalk5.dim("No parameters required.\n\n"));
3355
+ env.stdout.write(chalk7.dim("No parameters required.\n\n"));
2381
3356
  }
2382
3357
  if (gadget.timeoutMs) {
2383
- env.stdout.write(chalk5.bold("Timeout:\n"));
3358
+ env.stdout.write(chalk7.bold("Timeout:\n"));
2384
3359
  env.stdout.write(` ${gadget.timeoutMs}ms
2385
3360
 
2386
3361
  `);
2387
3362
  }
2388
3363
  if (gadget.examples && gadget.examples.length > 0) {
2389
- env.stdout.write(chalk5.bold("Examples:\n"));
3364
+ env.stdout.write(chalk7.bold("Examples:\n"));
2390
3365
  for (const example of gadget.examples) {
2391
3366
  if (example.comment) {
2392
- env.stdout.write(chalk5.dim(` # ${example.comment}
3367
+ env.stdout.write(chalk7.dim(` # ${example.comment}
2393
3368
  `));
2394
3369
  }
2395
- env.stdout.write(` Input: ${chalk5.cyan(JSON.stringify(example.params))}
3370
+ env.stdout.write(` Input: ${chalk7.cyan(JSON.stringify(example.params))}
2396
3371
  `);
2397
3372
  if (example.output !== void 0) {
2398
- env.stdout.write(` Output: ${chalk5.green(example.output)}
3373
+ env.stdout.write(` Output: ${chalk7.green(example.output)}
2399
3374
  `);
2400
3375
  }
2401
3376
  env.stdout.write("\n");
@@ -2428,27 +3403,27 @@ function formatSchemaAsText(schema, indent = "") {
2428
3403
  const isRequired = required.includes(key);
2429
3404
  const enumValues = prop.enum;
2430
3405
  const defaultValue = prop.default;
2431
- let line = `${indent}${chalk5.cyan(key)}`;
3406
+ let line = `${indent}${chalk7.cyan(key)}`;
2432
3407
  if (isRequired) {
2433
- line += chalk5.red("*");
3408
+ line += chalk7.red("*");
2434
3409
  }
2435
3410
  if (type === "array") {
2436
3411
  const items = prop.items;
2437
3412
  const itemType = items?.type || "any";
2438
- line += chalk5.dim(` (${itemType}[])`);
3413
+ line += chalk7.dim(` (${itemType}[])`);
2439
3414
  } else if (type === "object" && prop.properties) {
2440
- line += chalk5.dim(" (object)");
3415
+ line += chalk7.dim(" (object)");
2441
3416
  } else {
2442
- line += chalk5.dim(` (${type})`);
3417
+ line += chalk7.dim(` (${type})`);
2443
3418
  }
2444
3419
  if (defaultValue !== void 0) {
2445
- line += chalk5.dim(` [default: ${JSON.stringify(defaultValue)}]`);
3420
+ line += chalk7.dim(` [default: ${JSON.stringify(defaultValue)}]`);
2446
3421
  }
2447
3422
  if (description) {
2448
3423
  line += `: ${description}`;
2449
3424
  }
2450
3425
  if (enumValues) {
2451
- line += chalk5.yellow(` - one of: ${enumValues.join(", ")}`);
3426
+ line += chalk7.yellow(` - one of: ${enumValues.join(", ")}`);
2452
3427
  }
2453
3428
  lines.push(line);
2454
3429
  if (type === "object" && prop.properties) {
@@ -2488,20 +3463,20 @@ async function executeGadgetValidate(file, env) {
2488
3463
  throw new Error(`Validation issues:
2489
3464
  ${issues.map((i) => ` - ${i}`).join("\n")}`);
2490
3465
  }
2491
- env.stdout.write(chalk5.green.bold("\n\u2713 Valid\n\n"));
2492
- env.stdout.write(chalk5.bold("Gadgets found:\n"));
3466
+ env.stdout.write(chalk7.green.bold("\n\u2713 Valid\n\n"));
3467
+ env.stdout.write(chalk7.bold("Gadgets found:\n"));
2493
3468
  for (const gadget of gadgets) {
2494
3469
  const name = gadget.name ?? gadget.constructor.name;
2495
- const schemaInfo = gadget.parameterSchema ? chalk5.cyan("(with schema)") : chalk5.dim("(no schema)");
2496
- env.stdout.write(` ${chalk5.bold(name)} ${schemaInfo}
3470
+ const schemaInfo = gadget.parameterSchema ? chalk7.cyan("(with schema)") : chalk7.dim("(no schema)");
3471
+ env.stdout.write(` ${chalk7.bold(name)} ${schemaInfo}
2497
3472
  `);
2498
- env.stdout.write(chalk5.dim(` ${gadget.description}
3473
+ env.stdout.write(chalk7.dim(` ${gadget.description}
2499
3474
  `));
2500
3475
  }
2501
3476
  env.stdout.write("\n");
2502
3477
  } catch (error) {
2503
3478
  const message = error instanceof Error ? error.message : String(error);
2504
- env.stdout.write(chalk5.red.bold(`
3479
+ env.stdout.write(chalk7.red.bold(`
2505
3480
  \u2717 Invalid
2506
3481
 
2507
3482
  `));
@@ -2525,7 +3500,7 @@ function registerGadgetCommand(program, env) {
2525
3500
  }
2526
3501
 
2527
3502
  // src/cli/models-command.ts
2528
- import chalk6 from "chalk";
3503
+ import chalk8 from "chalk";
2529
3504
  init_model_shortcuts();
2530
3505
  async function handleModelsCommand(options, env) {
2531
3506
  const client = env.createClient();
@@ -2545,13 +3520,13 @@ function renderTable(models, verbose, stream) {
2545
3520
  }
2546
3521
  grouped.get(provider).push(model);
2547
3522
  }
2548
- stream.write(chalk6.bold.cyan("\nAvailable Models\n"));
2549
- stream.write(chalk6.cyan("=".repeat(80)) + "\n\n");
3523
+ stream.write(chalk8.bold.cyan("\nAvailable Models\n"));
3524
+ stream.write(chalk8.cyan("=".repeat(80)) + "\n\n");
2550
3525
  const providers = Array.from(grouped.keys()).sort();
2551
3526
  for (const provider of providers) {
2552
3527
  const providerModels = grouped.get(provider);
2553
3528
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2554
- stream.write(chalk6.bold.yellow(`${providerName} Models
3529
+ stream.write(chalk8.bold.yellow(`${providerName} Models
2555
3530
  `));
2556
3531
  if (verbose) {
2557
3532
  renderVerboseTable(providerModels, stream);
@@ -2560,11 +3535,11 @@ function renderTable(models, verbose, stream) {
2560
3535
  }
2561
3536
  stream.write("\n");
2562
3537
  }
2563
- stream.write(chalk6.bold.magenta("Model Shortcuts\n"));
2564
- stream.write(chalk6.dim("\u2500".repeat(80)) + "\n");
3538
+ stream.write(chalk8.bold.magenta("Model Shortcuts\n"));
3539
+ stream.write(chalk8.dim("\u2500".repeat(80)) + "\n");
2565
3540
  const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
2566
3541
  for (const [shortcut, fullName] of shortcuts) {
2567
- stream.write(chalk6.cyan(` ${shortcut.padEnd(15)}`) + chalk6.dim(" \u2192 ") + chalk6.white(fullName) + "\n");
3542
+ stream.write(chalk8.cyan(` ${shortcut.padEnd(15)}`) + chalk8.dim(" \u2192 ") + chalk8.white(fullName) + "\n");
2568
3543
  }
2569
3544
  stream.write("\n");
2570
3545
  }
@@ -2574,45 +3549,45 @@ function renderCompactTable(models, stream) {
2574
3549
  const contextWidth = 13;
2575
3550
  const inputWidth = 10;
2576
3551
  const outputWidth = 10;
2577
- stream.write(chalk6.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
3552
+ stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
2578
3553
  stream.write(
2579
- chalk6.bold(
3554
+ chalk8.bold(
2580
3555
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
2581
3556
  ) + "\n"
2582
3557
  );
2583
- stream.write(chalk6.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
3558
+ stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
2584
3559
  for (const model of models) {
2585
3560
  const contextFormatted = formatTokens2(model.contextWindow);
2586
3561
  const inputPrice = `$${model.pricing.input.toFixed(2)}`;
2587
3562
  const outputPrice = `$${model.pricing.output.toFixed(2)}`;
2588
3563
  stream.write(
2589
- chalk6.green(model.modelId.padEnd(idWidth)) + " " + chalk6.white(model.displayName.padEnd(nameWidth)) + " " + chalk6.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk6.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk6.cyan(outputPrice.padEnd(outputWidth)) + "\n"
3564
+ chalk8.green(model.modelId.padEnd(idWidth)) + " " + chalk8.white(model.displayName.padEnd(nameWidth)) + " " + chalk8.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk8.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk8.cyan(outputPrice.padEnd(outputWidth)) + "\n"
2590
3565
  );
2591
3566
  }
2592
- stream.write(chalk6.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
2593
- stream.write(chalk6.dim(` * Prices are per 1M tokens
3567
+ stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
3568
+ stream.write(chalk8.dim(` * Prices are per 1M tokens
2594
3569
  `));
2595
3570
  }
2596
3571
  function renderVerboseTable(models, stream) {
2597
3572
  for (const model of models) {
2598
- stream.write(chalk6.bold.green(`
3573
+ stream.write(chalk8.bold.green(`
2599
3574
  ${model.modelId}
2600
3575
  `));
2601
- stream.write(chalk6.dim(" " + "\u2500".repeat(60)) + "\n");
2602
- stream.write(` ${chalk6.dim("Name:")} ${chalk6.white(model.displayName)}
3576
+ stream.write(chalk8.dim(" " + "\u2500".repeat(60)) + "\n");
3577
+ stream.write(` ${chalk8.dim("Name:")} ${chalk8.white(model.displayName)}
2603
3578
  `);
2604
- stream.write(` ${chalk6.dim("Context:")} ${chalk6.yellow(formatTokens2(model.contextWindow))}
3579
+ stream.write(` ${chalk8.dim("Context:")} ${chalk8.yellow(formatTokens2(model.contextWindow))}
2605
3580
  `);
2606
- stream.write(` ${chalk6.dim("Max Output:")} ${chalk6.yellow(formatTokens2(model.maxOutputTokens))}
3581
+ stream.write(` ${chalk8.dim("Max Output:")} ${chalk8.yellow(formatTokens2(model.maxOutputTokens))}
2607
3582
  `);
2608
- stream.write(` ${chalk6.dim("Pricing:")} ${chalk6.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk6.dim("/")} ${chalk6.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk6.dim("(per 1M tokens)")}
3583
+ stream.write(` ${chalk8.dim("Pricing:")} ${chalk8.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk8.dim("/")} ${chalk8.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk8.dim("(per 1M tokens)")}
2609
3584
  `);
2610
3585
  if (model.pricing.cachedInput !== void 0) {
2611
- stream.write(` ${chalk6.dim("Cached Input:")} ${chalk6.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
3586
+ stream.write(` ${chalk8.dim("Cached Input:")} ${chalk8.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
2612
3587
  `);
2613
3588
  }
2614
3589
  if (model.knowledgeCutoff) {
2615
- stream.write(` ${chalk6.dim("Knowledge:")} ${model.knowledgeCutoff}
3590
+ stream.write(` ${chalk8.dim("Knowledge:")} ${model.knowledgeCutoff}
2616
3591
  `);
2617
3592
  }
2618
3593
  const features = [];
@@ -2623,20 +3598,20 @@ function renderVerboseTable(models, stream) {
2623
3598
  if (model.features.structuredOutputs) features.push("structured-outputs");
2624
3599
  if (model.features.fineTuning) features.push("fine-tuning");
2625
3600
  if (features.length > 0) {
2626
- stream.write(` ${chalk6.dim("Features:")} ${chalk6.blue(features.join(", "))}
3601
+ stream.write(` ${chalk8.dim("Features:")} ${chalk8.blue(features.join(", "))}
2627
3602
  `);
2628
3603
  }
2629
3604
  if (model.metadata) {
2630
3605
  if (model.metadata.family) {
2631
- stream.write(` ${chalk6.dim("Family:")} ${model.metadata.family}
3606
+ stream.write(` ${chalk8.dim("Family:")} ${model.metadata.family}
2632
3607
  `);
2633
3608
  }
2634
3609
  if (model.metadata.releaseDate) {
2635
- stream.write(` ${chalk6.dim("Released:")} ${model.metadata.releaseDate}
3610
+ stream.write(` ${chalk8.dim("Released:")} ${model.metadata.releaseDate}
2636
3611
  `);
2637
3612
  }
2638
3613
  if (model.metadata.notes) {
2639
- stream.write(` ${chalk6.dim("Notes:")} ${chalk6.italic(model.metadata.notes)}
3614
+ stream.write(` ${chalk8.dim("Notes:")} ${chalk8.italic(model.metadata.notes)}
2640
3615
  `);
2641
3616
  }
2642
3617
  }
@@ -2688,7 +3663,7 @@ function registerModelsCommand(program, env) {
2688
3663
  init_client();
2689
3664
  init_logger();
2690
3665
  import readline from "node:readline";
2691
- import chalk7 from "chalk";
3666
+ import chalk9 from "chalk";
2692
3667
  var LOG_LEVEL_MAP = {
2693
3668
  silly: 0,
2694
3669
  trace: 1,
@@ -2729,22 +3704,22 @@ function createLoggerFactory(config) {
2729
3704
  }
2730
3705
  function createPromptFunction(stdin, stdout) {
2731
3706
  return (question) => {
2732
- return new Promise((resolve) => {
3707
+ return new Promise((resolve2) => {
2733
3708
  const rl = readline.createInterface({
2734
3709
  input: stdin,
2735
3710
  output: stdout
2736
3711
  });
2737
3712
  stdout.write("\n");
2738
- stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
3713
+ stdout.write(`${chalk9.cyan("\u2500".repeat(60))}
2739
3714
  `);
2740
- stdout.write(chalk7.cyan.bold("\u{1F916} Agent asks:\n"));
3715
+ stdout.write(chalk9.cyan.bold("\u{1F916} Agent asks:\n"));
2741
3716
  stdout.write(`${question}
2742
3717
  `);
2743
- stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
3718
+ stdout.write(`${chalk9.cyan("\u2500".repeat(60))}
2744
3719
  `);
2745
- rl.question(chalk7.green.bold("You: "), (answer) => {
3720
+ rl.question(chalk9.green.bold("You: "), (answer) => {
2746
3721
  rl.close();
2747
- resolve(answer);
3722
+ resolve2(answer);
2748
3723
  });
2749
3724
  });
2750
3725
  };