@dsh-cc/cli 0.9.0-rc.3 → 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 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
- return ['plugin', '--profile', PROFILE, 'add', ...BUNDLES.map(name => `${name}@${version}`)]
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', ...BUNDLES.map(n => `${n}@${ownVersion}`)], {
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', ...BUNDLES.map(n => `${n}@${ownVersion}`)], {
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
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-cc/cli",
3
- "version": "0.9.0-rc.3",
3
+ "version": "0.9.0",
4
4
  "description": "Optional dsh-cc shortcut: bootstraps the tui profile and runs dsh --profile tui",
5
5
  "type": "module",
6
6
  "bin": {