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/dist/mc.js CHANGED
@@ -2,12 +2,12 @@
2
2
  // @bun
3
3
 
4
4
  // src/index.ts
5
- import * as c7 from "yoctocolors";
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 join14 } from "path";
10
- import * as c6 from "yoctocolors";
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 c3 from "yoctocolors";
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
- var ZEN_ANTHROPIC_MODELS = new Set([
94
- "claude-opus-4-6",
95
- "claude-opus-4-5",
96
- "claude-opus-4-1",
97
- "claude-sonnet-4-6",
98
- "claude-sonnet-4-5",
99
- "claude-sonnet-4",
100
- "claude-haiku-4-5",
101
- "claude-3-5-haiku"
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
- if (ZEN_ANTHROPIC_MODELS.has(modelId)) {
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 homedir3 } from "os";
524
+ import { homedir as homedir4 } from "os";
551
525
  import { basename as basename2, join as join3 } from "path";
552
- function loadFromDir2(dir, source) {
553
- const commands = new Map;
554
- if (!existsSync3(dir))
555
- return commands;
556
- let entries;
557
- try {
558
- entries = readdirSync2(dir);
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
- import { homedir as homedir4 } from "os";
744
- import * as c2 from "yoctocolors";
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 globalDir = join4(homedir5(), ".agents", "skills");
1407
- const localDir = join4(cwd, ".agents", "skills");
1408
- const global = loadFromDir3(globalDir, "global");
1409
- const local = loadFromDir3(localDir, "local");
1410
- return new Map([...global, ...local]);
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 ${c3.cyan(args)} ${c3.dim("\u2014 run /models for the full list")}`);
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 ${c3.cyan(modelId)}`);
1447
+ writeln(`${PREFIX.success} model \u2192 ${c4.cyan(modelId)}`);
1429
1448
  return;
1430
1449
  }
1431
- writeln(`${c3.dim(" fetching models\u2026")}`);
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(c3.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
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(c3.bold(` ${provider}`));
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 ? c3.green(" free") : "";
1454
- const ctxTag = m.context ? c3.dim(` ${Math.round(m.context / 1000)}k`) : "";
1455
- const cur = isCurrent ? c3.cyan(" \u25C0") : "";
1456
- writeln(` ${c3.dim("\xB7")} ${m.displayName}${freeTag}${ctxTag}${cur}`);
1457
- writeln(` ${c3.dim(m.id)}`);
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(c3.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
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} ${c3.yellow("plan mode")} ${c3.dim("\u2014 read-only tools + MCP, no writes or shell")}`);
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} ${c3.dim("plan mode off")}`);
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} ${c3.magenta("ralph mode")} ${c3.dim("\u2014 loops until done, fresh context each iteration")}`);
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} ${c3.dim("ralph mode off")}`);
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} ${c3.dim("last turn undone \u2014 history and files restored")}`);
1505
+ writeln(`${PREFIX.success} ${c4.dim("last turn undone \u2014 history and files restored")}`);
1487
1506
  } else {
1488
- writeln(`${PREFIX.info} ${c3.dim("nothing to undo")}`);
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(c3.dim(" no MCP servers configured"));
1499
- writeln(c3.dim(" /mcp add <name> http <url> \xB7 /mcp add <name> stdio <cmd> [args...]"));
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 ? c3.dim(` ${s.url}`) : s.command ? c3.dim(` ${s.command}`) : "";
1505
- writeln(` ${c3.yellow("\u2699")} ${c3.bold(s.name)} ${c3.dim(s.transport)}${detail}`);
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(c3.red(" usage: /mcp add <name> http <url>"));
1513
- writeln(c3.red(" /mcp add <name> stdio <cmd> [args...]"));
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(c3.red(" usage: /mcp add <name> http <url>"));
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(c3.red(" usage: /mcp add <name> stdio <cmd> [args...]"));
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(c3.red(` unknown transport: ${transport} (use http or stdio)`));
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 ${c3.cyan(name)} added and connected`);
1569
+ writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} added and connected`);
1551
1570
  } catch (e) {
1552
- writeln(`${PREFIX.success} mcp server ${c3.cyan(name)} saved ${c3.dim(`(connection failed: ${String(e)})`)}`);
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(c3.red(" usage: /mcp remove <name>"));
1579
+ writeln(c4.red(" usage: /mcp remove <name>"));
1561
1580
  return;
1562
1581
  }
1563
1582
  deleteMcpServer(name);
1564
- writeln(`${PREFIX.success} mcp server ${c3.cyan(name)} removed`);
1583
+ writeln(`${PREFIX.success} mcp server ${c4.cyan(name)} removed`);
1565
1584
  return;
1566
1585
  }
1567
1586
  default:
1568
- writeln(c3.red(` unknown: /mcp ${sub}`));
1569
- writeln(c3.dim(" subcommands: list \xB7 add \xB7 remove"));
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 ? `Focus: ${focus}` : ""}
1576
-
1577
- ## What to review
1578
- - No args: \`git diff\` (unstaged) + \`git diff --cached\` (staged) + \`git status --short\` (untracked)
1579
- - Commit hash: \`git show <hash>\`
1580
- - Branch: \`git diff <branch>...HEAD\`
1581
- - PR number/URL: \`gh pr view\` + \`gh pr diff\`
1582
-
1583
- ## How to review
1584
- After getting the diff, read the full files changed \u2014 diffs alone miss context.
1585
- Check for AGENTS.md or CONVENTIONS.md for project conventions.
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} ${c3.cyan("review")} ${c3.dim("\u2014 spawning review subagent\u2026")}`);
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} ${c3.dim("new session started \u2014 context cleared")}`);
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 = c3.cyan(cmd.name);
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 = c3.dim(`[${srcPath}]`);
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(` ${c3.cyan(cmd.padEnd(26))} ${c3.dim(desc)}`);
1682
+ writeln(` ${c4.cyan(cmd.padEnd(26))} ${c4.dim(desc)}`);
1678
1683
  }
1679
1684
  if (custom.size > 0) {
1680
1685
  writeln();
1681
- writeln(c3.dim(" custom commands:"));
1686
+ writeln(c4.dim(" custom commands:"));
1682
1687
  for (const cmd of custom.values()) {
1683
- const tag = cmd.source === "local" ? c3.dim(" (local)") : c3.dim(" (global)");
1684
- writeln(` ${c3.green(`/${cmd.name}`.padEnd(26))} ${c3.dim(cmd.description)}${tag}`);
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(c3.dim(" agents (~/.agents/agents/ or .agents/agents/):"));
1695
+ writeln(c4.dim(" agents (~/.agents/agents/ or .agents/agents/):"));
1691
1696
  for (const agent of agents.values()) {
1692
- const tag = agent.source === "local" ? c3.dim(" (local)") : c3.dim(" (global)");
1693
- writeln(` ${c3.magenta(`@${agent.name}`.padEnd(26))} ${c3.dim(agent.description)}${tag}`);
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(c3.dim(" skills (~/.agents/skills/ or .agents/skills/):"));
1704
+ writeln(c4.dim(" skills (~/.agents/skills/ or .agents/skills/):"));
1700
1705
  for (const skill of skills.values()) {
1701
- const tag = skill.source === "local" ? c3.dim(" (local)") : c3.dim(" (global)");
1702
- writeln(` ${c3.yellow(`@${skill.name}`.padEnd(26))} ${c3.dim(skill.description)}${tag}`);
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(` ${c3.green("@agent".padEnd(26))} ${c3.dim("run prompt through a custom agent (Tab to complete)")}`);
1707
- writeln(` ${c3.green("@skill".padEnd(26))} ${c3.dim("inject skill instructions into prompt (Tab to complete)")}`);
1708
- writeln(` ${c3.green("@file".padEnd(26))} ${c3.dim("inject file contents into prompt (Tab to complete)")}`);
1709
- writeln(` ${c3.green("!cmd".padEnd(26))} ${c3.dim("run shell command, output added as context")}`);
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(` ${c3.dim("ctrl+c")} cancel ${c3.dim("\xB7")} ${c3.dim("ctrl+d")} exit ${c3.dim("\xB7")} ${c3.dim("ctrl+r")} history search ${c3.dim("\xB7")} ${c3.dim("\u2191\u2193")} history`);
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} ${c3.dim("\u2014 /help for commands")}`);
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 c4 from "yoctocolors";
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 = c4.green("\u25B6 ");
1916
- var PROMPT_PLAN = c4.yellow("\u2B22 ");
1917
- var PROMPT_RALPH = c4.magenta("\u21BB ");
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, c4.dim(pasteLabel(pasteBuffer))) : buf).replace(/\[image: [^\]]+\]/g, (m) => c4.dim(c4.cyan(m)));
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}${c4.cyan("search:")} ${searchQuery}\u2588`);
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 c5 of completions)
2153
- process.stdout.write(` ${c5}
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 c5 = chunk;
2268
- switch (c5.type) {
2284
+ const c6 = chunk;
2285
+ switch (c6.type) {
2269
2286
  case "text-delta": {
2270
- const delta = typeof c5.text === "string" ? c5.text : typeof c5.textDelta === "string" ? c5.textDelta : "";
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(c5.toolCallId ?? ""),
2281
- toolName: String(c5.toolName ?? ""),
2282
- args: c5.input ?? c5.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(c5.toolCallId ?? ""),
2290
- toolName: String(c5.toolName ?? ""),
2291
- result: "output" in c5 ? c5.output : ("result" in c5) ? c5.result : undefined,
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(c5.toolCallId ?? ""),
2300
- toolName: String(c5.toolName ?? ""),
2301
- result: c5.error ?? "Tool execution failed",
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 = c5.error;
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 c5 from "yoctocolors";
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(c5.dim("No sessions found."));
2593
+ writeln(c6.dim("No sessions found."));
2577
2594
  return;
2578
2595
  }
2579
2596
  writeln(`
2580
- ${c5.bold("Recent sessions:")}`);
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 || c5.dim("(untitled)");
2585
- writeln(` ${c5.dim(s.id.padEnd(14))} ${title.padEnd(30)} ${c5.cyan(s.model.split("/").pop() ?? s.model).padEnd(20)} ${c5.dim(cwd)} ${c5.dim(date)}`);
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
- ${c5.dim("Use")} mc --resume <id> ${c5.dim("to continue a session.")}`);
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 join8, relative as relative3 } from "path";
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
- const ignored = ignorePatterns.some((pat) => {
2778
- const ig = new Bun.Glob(pat);
2779
- return ig.match(file);
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 = join8(cwd, file);
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, join8(cwd, m.path)));
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 join9 } from "path";
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 (ignoreGlob.some((g) => g.match(relPath) || g.match(relPath.split("/")[0] ?? ""))) {
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 = join9(cwd, relPath);
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 c6 = ctxStart;c6 <= ctxEnd; c6++) {
2939
+ for (let c7 = ctxStart;c7 <= ctxEnd; c7++) {
2909
2940
  context.push({
2910
- line: c6 + 1,
2911
- text: formatHashLine(c6 + 1, lines[c6] ?? ""),
2912
- isMatch: c6 === i
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 join10 } from "path";
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
- join10(cwd, ".agents", "hooks", scriptName),
2954
- join10(homedir6(), ".agents", "hooks", scriptName)
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 join11, relative as relative4 } from "path";
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 : join11(cwd, 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 join12, relative as relative5 } from "path";
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 : join12(cwd, 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 join13, relative as relative6 } from "path";
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 : join13(cwd, 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
- join14(cwd, "AGENTS.md"),
3413
- join14(cwd, "CLAUDE.md"),
3414
- join14(getConfigDir(), "AGENTS.md")
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(c6.dim(out));
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} (${c6.cyan(currentModel)})`);
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 ${c6.cyan(row.name)}`);
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(c6.dim("Goodbye."));
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(c6.dim("Goodbye."));
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} ${c6.dim("ralph mode off")}`);
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} ${c6.yellow("ralph")} ${c6.dim("\u2014 max iterations reached, stopping")}`);
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 : join14(cwd, 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(`${c7.bold("mini-coder")} \u2014 a small, fast CLI coding agent
3996
+ writeln(`${c8.bold("mini-coder")} \u2014 a small, fast CLI coding agent
3952
3997
  `);
3953
- writeln(`${c7.bold("Usage:")} mc [options] [prompt]
3998
+ writeln(`${c8.bold("Usage:")} mc [options] [prompt]
3954
3999
  `);
3955
- writeln(`${c7.bold("Options:")}`);
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(` ${c7.cyan((flag ?? "").padEnd(22))} ${c7.dim(desc ?? "")}`);
4010
+ writeln(` ${c8.cyan((flag ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
3966
4011
  }
3967
4012
  writeln(`
3968
- ${c7.bold("Provider env vars:")}`);
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(` ${c7.yellow((env ?? "").padEnd(22))} ${c7.dim(desc ?? "")}`);
4022
+ writeln(` ${c8.yellow((env ?? "").padEnd(22))} ${c8.dim(desc ?? "")}`);
3978
4023
  }
3979
4024
  writeln(`
3980
- ${c7.bold("Examples:")}`);
3981
- writeln(` mc ${c7.dim("# interactive session")}`);
3982
- writeln(` mc "explain this codebase" ${c7.dim("# one-shot prompt then interactive")}`);
3983
- writeln(` mc -c ${c7.dim("# continue last session")}`);
3984
- writeln(` mc -m ollama/llama3.2 ${c7.dim("# use local Ollama model")}`);
3985
- writeln(` mc -l ${c7.dim("# list sessions")}`);
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(c7.dim("No previous session found, starting fresh."));
4049
+ writeln(c8.dim("No previous session found, starting fresh."));
4005
4050
  }
4006
4051
  } else if (args.sessionId) {
4007
4052
  sessionId = args.sessionId;