mindweave 2.3.1 → 2.4.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.
Files changed (121) hide show
  1. package/README.md +26 -43
  2. package/dist/alternator/chassis/lsp.js +67 -0
  3. package/dist/alternator/chassis/lsp.js.map +1 -1
  4. package/dist/cli/App.js +1175 -99
  5. package/dist/cli/App.js.map +1 -1
  6. package/dist/cli/altScreen.js +80 -10
  7. package/dist/cli/altScreen.js.map +1 -1
  8. package/dist/cli/appIdentity.js +54 -0
  9. package/dist/cli/appIdentity.js.map +1 -0
  10. package/dist/cli/attachments.js +27 -3
  11. package/dist/cli/attachments.js.map +1 -1
  12. package/dist/cli/blockHeights.js +58 -0
  13. package/dist/cli/blockHeights.js.map +1 -0
  14. package/dist/cli/caretPark.js +94 -0
  15. package/dist/cli/caretPark.js.map +1 -0
  16. package/dist/cli/clipboard.js +68 -0
  17. package/dist/cli/clipboard.js.map +1 -0
  18. package/dist/cli/commandLabel.js +19 -2
  19. package/dist/cli/commandLabel.js.map +1 -1
  20. package/dist/cli/commands.js +1 -0
  21. package/dist/cli/commands.js.map +1 -1
  22. package/dist/cli/components/BlockView.js +1 -1
  23. package/dist/cli/components/BlockView.js.map +1 -1
  24. package/dist/cli/components/KeyManager.js +11 -5
  25. package/dist/cli/components/KeyManager.js.map +1 -1
  26. package/dist/cli/components/Picker.js +18 -3
  27. package/dist/cli/components/Picker.js.map +1 -1
  28. package/dist/cli/components/PromptInput.js +300 -74
  29. package/dist/cli/components/PromptInput.js.map +1 -1
  30. package/dist/cli/components/TipLine.js +33 -0
  31. package/dist/cli/components/TipLine.js.map +1 -0
  32. package/dist/cli/components/ToolLine.js +30 -3
  33. package/dist/cli/components/ToolLine.js.map +1 -1
  34. package/dist/cli/dropHandles.js +76 -0
  35. package/dist/cli/dropHandles.js.map +1 -0
  36. package/dist/cli/exitCursor.js +56 -0
  37. package/dist/cli/exitCursor.js.map +1 -0
  38. package/dist/cli/framebuffer/overlay.js +42 -0
  39. package/dist/cli/framebuffer/overlay.js.map +1 -0
  40. package/dist/cli/framebuffer/paint.js +8 -1
  41. package/dist/cli/framebuffer/paint.js.map +1 -1
  42. package/dist/cli/framebuffer/parse.js +54 -4
  43. package/dist/cli/framebuffer/parse.js.map +1 -1
  44. package/dist/cli/framebuffer/writer.js +357 -6
  45. package/dist/cli/framebuffer/writer.js.map +1 -1
  46. package/dist/cli/groupReveal.js +29 -3
  47. package/dist/cli/groupReveal.js.map +1 -1
  48. package/dist/cli/inputView.js +45 -0
  49. package/dist/cli/inputView.js.map +1 -1
  50. package/dist/cli/livePad.js +53 -0
  51. package/dist/cli/livePad.js.map +1 -0
  52. package/dist/cli/memoryTuning.js +31 -0
  53. package/dist/cli/memoryTuning.js.map +1 -0
  54. package/dist/cli/messageQueue.js +76 -7
  55. package/dist/cli/messageQueue.js.map +1 -1
  56. package/dist/cli/mouse.js +40 -7
  57. package/dist/cli/mouse.js.map +1 -1
  58. package/dist/cli/quietConsole.js +50 -0
  59. package/dist/cli/quietConsole.js.map +1 -0
  60. package/dist/cli/screenMode.js +118 -0
  61. package/dist/cli/screenMode.js.map +1 -0
  62. package/dist/cli/screenShell.js +67 -0
  63. package/dist/cli/screenShell.js.map +1 -0
  64. package/dist/cli/screenStore.js +46 -0
  65. package/dist/cli/screenStore.js.map +1 -0
  66. package/dist/cli/scrollPill.js +101 -0
  67. package/dist/cli/scrollPill.js.map +1 -0
  68. package/dist/cli/selection.js +130 -0
  69. package/dist/cli/selection.js.map +1 -0
  70. package/dist/cli/startupFill.js +48 -0
  71. package/dist/cli/startupFill.js.map +1 -0
  72. package/dist/cli/terminalRestore.js +1 -1
  73. package/dist/cli/terminalRestore.js.map +1 -1
  74. package/dist/cli/toolDisplay.js +11 -2
  75. package/dist/cli/toolDisplay.js.map +1 -1
  76. package/dist/cli/toolItems.js +1 -0
  77. package/dist/cli/toolItems.js.map +1 -1
  78. package/dist/cli/transcript.js +54 -3
  79. package/dist/cli/transcript.js.map +1 -1
  80. package/dist/cli/wordEdit.js +95 -0
  81. package/dist/cli/wordEdit.js.map +1 -0
  82. package/dist/drivers/openai/manifest.js +4 -1
  83. package/dist/drivers/openai/manifest.js.map +1 -1
  84. package/dist/dynamo/engine.js +126 -20
  85. package/dist/dynamo/engine.js.map +1 -1
  86. package/dist/index.js +42 -3
  87. package/dist/index.js.map +1 -1
  88. package/dist/memory/pastedText.js +37 -0
  89. package/dist/memory/pastedText.js.map +1 -0
  90. package/dist/memory/presence.js +47 -0
  91. package/dist/memory/presence.js.map +1 -1
  92. package/dist/memory/session.js +78 -4
  93. package/dist/memory/session.js.map +1 -1
  94. package/dist/memory/store.js +22 -1
  95. package/dist/memory/store.js.map +1 -1
  96. package/dist/memory/types.js.map +1 -1
  97. package/dist/tools/backgroundShells.js +105 -18
  98. package/dist/tools/backgroundShells.js.map +1 -1
  99. package/dist/tools/commandOutput.js +207 -0
  100. package/dist/tools/commandOutput.js.map +1 -0
  101. package/dist/tools/detail.js +2 -2
  102. package/dist/tools/detail.js.map +1 -1
  103. package/dist/tools/openDefault.js +150 -0
  104. package/dist/tools/openDefault.js.map +1 -0
  105. package/dist/tools/outputShape.js +31 -6
  106. package/dist/tools/outputShape.js.map +1 -1
  107. package/dist/tools/pathList.js +32 -0
  108. package/dist/tools/pathList.js.map +1 -0
  109. package/dist/tools/readFile.js +3 -15
  110. package/dist/tools/readFile.js.map +1 -1
  111. package/dist/tools/registry.js +3 -1
  112. package/dist/tools/registry.js.map +1 -1
  113. package/dist/tools/runCommand.js +154 -58
  114. package/dist/tools/runCommand.js.map +1 -1
  115. package/dist/tools/screenshot.js +35 -3
  116. package/dist/tools/screenshot.js.map +1 -1
  117. package/dist/tools/viewImage.js +88 -0
  118. package/dist/tools/viewImage.js.map +1 -0
  119. package/dist/tools/writeFile.js +9 -1
  120. package/dist/tools/writeFile.js.map +1 -1
  121. package/package.json +74 -74
@@ -0,0 +1,53 @@
1
+ /**
2
+ * livePad.ts — keeping the inline shell's prompt at the bottom of the screen.
3
+ *
4
+ * ## What goes wrong without it
5
+ *
6
+ * The inline shell re-renders only its live region: the tail, the status line, the input,
7
+ * whatever sits under it. Ink draws that region by erasing the rows it drew last time and
8
+ * writing the new ones in their place.
9
+ *
10
+ * Opening the command palette makes that region a dozen rows taller. There is no room
11
+ * below it — a working session is always at the bottom of the screen — so the TERMINAL
12
+ * SCROLLS to make room, and everything above moves up. That part is fine and expected.
13
+ *
14
+ * Closing it again is where it breaks. Ink erases the tall region and writes the short
15
+ * one starting at the top of where the tall one was, so the prompt lands a dozen rows
16
+ * higher than the bottom of the screen with nothing beneath it. And it cannot simply be
17
+ * moved back down: the rows that scrolled off went into the terminal's scrollback, and no
18
+ * escape sequence pulls them back. The scroll is one-way.
19
+ *
20
+ * ## What this does instead
21
+ *
22
+ * RESERVES the rows the palette will need, from the start, and fills them with blanks
23
+ * until it needs them. The region is then one constant height whether the palette is open
24
+ * or closed: the terminal never scrolls for it, so there is never a scroll to compensate
25
+ * for, and the prompt never moves at all.
26
+ *
27
+ * The version before this held the rows back only AFTER the palette had taken them, and
28
+ * released them on the next committed message. That was wrong in a way that only showed on
29
+ * the second use: releasing assumed real output refills what the palette borrowed, and one
30
+ * line of output does not refill five rows — so the pad vanished while the scroll it was
31
+ * compensating for was still there, and the gap opened under the prompt.
32
+ */
33
+ /** How tall the live region may be padded to, as a fraction of the visible rows.
34
+ *
35
+ * A ceiling matters because the pad is inserted, not measured: a resize, a terminal that
36
+ * reports its size late, or a palette taller than the window could otherwise ask for more
37
+ * blank rows than there are rows, and the region would push its own prompt off screen. */
38
+ const MAX_PAD_FRACTION = 0.6;
39
+ /**
40
+ * Blank rows to insert above the live content (pure).
41
+ *
42
+ * @param contentRows the region's height right now, WITHOUT any pad
43
+ * @param highWater the tallest it has been since the last commit
44
+ * @param viewportRows the terminal's height
45
+ */
46
+ export function livePad(contentRows, highWater, viewportRows) {
47
+ if (!Number.isFinite(contentRows) || !Number.isFinite(highWater))
48
+ return 0;
49
+ const wanted = Math.max(0, Math.floor(highWater) - Math.floor(contentRows));
50
+ const ceiling = Math.max(0, Math.floor(viewportRows * MAX_PAD_FRACTION));
51
+ return Math.min(wanted, ceiling);
52
+ }
53
+ //# sourceMappingURL=livePad.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livePad.js","sourceRoot":"","sources":["../../src/cli/livePad.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;;2FAI2F;AAC3F,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,WAAmB,EAAE,SAAiB,EAAE,YAAoB;IAClF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,gBAAgB,CAAC,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * memoryTuning.ts — bias V8 toward a smaller footprint.
3
+ *
4
+ * A coding agent spends almost all of its time waiting: for a keystroke, for a model, for
5
+ * a command. It is I/O-bound, not compute-bound, so the CPU that V8's speed-first defaults
6
+ * buy is spent on nothing here, while the memory those defaults reserve is real and shows
7
+ * up in every process listing.
8
+ *
9
+ * `--optimize-for-size` flips that trade: V8 prefers memory over speed in its garbage
10
+ * collection and code-generation heuristics from this point on. It is applied at runtime
11
+ * rather than as a launch flag on purpose — the alternative is re-executing the process
12
+ * with the flag set, and re-exec on a program that owns a terminal is a real hazard (the
13
+ * child inherits a raw-mode stdin and an alternate screen mid-handoff). Setting it in
14
+ * process is the same effect without that risk.
15
+ *
16
+ * Gated by an env var so a machine that would rather have the speed can opt out, and so a
17
+ * measurement can compare the two without editing code.
18
+ */
19
+ import { setFlagsFromString } from "node:v8";
20
+ /** Apply the footprint bias unless `MINDWEAVE_NO_MEM_TUNING` is set. Best-effort. */
21
+ export function tuneMemory() {
22
+ if (process.env["MINDWEAVE_NO_MEM_TUNING"])
23
+ return;
24
+ try {
25
+ setFlagsFromString("--optimize-for-size");
26
+ }
27
+ catch {
28
+ // A build of Node that does not accept the flag at runtime; nothing to do.
29
+ }
30
+ }
31
+ //# sourceMappingURL=memoryTuning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryTuning.js","sourceRoot":"","sources":["../../src/cli/memoryTuning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE7C,qFAAqF;AACrF,MAAM,UAAU,UAAU;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;QAAE,OAAO;IACnD,IAAI,CAAC;QACH,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;AACH,CAAC"}
@@ -22,15 +22,75 @@
22
22
  * the drain batches. Kept deliberately as pure functions
23
23
  * over a plain array — the queue is small, always local to one App, and every rule in
24
24
  * it is worth a test.
25
+ *
26
+ * ## Two drains, because there are two moments
27
+ *
28
+ * A fourth thing was missing for longer than the other three: the queue only ever
29
+ * emptied when the turn was completely over. A message typed twenty seconds into a four
30
+ * minute task was not read for four minutes, and by then it was a comment on finished
31
+ * work rather than a correction to work in progress. Waiting is the wrong default for
32
+ * the only kind of message anyone types while watching an agent run.
33
+ *
34
+ * So `takeSteerable` hands what it can to the turn that is ALREADY RUNNING, at its next
35
+ * step boundary, and `drain` keeps the turn-boundary behaviour for what cannot be handed
36
+ * over — commands, and anything typed in the gap before the turn ended. The rules for
37
+ * which is which are on each function.
25
38
  */
26
39
  /** Rows of queue the footer will show before collapsing the rest into a count. */
27
40
  export const MAX_VISIBLE_QUEUED = 3;
28
- /** Whether this entry runs as a command rather than being said to the model. */
41
+ /** Whether this text runs as a command rather than being said to the model. */
29
42
  export function isCommand(text) {
30
43
  return text.trimStart().startsWith("/");
31
44
  }
32
45
  /**
33
- * What to send next, and what stays queued.
46
+ * Queue a message, deciding when it may go.
47
+ *
48
+ * `interrupting` is true when the user has already pressed Esc and the turn is still
49
+ * winding down. Without it the message would be steered into the turn they just
50
+ * stopped — Esc, then a fast correction, and the correction lands in the dying turn as
51
+ * if the stop had never happened. There is no mode field beyond this: a slash command
52
+ * is recognised by its own syntax, the same way the send path recognises it, so the
53
+ * queue cannot disagree with the thing that eventually runs it.
54
+ */
55
+ export function queueMessage(text, opts = {}) {
56
+ if (isCommand(text))
57
+ return { text, priority: "later" };
58
+ return { text, priority: opts.interrupting ? "now" : "next" };
59
+ }
60
+ /**
61
+ * What may be delivered INTO a turn that is already running, and what stays queued.
62
+ *
63
+ * A message typed while Mindweave is working is almost always about the work in
64
+ * progress. Held until the turn ends, it arrives after the thing it was about is
65
+ * finished, which is the wrong moment for every message worth typing — a correction
66
+ * lands too late to correct anything. So prose is handed to the running turn at its next
67
+ * step boundary and the model changes course.
68
+ *
69
+ * Only `next` is eligible, and only the unbroken run of it at the FRONT.
70
+ *
71
+ * A slash command (`later`) is not something to say to the model, it is something to DO,
72
+ * and doing it needs the turn to be over: `/model` mid-flight would change models
73
+ * between two calls of one conversation.
74
+ *
75
+ * A message typed after Esc (`now`) is not eligible either, and that one is easy to get
76
+ * wrong. The user stopped the turn; feeding them back into it would carry out their
77
+ * correction inside the very turn they cancelled.
78
+ *
79
+ * Stopping at the FIRST ineligible entry is what keeps the order honest. Someone who
80
+ * types a message, then `/model`, then another message meant the last one to come after
81
+ * the switch; steering it would run the three in an order they were never typed in.
82
+ */
83
+ export function takeSteerable(queue) {
84
+ let i = 0;
85
+ while (i < queue.length && queue[i].priority === "next")
86
+ i++;
87
+ return { send: queue.slice(0, i), rest: queue.slice(i) };
88
+ }
89
+ /**
90
+ * What to send next as its OWN turn, and what stays queued.
91
+ *
92
+ * The turn-boundary drain. What reaches it is what could not be steered: slash commands,
93
+ * and anything typed in the gap between the last step boundary and the turn ending.
34
94
  *
35
95
  * Consecutive plain messages go out TOGETHER, as one turn. Someone who types three
36
96
  * sentences while waiting meant them as one thought, and answering the first without
@@ -41,19 +101,28 @@ export function isCommand(text) {
41
101
  * A slash command always goes alone. It is not something to say to the model, it is
42
102
  * something to DO — merging `/model` into the prose around it would send the literal
43
103
  * word to the model instead of switching anything.
104
+ *
105
+ * Messages batch only with others of the SAME priority, which in practice means only
106
+ * `next` with `next`. A message typed after Esc is put to the model differently — it
107
+ * says the user stopped the work to send it — and merging one into the prose around it
108
+ * would apply that to text it was not true of.
44
109
  */
45
110
  export function drain(queue) {
46
111
  if (queue.length === 0)
47
112
  return undefined;
48
113
  const first = queue[0];
49
- if (isCommand(first))
50
- return { send: first, rest: queue.slice(1) };
114
+ if (first.priority !== "next")
115
+ return { send: first.text, priority: first.priority, rest: queue.slice(1) };
51
116
  let i = 0;
52
- while (i < queue.length && !isCommand(queue[i]))
117
+ while (i < queue.length && queue[i].priority === "next")
53
118
  i++;
54
119
  // A blank line between them, not a bare newline: these were separate messages, and
55
120
  // run together they read as one rambling paragraph.
56
- return { send: queue.slice(0, i).join("\n\n"), rest: queue.slice(i) };
121
+ return {
122
+ send: queue.slice(0, i).map((q) => q.text).join("\n\n"),
123
+ priority: "next",
124
+ rest: queue.slice(i),
125
+ };
57
126
  }
58
127
  /**
59
128
  * Pull the whole queue back into the input for editing.
@@ -71,7 +140,7 @@ export function drain(queue) {
71
140
  export function popAll(queue, currentInput, currentCursor) {
72
141
  if (queue.length === 0)
73
142
  return undefined;
74
- const queuedText = queue.join("\n");
143
+ const queuedText = queue.map((q) => q.text).join("\n");
75
144
  if (currentInput === "")
76
145
  return { text: queuedText, cursor: queuedText.length };
77
146
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"messageQueue.js","sourceRoot":"","sources":["../../src/cli/messageQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AASpC,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,KAAK,CAAC,KAAwB;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACxB,IAAI,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;QAAE,CAAC,EAAE,CAAC;IACtD,mFAAmF;IACnF,oDAAoD;IACpD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AASD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,MAAM,CACpB,KAAwB,EACxB,YAAoB,EACpB,aAAqB;IAErB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,YAAY,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;IAChF,OAAO;QACL,IAAI,EAAE,GAAG,UAAU,KAAK,YAAY,EAAE;QACtC,8CAA8C;QAC9C,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,GAAG,aAAa;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC;QACxC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,kBAAkB,CAAC;KACvD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"messageQueue.js","sourceRoot":"","sources":["../../src/cli/messageQueue.ts"],"names":[],"mappings":"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"}
package/dist/cli/mouse.js CHANGED
@@ -16,15 +16,23 @@
16
16
  * `[<64;25;26M` into the prompt. Anything reading typed input must strip them
17
17
  * first.
18
18
  *
19
- * The trade this makes, stated plainly: while wheel reporting is on, dragging to
20
- * select text is captured by the app instead of the terminal. Every terminal
21
- * worth using keeps selection available on Shift+drag, the same deal vim, htop,
22
- * and every other full-screen tool strikes.
19
+ * The trade this makes, stated plainly: while reporting is on, the terminal stops
20
+ * selecting text for us, because it has handed the mouse to the app. That is not a
21
+ * setting that can be had both ways — a terminal either owns the mouse or the app
22
+ * does. So the app owns it and does the selecting itself; see selection.ts.
23
23
  */
24
24
  import { MOUSE_OFF } from "./terminalRestore.js";
25
- /** Report wheel/button presses (1000) using SGR encoding (1006), the only
26
- * encoding that stays correct past column 223. */
27
- const MOUSE_ON = "\x1b[?1000h\x1b[?1006h";
25
+ /**
26
+ * Report button presses AND movement while a button is held (1002), using SGR encoding
27
+ * (1006), the only encoding that stays correct past column 223.
28
+ *
29
+ * 1002 rather than 1000 because 1000 reports only the press and the release. A drag is
30
+ * the movement BETWEEN them, so under 1000 a selection could only ever be told where it
31
+ * started and where it stopped, and nothing could be highlighted while the button was
32
+ * still down. 1002 is the narrower of the two motion modes: it stays quiet until a
33
+ * button is held, where 1003 reports every idle mouse movement across the window.
34
+ */
35
+ const MOUSE_ON = "\x1b[?1002h\x1b[?1006h";
28
36
  /**
29
37
  * An SGR mouse report: `ESC [ < button ; col ; row (M|m)`.
30
38
  *
@@ -51,6 +59,31 @@ export function readWheel(data) {
51
59
  }
52
60
  return out;
53
61
  }
62
+ /**
63
+ * Every left-button pointer event in a chunk of terminal input, in order (pure).
64
+ *
65
+ * Only the left button, because that is the one that selects. The wheel is read
66
+ * separately by `readWheel`, and the two never collide: a wheel report sets bit 6, which
67
+ * is checked for and skipped here.
68
+ */
69
+ export function readMouse(data) {
70
+ const out = [];
71
+ for (const match of data.matchAll(SGR)) {
72
+ const button = Number(match[1]);
73
+ if ((button & 64) !== 0)
74
+ continue; // a wheel notch, not a button
75
+ // Bits 0-1 name the button. A RELEASE under SGR reports the button that was let go,
76
+ // so this stays correct for both ends of a drag.
77
+ if ((button & 3) !== 0)
78
+ continue; // middle or right button
79
+ const x = Number(match[2]) - 1;
80
+ const y = Number(match[3]) - 1;
81
+ // Bit 5 marks movement; the final `m` (rather than `M`) marks a release.
82
+ const kind = match[4] === "m" ? "release" : (button & 32) !== 0 ? "drag" : "press";
83
+ out.push({ kind, x, y });
84
+ }
85
+ return out;
86
+ }
54
87
  /**
55
88
  * The same input with every mouse report removed (pure).
56
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"mouse.js","sourceRoot":"","sources":["../../src/cli/mouse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD;mDACmD;AACnD,MAAM,QAAQ,GAAG,wBAAwB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,GAAG,GAAG,kCAAkC,CAAC;AAI/C;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,6EAA6E;QAC7E,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC;YAAE,SAAS;QAClC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;iEACiE;AACjE,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,OAAO,GAAG,EAAE;QACV,IAAI,IAAI;YAAE,OAAO;QACjB,IAAI,GAAG,IAAI,CAAC;QACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"mouse.js","sourceRoot":"","sources":["../../src/cli/mouse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,QAAQ,GAAG,wBAAwB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,GAAG,GAAG,kCAAkC,CAAC;AAI/C;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,6EAA6E;QAC7E,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC;YAAE,SAAS;QAClC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAaD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC;YAAE,SAAS,CAAC,8BAA8B;QACjE,oFAAoF;QACpF,iDAAiD;QACjD,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC;YAAE,SAAS,CAAC,yBAAyB;QAC3D,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/B,yEAAyE;QACzE,MAAM,IAAI,GAAc,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9F,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;iEACiE;AACjE,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,OAAO,GAAG,EAAE;QACV,IAAI,IAAI;YAAE,OAAO;QACjB,IAAI,GAAG,IAAI,CAAC;QACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * quietConsole.ts — nothing but the renderer writes to the screen while the UI is up.
3
+ *
4
+ * A full-screen terminal app owns every cell of the terminal, and the framebuffer owns
5
+ * the record of what is in them. Anything that prints outside that record — a dependency
6
+ * logging a warning, a stray `console.log`, Node announcing a deprecation — lands on the
7
+ * screen the renderer believes it already knows, and at the bottom row it SCROLLS: every
8
+ * row moves up one, and the model now describes a screen that no longer exists.
9
+ *
10
+ * What that looks like is a single character stranded in the middle of an unrelated line,
11
+ * because a scrolled row is only visible where the new content is shorter than the old.
12
+ * A tool row reading `● Tools(sessions)` picks up the last surviving letter of whatever
13
+ * used to be above it.
14
+ *
15
+ * Ink's own answer, `patchConsole`, is worse rather than better: it routes console output
16
+ * INTO the frame stream, where the framebuffer parses it as a frame and writes the foreign
17
+ * text into the model itself. A wrong cell in the model is never revisited, because as far
18
+ * as a diff can see nothing about it changed, so that one survives every later frame.
19
+ * Written straight to the terminal it at least heals on the next full repaint.
20
+ *
21
+ * So neither: while the UI is mounted the console goes nowhere. Errors reach the user
22
+ * through the app's own surfaces, which can render them where they belong.
23
+ */
24
+ /** The methods that print. `assert` and `trace` print too, and both go through `error`. */
25
+ const SILENCED = ["log", "info", "warn", "error", "debug", "trace", "dir", "table", "group", "groupEnd"];
26
+ /**
27
+ * Silence the console, returning a function that puts it back.
28
+ *
29
+ * Restoring matters as much as silencing: `/update` hands the terminal over to an
30
+ * installer after the UI unmounts, and a crash report is printed after the alternate
31
+ * screen has been left. Both need a console again.
32
+ */
33
+ export function silenceConsole(target = console) {
34
+ const saved = new Map();
35
+ for (const name of SILENCED) {
36
+ if (typeof target[name] !== "function")
37
+ continue;
38
+ saved.set(name, target[name]);
39
+ target[name] = () => { };
40
+ }
41
+ let restored = false;
42
+ return () => {
43
+ if (restored)
44
+ return;
45
+ restored = true;
46
+ for (const [name, fn] of saved)
47
+ target[name] = fn;
48
+ };
49
+ }
50
+ //# sourceMappingURL=quietConsole.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quietConsole.js","sourceRoot":"","sources":["../../src/cli/quietConsole.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,2FAA2F;AAC3F,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC;AAIlH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,SAA6C,OAAO;IACjF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,UAAU;YAAE,SAAS;QACjD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO,GAAG,EAAE;QACV,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * screenMode.ts — which shell the app is wearing.
3
+ *
4
+ * The same transcript, the same blocks, the same streaming. Two ways of putting them on
5
+ * a terminal, and they are opposites about one thing: who owns the screen.
6
+ *
7
+ * ## fullscreen
8
+ *
9
+ * We do. The alternate screen buffer, every cell ours to paint, the input pinned to the
10
+ * bottom, our own scrolling, our own selection, our own cursor. Nothing leaks into the
11
+ * user's scrollback. The cost is that every one of those is something we have to be good
12
+ * at, and the bill grows with the length of the session: a frame costs what is on screen,
13
+ * and scrolling means rebuilding it.
14
+ *
15
+ * ## inline
16
+ *
17
+ * The terminal does. Finished blocks are printed ONCE, into the terminal's own
18
+ * scrollback, and never touched again; only the live tail is re-rendered. Scrolling is
19
+ * the terminal's scrollbar. Selecting and copying is the terminal's selection. Both are
20
+ * native, instant, and cost us nothing at all — and a frame costs what is happening now
21
+ * rather than what has happened all session, so a four-hour conversation renders exactly
22
+ * as fast as a four-minute one.
23
+ *
24
+ * What is given up is everything that needs the screen back: the pinned prompt scrolls
25
+ * away like a shell prompt, there is no mouse, no click-to-place-caret, no drag-select of
26
+ * our own, and a row that has been printed is final. It also works in places fullscreen
27
+ * cannot — over a poor ssh link, inside a multiplexer that eats mouse reporting, in a
28
+ * terminal with no alternate screen at all.
29
+ *
30
+ * Neither is the "real" one. `/screen` switches, and the choice is remembered for the
31
+ * project, so it is made once rather than at the start of every session.
32
+ */
33
+ /**
34
+ * Which shell a session opens in.
35
+ *
36
+ * The env var wins, so a single laggy ssh session can be started inline without changing
37
+ * anything the project keeps. Below it is the saved choice: the shell is a decision about
38
+ * how you want to work, not about one sitting, and having to make it again at the start
39
+ * of every session is the same decision being asked repeatedly. Neither present means
40
+ * fullscreen.
41
+ */
42
+ export function startupMode(env = process.env["MINDWEAVE_SCREEN"], saved) {
43
+ const fromEnv = env?.trim().toLowerCase();
44
+ if (fromEnv === "inline")
45
+ return "inline";
46
+ if (fromEnv === "fullscreen")
47
+ return "fullscreen";
48
+ return saved ?? "fullscreen";
49
+ }
50
+ /** A stored value read back off disk, which may be anything at all. */
51
+ export function parseSavedMode(raw) {
52
+ return raw === "inline" || raw === "fullscreen" ? raw : null;
53
+ }
54
+ /**
55
+ * What `/screen <arg>` names: an explicit mode, or nothing it can act on.
56
+ *
57
+ * `full` and `fs` are here because they are what people type, and a command that only
58
+ * accepts its own formal spelling is a command that fails for the reason least worth
59
+ * failing for. Anything unrecognised returns undefined so the caller can say what it
60
+ * accepts rather than silently picking a mode that was not asked for.
61
+ *
62
+ * An EMPTY argument is undefined too, not a toggle. Bare `/screen` opens the chooser,
63
+ * which is the caller's business and happens before this is reached — the two shells
64
+ * are not equals any more, and swapping between them without showing what each takes
65
+ * from the terminal is the thing the chooser exists to stop.
66
+ */
67
+ export function parseScreenArg(arg) {
68
+ const word = arg?.trim().toLowerCase() ?? "";
69
+ if (word === "inline" || word === "normal" || word === "plain")
70
+ return "inline";
71
+ if (word === "fullscreen" || word === "full" || word === "fs")
72
+ return "fullscreen";
73
+ return undefined;
74
+ }
75
+ /** One line naming the mode and what it means, for the transcript after a switch. */
76
+ export function screenNotice(mode) {
77
+ return mode === "inline"
78
+ ? "inline — the terminal scrolls and selects, and the prompt scrolls with it"
79
+ : "fullscreen — the prompt is pinned, and scrolling and selection are the app's";
80
+ }
81
+ /**
82
+ * The inline shell is BETA, and the label is not a disclaimer for its own sake.
83
+ *
84
+ * Keeping the prompt pinned while the terminal scrolls means taking the mouse, and a
85
+ * terminal hands over the whole mouse or none of it. So while an inline session runs,
86
+ * the terminal's own wheel, scrollbar and drag-to-select all stop responding — the app
87
+ * is holding them. Fullscreen makes the same trade, but there it is the point: the app
88
+ * owns the screen and does the scrolling and selecting itself. Inline is meant to be the
89
+ * shell that leaves those to the terminal, so here the same trade is a real loss, and
90
+ * one a chooser deserves to be told about before picking rather than after.
91
+ */
92
+ export const SCREEN_BETA = "inline";
93
+ /**
94
+ * Both shells, for the chooser (pure).
95
+ *
96
+ * Fullscreen is listed FIRST and that is deliberate: a list is read top down, and the
97
+ * settled shell belongs above the one still being worked on.
98
+ *
99
+ * `current` only marks which is in use. It does not reorder them — a menu whose entries
100
+ * move depending on where you already are is a menu you have to read every time instead
101
+ * of learning once.
102
+ */
103
+ export function screenChoices(current) {
104
+ const tick = (mode) => (mode === current ? " ✓" : "");
105
+ return [
106
+ {
107
+ mode: "fullscreen",
108
+ label: `Fullscreen${tick("fullscreen")}`,
109
+ description: "the app owns the screen; pinned prompt, its own scrolling and selection",
110
+ },
111
+ {
112
+ mode: "inline",
113
+ label: `Inline (beta)${tick("inline")}`,
114
+ description: "prints to scrollback; a work in progress, still has rough edges",
115
+ },
116
+ ];
117
+ }
118
+ //# sourceMappingURL=screenMode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenMode.js","sourceRoot":"","sources":["../../src/cli/screenMode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,KAAyB;IAC1F,MAAM,OAAO,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC1C,IAAI,OAAO,KAAK,YAAY;QAAE,OAAO,YAAY,CAAC;IAClD,OAAO,KAAK,IAAI,YAAY,CAAC;AAC/B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,GAAuB;IACpD,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC;IAChF,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC;IACnF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,OAAO,IAAI,KAAK,QAAQ;QACtB,CAAC,CAAC,2EAA2E;QAC7E,CAAC,CAAC,8EAA8E,CAAC;AACrF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,WAAW,GAAe,QAAQ,CAAC;AAShD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,OAAmB;IAC/C,MAAM,IAAI,GAAG,CAAC,IAAgB,EAAU,EAAE,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO;QACL;YACE,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,aAAa,IAAI,CAAC,YAAY,CAAC,EAAE;YACxC,WAAW,EAAE,yEAAyE;SACvF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,gBAAgB,IAAI,CAAC,QAAQ,CAAC,EAAE;YACvC,WAAW,EAAE,iEAAiE;SAC/E;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * screenShell.ts — putting the terminal into one shell or the other.
3
+ *
4
+ * `screenMode.ts` decides WHICH; this does it. Three things move together and the order
5
+ * they move in is the whole content of this file, because getting it wrong leaves the
6
+ * terminal in a state that is neither shell: the alternate screen buffer, mouse
7
+ * reporting, and whether the framebuffer is diffing frames or standing aside.
8
+ *
9
+ * ## Going inline, the framebuffer stands down FIRST
10
+ *
11
+ * It is the thing between Ink and the terminal. While it is still diffing, every write
12
+ * is being reinterpreted as a full-screen frame — including the escape that leaves the
13
+ * alternate screen. Standing it down first means everything after this point reaches the
14
+ * terminal exactly as Ink wrote it, which is what the inline shell needs and what the
15
+ * leave sequence itself needs.
16
+ *
17
+ * ## Coming back, it goes up LAST
18
+ *
19
+ * For the mirror reason. The screen has to be the alternate buffer, and the cursor and
20
+ * autowrap have to be set, before anything starts diffing against a model of them.
21
+ * Switching it on also throws that model away (see `setFramebufferEnabled`), because
22
+ * while it was off the terminal was scrolling on its own and nothing the model remembers
23
+ * is still true.
24
+ *
25
+ * ## Mouse reporting is not optional either way
26
+ *
27
+ * On, a terminal sends a report for every movement while a button is held. Nothing reads
28
+ * them in the inline shell, so they arrive at the input as text: the stray letters and
29
+ * digits that a mouse press used to leave in the prompt. Off is not a tidy-up, it is
30
+ * what stops the pointer typing.
31
+ */
32
+ import { setAltScreen, setCursorVisible } from "./altScreen.js";
33
+ import { setFramebufferEnabled } from "./framebuffer/writer.js";
34
+ import { enableMouse } from "./mouse.js";
35
+ /** Turns mouse reporting off again. Null when it is already off. */
36
+ let mouseOff = null;
37
+ /**
38
+ * Put the terminal into `mode`. Safe to call with the mode it is already in.
39
+ *
40
+ * `enableMouse` is injected so a test can drive the sequence without a terminal; the
41
+ * default is the real one.
42
+ */
43
+ export function applyScreenMode(mode, deps = {}) {
44
+ const alt = deps.setAltScreen ?? setAltScreen;
45
+ const framebuffer = deps.setFramebufferEnabled ?? setFramebufferEnabled;
46
+ const mouse = deps.enableMouse ?? enableMouse;
47
+ const cursor = deps.setCursorVisible ?? setCursorVisible;
48
+ if (mode === "inline") {
49
+ framebuffer(false);
50
+ mouseOff?.();
51
+ mouseOff = null;
52
+ alt(false);
53
+ // Leaving the alternate screen restores the cursor, and the inline shell does not
54
+ // want it: it draws its own caret in the input, and the real one would sit below
55
+ // everything as a second one. After the leave, or the restore would undo this.
56
+ cursor(false);
57
+ return;
58
+ }
59
+ alt(true);
60
+ mouseOff ??= mouse();
61
+ framebuffer(true);
62
+ }
63
+ /** Forget any mouse subscription, for a test that runs the sequence more than once. */
64
+ export function resetScreenShell() {
65
+ mouseOff = null;
66
+ }
67
+ //# sourceMappingURL=screenShell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenShell.js","sourceRoot":"","sources":["../../src/cli/screenShell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,oEAAoE;AACpE,IAAI,QAAQ,GAAwB,IAAI,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAgB,EAChB,OAKI,EAAE;IAEN,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,IAAI,qBAAqB,CAAC;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,CAAC;IAEzD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,QAAQ,EAAE,EAAE,CAAC;QACb,QAAQ,GAAG,IAAI,CAAC;QAChB,GAAG,CAAC,KAAK,CAAC,CAAC;QACX,kFAAkF;QAClF,iFAAiF;QACjF,+EAA+E;QAC/E,MAAM,CAAC,KAAK,CAAC,CAAC;QACd,OAAO;IACT,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,CAAC;IACV,QAAQ,KAAK,KAAK,EAAE,CAAC;IACrB,WAAW,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,gBAAgB;IAC9B,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * screenStore.ts — remembering which shell a project is worked in.
3
+ *
4
+ * Beside the model config, in the project's own directory under `~/.mindweave`, for the
5
+ * same reason that one lives there: it is a decision about how you work on THIS project,
6
+ * not a global preference and not something to carry in the repository.
7
+ *
8
+ * Its own tiny file rather than a field on the model config, so a write here can never
9
+ * damage or race the thing that decides which provider answers. Both are best-effort:
10
+ * failing to remember a preference is worth nothing at all next to failing to start.
11
+ */
12
+ import { promises as fs } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { projectDir } from "../memory/store.js";
15
+ import { parseSavedMode } from "./screenMode.js";
16
+ function screenPath(projectCwd) {
17
+ return join(projectDir(projectCwd), "screen.json");
18
+ }
19
+ /**
20
+ * The shell this project was last left in, or null.
21
+ *
22
+ * Null for every reason: never saved, unreadable, corrupt, or holding a word that is not
23
+ * a mode any more. The caller falls back to the default, which is what someone who has
24
+ * never chosen would get — the safe direction, because a session that opens in the wrong
25
+ * shell is fixed by one command, while a session that fails to open is not.
26
+ */
27
+ export async function loadScreenMode(projectCwd) {
28
+ try {
29
+ const raw = await fs.readFile(screenPath(projectCwd), "utf8");
30
+ return parseSavedMode(JSON.parse(raw).screen);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** Remember the shell for next time. Best-effort. */
37
+ export async function saveScreenMode(projectCwd, mode) {
38
+ try {
39
+ await fs.mkdir(projectDir(projectCwd), { recursive: true });
40
+ await fs.writeFile(screenPath(projectCwd), JSON.stringify({ screen: mode }, null, 2), "utf8");
41
+ }
42
+ catch {
43
+ /* best-effort */
44
+ }
45
+ }
46
+ //# sourceMappingURL=screenStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenStore.js","sourceRoot":"","sources":["../../src/cli/screenStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAmB,MAAM,iBAAiB,CAAC;AAElE,SAAS,UAAU,CAAC,UAAkB;IACpC,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB;IACrD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;QAC9D,OAAO,cAAc,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC,MAAM,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAgB;IACvE,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;AACH,CAAC"}