@aiwayds/dsh-tui-pi 2.17.0 → 2.18.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/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/index.js +70 -124
- package/lib/index.js.map +1 -1
- package/lib/selectors.d.ts +7 -5
- package/lib/selectors.js +19 -23
- package/lib/selectors.js.map +1 -1
- package/lib/theme/index.d.ts +6 -3
- package/lib/theme/index.js +30 -2
- package/lib/theme/index.js.map +1 -1
- package/lib/theme/palette.d.ts +2 -0
- package/lib/theme/palette.js +1 -1
- package/lib/theme/palette.js.map +1 -1
- package/lib/theme/registry.d.ts +49 -0
- package/lib/theme/registry.js +211 -0
- package/lib/theme/registry.js.map +1 -0
- package/lib/theme-preview.d.ts +98 -0
- package/lib/theme-preview.js +266 -0
- package/lib/theme-preview.js.map +1 -0
- package/lib/theme-settings.js +6 -5
- package/lib/theme-settings.js.map +1 -1
- package/lib/tui.d.ts +3 -0
- package/lib/tui.js +1 -1
- package/lib/tui.js.map +1 -1
- package/package.json +4 -2
- package/skills/dsh-tui-pi-config/SKILL.md +11 -4
- package/themes/ayu-light.json +25 -0
- package/themes/catppuccin-latte.json +25 -0
- package/themes/catppuccin-mocha.json +25 -0
- package/themes/dracula-light.json +25 -0
- package/themes/dracula.json +25 -0
- package/themes/github-dark.json +25 -0
- package/themes/github-light.json +25 -0
- package/themes/gruvbox-dark.json +25 -0
- package/themes/gruvbox-light.json +25 -0
- package/themes/material-light.json +25 -0
- package/themes/monokai.json +25 -0
- package/themes/nord-dark.json +25 -0
- package/themes/nord-light.json +25 -0
- package/themes/one-dark.json +25 -0
- package/themes/one-light.json +25 -0
- package/themes/solarized-dark.json +25 -0
- package/themes/solarized-light.json +25 -0
- package/themes/synthwave.json +25 -0
- package/themes/tokyo-night-light.json +25 -0
- package/themes/tokyo-night.json +25 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-tui-pi
|
|
4
4
|
|
|
5
|
-
A fully-featured pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding-agent experience: /history look-back & fork-at-turn, guided preset switching, live subagent steering, model profiles,
|
|
5
|
+
A fully-featured pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding-agent experience: /history look-back & fork-at-turn, guided preset switching, live subagent steering, model profiles, 20+ built-in themes (10 light + 10 dark) with user theme directory discovery, and a powerline footer.
|
|
6
6
|
|
|
7
7
|
**Requires dsh >= 0.1.5-rc.2** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.** A startup guard logs a one-line warning and exits cleanly when the host is older than the floor (opt out with `DSH_TUI_SKIP_HOST_CHECK=1`). See [ADR 0002](docs/adr/0002-target-dsh-0.1.2-alpha.3-single-target.md) for the now-superseded alpha single-target decision.
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ https://github.com/user-attachments/assets/67a7c6ca-ff42-4005-b543-437ba61771bb
|
|
|
25
25
|
- [**Agent preset switching**](docs/features/preset-switch.md) — `/preset` between the shipped agent compositions (`standard`, `minimal`, …); a switch is confirmed and starts a NEW session on the preset (the current one stays resumable); what a preset really gates.
|
|
26
26
|
- [**Sessions & resume**](docs/features/sessions-resume.md) — sessions stay tidy automatically and resume in a few keystrokes; the host's kernel write lease keeps the log single-writer across processes.
|
|
27
27
|
- [**History browser**](docs/features/history.md) — `/history` opens a fixed two-pane look-back over the session: completed turns on the left, the selected turn's replies on the right; copy a prompt back to the editor, or cold-read any stored session without resuming it (read-only).
|
|
28
|
-
- [**Themes**](docs/features/themes.md) —
|
|
28
|
+
- [**Themes**](docs/features/themes.md) — 20 built-in palettes (10 light + 10 dark) plus user theme discovery (`~/.dsh/themes/`); hot-switchable, `auto` follows your terminal.
|
|
29
29
|
- [**Search, selection & images**](docs/features/search-selection-images.md) — `Ctrl+Shift+F` over the whole transcript, drag-select copies to the OS clipboard, attachments from web/Feishu render inline, LaTeX replies draw as Unicode math.
|
|
30
30
|
- [**Slash commands**](docs/features/slash-commands.md) — `/model`, `/resume`, `/btw`, … plus everything dsh-native.
|
|
31
31
|
- [**Startup plugin tree**](docs/features/startup-tree.md) — every profile plugin with its installed npm version, printed at launch.
|
|
@@ -125,7 +125,7 @@ Every knob lives under the `dsh-tui` settings namespace in `~/.dsh/settings.yaml
|
|
|
125
125
|
|
|
126
126
|
| Key | Default | Meaning |
|
|
127
127
|
|---|---|---|
|
|
128
|
-
| `theme` | `auto` | Color scheme: `auto` (follow the terminal) / `light` / `dark
|
|
128
|
+
| `theme` | `auto` | Color scheme: `auto` (follow the terminal) / `light` / `dark` / any registered theme name (`/theme` writes back to the same key) |
|
|
129
129
|
| `panelHeight` | `'1'` | Think/tool panel height: `'1'` / `'5'` / `'7'` / `'10'` / `'all'` (full content) |
|
|
130
130
|
| `maxAgents` | `4` | Max concurrently running subagents, `0` = unlimited (hot-tunable in `/agents → l` limits) |
|
|
131
131
|
| `maxRounds` | `75` | Max assistant messages per subagent before a wrap-up request is injected; `0` = unlimited |
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-tui-pi
|
|
4
4
|
|
|
5
|
-
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的功能齐全的 pi 风格终端 UI——一套把 dsh 变成 pi 式编码代理体验的插件套件:/history 回看与任意轮分叉、确认式 preset 切换、subagent 实时操控、模型档案、
|
|
5
|
+
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的功能齐全的 pi 风格终端 UI——一套把 dsh 变成 pi 式编码代理体验的插件套件:/history 回看与任意轮分叉、确认式 preset 切换、subagent 实时操控、模型档案、20+ 内置主题(10 亮 + 10 暗,支持用户主题目录自动发现)和 powerline 状态栏。
|
|
6
6
|
|
|
7
7
|
**要求 dsh >= 0.1.5-rc.2** —— 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。**不再支持 alpha 线。**宿主低于下限时,启动守卫会打一行 warning 并干净退出(可用 `DSH_TUI_SKIP_HOST_CHECK=1` 跳过)。alpha 单目标决策的历史见 [ADR 0002](docs/adr/0002-target-dsh-0.1.2-alpha.3-single-target.md)(已被取代)。
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ https://github.com/user-attachments/assets/67a7c6ca-ff42-4005-b543-437ba61771bb
|
|
|
24
24
|
- [**模型 profile 与收藏**](docs/features/model-profiles.md) —— 按项目切换整套模型配置,选择器保持精简。
|
|
25
25
|
- [**Agent preset 切换**](docs/features/preset-switch.md) —— `/preset` 在内置 agent 组合(`standard`、`minimal`……)间切换;切换需确认并会开启新会话(当前会话仍可 /resume 恢复);preset 到底管什么。
|
|
26
26
|
- [**Sessions 会话与恢复**](docs/features/sessions-resume.md) —— 会话自动保持整洁、几次按键恢复;跨进程写者守卫保证日志单写者。
|
|
27
|
-
- [**Themes 主题**](docs/features/themes.md) ——
|
|
27
|
+
- [**Themes 主题**](docs/features/themes.md) —— 20 个内置配色(10 亮 + 10 暗)+ 用户主题目录发现(`~/.dsh/themes/`);热切换,`auto` 跟随终端。
|
|
28
28
|
- [**搜索、选择与图片**](docs/features/search-selection-images.md) —— `Ctrl+Shift+F` 全文搜索、划选复制到系统剪贴板、web/飞书附件内联渲染、LaTeX 转 Unicode 数学。
|
|
29
29
|
- [**斜杠命令**](docs/features/slash-commands.md) —— `/model`、`/resume`、`/btw`、`/profile-switch`……外加全部 dsh 原生命令。
|
|
30
30
|
- [**启动插件树**](docs/features/startup-tree.md) —— 启动即打印每个 profile 插件及其安装的 npm 版本。
|
|
@@ -121,7 +121,7 @@ dsh plugin --profile tui add @aiwayds/dsh-topics-memory
|
|
|
121
121
|
|
|
122
122
|
| 键 | 默认 | 作用 |
|
|
123
123
|
|---|---|---|
|
|
124
|
-
| `theme` | `auto` | 配色:`auto`(跟随终端)/`light`/`dark
|
|
124
|
+
| `theme` | `auto` | 配色:`auto`(跟随终端)/`light`/`dark`/任意已注册主题名;`/theme` 写回同一段 |
|
|
125
125
|
| `panelHeight` | `'1'` | think/tool 面板高度:`'1'`/`'5'`/`'7'`/`'10'`/`'all'`(完整内容) |
|
|
126
126
|
| `maxAgents` | `4` | 并发子代理上限,`0` = 不限(`/agents → l` limits 面板可热调) |
|
|
127
127
|
| `maxRounds` | `75` | 每个子代理的 assistant 消息数上限,到达后注入收尾请求;`0` = 不限 |
|
package/lib/index.js
CHANGED
|
@@ -57,8 +57,9 @@ import { openSubmitRouteDialog } from "./route-dialog.js";
|
|
|
57
57
|
import { BtwController, BTW_IDLE_NOTICE, BTW_USAGE, buildBtwSnapshot, parseBtwInput, resolveSnapshotLimit, } from "./btw.js";
|
|
58
58
|
import { BtwOverlayWire } from "./btw-overlay.js";
|
|
59
59
|
import { openPendingQueuePanel } from "./queue-panel.js";
|
|
60
|
-
import { ansiFg, BOLD,
|
|
60
|
+
import { ansiFg, BOLD, RESET, resolveTheme } from "./theme/index.js";
|
|
61
61
|
import { rgbIsLight } from "./theme/palette.js";
|
|
62
|
+
import { discoverThemes, scanThemeDir, userThemesDir } from "./theme/registry.js";
|
|
62
63
|
import { clipToWidth } from "./text.js";
|
|
63
64
|
import {} from "./keymap.js";
|
|
64
65
|
import { keybindingsPath, loadKeyBindings, openHotkeysManager } from "./hotkeys.js";
|
|
@@ -174,6 +175,15 @@ export function apply(ctx) {
|
|
|
174
175
|
// silently dropping the bundled usage/config guide.
|
|
175
176
|
ctx.skills.registerProvider(() => skillProvider);
|
|
176
177
|
let handle;
|
|
178
|
+
// Theme registry: bundled themes/ plus $DSH_HOME/themes (user overrides
|
|
179
|
+
// bundled by name). Built ONCE per apply() and handed to every resolveTheme
|
|
180
|
+
// / pickTheme call site (startup, the settings watch sink, the /theme
|
|
181
|
+
// handler), so a custom theme named by DSH_TUI_THEME or by the persisted
|
|
182
|
+
// preference resolves through the same snapshot. `themeUserNames` marks
|
|
183
|
+
// which registry ids are user-owned (for the picker's "user theme" /
|
|
184
|
+
// "built-in" labels).
|
|
185
|
+
const themeRegistry = discoverThemes({ home: dshHome() });
|
|
186
|
+
const themeUserNames = new Set(scanThemeDir(userThemesDir(dshHome())).keys());
|
|
177
187
|
// Live-session handle for the startup janitor (assigned inside the
|
|
178
188
|
// render effect below, where the bridge is constructed): retention
|
|
179
189
|
// polls the CURRENT session id at selection time and again right
|
|
@@ -525,6 +535,7 @@ export function apply(ctx) {
|
|
|
525
535
|
}
|
|
526
536
|
},
|
|
527
537
|
themePreference,
|
|
538
|
+
themeRegistry,
|
|
528
539
|
});
|
|
529
540
|
handle = ui;
|
|
530
541
|
/**
|
|
@@ -591,7 +602,7 @@ export function apply(ctx) {
|
|
|
591
602
|
// 'auto' subscribes to the terminal's live color-scheme pushes; an
|
|
592
603
|
// explicit pin opts out. resolveTheme guards env-pinned displays.
|
|
593
604
|
ui.tui.setTerminalColorSchemeNotifications(pref === 'auto');
|
|
594
|
-
applyTheme(resolveTheme(process.env, pref));
|
|
605
|
+
applyTheme(resolveTheme(process.env, pref, themeRegistry));
|
|
595
606
|
};
|
|
596
607
|
const renderer = new TranscriptRenderer(ui.transcript, ui.theme, () => ui.requestRender(), startupInfo);
|
|
597
608
|
// Startup notices for a broken/misleading keybindings file — the TUI keeps
|
|
@@ -851,6 +862,20 @@ export function apply(ctx) {
|
|
|
851
862
|
// Wiring through the handle: the editor is rebuilt on theme hot-swap, and
|
|
852
863
|
// these providers are re-applied to the replacement instance.
|
|
853
864
|
ui.setEditorAutocompleteProvider(commands.autocompleteProvider());
|
|
865
|
+
// One registration for both dispatch surfaces: CommandService.registerLocal
|
|
866
|
+
// (direct dispatch when no agent exists — no throwaway session) plus
|
|
867
|
+
// ctx.commands.register (discovery + lifecycle events against a live
|
|
868
|
+
// agent). Agentless bodies go through both, so autocomplete matches web.
|
|
869
|
+
const registerLocalCommand = (name, description, handler) => {
|
|
870
|
+
commands.registerLocal(name, handler);
|
|
871
|
+
ctx.effect(() => ctx.commands.register({
|
|
872
|
+
name,
|
|
873
|
+
description,
|
|
874
|
+
handler: invocation => handler(invocation.rawInput, invocation.signal),
|
|
875
|
+
}), `dsh-tui-pi: /${name}`);
|
|
876
|
+
};
|
|
877
|
+
/** Error message for notices — unknown catch values stringify safely. */
|
|
878
|
+
const messageOf = (e) => e instanceof Error ? e.message : String(e);
|
|
854
879
|
// ------------------------------------------------------------ /btw (TUI-owned; ADR 0001) --
|
|
855
880
|
// By-the-way side questions: one tool-less one-shot model call over a
|
|
856
881
|
// read-only recent-conversation snapshot, streaming into a framed
|
|
@@ -928,12 +953,7 @@ export function apply(ctx) {
|
|
|
928
953
|
return { kind: 'error', text: `btw rejected — ${result.reason}.` };
|
|
929
954
|
}
|
|
930
955
|
};
|
|
931
|
-
|
|
932
|
-
ctx.effect(() => ctx.commands.register({
|
|
933
|
-
name: 'btw',
|
|
934
|
-
description: 'Ask a side question while the main task runs (temp overlay, not kept)',
|
|
935
|
-
handler: invocation => btwHandler(invocation.rawInput, invocation.signal),
|
|
936
|
-
}), 'dsh-tui-pi: /btw');
|
|
956
|
+
registerLocalCommand('btw', 'Ask a side question while the main task runs (temp overlay, not kept)', btwHandler);
|
|
937
957
|
// ------------------------------------------- TUI-owned slash commands --
|
|
938
958
|
// Web-surface parity: `model` is a browser client contribution there and
|
|
939
959
|
// `export` a web-only download plugin — the terminal gets native
|
|
@@ -960,12 +980,7 @@ export function apply(ctx) {
|
|
|
960
980
|
text: persistError === undefined ? modelText : `${modelText} · ⚠ not persisted: ${persistError}`,
|
|
961
981
|
};
|
|
962
982
|
};
|
|
963
|
-
|
|
964
|
-
ctx.effect(() => ctx.commands.register({
|
|
965
|
-
name: 'model',
|
|
966
|
-
description: 'Select the model (and think level) for this conversation',
|
|
967
|
-
handler: invocation => modelHandler(invocation.rawInput, invocation.signal),
|
|
968
|
-
}), 'dsh-tui-pi: /model');
|
|
983
|
+
registerLocalCommand('model', 'Select the model (and think level) for this conversation', modelHandler);
|
|
969
984
|
// /agents: manage agent definition markdown files (model, think level,
|
|
970
985
|
// spawn depth) — the terminal counterpart of pi's /fun-agent-cfg. An
|
|
971
986
|
// optional argument preselects one agent by name.
|
|
@@ -976,12 +991,7 @@ export function apply(ctx) {
|
|
|
976
991
|
return { kind: 'success', text: 'Agents unchanged.' };
|
|
977
992
|
return { kind: 'success', text: result };
|
|
978
993
|
};
|
|
979
|
-
|
|
980
|
-
ctx.effect(() => ctx.commands.register({
|
|
981
|
-
name: 'agents',
|
|
982
|
-
description: 'Manage agent definitions (model, think level, spawn depth) from markdown files',
|
|
983
|
-
handler: invocation => agentsHandler(invocation.rawInput, invocation.signal),
|
|
984
|
-
}), 'dsh-tui-pi: /agents');
|
|
994
|
+
registerLocalCommand('agents', 'Manage agent definitions (model, think level, spawn depth) from markdown files', agentsHandler);
|
|
985
995
|
// /subagents: the command twin of Ctrl+G — pick a running (or recently
|
|
986
996
|
// settled) subagent and inspect its live transcript in the 80% viewer.
|
|
987
997
|
// Same flow as the key path; empty board closes immediately.
|
|
@@ -989,12 +999,7 @@ export function apply(ctx) {
|
|
|
989
999
|
await openSubagentViewer(ctx, ui.tui, ui.theme, bridge, refocusEditor);
|
|
990
1000
|
return { kind: 'success', text: 'Subagent viewer closed.' };
|
|
991
1001
|
};
|
|
992
|
-
|
|
993
|
-
ctx.effect(() => ctx.commands.register({
|
|
994
|
-
name: 'subagents',
|
|
995
|
-
description: 'Browse subagents and inspect their live transcript',
|
|
996
|
-
handler: invocation => subagentsHandler(invocation.rawInput, invocation.signal),
|
|
997
|
-
}), 'dsh-tui-pi: /subagents');
|
|
1002
|
+
registerLocalCommand('subagents', 'Browse subagents and inspect their live transcript', subagentsHandler);
|
|
998
1003
|
// /think: cycle the current model's reasoning effort without re-picking
|
|
999
1004
|
// the model. A no-session /think still lands in the selection ref and
|
|
1000
1005
|
// survives the lazy session creation (bridge seeds only an empty ref).
|
|
@@ -1037,12 +1042,7 @@ export function apply(ctx) {
|
|
|
1037
1042
|
text: persistError === undefined ? thinkText : `${thinkText} · ⚠ not persisted: ${persistError}`,
|
|
1038
1043
|
};
|
|
1039
1044
|
};
|
|
1040
|
-
|
|
1041
|
-
ctx.effect(() => ctx.commands.register({
|
|
1042
|
-
name: 'think',
|
|
1043
|
-
description: 'Switch the current model\'s think (reasoning) level',
|
|
1044
|
-
handler: invocation => thinkHandler(invocation.rawInput, invocation.signal),
|
|
1045
|
-
}), 'dsh-tui-pi: /think');
|
|
1045
|
+
registerLocalCommand('think', 'Switch the current model\'s think (reasoning) level', thinkHandler);
|
|
1046
1046
|
/**
|
|
1047
1047
|
* Record one committed preset as THIS workspace's remembered selection
|
|
1048
1048
|
* (`$DSH_HOME/workspace-presets.json`, keyed by the session backend's
|
|
@@ -1179,12 +1179,7 @@ export function apply(ctx) {
|
|
|
1179
1179
|
persistRememberedPreset(target.id);
|
|
1180
1180
|
return { kind: 'success', text: outcome.message };
|
|
1181
1181
|
};
|
|
1182
|
-
|
|
1183
|
-
ctx.effect(() => ctx.commands.register({
|
|
1184
|
-
name: 'preset',
|
|
1185
|
-
description: 'Switch the agent preset (starts a new session on it; /preset next cycles)',
|
|
1186
|
-
handler: invocation => presetHandler(invocation.rawInput, invocation.signal),
|
|
1187
|
-
}), 'dsh-tui-pi: /preset');
|
|
1182
|
+
registerLocalCommand('preset', 'Switch the agent preset (starts a new session on it; /preset next cycles)', presetHandler);
|
|
1188
1183
|
// Model profiles moved to the dsh-profile-switch plugin (ask-user based,
|
|
1189
1184
|
// surface-agnostic). What stays HERE is the live-selection bridge it
|
|
1190
1185
|
// consumes: the TUI's /model + /think ride the same selection ref, and
|
|
@@ -1226,12 +1221,7 @@ export function apply(ctx) {
|
|
|
1226
1221
|
}, refocusEditor);
|
|
1227
1222
|
return { kind: 'success', text: agent === undefined ? 'No active session.' : 'Session info shown.' };
|
|
1228
1223
|
};
|
|
1229
|
-
|
|
1230
|
-
ctx.effect(() => ctx.commands.register({
|
|
1231
|
-
name: 'session',
|
|
1232
|
-
description: 'Show the current session\'s info (id, model, stats)',
|
|
1233
|
-
handler: invocation => sessionHandler(invocation.rawInput, invocation.signal),
|
|
1234
|
-
}), 'dsh-tui-pi: /session');
|
|
1224
|
+
registerLocalCommand('session', 'Show the current session\'s info (id, model, stats)', sessionHandler);
|
|
1235
1225
|
// /resume: pick a persisted session, validate its log, swap the live
|
|
1236
1226
|
// agent for it, and rebuild transcript + stats from the stored events.
|
|
1237
1227
|
const resumeHandler = async () => {
|
|
@@ -1245,7 +1235,7 @@ export function apply(ctx) {
|
|
|
1245
1235
|
picked = await pickPersistedSession(ctx, ui.tui, ui.theme, currentId === undefined ? undefined : String(currentId), refocusEditor, resumeSettings);
|
|
1246
1236
|
}
|
|
1247
1237
|
catch (error) {
|
|
1248
|
-
const message =
|
|
1238
|
+
const message = messageOf(error);
|
|
1249
1239
|
return { kind: 'error', text: message };
|
|
1250
1240
|
}
|
|
1251
1241
|
if (picked.kind === 'empty') {
|
|
@@ -1281,7 +1271,7 @@ export function apply(ctx) {
|
|
|
1281
1271
|
resumed = await bridge.resume(target.id);
|
|
1282
1272
|
}
|
|
1283
1273
|
catch (error) {
|
|
1284
|
-
const message =
|
|
1274
|
+
const message = messageOf(error);
|
|
1285
1275
|
if (error instanceof SessionAlreadyOwnedError) {
|
|
1286
1276
|
// The host's kernel write lease refused the cold resume: another
|
|
1287
1277
|
// process drives this session. Rather than a dead end, degrade to
|
|
@@ -1302,7 +1292,7 @@ export function apply(ctx) {
|
|
|
1302
1292
|
catch (watchError) {
|
|
1303
1293
|
return {
|
|
1304
1294
|
kind: 'error',
|
|
1305
|
-
text: `Cannot watch ${clipToWidth(String(target.id), 8)} read-only: ${
|
|
1295
|
+
text: `Cannot watch ${clipToWidth(String(target.id), 8)} read-only: ${messageOf(watchError)}`,
|
|
1306
1296
|
};
|
|
1307
1297
|
}
|
|
1308
1298
|
}
|
|
@@ -1366,7 +1356,7 @@ export function apply(ctx) {
|
|
|
1366
1356
|
notice = repairFailureNotice(await repairSessionLog(logPath));
|
|
1367
1357
|
}
|
|
1368
1358
|
catch (error) {
|
|
1369
|
-
const detail =
|
|
1359
|
+
const detail = messageOf(error);
|
|
1370
1360
|
notice = `repair failed: ${detail} — log untouched`;
|
|
1371
1361
|
}
|
|
1372
1362
|
if (notice !== undefined)
|
|
@@ -1381,7 +1371,7 @@ export function apply(ctx) {
|
|
|
1381
1371
|
await inspectPersistedSession(ctx, picked.id);
|
|
1382
1372
|
}
|
|
1383
1373
|
catch (error) {
|
|
1384
|
-
const message =
|
|
1374
|
+
const message = messageOf(error);
|
|
1385
1375
|
if (isCorruptLogError(message)) {
|
|
1386
1376
|
return offerCorruptedLogRepair(message);
|
|
1387
1377
|
}
|
|
@@ -1389,12 +1379,7 @@ export function apply(ctx) {
|
|
|
1389
1379
|
}
|
|
1390
1380
|
return resumeAndReplay();
|
|
1391
1381
|
};
|
|
1392
|
-
|
|
1393
|
-
ctx.effect(() => ctx.commands.register({
|
|
1394
|
-
name: 'resume',
|
|
1395
|
-
description: 'Resume a persisted session',
|
|
1396
|
-
handler: invocation => resumeHandler(invocation.rawInput, invocation.signal),
|
|
1397
|
-
}), 'dsh-tui-pi: /resume');
|
|
1382
|
+
registerLocalCommand('resume', 'Resume a persisted session', resumeHandler);
|
|
1398
1383
|
// /history: the read-only two-pane look-back (ADR 0003) — left pane lists
|
|
1399
1384
|
// the browsed session's completed turns, right pane shows the selected
|
|
1400
1385
|
// turn's replies; Enter/c refills the editor with the turn's prompt (a
|
|
@@ -1460,12 +1445,7 @@ export function apply(ctx) {
|
|
|
1460
1445
|
}, arg === '' ? undefined : arg);
|
|
1461
1446
|
return { kind: result.error ? 'error' : 'success', text: result.text };
|
|
1462
1447
|
};
|
|
1463
|
-
|
|
1464
|
-
ctx.effect(() => ctx.commands.register({
|
|
1465
|
-
name: 'history',
|
|
1466
|
-
description: 'Browse past turns of a session (read-only; /history <id> for a stored one)',
|
|
1467
|
-
handler: invocation => historyHandler(invocation.rawInput, invocation.signal),
|
|
1468
|
-
}), 'dsh-tui-pi: /history');
|
|
1448
|
+
registerLocalCommand('history', 'Browse past turns of a session (read-only; /history <id> for a stored one)', historyHandler);
|
|
1469
1449
|
// Auto-resume after a hot-reload: a `/reload` stashes the previously
|
|
1470
1450
|
// current session id before this fiber's teardown, and the freshly
|
|
1471
1451
|
// re-imported module consumes it best-effort here so the next prompt
|
|
@@ -1513,7 +1493,7 @@ export function apply(ctx) {
|
|
|
1513
1493
|
ui.requestRender();
|
|
1514
1494
|
}
|
|
1515
1495
|
catch (error) {
|
|
1516
|
-
const message =
|
|
1496
|
+
const message = messageOf(error);
|
|
1517
1497
|
renderer.renderNotice(`--resume ${clipToWidth(bootResumeId, 8)}: ${message} — starting fresh.`, 'error');
|
|
1518
1498
|
}
|
|
1519
1499
|
})();
|
|
@@ -1585,7 +1565,7 @@ export function apply(ctx) {
|
|
|
1585
1565
|
bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
|
|
1586
1566
|
}
|
|
1587
1567
|
catch (error) {
|
|
1588
|
-
const detail =
|
|
1568
|
+
const detail = messageOf(error);
|
|
1589
1569
|
renderer.renderNotice('could not re-attach the previous session — it stays resumable via /resume;'
|
|
1590
1570
|
+ ` the next prompt starts a fresh one (${detail})`, 'error');
|
|
1591
1571
|
}
|
|
@@ -1595,12 +1575,7 @@ export function apply(ctx) {
|
|
|
1595
1575
|
await startNewSession();
|
|
1596
1576
|
return { kind: 'success', text: 'New session started.' };
|
|
1597
1577
|
};
|
|
1598
|
-
|
|
1599
|
-
ctx.effect(() => ctx.commands.register({
|
|
1600
|
-
name: 'new',
|
|
1601
|
-
description: 'Start a new session',
|
|
1602
|
-
handler: invocation => newHandler(invocation.rawInput, invocation.signal),
|
|
1603
|
-
}), 'dsh-tui-pi: /new');
|
|
1578
|
+
registerLocalCommand('new', 'Start a new session', newHandler);
|
|
1604
1579
|
// /settings: text-based configuration browser — the terminal counterpart
|
|
1605
1580
|
// of the web GUI's settings surface. Enumerates ctx.settings.describe()
|
|
1606
1581
|
// and walks each namespace's schema (drill-ins, cycle rows, inline editors,
|
|
@@ -1629,12 +1604,7 @@ export function apply(ctx) {
|
|
|
1629
1604
|
: `Settings: ${changes} change${changes === 1 ? '' : 's'} applied.`,
|
|
1630
1605
|
};
|
|
1631
1606
|
};
|
|
1632
|
-
|
|
1633
|
-
ctx.effect(() => ctx.commands.register({
|
|
1634
|
-
name: 'settings',
|
|
1635
|
-
description: 'Browse and edit configuration (namespaces, values, resets)',
|
|
1636
|
-
handler: invocation => settingsHandler(invocation.rawInput, invocation.signal),
|
|
1637
|
-
}), 'dsh-tui-pi: /settings');
|
|
1607
|
+
registerLocalCommand('settings', 'Browse and edit configuration (namespaces, values, resets)', settingsHandler);
|
|
1638
1608
|
// /skills: standalone skill browser with Installed/Available dual mode.
|
|
1639
1609
|
// Enter/Space toggles or symlinks; Tab switches views; Esc exits.
|
|
1640
1610
|
const skillsHandler = async () => {
|
|
@@ -1642,12 +1612,7 @@ export function apply(ctx) {
|
|
|
1642
1612
|
openSkillsManagerPanel(ctx, ui.tui, ui.theme, refocusEditor, agent ?? undefined, () => { });
|
|
1643
1613
|
return { kind: 'success', text: 'Skills manager opened.' };
|
|
1644
1614
|
};
|
|
1645
|
-
|
|
1646
|
-
ctx.effect(() => ctx.commands.register({
|
|
1647
|
-
name: 'skills',
|
|
1648
|
-
description: 'Manage user skills (installed and available)',
|
|
1649
|
-
handler: invocation => skillsHandler(invocation.rawInput, invocation.signal),
|
|
1650
|
-
}), 'dsh-tui-pi: /skills');
|
|
1615
|
+
registerLocalCommand('skills', 'Manage user skills (installed and available)', skillsHandler);
|
|
1651
1616
|
// /theme: pick a color scheme and apply it immediately — the choice is
|
|
1652
1617
|
// persisted to the dsh-tui settings namespace (`applies: 'live'`, so the
|
|
1653
1618
|
// watch hook would re-apply the same change anyway) and hot-swapped into
|
|
@@ -1659,20 +1624,26 @@ export function apply(ctx) {
|
|
|
1659
1624
|
return { kind: 'error', text: 'Settings service is not available.' };
|
|
1660
1625
|
}
|
|
1661
1626
|
// Preselect from the live settings value (which may have changed since
|
|
1662
|
-
// startup via the /settings browser), not the startup snapshot.
|
|
1663
|
-
|
|
1627
|
+
// startup via the /settings browser), not the startup snapshot. The
|
|
1628
|
+
// picker lists the registry themes (user + built-in) after the
|
|
1629
|
+
// constant auto/light/dark rows.
|
|
1630
|
+
const picked = await pickTheme(ui.tui, ui.theme, currentThemePreference(ctx), refocusEditor, {
|
|
1631
|
+
themes: themeRegistry,
|
|
1632
|
+
userNames: themeUserNames,
|
|
1633
|
+
});
|
|
1664
1634
|
if (picked === undefined)
|
|
1665
1635
|
return { kind: 'success', text: 'Theme unchanged.' };
|
|
1666
1636
|
const writeError = await writeThemePreference(ctx, picked);
|
|
1667
1637
|
if (writeError !== undefined)
|
|
1668
1638
|
return { kind: 'error', text: writeError };
|
|
1669
1639
|
// DSH_TUI_THEME pins the display regardless of the preference — don't
|
|
1670
|
-
// claim the pick was applied when it wasn't.
|
|
1671
|
-
//
|
|
1672
|
-
//
|
|
1673
|
-
|
|
1640
|
+
// claim the pick was applied when it wasn't. Compare against the
|
|
1641
|
+
// env-free resolution of the same pick (the bundle cache makes the
|
|
1642
|
+
// reference comparison exact); 'auto' is skipped — it was never
|
|
1643
|
+
// env-pin-sensitive (its display follows the terminal either way).
|
|
1644
|
+
const applied = resolveTheme(process.env, picked, themeRegistry);
|
|
1674
1645
|
applyTheme(applied);
|
|
1675
|
-
const expected = picked === '
|
|
1646
|
+
const expected = picked === 'auto' ? undefined : resolveTheme({}, picked, themeRegistry);
|
|
1676
1647
|
if (expected !== undefined && applied !== expected) {
|
|
1677
1648
|
return {
|
|
1678
1649
|
kind: 'success',
|
|
@@ -1681,12 +1652,7 @@ export function apply(ctx) {
|
|
|
1681
1652
|
}
|
|
1682
1653
|
return { kind: 'success', text: `Theme: ${picked} — applied.` };
|
|
1683
1654
|
};
|
|
1684
|
-
|
|
1685
|
-
ctx.effect(() => ctx.commands.register({
|
|
1686
|
-
name: 'theme',
|
|
1687
|
-
description: 'Set the terminal color scheme (applies immediately)',
|
|
1688
|
-
handler: invocation => themeHandler(invocation.rawInput, invocation.signal),
|
|
1689
|
-
}), 'dsh-tui-pi: /theme');
|
|
1655
|
+
registerLocalCommand('theme', 'Set the terminal color scheme (applies immediately)', themeHandler);
|
|
1690
1656
|
// /reload: hot-reload this plugin from the current source — re-imports the
|
|
1691
1657
|
// module and its dependencies (picking up src changes after `pnpm build`)
|
|
1692
1658
|
// and swaps the plugin runtime. The old fiber is disposed, so the TUI and
|
|
@@ -1696,12 +1662,7 @@ export function apply(ctx) {
|
|
|
1696
1662
|
kind: 'success',
|
|
1697
1663
|
text: await reloadPlugin(ctx, import.meta.url),
|
|
1698
1664
|
});
|
|
1699
|
-
|
|
1700
|
-
ctx.effect(() => ctx.commands.register({
|
|
1701
|
-
name: 'reload',
|
|
1702
|
-
description: 'Reload the TUI from the current source (apply code changes without restarting dsh)',
|
|
1703
|
-
handler: invocation => reloadHandler(invocation.rawInput, invocation.signal),
|
|
1704
|
-
}), 'dsh-tui-pi: /reload');
|
|
1665
|
+
registerLocalCommand('reload', 'Reload the TUI from the current source (apply code changes without restarting dsh)', reloadHandler);
|
|
1705
1666
|
// /hotkeys — pi's keybinding browser, in the /agents select-panel style:
|
|
1706
1667
|
// a field list of the app keys, Enter opens an editor that writes
|
|
1707
1668
|
// ~/.dsh/keybindings.json and live-applies the change (no /reload).
|
|
@@ -1713,12 +1674,7 @@ export function apply(ctx) {
|
|
|
1713
1674
|
});
|
|
1714
1675
|
return { kind: 'success', text: summary ?? 'Keybindings unchanged.' };
|
|
1715
1676
|
};
|
|
1716
|
-
|
|
1717
|
-
ctx.effect(() => ctx.commands.register({
|
|
1718
|
-
name: 'hotkeys',
|
|
1719
|
-
description: 'Show the current keybindings (custom file: ~/.dsh/keybindings.json)',
|
|
1720
|
-
handler: invocation => hotkeysHandler(invocation.rawInput, invocation.signal),
|
|
1721
|
-
}), 'dsh-tui-pi: /hotkeys');
|
|
1677
|
+
registerLocalCommand('hotkeys', 'Show the current keybindings (custom file: ~/.dsh/keybindings.json)', hotkeysHandler);
|
|
1722
1678
|
// /login: register provider credentials — the terminal counterpart of
|
|
1723
1679
|
// pi-agent's /login, on the Models category's add-provider flow. Opens a
|
|
1724
1680
|
// searchable provider directory (including already-configured routes, so
|
|
@@ -1749,12 +1705,7 @@ export function apply(ctx) {
|
|
|
1749
1705
|
return { kind: 'success', text: 'Login cancelled.' };
|
|
1750
1706
|
return { kind: 'success', text: `Provider ${result.name} configured.` };
|
|
1751
1707
|
};
|
|
1752
|
-
|
|
1753
|
-
ctx.effect(() => ctx.commands.register({
|
|
1754
|
-
name: 'login',
|
|
1755
|
-
description: 'Configure a provider API key (register or replace credentials)',
|
|
1756
|
-
handler: invocation => loginHandler(invocation.rawInput, invocation.signal),
|
|
1757
|
-
}), 'dsh-tui-pi: /login');
|
|
1708
|
+
registerLocalCommand('login', 'Register provider credentials (API key) through the provider directory', loginHandler);
|
|
1758
1709
|
// /logout: unsubscribe a provider — pi-agent's /logout on the dsh side.
|
|
1759
1710
|
// Lists the providers with a stored credential; on selection removes the
|
|
1760
1711
|
// key AND the settings.yaml provider entry, so the provider's models
|
|
@@ -1800,12 +1751,7 @@ export function apply(ctx) {
|
|
|
1800
1751
|
text: `Logged out ${result.name} — API key and provider configuration removed.`,
|
|
1801
1752
|
};
|
|
1802
1753
|
};
|
|
1803
|
-
|
|
1804
|
-
ctx.effect(() => ctx.commands.register({
|
|
1805
|
-
name: 'logout',
|
|
1806
|
-
description: 'Log out a provider (removes its API key and provider configuration)',
|
|
1807
|
-
handler: invocation => logoutHandler(invocation.rawInput, invocation.signal),
|
|
1808
|
-
}), 'dsh-tui-pi: /logout');
|
|
1754
|
+
registerLocalCommand('logout', 'Log out a provider (removes its API key and provider configuration)', logoutHandler);
|
|
1809
1755
|
// ------------------------------------------------- powerline footer + git --
|
|
1810
1756
|
const git = new GitBranchWatcher(process.cwd());
|
|
1811
1757
|
git.onChange = () => ui.requestRender();
|
|
@@ -1915,12 +1861,12 @@ export function apply(ctx) {
|
|
|
1915
1861
|
void (async () => {
|
|
1916
1862
|
const scheme = await ui.tui.queryTerminalColorScheme({ timeoutMs: 200 }).catch(() => undefined);
|
|
1917
1863
|
if (scheme === 'light' || scheme === 'dark') {
|
|
1918
|
-
applyTheme(resolveTheme(process.env, scheme));
|
|
1864
|
+
applyTheme(resolveTheme(process.env, scheme, themeRegistry));
|
|
1919
1865
|
return;
|
|
1920
1866
|
}
|
|
1921
1867
|
const background = await ui.tui.queryTerminalBackgroundColor({ timeoutMs: 200 }).catch(() => undefined);
|
|
1922
1868
|
if (background !== undefined) {
|
|
1923
|
-
applyTheme(resolveTheme(process.env, rgbIsLight(background) ? 'light' : 'dark'));
|
|
1869
|
+
applyTheme(resolveTheme(process.env, rgbIsLight(background) ? 'light' : 'dark', themeRegistry));
|
|
1924
1870
|
}
|
|
1925
1871
|
})();
|
|
1926
1872
|
}
|
|
@@ -1930,7 +1876,7 @@ export function apply(ctx) {
|
|
|
1930
1876
|
const stopTerminalFollow = ui.tui.onTerminalColorSchemeChange(scheme => {
|
|
1931
1877
|
if (themePreferenceRef !== 'auto')
|
|
1932
1878
|
return;
|
|
1933
|
-
applyTheme(resolveTheme(process.env, scheme));
|
|
1879
|
+
applyTheme(resolveTheme(process.env, scheme, themeRegistry));
|
|
1934
1880
|
});
|
|
1935
1881
|
// Live clock: the footer is the only thing that changes each second.
|
|
1936
1882
|
const clockTimer = setInterval(() => ui.requestRender(), 1000);
|
|
@@ -1982,7 +1928,7 @@ export function apply(ctx) {
|
|
|
1982
1928
|
// again; surface the failure in the transcript like every other
|
|
1983
1929
|
// dispatch error. Buffered notice: this line is the only record of
|
|
1984
1930
|
// the failure and must survive a theme-switch rebuild (doc.clear()).
|
|
1985
|
-
const message =
|
|
1931
|
+
const message = messageOf(error);
|
|
1986
1932
|
renderer.renderNotice(message, 'error');
|
|
1987
1933
|
return;
|
|
1988
1934
|
}
|
|
@@ -2005,7 +1951,7 @@ export function apply(ctx) {
|
|
|
2005
1951
|
// the transcript instead of an unhandled rejection killing the TUI.
|
|
2006
1952
|
// Buffered notice: this line is the only record of the failure and
|
|
2007
1953
|
// must survive a theme-switch rebuild (doc.clear()).
|
|
2008
|
-
const message =
|
|
1954
|
+
const message = messageOf(error);
|
|
2009
1955
|
renderer.renderNotice(message, 'error');
|
|
2010
1956
|
return;
|
|
2011
1957
|
}
|
|
@@ -2045,7 +1991,7 @@ export function apply(ctx) {
|
|
|
2045
1991
|
catch (error) {
|
|
2046
1992
|
// Buffered notice: the failure line is the only on-screen record and
|
|
2047
1993
|
// must survive a theme-switch rebuild (doc.clear()).
|
|
2048
|
-
const message =
|
|
1994
|
+
const message = messageOf(error);
|
|
2049
1995
|
renderer.renderNotice(message, 'error');
|
|
2050
1996
|
}
|
|
2051
1997
|
// Session (re)created by the prompt: seed the badge cache. The initial
|