@sagmans/dsh-tui 0.3.0 → 0.5.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/LICENSE +1 -1
- package/README.md +407 -53
- package/cordis.patch.yml +14 -0
- package/lib/agent/present.js +1 -1
- package/lib/agent/present.js.map +1 -1
- package/lib/agent/projections.d.ts +22 -6
- package/lib/agent/projections.d.ts.map +1 -1
- package/lib/agent/projections.js +21 -10
- package/lib/agent/projections.js.map +1 -1
- package/lib/agent/prompt-history.d.ts +89 -0
- package/lib/agent/prompt-history.d.ts.map +1 -0
- package/lib/agent/prompt-history.js +382 -0
- package/lib/agent/prompt-history.js.map +1 -0
- package/lib/agent/status.d.ts +6 -0
- package/lib/agent/status.d.ts.map +1 -1
- package/lib/agent/status.js +1 -0
- package/lib/agent/status.js.map +1 -1
- package/lib/cards.d.ts +83 -13
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +96 -28
- package/lib/cards.js.map +1 -1
- package/lib/export.d.ts +3 -3
- package/lib/export.d.ts.map +1 -1
- package/lib/export.js +23 -13
- package/lib/export.js.map +1 -1
- package/lib/fold-cursor.d.ts +12 -5
- package/lib/fold-cursor.d.ts.map +1 -1
- package/lib/fold-cursor.js +16 -5
- package/lib/fold-cursor.js.map +1 -1
- package/lib/gates.d.ts.map +1 -1
- package/lib/gates.js +42 -9
- package/lib/gates.js.map +1 -1
- package/lib/herdr/client.d.ts +57 -0
- package/lib/herdr/client.d.ts.map +1 -0
- package/lib/herdr/client.js +218 -0
- package/lib/herdr/client.js.map +1 -0
- package/lib/herdr/constants.d.ts +93 -0
- package/lib/herdr/constants.d.ts.map +1 -0
- package/lib/herdr/constants.js +79 -0
- package/lib/herdr/constants.js.map +1 -0
- package/lib/herdr/reporter.d.ts +60 -0
- package/lib/herdr/reporter.d.ts.map +1 -0
- package/lib/herdr/reporter.js +217 -0
- package/lib/herdr/reporter.js.map +1 -0
- package/lib/herdr/state.d.ts +57 -0
- package/lib/herdr/state.d.ts.map +1 -0
- package/lib/herdr/state.js +62 -0
- package/lib/herdr/state.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +700 -81
- package/lib/index.js.map +1 -1
- package/lib/injection.d.ts +9 -0
- package/lib/injection.d.ts.map +1 -0
- package/lib/injection.js +128 -0
- package/lib/injection.js.map +1 -0
- package/lib/input/actions.d.ts +19 -2
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +60 -11
- package/lib/input/actions.js.map +1 -1
- package/lib/input/cancel.d.ts +55 -0
- package/lib/input/cancel.d.ts.map +1 -0
- package/lib/input/cancel.js +47 -0
- package/lib/input/cancel.js.map +1 -0
- package/lib/input/completion.d.ts +5 -2
- package/lib/input/completion.d.ts.map +1 -1
- package/lib/input/completion.js +79 -4
- package/lib/input/completion.js.map +1 -1
- package/lib/input/file-search.d.ts +105 -0
- package/lib/input/file-search.d.ts.map +1 -0
- package/lib/input/file-search.js +665 -0
- package/lib/input/file-search.js.map +1 -0
- package/lib/input/fuzzy.d.ts +42 -0
- package/lib/input/fuzzy.d.ts.map +1 -0
- package/lib/input/fuzzy.js +289 -0
- package/lib/input/fuzzy.js.map +1 -0
- package/lib/input/ghost.d.ts +46 -0
- package/lib/input/ghost.d.ts.map +1 -0
- package/lib/input/ghost.js +58 -0
- package/lib/input/ghost.js.map +1 -0
- package/lib/input/keymap.d.ts +3 -1
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +16 -3
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/match.d.ts +12 -4
- package/lib/input/match.d.ts.map +1 -1
- package/lib/input/match.js +50 -51
- package/lib/input/match.js.map +1 -1
- package/lib/input/submission.d.ts +34 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +33 -5
- package/lib/input/submission.js.map +1 -1
- package/lib/keys-command.d.ts +19 -3
- package/lib/keys-command.d.ts.map +1 -1
- package/lib/keys-command.js +34 -31
- package/lib/keys-command.js.map +1 -1
- package/lib/settings-notice.d.ts +5 -0
- package/lib/settings-notice.d.ts.map +1 -1
- package/lib/settings-notice.js +9 -7
- package/lib/settings-notice.js.map +1 -1
- package/lib/stash/lock.d.ts +72 -0
- package/lib/stash/lock.d.ts.map +1 -0
- package/lib/stash/lock.js +374 -0
- package/lib/stash/lock.js.map +1 -0
- package/lib/stash/paths.d.ts +22 -0
- package/lib/stash/paths.d.ts.map +1 -0
- package/lib/stash/paths.js +84 -0
- package/lib/stash/paths.js.map +1 -0
- package/lib/stash/private-fs.d.ts +61 -0
- package/lib/stash/private-fs.d.ts.map +1 -0
- package/lib/stash/private-fs.js +379 -0
- package/lib/stash/private-fs.js.map +1 -0
- package/lib/stash/schema.d.ts +56 -0
- package/lib/stash/schema.d.ts.map +1 -0
- package/lib/stash/schema.js +108 -0
- package/lib/stash/schema.js.map +1 -0
- package/lib/stash/store.d.ts +92 -0
- package/lib/stash/store.d.ts.map +1 -0
- package/lib/stash/store.js +262 -0
- package/lib/stash/store.js.map +1 -0
- package/lib/stash.d.ts +122 -0
- package/lib/stash.d.ts.map +1 -0
- package/lib/stash.js +353 -0
- package/lib/stash.js.map +1 -0
- package/lib/terminal/external-editor.d.ts +93 -0
- package/lib/terminal/external-editor.d.ts.map +1 -0
- package/lib/terminal/external-editor.js +263 -0
- package/lib/terminal/external-editor.js.map +1 -0
- package/lib/terminal/host-writes.d.ts +39 -0
- package/lib/terminal/host-writes.d.ts.map +1 -0
- package/lib/terminal/host-writes.js +136 -0
- package/lib/terminal/host-writes.js.map +1 -0
- package/lib/terminal/signals.d.ts +25 -0
- package/lib/terminal/signals.d.ts.map +1 -0
- package/lib/terminal/signals.js +39 -0
- package/lib/terminal/signals.js.map +1 -0
- package/lib/terminal/warning-screen.d.ts +19 -1
- package/lib/terminal/warning-screen.d.ts.map +1 -1
- package/lib/terminal/warning-screen.js +37 -1
- package/lib/terminal/warning-screen.js.map +1 -1
- package/lib/terminal-text.d.ts +72 -0
- package/lib/terminal-text.d.ts.map +1 -0
- package/lib/terminal-text.js +603 -0
- package/lib/terminal-text.js.map +1 -0
- package/lib/text.d.ts +45 -6
- package/lib/text.d.ts.map +1 -1
- package/lib/text.js +106 -46
- package/lib/text.js.map +1 -1
- package/lib/theme-command.d.ts +7 -4
- package/lib/theme-command.d.ts.map +1 -1
- package/lib/theme-command.js +32 -12
- package/lib/theme-command.js.map +1 -1
- package/lib/theme-files.d.ts +110 -0
- package/lib/theme-files.d.ts.map +1 -0
- package/lib/theme-files.js +352 -0
- package/lib/theme-files.js.map +1 -0
- package/lib/theme-schema.d.ts +121 -0
- package/lib/theme-schema.d.ts.map +1 -0
- package/lib/theme-schema.js +87 -0
- package/lib/theme-schema.js.map +1 -0
- package/lib/theme-settings.d.ts +72 -10
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +162 -45
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +39 -7
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +104 -13
- package/lib/theme-tokens.js.map +1 -1
- package/lib/theme.d.ts +24 -2
- package/lib/theme.d.ts.map +1 -1
- package/lib/theme.js +49 -6
- package/lib/theme.js.map +1 -1
- package/lib/todo-guard.d.ts +116 -0
- package/lib/todo-guard.d.ts.map +1 -0
- package/lib/todo-guard.js +259 -0
- package/lib/todo-guard.js.map +1 -0
- package/lib/tool-display.d.ts +57 -0
- package/lib/tool-display.d.ts.map +1 -0
- package/lib/tool-display.js +51 -0
- package/lib/tool-display.js.map +1 -0
- package/lib/transcript.d.ts +28 -0
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +126 -37
- package/lib/transcript.js.map +1 -1
- package/lib/ui/diff.d.ts +49 -0
- package/lib/ui/diff.d.ts.map +1 -0
- package/lib/ui/diff.js +208 -0
- package/lib/ui/diff.js.map +1 -0
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +9 -6
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/editor.d.ts +41 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +100 -4
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/frame.d.ts +16 -5
- package/lib/ui/frame.d.ts.map +1 -1
- package/lib/ui/frame.js +32 -13
- package/lib/ui/frame.js.map +1 -1
- package/lib/ui/history-picker.d.ts +15 -0
- package/lib/ui/history-picker.d.ts.map +1 -0
- package/lib/ui/history-picker.js +26 -0
- package/lib/ui/history-picker.js.map +1 -0
- package/lib/ui/keymap-picker.d.ts +16 -0
- package/lib/ui/keymap-picker.d.ts.map +1 -0
- package/lib/ui/keymap-picker.js +41 -0
- package/lib/ui/keymap-picker.js.map +1 -0
- package/lib/ui/layout.d.ts +33 -0
- package/lib/ui/layout.d.ts.map +1 -0
- package/lib/ui/layout.js +33 -0
- package/lib/ui/layout.js.map +1 -0
- package/lib/ui/markdown.d.ts +25 -3
- package/lib/ui/markdown.d.ts.map +1 -1
- package/lib/ui/markdown.js +12 -8
- package/lib/ui/markdown.js.map +1 -1
- package/lib/ui/picker-card.d.ts +42 -0
- package/lib/ui/picker-card.d.ts.map +1 -0
- package/lib/ui/picker-card.js +148 -0
- package/lib/ui/picker-card.js.map +1 -0
- package/lib/ui/picker.d.ts +50 -2
- package/lib/ui/picker.d.ts.map +1 -1
- package/lib/ui/picker.js +66 -7
- package/lib/ui/picker.js.map +1 -1
- package/lib/ui/prompt.d.ts +12 -2
- package/lib/ui/prompt.d.ts.map +1 -1
- package/lib/ui/prompt.js +25 -2
- package/lib/ui/prompt.js.map +1 -1
- package/lib/ui/rows.d.ts +8 -6
- package/lib/ui/rows.d.ts.map +1 -1
- package/lib/ui/rows.js +10 -18
- package/lib/ui/rows.js.map +1 -1
- package/lib/ui/stash-picker.d.ts +43 -0
- package/lib/ui/stash-picker.d.ts.map +1 -0
- package/lib/ui/stash-picker.js +78 -0
- package/lib/ui/stash-picker.js.map +1 -0
- package/lib/ui/status.d.ts +2 -0
- package/lib/ui/status.d.ts.map +1 -1
- package/lib/ui/status.js +25 -16
- package/lib/ui/status.js.map +1 -1
- package/lib/ui/theme-picker.d.ts +18 -0
- package/lib/ui/theme-picker.d.ts.map +1 -0
- package/lib/ui/theme-picker.js +68 -0
- package/lib/ui/theme-picker.js.map +1 -0
- package/lib/ui/view.d.ts +146 -22
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +461 -154
- package/lib/ui/view.js.map +1 -1
- package/lib/work.d.ts +3 -1
- package/lib/work.d.ts.map +1 -1
- package/lib/work.js +9 -1
- package/lib/work.js.map +1 -1
- package/package.json +7 -1
- package/themes/deepseek-blue.yaml +183 -0
- package/themes/violet-orbit.yaml +178 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Interactive terminal (TUI) surface for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): use `dsh` in a terminal instead of a browser.
|
|
4
4
|
|
|
5
|
-
Status: **v1 feature-complete; published on npm as `@sagmans/dsh-tui`.** The surface owns the alternate screen,
|
|
5
|
+
Status: **v1 feature-complete; published on npm as `@sagmans/dsh-tui`.** The surface owns the alternate screen, renders every message as markdown, renders every tool's own card, answers approvals and questions, restores and names stored conversations, switches model mid-session, runs any of the four shipped agent modes and switches between them before a session's first turn, reads a child agent's conversation in place, keeps the goal, plan mode, todo list, delegations, and background jobs above the editor with a status line below it, nudges an agent whose plan has aged without an update, parks and restores prompt drafts per session, and hands the terminal back on every graceful exit. Publication is tag-driven with GitHub OIDC provenance and no stored npm token; see [RELEASE.md](RELEASE.md).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -117,27 +117,34 @@ dsh --profile tui --no-color
|
|
|
117
117
|
dsh --profile tui --no-bell # do not ring when a long turn finishes
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Every key below is a shipped default. `/keys`
|
|
121
|
-
and its library can perform
|
|
122
|
-
moves any of them — see [Keys](#keys).
|
|
120
|
+
Every key below is a shipped default. `/keys` opens every action the surface
|
|
121
|
+
and its library can perform as a list you filter as you type, with the keys in
|
|
122
|
+
force, and the `keys:` section moves any of them — see [Keys](#keys).
|
|
123
123
|
|
|
124
124
|
| Key | Action |
|
|
125
125
|
|---|---|
|
|
126
126
|
| Enter / Shift+Enter | break the line: a prompt is written before it is sent |
|
|
127
127
|
| Ctrl+Enter / Alt+Enter / Ctrl+S | submit the prompt |
|
|
128
|
-
| Ctrl+C |
|
|
128
|
+
| Ctrl+C | take back one thing at a time: the draft in the bar, the prompts waiting in the agent's inbox, the running turn, or a child's conversation; with a picker, an approval, a question, or the transcript search open it closes that instead. With nothing left to cancel it does nothing — it never leaves |
|
|
129
|
+
| Ctrl+D | leave and print the resume command, when the bar holds no text and nothing is open; a running turn is cancelled first |
|
|
129
130
|
| Ctrl+O | open every tool card: its header plus every retained row. Folded, a card is one line, and a shell card keeps its command plus the last 20 rows of output with a hint naming what it dropped |
|
|
130
|
-
| Ctrl+Y | show or hide the calls a PTC program dispatched: one two-space-indented
|
|
131
|
-
| Shift+Tab | expand or fold
|
|
131
|
+
| Ctrl+Y | show or hide the calls a PTC program dispatched: one two-space-indented row per call under its `run_code` card, named and argued from the tool's own header and cut at the screen edge; clicking one row opens that call in full — the change it declared, then what it produced — for every tool, a failed call included; shown by default |
|
|
132
|
+
| Shift+Tab | expand or fold every thought behind the answers: folded, the row names itself, its token count, and the key; opened, it adds the thought, laid out as markdown; a click decides for one thought instead |
|
|
132
133
|
| Ctrl+T | pick the reasoning effort for the next step |
|
|
134
|
+
| Ctrl+R | reverse-search recorded prompts: the list opens filtered by whatever is in the bar, `enter` puts one back, `esc` keeps the draft |
|
|
135
|
+
| Ctrl+X then S | stash the current draft |
|
|
136
|
+
| Ctrl+X then L | open this session's stashed drafts |
|
|
133
137
|
| Ctrl+X then M | open the model picker |
|
|
134
138
|
| Ctrl+X then Y | copy the last answer to the clipboard |
|
|
135
|
-
|
|
|
136
|
-
|
|
|
139
|
+
| Ctrl+X then E | edit the draft in `$VISUAL` (or `$EDITOR`) and take back what it saves |
|
|
140
|
+
| Ctrl+X then ? | search the key map: every action and the keys in force, in a box over the transcript |
|
|
141
|
+
| `y` / `n` / Esc / Ctrl+C | allow once, reject, or cancel a pending approval |
|
|
142
|
+
| digits / space / ↑↓ / Enter / Esc / Ctrl+C | answer a question: pick or toggle, confirm, or skip one with Esc; Ctrl+C abandons the whole batch with no answers, like an aborted call; `0` answers with your own text in the input bar |
|
|
143
|
+
| ↑↓ / Ctrl+P / Ctrl+N | move through the open list: a picker's rows, a question's options, or the completion menu above the bar |
|
|
137
144
|
| typing in any picker or question | narrow the rows by fragment (`glm53` finds `GLM-5.3`); backspace widens, `esc` or Ctrl+C leaves |
|
|
138
145
|
| `/` then Tab | complete commands, including every command this session registered |
|
|
139
|
-
| `@`
|
|
140
|
-
| `ctrl+shift+f` | search the transcript (`enter` next, `shift+enter` previous, `esc` close) |
|
|
146
|
+
| `@` | open the workspace file menu, narrowed as you type; a path then Tab still completes a file reference |
|
|
147
|
+
| `ctrl+shift+f` | search the transcript (`enter` next, `shift+enter` previous, `esc` or Ctrl+C close) |
|
|
141
148
|
| `home` / `end` | jump to the start or the end of the transcript |
|
|
142
149
|
| `ctrl+down` | jump to the next prompt |
|
|
143
150
|
| `ctrl+b` | leave a child's conversation and return to this session (the status line names the key you have now) |
|
|
@@ -150,6 +157,7 @@ moves any of them — see [Keys](#keys).
|
|
|
150
157
|
| `/model <provider>/<model>/<effort>` | use that route and reasoning effort (the effort must be one the route advertises) |
|
|
151
158
|
| `/preset` | pick the agent mode for this session from the roster |
|
|
152
159
|
| `/preset <id>` | switch to that mode, while the session is still blank |
|
|
160
|
+
| `/new [title]` | start a fresh session without leaving the terminal (`ctrl+x` then `n` starts one untitled) |
|
|
153
161
|
| `/jobs` | list background jobs with their state and duration |
|
|
154
162
|
| `/jobs read <id>` / `/jobs kill <id>` | show the tail of a job's output, or stop it |
|
|
155
163
|
| `/subagents` | list the delegations this session started, with their provider and age |
|
|
@@ -160,34 +168,151 @@ moves any of them — see [Keys](#keys).
|
|
|
160
168
|
| `/export [path]` | write the visible transcript as markdown (default `dsh-session-<id>.md`) |
|
|
161
169
|
| `/resume` | open another stored session without leaving the terminal |
|
|
162
170
|
| `/clear` | clear the visible transcript |
|
|
163
|
-
| `/
|
|
164
|
-
| `/
|
|
171
|
+
| `/history` | show how many prompts are recorded and where the file is |
|
|
172
|
+
| `/history clear` | forget every recorded prompt, reporting how many went |
|
|
173
|
+
| `/theme` | open the theme picker: type to filter, the screen paints the row under the cursor |
|
|
174
|
+
| `/theme <name>` | apply a theme by name and write the choice to the settings document |
|
|
175
|
+
| `/theme tokens` | list every styled element and the value in force |
|
|
176
|
+
| `/theme export <built-in>` | copy a built-in into your own themes directory to edit |
|
|
177
|
+
| `/keys` | open the key map as a list you filter as you type; `/keys <layer>` opens it already narrowed to one layer (see [Keys](#keys)) |
|
|
178
|
+
| `/stash <draft>` | park the text given after the command (`ctrl+x` then `s` parks the editor) |
|
|
179
|
+
| `/stash-pop [index\|id]` | put a stashed draft into the editor and remove it (newest by default) |
|
|
180
|
+
| `/stash-apply [index\|id]` | put a stashed draft into the editor and keep it |
|
|
181
|
+
| `/stash-list` | pick from this session's stashed drafts; `enter` pops the marked one |
|
|
182
|
+
| `/stash-drop [index\|id]` | delete a stashed draft without using it |
|
|
183
|
+
| `/stash-clear` | delete every draft stashed in this session, after a confirmation |
|
|
165
184
|
| `/quit` | leave and print the resume command |
|
|
166
185
|
|
|
186
|
+
Typing `@` opens this workspace's files above the editor, ranked as the fragment is typed the way a fuzzy finder ranks a path list: `@edtr` reaches `src/ui/editor.ts` without spelling the separators, a directory offers itself with a trailing slash so typing continues into it, and a path holding a space is quoted. A directory whose own name holds a space offers no row, because the menu stops following the token once one is in it; the files under it are still listed, each quoted whole. The rows are what git tracks or would add, with ignored paths left out, so a suggestion never names build output or a secret the repository deliberately ignores; a tree git does not own is walked instead, skipping `node_modules`, `.git`, and the rest of the build litter. A path typed from the working directory still completes on Tab as before.
|
|
187
|
+
|
|
167
188
|
`Ctrl+X` starts a chord. For the next two seconds the footer leads with the
|
|
168
189
|
prefix alone — enough to say that a key is waiting, without reciting the map —
|
|
169
190
|
and a key that finishes nothing is typed as usual rather than swallowed, so a
|
|
170
191
|
prefix pressed by accident costs nothing; `/help` lists the chords, `m` for the
|
|
171
|
-
model picker, `p` for plan mode,
|
|
192
|
+
model picker, `p` for plan mode, `n` for a fresh session, `y` for the last
|
|
193
|
+
answer, `s` to stash the draft, `l` for the stashes, `e` for the draft in the
|
|
194
|
+
reader's own editor, and `?` for the key map.
|
|
172
195
|
`keys.chord.prefix: alt+x` starts the chord with another key — or with a list of
|
|
173
196
|
them, as so many ways in — and `prefixWindow: 0` waits for the next key instead
|
|
174
197
|
of lapsing; every second key is a row of its own (`chord.model`, `chord.plan`,
|
|
175
|
-
`chord.copy`
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
`/plan
|
|
182
|
-
|
|
183
|
-
|
|
198
|
+
`chord.new`, `chord.copy`, `chord.stash`, `chord.stashes`, `chord.editor`,
|
|
199
|
+
`chord.keys`), so a chord can be respelled whole. A prefix that is not a modifier
|
|
200
|
+
chord, that the surface or the prompt bar already answers (`ctrl+c`, `ctrl+s`),
|
|
201
|
+
or that the terminal keeps (`ctrl+q`) is refused with the reason, and the
|
|
202
|
+
shipped keymap stays in force. The chords themselves are the commands they stand
|
|
203
|
+
for: `m`, `p`, `n`, `y`, `s`, `l`, and `?` ask the same dispatcher `/model`,
|
|
204
|
+
`/plan`, `/new`, `/copy`, `/stash`, `/stash-list`, and `/keys` do; `e` is the
|
|
205
|
+
one chord with no command behind it, because it opens a program rather than
|
|
206
|
+
running a line. Plan mode is the one pair that cannot share a name: `/plan` only
|
|
207
|
+
enters, so the chord names `/plan off` instead when the agent is in plan mode —
|
|
208
|
+
or is waiting for the turn boundary to become so — and reads that state from the
|
|
209
|
+
plan package rather than from the dock.
|
|
184
210
|
|
|
185
211
|
An approval or a question draws inline above the editor and takes the keyboard. A question that lists options always adds row `0. other — type your own answer`: type or paste an answer the model did not offer, and the seam receives it as that question's free text — replacing a single-select choice, or supplementing a multi-select one. `0`, or `↓` past the last option, reaches the row; `↑` walks back to the list with the text kept, and `esc` does the same from that row, because a question skipped by accident is a question answered twice — an escape from the list skips it. Free text is written in the prompt bar's own editor, drawn under that row: movement, word and line deletion, undo, completion, and multi-line paste are all the editor the reader already uses, and the prompt bar steps aside while a question is open, so a prompt written but not sent comes back untouched once the question is answered. No question hides its answer — the reader is the one who has to check what they are about to send. Every gate row wraps at the screen edge under its own label, so a long option or question is readable rather than cut.
|
|
186
212
|
|
|
187
|
-
While a turn runs, a prompt submitted into the editor waits in the agent's own inbox instead of disappearing: it is drawn above the editor in the input bar's own frame, faint and italic, and moves into the transcript when the agent takes it — where it keeps that frame in the prompt's own mint shade, so what the reader typed is never mistaken for what the agent said. `editor.queued` and `editor.queued.more` restyle or hide the waiting rows; `transcript.user` restyles the submitted prompt.
|
|
213
|
+
While a turn runs, a prompt submitted into the editor waits in the agent's own inbox instead of disappearing: it is drawn above the editor in the input bar's own frame, faint and italic, and moves into the transcript when the agent takes it — where it keeps that frame in the prompt's own mint shade, so what the reader typed is never mistaken for what the agent said. Its markdown lays out inside that frame, so a list or a fence reads in the same box it was typed into. `editor.queued` and `editor.queued.more` restyle or hide the waiting rows; `transcript.user` restyles the submitted prompt. A reply is drawn in a frame of its own, so one exchange reads as two objects rather than as a box followed by a stream of rows: `transcript.assistant.border` restyles that frame, and hiding it draws the reply bare. `ctrl+c` takes them back: an interrupt drops whatever the agent has not started, so the waiting prompts are read first and put into the bar before the turn is stopped.
|
|
214
|
+
|
|
215
|
+
Every submitted line is also kept in a global prompt history at
|
|
216
|
+
`$DSH_HOME/prompt-history.json`. Typing the start of a prompt that was sent
|
|
217
|
+
before draws the rest of the newest match after the cursor in a faint shade.
|
|
218
|
+
`Ctrl+E` takes the whole suggestion and the word-movement key takes the next
|
|
219
|
+
word, and both keys fall back to their old meaning the moment nothing is
|
|
220
|
+
offered. The history is deliberately global — the same prompt is useful in
|
|
221
|
+
every checkout — so nothing records a directory. An exact repeat moves to the
|
|
222
|
+
front instead of being stored twice. `history.ghost: false` keeps reverse
|
|
223
|
+
search but stops drawing the suggestion, `history.enabled: false` stops
|
|
224
|
+
recording and offering, and `history.maxEntries` bounds the file. A file this
|
|
225
|
+
build cannot parse is left untouched and writes are refused, so a newer format
|
|
226
|
+
is never overwritten; `/history` names it and the count, and `/history clear`
|
|
227
|
+
forgets everything.
|
|
188
228
|
|
|
189
229
|
Any other `/command` goes to the command registry, so `/plan`, `/compact`, `/goal`, and `/feedback` behave as they do on the other surfaces.
|
|
190
230
|
|
|
231
|
+
## Prompt stash
|
|
232
|
+
|
|
233
|
+
`ctrl+x` then `s` parks the draft the editor is holding and clears it;
|
|
234
|
+
`/stash <draft>` parks a draft typed on the command line. A bare `/stash` only
|
|
235
|
+
says so, because submitting a command consumes the line it was typed on and there
|
|
236
|
+
is nothing left of the draft to park. `/stash-pop` puts a parked draft back and
|
|
237
|
+
removes it, so a prompt written for the wrong moment survives a restart instead
|
|
238
|
+
of being retyped or sent; `l` opens the list of them. A stash belongs to the
|
|
239
|
+
session it was parked in, so a second terminal in the same checkout never sees
|
|
240
|
+
these drafts while resuming the session does; the footer shows `stash N` while
|
|
241
|
+
any are waiting, ranked above the context and cache numbers it shares a row with.
|
|
242
|
+
|
|
243
|
+
A selector is the number the list shows in brackets — `0` is the newest — or the
|
|
244
|
+
entry's own id; leaving it out takes the newest. `apply` and `pop` refuse to
|
|
245
|
+
overwrite a draft already in the editor, because losing an unsent prompt to a
|
|
246
|
+
restore is the one outcome the feature exists to prevent. They refuse while a
|
|
247
|
+
question is borrowing the bar for the same reason: a draft written into an answer
|
|
248
|
+
would be sent as one. `pop` writes the editor first and removes the entry second,
|
|
249
|
+
so a crash between the two leaves the draft in the bank rather than only in a
|
|
250
|
+
terminal that is gone.
|
|
251
|
+
|
|
252
|
+
Nothing is cleared until the write has landed. A refusal — no room left, a bank
|
|
253
|
+
past its cap, another writer holding the lock — leaves the draft in the bar,
|
|
254
|
+
including a draft typed after `/stash`, which is written back into the bar before
|
|
255
|
+
the write is attempted. The bar is only cleared while it still holds that same
|
|
256
|
+
draft and no question has borrowed it, so an answer typed during the write is
|
|
257
|
+
never wiped by a stash finishing.
|
|
258
|
+
|
|
259
|
+
The bank is one JSON file per session under `$DSH_HOME/tui-stash`, written with
|
|
260
|
+
owner-only permissions (`0700` directory, `0600` file) through a no-follow open,
|
|
261
|
+
and every directory the path passes through must be owned by the reader (or by
|
|
262
|
+
root) and not writable by anyone else — the sticky bit is the only exception,
|
|
263
|
+
since it keeps renaming to an entry's owner. Links are walked one hop at a time,
|
|
264
|
+
with `..` left for the filesystem to resolve against what the link points at, and
|
|
265
|
+
a link this user does not own ends the walk: a chain that jumps through a shared
|
|
266
|
+
directory is refused at the directory it jumped through. A directory
|
|
267
|
+
that another user or a group member could redirect the storage through is refused
|
|
268
|
+
rather than trusted, which is also why a group-writable home directory fails the
|
|
269
|
+
stash with the offending path named. Every update is a locked read-modify-write
|
|
270
|
+
and an atomic temp-and-rename, so two surfaces using one bank cannot lose each
|
|
271
|
+
other's entries; reclaiming a lock whose owner is gone is serialized on a
|
|
272
|
+
per-bank claim file, and the removal only applies to the lock it judged, so a
|
|
273
|
+
holder that released in between cannot have its successor's live lock deleted. A
|
|
274
|
+
contender never deletes a lock it did not publish, so losing the name to a
|
|
275
|
+
successor costs a retry rather than the successor's turn.
|
|
276
|
+
|
|
277
|
+
A bank whose session id is not this one, or whose bytes do not parse, is
|
|
278
|
+
moved aside as `<name>.corrupt-<time>` and reported with its path — including when
|
|
279
|
+
the directory holding the copy could not be synced. A bank written by a newer
|
|
280
|
+
format, or one past the size cap, is refused in place rather than moved, because
|
|
281
|
+
neither is corruption. A storage directory a save had to create is flushed
|
|
282
|
+
through the directory that names it before the save reports anything. If that
|
|
283
|
+
flush fails, the empty directories are taken back so the retry starts clean; a
|
|
284
|
+
directory another surface has already saved into is left exactly as it is, because
|
|
285
|
+
an entry left unflushed costs durability while a removed bank costs the draft. Drafts are never written to a session log, and control and
|
|
286
|
+
Unicode bidi controls are stripped when a draft is stored and again when it is
|
|
287
|
+
read, so a hand-edited bank cannot park a terminal escape or a reordering trick in
|
|
288
|
+
the bar.
|
|
289
|
+
|
|
290
|
+
## External editor
|
|
291
|
+
|
|
292
|
+
`ctrl+x` then `e` hands the draft to the editor the environment already names:
|
|
293
|
+
`$VISUAL` first, then `$EDITOR`, split on whitespace with quotes grouping and
|
|
294
|
+
nothing else special — no shell, no backslash escapes — so `code --wait` works
|
|
295
|
+
and a quoted path stays one argument. This surface cannot
|
|
296
|
+
draw an editor inside its own screen, so it gives the terminal up — the
|
|
297
|
+
alternate screen leaves, the child runs on the same tty — and takes it back
|
|
298
|
+
when the child exits; the frame is repainted whole, and the bar holds whatever
|
|
299
|
+
was saved. Nothing is submitted: a draft written for later survives a detour
|
|
300
|
+
through a full editor.
|
|
301
|
+
|
|
302
|
+
No shell is involved: the configured line is split here and the program is
|
|
303
|
+
spawned directly, because an environment value is data and a typo in it must not
|
|
304
|
+
become a command. The scratch file is a fresh directory per handoff, mode `0700`
|
|
305
|
+
with a `0600` file, removed when the editor leaves; the text read back is read
|
|
306
|
+
through one handle that follows no link and accepts only a plain file, so a draft
|
|
307
|
+
swapped for a link, a fifo, or a device is refused rather than followed, and it
|
|
308
|
+
is stripped of control and bidi characters, because it is going into a live
|
|
309
|
+
editor rather than being drawn as text. A child that exits non-zero is not a failure —
|
|
310
|
+
an editor that refused to save has already said so, and what it did save is what
|
|
311
|
+
the reader meant to keep. Nothing configured, a program that could not start, a
|
|
312
|
+
save that cannot be read, and a draft past 1 MiB are notices that leave the bar
|
|
313
|
+
as it was; the oversized draft is left on disk with its path, because a refusal
|
|
314
|
+
must not also be a way to lose the work.
|
|
315
|
+
|
|
191
316
|
## Settings
|
|
192
317
|
|
|
193
318
|
Every styled element is a named token with a shipped default, and every key is
|
|
@@ -197,18 +322,28 @@ code. Preferences live in the same user-settings document as every other
|
|
|
197
322
|
|
|
198
323
|
```yaml
|
|
199
324
|
dsh-tui:
|
|
325
|
+
theme: violet-orbit # restyle the whole surface by name (default: deepseek-blue)
|
|
200
326
|
subcalls: collapsed # fold the calls a PTC program dispatched (default inline)
|
|
201
327
|
mermaid: streaming # draw a reply's mermaid fences: off, final, or streaming (default streaming)
|
|
328
|
+
tools:
|
|
329
|
+
default: { collapsed: true, output: hidden } # how every tool's card starts
|
|
330
|
+
bash: { output: tail, tail: 5 } # keep the last five output rows behind bash's fold
|
|
331
|
+
read: { collapsed: false } # start reads open
|
|
202
332
|
prefixWindow: 2 # seconds a chord waits for its second key; 0 waits for the next key instead
|
|
203
333
|
keys:
|
|
204
334
|
chord.prefix: ctrl+x # the key that starts a chord; "prefix:" is the older spelling of this row
|
|
335
|
+
chord.keys: '?' # quoted: a bare ? is a YAML indicator, not a key
|
|
205
336
|
prompt.submit: [ctrl+enter, alt+enter, ctrl+s]
|
|
206
337
|
surface.effort: ctrl+t # one key, or a list of them
|
|
207
338
|
tui.editor.yank: ctrl+y # any action the library draws, by the id /keys prints
|
|
339
|
+
history:
|
|
340
|
+
enabled: true # record prompts and offer them back (default true)
|
|
341
|
+
ghost: true # draw the dimmed completion; reverse search stays either way (default true)
|
|
342
|
+
maxEntries: 2000 # prompts kept, newest first (1-20000, default 2000)
|
|
208
343
|
palette:
|
|
209
344
|
muted: '#5c5c5c' # one shade quiets every receding element
|
|
210
345
|
tokens:
|
|
211
|
-
transcript.
|
|
346
|
+
transcript.notice:
|
|
212
347
|
fg: '#7a7a7a'
|
|
213
348
|
italic: true
|
|
214
349
|
tool.title:
|
|
@@ -224,12 +359,34 @@ keymap on the next press, and `/theme` shows each element's effective value and
|
|
|
224
359
|
whether it came from an override, the palette, or the default.
|
|
225
360
|
|
|
226
361
|
|
|
227
|
-
The section is not only shades. By default every session draws
|
|
228
|
-
program dispatched under
|
|
229
|
-
card alone instead. `Ctrl+Y` toggles the same choice for the current
|
|
230
|
-
and an edit to the document re-seeds it. An unknown key or value is
|
|
362
|
+
The section is not only shades. By default every session draws one row per call
|
|
363
|
+
a PTC program dispatched under its card, and `subcalls: collapsed` starts with
|
|
364
|
+
the card alone instead. `Ctrl+Y` toggles the same choice for the current
|
|
365
|
+
session, and an edit to the document re-seeds it. An unknown key or value is
|
|
231
366
|
refused with a notice naming it, so a typo cannot quietly do nothing.
|
|
232
367
|
|
|
368
|
+
The `tools` block decides how each tool's cards draw. `collapsed` starts a
|
|
369
|
+
tool folded to one header row (default `true`), and `output` is `hidden`
|
|
370
|
+
(default) or `tail`, where `tail` is how many output rows a folded card keeps
|
|
371
|
+
(default `20`). A folded row ends a few columns short of the screen edge and
|
|
372
|
+
the argument is what gives up that room: a wide terminal shows more of the call,
|
|
373
|
+
a narrow one still shows the tool, how it ended, and how much waits behind the
|
|
374
|
+
fold. The reserved `default` row applies to every tool without its own, and a
|
|
375
|
+
tool name nothing declares is inert: the surface cannot know which tools a
|
|
376
|
+
profile mounts. Clicking a card opens or folds that one message, and a dispatched
|
|
377
|
+
call's row opens on the same click to what the tool itself drew for it — an
|
|
378
|
+
edit's diff in the diff colours, a read's numbered lines, a command and its
|
|
379
|
+
output — followed by the outcome it produced; a call that failed opens to the
|
|
380
|
+
reason it reported instead of rows for work that never happened. `Ctrl+O` still
|
|
381
|
+
decides for every message nobody clicked.
|
|
382
|
+
|
|
383
|
+
The `history` block tunes the prompt history. `enabled: false` stops recording
|
|
384
|
+
and offering it; `ghost: false` keeps reverse search but stops the dimmed
|
|
385
|
+
completion; `maxEntries` bounds the file, and an exact repeat moves to the
|
|
386
|
+
front rather than being stored twice. `editor.ghost` styles the suggestion, and
|
|
387
|
+
`NO_COLOR` or `--no-color` suppresses it entirely, because a suggestion the
|
|
388
|
+
reader cannot see but could still accept is worse than none.
|
|
389
|
+
|
|
233
390
|
A reply whose fenced block names `mermaid` is drawn as terminal box art instead
|
|
234
391
|
of source, laid out at the width the transcript has. `mermaid: streaming` (the
|
|
235
392
|
default) draws a diagram while the reply is still arriving, `final` waits for
|
|
@@ -243,14 +400,81 @@ restyleable like anything else through `markdown.diagram.border`,
|
|
|
243
400
|
lists it with the rest. Nothing is lost by drawing: `/export` and the session
|
|
244
401
|
file keep the reply exactly as the model wrote it.
|
|
245
402
|
|
|
246
|
-
`
|
|
247
|
-
|
|
403
|
+
A fenced block whose language is `diff` or `patch` is drawn as the change it
|
|
404
|
+
describes rather than as one plain code block: file headers and hunk headers
|
|
405
|
+
recede, added rows draw green, removed rows draw red, and a row that replaced
|
|
406
|
+
another puts the characters that actually changed on a darker band of its own
|
|
407
|
+
colour, so a one-word edit reads at a glance instead of as two unrelated lines.
|
|
408
|
+
A pair that shares too little to be an edit draws whole-row, unchanged rows keep
|
|
409
|
+
the shade a code block always had, and any other language draws exactly as
|
|
410
|
+
before. The change is drawn in replies, submitted prompts, and thoughts alike,
|
|
411
|
+
because red and green say what the fence means rather than how loudly it is
|
|
412
|
+
drawn. The seven elements — `markdown.diff.header`, `.hunk`, `.context`,
|
|
413
|
+
`.added`, `.removed`, and the `.addedEmphasis` and `.removedEmphasis` bands
|
|
414
|
+
— are named by every shipped theme and overridden like any other, `hidden`
|
|
415
|
+
included; hiding an emphasis element keeps the row's own colour instead of
|
|
416
|
+
leaving a gap, and `NO_COLOR` draws the fence as plain text. `/export` and the
|
|
417
|
+
session file still keep the fence exactly as the model wrote it.
|
|
418
|
+
|
|
419
|
+
A theme restyles the whole surface by name, and a theme is a file. The package
|
|
420
|
+
ships two: `deepseek-blue`, the table written out in full in the colours the
|
|
421
|
+
project answers to, and `violet-orbit`, a port of pi's theme of that name — its
|
|
422
|
+
palette, plus the elements it draws its own way. `deepseek-blue` is also what a
|
|
423
|
+
document naming no theme draws, so the default look is a file you can read, list,
|
|
424
|
+
and copy rather than a table compiled in. Your own themes live in
|
|
425
|
+
`$DSH_HOME/themes/`, which the surface creates at start-up and watches, so saving
|
|
426
|
+
a file there is how you change the surface you are looking at. A bare `/theme`
|
|
427
|
+
opens the list of them, narrowing as you type, and the screen paints the row under
|
|
428
|
+
the cursor as it moves: two themes are compared on your own transcript, and nothing
|
|
429
|
+
is written until one is taken, so leaving the list puts back the theme that was in
|
|
430
|
+
force. `theme: violet-orbit` applies one from the document, and a name nothing
|
|
431
|
+
answers to is reported with the names that do, drawing the default while you fix it.
|
|
432
|
+
|
|
433
|
+
Both files name every element and every palette entry, so a copy of one is a
|
|
434
|
+
complete theme rather than a diff against something you cannot see.
|
|
435
|
+
`deepseek-blue` is the one to copy to move a single shade, because every element
|
|
436
|
+
follows one of its ten palette entries, each taken from DeepSeek's own design
|
|
437
|
+
tokens with the token named beside it — and the accent is one line.
|
|
438
|
+
`/theme export <built-in>` writes that copy into your own directory as
|
|
439
|
+
`<built-in>_export_<n>.yaml`, adding one comment naming the release it came from:
|
|
440
|
+
the package's own file is replaced whenever the package updates, so the copy is the
|
|
441
|
+
only one worth editing. A file whose name is a built-in's is ignored, and reported
|
|
442
|
+
at start-up with the rename that fixes it.
|
|
443
|
+
|
|
444
|
+
A theme is a layer and not a replacement: everything it says nothing about keeps
|
|
445
|
+
its shipped appearance, and a `tokens:` entry of your own still wins over it one
|
|
446
|
+
field at a time, so naming a single attribute does not discard the shade the theme
|
|
447
|
+
gave that same element. `/theme tokens` names the theme in force in its heading and
|
|
448
|
+
reports each element as `override`, `theme`, `palette`, or `default`, marking the
|
|
449
|
+
themes in your own directory and printing the export hint, so a screen that looks
|
|
450
|
+
wrong can be traced to the layer that drew it.
|
|
451
|
+
|
|
452
|
+
A fenced block whose language is `diff` or `patch` is drawn as the change it
|
|
453
|
+
describes rather than as one plain code block: file headers and hunk headers
|
|
454
|
+
recede, added rows draw green, removed rows draw red, and a row that replaced
|
|
455
|
+
another puts the characters that actually changed on a darker band of its own
|
|
456
|
+
colour, so a one-word edit reads at a glance instead of as two unrelated lines.
|
|
457
|
+
A pair that shares too little to be an edit draws whole-row, unchanged rows keep
|
|
458
|
+
the shade a code block always had, and any other language draws exactly as
|
|
459
|
+
before. The change is drawn in replies, submitted prompts, and thoughts alike,
|
|
460
|
+
because red and green say what the fence means rather than how loudly it is
|
|
461
|
+
drawn. The seven elements — `markdown.diff.header`, `.hunk`, `.context`,
|
|
462
|
+
`.added`, `.removed`, and the `.addedEmphasis` and `.removedEmphasis` bands
|
|
463
|
+
— are overridden like any other, `hidden` included; hiding an emphasis element
|
|
464
|
+
keeps the row's own colour instead of leaving a gap, and `NO_COLOR` draws the
|
|
465
|
+
fence as plain text. `/export` and the session file still keep the fence exactly
|
|
466
|
+
as the model wrote it.
|
|
467
|
+
|
|
468
|
+
`fg` and `bg` accept `#rrggbb`, a palette name (`default`, `muted`, `faint`,
|
|
469
|
+
`accent`, `arg`, `warn`, `added`, `removed`, `user`, `assistant`), or an index. A colour is
|
|
248
470
|
emitted as 24-bit when the terminal advertises it (`COLORTERM`) and degraded to
|
|
249
471
|
the nearest 256-colour entry or 16-colour slot otherwise; a hue keeps its family
|
|
250
472
|
there, so an addition stays green instead of collapsing to black. Muted elements
|
|
251
473
|
name the palette rather than a terminal slot, so on anything but a 16-colour
|
|
252
474
|
terminal their contrast does not depend on what the reader's colour scheme maps
|
|
253
|
-
slot 8 to. `
|
|
475
|
+
slot 8 to. `faint` is the shade below `muted`: a thought and the row naming it both take
|
|
476
|
+
it, and only the row is italic, so the signpost does not compete with the text
|
|
477
|
+
it introduces. `arg` is the pale blue a card gives the argument it was called with,
|
|
254
478
|
so `tool.args` is restyled on its own and stays distinct from the tool's own
|
|
255
479
|
label and from its output. `user` is the mint a submitted prompt takes, so a
|
|
256
480
|
reader's own turns stand apart from the reply without reading either.
|
|
@@ -260,22 +484,57 @@ outrank everything in this section. A token or palette name the surface does not
|
|
|
260
484
|
have is refused with the offending name, and the surface prints the refusal as a
|
|
261
485
|
notice when the document loads, so a typo cannot quietly paint nothing.
|
|
262
486
|
|
|
487
|
+
### Terminal text
|
|
488
|
+
|
|
489
|
+
A tool result, a file's contents, and a model's answer are text a terminal may
|
|
490
|
+
read as commands, so the surface reads them first. A whitelisted subset of the
|
|
491
|
+
SGR family (`1`, `2`, `3`, `4`, `7`, `9`, `21`/`22`, `23`, `24`, `27`, `29`, the
|
|
492
|
+
30–37/90–97 and 40–47/100–107 slots, `38`/`48` indexed and RGB, `39`/`49`, and
|
|
493
|
+
`0`) is re-emitted at the session's own colour budget: 24-bit where the terminal
|
|
494
|
+
advertises it, 256 or 16 colours otherwise, and nothing at all with `--no-color`
|
|
495
|
+
or `NO_COLOR`. A tab advances to the next eight-column stop measured from the
|
|
496
|
+
column the text starts at, and a carriage return repaints its row in place, so
|
|
497
|
+
the last state of a progress bar is the only one drawn.
|
|
498
|
+
|
|
499
|
+
Everything else a terminal would act on — cursor movement, screen clearing,
|
|
500
|
+
private modes, window titles, clipboard writes, hyperlinks — is consumed rather
|
|
501
|
+
than shown, and a control byte that is not a sequence is spelled out (`\x07`)
|
|
502
|
+
rather than silently dropped. A full reset inside tool output restores the colour
|
|
503
|
+
of the element holding the text, not the terminal default, and the surface never
|
|
504
|
+
writes a reset of its own inside a row; a carriage return cannot repaint past the
|
|
505
|
+
column the text started at, so indented output cannot reach the frame around it.
|
|
506
|
+
|
|
507
|
+
Text the surface draws itself — a ghost suggestion, a completion row, a queued
|
|
508
|
+
prompt, an export — is drawn without colour, because the surface is already
|
|
509
|
+
painting it and a second style would fight the first.
|
|
510
|
+
|
|
263
511
|
### Keys
|
|
264
512
|
|
|
265
513
|
Every press the surface answers is an action with an id and a shipped key.
|
|
266
|
-
`/keys
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
514
|
+
`/keys`, or `Ctrl+X` then `?`, opens the whole map in a box over the
|
|
515
|
+
transcript: one row per action with the keys in force, one row for every key your
|
|
516
|
+
map took from the library, and a filter over all of it — `gate` for a layer,
|
|
517
|
+
`ctrl+o` for a key, `stash` for what a row does. The heading counts the actions
|
|
518
|
+
shown and how many of them you wrote, the box gives up rows rather than grow past
|
|
519
|
+
four fifths of the screen, and `enter` or `esc` closes it with the transcript
|
|
520
|
+
exactly as it was. `/keys prompt`, `surface`, `chord`, `gate`, `question`,
|
|
521
|
+
`picker`, and `library` open it already narrowed to one part of the surface, and
|
|
522
|
+
a name that is none of them is refused with the names. The table above is the
|
|
523
|
+
complete account of the shipped keys.
|
|
271
524
|
|
|
272
525
|
An entry is one key or a list of them. A key is a modifier chord
|
|
273
526
|
(`ctrl`/`alt`/`shift` joined by `+`, written in that order), a named key
|
|
274
527
|
(`enter`, `escape`, `tab`, `space`, `backspace`, `delete`, `home`, `end`,
|
|
275
528
|
`pageUp`, `pageDown`, the arrows, `f1`–`f12`), or a bare character where the
|
|
276
|
-
layer reads one: `y` and `n` for an approval, or a chord's second key
|
|
277
|
-
beginning `tui.` are pi-tui's own actions,
|
|
278
|
-
transcript move where you tell them to.
|
|
529
|
+
layer reads one: `y` and `n` for an approval, or a chord's second key (`?` for
|
|
530
|
+
the key map, `m` for the model). Ids beginning `tui.` are pi-tui's own actions,
|
|
531
|
+
so the editor, the search, and the transcript move where you tell them to.
|
|
532
|
+
|
|
533
|
+
Ctrl+P and Ctrl+N ship as alternatives to ↑ and ↓ wherever a list moves — a
|
|
534
|
+
picker, a question's options, and the editor's completion menu. They are
|
|
535
|
+
ordinary rows: `picker.up`, `picker.down`, `question.up`, `question.down`,
|
|
536
|
+
and the library's `tui.select.up`/`tui.select.down` take other keys, or more
|
|
537
|
+
of them, like any other row.
|
|
279
538
|
|
|
280
539
|
Refused, with the reason in a notice and the shipped map left in force: an
|
|
281
540
|
action the surface does not have, a key no terminal reports, `ctrl+q` (the
|
|
@@ -295,9 +554,12 @@ control byte carries both `ctrl+-` and `ctrl+_`, and an escape with a letter
|
|
|
295
554
|
reaches `alt+up` as readily as `alt+p`.
|
|
296
555
|
|
|
297
556
|
A key the surface or a chord answers is a key the library never sees: that is
|
|
298
|
-
how `ctrl+y` shows nested calls instead of yanking a line in the editor
|
|
299
|
-
|
|
300
|
-
|
|
557
|
+
how `ctrl+y` shows nested calls instead of yanking a line in the editor, how
|
|
558
|
+
`ctrl+c` closes the transcript search the library owns, and how `ctrl+d` leaves
|
|
559
|
+
rather than deleting forward while the bar holds nothing. The key map carries a
|
|
560
|
+
row for every shadow your map introduces, naming the action that wins and the
|
|
561
|
+
library row that loses, and moving the surface key hands the library its own key
|
|
562
|
+
back.
|
|
301
563
|
|
|
302
564
|
Left alone, because they are typing rather than commands: the keys a question's
|
|
303
565
|
filter narrows with and the ones that leave its free-text row, the digits and
|
|
@@ -339,19 +601,63 @@ The package is a Cordis plugin bundle that stacks over `@deepseek-ai/dsh-base`:
|
|
|
339
601
|
- `@deepseek-ai/dsh-agent-presets` is the roster of modes, holding the id a session starts in when nobody names one.
|
|
340
602
|
- `@deepseek-ai/dsh-code-runtime-worker-thread` and `@deepseek-ai/dsh-cordis-host-runner` are the host machinery PTC mode and creator mode need; only the Web bundle shipped them, so a terminal profile has to mount them to offer those modes at all.
|
|
341
603
|
- `@sagmans/dsh-tui` owns the terminal: it creates or resumes one agent through `ctx.agents`, folds `session/event` into transcript rows and work state, renders them with `@earendil-works/pi-tui`, and releases the terminal on exit, on a boot failure, and on a signal.
|
|
604
|
+
- `@sagmans/dsh-tui/todo-guard` is the one advisory row this bundle adds to the agent plane: it watches the harness's own `todos` and `plan` projections and rides the next tool result with a reminder when a plan ages. See [Todo discipline](#todo-discipline).
|
|
342
605
|
|
|
343
606
|
A question whose id ends in `:secret` declares its typed answer a credential: the bar hides everything but its first and last four characters, and the free-text row a question with options offers is labelled `API KEY`. Wording is not a declaration, because hiding every question that mentions a key would hide answers their authors meant to be read.
|
|
344
607
|
|
|
345
608
|
The fold is durable-only: the live stream decorates the row that is still being written, and everything else — cards, reasoning, work state, compaction markers — comes from the log, so a resumed session renders what the live one did. Subagent start and finish are the exception: they arrive as service events, and the transcript shows them as decoration because the durable record of a delegation is the tool call that asked for it.
|
|
346
609
|
|
|
347
|
-
Tool cards are folded by default: a card draws
|
|
610
|
+
Tool cards are folded by default: a card draws one header row — the tool, its argument clipped to the configured budget, and the facts the result measured — so a long read, diff, or search cannot bury the conversation. A shell card's row also carries the exit status and the count of output rows waiting behind the fold, because its output is the answer the reader asked for and a fold that left no trace of it would read as a call that produced nothing. Clicking a card opens or folds that one message; `Ctrl+O` opens or folds every card at once, and `tools:` in the [settings](#settings) decides how each tool starts and whether a fold hides its rows or keeps a `tail` of them.
|
|
348
611
|
|
|
349
|
-
A PTC card is the one card with children: every call the `run_code` program dispatched hangs off the card that made it, and each draws under the header as the tool's own name and argument,
|
|
612
|
+
A PTC card is the one card with children: every call the `run_code` program dispatched hangs off the card that made it, and each draws under the header as the tool's own name and argument, on one row cut at the screen edge whether the card itself is open or folded — a program's work must stay legible without opening its card. Clicking one of those rows opens that call's argument in full and leaves its neighbours and the card as they were; a shell call also brings back the rows it printed, because the program's return value is all the card itself keeps. `Ctrl+Y` hides or shows them all, and `subcalls: collapsed` starts every session with them hidden; see [Settings](#settings).
|
|
350
613
|
|
|
351
614
|
A card's header names the tool, then the argument the call was made with — a path or a command — in the `tool.args` colour, then the facts the result measured: a read reports its line range, line count, and token size; a file change that carried no prior content to compare against reports its lines and tokens; one that did reports added, changed, and removed lines as `+n ~n -n` in green, yellow, and red. Each stat is its own token, so any of them can be recoloured or hidden independently.
|
|
352
615
|
|
|
353
616
|
The bundle also takes the base's global agent rows out of the composition, twenty-three of them. Every one is a row the shipped modes supply per session instead, so leaving it mounted registers the same tool names in two layers and doubles each prompt section it owns. What stays mounted is the host: sessions, storage, models, permissions, jobs, and the command registry.
|
|
354
617
|
|
|
618
|
+
### Todo discipline
|
|
619
|
+
|
|
620
|
+
The todo tool and its list belong to the agent; this bundle owns the surface and one advisory guard. `@sagmans/dsh-tui/todo-guard` mounts host-plane, reads the harness's own `todos` and `plan` projections, and — when a non-empty list has gone a threshold of model steps without a `todo_write`, or a long turn has produced no list at all — rides the next tool result with a model-visible reminder. It never vetoes a call, never steers a stopped turn, and never adds a prompt section, so its request prefix stays stable across deployments. A reminder costs the loop one extra model step to consume; the per-turn cap bounds that. It stays silent in plan mode, in a mode whose catalog has no `todo_write`, and when the projections are absent.
|
|
621
|
+
|
|
622
|
+
| Option | Default | Effect |
|
|
623
|
+
|---|---|---|
|
|
624
|
+
| `staleSteps` | `6` | model steps a non-empty open list may age before the guard speaks |
|
|
625
|
+
| `missingListSteps` | `12` | steps in a turn before the guard suggests a first list |
|
|
626
|
+
| `maxRemindersPerTurn` | `3` | hard cap on reminders, and on the extra steps they cost, per turn |
|
|
627
|
+
| `previewItems` | `5` | open items quoted in a reminder; the rest become a count |
|
|
628
|
+
|
|
629
|
+
Override them from the home-level patch, which outranks the profile's own layers. An id-targeted patch replaces the whole config, so restate every field you keep:
|
|
630
|
+
|
|
631
|
+
```yaml
|
|
632
|
+
# $DSH_HOME/cordis.patch.yml
|
|
633
|
+
- id: tui-todo-guard
|
|
634
|
+
config:
|
|
635
|
+
staleSteps: 4
|
|
636
|
+
missingListSteps: 12
|
|
637
|
+
maxRemindersPerTurn: 3
|
|
638
|
+
previewItems: 5
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
The list the dock and `/todo` draw follows the same lifetime every other surface shows: it is cleared when the next turn opens, because a fresh task must not inherit the previous turn's checklist.
|
|
642
|
+
|
|
643
|
+
## Herdr
|
|
644
|
+
|
|
645
|
+
[Herdr](https://github.com/herdrdev/herdr) is a terminal multiplexer for coding agents. When it starts this surface in one of its panes it exports `HERDR_ENV=1`, `HERDR_PANE_ID`, and `HERDR_SOCKET_PATH`, and the pane reports what it is doing over that socket — as the agent `dsh`, under the source `custom:dsh-tui`. Away from Herdr (an ordinary terminal, SSH, tmux) the reporter is inert: no socket is opened, and nothing reaches the screen the reader owns.
|
|
646
|
+
|
|
647
|
+
| This surface | Herdr |
|
|
648
|
+
|---|---|
|
|
649
|
+
| the screen is taken, before any session opens | `idle`, claiming the pane's agent row |
|
|
650
|
+
| `turn/start` | `working` |
|
|
651
|
+
| an approval, a question, or a picker takes the keyboard | `blocked`, with that card's title sent along |
|
|
652
|
+
| the decision settles | `working` if a turn is open, otherwise `idle` |
|
|
653
|
+
| `turn/end` | `idle` |
|
|
654
|
+
| a session opens, resumes, forks, or is switched to | its id and reason, plus the `dsh_session` / `dsh_cwd` pane tokens |
|
|
655
|
+
| exit, signal, or boot failure | `herdr pane release-agent`, so no row is left waiting on a process that is gone |
|
|
656
|
+
|
|
657
|
+
A wait outranks a running turn: a turn waiting on a human is not making progress, and the wait is the only thing worth acting on from a wall of panes. Reports are sequenced per source, so a delivery that arrives late cannot undo the state the surface already moved past, and a state Herdr is already showing is not sent again. A report is only counted as made when Herdr acknowledges it: one that failed is tried again — soon after, then with a growing wait — and a session identity Herdr never confirmed travels with the next report of any kind. The retry cannot wait for another state change, because the pane may have nothing left to say: a turn that ended while the socket was down produces no further event. States still waiting to be sent are collapsed into the newest one, so a socket that was down for a minute is told where the pane is rather than where it has been. The release carries the next number in that same sequence for the same reason: Herdr reads one that cannot beat the pane's last report as stale, and a stale release leaves the row waiting on a process that is gone. It also stops reporting first — a claim landing after the release would take the row back for a process that is leaving — and the report already on the wire is waited for before the row goes back, because Herdr ignores the release of a pane nothing has claimed yet and that late report would then claim it. What is still waiting behind it is dropped rather than sent.
|
|
658
|
+
|
|
659
|
+
Herdr persists a session reference only for its own built-in integrations, so this pane's session identity travels as metadata tokens instead: a script or a companion plugin reads them back with `herdr pane get <id>` and resumes that exact conversation with `dsh --profile tui --resume=<id>`. Herdr holds a token value up to 80 characters and shortens anything longer, so a session id or directory that cannot be sent whole has its token cleared instead: a shortened path would read as a different directory, and a pane must not claim one. What Herdr cannot do is identify the process itself — its detection table and its screen rules both name built-in agents only — so a pane that has not reported yet reads as an ordinary pane, and is not yet a target: `herdr agent wait` on it fails with `agent_not_found` until the first report lands. Herdr 0.9.1 also keeps the title that accompanies a `blocked` state without showing it anywhere, so a reader sees the state and reads the card on screen.
|
|
660
|
+
|
|
355
661
|
## Development
|
|
356
662
|
|
|
357
663
|
```sh
|
|
@@ -393,26 +699,48 @@ The automated checks drive a real PTY, but they run on this machine's terminal.
|
|
|
393
699
|
| `NO_COLOR=1 dsh --profile tui` | no styling anywhere, layout unchanged |
|
|
394
700
|
| `dsh --profile tui --no-bell` | a turn that runs for minutes still ends silently |
|
|
395
701
|
| `dsh --profile tui --preset ptc`, then a turn | the status line names `ptc`, and the agent reaches its tools through one TypeScript program rather than one shell call at a time |
|
|
396
|
-
| a PTC turn | one
|
|
397
|
-
| that turn
|
|
398
|
-
|
|
|
399
|
-
|
|
|
702
|
+
| a PTC turn | one row per dispatched call draws two spaces indented under the `run_code` header without opening it |
|
|
703
|
+
| that turn, then a click on one of those rows | that call unfolds in full at the same indent: its argument, then what the tool itself drew — an edit's diff in the diff colours, a read's lines, a shell's output — and its neighbours and the card stay as they were |
|
|
704
|
+
| a PTC turn in which a dispatched call failed, then a click on that red row | the row opens to the reason the call reported, and a click on those rows folds it back |
|
|
705
|
+
| that same turn, then `ctrl+y` | the rows fold away; `ctrl+y` again draws them back |
|
|
706
|
+
| `dsh-tui: { subcalls: collapsed }` in `$DSH_HOME/settings.yaml`, then a PTC turn | the card arrives alone, and editing the document to `inline` draws the one-line calls in a running session |
|
|
707
|
+
| a thought, folded | a click on the row opens the thought under its summary; a click on the body folds it back, and the other thoughts keep their own state |
|
|
708
|
+
| a bash card, then a click on it | the row opens to its command, its retained output, and its exit status; a click folds it back to one row |
|
|
709
|
+
| a call that failed, then a click on its card | the card opens to the reason it failed — the same words the model was shown — and a click folds it back |
|
|
710
|
+
| `dsh-tui: { tools: { bash: { output: tail, tail: 5 } } }`, then a bash run | the folded row keeps the last five output rows and counts the rest |
|
|
711
|
+
| `dsh-tui: { tools: { read: { collapsed: false } } }`, then a read | the card starts open; folded on a narrow terminal, the path gives up room first and the row stops short of the edge |
|
|
712
|
+
| `dsh-tui: { tools: { nope: { collapsed: false } } }` | accepted and inert, because the surface cannot know which tools a profile mounts |
|
|
713
|
+
| `dsh-tui: { tools: { bash: { collapse: true } } }` | refused with a notice naming `bash.collapse` |
|
|
400
714
|
| a reply carrying a mermaid fence | it draws as box art at the transcript width, with the prose around it untouched |
|
|
401
715
|
| that reply in a terminal narrower than the drawing | the fence stays source, and widening the window draws it without a new turn |
|
|
402
716
|
| `dsh-tui: { mermaid: off }` in `$DSH_HOME/settings.yaml`, then a mermaid reply | the fence stays source; editing the value to `streaming` draws a settled reply without a restart |
|
|
717
|
+
| a reply carrying a `` ```diff `` fence | the file and hunk headers recede, `+` rows draw green and `-` rows red, and the characters that changed in a paired row sit on a darker band |
|
|
718
|
+
| `dsh-tui: { tokens: { markdown.diff.addedEmphasis: { hidden: true } } }` in `$DSH_HOME/settings.yaml`, then that reply | the changed run keeps its row's colour instead of the band, and the row's text is unchanged |
|
|
719
|
+
| `NO_COLOR=1 dsh --profile tui`, then that reply | the fence draws as plain text with no escape sequences, and a re-run without it colours the fence again |
|
|
720
|
+
| type the start of a prompt already recorded | the rest of the newest match follows the cursor in a faint shade; `ctrl+e` takes it whole, the word-right key takes one word, and both keys do their old job when nothing is offered |
|
|
721
|
+
| `ctrl+r`, then a fragment | reverse search opens seeded with the bar's draft; `enter` puts a prompt back, `esc` keeps the draft |
|
|
722
|
+
| `dsh-tui: { history: { ghost: false } }`, then type a known prefix | no suggestion is drawn, and `ctrl+r` still searches |
|
|
723
|
+
| `/history clear`, then `/history` | the notice reports the count forgotten, and the second reports `1 prompt recorded` — the check line is itself recorded |
|
|
403
724
|
| `/model` on a configured profile | the picker lists only the configured providers' advertised models, heads itself with the route in force, and typing filters it while later rows stream in; `esc` or Ctrl+C leaves without changing the route, and `enter` chains into the route's reasoning efforts |
|
|
404
725
|
| `/preset` on a fresh session | the picker lists four modes, marks the current one, and the switch survives a resume |
|
|
405
726
|
| `/preset minimal` after a turn | refused, naming the reason; the session keeps the mode it composed with |
|
|
406
727
|
| `--resume --preset <mode>` and then picking a session that runs another mode | the list stays open and says why that row cannot be taken; `esc` leaves the picker |
|
|
407
728
|
| `dsh --profile tui --preset nope` | exits non-zero naming the modes that do exist, before the alternate screen appears |
|
|
408
729
|
| arrow keys in a picker, or on a question's options, in a terminal that reports key events (Kitty, WezTerm, Ghostty, iTerm2) | one press moves one row, and holding a key still repeats; a terminal that sends only the legacy sequence behaves the same |
|
|
730
|
+
| Ctrl+N / Ctrl+P in a picker, on a question's options, on its `0. other` row, or in the completion menu | the cursor moves down and up exactly as the arrows do |
|
|
409
731
|
| resize the window mid-turn | the transcript rewraps; the dock, editor, and status row stay put |
|
|
410
732
|
| a 40-column terminal | transcript and card rows end in `…` instead of wrapping into the next line |
|
|
411
733
|
| a question with a long option at 40 columns | the option wraps onto rows indented under its label, and `0. other — type your own answer` sits under the list |
|
|
412
734
|
| press `0` on a question, type an answer, press Enter | the editor under row `0` shows the text as it is edited, and the model receives it as that question's answer |
|
|
413
735
|
| type a prompt without sending it, then answer a question | the prompt bar steps aside while the question is open and holds the same prompt again afterwards |
|
|
414
736
|
| `echo hi \| dsh --profile tui` | refuses with a non-zero exit and a message naming the TTY requirement |
|
|
415
|
-
| `/
|
|
737
|
+
| `/stash`, `/stash-pop` in one terminal | the footer shows `stash 1` after the stash and the draft returns to the editor after the pop |
|
|
738
|
+
| a second `dsh --profile tui` in the same directory | `/stash-list` says `no stashed drafts`, and the first terminal's bank is untouched |
|
|
739
|
+
| `/quit`, then `dsh --profile tui --resume=<id>` | `/stash-list` still shows the draft that session parked |
|
|
740
|
+
| hand-edit `$DSH_HOME/tui-stash/<key>.json` into invalid JSON, then `/stash-list` | the surface reports the quarantine path, starts empty, and leaves the moved file readable |
|
|
741
|
+
| `ctrl+x` then `e` with `$VISUAL` set to your editor | the alternate screen gives way to that editor with the draft in it; saving returns to the same frame with what was saved in the bar, and nothing is submitted |
|
|
742
|
+
| the same with `$VISUAL` and `$EDITOR` unset | the draft stays in the bar, and a notice names the variables to set |
|
|
743
|
+
| `/quit`, Ctrl+D with an empty bar, `kill -TERM <pid>` | the shell returns with cursor, echo, mouse, and title restored |
|
|
416
744
|
|
|
417
745
|
## Releasing
|
|
418
746
|
|
|
@@ -430,15 +758,41 @@ The workflow stores no npm token: the registry trusts `release.yml` on the `npm-
|
|
|
430
758
|
- `/model` changes the route and reasoning effort for the running session only. Catalog membership is advisory — an adapter may accept an id it does not advertise, while an explicit effort is checked against the route's own levels before it is applied. The picker offers the routes this deployment configured, not the ones it can prove credentialed: a provider whose key or sign-in is still missing appears like any other, and its first request names the missing credential.
|
|
431
759
|
- Scrolling is the mouse wheel, or the terminal's own scrollback keys where it offers them.
|
|
432
760
|
- A turn that ran longer than ten seconds rings the terminal bell when it ends, because the reader may have walked away; `--no-bell` turns that off.
|
|
433
|
-
- The dock shows the goal, plan mode, the todo items still to do, and any background job or delegation still running; a settled item leaves rather than turns into a completed row. The transcript marks where older history was compacted away. `/plan` toggles plan mode; `/plan <message>` also steers that message, which is the base command's own behaviour.
|
|
761
|
+
- The dock shows the goal, plan mode, the todo items still to do, and any background job or delegation still running; a settled item leaves rather than turns into a completed row, and the list clears when the next turn opens so a fresh task never inherits the previous one's checklist. The transcript marks where older history was compacted away. `/plan` toggles plan mode; `/plan <message>` also steers that message, which is the base command's own behaviour.
|
|
434
762
|
- Background jobs and subagent runs are live process state, not durable events: they disappear when the run ends, and a resumed session starts with an empty board and roster.
|
|
435
763
|
- A card reads its tool's own render intent through the agent whose session is on screen, so a stored session with no live agent — one this process is not running, or a child that has already finished — folds to the generic card instead of the tool's own.
|
|
436
764
|
- Reading a child's conversation does not move the terminal: commands, approvals, and the status line stay with the session you launched, and the transcript is the only thing that switches. The status line carries the way back, read from the map in force, so a remap shows up without reopening the view.
|
|
437
765
|
- Delete is unimplemented: the session store exposes no delete, and the surface does not reach around that seam into its files. `/fork` covers the case that needs it — it branches into a new session and leaves the original alone.
|
|
766
|
+
- The prompt stash holds text only. It does not read Pi's `pi-stash` data, does not migrate an older key format (the directory-scoped banks written by 0.4.0 are left where they are and are never read), and keeps no pasted images: a draft larger than 1 MiB, or a bank larger than 16 MiB, is refused rather than stored — the cap is measured on the bytes the file will hold, escapes included, before anything is written, so a refusal cannot leave a bank that saves and then refuses to load. A corrupt bank is quarantined and reported, never repaired in place; a bank from a newer format is refused where it lies, so an older build cannot swallow a newer build's drafts.
|
|
438
767
|
- Approvals and questions render inline and take the keyboard; a question batch is answered in order, and a question that lists options can always be answered with free text on row `0`.
|
|
768
|
+
- Inside Herdr the pane reports its own state, and that report is the only thing that makes it an agent there: Herdr cannot start, resume, or prompt this surface, so launching and resuming stay with `dsh` itself (or a Herdr plugin that runs it).
|
|
439
769
|
- Styling is per element and overridable; see [Settings](#settings). Shipped defaults are emitted as 24-bit colour where the terminal advertises it and degraded to 256 or 16 colours otherwise, so a light or dark terminal still follows its own palette where it has one.
|
|
440
|
-
- Tool text, model text, and file content are
|
|
441
|
-
- Mermaid fences draw in assistant replies
|
|
770
|
+
- Tool text, model text, and file content are drawn the way the terminal that produced them would have drawn them — see [Terminal text](#terminal-text) — so a tab lands where its writer saw it and a colour is a colour. A hostile result still cannot reach the terminal: everything a terminal would act on is consumed before the row is measured. A stashed draft and a stored prompt are stripped of control and bidi characters instead, because they are restored into a live editor rather than drawn as text.
|
|
771
|
+
- Mermaid fences draw in assistant replies and submitted prompts, and only at the top level of one: a fence nested in a list, quoted inside another fence, or carried by a thought or a tool card stays source. A thought never draws one, because a diagram there would carry the answer's weight. A fenced diff is the exception: it is the change itself rather than a drawing of it, so replies, prompts, and thoughts all draw it in the diff elements. Author `:::class` styling and diagram links are ignored — the renderer reports what each run is, and the theme decides how it looks.
|
|
772
|
+
- Prompt history is global to this machine, not per project: `$DSH_HOME/prompt-history.json` holds every submitted line, deduplicated exactly, and a file this build cannot parse is left untouched with writes refused so a newer format is never overwritten. Every write re-reads the file under a lock shared by sessions, so a second session's prompts are folded in rather than overwritten, and a lock whose holder stopped is reclaimed or reported instead of guessed at; control characters are spelled out before a prompt is stored. A multiline suggestion draws its first line with `↵` marking the fold. `history.ghost: false` keeps reverse search without the suggestion, `history.enabled: false` stops recording and offering it, and `NO_COLOR`/`--no-color` suppresses the ghost because text the reader cannot see but could still accept is worse than none.
|
|
773
|
+
- The editor handoff gives the whole terminal to `$VISUAL` (or `$EDITOR`) and waits for it: while the child owns the screen this surface draws nothing: a title from a turn in flight is written again when the screen comes back, a bell that falls in the gap is dropped rather than rung late, and a second `ctrl+x` then `e` is ignored until the first editor leaves. A host that unloads the surface during the handoff gives the terminal back while the child is still running, because only the child's own exit can end the wait. What the editor saved is read back only up to 1 MiB; a larger draft is left on disk with its path in the notice rather than loaded into the bar.
|
|
774
|
+
|
|
775
|
+
## Related plugins
|
|
776
|
+
|
|
777
|
+
Two companion bundles stack onto the same profile and complement this surface.
|
|
778
|
+
Neither is a dependency of this package: a profile works without them, and each
|
|
779
|
+
publishes to npm under the same tag-driven, provenance-carrying release
|
|
780
|
+
discipline as this one.
|
|
781
|
+
|
|
782
|
+
| Plugin | What it adds |
|
|
783
|
+
|---|---|
|
|
784
|
+
| [`@sagmans/dsh-auto-compact`](https://github.com/sagmans/dsh-auto-compact) | An absolute token trigger for automatic compaction: the conversation condenses at `min(thresholdTokens, contextWindow × thresholdRatio)` instead of the window ratio alone, so a large-window model pays a fixed price, with per-route overrides. Its patch swaps the shipped `compaction-basic` backend — the same row this bundle already takes out of the global composition — so the profile still keeps exactly one compaction service, and this surface needs no setting for it. |
|
|
785
|
+
| [`@sagmans/dsh-provider-extra`](https://github.com/sagmans/dsh-provider-extra) | Extra provider routes: OpenCode Go, which sends the live conversation id in `x-opencode-session` for routing and prompt caching, and OpenAI Codex over a ChatGPT subscription's OAuth flow with the harness credential store. Its routes join the `/model` picker like every configured provider. |
|
|
786
|
+
|
|
787
|
+
```sh
|
|
788
|
+
dsh plugin --profile tui add @sagmans/dsh-auto-compact
|
|
789
|
+
dsh plugin --profile tui add @sagmans/dsh-provider-extra
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
The base stack this bundle itself mounts — `@deepseek-ai/dsh-base`, the agent
|
|
793
|
+
presets, and the host machinery PTC and creator modes need — is named in
|
|
794
|
+
[How it works](#how-it-works); the multiplexer this surface reports to is
|
|
795
|
+
[Herdr](#herdr).
|
|
442
796
|
|
|
443
797
|
## License
|
|
444
798
|
|