@xmoon76/dsh-pi-tui 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +421 -51
- package/README.zh-CN.md +417 -0
- package/dist/builtins.mjs +1 -1
- package/dist/extensions.mjs +1 -1
- package/dist/index.d.mts +16 -1
- package/dist/index.mjs +2 -2
- package/dist/{src-AgdnRnGA.mjs → src-BsSlpFFF.mjs} +917 -381
- package/package.json +20 -10
- package/scripts/naming-gate.mjs +33 -19
- package/scripts/tarball-smoke.mjs +3 -1
package/README.md
CHANGED
|
@@ -1,53 +1,421 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-pi-tui
|
|
2
|
+
|
|
3
|
+
English | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Release history: [CHANGELOG.md](CHANGELOG.md) · [简体中文更新日志](CHANGELOG.zh-CN.md)
|
|
2
6
|
|
|
3
7
|
A third-party TUI mode for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`), built on a vendored fork of [pi-tui](https://github.com/MoonshotAI/kimi-code/tree/main/packages/pi-tui).
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
Run `dsh --profile pi-tui` for a terminal UI instead of the browser GUI (`dsh --profile web`) or one-shot mode (`dsh --profile headless`).
|
|
10
|
+
|
|
11
|
+
> **Status: working.** The TUI covers the main session loop — input → session events,
|
|
12
|
+
> approvals, commands, session switching and full-text search — plus presets, skills,
|
|
13
|
+
> model/settings menus, and slash commands. Rendering and input routing are verified
|
|
14
|
+
> by headless tests (`@xterm/headless`) with no TTY or model connection needed.
|
|
15
|
+
|
|
16
|
+
## Screenshot
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Layout
|
|
21
|
+
|
|
22
|
+
The full repository layout lives in [AGENTS.md](AGENTS.md) (the contributor
|
|
23
|
+
operating manual). In one line: the **repository root is the published
|
|
24
|
+
`@xmoon76/dsh-pi-tui` bundle** (the only published package — its manifest
|
|
25
|
+
declares `dsh.bundle.patch` and the `exports` point at the built `dist/`),
|
|
26
|
+
and `packages/pi-tui/` is the vendored `@moonshot-ai/pi-tui` fork (rescoped
|
|
27
|
+
to `@xmoon76/pi-tui`, private, never published — its divergence ledger lives
|
|
28
|
+
in `packages/pi-tui/AGENTS.md`), bundled into the root package's build
|
|
29
|
+
output.
|
|
30
|
+
|
|
31
|
+
## Prerequisites
|
|
32
|
+
|
|
33
|
+
- A DeepSeek Harness installation with profiles support (`dsh` on your `PATH`).
|
|
34
|
+
- Node >= 22.19 (`^22.19.0 || >=24`, same range as dsh). Running from source
|
|
35
|
+
needs Node with native TypeScript support (>= 23.6) or the tsx ESM hook
|
|
36
|
+
(`node --import tsx/esm`, how dsh's own source launch works).
|
|
37
|
+
- [pnpm](https://pnpm.io) only when installing from source.
|
|
6
38
|
|
|
7
39
|
## Install
|
|
8
40
|
|
|
9
|
-
|
|
41
|
+
`dsh plugin` runs pnpm inside the target profile's directory, so the usual
|
|
42
|
+
pnpm verbs (`add`, `remove`, `update`, `list`) all work.
|
|
43
|
+
|
|
44
|
+
### Option A — from the npm registry (recommended)
|
|
45
|
+
|
|
46
|
+
The published package is self-contained: the vendored pi-tui fork is bundled
|
|
47
|
+
into its build output, so `@xmoon76/dsh-pi-tui` is the only package you install
|
|
48
|
+
(`@xmoon76/pi-tui` stays private in this repo, like kimi-code keeps
|
|
49
|
+
`@moonshot-ai/pi-tui` private):
|
|
10
50
|
|
|
11
51
|
```sh
|
|
52
|
+
# install the bundle into the pi-tui profile (creates the profile if needed)
|
|
12
53
|
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
|
|
54
|
+
|
|
55
|
+
# run it
|
|
13
56
|
dsh --profile pi-tui
|
|
14
57
|
```
|
|
15
58
|
|
|
16
|
-
|
|
59
|
+
Any dependency whose manifest declares `dsh.bundle` joins the profile's layer
|
|
60
|
+
stack automatically — no manual `cordis.patch.yml` wiring.
|
|
17
61
|
|
|
18
|
-
|
|
62
|
+
### Option B — from source
|
|
63
|
+
|
|
64
|
+
Build artifacts are not committed (`dist/` for both packages is gitignored and
|
|
65
|
+
the package `exports` point at the built files), so build before installing
|
|
66
|
+
from a clone:
|
|
19
67
|
|
|
20
68
|
```sh
|
|
21
|
-
|
|
22
|
-
|
|
69
|
+
git clone https://github.com/XMoon/dsh-pi-tui
|
|
70
|
+
cd dsh-pi-tui
|
|
71
|
+
pnpm install
|
|
72
|
+
pnpm build # pi-tui tsdown (packages/pi-tui/dist/) + root tsdown (dist/, bundles pi-tui)
|
|
73
|
+
|
|
74
|
+
# file: — the bundle is copied into the profile at add time; rebuild + re-add
|
|
75
|
+
# to refresh (see "Update / uninstall" below)
|
|
76
|
+
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
|
|
77
|
+
|
|
78
|
+
# link: — a live symlink instead; `pnpm build` output is picked up directly
|
|
79
|
+
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@link:$PWD
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Verify the install
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
dsh plugin --profile pi-tui -- list # @xmoon76/dsh-pi-tui present
|
|
86
|
+
dsh --profile pi-tui # TUI starts instead of the web GUI
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Update / uninstall
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
# registry installs:
|
|
93
|
+
dsh plugin --profile pi-tui -- update @xmoon76/dsh-pi-tui
|
|
94
|
+
# file: source installs copy at add time — rebuild + re-add to refresh
|
|
95
|
+
# (link: installs track the repo live and need only `pnpm build`):
|
|
96
|
+
pnpm build && dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
|
|
97
|
+
|
|
23
98
|
dsh plugin --profile pi-tui -- remove @xmoon76/dsh-pi-tui
|
|
24
99
|
```
|
|
25
100
|
|
|
26
|
-
##
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
pnpm install
|
|
105
|
+
pnpm build # pi-tui tsdown (packages/pi-tui/dist/) + root tsdown (dist/, bundles pi-tui)
|
|
106
|
+
pnpm test # pi-tui's own suite (node --test) + dsh-pi-tui headless tests
|
|
107
|
+
pnpm typecheck
|
|
108
|
+
node --expose-gc scripts/bench.mts # performance baseline (optional)
|
|
109
|
+
```
|
|
27
110
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- `/title [title]` — show or set the current session's title
|
|
31
|
-
- `/preset`, `/model`, `/settings`, `/export`, `/fork`, `/subagents` — see the command autocomplete (`/` + Tab)
|
|
111
|
+
Tests drive the UI through `@xterm/headless` (see `test/virtual-terminal.ts`),
|
|
112
|
+
so rendering and input routing are verified without a TTY or a model connection.
|
|
32
113
|
|
|
33
|
-
|
|
114
|
+
### Development history (dogfooding)
|
|
34
115
|
|
|
35
|
-
|
|
116
|
+
This project started development on the browser surface (`dsh --profile web`) and
|
|
117
|
+
switched to building itself with itself: since August 15 2026, all fixes and
|
|
118
|
+
features are developed inside this TUI, the same way this README and the
|
|
119
|
+
codebase are maintained. The dev loop runs on a dedicated `pi-tui-dev` profile
|
|
120
|
+
installed with Option B's `link:` specifier
|
|
121
|
+
(`dsh plugin --profile pi-tui-dev -- add @xmoon76/dsh-pi-tui@link:$PWD`)
|
|
122
|
+
— a live symlink, so `pnpm build` is picked up without re-adding — while the
|
|
123
|
+
`pi-tui` profile stays on the published registry package for real use.
|
|
36
124
|
|
|
37
|
-
|
|
125
|
+
## Extensions (early, stabilizing)
|
|
38
126
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
127
|
+
Since `0.2.0` the bundle ships a small, versioned extension surface so a
|
|
128
|
+
third-party Cordis plugin can contribute chrome without touching the TUI
|
|
129
|
+
internals. It is **early and stabilizing**: the capabilities below are the
|
|
130
|
+
current set; the API version (`1`) is bumped only on breaking changes, and
|
|
131
|
+
plugins must **feature-detect** capabilities instead of parsing the package
|
|
132
|
+
version.
|
|
133
|
+
|
|
134
|
+
All extension plugins remain standard DeepSeek Harness / Cordis plugins using
|
|
135
|
+
`name`, `inject`, and `apply(ctx)` against the single `piTuiExtensions`
|
|
136
|
+
service; the tiers are capability facades over that one service, not separate
|
|
137
|
+
plugin systems or runtimes.
|
|
138
|
+
|
|
139
|
+
The extension surface ships three tiers: a plugin imports ONLY the
|
|
140
|
+
public entry — never the stable entry's internals (`TuiApp`,
|
|
141
|
+
`TuiMainScreen`, `TuiAltScreen`) nor repository-relative paths.
|
|
142
|
+
|
|
143
|
+
| Tier | Entry | Contract |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| Stable | `@xmoon76/dsh-pi-tui/extensions` | compatibility-oriented; additive-first; existing semantics never silently change; removal requires a planned breaking change |
|
|
146
|
+
| Advanced | `@xmoon76/dsh-pi-tui/extensions/advanced` | experimental; minor releases may break; a migration note is required; no long-term shims |
|
|
147
|
+
| Unstable | `@xmoon76/dsh-pi-tui/extensions/unstable` | NO compatibility guarantee; implementation may change anytime |
|
|
148
|
+
|
|
149
|
+
All tiers reuse the SAME shared extension runtime: caller-fiber ownership,
|
|
150
|
+
surface lifecycle, invalidation, capability discovery. Do not fork a second
|
|
151
|
+
ownership/lifecycle model per tier. The tiers have grown since Phase 1:
|
|
152
|
+
|
|
153
|
+
- **Advanced** (`ADVANCED_API_LEVEL = 1`, Phase 2 + Phase 4): normalized
|
|
154
|
+
input capture, focused interactive surfaces (interactive managed
|
|
155
|
+
overlays), advanced editor control, the imperative UI broker
|
|
156
|
+
(select/confirm/input/notify), custom interactive UI and the host-state
|
|
157
|
+
facade (theme/title/working/tools-expanded) — still Host-mediated,
|
|
158
|
+
never raw terminal bytes. Author guide: `docs/extension-advanced.md`;
|
|
159
|
+
Pi capability reference: `docs/extension-capability-matrix.md`.
|
|
160
|
+
- **Unstable** (`UNSTABLE_API_LEVEL = 1`, Phase 3): raw input
|
|
161
|
+
interception (observe/consume/rewrite, exclusive raw ownership), the
|
|
162
|
+
Host emergency fail-safe (triple-Esc), and a selected low-level surface
|
|
163
|
+
seam — NO compatibility guarantee; a broken plugin can disrupt Host
|
|
164
|
+
behavior. Author guide: `docs/extension-unstable.md`.
|
|
165
|
+
- **Real-plugin validation (Phase 5):** the tier selection is proven by
|
|
166
|
+
real consumers in `examples/plugins/` — a
|
|
167
|
+
production-class vim modal editor (Advanced editor SDK), a
|
|
168
|
+
questionnaire form (Advanced imperative UI broker) and an interactive
|
|
169
|
+
shell (Unstable raw seam). The "which tier should I use?" decision
|
|
170
|
+
tree: `docs/plugin-authoring.md`.
|
|
171
|
+
|
|
172
|
+
A plugin imports only the public entry:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { PI_TUI_EXTENSIONS_SERVICE, type PiTuiExtensionService } from '@xmoon76/dsh-pi-tui/extensions'
|
|
176
|
+
|
|
177
|
+
export const name = 'my-plugin'
|
|
178
|
+
export const inject = ['tuiStartup', PI_TUI_EXTENSIONS_SERVICE]
|
|
179
|
+
|
|
180
|
+
export function apply(ctx: Context): void {
|
|
181
|
+
const service = ctx.get(PI_TUI_EXTENSIONS_SERVICE) as PiTuiExtensionService
|
|
182
|
+
if (!service.api().capabilities.has('slot.chrome.header.badge')) return
|
|
183
|
+
service.register<{ text: string; tone?: 'info' | 'warning' | 'error' | 'success' }>(
|
|
184
|
+
'chrome.header.badge',
|
|
185
|
+
{ id: 'my-badge', order: 100, description: 'A header badge from my plugin.' },
|
|
186
|
+
{ text: 'my-badge', tone: 'info' },
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Current extension points (v1):
|
|
192
|
+
|
|
193
|
+
| Slot | Semantics | Contribution |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `chrome.header.badge` | list | a short `[badge]` after the host title |
|
|
196
|
+
| `input.dock.item` | list | a dock line above the todo panel |
|
|
197
|
+
| `chrome.footer.status` | list | a footer segment (host owns width/truncation) |
|
|
198
|
+
| `input.widget.above` / `input.widget.below` | list | a bounded widget around the editor (M4 component kit) |
|
|
199
|
+
|
|
200
|
+
Contributions are **plain data**, not render functions: a plugin supplies
|
|
201
|
+
`HeaderBadge` / `DockItem` / `FooterSegment` / `InputWidget` values (text +
|
|
202
|
+
semantic tone spans, or a structured `ExtensionView` tree for widgets) and
|
|
203
|
+
the host owns rendering, ANSI compilation, width budgets and truncation.
|
|
204
|
+
There is deliberately no `render(context)` callback in v1 — plugins never
|
|
205
|
+
hold a rendering context, so a contribution can never capture or mutate
|
|
206
|
+
host internals.
|
|
207
|
+
|
|
208
|
+
Since `0.2.0` the widget slots (`input.widget.above` / `input.widget.below`)
|
|
209
|
+
accept a bounded component kit: `ExtensionView` is a structured view tree
|
|
210
|
+
(`text` / `markdown` / `spacer` / `stack` / `frame` / `rows` views with
|
|
211
|
+
semantic style tokens) that the host compiles into private components. A
|
|
212
|
+
plugin can add helper rows above or below the editor — for example a status
|
|
213
|
+
widget or a quick-reference line — without touching the root layout, the
|
|
214
|
+
editor, or focus. The host owns the row budgets: under height pressure the
|
|
215
|
+
lowest-importance widgets collapse first, and the editor always survives.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
service.register<InputWidget>('input.widget.below', {
|
|
219
|
+
id: 'my-widget',
|
|
220
|
+
order: 100,
|
|
221
|
+
}, {
|
|
222
|
+
view: {
|
|
223
|
+
kind: 'text',
|
|
224
|
+
spans: [{ text: 'my-plugin ready', tone: 'success' }],
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Since M5 the extension surface also covers registries (plan §10):
|
|
230
|
+
|
|
231
|
+
- `registerCommand(contribution)` — slash-command OWNERSHIP metadata
|
|
232
|
+
(`execution: 'local' | 'submission'`): a plugin-declared local command
|
|
233
|
+
always executes directly (never steered by the busy-Enter preference);
|
|
234
|
+
a submission command flows through the session policy. Actual execution
|
|
235
|
+
stays in the host's commands service; `/name args...` keeps
|
|
236
|
+
`rawInput` verbatim. Name conflicts are reported, never guessed.
|
|
237
|
+
- `registerTheme(contribution)` — a named semantic palette selectable
|
|
238
|
+
from the /settings theme picker; the owner's unload falls back to the
|
|
239
|
+
built-in palette (a selected plugin theme never dangles).
|
|
240
|
+
- `registerSetting(contribution)` — a settings row appended to the
|
|
241
|
+
/settings panel (label + current value + choices + optional rejection);
|
|
242
|
+
the host owns the panel.
|
|
243
|
+
- `registerAutocomplete(contribution)` — an autocomplete provider
|
|
244
|
+
consulted after the host's own provider returns null (deterministic
|
|
245
|
+
order, per-provider isolation, latest-only commit).
|
|
246
|
+
- `registerKeybinding(contribution)` — normalized-key → semantic-action
|
|
247
|
+
binding, routed by the host's InputRouter (M6). A plugin declares a key
|
|
248
|
+
with the public `NormalizedKey` shape (key + ctrl/alt/shift/super) and
|
|
249
|
+
a semantic action from the host's list (`submit-draft`, `queue-draft`,
|
|
250
|
+
`steer-draft`, `cancel-activity`, `open-search`, `toggle-fullscreen`,
|
|
251
|
+
`cycle-permission`). The host normalizes ALL terminal input (Kitty
|
|
252
|
+
CSI-u, modifyOtherKeys, legacy sequences) — a plugin never sees raw
|
|
253
|
+
escape data. Reserved host lifecycle keys (Ctrl+C/D/S/F/O/T/G/J,
|
|
254
|
+
Ctrl+Enter, Enter, Esc) cannot be claimed; plain printable keys never
|
|
255
|
+
fire a binding (typing always wins); bindings are non-capturing and
|
|
256
|
+
fire LAST in the precedence ladder (after questions, approvals,
|
|
257
|
+
overlays and the editor). The action executes through the host's own
|
|
258
|
+
paths — submission/session safety is never bypassed.
|
|
259
|
+
- `registerMessageRenderer(contribution)` — a TRANSCRIPT message renderer
|
|
260
|
+
(M7, chain slot): receives a semantic `MessagePresentationSnapshot`
|
|
261
|
+
(immutable; never the mutable message or the container) and returns an
|
|
262
|
+
`ExtensionView` or `undefined` (abdicate → the next renderer → the host
|
|
263
|
+
fallback). Kind-scoped renderers apply to one message kind.
|
|
264
|
+
- `registerToolRenderer(contribution)` — a TOOL card renderer (M7, keyed
|
|
265
|
+
slot): presents the card for ONE tool name from a
|
|
266
|
+
`ToolPresentationSnapshot` (callId, toolName, status, arguments,
|
|
267
|
+
result, expanded); the winner (lowest priority) abdicates to the next
|
|
268
|
+
renderer, then the host fallback. A priority tie on the same tool name
|
|
269
|
+
is an explicit error.
|
|
270
|
+
Renderers never stall the transcript: a throwing renderer is isolated
|
|
271
|
+
and the chain continues, and the message cache embeds the renderer
|
|
272
|
+
identity + registry revision, so an HMR/unload rebuilds exactly the
|
|
273
|
+
affected components.
|
|
274
|
+
- `showOverlay(view, options)` — a MANAGED overlay lease (M8): the plugin
|
|
275
|
+
supplies an `ExtensionView` + sizing hints; the host mounts it through
|
|
276
|
+
its overlay broker (modal stacking, focus, fullscreen migration). The
|
|
277
|
+
returned lease is generation-scoped (the surface's final dispose closes
|
|
278
|
+
every still-owned lease), close() is idempotent, and hide()/show()
|
|
279
|
+
toggle visibility without closing. A plugin can never mount a raw
|
|
280
|
+
component or steal focus — the host owns the terminal and the overlay
|
|
281
|
+
stack.
|
|
282
|
+
The [extension API v1 author guide](docs/extension-api.md) records the
|
|
283
|
+
import rules, the full surface table, the lifecycle/render contracts, the
|
|
284
|
+
M11 deprecation policy and the stability contract.
|
|
285
|
+
|
|
286
|
+
The M10 acceptance fixture (plan §15): the repo ships a vim-mode fixture
|
|
287
|
+
(`test/fixtures/vim-plugin/`) that validates the editor-extension seam — the
|
|
288
|
+
packed public SDK is consumable by a third-party Cordis plugin, its
|
|
289
|
+
replacement editor receives SEMANTIC `EditorInputEvent`s (never raw terminal
|
|
290
|
+
bytes), and editor `create()`/`dispose()` work — importing ONLY
|
|
291
|
+
`@xmoon76/dsh-pi-tui/extensions`. It is NOT a production Vim and NOT a
|
|
292
|
+
Stable-API completeness proof: modal-mode behavior (insert/normal) is not
|
|
293
|
+
part of the Stable contract, and the other public surfaces (commands,
|
|
294
|
+
themes, settings, autocomplete, keybindings, renderers, overlays, widgets)
|
|
295
|
+
have their own dedicated tests. Its CI gate forbids `@xmoon76/pi-tui`,
|
|
296
|
+
`src/tui-app` and repository-relative internal paths: if a STABLE plugin
|
|
297
|
+
ever needs a private import, the SDK is missing a capability (there is no
|
|
298
|
+
`unsafeGetTuiApp()` escape hatch).
|
|
299
|
+
|
|
300
|
+
- `registerEditor(contribution)` — the EDITOR SDK (M9, plan §14):
|
|
301
|
+
single-winner by priority (a tie is an explicit error); the winner
|
|
302
|
+
occupies the editor seat through the host's ATOMIC handoff (create →
|
|
303
|
+
transfer draft/cursor → mount → focus → dispose old). A creation throw
|
|
304
|
+
keeps the current editor working; winner unload restores the next
|
|
305
|
+
winner / the host default editor WITH the draft preserved. The plugin
|
|
306
|
+
editor receives an `EditorHost` (surfaceId, generation, getSnapshot,
|
|
307
|
+
replaceText, dispatch of semantic actions submit/queue-submit/steer/
|
|
308
|
+
open-external-editor, subscribe, invalidate) — but the host still owns
|
|
309
|
+
busy-Enter, Ctrl+Enter, local-command classification, paste protection,
|
|
310
|
+
approval/question capture, session guard/lock, external editor and
|
|
311
|
+
exit: a plugin editor can never bypass those.
|
|
312
|
+
|
|
313
|
+
Lifecycle is host-owned: registrations are disposed when the plugin's Cordis
|
|
314
|
+
fiber unloads (HMR, disable), regular and fullscreen both refresh, and
|
|
315
|
+
`handle.invalidate()/replace()` re-render through the active screen. The
|
|
316
|
+
`@xmoon76/dsh-pi-tui/builtins` entry is the Loader-only first-party
|
|
317
|
+
contributor (version badge, turn/step counters, todo-summary dock item) —
|
|
318
|
+
not a stable third-party SDK. Raw terminal access, pre-host input
|
|
319
|
+
interception and full input ownership are NOT part of the Stable tier (see
|
|
320
|
+
the Advanced/Unstable roadmap).
|
|
321
|
+
|
|
322
|
+
## Slash commands (selection)
|
|
323
|
+
|
|
324
|
+
- `/sessions [query]` — open the session picker: search-as-you-type over
|
|
325
|
+
session ids, titles, and workspaces, rows grouped by workspace with live
|
|
326
|
+
`filtered/total` counts, and titles loaded in the background as they are
|
|
327
|
+
read. Enter switches to the selected session.
|
|
328
|
+
- `/search <query>` — full-text search over persisted session logs, then
|
|
329
|
+
switch to a hit.
|
|
330
|
+
- `/title [title]` — with an argument, set the current session's title
|
|
331
|
+
(pins it against automatic generation; titles appear in the `/sessions`
|
|
332
|
+
picker); **without an argument, regenerate the title from the
|
|
333
|
+
conversation — this overwrites the current title, including one you
|
|
334
|
+
pinned earlier** (`/rename` is an alias).
|
|
335
|
+
- `/tasks` — the merged task browser: background jobs AND subagents in one
|
|
336
|
+
searchable list (type to filter rows by kind/label/status — `subagent`,
|
|
337
|
+
`bash`, `failed`…). `Enter` opens the detail (child transcript for a
|
|
338
|
+
subagent, status viewer for a job), `i` interrupts the selected subagent.
|
|
339
|
+
`/subagents` is an alias.
|
|
340
|
+
- `/yolo` — switch to `danger-full-access` (alias of `/permission danger-full-access`).
|
|
341
|
+
- `/status` — show the current session's stats and identity (turn counts,
|
|
342
|
+
token usage, workspace, installed dsh version).
|
|
343
|
+
- `/preset`, `/model`, `/settings`, `/export`, `/fork` — see
|
|
344
|
+
`dsh --profile pi-tui`'s command autocomplete (`/` + Tab).
|
|
345
|
+
|
|
346
|
+
## Keybindings (selection)
|
|
347
|
+
|
|
348
|
+
- `Ctrl+F` — toggle transcript search (the `/search <query>` overlay; a
|
|
349
|
+
second press closes it).
|
|
350
|
+
- `Shift+Tab` — cycle the permission preset (read-only → workspace-write →
|
|
351
|
+
danger-full-access); the footer's mode slot badges every preset
|
|
352
|
+
(`[workspace-write]` / `[read-only]` / `[custom]`, with `[yolo]` flagging
|
|
353
|
+
the no-approval mode).
|
|
354
|
+
- `Ctrl+S` — steer: with queued messages, sends the whole queue (plus the
|
|
355
|
+
draft, if any) into the running turn at once; otherwise sends the draft
|
|
356
|
+
alone. An idle agent starts a fresh turn with everything.
|
|
357
|
+
- `Alt+↑` — dequeue: pull every queued message back into the editor draft.
|
|
358
|
+
- `Ctrl+T` — toggle the full todo list (in fullscreen, clicking the panel
|
|
359
|
+
expands it to the full list and back); the dock above the editor always
|
|
360
|
+
shows the todo summary and background tasks, and queued input renders
|
|
361
|
+
between them.
|
|
362
|
+
- `@` — file/folder mentions in the editor: `@` + Tab completes files from the
|
|
363
|
+
whole workspace (fd-backed when `fd` is on PATH, with a built-in recursive
|
|
364
|
+
fallback otherwise). The literal `@path` is submitted and the model reads
|
|
365
|
+
the file itself. With background work running, an empty editor's `↓` opens
|
|
366
|
+
the same merged task browser as `/tasks`:
|
|
367
|
+
- **subagent rows** (live continuable children) — `Enter` opens the child's
|
|
368
|
+
transcript read-only (`Esc` returns); `i` interrupts the selected child.
|
|
369
|
+
They never register jobs records, so this browser is their only glanceable
|
|
370
|
+
home.
|
|
371
|
+
- **job rows** (bash and one-shot subagent jobs) — `Enter` shows the status
|
|
372
|
+
viewer only: a bash job's output read cursor belongs to the model's
|
|
373
|
+
`job_output`, and a one-shot subagent job record carries no child session
|
|
374
|
+
id, so a subagent job's transcript is reached through `/tasks` by picking
|
|
375
|
+
the child by its label.
|
|
376
|
+
The footer badge shows `[N tasks running · M agents · ↓ view]` while any
|
|
377
|
+
background work is live. The queue pane above the editor shows pending
|
|
378
|
+
input: user messages as `❯` rows, everything else (job notices, subagent
|
|
379
|
+
reports, injected instructions) as `⏳` notices that fold into a
|
|
380
|
+
`+N more` line beyond five — and disappear once the agent has received
|
|
381
|
+
them.
|
|
382
|
+
|
|
383
|
+
## Launch options
|
|
384
|
+
|
|
385
|
+
The TUI's startup row adds `--preset <id>` — the agent preset a fresh
|
|
386
|
+
session starts on (falls back to `$DSH_PI_TUI_PRESET`, then the saved
|
|
387
|
+
settings default). It exists because `/preset` only applies to a blank (not
|
|
388
|
+
yet created) session, so launch-time selection is the other half of choosing
|
|
389
|
+
a preset. All other flags are the dsh runner's own (`--session <id>`, …).
|
|
390
|
+
|
|
391
|
+
## Session lifecycle
|
|
392
|
+
|
|
393
|
+
Opening the TUI with no `--session` creates **no session at all**: the first
|
|
394
|
+
user message (text, slash command, `Ctrl+S` steer, or `!` shell) starts it
|
|
395
|
+
lazily. `--session <id>` still resumes immediately, and a local `!!` command
|
|
396
|
+
runs without needing a session.
|
|
397
|
+
|
|
398
|
+
## Verified in the P0 spike
|
|
399
|
+
|
|
400
|
+
- Vendored pi-tui: the fork's own suite passes under `node --test` (run it as
|
|
401
|
+
the sync gate after every re-vendor; the count is deliberately not copied here —
|
|
402
|
+
`packages/pi-tui/package.json` is the single source of version facts).
|
|
403
|
+
- `TuiApp` renders, accepts editor input, and handles Ctrl+C on a headless xterm.
|
|
404
|
+
- The whole import chain (pi-tui, tui-app, `@deepseek-ai/dsh-cmdline`, commander)
|
|
405
|
+
loads under the tsx ESM hook — the dsh source-launch contract.
|
|
406
|
+
- Native modifier-key addons are optional: on Linux the loader returns `undefined`
|
|
407
|
+
without attempting a load, and the non-TTY stdin path is guarded.
|
|
43
408
|
|
|
44
409
|
## Diagnostic log
|
|
45
410
|
|
|
46
|
-
The TUI writes its own diagnostics to stderr and a log file (`ctx.logger` is
|
|
411
|
+
The TUI writes its own diagnostics to stderr and a log file (`ctx.logger` is
|
|
412
|
+
invisible in this process — no exporter):
|
|
47
413
|
|
|
48
414
|
- Default file: `$DSH_HOME/logs/pi-tui-<pid>.log` (default `~/.dsh/logs/`);
|
|
49
415
|
- Line format: `[tui] <ISO time> <level> <message> k=v ...`;
|
|
50
|
-
- Default level `info`: key lifecycle events only (boot/resume/switch/exit,
|
|
416
|
+
- Default level `info`: key lifecycle events only (boot/resume/switch/exit,
|
|
417
|
+
divergence-guard warnings, errors); `debug` additionally logs every guard
|
|
418
|
+
check before a send.
|
|
51
419
|
|
|
52
420
|
Configuration (environment variables):
|
|
53
421
|
|
|
@@ -63,37 +431,39 @@ DSH_PI_TUI_LOG_LEVEL=debug dsh --profile pi-tui
|
|
|
63
431
|
tail -f ~/.dsh/logs/pi-tui-*.log
|
|
64
432
|
```
|
|
65
433
|
|
|
66
|
-
##
|
|
67
|
-
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
434
|
+
## Safety & operational notes
|
|
435
|
+
|
|
436
|
+
- **One surface per session.** dsh has no cross-process session coordination:
|
|
437
|
+
a session open in TWO dsh processes (TUI + web, or two TUIs) can corrupt its
|
|
438
|
+
log. The TUI refuses to open a session already held by another live dsh
|
|
439
|
+
process (an `owner.lock` file next to the log, with a pid/starttime probe
|
|
440
|
+
for stale locks left by crashes — so the second surface is stopped at
|
|
441
|
+
OPEN time, not after the damage). For writes, the TUI detects the other
|
|
442
|
+
writer and blocks the send; the SAME action pressed again (Enter for a
|
|
443
|
+
submit, Ctrl+S for a steer, unchanged draft) forces through — an edited
|
|
444
|
+
draft, a swapped key, a new file revision, or a session switch invalidates
|
|
445
|
+
the force. Never run two surfaces on one session (full contract:
|
|
446
|
+
`docs/concurrency.md`).
|
|
447
|
+
- **Session repair.** `node_modules/@xmoon76/dsh-pi-tui/scripts/repair-session.mjs`
|
|
448
|
+
repairs corrupted logs (`--scan` lists damage read-only; `--yes` applies with
|
|
449
|
+
a mandatory backup). A torn (truncated) tail is truncated at the last
|
|
450
|
+
complete frame and reported with exact byte accounting; references to a
|
|
451
|
+
duplicated seq are never auto-resolved — the repair refuses and asks for
|
|
452
|
+
`--duplicate-reference=first|last|segment`. Repaired logs are re-verified
|
|
453
|
+
with the dsh reader's own layout checks before the backup is considered
|
|
454
|
+
redundant. (Full repair contract, incl. the frame-layout constraint:
|
|
455
|
+
`docs/repair-session.md`.)
|
|
456
|
+
- **Exit.** `/exit` (alias `/quit`) flushes the session with a 10s hard
|
|
457
|
+
timeout: a hung provider cannot trap the TUI. If the flush fails or times
|
|
458
|
+
out, the terminal prints a warning (the tail may not be persisted) and the
|
|
459
|
+
process still exits.
|
|
460
|
+
- **Performance.** `scripts/bench.mts` (non-default) measures ingest,
|
|
461
|
+
projection, cold/warm rebuilds, streaming frames, theme switches, and heap;
|
|
462
|
+
the saved baseline lives in `docs/perf-baseline.md`. Unchanged transcript
|
|
463
|
+
messages reuse their rendered components, so the warm per-frame rebuild
|
|
464
|
+
does not grow with history.
|
|
95
465
|
|
|
96
466
|
## License
|
|
97
467
|
|
|
98
|
-
MIT.
|
|
468
|
+
MIT. `packages/pi-tui` retains its upstream MIT license and authorship
|
|
99
469
|
(Copyright (c) 2025 Mario Zechner; Moonshot AI fork).
|