mini-coder 0.2.0 → 0.2.1

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,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/index.ts
5
- import * as c23 from "yoctocolors";
5
+ import * as c22 from "yoctocolors";
6
6
 
7
7
  // src/agent/agent.ts
8
8
  import * as c12 from "yoctocolors";
@@ -12,8 +12,12 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
12
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
13
13
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
14
14
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
15
+ // src/internal/version.ts
16
+ var PACKAGE_VERSION = "0.2.1";
17
+
18
+ // src/mcp/client.ts
15
19
  async function connectMcpServer(config) {
16
- const client = new Client({ name: "mini-coder", version: "0.1.0" });
20
+ const client = new Client({ name: "mini-coder", version: PACKAGE_VERSION });
17
21
  if (config.transport === "http") {
18
22
  if (!config.url) {
19
23
  throw new Error(`MCP server "${config.name}" requires a url`);
@@ -78,7 +82,7 @@ function getDbPath() {
78
82
  mkdirSync(dir, { recursive: true });
79
83
  return join(dir, "sessions.db");
80
84
  }
81
- var DB_VERSION = 4;
85
+ var DB_VERSION = 6;
82
86
  var SCHEMA = `
83
87
  CREATE TABLE IF NOT EXISTS sessions (
84
88
  id TEXT PRIMARY KEY,
@@ -129,6 +133,7 @@ var SCHEMA = `
129
133
  CREATE TABLE IF NOT EXISTS model_capabilities (
130
134
  canonical_model_id TEXT PRIMARY KEY,
131
135
  context_window INTEGER,
136
+ max_output_tokens INTEGER,
132
137
  reasoning INTEGER NOT NULL,
133
138
  source_provider TEXT,
134
139
  raw_json TEXT,
@@ -164,6 +169,17 @@ var SCHEMA = `
164
169
  expires_at INTEGER NOT NULL,
165
170
  updated_at INTEGER NOT NULL
166
171
  );
172
+
173
+ CREATE TABLE IF NOT EXISTS logs (
174
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
175
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
176
+ level TEXT NOT NULL,
177
+ timestamp INTEGER NOT NULL,
178
+ data TEXT NOT NULL
179
+ );
180
+
181
+ CREATE INDEX IF NOT EXISTS idx_logs_session
182
+ ON logs(session_id, timestamp DESC);
167
183
  `;
168
184
  var _db = null;
169
185
  function isSqliteBusyError(err) {
@@ -232,9 +248,70 @@ function pruneOldData() {
232
248
  });
233
249
  }
234
250
  runBestEffortMaintenance(() => {
235
- db.exec("PRAGMA wal_checkpoint(PASSIVE);");
251
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE);");
236
252
  });
237
253
  }
254
+ // src/session/db/logs-repo.ts
255
+ class LogsRepo {
256
+ db;
257
+ constructor(db) {
258
+ this.db = db;
259
+ }
260
+ write(sessionId, level, data) {
261
+ const timestamp = Date.now();
262
+ const serialized = data === undefined ? "{}" : JSON.stringify(this.sanitize(data));
263
+ this.db.run(`INSERT INTO logs (session_id, level, timestamp, data)
264
+ VALUES (?, ?, ?, ?)`, [sessionId, level, timestamp, serialized]);
265
+ }
266
+ getLogs(sessionId, level) {
267
+ let sql = `SELECT id, session_id, level, timestamp, data
268
+ FROM logs
269
+ WHERE session_id = ?`;
270
+ if (level) {
271
+ sql += ` AND level = ? ORDER BY timestamp ASC`;
272
+ const params2 = [sessionId, level];
273
+ return this.db.query(sql).all(...params2);
274
+ }
275
+ sql += ` ORDER BY timestamp ASC`;
276
+ const params = [sessionId];
277
+ return this.db.query(sql).all(...params);
278
+ }
279
+ deleteOldLogs(beforeTimestamp) {
280
+ return this.db.run(`DELETE FROM logs WHERE timestamp < ?`, [
281
+ beforeTimestamp
282
+ ]).changes;
283
+ }
284
+ deleteSessionLogs(sessionId) {
285
+ return this.db.run(`DELETE FROM logs WHERE session_id = ?`, [sessionId]).changes;
286
+ }
287
+ sanitize(data) {
288
+ if (!this.isObject(data))
289
+ return data;
290
+ const dropKeys = new Set([
291
+ "requestBodyValues",
292
+ "responseBody",
293
+ "responseHeaders",
294
+ "stack"
295
+ ]);
296
+ const result = {};
297
+ for (const key in data) {
298
+ if (dropKeys.has(key))
299
+ continue;
300
+ const value = data[key];
301
+ if (key === "errors" && Array.isArray(value)) {
302
+ result[key] = value.map((e) => this.sanitize(e));
303
+ } else if (key === "lastError" && this.isObject(value)) {
304
+ result[key] = this.sanitize(value);
305
+ } else {
306
+ result[key] = value;
307
+ }
308
+ }
309
+ return result;
310
+ }
311
+ isObject(v) {
312
+ return typeof v === "object" && v !== null;
313
+ }
314
+ }
238
315
  // src/session/db/mcp-repo.ts
239
316
  function listMcpServers() {
240
317
  return getDb().query("SELECT name, transport, url, command, args, env FROM mcp_servers ORDER BY name").all();
@@ -261,113 +338,117 @@ function deleteMcpServer(name) {
261
338
  getDb().run("DELETE FROM mcp_servers WHERE name = ?", [name]);
262
339
  }
263
340
  // src/cli/output.ts
264
- import { existsSync as existsSync3 } from "fs";
265
341
  import { homedir as homedir4 } from "os";
266
- import { join as join4 } from "path";
267
342
  import * as c8 from "yoctocolors";
268
343
 
269
- // src/cli/error-log.ts
270
- import { writeFileSync } from "fs";
271
-
272
- // src/cli/log-paths.ts
273
- import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2 } from "fs";
344
+ // src/agent/context-files.ts
345
+ import { existsSync as existsSync2, readFileSync } from "fs";
274
346
  import { homedir as homedir2 } from "os";
275
- import { join as join2 } from "path";
276
- var LOGS_DIR = join2(homedir2(), ".config", "mini-coder", "logs");
277
- function getLogsDir() {
278
- mkdirSync2(LOGS_DIR, { recursive: true });
279
- return LOGS_DIR;
347
+ import { dirname, join as join2, resolve } from "path";
348
+ var HOME = homedir2();
349
+ function tilde(p) {
350
+ return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
351
+ }
352
+ function globalContextCandidates(homeDir = HOME) {
353
+ return [
354
+ {
355
+ abs: join2(homeDir, ".agents", "AGENTS.md"),
356
+ label: "~/.agents/AGENTS.md"
357
+ },
358
+ {
359
+ abs: join2(homeDir, ".agents", "CLAUDE.md"),
360
+ label: "~/.agents/CLAUDE.md"
361
+ },
362
+ {
363
+ abs: join2(homeDir, ".claude", "CLAUDE.md"),
364
+ label: "~/.claude/CLAUDE.md"
365
+ }
366
+ ];
367
+ }
368
+ function dirContextCandidates(dir) {
369
+ const rel = (p) => tilde(resolve(dir, p));
370
+ return [
371
+ {
372
+ abs: join2(dir, ".agents", "AGENTS.md"),
373
+ label: `${rel(".agents/AGENTS.md")}`
374
+ },
375
+ {
376
+ abs: join2(dir, ".agents", "CLAUDE.md"),
377
+ label: `${rel(".agents/CLAUDE.md")}`
378
+ },
379
+ {
380
+ abs: join2(dir, ".claude", "CLAUDE.md"),
381
+ label: `${rel(".claude/CLAUDE.md")}`
382
+ },
383
+ { abs: join2(dir, "CLAUDE.md"), label: `${rel("CLAUDE.md")}` },
384
+ { abs: join2(dir, "AGENTS.md"), label: `${rel("AGENTS.md")}` }
385
+ ];
280
386
  }
281
- function pidLogPath(prefix) {
282
- return join2(getLogsDir(), `${prefix}-${process.pid}.log`);
387
+ function discoverContextFiles(cwd, homeDir) {
388
+ const candidates = [
389
+ ...globalContextCandidates(homeDir),
390
+ ...dirContextCandidates(cwd)
391
+ ];
392
+ return candidates.filter((c) => existsSync2(c.abs)).map((c) => c.label);
283
393
  }
284
- function isProcessAlive(pid) {
394
+ function tryReadFile(p) {
395
+ if (!existsSync2(p))
396
+ return null;
285
397
  try {
286
- process.kill(pid, 0);
287
- return true;
398
+ return readFileSync(p, "utf-8");
288
399
  } catch {
289
- return false;
400
+ return null;
290
401
  }
291
402
  }
292
- var LOG_FILE_RE = /^(api|errors)-(\d+)\.log$/;
293
- function cleanStaleLogs() {
294
- let entries;
295
- try {
296
- entries = readdirSync(LOGS_DIR);
297
- } catch {
298
- return;
403
+ function readCandidates(candidates) {
404
+ const parts = [];
405
+ for (const c of candidates) {
406
+ const content = tryReadFile(c.abs);
407
+ if (content)
408
+ parts.push(content);
299
409
  }
300
- for (const name of entries) {
301
- const m = LOG_FILE_RE.exec(name);
302
- if (!m)
303
- continue;
304
- const pidStr = m[2];
305
- const pid = Number.parseInt(pidStr, 10);
306
- if (pid === process.pid)
307
- continue;
308
- if (!isProcessAlive(pid)) {
309
- try {
310
- unlinkSync2(join2(LOGS_DIR, name));
311
- } catch {}
312
- }
410
+ return parts.length > 0 ? parts.join(`
411
+
412
+ `) : null;
413
+ }
414
+ function loadGlobalContextFile(homeDir) {
415
+ return readCandidates(globalContextCandidates(homeDir));
416
+ }
417
+ function loadLocalContextFile(cwd) {
418
+ let current = resolve(cwd);
419
+ while (true) {
420
+ const content = readCandidates(dirContextCandidates(current));
421
+ if (content)
422
+ return content;
423
+ if (existsSync2(join2(current, ".git")))
424
+ break;
425
+ const parent = dirname(current);
426
+ if (parent === current)
427
+ break;
428
+ current = parent;
313
429
  }
430
+ return null;
314
431
  }
315
432
 
316
- // src/cli/error-log.ts
317
- var writer = null;
318
- function initErrorLog() {
319
- if (writer)
320
- return;
321
- const logPath = pidLogPath("errors");
322
- writeFileSync(logPath, "");
323
- writer = Bun.file(logPath).writer();
324
- process.on("uncaughtException", (err) => {
325
- logError(err, "uncaught");
326
- process.exit(1);
327
- });
433
+ // src/logging/context.ts
434
+ var _currentContext = null;
435
+ function setLogContext(context) {
436
+ _currentContext = context;
328
437
  }
329
- function isObject(v) {
330
- return typeof v === "object" && v !== null;
438
+ function getLogContext() {
439
+ return _currentContext;
331
440
  }
332
441
  function logError(err, context) {
333
- if (!writer)
442
+ const logCtx = _currentContext;
443
+ if (!logCtx)
334
444
  return;
335
- let entry = `[${new Date().toISOString()}]`;
336
- if (context)
337
- entry += ` context=${context}`;
338
- entry += `
339
- `;
340
- if (isObject(err)) {
341
- if (typeof err.name === "string")
342
- entry += ` name: ${err.name}
343
- `;
344
- if (typeof err.message === "string")
345
- entry += ` message: ${err.message}
346
- `;
347
- if ("statusCode" in err)
348
- entry += ` statusCode: ${err.statusCode}
349
- `;
350
- if ("url" in err)
351
- entry += ` url: ${err.url}
352
- `;
353
- if ("isRetryable" in err)
354
- entry += ` isRetryable: ${err.isRetryable}
355
- `;
356
- if (typeof err.stack === "string") {
357
- const indentedStack = err.stack.split(`
358
- `).map((line, i) => i === 0 ? line : ` ${line}`).join(`
359
- `);
360
- entry += ` stack: ${indentedStack}
361
- `;
362
- }
363
- } else {
364
- entry += ` value: ${String(err)}
365
- `;
366
- }
367
- entry += `---
368
- `;
369
- writer.write(entry);
370
- writer.flush();
445
+ logCtx.logsRepo.write(logCtx.sessionId, "error", { error: err, context });
446
+ }
447
+ function logApiEvent(event, data) {
448
+ const logCtx = _currentContext;
449
+ if (!logCtx)
450
+ return;
451
+ logCtx.logsRepo.write(logCtx.sessionId, "api", { event, data });
371
452
  }
372
453
 
373
454
  // src/cli/error-parse.ts
@@ -537,31 +618,61 @@ function parseAppError(err) {
537
618
  // src/cli/skills.ts
538
619
  import {
539
620
  closeSync,
540
- existsSync as existsSync2,
621
+ existsSync as existsSync3,
541
622
  openSync,
542
- readdirSync as readdirSync2,
543
- readFileSync,
623
+ readdirSync,
624
+ readFileSync as readFileSync2,
544
625
  readSync,
545
626
  statSync
546
627
  } from "fs";
547
628
  import { homedir as homedir3 } from "os";
548
- import { dirname, join as join3, resolve } from "path";
549
-
550
- // src/cli/config-conflicts.ts
629
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
551
630
  import * as c from "yoctocolors";
552
- function warnConventionConflicts(kind, scope, agentsNames, claudeNames) {
553
- const agents = new Set(agentsNames);
554
- const claude = new Set(claudeNames);
555
- const conflicts = [];
556
- for (const name of agents) {
557
- if (claude.has(name))
558
- conflicts.push(name);
631
+
632
+ // src/cli/frontmatter.ts
633
+ var FM_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
634
+ function parseFrontmatter(raw) {
635
+ const m = raw.match(FM_RE);
636
+ if (!m)
637
+ return { meta: {}, body: raw };
638
+ const meta = {};
639
+ const lines = (m[1] ?? "").split(`
640
+ `);
641
+ let parentKey = "";
642
+ for (const line of lines) {
643
+ const stripped = line.replace(/\r$/, "");
644
+ const indent = stripped.length - stripped.trimStart().length;
645
+ if (indent > 0 && parentKey) {
646
+ const colon2 = stripped.indexOf(":");
647
+ if (colon2 === -1)
648
+ continue;
649
+ const key2 = stripped.slice(0, colon2).trim();
650
+ const val2 = stripped.slice(colon2 + 1).trim().replace(/^["']|["']$/g, "");
651
+ if (!key2)
652
+ continue;
653
+ const parent = meta[parentKey];
654
+ if (typeof parent === "object")
655
+ parent[key2] = val2;
656
+ else
657
+ meta[parentKey] = { [key2]: val2 };
658
+ continue;
659
+ }
660
+ const colon = stripped.indexOf(":");
661
+ if (colon === -1)
662
+ continue;
663
+ const key = stripped.slice(0, colon).trim();
664
+ const val = stripped.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
665
+ if (!key)
666
+ continue;
667
+ if (val === "") {
668
+ parentKey = key;
669
+ meta[key] = {};
670
+ } else {
671
+ parentKey = "";
672
+ meta[key] = val;
673
+ }
559
674
  }
560
- if (conflicts.length === 0)
561
- return;
562
- conflicts.sort((a, b) => a.localeCompare(b));
563
- const list = conflicts.map((n) => c.cyan(n)).join(c.dim(", "));
564
- writeln(`${G.warn} conflicting ${kind} in ${scope} .agents and .claude: ${list} ${c.dim("\u2014 using .agents version")}`);
675
+ return { meta, body: (m[2] ?? "").trim() };
565
676
  }
566
677
 
567
678
  // src/cli/skills.ts
@@ -577,28 +688,17 @@ function parseSkillFrontmatter(filePath) {
577
688
  const chunk = Buffer.allocUnsafe(MAX_FRONTMATTER_BYTES);
578
689
  const bytesRead = readSync(fd, chunk, 0, MAX_FRONTMATTER_BYTES, 0);
579
690
  const text = chunk.toString("utf8", 0, bytesRead);
580
- const lines = text.split(`
581
- `);
582
- if ((lines[0] ?? "").trim() !== "---")
583
- return {};
584
- const meta = {};
585
- for (let i = 1;i < lines.length; i++) {
586
- const line = (lines[i] ?? "").replace(/\r$/, "");
587
- if (line.trim() === "---")
588
- break;
589
- const colon = line.indexOf(":");
590
- if (colon === -1)
591
- continue;
592
- const key = line.slice(0, colon).trim();
593
- const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
594
- if (key === "name")
595
- meta.name = value;
596
- if (key === "description")
597
- meta.description = value;
598
- if (key === "context")
599
- meta.context = value;
600
- }
601
- return meta;
691
+ const { meta } = parseFrontmatter(text);
692
+ const result = {};
693
+ if (typeof meta.name === "string" && meta.name)
694
+ result.name = meta.name;
695
+ if (typeof meta.description === "string" && meta.description)
696
+ result.description = meta.description;
697
+ if (typeof meta.context === "string" && meta.context)
698
+ result.context = meta.context;
699
+ if (typeof meta.compatibility === "string" && meta.compatibility)
700
+ result.compatibility = meta.compatibility;
701
+ return result;
602
702
  } catch {
603
703
  return {};
604
704
  } finally {
@@ -616,18 +716,18 @@ function candidateConflictName(candidate) {
616
716
  return getCandidateFrontmatter(candidate).name?.trim() || candidate.folderName;
617
717
  }
618
718
  function findGitBoundary(cwd) {
619
- let current = resolve(cwd);
719
+ let current = resolve2(cwd);
620
720
  while (true) {
621
- if (existsSync2(join3(current, ".git")))
721
+ if (existsSync3(join3(current, ".git")))
622
722
  return current;
623
- const parent = dirname(current);
723
+ const parent = dirname2(current);
624
724
  if (parent === current)
625
725
  return null;
626
726
  current = parent;
627
727
  }
628
728
  }
629
729
  function localSearchRoots(cwd) {
630
- const start = resolve(cwd);
730
+ const start = resolve2(cwd);
631
731
  const stop = findGitBoundary(start);
632
732
  if (!stop)
633
733
  return [start];
@@ -637,41 +737,54 @@ function localSearchRoots(cwd) {
637
737
  roots.push(current);
638
738
  if (current === stop)
639
739
  break;
640
- const parent = dirname(current);
740
+ const parent = dirname2(current);
641
741
  if (parent === current)
642
742
  break;
643
743
  current = parent;
644
744
  }
645
745
  return roots;
646
746
  }
747
+ var MAX_SKILL_SCAN_DEPTH = 5;
748
+ var MAX_SKILL_DIRS_SCANNED = 2000;
647
749
  function listSkillCandidates(skillsDir, source, rootPath) {
648
- if (!existsSync2(skillsDir))
649
- return [];
650
- let entries;
651
- try {
652
- entries = readdirSync2(skillsDir).sort((a, b) => a.localeCompare(b));
653
- } catch {
750
+ if (!existsSync3(skillsDir))
654
751
  return [];
655
- }
656
752
  const candidates = [];
657
- for (const entry of entries) {
658
- const skillDir = join3(skillsDir, entry);
753
+ let dirsScanned = 0;
754
+ function walk(dir, depth) {
755
+ if (depth > MAX_SKILL_SCAN_DEPTH || dirsScanned >= MAX_SKILL_DIRS_SCANNED)
756
+ return;
757
+ let entries;
659
758
  try {
660
- if (!statSync(skillDir).isDirectory())
661
- continue;
759
+ entries = readdirSync(dir).sort((a, b) => a.localeCompare(b));
662
760
  } catch {
663
- continue;
761
+ return;
762
+ }
763
+ for (const entry of entries) {
764
+ if (dirsScanned >= MAX_SKILL_DIRS_SCANNED)
765
+ return;
766
+ const entryPath = join3(dir, entry);
767
+ try {
768
+ if (!statSync(entryPath).isDirectory())
769
+ continue;
770
+ } catch {
771
+ continue;
772
+ }
773
+ dirsScanned++;
774
+ const filePath = join3(entryPath, "SKILL.md");
775
+ if (existsSync3(filePath)) {
776
+ candidates.push({
777
+ folderName: entry,
778
+ filePath,
779
+ rootPath,
780
+ source
781
+ });
782
+ } else {
783
+ walk(entryPath, depth + 1);
784
+ }
664
785
  }
665
- const filePath = join3(skillDir, "SKILL.md");
666
- if (!existsSync2(filePath))
667
- continue;
668
- candidates.push({
669
- folderName: entry,
670
- filePath,
671
- rootPath,
672
- source
673
- });
674
786
  }
787
+ walk(skillsDir, 1);
675
788
  return candidates;
676
789
  }
677
790
  function warnInvalidSkill(filePath, reason) {
@@ -688,6 +801,20 @@ function warnSkillIssue(filePath, reason) {
688
801
  warnedSkillIssues.add(key);
689
802
  writeln(`${G.warn} skill ${filePath}: ${reason}`);
690
803
  }
804
+ function warnConventionConflicts(kind, scope, agentsNames, claudeNames) {
805
+ const agents = new Set(agentsNames);
806
+ const claude = new Set(claudeNames);
807
+ const conflicts = [];
808
+ for (const name of agents) {
809
+ if (claude.has(name))
810
+ conflicts.push(name);
811
+ }
812
+ if (conflicts.length === 0)
813
+ return;
814
+ conflicts.sort((a, b) => a.localeCompare(b));
815
+ const list = conflicts.map((n) => c.cyan(n)).join(c.dim(", "));
816
+ writeln(`${G.warn} conflicting ${kind} in ${scope} .agents and .claude: ${list} ${c.dim("\u2014 using .agents version")}`);
817
+ }
691
818
  function validateSkill(candidate) {
692
819
  const meta = getCandidateFrontmatter(candidate);
693
820
  const name = meta.name?.trim();
@@ -712,7 +839,8 @@ function validateSkill(candidate) {
712
839
  source: candidate.source,
713
840
  rootPath: candidate.rootPath,
714
841
  filePath: candidate.filePath,
715
- ...meta.context === "fork" && { context: "fork" }
842
+ ...meta.context === "fork" && { context: "fork" },
843
+ ...meta.compatibility && { compatibility: meta.compatibility }
716
844
  };
717
845
  }
718
846
  function allSkillCandidates(cwd, homeDir) {
@@ -747,7 +875,7 @@ function listSkillResources(skillDir) {
747
875
  function walk(dir, prefix) {
748
876
  let entries;
749
877
  try {
750
- entries = readdirSync2(dir);
878
+ entries = readdirSync(dir);
751
879
  } catch {
752
880
  return;
753
881
  }
@@ -772,8 +900,8 @@ function listSkillResources(skillDir) {
772
900
  }
773
901
  function loadSkillContentFromMeta(skill) {
774
902
  try {
775
- const content = readFileSync(skill.filePath, "utf-8");
776
- const skillDir = dirname(skill.filePath);
903
+ const content = readFileSync2(skill.filePath, "utf-8");
904
+ const skillDir = dirname2(skill.filePath);
777
905
  return {
778
906
  name: skill.name,
779
907
  content,
@@ -944,15 +1072,17 @@ function stripAnsi(text) {
944
1072
  return text.replace(ANSI_REGEX, "");
945
1073
  }
946
1074
 
947
- // src/cli/status-bar.ts
948
- var STATUS_SEP = c3.dim(" \xB7 ");
949
- function truncatePlainText(value, maxLen) {
950
- if (value.length <= maxLen)
1075
+ // src/internal/text.ts
1076
+ function truncateText(value, max) {
1077
+ if (value.length <= max)
951
1078
  return value;
952
- if (maxLen <= 1)
1079
+ if (max <= 1)
953
1080
  return "\u2026";
954
- return `${value.slice(0, maxLen - 1)}\u2026`;
1081
+ return `${value.slice(0, max - 1)}\u2026`;
955
1082
  }
1083
+
1084
+ // src/cli/status-bar.ts
1085
+ var STATUS_SEP = c3.dim(" \xB7 ");
956
1086
  function fmtTokens(n) {
957
1087
  if (n >= 1000)
958
1088
  return `${(n / 1000).toFixed(1)}k`;
@@ -986,8 +1116,7 @@ function buildStatusBarSignature(opts) {
986
1116
  outputTokens: opts.outputTokens,
987
1117
  contextTokens: opts.contextTokens,
988
1118
  contextWindow: opts.contextWindow,
989
- thinkingEffort: opts.thinkingEffort ?? null,
990
- showReasoning: opts.showReasoning ?? false
1119
+ thinkingEffort: opts.thinkingEffort ?? null
991
1120
  });
992
1121
  }
993
1122
  function fitStatusSegments(required, optional, cols) {
@@ -1003,17 +1132,16 @@ function fitStatusSegments(required, optional, cols) {
1003
1132
  const sepLen = stripAnsi(STATUS_SEP).length;
1004
1133
  const fixedPrefix = plainRequired[0] ?? "";
1005
1134
  if (plainRequired.length <= 1)
1006
- return truncatePlainText(fixedPrefix, cols);
1135
+ return truncateText(fixedPrefix, cols);
1007
1136
  const maxTailLen = Math.max(8, cols - fixedPrefix.length - sepLen);
1008
- const truncatedTail = truncatePlainText(plainRequired[1] ?? "", maxTailLen);
1137
+ const truncatedTail = truncateText(plainRequired[1] ?? "", maxTailLen);
1009
1138
  return `${required[0]}${STATUS_SEP}${c3.dim(truncatedTail)}`;
1010
1139
  }
1011
1140
  function renderStatusBar(opts) {
1012
1141
  const cols = Math.max(20, terminal.stdoutColumns || 80);
1013
- const required = [c3.cyan(opts.model), c3.dim(`#${opts.sessionId}`)];
1142
+ const modelSegment = opts.thinkingEffort ? `${c3.cyan(opts.model)} ${c3.magenta(c3.italic(`\u2726 ${opts.thinkingEffort}`))}` : c3.cyan(opts.model);
1143
+ const required = [modelSegment, c3.dim(`#${opts.sessionId}`)];
1014
1144
  const optional = [];
1015
- if (opts.thinkingEffort)
1016
- optional.push(c3.dim(`\u2726 ${opts.thinkingEffort}`));
1017
1145
  if (opts.gitBranch)
1018
1146
  optional.push(c3.dim(`\u2387 ${opts.gitBranch}`));
1019
1147
  if (opts.inputTokens > 0 || opts.outputTokens > 0) {
@@ -1117,8 +1245,7 @@ function isToolCallPart(part) {
1117
1245
  function hasObjectToolCallInput(part) {
1118
1246
  return isToolCallPart(part) && "input" in part && isRecord(part.input) && !Array.isArray(part.input);
1119
1247
  }
1120
- var TOOL_RUNTIME_INPUT_KEYS = new Set(["cwd"]);
1121
- function stripToolRuntimeInputFields(messages) {
1248
+ function mapAssistantParts(messages, transform) {
1122
1249
  let mutated = false;
1123
1250
  const result = messages.map((message) => {
1124
1251
  if (message.role !== "assistant" || !Array.isArray(message.content)) {
@@ -1126,24 +1253,10 @@ function stripToolRuntimeInputFields(messages) {
1126
1253
  }
1127
1254
  let contentMutated = false;
1128
1255
  const nextContent = message.content.map((part) => {
1129
- if (!hasObjectToolCallInput(part)) {
1130
- return part;
1131
- }
1132
- let inputMutated = false;
1133
- const nextInput = { ...part.input };
1134
- for (const key of TOOL_RUNTIME_INPUT_KEYS) {
1135
- if (!(key in nextInput))
1136
- continue;
1137
- delete nextInput[key];
1138
- inputMutated = true;
1139
- }
1140
- if (!inputMutated)
1141
- return part;
1142
- contentMutated = true;
1143
- return {
1144
- ...part,
1145
- input: nextInput
1146
- };
1256
+ const next = transform(part);
1257
+ if (next !== part)
1258
+ contentMutated = true;
1259
+ return next;
1147
1260
  });
1148
1261
  if (!contentMutated)
1149
1262
  return message;
@@ -1155,6 +1268,22 @@ function stripToolRuntimeInputFields(messages) {
1155
1268
  });
1156
1269
  return mutated ? result : messages;
1157
1270
  }
1271
+ var TOOL_RUNTIME_INPUT_KEYS = new Set(["cwd"]);
1272
+ function stripToolRuntimeInputFields(messages) {
1273
+ return mapAssistantParts(messages, (part) => {
1274
+ if (!hasObjectToolCallInput(part))
1275
+ return part;
1276
+ let inputMutated = false;
1277
+ const nextInput = { ...part.input };
1278
+ for (const key of TOOL_RUNTIME_INPUT_KEYS) {
1279
+ if (!(key in nextInput))
1280
+ continue;
1281
+ delete nextInput[key];
1282
+ inputMutated = true;
1283
+ }
1284
+ return inputMutated ? { ...part, input: nextInput } : part;
1285
+ });
1286
+ }
1158
1287
 
1159
1288
  // src/llm-api/history/gemini.ts
1160
1289
  function getGeminiThoughtSignature(part) {
@@ -1294,38 +1423,19 @@ function isOpenAIGPT(modelString) {
1294
1423
  function normalizeOpenAICompatibleToolCallInputs(messages, modelString) {
1295
1424
  if (!isZenOpenAICompatibleChatModel(modelString))
1296
1425
  return messages;
1297
- let mutated = false;
1298
- const result = messages.map((message) => {
1299
- if (message.role !== "assistant" || !Array.isArray(message.content)) {
1300
- return message;
1426
+ return mapAssistantParts(messages, (part) => {
1427
+ if (!isToolCallPart(part) || !("input" in part) || typeof part.input !== "string") {
1428
+ return part;
1301
1429
  }
1302
- let contentMutated = false;
1303
- const nextContent = message.content.map((part) => {
1304
- if (!isToolCallPart(part) || !("input" in part) || typeof part.input !== "string") {
1305
- return part;
1306
- }
1307
- try {
1308
- const parsed = JSON.parse(part.input);
1309
- if (!isRecord(parsed) || Array.isArray(parsed))
1310
- return part;
1311
- contentMutated = true;
1312
- return {
1313
- ...part,
1314
- input: parsed
1315
- };
1316
- } catch {
1430
+ try {
1431
+ const parsed = JSON.parse(part.input);
1432
+ if (!isRecord(parsed) || Array.isArray(parsed))
1317
1433
  return part;
1318
- }
1319
- });
1320
- if (!contentMutated)
1321
- return message;
1322
- mutated = true;
1323
- return {
1324
- ...message,
1325
- content: nextContent
1326
- };
1434
+ return { ...part, input: parsed };
1435
+ } catch {
1436
+ return part;
1437
+ }
1327
1438
  });
1328
- return mutated ? result : messages;
1329
1439
  }
1330
1440
  function stripOpenAIHistory(messages, modelString, options) {
1331
1441
  if (!isOpenAIGPT(modelString))
@@ -1400,7 +1510,7 @@ function makeInterruptMessage(reason) {
1400
1510
  return { role: "assistant", content: text };
1401
1511
  }
1402
1512
  function isAbortError(error) {
1403
- return error.name === "AbortError" || error.name === "Error" && error.message.toLowerCase().includes("abort");
1513
+ return error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR" || error.name === "Error" && error.message === "aborted";
1404
1514
  }
1405
1515
  function buildAbortMessages(partialMessages, accumulatedText) {
1406
1516
  const stub = makeInterruptMessage("user");
@@ -1647,7 +1757,7 @@ function writePreviewLines(opts) {
1647
1757
  function truncateOneLine(value, max = 100, verboseOutput = false) {
1648
1758
  if (verboseOutput)
1649
1759
  return value;
1650
- return value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
1760
+ return truncateText(value, max);
1651
1761
  }
1652
1762
  function normalizeShellText(value) {
1653
1763
  return value.replace(/[\r\n]+$/, "");
@@ -1895,7 +2005,7 @@ function shellCmdGlyph(cmd, fullCmd) {
1895
2005
  case "touch":
1896
2006
  return G.write;
1897
2007
  case "sed":
1898
- return /\s-i\b/.test(fullCmd) ? G.write : G.run;
2008
+ return /\s-i\b/.test(fullCmd) ? G.write : G.read;
1899
2009
  case "git":
1900
2010
  case "bun":
1901
2011
  case "echo":
@@ -1921,15 +2031,12 @@ function extractFirstCommand(cmd) {
1921
2031
  }
1922
2032
  return tokens[0] ?? "";
1923
2033
  }
1924
- function truncate(s, max) {
1925
- return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
1926
- }
1927
2034
  function formatShellCallLine(cmd) {
1928
2035
  const { cwd, rest } = parseShellCdPrefix(cmd);
1929
2036
  const firstCmd = extractFirstCommand(rest);
1930
2037
  const glyph = shellCmdGlyph(firstCmd, rest);
1931
2038
  const cwdSuffix = cwd ? ` ${c6.dim(`in ${cwd}`)}` : "";
1932
- const display = truncate(rest, 80);
2039
+ const display = truncateText(rest, 80);
1933
2040
  return `${glyph} ${display}${cwdSuffix}`;
1934
2041
  }
1935
2042
  function buildToolCallLine(name, args) {
@@ -2115,6 +2222,9 @@ async function renderTurn(events, spinner, opts) {
2115
2222
  liveReasoning.finish();
2116
2223
  content.flushOpenContent();
2117
2224
  spinner.stop();
2225
+ inputTokens = event.inputTokens;
2226
+ outputTokens = event.outputTokens;
2227
+ contextTokens = event.contextTokens;
2118
2228
  if (isAbortError(event.error)) {
2119
2229
  newMessages = buildAbortMessages(event.partialMessages, content.getText());
2120
2230
  } else {
@@ -2135,10 +2245,9 @@ async function renderTurn(events, spinner, opts) {
2135
2245
  }
2136
2246
 
2137
2247
  // src/cli/output.ts
2138
- var HOME = homedir4();
2139
- var PACKAGE_VERSION = "0.2.0";
2248
+ var HOME2 = homedir4();
2140
2249
  function tildePath(p) {
2141
- return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
2250
+ return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
2142
2251
  }
2143
2252
  function restoreTerminal() {
2144
2253
  terminal.restoreTerminal();
@@ -2203,28 +2312,12 @@ function renderError(err, context = "render") {
2203
2312
  writeln(` ${c8.dim(parsed.hint)}`);
2204
2313
  }
2205
2314
  }
2206
- function discoverContextFiles(cwd) {
2207
- const found = [];
2208
- const globalDir = join4(HOME, ".agents");
2209
- const candidates = [
2210
- [join4(globalDir, "AGENTS.md"), "~/.agents/AGENTS.md"],
2211
- [join4(globalDir, "CLAUDE.md"), "~/.agents/CLAUDE.md"],
2212
- [join4(cwd, ".agents", "AGENTS.md"), ".agents/AGENTS.md"],
2213
- [join4(cwd, "CLAUDE.md"), "CLAUDE.md"],
2214
- [join4(cwd, "AGENTS.md"), "AGENTS.md"]
2215
- ];
2216
- for (const [abs, label] of candidates) {
2217
- if (existsSync3(abs))
2218
- found.push(label);
2219
- }
2220
- return found;
2221
- }
2222
2315
  function renderBanner(model, cwd) {
2223
2316
  writeln();
2224
2317
  const title = PACKAGE_VERSION ? `mini-coder \xB7 v${PACKAGE_VERSION}` : "mini-coder";
2225
2318
  writeln(` ${c8.cyan("mc")} ${c8.dim(title)}`);
2226
2319
  writeln(` ${c8.dim(model)} ${c8.dim("\xB7")} ${c8.dim(tildePath(cwd))}`);
2227
- writeln(` ${c8.dim("/help for commands \xB7 esc cancel \xB7 ctrl+c/ctrl+d exit")}`);
2320
+ writeln(` ${c8.dim("/help for commands \xB7 esc cancel \xB7 ctrl+d exit")}`);
2228
2321
  const items = [];
2229
2322
  if (getPreferredShowReasoning())
2230
2323
  items.push("reasoning: on");
@@ -2369,22 +2462,23 @@ function getPromptHistory(limit = 200) {
2369
2462
  }
2370
2463
  // src/session/db/model-info-repo.ts
2371
2464
  function listModelCapabilities() {
2372
- return getDb().query("SELECT canonical_model_id, context_window, reasoning, source_provider, raw_json, updated_at FROM model_capabilities").all();
2465
+ return getDb().query("SELECT canonical_model_id, context_window, max_output_tokens, reasoning, source_provider, raw_json, updated_at FROM model_capabilities").all();
2373
2466
  }
2374
2467
  function replaceModelCapabilities(rows) {
2375
2468
  const db = getDb();
2376
2469
  const insertStmt = db.prepare(`INSERT INTO model_capabilities (
2377
2470
  canonical_model_id,
2378
2471
  context_window,
2472
+ max_output_tokens,
2379
2473
  reasoning,
2380
2474
  source_provider,
2381
2475
  raw_json,
2382
2476
  updated_at
2383
- ) VALUES (?, ?, ?, ?, ?, ?)`);
2477
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`);
2384
2478
  const run = db.transaction(() => {
2385
2479
  db.run("DELETE FROM model_capabilities");
2386
2480
  for (const row of rows) {
2387
- insertStmt.run(row.canonical_model_id, row.context_window, row.reasoning, row.source_provider, row.raw_json, row.updated_at);
2481
+ insertStmt.run(row.canonical_model_id, row.context_window, row.max_output_tokens, row.reasoning, row.source_provider, row.raw_json, row.updated_at);
2388
2482
  }
2389
2483
  });
2390
2484
  run();
@@ -2446,6 +2540,12 @@ function touchSession(id, model) {
2446
2540
  id
2447
2541
  ]);
2448
2542
  }
2543
+ function setSessionTitle(id, title) {
2544
+ getDb().run("UPDATE sessions SET title = ? WHERE id = ? AND title = ''", [
2545
+ title,
2546
+ id
2547
+ ]);
2548
+ }
2449
2549
  function listSessions(limit = 20) {
2450
2550
  return getDb().query("SELECT * FROM sessions ORDER BY updated_at DESC LIMIT ?").all(limit);
2451
2551
  }
@@ -2586,7 +2686,7 @@ function buildPromptDisplay(text, cursor, maxLen) {
2586
2686
  }
2587
2687
 
2588
2688
  // src/cli/completions.ts
2589
- import { join as join5, relative } from "path";
2689
+ import { join as join4, relative } from "path";
2590
2690
 
2591
2691
  // src/session/oauth/anthropic.ts
2592
2692
  import { createServer } from "http";
@@ -2623,7 +2723,7 @@ var SUCCESS_HTML = `<!doctype html>
2623
2723
  <html lang="en"><head><meta charset="utf-8"><title>Authenticated</title></head>
2624
2724
  <body><p>Authentication successful. Return to your terminal.</p></body></html>`;
2625
2725
  function startCallbackServer(expectedState) {
2626
- return new Promise((resolve2, reject) => {
2726
+ return new Promise((resolve3, reject) => {
2627
2727
  let result = null;
2628
2728
  let cancelled = false;
2629
2729
  const server = createServer((req, res) => {
@@ -2644,7 +2744,7 @@ function startCallbackServer(expectedState) {
2644
2744
  });
2645
2745
  server.on("error", reject);
2646
2746
  server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
2647
- resolve2({
2747
+ resolve3({
2648
2748
  server,
2649
2749
  cancel: () => {
2650
2750
  cancelled = true;
@@ -2818,6 +2918,15 @@ function parseContextWindow(model) {
2818
2918
  return null;
2819
2919
  return Math.max(0, Math.trunc(context));
2820
2920
  }
2921
+ function parseMaxOutputTokens(model) {
2922
+ const limit = model.limit;
2923
+ if (!isRecord(limit))
2924
+ return null;
2925
+ const output = limit.output;
2926
+ if (typeof output !== "number" || !Number.isFinite(output))
2927
+ return null;
2928
+ return Math.max(0, Math.trunc(output));
2929
+ }
2821
2930
  function parseModelsDevCapabilities(payload, updatedAt) {
2822
2931
  if (!isRecord(payload))
2823
2932
  return [];
@@ -2836,6 +2945,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
2836
2945
  if (!canonicalModelId)
2837
2946
  continue;
2838
2947
  const contextWindow = parseContextWindow(modelValue);
2948
+ const maxOutputTokens = parseMaxOutputTokens(modelValue);
2839
2949
  const reasoning = modelValue.reasoning === true;
2840
2950
  const rawJson = JSON.stringify(modelValue);
2841
2951
  const prev = merged.get(canonicalModelId);
@@ -2843,6 +2953,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
2843
2953
  merged.set(canonicalModelId, {
2844
2954
  canonicalModelId,
2845
2955
  contextWindow,
2956
+ maxOutputTokens,
2846
2957
  reasoning,
2847
2958
  sourceProvider: provider,
2848
2959
  rawJson
@@ -2852,6 +2963,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
2852
2963
  merged.set(canonicalModelId, {
2853
2964
  canonicalModelId,
2854
2965
  contextWindow: prev.contextWindow ?? contextWindow,
2966
+ maxOutputTokens: prev.maxOutputTokens ?? maxOutputTokens,
2855
2967
  reasoning: prev.reasoning || reasoning,
2856
2968
  sourceProvider: prev.sourceProvider,
2857
2969
  rawJson: prev.rawJson ?? rawJson
@@ -2861,6 +2973,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
2861
2973
  return Array.from(merged.values()).map((entry) => ({
2862
2974
  canonical_model_id: entry.canonicalModelId,
2863
2975
  context_window: entry.contextWindow,
2976
+ max_output_tokens: entry.maxOutputTokens,
2864
2977
  reasoning: entry.reasoning ? 1 : 0,
2865
2978
  source_provider: entry.sourceProvider,
2866
2979
  raw_json: entry.rawJson,
@@ -2945,6 +3058,7 @@ function buildRuntimeCache(capabilityRows, providerRows, stateRows) {
2945
3058
  capabilitiesByCanonical.set(canonical, {
2946
3059
  canonicalModelId: canonical,
2947
3060
  contextWindow: row.context_window,
3061
+ maxOutputTokens: row.max_output_tokens,
2948
3062
  reasoning: row.reasoning === 1,
2949
3063
  sourceProvider: row.source_provider
2950
3064
  });
@@ -2992,6 +3106,7 @@ function resolveFromProviderRow(row, cache) {
2992
3106
  return {
2993
3107
  canonicalModelId: capability.canonicalModelId,
2994
3108
  contextWindow: capability.contextWindow ?? row.contextWindow,
3109
+ maxOutputTokens: capability.maxOutputTokens,
2995
3110
  reasoning: capability.reasoning
2996
3111
  };
2997
3112
  }
@@ -2999,6 +3114,7 @@ function resolveFromProviderRow(row, cache) {
2999
3114
  return {
3000
3115
  canonicalModelId: row.canonicalModelId,
3001
3116
  contextWindow: row.contextWindow,
3117
+ maxOutputTokens: null,
3002
3118
  reasoning: false
3003
3119
  };
3004
3120
  }
@@ -3019,6 +3135,7 @@ function resolveModelInfoInCache(modelString, cache) {
3019
3135
  return {
3020
3136
  canonicalModelId: capability.canonicalModelId,
3021
3137
  contextWindow: capability.contextWindow,
3138
+ maxOutputTokens: capability.maxOutputTokens,
3022
3139
  reasoning: capability.reasoning
3023
3140
  };
3024
3141
  }
@@ -3381,6 +3498,9 @@ function resolveModelInfo(modelString) {
3381
3498
  function getContextWindow(modelString) {
3382
3499
  return resolveModelInfo(modelString)?.contextWindow ?? null;
3383
3500
  }
3501
+ function getMaxOutputTokens(modelString) {
3502
+ return resolveModelInfo(modelString)?.maxOutputTokens ?? null;
3503
+ }
3384
3504
  function supportsThinking(modelString) {
3385
3505
  return resolveModelInfo(modelString)?.reasoning ?? false;
3386
3506
  }
@@ -3502,7 +3622,7 @@ async function getAtCompletions(prefix, cwd) {
3502
3622
  for await (const file of glob.scan({ cwd, onlyFiles: true })) {
3503
3623
  if (file.includes("node_modules") || file.includes(".git"))
3504
3624
  continue;
3505
- results.push(`@${relative(cwd, join5(cwd, file))}`);
3625
+ results.push(`@${relative(cwd, join4(cwd, file))}`);
3506
3626
  if (results.length >= MAX)
3507
3627
  break;
3508
3628
  }
@@ -3599,7 +3719,7 @@ function deleteWordBackward(buf, cursor) {
3599
3719
  }
3600
3720
 
3601
3721
  // src/cli/input-images.ts
3602
- import { join as join6 } from "path";
3722
+ import { join as join5 } from "path";
3603
3723
 
3604
3724
  // src/cli/image-types.ts
3605
3725
  var IMAGE_EXTENSIONS = new Set([
@@ -3652,7 +3772,7 @@ async function tryExtractImageFromPaste(pasted, cwd, loadImage = loadImageFile)
3652
3772
  }
3653
3773
  }
3654
3774
  if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
3655
- const filePath = trimmed.startsWith("/") ? trimmed : join6(cwd, trimmed);
3775
+ const filePath = trimmed.startsWith("/") ? trimmed : join5(cwd, trimmed);
3656
3776
  const attachment = await loadImage(filePath);
3657
3777
  if (attachment) {
3658
3778
  const name = filePath.split("/").pop() ?? trimmed;
@@ -3685,11 +3805,17 @@ var CTRL_L = "\f";
3685
3805
  var CTRL_R = "\x12";
3686
3806
  var TAB = "\t";
3687
3807
  var _stdinReader = null;
3808
+ var _stdinGated = false;
3809
+ function setStdinGated(gated) {
3810
+ _stdinGated = gated;
3811
+ }
3688
3812
  function getStdinReader() {
3689
3813
  if (!_stdinReader) {
3690
3814
  const stream = new ReadableStream({
3691
3815
  start(controller) {
3692
3816
  process.stdin.on("data", (chunk) => {
3817
+ if (_stdinGated)
3818
+ return;
3693
3819
  try {
3694
3820
  controller.enqueue(new Uint8Array(chunk));
3695
3821
  } catch {}
@@ -3727,6 +3853,7 @@ function exitOnCtrlC(opts) {
3727
3853
  function watchForCancel(abortController) {
3728
3854
  if (!terminal.isTTY)
3729
3855
  return () => {};
3856
+ setStdinGated(true);
3730
3857
  const onCancel = () => {
3731
3858
  cleanup();
3732
3859
  abortController.abort();
@@ -3747,6 +3874,7 @@ function watchForCancel(abortController) {
3747
3874
  process.stdin.removeListener("data", onData);
3748
3875
  terminal.setRawMode(false);
3749
3876
  process.stdin.pause();
3877
+ setStdinGated(false);
3750
3878
  };
3751
3879
  terminal.setRawMode(true);
3752
3880
  process.stdin.resume();
@@ -3818,8 +3946,11 @@ async function readline(opts) {
3818
3946
  try {
3819
3947
  while (true) {
3820
3948
  const raw = await readKey(reader);
3821
- if (!raw)
3822
- continue;
3949
+ if (!raw) {
3950
+ process.stdout.write(`
3951
+ `);
3952
+ return { type: "eof" };
3953
+ }
3823
3954
  if (searchMode) {
3824
3955
  if (raw === ESC2) {
3825
3956
  searchMode = false;
@@ -4060,79 +4191,6 @@ import { createAnthropic } from "@ai-sdk/anthropic";
4060
4191
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
4061
4192
  import { createOpenAI } from "@ai-sdk/openai";
4062
4193
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
4063
-
4064
- // src/llm-api/api-log.ts
4065
- import { writeFileSync as writeFileSync2 } from "fs";
4066
- var writer2 = null;
4067
- var MAX_ENTRY_BYTES = 8 * 1024;
4068
- function initApiLog() {
4069
- if (writer2)
4070
- return;
4071
- const logPath = pidLogPath("api");
4072
- writeFileSync2(logPath, "");
4073
- writer2 = Bun.file(logPath).writer();
4074
- }
4075
- function isObject2(v) {
4076
- return typeof v === "object" && v !== null;
4077
- }
4078
- var LOG_DROP_KEYS = new Set([
4079
- "requestBodyValues",
4080
- "responseBody",
4081
- "responseHeaders",
4082
- "stack"
4083
- ]);
4084
- function sanitizeForLog(data) {
4085
- if (!isObject2(data))
4086
- return data;
4087
- const result = {};
4088
- for (const key in data) {
4089
- if (LOG_DROP_KEYS.has(key))
4090
- continue;
4091
- const value = data[key];
4092
- if (key === "errors" && Array.isArray(value)) {
4093
- result[key] = value.map((e) => sanitizeForLog(e));
4094
- } else if (key === "lastError" && isObject2(value)) {
4095
- result[key] = sanitizeForLog(value);
4096
- } else {
4097
- result[key] = value;
4098
- }
4099
- }
4100
- return result;
4101
- }
4102
- function isApiLogEnabled() {
4103
- return writer2 !== null;
4104
- }
4105
- function logApiEvent(event, data) {
4106
- if (!writer2)
4107
- return;
4108
- const timestamp = new Date().toISOString();
4109
- let entry = `[${timestamp}] ${event}
4110
- `;
4111
- if (data !== undefined) {
4112
- try {
4113
- const safe = sanitizeForLog(data);
4114
- let serialized = JSON.stringify(safe, null, 2);
4115
- if (serialized.length > MAX_ENTRY_BYTES) {
4116
- serialized = `${serialized.slice(0, MAX_ENTRY_BYTES)}
4117
- \u2026truncated`;
4118
- }
4119
- entry += serialized.split(`
4120
- `).map((line) => ` ${line}`).join(`
4121
- `);
4122
- entry += `
4123
- `;
4124
- } catch (err) {
4125
- entry += ` [Error stringifying data: ${String(err)}]
4126
- `;
4127
- }
4128
- }
4129
- entry += `---
4130
- `;
4131
- writer2.write(entry);
4132
- writer2.flush();
4133
- }
4134
-
4135
- // src/llm-api/providers.ts
4136
4194
  var SUPPORTED_PROVIDERS = [
4137
4195
  "zen",
4138
4196
  "anthropic",
@@ -4141,46 +4199,8 @@ var SUPPORTED_PROVIDERS = [
4141
4199
  "ollama"
4142
4200
  ];
4143
4201
  var ZEN_BASE2 = "https://opencode.ai/zen/v1";
4144
- var REDACTED_HEADERS = new Set(["authorization"]);
4145
- function redactHeaders(headers) {
4146
- if (!headers)
4147
- return;
4148
- const h = new Headers(headers);
4149
- const out = {};
4150
- h.forEach((v, k) => {
4151
- out[k] = REDACTED_HEADERS.has(k) ? "[REDACTED]" : v;
4152
- });
4153
- return out;
4154
- }
4155
- function createFetchWithLogging() {
4156
- const customFetch = async (input, init) => {
4157
- if (init?.body) {
4158
- try {
4159
- const bodyStr = init.body.toString();
4160
- const bodyJson = JSON.parse(bodyStr);
4161
- logApiEvent("Provider Request", {
4162
- url: input.toString(),
4163
- method: init.method,
4164
- headers: redactHeaders(init.headers),
4165
- body: bodyJson
4166
- });
4167
- } catch {
4168
- logApiEvent("Provider Request", {
4169
- url: input.toString(),
4170
- method: init.method,
4171
- headers: redactHeaders(init.headers),
4172
- body: init.body
4173
- });
4174
- }
4175
- }
4176
- return fetch(input, init);
4177
- };
4178
- return customFetch;
4179
- }
4180
- var fetchWithLogging = createFetchWithLogging();
4181
4202
  var OAUTH_STRIP_BETAS = new Set;
4182
4203
  function createOAuthFetch(accessToken) {
4183
- const baseFetch = createFetchWithLogging();
4184
4204
  const oauthFetch = async (input, init) => {
4185
4205
  let opts = init;
4186
4206
  if (opts?.headers) {
@@ -4196,7 +4216,7 @@ function createOAuthFetch(accessToken) {
4196
4216
  h.set("x-app", "cli");
4197
4217
  opts = { ...opts, headers: Object.fromEntries(h.entries()) };
4198
4218
  }
4199
- return baseFetch(input, opts);
4219
+ return fetch(input, opts);
4200
4220
  };
4201
4221
  return oauthFetch;
4202
4222
  }
@@ -4225,22 +4245,22 @@ function lazy(factory) {
4225
4245
  }
4226
4246
  var zenProviders = {
4227
4247
  anthropic: lazy(() => createAnthropic({
4228
- fetch: fetchWithLogging,
4248
+ fetch,
4229
4249
  apiKey: requireEnv("OPENCODE_API_KEY"),
4230
4250
  baseURL: ZEN_BASE2
4231
4251
  })),
4232
4252
  openai: lazy(() => createOpenAI({
4233
- fetch: fetchWithLogging,
4253
+ fetch,
4234
4254
  apiKey: requireEnv("OPENCODE_API_KEY"),
4235
4255
  baseURL: ZEN_BASE2
4236
4256
  })),
4237
4257
  google: lazy(() => createGoogleGenerativeAI({
4238
- fetch: fetchWithLogging,
4258
+ fetch,
4239
4259
  apiKey: requireEnv("OPENCODE_API_KEY"),
4240
4260
  baseURL: ZEN_BASE2
4241
4261
  })),
4242
4262
  compat: lazy(() => createOpenAICompatible({
4243
- fetch: fetchWithLogging,
4263
+ fetch,
4244
4264
  name: "zen-compat",
4245
4265
  apiKey: requireEnv("OPENCODE_API_KEY"),
4246
4266
  baseURL: ZEN_BASE2
@@ -4248,15 +4268,15 @@ var zenProviders = {
4248
4268
  };
4249
4269
  var directProviders = {
4250
4270
  anthropic: lazy(() => createAnthropic({
4251
- fetch: fetchWithLogging,
4271
+ fetch,
4252
4272
  apiKey: requireEnv("ANTHROPIC_API_KEY")
4253
4273
  })),
4254
4274
  openai: lazy(() => createOpenAI({
4255
- fetch: fetchWithLogging,
4275
+ fetch,
4256
4276
  apiKey: requireEnv("OPENAI_API_KEY")
4257
4277
  })),
4258
4278
  google: lazy(() => createGoogleGenerativeAI({
4259
- fetch: fetchWithLogging,
4279
+ fetch,
4260
4280
  apiKey: requireAnyEnv(["GOOGLE_API_KEY", "GEMINI_API_KEY"])
4261
4281
  })),
4262
4282
  ollama: lazy(() => {
@@ -4265,7 +4285,7 @@ var directProviders = {
4265
4285
  name: "ollama",
4266
4286
  baseURL: `${baseURL}/v1`,
4267
4287
  apiKey: "ollama",
4268
- fetch: fetchWithLogging
4288
+ fetch
4269
4289
  });
4270
4290
  })
4271
4291
  };
@@ -4699,6 +4719,11 @@ async function* mapFullStreamToTurnEvents(stream, opts) {
4699
4719
  const toolCallTracker = new StreamToolCallTracker;
4700
4720
  const textPhaseTracker = new StreamTextPhaseTracker;
4701
4721
  for await (const originalChunk of stream) {
4722
+ if (originalChunk.type === "start-step" && opts.stepPruneQueue) {
4723
+ for (const rec of opts.stepPruneQueue.splice(0)) {
4724
+ yield { type: "context-pruned", ...rec };
4725
+ }
4726
+ }
4702
4727
  const prepared = toolCallTracker.prepare(originalChunk);
4703
4728
  const chunk = prepared.chunk;
4704
4729
  const route = textPhaseTracker.route(chunk);
@@ -4713,9 +4738,6 @@ async function* mapFullStreamToTurnEvents(stream, opts) {
4713
4738
  }
4714
4739
  }
4715
4740
 
4716
- // src/llm-api/turn-request.ts
4717
- import { wrapLanguageModel } from "ai";
4718
-
4719
4741
  // src/llm-api/turn-context.ts
4720
4742
  import { pruneMessages } from "ai";
4721
4743
  var DEFAULT_TOOL_RESULT_PAYLOAD_CAP_BYTES = 16 * 1024;
@@ -4802,6 +4824,15 @@ function applyContextPruning(messages) {
4802
4824
  emptyMessages: "remove"
4803
4825
  });
4804
4826
  }
4827
+ function applyStepPruning(messages, initialMessageCount) {
4828
+ const newMessageCount = Math.max(0, messages.length - initialMessageCount);
4829
+ return pruneMessages({
4830
+ messages,
4831
+ reasoning: "none",
4832
+ toolCalls: `before-last-${40 + newMessageCount}-messages`,
4833
+ emptyMessages: "remove"
4834
+ });
4835
+ }
4805
4836
  function compactHeadTail(serialized, maxChars = 4096) {
4806
4837
  const chars = Math.max(512, maxChars);
4807
4838
  const headLength = Math.floor(chars / 2);
@@ -4873,6 +4904,22 @@ function compactToolResultPayloads(messages) {
4873
4904
  });
4874
4905
  return mutated ? compacted : messages;
4875
4906
  }
4907
+ function stripCacheBreakpoint(msg) {
4908
+ const anthropic = msg?.providerOptions?.anthropic;
4909
+ if (!anthropic?.cacheControl)
4910
+ return msg;
4911
+ const { cacheControl: _, ...rest } = anthropic;
4912
+ const hasOtherKeys = Object.keys(rest).length > 0;
4913
+ const { anthropic: __, ...otherProviders } = msg.providerOptions;
4914
+ const hasOtherProviders = Object.keys(otherProviders).length > 0;
4915
+ return {
4916
+ ...msg,
4917
+ providerOptions: {
4918
+ ...hasOtherProviders ? otherProviders : {},
4919
+ ...hasOtherKeys ? { anthropic: rest } : {}
4920
+ }
4921
+ };
4922
+ }
4876
4923
  function withCacheBreakpoint(msg) {
4877
4924
  return {
4878
4925
  ...msg,
@@ -4886,20 +4933,29 @@ function withCacheBreakpoint(msg) {
4886
4933
  };
4887
4934
  }
4888
4935
  function annotateAnthropicCacheBreakpoints(prompt) {
4889
- const result = [...prompt];
4890
- const systemIdxs = [];
4891
- const nonSystemIdxs = [];
4936
+ const result = prompt.map(stripCacheBreakpoint);
4937
+ let firstSystemIdx = -1;
4938
+ let lastUserIdx = -1;
4939
+ let lastNonSystemIdx = -1;
4892
4940
  for (let i = 0;i < result.length; i++) {
4893
- if (result[i]?.role === "system")
4894
- systemIdxs.push(i);
4895
- else
4896
- nonSystemIdxs.push(i);
4941
+ const role = result[i]?.role;
4942
+ if (role === "system") {
4943
+ if (firstSystemIdx === -1)
4944
+ firstSystemIdx = i;
4945
+ } else {
4946
+ lastNonSystemIdx = i;
4947
+ if (role === "user")
4948
+ lastUserIdx = i;
4949
+ }
4897
4950
  }
4898
- for (const idx of systemIdxs.slice(0, 1)) {
4899
- result[idx] = withCacheBreakpoint(result[idx]);
4951
+ if (firstSystemIdx >= 0) {
4952
+ result[firstSystemIdx] = withCacheBreakpoint(result[firstSystemIdx]);
4900
4953
  }
4901
- for (const idx of nonSystemIdxs.slice(-2)) {
4902
- result[idx] = withCacheBreakpoint(result[idx]);
4954
+ if (lastUserIdx >= 0) {
4955
+ result[lastUserIdx] = withCacheBreakpoint(result[lastUserIdx]);
4956
+ }
4957
+ if (lastNonSystemIdx >= 0 && lastNonSystemIdx !== lastUserIdx) {
4958
+ result[lastNonSystemIdx] = withCacheBreakpoint(result[lastNonSystemIdx]);
4903
4959
  }
4904
4960
  return result;
4905
4961
  }
@@ -4907,7 +4963,7 @@ function annotateAnthropicCacheBreakpoints(prompt) {
4907
4963
  // src/llm-api/turn-prepare-messages.ts
4908
4964
  function prepareTurnMessages(input) {
4909
4965
  const { messages, modelString, toolCount, systemPrompt } = input;
4910
- const apiLogOn = isApiLogEnabled();
4966
+ const apiLogOn = getLogContext() !== null;
4911
4967
  const strippedRuntimeToolFields = stripToolRuntimeInputFields(messages);
4912
4968
  if (strippedRuntimeToolFields !== messages && apiLogOn) {
4913
4969
  logApiEvent("runtime tool input fields stripped", { modelString });
@@ -4970,7 +5026,7 @@ function prepareTurnMessages(input) {
4970
5026
  }
4971
5027
  finalMessages = [...systemMessages, ...finalMessages];
4972
5028
  }
4973
- const wasPruned = postStats.messageCount < preStats.messageCount || postStats.totalBytes < preStats.totalBytes;
5029
+ const wasPruned = postStats.messageCount < preStats.messageCount;
4974
5030
  return {
4975
5031
  messages: finalMessages,
4976
5032
  systemPrompt: finalSystemPrompt,
@@ -4983,7 +5039,7 @@ function prepareTurnMessages(input) {
4983
5039
  }
4984
5040
 
4985
5041
  // src/llm-api/provider-options.ts
4986
- var ANTHROPIC_BUDGET = {
5042
+ var ANTHROPIC_ZEN_BUDGET = {
4987
5043
  low: 4096,
4988
5044
  medium: 8192,
4989
5045
  high: 16384,
@@ -5001,27 +5057,31 @@ function clampEffort(effort, max) {
5001
5057
  const maxIdx = ORDER.indexOf(max);
5002
5058
  return ORDER[Math.min(effortIdx, maxIdx)];
5003
5059
  }
5004
- function getAnthropicThinkingOptions(modelId, effort) {
5005
- const isAdaptive = /^claude-3-7/.test(modelId) || /^claude-sonnet-4/.test(modelId) || /^claude-opus-4/.test(modelId);
5006
- if (isAdaptive) {
5007
- const isOpus = /^claude-opus-4/.test(modelId);
5008
- const xhighMapping = isOpus ? "max" : "high";
5009
- const mapped = effort === "xhigh" ? xhighMapping : effort;
5010
- return { anthropic: { thinking: { type: "adaptive" }, effort: mapped } };
5060
+ function getAnthropicThinkingOptions(modelString, effort) {
5061
+ const { provider, modelId } = parseModelString(modelString);
5062
+ if (provider === "zen") {
5063
+ return {
5064
+ anthropic: {
5065
+ thinking: {
5066
+ type: "enabled",
5067
+ budgetTokens: ANTHROPIC_ZEN_BUDGET[effort]
5068
+ }
5069
+ }
5070
+ };
5011
5071
  }
5012
- return {
5013
- anthropic: {
5014
- thinking: { type: "enabled", budgetTokens: ANTHROPIC_BUDGET[effort] },
5015
- betas: ["interleaved-thinking-2025-05-14"]
5016
- }
5017
- };
5072
+ const isOpus = /^claude-opus-4/.test(modelId);
5073
+ const xhighMapping = isOpus ? "max" : "high";
5074
+ const mapped = effort === "xhigh" ? xhighMapping : effort;
5075
+ return { anthropic: { thinking: { type: "adaptive" }, effort: mapped } };
5018
5076
  }
5019
- function getOpenAIThinkingOptions(modelId, effort) {
5077
+ function getOpenAIThinkingOptions(modelString, effort) {
5078
+ const { modelId } = parseModelString(modelString);
5020
5079
  const supportsXhigh = /^gpt-5\.[2-9]/.test(modelId) || /^o4/.test(modelId);
5021
5080
  const clamped = supportsXhigh ? effort : clampEffort(effort, "high");
5022
5081
  return { openai: { reasoningEffort: clamped, reasoningSummary: "auto" } };
5023
5082
  }
5024
- function getGeminiThinkingOptions(modelId, effort) {
5083
+ function getGeminiThinkingOptions(modelString, effort) {
5084
+ const { modelId } = parseModelString(modelString);
5025
5085
  if (/^gemini-3/.test(modelId)) {
5026
5086
  return {
5027
5087
  google: {
@@ -5058,11 +5118,10 @@ var THINKING_STRATEGIES = [
5058
5118
  function getThinkingProviderOptions(modelString, effort) {
5059
5119
  if (!supportsThinking(modelString))
5060
5120
  return null;
5061
- const { modelId } = parseModelString(modelString);
5062
5121
  for (const strategy of THINKING_STRATEGIES) {
5063
5122
  if (!strategy.supports(modelString))
5064
5123
  continue;
5065
- return strategy.build(modelId, effort);
5124
+ return strategy.build(modelString, effort);
5066
5125
  }
5067
5126
  return null;
5068
5127
  }
@@ -5118,28 +5177,39 @@ function buildTurnPreparation(input) {
5118
5177
  }
5119
5178
  function buildStreamTextRequest(input) {
5120
5179
  const isAnthropic = isAnthropicModelFamily(input.modelString);
5121
- const model = wrapLanguageModel({
5122
- model: input.model,
5123
- middleware: [
5124
- {
5125
- specificationVersion: "v3",
5126
- transformParams: async ({ params }) => {
5127
- const prompt = params.prompt;
5128
- const pruned = applyContextPruning(prompt);
5129
- const compacted = compactToolResultPayloads(pruned);
5130
- const final = isAnthropic ? annotateAnthropicCacheBreakpoints(compacted) : compacted;
5131
- return { ...params, prompt: final };
5132
- }
5133
- }
5134
- ]
5135
- });
5180
+ const initialMessageCount = input.prepared.messages.length;
5136
5181
  return {
5137
- model,
5138
- maxOutputTokens: 16384,
5182
+ model: input.model,
5183
+ maxOutputTokens: getMaxOutputTokens(input.modelString) ?? 16384,
5139
5184
  messages: input.prepared.messages,
5140
5185
  tools: input.toolSet,
5141
5186
  stopWhen: continueUntilModelStops,
5142
5187
  onStepFinish: input.onStepFinish,
5188
+ prepareStep: ({ stepNumber, messages }) => {
5189
+ if (stepNumber === 0) {
5190
+ return isAnthropic ? {
5191
+ messages: annotateAnthropicCacheBreakpoints(messages)
5192
+ } : {};
5193
+ }
5194
+ const preCount = messages.length;
5195
+ const pruned = applyStepPruning(messages, initialMessageCount);
5196
+ const postCount = pruned.length;
5197
+ if (postCount < preCount) {
5198
+ const pre = getMessageStats(messages);
5199
+ const post = getMessageStats(pruned);
5200
+ input.stepPruneQueue.push({
5201
+ beforeMessageCount: pre.messageCount,
5202
+ afterMessageCount: post.messageCount,
5203
+ removedMessageCount: pre.messageCount - post.messageCount,
5204
+ beforeTotalBytes: pre.totalBytes,
5205
+ afterTotalBytes: post.totalBytes,
5206
+ removedBytes: pre.totalBytes - post.totalBytes
5207
+ });
5208
+ }
5209
+ const compacted = compactToolResultPayloads(pruned);
5210
+ const final = isAnthropic ? annotateAnthropicCacheBreakpoints(compacted) : compacted;
5211
+ return { messages: final };
5212
+ },
5143
5213
  ...input.prepared.systemPrompt ? { system: input.prepared.systemPrompt } : {},
5144
5214
  ...Object.keys(input.providerOptions).length > 0 ? {
5145
5215
  providerOptions: input.providerOptions
@@ -5196,11 +5266,7 @@ async function* runTurn(options) {
5196
5266
  removedBytes: prepared.prePruneTotalBytes - prepared.postPruneTotalBytes
5197
5267
  };
5198
5268
  }
5199
- if (isApiLogEnabled()) {
5200
- logApiEvent("prompt caching configured", {
5201
- cacheFamily: providerOptionsResult.cacheFamily
5202
- });
5203
- }
5269
+ const stepPruneQueue = [];
5204
5270
  const result = streamText(buildStreamTextRequest({
5205
5271
  model,
5206
5272
  modelString,
@@ -5208,19 +5274,21 @@ async function* runTurn(options) {
5208
5274
  toolSet,
5209
5275
  onStepFinish: turnState.onStepFinish,
5210
5276
  signal,
5211
- providerOptions: providerOptionsResult.providerOptions
5277
+ providerOptions: providerOptionsResult.providerOptions,
5278
+ stepPruneQueue
5212
5279
  }));
5213
5280
  result.response.catch(() => {});
5214
5281
  for await (const event of mapFullStreamToTurnEvents(result.fullStream, {
5282
+ stepPruneQueue,
5215
5283
  onChunk: (streamChunk) => {
5216
- logApiEvent("stream chunk", {
5217
- type: streamChunk.type,
5218
- toolCallId: streamChunk.toolCallId,
5219
- toolName: streamChunk.toolName,
5220
- isError: streamChunk.isError,
5221
- hasArgs: "args" in streamChunk || "input" in streamChunk,
5222
- hasOutput: "output" in streamChunk || "result" in streamChunk
5223
- });
5284
+ if (streamChunk.type === "tool-call" || streamChunk.type === "tool-result") {
5285
+ logApiEvent("stream chunk", {
5286
+ type: streamChunk.type,
5287
+ toolCallId: streamChunk.toolCallId,
5288
+ toolName: streamChunk.toolName,
5289
+ isError: streamChunk.isError
5290
+ });
5291
+ }
5224
5292
  }
5225
5293
  })) {
5226
5294
  yield event;
@@ -5245,7 +5313,10 @@ async function* runTurn(options) {
5245
5313
  yield {
5246
5314
  type: "turn-error",
5247
5315
  error: normalizedError,
5248
- partialMessages: finalState.partialMessages
5316
+ partialMessages: finalState.partialMessages,
5317
+ inputTokens: finalState.inputTokens,
5318
+ outputTokens: finalState.outputTokens,
5319
+ contextTokens: finalState.contextTokens
5249
5320
  };
5250
5321
  }
5251
5322
  }
@@ -5267,6 +5338,14 @@ function resumeSession(id) {
5267
5338
  function touchActiveSession(session) {
5268
5339
  touchSession(session.id, session.model);
5269
5340
  }
5341
+ function autoTitleSession(sessionId, userText) {
5342
+ const line = userText.split(`
5343
+ `)[0]?.trim() ?? "";
5344
+ if (!line)
5345
+ return;
5346
+ const title = line.length > 60 ? `${line.slice(0, 57)}...` : line;
5347
+ setSessionTitle(sessionId, title);
5348
+ }
5270
5349
  function renderSessionTable(footer) {
5271
5350
  const sessions = listSessions(20);
5272
5351
  if (sessions.length === 0)
@@ -5294,84 +5373,33 @@ function getMostRecentSession() {
5294
5373
  }
5295
5374
 
5296
5375
  // src/agent/system-prompt.ts
5297
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
5298
5376
  import { homedir as homedir5 } from "os";
5299
- import { dirname as dirname2, join as join7, resolve as resolve2 } from "path";
5300
- function tryReadFile(p) {
5301
- if (!existsSync4(p))
5302
- return null;
5303
- try {
5304
- return readFileSync2(p, "utf-8");
5305
- } catch {
5306
- return null;
5307
- }
5308
- }
5309
- function collectFiles(...paths) {
5310
- const parts = [];
5311
- for (const p of paths) {
5312
- const content = tryReadFile(p);
5313
- if (content)
5314
- parts.push(content);
5315
- }
5316
- return parts.length > 0 ? parts.join(`
5317
-
5318
- `) : null;
5319
- }
5320
- function loadGlobalContextFile(homeDir) {
5321
- return collectFiles(join7(homeDir, ".agents", "AGENTS.md"), join7(homeDir, ".agents", "CLAUDE.md"), join7(homeDir, ".claude", "CLAUDE.md"));
5322
- }
5323
- function loadContextFileAt(dir) {
5324
- return collectFiles(join7(dir, ".agents", "AGENTS.md"), join7(dir, ".agents", "CLAUDE.md"), join7(dir, ".claude", "CLAUDE.md"), join7(dir, "CLAUDE.md"), join7(dir, "AGENTS.md"));
5325
- }
5326
- function loadLocalContextFile(cwd) {
5327
- const start = resolve2(cwd);
5328
- let current = start;
5329
- while (true) {
5330
- const content = loadContextFileAt(current);
5331
- if (content)
5332
- return content;
5333
- if (existsSync4(join7(current, ".git")))
5334
- break;
5335
- const parent = dirname2(current);
5336
- if (parent === current)
5337
- break;
5338
- current = parent;
5339
- }
5340
- return null;
5341
- }
5342
5377
  var AUTONOMY = `
5343
5378
 
5344
- # Autonomy and persistence
5345
- - You are a capable senior engineer. Once given a direction, proactively gather context and implement \u2014 don't ask for permission to start.
5346
- - Carry changes through to implementation and verify they work. Don't stop halfway through a task without a good reason.
5347
- - Skip preamble. Don't output a plan before working \u2014 start using tools right away.
5348
- - Don't ask "shall I proceed?" or "shall I start?" at the beginning of a turn. Just begin.
5349
- - Do not guess unknown facts. Inspect files and web or run commands to find out. Don't make assumptions, verify.
5350
- - Avoid excessive looping: if you find yourself re-reading or re-editing the same files without clear progress, stop and summarise what's blocking you.`;
5379
+ # Autonomy
5380
+ - Begin work immediately using tools. Gather context, implement, and verify \u2014 do not ask for permission to start.
5381
+ - Carry changes through to completion. If blocked, summarise what is preventing progress instead of looping.
5382
+ - Verify facts by inspecting files or running commands \u2014 never guess unknown state.`;
5351
5383
  var SAFETY = `
5352
5384
 
5353
- # Safety and risk boundaries
5354
- - Never expose, print, or commit secrets/tokens/keys.
5355
- - Never invent URLs. Use URLs explicitly provided by the user or found in trusted project files/docs.
5356
- - For destructive or irreversible actions (for example deleting data or force-resetting git history), ask one targeted confirmation question before proceeding.`;
5357
- var WORKSPACE_GUARDRAILS = `
5358
-
5359
- # Workspace guardrails
5385
+ # Safety
5386
+ - Never expose, print, or commit secrets, tokens, or keys.
5387
+ - Never invent URLs \u2014 only use URLs the user provided or that exist in project files.
5360
5388
  - Never revert user-authored changes unless explicitly asked.
5361
- - If unexpected modifications appear in files you are actively editing, pause and ask how to proceed.
5362
- - Avoid destructive git commands unless explicitly requested. Prefer non-interactive git commands.`;
5363
- var STATUS_UPDATES = `
5364
-
5365
- # Progress communication
5366
- - Do not send ceremonial preambles.
5367
- - For long-running or multi-phase tasks, send brief progress updates every few tool batches (one sentence with the next concrete action).`;
5368
- var FINAL_MESSAGE = `
5369
-
5370
- # Final response style
5371
- - Default to non verbose, concise output (short bullets or a brief paragraph).
5372
- - For substantial code changes, state what changed, where, and why.
5373
- - Reference files with line numbers when helpful.
5389
+ - Before any destructive or irreversible action (deleting data, force-pushing, resetting history), ask one targeted confirmation question \u2014 mistakes here are unrecoverable.
5390
+ - If files you are editing change unexpectedly, pause and ask how to proceed.`;
5391
+ var COMMUNICATION = `
5392
+
5393
+ # Communication
5394
+ - Be concise: short bullets or a brief paragraph. No ceremonial preambles.
5395
+ - For long tasks, send a one-sentence progress update every 3-5 tool calls.
5396
+ - For code changes, state what changed, where, and why. Reference files with line numbers.
5374
5397
  - Do not paste large file contents unless asked.`;
5398
+ var ERROR_HANDLING = `
5399
+
5400
+ # Error handling
5401
+ - On tool failure: read the error, adjust your approach, and retry once. If it fails again, explain the blocker to the user.
5402
+ - If you find yourself re-reading or re-editing the same files without progress, stop and summarise what is blocking you.`;
5375
5403
  function buildSystemPrompt(sessionTimeAnchor, cwd, homeDir) {
5376
5404
  const globalContext = loadGlobalContextFile(homeDir ?? homedir5());
5377
5405
  const localContext = loadLocalContextFile(cwd);
@@ -5383,32 +5411,24 @@ Current working directory: ${cwdDisplay}
5383
5411
  Current date/time: ${sessionTimeAnchor}
5384
5412
 
5385
5413
  Guidelines:
5386
- - Be concise and precise. Avoid unnecessary preamble. Don't be verbose.
5387
- - Inspect code and files primarily through shell commands.
5388
- - Use temp files to handle large content, prefer scanning over full reads.
5389
- - Prefer small, targeted edits over large file rewrites.
5390
- - For file edits, use shell commands that invoke \`mc-edit\`.
5391
- - Use the skill tools only when you need to inspect available community/project skills or load one skill body.
5392
- - Make parallel tool calls when independent searches/lookups can happen concurrently.
5393
- - Keep your context clean and focused on the user request.
5394
-
5395
- # Preferred file workflow
5396
- - Use shell for repo inspection, verification, temp-file orchestration, and any non-edit file operation.
5397
- - \`mc-edit\` is available inside shell commands.
5398
- - \`mc-edit\` applies one exact-text edit and fails if the expected old text is missing or ambiguous.
5414
+ - You are a capable senior engineer. Proactively gather context and implement \u2014 work the problem, not just the symptom. Prefer root-cause fixes over patches.
5415
+ - Inspect code and files primarily through shell commands. Use temp files for large content to avoid filling your context window.
5416
+ - For file edits, invoke \`mc-edit\` via shell. Prefer small, targeted edits over full rewrites so diffs stay reviewable.
5417
+ - Make parallel tool calls when the lookups are independent \u2014 this speeds up multi-file investigation.
5418
+ - Before starting work, scan the skills list below. If there is even a small chance a skill applies to your task, load it with \`readSkill\` and follow its instructions before writing code or responding. Skills are mandatory when they match \u2014 not optional references.
5419
+ - Keep it simple: DRY, KISS, YAGNI. Avoid unnecessary complexity.
5399
5420
 
5400
- Usage: mc-edit <path> (--old <text> | --old-file <path>) [--new <text> | --new-file <path>] [--cwd <path>]
5401
- Outputs a diff of the changes and meta information.
5421
+ # File editing with mc-edit
5422
+ \`mc-edit\` applies one exact-text replacement per invocation. It fails deterministically if the old text is missing or matches more than once.
5402
5423
 
5403
- Apply one safe exact-text edit to an existing file.
5404
- - The expected old text must match exactly once.
5424
+ Usage: mc-edit <path> (--old <text> | --old-file <path>) [--new <text> | --new-file <path>] [--cwd <path>]
5405
5425
  - Omit --new / --new-file to delete the matched text.
5426
+ - To create new files, use shell commands (e.g. \`cat > file.txt << 'EOF'\\n...\\nEOF\`).
5406
5427
  `;
5407
5428
  prompt += AUTONOMY;
5408
5429
  prompt += SAFETY;
5409
- prompt += WORKSPACE_GUARDRAILS;
5410
- prompt += STATUS_UPDATES;
5411
- prompt += FINAL_MESSAGE;
5430
+ prompt += COMMUNICATION;
5431
+ prompt += ERROR_HANDLING;
5412
5432
  if (globalContext || localContext) {
5413
5433
  prompt += `
5414
5434
 
@@ -5428,15 +5448,29 @@ ${localContext}`;
5428
5448
  if (skills.length > 0) {
5429
5449
  prompt += `
5430
5450
 
5431
- # Available skills (metadata only)`;
5432
- prompt += "\nUse `listSkills` to browse and `readSkill` to load one SKILL.md on demand.";
5451
+ # Skills`;
5452
+ prompt += "\nSkills provide specialized instructions for specific tasks. When a task matches a skill description, call `readSkill` with that skill name before doing anything else \u2014 including asking clarifying questions. Check ALL skills against the current task, not just the first match. When a skill references relative paths, resolve them against the skill directory (parent of SKILL.md).";
5433
5453
  prompt += `
5434
- When a skill references relative paths, resolve them against the skill directory (parent of SKILL.md).`;
5435
- prompt += '\nFor complex skills that would clutter your context, consider delegating to a subagent via `mc "prompt"` in the shell tool.\n';
5454
+
5455
+ <available_skills>`;
5436
5456
  for (const skill of skills) {
5457
+ const compat = skill.compatibility ? `
5458
+ <compatibility>${skill.compatibility}</compatibility>` : "";
5459
+ prompt += `
5460
+ <skill>`;
5437
5461
  prompt += `
5438
- - ${skill.name}: ${skill.description} (${skill.source}, ${skill.filePath})`;
5462
+ <name>${skill.name}</name>`;
5463
+ prompt += `
5464
+ <description>${skill.description}</description>`;
5465
+ prompt += `
5466
+ <location>${skill.filePath}</location>`;
5467
+ prompt += `
5468
+ <source>${skill.source}</source>${compat}`;
5469
+ prompt += `
5470
+ </skill>`;
5439
5471
  }
5472
+ prompt += `
5473
+ </available_skills>`;
5440
5474
  }
5441
5475
  return prompt;
5442
5476
  }
@@ -5492,7 +5526,6 @@ class SessionRunner {
5492
5526
  model: this.currentModel,
5493
5527
  sessionId: this.session.id,
5494
5528
  thinkingEffort: this.currentThinkingEffort,
5495
- showReasoning: this.showReasoning,
5496
5529
  totalIn: this.totalIn,
5497
5530
  totalOut: this.totalOut,
5498
5531
  lastContextTokens: this.lastContextTokens
@@ -5589,30 +5622,38 @@ ${output}
5589
5622
  let lastAssistantText = "";
5590
5623
  try {
5591
5624
  this.reporter.startSpinner("thinking");
5592
- const events = runTurn({
5593
- model: llm,
5594
- modelString: this.currentModel,
5595
- messages: this.coreHistory,
5596
- tools: this.tools,
5597
- ...systemPrompt ? { systemPrompt } : {},
5598
- signal: abortController.signal,
5599
- ...this.currentThinkingEffort ? { thinkingEffort: this.currentThinkingEffort } : {}
5600
- });
5601
- const { inputTokens, outputTokens, contextTokens, newMessages } = await this.reporter.renderTurn(events, {
5602
- showReasoning: this.showReasoning,
5603
- verboseOutput: this.verboseOutput
5604
- });
5605
- const historyMessages = sanitizeModelAuthoredMessages(newMessages, this.currentModel);
5606
- if (historyMessages.length > 0) {
5607
- this.coreHistory.push(...historyMessages);
5608
- this.session.messages.push(...historyMessages);
5609
- saveMessages(this.session.id, historyMessages, thisTurn);
5625
+ const logsRepo = new LogsRepo(getDb());
5626
+ setLogContext({ sessionId: this.session.id, logsRepo });
5627
+ try {
5628
+ const events = runTurn({
5629
+ model: llm,
5630
+ modelString: this.currentModel,
5631
+ messages: this.coreHistory,
5632
+ tools: this.tools,
5633
+ ...systemPrompt ? { systemPrompt } : {},
5634
+ signal: abortController.signal,
5635
+ ...this.currentThinkingEffort ? { thinkingEffort: this.currentThinkingEffort } : {}
5636
+ });
5637
+ const { inputTokens, outputTokens, contextTokens, newMessages } = await this.reporter.renderTurn(events, {
5638
+ showReasoning: this.showReasoning,
5639
+ verboseOutput: this.verboseOutput
5640
+ });
5641
+ const historyMessages = sanitizeModelAuthoredMessages(newMessages, this.currentModel);
5642
+ if (historyMessages.length > 0) {
5643
+ this.coreHistory.push(...historyMessages);
5644
+ this.session.messages.push(...historyMessages);
5645
+ saveMessages(this.session.id, historyMessages, thisTurn);
5646
+ }
5647
+ lastAssistantText = extractAssistantText(historyMessages);
5648
+ this.totalIn += inputTokens;
5649
+ this.totalOut += outputTokens;
5650
+ this.lastContextTokens = contextTokens;
5651
+ touchActiveSession(this.session);
5652
+ autoTitleSession(this.session.id, text);
5653
+ this.coreHistory = compactToolResultPayloads(applyContextPruning(this.coreHistory));
5654
+ } finally {
5655
+ setLogContext(null);
5610
5656
  }
5611
- lastAssistantText = extractAssistantText(historyMessages);
5612
- this.totalIn += inputTokens;
5613
- this.totalOut += outputTokens;
5614
- this.lastContextTokens = contextTokens;
5615
- touchActiveSession(this.session);
5616
5657
  } catch (err) {
5617
5658
  const stubMsg = makeInterruptMessage("error");
5618
5659
  this.coreHistory.push(stubMsg);
@@ -5689,17 +5730,9 @@ var webContentTool = {
5689
5730
  import { z as z2 } from "zod";
5690
5731
 
5691
5732
  // src/internal/file-edit/command.ts
5692
- import { existsSync as existsSync5 } from "fs";
5693
- import { dirname as dirname3, extname, join as join8 } from "path";
5733
+ import { existsSync as existsSync4 } from "fs";
5734
+ import { dirname as dirname3, extname, join as join6 } from "path";
5694
5735
  import { fileURLToPath } from "url";
5695
-
5696
- // src/internal/runtime/script.ts
5697
- function resolveProcessScriptPath(mainModule, argv1) {
5698
- const script = mainModule && !mainModule.endsWith("/[eval]") && !mainModule.endsWith("\\[eval]") ? mainModule : argv1;
5699
- return script && /\.(?:[cm]?[jt]s)$/.test(script) ? script : null;
5700
- }
5701
-
5702
- // src/internal/file-edit/command.ts
5703
5736
  function quoteShellArg(value) {
5704
5737
  return `'${value.replaceAll("'", `'\\''`)}'`;
5705
5738
  }
@@ -5710,7 +5743,7 @@ function resolveSiblingFileEditScript(scriptPath) {
5710
5743
  const mainDir = dirname3(scriptPath);
5711
5744
  const mainBase = scriptPath.slice(mainDir.length + 1);
5712
5745
  if (mainBase === `index${ext}` || mainBase === `mc${ext}`) {
5713
- return join8(mainDir, `mc-edit${ext}`);
5746
+ return join6(mainDir, `mc-edit${ext}`);
5714
5747
  }
5715
5748
  return null;
5716
5749
  }
@@ -5719,8 +5752,12 @@ function resolveModuleLocalFileEditScript(moduleUrl) {
5719
5752
  const ext = extname(modulePath);
5720
5753
  if (!ext)
5721
5754
  return null;
5722
- const helperPath = join8(dirname3(modulePath), "..", "..", `mc-edit${ext}`);
5723
- return existsSync5(helperPath) ? helperPath : null;
5755
+ const helperPath = join6(dirname3(modulePath), "..", "..", `mc-edit${ext}`);
5756
+ return existsSync4(helperPath) ? helperPath : null;
5757
+ }
5758
+ function resolveProcessScriptPath(mainModule, argv1) {
5759
+ const script = mainModule && !mainModule.endsWith("/[eval]") && !mainModule.endsWith("\\[eval]") ? mainModule : argv1;
5760
+ return script && /\.(?:[cm]?[jt]s)$/.test(script) ? script : null;
5724
5761
  }
5725
5762
  function resolveFileEditCommand(execPath, mainModule, argv1, moduleUrl = import.meta.url) {
5726
5763
  const scriptPath = resolveProcessScriptPath(mainModule, argv1);
@@ -5797,7 +5834,9 @@ ${input.command}`], {
5797
5834
  if (!value)
5798
5835
  continue;
5799
5836
  if (totalBytes + value.length > MAX_OUTPUT_BYTES) {
5800
- chunks.push(value.slice(0, MAX_OUTPUT_BYTES - totalBytes));
5837
+ const partial = value.slice(0, MAX_OUTPUT_BYTES - totalBytes);
5838
+ const lastNl = partial.lastIndexOf(10);
5839
+ chunks.push(lastNl >= 0 ? partial.slice(0, lastNl + 1) : partial);
5801
5840
  truncated = true;
5802
5841
  reader.cancel().catch(() => {});
5803
5842
  break;
@@ -5864,7 +5903,8 @@ var listSkillsTool = {
5864
5903
  name: skill.name,
5865
5904
  description: skill.description,
5866
5905
  source: skill.source,
5867
- ...skill.context && { context: skill.context }
5906
+ ...skill.context && { context: skill.context },
5907
+ ...skill.compatibility && { compatibility: skill.compatibility }
5868
5908
  }));
5869
5909
  return { skills };
5870
5910
  }
@@ -6102,38 +6142,50 @@ ${c13.bold("Examples:")}`);
6102
6142
  }
6103
6143
 
6104
6144
  // src/cli/bootstrap.ts
6105
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
6145
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync } from "fs";
6106
6146
  import { homedir as homedir6 } from "os";
6107
- import { join as join9 } from "path";
6147
+ import { join as join7 } from "path";
6108
6148
  import * as c14 from "yoctocolors";
6109
6149
  var REVIEW_SKILL_CONTENT = `---
6110
6150
  name: review
6111
- description: Review recent changes for correctness, code quality, and performance
6151
+ description: "Review recent changes for correctness, code quality, and performance. Use when the user asks to review, check, or audit recent code changes, diffs, or pull requests."
6112
6152
  context: fork
6113
6153
  ---
6114
- You are a code reviewer. Review recent changes and provide actionable feedback.
6115
6154
 
6116
- Perform a sensible code review:
6117
- - Correctness: Are the changes in alignment with the goal?
6118
- - Code quality: Is there duplicate, dead, or bad code patterns introduced?
6119
- - Is the code performant?
6120
- - Never flag style choices as bugs, don't be a zealot.
6121
- - Never flag false positives, check before raising an issue.
6155
+ Review recent changes and provide actionable feedback.
6156
+
6157
+ ## Steps
6122
6158
 
6123
- Output a small summary with only the issues found. If nothing is wrong, say so.
6159
+ 1. Identify the changes to review \u2014 check \`git diff\`, \`git log\`, and staged files.
6160
+ 2. Read the changed files and understand the intent behind each change.
6161
+ 3. Evaluate each change against the criteria below.
6162
+ 4. Output a concise summary with only the issues found. If nothing is wrong, say so.
6163
+
6164
+ ## Review criteria
6165
+
6166
+ - **Correctness** \u2014 Are the changes aligned with their stated goal? Do they introduce bugs or regressions?
6167
+ - **Code quality** \u2014 Is there duplicate, dead, or overly complex code? Are abstractions appropriate?
6168
+ - **Performance** \u2014 Are there unnecessary allocations, redundant I/O, or algorithmic concerns?
6169
+ - **Edge cases** \u2014 Are boundary conditions and error paths handled?
6170
+
6171
+ ## Guidelines
6172
+
6173
+ - Never flag style choices as bugs \u2014 don't be a zealot.
6174
+ - Never flag false positives \u2014 verify before raising an issue.
6175
+ - Keep feedback actionable: say what's wrong and suggest a fix.
6124
6176
  `;
6125
6177
  function bootstrapGlobalDefaults() {
6126
- const skillDir = join9(homedir6(), ".agents", "skills", "review");
6127
- const skillPath = join9(skillDir, "SKILL.md");
6128
- if (!existsSync6(skillPath)) {
6129
- mkdirSync3(skillDir, { recursive: true });
6130
- writeFileSync3(skillPath, REVIEW_SKILL_CONTENT, "utf-8");
6178
+ const skillDir = join7(homedir6(), ".agents", "skills", "review");
6179
+ const skillPath = join7(skillDir, "SKILL.md");
6180
+ if (!existsSync5(skillPath)) {
6181
+ mkdirSync2(skillDir, { recursive: true });
6182
+ writeFileSync(skillPath, REVIEW_SKILL_CONTENT, "utf-8");
6131
6183
  writeln(`${c14.green("\u2713")} created ${c14.dim("~/.agents/skills/review/SKILL.md")} ${c14.dim("(edit it to customise your reviews)")}`);
6132
6184
  }
6133
6185
  }
6134
6186
 
6135
6187
  // src/cli/file-refs.ts
6136
- import { join as join10 } from "path";
6188
+ import { join as join8 } from "path";
6137
6189
  async function resolveFileRefs(text, cwd) {
6138
6190
  const atPattern = /@([\w./\-_]+)/g;
6139
6191
  let result = text;
@@ -6143,7 +6195,7 @@ async function resolveFileRefs(text, cwd) {
6143
6195
  const ref = match[1];
6144
6196
  if (!ref)
6145
6197
  continue;
6146
- const filePath = ref.startsWith("/") ? ref : join10(cwd, ref);
6198
+ const filePath = ref.startsWith("/") ? ref : join8(cwd, ref);
6147
6199
  if (isImageFilename(ref)) {
6148
6200
  const attachment = await loadImageFile(filePath);
6149
6201
  if (attachment) {
@@ -6154,14 +6206,9 @@ async function resolveFileRefs(text, cwd) {
6154
6206
  }
6155
6207
  try {
6156
6208
  const content = await Bun.file(filePath).text();
6157
- const lines = content.split(`
6158
- `);
6159
- const preview = lines.length > 200 ? `${lines.slice(0, 200).join(`
6160
- `)}
6161
- [truncated]` : content;
6162
6209
  const replacement = `\`${ref}\`:
6163
6210
  \`\`\`
6164
- ${preview}
6211
+ ${content}
6165
6212
  \`\`\``;
6166
6213
  result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
6167
6214
  } catch {}
@@ -6170,84 +6217,25 @@ ${preview}
6170
6217
  }
6171
6218
 
6172
6219
  // src/cli/input-loop.ts
6173
- import * as c22 from "yoctocolors";
6174
-
6175
- // src/cli/cli-helpers.ts
6176
- async function getGitBranch(cwd) {
6177
- try {
6178
- const proc = Bun.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
6179
- cwd,
6180
- stdout: "pipe",
6181
- stderr: "pipe"
6182
- });
6183
- const out = await new Response(proc.stdout).text();
6184
- const code = await proc.exited;
6185
- if (code !== 0)
6186
- return null;
6187
- return out.trim() || null;
6188
- } catch {
6189
- return null;
6190
- }
6191
- }
6220
+ import * as c21 from "yoctocolors";
6192
6221
 
6193
6222
  // src/cli/commands.ts
6194
6223
  import { randomBytes } from "crypto";
6195
- import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
6224
+ import { unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
6196
6225
  import { tmpdir } from "os";
6197
- import { join as join11 } from "path";
6198
- import * as c21 from "yoctocolors";
6199
-
6200
- // src/cli/commands-config.ts
6201
- import * as c15 from "yoctocolors";
6202
- function handleBooleanToggleCommand(opts) {
6203
- const mode = opts.args.trim().toLowerCase();
6204
- if (!mode) {
6205
- const nextValue = !opts.current;
6206
- opts.set(nextValue);
6207
- writeln(`${PREFIX.success} ${opts.label} ${nextValue ? c15.green("on") : c15.dim("off")}`);
6208
- return;
6209
- }
6210
- if (mode === "on") {
6211
- opts.set(true);
6212
- writeln(`${PREFIX.success} ${opts.label} ${c15.green("on")}`);
6213
- return;
6214
- }
6215
- if (mode === "off") {
6216
- opts.set(false);
6217
- writeln(`${PREFIX.success} ${opts.label} ${c15.dim("off")}`);
6218
- return;
6219
- }
6220
- writeln(`${PREFIX.error} usage: ${opts.usage}`);
6221
- }
6222
- function handleReasoningCommand(ctx, args) {
6223
- handleBooleanToggleCommand({
6224
- args,
6225
- current: ctx.showReasoning,
6226
- set: (value) => ctx.setShowReasoning(value),
6227
- label: "reasoning display",
6228
- usage: "/reasoning <on|off>"
6229
- });
6230
- }
6231
- function handleVerboseCommand(ctx, args) {
6232
- handleBooleanToggleCommand({
6233
- args,
6234
- current: ctx.verboseOutput,
6235
- set: (value) => ctx.setVerboseOutput(value),
6236
- label: "verbose output",
6237
- usage: "/verbose <on|off>"
6238
- });
6239
- }
6226
+ import { join as join9 } from "path";
6227
+ import * as c20 from "yoctocolors";
6240
6228
 
6241
6229
  // src/cli/commands-help.ts
6242
- import * as c16 from "yoctocolors";
6230
+ import * as c15 from "yoctocolors";
6243
6231
  function renderEntries(entries) {
6244
6232
  for (const [label, description] of entries) {
6245
- writeln(` ${c16.cyan(label.padEnd(28))} ${c16.dim(description)}`);
6233
+ writeln(` ${c15.cyan(label.padEnd(28))} ${c15.dim(description)}`);
6246
6234
  }
6247
6235
  }
6248
6236
  function renderHelpCommand(ctx) {
6249
6237
  writeln();
6250
- writeln(` ${c16.dim("session")}`);
6238
+ writeln(` ${c15.dim("session")}`);
6251
6239
  renderEntries([
6252
6240
  ["/session [id]", "list sessions or switch to one"],
6253
6241
  ["/new", "start a fresh session"],
@@ -6255,7 +6243,7 @@ function renderHelpCommand(ctx) {
6255
6243
  ["/exit", "quit"]
6256
6244
  ]);
6257
6245
  writeln();
6258
- writeln(` ${c16.dim("model + context")}`);
6246
+ writeln(` ${c15.dim("model + context")}`);
6259
6247
  renderEntries([
6260
6248
  ["/model [id]", "list or switch models"],
6261
6249
  ["/reasoning [on|off]", "toggle reasoning display"],
@@ -6268,7 +6256,7 @@ function renderHelpCommand(ctx) {
6268
6256
  ["/help", "show this help"]
6269
6257
  ]);
6270
6258
  writeln();
6271
- writeln(` ${c16.dim("prompt")}`);
6259
+ writeln(` ${c15.dim("prompt")}`);
6272
6260
  renderEntries([
6273
6261
  ["ask normally", "send a prompt to the current agent"],
6274
6262
  ["!cmd", "run a shell command and keep the result in context"],
@@ -6278,43 +6266,44 @@ function renderHelpCommand(ctx) {
6278
6266
  const skills = loadSkillsIndex(ctx.cwd);
6279
6267
  if (skills.size > 0) {
6280
6268
  writeln();
6281
- writeln(` ${c16.dim("skills")}`);
6269
+ writeln(` ${c15.dim("skills")}`);
6282
6270
  for (const skill of skills.values()) {
6283
- const source = skill.source === "local" ? c16.dim("local") : c16.dim("global");
6284
- writeln(` ${c16.green(`/${skill.name}`.padEnd(28))} ${c16.dim(skill.description)} ${c16.dim("\xB7")} ${source}`);
6271
+ const source = skill.source === "local" ? c15.dim("local") : c15.dim("global");
6272
+ const desc = truncateText(skill.description, 80);
6273
+ writeln(` ${c15.green(`/${skill.name}`.padEnd(28))} ${c15.dim(desc)} ${c15.dim("\xB7")} ${source}`);
6285
6274
  }
6286
6275
  }
6287
6276
  writeln();
6288
- writeln(` ${c16.dim("keys")} ${c16.dim("esc")} cancel response ${c16.dim("\xB7")} ${c16.dim("ctrl+c / ctrl+d")} exit ${c16.dim("\xB7")} ${c16.dim("ctrl+r")} history search ${c16.dim("\xB7")} ${c16.dim("\u2191\u2193")} history`);
6277
+ writeln(` ${c15.dim("keys")} ${c15.dim("esc")} cancel response ${c15.dim("\xB7")} ${c15.dim("ctrl+c / ctrl+d")} exit ${c15.dim("\xB7")} ${c15.dim("ctrl+r")} history search ${c15.dim("\xB7")} ${c15.dim("\u2191\u2193")} history`);
6289
6278
  writeln();
6290
6279
  }
6291
6280
 
6292
6281
  // src/cli/commands-login.ts
6293
- import * as c17 from "yoctocolors";
6282
+ import * as c16 from "yoctocolors";
6294
6283
  function renderLoginHelp() {
6295
6284
  writeln();
6296
- writeln(` ${c17.dim("usage:")}`);
6297
- writeln(` /login ${c17.dim("show login status")}`);
6298
- writeln(` /login <provider> ${c17.dim("login via OAuth")}`);
6299
- writeln(` /logout <provider> ${c17.dim("clear saved tokens")}`);
6285
+ writeln(` ${c16.dim("usage:")}`);
6286
+ writeln(` /login ${c16.dim("show login status")}`);
6287
+ writeln(` /login <provider> ${c16.dim("login via OAuth")}`);
6288
+ writeln(` /logout <provider> ${c16.dim("clear saved tokens")}`);
6300
6289
  writeln();
6301
- writeln(` ${c17.dim("providers:")}`);
6290
+ writeln(` ${c16.dim("providers:")}`);
6302
6291
  for (const p of getOAuthProviders()) {
6303
- const status = isLoggedIn(p.id) ? c17.green("logged in") : c17.dim("not logged in");
6304
- writeln(` ${c17.cyan(p.id.padEnd(20))} ${p.name} ${c17.dim("\xB7")} ${status}`);
6292
+ const status = isLoggedIn(p.id) ? c16.green("logged in") : c16.dim("not logged in");
6293
+ writeln(` ${c16.cyan(p.id.padEnd(20))} ${p.name} ${c16.dim("\xB7")} ${status}`);
6305
6294
  }
6306
6295
  writeln();
6307
6296
  }
6308
6297
  function renderStatus() {
6309
6298
  const loggedIn = listLoggedInProviders();
6310
6299
  if (loggedIn.length === 0) {
6311
- writeln(`${PREFIX.info} ${c17.dim("no OAuth logins \u2014 use")} /login <provider>`);
6300
+ writeln(`${PREFIX.info} ${c16.dim("no OAuth logins \u2014 use")} /login <provider>`);
6312
6301
  return;
6313
6302
  }
6314
6303
  for (const id of loggedIn) {
6315
6304
  const provider = getOAuthProvider(id);
6316
6305
  const name = provider?.name ?? id;
6317
- writeln(`${PREFIX.success} ${c17.cyan(id)} ${c17.dim(name)}`);
6306
+ writeln(`${PREFIX.success} ${c16.cyan(id)} ${c16.dim(name)}`);
6318
6307
  }
6319
6308
  }
6320
6309
  async function handleLoginCommand(ctx, args) {
@@ -6335,7 +6324,7 @@ async function handleLoginCommand(ctx, args) {
6335
6324
  if (isLoggedIn(providerId)) {
6336
6325
  const token = await getAccessToken(providerId);
6337
6326
  if (token) {
6338
- writeln(`${PREFIX.success} already logged in to ${c17.cyan(provider.name)}`);
6327
+ writeln(`${PREFIX.success} already logged in to ${c16.cyan(provider.name)}`);
6339
6328
  return;
6340
6329
  }
6341
6330
  }
@@ -6346,7 +6335,7 @@ async function handleLoginCommand(ctx, args) {
6346
6335
  ctx.stopSpinner();
6347
6336
  writeln(`${PREFIX.info} ${instructions}`);
6348
6337
  writeln();
6349
- writeln(` ${c17.cyan(url)}`);
6338
+ writeln(` ${c16.cyan(url)}`);
6350
6339
  writeln();
6351
6340
  let open = "xdg-open";
6352
6341
  if (process.platform === "darwin")
@@ -6358,12 +6347,12 @@ async function handleLoginCommand(ctx, args) {
6358
6347
  },
6359
6348
  onProgress: (msg) => {
6360
6349
  ctx.stopSpinner();
6361
- writeln(`${PREFIX.info} ${c17.dim(msg)}`);
6350
+ writeln(`${PREFIX.info} ${c16.dim(msg)}`);
6362
6351
  ctx.startSpinner("exchanging tokens");
6363
6352
  }
6364
6353
  });
6365
6354
  ctx.stopSpinner();
6366
- writeln(`${PREFIX.success} logged in to ${c17.cyan(provider.name)}`);
6355
+ writeln(`${PREFIX.success} logged in to ${c16.cyan(provider.name)}`);
6367
6356
  } catch (err) {
6368
6357
  ctx.stopSpinner();
6369
6358
  writeln(`${PREFIX.error} login failed: ${err.message}`);
@@ -6376,15 +6365,15 @@ function handleLogoutCommand(_ctx, args) {
6376
6365
  return;
6377
6366
  }
6378
6367
  if (!isLoggedIn(providerId)) {
6379
- writeln(`${PREFIX.info} ${c17.dim("not logged in to")} ${providerId}`);
6368
+ writeln(`${PREFIX.info} ${c16.dim("not logged in to")} ${providerId}`);
6380
6369
  return;
6381
6370
  }
6382
6371
  logout(providerId);
6383
- writeln(`${PREFIX.success} logged out of ${c17.cyan(providerId)}`);
6372
+ writeln(`${PREFIX.success} logged out of ${c16.cyan(providerId)}`);
6384
6373
  }
6385
6374
 
6386
6375
  // src/cli/commands-mcp.ts
6387
- import * as c18 from "yoctocolors";
6376
+ import * as c17 from "yoctocolors";
6388
6377
  async function handleMcpCommand(ctx, args) {
6389
6378
  const parts = args.trim().split(/\s+/);
6390
6379
  const sub = parts[0] ?? "list";
@@ -6392,15 +6381,15 @@ async function handleMcpCommand(ctx, args) {
6392
6381
  case "list": {
6393
6382
  const servers = listMcpServers();
6394
6383
  if (servers.length === 0) {
6395
- writeln(c18.dim(" no MCP servers configured"));
6396
- writeln(c18.dim(" /mcp add <name> http <url> \xB7 /mcp add <name> stdio <cmd> [args...]"));
6384
+ writeln(c17.dim(" no MCP servers configured"));
6385
+ writeln(c17.dim(" /mcp add <name> http <url> \xB7 /mcp add <name> stdio <cmd> [args...]"));
6397
6386
  return;
6398
6387
  }
6399
6388
  writeln();
6400
6389
  for (const s of servers) {
6401
6390
  const detailText = s.url ?? s.command ?? "";
6402
- const detail = detailText ? c18.dim(` ${detailText}`) : "";
6403
- writeln(` ${c18.yellow("\u2699")} ${c18.bold(s.name)} ${c18.dim(s.transport)}${detail}`);
6391
+ const detail = detailText ? c17.dim(` ${detailText}`) : "";
6392
+ writeln(` ${c17.yellow("\u2699")} ${c17.bold(s.name)} ${c17.dim(s.transport)}${detail}`);
6404
6393
  }
6405
6394
  return;
6406
6395
  }
@@ -6445,9 +6434,9 @@ async function handleMcpCommand(ctx, args) {
6445
6434
  }
6446
6435
  try {
6447
6436
  await ctx.connectMcpServer(name);
6448
- writeln(`${PREFIX.success} mcp server ${c18.cyan(name)} added and connected`);
6437
+ writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} added and connected`);
6449
6438
  } catch (e) {
6450
- writeln(`${PREFIX.success} mcp server ${c18.cyan(name)} saved ${c18.dim(`(connection failed: ${String(e)})`)}`);
6439
+ writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} saved ${c17.dim(`(connection failed: ${String(e)})`)}`);
6451
6440
  }
6452
6441
  return;
6453
6442
  }
@@ -6459,17 +6448,17 @@ async function handleMcpCommand(ctx, args) {
6459
6448
  return;
6460
6449
  }
6461
6450
  deleteMcpServer(name);
6462
- writeln(`${PREFIX.success} mcp server ${c18.cyan(name)} removed`);
6451
+ writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} removed`);
6463
6452
  return;
6464
6453
  }
6465
6454
  default:
6466
6455
  writeln(`${PREFIX.error} unknown: /mcp ${sub}`);
6467
- writeln(c18.dim(" subcommands: list \xB7 add \xB7 remove"));
6456
+ writeln(c17.dim(" subcommands: list \xB7 add \xB7 remove"));
6468
6457
  }
6469
6458
  }
6470
6459
 
6471
6460
  // src/cli/commands-model.ts
6472
- import * as c19 from "yoctocolors";
6461
+ import * as c18 from "yoctocolors";
6473
6462
  var THINKING_EFFORTS = ["low", "medium", "high", "xhigh"];
6474
6463
  function parseThinkingEffort(value) {
6475
6464
  return THINKING_EFFORTS.includes(value) ? value : null;
@@ -6488,21 +6477,21 @@ function renderModelUpdatedMessage(ctx, modelId, effortArg) {
6488
6477
  if (effortArg) {
6489
6478
  if (effortArg === "off") {
6490
6479
  ctx.setThinkingEffort(null);
6491
- writeln(`${PREFIX.success} model \u2192 ${c19.cyan(modelId)} ${c19.dim("(thinking disabled)")}`);
6480
+ writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)} ${c18.dim("(thinking disabled)")}`);
6492
6481
  return;
6493
6482
  }
6494
6483
  const effort = parseThinkingEffort(effortArg);
6495
6484
  if (effort) {
6496
6485
  ctx.setThinkingEffort(effort);
6497
- writeln(`${PREFIX.success} model \u2192 ${c19.cyan(modelId)} ${c19.dim(`(\u2726 ${effort})`)}`);
6486
+ writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)} ${c18.dim(`(\u2726 ${effort})`)}`);
6498
6487
  return;
6499
6488
  }
6500
- writeln(`${PREFIX.success} model \u2192 ${c19.cyan(modelId)}`);
6501
- writeln(`${PREFIX.error} unknown effort level ${c19.cyan(effortArg)} (use low, medium, high, xhigh, off)`);
6489
+ writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)}`);
6490
+ writeln(`${PREFIX.error} unknown effort level ${c18.cyan(effortArg)} (use low, medium, high, xhigh, off)`);
6502
6491
  return;
6503
6492
  }
6504
- const effortTag = ctx.thinkingEffort ? c19.dim(` (\u2726 ${ctx.thinkingEffort})`) : "";
6505
- writeln(`${PREFIX.success} model \u2192 ${c19.cyan(modelId)}${effortTag}`);
6493
+ const effortTag = ctx.thinkingEffort ? c18.dim(` (\u2726 ${ctx.thinkingEffort})`) : "";
6494
+ writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)}${effortTag}`);
6506
6495
  }
6507
6496
  async function handleModelSet(ctx, args) {
6508
6497
  const parts = args.trim().split(/\s+/).filter(Boolean);
@@ -6515,7 +6504,7 @@ async function handleModelSet(ctx, args) {
6515
6504
  const snapshot = await fetchAvailableModels();
6516
6505
  const match = findModelIdByAlias(idArg, snapshot.models.map((model) => model.id));
6517
6506
  if (!match) {
6518
- writeln(`${PREFIX.error} unknown model ${c19.cyan(idArg)} ${c19.dim("\u2014 run /models for the full list")}`);
6507
+ writeln(`${PREFIX.error} unknown model ${c18.cyan(idArg)} ${c18.dim("\u2014 run /models for the full list")}`);
6519
6508
  return;
6520
6509
  }
6521
6510
  modelId = match;
@@ -6535,7 +6524,7 @@ function handleModelEffort(ctx, effortArg) {
6535
6524
  return;
6536
6525
  }
6537
6526
  ctx.setThinkingEffort(effort);
6538
- writeln(`${PREFIX.success} thinking effort \u2192 ${c19.cyan(effort)}`);
6527
+ writeln(`${PREFIX.success} thinking effort \u2192 ${c18.cyan(effort)}`);
6539
6528
  }
6540
6529
  async function renderModelList(ctx) {
6541
6530
  ctx.startSpinner("fetching models");
@@ -6543,13 +6532,13 @@ async function renderModelList(ctx) {
6543
6532
  ctx.stopSpinner();
6544
6533
  if (snapshot.models.length === 0) {
6545
6534
  writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
6546
- writeln(c19.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
6535
+ writeln(c18.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
6547
6536
  return;
6548
6537
  }
6549
6538
  if (snapshot.stale) {
6550
6539
  const lastSync = snapshot.lastSyncAt ? new Date(snapshot.lastSyncAt).toLocaleString() : "never";
6551
6540
  const refreshTag = snapshot.refreshing ? " (refreshing in background)" : "";
6552
- writeln(c19.dim(` model metadata is stale (last sync: ${lastSync})${refreshTag}`));
6541
+ writeln(c18.dim(` model metadata is stale (last sync: ${lastSync})${refreshTag}`));
6553
6542
  }
6554
6543
  const modelsByProvider = new Map;
6555
6544
  for (const model of snapshot.models) {
@@ -6562,20 +6551,20 @@ async function renderModelList(ctx) {
6562
6551
  }
6563
6552
  writeln();
6564
6553
  for (const [provider, providerModels] of modelsByProvider) {
6565
- writeln(c19.bold(` ${provider}`));
6554
+ writeln(c18.bold(` ${provider}`));
6566
6555
  for (const model of providerModels) {
6567
6556
  const isCurrent = ctx.currentModel === model.id;
6568
- const freeTag = model.free ? c19.green(" free") : "";
6569
- const contextTag = model.context ? c19.dim(` ${Math.round(model.context / 1000)}k`) : "";
6570
- const effortTag = isCurrent && ctx.thinkingEffort ? c19.dim(` \u2726 ${ctx.thinkingEffort}`) : "";
6571
- const currentTag = isCurrent ? c19.cyan(" \u25C0") : "";
6572
- writeln(` ${c19.dim("\xB7")} ${model.displayName}${freeTag}${contextTag}${currentTag}${effortTag}`);
6573
- writeln(` ${c19.dim(model.id)}`);
6557
+ const freeTag = model.free ? c18.green(" free") : "";
6558
+ const contextTag = model.context ? c18.dim(` ${Math.round(model.context / 1000)}k`) : "";
6559
+ const effortTag = isCurrent && ctx.thinkingEffort ? c18.dim(` \u2726 ${ctx.thinkingEffort}`) : "";
6560
+ const currentTag = isCurrent ? c18.cyan(" \u25C0") : "";
6561
+ writeln(` ${c18.dim("\xB7")} ${model.displayName}${freeTag}${contextTag}${currentTag}${effortTag}`);
6562
+ writeln(` ${c18.dim(model.id)}`);
6574
6563
  }
6575
6564
  }
6576
6565
  writeln();
6577
- writeln(c19.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
6578
- writeln(c19.dim(" /model effort <low|medium|high|xhigh|off> to set thinking effort"));
6566
+ writeln(c18.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
6567
+ writeln(c18.dim(" /model effort <low|medium|high|xhigh|off> to set thinking effort"));
6579
6568
  }
6580
6569
  async function handleModelCommand(ctx, args) {
6581
6570
  const parts = args.trim().split(/\s+/).filter(Boolean);
@@ -6591,7 +6580,7 @@ async function handleModelCommand(ctx, args) {
6591
6580
  }
6592
6581
 
6593
6582
  // src/cli/commands-session.ts
6594
- import * as c20 from "yoctocolors";
6583
+ import * as c19 from "yoctocolors";
6595
6584
  function handleSessionCommand(ctx, args) {
6596
6585
  const id = args.trim();
6597
6586
  if (id) {
@@ -6599,15 +6588,15 @@ function handleSessionCommand(ctx, args) {
6599
6588
  const ok = ctx.switchSession(id);
6600
6589
  ctx.stopSpinner();
6601
6590
  if (ok) {
6602
- writeln(`${PREFIX.success} switched to session ${c20.cyan(id)} (${c20.cyan(ctx.currentModel)})`);
6591
+ writeln(`${PREFIX.success} switched to session ${c19.cyan(id)} (${c19.cyan(ctx.currentModel)})`);
6603
6592
  } else {
6604
- writeln(`${PREFIX.error} session ${c20.cyan(id)} not found`);
6593
+ writeln(`${PREFIX.error} session ${c19.cyan(id)} not found`);
6605
6594
  }
6606
6595
  return;
6607
6596
  }
6608
- const shown = renderSessionTable(`${c20.dim("Use")} /session <id> ${c20.dim("to switch to a session.")}`);
6597
+ const shown = renderSessionTable(`${c19.dim("Use")} /session <id> ${c19.dim("to switch to a session.")}`);
6609
6598
  if (!shown) {
6610
- writeln(`${PREFIX.info} ${c20.dim("no sessions found")}`);
6599
+ writeln(`${PREFIX.info} ${c19.dim("no sessions found")}`);
6611
6600
  }
6612
6601
  }
6613
6602
 
@@ -6617,9 +6606,9 @@ async function handleUndo(ctx) {
6617
6606
  try {
6618
6607
  const ok = await ctx.undoLastTurn();
6619
6608
  if (ok) {
6620
- writeln(`${PREFIX.success} ${c21.dim("last conversation turn removed")}`);
6609
+ writeln(`${PREFIX.success} ${c20.dim("last conversation turn removed")}`);
6621
6610
  } else {
6622
- writeln(`${PREFIX.info} ${c21.dim("nothing to undo")}`);
6611
+ writeln(`${PREFIX.info} ${c20.dim("nothing to undo")}`);
6623
6612
  }
6624
6613
  } finally {
6625
6614
  ctx.stopSpinner();
@@ -6677,7 +6666,7 @@ async function handleCommand(command, args, ctx) {
6677
6666
  if (loaded2) {
6678
6667
  const srcPath = skill.source === "local" ? `.agents/skills/${skill.name}/SKILL.md` : `~/.agents/skills/${skill.name}/SKILL.md`;
6679
6668
  if (skill.context === "fork") {
6680
- writeln(`${PREFIX.info} ${c21.cyan(skill.name)} ${c21.dim(`[${srcPath}] (forked subagent)`)}`);
6669
+ writeln(`${PREFIX.info} ${c20.cyan(skill.name)} ${c20.dim(`[${srcPath}] (forked subagent)`)}`);
6681
6670
  writeln();
6682
6671
  const subagentPrompt = args ? `${loaded2.content}
6683
6672
 
@@ -6685,7 +6674,7 @@ ${args}` : loaded2.content;
6685
6674
  const result = await runForkedSkill(skill.name, subagentPrompt, ctx.cwd);
6686
6675
  return { type: "inject-user-message", text: result };
6687
6676
  }
6688
- writeln(`${PREFIX.info} ${c21.cyan(skill.name)} ${c21.dim(`[${srcPath}]`)}`);
6677
+ writeln(`${PREFIX.info} ${c20.cyan(skill.name)} ${c20.dim(`[${srcPath}]`)}`);
6689
6678
  writeln();
6690
6679
  const prompt = args ? `${loaded2.content}
6691
6680
 
@@ -6693,17 +6682,16 @@ ${args}` : loaded2.content;
6693
6682
  return { type: "inject-user-message", text: prompt };
6694
6683
  }
6695
6684
  }
6696
- writeln(`${PREFIX.error} unknown: /${command} ${c21.dim("\u2014 /help for commands")}`);
6685
+ writeln(`${PREFIX.error} unknown: /${command} ${c20.dim("\u2014 /help for commands")}`);
6697
6686
  return { type: "unknown", command };
6698
6687
  }
6699
6688
  }
6700
6689
  }
6701
- var FORK_TIMEOUT_MS = 5 * 60 * 1000;
6702
6690
  async function runForkedSkill(skillName, prompt, cwd) {
6703
- const tmpFile = join11(tmpdir(), `mc-fork-${randomBytes(8).toString("hex")}.md`);
6704
- writeFileSync4(tmpFile, prompt, "utf8");
6691
+ const tmpFile = join9(tmpdir(), `mc-fork-${randomBytes(8).toString("hex")}.md`);
6692
+ writeFileSync2(tmpFile, prompt, "utf8");
6705
6693
  try {
6706
- writeln(`${PREFIX.info} ${c21.dim("running subagent\u2026")} ${c21.dim(`(timeout: ${FORK_TIMEOUT_MS / 1000}s)`)}`);
6694
+ writeln(`${PREFIX.info} ${c20.dim("running subagent\u2026")}`);
6707
6695
  const proc = Bun.spawn([process.execPath, Bun.main], {
6708
6696
  cwd,
6709
6697
  stdin: Bun.file(tmpFile),
@@ -6715,15 +6703,9 @@ async function runForkedSkill(skillName, prompt, cwd) {
6715
6703
  stdout: "pipe",
6716
6704
  stderr: "pipe"
6717
6705
  });
6718
- const timer = setTimeout(() => {
6719
- try {
6720
- proc.kill("SIGTERM");
6721
- } catch {}
6722
- }, FORK_TIMEOUT_MS);
6723
6706
  const stdout = await new Response(proc.stdout).text();
6724
6707
  const stderr = await new Response(proc.stderr).text();
6725
6708
  const exitCode = await proc.exited;
6726
- clearTimeout(timer);
6727
6709
  if (exitCode !== 0 && !stdout.trim()) {
6728
6710
  return `[Subagent skill "${skillName}" failed (exit ${exitCode})]
6729
6711
  ${stderr.trim()}`;
@@ -6734,41 +6716,45 @@ ${stderr.trim()}`;
6734
6716
  ${output}`;
6735
6717
  } finally {
6736
6718
  try {
6737
- unlinkSync3(tmpFile);
6719
+ unlinkSync2(tmpFile);
6738
6720
  } catch {}
6739
6721
  }
6740
6722
  }
6741
-
6742
- // src/cli/git-branch-cache.ts
6743
- function createGitBranchCache(options) {
6744
- const fetchBranch = options.fetchBranch ?? getGitBranch;
6745
- const now = options.now ?? Date.now;
6746
- const ttlMs = options.ttlMs ?? 5000;
6747
- let value = null;
6748
- let lastRefreshAt = 0;
6749
- let inFlight = null;
6750
- const refresh = async (force = false) => {
6751
- if (!force && now() - lastRefreshAt < ttlMs)
6752
- return;
6753
- if (inFlight) {
6754
- await inFlight;
6755
- return;
6756
- }
6757
- inFlight = (async () => {
6758
- value = await fetchBranch(options.cwd);
6759
- lastRefreshAt = now();
6760
- })().finally(() => {
6761
- inFlight = null;
6762
- });
6763
- await inFlight;
6764
- };
6765
- return {
6766
- get: () => value,
6767
- refresh,
6768
- refreshInBackground: (force = false) => {
6769
- refresh(force);
6770
- }
6771
- };
6723
+ function handleBooleanToggleCommand(opts) {
6724
+ const mode = opts.args.trim().toLowerCase();
6725
+ if (!mode) {
6726
+ writeln(`${PREFIX.success} ${opts.label} ${opts.current ? c20.green("on") : c20.dim("off")}`);
6727
+ return;
6728
+ }
6729
+ if (mode === "on") {
6730
+ opts.set(true);
6731
+ writeln(`${PREFIX.success} ${opts.label} ${c20.green("on")}`);
6732
+ return;
6733
+ }
6734
+ if (mode === "off") {
6735
+ opts.set(false);
6736
+ writeln(`${PREFIX.success} ${opts.label} ${c20.dim("off")}`);
6737
+ return;
6738
+ }
6739
+ writeln(`${PREFIX.error} usage: ${opts.usage}`);
6740
+ }
6741
+ function handleReasoningCommand(ctx, args) {
6742
+ handleBooleanToggleCommand({
6743
+ args,
6744
+ current: ctx.showReasoning,
6745
+ set: (value) => ctx.setShowReasoning(value),
6746
+ label: "reasoning display",
6747
+ usage: "/reasoning <on|off>"
6748
+ });
6749
+ }
6750
+ function handleVerboseCommand(ctx, args) {
6751
+ handleBooleanToggleCommand({
6752
+ args,
6753
+ current: ctx.verboseOutput,
6754
+ set: (value) => ctx.setVerboseOutput(value),
6755
+ label: "verbose output",
6756
+ usage: "/verbose <on|off>"
6757
+ });
6772
6758
  }
6773
6759
 
6774
6760
  // src/cli/input-loop.ts
@@ -6783,20 +6769,33 @@ ${result.stderr}`);
6783
6769
  sections.push("command timed out");
6784
6770
  if (!result.success)
6785
6771
  sections.push(`exit code: ${result.exitCode}`);
6772
+ if (sections.length === 0)
6773
+ sections.push(`(no output, exit ${result.exitCode})`);
6786
6774
  return sections.join(`
6787
6775
 
6788
6776
  `).trim();
6789
6777
  }
6778
+ async function getGitBranch(cwd) {
6779
+ try {
6780
+ const proc = Bun.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
6781
+ cwd,
6782
+ stdout: "pipe",
6783
+ stderr: "pipe"
6784
+ });
6785
+ const out = await new Response(proc.stdout).text();
6786
+ const code = await proc.exited;
6787
+ if (code !== 0)
6788
+ return null;
6789
+ return out.trim() || null;
6790
+ } catch {
6791
+ return null;
6792
+ }
6793
+ }
6790
6794
  async function runInputLoop(opts) {
6791
6795
  const { cwd, reporter, cmdCtx, runner } = opts;
6792
6796
  let lastStatusSignature = null;
6793
- const gitBranchCache = createGitBranchCache({
6794
- cwd,
6795
- fetchBranch: getGitBranch
6796
- });
6797
- await gitBranchCache.refresh(true);
6798
6797
  while (true) {
6799
- const branch = gitBranchCache.get();
6798
+ const branch = await getGitBranch(cwd);
6800
6799
  const status = runner.getStatusInfo();
6801
6800
  const cwdDisplay = tildePath(cwd);
6802
6801
  const contextWindow = getContextWindow(status.model);
@@ -6809,8 +6808,7 @@ async function runInputLoop(opts) {
6809
6808
  outputTokens: status.totalOut,
6810
6809
  contextTokens: status.lastContextTokens,
6811
6810
  contextWindow,
6812
- thinkingEffort: status.thinkingEffort,
6813
- showReasoning: status.showReasoning
6811
+ thinkingEffort: status.thinkingEffort
6814
6812
  };
6815
6813
  const statusSignature = buildStatusBarSignature(statusData);
6816
6814
  if (statusSignature !== lastStatusSignature) {
@@ -6825,15 +6823,14 @@ async function runInputLoop(opts) {
6825
6823
  }
6826
6824
  switch (input.type) {
6827
6825
  case "eof":
6828
- reporter.writeText(c22.dim("Goodbye."));
6826
+ reporter.writeText(c21.dim("Goodbye."));
6829
6827
  return;
6830
6828
  case "interrupt":
6831
- gitBranchCache.refreshInBackground();
6832
6829
  continue;
6833
6830
  case "command": {
6834
6831
  const result = await handleCommand(input.command, input.args, cmdCtx);
6835
6832
  if (result.type === "exit") {
6836
- reporter.writeText(c22.dim("Goodbye."));
6833
+ reporter.writeText(c21.dim("Goodbye."));
6837
6834
  return;
6838
6835
  }
6839
6836
  if (result.type === "inject-user-message") {
@@ -6843,7 +6840,6 @@ async function runInputLoop(opts) {
6843
6840
  await runner.processUserInput(resolvedText, refImages);
6844
6841
  } catch {}
6845
6842
  }
6846
- gitBranchCache.refreshInBackground();
6847
6843
  continue;
6848
6844
  }
6849
6845
  case "shell": {
@@ -6860,7 +6856,6 @@ async function runInputLoop(opts) {
6860
6856
  if (context) {
6861
6857
  runner.addShellContext(input.command, context);
6862
6858
  }
6863
- gitBranchCache.refreshInBackground(true);
6864
6859
  continue;
6865
6860
  }
6866
6861
  case "submit": {
@@ -6870,7 +6865,6 @@ async function runInputLoop(opts) {
6870
6865
  try {
6871
6866
  await runner.processUserInput(resolvedText, allImages);
6872
6867
  } catch {}
6873
- gitBranchCache.refreshInBackground();
6874
6868
  continue;
6875
6869
  }
6876
6870
  }
@@ -6906,9 +6900,6 @@ async function resolvePromptInput(promptArg, opts) {
6906
6900
 
6907
6901
  // src/index.ts
6908
6902
  registerTerminalCleanup();
6909
- initErrorLog();
6910
- initApiLog();
6911
- cleanStaleLogs();
6912
6903
  initModelInfoCache();
6913
6904
  pruneOldData();
6914
6905
  refreshModelInfoInBackground().catch(() => {});
@@ -6945,7 +6936,7 @@ async function main() {
6945
6936
  if (last) {
6946
6937
  sessionId = last.id;
6947
6938
  } else {
6948
- writeln(c23.dim("No previous session found, starting fresh."));
6939
+ writeln(c22.dim("No previous session found, starting fresh."));
6949
6940
  }
6950
6941
  } else if (args.sessionId) {
6951
6942
  sessionId = args.sessionId;
@@ -6968,7 +6959,7 @@ async function main() {
6968
6959
  const { text: resolvedText, images: refImages } = await resolveFileRefs(prompt, args.cwd);
6969
6960
  const responseText = await runner.processUserInput(resolvedText, refImages);
6970
6961
  if (responseText) {
6971
- writeln(responseText);
6962
+ writeln(responseText.trimStart());
6972
6963
  }
6973
6964
  return;
6974
6965
  }
@@ -6985,7 +6976,7 @@ async function main() {
6985
6976
  process.exit(1);
6986
6977
  }
6987
6978
  }
6988
- main().catch((err) => {
6979
+ main().then(() => process.exit(0), (err) => {
6989
6980
  if (!(err instanceof RenderedError)) {
6990
6981
  renderError(err, "main");
6991
6982
  }