@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 +447 -151
- 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.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: "^
|
|
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": "^
|
|
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 {
|
|
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
|
-
|
|
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=
|
|
1579
|
-
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"
|
|
1580
1800
|
},
|
|
1581
1801
|
{
|
|
1582
1802
|
params: {
|
|
1583
|
-
filePath: "
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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=
|
|
1589
|
-
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"
|
|
1590
1813
|
},
|
|
1591
1814
|
{
|
|
1592
1815
|
params: {
|
|
1593
|
-
filePath: "
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
.
|
|
1597
|
-
w
|
|
1598
|
-
q`
|
|
1816
|
+
filePath: "src/app.ts",
|
|
1817
|
+
search: "unusedImport",
|
|
1818
|
+
replace: ""
|
|
1599
1819
|
},
|
|
1600
|
-
output:
|
|
1601
|
-
comment: "
|
|
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.",
|
|
@@ -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
|
|
7180
|
-
|
|
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
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
if (
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10025
|
+
writeFileSync4(options.output, audioBuffer);
|
|
9730
10026
|
if (!options.quiet) {
|
|
9731
10027
|
env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
|
|
9732
10028
|
`);
|