@aiwayds/dsh-tui-pi 2.31.0 → 2.32.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 CHANGED
@@ -38,13 +38,24 @@ https://github.com/user-attachments/assets/67a7c6ca-ff42-4005-b543-437ba61771bb
38
38
  ## Install and launch
39
39
 
40
40
  ```sh
41
+ npm i -g @aiwayds/dsh-tui-pi
42
+ dsh-tui-pi # or the short form: dst
43
+ ```
44
+
45
+ The global install sets the machine up by itself, printing one progress line per step: it installs the `@deepseek-ai/dsh` host when missing (via npm; if npm itself is not on PATH, it prints what to install and stops), installs pnpm, and creates the `tui` profile with this plugin and its bundled companions. Whatever the install cannot finish (e.g. an offline machine) completes automatically on the first launch. After a later `npm i -g` upgrade of this package, the profile's mounted copy is moved to the new version on the next launch — a profile that is newer than the global install (dev-upgraded ahead) is left untouched.
46
+
47
+ The manual steps do exactly what the onboarding runs, if you prefer them:
48
+
49
+ ```sh
50
+ npm i -g @deepseek-ai/dsh
51
+ npm i -g pnpm
41
52
  dsh plugin --profile tui add @aiwayds/dsh-tui-pi
42
- dsh --profile tui # launch (or: dsh-tui-pi)
53
+ dsh --profile tui # launch
43
54
  ```
44
55
 
45
56
  Legacy `session_projcache` records (written before dsh 0.1.2-alpha.4) are migrated automatically at profile load — idempotent, with per-file backups, never blocking startup; the `dsh-tui-pi` launcher runs the same preflight.
46
57
 
47
- Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
58
+ Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. For a normal install, upgrading the global package upgrades the profile on the next launch (see above). `dev-upgrade` remains for dev machines that install the profile from a checked-out repo:
48
59
 
49
60
  ```sh
50
61
  node scripts/dev-upgrade.mjs # latest
package/bin/boot.mjs ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Launcher entry behind the `dsh-tui-pi` and `dst` bin shims: boot the dsh
4
+ * `tui` profile, running the one-time onboarding first when this machine is
5
+ * missing any of the three pieces (dsh host, pnpm, mounted tui profile).
6
+ *
7
+ * Fast path — host present and profile current: no output, straight to the
8
+ * TUI, exactly like the plain `dsh --profile tui` this replaces.
9
+ *
10
+ * Version follow: after `npm i -g` upgrades this package, the profile still
11
+ * holds the old copy; the next launch (or the package's postinstall) re-runs
12
+ * the add to move the profile to the new version. A profile that is NEWER
13
+ * than the global install (dev-upgraded ahead) is left untouched.
14
+ *
15
+ * See bin/onboard-core.mjs for the setup logic and its failure contract.
16
+ */
17
+
18
+ import { spawnSync } from 'node:child_process'
19
+ import {
20
+ dshBinPath,
21
+ profileBehind,
22
+ profileMounted,
23
+ runOnboard,
24
+ } from './onboard-core.mjs'
25
+
26
+ const args = process.argv.slice(2)
27
+
28
+ function boot(dsh) {
29
+ const result = spawnSync(dsh, ['--profile', 'tui', ...args], { stdio: 'inherit' })
30
+ process.exit(result.status ?? 1)
31
+ }
32
+
33
+ const dsh = dshBinPath()
34
+
35
+ if (dsh && profileMounted() && !profileBehind()) {
36
+ boot(dsh)
37
+ }
38
+
39
+ console.log('dsh-tui-pi setup (first run / upgrade only):')
40
+ const setup = runOnboard({ log: (line) => console.log(line) })
41
+ if (!setup.ok) {
42
+ console.error(`\ndsh-tui-pi: setup stopped at "${setup.step}".`)
43
+ console.error(`dsh-tui-pi: ${setup.hint}`)
44
+ process.exit(1)
45
+ }
46
+ console.log('')
47
+ boot(setup.dsh)
package/bin/dsh-tui-pi CHANGED
@@ -1,7 +1,7 @@
1
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).
2
+ # Launcher for the dsh `tui` profile that mounts @aiwayds/dsh-tui-pi.
3
+ # First run performs a one-time setup (dsh host, pnpm, profile plugins) with
4
+ # progress output; every later run boots straight into the TUI. Aliased as `dst`.
5
5
  SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$(readlink -f -- "$0" 2>/dev/null || echo "$0")")" && pwd)
6
6
  node "$SCRIPT_DIR/preflight-projcache.mjs" >/dev/null 2>&1 || true
7
- exec dsh --profile tui "$@"
7
+ exec node "$SCRIPT_DIR/boot.mjs" "$@"
@@ -0,0 +1,275 @@
1
+ /**
2
+ * One-time onboarding for the `dsh-tui-pi` / `dst` launcher, shared by two
3
+ * entry points:
4
+ * - scripts/postinstall.mjs — runs at `npm i -g @aiwayds/dsh-tui-pi` time,
5
+ * so the package is usable the moment the install prints "added 1 package";
6
+ * - bin/boot.mjs — runs on the first `dsh-tui-pi` invocation, catching up
7
+ * anything the postinstall could not finish (offline install, pnpm that
8
+ * does not run postinstall scripts, non-global context, …).
9
+ *
10
+ * Contract:
11
+ * - idempotent: every step checks before it acts, so a re-run only prints
12
+ * "found" lines and the launcher fast-paths to a silent boot;
13
+ * - the dsh host is installed globally WITH NPM when missing (largest
14
+ * step); npm itself is a prerequisite — when it is not on PATH we print
15
+ * what to install and stop instead of guessing;
16
+ * - all functions are synchronous and never throw past the boundary:
17
+ * failures come back as { ok: false, step, hint }.
18
+ */
19
+
20
+ import { execFileSync, spawnSync } from 'node:child_process'
21
+ import { accessSync, constants, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'
22
+ import { homedir } from 'node:os'
23
+ import { delimiter, dirname, join } from 'node:path'
24
+ import { fileURLToPath } from 'node:url'
25
+
26
+ const PKG = '@aiwayds/dsh-tui-pi'
27
+ const HOST = '@deepseek-ai/dsh'
28
+ const PROFILE = 'tui'
29
+ const INSTALL_TIMEOUT_MS = 10 * 60_000
30
+
31
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
32
+
33
+ function readSelfPackage() {
34
+ return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
35
+ }
36
+
37
+ export function selfVersion() {
38
+ try {
39
+ return readSelfPackage().version ?? null
40
+ } catch {
41
+ return null
42
+ }
43
+ }
44
+
45
+ export function dshHome() {
46
+ return process.env.DSH_HOME || join(homedir(), '.dsh')
47
+ }
48
+
49
+ // The profile counts as set up once this package is resolvable inside it.
50
+ export function profileMounted() {
51
+ return existsSync(join(dshHome(), 'profiles', PROFILE, 'node_modules', PKG, 'package.json'))
52
+ }
53
+
54
+ export function profileVersion() {
55
+ try {
56
+ return JSON.parse(
57
+ readFileSync(join(dshHome(), 'profiles', PROFILE, 'node_modules', PKG, 'package.json'), 'utf8'),
58
+ ).version ?? null
59
+ } catch {
60
+ return null
61
+ }
62
+ }
63
+
64
+ // Companion plugins ship as npm dependencies of this package and land in the
65
+ // profile's node_modules together with it when `dsh plugin add` runs.
66
+ export function companions() {
67
+ try {
68
+ const deps = readSelfPackage().dependencies ?? {}
69
+ return Object.keys(deps).filter((name) => name.startsWith('@aiwayds/') && name !== PKG)
70
+ } catch {
71
+ return []
72
+ }
73
+ }
74
+
75
+ export function which(cmd) {
76
+ const dirs = (process.env.PATH || '').split(delimiter).filter(Boolean)
77
+ const suffixes = process.platform === 'win32' ? ['.cmd', '.exe', '.bat', ''] : ['']
78
+ for (const dir of dirs) {
79
+ for (const suffix of suffixes) {
80
+ const candidate = join(dir, cmd + suffix)
81
+ try {
82
+ if (!statSync(candidate).isFile()) continue
83
+ accessSync(candidate, constants.X_OK)
84
+ return candidate
85
+ } catch {
86
+ /* keep scanning */
87
+ }
88
+ }
89
+ }
90
+ return null
91
+ }
92
+
93
+ function queryVersion(cmd, args) {
94
+ try {
95
+ return execFileSync(cmd, args, {
96
+ encoding: 'utf8',
97
+ timeout: 20_000,
98
+ stdio: ['ignore', 'pipe', 'pipe'],
99
+ }).trim() || null
100
+ } catch {
101
+ return null
102
+ }
103
+ }
104
+
105
+ export function dshBinPath() {
106
+ const onPath = which('dsh')
107
+ if (onPath) return onPath
108
+ // A host installed moments ago can be missing from this shell's PATH cache;
109
+ // fall back to the npm global prefix the installer just wrote to.
110
+ try {
111
+ const prefix = execFileSync('npm', ['prefix', '-g'], {
112
+ encoding: 'utf8',
113
+ timeout: 20_000,
114
+ stdio: ['ignore', 'pipe', 'ignore'],
115
+ }).trim()
116
+ const candidate = join(prefix, 'bin', 'dsh')
117
+ if (existsSync(candidate)) return candidate
118
+ } catch {
119
+ /* no npm, no fallback */
120
+ }
121
+ return null
122
+ }
123
+
124
+ // Global installs always go through npm — one predictable path, regardless
125
+ // of which package manager pulled this package in.
126
+ function installGlobal(name) {
127
+ const result = spawnSync('npm', ['install', '-g', name], {
128
+ stdio: 'inherit',
129
+ timeout: INSTALL_TIMEOUT_MS,
130
+ shell: process.platform === 'win32',
131
+ })
132
+ return result.status === 0
133
+ }
134
+
135
+ // True when the mounted profile copy is OLDER than this package. Prerelease
136
+ // tags compare numerically per dot segment (rc.10 > rc.4); a profile that is
137
+ // NEWER (dev-upgraded ahead of the global install) is left alone.
138
+ export function profileBehind() {
139
+ const mounted = profileVersion()
140
+ const self = selfVersion()
141
+ if (!mounted || !self) return false
142
+ if (mounted === self) return false
143
+ return compareVersions(mounted, self) < 0
144
+ }
145
+
146
+ export function compareVersions(a, b) {
147
+ const parse = (version) => {
148
+ const [core, pre = ''] = String(version).replace(/^v/, '').split(/[-+]/, 2)
149
+ return { core: core.split('.').map((n) => Number.parseInt(n, 10) || 0), pre: pre ? pre.split('.') : [] }
150
+ }
151
+ const pa = parse(a)
152
+ const pb = parse(b)
153
+ for (let i = 0; i < 3; i += 1) {
154
+ if (pa.core[i] !== pb.core[i]) return pa.core[i] - pb.core[i]
155
+ }
156
+ if (pa.pre.length === 0 && pb.pre.length === 0) return 0
157
+ if (pa.pre.length === 0) return 1
158
+ if (pb.pre.length === 0) return -1
159
+ for (let i = 0; i < Math.max(pa.pre.length, pb.pre.length); i += 1) {
160
+ const x = pa.pre[i]
161
+ const y = pb.pre[i]
162
+ if (x === undefined) return -1
163
+ if (y === undefined) return 1
164
+ const xn = Number.parseInt(x, 10)
165
+ const yn = Number.parseInt(y, 10)
166
+ if (!Number.isNaN(xn) && !Number.isNaN(yn) && xn !== yn) return xn - yn
167
+ if (x !== y) return x < y ? -1 : 1
168
+ }
169
+ return 0
170
+ }
171
+
172
+ // pnpm ≥10 fails a fresh `add` with ERR_PNPM_IGNORED_BUILDS when the added
173
+ // packages carry lifecycle scripts. The only scripts our packages ship are
174
+ // the closure-linker postinstalls, which no-op on published tarballs (they
175
+ // guard on the repo's src/), so approving them is safe and is what `pnpm
176
+ // approve-builds` would do interactively. pnpm sometimes records an
177
+ // allowBuilds skeleton in the profile's pnpm-workspace.yaml with a
178
+ // "set this to true or false" placeholder — fill those placeholders; when
179
+ // it does not (behavior varies by pnpm build and environment), write the
180
+ // allowBuilds block ourselves for exactly the packages this suite ships.
181
+ // A user-written true/false decision is never overwritten. Returns whether
182
+ // the workspace file was updated.
183
+ function approvePrintedBuilds() {
184
+ const workspace = join(dshHome(), 'profiles', PROFILE, 'pnpm-workspace.yaml')
185
+ try {
186
+ const text = readFileSync(workspace, 'utf8')
187
+ if (text.includes('set this to true or false')) {
188
+ writeFileSync(workspace, text.replaceAll('set this to true or false', 'true'))
189
+ return true
190
+ }
191
+ if (text.includes('allowBuilds:')) return false
192
+ const names = [PKG, ...companions()]
193
+ const block = ['allowBuilds:', ...names.map((name) => ` '${name}': true`)].join('\n')
194
+ writeFileSync(workspace, `${text.replace(/\n*$/, '\n')}${block}\n`)
195
+ return true
196
+ } catch {
197
+ return false
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Bring the machine to a bootable state, printing one progress line per step.
203
+ * Steps:
204
+ * 1/3 dsh host — installed globally with npm when missing; when npm
205
+ * itself is not on PATH, print what to install and stop
206
+ * 2/3 pnpm — the host's plugin manager shells out to it; installed
207
+ * globally when missing
208
+ * 3/3 tui profile — `dsh plugin --profile tui add` this package (+ companions)
209
+ * Returns { ok: true, dsh } or { ok: false, step, hint }.
210
+ */
211
+ export function runOnboard({ log = () => {} } = {}) {
212
+ let dsh = dshBinPath()
213
+ if (dsh) {
214
+ log(` [1/3] dsh host found ${queryVersion(dsh, ['-V']) ?? '(version unknown)'}`)
215
+ } else if (!which('npm')) {
216
+ log(` [1/3] dsh host not found, and npm is not on PATH either`)
217
+ return {
218
+ ok: false,
219
+ step: 'npm',
220
+ hint: 'install Node.js (ships with npm) first: https://nodejs.org — then re-run dsh-tui-pi',
221
+ }
222
+ } else {
223
+ log(` [1/3] dsh host not found — installing ${HOST} globally with npm (largest step, a few minutes)…`)
224
+ if (!installGlobal(HOST)) {
225
+ return { ok: false, step: 'dsh host install', hint: `install it with: npm install -g ${HOST} — then re-run dsh-tui-pi` }
226
+ }
227
+ dsh = dshBinPath()
228
+ if (!dsh) {
229
+ return { ok: false, step: 'dsh host lookup', hint: `installed, but still not found — run: npm install -g ${HOST}` }
230
+ }
231
+ log(` [1/3] dsh host installed ${queryVersion(dsh, ['-V']) ?? ''}`)
232
+ }
233
+
234
+ const pnpm = which('pnpm')
235
+ if (pnpm) {
236
+ log(` [2/3] pnpm found ${queryVersion(pnpm, ['-v']) ?? ''}`)
237
+ } else {
238
+ log(' [2/3] pnpm not found — installing globally…')
239
+ if (!installGlobal('pnpm')) {
240
+ return { ok: false, step: 'pnpm install', hint: 'install it with: npm install -g pnpm — then re-run dsh-tui-pi' }
241
+ }
242
+ log(` [2/3] pnpm installed ${queryVersion('pnpm', ['-v']) ?? ''}`)
243
+ }
244
+
245
+ if (profileMounted() && !profileBehind()) {
246
+ log(' [3/3] tui profile already set up')
247
+ } else {
248
+ const verb = profileMounted() ? 'updating' : 'creating'
249
+ const list = companions()
250
+ log(` [3/3] tui profile ${verb}: installing ${PKG}${list.length ? ` + ${list.length} bundled companions` : ''}…`)
251
+ if (list.length) log(` ${list.join(', ')}`)
252
+ const result = spawnSync(dsh, ['plugin', '--profile', PROFILE, 'add', PKG], {
253
+ stdio: 'inherit',
254
+ timeout: INSTALL_TIMEOUT_MS,
255
+ })
256
+ if (result.status !== 0) {
257
+ if (!approvePrintedBuilds()) {
258
+ return { ok: false, step: 'tui profile setup', hint: `run it manually with: dsh plugin --profile ${PROFILE} add ${PKG} — then re-run dsh-tui-pi` }
259
+ }
260
+ // pnpm's skeleton is filled; the same idempotent add now succeeds
261
+ // (everything is already downloaded).
262
+ log(' [3/3] tui profile approved the printed build scripts (our closure-linker no-ops) — retrying…')
263
+ const retry = spawnSync(dsh, ['plugin', '--profile', PROFILE, 'add', PKG], {
264
+ stdio: 'inherit',
265
+ timeout: INSTALL_TIMEOUT_MS,
266
+ })
267
+ if (retry.status !== 0) {
268
+ return { ok: false, step: 'tui profile setup', hint: `run it manually with: dsh plugin --profile ${PROFILE} add ${PKG} — then re-run dsh-tui-pi` }
269
+ }
270
+ }
271
+ log(` [3/3] tui profile ${verb === 'updating' ? 'updated' : 'done'}`)
272
+ }
273
+
274
+ return { ok: true, dsh }
275
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `/profile-switch` + `/profile-cfg` — user model profiles: named snapshots
3
+ * of the whole model configuration (default model + think level, every
4
+ * subagent's model/thinking) that switch in one step between contexts like
5
+ * work and personal.
6
+ *
7
+ * Layers, all on the select-panel framework (src/panels.ts):
8
+ *
9
+ * 1. `/profile-switch` switcher (TablePanel) — one row per profile (name |
10
+ * default model | think | agents). Enter applies the profile to the
11
+ * live selection AND the agent markdown files, and BINDS this directory
12
+ * tree (.dsh-profile) so new sessions here start on it — switching is
13
+ * workspace-scoped: other trees keep their own binding, or the global
14
+ * default. Esc backs out; p pins/unpins manually.
15
+ * 2. `/profile-cfg` manager (TablePanel) — the roster: n new, d delete
16
+ * (double-press confirm), Enter opens the profile's fields.
17
+ * 3. profile fields (FieldPanel) — model / think / agents rows plus the
18
+ * s save-current, r rename, v review shortcuts.
19
+ * 4. agents sub-table (TablePanel) — every discovered agent's recorded
20
+ * model/thinking; Enter picks through the SAME favorites/hidden model
21
+ * table /model uses (pickModel with the shared PanelHost), i sets
22
+ * explicit inherit, t edits think alone.
23
+ *
24
+ * Every edit persists to `$DSH_HOME/model-profiles.json` immediately
25
+ * (src/model-profiles.ts). Applying a profile sets the live default model,
26
+ * binds the workspace tree via `.dsh-profile` (src/model-profiles.ts) and
27
+ * records the last-applied pointer — it writes NO agent files: each agent's
28
+ * runtime model/thinking is composed at spawn from the frontmatter baseline
29
+ * ⊕ this pinned profile's per-agent overrides (src/agent-runtime.ts, the
30
+ * registry's profile-aware composer), so a switch is fully workspace-scoped.
31
+ * The live default model rides the bridge selection. The GLOBAL
32
+ * agent-default-model is written only by /model, never by a profile switch.
33
+ * The ● "current" marker is the tree's own binding (boundProfileName), not a
34
+ * machine-wide pointer.
35
+ */
36
+ import type { Context } from '@deepseek-ai/cordis';
37
+ import type { ModelSelection } from '@deepseek-ai/dsh-agent';
38
+ import type { TUI } from '@earendil-works/pi-tui';
39
+ import { type ModelProfile, type ModelProfilesDoc } from './model-profiles.ts';
40
+ import { type TuiTheme } from './theme/index.ts';
41
+ /** Live-session handles the profile flows need (bridged in from index.ts). */
42
+ export interface ProfileDeps {
43
+ getSelection(): ModelSelection | undefined;
44
+ setSelection(selection: ModelSelection): void;
45
+ }
46
+ /**
47
+ * Apply `profile` end to end, scoped to the current workspace tree:
48
+ * default model + think level through the live selection ref (a profile
49
+ * without a default model leaves the selection alone), the tree binding
50
+ * (`.dsh-profile` in `cwd` — new sessions here assemble from this profile)
51
+ * and the store's informational last-applied pointer. Agent model/thinking
52
+ * values are NOT written anywhere here: each agent's runtime values compose
53
+ * at spawn from the frontmatter baseline ⊕ this pinned profile's overrides
54
+ * (src/agent-runtime.ts), which is what makes a switch workspace-scoped.
55
+ * Resolves the command summary text — failures ride along as ⚠ parts
56
+ * instead of aborting the rest. Deliberately does NOT touch the global
57
+ * agent-default-model: that is /model's write, and crossing tree boundaries
58
+ * is exactly what a profile switch must not do.
59
+ */
60
+ export declare function applyProfile(path: string, doc: ModelProfilesDoc, profile: ModelProfile, deps: ProfileDeps, cwd: string): Promise<string>;
61
+ /**
62
+ * Open the `/profile-switch` switcher. Resolves the apply summary when a
63
+ * profile was switched, or `undefined` when cancelled / nothing applied.
64
+ *
65
+ * Enter is a WORKSPACE-SCOPED switch: apply to the live session, and bind
66
+ * this directory tree via `.dsh-profile` (see src/model-profiles.ts) so
67
+ * every NEW session here auto-loads it — agent model/think values are not
68
+ * written anywhere; each agent composes its runtime values at spawn from
69
+ * the frontmatter baseline ⊕ this pinned profile (src/agent-runtime.ts).
70
+ * Other trees are untouched. A
71
+ * hand-decorated pin file refuses the binding and says so in the summary.
72
+ * `p` binds/unbinds the cwd manually — pressing it on the already-bound
73
+ * profile unpins — unless the pin lives in an ancestor or was
74
+ * hand-decorated, which is reported in the status line instead.
75
+ */
76
+ export declare function openProfileSwitcher(tui: TUI, theme: TuiTheme, deps: ProfileDeps, restoreFocus: () => void): Promise<string | undefined>;
77
+ /**
78
+ * Open the `/profile-cfg` manager. Resolves a summary when anything changed,
79
+ * or `undefined` when the manager closed untouched. `preselect` jumps
80
+ * straight into one profile's fields window.
81
+ */
82
+ export declare function openProfileManager(ctx: Context, tui: TUI, theme: TuiTheme, deps: ProfileDeps, restoreFocus: () => void, preselect?: string): Promise<string | undefined>;
package/lib/profile.js ADDED
@@ -0,0 +1,665 @@
1
+ /**
2
+ * `/profile-switch` + `/profile-cfg` — user model profiles: named snapshots
3
+ * of the whole model configuration (default model + think level, every
4
+ * subagent's model/thinking) that switch in one step between contexts like
5
+ * work and personal.
6
+ *
7
+ * Layers, all on the select-panel framework (src/panels.ts):
8
+ *
9
+ * 1. `/profile-switch` switcher (TablePanel) — one row per profile (name |
10
+ * default model | think | agents). Enter applies the profile to the
11
+ * live selection AND the agent markdown files, and BINDS this directory
12
+ * tree (.dsh-profile) so new sessions here start on it — switching is
13
+ * workspace-scoped: other trees keep their own binding, or the global
14
+ * default. Esc backs out; p pins/unpins manually.
15
+ * 2. `/profile-cfg` manager (TablePanel) — the roster: n new, d delete
16
+ * (double-press confirm), Enter opens the profile's fields.
17
+ * 3. profile fields (FieldPanel) — model / think / agents rows plus the
18
+ * s save-current, r rename, v review shortcuts.
19
+ * 4. agents sub-table (TablePanel) — every discovered agent's recorded
20
+ * model/thinking; Enter picks through the SAME favorites/hidden model
21
+ * table /model uses (pickModel with the shared PanelHost), i sets
22
+ * explicit inherit, t edits think alone.
23
+ *
24
+ * Every edit persists to `$DSH_HOME/model-profiles.json` immediately
25
+ * (src/model-profiles.ts). Applying a profile sets the live default model,
26
+ * binds the workspace tree via `.dsh-profile` (src/model-profiles.ts) and
27
+ * records the last-applied pointer — it writes NO agent files: each agent's
28
+ * runtime model/thinking is composed at spawn from the frontmatter baseline
29
+ * ⊕ this pinned profile's per-agent overrides (src/agent-runtime.ts, the
30
+ * registry's profile-aware composer), so a switch is fully workspace-scoped.
31
+ * The live default model rides the bridge selection. The GLOBAL
32
+ * agent-default-model is written only by /model, never by a profile switch.
33
+ * The ● "current" marker is the tree's own binding (boundProfileName), not a
34
+ * machine-wide pointer.
35
+ */
36
+ import { agentsDir, listAgentFiles, } from "./agent-manager.js";
37
+ import { bindWorkspaceProfile, boundProfileName, captureAgentsSnapshot, createProfile, deleteProfile, findProfile, formatProfileRoute, loadModelProfiles, modelProfilesPath, profileReviewLines, readNearestProfilePin, removeProfilePin, renameProfile, saveModelProfiles, writeProfilePin, PROFILE_PIN_FILE, } from "./model-profiles.js";
38
+ import { join } from 'node:path';
39
+ import { openEffortPicker, pickModel } from "./selectors.js";
40
+ import { EditField } from "./settings.js";
41
+ import { autoColumns, FieldPanel, PanelHost, TablePanel, ViewerPanel, } from "./panels.js";
42
+ import { ansiFg, BOLD, RESET } from "./theme/index.js";
43
+ import { clipToWidth } from "./text.js";
44
+ /** How long a `d` delete arm stays confirmable (double-press window). */
45
+ const DELETE_CONFIRM_MS = 2500;
46
+ /** The profile table columns: NAME/MODEL/THINK fit (capped), AGENTS right-aligned tail. */
47
+ function profileColumns(profiles, current) {
48
+ return autoColumns([
49
+ { key: 'name', title: 'Profile', cap: 18 },
50
+ { key: 'model', title: 'Default model', cap: 36 },
51
+ { key: 'think', title: 'Think', cap: 10 },
52
+ { key: 'agents', title: 'Agents', align: 'right' },
53
+ ], profiles, (profile, key) => profileCell(profile, key, current));
54
+ }
55
+ /** Cell text for one profile table row; the current profile gets the ● mark. */
56
+ function profileCell(profile, key, current) {
57
+ switch (key) {
58
+ case 'name': return current === profile.name ? `● ${profile.name}` : profile.name;
59
+ case 'model': return profile.defaultModel === undefined
60
+ ? '(not set)'
61
+ : `${profile.defaultModel.provider}/${profile.defaultModel.model}`;
62
+ case 'think': return profile.defaultModel?.reasoningEffort ?? 'default';
63
+ default: return String(Object.keys(profile.agents).length);
64
+ }
65
+ }
66
+ /** A profile's default route as a ModelSelection (the /model handler shape). */
67
+ function routeToSelection(route) {
68
+ return {
69
+ provider: route.provider,
70
+ model: route.model,
71
+ ...(route.reasoningEffort !== undefined
72
+ ? { reasoningEffort: route.reasoningEffort }
73
+ : {}),
74
+ };
75
+ }
76
+ /** Record what a live selection says into a plain storable route. */
77
+ function selectionToRoute(selection) {
78
+ return selection.reasoningEffort === undefined
79
+ ? { provider: selection.provider, model: selection.model }
80
+ : {
81
+ provider: selection.provider,
82
+ model: selection.model,
83
+ reasoningEffort: selection.reasoningEffort,
84
+ };
85
+ }
86
+ /**
87
+ * Apply `profile` end to end, scoped to the current workspace tree:
88
+ * default model + think level through the live selection ref (a profile
89
+ * without a default model leaves the selection alone), the tree binding
90
+ * (`.dsh-profile` in `cwd` — new sessions here assemble from this profile)
91
+ * and the store's informational last-applied pointer. Agent model/thinking
92
+ * values are NOT written anywhere here: each agent's runtime values compose
93
+ * at spawn from the frontmatter baseline ⊕ this pinned profile's overrides
94
+ * (src/agent-runtime.ts), which is what makes a switch workspace-scoped.
95
+ * Resolves the command summary text — failures ride along as ⚠ parts
96
+ * instead of aborting the rest. Deliberately does NOT touch the global
97
+ * agent-default-model: that is /model's write, and crossing tree boundaries
98
+ * is exactly what a profile switch must not do.
99
+ */
100
+ export async function applyProfile(path, doc, profile, deps, cwd) {
101
+ const parts = [];
102
+ if (profile.defaultModel !== undefined) {
103
+ deps.setSelection(routeToSelection(profile.defaultModel));
104
+ }
105
+ // Bind the tree BEFORE reporting: an ancestor pin would otherwise keep
106
+ // winning at assembly while the summary claims the switch took. A
107
+ // hand-decorated pin file is refused by bindWorkspaceProfile — surfaced,
108
+ // never clobbered.
109
+ const ancestor = readNearestProfilePin(cwd);
110
+ const pinError = bindWorkspaceProfile(cwd, profile.name);
111
+ if (pinError !== undefined) {
112
+ parts.push(`⚠ ${pinError}`);
113
+ }
114
+ else if (ancestor !== undefined
115
+ && ancestor.path !== join(cwd, PROFILE_PIN_FILE)
116
+ && ancestor.name.toLowerCase() !== profile.name.toLowerCase()) {
117
+ parts.push(`overrides ancestor pin "${ancestor.name}"`);
118
+ }
119
+ doc.current = profile.name;
120
+ const saveError = saveModelProfiles(path, doc);
121
+ if (saveError !== undefined)
122
+ parts.push(`⚠ profile not saved: ${saveError}`);
123
+ const modelText = profile.defaultModel !== undefined
124
+ ? formatProfileRoute(profile.defaultModel)
125
+ : 'model unchanged';
126
+ const pinText = pinError === undefined ? `pinned this tree (${PROFILE_PIN_FILE})` : '';
127
+ return [
128
+ `Profile → ${profile.name} · ${modelText} · agent values compose per-workspace at spawn`,
129
+ pinText,
130
+ ...parts,
131
+ ]
132
+ .filter(part => part !== '')
133
+ .join(' · ');
134
+ }
135
+ /**
136
+ * Open the `/profile-switch` switcher. Resolves the apply summary when a
137
+ * profile was switched, or `undefined` when cancelled / nothing applied.
138
+ *
139
+ * Enter is a WORKSPACE-SCOPED switch: apply to the live session, and bind
140
+ * this directory tree via `.dsh-profile` (see src/model-profiles.ts) so
141
+ * every NEW session here auto-loads it — agent model/think values are not
142
+ * written anywhere; each agent composes its runtime values at spawn from
143
+ * the frontmatter baseline ⊕ this pinned profile (src/agent-runtime.ts).
144
+ * Other trees are untouched. A
145
+ * hand-decorated pin file refuses the binding and says so in the summary.
146
+ * `p` binds/unbinds the cwd manually — pressing it on the already-bound
147
+ * profile unpins — unless the pin lives in an ancestor or was
148
+ * hand-decorated, which is reported in the status line instead.
149
+ */
150
+ export async function openProfileSwitcher(tui, theme, deps, restoreFocus) {
151
+ const path = modelProfilesPath();
152
+ const doc = loadModelProfiles(path);
153
+ const cwd = process.cwd();
154
+ // The ● current marker is THIS tree's binding, not a machine-wide pointer.
155
+ const currentName = boundProfileName(doc, cwd);
156
+ let settle;
157
+ const host = new PanelHost(tui, theme, message => {
158
+ restoreFocus();
159
+ settle?.(`✘ failed to open the profile switcher: ${message}`);
160
+ });
161
+ return new Promise(resolve => {
162
+ settle = resolve;
163
+ // Enter can fire again while the async apply runs — resolve-once guard,
164
+ // the same settle discipline the pickers use.
165
+ let applying = false;
166
+ // Session-local status flash for the p-toggle outcome.
167
+ let pinStatus;
168
+ /** The nearest `.dsh-profile` binding for the cwd, resolved per render. */
169
+ const boundPin = () => readNearestProfilePin(cwd);
170
+ /**
171
+ * Show the table once through the host (a p-toggle swaps the whole panel:
172
+ * simplest way to refresh the `(dir)` markers, and PanelHost's
173
+ * show-new-then-hide-old keeps focus stable).
174
+ */
175
+ const showTable = () => {
176
+ const pinnedName = boundPin()?.name;
177
+ const table = new TablePanel(theme, {
178
+ title: `● Model profiles · ${PROFILE_PIN_FILE} here: ${pinnedName ?? 'none'}`,
179
+ columns: profileColumns(doc.profiles, currentName),
180
+ rows: doc.profiles,
181
+ renderCell: (profile, column) => profileCell(profile, column.key, currentName),
182
+ preselect: Math.max(0, doc.profiles.findIndex(profile => profile.name === currentName)),
183
+ status: () => pinStatus,
184
+ onSelect: profile => { void switchTo(profile); },
185
+ onCancel: () => { host.close(); restoreFocus(); resolve(undefined); },
186
+ footer: '↑↓ navigate · Enter switch (binds this dir) · p pin/unpin · Esc back',
187
+ shortcuts: { p: () => togglePin(table) },
188
+ });
189
+ host.open(table);
190
+ };
191
+ /** Bind/unbind the cwd to the cursor profile; re-show to refresh markers. */
192
+ const togglePin = (table) => {
193
+ const profile = table.selectedRow();
194
+ if (profile === undefined)
195
+ return;
196
+ const existing = boundPin();
197
+ if (existing !== undefined
198
+ && existing.name.toLowerCase() === profile.name.toLowerCase()
199
+ && existing.path === join(cwd, PROFILE_PIN_FILE)) {
200
+ const error = removeProfilePin(cwd, profile.name);
201
+ pinStatus = error === undefined ? `unpinned ${cwd}` : `✘ ${error}`;
202
+ }
203
+ else {
204
+ const error = writeProfilePin(cwd, profile.name);
205
+ if (error !== undefined)
206
+ pinStatus = `✘ ${error}`;
207
+ else {
208
+ const inheritedNote = existing !== undefined && existing.path !== join(cwd, PROFILE_PIN_FILE)
209
+ ? ` (overrides ancestor pin "${existing.name}")`
210
+ : '';
211
+ pinStatus = `pinned "${profile.name}" → .dsh-profile${inheritedNote}`;
212
+ }
213
+ }
214
+ showTable();
215
+ };
216
+ // First mount; a mount failure has already settled through the host's
217
+ // error path (this promise resolves there, everything below is no-op).
218
+ showTable();
219
+ const switchTo = async (profile) => {
220
+ if (applying)
221
+ return;
222
+ applying = true;
223
+ // The summary carries the whole outcome — binding result, ancestor
224
+ // overrides, refusals — as its tail parts.
225
+ const summary = await applyProfile(path, doc, profile, deps, cwd);
226
+ host.close();
227
+ restoreFocus();
228
+ resolve(summary);
229
+ };
230
+ });
231
+ }
232
+ /**
233
+ * Open the `/profile-cfg` manager. Resolves a summary when anything changed,
234
+ * or `undefined` when the manager closed untouched. `preselect` jumps
235
+ * straight into one profile's fields window.
236
+ */
237
+ export async function openProfileManager(ctx, tui, theme, deps, restoreFocus, preselect) {
238
+ const path = modelProfilesPath();
239
+ const doc = loadModelProfiles(path);
240
+ // The ● current marker is THIS tree's binding, not a machine-wide pointer.
241
+ const currentName = boundProfileName(doc, process.cwd());
242
+ const changed = [];
243
+ let tableStatus;
244
+ let fieldsStatus;
245
+ let agentsStatus;
246
+ /** Armed double-press delete: profile name + when it was armed. */
247
+ let pendingDelete;
248
+ let pendingDeleteAt = 0;
249
+ let pendingPreselect = preselect;
250
+ let fieldsHandle;
251
+ let agentsHandle;
252
+ let agentsCursor;
253
+ /** Settles the manager promise (assigned inside the executor below). */
254
+ let settle;
255
+ const host = new PanelHost(tui, theme, message => {
256
+ restoreFocus();
257
+ settle?.(`✘ failed to open the profiles manager: ${message}`);
258
+ });
259
+ /** Persist the working doc; resolves a ✘ status line on failure. */
260
+ const persistDoc = () => {
261
+ const error = saveModelProfiles(path, doc);
262
+ return error === undefined ? undefined : `✘ could not save profiles: ${error}`;
263
+ };
264
+ /** Mark one profile as touched for the closing summary (first touch wins). */
265
+ const markChanged = (name) => {
266
+ if (!changed.includes(name))
267
+ changed.push(name);
268
+ };
269
+ return new Promise(resolve => {
270
+ settle = resolve;
271
+ const showTable = () => {
272
+ const preselectIndex = pendingPreselect === undefined
273
+ ? undefined
274
+ : doc.profiles.findIndex(profile => profile.name === pendingPreselect);
275
+ pendingPreselect = undefined;
276
+ const table = new TablePanel(theme, {
277
+ title: '● Model profiles — configure',
278
+ columns: profileColumns(doc.profiles, currentName),
279
+ rows: doc.profiles,
280
+ renderCell: (profile, column) => profileCell(profile, column.key, currentName),
281
+ preselect: preselectIndex !== undefined && preselectIndex >= 0 ? preselectIndex : undefined,
282
+ status: () => tableStatus,
283
+ footer: '↑↓ navigate · Enter open · n new · d delete · Esc back',
284
+ onSelect: profile => showFields(profile),
285
+ onCancel: closeManager,
286
+ shortcuts: {
287
+ n: showNew,
288
+ d: () => {
289
+ const profile = table.selectedRow();
290
+ if (profile === undefined)
291
+ return;
292
+ // Double-press confirm, time-boxed: the armed state must NOT
293
+ // rebuild the table (a rebuild resets the cursor to row 0, so
294
+ // the confirming press would target a different profile), and
295
+ // the window expires so a stale arm can never fire one press
296
+ // after the user navigated away and back.
297
+ const armed = pendingDelete === profile.name
298
+ && Date.now() - pendingDeleteAt < DELETE_CONFIRM_MS;
299
+ if (armed) {
300
+ pendingDelete = undefined;
301
+ const error = deleteProfile(doc, profile.name);
302
+ if (error !== undefined)
303
+ tableStatus = `✘ ${error}`;
304
+ else {
305
+ tableStatus = persistDoc() ?? `deleted profile "${profile.name}"`;
306
+ markChanged(`-${profile.name}`);
307
+ }
308
+ showTable();
309
+ }
310
+ else {
311
+ pendingDelete = profile.name;
312
+ pendingDeleteAt = Date.now();
313
+ tableStatus = `press d again to delete "${profile.name}"`;
314
+ tui.requestRender();
315
+ }
316
+ },
317
+ },
318
+ });
319
+ host.open(table);
320
+ };
321
+ const closeManager = () => {
322
+ host.close();
323
+ restoreFocus();
324
+ resolve(changed.length > 0 ? `Profiles updated: ${changed.join(', ')}` : undefined);
325
+ };
326
+ const showFields = (profile) => {
327
+ const content = [
328
+ `named model snapshot — /profile-switch applies it live and binds this directory tree · agent overrides compose per-workspace at spawn (frontmatter baseline ⊕ this profile — no global write)`,
329
+ ];
330
+ const agentsSet = Object.keys(profile.agents).length;
331
+ const fields = [
332
+ {
333
+ key: 'model',
334
+ value: profile.defaultModel === undefined
335
+ ? '(not set — switching keeps the current model)'
336
+ : formatProfileRoute(profile.defaultModel),
337
+ editable: true,
338
+ },
339
+ {
340
+ key: 'think',
341
+ value: profile.defaultModel?.reasoningEffort ?? '(provider default)',
342
+ editable: true,
343
+ },
344
+ { key: 'agents', value: `${String(agentsSet)} agent override(s) recorded`, editable: true },
345
+ ];
346
+ const view = new FieldPanel(theme, {
347
+ title: profileTitle(theme, profile, currentName),
348
+ content,
349
+ fields,
350
+ status: () => fieldsStatus,
351
+ footer: '↑↓ field · Enter edit (m model · t think · a agents) · s save current · r rename · v review · Esc back',
352
+ shortcuts: {
353
+ m: () => void changeModel(profile),
354
+ t: () => void changeThink(profile),
355
+ a: () => showAgentsTable(profile),
356
+ s: () => saveFromCurrent(profile),
357
+ r: () => showRename(profile),
358
+ v: () => showReview(profile),
359
+ },
360
+ onEdit: index => {
361
+ if (index === 0)
362
+ void changeModel(profile);
363
+ else if (index === 1)
364
+ void changeThink(profile);
365
+ else
366
+ showAgentsTable(profile);
367
+ },
368
+ onCancel: () => showTable(),
369
+ });
370
+ fieldsHandle = host.open(view);
371
+ };
372
+ /** Commit path shared by model/think/agent edits: flash and re-show. */
373
+ const writeFields = (profile, status, message) => {
374
+ if (status !== undefined)
375
+ fieldsStatus = status;
376
+ else {
377
+ markChanged(profile.name);
378
+ fieldsStatus = message;
379
+ }
380
+ showFields(profile);
381
+ };
382
+ const changeModel = async (profile) => {
383
+ const current = profile.defaultModel === undefined ? undefined : routeToSelection(profile.defaultModel);
384
+ const picked = await pickModel(ctx, tui, theme, current, () => { }, host);
385
+ if (picked === undefined) {
386
+ showFields(profile);
387
+ return;
388
+ }
389
+ profile.defaultModel = selectionToRoute(picked);
390
+ const status = persistDoc();
391
+ writeFields(profile, status, `saved ${profile.name} default model → ${formatProfileRoute(profile.defaultModel)}`);
392
+ };
393
+ const changeThink = async (profile) => {
394
+ if (profile.defaultModel === undefined) {
395
+ fieldsStatus = '✘ no default model set — pick a model first (m)';
396
+ showFields(profile);
397
+ return;
398
+ }
399
+ const llm = ctx.get('llm');
400
+ if (llm === undefined) {
401
+ fieldsStatus = '✘ llm service unavailable';
402
+ showFields(profile);
403
+ return;
404
+ }
405
+ const { provider, model } = profile.defaultModel;
406
+ let efforts;
407
+ try {
408
+ efforts = (await llm.resolveModelInfo(provider, model)).reasoning?.efforts;
409
+ }
410
+ catch {
411
+ efforts = undefined;
412
+ }
413
+ if (efforts === undefined || efforts.length === 0) {
414
+ fieldsStatus = `✘ ${provider}/${model} exposes no think levels`;
415
+ showFields(profile);
416
+ return;
417
+ }
418
+ const currentEffort = profile.defaultModel.reasoningEffort;
419
+ const chosen = await openEffortPicker(tui, theme, efforts, currentEffort, () => { }, () => fieldsHandle?.hide());
420
+ if (chosen === undefined) {
421
+ showFields(profile);
422
+ return;
423
+ }
424
+ profile.defaultModel.reasoningEffort = chosen.effort === 'default' ? undefined : chosen.effort;
425
+ const status = persistDoc();
426
+ const thinkText = profile.defaultModel.reasoningEffort === undefined
427
+ ? 'provider default'
428
+ : `think ${String(profile.defaultModel.reasoningEffort)}`;
429
+ writeFields(profile, status, `saved ${profile.name} think → ${thinkText}`);
430
+ };
431
+ const saveFromCurrent = (profile) => {
432
+ const selection = deps.getSelection();
433
+ if (selection !== undefined)
434
+ profile.defaultModel = selectionToRoute(selection);
435
+ const { agents } = listAgentFiles(agentsDir());
436
+ profile.agents = captureAgentsSnapshot(agents);
437
+ const status = persistDoc();
438
+ const message = selection === undefined
439
+ ? `saved ${String(agents.length)} agent(s) into "${profile.name}" · no live model selection — default model kept`
440
+ : `saved current configuration into "${profile.name}" (model + ${String(agents.length)} agent(s))`;
441
+ writeFields(profile, status, message);
442
+ };
443
+ const showAgentsTable = (profile) => {
444
+ const { agents } = listAgentFiles(agentsDir());
445
+ const rows = agents.map(agent => ({ agent, entry: profile.agents[agent.meta.name] }));
446
+ const cell = (row, key) => {
447
+ switch (key) {
448
+ case 'name': return row.agent.meta.displayName ?? row.agent.meta.name;
449
+ case 'model': return row.entry?.model ?? (row.entry === undefined ? '(not saved)' : '(inherit)');
450
+ default: return row.entry?.thinking ?? 'inherit';
451
+ }
452
+ };
453
+ const table = new TablePanel(theme, {
454
+ title: `● Agents — ${profile.name}`,
455
+ columns: autoColumns([
456
+ { key: 'name', title: 'Agent', cap: 16 },
457
+ { key: 'model', title: 'Model', cap: 36 },
458
+ { key: 'thinking', title: 'Think', cap: 10 },
459
+ ], rows, cell),
460
+ rows,
461
+ renderCell: (row, column) => cell(row, column.key),
462
+ preselect: Math.max(0, rows.findIndex(row => row.agent.meta.name === agentsCursor)),
463
+ status: () => agentsStatus,
464
+ footer: '↑↓ navigate · Enter pick model (i inherit · t think) · Esc back',
465
+ onSelect: row => {
466
+ agentsCursor = row.agent.meta.name;
467
+ void pickAgentModel(profile, row);
468
+ },
469
+ onCancel: () => showFields(profile),
470
+ shortcuts: {
471
+ i: () => {
472
+ const row = table.selectedRow();
473
+ if (row === undefined)
474
+ return;
475
+ profile.agents[row.agent.meta.name] = {};
476
+ const status = persistDoc();
477
+ agentsStatus = status ?? `saved ${row.agent.meta.name} → inherit`;
478
+ if (status === undefined)
479
+ markChanged(profile.name);
480
+ showAgentsTable(profile);
481
+ },
482
+ t: () => {
483
+ const row = table.selectedRow();
484
+ if (row === undefined)
485
+ return;
486
+ void changeAgentThinking(profile, row);
487
+ },
488
+ },
489
+ });
490
+ agentsHandle = host.open(table);
491
+ };
492
+ /** Pick one agent's model + think through the full /model picker. */
493
+ const pickAgentModel = async (profile, row) => {
494
+ const current = row.entry?.model === undefined
495
+ ? undefined
496
+ : selectionFromRoute(row.entry.model, row.entry.thinking);
497
+ const picked = await pickModel(ctx, tui, theme, current, () => { }, host);
498
+ if (picked === undefined) {
499
+ showAgentsTable(profile);
500
+ return;
501
+ }
502
+ profile.agents[row.agent.meta.name] = picked.reasoningEffort === undefined
503
+ ? { model: `${picked.provider}/${picked.model}` }
504
+ : { model: `${picked.provider}/${picked.model}`, thinking: picked.reasoningEffort };
505
+ const status = persistDoc();
506
+ const entry = profile.agents[row.agent.meta.name];
507
+ const thinkText = entry.thinking === undefined ? '' : ` · think ${entry.thinking}`;
508
+ agentsStatus = status ?? `saved ${row.agent.meta.name} → ${entry.model ?? ''}${thinkText}`;
509
+ if (status === undefined)
510
+ markChanged(profile.name);
511
+ showAgentsTable(profile);
512
+ };
513
+ /** Edit one agent's think level alone (its recorded model must be set). */
514
+ const changeAgentThinking = async (profile, row) => {
515
+ const route = row.entry?.model;
516
+ if (route === undefined) {
517
+ agentsStatus = '✘ no model recorded — pick a model first (Enter)';
518
+ showAgentsTable(profile);
519
+ return;
520
+ }
521
+ const llm = ctx.get('llm');
522
+ if (llm === undefined) {
523
+ agentsStatus = '✘ llm service unavailable';
524
+ showAgentsTable(profile);
525
+ return;
526
+ }
527
+ const slash = route.indexOf('/');
528
+ if (slash <= 0) {
529
+ agentsStatus = `✘ cannot resolve think levels for ${route}`;
530
+ showAgentsTable(profile);
531
+ return;
532
+ }
533
+ let efforts;
534
+ try {
535
+ efforts = (await llm.resolveModelInfo(route.slice(0, slash), route.slice(slash + 1))).reasoning?.efforts;
536
+ }
537
+ catch {
538
+ efforts = undefined;
539
+ }
540
+ if (efforts === undefined || efforts.length === 0) {
541
+ agentsStatus = `✘ ${route} exposes no think levels`;
542
+ showAgentsTable(profile);
543
+ return;
544
+ }
545
+ const entry = profile.agents[row.agent.meta.name] ?? {};
546
+ const currentEffort = entry.thinking;
547
+ const chosen = await openEffortPicker(tui, theme, efforts, currentEffort, () => { }, () => agentsHandle?.hide());
548
+ if (chosen === undefined) {
549
+ showAgentsTable(profile);
550
+ return;
551
+ }
552
+ const next = chosen.effort === 'default' ? undefined : chosen.effort;
553
+ if (next === undefined)
554
+ delete entry.thinking;
555
+ else
556
+ entry.thinking = next;
557
+ profile.agents[row.agent.meta.name] = entry;
558
+ const status = persistDoc();
559
+ agentsStatus = status ?? `saved ${row.agent.meta.name} → think ${next ?? 'inherit'}`;
560
+ if (status === undefined)
561
+ markChanged(profile.name);
562
+ showAgentsTable(profile);
563
+ };
564
+ const showRename = (profile) => {
565
+ let committed = false;
566
+ const field = new EditField(tui, {
567
+ title: `Rename profile — ${profile.name}`,
568
+ subtitle: 'letters, digits, dash, space · must stay unique',
569
+ initial: profile.name,
570
+ parse: text => parseProfileName(doc, profile.name, text),
571
+ onCommit: async (parsed) => {
572
+ if (parsed.kind !== 'value')
573
+ return undefined;
574
+ const name = parsed.value;
575
+ const error = renameProfile(doc, profile.name, name);
576
+ if (error !== undefined)
577
+ return { error };
578
+ committed = true;
579
+ pendingPreselect = name;
580
+ return undefined;
581
+ },
582
+ onDone: () => {
583
+ if (committed) {
584
+ const status = persistDoc();
585
+ tableStatus = status ?? `renamed → ${profile.name}`;
586
+ if (status === undefined)
587
+ markChanged(profile.name);
588
+ }
589
+ showTable();
590
+ },
591
+ onError: message => { tableStatus = `✘ ${message}`; },
592
+ }, theme);
593
+ host.open(field);
594
+ };
595
+ const showNew = () => {
596
+ let committed = false;
597
+ const field = new EditField(tui, {
598
+ title: 'New model profile',
599
+ subtitle: 'a name like "work" or "personal" · configured next',
600
+ initial: '',
601
+ parse: text => parseProfileName(doc, '', text),
602
+ onCommit: async (parsed) => {
603
+ if (parsed.kind !== 'value')
604
+ return undefined;
605
+ const { profile, error } = createProfile(doc, parsed.value);
606
+ if (error !== undefined || profile === undefined)
607
+ return { error: error ?? 'create failed' };
608
+ committed = true;
609
+ pendingPreselect = profile.name;
610
+ return undefined;
611
+ },
612
+ onDone: () => {
613
+ if (committed) {
614
+ const status = persistDoc();
615
+ tableStatus = status ?? `created profile "${pendingPreselect}"`;
616
+ if (status === undefined && pendingPreselect !== undefined)
617
+ markChanged(`+${pendingPreselect}`);
618
+ }
619
+ showTable();
620
+ },
621
+ onError: message => { tableStatus = `✘ ${message}`; },
622
+ }, theme);
623
+ host.open(field);
624
+ };
625
+ const showReview = (profile) => {
626
+ const { agents } = listAgentFiles(agentsDir());
627
+ host.open(new ViewerPanel(theme, {
628
+ title: `ⓘ Model profile — ${profile.name}`,
629
+ lines: profileReviewLines(profile, agents, currentName === profile.name),
630
+ footer: ' Esc to close',
631
+ onClose: () => showFields(profile),
632
+ }));
633
+ };
634
+ showTable();
635
+ });
636
+ }
637
+ /** The accent title line of the profile fields window (current profiles get the ● mark). */
638
+ function profileTitle(theme, profile, currentName) {
639
+ const mark = currentName === profile.name ? '● ' : '';
640
+ return ansiFg(theme.palette.accent) + BOLD + `${mark}${clipToWidth(profile.name, 100)}` + RESET;
641
+ }
642
+ /** Name-editor parse: trimmed non-empty, unique against the doc (case-insensitive). */
643
+ function parseProfileName(doc, currentName, text) {
644
+ const name = text.trim();
645
+ if (name === '')
646
+ return { kind: 'error', error: 'profile name must not be empty' };
647
+ if (name.length > 32)
648
+ return { kind: 'error', error: 'profile name must stay under 33 characters' };
649
+ if (name.toLowerCase() !== currentName.toLowerCase() && findProfile(doc, name) !== undefined) {
650
+ return { kind: 'error', error: `a profile named "${name}" already exists` };
651
+ }
652
+ return { kind: 'value', value: name };
653
+ }
654
+ /** An agent entry's `provider/model` route back into a picker preselection. */
655
+ function selectionFromRoute(route, thinking) {
656
+ const slash = route.indexOf('/');
657
+ if (slash <= 0)
658
+ return { provider: route, model: route };
659
+ return {
660
+ provider: route.slice(0, slash),
661
+ model: route.slice(slash + 1),
662
+ ...(thinking !== undefined ? { reasoningEffort: thinking } : {}),
663
+ };
664
+ }
665
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwayds/dsh-tui-pi",
3
- "version": "2.31.0",
3
+ "version": "2.32.0",
4
4
  "description": "A fully-featured pi-style terminal UI for DeepSeek Harness (dsh) — history look-back & fork-at-turn, guided preset switching, live subagent steering, model profiles & themes",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,8 @@
34
34
  "./locale/*.json": "./locale/*.json"
35
35
  },
36
36
  "bin": {
37
- "dsh-tui-pi": "bin/dsh-tui-pi"
37
+ "dsh-tui-pi": "bin/dsh-tui-pi",
38
+ "dst": "bin/dsh-tui-pi"
38
39
  },
39
40
  "license": "MIT",
40
41
  "engines": {
@@ -63,7 +64,7 @@
63
64
  "pretest": "npm run build",
64
65
  "test": "node --test test/*.test.mjs",
65
66
  "smoke": "node scripts/smoke-boot.mjs",
66
- "postinstall": "node scripts/link-dsh-closure.mjs"
67
+ "postinstall": "node scripts/postinstall.mjs"
67
68
  },
68
69
  "dependencies": {
69
70
  "@aiwayds/dsh-ask-router": "^0.6.0",
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npm postinstall for `@aiwayds/dsh-tui-pi` — two jobs, in order:
4
+ *
5
+ * 1. The repo's dev linker (link-dsh-closure.mjs). It self-guards: on a
6
+ * published tarball (no src/) it exits 0 without touching anything, so
7
+ * this is a no-op for consumers and the dev-machine behavior is
8
+ * unchanged.
9
+ *
10
+ * 2. Global installs then run the one-time onboarding — dsh host, pnpm and
11
+ * the `tui` profile are in place by the time npm prints "added 1
12
+ * package", so the first `dsh-tui-pi` (or `dst`) boots straight into the
13
+ * TUI. Anything this cannot finish (offline machine, no npm on PATH, a
14
+ * package manager that skips lifecycle scripts) is deferred to the
15
+ * launcher's first run, which re-runs the same idempotent onboarding.
16
+ *
17
+ * Guards:
18
+ * - local/dev installs (`npm i` inside a project, `pnpm link`, CI
19
+ * checkouts) skip the onboarding silently — it is only meaningful for a
20
+ * user-wide install;
21
+ * - this script NEVER fails the install: every failure path exits 0.
22
+ */
23
+
24
+ import { spawnSync } from 'node:child_process'
25
+ import { dirname, join } from 'node:path'
26
+ import { fileURLToPath } from 'node:url'
27
+
28
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
29
+
30
+ // 1) dev closure linker — self-guarding, always safe to run
31
+ const linker = spawnSync(process.execPath, [join(scriptDir, 'link-dsh-closure.mjs')], {
32
+ stdio: 'inherit',
33
+ })
34
+ if (linker.error) {
35
+ console.warn(`[postinstall] closure linker skipped: ${linker.error.message}`)
36
+ }
37
+
38
+ // 2) onboarding — global installs only, never fails the install
39
+ if (process.env.npm_config_global !== 'true') process.exit(0)
40
+
41
+ console.log('dsh-tui-pi: preparing the dsh host, pnpm and the "tui" profile…')
42
+ try {
43
+ const { runOnboard } = await import('../bin/onboard-core.mjs')
44
+ const setup = runOnboard({ log: (line) => console.log(line) })
45
+ if (!setup.ok) {
46
+ console.log(`dsh-tui-pi: setup stopped at "${setup.step}" — ${setup.hint}`)
47
+ console.log('dsh-tui-pi: it will complete automatically on first launch.')
48
+ } else {
49
+ console.log('dsh-tui-pi: ready — launch with `dsh-tui-pi` (short: `dst`).')
50
+ }
51
+ } catch (error) {
52
+ console.log(`dsh-tui-pi: setup skipped (${error?.message ?? error}) — it will run on first launch.`)
53
+ }
54
+ process.exit(0)
@@ -83,12 +83,34 @@ const install = spawnSync('pnpm', ['install'], { cwd: profile, encoding: 'utf8'
83
83
  if (install.status !== 0 || install.error) fail('pnpm install in the scratch profile failed', `${install.stdout}\n${install.stderr}`)
84
84
 
85
85
  const dshEnv = { ...process.env, DSH_HOME: home, TERM: process.env.TERM ?? 'xterm-256color' }
86
+ // `npm run` poisons the child environment in two ways that both break the
87
+ // real-host dump:
88
+ // 1. npm_config_* (notably npm_config_prefix) leaks npm's view of the
89
+ // world into the dsh CLI;
90
+ // 2. PATH now leads with this repo's node_modules/.bin — if a dev
91
+ // closure ever installed @deepseek-ai/dsh locally (a 0.1.7-rc.1 copy
92
+ // lives in .pnpm here), THAT stale host resolves first and fails the
93
+ // plugin peerDependencies check, skipping the bundle.
94
+ // Strip both so the smoke always exercises the global dsh on PATH.
95
+ const pathSep = process.platform === 'win32' ? ';' : ':'
96
+ dshEnv.PATH = (dshEnv.PATH ?? '')
97
+ .split(pathSep)
98
+ .filter((seg) => seg && seg !== path.join(repoRoot, 'node_modules', '.bin'))
99
+ .join(pathSep)
100
+ for (const key of Object.keys(dshEnv)) {
101
+ if (key.startsWith('npm_config_')) delete dshEnv[key]
102
+ }
86
103
 
87
104
  // Phase 1 — mount proof: the composed tree must include the plugin, disable
88
105
  // the stock projection-cache row and mount the projcache wrapper instead.
89
106
  const dump = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
90
107
  if (dump.status !== 0 || dump.error) fail('dsh --dump-config failed on the scratch profile', `${dump.stdout}\n${dump.stderr}`)
91
108
  if (!dump.stdout.includes(ownName)) {
109
+ writeFileSync('/tmp/smoke-dump-fail.txt', [
110
+ `status=${dump.status} stdoutBytes=${Buffer.byteLength(dump.stdout ?? '')} stderrBytes=${Buffer.byteLength(dump.stderr ?? '')}`,
111
+ '--- stdout head ---', (dump.stdout ?? '').slice(0, 2000),
112
+ '--- stderr head ---', (dump.stderr ?? '').slice(0, 2000),
113
+ ].join('\n'))
92
114
  fail(`the composed profile tree does not contain ${ownName} — the bundle patch insert is broken`, dump.stdout)
93
115
  }
94
116
  if (!/id: session-projection-cache[\s\S]*?disabled: true/.test(dump.stdout)) {