@llmist/cli 13.0.0 → 15.0.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: "13.0.0",
101
+ version: "15.0.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: "^13.0.0",
157
+ llmist: "^15.0.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": "^13.0.0",
171
+ "@llmist/testing": "^15.0.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`
1794
+ filePath: "src/config.ts",
1795
+ search: "const DEBUG = false;",
1796
+ replace: "const DEBUG = true;"
1567
1797
  },
1568
- output: "path=config.txt\n\n32\nkey=value\noption=true",
1569
- comment: "Print entire file contents (ed shows byte count, then content)"
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"
1570
1800
  },
1571
1801
  {
1572
1802
  params: {
1573
- filePath: "data.txt",
1574
- commands: `1,$s/foo/bar/g
1575
- w
1576
- q`
1803
+ filePath: "src/utils.ts",
1804
+ search: `function oldHelper() {
1805
+ return 1;
1806
+ }`,
1807
+ replace: `function newHelper() {
1808
+ return 2;
1809
+ }`
1577
1810
  },
1578
- output: "path=data.txt\n\n42\n42",
1579
- comment: "Replace all 'foo' with 'bar' (ed shows bytes read, then bytes written)"
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"
1580
1813
  },
1581
1814
  {
1582
1815
  params: {
1583
- filePath: "list.txt",
1584
- commands: `3d
1585
- w
1586
- q`
1816
+ filePath: "src/app.ts",
1817
+ search: "unusedImport",
1818
+ replace: ""
1587
1819
  },
1588
- output: "path=list.txt\n\n45\n28",
1589
- comment: "Delete line 3, save and quit"
1590
- },
1591
- {
1592
- params: {
1593
- filePath: "readme.txt",
1594
- commands: `$a
1595
- New last line
1596
- .
1597
- w
1598
- q`
1599
- },
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.",
@@ -8972,7 +9254,7 @@ function registerGadgetCommand(program, env) {
8972
9254
  }
8973
9255
 
8974
9256
  // src/image-command.ts
8975
- import { writeFileSync } from "fs";
9257
+ import { writeFileSync as writeFileSync2 } from "fs";
8976
9258
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
8977
9259
  async function executeImage(promptArg, options, env) {
8978
9260
  const prompt = await resolvePrompt(promptArg, env);
@@ -8996,7 +9278,7 @@ async function executeImage(promptArg, options, env) {
8996
9278
  const imageData = result.images[0];
8997
9279
  if (imageData.b64Json) {
8998
9280
  const buffer = Buffer.from(imageData.b64Json, "base64");
8999
- writeFileSync(options.output, buffer);
9281
+ writeFileSync2(options.output, buffer);
9000
9282
  if (!options.quiet) {
9001
9283
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
9002
9284
  `);
@@ -9035,7 +9317,7 @@ function registerImageCommand(program, env, config) {
9035
9317
  }
9036
9318
 
9037
9319
  // src/init-command.ts
9038
- import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
9320
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync3 } from "fs";
9039
9321
  import { dirname as dirname2 } from "path";
9040
9322
  var STARTER_CONFIG = `# ~/.llmist/cli.toml
9041
9323
  # llmist CLI configuration file
@@ -9106,7 +9388,7 @@ async function executeInit(_options, env) {
9106
9388
  if (!existsSync3(configDir)) {
9107
9389
  mkdirSync(configDir, { recursive: true });
9108
9390
  }
9109
- writeFileSync2(configPath, STARTER_CONFIG, "utf-8");
9391
+ writeFileSync3(configPath, STARTER_CONFIG, "utf-8");
9110
9392
  env.stderr.write(`Created ${configPath}
9111
9393
  `);
9112
9394
  env.stderr.write("\n");
@@ -9703,7 +9985,7 @@ async function initSession() {
9703
9985
  }
9704
9986
 
9705
9987
  // src/speech-command.ts
9706
- import { writeFileSync as writeFileSync3 } from "fs";
9988
+ import { writeFileSync as writeFileSync4 } from "fs";
9707
9989
  var DEFAULT_SPEECH_MODEL = "tts-1";
9708
9990
  var DEFAULT_VOICE = "nova";
9709
9991
  async function executeSpeech(textArg, options, env) {
@@ -9726,7 +10008,7 @@ async function executeSpeech(textArg, options, env) {
9726
10008
  });
9727
10009
  const audioBuffer = Buffer.from(result.audio);
9728
10010
  if (options.output) {
9729
- writeFileSync3(options.output, audioBuffer);
10011
+ writeFileSync4(options.output, audioBuffer);
9730
10012
  if (!options.quiet) {
9731
10013
  env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
9732
10014
  `);