reasonix 0.4.15 → 0.4.17

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
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-2P2MZLCE.js";
2
+ import {
3
+ PROJECT_MEMORY_FILE,
4
+ applyProjectMemory,
5
+ memoryEnabled,
6
+ readProjectMemory
7
+ } from "./chunk-3YQRWFES.js";
3
8
 
4
9
  // src/cli/index.ts
5
10
  import { Command } from "commander";
@@ -44,6 +49,21 @@ function saveApiKey(key, path = defaultConfigPath()) {
44
49
  cfg.apiKey = key.trim();
45
50
  writeConfig(cfg, path);
46
51
  }
52
+ function loadProjectShellAllowed(rootDir, path = defaultConfigPath()) {
53
+ const cfg = readConfig(path);
54
+ return cfg.projects?.[rootDir]?.shellAllowed ?? [];
55
+ }
56
+ function addProjectShellAllowed(rootDir, prefix, path = defaultConfigPath()) {
57
+ const trimmed = prefix.trim();
58
+ if (!trimmed) return;
59
+ const cfg = readConfig(path);
60
+ if (!cfg.projects) cfg.projects = {};
61
+ if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
62
+ const existing = cfg.projects[rootDir].shellAllowed ?? [];
63
+ if (existing.includes(trimmed)) return;
64
+ cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
65
+ writeConfig(cfg, path);
66
+ }
47
67
  function isPlausibleKey(key) {
48
68
  const trimmed = key.trim();
49
69
  return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
@@ -103,8 +123,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
103
123
  }
104
124
  function sleep(ms, signal) {
105
125
  if (ms <= 0) return Promise.resolve();
106
- return new Promise((resolve5, reject) => {
107
- const timer = setTimeout(resolve5, ms);
126
+ return new Promise((resolve6, reject) => {
127
+ const timer = setTimeout(resolve6, ms);
108
128
  if (signal) {
109
129
  const onAbort = () => {
110
130
  clearTimeout(timer);
@@ -1544,8 +1564,8 @@ var CacheFirstLoop = class {
1544
1564
  }
1545
1565
  );
1546
1566
  for (let k = 0; k < budget; k++) {
1547
- const sample = queue.shift() ?? await new Promise((resolve5) => {
1548
- waiter = resolve5;
1567
+ const sample = queue.shift() ?? await new Promise((resolve6) => {
1568
+ waiter = resolve6;
1549
1569
  });
1550
1570
  yield {
1551
1571
  turn: this._turn,
@@ -2239,6 +2259,221 @@ function lineDiff(a, b) {
2239
2259
  return out;
2240
2260
  }
2241
2261
 
2262
+ // src/tools/shell.ts
2263
+ import { spawn } from "child_process";
2264
+ import * as pathMod2 from "path";
2265
+ var DEFAULT_TIMEOUT_SEC = 60;
2266
+ var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
2267
+ var BUILTIN_ALLOWLIST = [
2268
+ // Repo inspection
2269
+ "git status",
2270
+ "git diff",
2271
+ "git log",
2272
+ "git show",
2273
+ "git blame",
2274
+ "git branch",
2275
+ "git remote",
2276
+ "git rev-parse",
2277
+ "git config --get",
2278
+ // Filesystem inspection
2279
+ "ls",
2280
+ "pwd",
2281
+ "cat",
2282
+ "head",
2283
+ "tail",
2284
+ "wc",
2285
+ "file",
2286
+ "tree",
2287
+ "find",
2288
+ "grep",
2289
+ "rg",
2290
+ // Language version probes
2291
+ "node --version",
2292
+ "node -v",
2293
+ "npm --version",
2294
+ "npx --version",
2295
+ "python --version",
2296
+ "python3 --version",
2297
+ "cargo --version",
2298
+ "go version",
2299
+ "rustc --version",
2300
+ "deno --version",
2301
+ "bun --version",
2302
+ // Test runners (non-destructive by convention)
2303
+ "npm test",
2304
+ "npm run test",
2305
+ "npx vitest run",
2306
+ "npx vitest",
2307
+ "npx jest",
2308
+ "pytest",
2309
+ "python -m pytest",
2310
+ "cargo test",
2311
+ "cargo check",
2312
+ "cargo clippy",
2313
+ "go test",
2314
+ "go vet",
2315
+ "deno test",
2316
+ "bun test",
2317
+ // Linters / typecheckers (read-only by convention)
2318
+ "npm run lint",
2319
+ "npm run typecheck",
2320
+ "npx tsc --noEmit",
2321
+ "npx biome check",
2322
+ "npx eslint",
2323
+ "npx prettier --check",
2324
+ "ruff",
2325
+ "mypy"
2326
+ ];
2327
+ function tokenizeCommand(cmd) {
2328
+ const out = [];
2329
+ let cur = "";
2330
+ let quote = null;
2331
+ for (let i = 0; i < cmd.length; i++) {
2332
+ const ch = cmd[i];
2333
+ if (quote) {
2334
+ if (ch === quote) {
2335
+ quote = null;
2336
+ } else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
2337
+ cur += cmd[++i];
2338
+ } else {
2339
+ cur += ch;
2340
+ }
2341
+ continue;
2342
+ }
2343
+ if (ch === '"' || ch === "'") {
2344
+ quote = ch;
2345
+ continue;
2346
+ }
2347
+ if (ch === " " || ch === " ") {
2348
+ if (cur.length > 0) {
2349
+ out.push(cur);
2350
+ cur = "";
2351
+ }
2352
+ continue;
2353
+ }
2354
+ cur += ch;
2355
+ }
2356
+ if (quote) throw new Error(`unclosed ${quote} in command`);
2357
+ if (cur.length > 0) out.push(cur);
2358
+ return out;
2359
+ }
2360
+ function isAllowed(cmd, extra = []) {
2361
+ const normalized = cmd.trim().replace(/\s+/g, " ");
2362
+ const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
2363
+ for (const prefix of allowlist) {
2364
+ if (normalized === prefix) return true;
2365
+ if (normalized.startsWith(`${prefix} `)) return true;
2366
+ }
2367
+ return false;
2368
+ }
2369
+ async function runCommand(cmd, opts) {
2370
+ const argv = tokenizeCommand(cmd);
2371
+ if (argv.length === 0) throw new Error("run_command: empty command");
2372
+ const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
2373
+ const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2374
+ const spawnOpts = {
2375
+ cwd: opts.cwd,
2376
+ shell: false,
2377
+ // no shell-expansion — see header comment
2378
+ windowsHide: true,
2379
+ env: process.env
2380
+ };
2381
+ return await new Promise((resolve6, reject) => {
2382
+ let child;
2383
+ try {
2384
+ child = spawn(argv[0], argv.slice(1), spawnOpts);
2385
+ } catch (err) {
2386
+ reject(err);
2387
+ return;
2388
+ }
2389
+ let buf = "";
2390
+ let timedOut = false;
2391
+ const killTimer = setTimeout(() => {
2392
+ timedOut = true;
2393
+ child.kill("SIGKILL");
2394
+ }, timeoutMs);
2395
+ const onAbort = () => child.kill("SIGKILL");
2396
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
2397
+ const onData = (chunk) => {
2398
+ buf += chunk.toString();
2399
+ if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
2400
+ };
2401
+ child.stdout?.on("data", onData);
2402
+ child.stderr?.on("data", onData);
2403
+ child.on("error", (err) => {
2404
+ clearTimeout(killTimer);
2405
+ opts.signal?.removeEventListener("abort", onAbort);
2406
+ reject(err);
2407
+ });
2408
+ child.on("close", (code) => {
2409
+ clearTimeout(killTimer);
2410
+ opts.signal?.removeEventListener("abort", onAbort);
2411
+ const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
2412
+
2413
+ [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
2414
+ resolve6({ exitCode: code, output, timedOut });
2415
+ });
2416
+ });
2417
+ }
2418
+ var NeedsConfirmationError = class extends Error {
2419
+ command;
2420
+ constructor(command) {
2421
+ super(
2422
+ `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.`
2423
+ );
2424
+ this.name = "NeedsConfirmationError";
2425
+ this.command = command;
2426
+ }
2427
+ };
2428
+ function registerShellTools(registry, opts) {
2429
+ const rootDir = pathMod2.resolve(opts.rootDir);
2430
+ const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
2431
+ const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2432
+ const extraAllowed = opts.extraAllowed ?? [];
2433
+ const allowAll = opts.allowAll ?? false;
2434
+ registry.register({
2435
+ name: "run_command",
2436
+ 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.",
2437
+ parameters: {
2438
+ type: "object",
2439
+ properties: {
2440
+ command: {
2441
+ type: "string",
2442
+ 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."
2443
+ },
2444
+ timeoutSec: {
2445
+ type: "integer",
2446
+ description: `Override the default ${timeoutSec}s timeout for a single command.`
2447
+ }
2448
+ },
2449
+ required: ["command"]
2450
+ },
2451
+ fn: async (args, ctx) => {
2452
+ const cmd = args.command.trim();
2453
+ if (!cmd) throw new Error("run_command: empty command");
2454
+ if (!allowAll && !isAllowed(cmd, extraAllowed)) {
2455
+ throw new NeedsConfirmationError(cmd);
2456
+ }
2457
+ const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
2458
+ const result = await runCommand(cmd, {
2459
+ cwd: rootDir,
2460
+ timeoutSec: effectiveTimeout,
2461
+ maxOutputChars,
2462
+ signal: ctx?.signal
2463
+ });
2464
+ return formatCommandResult(cmd, result);
2465
+ }
2466
+ });
2467
+ return registry;
2468
+ }
2469
+ function formatCommandResult(cmd, r) {
2470
+ const header = r.timedOut ? `$ ${cmd}
2471
+ [killed after timeout]` : `$ ${cmd}
2472
+ [exit ${r.exitCode ?? "?"}]`;
2473
+ return r.output ? `${header}
2474
+ ${r.output}` : header;
2475
+ }
2476
+
2242
2477
  // src/tools/web.ts
2243
2478
  var DEFAULT_FETCH_MAX_CHARS = 32e3;
2244
2479
  var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
@@ -2422,11 +2657,11 @@ ${i + 1}. ${r.title}`);
2422
2657
 
2423
2658
  // src/env.ts
2424
2659
  import { readFileSync as readFileSync3 } from "fs";
2425
- import { resolve as resolve2 } from "path";
2660
+ import { resolve as resolve3 } from "path";
2426
2661
  function loadDotenv(path = ".env") {
2427
2662
  let raw;
2428
2663
  try {
2429
- raw = readFileSync3(resolve2(process.cwd(), path), "utf8");
2664
+ raw = readFileSync3(resolve3(process.cwd(), path), "utf8");
2430
2665
  } catch {
2431
2666
  return;
2432
2667
  }
@@ -3139,7 +3374,7 @@ var McpClient = class {
3139
3374
  const id = this.nextId++;
3140
3375
  const frame = { jsonrpc: "2.0", id, method, params };
3141
3376
  let abortHandler = null;
3142
- const promise = new Promise((resolve5, reject) => {
3377
+ const promise = new Promise((resolve6, reject) => {
3143
3378
  const timeout = setTimeout(() => {
3144
3379
  this.pending.delete(id);
3145
3380
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -3148,7 +3383,7 @@ var McpClient = class {
3148
3383
  );
3149
3384
  }, this.requestTimeoutMs);
3150
3385
  this.pending.set(id, {
3151
- resolve: resolve5,
3386
+ resolve: resolve6,
3152
3387
  reject,
3153
3388
  timeout
3154
3389
  });
@@ -3230,7 +3465,7 @@ var McpClient = class {
3230
3465
  };
3231
3466
 
3232
3467
  // src/mcp/stdio.ts
3233
- import { spawn } from "child_process";
3468
+ import { spawn as spawn2 } from "child_process";
3234
3469
  var StdioTransport = class {
3235
3470
  child;
3236
3471
  queue = [];
@@ -3245,14 +3480,14 @@ var StdioTransport = class {
3245
3480
  opts.command,
3246
3481
  ...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
3247
3482
  ].join(" ");
3248
- this.child = spawn(line, [], {
3483
+ this.child = spawn2(line, [], {
3249
3484
  env,
3250
3485
  cwd: opts.cwd,
3251
3486
  stdio: ["pipe", "pipe", "inherit"],
3252
3487
  shell: true
3253
3488
  });
3254
3489
  } else {
3255
- this.child = spawn(opts.command, opts.args ?? [], {
3490
+ this.child = spawn2(opts.command, opts.args ?? [], {
3256
3491
  env,
3257
3492
  cwd: opts.cwd,
3258
3493
  stdio: ["pipe", "pipe", "inherit"]
@@ -3271,12 +3506,12 @@ var StdioTransport = class {
3271
3506
  }
3272
3507
  async send(message) {
3273
3508
  if (this.closed) throw new Error("MCP transport is closed");
3274
- return new Promise((resolve5, reject) => {
3509
+ return new Promise((resolve6, reject) => {
3275
3510
  const line = `${JSON.stringify(message)}
3276
3511
  `;
3277
3512
  this.child.stdin.write(line, "utf8", (err) => {
3278
3513
  if (err) reject(err);
3279
- else resolve5();
3514
+ else resolve6();
3280
3515
  });
3281
3516
  });
3282
3517
  }
@@ -3287,8 +3522,8 @@ var StdioTransport = class {
3287
3522
  continue;
3288
3523
  }
3289
3524
  if (this.closed) return;
3290
- const next = await new Promise((resolve5) => {
3291
- this.waiters.push(resolve5);
3525
+ const next = await new Promise((resolve6) => {
3526
+ this.waiters.push(resolve6);
3292
3527
  });
3293
3528
  if (next === null) return;
3294
3529
  yield next;
@@ -3354,8 +3589,8 @@ var SseTransport = class {
3354
3589
  constructor(opts) {
3355
3590
  this.url = opts.url;
3356
3591
  this.headers = opts.headers ?? {};
3357
- this.endpointReady = new Promise((resolve5, reject) => {
3358
- this.resolveEndpoint = resolve5;
3592
+ this.endpointReady = new Promise((resolve6, reject) => {
3593
+ this.resolveEndpoint = resolve6;
3359
3594
  this.rejectEndpoint = reject;
3360
3595
  });
3361
3596
  this.endpointReady.catch(() => void 0);
@@ -3382,8 +3617,8 @@ var SseTransport = class {
3382
3617
  continue;
3383
3618
  }
3384
3619
  if (this.closed) return;
3385
- const next = await new Promise((resolve5) => {
3386
- this.waiters.push(resolve5);
3620
+ const next = await new Promise((resolve6) => {
3621
+ this.waiters.push(resolve6);
3387
3622
  });
3388
3623
  if (next === null) return;
3389
3624
  yield next;
@@ -3583,7 +3818,7 @@ async function trySection(load) {
3583
3818
 
3584
3819
  // src/code/edit-blocks.ts
3585
3820
  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";
3821
+ import { dirname as dirname4, resolve as resolve4 } from "path";
3587
3822
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
3588
3823
  function parseEditBlocks(text) {
3589
3824
  const out = [];
@@ -3601,8 +3836,8 @@ function parseEditBlocks(text) {
3601
3836
  return out;
3602
3837
  }
3603
3838
  function applyEditBlock(block, rootDir) {
3604
- const absRoot = resolve3(rootDir);
3605
- const absTarget = resolve3(absRoot, block.path);
3839
+ const absRoot = resolve4(rootDir);
3840
+ const absTarget = resolve4(absRoot, block.path);
3606
3841
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
3607
3842
  return {
3608
3843
  path: block.path,
@@ -3652,13 +3887,13 @@ function applyEditBlocks(blocks, rootDir) {
3652
3887
  return blocks.map((b) => applyEditBlock(b, rootDir));
3653
3888
  }
3654
3889
  function snapshotBeforeEdits(blocks, rootDir) {
3655
- const absRoot = resolve3(rootDir);
3890
+ const absRoot = resolve4(rootDir);
3656
3891
  const seen = /* @__PURE__ */ new Set();
3657
3892
  const snapshots = [];
3658
3893
  for (const b of blocks) {
3659
3894
  if (seen.has(b.path)) continue;
3660
3895
  seen.add(b.path);
3661
- const abs = resolve3(absRoot, b.path);
3896
+ const abs = resolve4(absRoot, b.path);
3662
3897
  if (!existsSync2(abs)) {
3663
3898
  snapshots.push({ path: b.path, prevContent: null });
3664
3899
  continue;
@@ -3672,9 +3907,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
3672
3907
  return snapshots;
3673
3908
  }
3674
3909
  function restoreSnapshots(snapshots, rootDir) {
3675
- const absRoot = resolve3(rootDir);
3910
+ const absRoot = resolve4(rootDir);
3676
3911
  return snapshots.map((snap) => {
3677
- const abs = resolve3(absRoot, snap.path);
3912
+ const abs = resolve4(absRoot, snap.path);
3678
3913
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
3679
3914
  return {
3680
3915
  path: snap.path,
@@ -3707,15 +3942,16 @@ function sep() {
3707
3942
  }
3708
3943
 
3709
3944
  // src/index.ts
3710
- var VERSION = "0.4.15";
3945
+ var VERSION = "0.4.17";
3711
3946
 
3712
3947
  // src/cli/commands/chat.tsx
3948
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
3713
3949
  import { render } from "ink";
3714
- import React10, { useState as useState5 } from "react";
3950
+ import React13, { useState as useState6 } from "react";
3715
3951
 
3716
3952
  // 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";
3953
+ import { Box as Box9, Static, Text as Text9, useApp, useInput as useInput3 } from "ink";
3954
+ import React10, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
3719
3955
 
3720
3956
  // src/cli/ui/EventLog.tsx
3721
3957
  import { Box as Box3, Text as Text3 } from "ink";
@@ -3788,7 +4024,7 @@ function stripMath(s) {
3788
4024
  (_m, n, k) => `C(${n.trim()},${k.trim()})`
3789
4025
  ).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
4026
  }
3791
- var INLINE_RE = /(\*\*([^*\n]+?)\*\*|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
4027
+ var INLINE_RE = /(\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
3792
4028
  function InlineMd({ text }) {
3793
4029
  const parts = [];
3794
4030
  let last = 0;
@@ -3803,12 +4039,17 @@ function InlineMd({ text }) {
3803
4039
  /* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[2])
3804
4040
  );
3805
4041
  } else if (m[3] !== void 0) {
4042
+ const stripped = m[3].replace(/^(\w+)\s+/, "");
3806
4043
  parts.push(
3807
- /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[3])
4044
+ /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, stripped)
3808
4045
  );
3809
4046
  } else if (m[4] !== void 0) {
3810
4047
  parts.push(
3811
- /* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[4])
4048
+ /* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[4])
4049
+ );
4050
+ } else if (m[5] !== void 0) {
4051
+ parts.push(
4052
+ /* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[5])
3812
4053
  );
3813
4054
  }
3814
4055
  last = start + m[0].length;
@@ -3826,6 +4067,7 @@ function parseBlocks(raw) {
3826
4067
  let codeLang = "";
3827
4068
  let codeBuf = [];
3828
4069
  let listBuf = null;
4070
+ let codeFence = "";
3829
4071
  const flushPara = () => {
3830
4072
  if (para.length) {
3831
4073
  out.push({ kind: "paragraph", text: para.join(" ") });
@@ -3871,22 +4113,37 @@ function parseBlocks(raw) {
3871
4113
  para.push(filename);
3872
4114
  }
3873
4115
  }
3874
- const fence = line.match(/^```(\w*)/);
3875
- if (fence) {
3876
- if (inCode) {
4116
+ if (!inCode) {
4117
+ const open = line.match(/^ {0,3}(`{3,})(\w*)\s*(.*)$/);
4118
+ if (open) {
4119
+ const fence = open[1];
4120
+ const lang = open[2] ?? "";
4121
+ const rest = open[3] ?? "";
4122
+ const closeOnSame = rest.match(new RegExp(`^(.*?)${fence}\\s*$`));
4123
+ if (closeOnSame) {
4124
+ flushPara();
4125
+ flushList();
4126
+ out.push({ kind: "code", lang, text: (closeOnSame[1] ?? "").trim() });
4127
+ continue;
4128
+ }
4129
+ flushPara();
4130
+ flushList();
4131
+ inCode = true;
4132
+ codeLang = lang;
4133
+ codeFence = fence;
4134
+ if (rest.length > 0) codeBuf.push(rest);
4135
+ continue;
4136
+ }
4137
+ } else {
4138
+ const close = line.match(/^ {0,3}(`{3,})\s*$/);
4139
+ if (close && close[1].length >= codeFence.length) {
3877
4140
  out.push({ kind: "code", lang: codeLang, text: codeBuf.join("\n") });
3878
4141
  codeBuf = [];
3879
4142
  codeLang = "";
4143
+ codeFence = "";
3880
4144
  inCode = false;
3881
- } else {
3882
- flushPara();
3883
- flushList();
3884
- inCode = true;
3885
- codeLang = fence[1] ?? "";
4145
+ continue;
3886
4146
  }
3887
- continue;
3888
- }
3889
- if (inCode) {
3890
4147
  codeBuf.push(rawLine);
3891
4148
  continue;
3892
4149
  }
@@ -4214,7 +4471,7 @@ function processMultilineKey(value, cursor, key) {
4214
4471
  }
4215
4472
  return { next: null, cursor: null, submit: true, submitValue: value };
4216
4473
  }
4217
- if (key.backspace) {
4474
+ if (key.backspace || key.delete || key.input === "\x7F" || key.input === "\b") {
4218
4475
  if (cursor === 0) return NOOP;
4219
4476
  return {
4220
4477
  next: value.slice(0, cursor - 1) + value.slice(cursor),
@@ -4222,14 +4479,6 @@ function processMultilineKey(value, cursor, key) {
4222
4479
  submit: false
4223
4480
  };
4224
4481
  }
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
4482
  if ((key.ctrl || key.meta) && key.input.length === 0) return NOOP;
4234
4483
  if (key.ctrl || key.meta) return NOOP;
4235
4484
  if (key.input.length > 0) {
@@ -4371,16 +4620,177 @@ function LineWithCursor({
4371
4620
  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
4621
  }
4373
4622
 
4623
+ // src/cli/ui/ShellConfirm.tsx
4624
+ import { Box as Box6, Text as Text6 } from "ink";
4625
+ import React7 from "react";
4626
+
4627
+ // src/cli/ui/Select.tsx
4628
+ import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
4629
+ import React6, { useState as useState3 } from "react";
4630
+ function SingleSelect({
4631
+ items,
4632
+ initialValue,
4633
+ onSubmit,
4634
+ onCancel
4635
+ }) {
4636
+ const initialIndex = Math.max(
4637
+ 0,
4638
+ items.findIndex((i) => i.value === initialValue && !i.disabled)
4639
+ );
4640
+ const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
4641
+ useInput2((_input, key) => {
4642
+ if (key.upArrow) {
4643
+ setIndex((i) => findNextEnabled(items, i, -1));
4644
+ } else if (key.downArrow) {
4645
+ setIndex((i) => findNextEnabled(items, i, 1));
4646
+ } else if (key.return) {
4647
+ const chosen = items[index];
4648
+ if (chosen && !chosen.disabled) onSubmit(chosen.value);
4649
+ } else if (key.escape && onCancel) {
4650
+ onCancel();
4651
+ }
4652
+ });
4653
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
4654
+ SelectRow,
4655
+ {
4656
+ key: item.value,
4657
+ item,
4658
+ active: i === index,
4659
+ marker: i === index ? "\u25B8" : " "
4660
+ }
4661
+ )));
4662
+ }
4663
+ function MultiSelect({
4664
+ items,
4665
+ initialSelected = [],
4666
+ onSubmit,
4667
+ onCancel,
4668
+ footer
4669
+ }) {
4670
+ const [index, setIndex] = useState3(() => {
4671
+ const first = items.findIndex((i) => !i.disabled);
4672
+ return first === -1 ? 0 : first;
4673
+ });
4674
+ const [selected, setSelected] = useState3(new Set(initialSelected));
4675
+ useInput2((input, key) => {
4676
+ if (key.upArrow) {
4677
+ setIndex((i) => findNextEnabled(items, i, -1));
4678
+ } else if (key.downArrow) {
4679
+ setIndex((i) => findNextEnabled(items, i, 1));
4680
+ } else if (input === " ") {
4681
+ const item = items[index];
4682
+ if (!item || item.disabled) return;
4683
+ setSelected((prev) => {
4684
+ const next = new Set(prev);
4685
+ if (next.has(item.value)) next.delete(item.value);
4686
+ else next.add(item.value);
4687
+ return next;
4688
+ });
4689
+ } else if (key.return) {
4690
+ const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
4691
+ onSubmit(ordered);
4692
+ } else if (key.escape && onCancel) {
4693
+ onCancel();
4694
+ }
4695
+ });
4696
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
4697
+ const checked = selected.has(item.value);
4698
+ const marker = checked ? "[x]" : "[ ]";
4699
+ return /* @__PURE__ */ React6.createElement(
4700
+ SelectRow,
4701
+ {
4702
+ key: item.value,
4703
+ item,
4704
+ active: i === index,
4705
+ marker: `${i === index ? "\u25B8" : " "} ${marker}`
4706
+ }
4707
+ );
4708
+ }), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
4709
+ }
4710
+ function SelectRow({
4711
+ item,
4712
+ active,
4713
+ marker
4714
+ }) {
4715
+ const color = item.disabled ? "gray" : active ? "cyan" : void 0;
4716
+ 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);
4717
+ }
4718
+ function findNextEnabled(items, from, step) {
4719
+ if (items.length === 0) return 0;
4720
+ let i = from;
4721
+ for (let tries = 0; tries < items.length; tries++) {
4722
+ i = (i + step + items.length) % items.length;
4723
+ if (!items[i]?.disabled) return i;
4724
+ }
4725
+ return from;
4726
+ }
4727
+
4728
+ // src/cli/ui/ShellConfirm.tsx
4729
+ function ShellConfirm({ command, allowPrefix, onChoose }) {
4730
+ 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(
4731
+ SingleSelect,
4732
+ {
4733
+ initialValue: "run_once",
4734
+ items: [
4735
+ {
4736
+ value: "run_once",
4737
+ label: "Run once",
4738
+ hint: "Execute this command, don't remember it."
4739
+ },
4740
+ {
4741
+ value: "always_allow",
4742
+ label: `Always allow "${allowPrefix}" in this project`,
4743
+ hint: "Save the prefix to ~/.reasonix/config.json; future matches auto-run."
4744
+ },
4745
+ {
4746
+ value: "deny",
4747
+ label: "Deny",
4748
+ hint: "Tell the model the user refused; it will continue without this command."
4749
+ }
4750
+ ],
4751
+ onSubmit: (v) => onChoose(v)
4752
+ }
4753
+ )));
4754
+ }
4755
+ function derivePrefix(command) {
4756
+ const tokens = command.trim().split(/\s+/).filter(Boolean);
4757
+ if (tokens.length === 0) return "";
4758
+ if (tokens.length === 1) return tokens[0];
4759
+ const first = tokens[0];
4760
+ const TWO_TOKEN_WRAPPERS = /* @__PURE__ */ new Set([
4761
+ "npm",
4762
+ "npx",
4763
+ "pnpm",
4764
+ "yarn",
4765
+ "bun",
4766
+ "git",
4767
+ "cargo",
4768
+ "go",
4769
+ "docker",
4770
+ "kubectl",
4771
+ "python",
4772
+ "python3",
4773
+ "deno",
4774
+ "pip",
4775
+ "pip3",
4776
+ "make",
4777
+ "rake",
4778
+ "bundle",
4779
+ "gem"
4780
+ ]);
4781
+ return TWO_TOKEN_WRAPPERS.has(first) ? `${first} ${tokens[1]}` : first;
4782
+ }
4783
+
4374
4784
  // src/cli/ui/SlashSuggestions.tsx
4375
- import { Box as Box5, Text as Text5 } from "ink";
4376
- import React6 from "react";
4785
+ import { Box as Box7, Text as Text7 } from "ink";
4786
+ import React8 from "react";
4377
4787
  function SlashSuggestions({
4378
4788
  matches,
4379
4789
  selectedIndex
4380
4790
  }) {
4381
4791
  if (matches === null) return null;
4382
4792
  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"));
4793
+ 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
4794
  }
4385
4795
  const MAX = 8;
4386
4796
  const total = matches.length;
@@ -4388,21 +4798,21 @@ function SlashSuggestions({
4388
4798
  const shown = matches.slice(windowStart, windowStart + MAX);
4389
4799
  const hiddenAbove = windowStart;
4390
4800
  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"));
4801
+ 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
4802
  }
4393
4803
  function SuggestionRow({ spec, isSelected }) {
4394
4804
  const marker = isSelected ? "\u25B8" : " ";
4395
4805
  const name = `/${spec.cmd}`;
4396
4806
  const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
4397
4807
  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));
4808
+ 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
4809
  }
4400
- return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
4810
+ return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
4401
4811
  }
4402
4812
 
4403
4813
  // src/cli/ui/StatsPanel.tsx
4404
- import { Box as Box6, Text as Text6 } from "ink";
4405
- import React7 from "react";
4814
+ import { Box as Box8, Text as Text8 } from "ink";
4815
+ import React9 from "react";
4406
4816
  function StatsPanel({
4407
4817
  summary,
4408
4818
  model,
@@ -4417,7 +4827,7 @@ function StatsPanel({
4417
4827
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
4418
4828
  const ctxRatio = summary.lastPromptTokens / ctxMax;
4419
4829
  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));
4830
+ 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
4831
  }
4422
4832
  function formatTokens(n) {
4423
4833
  if (n < 1e3) return String(n);
@@ -4440,6 +4850,7 @@ var SLASH_COMMANDS = [
4440
4850
  { cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
4441
4851
  { cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
4442
4852
  { cmd: "tool", argsHint: "[N]", summary: "dump full output of the Nth tool call (1=latest)" },
4853
+ { cmd: "memory", summary: "show the project's REASONIX.md (pinned into the system prompt)" },
4443
4854
  { cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
4444
4855
  { cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
4445
4856
  { cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
@@ -4508,6 +4919,7 @@ function handleSlash(cmd, args, loop, ctx = {}) {
4508
4919
  " /compact [cap] shrink large tool results in history (default 4k/result)",
4509
4920
  " /think dump the most recent turn's full R1 reasoning (reasoner only)",
4510
4921
  " /tool [N] list tool calls (or dump full output of #N, 1=most recent)",
4922
+ " /memory show the project's REASONIX.md (pinned into the system prompt)",
4511
4923
  " /retry truncate & resend your last message (fresh sample from the model)",
4512
4924
  " /apply (code mode) commit the pending edit blocks to disk",
4513
4925
  " /discard (code mode) drop pending edits without writing",
@@ -4589,6 +5001,45 @@ function handleSlash(cmd, args, loop, ctx = {}) {
4589
5001
  resubmit: prev
4590
5002
  };
4591
5003
  }
5004
+ case "memory": {
5005
+ if (!memoryEnabled()) {
5006
+ return {
5007
+ info: "project memory is disabled (REASONIX_MEMORY=off in env). Unset the var to re-enable; no REASONIX.md will be pinned in the meantime."
5008
+ };
5009
+ }
5010
+ if (!ctx.memoryRoot) {
5011
+ return {
5012
+ info: "no project root on this session \u2014 `/memory` needs a working directory to resolve REASONIX.md from."
5013
+ };
5014
+ }
5015
+ const mem = readProjectMemory(ctx.memoryRoot);
5016
+ if (!mem) {
5017
+ return {
5018
+ info: [
5019
+ `no ${PROJECT_MEMORY_FILE} in ${ctx.memoryRoot}.`,
5020
+ "",
5021
+ "Project memory is an optional file you pin notes into \u2014 project conventions,",
5022
+ "things the model keeps forgetting, domain glossary, setup gotchas. When present,",
5023
+ "its contents are appended to the system prompt (the immutable-prefix region)",
5024
+ "so every turn sees it without eating per-turn context, and the prefix cache stays",
5025
+ "warm as long as the file is stable.",
5026
+ "",
5027
+ `Create it with: echo "# Project notes for Reasonix" > ${PROJECT_MEMORY_FILE}`,
5028
+ "Re-launch (or `/new`) to pick up changes \u2014 the prefix is hashed at session start."
5029
+ ].join("\n")
5030
+ };
5031
+ }
5032
+ const header = mem.truncated ? `\u25B8 project memory: ${mem.path} (${mem.originalChars.toLocaleString()} chars, truncated for the prefix)` : `\u25B8 project memory: ${mem.path} (${mem.originalChars.toLocaleString()} chars)`;
5033
+ return {
5034
+ info: [
5035
+ header,
5036
+ "",
5037
+ mem.content,
5038
+ "",
5039
+ "Changes take effect on the next launch or `/new` \u2014 the system prompt is hashed once per session to keep the prefix cache warm."
5040
+ ].join("\n")
5041
+ };
5042
+ }
4592
5043
  case "think":
4593
5044
  case "reasoning": {
4594
5045
  const raw = loop.scratch.reasoning;
@@ -4881,23 +5332,25 @@ function App({
4881
5332
  codeMode
4882
5333
  }) {
4883
5334
  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);
5335
+ const [historical, setHistorical] = useState4([]);
5336
+ const [streaming, setStreaming] = useState4(null);
5337
+ const [input, setInput] = useState4("");
5338
+ const [busy, setBusy] = useState4(false);
4888
5339
  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);
5340
+ const [ongoingTool, setOngoingTool] = useState4(null);
5341
+ const [toolProgress, setToolProgress] = useState4(null);
5342
+ const [statusLine, setStatusLine] = useState4(null);
5343
+ const [balance, setBalance] = useState4(null);
4893
5344
  const lastEditSnapshots = useRef2(null);
4894
5345
  const pendingEdits = useRef2([]);
5346
+ const [pendingShell, setPendingShell] = useState4(null);
5347
+ const [queuedSubmit, setQueuedSubmit] = useState4(null);
4895
5348
  const promptHistory = useRef2([]);
4896
5349
  const historyCursor = useRef2(-1);
4897
5350
  const assistantIterCounter = useRef2(0);
4898
5351
  const toolHistoryRef = useRef2([]);
4899
- const [slashSelected, setSlashSelected] = useState3(0);
4900
- const [summary, setSummary] = useState3({
5352
+ const [slashSelected, setSlashSelected] = useState4(0);
5353
+ const [summary, setSummary] = useState4({
4901
5354
  turns: 0,
4902
5355
  totalCostUsd: 0,
4903
5356
  totalInputCostUsd: 0,
@@ -5002,7 +5455,7 @@ function App({
5002
5455
  ]);
5003
5456
  }
5004
5457
  }, [session, loop]);
5005
- useInput2((_input, key) => {
5458
+ useInput3((_input, key) => {
5006
5459
  if (key.escape && busy) {
5007
5460
  if (abortedThisTurn.current) return;
5008
5461
  abortedThisTurn.current = true;
@@ -5010,6 +5463,7 @@ function App({
5010
5463
  return;
5011
5464
  }
5012
5465
  if (busy) return;
5466
+ if (pendingShell) return;
5013
5467
  if (slashMatches && slashMatches.length > 0) {
5014
5468
  if (key.upArrow) {
5015
5469
  setSlashSelected((i) => Math.max(0, i - 1));
@@ -5112,7 +5566,8 @@ function App({
5112
5566
  codeDiscard: codeMode ? codeDiscard : void 0,
5113
5567
  codeRoot: codeMode?.rootDir,
5114
5568
  pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
5115
- toolHistory: () => toolHistoryRef.current
5569
+ toolHistory: () => toolHistoryRef.current,
5570
+ memoryRoot: codeMode?.rootDir ?? process.cwd()
5116
5571
  });
5117
5572
  if (result.exit) {
5118
5573
  transcriptRef.current?.end();
@@ -5279,6 +5734,15 @@ function App({
5279
5734
  toolName: ev.toolName
5280
5735
  }
5281
5736
  ]);
5737
+ if (codeMode && ev.toolName === "run_command" && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
5738
+ try {
5739
+ const parsed = JSON.parse(ev.toolArgs);
5740
+ if (typeof parsed.command === "string" && parsed.command.trim()) {
5741
+ setPendingShell(parsed.command.trim());
5742
+ }
5743
+ } catch {
5744
+ }
5745
+ }
5282
5746
  } else if (ev.role === "error") {
5283
5747
  setHistorical((prev) => [
5284
5748
  ...prev,
@@ -5323,7 +5787,68 @@ function App({
5323
5787
  writeTranscript
5324
5788
  ]
5325
5789
  );
5326
- return /* @__PURE__ */ React8.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(
5790
+ const handleShellConfirm = useCallback(
5791
+ async (choice) => {
5792
+ const cmd = pendingShell;
5793
+ if (!cmd || !codeMode) return;
5794
+ setPendingShell(null);
5795
+ let synthetic;
5796
+ if (choice === "deny") {
5797
+ setHistorical((prev) => [
5798
+ ...prev,
5799
+ { id: `sh-deny-${Date.now()}`, role: "info", text: `\u25B8 denied: ${cmd}` }
5800
+ ]);
5801
+ synthetic = `I denied running \`${cmd}\`. Please continue without running it.`;
5802
+ } else {
5803
+ if (choice === "always_allow") {
5804
+ const prefix = derivePrefix(cmd);
5805
+ addProjectShellAllowed(codeMode.rootDir, prefix);
5806
+ setHistorical((prev) => [
5807
+ ...prev,
5808
+ {
5809
+ id: `sh-allow-${Date.now()}`,
5810
+ role: "info",
5811
+ text: `\u25B8 always allowed "${prefix}" for ${codeMode.rootDir}`
5812
+ }
5813
+ ]);
5814
+ }
5815
+ setHistorical((prev) => [
5816
+ ...prev,
5817
+ { id: `sh-run-${Date.now()}`, role: "info", text: `\u25B8 running: ${cmd}` }
5818
+ ]);
5819
+ let body;
5820
+ try {
5821
+ const res = await runCommand(cmd, { cwd: codeMode.rootDir });
5822
+ body = formatCommandResult(cmd, res);
5823
+ } catch (err) {
5824
+ body = `$ ${cmd}
5825
+ [failed to spawn] ${err.message}`;
5826
+ }
5827
+ setHistorical((prev) => [
5828
+ ...prev,
5829
+ { id: `sh-out-${Date.now()}`, role: "info", text: body }
5830
+ ]);
5831
+ synthetic = `I ran the command you requested. Output:
5832
+
5833
+ ${body}`;
5834
+ }
5835
+ if (busy) {
5836
+ loop.abort();
5837
+ setQueuedSubmit(synthetic);
5838
+ } else {
5839
+ await handleSubmit(synthetic);
5840
+ }
5841
+ },
5842
+ [pendingShell, codeMode, handleSubmit, busy, loop]
5843
+ );
5844
+ useEffect2(() => {
5845
+ if (!busy && queuedSubmit !== null) {
5846
+ const text = queuedSubmit;
5847
+ setQueuedSubmit(null);
5848
+ void handleSubmit(text);
5849
+ }
5850
+ }, [busy, queuedSubmit, handleSubmit]);
5851
+ return /* @__PURE__ */ React10.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(
5327
5852
  StatsPanel,
5328
5853
  {
5329
5854
  summary,
@@ -5333,13 +5858,28 @@ function App({
5333
5858
  branchBudget: loop.branchOptions.budget,
5334
5859
  balance
5335
5860
  }
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 })));
5861
+ ), /* @__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(
5862
+ ShellConfirm,
5863
+ {
5864
+ command: pendingShell,
5865
+ allowPrefix: derivePrefix(pendingShell),
5866
+ onChoose: handleShellConfirm
5867
+ }
5868
+ ) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
5869
+ PromptInput,
5870
+ {
5871
+ value: input,
5872
+ onChange: setInput,
5873
+ onSubmit: handleSubmit,
5874
+ disabled: busy
5875
+ }
5876
+ ), /* @__PURE__ */ React10.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
5337
5877
  }
5338
5878
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5339
5879
  function StatusRow({ text }) {
5340
5880
  const tick = useTick();
5341
5881
  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`));
5882
+ 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
5883
  }
5344
5884
  function OngoingToolRow({
5345
5885
  tool,
@@ -5348,7 +5888,7 @@ function OngoingToolRow({
5348
5888
  const tick = useTick();
5349
5889
  const elapsed = useElapsedSeconds();
5350
5890
  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);
5891
+ 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
5892
  }
5353
5893
  function renderProgressLine(p) {
5354
5894
  const msg = p.message ? ` ${p.message}` : "";
@@ -5443,13 +5983,60 @@ function describeRepair(repair) {
5443
5983
  return parts.length ? `[repair] ${parts.join(", ")}` : "";
5444
5984
  }
5445
5985
 
5986
+ // src/cli/ui/SessionPicker.tsx
5987
+ import { Box as Box10, Text as Text10 } from "ink";
5988
+ import React11 from "react";
5989
+ function SessionPicker({
5990
+ sessionName,
5991
+ messageCount,
5992
+ lastActive,
5993
+ onChoose
5994
+ }) {
5995
+ 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(
5996
+ SingleSelect,
5997
+ {
5998
+ initialValue: "new",
5999
+ items: [
6000
+ {
6001
+ value: "new",
6002
+ label: "Start new conversation",
6003
+ hint: "Previous messages kept on disk; your turn starts fresh."
6004
+ },
6005
+ {
6006
+ value: "resume",
6007
+ label: "Resume",
6008
+ hint: `Continue where you left off (${messageCount} messages in context).`
6009
+ },
6010
+ {
6011
+ value: "delete",
6012
+ label: "Delete and start new",
6013
+ hint: "Wipes the session file irreversibly. Other sessions untouched."
6014
+ }
6015
+ ],
6016
+ onSubmit: (v) => onChoose(v)
6017
+ }
6018
+ ), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
6019
+ }
6020
+ function relativeTime(date) {
6021
+ const ms = Date.now() - date.getTime();
6022
+ const mins = Math.floor(ms / 6e4);
6023
+ if (mins < 1) return "just now";
6024
+ if (mins < 60) return `${mins}m ago`;
6025
+ const hours = Math.floor(mins / 60);
6026
+ if (hours < 24) return `${hours}h ago`;
6027
+ const days = Math.floor(hours / 24);
6028
+ if (days === 1) return "yesterday";
6029
+ if (days < 7) return `${days}d ago`;
6030
+ return date.toISOString().slice(0, 10);
6031
+ }
6032
+
5446
6033
  // src/cli/ui/Setup.tsx
5447
- import { Box as Box8, Text as Text8, useApp as useApp2 } from "ink";
6034
+ import { Box as Box11, Text as Text11, useApp as useApp2 } from "ink";
5448
6035
  import TextInput from "ink-text-input";
5449
- import React9, { useState as useState4 } from "react";
6036
+ import React12, { useState as useState5 } from "react";
5450
6037
  function Setup({ onReady }) {
5451
- const [value, setValue] = useState4("");
5452
- const [error, setError] = useState4(null);
6038
+ const [value, setValue] = useState5("");
6039
+ const [error, setError] = useState5(null);
5453
6040
  const { exit } = useApp2();
5454
6041
  const handleSubmit = (raw) => {
5455
6042
  const trimmed = raw.trim();
@@ -5470,7 +6057,7 @@ function Setup({ onReady }) {
5470
6057
  }
5471
6058
  onReady(trimmed);
5472
6059
  };
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(
6060
+ 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
6061
  TextInput,
5475
6062
  {
5476
6063
  value,
@@ -5479,14 +6066,23 @@ function Setup({ onReady }) {
5479
6066
  mask: "\u2022",
5480
6067
  placeholder: "sk-..."
5481
6068
  }
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.)")));
6069
+ )), 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
6070
  }
5484
6071
 
5485
6072
  // src/cli/commands/chat.tsx
5486
- function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appProps }) {
5487
- const [key, setKey] = useState5(initialKey);
6073
+ function Root({
6074
+ initialKey,
6075
+ tools,
6076
+ mcpSpecs,
6077
+ mcpServers,
6078
+ progressSink,
6079
+ sessionPreview,
6080
+ ...appProps
6081
+ }) {
6082
+ const [key, setKey] = useState6(initialKey);
6083
+ const [pending, setPending] = useState6(sessionPreview);
5488
6084
  if (!key) {
5489
- return /* @__PURE__ */ React10.createElement(
6085
+ return /* @__PURE__ */ React13.createElement(
5490
6086
  Setup,
5491
6087
  {
5492
6088
  onReady: (k) => {
@@ -5497,7 +6093,23 @@ function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appPro
5497
6093
  );
5498
6094
  }
5499
6095
  process.env.DEEPSEEK_API_KEY = key;
5500
- return /* @__PURE__ */ React10.createElement(
6096
+ if (pending && appProps.session) {
6097
+ return /* @__PURE__ */ React13.createElement(
6098
+ SessionPicker,
6099
+ {
6100
+ sessionName: appProps.session,
6101
+ messageCount: pending.messageCount,
6102
+ lastActive: pending.lastActive,
6103
+ onChoose: (choice) => {
6104
+ if (choice === "new" || choice === "delete") {
6105
+ rewriteSession(appProps.session, []);
6106
+ }
6107
+ setPending(void 0);
6108
+ }
6109
+ }
6110
+ );
6111
+ }
6112
+ return /* @__PURE__ */ React13.createElement(
5501
6113
  App,
5502
6114
  {
5503
6115
  model: appProps.model,
@@ -5584,8 +6196,19 @@ async function chatCommand(opts) {
5584
6196
  if (!tools) tools = new ToolRegistry();
5585
6197
  registerWebTools(tools);
5586
6198
  }
6199
+ let sessionPreview;
6200
+ if (opts.session && !opts.forceResume && !opts.forceNew) {
6201
+ const prior = loadSessionMessages(opts.session);
6202
+ if (prior.length > 0) {
6203
+ const p = sessionPath(opts.session);
6204
+ const mtime = existsSync3(p) ? statSync2(p).mtime : /* @__PURE__ */ new Date();
6205
+ sessionPreview = { messageCount: prior.length, lastActive: mtime };
6206
+ }
6207
+ } else if (opts.session && opts.forceNew) {
6208
+ rewriteSession(opts.session, []);
6209
+ }
5587
6210
  const { waitUntilExit } = render(
5588
- /* @__PURE__ */ React10.createElement(
6211
+ /* @__PURE__ */ React13.createElement(
5589
6212
  Root,
5590
6213
  {
5591
6214
  initialKey,
@@ -5593,6 +6216,7 @@ async function chatCommand(opts) {
5593
6216
  mcpSpecs,
5594
6217
  mcpServers,
5595
6218
  progressSink,
6219
+ sessionPreview,
5596
6220
  ...opts
5597
6221
  }
5598
6222
  ),
@@ -5608,15 +6232,21 @@ async function chatCommand(opts) {
5608
6232
  }
5609
6233
 
5610
6234
  // src/cli/commands/code.tsx
5611
- import { basename, resolve as resolve4 } from "path";
6235
+ import { basename, resolve as resolve5 } from "path";
5612
6236
  async function codeCommand(opts = {}) {
5613
- const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
5614
- const rootDir = resolve4(opts.dir ?? process.cwd());
6237
+ const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-HK5XLH55.js");
6238
+ const rootDir = resolve5(opts.dir ?? process.cwd());
5615
6239
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
5616
6240
  const tools = new ToolRegistry();
5617
6241
  registerFilesystemTools(tools, { rootDir });
6242
+ registerShellTools(tools, {
6243
+ rootDir,
6244
+ // Per-project "always allow" list persisted from prior ShellConfirm
6245
+ // choices; merged on top of the built-in allowlist in shell.ts.
6246
+ extraAllowed: loadProjectShellAllowed(rootDir)
6247
+ });
5618
6248
  process.stderr.write(
5619
- `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native fs tool(s)
6249
+ `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
5620
6250
  `
5621
6251
  );
5622
6252
  await chatCommand({
@@ -5627,7 +6257,9 @@ async function codeCommand(opts = {}) {
5627
6257
  transcript: opts.transcript,
5628
6258
  session,
5629
6259
  seedTools: tools,
5630
- codeMode: { rootDir }
6260
+ codeMode: { rootDir },
6261
+ forceResume: opts.forceResume,
6262
+ forceNew: opts.forceNew
5631
6263
  });
5632
6264
  }
5633
6265
 
@@ -5635,34 +6267,34 @@ async function codeCommand(opts = {}) {
5635
6267
  import { writeFileSync as writeFileSync4 } from "fs";
5636
6268
  import { basename as basename2 } from "path";
5637
6269
  import { render as render2 } from "ink";
5638
- import React13 from "react";
6270
+ import React16 from "react";
5639
6271
 
5640
6272
  // 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";
6273
+ import { Box as Box13, Static as Static2, Text as Text13, useApp as useApp3, useInput as useInput4 } from "ink";
6274
+ import React15, { useState as useState7 } from "react";
5643
6275
 
5644
6276
  // src/cli/ui/RecordView.tsx
5645
- import { Box as Box9, Text as Text9 } from "ink";
5646
- import React11 from "react";
6277
+ import { Box as Box12, Text as Text12 } from "ink";
6278
+ import React14 from "react";
5647
6279
  function RecordView({ rec, compact = false }) {
5648
6280
  const toolArgsMax = compact ? 120 : 200;
5649
6281
  const toolContentMax = compact ? 200 : 400;
5650
6282
  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));
6283
+ 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
6284
  }
5653
6285
  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)"));
6286
+ 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
6287
  }
5656
6288
  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)));
6289
+ 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
6290
  }
5659
6291
  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));
6292
+ 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
6293
  }
5662
6294
  if (rec.role === "done" || rec.role === "assistant_delta") {
5663
6295
  return null;
5664
6296
  }
5665
- return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text9, { dimColor: true }, "[", rec.role, "] ", rec.content));
6297
+ return /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, "[", rec.role, "] ", rec.content));
5666
6298
  }
5667
6299
  function CacheBadge({ usage }) {
5668
6300
  const hit = usage.prompt_cache_hit_tokens ?? 0;
@@ -5671,7 +6303,7 @@ function CacheBadge({ usage }) {
5671
6303
  if (total === 0) return null;
5672
6304
  const pct2 = hit / total * 100;
5673
6305
  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), "%"));
6306
+ return /* @__PURE__ */ React14.createElement(Text12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React14.createElement(Text12, { color }, pct2.toFixed(1), "%"));
5675
6307
  }
5676
6308
  function truncate3(s, max) {
5677
6309
  return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
@@ -5682,8 +6314,8 @@ function DiffApp({ report }) {
5682
6314
  const { exit } = useApp3();
5683
6315
  const maxIdx = Math.max(0, report.pairs.length - 1);
5684
6316
  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) => {
6317
+ const [idx, setIdx] = useState7(Math.max(0, initialIdx));
6318
+ useInput4((input, key) => {
5687
6319
  if (input === "q" || key.ctrl && input === "c") {
5688
6320
  exit();
5689
6321
  return;
@@ -5705,7 +6337,7 @@ function DiffApp({ report }) {
5705
6337
  }
5706
6338
  });
5707
6339
  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")));
6340
+ 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
6341
  }
5710
6342
  function DiffHeader({ report }) {
5711
6343
  const a = report.a;
@@ -5723,15 +6355,15 @@ function DiffHeader({ report }) {
5723
6355
  } else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
5724
6356
  prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
5725
6357
  }
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);
6358
+ 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
6359
  }
5728
6360
  function Pane({
5729
6361
  label,
5730
6362
  headerColor,
5731
6363
  records
5732
6364
  }) {
5733
- return /* @__PURE__ */ React12.createElement(
5734
- Box10,
6365
+ return /* @__PURE__ */ React15.createElement(
6366
+ Box13,
5735
6367
  {
5736
6368
  flexDirection: "column",
5737
6369
  flexGrow: 1,
@@ -5739,21 +6371,21 @@ function Pane({
5739
6371
  borderStyle: "single",
5740
6372
  borderColor: headerColor
5741
6373
  },
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 }))
6374
+ /* @__PURE__ */ React15.createElement(Text13, { color: headerColor, bold: true }, label),
6375
+ 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
6376
  );
5745
6377
  }
5746
6378
  function KindBadge({ kind }) {
5747
6379
  if (kind === "match") {
5748
- return /* @__PURE__ */ React12.createElement(Text10, { color: "green" }, "\u2713 match");
6380
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "green" }, "\u2713 match");
5749
6381
  }
5750
6382
  if (kind === "diverge") {
5751
- return /* @__PURE__ */ React12.createElement(Text10, { color: "yellow" }, "\u2605 diverge");
6383
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 diverge");
5752
6384
  }
5753
6385
  if (kind === "only_in_a") {
5754
- return /* @__PURE__ */ React12.createElement(Text10, { color: "blue" }, "\u2190 only in A");
6386
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, "\u2190 only in A");
5755
6387
  }
5756
- return /* @__PURE__ */ React12.createElement(Text10, { color: "magenta" }, "\u2192 only in B");
6388
+ return /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, "\u2192 only in B");
5757
6389
  }
5758
6390
  function paneRecords(pair, side) {
5759
6391
  if (!pair) return [];
@@ -5784,7 +6416,7 @@ markdown report written to ${opts.mdPath}`);
5784
6416
  return;
5785
6417
  }
5786
6418
  if (wantTui) {
5787
- const { waitUntilExit } = render2(React13.createElement(DiffApp, { report }), {
6419
+ const { waitUntilExit } = render2(React16.createElement(DiffApp, { report }), {
5788
6420
  exitOnCtrlC: true,
5789
6421
  patchConsole: false
5790
6422
  });
@@ -5925,16 +6557,16 @@ function pad(s, width) {
5925
6557
 
5926
6558
  // src/cli/commands/replay.ts
5927
6559
  import { render as render3 } from "ink";
5928
- import React15 from "react";
6560
+ import React18 from "react";
5929
6561
 
5930
6562
  // 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";
6563
+ import { Box as Box14, Static as Static3, Text as Text14, useApp as useApp4, useInput as useInput5 } from "ink";
6564
+ import React17, { useMemo as useMemo2, useState as useState8 } from "react";
5933
6565
  function ReplayApp({ meta, pages }) {
5934
6566
  const { exit } = useApp4();
5935
6567
  const maxIdx = Math.max(0, pages.length - 1);
5936
- const [idx, setIdx] = useState7(maxIdx);
5937
- useInput4((input, key) => {
6568
+ const [idx, setIdx] = useState8(maxIdx);
6569
+ useInput5((input, key) => {
5938
6570
  if (input === "q" || key.ctrl && input === "c") {
5939
6571
  exit();
5940
6572
  return;
@@ -5968,14 +6600,14 @@ function ReplayApp({ meta, pages }) {
5968
6600
  const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
5969
6601
  const currentPage = pages[idx];
5970
6602
  const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
5971
- return /* @__PURE__ */ React14.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React14.createElement(
6603
+ return /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(
5972
6604
  StatsPanel,
5973
6605
  {
5974
6606
  summary,
5975
6607
  model: cumStats.models[0] ?? meta?.model ?? "?",
5976
6608
  prefixHash
5977
6609
  }
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")));
6610
+ ), /* @__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
6611
  }
5980
6612
 
5981
6613
  // src/cli/commands/replay.ts
@@ -5987,7 +6619,7 @@ async function replayCommand(opts) {
5987
6619
  }
5988
6620
  const { parsed } = replayFromFile(opts.path);
5989
6621
  const pages = groupRecordsByTurn(parsed.records);
5990
- const { waitUntilExit } = render3(React15.createElement(ReplayApp, { meta: parsed.meta, pages }), {
6622
+ const { waitUntilExit } = render3(React18.createElement(ReplayApp, { meta: parsed.meta, pages }), {
5991
6623
  exitOnCtrlC: true,
5992
6624
  patchConsole: false
5993
6625
  });
@@ -6114,7 +6746,7 @@ async function ensureApiKey() {
6114
6746
  rl.close();
6115
6747
  }
6116
6748
  }
6117
- async function runCommand(opts) {
6749
+ async function runCommand2(opts) {
6118
6750
  loadDotenv();
6119
6751
  const apiKey = await ensureApiKey();
6120
6752
  process.env.DEEPSEEK_API_KEY = apiKey;
@@ -6289,113 +6921,12 @@ function truncate4(s, max) {
6289
6921
 
6290
6922
  // src/cli/commands/setup.tsx
6291
6923
  import { render as render4 } from "ink";
6292
- import React18 from "react";
6924
+ import React20 from "react";
6293
6925
 
6294
6926
  // src/cli/ui/Wizard.tsx
6295
- import { Box as Box13, Text as Text13, useApp as useApp5, useInput as useInput6 } from "ink";
6927
+ import { Box as Box15, Text as Text15, useApp as useApp5, useInput as useInput6 } from "ink";
6296
6928
  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
- }
6929
+ import React19, { useState as useState9 } from "react";
6399
6930
 
6400
6931
  // src/cli/ui/presets.ts
6401
6932
  var PRESETS = {
@@ -6434,7 +6965,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6434
6965
  if (key.escape && step !== "saved" && onCancel) onCancel();
6435
6966
  });
6436
6967
  if (step === "apiKey") {
6437
- return /* @__PURE__ */ React17.createElement(
6968
+ return /* @__PURE__ */ React19.createElement(
6438
6969
  ApiKeyStep,
6439
6970
  {
6440
6971
  onSubmit: (key) => {
@@ -6448,7 +6979,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6448
6979
  );
6449
6980
  }
6450
6981
  if (step === "preset") {
6451
- return /* @__PURE__ */ React17.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React17.createElement(
6982
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React19.createElement(
6452
6983
  SingleSelect,
6453
6984
  {
6454
6985
  items: presetItems(),
@@ -6458,10 +6989,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6458
6989
  setStep("mcp");
6459
6990
  }
6460
6991
  }
6461
- ), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
6992
+ ), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
6462
6993
  }
6463
6994
  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(
6995
+ return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React19.createElement(
6465
6996
  MultiSelect,
6466
6997
  {
6467
6998
  items: mcpItems(),
@@ -6486,7 +7017,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6486
7017
  }
6487
7018
  const currentName = pending[0];
6488
7019
  const entry = CATALOG_BY_NAME.get(currentName);
6489
- return /* @__PURE__ */ React17.createElement(
7020
+ return /* @__PURE__ */ React19.createElement(
6490
7021
  McpArgsStep,
6491
7022
  {
6492
7023
  entry,
@@ -6504,7 +7035,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6504
7035
  }
6505
7036
  if (step === "review") {
6506
7037
  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(
7038
+ 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
7039
  SummaryLine,
6509
7040
  {
6510
7041
  label: "MCP",
@@ -6512,8 +7043,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6512
7043
  }
6513
7044
  ), specs.map((spec, i) => (
6514
7045
  // 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(
7046
+ /* @__PURE__ */ React19.createElement(Box15, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\xB7 ", spec))
7047
+ )), /* @__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
7048
  ReviewConfirm,
6518
7049
  {
6519
7050
  onConfirm: () => {
@@ -6539,7 +7070,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
6539
7070
  }
6540
7071
  ));
6541
7072
  }
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 }));
7073
+ 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
7074
  }
6544
7075
  function ApiKeyStep({
6545
7076
  onSubmit,
@@ -6547,7 +7078,7 @@ function ApiKeyStep({
6547
7078
  onError
6548
7079
  }) {
6549
7080
  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(
7081
+ 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
7082
  TextInput2,
6552
7083
  {
6553
7084
  value,
@@ -6564,7 +7095,7 @@ function ApiKeyStep({
6564
7095
  mask: "\u2022",
6565
7096
  placeholder: "sk-..."
6566
7097
  }
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);
7098
+ )), 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
7099
  }
6569
7100
  function McpArgsStep({
6570
7101
  entry,
@@ -6573,7 +7104,7 @@ function McpArgsStep({
6573
7104
  onError
6574
7105
  }) {
6575
7106
  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(
7107
+ 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
7108
  TextInput2,
6578
7109
  {
6579
7110
  value,
@@ -6589,7 +7120,7 @@ function McpArgsStep({
6589
7120
  },
6590
7121
  placeholder: placeholderFor(entry)
6591
7122
  }
6592
- )), error ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { color: "red" }, error)) : null));
7123
+ )), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null));
6593
7124
  }
6594
7125
  function ReviewConfirm({ onConfirm }) {
6595
7126
  useInput6((_i, key) => {
@@ -6609,10 +7140,10 @@ function StepFrame({
6609
7140
  total,
6610
7141
  children
6611
7142
  }) {
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));
7143
+ 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
7144
  }
6614
7145
  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));
7146
+ return /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, null, label.padEnd(12)), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, value));
6616
7147
  }
6617
7148
  function presetItems() {
6618
7149
  return ["fast", "smart", "max"].map((name) => ({
@@ -6668,7 +7199,7 @@ async function setupCommand(_opts = {}) {
6668
7199
  const existingKey = loadApiKey();
6669
7200
  const existing = readConfig();
6670
7201
  const { waitUntilExit, unmount } = render4(
6671
- /* @__PURE__ */ React18.createElement(
7202
+ /* @__PURE__ */ React20.createElement(
6672
7203
  Wizard,
6673
7204
  {
6674
7205
  existingApiKey: existingKey,
@@ -6686,9 +7217,9 @@ async function setupCommand(_opts = {}) {
6686
7217
  }
6687
7218
 
6688
7219
  // src/cli/commands/stats.ts
6689
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
7220
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
6690
7221
  function statsCommand(opts) {
6691
- if (!existsSync3(opts.transcript)) {
7222
+ if (!existsSync4(opts.transcript)) {
6692
7223
  console.error(`no such transcript: ${opts.transcript}`);
6693
7224
  process.exit(1);
6694
7225
  }
@@ -6763,7 +7294,7 @@ program.action(async () => {
6763
7294
  const defaults = resolveDefaults({});
6764
7295
  await chatCommand({
6765
7296
  model: defaults.model,
6766
- system: DEFAULT_SYSTEM,
7297
+ system: applyProjectMemory(DEFAULT_SYSTEM, process.cwd()),
6767
7298
  harvest: defaults.harvest,
6768
7299
  branch: defaults.branch,
6769
7300
  session: defaults.session,
@@ -6775,12 +7306,14 @@ program.command("setup").description("Interactive wizard \u2014 API key, preset,
6775
7306
  });
6776
7307
  program.command("code [dir]").description(
6777
7308
  "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) => {
7309
+ ).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
7310
  await codeCommand({
6780
7311
  dir,
6781
7312
  model: opts.model,
6782
7313
  noSession: opts.session === false,
6783
- transcript: opts.transcript
7314
+ transcript: opts.transcript,
7315
+ forceResume: !!opts.resume,
7316
+ forceNew: !!opts.new
6784
7317
  });
6785
7318
  });
6786
7319
  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 +7326,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
6793
7326
  "--branch <n>",
6794
7327
  "Self-consistency: run N parallel samples per turn and pick the most confident (disables streaming; enables harvest)",
6795
7328
  (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(
7329
+ ).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
7330
  "--mcp <spec>",
6798
7331
  'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
6799
7332
  (value, previous = []) => [...previous, value],
@@ -6813,13 +7346,15 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
6813
7346
  });
6814
7347
  await chatCommand({
6815
7348
  model: defaults.model,
6816
- system: opts.system,
7349
+ system: applyProjectMemory(opts.system, process.cwd()),
6817
7350
  transcript: opts.transcript,
6818
7351
  harvest: defaults.harvest,
6819
7352
  branch: defaults.branch,
6820
7353
  session: defaults.session,
6821
7354
  mcp: defaults.mcp,
6822
- mcpPrefix: opts.mcpPrefix
7355
+ mcpPrefix: opts.mcpPrefix,
7356
+ forceResume: !!opts.resume,
7357
+ forceNew: !!opts.new
6823
7358
  });
6824
7359
  });
6825
7360
  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,10 +7378,10 @@ program.command("run <task>").description("Run a single task non-interactively,
6843
7378
  preset: opts.preset,
6844
7379
  noConfig: opts.config === false
6845
7380
  });
6846
- await runCommand({
7381
+ await runCommand2({
6847
7382
  task,
6848
7383
  model: defaults.model,
6849
- system: opts.system,
7384
+ system: applyProjectMemory(opts.system, process.cwd()),
6850
7385
  harvest: defaults.harvest,
6851
7386
  branch: defaults.branch,
6852
7387
  transcript: opts.transcript,