mini-coder 0.0.18 → 0.0.20
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/README.md +3 -10
- package/dist/mc.js +591 -138
- package/docs/mini-coder.1.md +158 -0
- package/docs/skills.md +47 -42
- package/package.json +6 -6
- package/docs/chatgpt-subscription-auth.md +0 -68
package/dist/mc.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { writeSync } from "fs";
|
|
6
|
-
import * as
|
|
6
|
+
import * as c18 from "yoctocolors";
|
|
7
7
|
|
|
8
8
|
// src/agent/agent.ts
|
|
9
9
|
import * as c12 from "yoctocolors";
|
|
@@ -88,7 +88,7 @@ var terminal = new TerminalIO;
|
|
|
88
88
|
import * as c from "yoctocolors";
|
|
89
89
|
|
|
90
90
|
// src/cli/error-log.ts
|
|
91
|
-
import { mkdirSync } from "fs";
|
|
91
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
92
92
|
import { homedir } from "os";
|
|
93
93
|
import { join } from "path";
|
|
94
94
|
var writer = null;
|
|
@@ -98,6 +98,7 @@ function initErrorLog() {
|
|
|
98
98
|
const dirPath = join(homedir(), ".config", "mini-coder");
|
|
99
99
|
const logPath = join(dirPath, "errors.log");
|
|
100
100
|
mkdirSync(dirPath, { recursive: true });
|
|
101
|
+
writeFileSync(logPath, "");
|
|
101
102
|
writer = Bun.file(logPath).writer();
|
|
102
103
|
process.on("uncaughtException", (err) => {
|
|
103
104
|
logError(err, "uncaught");
|
|
@@ -212,6 +213,12 @@ function parseAppError(err) {
|
|
|
212
213
|
hint: "Check network or local server"
|
|
213
214
|
};
|
|
214
215
|
}
|
|
216
|
+
if (code === "ECONNRESET" || message.includes("ECONNRESET") || message.includes("socket connection was closed unexpectedly")) {
|
|
217
|
+
return {
|
|
218
|
+
headline: "Connection lost",
|
|
219
|
+
hint: "The server closed the connection \u2014 retry or switch model with /model"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
215
222
|
const firstLine = message.split(`
|
|
216
223
|
`)[0]?.trim() || "Unknown error";
|
|
217
224
|
return { headline: firstLine };
|
|
@@ -378,39 +385,53 @@ import * as c4 from "yoctocolors";
|
|
|
378
385
|
function renderInline(text) {
|
|
379
386
|
let out = "";
|
|
380
387
|
let i = 0;
|
|
388
|
+
let segStart = 0;
|
|
381
389
|
let prevWasBoldClose = false;
|
|
382
390
|
while (i < text.length) {
|
|
383
|
-
|
|
391
|
+
const ch = text[i];
|
|
392
|
+
if (ch === "`") {
|
|
384
393
|
const end = text.indexOf("`", i + 1);
|
|
385
394
|
if (end !== -1) {
|
|
395
|
+
if (i > segStart)
|
|
396
|
+
out += text.slice(segStart, i);
|
|
386
397
|
out += c4.yellow(text.slice(i, end + 1));
|
|
387
398
|
i = end + 1;
|
|
399
|
+
segStart = i;
|
|
388
400
|
prevWasBoldClose = false;
|
|
389
401
|
continue;
|
|
390
402
|
}
|
|
391
403
|
}
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
404
|
+
if (ch === "*") {
|
|
405
|
+
if (text[i + 1] === "*" && text[i + 2] !== "*") {
|
|
406
|
+
const end = text.indexOf("**", i + 2);
|
|
407
|
+
if (end !== -1) {
|
|
408
|
+
if (i > segStart)
|
|
409
|
+
out += text.slice(segStart, i);
|
|
410
|
+
out += c4.bold(text.slice(i + 2, end));
|
|
411
|
+
i = end + 2;
|
|
412
|
+
segStart = i;
|
|
413
|
+
prevWasBoldClose = true;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
399
416
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
417
|
+
if (text[i + 1] !== "*" && (prevWasBoldClose || text[i - 1] !== "*")) {
|
|
418
|
+
const end = text.indexOf("*", i + 1);
|
|
419
|
+
if (end !== -1 && text[end - 1] !== "*") {
|
|
420
|
+
if (i > segStart)
|
|
421
|
+
out += text.slice(segStart, i);
|
|
422
|
+
out += c4.dim(text.slice(i + 1, end));
|
|
423
|
+
i = end + 1;
|
|
424
|
+
segStart = i;
|
|
425
|
+
prevWasBoldClose = false;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
408
428
|
}
|
|
409
429
|
}
|
|
410
|
-
out += text[i];
|
|
411
|
-
i++;
|
|
412
430
|
prevWasBoldClose = false;
|
|
431
|
+
i++;
|
|
413
432
|
}
|
|
433
|
+
if (segStart < text.length)
|
|
434
|
+
out += text.slice(segStart);
|
|
414
435
|
return out;
|
|
415
436
|
}
|
|
416
437
|
function renderLine(raw, inFence) {
|
|
@@ -474,6 +495,39 @@ function renderMarkdown(text) {
|
|
|
474
495
|
`);
|
|
475
496
|
}
|
|
476
497
|
|
|
498
|
+
// src/cli/reasoning.ts
|
|
499
|
+
function normalizeReasoningDelta(delta) {
|
|
500
|
+
return delta.replace(/\r\n?/g, `
|
|
501
|
+
`);
|
|
502
|
+
}
|
|
503
|
+
function normalizeReasoningText(text) {
|
|
504
|
+
const normalized = normalizeReasoningDelta(text);
|
|
505
|
+
const lines = normalized.split(`
|
|
506
|
+
`).map((line) => line.replace(/[ \t]+$/g, ""));
|
|
507
|
+
let start = 0;
|
|
508
|
+
while (start < lines.length && lines[start]?.trim() === "")
|
|
509
|
+
start++;
|
|
510
|
+
let end = lines.length - 1;
|
|
511
|
+
while (end >= start && lines[end]?.trim() === "")
|
|
512
|
+
end--;
|
|
513
|
+
if (start > end)
|
|
514
|
+
return "";
|
|
515
|
+
const compact = [];
|
|
516
|
+
let blankRun = 0;
|
|
517
|
+
for (const line of lines.slice(start, end + 1)) {
|
|
518
|
+
if (line.trim() === "") {
|
|
519
|
+
blankRun += 1;
|
|
520
|
+
if (blankRun <= 1)
|
|
521
|
+
compact.push("");
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
blankRun = 0;
|
|
525
|
+
compact.push(line);
|
|
526
|
+
}
|
|
527
|
+
return compact.join(`
|
|
528
|
+
`);
|
|
529
|
+
}
|
|
530
|
+
|
|
477
531
|
// src/cli/tool-render.ts
|
|
478
532
|
import { homedir as homedir2 } from "os";
|
|
479
533
|
import * as c5 from "yoctocolors";
|
|
@@ -698,7 +752,19 @@ function renderToolResult(toolName, result, isError, _toolCallId) {
|
|
|
698
752
|
if (toolName === "subagent") {
|
|
699
753
|
const r = result;
|
|
700
754
|
const label = r.agentName ? ` ${c5.dim(c5.cyan(`[@${r.agentName}]`))}` : "";
|
|
701
|
-
writeln(` ${
|
|
755
|
+
writeln(` ${G.agent}${label} ${c5.dim(`subagent done (${r.inputTokens ?? 0}in / ${r.outputTokens ?? 0}out tokens)`)}`);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (toolName === "readSkill") {
|
|
759
|
+
const r = result;
|
|
760
|
+
if (!r.skill) {
|
|
761
|
+
writeln(` ${G.info} ${c5.dim("skill-auto-load miss")}`);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const name = r.skill.name ?? "(unknown)";
|
|
765
|
+
const source = r.skill.source ?? "unknown";
|
|
766
|
+
const description = r.skill.description?.trim();
|
|
767
|
+
writeln(` ${G.info} ${c5.dim(`skill-auto-loaded name=${name} source=${source}${description ? ` description=${JSON.stringify(description)}` : ""}`)}`);
|
|
702
768
|
return;
|
|
703
769
|
}
|
|
704
770
|
if (toolName === "webSearch") {
|
|
@@ -770,16 +836,30 @@ async function renderTurn(events, spinner, opts) {
|
|
|
770
836
|
let accumulatedText = "";
|
|
771
837
|
let accumulatedReasoning = "";
|
|
772
838
|
let inFence = false;
|
|
839
|
+
let reasoningBlankLineRun = 0;
|
|
773
840
|
let inputTokens = 0;
|
|
774
841
|
let outputTokens = 0;
|
|
775
842
|
let contextTokens = 0;
|
|
776
843
|
let newMessages = [];
|
|
844
|
+
function renderSingleLine(raw) {
|
|
845
|
+
const source = inReasoning ? raw.replace(/[ \t]+$/g, "") : raw;
|
|
846
|
+
if (inReasoning && source.trim() === "") {
|
|
847
|
+
reasoningBlankLineRun += 1;
|
|
848
|
+
if (reasoningBlankLineRun > 1)
|
|
849
|
+
return null;
|
|
850
|
+
} else if (inReasoning) {
|
|
851
|
+
reasoningBlankLineRun = 0;
|
|
852
|
+
}
|
|
853
|
+
const rendered = renderLine(source, inFence);
|
|
854
|
+
inFence = rendered.inFence;
|
|
855
|
+
return inReasoning ? `${c6.dim("\u2502 ")}${rendered.output}` : rendered.output;
|
|
856
|
+
}
|
|
777
857
|
function renderAndWrite(raw, endWithNewline) {
|
|
778
858
|
spinner.stop();
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
write(
|
|
859
|
+
const out = renderSingleLine(raw);
|
|
860
|
+
if (out === null)
|
|
861
|
+
return;
|
|
862
|
+
write(out);
|
|
783
863
|
if (endWithNewline) {
|
|
784
864
|
write(`
|
|
785
865
|
`);
|
|
@@ -791,17 +871,30 @@ async function renderTurn(events, spinner, opts) {
|
|
|
791
871
|
writeln();
|
|
792
872
|
inText = false;
|
|
793
873
|
inReasoning = false;
|
|
874
|
+
reasoningBlankLineRun = 0;
|
|
794
875
|
}
|
|
795
876
|
}
|
|
796
877
|
function flushCompleteLines() {
|
|
878
|
+
let start = 0;
|
|
797
879
|
let boundary = rawBuffer.indexOf(`
|
|
798
|
-
|
|
880
|
+
`, start);
|
|
881
|
+
if (boundary === -1)
|
|
882
|
+
return;
|
|
883
|
+
spinner.stop();
|
|
884
|
+
let batchOutput = "";
|
|
799
885
|
while (boundary !== -1) {
|
|
800
|
-
|
|
801
|
-
|
|
886
|
+
const raw = rawBuffer.slice(start, boundary);
|
|
887
|
+
start = boundary + 1;
|
|
802
888
|
boundary = rawBuffer.indexOf(`
|
|
803
|
-
|
|
889
|
+
`, start);
|
|
890
|
+
const out = renderSingleLine(raw);
|
|
891
|
+
if (out !== null)
|
|
892
|
+
batchOutput += `${out}
|
|
893
|
+
`;
|
|
804
894
|
}
|
|
895
|
+
rawBuffer = start > 0 ? rawBuffer.slice(start) : rawBuffer;
|
|
896
|
+
if (batchOutput)
|
|
897
|
+
write(batchOutput);
|
|
805
898
|
}
|
|
806
899
|
function flushAll() {
|
|
807
900
|
if (!rawBuffer) {
|
|
@@ -827,7 +920,8 @@ async function renderTurn(events, spinner, opts) {
|
|
|
827
920
|
break;
|
|
828
921
|
}
|
|
829
922
|
case "reasoning-delta": {
|
|
830
|
-
|
|
923
|
+
const delta = normalizeReasoningDelta(event.delta);
|
|
924
|
+
accumulatedReasoning += delta;
|
|
831
925
|
if (!showReasoning) {
|
|
832
926
|
break;
|
|
833
927
|
}
|
|
@@ -836,9 +930,10 @@ async function renderTurn(events, spinner, opts) {
|
|
|
836
930
|
flushAnyText();
|
|
837
931
|
}
|
|
838
932
|
spinner.stop();
|
|
933
|
+
writeln(`${G.info} ${c6.dim("reasoning")}`);
|
|
839
934
|
inReasoning = true;
|
|
840
935
|
}
|
|
841
|
-
rawBuffer +=
|
|
936
|
+
rawBuffer += delta;
|
|
842
937
|
flushCompleteLines();
|
|
843
938
|
break;
|
|
844
939
|
}
|
|
@@ -855,9 +950,18 @@ async function renderTurn(events, spinner, opts) {
|
|
|
855
950
|
spinner.start("thinking");
|
|
856
951
|
break;
|
|
857
952
|
}
|
|
953
|
+
case "context-pruned": {
|
|
954
|
+
flushAnyText();
|
|
955
|
+
spinner.stop();
|
|
956
|
+
writeln(`${G.info} ${c6.dim(`context-pruned mode=${event.mode} removed_messages=${event.removedMessageCount} removed_bytes=${event.removedBytes} messages_before=${event.beforeMessageCount} messages_after=${event.afterMessageCount}`)}`);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
858
959
|
case "turn-complete": {
|
|
960
|
+
const hadContent = inText || inReasoning;
|
|
859
961
|
flushAnyText();
|
|
860
962
|
spinner.stop();
|
|
963
|
+
if (!hadContent)
|
|
964
|
+
writeln();
|
|
861
965
|
inputTokens = event.inputTokens;
|
|
862
966
|
outputTokens = event.outputTokens;
|
|
863
967
|
contextTokens = event.contextTokens;
|
|
@@ -888,13 +992,13 @@ async function renderTurn(events, spinner, opts) {
|
|
|
888
992
|
outputTokens,
|
|
889
993
|
contextTokens,
|
|
890
994
|
newMessages,
|
|
891
|
-
reasoningText: accumulatedReasoning
|
|
995
|
+
reasoningText: normalizeReasoningText(accumulatedReasoning)
|
|
892
996
|
};
|
|
893
997
|
}
|
|
894
998
|
|
|
895
999
|
// src/cli/output.ts
|
|
896
1000
|
var HOME2 = homedir3();
|
|
897
|
-
var PACKAGE_VERSION = "0.0.
|
|
1001
|
+
var PACKAGE_VERSION = "0.0.20";
|
|
898
1002
|
function tildePath(p) {
|
|
899
1003
|
return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
|
|
900
1004
|
}
|
|
@@ -1295,6 +1399,21 @@ function getDb() {
|
|
|
1295
1399
|
}
|
|
1296
1400
|
return _db;
|
|
1297
1401
|
}
|
|
1402
|
+
var MAX_SESSIONS = 100;
|
|
1403
|
+
var MAX_PROMPT_HISTORY = 500;
|
|
1404
|
+
function pruneOldData() {
|
|
1405
|
+
const db = getDb();
|
|
1406
|
+
const deletedSessions = db.run(`DELETE FROM sessions WHERE id NOT IN (
|
|
1407
|
+
SELECT id FROM sessions ORDER BY updated_at DESC LIMIT ?
|
|
1408
|
+
)`, [MAX_SESSIONS]).changes;
|
|
1409
|
+
const deletedHistory = db.run(`DELETE FROM prompt_history WHERE id NOT IN (
|
|
1410
|
+
SELECT id FROM prompt_history ORDER BY id DESC LIMIT ?
|
|
1411
|
+
)`, [MAX_PROMPT_HISTORY]).changes;
|
|
1412
|
+
if (deletedSessions > 0 || deletedHistory > 0) {
|
|
1413
|
+
db.exec("VACUUM;");
|
|
1414
|
+
}
|
|
1415
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
1416
|
+
}
|
|
1298
1417
|
// src/session/db/mcp-repo.ts
|
|
1299
1418
|
function listMcpServers() {
|
|
1300
1419
|
return getDb().query("SELECT name, transport, url, command, args, env FROM mcp_servers ORDER BY name").all();
|
|
@@ -1584,7 +1703,7 @@ function deleteAllSnapshots(sessionId) {
|
|
|
1584
1703
|
import * as c11 from "yoctocolors";
|
|
1585
1704
|
|
|
1586
1705
|
// src/cli/input.ts
|
|
1587
|
-
import { join as
|
|
1706
|
+
import { join as join5, relative } from "path";
|
|
1588
1707
|
import * as c9 from "yoctocolors";
|
|
1589
1708
|
|
|
1590
1709
|
// src/cli/image-types.ts
|
|
@@ -1623,21 +1742,198 @@ async function loadImageFile(filePath) {
|
|
|
1623
1742
|
}
|
|
1624
1743
|
|
|
1625
1744
|
// src/cli/skills.ts
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1745
|
+
import {
|
|
1746
|
+
closeSync,
|
|
1747
|
+
existsSync as existsSync3,
|
|
1748
|
+
openSync,
|
|
1749
|
+
readdirSync as readdirSync2,
|
|
1750
|
+
readFileSync as readFileSync2,
|
|
1751
|
+
readSync,
|
|
1752
|
+
statSync as statSync2
|
|
1753
|
+
} from "fs";
|
|
1754
|
+
import { homedir as homedir6 } from "os";
|
|
1755
|
+
import { dirname, join as join4, resolve } from "path";
|
|
1756
|
+
var MAX_FRONTMATTER_BYTES = 64 * 1024;
|
|
1757
|
+
var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1758
|
+
var MIN_SKILL_NAME_LENGTH = 1;
|
|
1759
|
+
var MAX_SKILL_NAME_LENGTH = 64;
|
|
1760
|
+
var warnedInvalidSkills = new Set;
|
|
1761
|
+
function parseSkillFrontmatter(filePath) {
|
|
1762
|
+
let fd = null;
|
|
1763
|
+
try {
|
|
1764
|
+
fd = openSync(filePath, "r");
|
|
1765
|
+
const chunk = Buffer.allocUnsafe(MAX_FRONTMATTER_BYTES);
|
|
1766
|
+
const bytesRead = readSync(fd, chunk, 0, MAX_FRONTMATTER_BYTES, 0);
|
|
1767
|
+
const text = chunk.toString("utf8", 0, bytesRead);
|
|
1768
|
+
const lines = text.split(`
|
|
1769
|
+
`);
|
|
1770
|
+
if ((lines[0] ?? "").trim() !== "---")
|
|
1771
|
+
return {};
|
|
1772
|
+
const meta = {};
|
|
1773
|
+
for (let i = 1;i < lines.length; i++) {
|
|
1774
|
+
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
1775
|
+
if (line.trim() === "---")
|
|
1776
|
+
break;
|
|
1777
|
+
const colon = line.indexOf(":");
|
|
1778
|
+
if (colon === -1)
|
|
1779
|
+
continue;
|
|
1780
|
+
const key = line.slice(0, colon).trim();
|
|
1781
|
+
const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
1782
|
+
if (key === "name")
|
|
1783
|
+
meta.name = value;
|
|
1784
|
+
if (key === "description")
|
|
1785
|
+
meta.description = value;
|
|
1786
|
+
}
|
|
1787
|
+
return meta;
|
|
1788
|
+
} catch {
|
|
1789
|
+
return {};
|
|
1790
|
+
} finally {
|
|
1791
|
+
if (fd !== null)
|
|
1792
|
+
closeSync(fd);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function getCandidateFrontmatter(candidate) {
|
|
1796
|
+
if (!candidate.frontmatter) {
|
|
1797
|
+
candidate.frontmatter = parseSkillFrontmatter(candidate.filePath);
|
|
1798
|
+
}
|
|
1799
|
+
return candidate.frontmatter;
|
|
1800
|
+
}
|
|
1801
|
+
function candidateConflictName(candidate) {
|
|
1802
|
+
return getCandidateFrontmatter(candidate).name?.trim() || candidate.folderName;
|
|
1803
|
+
}
|
|
1804
|
+
function findGitBoundary(cwd) {
|
|
1805
|
+
let current = resolve(cwd);
|
|
1806
|
+
while (true) {
|
|
1807
|
+
if (existsSync3(join4(current, ".git")))
|
|
1808
|
+
return current;
|
|
1809
|
+
const parent = dirname(current);
|
|
1810
|
+
if (parent === current)
|
|
1811
|
+
return null;
|
|
1812
|
+
current = parent;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function localSearchRoots(cwd) {
|
|
1816
|
+
const start = resolve(cwd);
|
|
1817
|
+
const stop = findGitBoundary(start);
|
|
1818
|
+
if (!stop)
|
|
1819
|
+
return [start];
|
|
1820
|
+
const roots = [];
|
|
1821
|
+
let current = start;
|
|
1822
|
+
while (true) {
|
|
1823
|
+
roots.push(current);
|
|
1824
|
+
if (current === stop)
|
|
1825
|
+
break;
|
|
1826
|
+
const parent = dirname(current);
|
|
1827
|
+
if (parent === current)
|
|
1828
|
+
break;
|
|
1829
|
+
current = parent;
|
|
1830
|
+
}
|
|
1831
|
+
return roots;
|
|
1832
|
+
}
|
|
1833
|
+
function listSkillCandidates(skillsDir, source, rootPath) {
|
|
1834
|
+
if (!existsSync3(skillsDir))
|
|
1835
|
+
return [];
|
|
1836
|
+
let entries;
|
|
1837
|
+
try {
|
|
1838
|
+
entries = readdirSync2(skillsDir).sort((a, b) => a.localeCompare(b));
|
|
1839
|
+
} catch {
|
|
1840
|
+
return [];
|
|
1841
|
+
}
|
|
1842
|
+
const candidates = [];
|
|
1843
|
+
for (const entry of entries) {
|
|
1844
|
+
const skillDir = join4(skillsDir, entry);
|
|
1845
|
+
try {
|
|
1846
|
+
if (!statSync2(skillDir).isDirectory())
|
|
1847
|
+
continue;
|
|
1848
|
+
} catch {
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
const filePath = join4(skillDir, "SKILL.md");
|
|
1852
|
+
if (!existsSync3(filePath))
|
|
1853
|
+
continue;
|
|
1854
|
+
candidates.push({
|
|
1855
|
+
folderName: entry,
|
|
1856
|
+
filePath,
|
|
1857
|
+
rootPath,
|
|
1638
1858
|
source
|
|
1639
|
-
})
|
|
1640
|
-
}
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
return candidates;
|
|
1862
|
+
}
|
|
1863
|
+
function warnInvalidSkill(filePath, reason) {
|
|
1864
|
+
const key = `${filePath}:${reason}`;
|
|
1865
|
+
if (warnedInvalidSkills.has(key))
|
|
1866
|
+
return;
|
|
1867
|
+
warnedInvalidSkills.add(key);
|
|
1868
|
+
writeln(`${G.warn} skipping invalid skill ${filePath}: ${reason}`);
|
|
1869
|
+
}
|
|
1870
|
+
function validateSkill(candidate) {
|
|
1871
|
+
const meta = getCandidateFrontmatter(candidate);
|
|
1872
|
+
const name = meta.name?.trim();
|
|
1873
|
+
const description = meta.description?.trim();
|
|
1874
|
+
if (!name) {
|
|
1875
|
+
warnInvalidSkill(candidate.filePath, "frontmatter field `name` is required");
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
if (!description) {
|
|
1879
|
+
warnInvalidSkill(candidate.filePath, "frontmatter field `description` is required");
|
|
1880
|
+
return null;
|
|
1881
|
+
}
|
|
1882
|
+
if (name.length < MIN_SKILL_NAME_LENGTH || name.length > MAX_SKILL_NAME_LENGTH) {
|
|
1883
|
+
warnInvalidSkill(candidate.filePath, `name must be ${MIN_SKILL_NAME_LENGTH}-${MAX_SKILL_NAME_LENGTH} characters`);
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
if (!SKILL_NAME_RE.test(name)) {
|
|
1887
|
+
warnInvalidSkill(candidate.filePath, "name must match lowercase alnum + hyphen format");
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
return {
|
|
1891
|
+
name,
|
|
1892
|
+
description,
|
|
1893
|
+
source: candidate.source,
|
|
1894
|
+
rootPath: candidate.rootPath,
|
|
1895
|
+
filePath: candidate.filePath
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
function allSkillCandidates(cwd, homeDir) {
|
|
1899
|
+
const home = homeDir ?? homedir6();
|
|
1900
|
+
const localRootsNearToFar = localSearchRoots(cwd);
|
|
1901
|
+
const ordered = [];
|
|
1902
|
+
const globalClaude = listSkillCandidates(join4(home, ".claude", "skills"), "global", home);
|
|
1903
|
+
const globalAgents = listSkillCandidates(join4(home, ".agents", "skills"), "global", home);
|
|
1904
|
+
warnConventionConflicts("skills", "global", globalAgents.map((skill) => candidateConflictName(skill)), globalClaude.map((skill) => candidateConflictName(skill)));
|
|
1905
|
+
ordered.push(...globalClaude, ...globalAgents);
|
|
1906
|
+
for (const root of [...localRootsNearToFar].reverse()) {
|
|
1907
|
+
const localClaude = listSkillCandidates(join4(root, ".claude", "skills"), "local", root);
|
|
1908
|
+
const localAgents = listSkillCandidates(join4(root, ".agents", "skills"), "local", root);
|
|
1909
|
+
warnConventionConflicts("skills", "local", localAgents.map((skill) => candidateConflictName(skill)), localClaude.map((skill) => candidateConflictName(skill)));
|
|
1910
|
+
ordered.push(...localClaude, ...localAgents);
|
|
1911
|
+
}
|
|
1912
|
+
return ordered;
|
|
1913
|
+
}
|
|
1914
|
+
function loadSkillsIndex(cwd, homeDir) {
|
|
1915
|
+
const index = new Map;
|
|
1916
|
+
for (const candidate of allSkillCandidates(cwd, homeDir)) {
|
|
1917
|
+
const skill = validateSkill(candidate);
|
|
1918
|
+
if (!skill)
|
|
1919
|
+
continue;
|
|
1920
|
+
index.set(skill.name, skill);
|
|
1921
|
+
}
|
|
1922
|
+
return index;
|
|
1923
|
+
}
|
|
1924
|
+
function loadSkillContentFromMeta(skill) {
|
|
1925
|
+
try {
|
|
1926
|
+
const content = readFileSync2(skill.filePath, "utf-8");
|
|
1927
|
+
return { name: skill.name, content, source: skill.source };
|
|
1928
|
+
} catch {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
function loadSkillContent(name, cwd, homeDir) {
|
|
1933
|
+
const skill = loadSkillsIndex(cwd, homeDir).get(name);
|
|
1934
|
+
if (!skill)
|
|
1935
|
+
return null;
|
|
1936
|
+
return loadSkillContentFromMeta(skill);
|
|
1641
1937
|
}
|
|
1642
1938
|
|
|
1643
1939
|
// src/cli/input.ts
|
|
@@ -1674,7 +1970,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
1674
1970
|
const query = prefix.startsWith("@") ? prefix.slice(1) : prefix;
|
|
1675
1971
|
const results = [];
|
|
1676
1972
|
const MAX = 10;
|
|
1677
|
-
const skills =
|
|
1973
|
+
const skills = loadSkillsIndex(cwd);
|
|
1678
1974
|
for (const [name] of skills) {
|
|
1679
1975
|
if (results.length >= MAX)
|
|
1680
1976
|
break;
|
|
@@ -1693,7 +1989,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
1693
1989
|
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
1694
1990
|
if (file.includes("node_modules") || file.includes(".git"))
|
|
1695
1991
|
continue;
|
|
1696
|
-
results.push(`@${relative(cwd,
|
|
1992
|
+
results.push(`@${relative(cwd, join5(cwd, file))}`);
|
|
1697
1993
|
if (results.length >= MAX)
|
|
1698
1994
|
break;
|
|
1699
1995
|
}
|
|
@@ -1715,7 +2011,7 @@ async function tryExtractImageFromPaste(pasted, cwd) {
|
|
|
1715
2011
|
}
|
|
1716
2012
|
}
|
|
1717
2013
|
if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
|
|
1718
|
-
const filePath = trimmed.startsWith("/") ? trimmed :
|
|
2014
|
+
const filePath = trimmed.startsWith("/") ? trimmed : join5(cwd, trimmed);
|
|
1719
2015
|
const attachment = await loadImageFile(filePath);
|
|
1720
2016
|
if (attachment) {
|
|
1721
2017
|
const name = filePath.split("/").pop() ?? trimmed;
|
|
@@ -1761,6 +2057,17 @@ function getTurnControlAction(chunk) {
|
|
|
1761
2057
|
}
|
|
1762
2058
|
return null;
|
|
1763
2059
|
}
|
|
2060
|
+
function getSubagentControlAction(chunk) {
|
|
2061
|
+
for (const byte of chunk) {
|
|
2062
|
+
if (byte === CTRL_C_BYTE)
|
|
2063
|
+
return "quit";
|
|
2064
|
+
}
|
|
2065
|
+
for (const byte of chunk) {
|
|
2066
|
+
if (byte === ESC_BYTE)
|
|
2067
|
+
return "cancel";
|
|
2068
|
+
}
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
1764
2071
|
function exitOnCtrlC(opts) {
|
|
1765
2072
|
if (opts?.printNewline)
|
|
1766
2073
|
process.stdout.write(`
|
|
@@ -1773,15 +2080,16 @@ function exitOnCtrlC(opts) {
|
|
|
1773
2080
|
terminal.restoreTerminal();
|
|
1774
2081
|
process.exit(130);
|
|
1775
2082
|
}
|
|
1776
|
-
function watchForCancel(abortController) {
|
|
2083
|
+
function watchForCancel(abortController, options) {
|
|
1777
2084
|
if (!terminal.isTTY)
|
|
1778
2085
|
return () => {};
|
|
1779
2086
|
const onCancel = () => {
|
|
1780
2087
|
cleanup();
|
|
1781
2088
|
abortController.abort();
|
|
1782
2089
|
};
|
|
2090
|
+
const getAction = options?.allowSubagentEsc ? getSubagentControlAction : getTurnControlAction;
|
|
1783
2091
|
const onData = (chunk) => {
|
|
1784
|
-
const action =
|
|
2092
|
+
const action = getAction(chunk);
|
|
1785
2093
|
if (action === "cancel") {
|
|
1786
2094
|
onCancel();
|
|
1787
2095
|
return;
|
|
@@ -2193,18 +2501,47 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
2193
2501
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
2194
2502
|
|
|
2195
2503
|
// src/llm-api/api-log.ts
|
|
2196
|
-
import { mkdirSync as mkdirSync3 } from "fs";
|
|
2197
|
-
import { homedir as
|
|
2198
|
-
import { join as
|
|
2504
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2505
|
+
import { homedir as homedir7 } from "os";
|
|
2506
|
+
import { join as join6 } from "path";
|
|
2199
2507
|
var writer2 = null;
|
|
2508
|
+
var MAX_ENTRY_BYTES = 8 * 1024;
|
|
2200
2509
|
function initApiLog() {
|
|
2201
2510
|
if (writer2)
|
|
2202
2511
|
return;
|
|
2203
|
-
const dirPath =
|
|
2204
|
-
const logPath =
|
|
2512
|
+
const dirPath = join6(homedir7(), ".config", "mini-coder");
|
|
2513
|
+
const logPath = join6(dirPath, "api.log");
|
|
2205
2514
|
mkdirSync3(dirPath, { recursive: true });
|
|
2515
|
+
writeFileSync2(logPath, "");
|
|
2206
2516
|
writer2 = Bun.file(logPath).writer();
|
|
2207
2517
|
}
|
|
2518
|
+
function isObject2(v) {
|
|
2519
|
+
return typeof v === "object" && v !== null;
|
|
2520
|
+
}
|
|
2521
|
+
var LOG_DROP_KEYS = new Set([
|
|
2522
|
+
"requestBodyValues",
|
|
2523
|
+
"responseBody",
|
|
2524
|
+
"responseHeaders",
|
|
2525
|
+
"stack"
|
|
2526
|
+
]);
|
|
2527
|
+
function sanitizeForLog(data) {
|
|
2528
|
+
if (!isObject2(data))
|
|
2529
|
+
return data;
|
|
2530
|
+
const result = {};
|
|
2531
|
+
for (const key in data) {
|
|
2532
|
+
if (LOG_DROP_KEYS.has(key))
|
|
2533
|
+
continue;
|
|
2534
|
+
const value = data[key];
|
|
2535
|
+
if (key === "errors" && Array.isArray(value)) {
|
|
2536
|
+
result[key] = value.map((e) => sanitizeForLog(e));
|
|
2537
|
+
} else if (key === "lastError" && isObject2(value)) {
|
|
2538
|
+
result[key] = sanitizeForLog(value);
|
|
2539
|
+
} else {
|
|
2540
|
+
result[key] = value;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
return result;
|
|
2544
|
+
}
|
|
2208
2545
|
function logApiEvent(event, data) {
|
|
2209
2546
|
if (!writer2)
|
|
2210
2547
|
return;
|
|
@@ -2213,7 +2550,13 @@ function logApiEvent(event, data) {
|
|
|
2213
2550
|
`;
|
|
2214
2551
|
if (data !== undefined) {
|
|
2215
2552
|
try {
|
|
2216
|
-
|
|
2553
|
+
const safe = sanitizeForLog(data);
|
|
2554
|
+
let serialized = JSON.stringify(safe, null, 2);
|
|
2555
|
+
if (serialized.length > MAX_ENTRY_BYTES) {
|
|
2556
|
+
serialized = `${serialized.slice(0, MAX_ENTRY_BYTES)}
|
|
2557
|
+
\u2026truncated`;
|
|
2558
|
+
}
|
|
2559
|
+
entry += serialized.split(`
|
|
2217
2560
|
`).map((line) => ` ${line}`).join(`
|
|
2218
2561
|
`);
|
|
2219
2562
|
entry += `
|
|
@@ -3162,14 +3505,14 @@ function applyContextPruning(messages, mode) {
|
|
|
3162
3505
|
return pruneMessages({
|
|
3163
3506
|
messages,
|
|
3164
3507
|
reasoning: "before-last-message",
|
|
3165
|
-
toolCalls: "before-last-
|
|
3508
|
+
toolCalls: "before-last-20-messages",
|
|
3166
3509
|
emptyMessages: "remove"
|
|
3167
3510
|
});
|
|
3168
3511
|
}
|
|
3169
3512
|
return pruneMessages({
|
|
3170
3513
|
messages,
|
|
3171
3514
|
reasoning: "before-last-message",
|
|
3172
|
-
toolCalls: "before-last-
|
|
3515
|
+
toolCalls: "before-last-40-messages",
|
|
3173
3516
|
emptyMessages: "remove"
|
|
3174
3517
|
});
|
|
3175
3518
|
}
|
|
@@ -3603,9 +3946,23 @@ async function* runTurn(options) {
|
|
|
3603
3946
|
if (openAIItemIdsStrippedMessages !== gptSanitizedMessages) {
|
|
3604
3947
|
logApiEvent("openai item ids stripped from history", { modelString });
|
|
3605
3948
|
}
|
|
3606
|
-
|
|
3949
|
+
const prePruneDiagnostics = getMessageDiagnostics(openAIItemIdsStrippedMessages);
|
|
3950
|
+
logApiEvent("turn context pre-prune", prePruneDiagnostics);
|
|
3607
3951
|
const prunedMessages = applyContextPruning(openAIItemIdsStrippedMessages, pruningMode);
|
|
3608
|
-
|
|
3952
|
+
const postPruneDiagnostics = getMessageDiagnostics(prunedMessages);
|
|
3953
|
+
logApiEvent("turn context post-prune", postPruneDiagnostics);
|
|
3954
|
+
if ((pruningMode === "balanced" || pruningMode === "aggressive") && (postPruneDiagnostics.messageCount < prePruneDiagnostics.messageCount || postPruneDiagnostics.totalBytes < prePruneDiagnostics.totalBytes)) {
|
|
3955
|
+
yield {
|
|
3956
|
+
type: "context-pruned",
|
|
3957
|
+
mode: pruningMode,
|
|
3958
|
+
beforeMessageCount: prePruneDiagnostics.messageCount,
|
|
3959
|
+
afterMessageCount: postPruneDiagnostics.messageCount,
|
|
3960
|
+
removedMessageCount: prePruneDiagnostics.messageCount - postPruneDiagnostics.messageCount,
|
|
3961
|
+
beforeTotalBytes: prePruneDiagnostics.totalBytes,
|
|
3962
|
+
afterTotalBytes: postPruneDiagnostics.totalBytes,
|
|
3963
|
+
removedBytes: prePruneDiagnostics.totalBytes - postPruneDiagnostics.totalBytes
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3609
3966
|
const turnMessages = compactToolResultPayloads(prunedMessages, toolResultPayloadCapBytes);
|
|
3610
3967
|
if (turnMessages !== prunedMessages) {
|
|
3611
3968
|
logApiEvent("turn context post-compaction", {
|
|
@@ -3817,10 +4174,10 @@ function getMostRecentSession() {
|
|
|
3817
4174
|
|
|
3818
4175
|
// src/tools/snapshot.ts
|
|
3819
4176
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
3820
|
-
import { isAbsolute, join as
|
|
4177
|
+
import { isAbsolute, join as join7, relative as relative2 } from "path";
|
|
3821
4178
|
async function snapshotBeforeEdit(cwd, sessionId, turnIndex, filePath, snappedPaths) {
|
|
3822
4179
|
try {
|
|
3823
|
-
const absPath = isAbsolute(filePath) ? filePath :
|
|
4180
|
+
const absPath = isAbsolute(filePath) ? filePath : join7(cwd, filePath);
|
|
3824
4181
|
const relPath = isAbsolute(filePath) ? relative2(cwd, filePath) : filePath;
|
|
3825
4182
|
if (snappedPaths.has(relPath))
|
|
3826
4183
|
return;
|
|
@@ -3842,7 +4199,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
3842
4199
|
return { restored: false, reason: "not-found" };
|
|
3843
4200
|
let anyFailed = false;
|
|
3844
4201
|
for (const file of files) {
|
|
3845
|
-
const absPath =
|
|
4202
|
+
const absPath = join7(cwd, file.path);
|
|
3846
4203
|
if (!file.existed) {
|
|
3847
4204
|
try {
|
|
3848
4205
|
if (await Bun.file(absPath).exists()) {
|
|
@@ -3871,24 +4228,24 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
3871
4228
|
}
|
|
3872
4229
|
|
|
3873
4230
|
// src/agent/system-prompt.ts
|
|
3874
|
-
import { existsSync as
|
|
3875
|
-
import { homedir as
|
|
3876
|
-
import { join as
|
|
4231
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
4232
|
+
import { homedir as homedir8 } from "os";
|
|
4233
|
+
import { join as join8 } from "path";
|
|
3877
4234
|
function tryReadFile(p) {
|
|
3878
|
-
if (!
|
|
4235
|
+
if (!existsSync4(p))
|
|
3879
4236
|
return null;
|
|
3880
4237
|
try {
|
|
3881
|
-
return
|
|
4238
|
+
return readFileSync3(p, "utf-8");
|
|
3882
4239
|
} catch {
|
|
3883
4240
|
return null;
|
|
3884
4241
|
}
|
|
3885
4242
|
}
|
|
3886
4243
|
function loadGlobalContextFile(homeDir) {
|
|
3887
|
-
const globalDir =
|
|
3888
|
-
return tryReadFile(
|
|
4244
|
+
const globalDir = join8(homeDir, ".agents");
|
|
4245
|
+
return tryReadFile(join8(globalDir, "AGENTS.md")) ?? tryReadFile(join8(globalDir, "CLAUDE.md"));
|
|
3889
4246
|
}
|
|
3890
4247
|
function loadLocalContextFile(cwd) {
|
|
3891
|
-
return tryReadFile(
|
|
4248
|
+
return tryReadFile(join8(cwd, ".agents", "AGENTS.md")) ?? tryReadFile(join8(cwd, "CLAUDE.md")) ?? tryReadFile(join8(cwd, "AGENTS.md"));
|
|
3892
4249
|
}
|
|
3893
4250
|
var AUTONOMY = `
|
|
3894
4251
|
|
|
@@ -3929,7 +4286,7 @@ var FINAL_MESSAGE = `
|
|
|
3929
4286
|
- If verification could not be run, say so clearly.`;
|
|
3930
4287
|
var SUBAGENT_DELEGATION = `You are running as a subagent. Complete the task you have been given directly using your tools. Do not spawn further subagents unless the subtask is unambiguously separable and self-contained.`;
|
|
3931
4288
|
function buildSystemPrompt(sessionTimeAnchor, cwd, extraSystemPrompt, isSubagent, homeDir) {
|
|
3932
|
-
const globalContext = loadGlobalContextFile(homeDir ??
|
|
4289
|
+
const globalContext = loadGlobalContextFile(homeDir ?? homedir8());
|
|
3933
4290
|
const localContext = loadLocalContextFile(cwd);
|
|
3934
4291
|
const cwdDisplay = tildePath(cwd);
|
|
3935
4292
|
let prompt = `You are mini-coder, a small and fast CLI coding agent.
|
|
@@ -3967,6 +4324,17 @@ ${globalContext}`;
|
|
|
3967
4324
|
${localContext}`;
|
|
3968
4325
|
}
|
|
3969
4326
|
}
|
|
4327
|
+
const skills = Array.from(loadSkillsIndex(cwd, homeDir).values());
|
|
4328
|
+
if (skills.length > 0) {
|
|
4329
|
+
prompt += `
|
|
4330
|
+
|
|
4331
|
+
# Available skills (metadata only)`;
|
|
4332
|
+
prompt += "\nUse `listSkills` to browse and `readSkill` to load one SKILL.md on demand.\n";
|
|
4333
|
+
for (const skill of skills) {
|
|
4334
|
+
prompt += `
|
|
4335
|
+
- ${skill.name}: ${skill.description} (${skill.source})`;
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
3970
4338
|
if (isSubagent) {
|
|
3971
4339
|
prompt += `
|
|
3972
4340
|
|
|
@@ -3981,8 +4349,8 @@ ${extraSystemPrompt}`;
|
|
|
3981
4349
|
}
|
|
3982
4350
|
|
|
3983
4351
|
// src/tools/create.ts
|
|
3984
|
-
import { existsSync as
|
|
3985
|
-
import { dirname } from "path";
|
|
4352
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
4353
|
+
import { dirname as dirname2 } from "path";
|
|
3986
4354
|
import { z } from "zod";
|
|
3987
4355
|
|
|
3988
4356
|
// src/tools/diff.ts
|
|
@@ -4112,10 +4480,10 @@ ${lines.join(`
|
|
|
4112
4480
|
}
|
|
4113
4481
|
|
|
4114
4482
|
// src/tools/shared.ts
|
|
4115
|
-
import { join as
|
|
4483
|
+
import { join as join9, relative as relative3 } from "path";
|
|
4116
4484
|
function resolvePath(cwdInput, pathInput) {
|
|
4117
4485
|
const cwd = cwdInput ?? process.cwd();
|
|
4118
|
-
const filePath = pathInput.startsWith("/") ? pathInput :
|
|
4486
|
+
const filePath = pathInput.startsWith("/") ? pathInput : join9(cwd, pathInput);
|
|
4119
4487
|
const relPath = relative3(cwd, filePath);
|
|
4120
4488
|
return { cwd, filePath, relPath };
|
|
4121
4489
|
}
|
|
@@ -4165,8 +4533,8 @@ var createTool = {
|
|
|
4165
4533
|
schema: CreateSchema,
|
|
4166
4534
|
execute: async (input) => {
|
|
4167
4535
|
const { filePath, relPath } = resolvePath(input.cwd, input.path);
|
|
4168
|
-
const dir =
|
|
4169
|
-
if (!
|
|
4536
|
+
const dir = dirname2(filePath);
|
|
4537
|
+
if (!existsSync5(dir))
|
|
4170
4538
|
mkdirSync4(dir, { recursive: true });
|
|
4171
4539
|
const file = Bun.file(filePath);
|
|
4172
4540
|
const created = !await file.exists();
|
|
@@ -4237,15 +4605,15 @@ var webContentTool = {
|
|
|
4237
4605
|
};
|
|
4238
4606
|
|
|
4239
4607
|
// src/tools/glob.ts
|
|
4240
|
-
import { resolve as
|
|
4608
|
+
import { resolve as resolve3 } from "path";
|
|
4241
4609
|
import { z as z3 } from "zod";
|
|
4242
4610
|
|
|
4243
4611
|
// src/tools/ignore.ts
|
|
4244
|
-
import { join as
|
|
4612
|
+
import { join as join10 } from "path";
|
|
4245
4613
|
import ignore from "ignore";
|
|
4246
4614
|
async function loadGitignore(cwd) {
|
|
4247
4615
|
try {
|
|
4248
|
-
const gitignore = await Bun.file(
|
|
4616
|
+
const gitignore = await Bun.file(join10(cwd, ".gitignore")).text();
|
|
4249
4617
|
return ignore().add(gitignore);
|
|
4250
4618
|
} catch {
|
|
4251
4619
|
return null;
|
|
@@ -4253,11 +4621,11 @@ async function loadGitignore(cwd) {
|
|
|
4253
4621
|
}
|
|
4254
4622
|
|
|
4255
4623
|
// src/tools/scan-path.ts
|
|
4256
|
-
import { relative as relative4, resolve, sep } from "path";
|
|
4624
|
+
import { relative as relative4, resolve as resolve2, sep } from "path";
|
|
4257
4625
|
var LEADING_PARENT_SEGMENTS = /^(?:\.\.\/)+/;
|
|
4258
4626
|
function getScannedPathInfo(cwd, scanPath) {
|
|
4259
|
-
const cwdAbsolute =
|
|
4260
|
-
const absolute =
|
|
4627
|
+
const cwdAbsolute = resolve2(cwd);
|
|
4628
|
+
const absolute = resolve2(cwdAbsolute, scanPath);
|
|
4261
4629
|
const relativePath = relative4(cwdAbsolute, absolute).replaceAll("\\", "/");
|
|
4262
4630
|
const inCwd = absolute === cwdAbsolute || absolute.startsWith(cwdAbsolute === sep ? sep : `${cwdAbsolute}${sep}`);
|
|
4263
4631
|
const ignoreTargets = getIgnoreTargets(relativePath, inCwd);
|
|
@@ -4309,7 +4677,7 @@ var globTool = {
|
|
|
4309
4677
|
if (ignored)
|
|
4310
4678
|
continue;
|
|
4311
4679
|
try {
|
|
4312
|
-
const fullPath =
|
|
4680
|
+
const fullPath = resolve3(cwd, relativePath);
|
|
4313
4681
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
4314
4682
|
matches.push({
|
|
4315
4683
|
path: relativePath,
|
|
@@ -4473,8 +4841,8 @@ var grepTool = {
|
|
|
4473
4841
|
|
|
4474
4842
|
// src/tools/hooks.ts
|
|
4475
4843
|
import { accessSync, constants } from "fs";
|
|
4476
|
-
import { homedir as
|
|
4477
|
-
import { join as
|
|
4844
|
+
import { homedir as homedir9 } from "os";
|
|
4845
|
+
import { join as join11 } from "path";
|
|
4478
4846
|
function isExecutable(filePath) {
|
|
4479
4847
|
try {
|
|
4480
4848
|
accessSync(filePath, constants.X_OK);
|
|
@@ -4486,8 +4854,8 @@ function isExecutable(filePath) {
|
|
|
4486
4854
|
function findHook(toolName, cwd) {
|
|
4487
4855
|
const scriptName = `post-${toolName}`;
|
|
4488
4856
|
const candidates = [
|
|
4489
|
-
|
|
4490
|
-
|
|
4857
|
+
join11(cwd, ".agents", "hooks", scriptName),
|
|
4858
|
+
join11(homedir9(), ".agents", "hooks", scriptName)
|
|
4491
4859
|
];
|
|
4492
4860
|
for (const p of candidates) {
|
|
4493
4861
|
if (isExecutable(p))
|
|
@@ -4806,11 +5174,41 @@ var shellTool = {
|
|
|
4806
5174
|
}
|
|
4807
5175
|
};
|
|
4808
5176
|
|
|
4809
|
-
// src/tools/
|
|
5177
|
+
// src/tools/skills.ts
|
|
4810
5178
|
import { z as z9 } from "zod";
|
|
4811
|
-
var
|
|
4812
|
-
|
|
4813
|
-
|
|
5179
|
+
var ListSkillsSchema = z9.object({});
|
|
5180
|
+
var listSkillsTool = {
|
|
5181
|
+
name: "listSkills",
|
|
5182
|
+
description: "List available skills metadata (name, description, source) without loading SKILL.md bodies.",
|
|
5183
|
+
schema: ListSkillsSchema,
|
|
5184
|
+
execute: async (input) => {
|
|
5185
|
+
const cwd = input.cwd ?? process.cwd();
|
|
5186
|
+
const skills = Array.from(loadSkillsIndex(cwd).values()).map((skill) => ({
|
|
5187
|
+
name: skill.name,
|
|
5188
|
+
description: skill.description,
|
|
5189
|
+
source: skill.source
|
|
5190
|
+
}));
|
|
5191
|
+
return { skills };
|
|
5192
|
+
}
|
|
5193
|
+
};
|
|
5194
|
+
var ReadSkillSchema = z9.object({
|
|
5195
|
+
name: z9.string().describe("Skill name to load")
|
|
5196
|
+
});
|
|
5197
|
+
var readSkillTool = {
|
|
5198
|
+
name: "readSkill",
|
|
5199
|
+
description: "Load full SKILL.md content for one skill by name.",
|
|
5200
|
+
schema: ReadSkillSchema,
|
|
5201
|
+
execute: async (input) => {
|
|
5202
|
+
const cwd = input.cwd ?? process.cwd();
|
|
5203
|
+
return { skill: loadSkillContent(input.name, cwd) };
|
|
5204
|
+
}
|
|
5205
|
+
};
|
|
5206
|
+
|
|
5207
|
+
// src/tools/subagent.ts
|
|
5208
|
+
import { z as z10 } from "zod";
|
|
5209
|
+
var SubagentInput = z10.object({
|
|
5210
|
+
prompt: z10.string().describe("The task or question to give the subagent"),
|
|
5211
|
+
agentName: z10.string().optional().describe("Name of a custom agent to use (from .agents/agents/). Omit to use a generic subagent.")
|
|
4814
5212
|
});
|
|
4815
5213
|
function createSubagentTool(runSubagent, availableAgents) {
|
|
4816
5214
|
const agentSection = availableAgents.size > 0 ? `
|
|
@@ -4893,6 +5291,8 @@ function buildToolSet(opts) {
|
|
|
4893
5291
|
withHooks(withCwdDefault(globTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGlob(input, cwd), onHook),
|
|
4894
5292
|
withHooks(withCwdDefault(grepTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGrep(input, cwd), onHook),
|
|
4895
5293
|
withHooks(withCwdDefault(readTool, cwd), lookupHook, cwd, (_, input) => hookEnvForRead(input, cwd), onHook),
|
|
5294
|
+
withCwdDefault(listSkillsTool, cwd),
|
|
5295
|
+
withCwdDefault(readSkillTool, cwd),
|
|
4896
5296
|
withHooks(withCwdDefault(createTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForCreate(result, cwd), onHook, finalizeWriteResult),
|
|
4897
5297
|
withHooks(withCwdDefault(replaceTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForReplace(result, cwd), onHook, finalizeWriteResult),
|
|
4898
5298
|
withHooks(withCwdDefault(insertTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForInsert(result, cwd), onHook, finalizeWriteResult),
|
|
@@ -4912,7 +5312,9 @@ function buildReadOnlyToolSet(opts) {
|
|
|
4912
5312
|
const tools = [
|
|
4913
5313
|
withCwdDefault(globTool, cwd),
|
|
4914
5314
|
withCwdDefault(grepTool, cwd),
|
|
4915
|
-
withCwdDefault(readTool, cwd)
|
|
5315
|
+
withCwdDefault(readTool, cwd),
|
|
5316
|
+
withCwdDefault(listSkillsTool, cwd),
|
|
5317
|
+
withCwdDefault(readSkillTool, cwd)
|
|
4916
5318
|
];
|
|
4917
5319
|
if (process.env.EXA_API_KEY) {
|
|
4918
5320
|
tools.push(webSearchTool, webContentTool);
|
|
@@ -5125,10 +5527,13 @@ function formatSubagentDiagnostics(stdout, stderr) {
|
|
|
5125
5527
|
function createSubagentRunner(cwd, getCurrentModel) {
|
|
5126
5528
|
const activeProcs = new Set;
|
|
5127
5529
|
const subagentDepth = Number.parseInt(process.env.MC_SUBAGENT_DEPTH ?? "0", 10);
|
|
5128
|
-
const runSubagent = async (prompt, agentName, modelOverride) => {
|
|
5530
|
+
const runSubagent = async (prompt, agentName, modelOverride, abortSignal) => {
|
|
5129
5531
|
if (subagentDepth >= MAX_SUBAGENT_DEPTH) {
|
|
5130
5532
|
throw new Error(`Subagent recursion limit reached (depth ${subagentDepth}). ` + `Cannot spawn another subagent.`);
|
|
5131
5533
|
}
|
|
5534
|
+
if (abortSignal?.aborted) {
|
|
5535
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5536
|
+
}
|
|
5132
5537
|
const model = modelOverride ?? getCurrentModel();
|
|
5133
5538
|
const cmd = [
|
|
5134
5539
|
...getMcCommand(),
|
|
@@ -5151,6 +5556,16 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5151
5556
|
stdio: ["ignore", "pipe", "pipe", "pipe"]
|
|
5152
5557
|
});
|
|
5153
5558
|
activeProcs.add(proc);
|
|
5559
|
+
let aborted = false;
|
|
5560
|
+
const onAbort = () => {
|
|
5561
|
+
aborted = true;
|
|
5562
|
+
try {
|
|
5563
|
+
proc.kill("SIGTERM");
|
|
5564
|
+
} catch {}
|
|
5565
|
+
};
|
|
5566
|
+
if (abortSignal) {
|
|
5567
|
+
abortSignal.addEventListener("abort", onAbort);
|
|
5568
|
+
}
|
|
5154
5569
|
try {
|
|
5155
5570
|
const [text, stdout, stderr] = await Promise.all([
|
|
5156
5571
|
Bun.file(proc.stdio[3]).text(),
|
|
@@ -5158,6 +5573,9 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5158
5573
|
consumeTail(proc.stderr),
|
|
5159
5574
|
proc.exited
|
|
5160
5575
|
]);
|
|
5576
|
+
if (aborted || abortSignal?.aborted) {
|
|
5577
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5578
|
+
}
|
|
5161
5579
|
const diagnostics = formatSubagentDiagnostics(stdout, stderr);
|
|
5162
5580
|
const trimmed = text.trim();
|
|
5163
5581
|
if (!trimmed) {
|
|
@@ -5183,7 +5601,15 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5183
5601
|
if (agentName)
|
|
5184
5602
|
output.agentName = agentName;
|
|
5185
5603
|
return output;
|
|
5604
|
+
} catch (error) {
|
|
5605
|
+
if (aborted || abortSignal?.aborted) {
|
|
5606
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5607
|
+
}
|
|
5608
|
+
throw error;
|
|
5186
5609
|
} finally {
|
|
5610
|
+
if (abortSignal) {
|
|
5611
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
5612
|
+
}
|
|
5187
5613
|
activeProcs.delete(proc);
|
|
5188
5614
|
}
|
|
5189
5615
|
};
|
|
@@ -5380,7 +5806,7 @@ async function initAgent(opts) {
|
|
|
5380
5806
|
runner.planMode = v;
|
|
5381
5807
|
},
|
|
5382
5808
|
cwd,
|
|
5383
|
-
runSubagent: (prompt, agentName, model) => runSubagent(prompt, agentName, model),
|
|
5809
|
+
runSubagent: (prompt, agentName, model, abortSignal) => runSubagent(prompt, agentName, model, abortSignal),
|
|
5384
5810
|
get activeAgent() {
|
|
5385
5811
|
return activeAgentName;
|
|
5386
5812
|
},
|
|
@@ -5513,9 +5939,9 @@ ${c13.bold("Examples:")}`);
|
|
|
5513
5939
|
}
|
|
5514
5940
|
|
|
5515
5941
|
// src/cli/bootstrap.ts
|
|
5516
|
-
import { existsSync as
|
|
5517
|
-
import { homedir as
|
|
5518
|
-
import { join as
|
|
5942
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
5943
|
+
import { homedir as homedir10 } from "os";
|
|
5944
|
+
import { join as join12 } from "path";
|
|
5519
5945
|
import * as c14 from "yoctocolors";
|
|
5520
5946
|
var REVIEW_COMMAND_CONTENT = `---
|
|
5521
5947
|
description: Review recent changes for correctness, code quality, and performance
|
|
@@ -5535,36 +5961,45 @@ Perform a sensible code review:
|
|
|
5535
5961
|
Output a small summary with only the issues found. If nothing is wrong, say so.
|
|
5536
5962
|
`;
|
|
5537
5963
|
function bootstrapGlobalDefaults() {
|
|
5538
|
-
const commandsDir =
|
|
5539
|
-
const reviewPath =
|
|
5540
|
-
if (!
|
|
5964
|
+
const commandsDir = join12(homedir10(), ".agents", "commands");
|
|
5965
|
+
const reviewPath = join12(commandsDir, "review.md");
|
|
5966
|
+
if (!existsSync6(reviewPath)) {
|
|
5541
5967
|
mkdirSync5(commandsDir, { recursive: true });
|
|
5542
|
-
|
|
5968
|
+
writeFileSync3(reviewPath, REVIEW_COMMAND_CONTENT, "utf-8");
|
|
5543
5969
|
writeln(`${c14.green("\u2713")} created ${c14.dim("~/.agents/commands/review.md")} ${c14.dim("(edit it to customise your reviews)")}`);
|
|
5544
5970
|
}
|
|
5545
5971
|
}
|
|
5546
5972
|
|
|
5547
5973
|
// src/cli/file-refs.ts
|
|
5548
|
-
import { join as
|
|
5974
|
+
import { join as join13 } from "path";
|
|
5549
5975
|
async function resolveFileRefs(text, cwd) {
|
|
5550
5976
|
const atPattern = /@([\w./\-_]+)/g;
|
|
5551
5977
|
let result = text;
|
|
5552
5978
|
const matches = [...text.matchAll(atPattern)];
|
|
5553
5979
|
const images = [];
|
|
5554
|
-
const skills =
|
|
5980
|
+
const skills = loadSkillsIndex(cwd);
|
|
5981
|
+
const loadedSkills = new Map;
|
|
5555
5982
|
for (const match of [...matches].reverse()) {
|
|
5556
5983
|
const ref = match[1];
|
|
5557
5984
|
if (!ref)
|
|
5558
5985
|
continue;
|
|
5559
|
-
const
|
|
5560
|
-
if (
|
|
5561
|
-
|
|
5562
|
-
|
|
5986
|
+
const skillMeta = skills.get(ref);
|
|
5987
|
+
if (skillMeta) {
|
|
5988
|
+
let content = loadedSkills.get(skillMeta.name);
|
|
5989
|
+
if (!content) {
|
|
5990
|
+
const loaded2 = loadSkillContentFromMeta(skillMeta);
|
|
5991
|
+
if (!loaded2)
|
|
5992
|
+
continue;
|
|
5993
|
+
content = loaded2.content;
|
|
5994
|
+
loadedSkills.set(skillMeta.name, content);
|
|
5995
|
+
}
|
|
5996
|
+
const replacement = `<skill name="${skillMeta.name}">
|
|
5997
|
+
${content}
|
|
5563
5998
|
</skill>`;
|
|
5564
5999
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
5565
6000
|
continue;
|
|
5566
6001
|
}
|
|
5567
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
6002
|
+
const filePath = ref.startsWith("/") ? ref : join13(cwd, ref);
|
|
5568
6003
|
if (isImageFilename(ref)) {
|
|
5569
6004
|
const attachment = await loadImageFile(filePath);
|
|
5570
6005
|
if (attachment) {
|
|
@@ -5611,7 +6046,9 @@ class HeadlessReporter {
|
|
|
5611
6046
|
accumulatedText += event.delta;
|
|
5612
6047
|
break;
|
|
5613
6048
|
case "reasoning-delta":
|
|
5614
|
-
reasoningText += event.delta;
|
|
6049
|
+
reasoningText += normalizeReasoningDelta(event.delta);
|
|
6050
|
+
break;
|
|
6051
|
+
case "context-pruned":
|
|
5615
6052
|
break;
|
|
5616
6053
|
case "turn-complete":
|
|
5617
6054
|
inputTokens = event.inputTokens;
|
|
@@ -5635,7 +6072,7 @@ class HeadlessReporter {
|
|
|
5635
6072
|
outputTokens,
|
|
5636
6073
|
contextTokens,
|
|
5637
6074
|
newMessages,
|
|
5638
|
-
reasoningText
|
|
6075
|
+
reasoningText: normalizeReasoningText(reasoningText)
|
|
5639
6076
|
};
|
|
5640
6077
|
}
|
|
5641
6078
|
renderStatusBar(_data) {}
|
|
@@ -5690,8 +6127,8 @@ async function runShellPassthrough(command, cwd, reporter) {
|
|
|
5690
6127
|
import * as c16 from "yoctocolors";
|
|
5691
6128
|
|
|
5692
6129
|
// src/cli/custom-commands.ts
|
|
5693
|
-
import { existsSync as
|
|
5694
|
-
import { join as
|
|
6130
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
6131
|
+
import { join as join14 } from "path";
|
|
5695
6132
|
function loadCustomCommands(cwd, homeDir) {
|
|
5696
6133
|
return loadMarkdownConfigs({
|
|
5697
6134
|
type: "commands",
|
|
@@ -5748,12 +6185,12 @@ async function expandTemplate(template, args, cwd) {
|
|
|
5748
6185
|
const fileMatches = [...result.matchAll(FILE_REF_RE)];
|
|
5749
6186
|
for (const match of fileMatches) {
|
|
5750
6187
|
const filePath = match[1] ?? "";
|
|
5751
|
-
const fullPath =
|
|
5752
|
-
if (!
|
|
6188
|
+
const fullPath = join14(cwd, filePath);
|
|
6189
|
+
if (!existsSync7(fullPath))
|
|
5753
6190
|
continue;
|
|
5754
6191
|
let content = "";
|
|
5755
6192
|
try {
|
|
5756
|
-
content =
|
|
6193
|
+
content = readFileSync4(fullPath, "utf-8");
|
|
5757
6194
|
} catch {
|
|
5758
6195
|
continue;
|
|
5759
6196
|
}
|
|
@@ -5813,10 +6250,10 @@ async function handleModel(ctx, args) {
|
|
|
5813
6250
|
}
|
|
5814
6251
|
return;
|
|
5815
6252
|
}
|
|
5816
|
-
|
|
6253
|
+
ctx.startSpinner("fetching models");
|
|
5817
6254
|
const snapshot = await fetchAvailableModels();
|
|
5818
6255
|
const models = snapshot.models;
|
|
5819
|
-
|
|
6256
|
+
ctx.stopSpinner();
|
|
5820
6257
|
if (models.length === 0) {
|
|
5821
6258
|
writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
|
|
5822
6259
|
writeln(c16.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
|
|
@@ -6006,14 +6443,14 @@ async function handleMcp(ctx, args) {
|
|
|
6006
6443
|
case "add": {
|
|
6007
6444
|
const [, name, transport, ...rest] = parts;
|
|
6008
6445
|
if (!name || !transport || rest.length === 0) {
|
|
6009
|
-
writeln(
|
|
6010
|
-
writeln(
|
|
6446
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> http <url>`);
|
|
6447
|
+
writeln(`${PREFIX.error} /mcp add <name> stdio <cmd> [args...]`);
|
|
6011
6448
|
return;
|
|
6012
6449
|
}
|
|
6013
6450
|
if (transport === "http") {
|
|
6014
6451
|
const url = rest[0];
|
|
6015
6452
|
if (!url) {
|
|
6016
|
-
writeln(
|
|
6453
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> http <url>`);
|
|
6017
6454
|
return;
|
|
6018
6455
|
}
|
|
6019
6456
|
upsertMcpServer({
|
|
@@ -6027,7 +6464,7 @@ async function handleMcp(ctx, args) {
|
|
|
6027
6464
|
} else if (transport === "stdio") {
|
|
6028
6465
|
const [command, ...cmdArgs] = rest;
|
|
6029
6466
|
if (!command) {
|
|
6030
|
-
writeln(
|
|
6467
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> stdio <cmd> [args...]`);
|
|
6031
6468
|
return;
|
|
6032
6469
|
}
|
|
6033
6470
|
upsertMcpServer({
|
|
@@ -6039,7 +6476,7 @@ async function handleMcp(ctx, args) {
|
|
|
6039
6476
|
env: null
|
|
6040
6477
|
});
|
|
6041
6478
|
} else {
|
|
6042
|
-
writeln(
|
|
6479
|
+
writeln(`${PREFIX.error} unknown transport: ${transport} (use http or stdio)`);
|
|
6043
6480
|
return;
|
|
6044
6481
|
}
|
|
6045
6482
|
try {
|
|
@@ -6054,7 +6491,7 @@ async function handleMcp(ctx, args) {
|
|
|
6054
6491
|
case "rm": {
|
|
6055
6492
|
const [, name] = parts;
|
|
6056
6493
|
if (!name) {
|
|
6057
|
-
writeln(
|
|
6494
|
+
writeln(`${PREFIX.error} usage: /mcp remove <name>`);
|
|
6058
6495
|
return;
|
|
6059
6496
|
}
|
|
6060
6497
|
deleteMcpServer(name);
|
|
@@ -6062,7 +6499,7 @@ async function handleMcp(ctx, args) {
|
|
|
6062
6499
|
return;
|
|
6063
6500
|
}
|
|
6064
6501
|
default:
|
|
6065
|
-
writeln(
|
|
6502
|
+
writeln(`${PREFIX.error} unknown: /mcp ${sub}`);
|
|
6066
6503
|
writeln(c16.dim(" subcommands: list \xB7 add \xB7 remove"));
|
|
6067
6504
|
}
|
|
6068
6505
|
}
|
|
@@ -6081,9 +6518,13 @@ async function handleCustomCommand(cmd, args, ctx) {
|
|
|
6081
6518
|
if (!fork) {
|
|
6082
6519
|
return { type: "inject-user-message", text: prompt };
|
|
6083
6520
|
}
|
|
6521
|
+
const abortController = new AbortController;
|
|
6522
|
+
const stopWatcher = watchForCancel(abortController, {
|
|
6523
|
+
allowSubagentEsc: true
|
|
6524
|
+
});
|
|
6084
6525
|
try {
|
|
6085
6526
|
ctx.startSpinner("subagent");
|
|
6086
|
-
const output = await ctx.runSubagent(prompt, cmd.agent, cmd.model);
|
|
6527
|
+
const output = await ctx.runSubagent(prompt, cmd.agent, cmd.model, abortController.signal);
|
|
6087
6528
|
write(renderMarkdown(output.result));
|
|
6088
6529
|
writeln();
|
|
6089
6530
|
return {
|
|
@@ -6095,9 +6536,13 @@ ${output.result}
|
|
|
6095
6536
|
<system-message>Summarize the findings above to the user.</system-message>`
|
|
6096
6537
|
};
|
|
6097
6538
|
} catch (e) {
|
|
6539
|
+
if (isAbortError(e)) {
|
|
6540
|
+
return { type: "handled" };
|
|
6541
|
+
}
|
|
6098
6542
|
writeln(`${PREFIX.error} /${cmd.name} failed: ${String(e)}`);
|
|
6099
6543
|
return { type: "handled" };
|
|
6100
6544
|
} finally {
|
|
6545
|
+
stopWatcher();
|
|
6101
6546
|
ctx.stopSpinner();
|
|
6102
6547
|
}
|
|
6103
6548
|
}
|
|
@@ -6177,10 +6622,10 @@ function handleHelp(ctx, custom) {
|
|
|
6177
6622
|
writeln(` ${c16.magenta(`@${agent.name}`.padEnd(26))} ${c16.dim(agent.description)}${modeTag}${tag}`);
|
|
6178
6623
|
}
|
|
6179
6624
|
}
|
|
6180
|
-
const skills =
|
|
6625
|
+
const skills = loadSkillsIndex(ctx.cwd);
|
|
6181
6626
|
if (skills.size > 0) {
|
|
6182
6627
|
writeln();
|
|
6183
|
-
writeln(c16.dim(" skills (
|
|
6628
|
+
writeln(c16.dim(" skills (walk-up local .agents/.claude/skills + global ~/.agents/skills ~/.claude/skills):"));
|
|
6184
6629
|
for (const skill of skills.values()) {
|
|
6185
6630
|
const tag = skill.source === "local" ? c16.dim(" (local)") : c16.dim(" (global)");
|
|
6186
6631
|
writeln(` ${c16.yellow(`@${skill.name}`.padEnd(26))} ${c16.dim(skill.description)}${tag}`);
|
|
@@ -6188,7 +6633,7 @@ function handleHelp(ctx, custom) {
|
|
|
6188
6633
|
}
|
|
6189
6634
|
writeln();
|
|
6190
6635
|
writeln(` ${c16.green("@agent".padEnd(26))} ${c16.dim("run prompt through a custom agent (Tab to complete)")}`);
|
|
6191
|
-
writeln(` ${c16.green("@skill".padEnd(26))} ${c16.dim("
|
|
6636
|
+
writeln(` ${c16.green("@skill".padEnd(26))} ${c16.dim("load a skill body on demand into the prompt (Tab to complete)")}`);
|
|
6192
6637
|
writeln(` ${c16.green("@file".padEnd(26))} ${c16.dim("inject file contents into prompt (Tab to complete)")}`);
|
|
6193
6638
|
writeln(` ${c16.green("!cmd".padEnd(26))} ${c16.dim("run shell command, output added as context")}`);
|
|
6194
6639
|
writeln();
|
|
@@ -6296,7 +6741,9 @@ async function runInputLoop(opts) {
|
|
|
6296
6741
|
}
|
|
6297
6742
|
if (result.type === "inject-user-message") {
|
|
6298
6743
|
const { text: resolvedText, images: refImages } = await resolveFileRefs(result.text, cwd);
|
|
6299
|
-
|
|
6744
|
+
try {
|
|
6745
|
+
await runner.processUserInput(resolvedText, refImages);
|
|
6746
|
+
} catch {}
|
|
6300
6747
|
}
|
|
6301
6748
|
continue;
|
|
6302
6749
|
}
|
|
@@ -6322,7 +6769,9 @@ ${out}
|
|
|
6322
6769
|
const { text: resolvedText, images: refImages } = await resolveFileRefs(input.text, cwd);
|
|
6323
6770
|
const allImages = [...input.images || [], ...refImages];
|
|
6324
6771
|
if (!runner.ralphMode) {
|
|
6325
|
-
|
|
6772
|
+
try {
|
|
6773
|
+
await runner.processUserInput(resolvedText, allImages);
|
|
6774
|
+
} catch {}
|
|
6326
6775
|
continue;
|
|
6327
6776
|
}
|
|
6328
6777
|
if (allImages.length > 0) {
|
|
@@ -6360,13 +6809,14 @@ ${out}
|
|
|
6360
6809
|
}
|
|
6361
6810
|
|
|
6362
6811
|
// src/cli/output-reporter.ts
|
|
6363
|
-
import * as c18 from "yoctocolors";
|
|
6364
6812
|
class CliReporter {
|
|
6365
6813
|
spinner = new Spinner;
|
|
6366
6814
|
info(msg) {
|
|
6815
|
+
this.spinner.stop();
|
|
6367
6816
|
renderInfo(msg);
|
|
6368
6817
|
}
|
|
6369
6818
|
error(msg, hint) {
|
|
6819
|
+
this.spinner.stop();
|
|
6370
6820
|
if (typeof msg === "string") {
|
|
6371
6821
|
renderError(msg, hint);
|
|
6372
6822
|
} else {
|
|
@@ -6374,9 +6824,11 @@ class CliReporter {
|
|
|
6374
6824
|
}
|
|
6375
6825
|
}
|
|
6376
6826
|
warn(msg) {
|
|
6377
|
-
|
|
6827
|
+
this.spinner.stop();
|
|
6828
|
+
writeln(`${G.warn} ${msg}`);
|
|
6378
6829
|
}
|
|
6379
6830
|
writeText(text) {
|
|
6831
|
+
this.spinner.stop();
|
|
6380
6832
|
writeln(text);
|
|
6381
6833
|
}
|
|
6382
6834
|
startSpinner(label) {
|
|
@@ -6404,6 +6856,7 @@ registerTerminalCleanup();
|
|
|
6404
6856
|
initErrorLog();
|
|
6405
6857
|
initApiLog();
|
|
6406
6858
|
initModelInfoCache();
|
|
6859
|
+
pruneOldData();
|
|
6407
6860
|
refreshModelInfoInBackground().catch(() => {});
|
|
6408
6861
|
async function main() {
|
|
6409
6862
|
const argv = process.argv.slice(2);
|
|
@@ -6422,7 +6875,7 @@ async function main() {
|
|
|
6422
6875
|
if (last) {
|
|
6423
6876
|
sessionId = last.id;
|
|
6424
6877
|
} else {
|
|
6425
|
-
writeln(
|
|
6878
|
+
writeln(c18.dim("No previous session found, starting fresh."));
|
|
6426
6879
|
}
|
|
6427
6880
|
} else if (args.sessionId) {
|
|
6428
6881
|
sessionId = args.sessionId;
|