mini-coder 0.0.5 → 0.0.7
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 +8 -0
- package/better-errors.md +96 -0
- package/dist/mc.js +340 -295
- package/docs/configs.md +77 -0
- package/docs/tool-hooks.md +142 -0
- package/package.json +2 -1
- package/code-quality-issues.md +0 -135
package/dist/mc.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
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
8
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
9
|
-
import { join as
|
|
10
|
-
import * as
|
|
9
|
+
import { join as join15 } from "path";
|
|
10
|
+
import * as c7 from "yoctocolors";
|
|
11
11
|
|
|
12
12
|
// src/cli/agents.ts
|
|
13
13
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
@@ -81,7 +81,7 @@ function loadAgents(cwd) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// src/cli/commands.ts
|
|
84
|
-
import * as
|
|
84
|
+
import * as c4 from "yoctocolors";
|
|
85
85
|
|
|
86
86
|
// src/llm-api/providers.ts
|
|
87
87
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
@@ -90,32 +90,15 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
90
90
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
91
91
|
import { createOllama } from "ollama-ai-provider";
|
|
92
92
|
var ZEN_BASE = "https://opencode.ai/zen/v1";
|
|
93
|
-
|
|
94
|
-
"claude-
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]);
|
|
103
|
-
var ZEN_OPENAI_MODELS = new Set([
|
|
104
|
-
"gpt-5.2",
|
|
105
|
-
"gpt-5.2-codex",
|
|
106
|
-
"gpt-5.1",
|
|
107
|
-
"gpt-5.1-codex",
|
|
108
|
-
"gpt-5.1-codex-max",
|
|
109
|
-
"gpt-5.1-codex-mini",
|
|
110
|
-
"gpt-5",
|
|
111
|
-
"gpt-5-codex",
|
|
112
|
-
"gpt-5-nano"
|
|
113
|
-
]);
|
|
114
|
-
var ZEN_GOOGLE_MODELS = new Set([
|
|
115
|
-
"gemini-3.1-pro",
|
|
116
|
-
"gemini-3-pro",
|
|
117
|
-
"gemini-3-flash"
|
|
118
|
-
]);
|
|
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
|
+
}
|
|
119
102
|
var _zenAnthropic = null;
|
|
120
103
|
var _zenOpenAI = null;
|
|
121
104
|
var _zenGoogle = null;
|
|
@@ -220,16 +203,7 @@ function resolveModel(modelString) {
|
|
|
220
203
|
const modelId = modelString.slice(slashIdx + 1);
|
|
221
204
|
switch (provider) {
|
|
222
205
|
case "zen": {
|
|
223
|
-
|
|
224
|
-
return zenAnthropic()(modelId);
|
|
225
|
-
}
|
|
226
|
-
if (ZEN_OPENAI_MODELS.has(modelId)) {
|
|
227
|
-
return zenOpenAI()(modelId);
|
|
228
|
-
}
|
|
229
|
-
if (ZEN_GOOGLE_MODELS.has(modelId)) {
|
|
230
|
-
return zenGoogle()(modelId);
|
|
231
|
-
}
|
|
232
|
-
return zenCompat()(modelId);
|
|
206
|
+
return zenEndpointFor(modelId);
|
|
233
207
|
}
|
|
234
208
|
case "anthropic":
|
|
235
209
|
return directAnthropic()(modelId);
|
|
@@ -547,85 +521,15 @@ function generateSessionId() {
|
|
|
547
521
|
|
|
548
522
|
// src/cli/custom-commands.ts
|
|
549
523
|
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
550
|
-
import { homedir as
|
|
524
|
+
import { homedir as homedir4 } from "os";
|
|
551
525
|
import { basename as basename2, join as join3 } from "path";
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
} catch {
|
|
560
|
-
return commands;
|
|
561
|
-
}
|
|
562
|
-
for (const entry of entries) {
|
|
563
|
-
if (!entry.endsWith(".md"))
|
|
564
|
-
continue;
|
|
565
|
-
const name = basename2(entry, ".md");
|
|
566
|
-
const filePath = join3(dir, entry);
|
|
567
|
-
let raw;
|
|
568
|
-
try {
|
|
569
|
-
raw = readFileSync2(filePath, "utf-8");
|
|
570
|
-
} catch {
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
574
|
-
commands.set(name, {
|
|
575
|
-
name,
|
|
576
|
-
description: meta.description ?? name,
|
|
577
|
-
...meta.model ? { model: meta.model } : {},
|
|
578
|
-
template: body,
|
|
579
|
-
source
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
return commands;
|
|
583
|
-
}
|
|
584
|
-
function loadCustomCommands(cwd) {
|
|
585
|
-
const globalDir = join3(homedir3(), ".agents", "commands");
|
|
586
|
-
const localDir = join3(cwd, ".agents", "commands");
|
|
587
|
-
const global = loadFromDir2(globalDir, "global");
|
|
588
|
-
const local = loadFromDir2(localDir, "local");
|
|
589
|
-
return new Map([...global, ...local]);
|
|
590
|
-
}
|
|
591
|
-
async function expandTemplate(template, args, cwd) {
|
|
592
|
-
const tokens = args.match(/("([^"]*)")|('([^']*)')|(\S+)/g)?.map((t) => t.replace(/^["']|["']$/g, "")) ?? [];
|
|
593
|
-
let result = template;
|
|
594
|
-
for (let i = 9;i >= 1; i--) {
|
|
595
|
-
result = result.replaceAll(`$${i}`, tokens[i - 1] ?? "");
|
|
596
|
-
}
|
|
597
|
-
result = result.replaceAll("$ARGUMENTS", args);
|
|
598
|
-
const SHELL_RE = /!`([^`]+)`/g;
|
|
599
|
-
const shellMatches = [...result.matchAll(SHELL_RE)];
|
|
600
|
-
for (const match of shellMatches) {
|
|
601
|
-
const cmd = match[1] ?? "";
|
|
602
|
-
let output = "";
|
|
603
|
-
try {
|
|
604
|
-
const signal = AbortSignal.timeout(1e4);
|
|
605
|
-
const proc = Bun.spawn(["bash", "-c", cmd], {
|
|
606
|
-
cwd,
|
|
607
|
-
stdout: "pipe",
|
|
608
|
-
stderr: "pipe"
|
|
609
|
-
});
|
|
610
|
-
await Promise.race([
|
|
611
|
-
proc.exited,
|
|
612
|
-
new Promise((_, reject) => signal.addEventListener("abort", () => {
|
|
613
|
-
proc.kill();
|
|
614
|
-
reject(new Error("timeout"));
|
|
615
|
-
}))
|
|
616
|
-
]);
|
|
617
|
-
const [stdout, stderr] = await Promise.all([
|
|
618
|
-
new Response(proc.stdout).text(),
|
|
619
|
-
new Response(proc.stderr).text()
|
|
620
|
-
]);
|
|
621
|
-
const exitCode = proc.exitCode ?? 0;
|
|
622
|
-
output = exitCode === 0 ? [stdout, stderr].filter(Boolean).join(`
|
|
623
|
-
`).trim() : stdout.trim();
|
|
624
|
-
} catch {}
|
|
625
|
-
result = result.replaceAll(match[0], output);
|
|
626
|
-
}
|
|
627
|
-
return result;
|
|
628
|
-
}
|
|
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";
|
|
629
533
|
|
|
630
534
|
// src/cli/markdown.ts
|
|
631
535
|
import * as c from "yoctocolors";
|
|
@@ -740,10 +644,8 @@ function renderChunk(text, inFence) {
|
|
|
740
644
|
}
|
|
741
645
|
|
|
742
646
|
// src/cli/output.ts
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
var HOME = homedir4();
|
|
746
|
-
var PACKAGE_VERSION = "0.0.4";
|
|
647
|
+
var HOME = homedir3();
|
|
648
|
+
var PACKAGE_VERSION = "0.0.6";
|
|
747
649
|
function tildePath(p) {
|
|
748
650
|
return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
|
|
749
651
|
}
|
|
@@ -1368,6 +1270,112 @@ var PREFIX = {
|
|
|
1368
1270
|
success: G.ok
|
|
1369
1271
|
};
|
|
1370
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
|
+
|
|
1371
1379
|
// src/cli/skills.ts
|
|
1372
1380
|
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
1373
1381
|
import { homedir as homedir5 } from "os";
|
|
@@ -1403,11 +1411,22 @@ function loadFromDir3(dir, source) {
|
|
|
1403
1411
|
return skills;
|
|
1404
1412
|
}
|
|
1405
1413
|
function loadSkills(cwd) {
|
|
1406
|
-
const
|
|
1407
|
-
const
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1410
|
-
|
|
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
|
+
]);
|
|
1411
1430
|
}
|
|
1412
1431
|
|
|
1413
1432
|
// src/cli/commands.ts
|
|
@@ -1420,20 +1439,20 @@ async function handleModel(ctx, args) {
|
|
|
1420
1439
|
if (match) {
|
|
1421
1440
|
modelId = match.id;
|
|
1422
1441
|
} else {
|
|
1423
|
-
writeln(`${PREFIX.error} unknown model ${
|
|
1442
|
+
writeln(`${PREFIX.error} unknown model ${c4.cyan(args)} ${c4.dim("\u2014 run /models for the full list")}`);
|
|
1424
1443
|
return;
|
|
1425
1444
|
}
|
|
1426
1445
|
}
|
|
1427
1446
|
ctx.setModel(modelId);
|
|
1428
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
1447
|
+
writeln(`${PREFIX.success} model \u2192 ${c4.cyan(modelId)}`);
|
|
1429
1448
|
return;
|
|
1430
1449
|
}
|
|
1431
|
-
writeln(`${
|
|
1450
|
+
writeln(`${c4.dim(" fetching models\u2026")}`);
|
|
1432
1451
|
const models = await fetchAvailableModels();
|
|
1433
1452
|
process.stdout.write("\x1B[1A\r\x1B[2K");
|
|
1434
1453
|
if (models.length === 0) {
|
|
1435
1454
|
writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
|
|
1436
|
-
writeln(
|
|
1455
|
+
writeln(c4.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
|
|
1437
1456
|
return;
|
|
1438
1457
|
}
|
|
1439
1458
|
const byProvider = new Map;
|
|
@@ -1447,27 +1466,27 @@ async function handleModel(ctx, args) {
|
|
|
1447
1466
|
}
|
|
1448
1467
|
writeln();
|
|
1449
1468
|
for (const [provider, list] of byProvider) {
|
|
1450
|
-
writeln(
|
|
1469
|
+
writeln(c4.bold(` ${provider}`));
|
|
1451
1470
|
for (const m of list) {
|
|
1452
1471
|
const isCurrent = ctx.currentModel === m.id;
|
|
1453
|
-
const freeTag = m.free ?
|
|
1454
|
-
const ctxTag = m.context ?
|
|
1455
|
-
const cur = isCurrent ?
|
|
1456
|
-
writeln(` ${
|
|
1457
|
-
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)}`);
|
|
1458
1477
|
}
|
|
1459
1478
|
}
|
|
1460
1479
|
writeln();
|
|
1461
|
-
writeln(
|
|
1480
|
+
writeln(c4.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
|
|
1462
1481
|
}
|
|
1463
1482
|
function handlePlan(ctx) {
|
|
1464
1483
|
ctx.setPlanMode(!ctx.planMode);
|
|
1465
1484
|
if (ctx.planMode) {
|
|
1466
1485
|
if (ctx.ralphMode)
|
|
1467
1486
|
ctx.setRalphMode(false);
|
|
1468
|
-
writeln(`${PREFIX.info} ${
|
|
1487
|
+
writeln(`${PREFIX.info} ${c4.yellow("plan mode")} ${c4.dim("\u2014 read-only tools + MCP, no writes or shell")}`);
|
|
1469
1488
|
} else {
|
|
1470
|
-
writeln(`${PREFIX.info} ${
|
|
1489
|
+
writeln(`${PREFIX.info} ${c4.dim("plan mode off")}`);
|
|
1471
1490
|
}
|
|
1472
1491
|
}
|
|
1473
1492
|
function handleRalph(ctx) {
|
|
@@ -1475,17 +1494,17 @@ function handleRalph(ctx) {
|
|
|
1475
1494
|
if (ctx.ralphMode) {
|
|
1476
1495
|
if (ctx.planMode)
|
|
1477
1496
|
ctx.setPlanMode(false);
|
|
1478
|
-
writeln(`${PREFIX.info} ${
|
|
1497
|
+
writeln(`${PREFIX.info} ${c4.magenta("ralph mode")} ${c4.dim("\u2014 loops until done, fresh context each iteration")}`);
|
|
1479
1498
|
} else {
|
|
1480
|
-
writeln(`${PREFIX.info} ${
|
|
1499
|
+
writeln(`${PREFIX.info} ${c4.dim("ralph mode off")}`);
|
|
1481
1500
|
}
|
|
1482
1501
|
}
|
|
1483
1502
|
async function handleUndo(ctx) {
|
|
1484
1503
|
const ok = await ctx.undoLastTurn();
|
|
1485
1504
|
if (ok) {
|
|
1486
|
-
writeln(`${PREFIX.success} ${
|
|
1505
|
+
writeln(`${PREFIX.success} ${c4.dim("last turn undone \u2014 history and files restored")}`);
|
|
1487
1506
|
} else {
|
|
1488
|
-
writeln(`${PREFIX.info} ${
|
|
1507
|
+
writeln(`${PREFIX.info} ${c4.dim("nothing to undo")}`);
|
|
1489
1508
|
}
|
|
1490
1509
|
}
|
|
1491
1510
|
async function handleMcp(ctx, args) {
|
|
@@ -1495,28 +1514,28 @@ async function handleMcp(ctx, args) {
|
|
|
1495
1514
|
case "list": {
|
|
1496
1515
|
const servers = listMcpServers();
|
|
1497
1516
|
if (servers.length === 0) {
|
|
1498
|
-
writeln(
|
|
1499
|
-
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...]"));
|
|
1500
1519
|
return;
|
|
1501
1520
|
}
|
|
1502
1521
|
writeln();
|
|
1503
1522
|
for (const s of servers) {
|
|
1504
|
-
const detail = s.url ?
|
|
1505
|
-
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}`);
|
|
1506
1525
|
}
|
|
1507
1526
|
return;
|
|
1508
1527
|
}
|
|
1509
1528
|
case "add": {
|
|
1510
1529
|
const [, name, transport, ...rest] = parts;
|
|
1511
1530
|
if (!name || !transport || rest.length === 0) {
|
|
1512
|
-
writeln(
|
|
1513
|
-
writeln(
|
|
1531
|
+
writeln(c4.red(" usage: /mcp add <name> http <url>"));
|
|
1532
|
+
writeln(c4.red(" /mcp add <name> stdio <cmd> [args...]"));
|
|
1514
1533
|
return;
|
|
1515
1534
|
}
|
|
1516
1535
|
if (transport === "http") {
|
|
1517
1536
|
const url = rest[0];
|
|
1518
1537
|
if (!url) {
|
|
1519
|
-
writeln(
|
|
1538
|
+
writeln(c4.red(" usage: /mcp add <name> http <url>"));
|
|
1520
1539
|
return;
|
|
1521
1540
|
}
|
|
1522
1541
|
upsertMcpServer({
|
|
@@ -1530,7 +1549,7 @@ async function handleMcp(ctx, args) {
|
|
|
1530
1549
|
} else if (transport === "stdio") {
|
|
1531
1550
|
const [command, ...cmdArgs] = rest;
|
|
1532
1551
|
if (!command) {
|
|
1533
|
-
writeln(
|
|
1552
|
+
writeln(c4.red(" usage: /mcp add <name> stdio <cmd> [args...]"));
|
|
1534
1553
|
return;
|
|
1535
1554
|
}
|
|
1536
1555
|
upsertMcpServer({
|
|
@@ -1542,14 +1561,14 @@ async function handleMcp(ctx, args) {
|
|
|
1542
1561
|
env: null
|
|
1543
1562
|
});
|
|
1544
1563
|
} else {
|
|
1545
|
-
writeln(
|
|
1564
|
+
writeln(c4.red(` unknown transport: ${transport} (use http or stdio)`));
|
|
1546
1565
|
return;
|
|
1547
1566
|
}
|
|
1548
1567
|
try {
|
|
1549
1568
|
await ctx.connectMcpServer(name);
|
|
1550
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1569
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} added and connected`);
|
|
1551
1570
|
} catch (e) {
|
|
1552
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1571
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} saved ${c4.dim(`(connection failed: ${String(e)})`)}`);
|
|
1553
1572
|
}
|
|
1554
1573
|
return;
|
|
1555
1574
|
}
|
|
@@ -1557,50 +1576,36 @@ async function handleMcp(ctx, args) {
|
|
|
1557
1576
|
case "rm": {
|
|
1558
1577
|
const [, name] = parts;
|
|
1559
1578
|
if (!name) {
|
|
1560
|
-
writeln(
|
|
1579
|
+
writeln(c4.red(" usage: /mcp remove <name>"));
|
|
1561
1580
|
return;
|
|
1562
1581
|
}
|
|
1563
1582
|
deleteMcpServer(name);
|
|
1564
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
1583
|
+
writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} removed`);
|
|
1565
1584
|
return;
|
|
1566
1585
|
}
|
|
1567
1586
|
default:
|
|
1568
|
-
writeln(
|
|
1569
|
-
writeln(
|
|
1587
|
+
writeln(c4.red(` unknown: /mcp ${sub}`));
|
|
1588
|
+
writeln(c4.dim(" subcommands: list \xB7 add \xB7 remove"));
|
|
1570
1589
|
}
|
|
1571
1590
|
}
|
|
1572
1591
|
var REVIEW_PROMPT = (cwd, focus) => `You are a code reviewer. Review recent changes and provide actionable feedback.
|
|
1573
1592
|
|
|
1574
1593
|
Working directory: ${cwd}
|
|
1575
|
-
${focus ? `
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
-
|
|
1580
|
-
-
|
|
1581
|
-
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
## What to flag (priority order)
|
|
1588
|
-
1. **Bugs** \u2014 logic errors, missing edge cases, unhandled errors, race conditions, security issues. Be certain before flagging; investigate first.
|
|
1589
|
-
2. **Structure** \u2014 wrong abstraction, established patterns ignored, excessive nesting.
|
|
1590
|
-
3. **Performance** \u2014 only if obviously problematic (O(n\xB2) on unbounded data, N+1, blocking hot paths).
|
|
1591
|
-
4. **Style** \u2014 only clear violations of project conventions. Don't be a zealot.
|
|
1592
|
-
|
|
1593
|
-
Only review the changed code, not pre-existing code.
|
|
1594
|
-
|
|
1595
|
-
## Output
|
|
1596
|
-
- Be direct and specific: quote code, cite file and line number.
|
|
1597
|
-
- State the scenario/input that triggers a bug \u2014 severity depends on this.
|
|
1598
|
-
- No flattery, no filler. Matter-of-fact tone.
|
|
1599
|
-
- 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.
|
|
1600
1605
|
`;
|
|
1601
1606
|
async function handleReview(ctx, args) {
|
|
1602
1607
|
const focus = args.trim();
|
|
1603
|
-
writeln(`${PREFIX.info} ${
|
|
1608
|
+
writeln(`${PREFIX.info} ${c4.cyan("review")} ${c4.dim("\u2014 spawning review subagent\u2026")}`);
|
|
1604
1609
|
writeln();
|
|
1605
1610
|
try {
|
|
1606
1611
|
const output = await ctx.runSubagent(REVIEW_PROMPT(ctx.cwd, focus));
|
|
@@ -1625,13 +1630,13 @@ ${output.result}
|
|
|
1625
1630
|
}
|
|
1626
1631
|
function handleNew(ctx) {
|
|
1627
1632
|
ctx.startNewSession();
|
|
1628
|
-
writeln(`${PREFIX.success} ${
|
|
1633
|
+
writeln(`${PREFIX.success} ${c4.dim("new session started \u2014 context cleared")}`);
|
|
1629
1634
|
}
|
|
1630
1635
|
async function handleCustomCommand(cmd, args, ctx) {
|
|
1631
1636
|
const prompt = await expandTemplate(cmd.template, args, ctx.cwd);
|
|
1632
|
-
const label =
|
|
1637
|
+
const label = c4.cyan(cmd.name);
|
|
1633
1638
|
const srcPath = cmd.source === "local" ? `.agents/commands/${cmd.name}.md` : `~/.agents/commands/${cmd.name}.md`;
|
|
1634
|
-
const src =
|
|
1639
|
+
const src = c4.dim(`[${srcPath}]`);
|
|
1635
1640
|
writeln(`${PREFIX.info} ${label} ${src}`);
|
|
1636
1641
|
writeln();
|
|
1637
1642
|
try {
|
|
@@ -1674,41 +1679,41 @@ function handleHelp(ctx, custom) {
|
|
|
1674
1679
|
["/exit", "quit"]
|
|
1675
1680
|
];
|
|
1676
1681
|
for (const [cmd, desc] of cmds) {
|
|
1677
|
-
writeln(` ${
|
|
1682
|
+
writeln(` ${c4.cyan(cmd.padEnd(26))} ${c4.dim(desc)}`);
|
|
1678
1683
|
}
|
|
1679
1684
|
if (custom.size > 0) {
|
|
1680
1685
|
writeln();
|
|
1681
|
-
writeln(
|
|
1686
|
+
writeln(c4.dim(" custom commands:"));
|
|
1682
1687
|
for (const cmd of custom.values()) {
|
|
1683
|
-
const tag = cmd.source === "local" ?
|
|
1684
|
-
writeln(` ${
|
|
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}`);
|
|
1685
1690
|
}
|
|
1686
1691
|
}
|
|
1687
1692
|
const agents = loadAgents(ctx.cwd);
|
|
1688
1693
|
if (agents.size > 0) {
|
|
1689
1694
|
writeln();
|
|
1690
|
-
writeln(
|
|
1695
|
+
writeln(c4.dim(" agents (~/.agents/agents/ or .agents/agents/):"));
|
|
1691
1696
|
for (const agent of agents.values()) {
|
|
1692
|
-
const tag = agent.source === "local" ?
|
|
1693
|
-
writeln(` ${
|
|
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}`);
|
|
1694
1699
|
}
|
|
1695
1700
|
}
|
|
1696
1701
|
const skills = loadSkills(ctx.cwd);
|
|
1697
1702
|
if (skills.size > 0) {
|
|
1698
1703
|
writeln();
|
|
1699
|
-
writeln(
|
|
1704
|
+
writeln(c4.dim(" skills (~/.agents/skills/ or .agents/skills/):"));
|
|
1700
1705
|
for (const skill of skills.values()) {
|
|
1701
|
-
const tag = skill.source === "local" ?
|
|
1702
|
-
writeln(` ${
|
|
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}`);
|
|
1703
1708
|
}
|
|
1704
1709
|
}
|
|
1705
1710
|
writeln();
|
|
1706
|
-
writeln(` ${
|
|
1707
|
-
writeln(` ${
|
|
1708
|
-
writeln(` ${
|
|
1709
|
-
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")}`);
|
|
1710
1715
|
writeln();
|
|
1711
|
-
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`);
|
|
1712
1717
|
writeln();
|
|
1713
1718
|
}
|
|
1714
1719
|
async function handleCommand(command, args, ctx) {
|
|
@@ -1748,7 +1753,7 @@ async function handleCommand(command, args, ctx) {
|
|
|
1748
1753
|
case "q":
|
|
1749
1754
|
return { type: "exit" };
|
|
1750
1755
|
default: {
|
|
1751
|
-
writeln(`${PREFIX.error} unknown: /${command} ${
|
|
1756
|
+
writeln(`${PREFIX.error} unknown: /${command} ${c4.dim("\u2014 /help for commands")}`);
|
|
1752
1757
|
return { type: "unknown", command };
|
|
1753
1758
|
}
|
|
1754
1759
|
}
|
|
@@ -1791,7 +1796,7 @@ async function loadImageFile(filePath) {
|
|
|
1791
1796
|
|
|
1792
1797
|
// src/cli/input.ts
|
|
1793
1798
|
import { join as join5, relative } from "path";
|
|
1794
|
-
import * as
|
|
1799
|
+
import * as c5 from "yoctocolors";
|
|
1795
1800
|
var ESC = "\x1B";
|
|
1796
1801
|
var CSI = `${ESC}[`;
|
|
1797
1802
|
var CLEAR_LINE = `\r${CSI}2K`;
|
|
@@ -1912,9 +1917,9 @@ function pasteLabel(text) {
|
|
|
1912
1917
|
const more = extra > 0 ? ` +${extra} more line${extra === 1 ? "" : "s"}` : "";
|
|
1913
1918
|
return `[pasted: "${preview}"${more}]`;
|
|
1914
1919
|
}
|
|
1915
|
-
var PROMPT =
|
|
1916
|
-
var PROMPT_PLAN =
|
|
1917
|
-
var PROMPT_RALPH =
|
|
1920
|
+
var PROMPT = c5.green("\u25B6 ");
|
|
1921
|
+
var PROMPT_PLAN = c5.yellow("\u2B22 ");
|
|
1922
|
+
var PROMPT_RALPH = c5.magenta("\u21BB ");
|
|
1918
1923
|
var PROMPT_RAW_LEN = 2;
|
|
1919
1924
|
async function readline(opts) {
|
|
1920
1925
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -1933,7 +1938,7 @@ async function readline(opts) {
|
|
|
1933
1938
|
const reader = getStdinReader();
|
|
1934
1939
|
function renderPrompt() {
|
|
1935
1940
|
const cols = process.stdout.columns ?? 80;
|
|
1936
|
-
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)));
|
|
1937
1942
|
const visualCursor = pasteBuffer ? (() => {
|
|
1938
1943
|
const sentinelPos = buf.indexOf(PASTE_SENTINEL);
|
|
1939
1944
|
if (sentinelPos === -1 || cursor <= sentinelPos)
|
|
@@ -1945,7 +1950,7 @@ async function readline(opts) {
|
|
|
1945
1950
|
process.stdout.write(`${CLEAR_LINE}${prompt}${display}${CSI}${PROMPT_RAW_LEN + visualCursor + 1}G`);
|
|
1946
1951
|
}
|
|
1947
1952
|
function renderSearchPrompt() {
|
|
1948
|
-
process.stdout.write(`${CLEAR_LINE}${
|
|
1953
|
+
process.stdout.write(`${CLEAR_LINE}${c5.cyan("search:")} ${searchQuery}\u2588`);
|
|
1949
1954
|
}
|
|
1950
1955
|
function applyHistory() {
|
|
1951
1956
|
if (histIdx < history.length) {
|
|
@@ -2061,6 +2066,18 @@ async function readline(opts) {
|
|
|
2061
2066
|
renderPrompt();
|
|
2062
2067
|
continue;
|
|
2063
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
|
+
}
|
|
2064
2081
|
if (raw === ESC) {
|
|
2065
2082
|
process.stdout.write(`
|
|
2066
2083
|
`);
|
|
@@ -2149,8 +2166,8 @@ async function readline(opts) {
|
|
|
2149
2166
|
} else if (completions.length > 1) {
|
|
2150
2167
|
process.stdout.write(`
|
|
2151
2168
|
`);
|
|
2152
|
-
for (const
|
|
2153
|
-
process.stdout.write(` ${
|
|
2169
|
+
for (const c6 of completions)
|
|
2170
|
+
process.stdout.write(` ${c6}
|
|
2154
2171
|
`);
|
|
2155
2172
|
renderPrompt();
|
|
2156
2173
|
}
|
|
@@ -2264,10 +2281,10 @@ async function* runTurn(options) {
|
|
|
2264
2281
|
for await (const chunk of result.fullStream) {
|
|
2265
2282
|
if (signal?.aborted)
|
|
2266
2283
|
break;
|
|
2267
|
-
const
|
|
2268
|
-
switch (
|
|
2284
|
+
const c6 = chunk;
|
|
2285
|
+
switch (c6.type) {
|
|
2269
2286
|
case "text-delta": {
|
|
2270
|
-
const delta = typeof
|
|
2287
|
+
const delta = typeof c6.text === "string" ? c6.text : typeof c6.textDelta === "string" ? c6.textDelta : "";
|
|
2271
2288
|
yield {
|
|
2272
2289
|
type: "text-delta",
|
|
2273
2290
|
delta
|
|
@@ -2277,18 +2294,18 @@ async function* runTurn(options) {
|
|
|
2277
2294
|
case "tool-call": {
|
|
2278
2295
|
yield {
|
|
2279
2296
|
type: "tool-call-start",
|
|
2280
|
-
toolCallId: String(
|
|
2281
|
-
toolName: String(
|
|
2282
|
-
args:
|
|
2297
|
+
toolCallId: String(c6.toolCallId ?? ""),
|
|
2298
|
+
toolName: String(c6.toolName ?? ""),
|
|
2299
|
+
args: c6.input ?? c6.args
|
|
2283
2300
|
};
|
|
2284
2301
|
break;
|
|
2285
2302
|
}
|
|
2286
2303
|
case "tool-result": {
|
|
2287
2304
|
yield {
|
|
2288
2305
|
type: "tool-result",
|
|
2289
|
-
toolCallId: String(
|
|
2290
|
-
toolName: String(
|
|
2291
|
-
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,
|
|
2292
2309
|
isError: false
|
|
2293
2310
|
};
|
|
2294
2311
|
break;
|
|
@@ -2296,15 +2313,15 @@ async function* runTurn(options) {
|
|
|
2296
2313
|
case "tool-error": {
|
|
2297
2314
|
yield {
|
|
2298
2315
|
type: "tool-result",
|
|
2299
|
-
toolCallId: String(
|
|
2300
|
-
toolName: String(
|
|
2301
|
-
result:
|
|
2316
|
+
toolCallId: String(c6.toolCallId ?? ""),
|
|
2317
|
+
toolName: String(c6.toolName ?? ""),
|
|
2318
|
+
result: c6.error ?? "Tool execution failed",
|
|
2302
2319
|
isError: true
|
|
2303
2320
|
};
|
|
2304
2321
|
break;
|
|
2305
2322
|
}
|
|
2306
2323
|
case "error": {
|
|
2307
|
-
const err =
|
|
2324
|
+
const err = c6.error;
|
|
2308
2325
|
throw err instanceof Error ? err : new Error(String(err));
|
|
2309
2326
|
}
|
|
2310
2327
|
}
|
|
@@ -2554,7 +2571,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2554
2571
|
}
|
|
2555
2572
|
|
|
2556
2573
|
// src/session/manager.ts
|
|
2557
|
-
import * as
|
|
2574
|
+
import * as c6 from "yoctocolors";
|
|
2558
2575
|
function newSession(model, cwd) {
|
|
2559
2576
|
const id = generateSessionId();
|
|
2560
2577
|
createSession({ id, cwd, model });
|
|
@@ -2573,19 +2590,19 @@ function touchActiveSession(session) {
|
|
|
2573
2590
|
function printSessionList() {
|
|
2574
2591
|
const sessions = listSessions(20);
|
|
2575
2592
|
if (sessions.length === 0) {
|
|
2576
|
-
writeln(
|
|
2593
|
+
writeln(c6.dim("No sessions found."));
|
|
2577
2594
|
return;
|
|
2578
2595
|
}
|
|
2579
2596
|
writeln(`
|
|
2580
|
-
${
|
|
2597
|
+
${c6.bold("Recent sessions:")}`);
|
|
2581
2598
|
for (const s of sessions) {
|
|
2582
2599
|
const date = new Date(s.updated_at).toLocaleString();
|
|
2583
2600
|
const cwd = tildePath(s.cwd);
|
|
2584
|
-
const title = s.title ||
|
|
2585
|
-
writeln(` ${
|
|
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)}`);
|
|
2586
2603
|
}
|
|
2587
2604
|
writeln(`
|
|
2588
|
-
${
|
|
2605
|
+
${c6.dim("Use")} mc --resume <id> ${c6.dim("to continue a session.")}`);
|
|
2589
2606
|
}
|
|
2590
2607
|
function getMostRecentSession() {
|
|
2591
2608
|
const sessions = listSessions(1);
|
|
@@ -2749,8 +2766,22 @@ var createTool = {
|
|
|
2749
2766
|
};
|
|
2750
2767
|
|
|
2751
2768
|
// src/tools/glob.ts
|
|
2752
|
-
import { join as
|
|
2769
|
+
import { join as join9, relative as relative3 } from "path";
|
|
2753
2770
|
import { z as z3 } from "zod";
|
|
2771
|
+
|
|
2772
|
+
// src/tools/ignore.ts
|
|
2773
|
+
import { join as join8 } from "path";
|
|
2774
|
+
import ignore from "ignore";
|
|
2775
|
+
async function loadGitignore(cwd) {
|
|
2776
|
+
try {
|
|
2777
|
+
const gitignore = await Bun.file(join8(cwd, ".gitignore")).text();
|
|
2778
|
+
return ignore().add(gitignore);
|
|
2779
|
+
} catch {
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// src/tools/glob.ts
|
|
2754
2785
|
var GlobSchema = z3.object({
|
|
2755
2786
|
pattern: z3.string().describe("Glob pattern to match files against, e.g. '**/*.ts'"),
|
|
2756
2787
|
ignore: z3.array(z3.string()).optional().describe("Glob patterns to exclude")
|
|
@@ -2762,26 +2793,21 @@ var globTool = {
|
|
|
2762
2793
|
schema: GlobSchema,
|
|
2763
2794
|
execute: async (input) => {
|
|
2764
2795
|
const cwd = input.cwd ?? process.cwd();
|
|
2765
|
-
const defaultIgnore = [
|
|
2766
|
-
"node_modules/**",
|
|
2767
|
-
".git/**",
|
|
2768
|
-
"dist/**",
|
|
2769
|
-
"*.db",
|
|
2770
|
-
"*.db-shm",
|
|
2771
|
-
"*.db-wal"
|
|
2772
|
-
];
|
|
2796
|
+
const defaultIgnore = [".git/**", "node_modules/**"];
|
|
2773
2797
|
const ignorePatterns = [...defaultIgnore, ...input.ignore ?? []];
|
|
2798
|
+
const ignoreGlobs = ignorePatterns.map((pat) => new Bun.Glob(pat));
|
|
2799
|
+
const ig = await loadGitignore(cwd);
|
|
2774
2800
|
const glob = new Bun.Glob(input.pattern);
|
|
2775
2801
|
const matches = [];
|
|
2776
|
-
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2802
|
+
for await (const file of glob.scan({ cwd, onlyFiles: true, dot: true })) {
|
|
2803
|
+
if (ig?.ignores(file))
|
|
2804
|
+
continue;
|
|
2805
|
+
const firstSegment = file.split("/")[0] ?? "";
|
|
2806
|
+
const ignored = ignoreGlobs.some((g) => g.match(file) || g.match(firstSegment));
|
|
2781
2807
|
if (ignored)
|
|
2782
2808
|
continue;
|
|
2783
2809
|
try {
|
|
2784
|
-
const fullPath =
|
|
2810
|
+
const fullPath = join9(cwd, file);
|
|
2785
2811
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
2786
2812
|
matches.push({ path: file, mtime: stat?.mtime?.getTime() ?? 0 });
|
|
2787
2813
|
} catch {
|
|
@@ -2794,13 +2820,13 @@ var globTool = {
|
|
|
2794
2820
|
if (truncated)
|
|
2795
2821
|
matches.pop();
|
|
2796
2822
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
2797
|
-
const files = matches.map((m) => relative3(cwd,
|
|
2823
|
+
const files = matches.map((m) => relative3(cwd, join9(cwd, m.path)));
|
|
2798
2824
|
return { files, count: files.length, truncated };
|
|
2799
2825
|
}
|
|
2800
2826
|
};
|
|
2801
2827
|
|
|
2802
2828
|
// src/tools/grep.ts
|
|
2803
|
-
import { join as
|
|
2829
|
+
import { join as join10 } from "path";
|
|
2804
2830
|
import { z as z4 } from "zod";
|
|
2805
2831
|
|
|
2806
2832
|
// src/tools/hashline.ts
|
|
@@ -2878,15 +2904,20 @@ var grepTool = {
|
|
|
2878
2904
|
const ignoreGlob = DEFAULT_IGNORE.map((p) => new Bun.Glob(p));
|
|
2879
2905
|
const allMatches = [];
|
|
2880
2906
|
let truncated = false;
|
|
2907
|
+
const ig = await loadGitignore(cwd);
|
|
2881
2908
|
outer:
|
|
2882
2909
|
for await (const relPath of fileGlob.scan({
|
|
2883
2910
|
cwd,
|
|
2884
|
-
onlyFiles: true
|
|
2911
|
+
onlyFiles: true,
|
|
2912
|
+
dot: true
|
|
2885
2913
|
})) {
|
|
2886
|
-
if (
|
|
2914
|
+
if (ig?.ignores(relPath))
|
|
2915
|
+
continue;
|
|
2916
|
+
const firstSegment = relPath.split("/")[0] ?? "";
|
|
2917
|
+
if (ignoreGlob.some((g) => g.match(relPath) || g.match(firstSegment))) {
|
|
2887
2918
|
continue;
|
|
2888
2919
|
}
|
|
2889
|
-
const fullPath =
|
|
2920
|
+
const fullPath = join10(cwd, relPath);
|
|
2890
2921
|
let text;
|
|
2891
2922
|
try {
|
|
2892
2923
|
text = await Bun.file(fullPath).text();
|
|
@@ -2905,11 +2936,11 @@ var grepTool = {
|
|
|
2905
2936
|
const ctxStart = Math.max(0, i - contextLines);
|
|
2906
2937
|
const ctxEnd = Math.min(lines.length - 1, i + contextLines);
|
|
2907
2938
|
const context = [];
|
|
2908
|
-
for (let
|
|
2939
|
+
for (let c7 = ctxStart;c7 <= ctxEnd; c7++) {
|
|
2909
2940
|
context.push({
|
|
2910
|
-
line:
|
|
2911
|
-
text: formatHashLine(
|
|
2912
|
-
isMatch:
|
|
2941
|
+
line: c7 + 1,
|
|
2942
|
+
text: formatHashLine(c7 + 1, lines[c7] ?? ""),
|
|
2943
|
+
isMatch: c7 === i
|
|
2913
2944
|
});
|
|
2914
2945
|
}
|
|
2915
2946
|
allMatches.push({
|
|
@@ -2938,7 +2969,7 @@ var grepTool = {
|
|
|
2938
2969
|
// src/tools/hooks.ts
|
|
2939
2970
|
import { constants, accessSync } from "fs";
|
|
2940
2971
|
import { homedir as homedir6 } from "os";
|
|
2941
|
-
import { join as
|
|
2972
|
+
import { join as join11 } from "path";
|
|
2942
2973
|
function isExecutable(filePath) {
|
|
2943
2974
|
try {
|
|
2944
2975
|
accessSync(filePath, constants.X_OK);
|
|
@@ -2950,8 +2981,8 @@ function isExecutable(filePath) {
|
|
|
2950
2981
|
function findHook(toolName, cwd) {
|
|
2951
2982
|
const scriptName = `post-${toolName}`;
|
|
2952
2983
|
const candidates = [
|
|
2953
|
-
|
|
2954
|
-
|
|
2984
|
+
join11(cwd, ".agents", "hooks", scriptName),
|
|
2985
|
+
join11(homedir6(), ".agents", "hooks", scriptName)
|
|
2955
2986
|
];
|
|
2956
2987
|
for (const p of candidates) {
|
|
2957
2988
|
if (isExecutable(p))
|
|
@@ -3040,7 +3071,7 @@ function hookEnvForRead(input, cwd) {
|
|
|
3040
3071
|
}
|
|
3041
3072
|
|
|
3042
3073
|
// src/tools/insert.ts
|
|
3043
|
-
import { join as
|
|
3074
|
+
import { join as join12, relative as relative4 } from "path";
|
|
3044
3075
|
import { z as z5 } from "zod";
|
|
3045
3076
|
var InsertSchema = z5.object({
|
|
3046
3077
|
path: z5.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
@@ -3055,7 +3086,7 @@ var insertTool = {
|
|
|
3055
3086
|
schema: InsertSchema,
|
|
3056
3087
|
execute: async (input) => {
|
|
3057
3088
|
const cwd = input.cwd ?? process.cwd();
|
|
3058
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3089
|
+
const filePath = input.path.startsWith("/") ? input.path : join12(cwd, input.path);
|
|
3059
3090
|
const relPath = relative4(cwd, filePath);
|
|
3060
3091
|
const file = Bun.file(filePath);
|
|
3061
3092
|
if (!await file.exists()) {
|
|
@@ -3100,7 +3131,7 @@ function parseAnchor(value) {
|
|
|
3100
3131
|
}
|
|
3101
3132
|
|
|
3102
3133
|
// src/tools/read.ts
|
|
3103
|
-
import { join as
|
|
3134
|
+
import { join as join13, relative as relative5 } from "path";
|
|
3104
3135
|
import { z as z6 } from "zod";
|
|
3105
3136
|
var ReadSchema = z6.object({
|
|
3106
3137
|
path: z6.string().describe("File path to read (absolute or relative to cwd)"),
|
|
@@ -3115,7 +3146,7 @@ var readTool = {
|
|
|
3115
3146
|
schema: ReadSchema,
|
|
3116
3147
|
execute: async (input) => {
|
|
3117
3148
|
const cwd = input.cwd ?? process.cwd();
|
|
3118
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3149
|
+
const filePath = input.path.startsWith("/") ? input.path : join13(cwd, input.path);
|
|
3119
3150
|
const file = Bun.file(filePath);
|
|
3120
3151
|
const exists = await file.exists();
|
|
3121
3152
|
if (!exists) {
|
|
@@ -3148,7 +3179,7 @@ var readTool = {
|
|
|
3148
3179
|
};
|
|
3149
3180
|
|
|
3150
3181
|
// src/tools/replace.ts
|
|
3151
|
-
import { join as
|
|
3182
|
+
import { join as join14, relative as relative6 } from "path";
|
|
3152
3183
|
import { z as z7 } from "zod";
|
|
3153
3184
|
var ReplaceSchema = z7.object({
|
|
3154
3185
|
path: z7.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
@@ -3163,7 +3194,7 @@ var replaceTool = {
|
|
|
3163
3194
|
schema: ReplaceSchema,
|
|
3164
3195
|
execute: async (input) => {
|
|
3165
3196
|
const cwd = input.cwd ?? process.cwd();
|
|
3166
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3197
|
+
const filePath = input.path.startsWith("/") ? input.path : join14(cwd, input.path);
|
|
3167
3198
|
const relPath = relative6(cwd, filePath);
|
|
3168
3199
|
const file = Bun.file(filePath);
|
|
3169
3200
|
if (!await file.exists()) {
|
|
@@ -3409,9 +3440,9 @@ async function getGitBranch(cwd) {
|
|
|
3409
3440
|
}
|
|
3410
3441
|
function loadContextFile(cwd) {
|
|
3411
3442
|
const candidates = [
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3443
|
+
join15(cwd, "AGENTS.md"),
|
|
3444
|
+
join15(cwd, "CLAUDE.md"),
|
|
3445
|
+
join15(getConfigDir(), "AGENTS.md")
|
|
3415
3446
|
];
|
|
3416
3447
|
for (const p of candidates) {
|
|
3417
3448
|
if (existsSync6(p)) {
|
|
@@ -3463,7 +3494,7 @@ async function runShellPassthrough(command, cwd) {
|
|
|
3463
3494
|
const out = [stdout, stderr].filter(Boolean).join(`
|
|
3464
3495
|
`).trim();
|
|
3465
3496
|
if (out)
|
|
3466
|
-
writeln(
|
|
3497
|
+
writeln(c7.dim(out));
|
|
3467
3498
|
return out;
|
|
3468
3499
|
} finally {
|
|
3469
3500
|
restoreTerminal();
|
|
@@ -3482,7 +3513,7 @@ async function runAgent(opts) {
|
|
|
3482
3513
|
session = resumed;
|
|
3483
3514
|
currentModel = session.model;
|
|
3484
3515
|
deleteAllSnapshots(session.id);
|
|
3485
|
-
renderInfo(`Resumed session ${session.id} (${
|
|
3516
|
+
renderInfo(`Resumed session ${session.id} (${c7.cyan(currentModel)})`);
|
|
3486
3517
|
} else {
|
|
3487
3518
|
session = newSession(currentModel, cwd);
|
|
3488
3519
|
}
|
|
@@ -3573,7 +3604,7 @@ async function runAgent(opts) {
|
|
|
3573
3604
|
for (const row of listMcpServers()) {
|
|
3574
3605
|
try {
|
|
3575
3606
|
await connectAndAddMcp(row.name);
|
|
3576
|
-
renderInfo(`MCP: connected ${
|
|
3607
|
+
renderInfo(`MCP: connected ${c7.cyan(row.name)}`);
|
|
3577
3608
|
} catch (e) {
|
|
3578
3609
|
renderError(`MCP: failed to connect ${row.name}: ${String(e)}`);
|
|
3579
3610
|
}
|
|
@@ -3667,14 +3698,14 @@ async function runAgent(opts) {
|
|
|
3667
3698
|
}
|
|
3668
3699
|
switch (input.type) {
|
|
3669
3700
|
case "eof":
|
|
3670
|
-
writeln(
|
|
3701
|
+
writeln(c7.dim("Goodbye."));
|
|
3671
3702
|
return;
|
|
3672
3703
|
case "interrupt":
|
|
3673
3704
|
continue;
|
|
3674
3705
|
case "command": {
|
|
3675
3706
|
const result = await handleCommand(input.command, input.args, cmdCtx);
|
|
3676
3707
|
if (result.type === "exit") {
|
|
3677
|
-
writeln(
|
|
3708
|
+
writeln(c7.dim("Goodbye."));
|
|
3678
3709
|
return;
|
|
3679
3710
|
}
|
|
3680
3711
|
if (result.type === "inject-user-message") {
|
|
@@ -3709,11 +3740,11 @@ ${out}
|
|
|
3709
3740
|
while (ralphMode) {
|
|
3710
3741
|
if (hasRalphSignal(lastText)) {
|
|
3711
3742
|
ralphMode = false;
|
|
3712
|
-
writeln(`${PREFIX.info} ${
|
|
3743
|
+
writeln(`${PREFIX.info} ${c7.dim("ralph mode off")}`);
|
|
3713
3744
|
break;
|
|
3714
3745
|
}
|
|
3715
3746
|
if (ralphIteration >= RALPH_MAX_ITERATIONS) {
|
|
3716
|
-
writeln(`${PREFIX.info} ${
|
|
3747
|
+
writeln(`${PREFIX.info} ${c7.yellow("ralph")} ${c7.dim("\u2014 max iterations reached, stopping")}`);
|
|
3717
3748
|
ralphMode = false;
|
|
3718
3749
|
break;
|
|
3719
3750
|
}
|
|
@@ -3739,13 +3770,6 @@ ${out}
|
|
|
3739
3770
|
const allImages = [...pastedImages, ...refImages];
|
|
3740
3771
|
const thisTurn = turnIndex++;
|
|
3741
3772
|
const snapped = await takeSnapshot(cwd, session.id, thisTurn);
|
|
3742
|
-
if (wasAborted) {
|
|
3743
|
-
process.removeListener("SIGINT", onSigInt);
|
|
3744
|
-
if (snapped)
|
|
3745
|
-
deleteSnapshot(session.id, thisTurn);
|
|
3746
|
-
turnIndex--;
|
|
3747
|
-
return "";
|
|
3748
|
-
}
|
|
3749
3773
|
const coreContent = planMode ? `${resolvedText}
|
|
3750
3774
|
|
|
3751
3775
|
<system-message>PLAN MODE ACTIVE: Help the user gather context for the plan -- READ ONLY</system-message>` : ralphMode ? `${resolvedText}
|
|
@@ -3762,6 +3786,19 @@ ${out}
|
|
|
3762
3786
|
}))
|
|
3763
3787
|
]
|
|
3764
3788
|
} : { role: "user", content: coreContent };
|
|
3789
|
+
if (wasAborted) {
|
|
3790
|
+
process.removeListener("SIGINT", onSigInt);
|
|
3791
|
+
const stubMsg = {
|
|
3792
|
+
role: "assistant",
|
|
3793
|
+
content: "[interrupted]"
|
|
3794
|
+
};
|
|
3795
|
+
session.messages.push(userMsg, stubMsg);
|
|
3796
|
+
saveMessages(session.id, [userMsg, stubMsg], thisTurn);
|
|
3797
|
+
coreHistory.push(userMsg, stubMsg);
|
|
3798
|
+
snapshotStack.push(snapped ? thisTurn : null);
|
|
3799
|
+
touchActiveSession(session);
|
|
3800
|
+
return "";
|
|
3801
|
+
}
|
|
3765
3802
|
session.messages.push(userMsg);
|
|
3766
3803
|
saveMessages(session.id, [userMsg], thisTurn);
|
|
3767
3804
|
coreHistory.push(userMsg);
|
|
@@ -3796,6 +3833,14 @@ ${out}
|
|
|
3796
3833
|
coreHistory.push(...newMessages);
|
|
3797
3834
|
session.messages.push(...newMessages);
|
|
3798
3835
|
saveMessages(session.id, newMessages, thisTurn);
|
|
3836
|
+
} else if (wasAborted) {
|
|
3837
|
+
const stubMsg = {
|
|
3838
|
+
role: "assistant",
|
|
3839
|
+
content: "[interrupted]"
|
|
3840
|
+
};
|
|
3841
|
+
coreHistory.push(stubMsg);
|
|
3842
|
+
session.messages.push(stubMsg);
|
|
3843
|
+
saveMessages(session.id, [stubMsg], thisTurn);
|
|
3799
3844
|
} else {
|
|
3800
3845
|
rollbackTurn();
|
|
3801
3846
|
}
|
|
@@ -3872,7 +3917,7 @@ ${skill.content}
|
|
|
3872
3917
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
3873
3918
|
continue;
|
|
3874
3919
|
}
|
|
3875
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
3920
|
+
const filePath = ref.startsWith("/") ? ref : join15(cwd, ref);
|
|
3876
3921
|
if (isImageFilename(ref)) {
|
|
3877
3922
|
const attachment = await loadImageFile(filePath);
|
|
3878
3923
|
if (attachment) {
|
|
@@ -3948,11 +3993,11 @@ function parseArgs(argv) {
|
|
|
3948
3993
|
return args;
|
|
3949
3994
|
}
|
|
3950
3995
|
function printHelp() {
|
|
3951
|
-
writeln(`${
|
|
3996
|
+
writeln(`${c8.bold("mini-coder")} \u2014 a small, fast CLI coding agent
|
|
3952
3997
|
`);
|
|
3953
|
-
writeln(`${
|
|
3998
|
+
writeln(`${c8.bold("Usage:")} mc [options] [prompt]
|
|
3954
3999
|
`);
|
|
3955
|
-
writeln(`${
|
|
4000
|
+
writeln(`${c8.bold("Options:")}`);
|
|
3956
4001
|
const opts = [
|
|
3957
4002
|
["-m, --model <id>", "Model to use (e.g. zen/claude-sonnet-4-6)"],
|
|
3958
4003
|
["-c, --continue", "Continue the most recent session"],
|
|
@@ -3962,10 +4007,10 @@ function printHelp() {
|
|
|
3962
4007
|
["-h, --help", "Show this help"]
|
|
3963
4008
|
];
|
|
3964
4009
|
for (const [flag, desc] of opts) {
|
|
3965
|
-
writeln(` ${
|
|
4010
|
+
writeln(` ${c8.cyan((flag ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3966
4011
|
}
|
|
3967
4012
|
writeln(`
|
|
3968
|
-
${
|
|
4013
|
+
${c8.bold("Provider env vars:")}`);
|
|
3969
4014
|
const envs = [
|
|
3970
4015
|
["OPENCODE_API_KEY", "OpenCode Zen (recommended)"],
|
|
3971
4016
|
["ANTHROPIC_API_KEY", "Anthropic direct"],
|
|
@@ -3974,15 +4019,15 @@ ${c7.bold("Provider env vars:")}`);
|
|
|
3974
4019
|
["OLLAMA_BASE_URL", "Ollama base URL (default: http://localhost:11434)"]
|
|
3975
4020
|
];
|
|
3976
4021
|
for (const [env, desc] of envs) {
|
|
3977
|
-
writeln(` ${
|
|
4022
|
+
writeln(` ${c8.yellow((env ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3978
4023
|
}
|
|
3979
4024
|
writeln(`
|
|
3980
|
-
${
|
|
3981
|
-
writeln(` mc ${
|
|
3982
|
-
writeln(` mc "explain this codebase" ${
|
|
3983
|
-
writeln(` mc -c ${
|
|
3984
|
-
writeln(` mc -m ollama/llama3.2 ${
|
|
3985
|
-
writeln(` mc -l ${
|
|
4025
|
+
${c8.bold("Examples:")}`);
|
|
4026
|
+
writeln(` mc ${c8.dim("# interactive session")}`);
|
|
4027
|
+
writeln(` mc "explain this codebase" ${c8.dim("# one-shot prompt then interactive")}`);
|
|
4028
|
+
writeln(` mc -c ${c8.dim("# continue last session")}`);
|
|
4029
|
+
writeln(` mc -m ollama/llama3.2 ${c8.dim("# use local Ollama model")}`);
|
|
4030
|
+
writeln(` mc -l ${c8.dim("# list sessions")}`);
|
|
3986
4031
|
}
|
|
3987
4032
|
async function main() {
|
|
3988
4033
|
const argv = process.argv.slice(2);
|
|
@@ -4001,7 +4046,7 @@ async function main() {
|
|
|
4001
4046
|
if (last) {
|
|
4002
4047
|
sessionId = last.id;
|
|
4003
4048
|
} else {
|
|
4004
|
-
writeln(
|
|
4049
|
+
writeln(c8.dim("No previous session found, starting fresh."));
|
|
4005
4050
|
}
|
|
4006
4051
|
} else if (args.sessionId) {
|
|
4007
4052
|
sessionId = args.sessionId;
|