mini-coder 0.0.4 → 0.0.6
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 +33 -4
- package/better-errors.md +96 -0
- package/dist/mc.js +574 -242
- package/docs/configs.md +77 -0
- package/docs/custom-agents.md +70 -0
- package/docs/custom-commands.md +133 -0
- package/docs/skills.md +71 -0
- package/docs/tool-hooks.md +142 -0
- package/package.json +1 -1
package/dist/mc.js
CHANGED
|
@@ -2,16 +2,86 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import * as
|
|
5
|
+
import * as c8 from "yoctocolors";
|
|
6
6
|
|
|
7
7
|
// src/agent/agent.ts
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
|
|
8
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
9
|
+
import { join as join14 } from "path";
|
|
10
|
+
import * as c7 from "yoctocolors";
|
|
11
|
+
|
|
12
|
+
// src/cli/agents.ts
|
|
13
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { basename, join } from "path";
|
|
16
|
+
|
|
17
|
+
// src/cli/frontmatter.ts
|
|
18
|
+
var FM_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
19
|
+
function parseFrontmatter(raw) {
|
|
20
|
+
const m = raw.match(FM_RE);
|
|
21
|
+
if (!m)
|
|
22
|
+
return { meta: {}, body: raw };
|
|
23
|
+
const meta = {};
|
|
24
|
+
const yamlBlock = m[1] ?? "";
|
|
25
|
+
for (const line of yamlBlock.split(`
|
|
26
|
+
`)) {
|
|
27
|
+
const colon = line.indexOf(":");
|
|
28
|
+
if (colon === -1)
|
|
29
|
+
continue;
|
|
30
|
+
const key = line.slice(0, colon).trim();
|
|
31
|
+
const val = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
32
|
+
if (key === "name")
|
|
33
|
+
meta.name = val;
|
|
34
|
+
if (key === "description")
|
|
35
|
+
meta.description = val;
|
|
36
|
+
if (key === "model")
|
|
37
|
+
meta.model = val;
|
|
38
|
+
}
|
|
39
|
+
return { meta, body: (m[2] ?? "").trim() };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/cli/agents.ts
|
|
43
|
+
function loadFromDir(dir, source) {
|
|
44
|
+
const agents = new Map;
|
|
45
|
+
if (!existsSync(dir))
|
|
46
|
+
return agents;
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = readdirSync(dir);
|
|
50
|
+
} catch {
|
|
51
|
+
return agents;
|
|
52
|
+
}
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (!entry.endsWith(".md"))
|
|
55
|
+
continue;
|
|
56
|
+
const name = basename(entry, ".md");
|
|
57
|
+
const filePath = join(dir, entry);
|
|
58
|
+
let raw;
|
|
59
|
+
try {
|
|
60
|
+
raw = readFileSync(filePath, "utf-8");
|
|
61
|
+
} catch {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
65
|
+
agents.set(name, {
|
|
66
|
+
name,
|
|
67
|
+
description: meta.description ?? name,
|
|
68
|
+
...meta.model ? { model: meta.model } : {},
|
|
69
|
+
systemPrompt: body,
|
|
70
|
+
source
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return agents;
|
|
74
|
+
}
|
|
75
|
+
function loadAgents(cwd) {
|
|
76
|
+
const globalDir = join(homedir(), ".agents", "agents");
|
|
77
|
+
const localDir = join(cwd, ".agents", "agents");
|
|
78
|
+
const global = loadFromDir(globalDir, "global");
|
|
79
|
+
const local = loadFromDir(localDir, "local");
|
|
80
|
+
return new Map([...global, ...local]);
|
|
81
|
+
}
|
|
12
82
|
|
|
13
83
|
// src/cli/commands.ts
|
|
14
|
-
import * as
|
|
84
|
+
import * as c4 from "yoctocolors";
|
|
15
85
|
|
|
16
86
|
// src/llm-api/providers.ts
|
|
17
87
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
@@ -20,34 +90,18 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
20
90
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
21
91
|
import { createOllama } from "ollama-ai-provider";
|
|
22
92
|
var ZEN_BASE = "https://opencode.ai/zen/v1";
|
|
23
|
-
|
|
24
|
-
"claude-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]);
|
|
33
|
-
var ZEN_OPENAI_MODELS = new Set([
|
|
34
|
-
"gpt-5.2",
|
|
35
|
-
"gpt-5.2-codex",
|
|
36
|
-
"gpt-5.1",
|
|
37
|
-
"gpt-5.1-codex",
|
|
38
|
-
"gpt-5.1-codex-max",
|
|
39
|
-
"gpt-5.1-codex-mini",
|
|
40
|
-
"gpt-5",
|
|
41
|
-
"gpt-5-codex",
|
|
42
|
-
"gpt-5-nano"
|
|
43
|
-
]);
|
|
44
|
-
var ZEN_GOOGLE_MODELS = new Set([
|
|
45
|
-
"gemini-3.1-pro",
|
|
46
|
-
"gemini-3-pro",
|
|
47
|
-
"gemini-3-flash"
|
|
48
|
-
]);
|
|
93
|
+
function zenEndpointFor(modelId) {
|
|
94
|
+
if (modelId.startsWith("claude-"))
|
|
95
|
+
return zenAnthropic()(modelId);
|
|
96
|
+
if (modelId.startsWith("gpt-"))
|
|
97
|
+
return zenOpenAI()(modelId);
|
|
98
|
+
if (modelId.startsWith("gemini-"))
|
|
99
|
+
return zenGoogle()(modelId);
|
|
100
|
+
return zenCompat()(modelId);
|
|
101
|
+
}
|
|
49
102
|
var _zenAnthropic = null;
|
|
50
103
|
var _zenOpenAI = null;
|
|
104
|
+
var _zenGoogle = null;
|
|
51
105
|
var _zenCompat = null;
|
|
52
106
|
function getZenApiKey() {
|
|
53
107
|
const key = process.env.OPENCODE_API_KEY;
|
|
@@ -73,11 +127,14 @@ function zenOpenAI() {
|
|
|
73
127
|
}
|
|
74
128
|
return _zenOpenAI;
|
|
75
129
|
}
|
|
76
|
-
function zenGoogle(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
130
|
+
function zenGoogle() {
|
|
131
|
+
if (!_zenGoogle) {
|
|
132
|
+
_zenGoogle = createGoogleGenerativeAI({
|
|
133
|
+
apiKey: getZenApiKey(),
|
|
134
|
+
baseURL: ZEN_BASE
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return _zenGoogle;
|
|
81
138
|
}
|
|
82
139
|
function zenCompat() {
|
|
83
140
|
if (!_zenCompat) {
|
|
@@ -146,16 +203,7 @@ function resolveModel(modelString) {
|
|
|
146
203
|
const modelId = modelString.slice(slashIdx + 1);
|
|
147
204
|
switch (provider) {
|
|
148
205
|
case "zen": {
|
|
149
|
-
|
|
150
|
-
return zenAnthropic()(modelId);
|
|
151
|
-
}
|
|
152
|
-
if (ZEN_OPENAI_MODELS.has(modelId)) {
|
|
153
|
-
return zenOpenAI()(modelId);
|
|
154
|
-
}
|
|
155
|
-
if (ZEN_GOOGLE_MODELS.has(modelId)) {
|
|
156
|
-
return zenGoogle(modelId)(modelId);
|
|
157
|
-
}
|
|
158
|
-
return zenCompat()(modelId);
|
|
206
|
+
return zenEndpointFor(modelId);
|
|
159
207
|
}
|
|
160
208
|
case "anthropic":
|
|
161
209
|
return directAnthropic()(modelId);
|
|
@@ -235,17 +283,17 @@ async function fetchAvailableModels() {
|
|
|
235
283
|
|
|
236
284
|
// src/session/db.ts
|
|
237
285
|
import { Database } from "bun:sqlite";
|
|
238
|
-
import { existsSync, mkdirSync, unlinkSync } from "fs";
|
|
239
|
-
import { homedir } from "os";
|
|
240
|
-
import { join } from "path";
|
|
286
|
+
import { existsSync as existsSync2, mkdirSync, unlinkSync } from "fs";
|
|
287
|
+
import { homedir as homedir2 } from "os";
|
|
288
|
+
import { join as join2 } from "path";
|
|
241
289
|
function getConfigDir() {
|
|
242
|
-
return
|
|
290
|
+
return join2(homedir2(), ".config", "mini-coder");
|
|
243
291
|
}
|
|
244
292
|
function getDbPath() {
|
|
245
293
|
const dir = getConfigDir();
|
|
246
|
-
if (!
|
|
294
|
+
if (!existsSync2(dir))
|
|
247
295
|
mkdirSync(dir, { recursive: true });
|
|
248
|
-
return
|
|
296
|
+
return join2(dir, "sessions.db");
|
|
249
297
|
}
|
|
250
298
|
var DB_VERSION = 3;
|
|
251
299
|
var SCHEMA = `
|
|
@@ -321,7 +369,7 @@ function getDb() {
|
|
|
321
369
|
db.close();
|
|
322
370
|
} catch {}
|
|
323
371
|
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
324
|
-
if (
|
|
372
|
+
if (existsSync2(path))
|
|
325
373
|
unlinkSync(path);
|
|
326
374
|
}
|
|
327
375
|
db = new Database(dbPath, { create: true });
|
|
@@ -471,6 +519,18 @@ function generateSessionId() {
|
|
|
471
519
|
return `${ts}-${rand}`;
|
|
472
520
|
}
|
|
473
521
|
|
|
522
|
+
// src/cli/custom-commands.ts
|
|
523
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
524
|
+
import { homedir as homedir4 } from "os";
|
|
525
|
+
import { basename as basename2, join as join3 } from "path";
|
|
526
|
+
|
|
527
|
+
// src/cli/config-conflicts.ts
|
|
528
|
+
import * as c3 from "yoctocolors";
|
|
529
|
+
|
|
530
|
+
// src/cli/output.ts
|
|
531
|
+
import { homedir as homedir3 } from "os";
|
|
532
|
+
import * as c2 from "yoctocolors";
|
|
533
|
+
|
|
474
534
|
// src/cli/markdown.ts
|
|
475
535
|
import * as c from "yoctocolors";
|
|
476
536
|
function renderInline(text) {
|
|
@@ -584,10 +644,11 @@ function renderChunk(text, inFence) {
|
|
|
584
644
|
}
|
|
585
645
|
|
|
586
646
|
// src/cli/output.ts
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
647
|
+
var HOME = homedir3();
|
|
648
|
+
var PACKAGE_VERSION = "0.1.0";
|
|
649
|
+
function tildePath(p) {
|
|
650
|
+
return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
|
|
651
|
+
}
|
|
591
652
|
function restoreTerminal() {
|
|
592
653
|
try {
|
|
593
654
|
process.stderr.write("\x1B[?25h");
|
|
@@ -1209,6 +1270,165 @@ var PREFIX = {
|
|
|
1209
1270
|
success: G.ok
|
|
1210
1271
|
};
|
|
1211
1272
|
|
|
1273
|
+
// src/cli/config-conflicts.ts
|
|
1274
|
+
function warnConventionConflicts(kind, scope, agentsNames, claudeNames) {
|
|
1275
|
+
const agents = new Set(agentsNames);
|
|
1276
|
+
const claude = new Set(claudeNames);
|
|
1277
|
+
const conflicts = [];
|
|
1278
|
+
for (const name of agents) {
|
|
1279
|
+
if (claude.has(name))
|
|
1280
|
+
conflicts.push(name);
|
|
1281
|
+
}
|
|
1282
|
+
if (conflicts.length === 0)
|
|
1283
|
+
return;
|
|
1284
|
+
conflicts.sort((a, b) => a.localeCompare(b));
|
|
1285
|
+
const list = conflicts.map((n) => c3.cyan(n)).join(c3.dim(", "));
|
|
1286
|
+
writeln(`${G.warn} conflicting ${kind} in ${scope} .agents and .claude: ${list} ${c3.dim("\u2014 using .agents version")}`);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/cli/custom-commands.ts
|
|
1290
|
+
function loadFromDir2(dir, source) {
|
|
1291
|
+
const commands = new Map;
|
|
1292
|
+
if (!existsSync3(dir))
|
|
1293
|
+
return commands;
|
|
1294
|
+
let entries;
|
|
1295
|
+
try {
|
|
1296
|
+
entries = readdirSync2(dir);
|
|
1297
|
+
} catch {
|
|
1298
|
+
return commands;
|
|
1299
|
+
}
|
|
1300
|
+
for (const entry of entries) {
|
|
1301
|
+
if (!entry.endsWith(".md"))
|
|
1302
|
+
continue;
|
|
1303
|
+
const name = basename2(entry, ".md");
|
|
1304
|
+
const filePath = join3(dir, entry);
|
|
1305
|
+
let raw;
|
|
1306
|
+
try {
|
|
1307
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
1308
|
+
} catch {
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
1312
|
+
commands.set(name, {
|
|
1313
|
+
name,
|
|
1314
|
+
description: meta.description ?? name,
|
|
1315
|
+
...meta.model ? { model: meta.model } : {},
|
|
1316
|
+
template: body,
|
|
1317
|
+
source
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
return commands;
|
|
1321
|
+
}
|
|
1322
|
+
function loadCustomCommands(cwd) {
|
|
1323
|
+
const globalAgentsDir = join3(homedir4(), ".agents", "commands");
|
|
1324
|
+
const globalClaudeDir = join3(homedir4(), ".claude", "commands");
|
|
1325
|
+
const localAgentsDir = join3(cwd, ".agents", "commands");
|
|
1326
|
+
const localClaudeDir = join3(cwd, ".claude", "commands");
|
|
1327
|
+
const globalAgents = loadFromDir2(globalAgentsDir, "global");
|
|
1328
|
+
const globalClaude = loadFromDir2(globalClaudeDir, "global");
|
|
1329
|
+
const localAgents = loadFromDir2(localAgentsDir, "local");
|
|
1330
|
+
const localClaude = loadFromDir2(localClaudeDir, "local");
|
|
1331
|
+
warnConventionConflicts("commands", "global", globalAgents.keys(), globalClaude.keys());
|
|
1332
|
+
warnConventionConflicts("commands", "local", localAgents.keys(), localClaude.keys());
|
|
1333
|
+
return new Map([
|
|
1334
|
+
...globalClaude,
|
|
1335
|
+
...globalAgents,
|
|
1336
|
+
...localClaude,
|
|
1337
|
+
...localAgents
|
|
1338
|
+
]);
|
|
1339
|
+
}
|
|
1340
|
+
async function expandTemplate(template, args, cwd) {
|
|
1341
|
+
const tokens = args.match(/("([^"]*)")|('([^']*)')|(\S+)/g)?.map((t) => t.replace(/^["']|["']$/g, "")) ?? [];
|
|
1342
|
+
let result = template;
|
|
1343
|
+
for (let i = 9;i >= 1; i--) {
|
|
1344
|
+
result = result.replaceAll(`$${i}`, tokens[i - 1] ?? "");
|
|
1345
|
+
}
|
|
1346
|
+
result = result.replaceAll("$ARGUMENTS", args);
|
|
1347
|
+
const SHELL_RE = /!`([^`]+)`/g;
|
|
1348
|
+
const shellMatches = [...result.matchAll(SHELL_RE)];
|
|
1349
|
+
for (const match of shellMatches) {
|
|
1350
|
+
const cmd = match[1] ?? "";
|
|
1351
|
+
let output = "";
|
|
1352
|
+
try {
|
|
1353
|
+
const signal = AbortSignal.timeout(1e4);
|
|
1354
|
+
const proc = Bun.spawn(["bash", "-c", cmd], {
|
|
1355
|
+
cwd,
|
|
1356
|
+
stdout: "pipe",
|
|
1357
|
+
stderr: "pipe"
|
|
1358
|
+
});
|
|
1359
|
+
await Promise.race([
|
|
1360
|
+
proc.exited,
|
|
1361
|
+
new Promise((_, reject) => signal.addEventListener("abort", () => {
|
|
1362
|
+
proc.kill();
|
|
1363
|
+
reject(new Error("timeout"));
|
|
1364
|
+
}))
|
|
1365
|
+
]);
|
|
1366
|
+
const [stdout, stderr] = await Promise.all([
|
|
1367
|
+
new Response(proc.stdout).text(),
|
|
1368
|
+
new Response(proc.stderr).text()
|
|
1369
|
+
]);
|
|
1370
|
+
const exitCode = proc.exitCode ?? 0;
|
|
1371
|
+
output = exitCode === 0 ? [stdout, stderr].filter(Boolean).join(`
|
|
1372
|
+
`).trim() : stdout.trim();
|
|
1373
|
+
} catch {}
|
|
1374
|
+
result = result.replaceAll(match[0], output);
|
|
1375
|
+
}
|
|
1376
|
+
return result;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/cli/skills.ts
|
|
1380
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
1381
|
+
import { homedir as homedir5 } from "os";
|
|
1382
|
+
import { join as join4 } from "path";
|
|
1383
|
+
function loadFromDir3(dir, source) {
|
|
1384
|
+
const skills = new Map;
|
|
1385
|
+
if (!existsSync4(dir))
|
|
1386
|
+
return skills;
|
|
1387
|
+
let entries;
|
|
1388
|
+
try {
|
|
1389
|
+
entries = readdirSync3(dir);
|
|
1390
|
+
} catch {
|
|
1391
|
+
return skills;
|
|
1392
|
+
}
|
|
1393
|
+
for (const entry of entries) {
|
|
1394
|
+
const skillFile = join4(dir, entry, "SKILL.md");
|
|
1395
|
+
try {
|
|
1396
|
+
if (!statSync(join4(dir, entry)).isDirectory())
|
|
1397
|
+
continue;
|
|
1398
|
+
if (!existsSync4(skillFile))
|
|
1399
|
+
continue;
|
|
1400
|
+
const content = readFileSync3(skillFile, "utf-8");
|
|
1401
|
+
const { meta } = parseFrontmatter(content);
|
|
1402
|
+
const name = meta.name ?? entry;
|
|
1403
|
+
skills.set(name, {
|
|
1404
|
+
name,
|
|
1405
|
+
description: meta.description ?? name,
|
|
1406
|
+
content,
|
|
1407
|
+
source
|
|
1408
|
+
});
|
|
1409
|
+
} catch {}
|
|
1410
|
+
}
|
|
1411
|
+
return skills;
|
|
1412
|
+
}
|
|
1413
|
+
function loadSkills(cwd) {
|
|
1414
|
+
const globalAgentsDir = join4(homedir5(), ".agents", "skills");
|
|
1415
|
+
const globalClaudeDir = join4(homedir5(), ".claude", "skills");
|
|
1416
|
+
const localAgentsDir = join4(cwd, ".agents", "skills");
|
|
1417
|
+
const localClaudeDir = join4(cwd, ".claude", "skills");
|
|
1418
|
+
const globalAgents = loadFromDir3(globalAgentsDir, "global");
|
|
1419
|
+
const globalClaude = loadFromDir3(globalClaudeDir, "global");
|
|
1420
|
+
const localAgents = loadFromDir3(localAgentsDir, "local");
|
|
1421
|
+
const localClaude = loadFromDir3(localClaudeDir, "local");
|
|
1422
|
+
warnConventionConflicts("skills", "global", globalAgents.keys(), globalClaude.keys());
|
|
1423
|
+
warnConventionConflicts("skills", "local", localAgents.keys(), localClaude.keys());
|
|
1424
|
+
return new Map([
|
|
1425
|
+
...globalClaude,
|
|
1426
|
+
...globalAgents,
|
|
1427
|
+
...localClaude,
|
|
1428
|
+
...localAgents
|
|
1429
|
+
]);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1212
1432
|
// src/cli/commands.ts
|
|
1213
1433
|
async function handleModel(ctx, args) {
|
|
1214
1434
|
if (args) {
|
|
@@ -1219,20 +1439,20 @@ async function handleModel(ctx, args) {
|
|
|
1219
1439
|
if (match) {
|
|
1220
1440
|
modelId = match.id;
|
|
1221
1441
|
} else {
|
|
1222
|
-
writeln(`${PREFIX.error} unknown model ${
|
|
1442
|
+
writeln(`${PREFIX.error} unknown model ${c4.cyan(args)} ${c4.dim("\u2014 run /models for the full list")}`);
|
|
1223
1443
|
return;
|
|
1224
1444
|
}
|
|
1225
1445
|
}
|
|
1226
1446
|
ctx.setModel(modelId);
|
|
1227
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
1447
|
+
writeln(`${PREFIX.success} model \u2192 ${c4.cyan(modelId)}`);
|
|
1228
1448
|
return;
|
|
1229
1449
|
}
|
|
1230
|
-
writeln(`${
|
|
1450
|
+
writeln(`${c4.dim(" fetching models\u2026")}`);
|
|
1231
1451
|
const models = await fetchAvailableModels();
|
|
1232
1452
|
process.stdout.write("\x1B[1A\r\x1B[2K");
|
|
1233
1453
|
if (models.length === 0) {
|
|
1234
1454
|
writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
|
|
1235
|
-
writeln(
|
|
1455
|
+
writeln(c4.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
|
|
1236
1456
|
return;
|
|
1237
1457
|
}
|
|
1238
1458
|
const byProvider = new Map;
|
|
@@ -1246,27 +1466,27 @@ async function handleModel(ctx, args) {
|
|
|
1246
1466
|
}
|
|
1247
1467
|
writeln();
|
|
1248
1468
|
for (const [provider, list] of byProvider) {
|
|
1249
|
-
writeln(
|
|
1469
|
+
writeln(c4.bold(` ${provider}`));
|
|
1250
1470
|
for (const m of list) {
|
|
1251
1471
|
const isCurrent = ctx.currentModel === m.id;
|
|
1252
|
-
const freeTag = m.free ?
|
|
1253
|
-
const ctxTag = m.context ?
|
|
1254
|
-
const cur = isCurrent ?
|
|
1255
|
-
writeln(` ${
|
|
1256
|
-
writeln(` ${
|
|
1472
|
+
const freeTag = m.free ? c4.green(" free") : "";
|
|
1473
|
+
const ctxTag = m.context ? c4.dim(` ${Math.round(m.context / 1000)}k`) : "";
|
|
1474
|
+
const cur = isCurrent ? c4.cyan(" \u25C0") : "";
|
|
1475
|
+
writeln(` ${c4.dim("\xB7")} ${m.displayName}${freeTag}${ctxTag}${cur}`);
|
|
1476
|
+
writeln(` ${c4.dim(m.id)}`);
|
|
1257
1477
|
}
|
|
1258
1478
|
}
|
|
1259
1479
|
writeln();
|
|
1260
|
-
writeln(
|
|
1480
|
+
writeln(c4.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
|
|
1261
1481
|
}
|
|
1262
1482
|
function handlePlan(ctx) {
|
|
1263
1483
|
ctx.setPlanMode(!ctx.planMode);
|
|
1264
1484
|
if (ctx.planMode) {
|
|
1265
1485
|
if (ctx.ralphMode)
|
|
1266
1486
|
ctx.setRalphMode(false);
|
|
1267
|
-
writeln(`${PREFIX.info} ${
|
|
1487
|
+
writeln(`${PREFIX.info} ${c4.yellow("plan mode")} ${c4.dim("\u2014 read-only tools + MCP, no writes or shell")}`);
|
|
1268
1488
|
} else {
|
|
1269
|
-
writeln(`${PREFIX.info} ${
|
|
1489
|
+
writeln(`${PREFIX.info} ${c4.dim("plan mode off")}`);
|
|
1270
1490
|
}
|
|
1271
1491
|
}
|
|
1272
1492
|
function handleRalph(ctx) {
|
|
@@ -1274,17 +1494,17 @@ function handleRalph(ctx) {
|
|
|
1274
1494
|
if (ctx.ralphMode) {
|
|
1275
1495
|
if (ctx.planMode)
|
|
1276
1496
|
ctx.setPlanMode(false);
|
|
1277
|
-
writeln(`${PREFIX.info} ${
|
|
1497
|
+
writeln(`${PREFIX.info} ${c4.magenta("ralph mode")} ${c4.dim("\u2014 loops until done, fresh context each iteration")}`);
|
|
1278
1498
|
} else {
|
|
1279
|
-
writeln(`${PREFIX.info} ${
|
|
1499
|
+
writeln(`${PREFIX.info} ${c4.dim("ralph mode off")}`);
|
|
1280
1500
|
}
|
|
1281
1501
|
}
|
|
1282
1502
|
async function handleUndo(ctx) {
|
|
1283
1503
|
const ok = await ctx.undoLastTurn();
|
|
1284
1504
|
if (ok) {
|
|
1285
|
-
writeln(`${PREFIX.success} ${
|
|
1505
|
+
writeln(`${PREFIX.success} ${c4.dim("last turn undone \u2014 history and files restored")}`);
|
|
1286
1506
|
} else {
|
|
1287
|
-
writeln(`${PREFIX.info} ${
|
|
1507
|
+
writeln(`${PREFIX.info} ${c4.dim("nothing to undo")}`);
|
|
1288
1508
|
}
|
|
1289
1509
|
}
|
|
1290
1510
|
async function handleMcp(ctx, args) {
|
|
@@ -1294,28 +1514,28 @@ async function handleMcp(ctx, args) {
|
|
|
1294
1514
|
case "list": {
|
|
1295
1515
|
const servers = listMcpServers();
|
|
1296
1516
|
if (servers.length === 0) {
|
|
1297
|
-
writeln(
|
|
1298
|
-
writeln(
|
|
1517
|
+
writeln(c4.dim(" no MCP servers configured"));
|
|
1518
|
+
writeln(c4.dim(" /mcp add <name> http <url> \xB7 /mcp add <name> stdio <cmd> [args...]"));
|
|
1299
1519
|
return;
|
|
1300
1520
|
}
|
|
1301
1521
|
writeln();
|
|
1302
1522
|
for (const s of servers) {
|
|
1303
|
-
const detail = s.url ?
|
|
1304
|
-
writeln(` ${
|
|
1523
|
+
const detail = s.url ? c4.dim(` ${s.url}`) : s.command ? c4.dim(` ${s.command}`) : "";
|
|
1524
|
+
writeln(` ${c4.yellow("\u2699")} ${c4.bold(s.name)} ${c4.dim(s.transport)}${detail}`);
|
|
1305
1525
|
}
|
|
1306
1526
|
return;
|
|
1307
1527
|
}
|
|
1308
1528
|
case "add": {
|
|
1309
1529
|
const [, name, transport, ...rest] = parts;
|
|
1310
1530
|
if (!name || !transport || rest.length === 0) {
|
|
1311
|
-
writeln(
|
|
1312
|
-
writeln(
|
|
1531
|
+
writeln(c4.red(" usage: /mcp add <name> http <url>"));
|
|
1532
|
+
writeln(c4.red(" /mcp add <name> stdio <cmd> [args...]"));
|
|
1313
1533
|
return;
|
|
1314
1534
|
}
|
|
1315
1535
|
if (transport === "http") {
|
|
1316
1536
|
const url = rest[0];
|
|
1317
1537
|
if (!url) {
|
|
1318
|
-
writeln(
|
|
1538
|
+
writeln(c4.red(" usage: /mcp add <name> http <url>"));
|
|
1319
1539
|
return;
|
|
1320
1540
|
}
|
|
1321
1541
|
upsertMcpServer({
|
|
@@ -1329,7 +1549,7 @@ async function handleMcp(ctx, args) {
|
|
|
1329
1549
|
} else if (transport === "stdio") {
|
|
1330
1550
|
const [command, ...cmdArgs] = rest;
|
|
1331
1551
|
if (!command) {
|
|
1332
|
-
writeln(
|
|
1552
|
+
writeln(c4.red(" usage: /mcp add <name> stdio <cmd> [args...]"));
|
|
1333
1553
|
return;
|
|
1334
1554
|
}
|
|
1335
1555
|
upsertMcpServer({
|
|
@@ -1341,14 +1561,14 @@ async function handleMcp(ctx, args) {
|
|
|
1341
1561
|
env: null
|
|
1342
1562
|
});
|
|
1343
1563
|
} else {
|
|
1344
|
-
writeln(
|
|
1564
|
+
writeln(c4.red(` unknown transport: ${transport} (use http or stdio)`));
|
|
1345
1565
|
return;
|
|
1346
1566
|
}
|
|
1347
1567
|
try {
|
|
1348
1568
|
await ctx.connectMcpServer(name);
|
|
1349
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1569
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} added and connected`);
|
|
1350
1570
|
} catch (e) {
|
|
1351
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1571
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} saved ${c4.dim(`(connection failed: ${String(e)})`)}`);
|
|
1352
1572
|
}
|
|
1353
1573
|
return;
|
|
1354
1574
|
}
|
|
@@ -1356,50 +1576,36 @@ async function handleMcp(ctx, args) {
|
|
|
1356
1576
|
case "rm": {
|
|
1357
1577
|
const [, name] = parts;
|
|
1358
1578
|
if (!name) {
|
|
1359
|
-
writeln(
|
|
1579
|
+
writeln(c4.red(" usage: /mcp remove <name>"));
|
|
1360
1580
|
return;
|
|
1361
1581
|
}
|
|
1362
1582
|
deleteMcpServer(name);
|
|
1363
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1583
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} removed`);
|
|
1364
1584
|
return;
|
|
1365
1585
|
}
|
|
1366
1586
|
default:
|
|
1367
|
-
writeln(
|
|
1368
|
-
writeln(
|
|
1587
|
+
writeln(c4.red(` unknown: /mcp ${sub}`));
|
|
1588
|
+
writeln(c4.dim(" subcommands: list \xB7 add \xB7 remove"));
|
|
1369
1589
|
}
|
|
1370
1590
|
}
|
|
1371
1591
|
var REVIEW_PROMPT = (cwd, focus) => `You are a code reviewer. Review recent changes and provide actionable feedback.
|
|
1372
1592
|
|
|
1373
1593
|
Working directory: ${cwd}
|
|
1374
|
-
${focus ? `
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
-
|
|
1379
|
-
-
|
|
1380
|
-
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
## What to flag (priority order)
|
|
1387
|
-
1. **Bugs** \u2014 logic errors, missing edge cases, unhandled errors, race conditions, security issues. Be certain before flagging; investigate first.
|
|
1388
|
-
2. **Structure** \u2014 wrong abstraction, established patterns ignored, excessive nesting.
|
|
1389
|
-
3. **Performance** \u2014 only if obviously problematic (O(n\xB2) on unbounded data, N+1, blocking hot paths).
|
|
1390
|
-
4. **Style** \u2014 only clear violations of project conventions. Don't be a zealot.
|
|
1391
|
-
|
|
1392
|
-
Only review the changed code, not pre-existing code.
|
|
1393
|
-
|
|
1394
|
-
## Output
|
|
1395
|
-
- Be direct and specific: quote code, cite file and line number.
|
|
1396
|
-
- State the scenario/input that triggers a bug \u2014 severity depends on this.
|
|
1397
|
-
- No flattery, no filler. Matter-of-fact tone.
|
|
1398
|
-
- End with a short **Summary** of the most important items.
|
|
1594
|
+
${focus ? `Review: ${focus}` : "Review the current changes"}
|
|
1595
|
+
|
|
1596
|
+
Perform a sensible code review:
|
|
1597
|
+
|
|
1598
|
+
- Correctness: Are the changes in alignment with the goal?
|
|
1599
|
+
- Code quality: Is there duplicate, dead or bad code patterns introduced or as a result of the changes?
|
|
1600
|
+
- Is the code performant?
|
|
1601
|
+
- Never flag style choices as bugs, don't be a zeolot.
|
|
1602
|
+
- Never flag false positives, if you think something is wrong, check before saying it's an issue.
|
|
1603
|
+
|
|
1604
|
+
Output a small summary with only the issues found. If nothing of note was found reply saying that.
|
|
1399
1605
|
`;
|
|
1400
1606
|
async function handleReview(ctx, args) {
|
|
1401
1607
|
const focus = args.trim();
|
|
1402
|
-
writeln(`${PREFIX.info} ${
|
|
1608
|
+
writeln(`${PREFIX.info} ${c4.cyan("review")} ${c4.dim("\u2014 spawning review subagent\u2026")}`);
|
|
1403
1609
|
writeln();
|
|
1404
1610
|
try {
|
|
1405
1611
|
const output = await ctx.runSubagent(REVIEW_PROMPT(ctx.cwd, focus));
|
|
@@ -1424,9 +1630,37 @@ ${output.result}
|
|
|
1424
1630
|
}
|
|
1425
1631
|
function handleNew(ctx) {
|
|
1426
1632
|
ctx.startNewSession();
|
|
1427
|
-
writeln(`${PREFIX.success} ${
|
|
1633
|
+
writeln(`${PREFIX.success} ${c4.dim("new session started \u2014 context cleared")}`);
|
|
1634
|
+
}
|
|
1635
|
+
async function handleCustomCommand(cmd, args, ctx) {
|
|
1636
|
+
const prompt = await expandTemplate(cmd.template, args, ctx.cwd);
|
|
1637
|
+
const label = c4.cyan(cmd.name);
|
|
1638
|
+
const srcPath = cmd.source === "local" ? `.agents/commands/${cmd.name}.md` : `~/.agents/commands/${cmd.name}.md`;
|
|
1639
|
+
const src = c4.dim(`[${srcPath}]`);
|
|
1640
|
+
writeln(`${PREFIX.info} ${label} ${src}`);
|
|
1641
|
+
writeln();
|
|
1642
|
+
try {
|
|
1643
|
+
const output = await ctx.runSubagent(prompt, cmd.model);
|
|
1644
|
+
if (output.activity.length) {
|
|
1645
|
+
renderSubagentActivity(output.activity, " ", 1);
|
|
1646
|
+
writeln();
|
|
1647
|
+
}
|
|
1648
|
+
write(renderMarkdown(output.result));
|
|
1649
|
+
writeln();
|
|
1650
|
+
return {
|
|
1651
|
+
type: "inject-user-message",
|
|
1652
|
+
text: `/${cmd.name} output:
|
|
1653
|
+
|
|
1654
|
+
${output.result}
|
|
1655
|
+
|
|
1656
|
+
<system-message>Summarize the findings above to the user.</system-message>`
|
|
1657
|
+
};
|
|
1658
|
+
} catch (e) {
|
|
1659
|
+
writeln(`${PREFIX.error} /${cmd.name} failed: ${String(e)}`);
|
|
1660
|
+
return { type: "handled" };
|
|
1661
|
+
}
|
|
1428
1662
|
}
|
|
1429
|
-
function handleHelp() {
|
|
1663
|
+
function handleHelp(ctx, custom) {
|
|
1430
1664
|
writeln();
|
|
1431
1665
|
const cmds = [
|
|
1432
1666
|
["/model [id]", "list or switch models (fetches live list)"],
|
|
@@ -1445,16 +1679,49 @@ function handleHelp() {
|
|
|
1445
1679
|
["/exit", "quit"]
|
|
1446
1680
|
];
|
|
1447
1681
|
for (const [cmd, desc] of cmds) {
|
|
1448
|
-
writeln(` ${
|
|
1682
|
+
writeln(` ${c4.cyan(cmd.padEnd(26))} ${c4.dim(desc)}`);
|
|
1683
|
+
}
|
|
1684
|
+
if (custom.size > 0) {
|
|
1685
|
+
writeln();
|
|
1686
|
+
writeln(c4.dim(" custom commands:"));
|
|
1687
|
+
for (const cmd of custom.values()) {
|
|
1688
|
+
const tag = cmd.source === "local" ? c4.dim(" (local)") : c4.dim(" (global)");
|
|
1689
|
+
writeln(` ${c4.green(`/${cmd.name}`.padEnd(26))} ${c4.dim(cmd.description)}${tag}`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const agents = loadAgents(ctx.cwd);
|
|
1693
|
+
if (agents.size > 0) {
|
|
1694
|
+
writeln();
|
|
1695
|
+
writeln(c4.dim(" agents (~/.agents/agents/ or .agents/agents/):"));
|
|
1696
|
+
for (const agent of agents.values()) {
|
|
1697
|
+
const tag = agent.source === "local" ? c4.dim(" (local)") : c4.dim(" (global)");
|
|
1698
|
+
writeln(` ${c4.magenta(`@${agent.name}`.padEnd(26))} ${c4.dim(agent.description)}${tag}`);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const skills = loadSkills(ctx.cwd);
|
|
1702
|
+
if (skills.size > 0) {
|
|
1703
|
+
writeln();
|
|
1704
|
+
writeln(c4.dim(" skills (~/.agents/skills/ or .agents/skills/):"));
|
|
1705
|
+
for (const skill of skills.values()) {
|
|
1706
|
+
const tag = skill.source === "local" ? c4.dim(" (local)") : c4.dim(" (global)");
|
|
1707
|
+
writeln(` ${c4.yellow(`@${skill.name}`.padEnd(26))} ${c4.dim(skill.description)}${tag}`);
|
|
1708
|
+
}
|
|
1449
1709
|
}
|
|
1450
1710
|
writeln();
|
|
1451
|
-
writeln(` ${
|
|
1452
|
-
writeln(` ${
|
|
1711
|
+
writeln(` ${c4.green("@agent".padEnd(26))} ${c4.dim("run prompt through a custom agent (Tab to complete)")}`);
|
|
1712
|
+
writeln(` ${c4.green("@skill".padEnd(26))} ${c4.dim("inject skill instructions into prompt (Tab to complete)")}`);
|
|
1713
|
+
writeln(` ${c4.green("@file".padEnd(26))} ${c4.dim("inject file contents into prompt (Tab to complete)")}`);
|
|
1714
|
+
writeln(` ${c4.green("!cmd".padEnd(26))} ${c4.dim("run shell command, output added as context")}`);
|
|
1453
1715
|
writeln();
|
|
1454
|
-
writeln(` ${
|
|
1716
|
+
writeln(` ${c4.dim("ctrl+c")} cancel ${c4.dim("\xB7")} ${c4.dim("ctrl+d")} exit ${c4.dim("\xB7")} ${c4.dim("ctrl+r")} history search ${c4.dim("\xB7")} ${c4.dim("\u2191\u2193")} history`);
|
|
1455
1717
|
writeln();
|
|
1456
1718
|
}
|
|
1457
1719
|
async function handleCommand(command, args, ctx) {
|
|
1720
|
+
const custom = loadCustomCommands(ctx.cwd);
|
|
1721
|
+
const customCmd = custom.get(command.toLowerCase());
|
|
1722
|
+
if (customCmd) {
|
|
1723
|
+
return await handleCustomCommand(customCmd, args, ctx);
|
|
1724
|
+
}
|
|
1458
1725
|
switch (command.toLowerCase()) {
|
|
1459
1726
|
case "model":
|
|
1460
1727
|
case "models":
|
|
@@ -1479,15 +1746,16 @@ async function handleCommand(command, args, ctx) {
|
|
|
1479
1746
|
return await handleReview(ctx, args);
|
|
1480
1747
|
case "help":
|
|
1481
1748
|
case "?":
|
|
1482
|
-
handleHelp();
|
|
1749
|
+
handleHelp(ctx, custom);
|
|
1483
1750
|
return { type: "handled" };
|
|
1484
1751
|
case "exit":
|
|
1485
1752
|
case "quit":
|
|
1486
1753
|
case "q":
|
|
1487
1754
|
return { type: "exit" };
|
|
1488
|
-
default:
|
|
1489
|
-
writeln(`${PREFIX.error} unknown: /${command} ${
|
|
1755
|
+
default: {
|
|
1756
|
+
writeln(`${PREFIX.error} unknown: /${command} ${c4.dim("\u2014 /help for commands")}`);
|
|
1490
1757
|
return { type: "unknown", command };
|
|
1758
|
+
}
|
|
1491
1759
|
}
|
|
1492
1760
|
}
|
|
1493
1761
|
|
|
@@ -1527,8 +1795,8 @@ async function loadImageFile(filePath) {
|
|
|
1527
1795
|
}
|
|
1528
1796
|
|
|
1529
1797
|
// src/cli/input.ts
|
|
1530
|
-
import { join as
|
|
1531
|
-
import * as
|
|
1798
|
+
import { join as join5, relative } from "path";
|
|
1799
|
+
import * as c5 from "yoctocolors";
|
|
1532
1800
|
var ESC = "\x1B";
|
|
1533
1801
|
var CSI = `${ESC}[`;
|
|
1534
1802
|
var CLEAR_LINE = `\r${CSI}2K`;
|
|
@@ -1556,16 +1824,33 @@ var CTRL_K = "\v";
|
|
|
1556
1824
|
var CTRL_L = "\f";
|
|
1557
1825
|
var CTRL_R = "\x12";
|
|
1558
1826
|
var TAB = "\t";
|
|
1559
|
-
async function
|
|
1827
|
+
async function getAtCompletions(prefix, cwd) {
|
|
1560
1828
|
const query = prefix.startsWith("@") ? prefix.slice(1) : prefix;
|
|
1561
|
-
const glob = new Bun.Glob(`**/*${query}*`);
|
|
1562
1829
|
const results = [];
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
results.
|
|
1567
|
-
if (results.length >= 10)
|
|
1830
|
+
const MAX = 10;
|
|
1831
|
+
const skills = loadSkills(cwd);
|
|
1832
|
+
for (const [name] of skills) {
|
|
1833
|
+
if (results.length >= MAX)
|
|
1568
1834
|
break;
|
|
1835
|
+
if (name.includes(query))
|
|
1836
|
+
results.push(`@${name}`);
|
|
1837
|
+
}
|
|
1838
|
+
const agents = loadAgents(cwd);
|
|
1839
|
+
for (const [name] of agents) {
|
|
1840
|
+
if (results.length >= MAX)
|
|
1841
|
+
break;
|
|
1842
|
+
if (name.includes(query))
|
|
1843
|
+
results.push(`@${name}`);
|
|
1844
|
+
}
|
|
1845
|
+
if (results.length < MAX) {
|
|
1846
|
+
const glob = new Bun.Glob(`**/*${query}*`);
|
|
1847
|
+
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
1848
|
+
if (file.includes("node_modules") || file.includes(".git"))
|
|
1849
|
+
continue;
|
|
1850
|
+
results.push(`@${relative(cwd, join5(cwd, file))}`);
|
|
1851
|
+
if (results.length >= MAX)
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1569
1854
|
}
|
|
1570
1855
|
return results;
|
|
1571
1856
|
}
|
|
@@ -1584,7 +1869,7 @@ async function tryExtractImageFromPaste(pasted, cwd) {
|
|
|
1584
1869
|
}
|
|
1585
1870
|
}
|
|
1586
1871
|
if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
|
|
1587
|
-
const filePath = trimmed.startsWith("/") ? trimmed :
|
|
1872
|
+
const filePath = trimmed.startsWith("/") ? trimmed : join5(cwd, trimmed);
|
|
1588
1873
|
const attachment = await loadImageFile(filePath);
|
|
1589
1874
|
if (attachment) {
|
|
1590
1875
|
const name = filePath.split("/").pop() ?? trimmed;
|
|
@@ -1632,9 +1917,9 @@ function pasteLabel(text) {
|
|
|
1632
1917
|
const more = extra > 0 ? ` +${extra} more line${extra === 1 ? "" : "s"}` : "";
|
|
1633
1918
|
return `[pasted: "${preview}"${more}]`;
|
|
1634
1919
|
}
|
|
1635
|
-
var PROMPT =
|
|
1636
|
-
var PROMPT_PLAN =
|
|
1637
|
-
var PROMPT_RALPH =
|
|
1920
|
+
var PROMPT = c5.green("\u25B6 ");
|
|
1921
|
+
var PROMPT_PLAN = c5.yellow("\u2B22 ");
|
|
1922
|
+
var PROMPT_RALPH = c5.magenta("\u21BB ");
|
|
1638
1923
|
var PROMPT_RAW_LEN = 2;
|
|
1639
1924
|
async function readline(opts) {
|
|
1640
1925
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -1653,7 +1938,7 @@ async function readline(opts) {
|
|
|
1653
1938
|
const reader = getStdinReader();
|
|
1654
1939
|
function renderPrompt() {
|
|
1655
1940
|
const cols = process.stdout.columns ?? 80;
|
|
1656
|
-
const visualBuf = (pasteBuffer ? buf.replace(PASTE_SENTINEL,
|
|
1941
|
+
const visualBuf = (pasteBuffer ? buf.replace(PASTE_SENTINEL, c5.dim(pasteLabel(pasteBuffer))) : buf).replace(/\[image: [^\]]+\]/g, (m) => c5.dim(c5.cyan(m)));
|
|
1657
1942
|
const visualCursor = pasteBuffer ? (() => {
|
|
1658
1943
|
const sentinelPos = buf.indexOf(PASTE_SENTINEL);
|
|
1659
1944
|
if (sentinelPos === -1 || cursor <= sentinelPos)
|
|
@@ -1665,7 +1950,7 @@ async function readline(opts) {
|
|
|
1665
1950
|
process.stdout.write(`${CLEAR_LINE}${prompt}${display}${CSI}${PROMPT_RAW_LEN + visualCursor + 1}G`);
|
|
1666
1951
|
}
|
|
1667
1952
|
function renderSearchPrompt() {
|
|
1668
|
-
process.stdout.write(`${CLEAR_LINE}${
|
|
1953
|
+
process.stdout.write(`${CLEAR_LINE}${c5.cyan("search:")} ${searchQuery}\u2588`);
|
|
1669
1954
|
}
|
|
1670
1955
|
function applyHistory() {
|
|
1671
1956
|
if (histIdx < history.length) {
|
|
@@ -1781,6 +2066,18 @@ async function readline(opts) {
|
|
|
1781
2066
|
renderPrompt();
|
|
1782
2067
|
continue;
|
|
1783
2068
|
}
|
|
2069
|
+
if (raw === `${ESC}${BACKSPACE}`) {
|
|
2070
|
+
const end = cursor;
|
|
2071
|
+
while (cursor > 0 && buf[cursor - 1] === " ")
|
|
2072
|
+
cursor--;
|
|
2073
|
+
while (cursor > 0 && buf[cursor - 1] !== " ")
|
|
2074
|
+
cursor--;
|
|
2075
|
+
buf = buf.slice(0, cursor) + buf.slice(end);
|
|
2076
|
+
if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
|
|
2077
|
+
pasteBuffer = null;
|
|
2078
|
+
renderPrompt();
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
1784
2081
|
if (raw === ESC) {
|
|
1785
2082
|
process.stdout.write(`
|
|
1786
2083
|
`);
|
|
@@ -1860,7 +2157,7 @@ async function readline(opts) {
|
|
|
1860
2157
|
const beforeCursor = buf.slice(0, cursor);
|
|
1861
2158
|
const atMatch = beforeCursor.match(/@(\S*)$/);
|
|
1862
2159
|
if (atMatch) {
|
|
1863
|
-
const completions = await
|
|
2160
|
+
const completions = await getAtCompletions(atMatch[0], cwd);
|
|
1864
2161
|
if (completions.length === 1 && completions[0]) {
|
|
1865
2162
|
const replacement = completions[0];
|
|
1866
2163
|
buf = buf.slice(0, cursor - (atMatch[0] ?? "").length) + replacement + buf.slice(cursor);
|
|
@@ -1869,8 +2166,8 @@ async function readline(opts) {
|
|
|
1869
2166
|
} else if (completions.length > 1) {
|
|
1870
2167
|
process.stdout.write(`
|
|
1871
2168
|
`);
|
|
1872
|
-
for (const
|
|
1873
|
-
process.stdout.write(` ${
|
|
2169
|
+
for (const c6 of completions)
|
|
2170
|
+
process.stdout.write(` ${c6}
|
|
1874
2171
|
`);
|
|
1875
2172
|
renderPrompt();
|
|
1876
2173
|
}
|
|
@@ -1984,10 +2281,10 @@ async function* runTurn(options) {
|
|
|
1984
2281
|
for await (const chunk of result.fullStream) {
|
|
1985
2282
|
if (signal?.aborted)
|
|
1986
2283
|
break;
|
|
1987
|
-
const
|
|
1988
|
-
switch (
|
|
2284
|
+
const c6 = chunk;
|
|
2285
|
+
switch (c6.type) {
|
|
1989
2286
|
case "text-delta": {
|
|
1990
|
-
const delta = typeof
|
|
2287
|
+
const delta = typeof c6.text === "string" ? c6.text : typeof c6.textDelta === "string" ? c6.textDelta : "";
|
|
1991
2288
|
yield {
|
|
1992
2289
|
type: "text-delta",
|
|
1993
2290
|
delta
|
|
@@ -1997,18 +2294,18 @@ async function* runTurn(options) {
|
|
|
1997
2294
|
case "tool-call": {
|
|
1998
2295
|
yield {
|
|
1999
2296
|
type: "tool-call-start",
|
|
2000
|
-
toolCallId: String(
|
|
2001
|
-
toolName: String(
|
|
2002
|
-
args:
|
|
2297
|
+
toolCallId: String(c6.toolCallId ?? ""),
|
|
2298
|
+
toolName: String(c6.toolName ?? ""),
|
|
2299
|
+
args: c6.input ?? c6.args
|
|
2003
2300
|
};
|
|
2004
2301
|
break;
|
|
2005
2302
|
}
|
|
2006
2303
|
case "tool-result": {
|
|
2007
2304
|
yield {
|
|
2008
2305
|
type: "tool-result",
|
|
2009
|
-
toolCallId: String(
|
|
2010
|
-
toolName: String(
|
|
2011
|
-
result: "output" in
|
|
2306
|
+
toolCallId: String(c6.toolCallId ?? ""),
|
|
2307
|
+
toolName: String(c6.toolName ?? ""),
|
|
2308
|
+
result: "output" in c6 ? c6.output : ("result" in c6) ? c6.result : undefined,
|
|
2012
2309
|
isError: false
|
|
2013
2310
|
};
|
|
2014
2311
|
break;
|
|
@@ -2016,15 +2313,15 @@ async function* runTurn(options) {
|
|
|
2016
2313
|
case "tool-error": {
|
|
2017
2314
|
yield {
|
|
2018
2315
|
type: "tool-result",
|
|
2019
|
-
toolCallId: String(
|
|
2020
|
-
toolName: String(
|
|
2021
|
-
result:
|
|
2316
|
+
toolCallId: String(c6.toolCallId ?? ""),
|
|
2317
|
+
toolName: String(c6.toolName ?? ""),
|
|
2318
|
+
result: c6.error ?? "Tool execution failed",
|
|
2022
2319
|
isError: true
|
|
2023
2320
|
};
|
|
2024
2321
|
break;
|
|
2025
2322
|
}
|
|
2026
2323
|
case "error": {
|
|
2027
|
-
const err =
|
|
2324
|
+
const err = c6.error;
|
|
2028
2325
|
throw err instanceof Error ? err : new Error(String(err));
|
|
2029
2326
|
}
|
|
2030
2327
|
}
|
|
@@ -2104,8 +2401,8 @@ async function connectMcpServer(config) {
|
|
|
2104
2401
|
}
|
|
2105
2402
|
|
|
2106
2403
|
// src/tools/snapshot.ts
|
|
2107
|
-
import { readFileSync, unlinkSync as unlinkSync2 } from "fs";
|
|
2108
|
-
import { join as
|
|
2404
|
+
import { readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
2405
|
+
import { join as join6 } from "path";
|
|
2109
2406
|
async function gitBytes(args, cwd) {
|
|
2110
2407
|
try {
|
|
2111
2408
|
const proc = Bun.spawn(["git", ...args], {
|
|
@@ -2196,7 +2493,7 @@ async function takeSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2196
2493
|
return false;
|
|
2197
2494
|
const files = [];
|
|
2198
2495
|
for (const entry of entries) {
|
|
2199
|
-
const absPath =
|
|
2496
|
+
const absPath = join6(repoRoot, entry.path);
|
|
2200
2497
|
if (!entry.existsOnDisk) {
|
|
2201
2498
|
const { bytes, code } = await gitBytes(["show", `HEAD:${entry.path}`], repoRoot);
|
|
2202
2499
|
if (code === 0) {
|
|
@@ -2210,7 +2507,7 @@ async function takeSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2210
2507
|
}
|
|
2211
2508
|
if (entry.isNew) {
|
|
2212
2509
|
try {
|
|
2213
|
-
const content =
|
|
2510
|
+
const content = readFileSync4(absPath);
|
|
2214
2511
|
files.push({
|
|
2215
2512
|
path: entry.path,
|
|
2216
2513
|
content: new Uint8Array(content),
|
|
@@ -2220,7 +2517,7 @@ async function takeSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2220
2517
|
continue;
|
|
2221
2518
|
}
|
|
2222
2519
|
try {
|
|
2223
|
-
const content =
|
|
2520
|
+
const content = readFileSync4(absPath);
|
|
2224
2521
|
files.push({
|
|
2225
2522
|
path: entry.path,
|
|
2226
2523
|
content: new Uint8Array(content),
|
|
@@ -2245,7 +2542,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2245
2542
|
const root = repoRoot ?? cwd;
|
|
2246
2543
|
let anyFailed = false;
|
|
2247
2544
|
for (const file of files) {
|
|
2248
|
-
const absPath =
|
|
2545
|
+
const absPath = join6(root, file.path);
|
|
2249
2546
|
if (!file.existed) {
|
|
2250
2547
|
try {
|
|
2251
2548
|
if (await Bun.file(absPath).exists()) {
|
|
@@ -2274,8 +2571,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2274
2571
|
}
|
|
2275
2572
|
|
|
2276
2573
|
// src/session/manager.ts
|
|
2277
|
-
import
|
|
2278
|
-
import * as c5 from "yoctocolors";
|
|
2574
|
+
import * as c6 from "yoctocolors";
|
|
2279
2575
|
function newSession(model, cwd) {
|
|
2280
2576
|
const id = generateSessionId();
|
|
2281
2577
|
createSession({ id, cwd, model });
|
|
@@ -2294,19 +2590,19 @@ function touchActiveSession(session) {
|
|
|
2294
2590
|
function printSessionList() {
|
|
2295
2591
|
const sessions = listSessions(20);
|
|
2296
2592
|
if (sessions.length === 0) {
|
|
2297
|
-
writeln(
|
|
2593
|
+
writeln(c6.dim("No sessions found."));
|
|
2298
2594
|
return;
|
|
2299
2595
|
}
|
|
2300
2596
|
writeln(`
|
|
2301
|
-
${
|
|
2597
|
+
${c6.bold("Recent sessions:")}`);
|
|
2302
2598
|
for (const s of sessions) {
|
|
2303
2599
|
const date = new Date(s.updated_at).toLocaleString();
|
|
2304
|
-
const cwd =
|
|
2305
|
-
const title = s.title ||
|
|
2306
|
-
writeln(` ${
|
|
2600
|
+
const cwd = tildePath(s.cwd);
|
|
2601
|
+
const title = s.title || c6.dim("(untitled)");
|
|
2602
|
+
writeln(` ${c6.dim(s.id.padEnd(14))} ${title.padEnd(30)} ${c6.cyan(s.model.split("/").pop() ?? s.model).padEnd(20)} ${c6.dim(cwd)} ${c6.dim(date)}`);
|
|
2307
2603
|
}
|
|
2308
2604
|
writeln(`
|
|
2309
|
-
${
|
|
2605
|
+
${c6.dim("Use")} mc --resume <id> ${c6.dim("to continue a session.")}`);
|
|
2310
2606
|
}
|
|
2311
2607
|
function getMostRecentSession() {
|
|
2312
2608
|
const sessions = listSessions(1);
|
|
@@ -2314,8 +2610,8 @@ function getMostRecentSession() {
|
|
|
2314
2610
|
}
|
|
2315
2611
|
|
|
2316
2612
|
// src/tools/create.ts
|
|
2317
|
-
import { existsSync as
|
|
2318
|
-
import { dirname, join as
|
|
2613
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
2614
|
+
import { dirname, join as join7, relative as relative2 } from "path";
|
|
2319
2615
|
import { z as z2 } from "zod";
|
|
2320
2616
|
|
|
2321
2617
|
// src/tools/diff.ts
|
|
@@ -2455,10 +2751,10 @@ var createTool = {
|
|
|
2455
2751
|
schema: CreateSchema,
|
|
2456
2752
|
execute: async (input) => {
|
|
2457
2753
|
const cwd = input.cwd ?? process.cwd();
|
|
2458
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
2754
|
+
const filePath = input.path.startsWith("/") ? input.path : join7(cwd, input.path);
|
|
2459
2755
|
const relPath = relative2(cwd, filePath);
|
|
2460
2756
|
const dir = dirname(filePath);
|
|
2461
|
-
if (!
|
|
2757
|
+
if (!existsSync5(dir))
|
|
2462
2758
|
mkdirSync2(dir, { recursive: true });
|
|
2463
2759
|
const file = Bun.file(filePath);
|
|
2464
2760
|
const created = !await file.exists();
|
|
@@ -2470,7 +2766,7 @@ var createTool = {
|
|
|
2470
2766
|
};
|
|
2471
2767
|
|
|
2472
2768
|
// src/tools/glob.ts
|
|
2473
|
-
import { join as
|
|
2769
|
+
import { join as join8, relative as relative3 } from "path";
|
|
2474
2770
|
import { z as z3 } from "zod";
|
|
2475
2771
|
var GlobSchema = z3.object({
|
|
2476
2772
|
pattern: z3.string().describe("Glob pattern to match files against, e.g. '**/*.ts'"),
|
|
@@ -2502,7 +2798,7 @@ var globTool = {
|
|
|
2502
2798
|
if (ignored)
|
|
2503
2799
|
continue;
|
|
2504
2800
|
try {
|
|
2505
|
-
const fullPath =
|
|
2801
|
+
const fullPath = join8(cwd, file);
|
|
2506
2802
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
2507
2803
|
matches.push({ path: file, mtime: stat?.mtime?.getTime() ?? 0 });
|
|
2508
2804
|
} catch {
|
|
@@ -2515,13 +2811,13 @@ var globTool = {
|
|
|
2515
2811
|
if (truncated)
|
|
2516
2812
|
matches.pop();
|
|
2517
2813
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
2518
|
-
const files = matches.map((m) => relative3(cwd,
|
|
2814
|
+
const files = matches.map((m) => relative3(cwd, join8(cwd, m.path)));
|
|
2519
2815
|
return { files, count: files.length, truncated };
|
|
2520
2816
|
}
|
|
2521
2817
|
};
|
|
2522
2818
|
|
|
2523
2819
|
// src/tools/grep.ts
|
|
2524
|
-
import { join as
|
|
2820
|
+
import { join as join9 } from "path";
|
|
2525
2821
|
import { z as z4 } from "zod";
|
|
2526
2822
|
|
|
2527
2823
|
// src/tools/hashline.ts
|
|
@@ -2607,7 +2903,7 @@ var grepTool = {
|
|
|
2607
2903
|
if (ignoreGlob.some((g) => g.match(relPath) || g.match(relPath.split("/")[0] ?? ""))) {
|
|
2608
2904
|
continue;
|
|
2609
2905
|
}
|
|
2610
|
-
const fullPath =
|
|
2906
|
+
const fullPath = join9(cwd, relPath);
|
|
2611
2907
|
let text;
|
|
2612
2908
|
try {
|
|
2613
2909
|
text = await Bun.file(fullPath).text();
|
|
@@ -2626,11 +2922,11 @@ var grepTool = {
|
|
|
2626
2922
|
const ctxStart = Math.max(0, i - contextLines);
|
|
2627
2923
|
const ctxEnd = Math.min(lines.length - 1, i + contextLines);
|
|
2628
2924
|
const context = [];
|
|
2629
|
-
for (let
|
|
2925
|
+
for (let c7 = ctxStart;c7 <= ctxEnd; c7++) {
|
|
2630
2926
|
context.push({
|
|
2631
|
-
line:
|
|
2632
|
-
text: formatHashLine(
|
|
2633
|
-
isMatch:
|
|
2927
|
+
line: c7 + 1,
|
|
2928
|
+
text: formatHashLine(c7 + 1, lines[c7] ?? ""),
|
|
2929
|
+
isMatch: c7 === i
|
|
2634
2930
|
});
|
|
2635
2931
|
}
|
|
2636
2932
|
allMatches.push({
|
|
@@ -2658,8 +2954,8 @@ var grepTool = {
|
|
|
2658
2954
|
|
|
2659
2955
|
// src/tools/hooks.ts
|
|
2660
2956
|
import { constants, accessSync } from "fs";
|
|
2661
|
-
import { homedir as
|
|
2662
|
-
import { join as
|
|
2957
|
+
import { homedir as homedir6 } from "os";
|
|
2958
|
+
import { join as join10 } from "path";
|
|
2663
2959
|
function isExecutable(filePath) {
|
|
2664
2960
|
try {
|
|
2665
2961
|
accessSync(filePath, constants.X_OK);
|
|
@@ -2671,8 +2967,8 @@ function isExecutable(filePath) {
|
|
|
2671
2967
|
function findHook(toolName, cwd) {
|
|
2672
2968
|
const scriptName = `post-${toolName}`;
|
|
2673
2969
|
const candidates = [
|
|
2674
|
-
|
|
2675
|
-
|
|
2970
|
+
join10(cwd, ".agents", "hooks", scriptName),
|
|
2971
|
+
join10(homedir6(), ".agents", "hooks", scriptName)
|
|
2676
2972
|
];
|
|
2677
2973
|
for (const p of candidates) {
|
|
2678
2974
|
if (isExecutable(p))
|
|
@@ -2761,7 +3057,7 @@ function hookEnvForRead(input, cwd) {
|
|
|
2761
3057
|
}
|
|
2762
3058
|
|
|
2763
3059
|
// src/tools/insert.ts
|
|
2764
|
-
import { join as
|
|
3060
|
+
import { join as join11, relative as relative4 } from "path";
|
|
2765
3061
|
import { z as z5 } from "zod";
|
|
2766
3062
|
var InsertSchema = z5.object({
|
|
2767
3063
|
path: z5.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
@@ -2776,7 +3072,7 @@ var insertTool = {
|
|
|
2776
3072
|
schema: InsertSchema,
|
|
2777
3073
|
execute: async (input) => {
|
|
2778
3074
|
const cwd = input.cwd ?? process.cwd();
|
|
2779
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3075
|
+
const filePath = input.path.startsWith("/") ? input.path : join11(cwd, input.path);
|
|
2780
3076
|
const relPath = relative4(cwd, filePath);
|
|
2781
3077
|
const file = Bun.file(filePath);
|
|
2782
3078
|
if (!await file.exists()) {
|
|
@@ -2821,7 +3117,7 @@ function parseAnchor(value) {
|
|
|
2821
3117
|
}
|
|
2822
3118
|
|
|
2823
3119
|
// src/tools/read.ts
|
|
2824
|
-
import { join as
|
|
3120
|
+
import { join as join12, relative as relative5 } from "path";
|
|
2825
3121
|
import { z as z6 } from "zod";
|
|
2826
3122
|
var ReadSchema = z6.object({
|
|
2827
3123
|
path: z6.string().describe("File path to read (absolute or relative to cwd)"),
|
|
@@ -2836,7 +3132,7 @@ var readTool = {
|
|
|
2836
3132
|
schema: ReadSchema,
|
|
2837
3133
|
execute: async (input) => {
|
|
2838
3134
|
const cwd = input.cwd ?? process.cwd();
|
|
2839
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3135
|
+
const filePath = input.path.startsWith("/") ? input.path : join12(cwd, input.path);
|
|
2840
3136
|
const file = Bun.file(filePath);
|
|
2841
3137
|
const exists = await file.exists();
|
|
2842
3138
|
if (!exists) {
|
|
@@ -2869,7 +3165,7 @@ var readTool = {
|
|
|
2869
3165
|
};
|
|
2870
3166
|
|
|
2871
3167
|
// src/tools/replace.ts
|
|
2872
|
-
import { join as
|
|
3168
|
+
import { join as join13, relative as relative6 } from "path";
|
|
2873
3169
|
import { z as z7 } from "zod";
|
|
2874
3170
|
var ReplaceSchema = z7.object({
|
|
2875
3171
|
path: z7.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
@@ -2884,7 +3180,7 @@ var replaceTool = {
|
|
|
2884
3180
|
schema: ReplaceSchema,
|
|
2885
3181
|
execute: async (input) => {
|
|
2886
3182
|
const cwd = input.cwd ?? process.cwd();
|
|
2887
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3183
|
+
const filePath = input.path.startsWith("/") ? input.path : join13(cwd, input.path);
|
|
2888
3184
|
const relPath = relative6(cwd, filePath);
|
|
2889
3185
|
const file = Bun.file(filePath);
|
|
2890
3186
|
if (!await file.exists()) {
|
|
@@ -3025,15 +3321,19 @@ var shellTool = {
|
|
|
3025
3321
|
// src/tools/subagent.ts
|
|
3026
3322
|
import { z as z9 } from "zod";
|
|
3027
3323
|
var SubagentInput = z9.object({
|
|
3028
|
-
prompt: z9.string().describe("The task or question to give the subagent")
|
|
3324
|
+
prompt: z9.string().describe("The task or question to give the subagent"),
|
|
3325
|
+
agentName: z9.string().optional().describe("Name of a custom agent to use (from .agents/agents/). Omit to use a generic subagent.")
|
|
3029
3326
|
});
|
|
3030
|
-
function createSubagentTool(runSubagent) {
|
|
3327
|
+
function createSubagentTool(runSubagent, availableAgents) {
|
|
3328
|
+
const agentSection = availableAgents.size > 0 ? `
|
|
3329
|
+
|
|
3330
|
+
When the user's message contains @<agent-name>, delegate to that agent by setting agentName to the exact agent name. Available custom agents: ${[...availableAgents.entries()].map(([name, cfg]) => `"${name}" (${cfg.description})`).join(", ")}.` : "";
|
|
3031
3331
|
return {
|
|
3032
3332
|
name: "subagent",
|
|
3033
|
-
description:
|
|
3333
|
+
description: `Spawn a sub-agent to handle a focused subtask. Use this for parallel exploration, specialised analysis, or tasks that benefit from a fresh context window. The subagent has access to all the same tools.${agentSection}`,
|
|
3034
3334
|
schema: SubagentInput,
|
|
3035
3335
|
execute: async (input) => {
|
|
3036
|
-
return runSubagent(input.prompt);
|
|
3336
|
+
return runSubagent(input.prompt, input.agentName);
|
|
3037
3337
|
}
|
|
3038
3338
|
};
|
|
3039
3339
|
}
|
|
@@ -3090,12 +3390,12 @@ function buildToolSet(opts) {
|
|
|
3090
3390
|
withHooks(withCwdDefault(replaceTool, cwd), lookupHook, cwd, (result) => hookEnvForReplace(result, cwd), onHook),
|
|
3091
3391
|
withHooks(withCwdDefault(insertTool, cwd), lookupHook, cwd, (result) => hookEnvForInsert(result, cwd), onHook),
|
|
3092
3392
|
withHooks(withCwdDefault(shellTool, cwd), lookupHook, cwd, (result, input) => hookEnvForShell(result, input, cwd), onHook),
|
|
3093
|
-
createSubagentTool(async (prompt) => {
|
|
3393
|
+
createSubagentTool(async (prompt, agentName) => {
|
|
3094
3394
|
if (depth >= MAX_SUBAGENT_DEPTH) {
|
|
3095
3395
|
throw new Error(`Subagent depth limit reached (max ${MAX_SUBAGENT_DEPTH}). ` + `Cannot spawn another subagent from depth ${depth}.`);
|
|
3096
3396
|
}
|
|
3097
|
-
return opts.runSubagent(prompt, depth + 1);
|
|
3098
|
-
})
|
|
3397
|
+
return opts.runSubagent(prompt, depth + 1, agentName);
|
|
3398
|
+
}, opts.availableAgents)
|
|
3099
3399
|
];
|
|
3100
3400
|
}
|
|
3101
3401
|
function buildReadOnlyToolSet(opts) {
|
|
@@ -3126,14 +3426,14 @@ async function getGitBranch(cwd) {
|
|
|
3126
3426
|
}
|
|
3127
3427
|
function loadContextFile(cwd) {
|
|
3128
3428
|
const candidates = [
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3429
|
+
join14(cwd, "AGENTS.md"),
|
|
3430
|
+
join14(cwd, "CLAUDE.md"),
|
|
3431
|
+
join14(getConfigDir(), "AGENTS.md")
|
|
3132
3432
|
];
|
|
3133
3433
|
for (const p of candidates) {
|
|
3134
|
-
if (
|
|
3434
|
+
if (existsSync6(p)) {
|
|
3135
3435
|
try {
|
|
3136
|
-
return
|
|
3436
|
+
return readFileSync5(p, "utf-8");
|
|
3137
3437
|
} catch {}
|
|
3138
3438
|
}
|
|
3139
3439
|
}
|
|
@@ -3141,7 +3441,7 @@ function loadContextFile(cwd) {
|
|
|
3141
3441
|
}
|
|
3142
3442
|
function buildSystemPrompt(cwd) {
|
|
3143
3443
|
const contextFile = loadContextFile(cwd);
|
|
3144
|
-
const cwdDisplay =
|
|
3444
|
+
const cwdDisplay = tildePath(cwd);
|
|
3145
3445
|
const now = new Date().toLocaleString(undefined, { hour12: false });
|
|
3146
3446
|
let prompt = `You are mini-coder, a small and fast CLI coding agent.
|
|
3147
3447
|
You have access to tools to read files, search code, make edits, run shell commands, and spawn subagents.
|
|
@@ -3180,7 +3480,7 @@ async function runShellPassthrough(command, cwd) {
|
|
|
3180
3480
|
const out = [stdout, stderr].filter(Boolean).join(`
|
|
3181
3481
|
`).trim();
|
|
3182
3482
|
if (out)
|
|
3183
|
-
writeln(
|
|
3483
|
+
writeln(c7.dim(out));
|
|
3184
3484
|
return out;
|
|
3185
3485
|
} finally {
|
|
3186
3486
|
restoreTerminal();
|
|
@@ -3199,22 +3499,29 @@ async function runAgent(opts) {
|
|
|
3199
3499
|
session = resumed;
|
|
3200
3500
|
currentModel = session.model;
|
|
3201
3501
|
deleteAllSnapshots(session.id);
|
|
3202
|
-
renderInfo(`Resumed session ${session.id} (${
|
|
3502
|
+
renderInfo(`Resumed session ${session.id} (${c7.cyan(currentModel)})`);
|
|
3203
3503
|
} else {
|
|
3204
3504
|
session = newSession(currentModel, cwd);
|
|
3205
3505
|
}
|
|
3206
3506
|
let turnIndex = getMaxTurnIndex(session.id) + 1;
|
|
3207
3507
|
const coreHistory = [...session.messages];
|
|
3208
|
-
const runSubagent = async (prompt, depth = 0) => {
|
|
3508
|
+
const runSubagent = async (prompt, depth = 0, agentName, modelOverride) => {
|
|
3509
|
+
const allAgents = loadAgents(cwd);
|
|
3510
|
+
const agentConfig = agentName ? allAgents.get(agentName) : undefined;
|
|
3511
|
+
if (agentName && !agentConfig) {
|
|
3512
|
+
throw new Error(`Unknown agent "${agentName}". Available agents: ${[...allAgents.keys()].join(", ") || "(none)"}`);
|
|
3513
|
+
}
|
|
3514
|
+
const model = modelOverride ?? agentConfig?.model ?? currentModel;
|
|
3515
|
+
const systemPrompt = agentConfig?.systemPrompt ?? buildSystemPrompt(cwd);
|
|
3209
3516
|
const subMessages = [{ role: "user", content: prompt }];
|
|
3210
3517
|
const subTools = buildToolSet({
|
|
3211
3518
|
cwd,
|
|
3212
3519
|
depth,
|
|
3213
3520
|
runSubagent,
|
|
3214
|
-
onHook: renderHook
|
|
3521
|
+
onHook: renderHook,
|
|
3522
|
+
availableAgents: allAgents
|
|
3215
3523
|
});
|
|
3216
|
-
const subLlm = resolveModel(
|
|
3217
|
-
const systemPrompt = buildSystemPrompt(cwd);
|
|
3524
|
+
const subLlm = resolveModel(model);
|
|
3218
3525
|
let result = "";
|
|
3219
3526
|
let inputTokens = 0;
|
|
3220
3527
|
let outputTokens = 0;
|
|
@@ -3254,11 +3561,13 @@ async function runAgent(opts) {
|
|
|
3254
3561
|
}
|
|
3255
3562
|
return { result, inputTokens, outputTokens, activity };
|
|
3256
3563
|
};
|
|
3564
|
+
const agents = loadAgents(cwd);
|
|
3257
3565
|
const tools = buildToolSet({
|
|
3258
3566
|
cwd,
|
|
3259
3567
|
depth: 0,
|
|
3260
3568
|
runSubagent,
|
|
3261
|
-
onHook: renderHook
|
|
3569
|
+
onHook: renderHook,
|
|
3570
|
+
availableAgents: agents
|
|
3262
3571
|
});
|
|
3263
3572
|
const mcpTools = [];
|
|
3264
3573
|
async function connectAndAddMcp(name) {
|
|
@@ -3281,7 +3590,7 @@ async function runAgent(opts) {
|
|
|
3281
3590
|
for (const row of listMcpServers()) {
|
|
3282
3591
|
try {
|
|
3283
3592
|
await connectAndAddMcp(row.name);
|
|
3284
|
-
renderInfo(`MCP: connected ${
|
|
3593
|
+
renderInfo(`MCP: connected ${c7.cyan(row.name)}`);
|
|
3285
3594
|
} catch (e) {
|
|
3286
3595
|
renderError(`MCP: failed to connect ${row.name}: ${String(e)}`);
|
|
3287
3596
|
}
|
|
@@ -3310,7 +3619,7 @@ async function runAgent(opts) {
|
|
|
3310
3619
|
planMode = v;
|
|
3311
3620
|
},
|
|
3312
3621
|
cwd,
|
|
3313
|
-
runSubagent: (prompt) => runSubagent(prompt),
|
|
3622
|
+
runSubagent: (prompt, model) => runSubagent(prompt, 0, undefined, model),
|
|
3314
3623
|
undoLastTurn: async () => {
|
|
3315
3624
|
if (session.messages.length === 0)
|
|
3316
3625
|
return false;
|
|
@@ -3375,14 +3684,14 @@ async function runAgent(opts) {
|
|
|
3375
3684
|
}
|
|
3376
3685
|
switch (input.type) {
|
|
3377
3686
|
case "eof":
|
|
3378
|
-
writeln(
|
|
3687
|
+
writeln(c7.dim("Goodbye."));
|
|
3379
3688
|
return;
|
|
3380
3689
|
case "interrupt":
|
|
3381
3690
|
continue;
|
|
3382
3691
|
case "command": {
|
|
3383
3692
|
const result = await handleCommand(input.command, input.args, cmdCtx);
|
|
3384
3693
|
if (result.type === "exit") {
|
|
3385
|
-
writeln(
|
|
3694
|
+
writeln(c7.dim("Goodbye."));
|
|
3386
3695
|
return;
|
|
3387
3696
|
}
|
|
3388
3697
|
if (result.type === "inject-user-message") {
|
|
@@ -3417,11 +3726,11 @@ ${out}
|
|
|
3417
3726
|
while (ralphMode) {
|
|
3418
3727
|
if (hasRalphSignal(lastText)) {
|
|
3419
3728
|
ralphMode = false;
|
|
3420
|
-
writeln(`${PREFIX.info} ${
|
|
3729
|
+
writeln(`${PREFIX.info} ${c7.dim("ralph mode off")}`);
|
|
3421
3730
|
break;
|
|
3422
3731
|
}
|
|
3423
3732
|
if (ralphIteration >= RALPH_MAX_ITERATIONS) {
|
|
3424
|
-
writeln(`${PREFIX.info} ${
|
|
3733
|
+
writeln(`${PREFIX.info} ${c7.yellow("ralph")} ${c7.dim("\u2014 max iterations reached, stopping")}`);
|
|
3425
3734
|
ralphMode = false;
|
|
3426
3735
|
break;
|
|
3427
3736
|
}
|
|
@@ -3447,13 +3756,6 @@ ${out}
|
|
|
3447
3756
|
const allImages = [...pastedImages, ...refImages];
|
|
3448
3757
|
const thisTurn = turnIndex++;
|
|
3449
3758
|
const snapped = await takeSnapshot(cwd, session.id, thisTurn);
|
|
3450
|
-
if (wasAborted) {
|
|
3451
|
-
process.removeListener("SIGINT", onSigInt);
|
|
3452
|
-
if (snapped)
|
|
3453
|
-
deleteSnapshot(session.id, thisTurn);
|
|
3454
|
-
turnIndex--;
|
|
3455
|
-
return "";
|
|
3456
|
-
}
|
|
3457
3759
|
const coreContent = planMode ? `${resolvedText}
|
|
3458
3760
|
|
|
3459
3761
|
<system-message>PLAN MODE ACTIVE: Help the user gather context for the plan -- READ ONLY</system-message>` : ralphMode ? `${resolvedText}
|
|
@@ -3470,6 +3772,19 @@ ${out}
|
|
|
3470
3772
|
}))
|
|
3471
3773
|
]
|
|
3472
3774
|
} : { role: "user", content: coreContent };
|
|
3775
|
+
if (wasAborted) {
|
|
3776
|
+
process.removeListener("SIGINT", onSigInt);
|
|
3777
|
+
const stubMsg = {
|
|
3778
|
+
role: "assistant",
|
|
3779
|
+
content: "[interrupted]"
|
|
3780
|
+
};
|
|
3781
|
+
session.messages.push(userMsg, stubMsg);
|
|
3782
|
+
saveMessages(session.id, [userMsg, stubMsg], thisTurn);
|
|
3783
|
+
coreHistory.push(userMsg, stubMsg);
|
|
3784
|
+
snapshotStack.push(snapped ? thisTurn : null);
|
|
3785
|
+
touchActiveSession(session);
|
|
3786
|
+
return "";
|
|
3787
|
+
}
|
|
3473
3788
|
session.messages.push(userMsg);
|
|
3474
3789
|
saveMessages(session.id, [userMsg], thisTurn);
|
|
3475
3790
|
coreHistory.push(userMsg);
|
|
@@ -3504,6 +3819,14 @@ ${out}
|
|
|
3504
3819
|
coreHistory.push(...newMessages);
|
|
3505
3820
|
session.messages.push(...newMessages);
|
|
3506
3821
|
saveMessages(session.id, newMessages, thisTurn);
|
|
3822
|
+
} else if (wasAborted) {
|
|
3823
|
+
const stubMsg = {
|
|
3824
|
+
role: "assistant",
|
|
3825
|
+
content: "[interrupted]"
|
|
3826
|
+
};
|
|
3827
|
+
coreHistory.push(stubMsg);
|
|
3828
|
+
session.messages.push(stubMsg);
|
|
3829
|
+
saveMessages(session.id, [stubMsg], thisTurn);
|
|
3507
3830
|
} else {
|
|
3508
3831
|
rollbackTurn();
|
|
3509
3832
|
}
|
|
@@ -3526,7 +3849,7 @@ ${out}
|
|
|
3526
3849
|
const branch = await getGitBranch(cwd);
|
|
3527
3850
|
const provider = currentModel.split("/")[0] ?? "";
|
|
3528
3851
|
const modelShort = currentModel.split("/").slice(1).join("/");
|
|
3529
|
-
const cwdDisplay =
|
|
3852
|
+
const cwdDisplay = tildePath(cwd);
|
|
3530
3853
|
renderStatusBar({
|
|
3531
3854
|
model: modelShort,
|
|
3532
3855
|
provider,
|
|
@@ -3567,11 +3890,20 @@ async function resolveFileRefs(text, cwd) {
|
|
|
3567
3890
|
let result = text;
|
|
3568
3891
|
const matches = [...text.matchAll(atPattern)];
|
|
3569
3892
|
const images = [];
|
|
3570
|
-
|
|
3893
|
+
const skills = loadSkills(cwd);
|
|
3894
|
+
for (const match of [...matches].reverse()) {
|
|
3571
3895
|
const ref = match[1];
|
|
3572
3896
|
if (!ref)
|
|
3573
3897
|
continue;
|
|
3574
|
-
const
|
|
3898
|
+
const skill = skills.get(ref);
|
|
3899
|
+
if (skill) {
|
|
3900
|
+
const replacement = `<skill name="${skill.name}">
|
|
3901
|
+
${skill.content}
|
|
3902
|
+
</skill>`;
|
|
3903
|
+
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3906
|
+
const filePath = ref.startsWith("/") ? ref : join14(cwd, ref);
|
|
3575
3907
|
if (isImageFilename(ref)) {
|
|
3576
3908
|
const attachment = await loadImageFile(filePath);
|
|
3577
3909
|
if (attachment) {
|
|
@@ -3647,11 +3979,11 @@ function parseArgs(argv) {
|
|
|
3647
3979
|
return args;
|
|
3648
3980
|
}
|
|
3649
3981
|
function printHelp() {
|
|
3650
|
-
writeln(`${
|
|
3982
|
+
writeln(`${c8.bold("mini-coder")} \u2014 a small, fast CLI coding agent
|
|
3651
3983
|
`);
|
|
3652
|
-
writeln(`${
|
|
3984
|
+
writeln(`${c8.bold("Usage:")} mc [options] [prompt]
|
|
3653
3985
|
`);
|
|
3654
|
-
writeln(`${
|
|
3986
|
+
writeln(`${c8.bold("Options:")}`);
|
|
3655
3987
|
const opts = [
|
|
3656
3988
|
["-m, --model <id>", "Model to use (e.g. zen/claude-sonnet-4-6)"],
|
|
3657
3989
|
["-c, --continue", "Continue the most recent session"],
|
|
@@ -3661,10 +3993,10 @@ function printHelp() {
|
|
|
3661
3993
|
["-h, --help", "Show this help"]
|
|
3662
3994
|
];
|
|
3663
3995
|
for (const [flag, desc] of opts) {
|
|
3664
|
-
writeln(` ${
|
|
3996
|
+
writeln(` ${c8.cyan((flag ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3665
3997
|
}
|
|
3666
3998
|
writeln(`
|
|
3667
|
-
${
|
|
3999
|
+
${c8.bold("Provider env vars:")}`);
|
|
3668
4000
|
const envs = [
|
|
3669
4001
|
["OPENCODE_API_KEY", "OpenCode Zen (recommended)"],
|
|
3670
4002
|
["ANTHROPIC_API_KEY", "Anthropic direct"],
|
|
@@ -3673,15 +4005,15 @@ ${c7.bold("Provider env vars:")}`);
|
|
|
3673
4005
|
["OLLAMA_BASE_URL", "Ollama base URL (default: http://localhost:11434)"]
|
|
3674
4006
|
];
|
|
3675
4007
|
for (const [env, desc] of envs) {
|
|
3676
|
-
writeln(` ${
|
|
4008
|
+
writeln(` ${c8.yellow((env ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3677
4009
|
}
|
|
3678
4010
|
writeln(`
|
|
3679
|
-
${
|
|
3680
|
-
writeln(` mc ${
|
|
3681
|
-
writeln(` mc "explain this codebase" ${
|
|
3682
|
-
writeln(` mc -c ${
|
|
3683
|
-
writeln(` mc -m ollama/llama3.2 ${
|
|
3684
|
-
writeln(` mc -l ${
|
|
4011
|
+
${c8.bold("Examples:")}`);
|
|
4012
|
+
writeln(` mc ${c8.dim("# interactive session")}`);
|
|
4013
|
+
writeln(` mc "explain this codebase" ${c8.dim("# one-shot prompt then interactive")}`);
|
|
4014
|
+
writeln(` mc -c ${c8.dim("# continue last session")}`);
|
|
4015
|
+
writeln(` mc -m ollama/llama3.2 ${c8.dim("# use local Ollama model")}`);
|
|
4016
|
+
writeln(` mc -l ${c8.dim("# list sessions")}`);
|
|
3685
4017
|
}
|
|
3686
4018
|
async function main() {
|
|
3687
4019
|
const argv = process.argv.slice(2);
|
|
@@ -3700,7 +4032,7 @@ async function main() {
|
|
|
3700
4032
|
if (last) {
|
|
3701
4033
|
sessionId = last.id;
|
|
3702
4034
|
} else {
|
|
3703
|
-
writeln(
|
|
4035
|
+
writeln(c8.dim("No previous session found, starting fresh."));
|
|
3704
4036
|
}
|
|
3705
4037
|
} else if (args.sessionId) {
|
|
3706
4038
|
sessionId = args.sessionId;
|