@llmist/cli 14.0.0 → 15.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
@@ -98,7 +98,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
98
98
  // package.json
99
99
  var package_default = {
100
100
  name: "@llmist/cli",
101
- version: "14.0.0",
101
+ version: "15.1.0",
102
102
  description: "CLI for llmist - run LLM agents from the command line",
103
103
  type: "module",
104
104
  main: "dist/cli.js",
@@ -154,7 +154,7 @@ var package_default = {
154
154
  node: ">=22.0.0"
155
155
  },
156
156
  dependencies: {
157
- llmist: "^14.0.0",
157
+ llmist: "^15.1.0",
158
158
  "@unblessed/node": "^1.0.0-alpha.23",
159
159
  chalk: "^5.6.2",
160
160
  commander: "^12.1.0",
@@ -168,7 +168,7 @@ var package_default = {
168
168
  zod: "^4.1.12"
169
169
  },
170
170
  devDependencies: {
171
- "@llmist/testing": "^14.0.0",
171
+ "@llmist/testing": "^15.1.0",
172
172
  "@types/diff": "^8.0.0",
173
173
  "@types/js-yaml": "^4.0.9",
174
174
  "@types/marked-terminal": "^6.1.1",
@@ -1456,66 +1456,259 @@ import { createJiti } from "jiti";
1456
1456
  import { AbstractGadget } from "llmist";
1457
1457
 
1458
1458
  // src/builtins/filesystem/edit-file.ts
1459
- import { z as z2 } from "zod";
1459
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
1460
1460
  import { createGadget as createGadget2 } from "llmist";
1461
+ import { z as z2 } from "zod";
1461
1462
 
1462
- // src/spawn.ts
1463
- import { spawn as nodeSpawn } from "child_process";
1464
- function nodeStreamToReadableStream(nodeStream) {
1465
- if (!nodeStream) return null;
1466
- return new ReadableStream({
1467
- start(controller) {
1468
- nodeStream.on("data", (chunk) => {
1469
- controller.enqueue(new Uint8Array(chunk));
1470
- });
1471
- nodeStream.on("end", () => {
1472
- controller.close();
1473
- });
1474
- nodeStream.on("error", (err) => {
1475
- controller.error(err);
1476
- });
1477
- },
1478
- cancel() {
1479
- nodeStream.destroy();
1463
+ // src/builtins/filesystem/editfile/matcher.ts
1464
+ var DEFAULT_OPTIONS = {
1465
+ fuzzyThreshold: 0.8,
1466
+ maxSuggestions: 3,
1467
+ contextLines: 5
1468
+ };
1469
+ function findMatch(content, search, options = {}) {
1470
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1471
+ const strategies = [
1472
+ { name: "exact", fn: exactMatch },
1473
+ { name: "whitespace", fn: whitespaceMatch },
1474
+ { name: "indentation", fn: indentationMatch },
1475
+ { name: "fuzzy", fn: (c, s) => fuzzyMatch(c, s, opts.fuzzyThreshold) }
1476
+ ];
1477
+ for (const { name, fn } of strategies) {
1478
+ const result = fn(content, search);
1479
+ if (result) {
1480
+ return { ...result, strategy: name };
1480
1481
  }
1481
- });
1482
+ }
1483
+ return null;
1482
1484
  }
1483
- function spawn(argv, options = {}) {
1484
- const [command, ...args] = argv;
1485
- const proc = nodeSpawn(command, args, {
1486
- cwd: options.cwd,
1487
- stdio: [
1488
- options.stdin === "pipe" ? "pipe" : options.stdin ?? "ignore",
1489
- options.stdout === "pipe" ? "pipe" : options.stdout ?? "ignore",
1490
- options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
1491
- ]
1492
- });
1493
- const exited = new Promise((resolve3, reject) => {
1494
- proc.on("exit", (code) => {
1495
- resolve3(code ?? 1);
1496
- });
1497
- proc.on("error", (err) => {
1498
- reject(err);
1499
- });
1500
- });
1501
- const stdin = proc.stdin ? {
1502
- write(data) {
1503
- proc.stdin?.write(data);
1504
- },
1505
- end() {
1506
- proc.stdin?.end();
1507
- }
1508
- } : null;
1485
+ function applyReplacement(content, match, replacement) {
1486
+ return content.slice(0, match.startIndex) + replacement + content.slice(match.endIndex);
1487
+ }
1488
+ function getMatchFailure(content, search, options = {}) {
1489
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1490
+ const suggestions = findSuggestions(content, search, opts.maxSuggestions, opts.fuzzyThreshold);
1491
+ const nearbyContext = suggestions.length > 0 ? getContext(content, suggestions[0].lineNumber, opts.contextLines) : "";
1509
1492
  return {
1510
- exited,
1511
- stdout: nodeStreamToReadableStream(proc.stdout),
1512
- stderr: nodeStreamToReadableStream(proc.stderr),
1513
- stdin,
1514
- kill() {
1515
- proc.kill();
1493
+ reason: "Search content not found in file",
1494
+ suggestions,
1495
+ nearbyContext
1496
+ };
1497
+ }
1498
+ function exactMatch(content, search) {
1499
+ const index = content.indexOf(search);
1500
+ if (index === -1) return null;
1501
+ const { startLine, endLine } = getLineNumbers(content, index, index + search.length);
1502
+ return {
1503
+ found: true,
1504
+ strategy: "exact",
1505
+ confidence: 1,
1506
+ matchedContent: search,
1507
+ startIndex: index,
1508
+ endIndex: index + search.length,
1509
+ startLine,
1510
+ endLine
1511
+ };
1512
+ }
1513
+ function whitespaceMatch(content, search) {
1514
+ const normalizeWs = (s) => s.replace(/[ \t]+/g, " ");
1515
+ const normalizedContent = normalizeWs(content);
1516
+ const normalizedSearch = normalizeWs(search);
1517
+ const normalizedIndex = normalizedContent.indexOf(normalizedSearch);
1518
+ if (normalizedIndex === -1) return null;
1519
+ const { originalStart, originalEnd } = mapNormalizedToOriginal(
1520
+ content,
1521
+ normalizedIndex,
1522
+ normalizedSearch.length
1523
+ );
1524
+ const matchedContent = content.slice(originalStart, originalEnd);
1525
+ const { startLine, endLine } = getLineNumbers(content, originalStart, originalEnd);
1526
+ return {
1527
+ found: true,
1528
+ strategy: "whitespace",
1529
+ confidence: 0.95,
1530
+ matchedContent,
1531
+ startIndex: originalStart,
1532
+ endIndex: originalEnd,
1533
+ startLine,
1534
+ endLine
1535
+ };
1536
+ }
1537
+ function indentationMatch(content, search) {
1538
+ const stripIndent = (s) => s.split("\n").map((line) => line.trimStart()).join("\n");
1539
+ const strippedSearch = stripIndent(search);
1540
+ const contentLines = content.split("\n");
1541
+ const searchLineCount = search.split("\n").length;
1542
+ for (let i = 0; i <= contentLines.length - searchLineCount; i++) {
1543
+ const windowLines = contentLines.slice(i, i + searchLineCount);
1544
+ const strippedWindow = stripIndent(windowLines.join("\n"));
1545
+ if (strippedWindow === strippedSearch) {
1546
+ const startIndex = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
1547
+ const matchedContent = windowLines.join("\n");
1548
+ const endIndex = startIndex + matchedContent.length;
1549
+ const { startLine, endLine } = getLineNumbers(content, startIndex, endIndex);
1550
+ return {
1551
+ found: true,
1552
+ strategy: "indentation",
1553
+ confidence: 0.9,
1554
+ matchedContent,
1555
+ startIndex,
1556
+ endIndex,
1557
+ startLine,
1558
+ endLine
1559
+ };
1516
1560
  }
1561
+ }
1562
+ return null;
1563
+ }
1564
+ function fuzzyMatch(content, search, threshold) {
1565
+ const searchLines = search.split("\n");
1566
+ const contentLines = content.split("\n");
1567
+ if (searchLines.length > contentLines.length) return null;
1568
+ let bestMatch = null;
1569
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
1570
+ const windowLines = contentLines.slice(i, i + searchLines.length);
1571
+ const similarity = calculateLineSimilarity(searchLines, windowLines);
1572
+ if (similarity >= threshold && (!bestMatch || similarity > bestMatch.similarity)) {
1573
+ bestMatch = {
1574
+ startLineIndex: i,
1575
+ endLineIndex: i + searchLines.length,
1576
+ similarity
1577
+ };
1578
+ }
1579
+ }
1580
+ if (!bestMatch) return null;
1581
+ const startIndex = contentLines.slice(0, bestMatch.startLineIndex).join("\n").length + (bestMatch.startLineIndex > 0 ? 1 : 0);
1582
+ const matchedContent = contentLines.slice(bestMatch.startLineIndex, bestMatch.endLineIndex).join("\n");
1583
+ const endIndex = startIndex + matchedContent.length;
1584
+ const { startLine, endLine } = getLineNumbers(content, startIndex, endIndex);
1585
+ return {
1586
+ found: true,
1587
+ strategy: "fuzzy",
1588
+ confidence: bestMatch.similarity,
1589
+ matchedContent,
1590
+ startIndex,
1591
+ endIndex,
1592
+ startLine,
1593
+ endLine
1517
1594
  };
1518
1595
  }
1596
+ function findSuggestions(content, search, maxSuggestions, minSimilarity) {
1597
+ const searchLines = search.split("\n");
1598
+ const contentLines = content.split("\n");
1599
+ const suggestions = [];
1600
+ const suggestionThreshold = Math.max(0.5, minSimilarity - 0.2);
1601
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
1602
+ const windowLines = contentLines.slice(i, i + searchLines.length);
1603
+ const similarity = calculateLineSimilarity(searchLines, windowLines);
1604
+ if (similarity >= suggestionThreshold) {
1605
+ suggestions.push({
1606
+ lineIndex: i,
1607
+ similarity,
1608
+ content: windowLines.join("\n")
1609
+ });
1610
+ }
1611
+ }
1612
+ suggestions.sort((a, b) => b.similarity - a.similarity);
1613
+ return suggestions.slice(0, maxSuggestions).map((s) => ({
1614
+ content: s.content,
1615
+ lineNumber: s.lineIndex + 1,
1616
+ // 1-based
1617
+ similarity: s.similarity
1618
+ }));
1619
+ }
1620
+ function calculateLineSimilarity(a, b) {
1621
+ if (a.length !== b.length) return 0;
1622
+ if (a.length === 0) return 1;
1623
+ let totalSimilarity = 0;
1624
+ let totalWeight = 0;
1625
+ for (let i = 0; i < a.length; i++) {
1626
+ const lineA = a[i];
1627
+ const lineB = b[i];
1628
+ const weight = Math.max(lineA.length, lineB.length, 1);
1629
+ const similarity = stringSimilarity(lineA, lineB);
1630
+ totalSimilarity += similarity * weight;
1631
+ totalWeight += weight;
1632
+ }
1633
+ return totalWeight > 0 ? totalSimilarity / totalWeight : 0;
1634
+ }
1635
+ function stringSimilarity(a, b) {
1636
+ if (a === b) return 1;
1637
+ if (a.length === 0 || b.length === 0) return 0;
1638
+ const distance = levenshteinDistance(a, b);
1639
+ const maxLen = Math.max(a.length, b.length);
1640
+ return 1 - distance / maxLen;
1641
+ }
1642
+ function levenshteinDistance(a, b) {
1643
+ const matrix = [];
1644
+ for (let i = 0; i <= b.length; i++) {
1645
+ matrix[i] = [i];
1646
+ }
1647
+ for (let j = 0; j <= a.length; j++) {
1648
+ matrix[0][j] = j;
1649
+ }
1650
+ for (let i = 1; i <= b.length; i++) {
1651
+ for (let j = 1; j <= a.length; j++) {
1652
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1653
+ matrix[i][j] = matrix[i - 1][j - 1];
1654
+ } else {
1655
+ matrix[i][j] = Math.min(
1656
+ matrix[i - 1][j - 1] + 1,
1657
+ // substitution
1658
+ matrix[i][j - 1] + 1,
1659
+ // insertion
1660
+ matrix[i - 1][j] + 1
1661
+ // deletion
1662
+ );
1663
+ }
1664
+ }
1665
+ }
1666
+ return matrix[b.length][a.length];
1667
+ }
1668
+ function getLineNumbers(content, startIndex, endIndex) {
1669
+ const beforeStart = content.slice(0, startIndex);
1670
+ const beforeEnd = content.slice(0, endIndex);
1671
+ const startLine = (beforeStart.match(/\n/g) || []).length + 1;
1672
+ const endLine = (beforeEnd.match(/\n/g) || []).length + 1;
1673
+ return { startLine, endLine };
1674
+ }
1675
+ function isHorizontalWhitespace(char) {
1676
+ return char === " " || char === " ";
1677
+ }
1678
+ function mapNormalizedToOriginal(original, normalizedStart, normalizedLength) {
1679
+ const originalStart = findOriginalIndex(original, normalizedStart);
1680
+ const originalEnd = findOriginalIndex(original, normalizedStart + normalizedLength);
1681
+ return { originalStart, originalEnd: originalEnd === -1 ? original.length : originalEnd };
1682
+ }
1683
+ function findOriginalIndex(original, targetNormalizedPos) {
1684
+ let normalizedPos = 0;
1685
+ let inWhitespace = false;
1686
+ for (let i = 0; i < original.length; i++) {
1687
+ if (normalizedPos === targetNormalizedPos) {
1688
+ return i;
1689
+ }
1690
+ const isWs = isHorizontalWhitespace(original[i]);
1691
+ if (isWs && !inWhitespace) {
1692
+ normalizedPos++;
1693
+ inWhitespace = true;
1694
+ } else if (!isWs) {
1695
+ normalizedPos++;
1696
+ inWhitespace = false;
1697
+ }
1698
+ }
1699
+ return normalizedPos === targetNormalizedPos ? original.length : -1;
1700
+ }
1701
+ function getContext(content, lineNumber, contextLines) {
1702
+ const lines = content.split("\n");
1703
+ const start = Math.max(0, lineNumber - 1 - contextLines);
1704
+ const end = Math.min(lines.length, lineNumber + contextLines);
1705
+ const contextWithNumbers = lines.slice(start, end).map((line, i) => {
1706
+ const num = start + i + 1;
1707
+ const marker = num === lineNumber ? ">" : " ";
1708
+ return `${marker}${String(num).padStart(4)} | ${line}`;
1709
+ });
1710
+ return contextWithNumbers.join("\n");
1711
+ }
1519
1712
 
1520
1713
  // src/builtins/filesystem/utils.ts
1521
1714
  import fs from "fs";
@@ -1548,110 +1741,139 @@ function validatePathIsWithinCwd(inputPath) {
1548
1741
  }
1549
1742
 
1550
1743
  // src/builtins/filesystem/edit-file.ts
1551
- function filterDangerousCommands(commands) {
1552
- return commands.split("\n").filter((line) => !line.trimStart().startsWith("!")).join("\n");
1744
+ function formatFailure(filePath, search, failure, fileContent) {
1745
+ const lines = [
1746
+ `path=${filePath} status=failed`,
1747
+ "",
1748
+ `Error: ${failure.reason}`,
1749
+ "",
1750
+ "SEARCH CONTENT:",
1751
+ "```",
1752
+ search,
1753
+ "```"
1754
+ ];
1755
+ if (failure.suggestions.length > 0) {
1756
+ lines.push("", "SUGGESTIONS (similar content found):");
1757
+ for (const suggestion of failure.suggestions) {
1758
+ const percent = Math.round(suggestion.similarity * 100);
1759
+ lines.push(
1760
+ "",
1761
+ `Line ${suggestion.lineNumber} (${percent}% similar):`,
1762
+ "```",
1763
+ suggestion.content,
1764
+ "```"
1765
+ );
1766
+ }
1767
+ if (failure.nearbyContext) {
1768
+ lines.push("", "CONTEXT:", failure.nearbyContext);
1769
+ }
1770
+ }
1771
+ lines.push("", "CURRENT FILE CONTENT:", "```", fileContent, "```");
1772
+ return lines.join("\n");
1553
1773
  }
1554
1774
  var editFile = createGadget2({
1555
1775
  name: "EditFile",
1556
- 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.",
1776
+ description: `Edit a file by searching for content and replacing it.
1777
+
1778
+ Uses layered matching strategies (in order):
1779
+ 1. Exact match - byte-for-byte comparison
1780
+ 2. Whitespace-insensitive - ignores differences in spaces/tabs
1781
+ 3. Indentation-preserving - matches structure ignoring leading whitespace
1782
+ 4. Fuzzy match - similarity-based matching (80% threshold)
1783
+
1784
+ For multiple edits to the same file, call this gadget multiple times.
1785
+ Each call provides immediate feedback, allowing you to adjust subsequent edits.`,
1557
1786
  schema: z2.object({
1558
1787
  filePath: z2.string().describe("Path to the file to edit (relative or absolute)"),
1559
- commands: z2.string().describe("Ed commands to execute, one per line")
1788
+ search: z2.string().describe("The content to search for in the file"),
1789
+ replace: z2.string().describe("The content to replace it with (empty string to delete)")
1560
1790
  }),
1561
1791
  examples: [
1562
1792
  {
1563
1793
  params: {
1564
- filePath: "config.txt",
1565
- commands: `1,$p
1566
- q`
1567
- },
1568
- output: "path=config.txt\n\n32\nkey=value\noption=true",
1569
- comment: "Print entire file contents (ed shows byte count, then content)"
1570
- },
1571
- {
1572
- params: {
1573
- filePath: "data.txt",
1574
- commands: `1,$s/foo/bar/g
1575
- w
1576
- q`
1794
+ filePath: "src/config.ts",
1795
+ search: "const DEBUG = false;",
1796
+ replace: "const DEBUG = true;"
1577
1797
  },
1578
- output: "path=data.txt\n\n42\n42",
1579
- comment: "Replace all 'foo' with 'bar' (ed shows bytes read, then bytes written)"
1798
+ output: "path=src/config.ts status=success strategy=exact lines=5-5\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// config.ts\nconst DEBUG = true;\nexport default { DEBUG };\n```",
1799
+ comment: "Simple single-line edit"
1580
1800
  },
1581
1801
  {
1582
1802
  params: {
1583
- filePath: "list.txt",
1584
- commands: `3d
1585
- w
1586
- q`
1803
+ filePath: "src/utils.ts",
1804
+ search: `function oldHelper() {
1805
+ return 1;
1806
+ }`,
1807
+ replace: `function newHelper() {
1808
+ return 2;
1809
+ }`
1587
1810
  },
1588
- output: "path=list.txt\n\n45\n28",
1589
- comment: "Delete line 3, save and quit"
1811
+ output: "path=src/utils.ts status=success strategy=exact lines=10-12\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// utils.ts\nfunction newHelper() {\n return 2;\n}\n```",
1812
+ comment: "Multi-line replacement"
1590
1813
  },
1591
1814
  {
1592
1815
  params: {
1593
- filePath: "readme.txt",
1594
- commands: `$a
1595
- New last line
1596
- .
1597
- w
1598
- q`
1816
+ filePath: "src/app.ts",
1817
+ search: "unusedImport",
1818
+ replace: ""
1599
1819
  },
1600
- output: "path=readme.txt\n\n40\n56",
1601
- comment: "Append text after last line ($ = last line, . = end input mode)"
1820
+ output: 'path=src/app.ts status=success strategy=exact lines=3-3\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// app.ts\nimport { usedImport } from "./lib";\n```',
1821
+ comment: "Delete content by replacing with empty string"
1602
1822
  }
1603
1823
  ],
1604
1824
  timeoutMs: 3e4,
1605
- execute: async ({ filePath, commands }) => {
1606
- const validatedPath = validatePathIsWithinCwd(filePath);
1607
- const safeCommands = filterDangerousCommands(commands);
1825
+ execute: ({ filePath, search, replace }) => {
1826
+ if (search.trim() === "") {
1827
+ return `path=${filePath} status=error
1828
+
1829
+ Error: Search content cannot be empty.`;
1830
+ }
1831
+ let validatedPath;
1608
1832
  try {
1609
- const proc = spawn(["ed", validatedPath], {
1610
- stdin: "pipe",
1611
- stdout: "pipe",
1612
- stderr: "pipe"
1613
- });
1614
- if (!proc.stdin) {
1615
- return `path=${filePath}
1833
+ validatedPath = validatePathIsWithinCwd(filePath);
1834
+ } catch (error) {
1835
+ const message = error instanceof Error ? error.message : String(error);
1836
+ return `path=${filePath} status=error
1616
1837
 
1617
- error: Failed to open stdin for ed process`;
1618
- }
1619
- proc.stdin.write(`${safeCommands}
1620
- `);
1621
- proc.stdin.end();
1622
- let timeoutId;
1623
- const timeoutPromise = new Promise((_, reject) => {
1624
- timeoutId = setTimeout(() => {
1625
- proc.kill();
1626
- reject(new Error("ed command timed out after 30000ms"));
1627
- }, 3e4);
1628
- });
1629
- const [exitCode, stdout, stderr] = await Promise.race([
1630
- Promise.all([
1631
- proc.exited,
1632
- new Response(proc.stdout).text(),
1633
- new Response(proc.stderr).text()
1634
- ]),
1635
- timeoutPromise
1636
- ]);
1637
- if (timeoutId) {
1638
- clearTimeout(timeoutId);
1639
- }
1640
- const output = [stdout, stderr].filter(Boolean).join("\n").trim();
1641
- if (exitCode !== 0) {
1642
- return `path=${filePath}
1838
+ Error: ${message}`;
1839
+ }
1840
+ let content;
1841
+ try {
1842
+ content = readFileSync3(validatedPath, "utf-8");
1843
+ } catch (error) {
1844
+ const nodeError = error;
1845
+ if (nodeError.code === "ENOENT") {
1846
+ return `path=${filePath} status=error
1643
1847
 
1644
- ${output || "ed exited with non-zero status"}`;
1848
+ Error: File not found: ${filePath}`;
1645
1849
  }
1646
- return `path=${filePath}
1850
+ const message = error instanceof Error ? error.message : String(error);
1851
+ return `path=${filePath} status=error
1647
1852
 
1648
- ${output || "(no output)"}`;
1853
+ Error reading file: ${message}`;
1854
+ }
1855
+ const match = findMatch(content, search);
1856
+ if (!match) {
1857
+ const failure = getMatchFailure(content, search);
1858
+ return formatFailure(filePath, search, failure, content);
1859
+ }
1860
+ const newContent = applyReplacement(content, match, replace);
1861
+ try {
1862
+ writeFileSync(validatedPath, newContent, "utf-8");
1649
1863
  } catch (error) {
1650
1864
  const message = error instanceof Error ? error.message : String(error);
1651
- return `path=${filePath}
1865
+ return `path=${filePath} status=error
1652
1866
 
1653
- error: ${message}`;
1867
+ Error writing file: ${message}`;
1654
1868
  }
1869
+ return `path=${filePath} status=success strategy=${match.strategy} lines=${match.startLine}-${match.endLine}
1870
+
1871
+ Replaced content successfully.
1872
+
1873
+ UPDATED FILE CONTENT:
1874
+ \`\`\`
1875
+ ${newContent}
1876
+ \`\`\``;
1655
1877
  }
1656
1878
  });
1657
1879
 
@@ -1866,6 +2088,66 @@ Wrote ${bytesWritten} bytes${dirNote}`;
1866
2088
  // src/builtins/run-command.ts
1867
2089
  import { z as z6 } from "zod";
1868
2090
  import { createGadget as createGadget6 } from "llmist";
2091
+
2092
+ // src/spawn.ts
2093
+ import { spawn as nodeSpawn } from "child_process";
2094
+ function nodeStreamToReadableStream(nodeStream) {
2095
+ if (!nodeStream) return null;
2096
+ return new ReadableStream({
2097
+ start(controller) {
2098
+ nodeStream.on("data", (chunk) => {
2099
+ controller.enqueue(new Uint8Array(chunk));
2100
+ });
2101
+ nodeStream.on("end", () => {
2102
+ controller.close();
2103
+ });
2104
+ nodeStream.on("error", (err) => {
2105
+ controller.error(err);
2106
+ });
2107
+ },
2108
+ cancel() {
2109
+ nodeStream.destroy();
2110
+ }
2111
+ });
2112
+ }
2113
+ function spawn(argv, options = {}) {
2114
+ const [command, ...args] = argv;
2115
+ const proc = nodeSpawn(command, args, {
2116
+ cwd: options.cwd,
2117
+ stdio: [
2118
+ options.stdin === "pipe" ? "pipe" : options.stdin ?? "ignore",
2119
+ options.stdout === "pipe" ? "pipe" : options.stdout ?? "ignore",
2120
+ options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
2121
+ ]
2122
+ });
2123
+ const exited = new Promise((resolve3, reject) => {
2124
+ proc.on("exit", (code) => {
2125
+ resolve3(code ?? 1);
2126
+ });
2127
+ proc.on("error", (err) => {
2128
+ reject(err);
2129
+ });
2130
+ });
2131
+ const stdin = proc.stdin ? {
2132
+ write(data) {
2133
+ proc.stdin?.write(data);
2134
+ },
2135
+ end() {
2136
+ proc.stdin?.end();
2137
+ }
2138
+ } : null;
2139
+ return {
2140
+ exited,
2141
+ stdout: nodeStreamToReadableStream(proc.stdout),
2142
+ stderr: nodeStreamToReadableStream(proc.stderr),
2143
+ stdin,
2144
+ kill() {
2145
+ proc.kill();
2146
+ }
2147
+ };
2148
+ }
2149
+
2150
+ // src/builtins/run-command.ts
1869
2151
  var runCommand = createGadget6({
1870
2152
  name: "RunCommand",
1871
2153
  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.",
@@ -6920,9 +7202,10 @@ var StatusBar = class {
6920
7202
  /**
6921
7203
  * Show rate limiting throttle indicator.
6922
7204
  * @param delayMs - Delay in milliseconds before next request
7205
+ * @param triggeredBy - Which limit(s) triggered the throttle
6923
7206
  */
6924
- showThrottling(delayMs) {
6925
- this.rateLimitState = { isThrottling: true, delayMs };
7207
+ showThrottling(delayMs, triggeredBy) {
7208
+ this.rateLimitState = { isThrottling: true, delayMs, triggeredBy };
6926
7209
  this.render(true);
6927
7210
  }
6928
7211
  /**
@@ -7176,8 +7459,14 @@ var StatusBar = class {
7176
7459
  parts.push(`${GRAY}${debugStr}${typeStr}${RESET2}`);
7177
7460
  }
7178
7461
  if (this.rateLimitState?.isThrottling) {
7179
- const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
7180
- parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${RESET2}`);
7462
+ const { triggeredBy } = this.rateLimitState;
7463
+ if (triggeredBy?.daily) {
7464
+ parts.push(`${YELLOW2}\u23F8 Daily limit, resets midnight UTC${RESET2}`);
7465
+ } else {
7466
+ const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
7467
+ const reason = triggeredBy?.rpm ? " (RPM)" : triggeredBy?.tpm ? " (TPM)" : "";
7468
+ parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${reason}${RESET2}`);
7469
+ }
7181
7470
  }
7182
7471
  if (this.retryState) {
7183
7472
  const { attemptNumber, retriesLeft } = this.retryState;
@@ -7571,9 +7860,10 @@ var TUIApp = class _TUIApp {
7571
7860
  /**
7572
7861
  * Show rate limiting throttle indicator in status bar.
7573
7862
  * @param delayMs - Delay in milliseconds before next request
7863
+ * @param triggeredBy - Which limit(s) triggered the throttle
7574
7864
  */
7575
- showThrottling(delayMs) {
7576
- this.statusBar.showThrottling(delayMs);
7865
+ showThrottling(delayMs, triggeredBy) {
7866
+ this.statusBar.showThrottling(delayMs, triggeredBy);
7577
7867
  }
7578
7868
  /**
7579
7869
  * Clear rate limiting throttle indicator from status bar.
@@ -7907,16 +8197,22 @@ async function executeAgent(promptArg, options, env, commandName) {
7907
8197
  if (context.subagentContext) return;
7908
8198
  if (tui) {
7909
8199
  const seconds = Math.ceil(context.delayMs / 1e3);
7910
- tui.showThrottling(context.delayMs);
7911
- const statsMsg = [];
7912
- if (context.stats.rpm > 0) statsMsg.push(`${context.stats.rpm} RPM`);
7913
- if (context.stats.tpm > 0)
7914
- statsMsg.push(`${Math.round(context.stats.tpm / 1e3)}K TPM`);
7915
- const statsStr = statsMsg.length > 0 ? ` (${statsMsg.join(", ")})` : "";
7916
- tui.addSystemMessage(
7917
- `Rate limit approaching${statsStr}, waiting ${seconds}s...`,
7918
- "throttle"
7919
- );
8200
+ const { triggeredBy } = context.stats;
8201
+ tui.showThrottling(context.delayMs, triggeredBy);
8202
+ let message;
8203
+ if (triggeredBy?.daily) {
8204
+ const current = Math.round(triggeredBy.daily.current / 1e3);
8205
+ const limit = Math.round(triggeredBy.daily.limit / 1e3);
8206
+ message = `Daily token limit reached (${current}K/${limit}K), waiting until midnight UTC...`;
8207
+ } else {
8208
+ const statsMsg = [];
8209
+ if (context.stats.rpm > 0) statsMsg.push(`${context.stats.rpm} RPM`);
8210
+ if (context.stats.tpm > 0)
8211
+ statsMsg.push(`${Math.round(context.stats.tpm / 1e3)}K TPM`);
8212
+ const statsStr = statsMsg.length > 0 ? ` (${statsMsg.join(", ")})` : "";
8213
+ message = `Rate limit approaching${statsStr}, waiting ${seconds}s...`;
8214
+ }
8215
+ tui.addSystemMessage(message, "throttle");
7920
8216
  setTimeout(() => tui.clearThrottling(), context.delayMs);
7921
8217
  }
7922
8218
  },
@@ -8972,7 +9268,7 @@ function registerGadgetCommand(program, env) {
8972
9268
  }
8973
9269
 
8974
9270
  // src/image-command.ts
8975
- import { writeFileSync } from "fs";
9271
+ import { writeFileSync as writeFileSync2 } from "fs";
8976
9272
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
8977
9273
  async function executeImage(promptArg, options, env) {
8978
9274
  const prompt = await resolvePrompt(promptArg, env);
@@ -8996,7 +9292,7 @@ async function executeImage(promptArg, options, env) {
8996
9292
  const imageData = result.images[0];
8997
9293
  if (imageData.b64Json) {
8998
9294
  const buffer = Buffer.from(imageData.b64Json, "base64");
8999
- writeFileSync(options.output, buffer);
9295
+ writeFileSync2(options.output, buffer);
9000
9296
  if (!options.quiet) {
9001
9297
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
9002
9298
  `);
@@ -9035,7 +9331,7 @@ function registerImageCommand(program, env, config) {
9035
9331
  }
9036
9332
 
9037
9333
  // src/init-command.ts
9038
- import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
9334
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync3 } from "fs";
9039
9335
  import { dirname as dirname2 } from "path";
9040
9336
  var STARTER_CONFIG = `# ~/.llmist/cli.toml
9041
9337
  # llmist CLI configuration file
@@ -9106,7 +9402,7 @@ async function executeInit(_options, env) {
9106
9402
  if (!existsSync3(configDir)) {
9107
9403
  mkdirSync(configDir, { recursive: true });
9108
9404
  }
9109
- writeFileSync2(configPath, STARTER_CONFIG, "utf-8");
9405
+ writeFileSync3(configPath, STARTER_CONFIG, "utf-8");
9110
9406
  env.stderr.write(`Created ${configPath}
9111
9407
  `);
9112
9408
  env.stderr.write("\n");
@@ -9703,7 +9999,7 @@ async function initSession() {
9703
9999
  }
9704
10000
 
9705
10001
  // src/speech-command.ts
9706
- import { writeFileSync as writeFileSync3 } from "fs";
10002
+ import { writeFileSync as writeFileSync4 } from "fs";
9707
10003
  var DEFAULT_SPEECH_MODEL = "tts-1";
9708
10004
  var DEFAULT_VOICE = "nova";
9709
10005
  async function executeSpeech(textArg, options, env) {
@@ -9726,7 +10022,7 @@ async function executeSpeech(textArg, options, env) {
9726
10022
  });
9727
10023
  const audioBuffer = Buffer.from(result.audio);
9728
10024
  if (options.output) {
9729
- writeFileSync3(options.output, audioBuffer);
10025
+ writeFileSync4(options.output, audioBuffer);
9730
10026
  if (!options.quiet) {
9731
10027
  env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
9732
10028
  `);