reasonix 0.7.12 → 0.8.0

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
@@ -10,7 +10,7 @@ import {
10
10
  memoryEnabled,
11
11
  readProjectMemory,
12
12
  sanitizeMemoryName
13
- } from "./chunk-5DZMZCCW.js";
13
+ } from "./chunk-DVBNMXA6.js";
14
14
 
15
15
  // src/cli/index.ts
16
16
  import { Command } from "commander";
@@ -7111,7 +7111,7 @@ function formatLogSize(path = defaultUsageLogPath()) {
7111
7111
  }
7112
7112
 
7113
7113
  // src/cli/commands/chat.tsx
7114
- import { existsSync as existsSync12, statSync as statSync7 } from "fs";
7114
+ import { existsSync as existsSync13, statSync as statSync7 } from "fs";
7115
7115
  import { render } from "ink";
7116
7116
  import React26, { useState as useState12 } from "react";
7117
7117
 
@@ -7991,7 +7991,8 @@ function formatAllBlockDiffs(blocks, opts = {}) {
7991
7991
  const added = countLines2(b.replace);
7992
7992
  const tag = b.search === "" ? "NEW " : " ";
7993
7993
  if (i > 0) out.push("");
7994
- out.push(` ${tag}${b.path} (-${removed} +${added} lines)`);
7994
+ const label = opts.numbered ? `[${i + 1}] ` : "";
7995
+ out.push(` ${label}${tag}${b.path} (-${removed} +${added} lines)`);
7995
7996
  out.push(...formatEditBlockDiff(b, opts));
7996
7997
  }
7997
7998
  return out;
@@ -10866,10 +10867,50 @@ function formatEditResults(results) {
10866
10867
  return [header2, ...lines].join("\n");
10867
10868
  }
10868
10869
  function formatPendingPreview(blocks) {
10869
- const header2 = `\u25B8 ${blocks.length} pending edit block(s) \u2014 /apply (or y) to commit \xB7 /discard (or n) to drop`;
10870
- const diffLines = formatAllBlockDiffs(blocks);
10870
+ const partial = blocks.length > 1 ? " \xB7 /apply N or 1,3-4 for partial" : "";
10871
+ const header2 = `\u25B8 ${blocks.length} pending edit block(s) \u2014 /apply (or y) to commit \xB7 /discard (or n) to drop${partial}`;
10872
+ const diffLines = formatAllBlockDiffs(blocks, { numbered: blocks.length > 1 });
10871
10873
  return [header2, ...diffLines].join("\n");
10872
10874
  }
10875
+ function parseEditIndices(raw, max) {
10876
+ const trimmed = raw.trim();
10877
+ if (!trimmed) return { ok: [] };
10878
+ if (max <= 0) return { error: "no pending edits to address" };
10879
+ const seen = /* @__PURE__ */ new Set();
10880
+ const tokens = trimmed.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
10881
+ if (tokens.length === 0) return { ok: [] };
10882
+ for (const tok of tokens) {
10883
+ const range = tok.match(/^(\d+)-(\d+)$/);
10884
+ if (range) {
10885
+ const a = Number.parseInt(range[1] ?? "", 10);
10886
+ const b = Number.parseInt(range[2] ?? "", 10);
10887
+ if (!Number.isFinite(a) || !Number.isFinite(b) || a < 1 || b < 1) {
10888
+ return { error: `invalid range: "${tok}"` };
10889
+ }
10890
+ const lo = Math.min(a, b);
10891
+ const hi = Math.max(a, b);
10892
+ if (hi > max) return { error: `index ${hi} out of range (max ${max})` };
10893
+ for (let i = lo; i <= hi; i++) seen.add(i);
10894
+ continue;
10895
+ }
10896
+ if (!/^\d+$/.test(tok)) return { error: `invalid index: "${tok}"` };
10897
+ const n = Number.parseInt(tok, 10);
10898
+ if (!Number.isFinite(n) || n < 1) return { error: `invalid index: "${tok}"` };
10899
+ if (n > max) return { error: `index ${n} out of range (max ${max})` };
10900
+ seen.add(n);
10901
+ }
10902
+ return { ok: [...seen].sort((a, b) => a - b) };
10903
+ }
10904
+ function partitionEdits(edits, indices1Based) {
10905
+ const picked = new Set(indices1Based);
10906
+ const selected = [];
10907
+ const remaining = [];
10908
+ for (let i = 0; i < edits.length; i++) {
10909
+ if (picked.has(i + 1)) selected.push(edits[i]);
10910
+ else remaining.push(edits[i]);
10911
+ }
10912
+ return { selected, remaining };
10913
+ }
10873
10914
  function formatUndoRows(results) {
10874
10915
  return results.map((r) => {
10875
10916
  const mark = r.status === "applied" ? "\u2713" : "\u2717";
@@ -10885,6 +10926,45 @@ function describeRepair(repair) {
10885
10926
  return parts.length ? `[repair] ${parts.join(", ")}` : "";
10886
10927
  }
10887
10928
 
10929
+ // src/cli/ui/hash-memory.ts
10930
+ import { appendFileSync as appendFileSync3, existsSync as existsSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
10931
+ import { join as join12 } from "path";
10932
+ var NEW_FILE_HEADER = `# Reasonix project memory
10933
+
10934
+ Notes the user pinned via the \`#\` prompt prefix. The whole file is
10935
+ loaded into the immutable system prefix every session \u2014 keep it terse.
10936
+
10937
+ `;
10938
+ function detectHashMemory(text) {
10939
+ if (text.startsWith("\\#")) {
10940
+ return { kind: "escape", text: text.slice(1) };
10941
+ }
10942
+ if (!text.startsWith("#")) return null;
10943
+ if (text.startsWith("##")) return null;
10944
+ const body = text.slice(1).trim();
10945
+ if (!body) return null;
10946
+ return { kind: "memory", note: body };
10947
+ }
10948
+ function appendProjectMemory(rootDir, note) {
10949
+ const path = join12(rootDir, PROJECT_MEMORY_FILE);
10950
+ const trimmed = note.trim();
10951
+ if (!trimmed) throw new Error("note body cannot be empty");
10952
+ const bullet = `- ${trimmed}
10953
+ `;
10954
+ if (!existsSync11(path)) {
10955
+ writeFileSync7(path, `${NEW_FILE_HEADER}${bullet}`, "utf8");
10956
+ return { path, created: true };
10957
+ }
10958
+ let prefix = "";
10959
+ try {
10960
+ const existing = readFileSync14(path, "utf8");
10961
+ if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
10962
+ } catch {
10963
+ }
10964
+ appendFileSync3(path, `${prefix}${bullet}`, "utf8");
10965
+ return { path, created: false };
10966
+ }
10967
+
10888
10968
  // src/cli/ui/mcp-browse.ts
10889
10969
  function formatResourceList(servers) {
10890
10970
  const lines = [];
@@ -11192,8 +11272,18 @@ var SLASH_COMMANDS = [
11192
11272
  { cmd: "new", summary: "start a fresh conversation (clear context + scrollback)" },
11193
11273
  { cmd: "exit", summary: "quit the TUI" },
11194
11274
  // Code-mode only
11195
- { cmd: "apply", summary: "commit pending edit blocks to disk", contextual: "code" },
11196
- { cmd: "discard", summary: "drop pending edit blocks without writing", contextual: "code" },
11275
+ {
11276
+ cmd: "apply",
11277
+ argsHint: "[N|N,M|N-M]",
11278
+ summary: "commit pending edit blocks to disk (no arg \u2192 all; `1`, `1,3`, or `1-4` \u2192 that subset, rest stay pending)",
11279
+ contextual: "code"
11280
+ },
11281
+ {
11282
+ cmd: "discard",
11283
+ argsHint: "[N|N,M|N-M]",
11284
+ summary: "drop pending edit blocks without writing (no arg \u2192 all; indices \u2192 that subset)",
11285
+ contextual: "code"
11286
+ },
11197
11287
  { cmd: "undo", summary: "roll back the last applied edit batch", contextual: "code" },
11198
11288
  {
11199
11289
  cmd: "history",
@@ -11282,7 +11372,7 @@ function parseSlash(text) {
11282
11372
  }
11283
11373
 
11284
11374
  // src/cli/commands/stats.ts
11285
- import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
11375
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
11286
11376
  function statsCommand(opts) {
11287
11377
  if (opts.transcript) {
11288
11378
  transcriptSummary(opts.transcript);
@@ -11291,11 +11381,11 @@ function statsCommand(opts) {
11291
11381
  dashboard(opts);
11292
11382
  }
11293
11383
  function transcriptSummary(path) {
11294
- if (!existsSync11(path)) {
11384
+ if (!existsSync12(path)) {
11295
11385
  console.error(`no such transcript: ${path}`);
11296
11386
  process.exit(1);
11297
11387
  }
11298
- const lines = readFileSync14(path, "utf8").split(/\r?\n/).filter(Boolean);
11388
+ const lines = readFileSync15(path, "utf8").split(/\r?\n/).filter(Boolean);
11299
11389
  let assistantTurns = 0;
11300
11390
  let toolCalls = 0;
11301
11391
  let lastTurn = 0;
@@ -11559,6 +11649,8 @@ var keys = () => ({
11559
11649
  " Trailing `@\u2026` opens a file picker; \u2191/\u2193 navigate, Tab/Enter pick.",
11560
11650
  " !<cmd> run <cmd> as shell in the sandbox root; output goes into context",
11561
11651
  " so the model sees it next turn. No allowlist gate.",
11652
+ " #<note> append <note> to REASONIX.md so it pins into every future session.",
11653
+ " Use `\\#literal` if you actually want a `#` heading sent to the model.",
11562
11654
  "",
11563
11655
  "Pickers (slash + @-mention):",
11564
11656
  " \u2191 / \u2193 navigate the suggestion list",
@@ -11597,8 +11689,8 @@ var help = () => ({
11597
11689
  " /skill [sub] list / run user skills (project/.reasonix/skills + ~/.reasonix/skills).",
11598
11690
  " subs: list | show <name> | <name> [args] (injects skill body as user turn)",
11599
11691
  " /retry truncate & resend your last message (fresh sample from the model)",
11600
- " /apply (code mode) commit the pending edit blocks to disk",
11601
- " /discard (code mode) drop pending edits without writing",
11692
+ " /apply [N|1,3|1-4] (code mode) commit pending edit blocks (no arg \u2192 all; index \u2192 subset)",
11693
+ " /discard [N|1,3|1-4] (code mode) drop pending edits (no arg \u2192 all; index \u2192 subset)",
11602
11694
  " /undo (code mode) roll back the latest non-undone edit batch",
11603
11695
  " /history (code mode) list every edit batch this session",
11604
11696
  " /show [id] (code mode) dump a stored edit diff (newest when id omitted)",
@@ -11621,6 +11713,12 @@ var help = () => ({
11621
11713
  " No allowlist gate \u2014 user-typed = explicit consent.",
11622
11714
  " Example: !git status !ls src/ !npm test",
11623
11715
  "",
11716
+ "Quick memory:",
11717
+ " #<note> append <note> to REASONIX.md (pinned into every",
11718
+ " future session's prefix). Faster than /memory for",
11719
+ " one-liners. Example: #always use pnpm not npm",
11720
+ " Use `\\#text` to send a literal `#text` to the model.",
11721
+ "",
11624
11722
  "File references (code mode):",
11625
11723
  " @path/to/file inline file content under [Referenced files] on send.",
11626
11724
  " Type `@` to open the picker (\u2191\u2193 navigate, Tab/Enter pick).",
@@ -11790,22 +11888,33 @@ var show = (args, _loop, ctx) => {
11790
11888
  }
11791
11889
  return { info: ctx.codeShowEdit(args) };
11792
11890
  };
11793
- var apply = (_args, _loop, ctx) => {
11891
+ var apply = (args, _loop, ctx) => {
11794
11892
  if (!ctx.codeApply) {
11795
11893
  return {
11796
11894
  info: "/apply is only available inside `reasonix code` (nothing to apply here)."
11797
11895
  };
11798
11896
  }
11799
- return { info: ctx.codeApply() };
11897
+ const parsed = parseIndicesArg(args, ctx.pendingEditCount ?? 0);
11898
+ if ("error" in parsed) return { info: `/apply: ${parsed.error}` };
11899
+ return { info: ctx.codeApply(parsed.indices) };
11800
11900
  };
11801
- var discard = (_args, _loop, ctx) => {
11901
+ var discard = (args, _loop, ctx) => {
11802
11902
  if (!ctx.codeDiscard) {
11803
11903
  return {
11804
11904
  info: "/discard is only available inside `reasonix code`."
11805
11905
  };
11806
11906
  }
11807
- return { info: ctx.codeDiscard() };
11907
+ const parsed = parseIndicesArg(args, ctx.pendingEditCount ?? 0);
11908
+ if ("error" in parsed) return { info: `/discard: ${parsed.error}` };
11909
+ return { info: ctx.codeDiscard(parsed.indices) };
11808
11910
  };
11911
+ function parseIndicesArg(args, max) {
11912
+ const raw = args.join(",").replace(/,+/g, ",").replace(/^,|,$/g, "");
11913
+ if (!raw) return { indices: [] };
11914
+ const parsed = parseEditIndices(raw, max);
11915
+ if ("error" in parsed) return { error: parsed.error };
11916
+ return { indices: parsed.ok };
11917
+ }
11809
11918
  var plan = (args, _loop, ctx) => {
11810
11919
  if (!ctx.setPlanMode) {
11811
11920
  return {
@@ -13674,29 +13783,50 @@ function App({
13674
13783
  tools.setToolInterceptor(null);
13675
13784
  };
13676
13785
  }, [tools, codeMode, session, recordEdit, armUndoBanner, syncPendingCount, setEditMode]);
13677
- const codeApply = useCallback4(() => {
13678
- if (!codeMode) return "not in code mode";
13679
- const blocks = pendingEdits.current;
13680
- if (blocks.length === 0) {
13681
- return "nothing pending \u2014 the model hasn't proposed edits since the last /apply or /discard.";
13682
- }
13683
- const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
13684
- const results = applyEditBlocks(blocks, codeMode.rootDir);
13685
- const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
13686
- if (anyApplied) recordEdit("review-apply", blocks, results, snaps);
13687
- pendingEdits.current = [];
13688
- clearPendingEdits(session ?? null);
13689
- syncPendingCount();
13690
- return formatEditResults(results);
13691
- }, [codeMode, session, syncPendingCount, recordEdit]);
13692
- const codeDiscard = useCallback4(() => {
13693
- const count = pendingEdits.current.length;
13694
- if (count === 0) return "nothing pending to discard.";
13695
- pendingEdits.current = [];
13696
- clearPendingEdits(session ?? null);
13697
- syncPendingCount();
13698
- return `\u25B8 discarded ${count} pending edit block(s). Nothing was written to disk.`;
13699
- }, [session, syncPendingCount]);
13786
+ const codeApply = useCallback4(
13787
+ (indices) => {
13788
+ if (!codeMode) return "not in code mode";
13789
+ const blocks = pendingEdits.current;
13790
+ if (blocks.length === 0) {
13791
+ return "nothing pending \u2014 the model hasn't proposed edits since the last /apply or /discard.";
13792
+ }
13793
+ const useSubset = indices !== void 0 && indices.length > 0;
13794
+ const { selected, remaining } = useSubset ? partitionEdits(blocks, indices) : { selected: blocks, remaining: [] };
13795
+ if (selected.length === 0) {
13796
+ return "\u25B8 no edits matched those indices \u2014 nothing applied. Use /apply with no args to commit them all.";
13797
+ }
13798
+ const snaps = snapshotBeforeEdits(selected, codeMode.rootDir);
13799
+ const results = applyEditBlocks(selected, codeMode.rootDir);
13800
+ const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
13801
+ if (anyApplied) recordEdit("review-apply", selected, results, snaps);
13802
+ pendingEdits.current = remaining;
13803
+ if (remaining.length === 0) clearPendingEdits(session ?? null);
13804
+ else savePendingEdits(session ?? null, remaining);
13805
+ syncPendingCount();
13806
+ const tail = remaining.length > 0 ? `
13807
+ \u25B8 ${remaining.length} edit block(s) still pending \u2014 /apply or /discard to clear them.` : "";
13808
+ return formatEditResults(results) + tail;
13809
+ },
13810
+ [codeMode, session, syncPendingCount, recordEdit]
13811
+ );
13812
+ const codeDiscard = useCallback4(
13813
+ (indices) => {
13814
+ const blocks = pendingEdits.current;
13815
+ if (blocks.length === 0) return "nothing pending to discard.";
13816
+ const useSubset = indices !== void 0 && indices.length > 0;
13817
+ const { selected, remaining } = useSubset ? partitionEdits(blocks, indices) : { selected: blocks, remaining: [] };
13818
+ if (selected.length === 0) {
13819
+ return "\u25B8 no edits matched those indices \u2014 nothing discarded.";
13820
+ }
13821
+ pendingEdits.current = remaining;
13822
+ if (remaining.length === 0) clearPendingEdits(session ?? null);
13823
+ else savePendingEdits(session ?? null, remaining);
13824
+ syncPendingCount();
13825
+ const tail = remaining.length > 0 ? ` (${remaining.length} block(s) still pending)` : ". Nothing was written to disk.";
13826
+ return `\u25B8 discarded ${selected.length} pending edit block(s)${tail}`;
13827
+ },
13828
+ [session, syncPendingCount]
13829
+ );
13700
13830
  const prefixHash = loop.prefix.fingerprint;
13701
13831
  const writeTranscript = useCallback4(
13702
13832
  (ev) => {
@@ -13751,6 +13881,36 @@ function App({
13751
13881
  promptHistory.current.push(text);
13752
13882
  return;
13753
13883
  }
13884
+ const hashParse = detectHashMemory(text);
13885
+ if (hashParse?.kind === "memory") {
13886
+ const memRoot = codeMode?.rootDir ?? process.cwd();
13887
+ promptHistory.current.push(text);
13888
+ try {
13889
+ const result = appendProjectMemory(memRoot, hashParse.note);
13890
+ const verb = result.created ? "created" : "appended to";
13891
+ setHistorical((prev) => [
13892
+ ...prev,
13893
+ {
13894
+ id: `hash-${Date.now()}`,
13895
+ role: "info",
13896
+ text: `\u25B8 noted \u2014 ${verb} ${result.path}`
13897
+ }
13898
+ ]);
13899
+ } catch (err) {
13900
+ setHistorical((prev) => [
13901
+ ...prev,
13902
+ {
13903
+ id: `hash-e-${Date.now()}`,
13904
+ role: "warning",
13905
+ text: `# memory write failed: ${err.message}`
13906
+ }
13907
+ ]);
13908
+ }
13909
+ return;
13910
+ }
13911
+ if (hashParse?.kind === "escape") {
13912
+ text = hashParse.text;
13913
+ }
13754
13914
  const bangCmd = detectBangCommand(text);
13755
13915
  if (bangCmd !== null) {
13756
13916
  const bangRoot = codeMode?.rootDir ?? process.cwd();
@@ -15139,7 +15299,7 @@ async function chatCommand(opts) {
15139
15299
  const prior = loadSessionMessages(opts.session);
15140
15300
  if (prior.length > 0) {
15141
15301
  const p = sessionPath(opts.session);
15142
- const mtime = existsSync12(p) ? statSync7(p).mtime : /* @__PURE__ */ new Date();
15302
+ const mtime = existsSync13(p) ? statSync7(p).mtime : /* @__PURE__ */ new Date();
15143
15303
  sessionPreview = { messageCount: prior.length, lastActive: mtime };
15144
15304
  }
15145
15305
  } else if (opts.session && opts.forceNew) {
@@ -15172,7 +15332,7 @@ async function chatCommand(opts) {
15172
15332
  // src/cli/commands/code.tsx
15173
15333
  import { basename as basename2, resolve as resolve7 } from "path";
15174
15334
  async function codeCommand(opts = {}) {
15175
- const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-2OABSPAW.js");
15335
+ const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-POARCKKR.js");
15176
15336
  const rootDir = resolve7(opts.dir ?? process.cwd());
15177
15337
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
15178
15338
  const tools = new ToolRegistry();
@@ -15212,7 +15372,7 @@ async function codeCommand(opts = {}) {
15212
15372
  }
15213
15373
 
15214
15374
  // src/cli/commands/diff.ts
15215
- import { writeFileSync as writeFileSync7 } from "fs";
15375
+ import { writeFileSync as writeFileSync8 } from "fs";
15216
15376
  import { basename as basename3 } from "path";
15217
15377
  import { render as render2 } from "ink";
15218
15378
  import React29 from "react";
@@ -15359,7 +15519,7 @@ async function diffCommand(opts) {
15359
15519
  if (wantMarkdown) {
15360
15520
  console.log(renderSummaryTable(report));
15361
15521
  const md = renderMarkdown(report);
15362
- writeFileSync7(opts.mdPath, md, "utf8");
15522
+ writeFileSync8(opts.mdPath, md, "utf8");
15363
15523
  console.log(`
15364
15524
  markdown report written to ${opts.mdPath}`);
15365
15525
  return;
@@ -16295,6 +16455,16 @@ function resolveSession(flag, configSession) {
16295
16455
  if (typeof configSession === "string" && configSession.length > 0) return configSession;
16296
16456
  return "default";
16297
16457
  }
16458
+ function resolveContinueFlag(flag, fallbackSession, getLatestSession, warn = () => {
16459
+ }) {
16460
+ if (!flag) return { session: fallbackSession, forceResume: false };
16461
+ const latest = getLatestSession();
16462
+ if (!latest) {
16463
+ warn("\u25B8 -c/--continue: no saved sessions yet \u2014 starting a fresh one.");
16464
+ return { session: fallbackSession, forceResume: false };
16465
+ }
16466
+ return { session: latest.name, forceResume: true };
16467
+ }
16298
16468
 
16299
16469
  // src/cli/index.ts
16300
16470
  var DEFAULT_SYSTEM = `You are Reasonix, a helpful DeepSeek-powered assistant. Be concise and accurate. Use tools when available.
@@ -16319,21 +16489,32 @@ The signal isn't a topic list \u2014 it's: "if I'm wrong about this, is it becau
16319
16489
 
16320
16490
  ${ESCALATION_CONTRACT}`;
16321
16491
  var program = new Command();
16322
- program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION);
16323
- program.action(async () => {
16492
+ program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION).option(
16493
+ "-c, --continue",
16494
+ "Resume the most recently used chat session without showing the picker."
16495
+ );
16496
+ program.action(async (opts) => {
16324
16497
  const cfg = readConfig();
16325
16498
  if (!cfg.setupCompleted) {
16326
16499
  await setupCommand({});
16327
16500
  return;
16328
16501
  }
16329
16502
  const defaults = resolveDefaults({});
16503
+ const continueOpts = resolveContinueFlag(
16504
+ opts.continue,
16505
+ defaults.session,
16506
+ () => listSessions()[0],
16507
+ (msg) => process.stderr.write(`${msg}
16508
+ `)
16509
+ );
16330
16510
  await chatCommand({
16331
16511
  model: defaults.model,
16332
16512
  system: applyMemoryStack(DEFAULT_SYSTEM, process.cwd()),
16333
16513
  harvest: defaults.harvest,
16334
16514
  branch: defaults.branch,
16335
- session: defaults.session,
16336
- mcp: defaults.mcp
16515
+ session: continueOpts.session,
16516
+ mcp: defaults.mcp,
16517
+ forceResume: continueOpts.forceResume
16337
16518
  });
16338
16519
  });
16339
16520
  program.command("setup").description("Interactive wizard \u2014 API key, preset, MCP servers. Re-run any time to reconfigure.").action(async () => {
@@ -16365,7 +16546,10 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
16365
16546
  "--branch <n>",
16366
16547
  "Self-consistency: run N parallel samples per turn (N\xD7 cost). Manual only \u2014 never auto-enabled.",
16367
16548
  (v) => Number.parseInt(v, 10)
16368
- ).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(
16549
+ ).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(
16550
+ "-c, --continue",
16551
+ "Resume the most-recently-used session (any name) without showing the picker."
16552
+ ).option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option(
16369
16553
  "--mcp <spec>",
16370
16554
  'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
16371
16555
  (value, previous = []) => [...previous, value],
@@ -16383,16 +16567,23 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
16383
16567
  preset: opts.preset,
16384
16568
  noConfig: opts.config === false
16385
16569
  });
16570
+ const continueOpts = opts.resume ? { session: defaults.session, forceResume: true } : resolveContinueFlag(
16571
+ opts.continue,
16572
+ defaults.session,
16573
+ () => listSessions()[0],
16574
+ (msg) => process.stderr.write(`${msg}
16575
+ `)
16576
+ );
16386
16577
  await chatCommand({
16387
16578
  model: defaults.model,
16388
16579
  system: applyMemoryStack(opts.system, process.cwd()),
16389
16580
  transcript: opts.transcript,
16390
16581
  harvest: defaults.harvest,
16391
16582
  branch: defaults.branch,
16392
- session: defaults.session,
16583
+ session: continueOpts.session,
16393
16584
  mcp: defaults.mcp,
16394
16585
  mcpPrefix: opts.mcpPrefix,
16395
- forceResume: !!opts.resume,
16586
+ forceResume: continueOpts.forceResume,
16396
16587
  forceNew: !!opts.new
16397
16588
  });
16398
16589
  });