@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 +13 -2
- package/bin/boot.mjs +47 -0
- package/bin/dsh-tui-pi +4 -4
- package/bin/onboard-core.mjs +275 -0
- package/lib/profile.d.ts +82 -0
- package/lib/profile.js +665 -0
- package/lib/profile.js.map +1 -0
- package/package.json +4 -3
- package/scripts/postinstall.mjs +54 -0
- package/scripts/smoke-boot.mjs +22 -0
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
|
|
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.
|
|
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
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
|
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
|
+
}
|
package/lib/profile.d.ts
ADDED
|
@@ -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.
|
|
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/
|
|
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)
|
package/scripts/smoke-boot.mjs
CHANGED
|
@@ -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)) {
|