dsh-ssh-tui 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +34 -0
- package/README.md +435 -577
- package/docs/display-mode.md +122 -0
- package/docs/remote-ops.md +51 -0
- package/docs/terminals.md +53 -0
- package/lib/attach.js +4 -4
- package/lib/attach.js.map +1 -1
- package/lib/auth-failure.js +36 -0
- package/lib/auth-failure.js.map +1 -1
- package/lib/commands.js +2 -0
- package/lib/commands.js.map +1 -1
- package/lib/dialogs.js +43 -0
- package/lib/dialogs.js.map +1 -1
- package/lib/display-mode.js +147 -0
- package/lib/display-mode.js.map +1 -0
- package/lib/display-sock.js +361 -21
- package/lib/display-sock.js.map +1 -1
- package/lib/footer.js +6 -9
- package/lib/footer.js.map +1 -1
- package/lib/glyph-measure.js +92 -0
- package/lib/glyph-measure.js.map +1 -0
- package/lib/i18n/en.js +18 -2
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +18 -2
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +73 -5
- package/lib/index.js.map +1 -1
- package/lib/paint.js +22 -9
- package/lib/paint.js.map +1 -1
- package/lib/picker.js +14 -13
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +11 -11
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +96 -0
- package/lib/platform.js.map +1 -1
- package/lib/session-blank.js +81 -0
- package/lib/session-blank.js.map +1 -0
- package/lib/session-list.js +102 -81
- package/lib/session-list.js.map +1 -1
- package/lib/startup.js +7 -0
- package/lib/startup.js.map +1 -1
- package/lib/subagent-model.js +8 -7
- package/lib/subagent-model.js.map +1 -1
- package/lib/term-text.js +296 -28
- package/lib/term-text.js.map +1 -1
- package/lib/terminal-input.js +132 -10
- package/lib/terminal-input.js.map +1 -1
- package/lib/theme.js +318 -0
- package/lib/theme.js.map +1 -0
- package/lib/tool-present.js +11 -9
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +420 -38
- package/lib/tui.js.map +1 -1
- package/lib/types/attach.d.ts +6 -2
- package/lib/types/auth-failure.d.ts +30 -0
- package/lib/types/commands.d.ts +6 -0
- package/lib/types/dialogs.d.ts +36 -0
- package/lib/types/display-mode.d.ts +99 -0
- package/lib/types/display-sock.d.ts +75 -0
- package/lib/types/footer.d.ts +1 -1
- package/lib/types/glyph-measure.d.ts +41 -0
- package/lib/types/index.d.ts +13 -0
- package/lib/types/plan.d.ts +5 -2
- package/lib/types/platform.d.ts +83 -0
- package/lib/types/session-blank.d.ts +51 -0
- package/lib/types/session-list.d.ts +34 -0
- package/lib/types/startup.d.ts +6 -0
- package/lib/types/subagent-model.d.ts +7 -6
- package/lib/types/term-text.d.ts +55 -23
- package/lib/types/terminal-input.d.ts +37 -0
- package/lib/types/theme.d.ts +109 -0
- package/lib/types/tool-present.d.ts +2 -2
- package/lib/types/tui.d.ts +98 -1
- package/package.json +68 -66
|
@@ -44,13 +44,14 @@ export declare function canonicalProviderId(provider: string | undefined): strin
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function subagentProviderDiffers(parent: string | undefined, child: string | undefined): boolean;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* Which identity role a subagent chip title wears.
|
|
48
|
+
*
|
|
49
|
+
* The colours themselves live in `theme.ts`: the same provider as the parent
|
|
50
|
+
* gets the identity role, a different provider gets the foreign one, and the
|
|
51
|
+
* palette decides what those look like (under `mono` they become an attribute,
|
|
52
|
+
* because the distinction has to survive a terminal without colour).
|
|
50
53
|
*/
|
|
51
|
-
export declare
|
|
52
|
-
export declare const SUBAGENT_FOREIGN_SGR = "38;5;80";
|
|
53
|
-
export declare function subagentIdentitySgr(foreign: boolean): typeof SUBAGENT_IDENTITY_SGR | typeof SUBAGENT_FOREIGN_SGR;
|
|
54
|
+
export declare function subagentIdentityRole(foreign: boolean): 'subagent-self' | 'subagent-foreign';
|
|
54
55
|
/**
|
|
55
56
|
* True when the stored subagent model still belongs to the parent provider
|
|
56
57
|
* family. An explicit leftover DeepSeek flash id after switching to xAI is
|
package/lib/types/term-text.d.ts
CHANGED
|
@@ -53,25 +53,65 @@ export declare function resetAsciiChrome(): void;
|
|
|
53
53
|
*/
|
|
54
54
|
export declare function mapAsciiChrome(text: string): string;
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Resolve the ambiguous-glyph policy for this process.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
58
|
+
* Cheap enough to call once per line (it compares one string) and far too
|
|
59
|
+
* expensive to call per character, which is the distinction that matters.
|
|
60
|
+
* @param env - the environment to read.
|
|
61
|
+
* @param onTerminal - whether the output is a terminal at all.
|
|
62
|
+
* @returns whether the glyphs are two cells wide, and whether the second cell is
|
|
63
|
+
* ours to reserve.
|
|
64
|
+
*/
|
|
65
|
+
export declare function ambiguousPolicy(env?: NodeJS.ProcessEnv, onTerminal?: boolean): {
|
|
66
|
+
wide: boolean;
|
|
67
|
+
reserve: boolean;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Record what the terminal's own answer said about these glyphs.
|
|
63
71
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
* Called by the launcher after measuring; also the escape hatch for a caller
|
|
73
|
+
* that knows better than the locale (a relay that has already measured for its
|
|
74
|
+
* own accounting, or a test).
|
|
75
|
+
* @param wide - true when the terminal advances two cells, false for one, or
|
|
76
|
+
* undefined to fall back to the locale.
|
|
77
|
+
*/
|
|
78
|
+
export declare function setAmbiguousWidthMeasured(wide: boolean | undefined): void;
|
|
79
|
+
/**
|
|
80
|
+
* Force the reserve behaviour, for a caller that measured the same terminal for
|
|
81
|
+
* its own accounting (a relay) or a test.
|
|
82
|
+
* @param reserve - whether to spend a space after each ambiguous glyph.
|
|
83
|
+
*/
|
|
84
|
+
export declare function setAmbiguousWidthReserve(reserve: boolean): void;
|
|
85
|
+
/** Whether the second cell is currently reserved with a space. */
|
|
86
|
+
export declare function ambiguousWidthReserved(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Cells one character costs in this TUI's layout.
|
|
71
89
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
90
|
+
* The reserve case still costs two: the glyph advances one cell and the reserving
|
|
91
|
+
* space takes the next, so the layout must budget both or every row holding one
|
|
92
|
+
* comes up short. Non-ambiguous characters are unchanged.
|
|
93
|
+
* @param cp - the code point.
|
|
94
|
+
* @returns 0, 1 or 2 cells.
|
|
74
95
|
*/
|
|
96
|
+
export declare function ambiguousCellCost(cp: number): number;
|
|
97
|
+
/** What the last measurement decided, for diagnostics and tests. */
|
|
98
|
+
export declare function ambiguousWidthMeasured(): boolean | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Whether ambiguous glyphs in {@link AMBIGUOUS_WIDE_RANGES} are drawn two cells
|
|
101
|
+
* wide here.
|
|
102
|
+
*
|
|
103
|
+
* `DSH_TUI_AMBIGUOUS_WIDTH=1|2` answers outright. Otherwise the locale decides,
|
|
104
|
+
* and only when there really is a terminal: a zh/ja/ko locale means the terminal
|
|
105
|
+
* is very likely using a CJK font, where these glyphs are full width. The UI
|
|
106
|
+
* language is deliberately *not* consulted — a Chinese reader on a Western
|
|
107
|
+
* terminal has narrow glyphs, and typing in Chinese does not change the font
|
|
108
|
+
* metrics.
|
|
109
|
+
* @param env - the environment to read (tests pass their own).
|
|
110
|
+
* @param onTerminal - whether the output is a terminal at all; a pipe or a test
|
|
111
|
+
* harness has no font metrics, so there the narrow default applies.
|
|
112
|
+
* @returns true when those glyphs should be budgeted two cells.
|
|
113
|
+
*/
|
|
114
|
+
export declare function ambiguousWidthIsTwo(env?: NodeJS.ProcessEnv, onTerminal?: boolean): boolean;
|
|
75
115
|
export declare function displayWidth(text: string): number;
|
|
76
116
|
/**
|
|
77
117
|
* Make a painted row spend the two cells {@link displayWidth} budgets for every
|
|
@@ -201,14 +241,6 @@ export interface InputView {
|
|
|
201
241
|
cursorOffset: number;
|
|
202
242
|
folded: boolean;
|
|
203
243
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Fold a long input into one terminal row around the cursor.
|
|
206
|
-
*
|
|
207
|
-
* Newlines from a paste are display-only: they do not occupy cells, so a
|
|
208
|
-
* naive `displayWidth(input)` under-counts a multi-line paste and parks the
|
|
209
|
-
* caret in the middle of later text. Fold the *current line* (between the
|
|
210
|
-
* surrounding newlines) and keep `\n` out of the visible slice.
|
|
211
|
-
*/
|
|
212
244
|
export declare function foldInputView(input: string, cursor: number, maxWidth: number): InputView;
|
|
213
245
|
/**
|
|
214
246
|
* Map a character index in the input text to its visual (row, col) after the
|
|
@@ -28,6 +28,7 @@ export declare const RTT_OUTLIER_RATIO = 0.25;
|
|
|
28
28
|
export declare function stripCursorReplies(text: string): {
|
|
29
29
|
text: string;
|
|
30
30
|
replies: number;
|
|
31
|
+
last?: string;
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
33
34
|
* Drop cursor replies from a byte stream that is otherwise user input.
|
|
@@ -38,6 +39,8 @@ export declare function stripCursorReplies(text: string): {
|
|
|
38
39
|
*/
|
|
39
40
|
export declare class TerminalInputFilter {
|
|
40
41
|
private held;
|
|
42
|
+
/** The most recent cursor reply this filter swallowed, if any. */
|
|
43
|
+
lastReply: string | undefined;
|
|
41
44
|
push(text: string): {
|
|
42
45
|
forward: string;
|
|
43
46
|
replies: number;
|
|
@@ -103,10 +106,26 @@ export declare class TerminalInputPump {
|
|
|
103
106
|
private slowestSampleMs;
|
|
104
107
|
private holdTimer;
|
|
105
108
|
private listening;
|
|
109
|
+
/** Set by {@link quiet}: the held tail is dropped instead of forwarded. */
|
|
110
|
+
private dropping;
|
|
106
111
|
readonly ssh: boolean;
|
|
107
112
|
constructor(options: TerminalInputPumpOptions);
|
|
108
113
|
start(): void;
|
|
109
114
|
stop(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Wait one more round trip before letting go of the terminal.
|
|
117
|
+
*
|
|
118
|
+
* A cursor reply can still be on the wire when a relay stops. The terminal
|
|
119
|
+
* answers a Device Status Report after the round trip, so on a slow SSH link
|
|
120
|
+
* that is hundreds of milliseconds after the request; restoring cooked mode
|
|
121
|
+
* first lets the tty echo the answer as literal `^[[25;1R` text at the user's
|
|
122
|
+
* prompt, and leaves it in the tty queue for the shell to read as typing.
|
|
123
|
+
* Staying in raw mode for one round trip swallows it instead. Typing that
|
|
124
|
+
* lands in this window is forwarded as usual — the window exists to drop
|
|
125
|
+
* answers, not keystrokes.
|
|
126
|
+
* @param graceMs - how long the terminal may still owe us a reply.
|
|
127
|
+
*/
|
|
128
|
+
handBack(graceMs?: number): Promise<void>;
|
|
110
129
|
/**
|
|
111
130
|
* Ask the terminal for its cursor a few times and report the round-trip that
|
|
112
131
|
* best describes the link. `undefined` means the terminal never answered in
|
|
@@ -170,5 +189,23 @@ export declare class TerminalInputPump {
|
|
|
170
189
|
* from being attributed to the next request.
|
|
171
190
|
*/
|
|
172
191
|
private answer;
|
|
192
|
+
/**
|
|
193
|
+
* Ask the terminal for its cursor after printing `probe`, and report where it
|
|
194
|
+
* says the cursor ended up.
|
|
195
|
+
*
|
|
196
|
+
* This is the measurement half of the pump: `measure()` answers "how long did
|
|
197
|
+
* the round trip take", this answers "how far did the cursor move". Replies are
|
|
198
|
+
* swallowed by the same filter that swallows them for the timing probe — which
|
|
199
|
+
* matters, because a reply nobody consumes is echoed on screen as literal
|
|
200
|
+
* `^[[1;5R` text, and that is exactly what a hand-rolled version of this did.
|
|
201
|
+
* @param probe - the text to print before asking (cleared afterwards).
|
|
202
|
+
* @param timeoutMs - how long to wait for the answer.
|
|
203
|
+
* @returns the coordinates the terminal reported, or undefined when it stayed
|
|
204
|
+
* silent.
|
|
205
|
+
*/
|
|
206
|
+
askPosition(probe: string, timeoutMs?: number): Promise<{
|
|
207
|
+
row: number;
|
|
208
|
+
column: number;
|
|
209
|
+
} | undefined>;
|
|
173
210
|
private scheduleHold;
|
|
174
211
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI's colour system: semantic roles, and the palettes that fill them.
|
|
3
|
+
*
|
|
4
|
+
* Three rules shape this file, and each one came from a product that had to
|
|
5
|
+
* learn it the hard way:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Primary text takes the terminal's own foreground.** Forcing `37` looks
|
|
8
|
+
* right on a dark terminal and is unreadable on a light one; Codex's own TUI
|
|
9
|
+
* style guide says "most of the time, just use the default foreground colour"
|
|
10
|
+
* and warns against unchecked custom foregrounds. So assistant text and tool
|
|
11
|
+
* output carry *no* colour token at all.
|
|
12
|
+
* 2. **A theme is a role table, not a palette.** Callers ask for `error`, not for
|
|
13
|
+
* red — that is what lets the monochrome theme keep bold/underline while it
|
|
14
|
+
* drops colour, and what lets a low-colour terminal (`color-depth.ts`)
|
|
15
|
+
* downgrade by hue family without any theme knowing about it.
|
|
16
|
+
* 3. **A theme must survive 8 colours and no colour.** Every token here is
|
|
17
|
+
* emitted as ordinary SGR and passes through `downgradeSgr`, so a `38;2;…`
|
|
18
|
+
* token on a 256- or 16-colour terminal becomes the nearest hue its palette
|
|
19
|
+
* has. `mono` goes further and never emits colour at all — the shape of the
|
|
20
|
+
* line (bold, dim, underline) is what carries meaning, which is exactly what
|
|
21
|
+
* Gemini CLI's first-class `no-color` theme does.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-ssh-tui/theme
|
|
24
|
+
*/
|
|
25
|
+
import type { DisplayKind } from './transcript-types.js';
|
|
26
|
+
/**
|
|
27
|
+
* Roles that are not transcript rows: subagent chips, warning glyphs, links.
|
|
28
|
+
*
|
|
29
|
+
* They live here rather than as constants beside their callers for one reason: a
|
|
30
|
+
* colour literal anywhere outside this module is a colour the palettes cannot
|
|
31
|
+
* reach, and `tests/theme.test.mjs` fails over exactly that.
|
|
32
|
+
*/
|
|
33
|
+
export type ExtraRole = 'subagent-self' | 'subagent-foreign' | 'warn' | 'link' | 'accent' | 'notice' | 'md-bold' | 'md-italic' | 'md-code' | 'md-link' | 'md-muted' | 'md-h1' | 'md-h2' | 'md-h3' | 'md-quote' | 'md-rule' | 'tool-running' | 'tool-ok';
|
|
34
|
+
/** One palette. Tokens are SGR parameter strings, without the escape. */
|
|
35
|
+
export interface Theme {
|
|
36
|
+
/** Key used by `/theme`, `DSH_TUI_THEME`, and the `theme` setting. */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Order in `/theme` output; lower is closer to the original look. */
|
|
39
|
+
rank: number;
|
|
40
|
+
/** Row role → SGR parameters. A missing role falls back to `default`'s. */
|
|
41
|
+
tokens: Partial<Record<DisplayKind, string>>;
|
|
42
|
+
/** Diff emphasis uses a second, brighter variant of the same fill. */
|
|
43
|
+
emphasis?: Partial<Record<DisplayKind, string>>;
|
|
44
|
+
/** Non-row roles. */
|
|
45
|
+
extra?: Partial<Record<ExtraRole, string>>;
|
|
46
|
+
}
|
|
47
|
+
export declare const THEMES: readonly Theme[];
|
|
48
|
+
/** Every theme name, in the order `/theme` lists them. */
|
|
49
|
+
export declare function themeNames(): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a theme name.
|
|
52
|
+
* @param name - a name from the environment, the settings file, or `/theme`.
|
|
53
|
+
* @returns the theme, or undefined when the name is unknown (the caller decides
|
|
54
|
+
* whether to fall back quietly — a typo in a settings file should not crash a
|
|
55
|
+
* session, but `/theme bogus` has to say so).
|
|
56
|
+
*/
|
|
57
|
+
export declare function themeByName(name: string | undefined): Theme | undefined;
|
|
58
|
+
/** The theme a `DSH_TUI_THEME`/`theme` value selects, defaulting to `default`. */
|
|
59
|
+
export declare function resolveTheme(name: string | undefined): Theme;
|
|
60
|
+
/**
|
|
61
|
+
* Publish the active palette.
|
|
62
|
+
* @param name - the theme name, as resolved from the environment or settings.
|
|
63
|
+
* @returns the theme that is now active.
|
|
64
|
+
*/
|
|
65
|
+
export declare function setActiveTheme(name: string | undefined): Theme;
|
|
66
|
+
/** The active palette, for renderers that have no session handle. */
|
|
67
|
+
export declare function activeTheme(): Theme;
|
|
68
|
+
/**
|
|
69
|
+
* The SGR parameters for one row role.
|
|
70
|
+
* @param theme - the active theme.
|
|
71
|
+
* @param kind - the display role being painted.
|
|
72
|
+
* @returns SGR parameters without the escape, or `''` for "leave it to the
|
|
73
|
+
* terminal" (which is what primary text wants).
|
|
74
|
+
*/
|
|
75
|
+
export declare function themeToken(theme: Theme, kind: DisplayKind): string;
|
|
76
|
+
/**
|
|
77
|
+
* The SGR parameters for a changed word inside a diff row.
|
|
78
|
+
* @param theme - the active theme.
|
|
79
|
+
* @param kind - `diff-add` or `diff-del`.
|
|
80
|
+
* @returns the emphasised variant, or undefined when the theme has none.
|
|
81
|
+
*/
|
|
82
|
+
export declare function themeEmphasisToken(theme: Theme, kind: DisplayKind): string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* The SGR parameters for a non-row role.
|
|
85
|
+
* @param theme - the active theme.
|
|
86
|
+
* @param role - the extra role requested.
|
|
87
|
+
* @returns SGR parameters without the escape.
|
|
88
|
+
*/
|
|
89
|
+
export declare function themeExtraToken(theme: Theme, role: ExtraRole): string;
|
|
90
|
+
/** A threshold level used by the status area's gauges. */
|
|
91
|
+
export type ThresholdLevel = 'ok' | 'warn' | 'over';
|
|
92
|
+
/**
|
|
93
|
+
* The token for a gauge threshold: context usage, a quota window, a spend bar.
|
|
94
|
+
*
|
|
95
|
+
* One vocabulary for all of them, so a reader learns it once — under 70% is
|
|
96
|
+
* fine, 70–89% is worth noting, 90% and up is a problem. The numbers behind the
|
|
97
|
+
* levels live in the status area; the colours live here, and `mono` answers with
|
|
98
|
+
* attributes instead of giving up the distinction.
|
|
99
|
+
* @param theme - the active theme.
|
|
100
|
+
* @param level - which threshold the gauge is in.
|
|
101
|
+
* @returns SGR parameters without the escape.
|
|
102
|
+
*/
|
|
103
|
+
export declare function themeThresholdToken(theme: Theme, level: ThresholdLevel): string;
|
|
104
|
+
/**
|
|
105
|
+
* Whether a theme paints colour at all.
|
|
106
|
+
* @param theme - the active theme.
|
|
107
|
+
* @returns false for `mono`, whose tokens are attributes only.
|
|
108
|
+
*/
|
|
109
|
+
export declare function themeHasColour(theme: Theme): boolean;
|
|
@@ -123,8 +123,8 @@ export declare function diffContentLines(text: string): string[];
|
|
|
123
123
|
export type { DiffDisplayLine } from './transcript-types.js';
|
|
124
124
|
/** Cap one flat diff/body row list to `maxLines` while preserving the final line. */
|
|
125
125
|
export declare function capDisplayLines(lines: readonly DiffDisplayLine[], maxLines: number): DiffDisplayLine[];
|
|
126
|
-
/** Running / ok / error →
|
|
127
|
-
export declare function toolStateColor(status: 'running' | 'ok' | 'error' | undefined):
|
|
126
|
+
/** Running / ok / error → theme token for the status dot and status word only. */
|
|
127
|
+
export declare function toolStateColor(status: 'running' | 'ok' | 'error' | undefined): string;
|
|
128
128
|
export declare function toolStateLabel(status: 'running' | 'ok' | 'error' | undefined): string;
|
|
129
129
|
/** Header + SGR spans: default title, dim operand, colored ●. `[ok]` is omitted — the green dot is enough. */
|
|
130
130
|
export declare function buildToolHeader(input: {
|
package/lib/types/tui.d.ts
CHANGED
|
@@ -125,11 +125,17 @@ export interface TuiConfig {
|
|
|
125
125
|
/** Hangup policy while busy: pause cancels the turn; continue lets it finish detached. Idle hangup always exits. */
|
|
126
126
|
disconnectPolicy?: DisconnectPolicyName;
|
|
127
127
|
}
|
|
128
|
-
/** Lifecycle handle for a mounted interactive terminal channel. */
|
|
129
128
|
export interface TuiController {
|
|
130
129
|
dispose(): Promise<void>;
|
|
131
130
|
handleHangup(): Promise<void>;
|
|
132
131
|
disconnectPolicy(): DisconnectPolicyName;
|
|
132
|
+
/**
|
|
133
|
+
* Whether the reader ever typed into this session.
|
|
134
|
+
*
|
|
135
|
+
* The launch path uses it on the way out: a fresh session nothing was typed
|
|
136
|
+
* into is deleted rather than left for other profiles' menus to list.
|
|
137
|
+
*/
|
|
138
|
+
sessionHadUserInput(): boolean;
|
|
133
139
|
}
|
|
134
140
|
export type WorkspaceView = 'detailed' | 'compact';
|
|
135
141
|
/**
|
|
@@ -274,11 +280,78 @@ export declare class SshTui {
|
|
|
274
280
|
private commandSuggestions;
|
|
275
281
|
private suggestionIndex;
|
|
276
282
|
private focusedRow;
|
|
283
|
+
/**
|
|
284
|
+
* The active palette. Roles resolve through `theme.ts`, so switching themes is
|
|
285
|
+
* a repaint, not a re-render — no row holds a colour of its own.
|
|
286
|
+
*/
|
|
287
|
+
private theme;
|
|
288
|
+
/**
|
|
289
|
+
* Rendered display lines per row, keyed by the row object.
|
|
290
|
+
*
|
|
291
|
+
* Every frame used to re-render the whole transcript — markdown parsing, card
|
|
292
|
+
* layout, wrapping and clipping for thousands of rows — to show the twenty on
|
|
293
|
+
* screen. Measured on a 5000-row session that was ~550 ms per frame, which is
|
|
294
|
+
* what "rendering feels slower the longer the session runs" was: a keystroke
|
|
295
|
+
* costs the same as a full repaint because both redo all of it.
|
|
296
|
+
*
|
|
297
|
+
* The cache is `WeakMap`-keyed on the row object, so a replaced row starts a new
|
|
298
|
+
* entry and an unreferenced one is collected. What each entry stores is a
|
|
299
|
+
* *fingerprint* of every input the row's rendering depends on plus the lines it
|
|
300
|
+
* produced; a row whose fingerprint is unchanged replays instead of re-rendering,
|
|
301
|
+
* which is the common case — during a turn only the streaming row changes.
|
|
302
|
+
*/
|
|
303
|
+
private displayRowCache;
|
|
304
|
+
/** Fingerprint of the state that affects *every* row's rendering. */
|
|
305
|
+
private displayBaseKey;
|
|
306
|
+
/**
|
|
307
|
+
* Fingerprint of one tool burst.
|
|
308
|
+
*
|
|
309
|
+
* A burst is a *group of tool rows drawn inside the reply above them*, so the
|
|
310
|
+
* reply's cached lines contain cards belonging to rows the reply's own
|
|
311
|
+
* fingerprint never sees. Without this, the reply kept replaying its cached
|
|
312
|
+
* burst: a finished tool went on saying "processing" and the stale card stayed
|
|
313
|
+
* on screen beside the live one — the residue a reader reported twice.
|
|
314
|
+
*/
|
|
315
|
+
private static burstKey;
|
|
316
|
+
/**
|
|
317
|
+
* Whether a row's rendering depends on the clock.
|
|
318
|
+
*
|
|
319
|
+
* A running card draws a spinner and an elapsed time, and a streaming row is
|
|
320
|
+
* being appended to: their *fields* may not change between two frames while
|
|
321
|
+
* their lines must. Such a row gets a tick in its cache key, so it re-renders
|
|
322
|
+
* every frame and becomes cacheable again once it settles. A frozen spinner was
|
|
323
|
+
* the alternative — and, once the same card was also drawn live elsewhere, the
|
|
324
|
+
* duplicate a reader actually reported.
|
|
325
|
+
*/
|
|
326
|
+
private static isLiveRow;
|
|
327
|
+
/**
|
|
328
|
+
* Fingerprint of everything one row's rendering reads.
|
|
329
|
+
*
|
|
330
|
+
* Written by hand rather than hashing the object: the rendering reads a known
|
|
331
|
+
* set of fields, and a generic walk would cost more per frame than the render
|
|
332
|
+
* it is meant to avoid. Scalar fields are compared by value; nested arrays the
|
|
333
|
+
* rendering walks (todos, sources, intent) get a shallow signature of their own
|
|
334
|
+
* scalars, because those are updated in place.
|
|
335
|
+
*
|
|
336
|
+
* The list is a contract: a field the rendering starts reading must be added
|
|
337
|
+
* here, or a frame replays stale lines. A test mutates one field in place and
|
|
338
|
+
* asserts the next frame notices, which is the pattern to copy for a new field.
|
|
339
|
+
*/
|
|
340
|
+
private static rowKey;
|
|
341
|
+
private static rowRendersEqual;
|
|
277
342
|
/**
|
|
278
343
|
* The text of the last prompt the user sent, kept so an opted-in retry can
|
|
279
344
|
* send the same thing again after a provider-side auth failure. Cleared when
|
|
280
345
|
* the retry fires, which is what bounds it to one attempt per user message.
|
|
281
346
|
*/
|
|
347
|
+
/**
|
|
348
|
+
* Whether this session ever saw the reader's input.
|
|
349
|
+
*
|
|
350
|
+
* A fresh launch creates a session before anything is typed; quitting straight
|
|
351
|
+
* away leaves an artifact the TUI hides but other profiles' menus list. On the
|
|
352
|
+
* way out, a session that never saw input is deleted (see `session-blank.ts`).
|
|
353
|
+
*/
|
|
354
|
+
private sawUserInput;
|
|
282
355
|
private lastUserText;
|
|
283
356
|
/** Set on every turn/start; a retry consumes it. */
|
|
284
357
|
private authRetryArmed;
|
|
@@ -1237,6 +1310,30 @@ export declare class SshTui {
|
|
|
1237
1310
|
private explainAuthFailure;
|
|
1238
1311
|
/** Whether the opt-in retry is on (`/retryauth`, the settings form, or the env). */
|
|
1239
1312
|
private retryProviderAuthEnabled;
|
|
1313
|
+
/** Whether anything the reader typed reached this session (used on exit). */
|
|
1314
|
+
sessionHadUserInput(): boolean;
|
|
1315
|
+
/**
|
|
1316
|
+
* `/cleanup [--dry-run]` — delete sessions that never saw user input.
|
|
1317
|
+
*
|
|
1318
|
+
* Every fresh start creates a session, so quitting without typing leaves an
|
|
1319
|
+
* artifact behind. The picker hides those, but the web session list reads the
|
|
1320
|
+
* same files without that filter, which is how an unused session still shows
|
|
1321
|
+
* up in another profile's menu. This walks the whole history once and deletes
|
|
1322
|
+
* the blank ones (the listing prunes as it resolves, which is the same rule the
|
|
1323
|
+
* picker applies).
|
|
1324
|
+
*/
|
|
1325
|
+
private runCleanupCommand;
|
|
1326
|
+
/** The theme saved in the `ssh-tui` settings section, if any. */
|
|
1327
|
+
private readThemeName;
|
|
1328
|
+
/**
|
|
1329
|
+
* `/theme [name]` — list the palettes, or switch to one and remember it.
|
|
1330
|
+
*
|
|
1331
|
+
* Switching repaints from the row cache, so it costs one frame rather than a
|
|
1332
|
+
* re-render of the transcript; `mono` exists for terminals where colour is the
|
|
1333
|
+
* problem rather than the answer, and it keeps every difference as an
|
|
1334
|
+
* attribute instead of throwing the difference away.
|
|
1335
|
+
*/
|
|
1336
|
+
private runThemeCommand;
|
|
1240
1337
|
/** `/retryauth [on|off]` — the setting that lets one provider 401/403 retry itself. */
|
|
1241
1338
|
private runRetryAuthCommand;
|
|
1242
1339
|
private runDisconnectCommand;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ssh-tui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"docs/screenshots/workspace.png",
|
|
60
60
|
"docs/screenshots/slow-link.gif",
|
|
61
61
|
"LICENSE",
|
|
62
|
+
"docs/display-mode.md",
|
|
62
63
|
"docs/terminals.md",
|
|
63
64
|
"docs/windows.md"
|
|
64
65
|
],
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
"0.1.5-rc.3": "incompatible",
|
|
81
82
|
"0.1.7-rc.1": "compatible",
|
|
82
83
|
"0.1.7-rc.2": "compatible",
|
|
83
|
-
"0.2.0-rc.1": "compatible"
|
|
84
|
+
"0.2.0-rc.1": "compatible",
|
|
85
|
+
"0.2.0-rc.2": "compatible"
|
|
84
86
|
},
|
|
85
87
|
"profiles": [
|
|
86
88
|
"tui"
|
|
@@ -156,75 +158,75 @@
|
|
|
156
158
|
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
157
159
|
"@deepseek-ai/cordis-plugin-loader": "1.0.5",
|
|
158
160
|
"@deepseek-ai/cordis-plugin-timer": "1.1.6",
|
|
159
|
-
"@deepseek-ai/dsh": "0.2.0-rc.
|
|
160
|
-
"@deepseek-ai/dsh-agent": "0.2.0-rc.
|
|
161
|
-
"@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.
|
|
162
|
-
"@deepseek-ai/dsh-agent-loop": "0.2.0-rc.
|
|
163
|
-
"@deepseek-ai/dsh-agent-preset": "0.2.0-rc.
|
|
164
|
-
"@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.
|
|
165
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.
|
|
166
|
-
"@deepseek-ai/dsh-attachment": "0.2.0-rc.
|
|
167
|
-
"@deepseek-ai/dsh-brand": "0.2.0-rc.
|
|
168
|
-
"@deepseek-ai/dsh-cmdline": "0.2.0-rc.
|
|
169
|
-
"@deepseek-ai/dsh-commands": "0.2.0-rc.
|
|
170
|
-
"@deepseek-ai/dsh-credentials": "0.2.0-rc.
|
|
171
|
-
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.
|
|
172
|
-
"@deepseek-ai/dsh-invariants": "0.2.0-rc.
|
|
173
|
-
"@deepseek-ai/dsh-jobs": "0.2.0-rc.
|
|
174
|
-
"@deepseek-ai/dsh-llm": "0.2.0-rc.
|
|
175
|
-
"@deepseek-ai/dsh-llm-mock-server": "0.2.0-rc.
|
|
176
|
-
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.
|
|
177
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.
|
|
178
|
-
"@deepseek-ai/dsh-scope": "0.2.0-rc.
|
|
179
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.
|
|
180
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.0-rc.
|
|
181
|
-
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.
|
|
182
|
-
"@deepseek-ai/dsh-session-projection-cache": "0.2.0-rc.
|
|
183
|
-
"@deepseek-ai/dsh-settings": "0.2.0-rc.
|
|
184
|
-
"@deepseek-ai/dsh-subagent": "0.2.0-rc.
|
|
185
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.
|
|
186
|
-
"@deepseek-ai/dsh-timeout": "0.2.0-rc.
|
|
187
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.
|
|
188
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.
|
|
189
|
-
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.
|
|
190
|
-
"@deepseek-ai/dsh-user-questions": "0.2.0-rc.
|
|
161
|
+
"@deepseek-ai/dsh": "0.2.0-rc.2",
|
|
162
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
163
|
+
"@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.2",
|
|
164
|
+
"@deepseek-ai/dsh-agent-loop": "0.2.0-rc.2",
|
|
165
|
+
"@deepseek-ai/dsh-agent-preset": "0.2.0-rc.2",
|
|
166
|
+
"@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.2",
|
|
167
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
|
|
168
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
169
|
+
"@deepseek-ai/dsh-brand": "0.2.0-rc.2",
|
|
170
|
+
"@deepseek-ai/dsh-cmdline": "0.2.0-rc.2",
|
|
171
|
+
"@deepseek-ai/dsh-commands": "0.2.0-rc.2",
|
|
172
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
173
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
174
|
+
"@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
|
|
175
|
+
"@deepseek-ai/dsh-jobs": "0.2.0-rc.2",
|
|
176
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
177
|
+
"@deepseek-ai/dsh-llm-mock-server": "0.2.0-rc.2",
|
|
178
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
179
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
180
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|
|
181
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
182
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.0-rc.2",
|
|
183
|
+
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
|
|
184
|
+
"@deepseek-ai/dsh-session-projection-cache": "0.2.0-rc.2",
|
|
185
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
186
|
+
"@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
|
|
187
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
|
|
188
|
+
"@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
|
|
189
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
190
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
191
|
+
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.2",
|
|
192
|
+
"@deepseek-ai/dsh-user-questions": "0.2.0-rc.2",
|
|
191
193
|
"@types/node": "^24.0.0",
|
|
192
194
|
"@xterm/headless": "^6.0.0",
|
|
193
195
|
"semver": "^7.8.5",
|
|
194
196
|
"typescript": "^5.9.0"
|
|
195
197
|
},
|
|
196
198
|
"overrides": {
|
|
197
|
-
"@deepseek-ai/dsh": "0.2.0-rc.
|
|
198
|
-
"@deepseek-ai/dsh-agent": "0.2.0-rc.
|
|
199
|
-
"@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.
|
|
200
|
-
"@deepseek-ai/dsh-agent-loop": "0.2.0-rc.
|
|
201
|
-
"@deepseek-ai/dsh-agent-preset": "0.2.0-rc.
|
|
202
|
-
"@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.
|
|
203
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.
|
|
204
|
-
"@deepseek-ai/dsh-attachment": "0.2.0-rc.
|
|
205
|
-
"@deepseek-ai/dsh-brand": "0.2.0-rc.
|
|
206
|
-
"@deepseek-ai/dsh-cmdline": "0.2.0-rc.
|
|
207
|
-
"@deepseek-ai/dsh-commands": "0.2.0-rc.
|
|
208
|
-
"@deepseek-ai/dsh-credentials": "0.2.0-rc.
|
|
209
|
-
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.
|
|
210
|
-
"@deepseek-ai/dsh-invariants": "0.2.0-rc.
|
|
211
|
-
"@deepseek-ai/dsh-jobs": "0.2.0-rc.
|
|
212
|
-
"@deepseek-ai/dsh-llm": "0.2.0-rc.
|
|
213
|
-
"@deepseek-ai/dsh-llm-mock-server": "0.2.0-rc.
|
|
214
|
-
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.
|
|
215
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.
|
|
216
|
-
"@deepseek-ai/dsh-scope": "0.2.0-rc.
|
|
217
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.
|
|
218
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.0-rc.
|
|
219
|
-
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.
|
|
220
|
-
"@deepseek-ai/dsh-session-projection-cache": "0.2.0-rc.
|
|
221
|
-
"@deepseek-ai/dsh-settings": "0.2.0-rc.
|
|
222
|
-
"@deepseek-ai/dsh-subagent": "0.2.0-rc.
|
|
223
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.
|
|
224
|
-
"@deepseek-ai/dsh-timeout": "0.2.0-rc.
|
|
225
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.
|
|
226
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.
|
|
227
|
-
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.
|
|
228
|
-
"@deepseek-ai/dsh-user-questions": "0.2.0-rc.
|
|
199
|
+
"@deepseek-ai/dsh": "0.2.0-rc.2",
|
|
200
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
201
|
+
"@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.2",
|
|
202
|
+
"@deepseek-ai/dsh-agent-loop": "0.2.0-rc.2",
|
|
203
|
+
"@deepseek-ai/dsh-agent-preset": "0.2.0-rc.2",
|
|
204
|
+
"@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.2",
|
|
205
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
|
|
206
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
207
|
+
"@deepseek-ai/dsh-brand": "0.2.0-rc.2",
|
|
208
|
+
"@deepseek-ai/dsh-cmdline": "0.2.0-rc.2",
|
|
209
|
+
"@deepseek-ai/dsh-commands": "0.2.0-rc.2",
|
|
210
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
211
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
212
|
+
"@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
|
|
213
|
+
"@deepseek-ai/dsh-jobs": "0.2.0-rc.2",
|
|
214
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
215
|
+
"@deepseek-ai/dsh-llm-mock-server": "0.2.0-rc.2",
|
|
216
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
217
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
218
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|
|
219
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
220
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.0-rc.2",
|
|
221
|
+
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
|
|
222
|
+
"@deepseek-ai/dsh-session-projection-cache": "0.2.0-rc.2",
|
|
223
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
224
|
+
"@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
|
|
225
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
|
|
226
|
+
"@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
|
|
227
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
228
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
229
|
+
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.2",
|
|
230
|
+
"@deepseek-ai/dsh-user-questions": "0.2.0-rc.2"
|
|
229
231
|
}
|
|
230
232
|
}
|