@dsh-cc/cli 0.9.0-rc.4 → 0.9.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/bin/dsh-cc.js +74 -1
- package/bootstrap.mjs +39 -6
- package/package.json +1 -1
package/bin/dsh-cc.js
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
9
9
|
import { homedir } from 'node:os'
|
|
10
10
|
import { dirname, join } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
|
-
import { bootstrapCommand, BOOTSTRAP_STAMP, devStoreRestoreDecision, dshUnavailableMessage, existingWorktreeDecision, formatVersionLabel, healDecision, interceptResume, parseLocalConfig, parseWorktreeFlag, parseWorktreeRef, planWorktree, planWorktreeRef, prFetchRefs, readBootstrapVersion, remoteHost, PROFILE, readBuildInfo, repoRootFromCommonDir, runStoreHeal, runStoreRestore, sanitizeInheritedEnv, slugRetryDecision, spawnEnv, symlinkedPath, versionGate, worktreeAddArgv, worktreeEnv, worktreeIdentityRefusal, writeBootstrapStamp } from '../bootstrap.mjs'
|
|
12
|
+
import { bootstrapCommand, BOOTSTRAP_STAMP, ACP_PROFILE, ACP_BUNDLES, acpBundleSpec, devStoreRestoreDecision, dshUnavailableMessage, existingWorktreeDecision, formatVersionLabel, healDecision, interceptResume, parseLocalConfig, parseWorktreeFlag, parseWorktreeRef, planWorktree, planWorktreeRef, prFetchRefs, readBootstrapVersion, remoteHost, PROFILE, readBuildInfo, repoRootFromCommonDir, runStoreHeal, runStoreRestore, sanitizeInheritedEnv, slugRetryDecision, spawnEnv, symlinkedPath, versionGate, worktreeAddArgv, worktreeEnv, worktreeIdentityRefusal, writeBootstrapStamp } from '../bootstrap.mjs'
|
|
13
13
|
import { readWorktreeSettings, resolveBaseRef, sweepWorktrees, SWEEP_CAP_MS, worktreeReuseReset, worktreeSettingsPaths } from '../worktree-lifecycle.mjs'
|
|
14
14
|
|
|
15
15
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
@@ -27,6 +27,78 @@ if (process.argv.includes('--version') || process.argv.includes('-V')) {
|
|
|
27
27
|
console.log(formatVersionLabel(ownVersion, buildInfo))
|
|
28
28
|
process.exit(0)
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
// `dsh-cc acp [...]` — the Agent Client Protocol lane (design
|
|
32
|
+
// 2026-10-09-acp-m2-own-plugin §5.1). Dispatched BEFORE any TUI-specific
|
|
33
|
+
// processing (worktree parsing, interceptResume, DSH_CC_* env): stdout
|
|
34
|
+
// belongs to the ACP NDJSON protocol, so bootstrap/install output goes to
|
|
35
|
+
// stderr only, the branch never consumes stdin, and the child dsh inherits
|
|
36
|
+
// stdio so the protocol flows through.
|
|
37
|
+
if (process.argv[2] === 'acp') {
|
|
38
|
+
const restArgs = process.argv.slice(3)
|
|
39
|
+
const acpSpecFor = name => acpBundleSpec(name, ownVersion)
|
|
40
|
+
const acpProfileDir = join(home, 'profiles', ACP_PROFILE)
|
|
41
|
+
const acpExisted = existsSync(join(acpProfileDir, 'package.json'))
|
|
42
|
+
const acpAdd = bootstrapCommand(acpExisted, ownVersion, { profile: ACP_PROFILE, bundles: ACP_BUNDLES, specFor: acpSpecFor })
|
|
43
|
+
if (acpAdd !== undefined) {
|
|
44
|
+
console.error(`dsh-cc: initializing profile "${ACP_PROFILE}"…`)
|
|
45
|
+
const acpGate = versionGate(() => spawnSync('dsh', ['--version'], { encoding: 'utf8' }))
|
|
46
|
+
if (acpGate.warning) console.error(acpGate.warning)
|
|
47
|
+
if (!acpGate.ok) {
|
|
48
|
+
console.error(acpGate.message)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
const installed = spawnSync('dsh', acpAdd, { encoding: 'utf8', stdio: 'inherit' })
|
|
52
|
+
if (installed.error) {
|
|
53
|
+
console.error(dshUnavailableMessage())
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
if (installed.status !== 0) {
|
|
57
|
+
console.error(`dsh-cc: plugin install failed. Retry:\n dsh ${acpAdd.join(' ')}`)
|
|
58
|
+
process.exit(installed.status ?? 1)
|
|
59
|
+
}
|
|
60
|
+
writeBootstrapStamp(join(acpProfileDir, BOOTSTRAP_STAMP), ownVersion)
|
|
61
|
+
}
|
|
62
|
+
// Same converge/restore machinery as the TUI path, with the ACP floor list
|
|
63
|
+
// and specifiers; all of it logs to stderr.
|
|
64
|
+
const acpBuildInfo = readBuildInfo(join(acpProfileDir, 'node_modules', '@dsh-cc', 'dsh-cc-build.json'))
|
|
65
|
+
const acpRestore = devStoreRestoreDecision(acpBuildInfo, ownVersion)
|
|
66
|
+
const acpConvergeOpts = { bundles: ACP_BUNDLES, specFor: acpSpecFor }
|
|
67
|
+
if (acpRestore !== null) {
|
|
68
|
+
const restored = runStoreRestore(acpProfileDir, ownVersion, acpConvergeOpts)
|
|
69
|
+
if (restored.restored) writeBootstrapStamp(join(acpProfileDir, BOOTSTRAP_STAMP), ownVersion)
|
|
70
|
+
} else {
|
|
71
|
+
const heal = healDecision({
|
|
72
|
+
profileExists: acpExisted && acpAdd === undefined,
|
|
73
|
+
stampVersion: readBootstrapVersion(join(acpProfileDir, BOOTSTRAP_STAMP)),
|
|
74
|
+
ownVersion,
|
|
75
|
+
buildInfo: acpBuildInfo,
|
|
76
|
+
})
|
|
77
|
+
if (heal !== null) runStoreHeal(acpProfileDir, ownVersion, { from: heal.from, ...acpConvergeOpts })
|
|
78
|
+
}
|
|
79
|
+
// Never run TUI arg processing on this path: restArgs pass through as-is.
|
|
80
|
+
const acpEnv = sanitizeInheritedEnv({ ...process.env })
|
|
81
|
+
acpEnv.NODE_ENV ??= 'production'
|
|
82
|
+
acpEnv.DSH_CC_PROFILE = ACP_PROFILE
|
|
83
|
+
const acpChild = spawn('dsh', ['--profile', ACP_PROFILE, ...restArgs], {
|
|
84
|
+
env: spawnEnv(acpEnv, home),
|
|
85
|
+
stdio: 'inherit',
|
|
86
|
+
})
|
|
87
|
+
acpChild.on('error', (error) => {
|
|
88
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT') {
|
|
89
|
+
console.error(dshUnavailableMessage())
|
|
90
|
+
process.exit(1)
|
|
91
|
+
}
|
|
92
|
+
throw error
|
|
93
|
+
})
|
|
94
|
+
acpChild.on('exit', (code, signal) => {
|
|
95
|
+
if (signal) {
|
|
96
|
+
process.kill(process.pid, signal)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
process.exit(code ?? 0)
|
|
100
|
+
})
|
|
101
|
+
} else {
|
|
30
102
|
const profileExisted = existsSync(join(profileDir, 'package.json'))
|
|
31
103
|
const add = bootstrapCommand(profileExisted, ownVersion)
|
|
32
104
|
if (add !== undefined) {
|
|
@@ -336,3 +408,4 @@ child.on('exit', (code, signal) => {
|
|
|
336
408
|
}
|
|
337
409
|
process.exit(code ?? 0)
|
|
338
410
|
})
|
|
411
|
+
}
|
package/bootstrap.mjs
CHANGED
|
@@ -14,6 +14,35 @@ export const BUNDLES = [
|
|
|
14
14
|
'@dsh-cc/bundle-tui',
|
|
15
15
|
]
|
|
16
16
|
|
|
17
|
+
/** Profile name for the ACP (`dsh-cc acp`) lane. */
|
|
18
|
+
export const ACP_PROFILE = 'cc-acp'
|
|
19
|
+
/**
|
|
20
|
+
* Floor bundle list for the ACP lane. dsh-base resolves from the
|
|
21
|
+
* installation (tui precedent: not a profile dependency); dsh-acp-app stays
|
|
22
|
+
* in the floor carrying the startup latch + hmr/system-prompt rows.
|
|
23
|
+
*/
|
|
24
|
+
export const ACP_BUNDLES = [
|
|
25
|
+
'@deepseek-ai/dsh-base',
|
|
26
|
+
'@deepseek-ai/dsh-acp-app',
|
|
27
|
+
'@dsh-cc/bundle-permissions',
|
|
28
|
+
'@dsh-cc/bundle-shell',
|
|
29
|
+
'@dsh-cc/bundle-acp',
|
|
30
|
+
]
|
|
31
|
+
/** Exact pin for dsh-acp-app: its npm latest is a fossil, never bare-install it. */
|
|
32
|
+
export const ACP_APP_PIN = '0.2.0-rc.2'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Install specifier for an ACP floor bundle. dsh-base is passed bare (it
|
|
36
|
+
* resolves from the dsh installation, never becomes a profile dependency);
|
|
37
|
+
* dsh-acp-app is pinned to the harness release; @dsh-cc bundles follow the
|
|
38
|
+
* launcher version scheme, like the TUI path.
|
|
39
|
+
*/
|
|
40
|
+
export function acpBundleSpec(name, version) {
|
|
41
|
+
if (name === '@deepseek-ai/dsh-base') return name
|
|
42
|
+
if (name === '@deepseek-ai/dsh-acp-app') return `${name}@${ACP_APP_PIN}`
|
|
43
|
+
return `${name}@${version}`
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
/**
|
|
18
47
|
* Scan dsh-cc args for resume-mode flags and translate them into the env
|
|
19
48
|
* contract the TUI plugin consumes. Collection is order-independent: all
|
|
@@ -138,10 +167,12 @@ export function spawnEnv(env, dshHome) {
|
|
|
138
167
|
/**
|
|
139
168
|
* @param {boolean} profileExists
|
|
140
169
|
* @param {string} version
|
|
170
|
+
* @param {{ profile?: string, bundles?: string[], specFor?: (name: string) => string }} [opts]
|
|
141
171
|
*/
|
|
142
|
-
export function bootstrapCommand(profileExists, version) {
|
|
172
|
+
export function bootstrapCommand(profileExists, version, { profile = PROFILE, bundles = BUNDLES, specFor } = {}) {
|
|
143
173
|
if (profileExists) return undefined
|
|
144
|
-
|
|
174
|
+
const spec = specFor ?? (name => `${name}@${version}`)
|
|
175
|
+
return ['plugin', '--profile', profile, 'add', ...bundles.map(spec)]
|
|
145
176
|
}
|
|
146
177
|
|
|
147
178
|
// --- worktree support (--worktree) ------------------------------------------
|
|
@@ -692,7 +723,8 @@ export function devStoreRestoreDecision(info, ownVersion) {
|
|
|
692
723
|
* @returns {{ restored: boolean, reason?: string, from?: string, to?: string }>}
|
|
693
724
|
*/
|
|
694
725
|
export function runStoreRestore(profileDir, ownVersion, deps = {}) {
|
|
695
|
-
const { spawnSyncImpl = spawnSync, log = console.error, now = Date.now } = deps
|
|
726
|
+
const { spawnSyncImpl = spawnSync, log = console.error, now = Date.now, bundles = BUNDLES, specFor } = deps
|
|
727
|
+
const spec = specFor ?? (name => `${name}@${ownVersion}`)
|
|
696
728
|
const scope = join(profileDir, 'node_modules', '@dsh-cc')
|
|
697
729
|
const backup = `${scope}.__dev-restore-backup`
|
|
698
730
|
const lock = join(profileDir, 'node_modules', '.dsh-cc-restore.lock')
|
|
@@ -775,7 +807,7 @@ export function runStoreRestore(profileDir, ownVersion, deps = {}) {
|
|
|
775
807
|
setAside = true
|
|
776
808
|
|
|
777
809
|
// 4. Re-materialize from the registry, same command surface as first boot.
|
|
778
|
-
const result = spawnSyncImpl('dsh', ['plugin', '--profile', profileName, 'add', ...
|
|
810
|
+
const result = spawnSyncImpl('dsh', ['plugin', '--profile', profileName, 'add', ...bundles.map(spec)], {
|
|
779
811
|
stdio: 'inherit',
|
|
780
812
|
env: spawnEnv(sanitizeInheritedEnv(process.env), home),
|
|
781
813
|
})
|
|
@@ -896,7 +928,8 @@ export function writeBootstrapStamp(stampPath, ownVersion, log = console.error)
|
|
|
896
928
|
* @returns {{ healed: boolean, reason?: 'locked' | 'plugin-add-failed' }}
|
|
897
929
|
*/
|
|
898
930
|
export function runStoreHeal(profileDir, ownVersion, options = {}) {
|
|
899
|
-
const { from, spawnSyncImpl = spawnSync, log = console.error, now = Date.now } = options
|
|
931
|
+
const { from, spawnSyncImpl = spawnSync, log = console.error, now = Date.now, bundles = BUNDLES, specFor } = options
|
|
932
|
+
const spec = specFor ?? (name => `${name}@${ownVersion}`)
|
|
900
933
|
const lock = join(profileDir, HEAL_LOCK)
|
|
901
934
|
const stamp = join(profileDir, BOOTSTRAP_STAMP)
|
|
902
935
|
const home = dirname(dirname(profileDir))
|
|
@@ -929,7 +962,7 @@ export function runStoreHeal(profileDir, ownVersion, options = {}) {
|
|
|
929
962
|
}
|
|
930
963
|
}
|
|
931
964
|
|
|
932
|
-
const result = spawnSyncImpl('dsh', ['plugin', '--profile', profileName, 'add', ...
|
|
965
|
+
const result = spawnSyncImpl('dsh', ['plugin', '--profile', profileName, 'add', ...bundles.map(spec)], {
|
|
933
966
|
stdio: 'inherit',
|
|
934
967
|
env: spawnEnv(sanitizeInheritedEnv(process.env), home),
|
|
935
968
|
})
|