@namzu/cli 8.2.0 → 8.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/dist/doctor/checks/credentials.d.ts.map +1 -1
- package/dist/doctor/checks/credentials.js +2 -0
- package/dist/doctor/checks/credentials.js.map +1 -1
- package/dist/integrations/providers/credential-store.d.ts +118 -0
- package/dist/integrations/providers/credential-store.d.ts.map +1 -0
- package/dist/integrations/providers/credential-store.js +322 -0
- package/dist/integrations/providers/credential-store.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts +37 -10
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +67 -13
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/identity.d.ts +75 -0
- package/dist/integrations/providers/identity.d.ts.map +1 -0
- package/dist/integrations/providers/identity.js +75 -0
- package/dist/integrations/providers/identity.js.map +1 -0
- package/dist/integrations/providers/index.d.ts +4 -1
- package/dist/integrations/providers/index.d.ts.map +1 -1
- package/dist/integrations/providers/index.js +4 -1
- package/dist/integrations/providers/index.js.map +1 -1
- package/dist/integrations/providers/oauth.d.ts +48 -7
- package/dist/integrations/providers/oauth.d.ts.map +1 -1
- package/dist/integrations/providers/oauth.js +73 -17
- package/dist/integrations/providers/oauth.js.map +1 -1
- package/dist/integrations/providers/subscription-login.d.ts +123 -0
- package/dist/integrations/providers/subscription-login.d.ts.map +1 -0
- package/dist/integrations/providers/subscription-login.js +348 -0
- package/dist/integrations/providers/subscription-login.js.map +1 -0
- package/dist/tui/App.d.ts +1 -17
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +185 -49
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Markdown.d.ts +14 -0
- package/dist/tui/Markdown.d.ts.map +1 -1
- package/dist/tui/Markdown.js +31 -4
- package/dist/tui/Markdown.js.map +1 -1
- package/dist/tui/Picker.js +4 -0
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/Transcript.d.ts +18 -2
- package/dist/tui/Transcript.d.ts.map +1 -1
- package/dist/tui/Transcript.js +48 -13
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +15 -7
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/bottom-spacer.d.ts +22 -8
- package/dist/tui/bottom-spacer.d.ts.map +1 -1
- package/dist/tui/bottom-spacer.js.map +1 -1
- package/dist/tui/credential-entry.d.ts +16 -12
- package/dist/tui/credential-entry.d.ts.map +1 -1
- package/dist/tui/credential-entry.js +16 -12
- package/dist/tui/credential-entry.js.map +1 -1
- package/dist/tui/live-window.d.ts +97 -0
- package/dist/tui/live-window.d.ts.map +1 -0
- package/dist/tui/live-window.js +140 -0
- package/dist/tui/live-window.js.map +1 -0
- package/dist/tui/login-prompt.d.ts +41 -0
- package/dist/tui/login-prompt.d.ts.map +1 -0
- package/dist/tui/login-prompt.js +69 -0
- package/dist/tui/login-prompt.js.map +1 -0
- package/dist/tui/markdown-block-cache.d.ts +57 -0
- package/dist/tui/markdown-block-cache.d.ts.map +1 -0
- package/dist/tui/markdown-block-cache.js +74 -0
- package/dist/tui/markdown-block-cache.js.map +1 -0
- package/dist/tui/markdownParser.d.ts +33 -0
- package/dist/tui/markdownParser.d.ts.map +1 -1
- package/dist/tui/markdownParser.js +124 -54
- package/dist/tui/markdownParser.js.map +1 -1
- package/dist/tui/open-browser.d.ts +24 -0
- package/dist/tui/open-browser.d.ts.map +1 -0
- package/dist/tui/open-browser.js +57 -0
- package/dist/tui/open-browser.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +16 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +13 -0
- package/dist/tui/slashCommands.js.map +1 -1
- package/package.json +3 -3
|
@@ -7,19 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* ## Why session-only, stated as a decision rather than a limitation
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Writing a namzu key into that envelope would put our secret under their name.
|
|
14
|
-
* On Windows, which is where this was asked for, there is no keychain path at
|
|
15
|
-
* all.
|
|
10
|
+
* A durable store now exists next door (`credential-store.ts`), and this path
|
|
11
|
+
* still does not use it — which is the interesting part, because the original
|
|
12
|
+
* reason for session-only was that no such store had been written.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
14
|
+
* That reason is gone; the decision is not, and it rests on the one thing the
|
|
15
|
+
* two paths do not share. `/login` OBTAINS a credential: the operator asked for
|
|
16
|
+
* one, namzu got it, and namzu can refresh it, name where it came from, and
|
|
17
|
+
* remove it again. A key typed here arrived from somewhere namzu had no part
|
|
18
|
+
* in — there is nothing to refresh it with, no origin to report, and no
|
|
19
|
+
* meaningful way to revoke it on the operator's behalf. Writing that to disk
|
|
20
|
+
* uninvited would be namzu deciding, for someone else, that their secret should
|
|
21
|
+
* persist.
|
|
22
|
+
*
|
|
23
|
+
* So the key stays in memory: nothing lands on disk, it works everywhere, and
|
|
24
|
+
* the screen names the environment variable that makes it durable. A secret at
|
|
25
|
+
* rest should be something the operator chose, not something that arrived
|
|
26
|
+
* because they typed into a text field — and now there are two ways to choose.
|
|
23
27
|
*
|
|
24
28
|
* Everything decidable lives here rather than in the component, because this
|
|
25
29
|
* package has no component tests and a secret is the worst thing to leave
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-entry.d.ts","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential-entry.d.ts","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAE1B,MAAM,oCAAoC,CAAA;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,oBAAoB,CAAA;AAE7D;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAG5F;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmBxF;AAED,qDAAqD;AACrD,MAAM,MAAM,YAAY,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE;AAC/B,oEAAoE;GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,qBAAqB,EAC5B,YAAY,EAAE,YAAY,EAC1B,IAAI,GAAE,cAA0B,GAC9B,MAAM,CAmCR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAQ7F"}
|
|
@@ -7,19 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* ## Why session-only, stated as a decision rather than a limitation
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Writing a namzu key into that envelope would put our secret under their name.
|
|
14
|
-
* On Windows, which is where this was asked for, there is no keychain path at
|
|
15
|
-
* all.
|
|
10
|
+
* A durable store now exists next door (`credential-store.ts`), and this path
|
|
11
|
+
* still does not use it — which is the interesting part, because the original
|
|
12
|
+
* reason for session-only was that no such store had been written.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
14
|
+
* That reason is gone; the decision is not, and it rests on the one thing the
|
|
15
|
+
* two paths do not share. `/login` OBTAINS a credential: the operator asked for
|
|
16
|
+
* one, namzu got it, and namzu can refresh it, name where it came from, and
|
|
17
|
+
* remove it again. A key typed here arrived from somewhere namzu had no part
|
|
18
|
+
* in — there is nothing to refresh it with, no origin to report, and no
|
|
19
|
+
* meaningful way to revoke it on the operator's behalf. Writing that to disk
|
|
20
|
+
* uninvited would be namzu deciding, for someone else, that their secret should
|
|
21
|
+
* persist.
|
|
22
|
+
*
|
|
23
|
+
* So the key stays in memory: nothing lands on disk, it works everywhere, and
|
|
24
|
+
* the screen names the environment variable that makes it durable. A secret at
|
|
25
|
+
* rest should be something the operator chose, not something that arrived
|
|
26
|
+
* because they typed into a text field — and now there are two ways to choose.
|
|
23
27
|
*
|
|
24
28
|
* Everything decidable lives here rather than in the component, because this
|
|
25
29
|
* package has no component tests and a secret is the worst thing to leave
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-entry.js","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential-entry.js","sourceRoot":"","sources":["../../src/tui/credential-entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of the transcript is still ALIVE — redrawable — and how much has
|
|
3
|
+
* already been handed to the terminal's own scrollback.
|
|
4
|
+
*
|
|
5
|
+
* ## Why there is a window at all
|
|
6
|
+
*
|
|
7
|
+
* Every finalized row used to go straight into `<Static>`, which prints a row
|
|
8
|
+
* once and never redraws it. That kept per-frame work bounded, and it also made
|
|
9
|
+
* changing a row that is already on screen impossible by construction: an
|
|
10
|
+
* expand key could not reopen a collapsed body, so expansion had to become a
|
|
11
|
+
* command that appends a second copy of the output further down.
|
|
12
|
+
*
|
|
13
|
+
* A small window of the most recent rows is kept live instead. Inside it a row
|
|
14
|
+
* can be redrawn in place, so expansion works where an operator actually looks;
|
|
15
|
+
* `/expand <n>` remains the way to reach anything older, and the hint under a
|
|
16
|
+
* collapsed body already degrades correctly when a row has no number.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the window is bounded by HEIGHT, not by a count of rows
|
|
19
|
+
*
|
|
20
|
+
* This is the constraint the whole design turns on, and it is a property of the
|
|
21
|
+
* renderer rather than a preference. When the live region's height reaches the
|
|
22
|
+
* viewport's, ink stops doing an incremental repaint and instead writes
|
|
23
|
+
* `clearTerminal` followed by **the entire static output it has accumulated
|
|
24
|
+
* this session** plus the frame. On a long session that is the whole transcript,
|
|
25
|
+
* re-serialized on every spinner tick — which is the allocation churn that
|
|
26
|
+
* pushed `<Static>` to swallow everything in the first place.
|
|
27
|
+
*
|
|
28
|
+
* So the window takes rows from the end only while their estimated height fits
|
|
29
|
+
* a budget the live furniture and a safety margin are already subtracted from.
|
|
30
|
+
* A single row taller than the budget leaves the window empty, and an empty
|
|
31
|
+
* window is exactly the previous behaviour — the failure direction is "no
|
|
32
|
+
* retroactive expansion", never "repaint the session".
|
|
33
|
+
*
|
|
34
|
+
* ## Why it only ever moves one way
|
|
35
|
+
*
|
|
36
|
+
* `settled` is a floor the caller carries between renders. `<Static>` keeps an
|
|
37
|
+
* index of how many items it has emitted and renders only what is past it, so a
|
|
38
|
+
* shrinking item list would leave rows unprinted; and a row that has already
|
|
39
|
+
* been drawn live must not be handed back to a mechanism that would print it
|
|
40
|
+
* again. Rows therefore leave the window in one direction, oldest first, and a
|
|
41
|
+
* row that has settled never comes back.
|
|
42
|
+
*/
|
|
43
|
+
import type { TranscriptMessage } from './types.js';
|
|
44
|
+
/**
|
|
45
|
+
* The most rows the window will ever hold, whatever the terminal's height.
|
|
46
|
+
*
|
|
47
|
+
* A cap on top of the height budget, because the budget alone would put thirty
|
|
48
|
+
* short rows in the live region on a tall terminal — thirty rows re-laid-out
|
|
49
|
+
* per spinner tick to make retroactive expansion available on output nobody is
|
|
50
|
+
* still looking at. Six covers what an operator is working with: a tool call,
|
|
51
|
+
* its result, and the exchange around them.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_LIVE_ROWS = 6;
|
|
54
|
+
export interface LiveWindowInput {
|
|
55
|
+
/** Finalized rows, oldest first. The pending row is not one of these. */
|
|
56
|
+
readonly messages: readonly TranscriptMessage[];
|
|
57
|
+
/** Terminal height. `undefined` when not a TTY. */
|
|
58
|
+
readonly rows: number | undefined;
|
|
59
|
+
/** Terminal width, for wrapping. `undefined` falls back to 80. */
|
|
60
|
+
readonly columns: number | undefined;
|
|
61
|
+
/** Rows the live region occupies apart from the window: activity, composer, status bar. */
|
|
62
|
+
readonly furnitureRows: number;
|
|
63
|
+
/** How many rows have already settled into scrollback. The window never reaches back past this. */
|
|
64
|
+
readonly settled: number;
|
|
65
|
+
}
|
|
66
|
+
export interface LiveWindow {
|
|
67
|
+
/** `messages[0, settled)` are in scrollback; the rest are drawn live. */
|
|
68
|
+
readonly settled: number;
|
|
69
|
+
/** The live rows' estimated height, for the bottom spacer to account for. */
|
|
70
|
+
readonly rows: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Every line the finalized transcript will print, for a height estimate.
|
|
74
|
+
*
|
|
75
|
+
* Exported so the wiring is testable rather than only typecheckable. The
|
|
76
|
+
* spacer's own docblock states the asymmetry it depends on: over-count the
|
|
77
|
+
* content and the composer merely floats, under-count it and the composer is
|
|
78
|
+
* pushed off the bottom. The caller used to pass each row's `content` alone,
|
|
79
|
+
* which counted a six-line collapsed tool body as nothing at all — the estimate
|
|
80
|
+
* ran low by six per tool call, in the direction that costs the usability
|
|
81
|
+
* rather than the feature. `/expand` makes it acute: a row whose entire
|
|
82
|
+
* substance is a two-hundred-line body would have been handed over as one line.
|
|
83
|
+
*
|
|
84
|
+
* A pending row is excluded because it is not in the static log yet; the
|
|
85
|
+
* spacer's `liveRows` covers the live region.
|
|
86
|
+
*/
|
|
87
|
+
export declare function transcriptLines(messages: readonly TranscriptMessage[]): readonly string[];
|
|
88
|
+
/**
|
|
89
|
+
* How much of the transcript stays live, given the room there is for it.
|
|
90
|
+
*
|
|
91
|
+
* Returns `settled === messages.length` — everything in scrollback, nothing
|
|
92
|
+
* redrawable — whenever the answer is not knowable: no TTY, an implausible
|
|
93
|
+
* height, or a most-recent row too tall to hold. That is the previous
|
|
94
|
+
* behaviour, and it is the safe direction.
|
|
95
|
+
*/
|
|
96
|
+
export declare function liveWindow(input: LiveWindowInput): LiveWindow;
|
|
97
|
+
//# sourceMappingURL=live-window.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-window.d.ts","sourceRoot":"","sources":["../../src/tui/live-window.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;IAC/C,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,2FAA2F;IAC3F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,UAAU;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACrB;AAmBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GAAG,SAAS,MAAM,EAAE,CAGzF;AA4BD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,CAmB7D"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of the transcript is still ALIVE — redrawable — and how much has
|
|
3
|
+
* already been handed to the terminal's own scrollback.
|
|
4
|
+
*
|
|
5
|
+
* ## Why there is a window at all
|
|
6
|
+
*
|
|
7
|
+
* Every finalized row used to go straight into `<Static>`, which prints a row
|
|
8
|
+
* once and never redraws it. That kept per-frame work bounded, and it also made
|
|
9
|
+
* changing a row that is already on screen impossible by construction: an
|
|
10
|
+
* expand key could not reopen a collapsed body, so expansion had to become a
|
|
11
|
+
* command that appends a second copy of the output further down.
|
|
12
|
+
*
|
|
13
|
+
* A small window of the most recent rows is kept live instead. Inside it a row
|
|
14
|
+
* can be redrawn in place, so expansion works where an operator actually looks;
|
|
15
|
+
* `/expand <n>` remains the way to reach anything older, and the hint under a
|
|
16
|
+
* collapsed body already degrades correctly when a row has no number.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the window is bounded by HEIGHT, not by a count of rows
|
|
19
|
+
*
|
|
20
|
+
* This is the constraint the whole design turns on, and it is a property of the
|
|
21
|
+
* renderer rather than a preference. When the live region's height reaches the
|
|
22
|
+
* viewport's, ink stops doing an incremental repaint and instead writes
|
|
23
|
+
* `clearTerminal` followed by **the entire static output it has accumulated
|
|
24
|
+
* this session** plus the frame. On a long session that is the whole transcript,
|
|
25
|
+
* re-serialized on every spinner tick — which is the allocation churn that
|
|
26
|
+
* pushed `<Static>` to swallow everything in the first place.
|
|
27
|
+
*
|
|
28
|
+
* So the window takes rows from the end only while their estimated height fits
|
|
29
|
+
* a budget the live furniture and a safety margin are already subtracted from.
|
|
30
|
+
* A single row taller than the budget leaves the window empty, and an empty
|
|
31
|
+
* window is exactly the previous behaviour — the failure direction is "no
|
|
32
|
+
* retroactive expansion", never "repaint the session".
|
|
33
|
+
*
|
|
34
|
+
* ## Why it only ever moves one way
|
|
35
|
+
*
|
|
36
|
+
* `settled` is a floor the caller carries between renders. `<Static>` keeps an
|
|
37
|
+
* index of how many items it has emitted and renders only what is past it, so a
|
|
38
|
+
* shrinking item list would leave rows unprinted; and a row that has already
|
|
39
|
+
* been drawn live must not be handed back to a mechanism that would print it
|
|
40
|
+
* again. Rows therefore leave the window in one direction, oldest first, and a
|
|
41
|
+
* row that has settled never comes back.
|
|
42
|
+
*/
|
|
43
|
+
import { renderedDetailLines } from './Transcript.js';
|
|
44
|
+
import { SAFETY_ROWS, estimateRenderedLines } from './bottom-spacer.js';
|
|
45
|
+
/**
|
|
46
|
+
* The most rows the window will ever hold, whatever the terminal's height.
|
|
47
|
+
*
|
|
48
|
+
* A cap on top of the height budget, because the budget alone would put thirty
|
|
49
|
+
* short rows in the live region on a tall terminal — thirty rows re-laid-out
|
|
50
|
+
* per spinner tick to make retroactive expansion available on output nobody is
|
|
51
|
+
* still looking at. Six covers what an operator is working with: a tool call,
|
|
52
|
+
* its result, and the exchange around them.
|
|
53
|
+
*/
|
|
54
|
+
export const MAX_LIVE_ROWS = 6;
|
|
55
|
+
/**
|
|
56
|
+
* Every line one row will print, including the body under a tool call.
|
|
57
|
+
*
|
|
58
|
+
* The blank row `MessageRow` puts above every entry but the first and the `⎿`
|
|
59
|
+
* result rows is counted, because one row per entry is not negligible: forty
|
|
60
|
+
* entries is forty rows, which on most terminals is the whole viewport. The
|
|
61
|
+
* content is indented by the two-column glyph gutter it renders beside, so a
|
|
62
|
+
* long line is measured against the width it actually has.
|
|
63
|
+
*/
|
|
64
|
+
function messageLines(message, hasPrev) {
|
|
65
|
+
return [
|
|
66
|
+
...(hasPrev && message.glyph !== '⎿' ? [''] : []),
|
|
67
|
+
` ${message.content}`,
|
|
68
|
+
...renderedDetailLines(message),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Every line the finalized transcript will print, for a height estimate.
|
|
73
|
+
*
|
|
74
|
+
* Exported so the wiring is testable rather than only typecheckable. The
|
|
75
|
+
* spacer's own docblock states the asymmetry it depends on: over-count the
|
|
76
|
+
* content and the composer merely floats, under-count it and the composer is
|
|
77
|
+
* pushed off the bottom. The caller used to pass each row's `content` alone,
|
|
78
|
+
* which counted a six-line collapsed tool body as nothing at all — the estimate
|
|
79
|
+
* ran low by six per tool call, in the direction that costs the usability
|
|
80
|
+
* rather than the feature. `/expand` makes it acute: a row whose entire
|
|
81
|
+
* substance is a two-hundred-line body would have been handed over as one line.
|
|
82
|
+
*
|
|
83
|
+
* A pending row is excluded because it is not in the static log yet; the
|
|
84
|
+
* spacer's `liveRows` covers the live region.
|
|
85
|
+
*/
|
|
86
|
+
export function transcriptLines(messages) {
|
|
87
|
+
const finalized = messages.filter((m) => !m.pending);
|
|
88
|
+
return finalized.flatMap((message, i) => messageLines(message, i > 0));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Rows added to each windowed row's estimate.
|
|
92
|
+
*
|
|
93
|
+
* `estimateRenderedLines` is deliberately an UNDER-count, because for the
|
|
94
|
+
* bottom spacer counting low leaves more apparent room and the safety margin
|
|
95
|
+
* absorbs the difference. Here the asymmetry runs the other way: under-count a
|
|
96
|
+
* live row and the window takes more of the viewport than it was budgeted, and
|
|
97
|
+
* the price of exceeding the viewport is the whole-session repaint this window
|
|
98
|
+
* exists to stay clear of. Over-count and the window merely holds one row
|
|
99
|
+
* fewer.
|
|
100
|
+
*
|
|
101
|
+
* Two per row covers what the line estimate cannot see — the rule a code block
|
|
102
|
+
* draws around itself, the blank line markdown puts between blocks — without
|
|
103
|
+
* needing to model any of it.
|
|
104
|
+
*/
|
|
105
|
+
const ROW_HEIGHT_ALLOWANCE = 2;
|
|
106
|
+
/** How tall one row is expected to render, rounded UP. */
|
|
107
|
+
function messageHeight(message, hasPrev, columns) {
|
|
108
|
+
return estimateRenderedLines(messageLines(message, hasPrev), columns) + ROW_HEIGHT_ALLOWANCE;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* How much of the transcript stays live, given the room there is for it.
|
|
112
|
+
*
|
|
113
|
+
* Returns `settled === messages.length` — everything in scrollback, nothing
|
|
114
|
+
* redrawable — whenever the answer is not knowable: no TTY, an implausible
|
|
115
|
+
* height, or a most-recent row too tall to hold. That is the previous
|
|
116
|
+
* behaviour, and it is the safe direction.
|
|
117
|
+
*/
|
|
118
|
+
export function liveWindow(input) {
|
|
119
|
+
const { messages, rows, columns, furnitureRows, settled } = input;
|
|
120
|
+
const floor = Math.min(Math.max(settled, 0), messages.length);
|
|
121
|
+
if (rows === undefined || !Number.isFinite(rows))
|
|
122
|
+
return { settled: messages.length, rows: 0 };
|
|
123
|
+
const budget = rows - furnitureRows - SAFETY_ROWS;
|
|
124
|
+
if (budget < 1)
|
|
125
|
+
return { settled: messages.length, rows: 0 };
|
|
126
|
+
let height = 0;
|
|
127
|
+
let held = 0;
|
|
128
|
+
for (let i = messages.length - 1; i >= floor && held < MAX_LIVE_ROWS; i--) {
|
|
129
|
+
const message = messages[i];
|
|
130
|
+
if (!message)
|
|
131
|
+
break;
|
|
132
|
+
const next = height + messageHeight(message, i > 0, columns);
|
|
133
|
+
if (next > budget)
|
|
134
|
+
break;
|
|
135
|
+
height = next;
|
|
136
|
+
held += 1;
|
|
137
|
+
}
|
|
138
|
+
return { settled: messages.length - held, rows: height };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=live-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-window.js","sourceRoot":"","sources":["../../src/tui/live-window.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What namzu says while an operator signs in to their subscription.
|
|
3
|
+
*
|
|
4
|
+
* Every decidable thing lives here rather than in the component, for the
|
|
5
|
+
* reason `credential-entry.ts` gives next door: this package has no component
|
|
6
|
+
* tests, and wording that handles a credential is the worst thing to leave
|
|
7
|
+
* unverifiable. What the component does with these strings is print them.
|
|
8
|
+
*
|
|
9
|
+
* The one rule these functions exist to keep: **an outcome is described by
|
|
10
|
+
* what happened, never by quoting what came back.** A refusal from a token
|
|
11
|
+
* endpoint arrives in a document that can contain a token, so no function
|
|
12
|
+
* here ever receives one — `LoginOutcome.reason` is already sanitised at the
|
|
13
|
+
* protocol layer, and the success arm is handed a path, not a credential.
|
|
14
|
+
*/
|
|
15
|
+
import type { LoginOutcome } from '../integrations/providers/index.js';
|
|
16
|
+
/**
|
|
17
|
+
* The screen the operator reads when a sign-in starts.
|
|
18
|
+
*
|
|
19
|
+
* The URL is printed WHATEVER happened with the browser. A launcher that
|
|
20
|
+
* started is not a browser that appeared — no platform reports that — and a
|
|
21
|
+
* message that says "your browser is opening" and nothing else leaves someone
|
|
22
|
+
* on a machine with no browser staring at a line that is simply false.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeLoginStart(start: {
|
|
25
|
+
readonly url: string;
|
|
26
|
+
readonly loopback: boolean;
|
|
27
|
+
readonly browserOpened: boolean;
|
|
28
|
+
}): string;
|
|
29
|
+
/** What the operator reads when the attempt ends, either way. */
|
|
30
|
+
export declare function describeLoginOutcome(outcome: LoginOutcome): string;
|
|
31
|
+
/** What the operator reads after asking namzu to forget the credential. */
|
|
32
|
+
export declare function describeLogout(path: string, removed: boolean): string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a `/login` argument is an attempt to finish, rather than to start.
|
|
35
|
+
*
|
|
36
|
+
* Bare `/login` starts an attempt; `/login <something>` finishes one. Drawn
|
|
37
|
+
* here rather than in the command so the rule is testable and so the command
|
|
38
|
+
* cannot quietly grow a third meaning.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isCompletionArgument(args: readonly string[]): boolean;
|
|
41
|
+
//# sourceMappingURL=login-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-prompt.d.ts","sourceRoot":"","sources":["../../src/tui/login-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;CAC/B,GAAG,MAAM,CAmBT;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAUlE;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAIrE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAErE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What namzu says while an operator signs in to their subscription.
|
|
3
|
+
*
|
|
4
|
+
* Every decidable thing lives here rather than in the component, for the
|
|
5
|
+
* reason `credential-entry.ts` gives next door: this package has no component
|
|
6
|
+
* tests, and wording that handles a credential is the worst thing to leave
|
|
7
|
+
* unverifiable. What the component does with these strings is print them.
|
|
8
|
+
*
|
|
9
|
+
* The one rule these functions exist to keep: **an outcome is described by
|
|
10
|
+
* what happened, never by quoting what came back.** A refusal from a token
|
|
11
|
+
* endpoint arrives in a document that can contain a token, so no function
|
|
12
|
+
* here ever receives one — `LoginOutcome.reason` is already sanitised at the
|
|
13
|
+
* protocol layer, and the success arm is handed a path, not a credential.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The screen the operator reads when a sign-in starts.
|
|
17
|
+
*
|
|
18
|
+
* The URL is printed WHATEVER happened with the browser. A launcher that
|
|
19
|
+
* started is not a browser that appeared — no platform reports that — and a
|
|
20
|
+
* message that says "your browser is opening" and nothing else leaves someone
|
|
21
|
+
* on a machine with no browser staring at a line that is simply false.
|
|
22
|
+
*/
|
|
23
|
+
export function describeLoginStart(start) {
|
|
24
|
+
const lines = [
|
|
25
|
+
start.browserOpened
|
|
26
|
+
? 'Opening your browser to sign in. If nothing opened, or the browser is on another machine, use this address:'
|
|
27
|
+
: 'Open this address to sign in — on this machine, or on any machine you can reach it from:',
|
|
28
|
+
'',
|
|
29
|
+
start.url,
|
|
30
|
+
'',
|
|
31
|
+
];
|
|
32
|
+
lines.push(start.loopback
|
|
33
|
+
? 'When the page finishes, namzu will pick it up automatically. If your browser is somewhere this machine cannot be reached from — a container, a remote shell — copy the address it lands on and run:'
|
|
34
|
+
: // Said plainly. The listener is what makes the automatic path work,
|
|
35
|
+
// and an operator who is not told it is missing will sit waiting for
|
|
36
|
+
// something that is never going to happen.
|
|
37
|
+
'The automatic hand-back is not available in this session (the port is in use, or this environment does not allow listening), so finish it by hand. Copy the address the browser lands on and run:');
|
|
38
|
+
lines.push('', ' /login <the address, or just the code>');
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
/** What the operator reads when the attempt ends, either way. */
|
|
42
|
+
export function describeLoginOutcome(outcome) {
|
|
43
|
+
if (!outcome.ok) {
|
|
44
|
+
return `${outcome.reason}\n\nRun /login to try again.`;
|
|
45
|
+
}
|
|
46
|
+
return [
|
|
47
|
+
'Signed in. The credential is stored on this machine, readable only by your account:',
|
|
48
|
+
` ${outcome.storedAt}`,
|
|
49
|
+
'',
|
|
50
|
+
'namzu will refresh it as it expires, and will find it again next time it starts. Run /logout to remove it.',
|
|
51
|
+
].join('\n');
|
|
52
|
+
}
|
|
53
|
+
/** What the operator reads after asking namzu to forget the credential. */
|
|
54
|
+
export function describeLogout(path, removed) {
|
|
55
|
+
return removed
|
|
56
|
+
? `Removed the stored credential at ${path}. This session keeps working until it ends; the next one will ask you to sign in again.\n\nSigning out here does not revoke anything at the provider — do that in your account settings if you need to.`
|
|
57
|
+
: "There was no stored credential to remove. If namzu is using one, it came from your environment or from another tool on this machine, and neither is namzu's to delete.";
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a `/login` argument is an attempt to finish, rather than to start.
|
|
61
|
+
*
|
|
62
|
+
* Bare `/login` starts an attempt; `/login <something>` finishes one. Drawn
|
|
63
|
+
* here rather than in the command so the rule is testable and so the command
|
|
64
|
+
* cannot quietly grow a third meaning.
|
|
65
|
+
*/
|
|
66
|
+
export function isCompletionArgument(args) {
|
|
67
|
+
return args.join(' ').trim().length > 0;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=login-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-prompt.js","sourceRoot":"","sources":["../../src/tui/login-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAIlC;IACA,MAAM,KAAK,GAAG;QACb,KAAK,CAAC,aAAa;YAClB,CAAC,CAAC,6GAA6G;YAC/G,CAAC,CAAC,0FAA0F;QAC7F,EAAE;QACF,KAAK,CAAC,GAAG;QACT,EAAE;KACF,CAAA;IACD,KAAK,CAAC,IAAI,CACT,KAAK,CAAC,QAAQ;QACb,CAAC,CAAC,qMAAqM;QACvM,CAAC,CAAC,oEAAoE;YACrE,qEAAqE;YACrE,2CAA2C;YAC3C,mMAAmM,CACrM,CAAA;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,0CAA0C,CAAC,CAAA;IAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,oBAAoB,CAAC,OAAqB;IACzD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,GAAG,OAAO,CAAC,MAAM,8BAA8B,CAAA;IACvD,CAAC;IACD,OAAO;QACN,qFAAqF;QACrF,KAAK,OAAO,CAAC,QAAQ,EAAE;QACvB,EAAE;QACF,4GAA4G;KAC5G,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAgB;IAC5D,OAAO,OAAO;QACb,CAAC,CAAC,oCAAoC,IAAI,yMAAyM;QACnP,CAAC,CAAC,wKAAwK,CAAA;AAC5K,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAuB;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a growing markdown document without re-parsing what has not changed.
|
|
3
|
+
*
|
|
4
|
+
* ## The cost this removes
|
|
5
|
+
*
|
|
6
|
+
* A streaming assistant reply is one message whose text grows by a token at a
|
|
7
|
+
* time, and the row rendering it re-renders on every one of them. Parsing the
|
|
8
|
+
* whole message each time makes the work quadratic in the reply's length: a
|
|
9
|
+
* forty-block answer is parsed forty blocks deep on every token, when
|
|
10
|
+
* thirty-nine of those blocks are byte-for-byte what they were a token ago.
|
|
11
|
+
*
|
|
12
|
+
* So the document is scanned into top-level blocks — cheap, one pass over the
|
|
13
|
+
* lines — and only the blocks that are new are parsed. The rest come back as
|
|
14
|
+
* **the same objects**, which is the half that matters to the renderer:
|
|
15
|
+
* `BlockView` is memoised, so an unchanged block is not re-rendered, not
|
|
16
|
+
* re-split into inline spans, and not turned into fresh elements either.
|
|
17
|
+
*
|
|
18
|
+
* ## Why raw text is a sound key
|
|
19
|
+
*
|
|
20
|
+
* `parseBlock` reads only the segment it is given: nothing in this markdown
|
|
21
|
+
* subset makes one block's meaning depend on another (there are no
|
|
22
|
+
* reference-style link definitions, which is the construct that would). Same
|
|
23
|
+
* segment ⇒ same block, so a hit cannot differ from a fresh parse. The
|
|
24
|
+
* `markdownParser` docblock states that property; it is the contract this file
|
|
25
|
+
* depends on, and it is why there is no "give up on caching when the document
|
|
26
|
+
* contains X" escape hatch here. There is no such X.
|
|
27
|
+
*
|
|
28
|
+
* ## Why the last two blocks are never cached
|
|
29
|
+
*
|
|
30
|
+
* Not for correctness — a key is a whole segment, so a block whose text grows
|
|
31
|
+
* simply misses under its new text and is parsed. It is to keep the cache from
|
|
32
|
+
* accumulating garbage: a block that is still being written passes through
|
|
33
|
+
* every one of its own prefixes, and storing them would put an entry per TOKEN
|
|
34
|
+
* into a structure meant to hold an entry per block. That is the memory the
|
|
35
|
+
* quadratic parse was costing, moved rather than removed.
|
|
36
|
+
*
|
|
37
|
+
* Two, not one, because the block still being written is not always the last
|
|
38
|
+
* segment. A half-typed table row is not yet a table row — `| 1 | 2` has no
|
|
39
|
+
* closing pipe — so it scans as a paragraph of its own, and the table above it
|
|
40
|
+
* becomes the second-to-last segment while still growing a row at a time. One
|
|
41
|
+
* excluded segment would have cached that table once per row, at increasing
|
|
42
|
+
* size. The line being typed can reach back exactly one segment and no further:
|
|
43
|
+
* it can be absorbed by the block above it, and that block is maximal, so there
|
|
44
|
+
* is nothing for it to reach past.
|
|
45
|
+
*
|
|
46
|
+
* The cost of the wider exclusion is one extra parse per block over the life of
|
|
47
|
+
* the message, against a saving of one per block per token.
|
|
48
|
+
*/
|
|
49
|
+
import { type MdBlock } from './markdownParser.js';
|
|
50
|
+
export interface BlockCache {
|
|
51
|
+
/** Blocks of `src`, reusing the block objects of unchanged segments. */
|
|
52
|
+
parse(src: string): MdBlock[];
|
|
53
|
+
/** How many segments are being held. One per completed block. */
|
|
54
|
+
readonly size: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function createBlockCache(): BlockCache;
|
|
57
|
+
//# sourceMappingURL=markdown-block-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-block-cache.d.ts","sourceRoot":"","sources":["../../src/tui/markdown-block-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,KAAK,OAAO,EAA0B,MAAM,qBAAqB,CAAA;AAE1E,MAAM,WAAW,UAAU;IAC1B,wEAAwE;IACxE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAAA;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAqB7C"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a growing markdown document without re-parsing what has not changed.
|
|
3
|
+
*
|
|
4
|
+
* ## The cost this removes
|
|
5
|
+
*
|
|
6
|
+
* A streaming assistant reply is one message whose text grows by a token at a
|
|
7
|
+
* time, and the row rendering it re-renders on every one of them. Parsing the
|
|
8
|
+
* whole message each time makes the work quadratic in the reply's length: a
|
|
9
|
+
* forty-block answer is parsed forty blocks deep on every token, when
|
|
10
|
+
* thirty-nine of those blocks are byte-for-byte what they were a token ago.
|
|
11
|
+
*
|
|
12
|
+
* So the document is scanned into top-level blocks — cheap, one pass over the
|
|
13
|
+
* lines — and only the blocks that are new are parsed. The rest come back as
|
|
14
|
+
* **the same objects**, which is the half that matters to the renderer:
|
|
15
|
+
* `BlockView` is memoised, so an unchanged block is not re-rendered, not
|
|
16
|
+
* re-split into inline spans, and not turned into fresh elements either.
|
|
17
|
+
*
|
|
18
|
+
* ## Why raw text is a sound key
|
|
19
|
+
*
|
|
20
|
+
* `parseBlock` reads only the segment it is given: nothing in this markdown
|
|
21
|
+
* subset makes one block's meaning depend on another (there are no
|
|
22
|
+
* reference-style link definitions, which is the construct that would). Same
|
|
23
|
+
* segment ⇒ same block, so a hit cannot differ from a fresh parse. The
|
|
24
|
+
* `markdownParser` docblock states that property; it is the contract this file
|
|
25
|
+
* depends on, and it is why there is no "give up on caching when the document
|
|
26
|
+
* contains X" escape hatch here. There is no such X.
|
|
27
|
+
*
|
|
28
|
+
* ## Why the last two blocks are never cached
|
|
29
|
+
*
|
|
30
|
+
* Not for correctness — a key is a whole segment, so a block whose text grows
|
|
31
|
+
* simply misses under its new text and is parsed. It is to keep the cache from
|
|
32
|
+
* accumulating garbage: a block that is still being written passes through
|
|
33
|
+
* every one of its own prefixes, and storing them would put an entry per TOKEN
|
|
34
|
+
* into a structure meant to hold an entry per block. That is the memory the
|
|
35
|
+
* quadratic parse was costing, moved rather than removed.
|
|
36
|
+
*
|
|
37
|
+
* Two, not one, because the block still being written is not always the last
|
|
38
|
+
* segment. A half-typed table row is not yet a table row — `| 1 | 2` has no
|
|
39
|
+
* closing pipe — so it scans as a paragraph of its own, and the table above it
|
|
40
|
+
* becomes the second-to-last segment while still growing a row at a time. One
|
|
41
|
+
* excluded segment would have cached that table once per row, at increasing
|
|
42
|
+
* size. The line being typed can reach back exactly one segment and no further:
|
|
43
|
+
* it can be absorbed by the block above it, and that block is maximal, so there
|
|
44
|
+
* is nothing for it to reach past.
|
|
45
|
+
*
|
|
46
|
+
* The cost of the wider exclusion is one extra parse per block over the life of
|
|
47
|
+
* the message, against a saving of one per block per token.
|
|
48
|
+
*/
|
|
49
|
+
import { parseBlock, scanBlocks } from './markdownParser.js';
|
|
50
|
+
export function createBlockCache() {
|
|
51
|
+
const blocks = new Map();
|
|
52
|
+
return {
|
|
53
|
+
parse(src) {
|
|
54
|
+
const segments = scanBlocks(src);
|
|
55
|
+
const settled = segments.length - 2;
|
|
56
|
+
return segments.map((segment, i) => {
|
|
57
|
+
// The tail is where the text is still being written, so it is parsed
|
|
58
|
+
// every time and stored never.
|
|
59
|
+
if (i >= settled)
|
|
60
|
+
return parseBlock(segment);
|
|
61
|
+
const hit = blocks.get(segment);
|
|
62
|
+
if (hit)
|
|
63
|
+
return hit;
|
|
64
|
+
const parsed = parseBlock(segment);
|
|
65
|
+
blocks.set(segment, parsed);
|
|
66
|
+
return parsed;
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
get size() {
|
|
70
|
+
return blocks.size;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=markdown-block-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-block-cache.js","sourceRoot":"","sources":["../../src/tui/markdown-block-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAgB,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAS1E,MAAM,UAAU,gBAAgB;IAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAA;IACzC,OAAO;QACN,KAAK,CAAC,GAAW;YAChB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;YAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;YACnC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;gBAClC,qEAAqE;gBACrE,+BAA+B;gBAC/B,IAAI,CAAC,IAAI,OAAO;oBAAE,OAAO,UAAU,CAAC,OAAO,CAAC,CAAA;gBAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC/B,IAAI,GAAG;oBAAE,OAAO,GAAG,CAAA;gBACnB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;gBAClC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAC3B,OAAO,MAAM,CAAA;YACd,CAAC,CAAC,CAAA;QACH,CAAC;QACD,IAAI,IAAI;YACP,OAAO,MAAM,CAAC,IAAI,CAAA;QACnB,CAAC;KACD,CAAA;AACF,CAAC"}
|