mindweave 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +6 -2
  2. package/dist/cli/App.js +140 -40
  3. package/dist/cli/App.js.map +1 -1
  4. package/dist/cli/altScreen.js +17 -1
  5. package/dist/cli/altScreen.js.map +1 -1
  6. package/dist/cli/analytics.js +83 -0
  7. package/dist/cli/analytics.js.map +1 -0
  8. package/dist/cli/blockSpacing.js +29 -0
  9. package/dist/cli/blockSpacing.js.map +1 -0
  10. package/dist/cli/commandLabel.js +85 -0
  11. package/dist/cli/commandLabel.js.map +1 -0
  12. package/dist/cli/commands.js +41 -0
  13. package/dist/cli/commands.js.map +1 -0
  14. package/dist/cli/components/KeyManager.js +3 -3
  15. package/dist/cli/components/KeyManager.js.map +1 -1
  16. package/dist/cli/components/Picker.js +10 -6
  17. package/dist/cli/components/Picker.js.map +1 -1
  18. package/dist/cli/components/PromptInput.js +8 -1
  19. package/dist/cli/components/PromptInput.js.map +1 -1
  20. package/dist/cli/components/ToolGroup.js +5 -1
  21. package/dist/cli/components/ToolGroup.js.map +1 -1
  22. package/dist/cli/components/ToolLine.js +61 -6
  23. package/dist/cli/components/ToolLine.js.map +1 -1
  24. package/dist/cli/framebuffer/parse.js +10 -4
  25. package/dist/cli/framebuffer/parse.js.map +1 -1
  26. package/dist/cli/framebuffer/screen.js +26 -0
  27. package/dist/cli/framebuffer/screen.js.map +1 -1
  28. package/dist/cli/framebuffer/writer.js +131 -23
  29. package/dist/cli/framebuffer/writer.js.map +1 -1
  30. package/dist/cli/restart.js +99 -0
  31. package/dist/cli/restart.js.map +1 -0
  32. package/dist/cli/selfUpdate.js +124 -0
  33. package/dist/cli/selfUpdate.js.map +1 -0
  34. package/dist/cli/startupArgs.js +29 -2
  35. package/dist/cli/startupArgs.js.map +1 -1
  36. package/dist/cli/terminalRestore.js +9 -1
  37. package/dist/cli/terminalRestore.js.map +1 -1
  38. package/dist/cli/toolDisplay.js +15 -6
  39. package/dist/cli/toolDisplay.js.map +1 -1
  40. package/dist/cli/transcript.js +1 -1
  41. package/dist/cli/transcript.js.map +1 -1
  42. package/dist/cli/updateRunner.js +105 -0
  43. package/dist/cli/updateRunner.js.map +1 -0
  44. package/dist/drivers/gemini/manifest.js +16 -11
  45. package/dist/drivers/gemini/manifest.js.map +1 -1
  46. package/dist/dynamo/engine.js +30 -4
  47. package/dist/dynamo/engine.js.map +1 -1
  48. package/dist/dynamo/prompt.js +7 -4
  49. package/dist/dynamo/prompt.js.map +1 -1
  50. package/dist/dynamo/verify.js +8 -5
  51. package/dist/dynamo/verify.js.map +1 -1
  52. package/dist/index.js +32 -2
  53. package/dist/index.js.map +1 -1
  54. package/dist/memory/types.js.map +1 -1
  55. package/dist/tools/askUser.js +17 -5
  56. package/dist/tools/askUser.js.map +1 -1
  57. package/dist/tools/detail.js +69 -11
  58. package/dist/tools/detail.js.map +1 -1
  59. package/dist/tools/exitPlan.js +17 -1
  60. package/dist/tools/exitPlan.js.map +1 -1
  61. package/dist/tools/outputShape.js +137 -0
  62. package/dist/tools/outputShape.js.map +1 -0
  63. package/dist/tools/registry.js +0 -2
  64. package/dist/tools/registry.js.map +1 -1
  65. package/dist/tools/runCommand.js +31 -7
  66. package/dist/tools/runCommand.js.map +1 -1
  67. package/dist/tools/screenshot.js +33 -5
  68. package/dist/tools/screenshot.js.map +1 -1
  69. package/dist/tools/testSummary.js +211 -0
  70. package/dist/tools/testSummary.js.map +1 -0
  71. package/package.json +74 -74
  72. package/dist/alternator/chassis/rank.js +0 -72
  73. package/dist/alternator/chassis/rank.js.map +0 -1
  74. package/dist/cli/components/MenuFrame.js +0 -24
  75. package/dist/cli/components/MenuFrame.js.map +0 -1
  76. package/dist/cli/reveal.js +0 -32
  77. package/dist/cli/reveal.js.map +0 -1
  78. package/dist/cli/smoothScroll.js +0 -74
  79. package/dist/cli/smoothScroll.js.map +0 -1
  80. package/dist/cli/viewport.js +0 -179
  81. package/dist/cli/viewport.js.map +0 -1
  82. package/dist/dynamo/_probe.tmp.js +0 -20
  83. package/dist/dynamo/_probe.tmp.js.map +0 -1
  84. package/dist/tools/captureSweep.js +0 -77
  85. package/dist/tools/captureSweep.js.map +0 -1
  86. package/dist/tools/diagnostics.js +0 -174
  87. package/dist/tools/diagnostics.js.map +0 -1
  88. package/dist/tools/editFile.js +0 -183
  89. package/dist/tools/editFile.js.map +0 -1
  90. package/dist/tools/multiEdit.js +0 -168
  91. package/dist/tools/multiEdit.js.map +0 -1
package/README.md CHANGED
@@ -41,6 +41,8 @@ the agent maintains across sessions, and a long list of things that were quietly
41
41
  Requires **Windows** and **Node.js 20+**. macOS and Linux are not supported yet, and the
42
42
  reason is written down in [KNOWN-ISSUES.md](KNOWN-ISSUES.md) rather than glossed over.
43
43
 
44
+ Big features coming out soon!
45
+
44
46
  ```bash
45
47
  npm install -g mindweave
46
48
  ```
@@ -69,7 +71,7 @@ else is configured inside a session.
69
71
 
70
72
  ## What it can do
71
73
 
72
- **14 providers, 52 models, one key.** DeepSeek, Anthropic, OpenAI, Gemini, xAI, Mistral,
74
+ **14 providers, 53 models, one key.** DeepSeek, Anthropic, OpenAI, Gemini, xAI, Mistral,
73
75
  Groq, Cerebras, Qwen, Kimi, GLM, Meta, MiniMax and Tencent. Each family gets its own driver so it
74
76
  runs at its best without bloating the shared core, and only the driver you are using is
75
77
  ever loaded. Switch with `/provider` and `/model`; the choice is remembered per project.
@@ -80,7 +82,8 @@ and language servers, costing no tokens, so the agent understands your codebase
80
82
  than just the file you opened.
81
83
 
82
84
  **Real tools.** File read and edit, multi-file edits, ripgrep search, a shell with
83
- background jobs, sub-agents, and compiler diagnostics. Read-before-edit is enforced, and
85
+ background jobs, and sub-agents. Every edit comes back with the language server's own
86
+ errors for that file, so a mistake surfaces where it was made. Read-before-edit is enforced, and
84
87
  `/undo` is a real net rather than a hope.
85
88
 
86
89
  **Session memory.** Long sessions stay sharp through automatic compaction plus a running
@@ -118,6 +121,7 @@ paths or commands the agent has to respect.
118
121
  | `/provider` · `/model` · `/think` | Who answers, which model, how hard it reasons |
119
122
  | `/clear` · `/continue` | Start fresh, or resume an earlier session |
120
123
  | `/undo` | Reverts what the last turn changed |
124
+ | `/update` | Installs the newest version and reopens on this conversation |
121
125
  | `/mcp` | Manages connected MCP servers |
122
126
  | `shift-tab` | Cycles interaction modes |
123
127
 
package/dist/cli/App.js CHANGED
@@ -53,11 +53,16 @@ import { formatHelp } from "./help.js";
53
53
  import { hasApiKey, saveApiKey, removeApiKey, useApiKey, globalEnvPath, reloadConfig } from "./bootstrap.js";
54
54
  import { versionLabel, appVersion } from "./version.js";
55
55
  import { checkForUpdate } from "./updateCheck.js";
56
+ import { ANALYTICS_EXPLANATION, analyticsEnabled, sendAnalyticsPing, setAnalyticsEnabled, startupStatusLine, } from "./analytics.js";
56
57
  import { PromptInput } from "./components/PromptInput.js";
57
58
  import { Picker } from "./components/Picker.js";
58
59
  import { ApprovalBox } from "./components/ApprovalBox.js";
59
60
  import { BlockView } from "./components/BlockView.js";
60
61
  import { initialState, reduce, trimNarration } from "./transcript.js";
62
+ import { isTight } from "./blockSpacing.js";
63
+ import { BASE_COMMANDS } from "./commands.js";
64
+ import { manualCommand, refusalReason } from "./selfUpdate.js";
65
+ import { currentInstall, requestRestart, runUpdate } from "./updateRunner.js";
61
66
  import { enableMouse, readWheel } from "./mouse.js";
62
67
  import { chatLayout, reflowScroll } from "./chatAnchor.js";
63
68
  import { virtualWindow } from "./virtualWindow.js";
@@ -81,6 +86,7 @@ const MINDWEAVE_DOCS_URL = "https://mindweave.dev";
81
86
  * in full, because only a bare invocation opens anything: given an argument each of these
82
87
  * acts directly and there is no surface to hold the frame for. */
83
88
  const OVERLAY_COMMANDS = new Set([
89
+ "/analytics",
84
90
  "/continue",
85
91
  "/key",
86
92
  "/mcp",
@@ -106,7 +112,7 @@ const RESUME_MODES = [
106
112
  { label: "Continue as-is", description: "resume the full conversation unchanged" },
107
113
  { label: "Fresh start", description: "leave it and start a new empty session here instead" },
108
114
  ];
109
- export function App() {
115
+ export function App({ resumeSessionId }) {
110
116
  // The transcript state machine lives in a ref and is advanced by the reducer as
111
117
  // the stream arrives; `render` forces a paint. A ref (not useState) so the async
112
118
  // streaming loop always reads/writes the latest state without stale closures.
@@ -321,8 +327,14 @@ export function App() {
321
327
  // the SAME reducer actions the live stream uses — so a resumed chat shows the
322
328
  // exact rows it did before: user/assistant prose, every `● Tool(arg)` with its
323
329
  // `⎿` result/diff, the consolidated discovery groups, and a marker where context
324
- // was summarized. The tool display fields (summary/detail/isError) were stored on
325
- // each tool entry at run time, so nothing about a row is lost across a resume.
330
+ // was summarized. The tool display fields (summary/detail/detailKind/isError) were
331
+ // stored on each tool entry at run time, so nothing about a row is lost across a resume.
332
+ //
333
+ // `detailKind` used to be missing from that list while this comment already claimed
334
+ // the rows came back identical. They did not: the diff text survived and the fact that
335
+ // it WAS a diff did not, so every resumed edit rendered as dim plain lines. Anything
336
+ // added to a row's appearance has to be stored here too, or the claim above quietly
337
+ // stops being true again.
326
338
  function showResumed(transcript) {
327
339
  for (const e of transcript) {
328
340
  if (e.role === "user") {
@@ -346,8 +358,22 @@ export function App() {
346
358
  dispatch({ type: "say", text: intermediate ? trimNarration(e.content) : e.content });
347
359
  }
348
360
  for (const call of e.toolCalls ?? []) {
361
+ // The spawn itself is drawn by its sub-agent block live, never as a raw row.
362
+ // Replaying it as one puts a `● SpawnSubagent(...)` in a resumed transcript
363
+ // that was not in the live one.
364
+ if (call.name === "spawn_subagent")
365
+ continue;
349
366
  const d = toolDisplay(call.name, parseToolArgs(call.arguments));
350
- dispatch({ type: "toolStart", toolId: call.id, name: d.name, arg: d.arg, action: d.kind, group: isGroupable(call.name) });
367
+ dispatch({
368
+ type: "toolStart",
369
+ toolId: call.id,
370
+ name: d.name,
371
+ arg: d.arg,
372
+ ...(d.meta ? { meta: d.meta } : {}),
373
+ action: d.kind,
374
+ group: isGroupable(call.name),
375
+ ...(d.covers ? { covers: d.covers } : {}),
376
+ });
351
377
  }
352
378
  }
353
379
  else {
@@ -358,6 +384,10 @@ export function App() {
358
384
  ok: e.isError === undefined ? !e.content.startsWith("Error:") : !e.isError,
359
385
  summary: e.summary,
360
386
  detail: e.detail,
387
+ ...(e.detailKind ? { detailKind: e.detailKind } : {}),
388
+ ...(e.quiet ? { quiet: true } : {}),
389
+ ...(e.displayName ? { name: e.displayName } : {}),
390
+ ...(e.displayKind ? { action: e.displayKind } : {}),
361
391
  });
362
392
  }
363
393
  }
@@ -407,6 +437,9 @@ export function App() {
407
437
  }
408
438
  function attachApproval(s) {
409
439
  s.toolContext.requestApproval = (q, o, detail, title) => askApproval.current(q, o, detail, title);
440
+ // Same stop Esc performs. A tool reaches for this when the USER has said, by
441
+ // dismissing a question, that the work should not carry on without them.
442
+ s.toolContext.interrupt = () => abortRef.current?.abort();
410
443
  s.toolContext.backgroundShells?.setOnChange(handleBgChange);
411
444
  // Servers connect in the background and can die or revive at any time; without this
412
445
  // the /mcp view would only ever show what was true when the last key was pressed.
@@ -480,10 +513,22 @@ export function App() {
480
513
  // One session for the whole conversation (a ref so it survives re-renders).
481
514
  const session = useRef(null);
482
515
  useEffect(() => {
483
- createSession().then((s) => {
516
+ // A relaunch after `/update` opens on the conversation it left. A resume that does
517
+ // not work out — an id that names nothing, a session file that will not load —
518
+ // falls through to a normal start rather than refusing to open: the point of the
519
+ // whole mechanism is that an update costs you nothing, and failing to come up at
520
+ // all is the one outcome worse than losing the thread. `/continue` still reaches it.
521
+ const open = resumeSessionId
522
+ ? resumeSession(process.cwd(), resumeSessionId).then((s) => s ?? createSession())
523
+ : createSession();
524
+ open.then((s) => {
484
525
  attachApproval(s);
485
526
  session.current = s;
486
527
  setReady(true);
528
+ if (resumeSessionId && s.id === resumeSessionId) {
529
+ showResumed(s.transcript);
530
+ note(`— updated to v${appVersion()}, continuing —`);
531
+ }
487
532
  // The project may have a model saved from a different provider than the
488
533
  // default, so re-check against what we're actually about to run.
489
534
  const cur = session.current;
@@ -505,6 +550,12 @@ export function App() {
505
550
  setSetupOpen(true);
506
551
  });
507
552
  }, []);
553
+ // Anonymous usage ping — on by default. The status line shows every launch, not just
554
+ // the first one; the full explanation lives in the /analytics box itself.
555
+ useEffect(() => {
556
+ note(startupStatusLine());
557
+ sendAnalyticsPing(appVersion());
558
+ }, []);
508
559
  // A quiet, backgrounded check for a newer release — independent of session startup so a
509
560
  // slow or unreachable registry can never delay the first prompt. checkForUpdate() is
510
561
  // itself the one that fails silent on every network path; a note only appears when a
@@ -513,7 +564,7 @@ export function App() {
513
564
  void checkForUpdate().then((latest) => {
514
565
  if (!latest)
515
566
  return;
516
- note(`update available: v${appVersion()} → v${latest} — npm install -g mindweave`);
567
+ note(`update available: v${appVersion()} → v${latest} — /update to take it`);
517
568
  });
518
569
  }, []);
519
570
  // When a background shell finishes and Mindweave is idle, react to it automatically.
@@ -1035,7 +1086,7 @@ export function App() {
1035
1086
  enqueueReveal({ type: "subToolStart", agentId: e.agent, toolId: e.id, name: d.name, arg: d.arg, action: d.kind });
1036
1087
  }
1037
1088
  else {
1038
- enqueueReveal({ type: "toolStart", toolId: e.id, name: d.name, arg: d.arg, meta: d.meta, action: d.kind, group: isGroupable(e.name) });
1089
+ enqueueReveal({ type: "toolStart", toolId: e.id, name: d.name, arg: d.arg, meta: d.meta, action: d.kind, group: isGroupable(e.name), ...(d.covers ? { covers: d.covers } : {}) });
1039
1090
  }
1040
1091
  }
1041
1092
  else if (e.type === "tool" && e.phase === "end") {
@@ -1319,7 +1370,11 @@ export function App() {
1319
1370
  return;
1320
1371
  }
1321
1372
  setOverlay(null);
1322
- if (o.kind === "resumeMode")
1373
+ if (o.kind === "analytics") {
1374
+ setAnalyticsEnabled(index === 0);
1375
+ note(`Analytics turned ${index === 0 ? "on" : "off"}.`);
1376
+ }
1377
+ else if (o.kind === "resumeMode")
1323
1378
  void applyResume(o.meta, index);
1324
1379
  else if (o.kind === "provider")
1325
1380
  void applyProvider(index);
@@ -1409,6 +1464,75 @@ export function App() {
1409
1464
  ]));
1410
1465
  return;
1411
1466
  }
1467
+ // /update — take the newer version and come back to this same conversation.
1468
+ //
1469
+ // The restart is the point. A process cannot replace its own code: driver modules
1470
+ // are imported lazily, so a copy that rewrote itself mid-session would be an old
1471
+ // process reading new files, which fails later and somewhere else. Ending and
1472
+ // reopening is what makes the new version actually the version running, and landing
1473
+ // back in this session is what makes that invisible.
1474
+ if (name === "/update") {
1475
+ if (busy) {
1476
+ note("Finish or stop the current turn first — restarting would strand it.", { error: true });
1477
+ return;
1478
+ }
1479
+ const install = currentInstall();
1480
+ if (install.kind !== "global") {
1481
+ // Never guessed at. Overwriting a working tree or another project's dependency
1482
+ // is the one thing this command must not do; see selfUpdate.ts.
1483
+ note(`${refusalReason(install)}\n\nTo do it by hand:\n ${manualCommand(install)}`, { error: true });
1484
+ return;
1485
+ }
1486
+ // Past the once-a-day cache deliberately: someone typing this is asking NOW, and
1487
+ // answering from a check made this morning would report "up to date" about a
1488
+ // release that has happened since.
1489
+ const latest = await checkForUpdate({ readCacheImpl: () => null });
1490
+ const current = appVersion();
1491
+ if (!latest) {
1492
+ note(`Already on the latest${current ? ` (v${current})` : ""}.`);
1493
+ return;
1494
+ }
1495
+ const choice = await askApproval.current(`Update from v${current} to v${latest}? Mindweave will restart into this same conversation.`, ["Yes, update and restart", "No"], manualCommand(install), "This will run");
1496
+ if (!choice.startsWith("Yes")) {
1497
+ note("Left as it is.");
1498
+ return;
1499
+ }
1500
+ note(`Updating to v${latest}…`);
1501
+ const result = await runUpdate(install.prefix);
1502
+ if (!result.ok) {
1503
+ // A failure here is a no-op rather than a half-state: npm either replaced the
1504
+ // package or it did not, and the copy running is the copy that always was.
1505
+ note(`The update did not run, and nothing was changed.\n${result.message}\n\nTo do it by hand:\n ${manualCommand(install)}`, { error: true });
1506
+ return;
1507
+ }
1508
+ // On disk before the successor goes looking for it.
1509
+ await saveSession(s);
1510
+ // Recorded rather than done here: the terminal can only be handed over once Ink
1511
+ // has unmounted, and unmounting is what ends this render. `index.ts` picks it up.
1512
+ requestRestart({
1513
+ packageRoot: install.packageRoot,
1514
+ sessionId: s.id,
1515
+ previousVersion: current,
1516
+ prefix: install.prefix,
1517
+ });
1518
+ exit();
1519
+ return;
1520
+ }
1521
+ // /analytics — the anonymous usage ping. Bare opens the on/off switch, the same
1522
+ // fixed box every other setting uses; an argument acts directly, same as /model.
1523
+ if (name === "/analytics") {
1524
+ const verb = arg.trim().toLowerCase();
1525
+ if (verb === "on" || verb === "off") {
1526
+ setAnalyticsEnabled(verb === "on");
1527
+ note(`Analytics turned ${verb}.`);
1528
+ return;
1529
+ }
1530
+ if (verb) {
1531
+ return say("/analytics on|off, or bare to open the switch.");
1532
+ }
1533
+ setOverlay({ kind: "analytics" });
1534
+ return;
1535
+ }
1412
1536
  // /clear — a fresh conversation without leaving the folder. Until now the only
1413
1537
  // way to start over was to quit and relaunch, or to go through /continue and pick
1414
1538
  // its third option, which is not somewhere anyone looks for "start over".
@@ -2016,6 +2140,14 @@ export function App() {
2016
2140
  if (!overlay)
2017
2141
  return null;
2018
2142
  const cur = session.current;
2143
+ if (overlay.kind === "analytics") {
2144
+ const enabled = analyticsEnabled();
2145
+ const items = [
2146
+ { label: "On" + (enabled ? " ✓" : ""), description: "sends the ping" },
2147
+ { label: "Off" + (!enabled ? " ✓" : ""), description: "sends nothing" },
2148
+ ];
2149
+ return (_jsx(Picker, { title: "Anonymous usage analytics", note: ANALYTICS_EXPLANATION, items: items, width: width, maxRows: maxRows, initialIndex: enabled ? 0 : 1, onSelect: onOverlaySelect, onCancel: onOverlayCancel }));
2150
+ }
2019
2151
  if (overlay.kind === "sessions") {
2020
2152
  const items = overlay.items.map((m) => ({
2021
2153
  label: sessionTitle(m),
@@ -2243,14 +2375,6 @@ const MIN_CHAT_ROWS = 3;
2243
2375
  const FOOTER_BASE_ROWS = 7;
2244
2376
  /** The palette's own chrome: title, the "Tab completes" hint, top+bottom border. */
2245
2377
  const MENU_CHROME_ROWS = 4;
2246
- /** Whether a block hugs the one above it — consecutive tool rows have no blank
2247
- * line between them. Takes the FULL list, not the rendered slice, so the first
2248
- * visible block still spaces correctly against the one scrolled off above it. */
2249
- function isTight(all, i) {
2250
- const block = all[i];
2251
- const prev = i > 0 ? all[i - 1] : undefined;
2252
- return !!block && block.kind === "tool" && !!prev && prev.kind === "tool";
2253
- }
2254
2378
  /** Lines PageUp/PageDown move per press. */
2255
2379
  const PAGE_LINES = 10;
2256
2380
  /** Lines one wheel notch moves. Three is the usual terminal step, and a flick
@@ -2383,30 +2507,6 @@ function useTerminalSize() {
2383
2507
  }, [stdout]);
2384
2508
  return size;
2385
2509
  }
2386
- // The built-in slash commands offered by the input's autocomplete. Project skills
2387
- // are appended at render time from the live session.
2388
- const BASE_COMMANDS = [
2389
- { name: "/help", description: "show this list" },
2390
- { name: "/init", description: "write MINDWEAVE.md — what the agent should always know about this project" },
2391
- { name: "/provider", description: "choose which provider serves this project" },
2392
- { name: "/key", description: "add or replace an API key" },
2393
- { name: "/model", description: "choose which model answers, from the current provider" },
2394
- { name: "/think", description: "set the reasoning level for the model" },
2395
- { name: "/rules", description: "list rules, or add one: /rules <directive>" },
2396
- { name: "/skills", description: "list skills, or make one: /skills <description>" },
2397
- { name: "/forbidden", description: "list protected paths, or add: /forbidden <path>" },
2398
- { name: "/forbid-command", description: "list forbidden commands, or add: /forbid-command <command>" },
2399
- { name: "/link", description: "pull in the rest of the project (monorepo / sibling repos)" },
2400
- { name: "/include", description: "add a folder to the workspace: /include <path>" },
2401
- { name: "/exclude", description: "remove an added folder: /exclude <label>" },
2402
- { name: "/shells", description: "view or stop background commands (tests, servers)" },
2403
- { name: "/mcp", description: "view MCP servers; /mcp add <name> <command|url> to connect one" },
2404
- { name: "/context", description: "show what Mindweave sees about this project" },
2405
- { name: "/undo", description: "roll back file changes: /undo, /undo list, /undo <n>" },
2406
- { name: "/compact", description: "summarize the conversation to free up context" },
2407
- { name: "/clear", description: "start a fresh conversation in this project" },
2408
- { name: "/continue", description: "pick a past session to resume" },
2409
- ];
2410
2510
  /** Split a /include argument into paths: quoted segments (spaces) or bare tokens. */
2411
2511
  function parsePaths(arg) {
2412
2512
  const out = [];