dsh-code 1.0.7 → 1.2.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.
Files changed (81) hide show
  1. package/README.en.md +70 -24
  2. package/README.md +71 -25
  3. package/bin/deepseek.mjs +202 -39
  4. package/cordis.patch.yml +13 -4
  5. package/lib/index.mjs +4063 -844
  6. package/lib/session-query.mjs +3 -2
  7. package/lib/startup.mjs +4 -4
  8. package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
  9. package/lib/types/app.d.ts +100 -63
  10. package/lib/types/authorization-panel.d.ts +3 -3
  11. package/lib/types/git-workflow.d.ts +91 -2
  12. package/lib/types/i18n.d.ts +39 -0
  13. package/lib/types/index.d.ts +73 -1
  14. package/lib/types/input-split.d.ts +1 -1
  15. package/lib/types/kernel-panels.d.ts +86 -31
  16. package/lib/types/language-panel.d.ts +12 -0
  17. package/lib/types/locales/en.d.ts +450 -0
  18. package/lib/types/locales/zh.d.ts +9 -0
  19. package/lib/types/mentions.d.ts +7 -3
  20. package/lib/types/models.d.ts +14 -0
  21. package/lib/types/panel-accent.d.ts +28 -0
  22. package/lib/types/rainbow.d.ts +69 -0
  23. package/lib/types/render/animations.d.ts +42 -0
  24. package/lib/types/render/inspector.d.ts +26 -0
  25. package/lib/types/render/lines.d.ts +21 -1
  26. package/lib/types/render/markdown.d.ts +1 -1
  27. package/lib/types/render/projection.d.ts +95 -4
  28. package/lib/types/render/status.d.ts +8 -8
  29. package/lib/types/render/text.d.ts +6 -0
  30. package/lib/types/render/usage.d.ts +113 -0
  31. package/lib/types/session-directory.d.ts +17 -0
  32. package/lib/types/startup.d.ts +1 -1
  33. package/lib/types/terminal-title.d.ts +8 -0
  34. package/lib/types/theme-panel.d.ts +2 -2
  35. package/lib/types/theme.d.ts +271 -52
  36. package/lib/types/update-panel.d.ts +5 -5
  37. package/lib/types/update.d.ts +10 -1
  38. package/lib/types/version.d.ts +4 -3
  39. package/package.json +24 -7
  40. package/src/app.ts +1155 -478
  41. package/src/approval.ts +166 -166
  42. package/src/authorization-panel.ts +19 -16
  43. package/src/editor-keys.ts +371 -371
  44. package/src/git-workflow.ts +229 -3
  45. package/src/i18n.ts +68 -0
  46. package/src/index.ts +412 -76
  47. package/src/input-split.ts +3 -3
  48. package/src/kernel-panels.ts +471 -89
  49. package/src/keyboard.ts +5 -4
  50. package/src/language-panel.ts +53 -0
  51. package/src/locales/en.ts +489 -0
  52. package/src/locales/zh.ts +488 -0
  53. package/src/mentions.ts +8 -4
  54. package/src/models.ts +264 -212
  55. package/src/panel-accent.ts +41 -0
  56. package/src/presets.ts +1 -1
  57. package/src/provider-settings.ts +1 -1
  58. package/src/rainbow.ts +208 -0
  59. package/src/render/animations.ts +104 -6
  60. package/src/render/editor.ts +20 -20
  61. package/src/render/export.ts +116 -95
  62. package/src/render/inspector.ts +42 -0
  63. package/src/render/lines.ts +628 -415
  64. package/src/render/markdown.ts +15 -3
  65. package/src/render/projection.ts +429 -19
  66. package/src/render/status.ts +41 -35
  67. package/src/render/text.ts +14 -0
  68. package/src/render/tool-preview.ts +77 -77
  69. package/src/render/usage.ts +430 -0
  70. package/src/render/width.ts +2 -2
  71. package/src/session-directory.ts +8 -6
  72. package/src/session-query.ts +8 -4
  73. package/src/startup.ts +3 -3
  74. package/src/subagents.ts +229 -229
  75. package/src/terminal-title.ts +22 -5
  76. package/src/theme-panel.ts +17 -21
  77. package/src/theme.ts +281 -33
  78. package/src/update-panel.ts +37 -27
  79. package/src/update.ts +19 -3
  80. package/src/version.ts +58 -20
  81. package/src/whale-glyph.ts +23 -23
package/bin/deepseek.mjs CHANGED
@@ -116,6 +116,35 @@ export function harnessLineFromPeers(peers) {
116
116
  return undefined
117
117
  }
118
118
 
119
+ /**
120
+ * The exact global install that pairs with this launcher: the harness line
121
+ * its own package declares in peers, and its own release. The guidance a
122
+ * missing host prints must not send the user to an unpinned `dsh-code`,
123
+ * which could mount a newer bundle beside this older wrapper.
124
+ */
125
+ export function selfInstallHint(options = {}) {
126
+ const version = options.version ?? packageVersion
127
+ // An explicitly passed peers value — including undefined, the "no peers
128
+ // were readable" case — must win over this launcher's own manifest, which
129
+ // a destructuring default would conflate with the argument being absent.
130
+ const peers = 'peers' in options ? options.peers : packageRequire('../package.json').peerDependencies
131
+ const line = harnessLineFromPeers(peers)
132
+ return `npm install -g @deepseek-ai/dsh${line === undefined ? '' : `@${line}`} dsh-code@${version}`
133
+ }
134
+
135
+ /**
136
+ * Refusal line when npm's latest dsh-code is OLDER than this running
137
+ * launcher — a registry rollback, or a launcher installed from a GitHub
138
+ * release that sits ahead of the registry — and applying would downgrade
139
+ * the bundle back over the user's profile. Undefined when the upgrade may
140
+ * proceed.
141
+ */
142
+ export function bundleDowngradeRefusal(latest, running = packageVersion) {
143
+ return latest !== undefined && compareHarnessLines(latest, running) < 0
144
+ ? `dsh-code: npm latest is dsh-code@${latest}, older than this launcher (dsh-code@${running}); refusing to downgrade the bundle`
145
+ : undefined
146
+ }
147
+
119
148
  /**
120
149
  * Refusal reason when a local-checkout profile would pair an older local
121
150
  * build with the newer global host this upgrade installs, or undefined when
@@ -123,7 +152,15 @@ export function harnessLineFromPeers(peers) {
123
152
  * matches the release). The caller prints the lines verbatim.
124
153
  */
125
154
  export function localCheckoutRefusal(mounted, latestCode, codeSpec) {
126
- if (mounted === undefined || mounted === latestCode) return undefined
155
+ if (mounted === latestCode) return undefined
156
+ if (mounted === undefined) {
157
+ return [
158
+ 'dsh-code: the cli profile mounts a local checkout, but its version could not be read',
159
+ 'dsh-code: refusing to upgrade the global host beside an unread checkout',
160
+ 'dsh-code: update the checkout first (git pull, pnpm install, pnpm build), or switch the profile to the published package:',
161
+ `dsh-code: dsh plugin --profile cli remove dsh-code && dsh plugin --profile cli add ${codeSpec}`,
162
+ ]
163
+ }
127
164
  return [
128
165
  `dsh-code: the cli profile mounts a local checkout of dsh-code ${mounted}, while this upgrade would install ${latestCode} globally`,
129
166
  'dsh-code: upgrading the host beside an older local checkout pairs incompatible code; refusing to install',
@@ -132,6 +169,44 @@ export function localCheckoutRefusal(mounted, latestCode, codeSpec) {
132
169
  ]
133
170
  }
134
171
 
172
+ /** Version after the last @ in an npm spec (`dsh-code@1.0.8`). */
173
+ export function specVersion(spec) {
174
+ if (typeof spec !== 'string') return undefined
175
+ const at = spec.lastIndexOf('@')
176
+ if (at <= 0 || at === spec.length - 1) return undefined
177
+ return spec.slice(at + 1)
178
+ }
179
+
180
+ /**
181
+ * Exact install plan passed to `update --apply` after a probe. Missing
182
+ * `--code` means apply should query npm itself (CLI one-shot).
183
+ */
184
+ export function parsePinnedPlan(args) {
185
+ let dshSpec
186
+ let codeSpec
187
+ const pluginSpecs = []
188
+ for (let index = 0; index < args.length; index += 1) {
189
+ const flag = args[index]
190
+ const value = args[index + 1]
191
+ if (flag === '--dsh' && value !== undefined) {
192
+ dshSpec = value
193
+ index += 1
194
+ continue
195
+ }
196
+ if (flag === '--code' && value !== undefined) {
197
+ codeSpec = value
198
+ index += 1
199
+ continue
200
+ }
201
+ if (flag === '--plugin' && value !== undefined) {
202
+ pluginSpecs.push(value)
203
+ index += 1
204
+ }
205
+ }
206
+ if (codeSpec === undefined || dshSpec === undefined) return undefined
207
+ return { dshSpec, codeSpec, pluginSpecs }
208
+ }
209
+
135
210
  /**
136
211
  * Decide what `update --apply` installs. The global DSH launcher is pinned
137
212
  * to the harness line the target dsh-code release declares in its peers,
@@ -155,6 +230,56 @@ export function updatePlan({ latestCode, peers, profileSpec, profilePlugins = []
155
230
  }
156
231
  }
157
232
 
233
+ /**
234
+ * The concrete steps `update --apply` runs. The host install and the
235
+ * dsh-code mount are fatal: a failure there strands the global host and the
236
+ * profile bundle on different release lines, so the sequence stops. The
237
+ * companion plugin carries are best-effort — one plugin without a build on
238
+ * the target line must not strand the rest of the upgrade, so each reports
239
+ * its manual retry command and lets the sequence finish. Unresolvable dsh
240
+ * commands come back as blockers: they are enforced before any step runs,
241
+ * because starting the host install without a working profile step is
242
+ * exactly the half-updated state the fatal marking exists to prevent.
243
+ */
244
+ export function updateSteps({ plan, npm = npmInvocation(), resolveCommand = rawDshCommand }) {
245
+ const steps = [{
246
+ command: npm.command,
247
+ args: [...npm.args, 'install', '-g', plan.dshSpec, plan.codeSpec],
248
+ label: 'npm install',
249
+ fatal: true,
250
+ remedy: `npm install -g ${plan.dshSpec} ${plan.codeSpec}`,
251
+ }]
252
+ if (!plan.profileStep) return { steps, blockers: [] }
253
+ const blockers = []
254
+ const codeCommand = resolveCommand(['plugin', '--profile', 'cli', 'add', plan.codeSpec])
255
+ if (codeCommand === undefined) {
256
+ blockers.push('dsh-code: could not resolve the dsh command for the profile update')
257
+ } else {
258
+ steps.push({
259
+ command: codeCommand.command,
260
+ args: codeCommand.args,
261
+ label: 'dsh plugin add',
262
+ fatal: true,
263
+ remedy: `dsh plugin --profile cli add ${plan.codeSpec}`,
264
+ })
265
+ }
266
+ for (const pluginSpec of plan.pluginSpecs) {
267
+ const pluginCommand = resolveCommand(['plugin', '--profile', 'cli', 'add', pluginSpec])
268
+ if (pluginCommand === undefined) {
269
+ blockers.push(`dsh-code: could not resolve the dsh command for ${pluginSpec}`)
270
+ continue
271
+ }
272
+ steps.push({
273
+ command: pluginCommand.command,
274
+ args: pluginCommand.args,
275
+ label: `dsh plugin add ${pluginSpec}`,
276
+ fatal: false,
277
+ remedy: `dsh plugin --profile cli add ${pluginSpec}`,
278
+ })
279
+ }
280
+ return { steps, blockers }
281
+ }
282
+
158
283
  /**
159
284
  * Compare two @deepseek-ai/dsh release-line versions (`0.1.x`, `0.1.x-rc.N`,
160
285
  * `0.1.x-alpha.N`). Positive when `left` is newer, negative when older, and 0
@@ -201,23 +326,37 @@ export function installedGlobalDshVersion(roots = globalDshRoots(), fileExists =
201
326
  return undefined
202
327
  }
203
328
 
204
- /** Run wrapper-owned child steps in order, stopping at the first failure. */
329
+ /**
330
+ * Run wrapper-owned child steps in order. A step marked fatal stops the
331
+ * sequence at its failure (the default: the next step depends on this one);
332
+ * a `fatal: false` step is best-effort — its failure is reported with the
333
+ * step's manual retry command, the sequence continues, and the resolved
334
+ * code stays non-zero so the caller still sees the incomplete pass.
335
+ */
205
336
  export function runSequence(steps, spawnProcess = spawn) {
206
337
  return new Promise(resolve => {
338
+ const failures = []
207
339
  const run = index => {
208
340
  const step = steps[index]
209
341
  if (step === undefined) {
210
- resolve(0)
342
+ resolve(failures.length === 0 ? 0 : failures[failures.length - 1])
211
343
  return
212
344
  }
213
345
  const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/iu.test(step.command)
214
346
  const child = spawnProcess(step.command, step.args, { stdio: 'inherit', ...(needsShell ? { shell: true } : {}) })
347
+ const report = code => {
348
+ if (step.remedy !== undefined) console.error(`dsh-code: to retry ${step.label} manually: ${step.remedy}`)
349
+ failures.push(code ?? 1)
350
+ run(index + 1)
351
+ }
215
352
  child.once('error', error => {
216
353
  console.error(`dsh-code: ${step.label} failed: ${error.message}`)
217
- resolve(1)
354
+ if (step.fatal === false) report(undefined)
355
+ else resolve(1)
218
356
  })
219
- child.once('exit', (code, signal) => {
357
+ child.once('exit', code => {
220
358
  if (code === 0) run(index + 1)
359
+ else if (step.fatal === false) report(code)
221
360
  else resolve(code ?? 1)
222
361
  })
223
362
  }
@@ -432,13 +571,17 @@ export function buildUpdateStatus({
432
571
  const downgrade = plan.line !== undefined && installed !== undefined && compareHarnessLines(plan.line, installed) < 0
433
572
  const localCheckout = plan.profileStep ? undefined : localCheckoutRefusal(mounted, codeLatest, plan.codeSpec)
434
573
  const hostOnLine = installed !== undefined && (plan.line === undefined || compareHarnessLines(installed, plan.line) === 0)
435
- const upToDate = registry === undefined
574
+ // A launcher ahead of the registry (a GitHub-release install) has nothing
575
+ // npm can offer: the registry's older latest must not present as a pending
576
+ // update, and applyUpdate refuses it anyway.
577
+ const bundleAhead = codeLatest !== undefined && compareHarnessLines(codeLatest, packageVersion) < 0
578
+ const upToDate = bundleAhead || (registry === undefined
436
579
  && codeLatest === packageVersion
437
580
  && !downgrade
438
581
  && localCheckout === undefined
439
582
  && hostOnLine
440
583
  && plan.pluginSpecs.length === 0
441
- && (mounted === undefined || mounted === codeLatest)
584
+ && (mounted === undefined || mounted === codeLatest))
442
585
  return {
443
586
  code: { running: packageVersion, latest: codeLatest ?? null },
444
587
  host: { installed: installed ?? null, targetLine: plan.line ?? null },
@@ -449,6 +592,7 @@ export function buildUpdateStatus({
449
592
  // key drift into a truthy comparison.
450
593
  blockers: { registry: registry ?? null, downgrade, localCheckout: localCheckout ?? null },
451
594
  upToDate,
595
+ aheadOfRegistry: bundleAhead,
452
596
  }
453
597
  }
454
598
 
@@ -462,21 +606,53 @@ async function applyUpdate({
462
606
  view = viewJson,
463
607
  resolveCommand = rawDshCommand,
464
608
  spawnProcess = spawn,
609
+ pinned,
465
610
  } = {}) {
466
611
  const npm = npmInvocation()
467
- const latestCode = view(['dsh-code', 'version'], undefined, npm)
468
- if (latestCode === undefined) {
469
- console.error('dsh-code: could not read the latest dsh-code version from npm')
612
+ const profileSpec = profileDependencySpec()
613
+ const profilePlugins = profilePluginDependencies()
614
+ let latestCode
615
+ let plan
616
+ if (pinned !== undefined) {
617
+ latestCode = specVersion(pinned.codeSpec)
618
+ if (latestCode === undefined) {
619
+ console.error(`dsh-code: could not read a version from ${pinned.codeSpec}`)
620
+ process.exitCode = 1
621
+ return
622
+ }
623
+ const line = specVersion(pinned.dshSpec)
624
+ plan = {
625
+ dshSpec: pinned.dshSpec,
626
+ line,
627
+ lineLocked: line !== undefined,
628
+ codeSpec: pinned.codeSpec,
629
+ profileStep: !(typeof profileSpec === 'string' && /^(link|file):/iu.test(profileSpec)),
630
+ pluginSpecs: pinned.pluginSpecs,
631
+ }
632
+ } else {
633
+ latestCode = view(['dsh-code', 'version'], undefined, npm)
634
+ if (latestCode === undefined) {
635
+ console.error('dsh-code: could not read the latest dsh-code version from npm')
636
+ process.exitCode = 1
637
+ return
638
+ }
639
+ const peers = view([`dsh-code@${latestCode}`, 'peerDependencies'], undefined, npm)
640
+ plan = updatePlan({
641
+ latestCode,
642
+ peers,
643
+ profileSpec,
644
+ profilePlugins,
645
+ })
646
+ }
647
+ // A launcher installed from a GitHub release (or any source ahead of the
648
+ // registry) must not be walked back by an apply targeting npm's older
649
+ // latest - the downgrade guard below only covers the host line.
650
+ const bundleRefusal = bundleDowngradeRefusal(latestCode)
651
+ if (bundleRefusal !== undefined) {
652
+ console.error(bundleRefusal)
470
653
  process.exitCode = 1
471
654
  return
472
655
  }
473
- const peers = view([`dsh-code@${latestCode}`, 'peerDependencies'], undefined, npm)
474
- const plan = updatePlan({
475
- latestCode,
476
- peers,
477
- profileSpec: profileDependencySpec(),
478
- profilePlugins: profilePluginDependencies(),
479
- })
480
656
  if (!plan.lineLocked) {
481
657
  console.log('dsh-code: could not read the compatible harness line; installing @deepseek-ai/dsh@latest')
482
658
  }
@@ -505,26 +681,13 @@ async function applyUpdate({
505
681
  return
506
682
  }
507
683
  }
508
- const steps = [{ command: npm.command, args: [...npm.args, 'install', '-g', plan.dshSpec, plan.codeSpec], label: 'npm install' }]
684
+ const { steps, blockers } = updateSteps({ plan, npm, resolveCommand })
685
+ if (blockers.length > 0) {
686
+ for (const line of blockers) console.error(line)
687
+ process.exitCode = 1
688
+ return
689
+ }
509
690
  if (plan.profileStep) {
510
- const command = resolveCommand(['plugin', '--profile', 'cli', 'add', plan.codeSpec])
511
- if (command === undefined) {
512
- console.error('dsh-code: could not resolve the dsh command for the profile update')
513
- process.exitCode = 1
514
- return
515
- }
516
- steps.push({ command: command.command, args: command.args, label: 'dsh plugin add' })
517
- // Companion plugins ride the same line: without this, `dsh plugin add`-ed
518
- // extras stay pinned to the old harness line after an upgrade.
519
- for (const pluginSpec of plan.pluginSpecs) {
520
- const pluginCommand = resolveCommand(['plugin', '--profile', 'cli', 'add', pluginSpec])
521
- if (pluginCommand === undefined) {
522
- console.error(`dsh-code: could not resolve the dsh command for ${pluginSpec}`)
523
- process.exitCode = 1
524
- return
525
- }
526
- steps.push({ command: pluginCommand.command, args: pluginCommand.args, label: `dsh plugin add ${pluginSpec}` })
527
- }
528
691
  if (plan.pluginSpecs.length > 0) {
529
692
  console.log(`carrying ${plan.pluginSpecs.length} profile plugin${plan.pluginSpecs.length === 1 ? '' : 's'} to the same line: ${plan.pluginSpecs.join(', ')}`)
530
693
  }
@@ -564,7 +727,7 @@ export function launchOperation(args = process.argv.slice(2)) {
564
727
  // Setup must mount the exact globally installed release on launch day.
565
728
  const command = rawDshCommand(['plugin', '--profile', 'cli', 'add', setupBundle(args)])
566
729
  if (command === undefined) {
567
- console.error('dsh-code: @deepseek-ai/dsh is not installed; run: npm install -g @deepseek-ai/dsh dsh-code')
730
+ console.error(`dsh-code: @deepseek-ai/dsh is not installed; run: ${selfInstallHint()}`)
568
731
  process.exitCode = 1
569
732
  return true
570
733
  }
@@ -576,7 +739,7 @@ export function launchOperation(args = process.argv.slice(2)) {
576
739
  return true
577
740
  }
578
741
  if (args.includes('--apply')) {
579
- void applyUpdate()
742
+ void applyUpdate({ pinned: parsePinnedPlan(args) })
580
743
  return true
581
744
  }
582
745
  printUpdateStatus()
@@ -607,7 +770,7 @@ export function launchDsh(
607
770
  }
608
771
  const command = resolveCommand(args)
609
772
  if (command === undefined) {
610
- console.error('dsh-code: @deepseek-ai/dsh must be installed globally beside dsh-code; run: npm install -g @deepseek-ai/dsh dsh-code')
773
+ console.error(`dsh-code: @deepseek-ai/dsh must be installed globally beside dsh-code; run: ${selfInstallHint()}`)
611
774
  process.exitCode = 1
612
775
  return undefined
613
776
  }
package/cordis.patch.yml CHANGED
@@ -156,7 +156,9 @@
156
156
  # service (read-only, workspace-scoped by exact cwd). A host-plane mount
157
157
  # is deployment-global: every preset session sees the five tools. Kept
158
158
  # ENABLED deliberately — read-only search leaks no capability — but note
159
- # it does reach minimal-preset sessions too.
159
+ # it does reach minimal-preset sessions too. The host CLI does not bundle
160
+ # this package, so it ships as a real dependency and resolves from the
161
+ # profile's own node_modules.
160
162
  - id: tool-session-query
161
163
  name: '@deepseek-ai/dsh-tool-session-query'
162
164
 
@@ -180,7 +182,10 @@
180
182
  # enabling them at the host plane would hand every preset (including the
181
183
  # shell-restricted minimal one) raw shell capability, the exact leak the
182
184
  # disables above exist to prevent. Flip tool-terminal to disabled: false
183
- # in the PROFILE layer to opt the deployment into PTY tools.
185
+ # in the PROFILE layer to opt the deployment into PTY tools — but install
186
+ # @deepseek-ai/dsh-tool-terminal into the profile first (the host CLI does
187
+ # not bundle it; an enabled row over a missing package fails the boot):
188
+ # dsh plugin --profile cli add @deepseek-ai/dsh-tool-terminal
184
189
  - id: pty
185
190
  name: '@deepseek-ai/dsh-terminal'
186
191
 
@@ -203,6 +208,8 @@
203
208
  # Enable in the PROFILE layer with real servers, e.g.:
204
209
  # lsp / lsp-stdio / tool-lsp rows with disabled: false and
205
210
  # lsp-stdio config.servers mapping extensions to server commands.
211
+ # The three packages are also not bundled by the host CLI: install them
212
+ # into the profile first or the enabled rows fail the boot.
206
213
  - id: lsp
207
214
  name: '@deepseek-ai/dsh-lsp'
208
215
  disabled: true
@@ -217,8 +224,10 @@
217
224
 
218
225
  # MCP servers (dsh-mcp-client) and hooks bridges (dsh-hooks-claude-code /
219
226
  # -codex) take per-instance config (server transports, hooks.json paths),
220
- # so they mount as rows in the USER profile layer, not here; the packages
221
- # ship as peers so those rows resolve. README documents the row shapes.
227
+ # so they mount as rows in the USER profile layer, not here; the host CLI
228
+ # bundles these peers, so those rows resolve. README documents the row
229
+ # shapes, and rows over packages the host does not bundle need the package
230
+ # added to the profile first.
222
231
 
223
232
  - id: tui-startup
224
233
  name: 'dsh-code/startup'