@askdkc/dsh-cli 0.11.5 → 0.11.6

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
@@ -121,6 +121,20 @@ it. The managed PATH block goes in `${ZDOTDIR:-$HOME}/.zshrc` for zsh;
121
121
  it, remove the managed command and that block; delete the fish file only if
122
122
  it contains nothing else (Windows: remove the command and user PATH entry). The
123
123
  existing `dsh-tui` and `dst` commands remain compatibility aliases.
124
+
125
+ A Harness source checkout does not put `dsh` on PATH. When the TUI first starts
126
+ through `pnpm dsh --profile dsh-cli` in a Harness checkout, registration also
127
+ records that checkout as `DSH_TUI_DSH_ROOT` in the managed shell startup block
128
+ (`.zshrc` for zsh). Open a new shell, then launch from any project directory:
129
+
130
+ ```sh
131
+ cd /absolute/path/to/your-project
132
+ dsh-cli
133
+ ```
134
+
135
+ The launcher uses the checkout's built `apps/cli/lib/bin.js` and preserves the
136
+ project directory. Rebuild Harness after changing its source. An explicit
137
+ `DSH_TUI_DSH_ROOT` value overrides the registered default.
124
138
  A locally packed fork should be updated by rebuilding and reinstalling the
125
139
  archive, rather than by using the registry-backed `/update` command.
126
140
 
package/bin/dsh-tui.js CHANGED
@@ -97,7 +97,7 @@ const shellOpt = isWin ? { shell: true } : {}
97
97
  // DEP0190(issue #148):shell:true + 非空参数数组触发语法级弃用告警——
98
98
  // 转义后拼进命令字符串(空参数数组不触发),非 Windows 保持数组直传。
99
99
  const cmd = (command, args) =>
100
- isWin ? [`${command} ${shellQuote(args).join(' ')}`, []] : [command, args]
100
+ isWin ? [`${shellQuote([command])[0]} ${shellQuote(args).join(' ')}`, []] : [command, args]
101
101
 
102
102
  // 内联 semver(解析 + 严格大于):启动器可能在依赖不完整的环境里被执行
103
103
  // (迁移、半损坏安装、测试沙箱),零外部依赖是自保底线。覆盖 semver 的
@@ -136,8 +136,12 @@ const isVersionNewer = (a, b) => {
136
136
  const lang = process.env.DSH_TUI_LANG === 'en' ? 'en' : 'zh'
137
137
  const MSG = {
138
138
  noDsh: {
139
- en: '[dsh-cli] dsh CLI not found. Install the official client first:\n npm install -g @deepseek-ai/dsh',
140
- zh: '[dsh-cli] 未检测到 dsh CLI。请先安装官方客户端:\n npm install -g @deepseek-ai/dsh',
139
+ en: '[dsh-cli] dsh CLI not found. Install it or set DSH_TUI_DSH_ROOT to a built Harness checkout:\n npm install -g @deepseek-ai/dsh',
140
+ zh: '[dsh-cli] 未检测到 dsh CLI。可安装官方客户端,或将 DSH_TUI_DSH_ROOT 指向已构建的 Harness 源码目录:\n npm install -g @deepseek-ai/dsh',
141
+ },
142
+ invalidDshRoot: {
143
+ en: root => `[dsh-cli] DSH_TUI_DSH_ROOT must be an absolute, built DeepSeek Harness checkout: ${root}`,
144
+ zh: root => `[dsh-cli] DSH_TUI_DSH_ROOT 必须指向已构建的 DeepSeek Harness 源码绝对路径:${root}`,
141
145
  },
142
146
  noPnpm: {
143
147
  en: '[dsh-cli] The first-time setup needs pnpm (dsh plugin delegates installs to it):\n npm install -g pnpm (or via corepack: corepack enable pnpm)',
@@ -541,6 +545,19 @@ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
541
545
  console.log(msg('helpText'))
542
546
  process.exit(0)
543
547
  }
548
+ // A source checkout does not put `dsh` on PATH. An explicit Harness root lets
549
+ // the launcher run its built CLI while keeping the caller's project cwd.
550
+ const dshRoot = process.env.DSH_TUI_DSH_ROOT
551
+ let dshBin
552
+ if (dshRoot !== undefined) {
553
+ const candidate = join(dshRoot, 'apps', 'cli', 'lib', 'bin.js')
554
+ if (!isAbsolute(dshRoot) || readJson(join(dshRoot, 'apps', 'cli', 'package.json'))?.name !== '@deepseek-ai/dsh' || !existsSync(candidate)) {
555
+ console.error(msg('invalidDshRoot')(dshRoot))
556
+ process.exit(1)
557
+ }
558
+ dshBin = candidate
559
+ }
560
+ const dshCmd = args => dshBin === undefined ? cmd('dsh', args) : cmd(process.execPath, [dshBin, ...args])
544
561
  /**
545
562
  * Whether the DSH credential store declares a reference by this name.
546
563
  *
@@ -578,7 +595,7 @@ const runDoctorChecks = () => {
578
595
  lines.push(`dsh-cli doctor · ${PACKAGE} ${ownVersion ?? 'unknown'}`)
579
596
  report(true, 'node', `${process.version} · ${process.platform} ${process.arch}`)
580
597
  const probeVersion = command => {
581
- const probe = spawnSync(...cmd(command, ['--version']), { stdio: 'pipe', encoding: 'utf8', ...shellOpt })
598
+ const probe = spawnSync(...command(['--version']), { stdio: 'pipe', encoding: 'utf8', ...shellOpt })
582
599
  if (probe.error || probe.status !== 0) return undefined
583
600
  // 白名单校验:只回显版本号形状的首行。诊断输出的红线是绝不泄露密钥,
584
601
  // 而 PATH 上的 wrapper 理论上可以把任意环境变量 echo 进 --version——
@@ -586,14 +603,14 @@ const runDoctorChecks = () => {
586
603
  const line = String(probe.stdout ?? '').trim().split('\n')[0] ?? ''
587
604
  return /^v?\d[\w.+-]*$/.test(line) ? line : '(version unreadable)'
588
605
  }
589
- const dshVersion = probeVersion('dsh')
606
+ const dshVersion = probeVersion(dshCmd)
590
607
  if (dshVersion === undefined) {
591
608
  hardFailure = true
592
609
  report(false, 'dsh', L.dshMissing)
593
610
  } else {
594
611
  report(true, 'dsh', dshVersion)
595
612
  }
596
- const pnpmVersion = probeVersion('pnpm')
613
+ const pnpmVersion = probeVersion(args => cmd('pnpm', args))
597
614
  report(pnpmVersion !== undefined, 'pnpm', pnpmVersion ?? L.pnpmMissing)
598
615
  const profileVersion = readJson(installedPkgPath)?.version
599
616
  if (profileVersion === undefined) {
@@ -722,7 +739,7 @@ const forwardExit = child => {
722
739
  // 不从数值反推信号(spec §5.1)。
723
740
  const startDshSession = (dshArgs, profile = PROFILE, env = process.env) =>
724
741
  new Promise(resolve => {
725
- const child = spawn(...cmd('dsh', ['--profile', profile, ...dshArgs]), {
742
+ const child = spawn(...dshCmd(['--profile', profile, ...dshArgs]), {
726
743
  stdio: 'inherit',
727
744
  env,
728
745
  ...shellOpt,
@@ -1029,11 +1046,11 @@ const createRescueProfile = () => {
1029
1046
  }
1030
1047
  console.log(msg('safeRescueCleanup')(rescueProfileDir))
1031
1048
  }
1032
- const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
1049
+ const probe = spawnSync(...dshCmd(['--version']), { stdio: 'pipe', ...shellOpt })
1033
1050
  if (probe.error || probe.status !== 0) return { kind: 'failed', lines: [msg('safeRescueFailed')('dsh missing')] }
1034
1051
  console.log(msg('safeRescueCreating'))
1035
1052
  const runAdd = extraArgs => spawnSync(
1036
- ...cmd('dsh', ['plugin', '--profile', RESCUE_PROFILE, 'add', ...extraArgs, `${PACKAGE}@${installVersion}`]),
1053
+ ...dshCmd(['plugin', '--profile', RESCUE_PROFILE, 'add', ...extraArgs, `${PACKAGE}@${installVersion}`]),
1037
1054
  { stdio: ['inherit', 'pipe', 'pipe'], env: rescueEnv(), ...shellOpt },
1038
1055
  )
1039
1056
  let add = runAdd([])
@@ -1099,7 +1116,7 @@ const profileReady = () => {
1099
1116
  }
1100
1117
  }
1101
1118
  const bootstrapProfile = () => {
1102
- const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
1119
+ const probe = spawnSync(...dshCmd(['--version']), { stdio: 'pipe', ...shellOpt })
1103
1120
  if (probe.error || probe.status !== 0) {
1104
1121
  console.error(msg('noDsh'))
1105
1122
  process.exit(1)
@@ -1111,7 +1128,7 @@ const bootstrapProfile = () => {
1111
1128
  }
1112
1129
  console.log(msg('bootstrapStart'))
1113
1130
  const runAdd = (extraArgs, capture) => spawnSync(
1114
- ...cmd('dsh', ['plugin', '--profile', PROFILE, 'add', ...extraArgs, `${PACKAGE}@${installVersion}`]),
1131
+ ...dshCmd(['plugin', '--profile', PROFILE, 'add', ...extraArgs, `${PACKAGE}@${installVersion}`]),
1115
1132
  { stdio: capture ? ['inherit', 'pipe', 'pipe'] : 'inherit', ...shellOpt },
1116
1133
  )
1117
1134
  let add = runAdd([], true)
@@ -1225,7 +1242,7 @@ if (subcommand === 'safe') {
1225
1242
  if (subcommand === 'update') {
1226
1243
  if (!profileReady()) bootstrapProfile()
1227
1244
  {
1228
- const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
1245
+ const probe = spawnSync(...dshCmd(['--version']), { stdio: 'pipe', ...shellOpt })
1229
1246
  if (probe.error || probe.status !== 0) {
1230
1247
  console.error(msg('noDsh'))
1231
1248
  process.exit(1)
@@ -1291,7 +1308,7 @@ if (!runningInsideProfile && ownVersion !== undefined && process.env.DSH_TUI_NO_
1291
1308
  // ─── profile 副本(或源码运行):完整启动逻辑 ─────────────────────────────
1292
1309
  // dsh CLI 预检(缺失时给安装指引,先于一切 profile 逻辑)。
1293
1310
  {
1294
- const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
1311
+ const probe = spawnSync(...dshCmd(['--version']), { stdio: 'pipe', ...shellOpt })
1295
1312
  if (probe.error || probe.status !== 0) {
1296
1313
  console.error(msg('noDsh'))
1297
1314
  process.exit(1)
@@ -7,6 +7,8 @@ export interface CliRegistrationOptions {
7
7
  zdotdir?: string;
8
8
  xdgConfigHome?: string;
9
9
  localAppData?: string;
10
+ cliEntry?: string;
11
+ dshRoot?: string;
10
12
  windowsUserPath?: {
11
13
  read(): string;
12
14
  write(value: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"cli-registration.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/cli-registration.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE;QAAE,IAAI,IAAI,MAAM,CAAC;QAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACjE;AA4DD,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CA0EhF"}
1
+ {"version":3,"file":"cli-registration.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/cli-registration.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE;QAAE,IAAI,IAAI,MAAM,CAAC;QAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACjE;AAqFD,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CAgFhF"}
@@ -1,7 +1,7 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
- import { basename, dirname, join } from 'node:path';
4
+ import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
5
5
  const begin = '# BEGIN dsh-cli managed PATH';
6
6
  const end = '# END dsh-cli managed PATH';
7
7
  const marker = '# dsh-cli managed launcher';
@@ -71,6 +71,34 @@ function posixShellFiles(home, shell, zdotdir, xdg) {
71
71
  default: throw new Error(`unsupported shell ${shell}; add ~/.local/bin to PATH manually`);
72
72
  }
73
73
  }
74
+ /** Resolve a Harness source entry, never the caller's project directory. */
75
+ function harnessSourceRoot(cliEntry) {
76
+ if (cliEntry === undefined)
77
+ return undefined;
78
+ try {
79
+ const entry = realpathSync(cliEntry);
80
+ const root = resolve(dirname(entry), '../../..');
81
+ if (entry !== join(root, 'apps', 'cli', 'src', 'bin.ts')
82
+ && entry !== join(root, 'apps', 'cli', 'lib', 'bin.js'))
83
+ return undefined;
84
+ const pkg = JSON.parse(readFileSync(join(root, 'apps', 'cli', 'package.json'), 'utf8'));
85
+ return pkg.name === '@deepseek-ai/dsh' ? root : undefined;
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ function configuredHarnessRoot(root) {
92
+ if (root === undefined || !isAbsolute(root))
93
+ return undefined;
94
+ try {
95
+ const pkg = JSON.parse(readFileSync(join(root, 'apps', 'cli', 'package.json'), 'utf8'));
96
+ return pkg.name === '@deepseek-ai/dsh' ? realpathSync(root) : undefined;
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ }
74
102
  /** Register a user-owned launcher for the existing dsh-cli profile. No DSH state is changed. */
75
103
  export function ensureCliRegistered(options = {}) {
76
104
  const home = options.home ?? homedir();
@@ -78,6 +106,8 @@ export function ensureCliRegistered(options = {}) {
78
106
  const platform = options.platform ?? process.platform;
79
107
  const shell = options.shell ?? process.env.SHELL ?? '';
80
108
  const pathValue = options.path ?? process.env.PATH ?? '';
109
+ const sourceRoot = harnessSourceRoot(options.cliEntry ?? process.argv[1])
110
+ ?? configuredHarnessRoot(options.dshRoot ?? process.env.DSH_TUI_DSH_ROOT);
81
111
  const binDir = platform === 'win32'
82
112
  ? join(options.localAppData ?? process.env.LOCALAPPDATA ?? join(home, 'AppData', 'Local'), 'dsh-cli', 'bin')
83
113
  : join(home, '.local', 'bin');
@@ -140,9 +170,13 @@ export function ensureCliRegistered(options = {}) {
140
170
  }
141
171
  else {
142
172
  const files = posixShellFiles(home, shell, options.zdotdir ?? process.env.ZDOTDIR, options.xdgConfigHome ?? process.env.XDG_CONFIG_HOME);
143
- const line = basename(shell) === 'fish'
173
+ const pathLine = basename(shell) === 'fish'
144
174
  ? `contains -- ${quoted(binDir)} $PATH; or set -gx PATH ${quoted(binDir)} $PATH`
145
175
  : `case ":$PATH:" in *:${quoted(binDir)}:*) ;; *) export PATH=${quoted(binDir)}:"$PATH" ;; esac`;
176
+ const sourceLine = sourceRoot === undefined ? '' : basename(shell) === 'fish'
177
+ ? `\nset -q DSH_TUI_DSH_ROOT; or set -gx DSH_TUI_DSH_ROOT ${quoted(sourceRoot)}`
178
+ : `\nif [ -z "\${DSH_TUI_DSH_ROOT:-}" ]; then export DSH_TUI_DSH_ROOT=${quoted(sourceRoot)}; fi`;
179
+ const line = pathLine + sourceLine;
146
180
  for (const file of files)
147
181
  updateShellFile(file, line);
148
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askdkc/dsh-cli",
3
- "version": "0.11.5",
3
+ "version": "0.11.6",
4
4
  "description": "Interactive terminal interface for DeepSeek Harness agents, sessions and tools.",
5
5
  "keywords": [
6
6
  "dsh",