reasonix 0.4.15 → 0.4.16

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/cli/index.js CHANGED
@@ -44,6 +44,21 @@ function saveApiKey(key, path = defaultConfigPath()) {
44
44
  cfg.apiKey = key.trim();
45
45
  writeConfig(cfg, path);
46
46
  }
47
+ function loadProjectShellAllowed(rootDir, path = defaultConfigPath()) {
48
+ const cfg = readConfig(path);
49
+ return cfg.projects?.[rootDir]?.shellAllowed ?? [];
50
+ }
51
+ function addProjectShellAllowed(rootDir, prefix, path = defaultConfigPath()) {
52
+ const trimmed = prefix.trim();
53
+ if (!trimmed) return;
54
+ const cfg = readConfig(path);
55
+ if (!cfg.projects) cfg.projects = {};
56
+ if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
57
+ const existing = cfg.projects[rootDir].shellAllowed ?? [];
58
+ if (existing.includes(trimmed)) return;
59
+ cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
60
+ writeConfig(cfg, path);
61
+ }
47
62
  function isPlausibleKey(key) {
48
63
  const trimmed = key.trim();
49
64
  return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
@@ -103,8 +118,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
103
118
  }
104
119
  function sleep(ms, signal) {
105
120
  if (ms <= 0) return Promise.resolve();
106
- return new Promise((resolve5, reject) => {
107
- const timer = setTimeout(resolve5, ms);
121
+ return new Promise((resolve6, reject) => {
122
+ const timer = setTimeout(resolve6, ms);
108
123
  if (signal) {
109
124
  const onAbort = () => {
110
125
  clearTimeout(timer);
@@ -1544,8 +1559,8 @@ var CacheFirstLoop = class {
1544
1559
  }
1545
1560
  );
1546
1561
  for (let k = 0; k < budget; k++) {
1547
- const sample = queue.shift() ?? await new Promise((resolve5) => {
1548
- waiter = resolve5;
1562
+ const sample = queue.shift() ?? await new Promise((resolve6) => {
1563
+ waiter = resolve6;
1549
1564
  });
1550
1565
  yield {
1551
1566
  turn: this._turn,
@@ -2239,6 +2254,221 @@ function lineDiff(a, b) {
2239
2254
  return out;
2240
2255
  }
2241
2256
 
2257
+ // src/tools/shell.ts
2258
+ import { spawn } from "child_process";
2259
+ import * as pathMod2 from "path";
2260
+ var DEFAULT_TIMEOUT_SEC = 60;
2261
+ var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
2262
+ var BUILTIN_ALLOWLIST = [
2263
+ // Repo inspection
2264
+ "git status",
2265
+ "git diff",
2266
+ "git log",
2267
+ "git show",
2268
+ "git blame",
2269
+ "git branch",
2270
+ "git remote",
2271
+ "git rev-parse",
2272
+ "git config --get",
2273
+ // Filesystem inspection
2274
+ "ls",
2275
+ "pwd",
2276
+ "cat",
2277
+ "head",
2278
+ "tail",
2279
+ "wc",
2280
+ "file",
2281
+ "tree",
2282
+ "find",
2283
+ "grep",
2284
+ "rg",
2285
+ // Language version probes
2286
+ "node --version",
2287
+ "node -v",
2288
+ "npm --version",
2289
+ "npx --version",
2290
+ "python --version",
2291
+ "python3 --version",
2292
+ "cargo --version",
2293
+ "go version",
2294
+ "rustc --version",
2295
+ "deno --version",
2296
+ "bun --version",
2297
+ // Test runners (non-destructive by convention)
2298
+ "npm test",
2299
+ "npm run test",
2300
+ "npx vitest run",
2301
+ "npx vitest",
2302
+ "npx jest",
2303
+ "pytest",
2304
+ "python -m pytest",
2305
+ "cargo test",
2306
+ "cargo check",
2307
+ "cargo clippy",
2308
+ "go test",
2309
+ "go vet",
2310
+ "deno test",
2311
+ "bun test",
2312
+ // Linters / typecheckers (read-only by convention)
2313
+ "npm run lint",
2314
+ "npm run typecheck",
2315
+ "npx tsc --noEmit",
2316
+ "npx biome check",
2317
+ "npx eslint",
2318
+ "npx prettier --check",
2319
+ "ruff",
2320
+ "mypy"
2321
+ ];
2322
+ function tokenizeCommand(cmd) {
2323
+ const out = [];
2324
+ let cur = "";
2325
+ let quote = null;
2326
+ for (let i = 0; i < cmd.length; i++) {
2327
+ const ch = cmd[i];
2328
+ if (quote) {
2329
+ if (ch === quote) {
2330
+ quote = null;
2331
+ } else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
2332
+ cur += cmd[++i];
2333
+ } else {
2334
+ cur += ch;
2335
+ }
2336
+ continue;
2337
+ }
2338
+ if (ch === '"' || ch === "'") {
2339
+ quote = ch;
2340
+ continue;
2341
+ }
2342
+ if (ch === " " || ch === " ") {
2343
+ if (cur.length > 0) {
2344
+ out.push(cur);
2345
+ cur = "";
2346
+ }
2347
+ continue;
2348
+ }
2349
+ cur += ch;
2350
+ }
2351
+ if (quote) throw new Error(`unclosed ${quote} in command`);
2352
+ if (cur.length > 0) out.push(cur);
2353
+ return out;
2354
+ }
2355
+ function isAllowed(cmd, extra = []) {
2356
+ const normalized = cmd.trim().replace(/\s+/g, " ");
2357
+ const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
2358
+ for (const prefix of allowlist) {
2359
+ if (normalized === prefix) return true;
2360
+ if (normalized.startsWith(`${prefix} `)) return true;
2361
+ }
2362
+ return false;
2363
+ }
2364
+ async function runCommand(cmd, opts) {
2365
+ const argv = tokenizeCommand(cmd);
2366
+ if (argv.length === 0) throw new Error("run_command: empty command");
2367
+ const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
2368
+ const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2369
+ const spawnOpts = {
2370
+ cwd: opts.cwd,
2371
+ shell: false,
2372
+ // no shell-expansion — see header comment
2373
+ windowsHide: true,
2374
+ env: process.env
2375
+ };
2376
+ return await new Promise((resolve6, reject) => {
2377
+ let child;
2378
+ try {
2379
+ child = spawn(argv[0], argv.slice(1), spawnOpts);
2380
+ } catch (err) {
2381
+ reject(err);
2382
+ return;
2383
+ }
2384
+ let buf = "";
2385
+ let timedOut = false;
2386
+ const killTimer = setTimeout(() => {
2387
+ timedOut = true;
2388
+ child.kill("SIGKILL");
2389
+ }, timeoutMs);
2390
+ const onAbort = () => child.kill("SIGKILL");
2391
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
2392
+ const onData = (chunk) => {
2393
+ buf += chunk.toString();
2394
+ if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
2395
+ };
2396
+ child.stdout?.on("data", onData);
2397
+ child.stderr?.on("data", onData);
2398
+ child.on("error", (err) => {
2399
+ clearTimeout(killTimer);
2400
+ opts.signal?.removeEventListener("abort", onAbort);
2401
+ reject(err);
2402
+ });
2403
+ child.on("close", (code) => {
2404
+ clearTimeout(killTimer);
2405
+ opts.signal?.removeEventListener("abort", onAbort);
2406
+ const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
2407
+
2408
+ [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
2409
+ resolve6({ exitCode: code, output, timedOut });
2410
+ });
2411
+ });
2412
+ }
2413
+ var NeedsConfirmationError = class extends Error {
2414
+ command;
2415
+ constructor(command) {
2416
+ super(
2417
+ `run_command: "${command}" needs the user's approval before it runs. STOP calling tools now \u2014 the TUI has already prompted the user to press y (run) or n (deny). Wait for their next message; it will either be the command's output (if they approved) or an instruction to continue without it (if they denied). Don't retry the command or call other shell commands in the meantime.`
2418
+ );
2419
+ this.name = "NeedsConfirmationError";
2420
+ this.command = command;
2421
+ }
2422
+ };
2423
+ function registerShellTools(registry, opts) {
2424
+ const rootDir = pathMod2.resolve(opts.rootDir);
2425
+ const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
2426
+ const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2427
+ const extraAllowed = opts.extraAllowed ?? [];
2428
+ const allowAll = opts.allowAll ?? false;
2429
+ registry.register({
2430
+ name: "run_command",
2431
+ description: "Run a shell command in the project root and return its combined stdout+stderr. Read-only and test commands (git status, ls, npm test, pytest, cargo test, grep, etc.) run immediately. Anything that could mutate state (npm install, git commit, rm, chmod) is refused and the user has to confirm in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
2432
+ parameters: {
2433
+ type: "object",
2434
+ properties: {
2435
+ command: {
2436
+ type: "string",
2437
+ description: "Full command line, e.g. 'npm test' or 'git diff src/foo.ts'. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
2438
+ },
2439
+ timeoutSec: {
2440
+ type: "integer",
2441
+ description: `Override the default ${timeoutSec}s timeout for a single command.`
2442
+ }
2443
+ },
2444
+ required: ["command"]
2445
+ },
2446
+ fn: async (args, ctx) => {
2447
+ const cmd = args.command.trim();
2448
+ if (!cmd) throw new Error("run_command: empty command");
2449
+ if (!allowAll && !isAllowed(cmd, extraAllowed)) {
2450
+ throw new NeedsConfirmationError(cmd);
2451
+ }
2452
+ const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
2453
+ const result = await runCommand(cmd, {
2454
+ cwd: rootDir,
2455
+ timeoutSec: effectiveTimeout,
2456
+ maxOutputChars,
2457
+ signal: ctx?.signal
2458
+ });
2459
+ return formatCommandResult(cmd, result);
2460
+ }
2461
+ });
2462
+ return registry;
2463
+ }
2464
+ function formatCommandResult(cmd, r) {
2465
+ const header = r.timedOut ? `$ ${cmd}
2466
+ [killed after timeout]` : `$ ${cmd}
2467
+ [exit ${r.exitCode ?? "?"}]`;
2468
+ return r.output ? `${header}
2469
+ ${r.output}` : header;
2470
+ }
2471
+
2242
2472
  // src/tools/web.ts
2243
2473
  var DEFAULT_FETCH_MAX_CHARS = 32e3;
2244
2474
  var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
@@ -2422,11 +2652,11 @@ ${i + 1}. ${r.title}`);
2422
2652
 
2423
2653
  // src/env.ts
2424
2654
  import { readFileSync as readFileSync3 } from "fs";
2425
- import { resolve as resolve2 } from "path";
2655
+ import { resolve as resolve3 } from "path";
2426
2656
  function loadDotenv(path = ".env") {
2427
2657
  let raw;
2428
2658
  try {
2429
- raw = readFileSync3(resolve2(process.cwd(), path), "utf8");
2659
+ raw = readFileSync3(resolve3(process.cwd(), path), "utf8");
2430
2660
  } catch {
2431
2661
  return;
2432
2662
  }
@@ -3139,7 +3369,7 @@ var McpClient = class {
3139
3369
  const id = this.nextId++;
3140
3370
  const frame = { jsonrpc: "2.0", id, method, params };
3141
3371
  let abortHandler = null;
3142
- const promise = new Promise((resolve5, reject) => {
3372
+ const promise = new Promise((resolve6, reject) => {
3143
3373
  const timeout = setTimeout(() => {
3144
3374
  this.pending.delete(id);
3145
3375
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -3148,7 +3378,7 @@ var McpClient = class {
3148
3378
  );
3149
3379
  }, this.requestTimeoutMs);
3150
3380
  this.pending.set(id, {
3151
- resolve: resolve5,
3381
+ resolve: resolve6,
3152
3382
  reject,
3153
3383
  timeout
3154
3384
  });
@@ -3230,7 +3460,7 @@ var McpClient = class {
3230
3460
  };
3231
3461
 
3232
3462
  // src/mcp/stdio.ts
3233
- import { spawn } from "child_process";
3463
+ import { spawn as spawn2 } from "child_process";
3234
3464
  var StdioTransport = class {
3235
3465
  child;
3236
3466
  queue = [];
@@ -3245,14 +3475,14 @@ var StdioTransport = class {
3245
3475
  opts.command,
3246
3476
  ...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
3247
3477
  ].join(" ");
3248
- this.child = spawn(line, [], {
3478
+ this.child = spawn2(line, [], {
3249
3479
  env,
3250
3480
  cwd: opts.cwd,
3251
3481
  stdio: ["pipe", "pipe", "inherit"],
3252
3482
  shell: true
3253
3483
  });
3254
3484
  } else {
3255
- this.child = spawn(opts.command, opts.args ?? [], {
3485
+ this.child = spawn2(opts.command, opts.args ?? [], {
3256
3486
  env,
3257
3487
  cwd: opts.cwd,
3258
3488
  stdio: ["pipe", "pipe", "inherit"]
@@ -3271,12 +3501,12 @@ var StdioTransport = class {
3271
3501
  }
3272
3502
  async send(message) {
3273
3503
  if (this.closed) throw new Error("MCP transport is closed");
3274
- return new Promise((resolve5, reject) => {
3504
+ return new Promise((resolve6, reject) => {
3275
3505
  const line = `${JSON.stringify(message)}
3276
3506
  `;
3277
3507
  this.child.stdin.write(line, "utf8", (err) => {
3278
3508
  if (err) reject(err);
3279
- else resolve5();
3509
+ else resolve6();
3280
3510
  });
3281
3511
  });
3282
3512
  }
@@ -3287,8 +3517,8 @@ var StdioTransport = class {
3287
3517
  continue;
3288
3518
  }
3289
3519
  if (this.closed) return;
3290
- const next = await new Promise((resolve5) => {
3291
- this.waiters.push(resolve5);
3520
+ const next = await new Promise((resolve6) => {
3521
+ this.waiters.push(resolve6);
3292
3522
  });
3293
3523
  if (next === null) return;
3294
3524
  yield next;
@@ -3354,8 +3584,8 @@ var SseTransport = class {
3354
3584
  constructor(opts) {
3355
3585
  this.url = opts.url;
3356
3586
  this.headers = opts.headers ?? {};
3357
- this.endpointReady = new Promise((resolve5, reject) => {
3358
- this.resolveEndpoint = resolve5;
3587
+ this.endpointReady = new Promise((resolve6, reject) => {
3588
+ this.resolveEndpoint = resolve6;
3359
3589
  this.rejectEndpoint = reject;
3360
3590
  });
3361
3591
  this.endpointReady.catch(() => void 0);
@@ -3382,8 +3612,8 @@ var SseTransport = class {
3382
3612
  continue;
3383
3613
  }
3384
3614
  if (this.closed) return;
3385
- const next = await new Promise((resolve5) => {
3386
- this.waiters.push(resolve5);
3615
+ const next = await new Promise((resolve6) => {
3616
+ this.waiters.push(resolve6);
3387
3617
  });
3388
3618
  if (next === null) return;
3389
3619
  yield next;
@@ -3583,7 +3813,7 @@ async function trySection(load) {
3583
3813
 
3584
3814
  // src/code/edit-blocks.ts
3585
3815
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
3586
- import { dirname as dirname4, resolve as resolve3 } from "path";
3816
+ import { dirname as dirname4, resolve as resolve4 } from "path";
3587
3817
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
3588
3818
  function parseEditBlocks(text) {
3589
3819
  const out = [];
@@ -3601,8 +3831,8 @@ function parseEditBlocks(text) {
3601
3831
  return out;
3602
3832
  }
3603
3833
  function applyEditBlock(block, rootDir) {
3604
- const absRoot = resolve3(rootDir);
3605
- const absTarget = resolve3(absRoot, block.path);
3834
+ const absRoot = resolve4(rootDir);
3835
+ const absTarget = resolve4(absRoot, block.path);
3606
3836
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
3607
3837
  return {
3608
3838
  path: block.path,
@@ -3652,13 +3882,13 @@ function applyEditBlocks(blocks, rootDir) {
3652
3882
  return blocks.map((b) => applyEditBlock(b, rootDir));
3653
3883
  }
3654
3884
  function snapshotBeforeEdits(blocks, rootDir) {
3655
- const absRoot = resolve3(rootDir);
3885
+ const absRoot = resolve4(rootDir);
3656
3886
  const seen = /* @__PURE__ */ new Set();
3657
3887
  const snapshots = [];
3658
3888
  for (const b of blocks) {
3659
3889
  if (seen.has(b.path)) continue;
3660
3890
  seen.add(b.path);
3661
- const abs = resolve3(absRoot, b.path);
3891
+ const abs = resolve4(absRoot, b.path);
3662
3892
  if (!existsSync2(abs)) {
3663
3893
  snapshots.push({ path: b.path, prevContent: null });
3664
3894
  continue;
@@ -3672,9 +3902,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
3672
3902
  return snapshots;
3673
3903
  }
3674
3904
  function restoreSnapshots(snapshots, rootDir) {
3675
- const absRoot = resolve3(rootDir);
3905
+ const absRoot = resolve4(rootDir);
3676
3906
  return snapshots.map((snap) => {
3677
- const abs = resolve3(absRoot, snap.path);
3907
+ const abs = resolve4(absRoot, snap.path);
3678
3908
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
3679
3909
  return {
3680
3910
  path: snap.path,
@@ -3707,15 +3937,16 @@ function sep() {
3707
3937
  }
3708
3938
 
3709
3939
  // src/index.ts
3710
- var VERSION = "0.4.15";
3940
+ var VERSION = "0.4.16";
3711
3941
 
3712
3942
  // src/cli/commands/chat.tsx
3943
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
3713
3944
  import { render } from "ink";
3714
- import React10, { useState as useState5 } from "react";
3945
+ import React13, { useState as useState6 } from "react";
3715
3946
 
3716
3947
  // src/cli/ui/App.tsx
3717
- import { Box as Box7, Static, Text as Text7, useApp, useInput as useInput2 } from "ink";
3718
- import React8, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState3 } from "react";
3948
+ import { Box as Box9, Static, Text as Text9, useApp, useInput as useInput3 } from "ink";
3949
+ import React10, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
3719
3950
 
3720
3951
  // src/cli/ui/EventLog.tsx
3721
3952
  import { Box as Box3, Text as Text3 } from "ink";
@@ -3788,7 +4019,7 @@ function stripMath(s) {
3788
4019
  (_m, n, k) => `C(${n.trim()},${k.trim()})`
3789
4020
  ).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in\b/g, "\u2208").replace(/\\notin\b/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum\b/g, "\u03A3").replace(/\\prod\b/g, "\u03A0").replace(/\\int\b/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies\b/g, "\u21D2").replace(/\\iff\b/g, "\u21D4").replace(/\\to\b/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
3790
4021
  }
3791
- var INLINE_RE = /(\*\*([^*\n]+?)\*\*|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
4022
+ var INLINE_RE = /(\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
3792
4023
  function InlineMd({ text }) {
3793
4024
  const parts = [];
3794
4025
  let last = 0;
@@ -3803,12 +4034,17 @@ function InlineMd({ text }) {
3803
4034
  /* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[2])
3804
4035
  );
3805
4036
  } else if (m[3] !== void 0) {
4037
+ const stripped = m[3].replace(/^(\w+)\s+/, "");
3806
4038
  parts.push(
3807
- /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[3])
4039
+ /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, stripped)
3808
4040
  );
3809
4041
  } else if (m[4] !== void 0) {
3810
4042
  parts.push(
3811
- /* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[4])
4043
+ /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[4])
4044
+ );
4045
+ } else if (m[5] !== void 0) {
4046
+ parts.push(
4047
+ /* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[5])
3812
4048
  );
3813
4049
  }
3814
4050
  last = start + m[0].length;
@@ -3826,6 +4062,7 @@ function parseBlocks(raw) {
3826
4062
  let codeLang = "";
3827
4063
  let codeBuf = [];
3828
4064
  let listBuf = null;
4065
+ let codeFence = "";
3829
4066
  const flushPara = () => {
3830
4067
  if (para.length) {
3831
4068
  out.push({ kind: "paragraph", text: para.join(" ") });
@@ -3871,22 +4108,37 @@ function parseBlocks(raw) {
3871
4108
  para.push(filename);
3872
4109
  }
3873
4110
  }
3874
- const fence = line.match(/^```(\w*)/);
3875
- if (fence) {
3876
- if (inCode) {
4111
+ if (!inCode) {
4112
+ const open = line.match(/^ {0,3}(`{3,})(\w*)\s*(.*)$/);
4113
+ if (open) {
4114
+ const fence = open[1];
4115
+ const lang = open[2] ?? "";
4116
+ const rest = open[3] ?? "";
4117
+ const closeOnSame = rest.match(new RegExp(`^(.*?)${fence}\\s*$`));
4118
+ if (closeOnSame) {
4119
+ flushPara();
4120
+ flushList();
4121
+ out.push({ kind: "code", lang, text: (closeOnSame[1] ?? "").trim() });
4122
+ continue;
4123
+ }
4124
+ flushPara();
4125
+ flushList();
4126
+ inCode = true;
4127
+ codeLang = lang;
4128
+ codeFence = fence;
4129
+ if (rest.length > 0) codeBuf.push(rest);
4130
+ continue;
4131
+ }
4132
+ } else {
4133
+ const close = line.match(/^ {0,3}(`{3,})\s*$/);
4134
+ if (close && close[1].length >= codeFence.length) {
3877
4135
  out.push({ kind: "code", lang: codeLang, text: codeBuf.join("\n") });
3878
4136
  codeBuf = [];
3879
4137
  codeLang = "";
4138
+ codeFence = "";
3880
4139
  inCode = false;
3881
- } else {
3882
- flushPara();
3883
- flushList();
3884
- inCode = true;
3885
- codeLang = fence[1] ?? "";
4140
+ continue;
3886
4141
  }
3887
- continue;
3888
- }
3889
- if (inCode) {
3890
4142
  codeBuf.push(rawLine);
3891
4143
  continue;
3892
4144
  }
@@ -4214,7 +4466,7 @@ function processMultilineKey(value, cursor, key) {
4214
4466
  }
4215
4467
  return { next: null, cursor: null, submit: true, submitValue: value };
4216
4468
  }
4217
- if (key.backspace) {
4469
+ if (key.backspace || key.delete || key.input === "\x7F" || key.input === "\b") {
4218
4470
  if (cursor === 0) return NOOP;
4219
4471
  return {
4220
4472
  next: value.slice(0, cursor - 1) + value.slice(cursor),
@@ -4222,14 +4474,6 @@ function processMultilineKey(value, cursor, key) {
4222
4474
  submit: false
4223
4475
  };
4224
4476
  }
4225
- if (key.delete) {
4226
- if (cursor === value.length) return NOOP;
4227
- return {
4228
- next: value.slice(0, cursor) + value.slice(cursor + 1),
4229
- cursor,
4230
- submit: false
4231
- };
4232
- }
4233
4477
  if ((key.ctrl || key.meta) && key.input.length === 0) return NOOP;
4234
4478
  if (key.ctrl || key.meta) return NOOP;
4235
4479
  if (key.input.length > 0) {
@@ -4371,16 +4615,177 @@ function LineWithCursor({
4371
4615
  return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { inverse: showCursor }, atCursor), /* @__PURE__ */ React5.createElement(Text4, null, after));
4372
4616
  }
4373
4617
 
4618
+ // src/cli/ui/ShellConfirm.tsx
4619
+ import { Box as Box6, Text as Text6 } from "ink";
4620
+ import React7 from "react";
4621
+
4622
+ // src/cli/ui/Select.tsx
4623
+ import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
4624
+ import React6, { useState as useState3 } from "react";
4625
+ function SingleSelect({
4626
+ items,
4627
+ initialValue,
4628
+ onSubmit,
4629
+ onCancel
4630
+ }) {
4631
+ const initialIndex = Math.max(
4632
+ 0,
4633
+ items.findIndex((i) => i.value === initialValue && !i.disabled)
4634
+ );
4635
+ const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
4636
+ useInput2((_input, key) => {
4637
+ if (key.upArrow) {
4638
+ setIndex((i) => findNextEnabled(items, i, -1));
4639
+ } else if (key.downArrow) {
4640
+ setIndex((i) => findNextEnabled(items, i, 1));
4641
+ } else if (key.return) {
4642
+ const chosen = items[index];
4643
+ if (chosen && !chosen.disabled) onSubmit(chosen.value);
4644
+ } else if (key.escape && onCancel) {
4645
+ onCancel();
4646
+ }
4647
+ });
4648
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
4649
+ SelectRow,
4650
+ {
4651
+ key: item.value,
4652
+ item,
4653
+ active: i === index,
4654
+ marker: i === index ? "\u25B8" : " "
4655
+ }
4656
+ )));
4657
+ }
4658
+ function MultiSelect({
4659
+ items,
4660
+ initialSelected = [],
4661
+ onSubmit,
4662
+ onCancel,
4663
+ footer
4664
+ }) {
4665
+ const [index, setIndex] = useState3(() => {
4666
+ const first = items.findIndex((i) => !i.disabled);
4667
+ return first === -1 ? 0 : first;
4668
+ });
4669
+ const [selected, setSelected] = useState3(new Set(initialSelected));
4670
+ useInput2((input, key) => {
4671
+ if (key.upArrow) {
4672
+ setIndex((i) => findNextEnabled(items, i, -1));
4673
+ } else if (key.downArrow) {
4674
+ setIndex((i) => findNextEnabled(items, i, 1));
4675
+ } else if (input === " ") {
4676
+ const item = items[index];
4677
+ if (!item || item.disabled) return;
4678
+ setSelected((prev) => {
4679
+ const next = new Set(prev);
4680
+ if (next.has(item.value)) next.delete(item.value);
4681
+ else next.add(item.value);
4682
+ return next;
4683
+ });
4684
+ } else if (key.return) {
4685
+ const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
4686
+ onSubmit(ordered);
4687
+ } else if (key.escape && onCancel) {
4688
+ onCancel();
4689
+ }
4690
+ });
4691
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
4692
+ const checked = selected.has(item.value);
4693
+ const marker = checked ? "[x]" : "[ ]";
4694
+ return /* @__PURE__ */ React6.createElement(
4695
+ SelectRow,
4696
+ {
4697
+ key: item.value,
4698
+ item,
4699
+ active: i === index,
4700
+ marker: `${i === index ? "\u25B8" : " "} ${marker}`
4701
+ }
4702
+ );
4703
+ }), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
4704
+ }
4705
+ function SelectRow({
4706
+ item,
4707
+ active,
4708
+ marker
4709
+ }) {
4710
+ const color = item.disabled ? "gray" : active ? "cyan" : void 0;
4711
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, item.hint)) : null);
4712
+ }
4713
+ function findNextEnabled(items, from, step) {
4714
+ if (items.length === 0) return 0;
4715
+ let i = from;
4716
+ for (let tries = 0; tries < items.length; tries++) {
4717
+ i = (i + step + items.length) % items.length;
4718
+ if (!items[i]?.disabled) return i;
4719
+ }
4720
+ return from;
4721
+ }
4722
+
4723
+ // src/cli/ui/ShellConfirm.tsx
4724
+ function ShellConfirm({ command, allowPrefix, onChoose }) {
4725
+ return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "$ "), /* @__PURE__ */ React7.createElement(Text6, { color: "cyan" }, command))), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(
4726
+ SingleSelect,
4727
+ {
4728
+ initialValue: "run_once",
4729
+ items: [
4730
+ {
4731
+ value: "run_once",
4732
+ label: "Run once",
4733
+ hint: "Execute this command, don't remember it."
4734
+ },
4735
+ {
4736
+ value: "always_allow",
4737
+ label: `Always allow "${allowPrefix}" in this project`,
4738
+ hint: "Save the prefix to ~/.reasonix/config.json; future matches auto-run."
4739
+ },
4740
+ {
4741
+ value: "deny",
4742
+ label: "Deny",
4743
+ hint: "Tell the model the user refused; it will continue without this command."
4744
+ }
4745
+ ],
4746
+ onSubmit: (v) => onChoose(v)
4747
+ }
4748
+ )));
4749
+ }
4750
+ function derivePrefix(command) {
4751
+ const tokens = command.trim().split(/\s+/).filter(Boolean);
4752
+ if (tokens.length === 0) return "";
4753
+ if (tokens.length === 1) return tokens[0];
4754
+ const first = tokens[0];
4755
+ const TWO_TOKEN_WRAPPERS = /* @__PURE__ */ new Set([
4756
+ "npm",
4757
+ "npx",
4758
+ "pnpm",
4759
+ "yarn",
4760
+ "bun",
4761
+ "git",
4762
+ "cargo",
4763
+ "go",
4764
+ "docker",
4765
+ "kubectl",
4766
+ "python",
4767
+ "python3",
4768
+ "deno",
4769
+ "pip",
4770
+ "pip3",
4771
+ "make",
4772
+ "rake",
4773
+ "bundle",
4774
+ "gem"
4775
+ ]);
4776
+ return TWO_TOKEN_WRAPPERS.has(first) ? `${first} ${tokens[1]}` : first;
4777
+ }
4778
+
4374
4779
  // src/cli/ui/SlashSuggestions.tsx
4375
- import { Box as Box5, Text as Text5 } from "ink";
4376
- import React6 from "react";
4780
+ import { Box as Box7, Text as Text7 } from "ink";
4781
+ import React8 from "react";
4377
4782
  function SlashSuggestions({
4378
4783
  matches,
4379
4784
  selectedIndex
4380
4785
  }) {
4381
4786
  if (matches === null) return null;
4382
4787
  if (matches.length === 0) {
4383
- return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
4788
+ return /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
4384
4789
  }
4385
4790
  const MAX = 8;
4386
4791
  const total = matches.length;
@@ -4388,21 +4793,21 @@ function SlashSuggestions({
4388
4793
  const shown = matches.slice(windowStart, windowStart + MAX);
4389
4794
  const hiddenAbove = windowStart;
4390
4795
  const hiddenBelow = total - windowStart - shown.length;
4391
- return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React6.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
4796
+ return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React8.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
4392
4797
  }
4393
4798
  function SuggestionRow({ spec, isSelected }) {
4394
4799
  const marker = isSelected ? "\u25B8" : " ";
4395
4800
  const name = `/${spec.cmd}`;
4396
4801
  const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
4397
4802
  if (isSelected) {
4398
- return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React6.createElement(Text5, { color: "cyan" }, " ", spec.summary));
4803
+ return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React8.createElement(Text7, { color: "cyan" }, " ", spec.summary));
4399
4804
  }
4400
- return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
4805
+ return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
4401
4806
  }
4402
4807
 
4403
4808
  // src/cli/ui/StatsPanel.tsx
4404
- import { Box as Box6, Text as Text6 } from "ink";
4405
- import React7 from "react";
4809
+ import { Box as Box8, Text as Text8 } from "ink";
4810
+ import React9 from "react";
4406
4811
  function StatsPanel({
4407
4812
  summary,
4408
4813
  model,
@@ -4417,7 +4822,7 @@ function StatsPanel({
4417
4822
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
4418
4823
  const ctxRatio = summary.lastPromptTokens / ctxMax;
4419
4824
  const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
4420
- return /* @__PURE__ */ React7.createElement(Box6, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Box6, { justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, model), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React7.createElement(Text6, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React7.createElement(Text6, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "cache hit "), /* @__PURE__ */ React7.createElement(Text6, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "cost "), /* @__PURE__ */ React7.createElement(Text6, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "ctx "), /* @__PURE__ */ React7.createElement(Text6, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React7.createElement(Text6, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "balance "), /* @__PURE__ */ React7.createElement(Text6, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
4825
+ return /* @__PURE__ */ React9.createElement(Box8, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Box8, { justifyContent: "space-between" }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, model), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React9.createElement(Text8, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "cache hit "), /* @__PURE__ */ React9.createElement(Text8, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "cost "), /* @__PURE__ */ React9.createElement(Text8, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "ctx "), /* @__PURE__ */ React9.createElement(Text8, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React9.createElement(Text8, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "balance "), /* @__PURE__ */ React9.createElement(Text8, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
4421
4826
  }
4422
4827
  function formatTokens(n) {
4423
4828
  if (n < 1e3) return String(n);
@@ -4881,23 +5286,25 @@ function App({
4881
5286
  codeMode
4882
5287
  }) {
4883
5288
  const { exit } = useApp();
4884
- const [historical, setHistorical] = useState3([]);
4885
- const [streaming, setStreaming] = useState3(null);
4886
- const [input, setInput] = useState3("");
4887
- const [busy, setBusy] = useState3(false);
5289
+ const [historical, setHistorical] = useState4([]);
5290
+ const [streaming, setStreaming] = useState4(null);
5291
+ const [input, setInput] = useState4("");
5292
+ const [busy, setBusy] = useState4(false);
4888
5293
  const abortedThisTurn = useRef2(false);
4889
- const [ongoingTool, setOngoingTool] = useState3(null);
4890
- const [toolProgress, setToolProgress] = useState3(null);
4891
- const [statusLine, setStatusLine] = useState3(null);
4892
- const [balance, setBalance] = useState3(null);
5294
+ const [ongoingTool, setOngoingTool] = useState4(null);
5295
+ const [toolProgress, setToolProgress] = useState4(null);
5296
+ const [statusLine, setStatusLine] = useState4(null);
5297
+ const [balance, setBalance] = useState4(null);
4893
5298
  const lastEditSnapshots = useRef2(null);
4894
5299
  const pendingEdits = useRef2([]);
5300
+ const [pendingShell, setPendingShell] = useState4(null);
5301
+ const [queuedSubmit, setQueuedSubmit] = useState4(null);
4895
5302
  const promptHistory = useRef2([]);
4896
5303
  const historyCursor = useRef2(-1);
4897
5304
  const assistantIterCounter = useRef2(0);
4898
5305
  const toolHistoryRef = useRef2([]);
4899
- const [slashSelected, setSlashSelected] = useState3(0);
4900
- const [summary, setSummary] = useState3({
5306
+ const [slashSelected, setSlashSelected] = useState4(0);
5307
+ const [summary, setSummary] = useState4({
4901
5308
  turns: 0,
4902
5309
  totalCostUsd: 0,
4903
5310
  totalInputCostUsd: 0,
@@ -5002,7 +5409,7 @@ function App({
5002
5409
  ]);
5003
5410
  }
5004
5411
  }, [session, loop]);
5005
- useInput2((_input, key) => {
5412
+ useInput3((_input, key) => {
5006
5413
  if (key.escape && busy) {
5007
5414
  if (abortedThisTurn.current) return;
5008
5415
  abortedThisTurn.current = true;
@@ -5010,6 +5417,7 @@ function App({
5010
5417
  return;
5011
5418
  }
5012
5419
  if (busy) return;
5420
+ if (pendingShell) return;
5013
5421
  if (slashMatches && slashMatches.length > 0) {
5014
5422
  if (key.upArrow) {
5015
5423
  setSlashSelected((i) => Math.max(0, i - 1));
@@ -5279,6 +5687,15 @@ function App({
5279
5687
  toolName: ev.toolName
5280
5688
  }
5281
5689
  ]);
5690
+ if (codeMode && ev.toolName === "run_command" && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
5691
+ try {
5692
+ const parsed = JSON.parse(ev.toolArgs);
5693
+ if (typeof parsed.command === "string" && parsed.command.trim()) {
5694
+ setPendingShell(parsed.command.trim());
5695
+ }
5696
+ } catch {
5697
+ }
5698
+ }
5282
5699
  } else if (ev.role === "error") {
5283
5700
  setHistorical((prev) => [
5284
5701
  ...prev,
@@ -5323,7 +5740,68 @@ function App({
5323
5740
  writeTranscript
5324
5741
  ]
5325
5742
  );
5326
- return /* @__PURE__ */ React8.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(
5743
+ const handleShellConfirm = useCallback(
5744
+ async (choice) => {
5745
+ const cmd = pendingShell;
5746
+ if (!cmd || !codeMode) return;
5747
+ setPendingShell(null);
5748
+ let synthetic;
5749
+ if (choice === "deny") {
5750
+ setHistorical((prev) => [
5751
+ ...prev,
5752
+ { id: `sh-deny-${Date.now()}`, role: "info", text: `\u25B8 denied: ${cmd}` }
5753
+ ]);
5754
+ synthetic = `I denied running \`${cmd}\`. Please continue without running it.`;
5755
+ } else {
5756
+ if (choice === "always_allow") {
5757
+ const prefix = derivePrefix(cmd);
5758
+ addProjectShellAllowed(codeMode.rootDir, prefix);
5759
+ setHistorical((prev) => [
5760
+ ...prev,
5761
+ {
5762
+ id: `sh-allow-${Date.now()}`,
5763
+ role: "info",
5764
+ text: `\u25B8 always allowed "${prefix}" for ${codeMode.rootDir}`
5765
+ }
5766
+ ]);
5767
+ }
5768
+ setHistorical((prev) => [
5769
+ ...prev,
5770
+ { id: `sh-run-${Date.now()}`, role: "info", text: `\u25B8 running: ${cmd}` }
5771
+ ]);
5772
+ let body;
5773
+ try {
5774
+ const res = await runCommand(cmd, { cwd: codeMode.rootDir });
5775
+ body = formatCommandResult(cmd, res);
5776
+ } catch (err) {
5777
+ body = `$ ${cmd}
5778
+ [failed to spawn] ${err.message}`;
5779
+ }
5780
+ setHistorical((prev) => [
5781
+ ...prev,
5782
+ { id: `sh-out-${Date.now()}`, role: "info", text: body }
5783
+ ]);
5784
+ synthetic = `I ran the command you requested. Output:
5785
+
5786
+ ${body}`;
5787
+ }
5788
+ if (busy) {
5789
+ loop.abort();
5790
+ setQueuedSubmit(synthetic);
5791
+ } else {
5792
+ await handleSubmit(synthetic);
5793
+ }
5794
+ },
5795
+ [pendingShell, codeMode, handleSubmit, busy, loop]
5796
+ );
5797
+ useEffect2(() => {
5798
+ if (!busy && queuedSubmit !== null) {
5799
+ const text = queuedSubmit;
5800
+ setQueuedSubmit(null);
5801
+ void handleSubmit(text);
5802
+ }
5803
+ }, [busy, queuedSubmit, handleSubmit]);
5804
+ return /* @__PURE__ */ React10.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(
5327
5805
  StatsPanel,
5328
5806
  {
5329
5807
  summary,
@@ -5333,13 +5811,28 @@ function App({
5333
5811
  branchBudget: loop.branchOptions.budget,
5334
5812
  balance
5335
5813
  }
5336
- ), /* @__PURE__ */ React8.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React8.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && streaming ? /* @__PURE__ */ React8.createElement(Box7, { marginY: 1 }, /* @__PURE__ */ React8.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && ongoingTool ? /* @__PURE__ */ React8.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !ongoingTool && statusLine ? /* @__PURE__ */ React8.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React8.createElement(StatusRow, { text: "processing\u2026" }) : null, /* @__PURE__ */ React8.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React8.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected })));
5814
+ ), /* @__PURE__ */ React10.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React10.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && streaming ? /* @__PURE__ */ React10.createElement(Box9, { marginY: 1 }, /* @__PURE__ */ React10.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && !pendingShell && ongoingTool ? /* @__PURE__ */ React10.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !ongoingTool && statusLine ? /* @__PURE__ */ React10.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React10.createElement(StatusRow, { text: "processing\u2026" }) : null, pendingShell ? /* @__PURE__ */ React10.createElement(
5815
+ ShellConfirm,
5816
+ {
5817
+ command: pendingShell,
5818
+ allowPrefix: derivePrefix(pendingShell),
5819
+ onChoose: handleShellConfirm
5820
+ }
5821
+ ) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
5822
+ PromptInput,
5823
+ {
5824
+ value: input,
5825
+ onChange: setInput,
5826
+ onSubmit: handleSubmit,
5827
+ disabled: busy
5828
+ }
5829
+ ), /* @__PURE__ */ React10.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
5337
5830
  }
5338
5831
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5339
5832
  function StatusRow({ text }) {
5340
5833
  const tick = useTick();
5341
5834
  const elapsed = useElapsedSeconds();
5342
- return /* @__PURE__ */ React8.createElement(Box7, { marginY: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React8.createElement(Text7, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, ` ${elapsed}s`));
5835
+ return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`));
5343
5836
  }
5344
5837
  function OngoingToolRow({
5345
5838
  tool,
@@ -5348,7 +5841,7 @@ function OngoingToolRow({
5348
5841
  const tick = useTick();
5349
5842
  const elapsed = useElapsedSeconds();
5350
5843
  const summary = summarizeToolArgs(tool.name, tool.args);
5351
- return /* @__PURE__ */ React8.createElement(Box7, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React8.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React8.createElement(Text7, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React8.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, summary)) : null);
5844
+ return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, summary)) : null);
5352
5845
  }
5353
5846
  function renderProgressLine(p) {
5354
5847
  const msg = p.message ? ` ${p.message}` : "";
@@ -5443,13 +5936,60 @@ function describeRepair(repair) {
5443
5936
  return parts.length ? `[repair] ${parts.join(", ")}` : "";
5444
5937
  }
5445
5938
 
5939
+ // src/cli/ui/SessionPicker.tsx
5940
+ import { Box as Box10, Text as Text10 } from "ink";
5941
+ import React11 from "react";
5942
+ function SessionPicker({
5943
+ sessionName,
5944
+ messageCount,
5945
+ lastActive,
5946
+ onChoose
5947
+ }) {
5948
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React11.createElement(
5949
+ SingleSelect,
5950
+ {
5951
+ initialValue: "new",
5952
+ items: [
5953
+ {
5954
+ value: "new",
5955
+ label: "Start new conversation",
5956
+ hint: "Previous messages kept on disk; your turn starts fresh."
5957
+ },
5958
+ {
5959
+ value: "resume",
5960
+ label: "Resume",
5961
+ hint: `Continue where you left off (${messageCount} messages in context).`
5962
+ },
5963
+ {
5964
+ value: "delete",
5965
+ label: "Delete and start new",
5966
+ hint: "Wipes the session file irreversibly. Other sessions untouched."
5967
+ }
5968
+ ],
5969
+ onSubmit: (v) => onChoose(v)
5970
+ }
5971
+ ), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
5972
+ }
5973
+ function relativeTime(date) {
5974
+ const ms = Date.now() - date.getTime();
5975
+ const mins = Math.floor(ms / 6e4);
5976
+ if (mins < 1) return "just now";
5977
+ if (mins < 60) return `${mins}m ago`;
5978
+ const hours = Math.floor(mins / 60);
5979
+ if (hours < 24) return `${hours}h ago`;
5980
+ const days = Math.floor(hours / 24);
5981
+ if (days === 1) return "yesterday";
5982
+ if (days < 7) return `${days}d ago`;
5983
+ return date.toISOString().slice(0, 10);
5984
+ }
5985
+
5446
5986
  // src/cli/ui/Setup.tsx
5447
- import { Box as Box8, Text as Text8, useApp as useApp2 } from "ink";
5987
+ import { Box as Box11, Text as Text11, useApp as useApp2 } from "ink";
5448
5988
  import TextInput from "ink-text-input";
5449
- import React9, { useState as useState4 } from "react";
5989
+ import React12, { useState as useState5 } from "react";
5450
5990
  function Setup({ onReady }) {
5451
- const [value, setValue] = useState4("");
5452
- const [error, setError] = useState4(null);
5991
+ const [value, setValue] = useState5("");
5992
+ const [error, setError] = useState5(null);
5453
5993
  const { exit } = useApp2();
5454
5994
  const handleSubmit = (raw) => {
5455
5995
  const trimmed = raw.trim();
@@ -5470,7 +6010,7 @@ function Setup({ onReady }) {
5470
6010
  }
5471
6011
  onReady(trimmed);
5472
6012
  };
5473
- return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React9.createElement(
6013
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React12.createElement(
5474
6014
  TextInput,
5475
6015
  {
5476
6016
  value,
@@ -5479,14 +6019,23 @@ function Setup({ onReady }) {
5479
6019
  mask: "\u2022",
5480
6020
  placeholder: "sk-..."
5481
6021
  }
5482
- )), error ? /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "red" }, error)) : value ? /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "(Type /exit to abort.)")));
6022
+ )), error ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)) : value ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "(Type /exit to abort.)")));
5483
6023
  }
5484
6024
 
5485
6025
  // src/cli/commands/chat.tsx
5486
- function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appProps }) {
5487
- const [key, setKey] = useState5(initialKey);
6026
+ function Root({
6027
+ initialKey,
6028
+ tools,
6029
+ mcpSpecs,
6030
+ mcpServers,
6031
+ progressSink,
6032
+ sessionPreview,
6033
+ ...appProps
6034
+ }) {
6035
+ const [key, setKey] = useState6(initialKey);
6036
+ const [pending, setPending] = useState6(sessionPreview);
5488
6037
  if (!key) {
5489
- return /* @__PURE__ */ React10.createElement(
6038
+ return /* @__PURE__ */ React13.createElement(
5490
6039
  Setup,
5491
6040
  {
5492
6041
  onReady: (k) => {
@@ -5497,7 +6046,23 @@ function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appPro
5497
6046
  );
5498
6047
  }
5499
6048
  process.env.DEEPSEEK_API_KEY = key;
5500
- return /* @__PURE__ */ React10.createElement(
6049
+ if (pending && appProps.session) {
6050
+ return /* @__PURE__ */ React13.createElement(
6051
+ SessionPicker,
6052
+ {
6053
+ sessionName: appProps.session,
6054
+ messageCount: pending.messageCount,
6055
+ lastActive: pending.lastActive,
6056
+ onChoose: (choice) => {
6057
+ if (choice === "new" || choice === "delete") {
6058
+ rewriteSession(appProps.session, []);
6059
+ }
6060
+ setPending(void 0);
6061
+ }
6062
+ }
6063
+ );
6064
+ }
6065
+ return /* @__PURE__ */ React13.createElement(
5501
6066
  App,
5502
6067
  {
5503
6068
  model: appProps.model,
@@ -5584,8 +6149,19 @@ async function chatCommand(opts) {
5584
6149
  if (!tools) tools = new ToolRegistry();
5585
6150
  registerWebTools(tools);
5586
6151
  }
6152
+ let sessionPreview;
6153
+ if (opts.session && !opts.forceResume && !opts.forceNew) {
6154
+ const prior = loadSessionMessages(opts.session);
6155
+ if (prior.length > 0) {
6156
+ const p = sessionPath(opts.session);
6157
+ const mtime = existsSync3(p) ? statSync2(p).mtime : /* @__PURE__ */ new Date();
6158
+ sessionPreview = { messageCount: prior.length, lastActive: mtime };
6159
+ }
6160
+ } else if (opts.session && opts.forceNew) {
6161
+ rewriteSession(opts.session, []);
6162
+ }
5587
6163
  const { waitUntilExit } = render(
5588
- /* @__PURE__ */ React10.createElement(
6164
+ /* @__PURE__ */ React13.createElement(
5589
6165
  Root,
5590
6166
  {
5591
6167
  initialKey,
@@ -5593,6 +6169,7 @@ async function chatCommand(opts) {
5593
6169
  mcpSpecs,
5594
6170
  mcpServers,
5595
6171
  progressSink,
6172
+ sessionPreview,
5596
6173
  ...opts
5597
6174
  }
5598
6175
  ),
@@ -5608,15 +6185,21 @@ async function chatCommand(opts) {
5608
6185
  }
5609
6186
 
5610
6187
  // src/cli/commands/code.tsx
5611
- import { basename, resolve as resolve4 } from "path";
6188
+ import { basename, resolve as resolve5 } from "path";
5612
6189
  async function codeCommand(opts = {}) {
5613
6190
  const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
5614
- const rootDir = resolve4(opts.dir ?? process.cwd());
6191
+ const rootDir = resolve5(opts.dir ?? process.cwd());
5615
6192
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
5616
6193
  const tools = new ToolRegistry();
5617
6194
  registerFilesystemTools(tools, { rootDir });
6195
+ registerShellTools(tools, {
6196
+ rootDir,
6197
+ // Per-project "always allow" list persisted from prior ShellConfirm
6198
+ // choices; merged on top of the built-in allowlist in shell.ts.
6199
+ extraAllowed: loadProjectShellAllowed(rootDir)
6200
+ });
5618
6201
  process.stderr.write(
5619
- `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native fs tool(s)
6202
+ `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
5620
6203
  `
5621
6204
  );
5622
6205
  await chatCommand({
@@ -5627,7 +6210,9 @@ async function codeCommand(opts = {}) {
5627
6210
  transcript: opts.transcript,
5628
6211
  session,
5629
6212
  seedTools: tools,
5630
- codeMode: { rootDir }
6213
+ codeMode: { rootDir },
6214
+ forceResume: opts.forceResume,
6215
+ forceNew: opts.forceNew
5631
6216
  });
5632
6217
  }
5633
6218
 
@@ -5635,34 +6220,34 @@ async function codeCommand(opts = {}) {
5635
6220
  import { writeFileSync as writeFileSync4 } from "fs";
5636
6221
  import { basename as basename2 } from "path";
5637
6222
  import { render as render2 } from "ink";
5638
- import React13 from "react";
6223
+ import React16 from "react";
5639
6224
 
5640
6225
  // src/cli/ui/DiffApp.tsx
5641
- import { Box as Box10, Static as Static2, Text as Text10, useApp as useApp3, useInput as useInput3 } from "ink";
5642
- import React12, { useState as useState6 } from "react";
6226
+ import { Box as Box13, Static as Static2, Text as Text13, useApp as useApp3, useInput as useInput4 } from "ink";
6227
+ import React15, { useState as useState7 } from "react";
5643
6228
 
5644
6229
  // src/cli/ui/RecordView.tsx
5645
- import { Box as Box9, Text as Text9 } from "ink";
5646
- import React11 from "react";
6230
+ import { Box as Box12, Text as Text12 } from "ink";
6231
+ import React14 from "react";
5647
6232
  function RecordView({ rec, compact = false }) {
5648
6233
  const toolArgsMax = compact ? 120 : 200;
5649
6234
  const toolContentMax = compact ? 200 : 400;
5650
6235
  if (rec.role === "user") {
5651
- return /* @__PURE__ */ React11.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text9, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React11.createElement(Text9, null, rec.content));
6236
+ return /* @__PURE__ */ React14.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React14.createElement(Text12, null, rec.content));
5652
6237
  }
5653
6238
  if (rec.role === "assistant_final") {
5654
- return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text9, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React11.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React11.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React11.createElement(Text9, null, rec.content) : /* @__PURE__ */ React11.createElement(Text9, { dimColor: true, italic: true }, "(tool-call response only)"));
6239
+ return /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React14.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React14.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React14.createElement(Text12, null, rec.content) : /* @__PURE__ */ React14.createElement(Text12, { dimColor: true, italic: true }, "(tool-call response only)"));
5655
6240
  }
5656
6241
  if (rec.role === "tool") {
5657
- return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text9, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
6242
+ return /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
5658
6243
  }
5659
6244
  if (rec.role === "error") {
5660
- return /* @__PURE__ */ React11.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text9, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React11.createElement(Text9, { color: "red" }, rec.error ?? rec.content));
6245
+ return /* @__PURE__ */ React14.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React14.createElement(Text12, { color: "red" }, rec.error ?? rec.content));
5661
6246
  }
5662
6247
  if (rec.role === "done" || rec.role === "assistant_delta") {
5663
6248
  return null;
5664
6249
  }
5665
- return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, "[", rec.role, "] ", rec.content));
6250
+ return /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, "[", rec.role, "] ", rec.content));
5666
6251
  }
5667
6252
  function CacheBadge({ usage }) {
5668
6253
  const hit = usage.prompt_cache_hit_tokens ?? 0;
@@ -5671,7 +6256,7 @@ function CacheBadge({ usage }) {
5671
6256
  if (total === 0) return null;
5672
6257
  const pct2 = hit / total * 100;
5673
6258
  const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
5674
- return /* @__PURE__ */ React11.createElement(Text9, null, /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React11.createElement(Text9, { color }, pct2.toFixed(1), "%"));
6259
+ return /* @__PURE__ */ React14.createElement(Text12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React14.createElement(Text12, { color }, pct2.toFixed(1), "%"));
5675
6260
  }
5676
6261
  function truncate3(s, max) {
5677
6262
  return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
@@ -5682,8 +6267,8 @@ function DiffApp({ report }) {
5682
6267
  const { exit } = useApp3();
5683
6268
  const maxIdx = Math.max(0, report.pairs.length - 1);
5684
6269
  const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
5685
- const [idx, setIdx] = useState6(Math.max(0, initialIdx));
5686
- useInput3((input, key) => {
6270
+ const [idx, setIdx] = useState7(Math.max(0, initialIdx));
6271
+ useInput4((input, key) => {
5687
6272
  if (input === "q" || key.ctrl && input === "c") {
5688
6273
  exit();
5689
6274
  return;
@@ -5705,7 +6290,7 @@ function DiffApp({ report }) {
5705
6290
  }
5706
6291
  });
5707
6292
  const pair = report.pairs[idx];
5708
- return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(DiffHeader, { report }), /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React12.createElement(Text10, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React12.createElement(Text10, null, pair ? /* @__PURE__ */ React12.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React12.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React12.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text10, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React12.createElement(Text10, null, pair.divergenceNote)) : null, /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "j"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "k"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "N"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "g"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "q"), " ", "quit")));
6293
+ return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React15.createElement(DiffHeader, { report }), /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React15.createElement(Text13, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React15.createElement(Text13, null, pair ? /* @__PURE__ */ React15.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React15.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React15.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React15.createElement(Text13, null, pair.divergenceNote)) : null, /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "j"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "k"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "N"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "g"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "q"), " ", "quit")));
5709
6294
  }
5710
6295
  function DiffHeader({ report }) {
5711
6296
  const a = report.a;
@@ -5723,15 +6308,15 @@ function DiffHeader({ report }) {
5723
6308
  } else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
5724
6309
  prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
5725
6310
  }
5726
- return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React12.createElement(Text10, null, /* @__PURE__ */ React12.createElement(Text10, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React12.createElement(Text10, { color: "blue" }, a.label), /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, " vs B="), /* @__PURE__ */ React12.createElement(Text10, { color: "magenta" }, b.label)), /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React12.createElement(Text10, null, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, "cache "), /* @__PURE__ */ React12.createElement(Text10, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React12.createElement(Text10, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React12.createElement(Text10, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React12.createElement(Text10, null, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React12.createElement(Text10, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React12.createElement(Text10, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React12.createElement(Text10, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React12.createElement(Text10, null, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, "model calls "), /* @__PURE__ */ React12.createElement(Text10, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true, italic: true }, prefixLine)) : null);
6311
+ return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React15.createElement(Box13, { justifyContent: "space-between" }, /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, a.label), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " vs B="), /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, b.label)), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "cache "), /* @__PURE__ */ React15.createElement(Text13, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React15.createElement(Text13, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React15.createElement(Text13, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "cost "), /* @__PURE__ */ React15.createElement(Text13, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React15.createElement(Text13, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React15.createElement(Text13, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "model calls "), /* @__PURE__ */ React15.createElement(Text13, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true, italic: true }, prefixLine)) : null);
5727
6312
  }
5728
6313
  function Pane({
5729
6314
  label,
5730
6315
  headerColor,
5731
6316
  records
5732
6317
  }) {
5733
- return /* @__PURE__ */ React12.createElement(
5734
- Box10,
6318
+ return /* @__PURE__ */ React15.createElement(
6319
+ Box13,
5735
6320
  {
5736
6321
  flexDirection: "column",
5737
6322
  flexGrow: 1,
@@ -5739,21 +6324,21 @@ function Pane({
5739
6324
  borderStyle: "single",
5740
6325
  borderColor: headerColor
5741
6326
  },
5742
- /* @__PURE__ */ React12.createElement(Text10, { color: headerColor, bold: true }, label),
5743
- records.length === 0 ? /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React12.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React12.createElement(RecordView, { key, rec, compact: true }))
6327
+ /* @__PURE__ */ React15.createElement(Text13, { color: headerColor, bold: true }, label),
6328
+ records.length === 0 ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React15.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React15.createElement(RecordView, { key, rec, compact: true }))
5744
6329
  );
5745
6330
  }
5746
6331
  function KindBadge({ kind }) {
5747
6332
  if (kind === "match") {
5748
- return /* @__PURE__ */ React12.createElement(Text10, { color: "green" }, "\u2713 match");
6333
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "green" }, "\u2713 match");
5749
6334
  }
5750
6335
  if (kind === "diverge") {
5751
- return /* @__PURE__ */ React12.createElement(Text10, { color: "yellow" }, "\u2605 diverge");
6336
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 diverge");
5752
6337
  }
5753
6338
  if (kind === "only_in_a") {
5754
- return /* @__PURE__ */ React12.createElement(Text10, { color: "blue" }, "\u2190 only in A");
6339
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, "\u2190 only in A");
5755
6340
  }
5756
- return /* @__PURE__ */ React12.createElement(Text10, { color: "magenta" }, "\u2192 only in B");
6341
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, "\u2192 only in B");
5757
6342
  }
5758
6343
  function paneRecords(pair, side) {
5759
6344
  if (!pair) return [];
@@ -5784,7 +6369,7 @@ markdown report written to ${opts.mdPath}`);
5784
6369
  return;
5785
6370
  }
5786
6371
  if (wantTui) {
5787
- const { waitUntilExit } = render2(React13.createElement(DiffApp, { report }), {
6372
+ const { waitUntilExit } = render2(React16.createElement(DiffApp, { report }), {
5788
6373
  exitOnCtrlC: true,
5789
6374
  patchConsole: false
5790
6375
  });
@@ -5925,16 +6510,16 @@ function pad(s, width) {
5925
6510
 
5926
6511
  // src/cli/commands/replay.ts
5927
6512
  import { render as render3 } from "ink";
5928
- import React15 from "react";
6513
+ import React18 from "react";
5929
6514
 
5930
6515
  // src/cli/ui/ReplayApp.tsx
5931
- import { Box as Box11, Static as Static3, Text as Text11, useApp as useApp4, useInput as useInput4 } from "ink";
5932
- import React14, { useMemo as useMemo2, useState as useState7 } from "react";
6516
+ import { Box as Box14, Static as Static3, Text as Text14, useApp as useApp4, useInput as useInput5 } from "ink";
6517
+ import React17, { useMemo as useMemo2, useState as useState8 } from "react";
5933
6518
  function ReplayApp({ meta, pages }) {
5934
6519
  const { exit } = useApp4();
5935
6520
  const maxIdx = Math.max(0, pages.length - 1);
5936
- const [idx, setIdx] = useState7(maxIdx);
5937
- useInput4((input, key) => {
6521
+ const [idx, setIdx] = useState8(maxIdx);
6522
+ useInput5((input, key) => {
5938
6523
  if (input === "q" || key.ctrl && input === "c") {
5939
6524
  exit();
5940
6525
  return;
@@ -5968,14 +6553,14 @@ function ReplayApp({ meta, pages }) {
5968
6553
  const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
5969
6554
  const currentPage = pages[idx];
5970
6555
  const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
5971
- return /* @__PURE__ */ React14.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React14.createElement(
6556
+ return /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(
5972
6557
  StatsPanel,
5973
6558
  {
5974
6559
  summary,
5975
6560
  model: cumStats.models[0] ?? meta?.model ?? "?",
5976
6561
  prefixHash
5977
6562
  }
5978
- ), /* @__PURE__ */ React14.createElement(Box11, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React14.createElement(Box11, { justifyContent: "space-between" }, /* @__PURE__ */ React14.createElement(Text11, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React14.createElement(Text11, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React14.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React14.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React14.createElement(Text11, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React14.createElement(Box11, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React14.createElement(Text11, { dimColor: true }, /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "j"), "/", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "k"), "/", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React14.createElement(Text11, { bold: true }, "q"), " quit")));
6563
+ ), /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React17.createElement(Box14, { justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text14, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React17.createElement(Text14, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React17.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React17.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React17.createElement(Text14, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React17.createElement(Box14, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React17.createElement(Text14, { dimColor: true }, /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "j"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "k"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "q"), " quit")));
5979
6564
  }
5980
6565
 
5981
6566
  // src/cli/commands/replay.ts
@@ -5987,7 +6572,7 @@ async function replayCommand(opts) {
5987
6572
  }
5988
6573
  const { parsed } = replayFromFile(opts.path);
5989
6574
  const pages = groupRecordsByTurn(parsed.records);
5990
- const { waitUntilExit } = render3(React15.createElement(ReplayApp, { meta: parsed.meta, pages }), {
6575
+ const { waitUntilExit } = render3(React18.createElement(ReplayApp, { meta: parsed.meta, pages }), {
5991
6576
  exitOnCtrlC: true,
5992
6577
  patchConsole: false
5993
6578
  });
@@ -6114,7 +6699,7 @@ async function ensureApiKey() {
6114
6699
  rl.close();
6115
6700
  }
6116
6701
  }
6117
- async function runCommand(opts) {
6702
+ async function runCommand2(opts) {
6118
6703
  loadDotenv();
6119
6704
  const apiKey = await ensureApiKey();
6120
6705
  process.env.DEEPSEEK_API_KEY = apiKey;
@@ -6289,113 +6874,12 @@ function truncate4(s, max) {
6289
6874
 
6290
6875
  // src/cli/commands/setup.tsx
6291
6876
  import { render as render4 } from "ink";
6292
- import React18 from "react";
6877
+ import React20 from "react";
6293
6878
 
6294
6879
  // src/cli/ui/Wizard.tsx
6295
- import { Box as Box13, Text as Text13, useApp as useApp5, useInput as useInput6 } from "ink";
6880
+ import { Box as Box15, Text as Text15, useApp as useApp5, useInput as useInput6 } from "ink";
6296
6881
  import TextInput2 from "ink-text-input";
6297
- import React17, { useState as useState9 } from "react";
6298
-
6299
- // src/cli/ui/Select.tsx
6300
- import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
6301
- import React16, { useState as useState8 } from "react";
6302
- function SingleSelect({
6303
- items,
6304
- initialValue,
6305
- onSubmit,
6306
- onCancel
6307
- }) {
6308
- const initialIndex = Math.max(
6309
- 0,
6310
- items.findIndex((i) => i.value === initialValue && !i.disabled)
6311
- );
6312
- const [index, setIndex] = useState8(initialIndex === -1 ? 0 : initialIndex);
6313
- useInput5((_input, key) => {
6314
- if (key.upArrow) {
6315
- setIndex((i) => findNextEnabled(items, i, -1));
6316
- } else if (key.downArrow) {
6317
- setIndex((i) => findNextEnabled(items, i, 1));
6318
- } else if (key.return) {
6319
- const chosen = items[index];
6320
- if (chosen && !chosen.disabled) onSubmit(chosen.value);
6321
- } else if (key.escape && onCancel) {
6322
- onCancel();
6323
- }
6324
- });
6325
- return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React16.createElement(
6326
- SelectRow,
6327
- {
6328
- key: item.value,
6329
- item,
6330
- active: i === index,
6331
- marker: i === index ? "\u25B8" : " "
6332
- }
6333
- )));
6334
- }
6335
- function MultiSelect({
6336
- items,
6337
- initialSelected = [],
6338
- onSubmit,
6339
- onCancel,
6340
- footer
6341
- }) {
6342
- const [index, setIndex] = useState8(() => {
6343
- const first = items.findIndex((i) => !i.disabled);
6344
- return first === -1 ? 0 : first;
6345
- });
6346
- const [selected, setSelected] = useState8(new Set(initialSelected));
6347
- useInput5((input, key) => {
6348
- if (key.upArrow) {
6349
- setIndex((i) => findNextEnabled(items, i, -1));
6350
- } else if (key.downArrow) {
6351
- setIndex((i) => findNextEnabled(items, i, 1));
6352
- } else if (input === " ") {
6353
- const item = items[index];
6354
- if (!item || item.disabled) return;
6355
- setSelected((prev) => {
6356
- const next = new Set(prev);
6357
- if (next.has(item.value)) next.delete(item.value);
6358
- else next.add(item.value);
6359
- return next;
6360
- });
6361
- } else if (key.return) {
6362
- const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
6363
- onSubmit(ordered);
6364
- } else if (key.escape && onCancel) {
6365
- onCancel();
6366
- }
6367
- });
6368
- return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => {
6369
- const checked = selected.has(item.value);
6370
- const marker = checked ? "[x]" : "[ ]";
6371
- return /* @__PURE__ */ React16.createElement(
6372
- SelectRow,
6373
- {
6374
- key: item.value,
6375
- item,
6376
- active: i === index,
6377
- marker: `${i === index ? "\u25B8" : " "} ${marker}`
6378
- }
6379
- );
6380
- }), footer ? /* @__PURE__ */ React16.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text12, { dimColor: true }, footer)) : null);
6381
- }
6382
- function SelectRow({
6383
- item,
6384
- active,
6385
- marker
6386
- }) {
6387
- const color = item.disabled ? "gray" : active ? "cyan" : void 0;
6388
- return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Box12, null, /* @__PURE__ */ React16.createElement(Text12, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React16.createElement(Box12, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React16.createElement(Text12, { dimColor: true }, item.hint)) : null);
6389
- }
6390
- function findNextEnabled(items, from, step) {
6391
- if (items.length === 0) return 0;
6392
- let i = from;
6393
- for (let tries = 0; tries < items.length; tries++) {
6394
- i = (i + step + items.length) % items.length;
6395
- if (!items[i]?.disabled) return i;
6396
- }
6397
- return from;
6398
- }
6882
+ import React19, { useState as useState9 } from "react";
6399
6883
 
6400
6884
  // src/cli/ui/presets.ts
6401
6885
  var PRESETS = {
@@ -6434,7 +6918,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6434
6918
  if (key.escape && step !== "saved" && onCancel) onCancel();
6435
6919
  });
6436
6920
  if (step === "apiKey") {
6437
- return /* @__PURE__ */ React17.createElement(
6921
+ return /* @__PURE__ */ React19.createElement(
6438
6922
  ApiKeyStep,
6439
6923
  {
6440
6924
  onSubmit: (key) => {
@@ -6448,7 +6932,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6448
6932
  );
6449
6933
  }
6450
6934
  if (step === "preset") {
6451
- return /* @__PURE__ */ React17.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React17.createElement(
6935
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React19.createElement(
6452
6936
  SingleSelect,
6453
6937
  {
6454
6938
  items: presetItems(),
@@ -6458,10 +6942,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6458
6942
  setStep("mcp");
6459
6943
  }
6460
6944
  }
6461
- ), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
6945
+ ), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
6462
6946
  }
6463
6947
  if (step === "mcp") {
6464
- return /* @__PURE__ */ React17.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React17.createElement(
6948
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React19.createElement(
6465
6949
  MultiSelect,
6466
6950
  {
6467
6951
  items: mcpItems(),
@@ -6486,7 +6970,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6486
6970
  }
6487
6971
  const currentName = pending[0];
6488
6972
  const entry = CATALOG_BY_NAME.get(currentName);
6489
- return /* @__PURE__ */ React17.createElement(
6973
+ return /* @__PURE__ */ React19.createElement(
6490
6974
  McpArgsStep,
6491
6975
  {
6492
6976
  entry,
@@ -6504,7 +6988,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6504
6988
  }
6505
6989
  if (step === "review") {
6506
6990
  const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
6507
- return /* @__PURE__ */ React17.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React17.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React17.createElement(
6991
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React19.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React19.createElement(
6508
6992
  SummaryLine,
6509
6993
  {
6510
6994
  label: "MCP",
@@ -6512,8 +6996,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6512
6996
  }
6513
6997
  ), specs.map((spec, i) => (
6514
6998
  // biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
6515
- /* @__PURE__ */ React17.createElement(Box13, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "\xB7 ", spec))
6516
- )), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { color: "red" }, error)) : null, /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React17.createElement(
6999
+ /* @__PURE__ */ React19.createElement(Box15, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\xB7 ", spec))
7000
+ )), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null, /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React19.createElement(
6517
7001
  ReviewConfirm,
6518
7002
  {
6519
7003
  onConfirm: () => {
@@ -6539,7 +7023,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6539
7023
  }
6540
7024
  ));
6541
7025
  }
6542
- return /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React17.createElement(ExitOnEnter, { onExit: exit }));
7026
+ return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React19.createElement(ExitOnEnter, { onExit: exit }));
6543
7027
  }
6544
7028
  function ApiKeyStep({
6545
7029
  onSubmit,
@@ -6547,7 +7031,7 @@ function ApiKeyStep({
6547
7031
  onError
6548
7032
  }) {
6549
7033
  const [value, setValue] = useState9("");
6550
- return /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React17.createElement(
7034
+ return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React19.createElement(
6551
7035
  TextInput2,
6552
7036
  {
6553
7037
  value,
@@ -6564,7 +7048,7 @@ function ApiKeyStep({
6564
7048
  mask: "\u2022",
6565
7049
  placeholder: "sk-..."
6566
7050
  }
6567
- )), error ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { color: "red" }, error)) : value ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "preview: ", redactKey(value))) : null);
7051
+ )), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : value ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "preview: ", redactKey(value))) : null);
6568
7052
  }
6569
7053
  function McpArgsStep({
6570
7054
  entry,
@@ -6573,7 +7057,7 @@ function McpArgsStep({
6573
7057
  onError
6574
7058
  }) {
6575
7059
  const [value, setValue] = useState9("");
6576
- return /* @__PURE__ */ React17.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(Text13, null, entry.summary), entry.note ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Required parameter: "), /* @__PURE__ */ React17.createElement(Text13, { bold: true }, entry.userArgs)), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React17.createElement(
7060
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(Text15, null, entry.summary), entry.note ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Required parameter: "), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, entry.userArgs)), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React19.createElement(
6577
7061
  TextInput2,
6578
7062
  {
6579
7063
  value,
@@ -6589,7 +7073,7 @@ function McpArgsStep({
6589
7073
  },
6590
7074
  placeholder: placeholderFor(entry)
6591
7075
  }
6592
- )), error ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { color: "red" }, error)) : null));
7076
+ )), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null));
6593
7077
  }
6594
7078
  function ReviewConfirm({ onConfirm }) {
6595
7079
  useInput6((_i, key) => {
@@ -6609,10 +7093,10 @@ function StepFrame({
6609
7093
  total,
6610
7094
  children
6611
7095
  }) {
6612
- return /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Box13, null, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1, flexDirection: "column" }, children));
7096
+ return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1, flexDirection: "column" }, children));
6613
7097
  }
6614
7098
  function SummaryLine({ label, value }) {
6615
- return /* @__PURE__ */ React17.createElement(Box13, null, /* @__PURE__ */ React17.createElement(Text13, null, label.padEnd(12)), /* @__PURE__ */ React17.createElement(Text13, { bold: true }, value));
7099
+ return /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, null, label.padEnd(12)), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, value));
6616
7100
  }
6617
7101
  function presetItems() {
6618
7102
  return ["fast", "smart", "max"].map((name) => ({
@@ -6668,7 +7152,7 @@ async function setupCommand(_opts = {}) {
6668
7152
  const existingKey = loadApiKey();
6669
7153
  const existing = readConfig();
6670
7154
  const { waitUntilExit, unmount } = render4(
6671
- /* @__PURE__ */ React18.createElement(
7155
+ /* @__PURE__ */ React20.createElement(
6672
7156
  Wizard,
6673
7157
  {
6674
7158
  existingApiKey: existingKey,
@@ -6686,9 +7170,9 @@ async function setupCommand(_opts = {}) {
6686
7170
  }
6687
7171
 
6688
7172
  // src/cli/commands/stats.ts
6689
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
7173
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
6690
7174
  function statsCommand(opts) {
6691
- if (!existsSync3(opts.transcript)) {
7175
+ if (!existsSync4(opts.transcript)) {
6692
7176
  console.error(`no such transcript: ${opts.transcript}`);
6693
7177
  process.exit(1);
6694
7178
  }
@@ -6775,12 +7259,14 @@ program.command("setup").description("Interactive wizard \u2014 API key, preset,
6775
7259
  });
6776
7260
  program.command("code [dir]").description(
6777
7261
  "Code-editing chat \u2014 filesystem MCP auto-bridged at <dir> (default: cwd), coding system prompt, smart preset. Model proposes SEARCH/REPLACE blocks; Reasonix applies them to disk."
6778
- ).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("--transcript <path>", "Write a JSONL transcript to this path").action(async (dir, opts) => {
7262
+ ).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option("--transcript <path>", "Write a JSONL transcript to this path").action(async (dir, opts) => {
6779
7263
  await codeCommand({
6780
7264
  dir,
6781
7265
  model: opts.model,
6782
7266
  noSession: opts.session === false,
6783
- transcript: opts.transcript
7267
+ transcript: opts.transcript,
7268
+ forceResume: !!opts.resume,
7269
+ forceNew: !!opts.new
6784
7270
  });
6785
7271
  });
6786
7272
  program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id (overrides preset)").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").option(
@@ -6793,7 +7279,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
6793
7279
  "--branch <n>",
6794
7280
  "Self-consistency: run N parallel samples per turn and pick the most confident (disables streaming; enables harvest)",
6795
7281
  (v) => Number.parseInt(v, 10)
6796
- ).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option(
7282
+ ).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option(
6797
7283
  "--mcp <spec>",
6798
7284
  'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
6799
7285
  (value, previous = []) => [...previous, value],
@@ -6819,7 +7305,9 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
6819
7305
  branch: defaults.branch,
6820
7306
  session: defaults.session,
6821
7307
  mcp: defaults.mcp,
6822
- mcpPrefix: opts.mcpPrefix
7308
+ mcpPrefix: opts.mcpPrefix,
7309
+ forceResume: !!opts.resume,
7310
+ forceNew: !!opts.new
6823
7311
  });
6824
7312
  });
6825
7313
  program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id (overrides preset)").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).option("--preset <name>", "Bundle of model + harvest + branch: fast | smart | max").option("--harvest", "Extract typed plan state from R1 reasoning (Pillar 2)").option(
@@ -6843,7 +7331,7 @@ program.command("run <task>").description("Run a single task non-interactively,
6843
7331
  preset: opts.preset,
6844
7332
  noConfig: opts.config === false
6845
7333
  });
6846
- await runCommand({
7334
+ await runCommand2({
6847
7335
  task,
6848
7336
  model: defaults.model,
6849
7337
  system: opts.system,