mini-coder 0.0.5 → 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 +8 -0
- package/better-errors.md +96 -0
- package/dist/mc.js +292 -261
- package/docs/configs.md +77 -0
- package/docs/tool-hooks.md +142 -0
- package/package.json +1 -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
9
|
import { join as join14 } from "path";
|
|
10
|
-
import * as
|
|
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.1.0";
|
|
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);
|
|
@@ -2905,11 +2922,11 @@ var grepTool = {
|
|
|
2905
2922
|
const ctxStart = Math.max(0, i - contextLines);
|
|
2906
2923
|
const ctxEnd = Math.min(lines.length - 1, i + contextLines);
|
|
2907
2924
|
const context = [];
|
|
2908
|
-
for (let
|
|
2925
|
+
for (let c7 = ctxStart;c7 <= ctxEnd; c7++) {
|
|
2909
2926
|
context.push({
|
|
2910
|
-
line:
|
|
2911
|
-
text: formatHashLine(
|
|
2912
|
-
isMatch:
|
|
2927
|
+
line: c7 + 1,
|
|
2928
|
+
text: formatHashLine(c7 + 1, lines[c7] ?? ""),
|
|
2929
|
+
isMatch: c7 === i
|
|
2913
2930
|
});
|
|
2914
2931
|
}
|
|
2915
2932
|
allMatches.push({
|
|
@@ -3463,7 +3480,7 @@ async function runShellPassthrough(command, cwd) {
|
|
|
3463
3480
|
const out = [stdout, stderr].filter(Boolean).join(`
|
|
3464
3481
|
`).trim();
|
|
3465
3482
|
if (out)
|
|
3466
|
-
writeln(
|
|
3483
|
+
writeln(c7.dim(out));
|
|
3467
3484
|
return out;
|
|
3468
3485
|
} finally {
|
|
3469
3486
|
restoreTerminal();
|
|
@@ -3482,7 +3499,7 @@ async function runAgent(opts) {
|
|
|
3482
3499
|
session = resumed;
|
|
3483
3500
|
currentModel = session.model;
|
|
3484
3501
|
deleteAllSnapshots(session.id);
|
|
3485
|
-
renderInfo(`Resumed session ${session.id} (${
|
|
3502
|
+
renderInfo(`Resumed session ${session.id} (${c7.cyan(currentModel)})`);
|
|
3486
3503
|
} else {
|
|
3487
3504
|
session = newSession(currentModel, cwd);
|
|
3488
3505
|
}
|
|
@@ -3573,7 +3590,7 @@ async function runAgent(opts) {
|
|
|
3573
3590
|
for (const row of listMcpServers()) {
|
|
3574
3591
|
try {
|
|
3575
3592
|
await connectAndAddMcp(row.name);
|
|
3576
|
-
renderInfo(`MCP: connected ${
|
|
3593
|
+
renderInfo(`MCP: connected ${c7.cyan(row.name)}`);
|
|
3577
3594
|
} catch (e) {
|
|
3578
3595
|
renderError(`MCP: failed to connect ${row.name}: ${String(e)}`);
|
|
3579
3596
|
}
|
|
@@ -3667,14 +3684,14 @@ async function runAgent(opts) {
|
|
|
3667
3684
|
}
|
|
3668
3685
|
switch (input.type) {
|
|
3669
3686
|
case "eof":
|
|
3670
|
-
writeln(
|
|
3687
|
+
writeln(c7.dim("Goodbye."));
|
|
3671
3688
|
return;
|
|
3672
3689
|
case "interrupt":
|
|
3673
3690
|
continue;
|
|
3674
3691
|
case "command": {
|
|
3675
3692
|
const result = await handleCommand(input.command, input.args, cmdCtx);
|
|
3676
3693
|
if (result.type === "exit") {
|
|
3677
|
-
writeln(
|
|
3694
|
+
writeln(c7.dim("Goodbye."));
|
|
3678
3695
|
return;
|
|
3679
3696
|
}
|
|
3680
3697
|
if (result.type === "inject-user-message") {
|
|
@@ -3709,11 +3726,11 @@ ${out}
|
|
|
3709
3726
|
while (ralphMode) {
|
|
3710
3727
|
if (hasRalphSignal(lastText)) {
|
|
3711
3728
|
ralphMode = false;
|
|
3712
|
-
writeln(`${PREFIX.info} ${
|
|
3729
|
+
writeln(`${PREFIX.info} ${c7.dim("ralph mode off")}`);
|
|
3713
3730
|
break;
|
|
3714
3731
|
}
|
|
3715
3732
|
if (ralphIteration >= RALPH_MAX_ITERATIONS) {
|
|
3716
|
-
writeln(`${PREFIX.info} ${
|
|
3733
|
+
writeln(`${PREFIX.info} ${c7.yellow("ralph")} ${c7.dim("\u2014 max iterations reached, stopping")}`);
|
|
3717
3734
|
ralphMode = false;
|
|
3718
3735
|
break;
|
|
3719
3736
|
}
|
|
@@ -3739,13 +3756,6 @@ ${out}
|
|
|
3739
3756
|
const allImages = [...pastedImages, ...refImages];
|
|
3740
3757
|
const thisTurn = turnIndex++;
|
|
3741
3758
|
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
3759
|
const coreContent = planMode ? `${resolvedText}
|
|
3750
3760
|
|
|
3751
3761
|
<system-message>PLAN MODE ACTIVE: Help the user gather context for the plan -- READ ONLY</system-message>` : ralphMode ? `${resolvedText}
|
|
@@ -3762,6 +3772,19 @@ ${out}
|
|
|
3762
3772
|
}))
|
|
3763
3773
|
]
|
|
3764
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
|
+
}
|
|
3765
3788
|
session.messages.push(userMsg);
|
|
3766
3789
|
saveMessages(session.id, [userMsg], thisTurn);
|
|
3767
3790
|
coreHistory.push(userMsg);
|
|
@@ -3796,6 +3819,14 @@ ${out}
|
|
|
3796
3819
|
coreHistory.push(...newMessages);
|
|
3797
3820
|
session.messages.push(...newMessages);
|
|
3798
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);
|
|
3799
3830
|
} else {
|
|
3800
3831
|
rollbackTurn();
|
|
3801
3832
|
}
|
|
@@ -3948,11 +3979,11 @@ function parseArgs(argv) {
|
|
|
3948
3979
|
return args;
|
|
3949
3980
|
}
|
|
3950
3981
|
function printHelp() {
|
|
3951
|
-
writeln(`${
|
|
3982
|
+
writeln(`${c8.bold("mini-coder")} \u2014 a small, fast CLI coding agent
|
|
3952
3983
|
`);
|
|
3953
|
-
writeln(`${
|
|
3984
|
+
writeln(`${c8.bold("Usage:")} mc [options] [prompt]
|
|
3954
3985
|
`);
|
|
3955
|
-
writeln(`${
|
|
3986
|
+
writeln(`${c8.bold("Options:")}`);
|
|
3956
3987
|
const opts = [
|
|
3957
3988
|
["-m, --model <id>", "Model to use (e.g. zen/claude-sonnet-4-6)"],
|
|
3958
3989
|
["-c, --continue", "Continue the most recent session"],
|
|
@@ -3962,10 +3993,10 @@ function printHelp() {
|
|
|
3962
3993
|
["-h, --help", "Show this help"]
|
|
3963
3994
|
];
|
|
3964
3995
|
for (const [flag, desc] of opts) {
|
|
3965
|
-
writeln(` ${
|
|
3996
|
+
writeln(` ${c8.cyan((flag ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3966
3997
|
}
|
|
3967
3998
|
writeln(`
|
|
3968
|
-
${
|
|
3999
|
+
${c8.bold("Provider env vars:")}`);
|
|
3969
4000
|
const envs = [
|
|
3970
4001
|
["OPENCODE_API_KEY", "OpenCode Zen (recommended)"],
|
|
3971
4002
|
["ANTHROPIC_API_KEY", "Anthropic direct"],
|
|
@@ -3974,15 +4005,15 @@ ${c7.bold("Provider env vars:")}`);
|
|
|
3974
4005
|
["OLLAMA_BASE_URL", "Ollama base URL (default: http://localhost:11434)"]
|
|
3975
4006
|
];
|
|
3976
4007
|
for (const [env, desc] of envs) {
|
|
3977
|
-
writeln(` ${
|
|
4008
|
+
writeln(` ${c8.yellow((env ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
|
|
3978
4009
|
}
|
|
3979
4010
|
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 ${
|
|
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")}`);
|
|
3986
4017
|
}
|
|
3987
4018
|
async function main() {
|
|
3988
4019
|
const argv = process.argv.slice(2);
|
|
@@ -4001,7 +4032,7 @@ async function main() {
|
|
|
4001
4032
|
if (last) {
|
|
4002
4033
|
sessionId = last.id;
|
|
4003
4034
|
} else {
|
|
4004
|
-
writeln(
|
|
4035
|
+
writeln(c8.dim("No previous session found, starting fresh."));
|
|
4005
4036
|
}
|
|
4006
4037
|
} else if (args.sessionId) {
|
|
4007
4038
|
sessionId = args.sessionId;
|