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.
- package/README.md +26 -43
- package/dist/alternator/chassis/lsp.js +67 -0
- package/dist/alternator/chassis/lsp.js.map +1 -1
- package/dist/cli/App.js +1175 -99
- package/dist/cli/App.js.map +1 -1
- package/dist/cli/altScreen.js +80 -10
- package/dist/cli/altScreen.js.map +1 -1
- package/dist/cli/appIdentity.js +54 -0
- package/dist/cli/appIdentity.js.map +1 -0
- package/dist/cli/attachments.js +27 -3
- package/dist/cli/attachments.js.map +1 -1
- package/dist/cli/blockHeights.js +58 -0
- package/dist/cli/blockHeights.js.map +1 -0
- package/dist/cli/caretPark.js +94 -0
- package/dist/cli/caretPark.js.map +1 -0
- package/dist/cli/clipboard.js +68 -0
- package/dist/cli/clipboard.js.map +1 -0
- package/dist/cli/commandLabel.js +19 -2
- package/dist/cli/commandLabel.js.map +1 -1
- package/dist/cli/commands.js +1 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/components/BlockView.js +1 -1
- package/dist/cli/components/BlockView.js.map +1 -1
- package/dist/cli/components/KeyManager.js +11 -5
- package/dist/cli/components/KeyManager.js.map +1 -1
- package/dist/cli/components/Picker.js +18 -3
- package/dist/cli/components/Picker.js.map +1 -1
- package/dist/cli/components/PromptInput.js +300 -74
- package/dist/cli/components/PromptInput.js.map +1 -1
- package/dist/cli/components/TipLine.js +33 -0
- package/dist/cli/components/TipLine.js.map +1 -0
- package/dist/cli/components/ToolLine.js +30 -3
- package/dist/cli/components/ToolLine.js.map +1 -1
- package/dist/cli/dropHandles.js +76 -0
- package/dist/cli/dropHandles.js.map +1 -0
- package/dist/cli/exitCursor.js +56 -0
- package/dist/cli/exitCursor.js.map +1 -0
- package/dist/cli/framebuffer/overlay.js +42 -0
- package/dist/cli/framebuffer/overlay.js.map +1 -0
- package/dist/cli/framebuffer/paint.js +8 -1
- package/dist/cli/framebuffer/paint.js.map +1 -1
- package/dist/cli/framebuffer/parse.js +54 -4
- package/dist/cli/framebuffer/parse.js.map +1 -1
- package/dist/cli/framebuffer/writer.js +357 -6
- package/dist/cli/framebuffer/writer.js.map +1 -1
- package/dist/cli/groupReveal.js +29 -3
- package/dist/cli/groupReveal.js.map +1 -1
- package/dist/cli/inputView.js +45 -0
- package/dist/cli/inputView.js.map +1 -1
- package/dist/cli/livePad.js +53 -0
- package/dist/cli/livePad.js.map +1 -0
- package/dist/cli/memoryTuning.js +31 -0
- package/dist/cli/memoryTuning.js.map +1 -0
- package/dist/cli/messageQueue.js +76 -7
- package/dist/cli/messageQueue.js.map +1 -1
- package/dist/cli/mouse.js +40 -7
- package/dist/cli/mouse.js.map +1 -1
- package/dist/cli/quietConsole.js +50 -0
- package/dist/cli/quietConsole.js.map +1 -0
- package/dist/cli/screenMode.js +118 -0
- package/dist/cli/screenMode.js.map +1 -0
- package/dist/cli/screenShell.js +67 -0
- package/dist/cli/screenShell.js.map +1 -0
- package/dist/cli/screenStore.js +46 -0
- package/dist/cli/screenStore.js.map +1 -0
- package/dist/cli/scrollPill.js +101 -0
- package/dist/cli/scrollPill.js.map +1 -0
- package/dist/cli/selection.js +130 -0
- package/dist/cli/selection.js.map +1 -0
- package/dist/cli/startupFill.js +48 -0
- package/dist/cli/startupFill.js.map +1 -0
- package/dist/cli/terminalRestore.js +1 -1
- package/dist/cli/terminalRestore.js.map +1 -1
- package/dist/cli/toolDisplay.js +11 -2
- package/dist/cli/toolDisplay.js.map +1 -1
- package/dist/cli/toolItems.js +1 -0
- package/dist/cli/toolItems.js.map +1 -1
- package/dist/cli/transcript.js +54 -3
- package/dist/cli/transcript.js.map +1 -1
- package/dist/cli/wordEdit.js +95 -0
- package/dist/cli/wordEdit.js.map +1 -0
- package/dist/drivers/openai/manifest.js +4 -1
- package/dist/drivers/openai/manifest.js.map +1 -1
- package/dist/dynamo/engine.js +126 -20
- package/dist/dynamo/engine.js.map +1 -1
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/dist/memory/pastedText.js +37 -0
- package/dist/memory/pastedText.js.map +1 -0
- package/dist/memory/presence.js +47 -0
- package/dist/memory/presence.js.map +1 -1
- package/dist/memory/session.js +78 -4
- package/dist/memory/session.js.map +1 -1
- package/dist/memory/store.js +22 -1
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/tools/backgroundShells.js +105 -18
- package/dist/tools/backgroundShells.js.map +1 -1
- package/dist/tools/commandOutput.js +207 -0
- package/dist/tools/commandOutput.js.map +1 -0
- package/dist/tools/detail.js +2 -2
- package/dist/tools/detail.js.map +1 -1
- package/dist/tools/openDefault.js +150 -0
- package/dist/tools/openDefault.js.map +1 -0
- package/dist/tools/outputShape.js +31 -6
- package/dist/tools/outputShape.js.map +1 -1
- package/dist/tools/pathList.js +32 -0
- package/dist/tools/pathList.js.map +1 -0
- package/dist/tools/readFile.js +3 -15
- package/dist/tools/readFile.js.map +1 -1
- package/dist/tools/registry.js +3 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/runCommand.js +154 -58
- package/dist/tools/runCommand.js.map +1 -1
- package/dist/tools/screenshot.js +35 -3
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/viewImage.js +88 -0
- package/dist/tools/viewImage.js.map +1 -0
- package/dist/tools/writeFile.js +9 -1
- package/dist/tools/writeFile.js.map +1 -1
- 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"}
|
package/dist/cli/messageQueue.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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 (
|
|
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 &&
|
|
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 {
|
|
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
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* and
|
|
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
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
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
|
*
|
package/dist/cli/mouse.js.map
CHANGED
|
@@ -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;
|
|
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"}
|