@aiwayds/dsh-tui-pi 0.1.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/bin/dsh-tui-pi +5 -0
  4. package/cordis.patch.yml +9 -0
  5. package/lib/append-system.d.ts +66 -0
  6. package/lib/append-system.js +161 -0
  7. package/lib/append-system.js.map +1 -0
  8. package/lib/commands.d.ts +53 -0
  9. package/lib/commands.js +167 -0
  10. package/lib/commands.js.map +1 -0
  11. package/lib/dsh-events.d.ts +106 -0
  12. package/lib/dsh-events.js +30 -0
  13. package/lib/dsh-events.js.map +1 -0
  14. package/lib/editor.d.ts +28 -0
  15. package/lib/editor.js +70 -0
  16. package/lib/editor.js.map +1 -0
  17. package/lib/footer.d.ts +36 -0
  18. package/lib/footer.js +112 -0
  19. package/lib/footer.js.map +1 -0
  20. package/lib/frame.d.ts +35 -0
  21. package/lib/frame.js +75 -0
  22. package/lib/frame.js.map +1 -0
  23. package/lib/git.d.ts +17 -0
  24. package/lib/git.js +51 -0
  25. package/lib/git.js.map +1 -0
  26. package/lib/index.d.ts +15 -0
  27. package/lib/index.js +781 -0
  28. package/lib/index.js.map +1 -0
  29. package/lib/instructions.d.ts +29 -0
  30. package/lib/instructions.js +67 -0
  31. package/lib/instructions.js.map +1 -0
  32. package/lib/live-widgets.d.ts +85 -0
  33. package/lib/live-widgets.js +218 -0
  34. package/lib/live-widgets.js.map +1 -0
  35. package/lib/messages.d.ts +277 -0
  36. package/lib/messages.js +734 -0
  37. package/lib/messages.js.map +1 -0
  38. package/lib/permission.d.ts +27 -0
  39. package/lib/permission.js +48 -0
  40. package/lib/permission.js.map +1 -0
  41. package/lib/provider-catalog.d.ts +114 -0
  42. package/lib/provider-catalog.js +124 -0
  43. package/lib/provider-catalog.js.map +1 -0
  44. package/lib/quotes.d.ts +28 -0
  45. package/lib/quotes.js +144 -0
  46. package/lib/quotes.js.map +1 -0
  47. package/lib/reload.d.ts +23 -0
  48. package/lib/reload.js +171 -0
  49. package/lib/reload.js.map +1 -0
  50. package/lib/selectors.d.ts +48 -0
  51. package/lib/selectors.js +261 -0
  52. package/lib/selectors.js.map +1 -0
  53. package/lib/session.d.ts +157 -0
  54. package/lib/session.js +555 -0
  55. package/lib/session.js.map +1 -0
  56. package/lib/sessions.d.ts +73 -0
  57. package/lib/sessions.js +253 -0
  58. package/lib/sessions.js.map +1 -0
  59. package/lib/settings.d.ts +180 -0
  60. package/lib/settings.js +1328 -0
  61. package/lib/settings.js.map +1 -0
  62. package/lib/text.d.ts +22 -0
  63. package/lib/text.js +45 -0
  64. package/lib/text.js.map +1 -0
  65. package/lib/theme/index.d.ts +79 -0
  66. package/lib/theme/index.js +121 -0
  67. package/lib/theme/index.js.map +1 -0
  68. package/lib/theme/palette.d.ts +56 -0
  69. package/lib/theme/palette.js +154 -0
  70. package/lib/theme/palette.js.map +1 -0
  71. package/lib/theme-settings.d.ts +68 -0
  72. package/lib/theme-settings.js +223 -0
  73. package/lib/theme-settings.js.map +1 -0
  74. package/lib/tui.d.ts +70 -0
  75. package/lib/tui.js +206 -0
  76. package/lib/tui.js.map +1 -0
  77. package/lib/welcome.d.ts +91 -0
  78. package/lib/welcome.js +281 -0
  79. package/lib/welcome.js.map +1 -0
  80. package/package.json +50 -0
  81. package/patches/@earendil-works__pi-tui.patch +72 -0
  82. package/pnpm-workspace.yaml +2 -0
  83. package/templates/APPEND_SYSTEM.md +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-tui-pi contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # dsh-tui-pi
2
+
3
+ pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).
4
+
5
+ ## Preview (ASCII mock-up)
6
+
7
+ ```
8
+ ┌──────────────────────────────────────────────────────────────┐
9
+ │ 🐳: 两个 subagent 已在后台并行启动 (A: 5b19b15c, B: d2072ffd) │ ← transcript (scrolls)
10
+ │ ┌─ 💭 thinking ──────────────────────────────────────────┐ │
11
+ │ │ Actually, I can check list_agents or wait… │ │
12
+ │ └────────────────────────────────────────────────────────┘ │
13
+ │ ⚙ bash python scripts/demo.py … ✔ bash │ │
14
+ └──────────────────────────────────────────────────────────────┘
15
+ ┌─ ● Todos (1/2) ────────────────────────────────────────────┐
16
+ │ ├─ ☑ Todo 1: 启动 subagent A 执行 10s 任务并收集结果 │ ← fixed widgets
17
+ │ └─ ◐ Todo 2: 启动 subagent B 执行 10s 任务并收集结果 │ above the input
18
+ └─────────────────────────────────────────────────────────────┘
19
+ ┌─ ● Agents ─────────────────────────────────────────────────┐
20
+ │ ├─ ⠼ spawn Subagent A 10s 任务 · 1.2k token · 19.0s │
21
+ │ │ ⎿ running sleep… │
22
+ │ └─ ⠼ spawn Subagent B 10s 任务 · 562 token · 6.0s │
23
+ │ ⎿ working… │
24
+ └─────────────────────────────────────────────────────────────┘
25
+ ∴ working… ← status
26
+ 📁 ~/github (Full access) │ ⎇ main ← editor border
27
+ [ 请输入指令… ] ← input
28
+ ↳ 创建 2 个 todo, 每个 todo 起一个 10s 的 subagent ← last request
29
+ dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1.0M (1.2%) ▸ ⚡ CH98.9% ▸ 💬 8 ▸ 🔧 4 00:00:14 ← footer
30
+ ⌨ Enter: send · Ctrl+C: cancel / double: quit ← hints
31
+ ```
32
+
33
+ - **Look & feel**: pi coding agent interactive TUI, built on
34
+ `@earendil-works/pi-tui` 0.84.2 (pinned) — alt-screen scrollable transcript,
35
+ docked editor/status/footer, markdown messages, slash-command autocomplete.
36
+ - **Slash commands**: dsh's own, untouched. Autocomplete from
37
+ `ctx.commands.list(agent)`, executed via `ctx.commands.execute(agent, line, signal)`.
38
+ Verified: `/compact`, `/plan`, `/goal`, `/permission`, `/feedback`. The TUI
39
+ adds its own surface commands — `/model /think /session /resume /new
40
+ /settings /export /theme /reload` (see below).
41
+ - **Themes**: GitHub light / GitHub dark palettes (aligned with the
42
+ `cmux-theme.sh` GitHub terminal themes). Hot-switchable at runtime: pick one
43
+ with `/theme` (applies immediately), edit the `dsh-tui.theme` setting
44
+ (external edits hot-apply too), or pin with `DSH_TUI_THEME=light|dark` — the
45
+ env var wins over every preference. Without a preference, terminal-background
46
+ detection (COLORFGBG).
47
+ - **Footer**: powerline segments ported from
48
+ [pi-powerline-footer](https://github.com/fan56/pi-powerline-footer) —
49
+ provider / model+thinking / context / cache-hit / msgs / tools with U+E0B0
50
+ arrows, right-aligned live clock, cwd+git-branch editor top border, and the
51
+ `↳ last-request` widget.
52
+ - **Live todos & subagents**: bordered panels pinned **above the chat input** —
53
+ a `● Todos (done/total)` tree (`☐`/`◐`/`☑` status icons) and a live
54
+ `● Agents` board (spinner, provider + label, retries, token count + context
55
+ percent, elapsed and the current tool) refreshed ~10×/s. Show while there is
56
+ content, clear when done — a settled child drops off the board and an empty
57
+ panel collapses to zero rows. Subagents are tracked from the child sessions
58
+ themselves (header `origin: subagent` + `parentSession`), so any spawn
59
+ mechanism works. The TUI also supports pi's `APPEND_SYSTEM.md` convention
60
+ (dsh side: `~/.dsh/APPEND_SYSTEM.md`): a user-editable file whose content is
61
+ appended to the system prompt of every agent the TUI creates — read at each
62
+ assembly, so edits apply to the next request without a restart. The TUI's
63
+ own `dsh-tui-pi:todo-lifecycle` guidance (marker
64
+ `<!-- dsh-tui-pi:todo-lifecycle -->`) lives in that file, telling the model
65
+ to write an empty todo list once everything is completed (idempotent,
66
+ atomic, best-effort; the panel-side all-completed hide remains as fallback).
67
+
68
+ ## Commands
69
+
70
+ | Command | What it does |
71
+ |---|---|
72
+ | `/model` | pick provider/model — every route the mounted llm services list (pi-ai's built-in catalog + llm-deepseek's static pair). Two-stage picker: choose the model, then a think level when the route exposes one (Esc on stage 2 abandons the whole pick). Live switch with footer sync, persisted as the default. |
73
+ | `/think` | reasoning-effort picker for the current model — `(provider default)` clears the override, then the route's efforts (Off / High / Max …). Live + persisted. |
74
+ | `/session` | read-only info panel: id, cwd, created, model, think level, status, message/tool counts, token usage, event count, parent session. |
75
+ | `/resume` | pick a persisted session (subagent children and the current session are filtered out), validate its log *before* touching the live agent, then restore it — transcript and footer stats rebuild from the stored events. |
76
+ | `/new` | detach the current session and clear the transcript; the next prompt opens a fresh one (the escape hatch when the current history must not follow, e.g. images in it). |
77
+ | `/settings` | text-based settings browser: namespaces grouped into categories (General / Models / Plugins / Agent Presets / Other), schema walk with drill-ins, cycle rows, inline editors (secrets masked), dict add-key, reset-to-defaults. Writes go through the settings mutate chain. |
78
+ | `/export` | write the current session log as JSONL — default `~/Downloads/dsh-session-<id>.jsonl`, or a path argument. |
79
+ | `/permission` | permission-preset picker (whatever the deployment table advertises — read-only / workspace-write / danger-full-access). Select a preset to apply it through dsh's canonical `/permission <name>` command, or Esc to keep the current one. The editor's top border shows the live preset badge (danger-full-access → "Full access"). |
80
+ | `/theme` | color-scheme picker (auto / light / dark). The choice applies immediately and is persisted to `dsh-tui.theme`. |
81
+ | `/reload` | hot-reload the plugin from the current source (after `pnpm build`) without restarting dsh — the TUI and the live agent are torn down; the session log persists and can be rejoined with `/resume`. |
82
+
83
+ Anything that is not a resolvable command falls through to the model as an
84
+ ordinary prompt, so dsh packages' commands (and future registrations) appear
85
+ automatically.
86
+
87
+ ### Models: provider-first
88
+
89
+ `/settings → Models` does not expose the raw `llm-pi-ai` namespace. It lists
90
+ one row per configured provider — label (`displayName` ?? catalog name ??
91
+ route key), value column (first model / `N models` / `catalog` when pi-ai
92
+ serves the route), and one-line API-key state (`API key set` / `missing` /
93
+ `not configured`) probed from the process environment plus the credentials
94
+ document — with dedicated `DeepSeek (official)` and `Default model` rows and a
95
+ `+ Add provider…` action. The add flow mirrors pi's `/login`:
96
+
97
+ 1. pick from the built-in directory (10 searchable catalog routes — Anthropic,
98
+ DeepSeek, Google Gemini, Groq, Mistral, OpenAI, OpenCode Go, OpenRouter,
99
+ Together AI, xAI);
100
+ 2. enter exactly one API key — masked dot-row editor, the value never echoes
101
+ and never reaches the rendered output;
102
+ 3. the commit double-writes like the web Models page: `llm-pi-ai.providers.<id>`
103
+ gets `{ apiKeyEnv: <ref> }` through the settings mutate chain (ref derived
104
+ by convention: route key uppercased, non-alphanumerics → `_`, `_API_KEY`
105
+ suffix, e.g. `opencode-go → OPENCODE_GO_API_KEY`) and the key is stored via
106
+ `ctx.credentials.set` — never in `settings.yaml`. Without a credentials
107
+ service the profile still commits and the UI says
108
+ `export <REF>=<key> to use it` instead.
109
+
110
+ ### Theme hot-switch
111
+
112
+ Themes change live, no restart:
113
+
114
+ - `/theme` (or an edit through `/settings → General → dsh-tui`) commits the
115
+ preference to the `dsh-tui` settings namespace, which is registered
116
+ `applies: 'live'`. The namespace's watch hook pushes the commit to the
117
+ running TUI, which repaints everything on the next frame: transcript
118
+ (replayed from its operation buffer), editor border, footer hint, spinner.
119
+ - An **external edit** of `~/.dsh/settings.yaml` (`dsh-tui.theme: dark`)
120
+ hot-applies through the same watch path.
121
+ - `DSH_TUI_THEME=light|dark` **pins** the display regardless of preference —
122
+ it wins at startup and keeps winning; `/theme` still persists the
123
+ preference and honestly reports `Theme preference saved — display is pinned
124
+ by DSH_TUI_THEME=…` instead of claiming it applied.
125
+ - The choice survives restarts (`auto` falls back to terminal detection).
126
+
127
+ ## APPEND_SYSTEM.md
128
+
129
+ dsh-tui-pi supports pi's `APPEND_SYSTEM.md` convention on the dsh side:
130
+ **`~/.dsh/APPEND_SYSTEM.md`** (`$DSH_HOME` or `~/.dsh`) is appended to the
131
+ system prompt of every agent this TUI creates. The file is read at each
132
+ prompt assembly, so **edits apply to the very next request** — no restart,
133
+ no reload, no watcher.
134
+
135
+ - **The file is not shipped with the source**: the repo contains no
136
+ `APPEND_SYSTEM.md` at its root — it lives in your `~/.dsh` and is yours to
137
+ edit freely (identity, persona rules, UI conventions — anything you want
138
+ the model to know). The English **template** the installer seeds from is
139
+ `templates/APPEND_SYSTEM.md` (the pi orchestrator-identity definition,
140
+ translated — content lives in that file, not in code).
141
+ - On first run the TUI **creates** the file from the template if missing and
142
+ **maintains one marked section** in it (`<!-- dsh-tui-pi:todo-lifecycle
143
+ -->`, telling the model to clear the todo list once everything is
144
+ completed). Your own content is never touched; the maintenance is
145
+ idempotent, atomic (tmp + rename) and best-effort.
146
+ - An empty or missing file contributes nothing to the prompt (the section is
147
+ dropped by the prompt renderer).
148
+
149
+ ## Install (local)
150
+
151
+ ```sh
152
+ # build once
153
+ cd dsh-tui-pi && pnpm install && pnpm build
154
+
155
+ # live development link (recommended; edits to src/ + pnpm build apply on next launch)
156
+ dsh plugin --profile tui add link:/path/to/dsh-tui-pi
157
+
158
+ # or an npm tarball
159
+ npm pack # → aiwayds-dsh-tui-pi-0.1.0.tgz
160
+ dsh plugin --profile tui add /path/to/aiwayds-dsh-tui-pi-0.1.0.tgz
161
+ ```
162
+
163
+ Both paths auto-add `dsh-tui-pi` to the profile's `dsh.profile.bundles`.
164
+
165
+ ## Use
166
+
167
+ ```sh
168
+ dsh --profile tui # or: dsh-tui-pi (bin shim)
169
+ ```
170
+
171
+ - Type a prompt → Enter. Streaming reply renders live; tool calls render as
172
+ `⚙/✔/✘` cards.
173
+ - Todos and subagent children the model spawns show in bordered panels pinned
174
+ **above the chat input** (they never scroll with the transcript): a
175
+ `● Todos (done/total)` tree and a live `● Agents` board — spinner, provider
176
+ + label, retries (`↻N≤M`), tokens (+ context percent), elapsed, and the
177
+ current tool (`⎿ running …`). A finished child drops off the board; when
178
+ nothing is left the panels collapse away.
179
+ - `/` opens slash-command autocomplete (Tab/arrows/Enter).
180
+ - Ctrl+C quits — while the agent is mid-turn the first press cancels the turn
181
+ (`⏹ canceling current turn…`), any further press quits.
182
+
183
+ ## Performance rules (from the pi-turbo findings)
184
+
185
+ pi's TUI lags in long sessions because its footer re-scans the whole session
186
+ log on every render (O(n)) and a 1s clock tick recomputes everything.
187
+ dsh-tui-pi avoids both by construction:
188
+
189
+ - **Event-driven incremental state**: `session/event` listeners maintain an
190
+ append-only transcript model + running counters (tokens, messages, tools,
191
+ cache-hit rate). Render never re-scans the dsh session log.
192
+ - **Footer reads O(1) maintained values** — never derived in `render()`.
193
+ - **Clock tick only re-renders the footer line**; transcript components cache.
194
+ - **Live widgets tick at 100 ms** (`AGENT_TICK_MS`, unref'd timer): a tick
195
+ only re-setTexts the widget's single Text (O(agents)) and is a no-op while
196
+ no child runs — never a transcript re-scan.
197
+ - **Streaming strategy**: deltas accumulate in a plain Text via `setText` on
198
+ the same component (never remove+re-add per token); markdown renders once on
199
+ the assembled `assistant/message` (no per-token markdown parsing).
200
+ - **Configurable think/tool panels** (`dsh-tui.panelHeight`, default `'5'`):
201
+ `'5'/'7'/'10'` set the total panel rows (top border + header row + body rows
202
+ + bottom border); `'all'` prints the full body, with bounded on-screen
203
+ content — a streaming reasoning panel boxes a 200-line live tail while
204
+ chunks are in flight (the assembled message renders everything) and a
205
+ settled tool result keeps at most 2000 lines (a `… (+N lines)` marker
206
+ reports the drop). No inner scroll — pi-tui 0.84.2 never lays out nested
207
+ components, so a nested ScrollView cannot obtain a viewport. Body lines are
208
+ clipped to one physical row *before* styling, so long output can never wrap
209
+ the panel past its configured rows.
210
+ - **Width safety**: every truncation goes through `clipToWidth` (src/text.ts)
211
+ — CJK full-width characters count 2 columns and graphemes are never split.
212
+ Bare `String.length` clipping is banned.
213
+
214
+ ## Dev
215
+
216
+ ```sh
217
+ pnpm check # tsc --noEmit
218
+ pnpm build # emit lib/
219
+ pnpm test # unit tests, node --test against lib/ (171 tests, pretest builds)
220
+ ```
221
+
222
+ Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
223
+ dsh closure (`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);
224
+ at runtime those imports resolve to the same module instances the running dsh
225
+ uses. Those symlinks stay out of any tarball (`files` ships lib/bin/patch only).
226
+
227
+ ⚠️ `pnpm install` regenerates the three type-check symlinks declared in
228
+ `package.json` (`dsh-settings`, `dsh-client-schema-form`, `schemastery`)
229
+ into local `.pnpm` copies, splitting the cordis module identity and breaking
230
+ `pnpm check` — after any install, re-link them:
231
+
232
+ ```sh
233
+ ln -sfn /opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/{dsh-settings,dsh-client-schema-form,schemastery} node_modules/@deepseek-ai/
234
+ ```
235
+
236
+ `dsh-permission-presets` is a fourth, undeclared type-check link — the same
237
+ extraneous-closure pattern as `cordis`/`dsh-agent` above: `pnpm install`
238
+ never regenerates it (it is not in the dependency tree), but a wiped
239
+ `node_modules` needs it re-created by hand:
240
+
241
+ ```sh
242
+ ln -sfn /opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-permission-presets node_modules/@deepseek-ai/
243
+ ```
244
+
245
+ **pi-tui patch**: this plugin applies a small patch to the pinned
246
+ `@earendil-works/pi-tui` 0.84.2 (`pnpm.patchedDependencies` in
247
+ `pnpm-workspace.yaml`, patch in `patches/`): it adds an `unselectedText`
248
+ SelectListTheme hook (full-row background on unselected rows), wires the
249
+ previously dead `selectedPrefix` hook (accent arrow on the selected row), and
250
+ frames the editor's slash-autocomplete list in a `│` box. The patch travels in
251
+ the tarball (`files` includes `patches` + `pnpm-workspace.yaml`); the
252
+ link:-mounted dev workflow uses the already-patched copy in this repo's
253
+ `node_modules`.
254
+
255
+ ## Layout
256
+
257
+ ```
258
+ bin/dsh-tui-pi launcher shim (exec dsh --profile tui)
259
+ cordis.patch.yml bundle patch: mounts the plugin as `tui-pi`
260
+ src/
261
+ index.ts cordis plugin entry + wiring: command registration, footer,
262
+ git watcher, clock, bridge, theme hot-swap sink, shutdown
263
+ tui.ts TUI bootstrap: alt-screen tree, transcript ScrollView,
264
+ dock (status/editor/last-request/footer), editor rebuild
265
+ session.ts DshSessionBridge: lazy agent create, followup, resume,
266
+ replay, cancel, O(1) incremental stats, persistDefaultModel,
267
+ subagent tracker (tool-workflow + child events → live rows)
268
+ dsh-events.ts local types + guards for tool-workflow/subagent/llm-retry
269
+ events (declaring packages not installed) + AgentView
270
+ live-widgets.ts LiveWidgets: fixed Todos/Agents widgets pinned above the
271
+ chat window (renderTodos/renderAgents/tickLive/setTheme)
272
+ commands.ts CommandService: slash autocomplete + dual-channel dispatch
273
+ (registerLocal agentless direct / ctx.commands host path)
274
+ messages.ts TranscriptRenderer: session events → pi-tui components;
275
+ streaming setText, height-configurable panels, ReplayOp
276
+ buffer for theme-switch rebuilds
277
+ footer.ts PowerlineFooter (ported segment palette, 7 segments + clock)
278
+ editor.ts CwdBorderEditor (top border: 📁 cwd │ ⎇ branch)
279
+ git.ts GitBranchWatcher (polled, cached)
280
+ frame.ts FramedOverlay: shared top/bottom ─ border for every popup
281
+ provider-catalog.ts built-in provider directory + deriveKeyRef + row views
282
+ (pure data/functions for the Models add-provider flow)
283
+ reload.ts /reload hot-reload (cordis-plugin-hmr style partial reload)
284
+ text.ts clipToWidth / visibleWidth (grapheme-safe column clipping)
285
+ theme-settings.ts dsh-tui settings namespace (applies: 'live') + watch sink
286
+ + preference read/write with conflict retry
287
+ selectors.ts /model (two-stage), /think, /theme and /permission picker
288
+ overlays
289
+ permission.ts permission display names (web-client conventions) + picker
290
+ option assembly (pure, unit-tested)
291
+ sessions.ts /session info panel + /resume persisted-session picker
292
+ settings.ts /settings browser: categories, schema walk, inline editors,
293
+ serialized mutate write chain, add-provider flow
294
+ theme/
295
+ palette.ts GitHub light/dark palettes + terminal-background detection
296
+ index.ts buildTheme: Editor/Markdown/SelectList/chat roles, POWERLINE
297
+ segment palette, resolveTheme (env > preference > detect)
298
+ test/*.test.mjs unit tests, node --test against lib/ (171 across 14 files)
299
+ ```
300
+
301
+ ## Status (2026-08-15)
302
+
303
+ All surface commands shipped and tmux-e2e verified: `/model /think /session
304
+ /resume /new /settings /export /theme /reload`; provider-first Models with the
305
+ add-provider flow; overlay chrome (backgrounds + borders); theme hot-switch
306
+ (immediate apply, external-change watch, env pinning); graded Ctrl+C; live
307
+ todos + subagent progress blocks; clean Ctrl+C exit. `pnpm check` clean,
308
+ 171 unit tests green, e2e run confirmed the settings/credentials files are
309
+ restored byte-for-byte.
310
+
311
+ Known limitations (accepted, pi-tui 0.84.2 constraints):
312
+
313
+ - **SelectList unselected rows and the SettingsList search row have no
314
+ background**: unselected rows render as raw `prefix + value` and the search
315
+ input row is pushed without a theme hook — the popup backdrop is striped
316
+ rather than one solid surface.
317
+ - **On a 24-row terminal a popup can reach the dock rows**: overlays float
318
+ over the status/editor area (the frame adds 4 rows on top of the list cap),
319
+ so the popup bottom border and the status line share screen rows while a
320
+ popup is open. Cosmetic — popups are modal.
321
+ - **An overlay open at switch time does not follow a theme hot-switch**: the
322
+ transcript, dock and editor repaint immediately, but an open popup keeps the
323
+ palette it was built with until it closes (its submenus inherit the same
324
+ stale bundle).
package/bin/dsh-tui-pi ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ # Convenience launcher: boots the dsh `tui` profile that mounts @aiwayds/dsh-tui-pi.
3
+ # Requires the dsh CLI on PATH and @aiwayds/dsh-tui-pi installed into that profile
4
+ # (dsh plugin --profile tui add @aiwayds/dsh-tui-pi).
5
+ exec dsh --profile tui "$@"
@@ -0,0 +1,9 @@
1
+ # dsh-tui-pi bundle patch: mounts the pi-style TUI plugin over the profile's
2
+ # base bundle under the stable plugin id `tui-pi`. The plugin renders the
3
+ # terminal UI in-process with @earendil-works/pi-tui and talks to the dsh tree
4
+ # directly (ctx.agents / ctx.commands / session events), keeping dsh's slash
5
+ # commands untouched.
6
+
7
+ - insert:
8
+ - id: tui-pi
9
+ name: '@aiwayds/dsh-tui-pi'
@@ -0,0 +1,66 @@
1
+ /**
2
+ * APPEND_SYSTEM.md support — pi's convention (`~/.pi/agent/APPEND_SYSTEM.md`),
3
+ * dsh side: `~/.dsh/APPEND_SYSTEM.md`. A user-editable file whose content is
4
+ * appended to the system prompt of every agent this TUI creates. The TUI
5
+ * registers a system-prompt section whose text provider reads the file at
6
+ * each assembly, so edits apply to the very next request — no restart, no
7
+ * watcher.
8
+ *
9
+ * The TUI seeds a fresh file at first run (`ensureAppendSystemFile`) from
10
+ * the shipped template `templates/APPEND_SYSTEM.md` (the user's pi
11
+ * orchestrator-identity definition in English — content lives in the FILE,
12
+ * not in code), then maintains its marked todo-lifecycle section. An
13
+ * existing file is user-owned — the TUI only maintains its marker section
14
+ * there, idempotently (marker `<!-- dsh-tui-pi:todo-lifecycle -->`),
15
+ * atomically (tmp + rename) and best-effort — a failure is contained and
16
+ * reported, never breaks TUI startup. `migrateAgentsMdTodoSection` removes
17
+ * the todo section's earlier incarnation from `~/.dsh/AGENTS.md` (the
18
+ * pre-APPEND_SYSTEM.md delivery channel) so the guidance is not delivered
19
+ * twice.
20
+ */
21
+ /** Idempotency marker for the TUI-owned section of the append file. */
22
+ export declare const TODO_LIFECYCLE_MARKER = "<!-- dsh-tui-pi:todo-lifecycle -->";
23
+ /**
24
+ * The TUI-owned section: the model must clear the todo list when the work is
25
+ * done, because `todo/write` is a whole-list snapshot (last-write-wins) and
26
+ * an all-completed list would stay pinned above the chat input.
27
+ */
28
+ export declare const TODO_LIFECYCLE_SECTION = "<!-- dsh-tui-pi:todo-lifecycle -->\n## Todo list lifecycle (dsh-tui-pi)\n\nThe UI renders your todo list as a fixed panel above the chat input. Keep\nitems `pending` or `in_progress` while they are not done. When EVERY todo is\ncompleted \u2014 no pending or in-progress items remain \u2014 write an EMPTY todo list\n(`todos: []`) so the panel clears. Never leave a fully-completed list behind.\n";
29
+ /** The user-editable append file path (`$DSH_HOME/APPEND_SYSTEM.md`). */
30
+ export declare function appendSystemPath(home?: string): string;
31
+ /**
32
+ * The shipped English template (`templates/APPEND_SYSTEM.md`): the user's pi
33
+ * orchestrator-identity definition translated to English. The content lives
34
+ * in this FILE, not in code — installation seeds `~/.dsh/APPEND_SYSTEM.md`
35
+ * from it (a fresh file only; existing files are user-owned).
36
+ */
37
+ export declare function appendSystemTemplatePath(): string;
38
+ /**
39
+ * Synchronous read for the system-prompt section provider (the provider
40
+ * signature is sync; the file is small and read once per assembly).
41
+ * @returns the file content, or '' when missing/unreadable — an empty
42
+ * section is dropped by the prompt renderer.
43
+ */
44
+ export declare function readAppendSystem(path?: string): string;
45
+ /**
46
+ * Ensure the append file exists with the shipped template and the TUI's
47
+ * marked todo-lifecycle section: a missing file is seeded from
48
+ * `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
49
+ * by the marked section; an existing file is user-owned — only the marked
50
+ * section is appended when its marker is missing, and a marked file is left
51
+ * untouched.
52
+ * @param path - target file (injectable for tests).
53
+ * @param templatePath - the shipped template (injectable for tests).
54
+ * @returns an error message on failure, undefined on success (including the
55
+ * no-op case where the marker is already present).
56
+ */
57
+ export declare function ensureAppendSystemFile(path?: string, templatePath?: string): Promise<string | undefined>;
58
+ /**
59
+ * Migration: strip the TUI-owned todo-lifecycle block from `~/.dsh/AGENTS.md`
60
+ * (the earlier delivery channel). No-op when the marker is absent; the file
61
+ * is deleted when the strip leaves it empty. The block is removed by exact
62
+ * content match, so a user-edited block is left alone (best-effort). Every
63
+ * write here is atomic and failure-contained.
64
+ * @returns an error message on failure, undefined on success or no-op.
65
+ */
66
+ export declare function migrateAgentsMdTodoSection(path?: string): Promise<string | undefined>;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * APPEND_SYSTEM.md support — pi's convention (`~/.pi/agent/APPEND_SYSTEM.md`),
3
+ * dsh side: `~/.dsh/APPEND_SYSTEM.md`. A user-editable file whose content is
4
+ * appended to the system prompt of every agent this TUI creates. The TUI
5
+ * registers a system-prompt section whose text provider reads the file at
6
+ * each assembly, so edits apply to the very next request — no restart, no
7
+ * watcher.
8
+ *
9
+ * The TUI seeds a fresh file at first run (`ensureAppendSystemFile`) from
10
+ * the shipped template `templates/APPEND_SYSTEM.md` (the user's pi
11
+ * orchestrator-identity definition in English — content lives in the FILE,
12
+ * not in code), then maintains its marked todo-lifecycle section. An
13
+ * existing file is user-owned — the TUI only maintains its marker section
14
+ * there, idempotently (marker `<!-- dsh-tui-pi:todo-lifecycle -->`),
15
+ * atomically (tmp + rename) and best-effort — a failure is contained and
16
+ * reported, never breaks TUI startup. `migrateAgentsMdTodoSection` removes
17
+ * the todo section's earlier incarnation from `~/.dsh/AGENTS.md` (the
18
+ * pre-APPEND_SYSTEM.md delivery channel) so the guidance is not delivered
19
+ * twice.
20
+ */
21
+ import { readFile, rename, unlink, writeFile } from 'node:fs/promises';
22
+ import { readFileSync } from 'node:fs';
23
+ import { homedir } from 'node:os';
24
+ import { dirname, join, resolve } from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+ /** Idempotency marker for the TUI-owned section of the append file. */
27
+ export const TODO_LIFECYCLE_MARKER = '<!-- dsh-tui-pi:todo-lifecycle -->';
28
+ /**
29
+ * The TUI-owned section: the model must clear the todo list when the work is
30
+ * done, because `todo/write` is a whole-list snapshot (last-write-wins) and
31
+ * an all-completed list would stay pinned above the chat input.
32
+ */
33
+ export const TODO_LIFECYCLE_SECTION = `${TODO_LIFECYCLE_MARKER}
34
+ ## Todo list lifecycle (dsh-tui-pi)
35
+
36
+ The UI renders your todo list as a fixed panel above the chat input. Keep
37
+ items \`pending\` or \`in_progress\` while they are not done. When EVERY todo is
38
+ completed — no pending or in-progress items remain — write an EMPTY todo list
39
+ (\`todos: []\`) so the panel clears. Never leave a fully-completed list behind.
40
+ `;
41
+ /** Default harness home: `$DSH_HOME` or `~/.dsh`. */
42
+ function dshHome() {
43
+ return process.env.DSH_HOME ?? join(homedir(), '.dsh');
44
+ }
45
+ /** The user-editable append file path (`$DSH_HOME/APPEND_SYSTEM.md`). */
46
+ export function appendSystemPath(home = dshHome()) {
47
+ return join(home, 'APPEND_SYSTEM.md');
48
+ }
49
+ /**
50
+ * The shipped English template (`templates/APPEND_SYSTEM.md`): the user's pi
51
+ * orchestrator-identity definition translated to English. The content lives
52
+ * in this FILE, not in code — installation seeds `~/.dsh/APPEND_SYSTEM.md`
53
+ * from it (a fresh file only; existing files are user-owned).
54
+ */
55
+ export function appendSystemTemplatePath() {
56
+ return resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'APPEND_SYSTEM.md');
57
+ }
58
+ /** The user-global AGENTS.md path (the legacy delivery channel). */
59
+ function agentsMdPath(home = dshHome()) {
60
+ return join(home, 'AGENTS.md');
61
+ }
62
+ /**
63
+ * Synchronous read for the system-prompt section provider (the provider
64
+ * signature is sync; the file is small and read once per assembly).
65
+ * @returns the file content, or '' when missing/unreadable — an empty
66
+ * section is dropped by the prompt renderer.
67
+ */
68
+ export function readAppendSystem(path = appendSystemPath()) {
69
+ try {
70
+ return readFileSync(path, 'utf8');
71
+ }
72
+ catch {
73
+ return '';
74
+ }
75
+ }
76
+ /**
77
+ * Ensure the append file exists with the shipped template and the TUI's
78
+ * marked todo-lifecycle section: a missing file is seeded from
79
+ * `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
80
+ * by the marked section; an existing file is user-owned — only the marked
81
+ * section is appended when its marker is missing, and a marked file is left
82
+ * untouched.
83
+ * @param path - target file (injectable for tests).
84
+ * @param templatePath - the shipped template (injectable for tests).
85
+ * @returns an error message on failure, undefined on success (including the
86
+ * no-op case where the marker is already present).
87
+ */
88
+ export async function ensureAppendSystemFile(path = appendSystemPath(), templatePath = appendSystemTemplatePath()) {
89
+ let existing;
90
+ try {
91
+ existing = await readFile(path, 'utf8');
92
+ }
93
+ catch {
94
+ existing = '';
95
+ }
96
+ if (existing.includes(TODO_LIFECYCLE_MARKER))
97
+ return undefined;
98
+ if (existing !== '') {
99
+ return writeAtomically(path, `${existing.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`);
100
+ }
101
+ // Fresh file: seed from the shipped template, then the marked section. A
102
+ // missing template (broken tarball) degrades to the marked section alone.
103
+ let template;
104
+ try {
105
+ template = await readFile(templatePath, 'utf8');
106
+ }
107
+ catch {
108
+ template = '';
109
+ }
110
+ const seed = template === '' ? TODO_LIFECYCLE_SECTION : `${template.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`;
111
+ return writeAtomically(path, seed);
112
+ }
113
+ /**
114
+ * Migration: strip the TUI-owned todo-lifecycle block from `~/.dsh/AGENTS.md`
115
+ * (the earlier delivery channel). No-op when the marker is absent; the file
116
+ * is deleted when the strip leaves it empty. The block is removed by exact
117
+ * content match, so a user-edited block is left alone (best-effort). Every
118
+ * write here is atomic and failure-contained.
119
+ * @returns an error message on failure, undefined on success or no-op.
120
+ */
121
+ export async function migrateAgentsMdTodoSection(path = agentsMdPath()) {
122
+ let content;
123
+ try {
124
+ content = await readFile(path, 'utf8');
125
+ }
126
+ catch {
127
+ return undefined; // no AGENTS.md — nothing to migrate
128
+ }
129
+ if (!content.includes(TODO_LIFECYCLE_MARKER))
130
+ return undefined;
131
+ const next = content.trim() === TODO_LIFECYCLE_SECTION.trim()
132
+ ? ''
133
+ : content.split(TODO_LIFECYCLE_SECTION).join('').replace(/\n{3,}/gu, '\n\n').trim();
134
+ if (next === '') {
135
+ try {
136
+ await unlink(path);
137
+ }
138
+ catch (error) {
139
+ return error instanceof Error ? error.message : String(error);
140
+ }
141
+ return undefined;
142
+ }
143
+ return writeAtomically(path, `${next}\n`);
144
+ }
145
+ /** Atomic write (tmp + rename); the tmp file is cleaned up on failure. */
146
+ async function writeAtomically(path, content) {
147
+ const tmp = `${path}.tmp`;
148
+ try {
149
+ await writeFile(tmp, content, 'utf8');
150
+ await rename(tmp, path);
151
+ return undefined;
152
+ }
153
+ catch (error) {
154
+ try {
155
+ await unlink(tmp);
156
+ }
157
+ catch { /* already gone */ }
158
+ return error instanceof Error ? error.message : String(error);
159
+ }
160
+ }
161
+ //# sourceMappingURL=append-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append-system.js","sourceRoot":"","sources":["../src/append-system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Slash-command integration with dsh's own command registry.
3
+ *
4
+ * dsh-tui-pi never re-implements a command: autocomplete lists
5
+ * `ctx.commands.list(agent)` and submission routes through
6
+ * `ctx.commands.execute(agent, line, signal)`. Anything that is not a
7
+ * resolvable command falls through to the model as an ordinary prompt, so
8
+ * every command registered by dsh packages (plan, compact, feedback, export,
9
+ * permission, goal, …) works here unchanged and future registrations appear
10
+ * automatically.
11
+ *
12
+ * One exemption: TUI-owned commands that never touch the receiving agent
13
+ * (model pickers, settings browser, session info/resume) are dispatched
14
+ * locally when no live agent exists — dsh's execute path addresses an agent
15
+ * and would mint a throwaway session just to run them.
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import { type CommandDescriptor, type CommandResult } from '@deepseek-ai/dsh-commands';
19
+ import type { AutocompleteProvider } from '@earendil-works/pi-tui';
20
+ import type { DshSessionBridge } from './session.ts';
21
+ /** A TUI-owned command body that needs no receiving agent. */
22
+ export type LocalCommandHandler = (rawInput: string, signal: AbortSignal) => CommandResult | Promise<CommandResult>;
23
+ export declare class CommandService {
24
+ private readonly ctx;
25
+ private readonly bridge;
26
+ /** TUI-owned command bodies, dispatchable without a live agent. */
27
+ private readonly local;
28
+ constructor(ctx: Context, bridge: DshSessionBridge);
29
+ /**
30
+ * Register a local command body (the same handler the ctx.commands
31
+ * registration wraps). It is dispatched directly when no live agent
32
+ * exists, avoiding a throwaway session for agentless commands.
33
+ */
34
+ registerLocal(name: string, handler: LocalCommandHandler): void;
35
+ /**
36
+ * Try to run `line` as a dsh slash command.
37
+ * @returns `handled: true` when the line was admitted as a command (the
38
+ * caller must not forward it to the model), with an optional `error` or
39
+ * success `text` to surface; `handled: false` when it is not a command.
40
+ */
41
+ tryExecute(line: string, signal: AbortSignal): Promise<{
42
+ handled: boolean;
43
+ error?: string;
44
+ text?: string;
45
+ }>;
46
+ /**
47
+ * Effective command descriptors for the current agent. Warms the session on
48
+ * first use so `/` autocompletes before any prompt was sent.
49
+ */
50
+ list(): Promise<readonly CommandDescriptor[]>;
51
+ /** pi-tui autocomplete provider over the live command registry. */
52
+ autocompleteProvider(): AutocompleteProvider;
53
+ }