@zhangweiii/dsh-tui
English | 中文
An out-of-tree interactive terminal bundle for DeepSeek Harness. This directory is a self-contained npm package: it does not join or patch the DeepSeek Harness source workspace. Its cordis.patch.yml layers terminal Host services and the agent-preset roster over dsh-base, without mounting an HTTP server or browser runtime. At startup the renderer connects to a Web Host already listening at http://127.0.0.1:3080; when that address is unavailable it falls back to an InProcessApiClient over its own ctx.apiProxy.
Usage
dsh plugin --profile tui add @zhangweiii/dsh-tui
dsh --profile tui
dsh --profile tui "explain this repository"
dsh --profile tui --continue
dsh --profile tui --resume <session-id>
dsh --profile tui --cwd <path>
dsh --profile tui --connect http://127.0.0.1:8080
dsh --profile tui --standalone
The install command pulls the package straight from the npm registry (a local checkout also works with add .). The installed dsh command initializes the tui profile with dsh-base and records this package's declared dsh.bundle; neither the CLI nor its built-in profile templates contain TUI-specific code.
The package requires an installed dsh 0.1.0-rc.6 or compatible host and does not ship a second copy of the DSH core modules.
The profile creates a persisted session by default and attaches it to the workspace for its working directory, creating or reusing that workspace record so Web lists the session in the same group. When the default Web Host is reachable, both clients use that Host's workspace registry and event streams; Web therefore receives the grouping mutation and every later session event live. --connect selects a non-default Web origin, while --standalone skips discovery and forces the isolated in-process Host. An explicit connection failure stops startup; implicit discovery failure falls back locally. A remote new session defaults to the TUI process's current directory rather than the Web process's directory.
Plain HTTP is limited to localhost, *.localhost, 127.0.0.0/8, and [::1]; every other Host must use HTTPS and must be trusted by the user. An unexpectedly closed remote WebSocket reconnects automatically. When the new subscription reports a later durable sequence, the TUI refetches history to recover records committed while disconnected.
Resuming or continuing a stored root session with a recorded directory performs the same idempotent attachment, so an older cwd-only session joins that directory's workspace; a session without a recorded directory remains ungrouped. --continue selects the most recently updated root session and creates one when none exists; --resume selects an exact persisted id. --resume and --continue are mutually exclusive, as are --connect and --standalone. The application requires an interactive TTY.
The composer uses @earendil-works/pi-tui's Editor: Enter queues a message, Alt+Enter steers the active turn, Shift+Enter inserts a newline, Escape clears the draft or cancels the active turn, Backspace/terminal DEL edits before the cursor, Up/Down traverses input history, and Ctrl+C exits. Built-in autocomplete proactively displays matches for a single slash token; Up/Down selects and Tab completes. Sessions, models, presets, subagents, setting namespaces, providers/models, and directory browsing use SelectList with Up/Down and Enter. Approval prompts accept y once or n to reject. Structured questions use a separate editor, preserving the ordinary composer draft; a single-select question with options renders as an arrow-navigable menu (the options are listed once, no separate numbered header) where Up/Down moves the highlight, Enter confirms the highlighted option, and typing any printable character (or selecting the trailing ✎ 其他 / 自定义… entry) opens a free-text editor — Escape returns to the menu. Multi-select and option-less questions instead accept option numbers, comma-separated multi-select numbers, or free text. In every question, Escape cancels the complete request. After the last question the batch moves to a confirmation summary listing each "question → your answer"; Up/Down navigates it, Enter on a question row reopens that single entry for revision, Enter on the trailing 确认提交全部回答 row sends the whole batch, and Escape cancels the request.
TuiAltScreen, VStack, and ScrollView provide the fixed-height layout. pi-tui owns alternate-screen setup, synchronized differential updates, mouse and trackpad scrolling, the scrollbar, text selection, and terminal restoration; this package contains no terminal repaint or scroll-offset algorithm. The mouse wheel and PageUp/PageDown scroll the transcript, Ctrl+Shift+F searches it, Ctrl+Shift+Up/Down jumps between user prompts, and Ctrl+Shift+Home/End moves to its boundaries. Streaming follows the end only while the reader remains there, preserving a manually selected history position until the reader returns to the bottom. Injected context rows (skill catalog, plugin context, workspace instructions, recalls), tool output, compaction, and retry summaries all render folded into a one-line header like the Web disclosure row, so long startup context and verbose tool output do not crowd the transcript; a running row stays unfolded so live output remains readable. Ctrl+Shift+E unfolds the most recent folded row, and pressing it again peels progressively older ones until all are unfolded, at which point the next press folds them back together. Ctrl+T expands or collapses the todo/job activity dock above the composer.
Terminal management commands
Commands owned by the TUI open a terminal-native panel. Other slash commands remain ordinary Harness commands or skill invocations.
| Command | Action |
|---|---|
/help, /status, /close |
Show command help, runtime and projection status, or close the current panel. |
/sessions [query], /new [cwd], /resume [id-or-prefix] |
Select, search, or create persisted sessions; /sessions and /resume without an id open a picker. |
/rename <title>, /fork [event-seq], /older |
Rename, fork, or page backward through durable history. |
/archive [session-id] --yes, /export [path] [--descendants] |
Archive a session or export its logs and referenced media. |
/models, /model [provider/model] [effort] |
Select a model, or switch directly with an explicit route. |
/providers, /provider-models [provider], /discover-models <settings-ns> … |
Select a provider/model or discover endpoint models. |
/provider-add [new-provider-id] [--name <display-name>] [--base-url <url>] [--api <protocol>] [--key-env <environment-variable>] [--model <id>…] [--discover] |
Uses a progressive flow like pi's /login: the provider list has its own fuzzy-search input, and choosing an existing provider switches to a separate single-line API-key input that saves immediately; custom routes ask for id, endpoint, a Host-schema protocol, optional key, and models one step at a time before a compact review. Custom routes are written only to llm-pi-ai/providers.<id>; addresses come from Host settingsNs/settingsPath, and key values travel one way through credentials.set. |
/permission [preset] |
Arrow-select, or switch directly, the permission mode (from the permissions projection's preset list). |
/presets, /preset [id], /preset-read <id> |
Select a blank session's preset or inspect preset content. |
/preset-copy <source> <new-id> [name], /preset-open <id>, /preset-remove <id> --yes |
Author and remove user presets through the shared preset service. |
/queue, /queue-edit <item-id> <text>, /queue-steer <item-id> |
Inspect and mutate pending inbox items by stable id. |
/queue-remove <item-id> --yes |
Remove one pending inbox occurrence. |
/jobs, /job-kill <id-or-prefix> --yes |
List background jobs, or stop one (local standalone only). |
/workspaces, /workspace-new <path>, /workspace-rename <id> <title> |
Inspect, create, or rename workspace records. |
/workspace-move <id> [before-id|end], /workspace-session-move <id> <session-id> [before-id|end] |
Reorder workspaces or their session entries. |
/workspace-delete <id> --yes |
Unregister a workspace without deleting its directory or session logs. |
/settings, /settings-show <ns> [--schema], /settings-open |
Select a setting namespace, inspect its effective value and optional schema, or open the configuration file. |
/settings-set <ns> <json-pointer> <json> |
Update a setting through its revision guard. |
/settings-unset <ns> <json-pointer> --yes, /settings-reset <ns> --yes |
Remove a field or reset one namespace, including its stored secrets. |
/credentials <REF> …, /credential-set <REF> <VALUE_ENV_VAR> |
Inspect credentials or write one from an environment variable without putting its value in input history. |
/credential-unset <REF> --yes |
Remove a stored credential. |
/goal <objective>, /goal-show, /goal-edit <objective>, /goal-pause, /goal-resume, /goal-complete, /goal-clear --yes |
Create a goal, and inspect and mutate the projected goal through its revision guard. |
/skills, /subagents, /subagent [id-or-prefix], /back |
Inspect skills, select and navigate child transcripts, continue a continuable child, and return. |
/feedback <message-id|last> <positive|negative> [note], /feedback-clear <message-id|last> --yes |
Create, replace, or remove assistant-message feedback. |
/image <path> [caption], /image-steer <path> [caption] |
Admit a raster image through the Host and queue or steer it. |
/save-image <attachment-id> [path] |
Save a transcript image with create-only file semantics. |
/directories [path], /mkdir <parent> <name>, /open <path> |
Browse, create, or ask the operating system to open paths. |
/plugins, /host |
Inspect the live Host plugin inventory and Host capabilities. |
/cordis, /cordis-run <plugin-id> [package-id] |
Inspect dynamic packages or run/update a Host-only package. |
/cordis-stop <plugin-id> --yes, /cordis-remove <plugin-id> --yes |
Stop a dynamic package or remove its complete definition. |
Paths containing whitespace can be quoted. Destructive commands require a trailing --yes. Export and image-save commands use create-only writes and refuse to overwrite an existing file. /credential-set reads the secret from the named environment variable, so the value never enters the terminal's command history or transcript.
Shared /plan and /compact commands follow the Harness command or skill path like any other unknown slash command. Their durable lifecycle returns to the same transcript. /permission is intercepted by the TUI to open an arrow-navigable list of permission modes (from the permissions projection); picking one forwards /permission <preset> back to the Harness to perform the switch.
Conversation state comes from durable history plus the mux and Host streams. The terminal folds assistant text and reasoning, injected model context, command, compaction, and model-retry lifecycles, Host-provided tool presentation, successful mutation locations as deduplicated produced-file rows, durable workflow runs and their member status, stable assistant message ids, pending queue items, background jobs, todos, goal and other projection values, running state, and live errors. Settled assistant and reasoning text uses pi-tui Markdown for headings, emphasis, lists, quotes, code blocks, links, and tables; raw HTML is displayed as text and terminals with OSC 8 support expose clickable links, while the streaming tail remains compact plain text. There is no persistent header; a footer below the composer shows the running state, agent preset, model, cwd, session turns and steps, cumulative tokens, context occupancy, and the permission mode, surfacing plan mode only while active or switching; it collapses to a single line when wide enough and otherwise splits into two width-balanced lines. The compact activity dock above the composer appears only for unfinished todos or active goal, queue, job, and workflow summaries. With unfinished todos it collapses by default to one line showing the progress count (completed/total), the currently executing todo, and the background job summary in a single strip; press Ctrl+T to expand it into a full, sectioned list — plans under 待办 (completed ✓ grey, running ◆ highlighted, pending ·) and live processes under 任务 (running ●, stopping ◌, failed ✗; a job that settled no longer occupies the dock) each in their own section — and press again to collapse. /status exposes the complete projection breakdown and image limits. ScrollView constrains the transcript by rendered lines, so long content cannot overwrite the fixed activity dock, editor, or footer. Produced paths can be passed directly to /open; unfinished tools and workflows become interrupted when their owning turn closes. The history/live boundary is sequence-deduplicated, surface replacements do not render compaction checkpoints twice, and answerable approval and question frames echo their original RPC identity.
Model Experience
Terminal prompt submission
What the model sees
This package adds no system prompt or tool schema. Text submitted in the terminal reaches ApiProxy as ordinary user content; the composed base bundle and selected agent preset own the system prompt, tools, and other model-visible context. Unknown slash commands follow the ordinary session prompt path. Terminal-native commands call the same Host business operations as Web; when an owning Host service deliberately queues user context, such as a goal or dynamic-Cordis lifecycle change, the model sees that service-owned context on its next step.
Token effect
Read-only terminal panels add zero model tokens. An ordinary prompt adds only the user content and the request envelope already owned by the active session composition. A Host mutation adds only any model-visible context already specified by that shared Host contract; the TUI adds no wrapper text of its own.
KV Cache effect
The terminal does not rewrite the request prefix. Ordinary prompts and Host-authored context append at the durable session boundary, while model or preset changes can affect reuse only through the Host-owned route and composition they select.
Known Limitations and Deferred Work
- Trajectory is intentionally absent — this is the one Web feature outside the TUI parity target.
- Browser Client halves are not emulated — Host-only dynamic Cordis packages can run in the terminal. A model-driven package that requires a browser Client half is rejected immediately with an actionable result instead of leaving the turn suspended.
- Browser appearance controls are not emulated — DOM component slots, drag-and-drop, browser routing, and Web theme or locale controls are platform presentation rather than Host domain behavior. The TUI uses keyboard commands, filesystem paths, terminal colors, and the process locale instead.
- Concurrent display requires the shared Web Host — default discovery covers the shipped Web port. Pass
--connectwhen Web listens elsewhere.--standaloneretains the isolated Host path and must not run concurrently with Web against the same Harness home because process-local domain events and JSON storage do not coordinate across Host processes. - Some Host-local management commands remain standalone-only — message feedback, session export, live plugin inventory, dynamic Cordis control, and background-job kill intentionally sit outside
IApiClient; their TUI commands report that the capability is unavailable when the terminal is connected to a Web Host. Chat, grouping, history, models, projections, queue, approvals, questions, workspaces, settings, images, skills, goals, and subagents use the remote Host contract. - One selected session per terminal — other sessions continue on the Host, but this process renders one transcript at a time and keeps only the current answerable interaction in its panel.
ctx.appExitis launcher-owned — mounting the bundle outsidedshrequires the host to provide the bounded exit request.
Releasing
Publishes run through the Release workflow: pushing a version tag runs the test matrix, publishes to npm with provenance, then creates the matching GitHub Release. The workflow is idempotent: it skips an npm version that already exists and does not recreate an existing GitHub Release. The dist-tag follows the version — prerelease versions publish under beta, stable versions under latest:
npm version prerelease --preid beta # 0.2.0 -> 0.2.1-beta.0 -> npm dist-tag "beta"
npm version minor # 0.2.0 -> 0.3.0 -> npm dist-tag "latest"
git push --follow-tags # the pushed v* tag triggers the release
The workflow authenticates through npm trusted publishing (GitHub OIDC) — no token secret is required. In the package settings on npmjs.com, configure GitHub Actions with organization or user zhangweiii, repository dsh-tui, workflow filename release.yml, no environment, and allow the npm publish action. Locally, npm run release performs the same check-then-publish flow with the same dist-tag rules.
No comments yet. Be the first to write one.