@llmist/cli 14.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 +417 -135
- package/dist/cli.js.map +1 -1
- package/dist/index.js +408 -126
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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: "
|
|
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: "^
|
|
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": "^
|
|
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 {
|
|
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/
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
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
|
|
1552
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
1565
|
-
|
|
1566
|
-
|
|
1794
|
+
filePath: "src/config.ts",
|
|
1795
|
+
search: "const DEBUG = false;",
|
|
1796
|
+
replace: "const DEBUG = true;"
|
|
1567
1797
|
},
|
|
1568
|
-
output: "path=config.
|
|
1569
|
-
comment: "
|
|
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: "
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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=
|
|
1579
|
-
comment: "
|
|
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: "
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
q`
|
|
1816
|
+
filePath: "src/app.ts",
|
|
1817
|
+
search: "unusedImport",
|
|
1818
|
+
replace: ""
|
|
1587
1819
|
},
|
|
1588
|
-
output:
|
|
1589
|
-
comment: "Delete
|
|
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:
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
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
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1848
|
+
Error: File not found: ${filePath}`;
|
|
1645
1849
|
}
|
|
1646
|
-
|
|
1850
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1851
|
+
return `path=${filePath} status=error
|
|
1647
1852
|
|
|
1648
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10011
|
+
writeFileSync4(options.output, audioBuffer);
|
|
9730
10012
|
if (!options.quiet) {
|
|
9731
10013
|
env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
|
|
9732
10014
|
`);
|