mini-coder 0.0.19 → 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/dist/mc.js +521 -130
- package/docs/skills.md +47 -42
- package/package.json +6 -6
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";
|
|
@@ -385,39 +385,53 @@ import * as c4 from "yoctocolors";
|
|
|
385
385
|
function renderInline(text) {
|
|
386
386
|
let out = "";
|
|
387
387
|
let i = 0;
|
|
388
|
+
let segStart = 0;
|
|
388
389
|
let prevWasBoldClose = false;
|
|
389
390
|
while (i < text.length) {
|
|
390
|
-
|
|
391
|
+
const ch = text[i];
|
|
392
|
+
if (ch === "`") {
|
|
391
393
|
const end = text.indexOf("`", i + 1);
|
|
392
394
|
if (end !== -1) {
|
|
395
|
+
if (i > segStart)
|
|
396
|
+
out += text.slice(segStart, i);
|
|
393
397
|
out += c4.yellow(text.slice(i, end + 1));
|
|
394
398
|
i = end + 1;
|
|
399
|
+
segStart = i;
|
|
395
400
|
prevWasBoldClose = false;
|
|
396
401
|
continue;
|
|
397
402
|
}
|
|
398
403
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
}
|
|
406
416
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
+
}
|
|
415
428
|
}
|
|
416
429
|
}
|
|
417
|
-
out += text[i];
|
|
418
|
-
i++;
|
|
419
430
|
prevWasBoldClose = false;
|
|
431
|
+
i++;
|
|
420
432
|
}
|
|
433
|
+
if (segStart < text.length)
|
|
434
|
+
out += text.slice(segStart);
|
|
421
435
|
return out;
|
|
422
436
|
}
|
|
423
437
|
function renderLine(raw, inFence) {
|
|
@@ -481,6 +495,39 @@ function renderMarkdown(text) {
|
|
|
481
495
|
`);
|
|
482
496
|
}
|
|
483
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
|
+
|
|
484
531
|
// src/cli/tool-render.ts
|
|
485
532
|
import { homedir as homedir2 } from "os";
|
|
486
533
|
import * as c5 from "yoctocolors";
|
|
@@ -705,7 +752,19 @@ function renderToolResult(toolName, result, isError, _toolCallId) {
|
|
|
705
752
|
if (toolName === "subagent") {
|
|
706
753
|
const r = result;
|
|
707
754
|
const label = r.agentName ? ` ${c5.dim(c5.cyan(`[@${r.agentName}]`))}` : "";
|
|
708
|
-
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)}` : ""}`)}`);
|
|
709
768
|
return;
|
|
710
769
|
}
|
|
711
770
|
if (toolName === "webSearch") {
|
|
@@ -777,16 +836,30 @@ async function renderTurn(events, spinner, opts) {
|
|
|
777
836
|
let accumulatedText = "";
|
|
778
837
|
let accumulatedReasoning = "";
|
|
779
838
|
let inFence = false;
|
|
839
|
+
let reasoningBlankLineRun = 0;
|
|
780
840
|
let inputTokens = 0;
|
|
781
841
|
let outputTokens = 0;
|
|
782
842
|
let contextTokens = 0;
|
|
783
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
|
+
}
|
|
784
857
|
function renderAndWrite(raw, endWithNewline) {
|
|
785
858
|
spinner.stop();
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
write(
|
|
859
|
+
const out = renderSingleLine(raw);
|
|
860
|
+
if (out === null)
|
|
861
|
+
return;
|
|
862
|
+
write(out);
|
|
790
863
|
if (endWithNewline) {
|
|
791
864
|
write(`
|
|
792
865
|
`);
|
|
@@ -798,17 +871,30 @@ async function renderTurn(events, spinner, opts) {
|
|
|
798
871
|
writeln();
|
|
799
872
|
inText = false;
|
|
800
873
|
inReasoning = false;
|
|
874
|
+
reasoningBlankLineRun = 0;
|
|
801
875
|
}
|
|
802
876
|
}
|
|
803
877
|
function flushCompleteLines() {
|
|
878
|
+
let start = 0;
|
|
804
879
|
let boundary = rawBuffer.indexOf(`
|
|
805
|
-
|
|
880
|
+
`, start);
|
|
881
|
+
if (boundary === -1)
|
|
882
|
+
return;
|
|
883
|
+
spinner.stop();
|
|
884
|
+
let batchOutput = "";
|
|
806
885
|
while (boundary !== -1) {
|
|
807
|
-
|
|
808
|
-
|
|
886
|
+
const raw = rawBuffer.slice(start, boundary);
|
|
887
|
+
start = boundary + 1;
|
|
809
888
|
boundary = rawBuffer.indexOf(`
|
|
810
|
-
|
|
889
|
+
`, start);
|
|
890
|
+
const out = renderSingleLine(raw);
|
|
891
|
+
if (out !== null)
|
|
892
|
+
batchOutput += `${out}
|
|
893
|
+
`;
|
|
811
894
|
}
|
|
895
|
+
rawBuffer = start > 0 ? rawBuffer.slice(start) : rawBuffer;
|
|
896
|
+
if (batchOutput)
|
|
897
|
+
write(batchOutput);
|
|
812
898
|
}
|
|
813
899
|
function flushAll() {
|
|
814
900
|
if (!rawBuffer) {
|
|
@@ -834,7 +920,8 @@ async function renderTurn(events, spinner, opts) {
|
|
|
834
920
|
break;
|
|
835
921
|
}
|
|
836
922
|
case "reasoning-delta": {
|
|
837
|
-
|
|
923
|
+
const delta = normalizeReasoningDelta(event.delta);
|
|
924
|
+
accumulatedReasoning += delta;
|
|
838
925
|
if (!showReasoning) {
|
|
839
926
|
break;
|
|
840
927
|
}
|
|
@@ -843,9 +930,10 @@ async function renderTurn(events, spinner, opts) {
|
|
|
843
930
|
flushAnyText();
|
|
844
931
|
}
|
|
845
932
|
spinner.stop();
|
|
933
|
+
writeln(`${G.info} ${c6.dim("reasoning")}`);
|
|
846
934
|
inReasoning = true;
|
|
847
935
|
}
|
|
848
|
-
rawBuffer +=
|
|
936
|
+
rawBuffer += delta;
|
|
849
937
|
flushCompleteLines();
|
|
850
938
|
break;
|
|
851
939
|
}
|
|
@@ -862,9 +950,18 @@ async function renderTurn(events, spinner, opts) {
|
|
|
862
950
|
spinner.start("thinking");
|
|
863
951
|
break;
|
|
864
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
|
+
}
|
|
865
959
|
case "turn-complete": {
|
|
960
|
+
const hadContent = inText || inReasoning;
|
|
866
961
|
flushAnyText();
|
|
867
962
|
spinner.stop();
|
|
963
|
+
if (!hadContent)
|
|
964
|
+
writeln();
|
|
868
965
|
inputTokens = event.inputTokens;
|
|
869
966
|
outputTokens = event.outputTokens;
|
|
870
967
|
contextTokens = event.contextTokens;
|
|
@@ -895,13 +992,13 @@ async function renderTurn(events, spinner, opts) {
|
|
|
895
992
|
outputTokens,
|
|
896
993
|
contextTokens,
|
|
897
994
|
newMessages,
|
|
898
|
-
reasoningText: accumulatedReasoning
|
|
995
|
+
reasoningText: normalizeReasoningText(accumulatedReasoning)
|
|
899
996
|
};
|
|
900
997
|
}
|
|
901
998
|
|
|
902
999
|
// src/cli/output.ts
|
|
903
1000
|
var HOME2 = homedir3();
|
|
904
|
-
var PACKAGE_VERSION = "0.0.
|
|
1001
|
+
var PACKAGE_VERSION = "0.0.20";
|
|
905
1002
|
function tildePath(p) {
|
|
906
1003
|
return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
|
|
907
1004
|
}
|
|
@@ -1606,7 +1703,7 @@ function deleteAllSnapshots(sessionId) {
|
|
|
1606
1703
|
import * as c11 from "yoctocolors";
|
|
1607
1704
|
|
|
1608
1705
|
// src/cli/input.ts
|
|
1609
|
-
import { join as
|
|
1706
|
+
import { join as join5, relative } from "path";
|
|
1610
1707
|
import * as c9 from "yoctocolors";
|
|
1611
1708
|
|
|
1612
1709
|
// src/cli/image-types.ts
|
|
@@ -1645,21 +1742,198 @@ async function loadImageFile(filePath) {
|
|
|
1645
1742
|
}
|
|
1646
1743
|
|
|
1647
1744
|
// src/cli/skills.ts
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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,
|
|
1660
1858
|
source
|
|
1661
|
-
})
|
|
1662
|
-
}
|
|
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);
|
|
1663
1937
|
}
|
|
1664
1938
|
|
|
1665
1939
|
// src/cli/input.ts
|
|
@@ -1696,7 +1970,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
1696
1970
|
const query = prefix.startsWith("@") ? prefix.slice(1) : prefix;
|
|
1697
1971
|
const results = [];
|
|
1698
1972
|
const MAX = 10;
|
|
1699
|
-
const skills =
|
|
1973
|
+
const skills = loadSkillsIndex(cwd);
|
|
1700
1974
|
for (const [name] of skills) {
|
|
1701
1975
|
if (results.length >= MAX)
|
|
1702
1976
|
break;
|
|
@@ -1715,7 +1989,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
1715
1989
|
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
1716
1990
|
if (file.includes("node_modules") || file.includes(".git"))
|
|
1717
1991
|
continue;
|
|
1718
|
-
results.push(`@${relative(cwd,
|
|
1992
|
+
results.push(`@${relative(cwd, join5(cwd, file))}`);
|
|
1719
1993
|
if (results.length >= MAX)
|
|
1720
1994
|
break;
|
|
1721
1995
|
}
|
|
@@ -1737,7 +2011,7 @@ async function tryExtractImageFromPaste(pasted, cwd) {
|
|
|
1737
2011
|
}
|
|
1738
2012
|
}
|
|
1739
2013
|
if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
|
|
1740
|
-
const filePath = trimmed.startsWith("/") ? trimmed :
|
|
2014
|
+
const filePath = trimmed.startsWith("/") ? trimmed : join5(cwd, trimmed);
|
|
1741
2015
|
const attachment = await loadImageFile(filePath);
|
|
1742
2016
|
if (attachment) {
|
|
1743
2017
|
const name = filePath.split("/").pop() ?? trimmed;
|
|
@@ -1783,6 +2057,17 @@ function getTurnControlAction(chunk) {
|
|
|
1783
2057
|
}
|
|
1784
2058
|
return null;
|
|
1785
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
|
+
}
|
|
1786
2071
|
function exitOnCtrlC(opts) {
|
|
1787
2072
|
if (opts?.printNewline)
|
|
1788
2073
|
process.stdout.write(`
|
|
@@ -1795,15 +2080,16 @@ function exitOnCtrlC(opts) {
|
|
|
1795
2080
|
terminal.restoreTerminal();
|
|
1796
2081
|
process.exit(130);
|
|
1797
2082
|
}
|
|
1798
|
-
function watchForCancel(abortController) {
|
|
2083
|
+
function watchForCancel(abortController, options) {
|
|
1799
2084
|
if (!terminal.isTTY)
|
|
1800
2085
|
return () => {};
|
|
1801
2086
|
const onCancel = () => {
|
|
1802
2087
|
cleanup();
|
|
1803
2088
|
abortController.abort();
|
|
1804
2089
|
};
|
|
2090
|
+
const getAction = options?.allowSubagentEsc ? getSubagentControlAction : getTurnControlAction;
|
|
1805
2091
|
const onData = (chunk) => {
|
|
1806
|
-
const action =
|
|
2092
|
+
const action = getAction(chunk);
|
|
1807
2093
|
if (action === "cancel") {
|
|
1808
2094
|
onCancel();
|
|
1809
2095
|
return;
|
|
@@ -2216,15 +2502,15 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
|
2216
2502
|
|
|
2217
2503
|
// src/llm-api/api-log.ts
|
|
2218
2504
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2219
|
-
import { homedir as
|
|
2220
|
-
import { join as
|
|
2505
|
+
import { homedir as homedir7 } from "os";
|
|
2506
|
+
import { join as join6 } from "path";
|
|
2221
2507
|
var writer2 = null;
|
|
2222
2508
|
var MAX_ENTRY_BYTES = 8 * 1024;
|
|
2223
2509
|
function initApiLog() {
|
|
2224
2510
|
if (writer2)
|
|
2225
2511
|
return;
|
|
2226
|
-
const dirPath =
|
|
2227
|
-
const logPath =
|
|
2512
|
+
const dirPath = join6(homedir7(), ".config", "mini-coder");
|
|
2513
|
+
const logPath = join6(dirPath, "api.log");
|
|
2228
2514
|
mkdirSync3(dirPath, { recursive: true });
|
|
2229
2515
|
writeFileSync2(logPath, "");
|
|
2230
2516
|
writer2 = Bun.file(logPath).writer();
|
|
@@ -3660,9 +3946,23 @@ async function* runTurn(options) {
|
|
|
3660
3946
|
if (openAIItemIdsStrippedMessages !== gptSanitizedMessages) {
|
|
3661
3947
|
logApiEvent("openai item ids stripped from history", { modelString });
|
|
3662
3948
|
}
|
|
3663
|
-
|
|
3949
|
+
const prePruneDiagnostics = getMessageDiagnostics(openAIItemIdsStrippedMessages);
|
|
3950
|
+
logApiEvent("turn context pre-prune", prePruneDiagnostics);
|
|
3664
3951
|
const prunedMessages = applyContextPruning(openAIItemIdsStrippedMessages, pruningMode);
|
|
3665
|
-
|
|
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
|
+
}
|
|
3666
3966
|
const turnMessages = compactToolResultPayloads(prunedMessages, toolResultPayloadCapBytes);
|
|
3667
3967
|
if (turnMessages !== prunedMessages) {
|
|
3668
3968
|
logApiEvent("turn context post-compaction", {
|
|
@@ -3874,10 +4174,10 @@ function getMostRecentSession() {
|
|
|
3874
4174
|
|
|
3875
4175
|
// src/tools/snapshot.ts
|
|
3876
4176
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
3877
|
-
import { isAbsolute, join as
|
|
4177
|
+
import { isAbsolute, join as join7, relative as relative2 } from "path";
|
|
3878
4178
|
async function snapshotBeforeEdit(cwd, sessionId, turnIndex, filePath, snappedPaths) {
|
|
3879
4179
|
try {
|
|
3880
|
-
const absPath = isAbsolute(filePath) ? filePath :
|
|
4180
|
+
const absPath = isAbsolute(filePath) ? filePath : join7(cwd, filePath);
|
|
3881
4181
|
const relPath = isAbsolute(filePath) ? relative2(cwd, filePath) : filePath;
|
|
3882
4182
|
if (snappedPaths.has(relPath))
|
|
3883
4183
|
return;
|
|
@@ -3899,7 +4199,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
3899
4199
|
return { restored: false, reason: "not-found" };
|
|
3900
4200
|
let anyFailed = false;
|
|
3901
4201
|
for (const file of files) {
|
|
3902
|
-
const absPath =
|
|
4202
|
+
const absPath = join7(cwd, file.path);
|
|
3903
4203
|
if (!file.existed) {
|
|
3904
4204
|
try {
|
|
3905
4205
|
if (await Bun.file(absPath).exists()) {
|
|
@@ -3928,24 +4228,24 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
3928
4228
|
}
|
|
3929
4229
|
|
|
3930
4230
|
// src/agent/system-prompt.ts
|
|
3931
|
-
import { existsSync as
|
|
3932
|
-
import { homedir as
|
|
3933
|
-
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";
|
|
3934
4234
|
function tryReadFile(p) {
|
|
3935
|
-
if (!
|
|
4235
|
+
if (!existsSync4(p))
|
|
3936
4236
|
return null;
|
|
3937
4237
|
try {
|
|
3938
|
-
return
|
|
4238
|
+
return readFileSync3(p, "utf-8");
|
|
3939
4239
|
} catch {
|
|
3940
4240
|
return null;
|
|
3941
4241
|
}
|
|
3942
4242
|
}
|
|
3943
4243
|
function loadGlobalContextFile(homeDir) {
|
|
3944
|
-
const globalDir =
|
|
3945
|
-
return tryReadFile(
|
|
4244
|
+
const globalDir = join8(homeDir, ".agents");
|
|
4245
|
+
return tryReadFile(join8(globalDir, "AGENTS.md")) ?? tryReadFile(join8(globalDir, "CLAUDE.md"));
|
|
3946
4246
|
}
|
|
3947
4247
|
function loadLocalContextFile(cwd) {
|
|
3948
|
-
return tryReadFile(
|
|
4248
|
+
return tryReadFile(join8(cwd, ".agents", "AGENTS.md")) ?? tryReadFile(join8(cwd, "CLAUDE.md")) ?? tryReadFile(join8(cwd, "AGENTS.md"));
|
|
3949
4249
|
}
|
|
3950
4250
|
var AUTONOMY = `
|
|
3951
4251
|
|
|
@@ -3986,7 +4286,7 @@ var FINAL_MESSAGE = `
|
|
|
3986
4286
|
- If verification could not be run, say so clearly.`;
|
|
3987
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.`;
|
|
3988
4288
|
function buildSystemPrompt(sessionTimeAnchor, cwd, extraSystemPrompt, isSubagent, homeDir) {
|
|
3989
|
-
const globalContext = loadGlobalContextFile(homeDir ??
|
|
4289
|
+
const globalContext = loadGlobalContextFile(homeDir ?? homedir8());
|
|
3990
4290
|
const localContext = loadLocalContextFile(cwd);
|
|
3991
4291
|
const cwdDisplay = tildePath(cwd);
|
|
3992
4292
|
let prompt = `You are mini-coder, a small and fast CLI coding agent.
|
|
@@ -4024,6 +4324,17 @@ ${globalContext}`;
|
|
|
4024
4324
|
${localContext}`;
|
|
4025
4325
|
}
|
|
4026
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
|
+
}
|
|
4027
4338
|
if (isSubagent) {
|
|
4028
4339
|
prompt += `
|
|
4029
4340
|
|
|
@@ -4038,8 +4349,8 @@ ${extraSystemPrompt}`;
|
|
|
4038
4349
|
}
|
|
4039
4350
|
|
|
4040
4351
|
// src/tools/create.ts
|
|
4041
|
-
import { existsSync as
|
|
4042
|
-
import { dirname } from "path";
|
|
4352
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
4353
|
+
import { dirname as dirname2 } from "path";
|
|
4043
4354
|
import { z } from "zod";
|
|
4044
4355
|
|
|
4045
4356
|
// src/tools/diff.ts
|
|
@@ -4169,10 +4480,10 @@ ${lines.join(`
|
|
|
4169
4480
|
}
|
|
4170
4481
|
|
|
4171
4482
|
// src/tools/shared.ts
|
|
4172
|
-
import { join as
|
|
4483
|
+
import { join as join9, relative as relative3 } from "path";
|
|
4173
4484
|
function resolvePath(cwdInput, pathInput) {
|
|
4174
4485
|
const cwd = cwdInput ?? process.cwd();
|
|
4175
|
-
const filePath = pathInput.startsWith("/") ? pathInput :
|
|
4486
|
+
const filePath = pathInput.startsWith("/") ? pathInput : join9(cwd, pathInput);
|
|
4176
4487
|
const relPath = relative3(cwd, filePath);
|
|
4177
4488
|
return { cwd, filePath, relPath };
|
|
4178
4489
|
}
|
|
@@ -4222,8 +4533,8 @@ var createTool = {
|
|
|
4222
4533
|
schema: CreateSchema,
|
|
4223
4534
|
execute: async (input) => {
|
|
4224
4535
|
const { filePath, relPath } = resolvePath(input.cwd, input.path);
|
|
4225
|
-
const dir =
|
|
4226
|
-
if (!
|
|
4536
|
+
const dir = dirname2(filePath);
|
|
4537
|
+
if (!existsSync5(dir))
|
|
4227
4538
|
mkdirSync4(dir, { recursive: true });
|
|
4228
4539
|
const file = Bun.file(filePath);
|
|
4229
4540
|
const created = !await file.exists();
|
|
@@ -4294,15 +4605,15 @@ var webContentTool = {
|
|
|
4294
4605
|
};
|
|
4295
4606
|
|
|
4296
4607
|
// src/tools/glob.ts
|
|
4297
|
-
import { resolve as
|
|
4608
|
+
import { resolve as resolve3 } from "path";
|
|
4298
4609
|
import { z as z3 } from "zod";
|
|
4299
4610
|
|
|
4300
4611
|
// src/tools/ignore.ts
|
|
4301
|
-
import { join as
|
|
4612
|
+
import { join as join10 } from "path";
|
|
4302
4613
|
import ignore from "ignore";
|
|
4303
4614
|
async function loadGitignore(cwd) {
|
|
4304
4615
|
try {
|
|
4305
|
-
const gitignore = await Bun.file(
|
|
4616
|
+
const gitignore = await Bun.file(join10(cwd, ".gitignore")).text();
|
|
4306
4617
|
return ignore().add(gitignore);
|
|
4307
4618
|
} catch {
|
|
4308
4619
|
return null;
|
|
@@ -4310,11 +4621,11 @@ async function loadGitignore(cwd) {
|
|
|
4310
4621
|
}
|
|
4311
4622
|
|
|
4312
4623
|
// src/tools/scan-path.ts
|
|
4313
|
-
import { relative as relative4, resolve, sep } from "path";
|
|
4624
|
+
import { relative as relative4, resolve as resolve2, sep } from "path";
|
|
4314
4625
|
var LEADING_PARENT_SEGMENTS = /^(?:\.\.\/)+/;
|
|
4315
4626
|
function getScannedPathInfo(cwd, scanPath) {
|
|
4316
|
-
const cwdAbsolute =
|
|
4317
|
-
const absolute =
|
|
4627
|
+
const cwdAbsolute = resolve2(cwd);
|
|
4628
|
+
const absolute = resolve2(cwdAbsolute, scanPath);
|
|
4318
4629
|
const relativePath = relative4(cwdAbsolute, absolute).replaceAll("\\", "/");
|
|
4319
4630
|
const inCwd = absolute === cwdAbsolute || absolute.startsWith(cwdAbsolute === sep ? sep : `${cwdAbsolute}${sep}`);
|
|
4320
4631
|
const ignoreTargets = getIgnoreTargets(relativePath, inCwd);
|
|
@@ -4366,7 +4677,7 @@ var globTool = {
|
|
|
4366
4677
|
if (ignored)
|
|
4367
4678
|
continue;
|
|
4368
4679
|
try {
|
|
4369
|
-
const fullPath =
|
|
4680
|
+
const fullPath = resolve3(cwd, relativePath);
|
|
4370
4681
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
4371
4682
|
matches.push({
|
|
4372
4683
|
path: relativePath,
|
|
@@ -4530,8 +4841,8 @@ var grepTool = {
|
|
|
4530
4841
|
|
|
4531
4842
|
// src/tools/hooks.ts
|
|
4532
4843
|
import { accessSync, constants } from "fs";
|
|
4533
|
-
import { homedir as
|
|
4534
|
-
import { join as
|
|
4844
|
+
import { homedir as homedir9 } from "os";
|
|
4845
|
+
import { join as join11 } from "path";
|
|
4535
4846
|
function isExecutable(filePath) {
|
|
4536
4847
|
try {
|
|
4537
4848
|
accessSync(filePath, constants.X_OK);
|
|
@@ -4543,8 +4854,8 @@ function isExecutable(filePath) {
|
|
|
4543
4854
|
function findHook(toolName, cwd) {
|
|
4544
4855
|
const scriptName = `post-${toolName}`;
|
|
4545
4856
|
const candidates = [
|
|
4546
|
-
|
|
4547
|
-
|
|
4857
|
+
join11(cwd, ".agents", "hooks", scriptName),
|
|
4858
|
+
join11(homedir9(), ".agents", "hooks", scriptName)
|
|
4548
4859
|
];
|
|
4549
4860
|
for (const p of candidates) {
|
|
4550
4861
|
if (isExecutable(p))
|
|
@@ -4863,11 +5174,41 @@ var shellTool = {
|
|
|
4863
5174
|
}
|
|
4864
5175
|
};
|
|
4865
5176
|
|
|
4866
|
-
// src/tools/
|
|
5177
|
+
// src/tools/skills.ts
|
|
4867
5178
|
import { z as z9 } from "zod";
|
|
4868
|
-
var
|
|
4869
|
-
|
|
4870
|
-
|
|
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.")
|
|
4871
5212
|
});
|
|
4872
5213
|
function createSubagentTool(runSubagent, availableAgents) {
|
|
4873
5214
|
const agentSection = availableAgents.size > 0 ? `
|
|
@@ -4950,6 +5291,8 @@ function buildToolSet(opts) {
|
|
|
4950
5291
|
withHooks(withCwdDefault(globTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGlob(input, cwd), onHook),
|
|
4951
5292
|
withHooks(withCwdDefault(grepTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGrep(input, cwd), onHook),
|
|
4952
5293
|
withHooks(withCwdDefault(readTool, cwd), lookupHook, cwd, (_, input) => hookEnvForRead(input, cwd), onHook),
|
|
5294
|
+
withCwdDefault(listSkillsTool, cwd),
|
|
5295
|
+
withCwdDefault(readSkillTool, cwd),
|
|
4953
5296
|
withHooks(withCwdDefault(createTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForCreate(result, cwd), onHook, finalizeWriteResult),
|
|
4954
5297
|
withHooks(withCwdDefault(replaceTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForReplace(result, cwd), onHook, finalizeWriteResult),
|
|
4955
5298
|
withHooks(withCwdDefault(insertTool, cwd, opts.snapshotCallback), lookupHook, cwd, (result) => hookEnvForInsert(result, cwd), onHook, finalizeWriteResult),
|
|
@@ -4969,7 +5312,9 @@ function buildReadOnlyToolSet(opts) {
|
|
|
4969
5312
|
const tools = [
|
|
4970
5313
|
withCwdDefault(globTool, cwd),
|
|
4971
5314
|
withCwdDefault(grepTool, cwd),
|
|
4972
|
-
withCwdDefault(readTool, cwd)
|
|
5315
|
+
withCwdDefault(readTool, cwd),
|
|
5316
|
+
withCwdDefault(listSkillsTool, cwd),
|
|
5317
|
+
withCwdDefault(readSkillTool, cwd)
|
|
4973
5318
|
];
|
|
4974
5319
|
if (process.env.EXA_API_KEY) {
|
|
4975
5320
|
tools.push(webSearchTool, webContentTool);
|
|
@@ -5182,10 +5527,13 @@ function formatSubagentDiagnostics(stdout, stderr) {
|
|
|
5182
5527
|
function createSubagentRunner(cwd, getCurrentModel) {
|
|
5183
5528
|
const activeProcs = new Set;
|
|
5184
5529
|
const subagentDepth = Number.parseInt(process.env.MC_SUBAGENT_DEPTH ?? "0", 10);
|
|
5185
|
-
const runSubagent = async (prompt, agentName, modelOverride) => {
|
|
5530
|
+
const runSubagent = async (prompt, agentName, modelOverride, abortSignal) => {
|
|
5186
5531
|
if (subagentDepth >= MAX_SUBAGENT_DEPTH) {
|
|
5187
5532
|
throw new Error(`Subagent recursion limit reached (depth ${subagentDepth}). ` + `Cannot spawn another subagent.`);
|
|
5188
5533
|
}
|
|
5534
|
+
if (abortSignal?.aborted) {
|
|
5535
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5536
|
+
}
|
|
5189
5537
|
const model = modelOverride ?? getCurrentModel();
|
|
5190
5538
|
const cmd = [
|
|
5191
5539
|
...getMcCommand(),
|
|
@@ -5208,6 +5556,16 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5208
5556
|
stdio: ["ignore", "pipe", "pipe", "pipe"]
|
|
5209
5557
|
});
|
|
5210
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
|
+
}
|
|
5211
5569
|
try {
|
|
5212
5570
|
const [text, stdout, stderr] = await Promise.all([
|
|
5213
5571
|
Bun.file(proc.stdio[3]).text(),
|
|
@@ -5215,6 +5573,9 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5215
5573
|
consumeTail(proc.stderr),
|
|
5216
5574
|
proc.exited
|
|
5217
5575
|
]);
|
|
5576
|
+
if (aborted || abortSignal?.aborted) {
|
|
5577
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5578
|
+
}
|
|
5218
5579
|
const diagnostics = formatSubagentDiagnostics(stdout, stderr);
|
|
5219
5580
|
const trimmed = text.trim();
|
|
5220
5581
|
if (!trimmed) {
|
|
@@ -5240,7 +5601,15 @@ function createSubagentRunner(cwd, getCurrentModel) {
|
|
|
5240
5601
|
if (agentName)
|
|
5241
5602
|
output.agentName = agentName;
|
|
5242
5603
|
return output;
|
|
5604
|
+
} catch (error) {
|
|
5605
|
+
if (aborted || abortSignal?.aborted) {
|
|
5606
|
+
throw new DOMException("Subagent execution was interrupted", "AbortError");
|
|
5607
|
+
}
|
|
5608
|
+
throw error;
|
|
5243
5609
|
} finally {
|
|
5610
|
+
if (abortSignal) {
|
|
5611
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
5612
|
+
}
|
|
5244
5613
|
activeProcs.delete(proc);
|
|
5245
5614
|
}
|
|
5246
5615
|
};
|
|
@@ -5437,7 +5806,7 @@ async function initAgent(opts) {
|
|
|
5437
5806
|
runner.planMode = v;
|
|
5438
5807
|
},
|
|
5439
5808
|
cwd,
|
|
5440
|
-
runSubagent: (prompt, agentName, model) => runSubagent(prompt, agentName, model),
|
|
5809
|
+
runSubagent: (prompt, agentName, model, abortSignal) => runSubagent(prompt, agentName, model, abortSignal),
|
|
5441
5810
|
get activeAgent() {
|
|
5442
5811
|
return activeAgentName;
|
|
5443
5812
|
},
|
|
@@ -5570,9 +5939,9 @@ ${c13.bold("Examples:")}`);
|
|
|
5570
5939
|
}
|
|
5571
5940
|
|
|
5572
5941
|
// src/cli/bootstrap.ts
|
|
5573
|
-
import { existsSync as
|
|
5574
|
-
import { homedir as
|
|
5575
|
-
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";
|
|
5576
5945
|
import * as c14 from "yoctocolors";
|
|
5577
5946
|
var REVIEW_COMMAND_CONTENT = `---
|
|
5578
5947
|
description: Review recent changes for correctness, code quality, and performance
|
|
@@ -5592,9 +5961,9 @@ Perform a sensible code review:
|
|
|
5592
5961
|
Output a small summary with only the issues found. If nothing is wrong, say so.
|
|
5593
5962
|
`;
|
|
5594
5963
|
function bootstrapGlobalDefaults() {
|
|
5595
|
-
const commandsDir =
|
|
5596
|
-
const reviewPath =
|
|
5597
|
-
if (!
|
|
5964
|
+
const commandsDir = join12(homedir10(), ".agents", "commands");
|
|
5965
|
+
const reviewPath = join12(commandsDir, "review.md");
|
|
5966
|
+
if (!existsSync6(reviewPath)) {
|
|
5598
5967
|
mkdirSync5(commandsDir, { recursive: true });
|
|
5599
5968
|
writeFileSync3(reviewPath, REVIEW_COMMAND_CONTENT, "utf-8");
|
|
5600
5969
|
writeln(`${c14.green("\u2713")} created ${c14.dim("~/.agents/commands/review.md")} ${c14.dim("(edit it to customise your reviews)")}`);
|
|
@@ -5602,26 +5971,35 @@ function bootstrapGlobalDefaults() {
|
|
|
5602
5971
|
}
|
|
5603
5972
|
|
|
5604
5973
|
// src/cli/file-refs.ts
|
|
5605
|
-
import { join as
|
|
5974
|
+
import { join as join13 } from "path";
|
|
5606
5975
|
async function resolveFileRefs(text, cwd) {
|
|
5607
5976
|
const atPattern = /@([\w./\-_]+)/g;
|
|
5608
5977
|
let result = text;
|
|
5609
5978
|
const matches = [...text.matchAll(atPattern)];
|
|
5610
5979
|
const images = [];
|
|
5611
|
-
const skills =
|
|
5980
|
+
const skills = loadSkillsIndex(cwd);
|
|
5981
|
+
const loadedSkills = new Map;
|
|
5612
5982
|
for (const match of [...matches].reverse()) {
|
|
5613
5983
|
const ref = match[1];
|
|
5614
5984
|
if (!ref)
|
|
5615
5985
|
continue;
|
|
5616
|
-
const
|
|
5617
|
-
if (
|
|
5618
|
-
|
|
5619
|
-
|
|
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}
|
|
5620
5998
|
</skill>`;
|
|
5621
5999
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
5622
6000
|
continue;
|
|
5623
6001
|
}
|
|
5624
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
6002
|
+
const filePath = ref.startsWith("/") ? ref : join13(cwd, ref);
|
|
5625
6003
|
if (isImageFilename(ref)) {
|
|
5626
6004
|
const attachment = await loadImageFile(filePath);
|
|
5627
6005
|
if (attachment) {
|
|
@@ -5668,7 +6046,9 @@ class HeadlessReporter {
|
|
|
5668
6046
|
accumulatedText += event.delta;
|
|
5669
6047
|
break;
|
|
5670
6048
|
case "reasoning-delta":
|
|
5671
|
-
reasoningText += event.delta;
|
|
6049
|
+
reasoningText += normalizeReasoningDelta(event.delta);
|
|
6050
|
+
break;
|
|
6051
|
+
case "context-pruned":
|
|
5672
6052
|
break;
|
|
5673
6053
|
case "turn-complete":
|
|
5674
6054
|
inputTokens = event.inputTokens;
|
|
@@ -5692,7 +6072,7 @@ class HeadlessReporter {
|
|
|
5692
6072
|
outputTokens,
|
|
5693
6073
|
contextTokens,
|
|
5694
6074
|
newMessages,
|
|
5695
|
-
reasoningText
|
|
6075
|
+
reasoningText: normalizeReasoningText(reasoningText)
|
|
5696
6076
|
};
|
|
5697
6077
|
}
|
|
5698
6078
|
renderStatusBar(_data) {}
|
|
@@ -5747,8 +6127,8 @@ async function runShellPassthrough(command, cwd, reporter) {
|
|
|
5747
6127
|
import * as c16 from "yoctocolors";
|
|
5748
6128
|
|
|
5749
6129
|
// src/cli/custom-commands.ts
|
|
5750
|
-
import { existsSync as
|
|
5751
|
-
import { join as
|
|
6130
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
6131
|
+
import { join as join14 } from "path";
|
|
5752
6132
|
function loadCustomCommands(cwd, homeDir) {
|
|
5753
6133
|
return loadMarkdownConfigs({
|
|
5754
6134
|
type: "commands",
|
|
@@ -5805,12 +6185,12 @@ async function expandTemplate(template, args, cwd) {
|
|
|
5805
6185
|
const fileMatches = [...result.matchAll(FILE_REF_RE)];
|
|
5806
6186
|
for (const match of fileMatches) {
|
|
5807
6187
|
const filePath = match[1] ?? "";
|
|
5808
|
-
const fullPath =
|
|
5809
|
-
if (!
|
|
6188
|
+
const fullPath = join14(cwd, filePath);
|
|
6189
|
+
if (!existsSync7(fullPath))
|
|
5810
6190
|
continue;
|
|
5811
6191
|
let content = "";
|
|
5812
6192
|
try {
|
|
5813
|
-
content =
|
|
6193
|
+
content = readFileSync4(fullPath, "utf-8");
|
|
5814
6194
|
} catch {
|
|
5815
6195
|
continue;
|
|
5816
6196
|
}
|
|
@@ -5870,10 +6250,10 @@ async function handleModel(ctx, args) {
|
|
|
5870
6250
|
}
|
|
5871
6251
|
return;
|
|
5872
6252
|
}
|
|
5873
|
-
|
|
6253
|
+
ctx.startSpinner("fetching models");
|
|
5874
6254
|
const snapshot = await fetchAvailableModels();
|
|
5875
6255
|
const models = snapshot.models;
|
|
5876
|
-
|
|
6256
|
+
ctx.stopSpinner();
|
|
5877
6257
|
if (models.length === 0) {
|
|
5878
6258
|
writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
|
|
5879
6259
|
writeln(c16.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
|
|
@@ -6063,14 +6443,14 @@ async function handleMcp(ctx, args) {
|
|
|
6063
6443
|
case "add": {
|
|
6064
6444
|
const [, name, transport, ...rest] = parts;
|
|
6065
6445
|
if (!name || !transport || rest.length === 0) {
|
|
6066
|
-
writeln(
|
|
6067
|
-
writeln(
|
|
6446
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> http <url>`);
|
|
6447
|
+
writeln(`${PREFIX.error} /mcp add <name> stdio <cmd> [args...]`);
|
|
6068
6448
|
return;
|
|
6069
6449
|
}
|
|
6070
6450
|
if (transport === "http") {
|
|
6071
6451
|
const url = rest[0];
|
|
6072
6452
|
if (!url) {
|
|
6073
|
-
writeln(
|
|
6453
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> http <url>`);
|
|
6074
6454
|
return;
|
|
6075
6455
|
}
|
|
6076
6456
|
upsertMcpServer({
|
|
@@ -6084,7 +6464,7 @@ async function handleMcp(ctx, args) {
|
|
|
6084
6464
|
} else if (transport === "stdio") {
|
|
6085
6465
|
const [command, ...cmdArgs] = rest;
|
|
6086
6466
|
if (!command) {
|
|
6087
|
-
writeln(
|
|
6467
|
+
writeln(`${PREFIX.error} usage: /mcp add <name> stdio <cmd> [args...]`);
|
|
6088
6468
|
return;
|
|
6089
6469
|
}
|
|
6090
6470
|
upsertMcpServer({
|
|
@@ -6096,7 +6476,7 @@ async function handleMcp(ctx, args) {
|
|
|
6096
6476
|
env: null
|
|
6097
6477
|
});
|
|
6098
6478
|
} else {
|
|
6099
|
-
writeln(
|
|
6479
|
+
writeln(`${PREFIX.error} unknown transport: ${transport} (use http or stdio)`);
|
|
6100
6480
|
return;
|
|
6101
6481
|
}
|
|
6102
6482
|
try {
|
|
@@ -6111,7 +6491,7 @@ async function handleMcp(ctx, args) {
|
|
|
6111
6491
|
case "rm": {
|
|
6112
6492
|
const [, name] = parts;
|
|
6113
6493
|
if (!name) {
|
|
6114
|
-
writeln(
|
|
6494
|
+
writeln(`${PREFIX.error} usage: /mcp remove <name>`);
|
|
6115
6495
|
return;
|
|
6116
6496
|
}
|
|
6117
6497
|
deleteMcpServer(name);
|
|
@@ -6119,7 +6499,7 @@ async function handleMcp(ctx, args) {
|
|
|
6119
6499
|
return;
|
|
6120
6500
|
}
|
|
6121
6501
|
default:
|
|
6122
|
-
writeln(
|
|
6502
|
+
writeln(`${PREFIX.error} unknown: /mcp ${sub}`);
|
|
6123
6503
|
writeln(c16.dim(" subcommands: list \xB7 add \xB7 remove"));
|
|
6124
6504
|
}
|
|
6125
6505
|
}
|
|
@@ -6138,9 +6518,13 @@ async function handleCustomCommand(cmd, args, ctx) {
|
|
|
6138
6518
|
if (!fork) {
|
|
6139
6519
|
return { type: "inject-user-message", text: prompt };
|
|
6140
6520
|
}
|
|
6521
|
+
const abortController = new AbortController;
|
|
6522
|
+
const stopWatcher = watchForCancel(abortController, {
|
|
6523
|
+
allowSubagentEsc: true
|
|
6524
|
+
});
|
|
6141
6525
|
try {
|
|
6142
6526
|
ctx.startSpinner("subagent");
|
|
6143
|
-
const output = await ctx.runSubagent(prompt, cmd.agent, cmd.model);
|
|
6527
|
+
const output = await ctx.runSubagent(prompt, cmd.agent, cmd.model, abortController.signal);
|
|
6144
6528
|
write(renderMarkdown(output.result));
|
|
6145
6529
|
writeln();
|
|
6146
6530
|
return {
|
|
@@ -6152,9 +6536,13 @@ ${output.result}
|
|
|
6152
6536
|
<system-message>Summarize the findings above to the user.</system-message>`
|
|
6153
6537
|
};
|
|
6154
6538
|
} catch (e) {
|
|
6539
|
+
if (isAbortError(e)) {
|
|
6540
|
+
return { type: "handled" };
|
|
6541
|
+
}
|
|
6155
6542
|
writeln(`${PREFIX.error} /${cmd.name} failed: ${String(e)}`);
|
|
6156
6543
|
return { type: "handled" };
|
|
6157
6544
|
} finally {
|
|
6545
|
+
stopWatcher();
|
|
6158
6546
|
ctx.stopSpinner();
|
|
6159
6547
|
}
|
|
6160
6548
|
}
|
|
@@ -6234,10 +6622,10 @@ function handleHelp(ctx, custom) {
|
|
|
6234
6622
|
writeln(` ${c16.magenta(`@${agent.name}`.padEnd(26))} ${c16.dim(agent.description)}${modeTag}${tag}`);
|
|
6235
6623
|
}
|
|
6236
6624
|
}
|
|
6237
|
-
const skills =
|
|
6625
|
+
const skills = loadSkillsIndex(ctx.cwd);
|
|
6238
6626
|
if (skills.size > 0) {
|
|
6239
6627
|
writeln();
|
|
6240
|
-
writeln(c16.dim(" skills (
|
|
6628
|
+
writeln(c16.dim(" skills (walk-up local .agents/.claude/skills + global ~/.agents/skills ~/.claude/skills):"));
|
|
6241
6629
|
for (const skill of skills.values()) {
|
|
6242
6630
|
const tag = skill.source === "local" ? c16.dim(" (local)") : c16.dim(" (global)");
|
|
6243
6631
|
writeln(` ${c16.yellow(`@${skill.name}`.padEnd(26))} ${c16.dim(skill.description)}${tag}`);
|
|
@@ -6245,7 +6633,7 @@ function handleHelp(ctx, custom) {
|
|
|
6245
6633
|
}
|
|
6246
6634
|
writeln();
|
|
6247
6635
|
writeln(` ${c16.green("@agent".padEnd(26))} ${c16.dim("run prompt through a custom agent (Tab to complete)")}`);
|
|
6248
|
-
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)")}`);
|
|
6249
6637
|
writeln(` ${c16.green("@file".padEnd(26))} ${c16.dim("inject file contents into prompt (Tab to complete)")}`);
|
|
6250
6638
|
writeln(` ${c16.green("!cmd".padEnd(26))} ${c16.dim("run shell command, output added as context")}`);
|
|
6251
6639
|
writeln();
|
|
@@ -6421,13 +6809,14 @@ ${out}
|
|
|
6421
6809
|
}
|
|
6422
6810
|
|
|
6423
6811
|
// src/cli/output-reporter.ts
|
|
6424
|
-
import * as c18 from "yoctocolors";
|
|
6425
6812
|
class CliReporter {
|
|
6426
6813
|
spinner = new Spinner;
|
|
6427
6814
|
info(msg) {
|
|
6815
|
+
this.spinner.stop();
|
|
6428
6816
|
renderInfo(msg);
|
|
6429
6817
|
}
|
|
6430
6818
|
error(msg, hint) {
|
|
6819
|
+
this.spinner.stop();
|
|
6431
6820
|
if (typeof msg === "string") {
|
|
6432
6821
|
renderError(msg, hint);
|
|
6433
6822
|
} else {
|
|
@@ -6435,9 +6824,11 @@ class CliReporter {
|
|
|
6435
6824
|
}
|
|
6436
6825
|
}
|
|
6437
6826
|
warn(msg) {
|
|
6438
|
-
|
|
6827
|
+
this.spinner.stop();
|
|
6828
|
+
writeln(`${G.warn} ${msg}`);
|
|
6439
6829
|
}
|
|
6440
6830
|
writeText(text) {
|
|
6831
|
+
this.spinner.stop();
|
|
6441
6832
|
writeln(text);
|
|
6442
6833
|
}
|
|
6443
6834
|
startSpinner(label) {
|
|
@@ -6484,7 +6875,7 @@ async function main() {
|
|
|
6484
6875
|
if (last) {
|
|
6485
6876
|
sessionId = last.id;
|
|
6486
6877
|
} else {
|
|
6487
|
-
writeln(
|
|
6878
|
+
writeln(c18.dim("No previous session found, starting fresh."));
|
|
6488
6879
|
}
|
|
6489
6880
|
} else if (args.sessionId) {
|
|
6490
6881
|
sessionId = args.sessionId;
|
package/docs/skills.md
CHANGED
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
# Skills
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Use `@skill-name` to load it — the content is inserted into the message
|
|
5
|
-
before it's sent to the LLM.
|
|
3
|
+
Skills are reusable instruction files discovered automatically from local and global directories.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- The model sees **skill metadata only** by default (name, description, source).
|
|
6
|
+
- Full `SKILL.md` content is loaded **on demand**:
|
|
7
|
+
- when explicitly requested with the runtime skill tools (`listSkills` / `readSkill`), or
|
|
8
|
+
- when you reference `@skill-name` in your prompt.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Discovery locations
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Skills live in folders containing `SKILL.md`:
|
|
13
13
|
|
|
14
14
|
| Location | Scope |
|
|
15
15
|
|---|---|
|
|
16
|
-
| `.agents/skills/<name>/SKILL.md` |
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
| `~/.claude/skills/<name>/SKILL.md` |
|
|
16
|
+
| `.agents/skills/<name>/SKILL.md` | Local |
|
|
17
|
+
| `.claude/skills/<name>/SKILL.md` | Local (Claude-compatible) |
|
|
18
|
+
| `~/.agents/skills/<name>/SKILL.md` | Global |
|
|
19
|
+
| `~/.claude/skills/<name>/SKILL.md` | Global (Claude-compatible) |
|
|
20
20
|
|
|
21
|
-
Local
|
|
21
|
+
Local discovery walks up from the current working directory to the git worktree root.
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Precedence rules
|
|
24
|
+
|
|
25
|
+
If multiple skills share the same `name`, precedence is deterministic:
|
|
26
|
+
|
|
27
|
+
1. Nearest local directory wins over farther ancestor directories.
|
|
28
|
+
2. Any local skill wins over global.
|
|
29
|
+
3. At the same scope/path level, `.agents` wins over `.claude`.
|
|
30
|
+
|
|
31
|
+
## Frontmatter validation
|
|
32
|
+
|
|
33
|
+
`SKILL.md` frontmatter must include:
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
- `name` (required)
|
|
36
|
+
- `description` (required)
|
|
37
|
+
|
|
38
|
+
`name` constraints:
|
|
39
|
+
|
|
40
|
+
- lowercase alphanumeric and hyphen format (`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
|
|
41
|
+
- 1–64 characters
|
|
42
|
+
|
|
43
|
+
Invalid skills are skipped with warnings. Unknown frontmatter fields are allowed.
|
|
44
|
+
|
|
45
|
+
## Create a skill
|
|
26
46
|
|
|
27
47
|
`.agents/skills/conventional-commits/SKILL.md`:
|
|
28
48
|
|
|
@@ -34,40 +54,25 @@ description: Conventional commit message format rules
|
|
|
34
54
|
|
|
35
55
|
# Conventional Commits
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<type>(<scope>): <short summary>
|
|
40
|
-
|
|
41
|
-
Types: feat, fix, docs, refactor, test, chore
|
|
42
|
-
- Summary is lowercase, no period at the end
|
|
43
|
-
- Breaking changes: add `!` after type, e.g. `feat!:`
|
|
44
|
-
- Body is optional, wrapped at 72 chars
|
|
57
|
+
Use:
|
|
58
|
+
<type>(<scope>): <short summary>
|
|
45
59
|
```
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
## Use a skill explicitly
|
|
48
62
|
|
|
49
|
-
```
|
|
63
|
+
```text
|
|
50
64
|
@conventional-commits write a commit message for my staged changes
|
|
51
65
|
```
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
included in the message sent to the LLM.
|
|
55
|
-
|
|
56
|
-
## Frontmatter fields
|
|
57
|
-
|
|
58
|
-
| Field | Required | Description |
|
|
59
|
-
|---|---|---|
|
|
60
|
-
| `name` | No | Skill name for `@` reference. Defaults to folder name. |
|
|
61
|
-
| `description` | No | Shown in `/help`. Defaults to name. |
|
|
62
|
-
|
|
63
|
-
## Tab completion
|
|
64
|
-
|
|
65
|
-
Type `@` and press `Tab` to autocomplete skill names alongside agents and files.
|
|
66
|
-
|
|
67
|
-
## Listing skills
|
|
67
|
+
`@skill-name` injects the raw skill body wrapped as:
|
|
68
68
|
|
|
69
|
+
```xml
|
|
70
|
+
<skill name="conventional-commits">
|
|
71
|
+
...
|
|
72
|
+
</skill>
|
|
69
73
|
```
|
|
70
|
-
/help
|
|
71
|
-
```
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
## Tab completion and help
|
|
76
|
+
|
|
77
|
+
- Type `@` then `Tab` to complete skill names.
|
|
78
|
+
- Run `/help` to list discovered skills with `(local)` / `(global)` tags.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mini-coder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "A small, fast CLI coding agent",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"jscpd": "jscpd src"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
23
|
-
"@ai-sdk/google": "^3.0.
|
|
24
|
-
"@ai-sdk/openai": "^3.0.
|
|
25
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
22
|
+
"@ai-sdk/anthropic": "^3.0.60",
|
|
23
|
+
"@ai-sdk/google": "^3.0.51",
|
|
24
|
+
"@ai-sdk/openai": "^3.0.45",
|
|
25
|
+
"@ai-sdk/openai-compatible": "^2.0.36",
|
|
26
26
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
27
|
-
"ai": "^6.0.
|
|
27
|
+
"ai": "^6.0.127",
|
|
28
28
|
"ignore": "^7.0.5",
|
|
29
29
|
"yoctocolors": "^2.1.2",
|
|
30
30
|
"zod": "^4.3.6"
|