@toddzheng024/dscode-bundle 0.7.12 → 0.7.14

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 (167) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/cordis.patch.yml +6 -2
  3. package/package.json +7 -5
  4. package/plugins/code-review/index.mjs +5 -2
  5. package/plugins/compaction/threshold.mjs +12 -0
  6. package/plugins/credentials/index.mjs +13 -0
  7. package/plugins/grok/adapter.mjs +114 -0
  8. package/plugins/grok/auth.mjs +76 -0
  9. package/plugins/grok/billing.mjs +146 -0
  10. package/plugins/grok/index.mjs +118 -0
  11. package/plugins/grok/models.mjs +129 -0
  12. package/plugins/grok/status.mjs +28 -0
  13. package/plugins/grok/wire.mjs +268 -0
  14. package/plugins/i18n/messages.mjs +6 -6
  15. package/plugins/providers/catalog.mjs +2 -0
  16. package/plugins/session-metrics/view.mjs +34 -3
  17. package/vendor/compaction-basic/index.js +91 -1
  18. package/vendor/persistent/index.js +10 -9
  19. package/vendor/terminal/index.js +5 -0
  20. package/vendor/tui/lib/app.mjs +6711 -0
  21. package/vendor/tui/lib/approval.mjs +122 -0
  22. package/vendor/tui/lib/attachments.mjs +218 -0
  23. package/vendor/tui/lib/authorization-panel.mjs +242 -0
  24. package/vendor/tui/lib/authorization.mjs +111 -0
  25. package/vendor/tui/lib/commands.mjs +123 -0
  26. package/vendor/tui/lib/dscode/chat.mjs +84 -0
  27. package/vendor/tui/lib/dscode/flags.mjs +32 -0
  28. package/vendor/tui/lib/dscode/model-search.mjs +68 -0
  29. package/vendor/tui/lib/dscode/paste.mjs +87 -0
  30. package/vendor/tui/lib/dscode/telemetry.mjs +82 -0
  31. package/vendor/tui/lib/dscode/welcome.mjs +100 -0
  32. package/vendor/tui/lib/editor-keys.mjs +346 -0
  33. package/vendor/tui/lib/editor.mjs +50 -0
  34. package/vendor/tui/lib/fork.mjs +24 -0
  35. package/vendor/tui/lib/git-workflow.mjs +278 -0
  36. package/vendor/tui/{types/history.d.ts → lib/history.mjs} +69 -26
  37. package/vendor/tui/{types/i18n.d.ts → lib/i18n.mjs} +23 -15
  38. package/vendor/tui/lib/index.mjs +2200 -0
  39. package/vendor/tui/lib/input-split.mjs +170 -0
  40. package/vendor/tui/lib/internals.mjs +90 -0
  41. package/vendor/tui/lib/invariant.mjs +22 -0
  42. package/vendor/tui/lib/kernel-panels.mjs +1265 -0
  43. package/vendor/tui/lib/keyboard.mjs +283 -0
  44. package/vendor/tui/lib/language-panel.mjs +41 -0
  45. package/vendor/tui/lib/locales/en.mjs +495 -0
  46. package/vendor/tui/lib/locales/zh.mjs +495 -0
  47. package/vendor/tui/lib/mentions.mjs +150 -0
  48. package/vendor/tui/lib/model-capabilities.mjs +245 -0
  49. package/vendor/tui/lib/models.mjs +215 -0
  50. package/vendor/tui/{types/panel-accent.d.ts → lib/panel-accent.mjs} +11 -7
  51. package/vendor/tui/lib/permissions.mjs +45 -0
  52. package/vendor/tui/lib/plugin-inventory.mjs +27 -0
  53. package/vendor/tui/lib/presets.mjs +46 -0
  54. package/vendor/tui/lib/provider-settings.mjs +600 -0
  55. package/vendor/tui/lib/questions.mjs +116 -0
  56. package/vendor/tui/lib/rainbow.mjs +181 -0
  57. package/vendor/tui/lib/render/animations.mjs +665 -0
  58. package/vendor/tui/lib/render/editor.mjs +437 -0
  59. package/vendor/tui/lib/render/export.mjs +119 -0
  60. package/vendor/tui/lib/render/fuzzy.mjs +79 -0
  61. package/vendor/tui/lib/render/ime-cursor.mjs +135 -0
  62. package/vendor/tui/lib/render/inspector.mjs +93 -0
  63. package/vendor/tui/lib/render/lines.mjs +538 -0
  64. package/vendor/tui/lib/render/markdown.mjs +592 -0
  65. package/vendor/tui/lib/render/projection.mjs +1864 -0
  66. package/vendor/tui/lib/render/status.mjs +591 -0
  67. package/vendor/tui/lib/render/text.mjs +157 -0
  68. package/vendor/tui/lib/render/tool-detail.mjs +187 -0
  69. package/vendor/tui/lib/render/tool-preview.mjs +79 -0
  70. package/vendor/tui/lib/render/usage.mjs +336 -0
  71. package/vendor/tui/lib/render/width.mjs +191 -0
  72. package/vendor/tui/lib/session-directory.mjs +329 -0
  73. package/vendor/tui/lib/session-query.mjs +184 -0
  74. package/vendor/tui/lib/session-switch.mjs +51 -0
  75. package/vendor/tui/lib/settings-file.mjs +75 -0
  76. package/vendor/tui/lib/skills.mjs +119 -0
  77. package/vendor/tui/lib/startup.mjs +111 -0
  78. package/vendor/tui/lib/store.mjs +129 -0
  79. package/vendor/tui/lib/subagents.mjs +200 -0
  80. package/vendor/tui/lib/terminal-title.mjs +186 -0
  81. package/vendor/tui/lib/theme-panel.mjs +60 -0
  82. package/vendor/tui/lib/theme.mjs +379 -0
  83. package/vendor/tui/lib/update-panel.mjs +239 -0
  84. package/vendor/tui/lib/update.mjs +101 -0
  85. package/vendor/tui/lib/version.mjs +105 -0
  86. package/vendor/tui/lib/whale-glyph.mjs +20 -0
  87. package/vendor/tui/LICENSE +0 -21
  88. package/vendor/tui/devtools-CdTl3MNy.mjs +0 -3643
  89. package/vendor/tui/dscode-email/cli.mjs +0 -32
  90. package/vendor/tui/dscode-email/contacts.mjs +0 -36
  91. package/vendor/tui/dscode-email/gmail-oauth.mjs +0 -69
  92. package/vendor/tui/dscode-email/gmail-store.mjs +0 -2
  93. package/vendor/tui/dscode-email/gmail.mjs +0 -194
  94. package/vendor/tui/dscode-email/imap.mjs +0 -136
  95. package/vendor/tui/dscode-email/inbox.d.mts +0 -25
  96. package/vendor/tui/dscode-email/inbox.mjs +0 -76
  97. package/vendor/tui/dscode-email/smtp.mjs +0 -69
  98. package/vendor/tui/dscode-email/store.mjs +0 -32
  99. package/vendor/tui/dscode-providers/catalog.mjs +0 -136
  100. package/vendor/tui/dscode-providers/effort.mjs +0 -35
  101. package/vendor/tui/dscode-providers/openrouter-account.mjs +0 -171
  102. package/vendor/tui/index.mjs +0 -44441
  103. package/vendor/tui/invariant.mjs +0 -21
  104. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +0 -26
  105. package/vendor/tui/session-query.mjs +0 -150
  106. package/vendor/tui/startup.mjs +0 -109
  107. package/vendor/tui/theme-7u5Qo3dF.mjs +0 -1265
  108. package/vendor/tui/types/app.d.ts +0 -348
  109. package/vendor/tui/types/approval.d.ts +0 -59
  110. package/vendor/tui/types/attachments.d.ts +0 -52
  111. package/vendor/tui/types/authorization-panel.d.ts +0 -22
  112. package/vendor/tui/types/authorization.d.ts +0 -36
  113. package/vendor/tui/types/commands.d.ts +0 -52
  114. package/vendor/tui/types/editor-keys.d.ts +0 -105
  115. package/vendor/tui/types/editor.d.ts +0 -6
  116. package/vendor/tui/types/fork.d.ts +0 -8
  117. package/vendor/tui/types/git-workflow.d.ts +0 -121
  118. package/vendor/tui/types/index.d.ts +0 -223
  119. package/vendor/tui/types/input-split.d.ts +0 -54
  120. package/vendor/tui/types/internals.d.ts +0 -26
  121. package/vendor/tui/types/invariant.d.ts +0 -15
  122. package/vendor/tui/types/kernel-panels.d.ts +0 -245
  123. package/vendor/tui/types/keyboard.d.ts +0 -80
  124. package/vendor/tui/types/language-panel.d.ts +0 -12
  125. package/vendor/tui/types/locales/en.d.ts +0 -450
  126. package/vendor/tui/types/locales/zh.d.ts +0 -9
  127. package/vendor/tui/types/mentions.d.ts +0 -85
  128. package/vendor/tui/types/model-capabilities.d.ts +0 -82
  129. package/vendor/tui/types/models.d.ts +0 -133
  130. package/vendor/tui/types/permissions.d.ts +0 -27
  131. package/vendor/tui/types/plugin-inventory.d.ts +0 -11
  132. package/vendor/tui/types/presets.d.ts +0 -22
  133. package/vendor/tui/types/provider-settings.d.ts +0 -239
  134. package/vendor/tui/types/questions.d.ts +0 -54
  135. package/vendor/tui/types/rainbow.d.ts +0 -69
  136. package/vendor/tui/types/render/animations.d.ts +0 -307
  137. package/vendor/tui/types/render/editor.d.ts +0 -163
  138. package/vendor/tui/types/render/export.d.ts +0 -9
  139. package/vendor/tui/types/render/fuzzy.d.ts +0 -21
  140. package/vendor/tui/types/render/ime-cursor.d.ts +0 -60
  141. package/vendor/tui/types/render/inspector.d.ts +0 -62
  142. package/vendor/tui/types/render/lines.d.ts +0 -86
  143. package/vendor/tui/types/render/markdown.d.ts +0 -29
  144. package/vendor/tui/types/render/projection.d.ts +0 -587
  145. package/vendor/tui/types/render/status.d.ts +0 -196
  146. package/vendor/tui/types/render/text.d.ts +0 -64
  147. package/vendor/tui/types/render/tool-detail.d.ts +0 -94
  148. package/vendor/tui/types/render/tool-preview.d.ts +0 -28
  149. package/vendor/tui/types/render/usage.d.ts +0 -113
  150. package/vendor/tui/types/render/width.d.ts +0 -29
  151. package/vendor/tui/types/session-directory.d.ts +0 -190
  152. package/vendor/tui/types/session-query.d.ts +0 -92
  153. package/vendor/tui/types/session-switch.d.ts +0 -17
  154. package/vendor/tui/types/settings-file.d.ts +0 -41
  155. package/vendor/tui/types/skills.d.ts +0 -47
  156. package/vendor/tui/types/startup.d.ts +0 -65
  157. package/vendor/tui/types/store.d.ts +0 -58
  158. package/vendor/tui/types/subagents.d.ts +0 -70
  159. package/vendor/tui/types/terminal-title.d.ts +0 -66
  160. package/vendor/tui/types/theme-panel.d.ts +0 -24
  161. package/vendor/tui/types/theme.d.ts +0 -434
  162. package/vendor/tui/types/update-panel.d.ts +0 -49
  163. package/vendor/tui/types/update.d.ts +0 -75
  164. package/vendor/tui/types/version.d.ts +0 -19
  165. package/vendor/tui/types/whale-glyph.d.ts +0 -6
  166. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/clipboard-image.swift +0 -0
  167. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/index.mjs +0 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * The /update panel: one bounded surface over the launcher update
3
+ * pipeline. The panel never decides versions itself — it renders the
4
+ * launcher `update --json` probe (plan plus refusals), takes one
5
+ * confirmation, then streams `update --apply` progress and reports the
6
+ * result with a restart hint. Every alignment guarantee (host pinned to
7
+ * the release peers line, companion plugins carried, downgrade and
8
+ * local-checkout refusals) lives in the launcher and is only displayed
9
+ * here.
10
+ */
11
+ import { createElement, useEffect, useState } from 'react';
12
+ import { Box, Text, useInput, useStdout } from 'ink';
13
+ import { clampScroll, panelViewport } from './render/inspector.mjs';
14
+ import { singleLineText, truncateColumns } from './render/text.mjs';
15
+ import { panelAccent } from './panel-accent.mjs';
16
+ import { getPalette, inkColor } from './theme.mjs';
17
+ import { t } from './i18n.mjs';
18
+ /** Retained apply-progress lines (ring tail; npm output is ephemeral). */
19
+ export const UPDATE_OUTPUT_CAP = 800;
20
+ /** Keep the newest UPDATE_OUTPUT_CAP lines of streamed update output. */
21
+ export function clipUpdateLines(lines) {
22
+ return lines.length <= UPDATE_OUTPUT_CAP ? lines : lines.slice(lines.length - UPDATE_OUTPUT_CAP);
23
+ }
24
+ /** Rendered facts of one probe: current/target versions, actions, refusals. */
25
+ export function updatePlanView(status) {
26
+ const rows = [];
27
+ const latest = status.code.latest ?? 'unknown';
28
+ rows.push(status.aheadOfRegistry === true
29
+ ? { key: 'code', text: `dsh-code ${status.code.running} (newer than npm ${latest})` }
30
+ : status.code.latest !== null && status.code.latest === status.code.running
31
+ ? { key: 'code', text: `dsh-code ${status.code.running} (latest)` }
32
+ : { key: 'code', text: `dsh-code ${status.code.running} → ${latest}` });
33
+ if (status.host.targetLine === null) {
34
+ rows.push({ key: 'host', text: 'harness pinned line unreadable — update would install @deepseek-ai/dsh@latest', tone: 'warn' });
35
+ }
36
+ else if (status.host.installed === null) {
37
+ rows.push({ key: 'host', text: `harness not installed → ${status.host.targetLine}` });
38
+ }
39
+ else if (status.host.installed === status.host.targetLine) {
40
+ rows.push({ key: 'host', text: `harness ${status.host.installed} (on pinned line)` });
41
+ }
42
+ else {
43
+ rows.push({ key: 'host', text: `harness ${status.host.installed} → ${status.host.targetLine}` });
44
+ }
45
+ const mounted = status.profile.mounted === null
46
+ ? status.profile.spec ?? 'not mounted'
47
+ : `dsh-code ${status.profile.mounted}`;
48
+ rows.push({
49
+ key: 'profile',
50
+ text: `profile ${mounted}${status.profile.localCheckout ? ' · local checkout' : ''}`,
51
+ tone: status.profile.localCheckout ? 'dim' : undefined,
52
+ });
53
+ for (const plugin of status.plan.pluginSpecs) {
54
+ rows.push({ key: `plugin:${plugin}`, text: `plugin ${plugin}`, tone: 'dim' });
55
+ }
56
+ // A blocker key may be absent entirely (JSON.stringify drops undefined);
57
+ // a missing value reads as "no blocker", never as an undefined row body.
58
+ const registryBlocker = status.blockers.registry ?? null;
59
+ if (registryBlocker !== null) {
60
+ rows.push({ key: 'blocker:registry', text: registryBlocker, tone: 'error' });
61
+ }
62
+ if (status.blockers.downgrade) {
63
+ rows.push({
64
+ key: 'blocker:downgrade',
65
+ text: `refusing to downgrade the host: dsh-code@${latest} needs ${status.host.targetLine ?? 'the pinned line'}, but ${status.host.installed ?? 'the installed host'} is newer — wait for the next dsh-code release`,
66
+ tone: 'error',
67
+ });
68
+ }
69
+ const checkoutBlocker = status.blockers.localCheckout ?? null;
70
+ if (checkoutBlocker !== null) {
71
+ for (const [index, line] of checkoutBlocker.entries()) {
72
+ rows.push({ key: `blocker:checkout:${index}`, text: line, tone: 'error' });
73
+ }
74
+ }
75
+ if (status.aheadOfRegistry === true) {
76
+ rows.push({ key: 'ahead', text: 'this install is newer than npm latest — refusing to downgrade', tone: 'warn' });
77
+ }
78
+ else if (status.upToDate) {
79
+ rows.push({ key: 'uptodate', text: 'everything is already on the pinned line — nothing to update', tone: 'ok' });
80
+ }
81
+ const runnable = status.upToDate !== true
82
+ && registryBlocker === null
83
+ && status.blockers.downgrade !== true
84
+ && checkoutBlocker === null;
85
+ return { rows, runnable };
86
+ }
87
+ /** Footer hint line per phase; the plan phase names the confirm key only when runnable. */
88
+ export function updateFooter(phase, runnable, upToDate, aheadOfRegistry = false) {
89
+ if (phase === 'probe')
90
+ return t('panel.update.footer.probe');
91
+ if (phase === 'error')
92
+ return t('panel.update.footer.error');
93
+ if (phase === 'apply')
94
+ return t('panel.update.footer.apply');
95
+ if (phase === 'done')
96
+ return t('panel.update.footer.error');
97
+ if (aheadOfRegistry)
98
+ return t('panel.update.footer.ahead');
99
+ if (upToDate)
100
+ return t('panel.update.footer.current');
101
+ return runnable ? t('panel.update.footer.update') : t('panel.update.footer.blocked');
102
+ }
103
+ /**
104
+ * The /update surface: probe on open (and on r), confirm with enter/y,
105
+ * stream the aligned apply, and land on a bounded result view. Escape is
106
+ * locked while the apply child runs — killing npm mid-install is exactly
107
+ * the half-updated state this command exists to prevent.
108
+ */
109
+ export function UpdatePanel({ probe, apply, close, notify }) {
110
+ const [phase, setPhase] = useState('probe');
111
+ const [status, setStatus] = useState();
112
+ const [probeError, setProbeError] = useState();
113
+ const [lines, setLines] = useState([]);
114
+ const [exit, setExit] = useState();
115
+ const [applyError, setApplyError] = useState();
116
+ // Viewport anchor: 'tail' follows new output; a number pins the first
117
+ // visible row (up moves away from the tail, down onto it re-follows).
118
+ const [anchor, setAnchor] = useState('tail');
119
+ const [epoch, setEpoch] = useState(0);
120
+ useEffect(() => {
121
+ let disposed = false;
122
+ setPhase('probe');
123
+ setStatus(undefined);
124
+ setProbeError(undefined);
125
+ setAnchor('tail');
126
+ probe().then(value => {
127
+ if (disposed)
128
+ return;
129
+ setStatus(value);
130
+ setPhase('plan');
131
+ }, reason => {
132
+ if (disposed)
133
+ return;
134
+ setProbeError(reason instanceof Error ? reason.message : String(reason));
135
+ setPhase('error');
136
+ });
137
+ return () => { disposed = true; };
138
+ }, [epoch, probe]);
139
+ const stdout = useStdout().stdout;
140
+ const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
141
+ const start = () => {
142
+ if (phase !== 'plan' || status === undefined)
143
+ return;
144
+ if (!updatePlanView(status).runnable)
145
+ return;
146
+ setPhase('apply');
147
+ setLines([]);
148
+ setExit(undefined);
149
+ setApplyError(undefined);
150
+ setAnchor('tail');
151
+ apply(line => {
152
+ setLines(previous => clipUpdateLines([...previous, singleLineText(line)]));
153
+ }, status.plan).then(code => {
154
+ setExit(code);
155
+ setPhase('done');
156
+ notify(code === 0 ? 'update installed — restart dsh to activate' : `update failed (exit ${code})`, code === 0 ? 'info' : 'error');
157
+ }, reason => {
158
+ const message = reason instanceof Error ? reason.message : String(reason);
159
+ setApplyError(message);
160
+ setPhase('done');
161
+ notify(`update failed: ${message}`, 'error');
162
+ });
163
+ };
164
+ const planView = status === undefined ? undefined : updatePlanView(status);
165
+ const rows = phase === 'probe'
166
+ ? [{ key: 'probe', text: 'checking npm for the aligned update…', tone: 'dim' }]
167
+ : phase === 'error'
168
+ ? [{ key: 'error', text: singleLineText(probeError ?? 'probe failed'), tone: 'error' }]
169
+ : phase === 'plan' && planView !== undefined
170
+ ? planView.rows
171
+ : phase === 'apply'
172
+ ? lines.map((line, index) => ({ key: `out:${index}`, text: line, tone: 'dim' }))
173
+ : [
174
+ ...(exit === 0 ? [{ key: 'ok', text: 'update installed — restart dsh to load the new version (/quit or ctrl+c)', tone: 'ok' }] : []),
175
+ ...(exit !== undefined && exit !== 0 ? [{ key: 'fail', text: `update failed (exit ${exit})`, tone: 'error' }] : []),
176
+ ...(applyError !== undefined ? [{ key: 'fail:start', text: singleLineText(applyError), tone: 'error' }] : []),
177
+ ...lines.map((line, index) => ({ key: `out:${index}`, text: line, tone: 'dim' })),
178
+ ];
179
+ const budget = Math.max(1, viewport.bodyRows);
180
+ const tailOffset = clampScroll(Math.max(0, rows.length - budget), rows.length, budget);
181
+ const offset = anchor === 'tail' ? tailOffset : clampScroll(anchor, rows.length, budget);
182
+ useInput((input, key) => {
183
+ if (phase === 'apply') {
184
+ // Scroll-only while the installer runs: escape stays locked.
185
+ if (key.upArrow)
186
+ setAnchor(offset <= 0 ? 0 : offset - 1);
187
+ if (key.downArrow && offset >= tailOffset)
188
+ setAnchor('tail');
189
+ else if (key.downArrow)
190
+ setAnchor(offset + 1);
191
+ return;
192
+ }
193
+ if (key.escape || input === 'q')
194
+ return close();
195
+ if (input === 'r') {
196
+ setEpoch(value => value + 1);
197
+ return;
198
+ }
199
+ if (key.return || input === 'y') {
200
+ void start();
201
+ return;
202
+ }
203
+ if (key.upArrow)
204
+ setAnchor(offset <= 0 ? 0 : offset - 1);
205
+ if (key.downArrow && offset >= tailOffset)
206
+ setAnchor('tail');
207
+ else if (key.downArrow)
208
+ setAnchor(offset + 1);
209
+ });
210
+ if (viewport.maxHeight === 0 || viewport.compact) {
211
+ const summary = phase === 'probe' ? t('panel.update.checking')
212
+ : phase === 'error' ? t('panel.update.probeFailed')
213
+ : phase === 'apply' ? singleLineText(lines[lines.length - 1] ?? t('panel.update.updating'))
214
+ : phase === 'done' ? (exit === 0 ? t('panel.update.installed') : t('panel.update.failed'))
215
+ : status?.aheadOfRegistry === true ? t('panel.update.aheadOfNpm') : status?.upToDate === true ? t('panel.update.upToDate') : planView?.runnable === true ? t('panel.update.enter') : t('panel.update.blocked');
216
+ return createElement(Text, { wrap: 'truncate-end' }, truncateColumns(singleLineText(t('panel.update.compact', { summary })), viewport.contentColumns));
217
+ }
218
+ const visible = rows.slice(offset, offset + budget);
219
+ const toneColor = (tone) => tone === 'ok'
220
+ ? inkColor(getPalette().success)
221
+ : tone === 'error'
222
+ ? inkColor(getPalette().error)
223
+ : tone === 'warn'
224
+ ? inkColor(getPalette().warn)
225
+ : tone === 'dim'
226
+ ? inkColor(getPalette().dim)
227
+ : undefined;
228
+ const title = status === undefined
229
+ ? t('panel.update.title')
230
+ : status.aheadOfRegistry === true
231
+ ? `/update · dsh-code ${status.code.running}`
232
+ : `/update · dsh-code ${status.code.running}${status.code.latest !== null && status.code.latest !== status.code.running ? ` → ${status.code.latest}` : ''}`;
233
+ const accent = panelAccent('update', getPalette().dim, getPalette().brandBright);
234
+ return createElement(Box, { width: viewport.outerColumns, borderStyle: 'round', borderColor: inkColor(accent.border), flexDirection: 'column', paddingX: 1 }, createElement(Text, { color: inkColor(accent.title), wrap: 'truncate-end' }, truncateColumns(singleLineText(title), viewport.contentColumns)), ...visible.map(row => createElement(Text, {
235
+ key: row.key,
236
+ color: toneColor(row.tone),
237
+ wrap: 'truncate-end',
238
+ }, truncateColumns(` ${singleLineText(row.text)}`, viewport.contentColumns))), createElement(Text, { dimColor: true, wrap: 'truncate-end' }, truncateColumns(singleLineText(updateFooter(phase, planView?.runnable ?? false, status?.upToDate === true, status?.aheadOfRegistry === true)), viewport.contentColumns)));
239
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Child-process adapter for the launcher update pipeline. The launcher
3
+ * (bin/deepseek.mjs) stays the single owner of update semantics — plan,
4
+ * guards, and the aligned install sequence — so the TUI only spawns
5
+ * `update --json` (read-only probe) and `update --apply` (streamed run)
6
+ * and never re-implements version-line decisions.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { fileURLToPath } from 'node:url';
10
+ /** The launcher entrypoint that ships beside this bundle (lib/../bin). */
11
+ export function launcherUpdateCommand(args, moduleUrl = import.meta.url) {
12
+ return { command: process.execPath, args: [fileURLToPath(new URL('../bin/deepseek.mjs', moduleUrl)), ...args] };
13
+ }
14
+ /**
15
+ * Split a streamed chunk sequence into complete display lines: CR is
16
+ * stripped, a chunk boundary may split a line, and the trailing partial
17
+ * stays pending until its newline arrives (npm writes whole lines, but a
18
+ * pipe may cut anywhere). Blank lines carry no progress information and
19
+ * are dropped so the panel budget is not spent on gaps.
20
+ */
21
+ export function createLineSplitter(onLine) {
22
+ let pending = '';
23
+ return chunk => {
24
+ pending += chunk;
25
+ for (let index = pending.indexOf('\n'); index >= 0; index = pending.indexOf('\n')) {
26
+ const line = pending.slice(0, index).replace(/\r$/u, '');
27
+ pending = pending.slice(index + 1);
28
+ if (line !== '')
29
+ onLine(line);
30
+ }
31
+ };
32
+ }
33
+ /**
34
+ * Probe the aligned update status. Read-only: `update --json` never
35
+ * installs anything. The probe is bounded (npm view may hang on a broken
36
+ * network) and resolves with the parsed status.
37
+ */
38
+ export async function probeLauncherUpdate(spawnProcess = spawn) {
39
+ const command = launcherUpdateCommand(['update', '--json']);
40
+ return await new Promise((resolve, reject) => {
41
+ const child = spawnProcess(command.command, command.args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
42
+ const timer = setTimeout(() => {
43
+ child.kill();
44
+ reject(new Error('update probe timed out'));
45
+ }, 30_000);
46
+ let stdout = '';
47
+ let stderr = '';
48
+ child.stdout?.on('data', (chunk) => { stdout += chunk.toString(); });
49
+ child.stderr?.on('data', (chunk) => { stderr += chunk.toString(); });
50
+ child.once('error', error => {
51
+ clearTimeout(timer);
52
+ reject(new Error(`update probe failed to start: ${error.message}`));
53
+ });
54
+ child.once('exit', code => {
55
+ clearTimeout(timer);
56
+ if (code === 0) {
57
+ try {
58
+ resolve(JSON.parse(stdout));
59
+ }
60
+ catch {
61
+ reject(new Error('update probe returned an unreadable status'));
62
+ }
63
+ return;
64
+ }
65
+ const tail = stderr.trim().split(/\r?\n/u).pop() ?? '';
66
+ reject(new Error(`update probe failed${tail === '' ? '' : `: ${tail}`}`));
67
+ });
68
+ });
69
+ }
70
+ /**
71
+ * Run the aligned update (`update --apply`) as a child process and stream
72
+ * its sanitized progress lines to the caller. Resolves with the child
73
+ * exit code (0 success); rejects only when the process could not start.
74
+ * No timeout: an npm install may legitimately take minutes.
75
+ */
76
+ export function applyPlanArgs(plan) {
77
+ const args = ['update', '--apply', '--dsh', plan.dshSpec, '--code', plan.codeSpec];
78
+ for (const plugin of plan.pluginSpecs)
79
+ args.push('--plugin', plugin);
80
+ return args;
81
+ }
82
+ export function applyLauncherUpdate(onLine, spawnProcess, plan) {
83
+ const spawnFn = spawnProcess ?? spawn;
84
+ const command = launcherUpdateCommand(plan === undefined ? ['update', '--apply'] : applyPlanArgs(plan));
85
+ return new Promise((resolve, reject) => {
86
+ const child = spawnFn(command.command, command.args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
87
+ const split = createLineSplitter(onLine);
88
+ child.stdout?.on('data', (chunk) => { split(chunk.toString()); });
89
+ child.stderr?.on('data', (chunk) => { split(chunk.toString()); });
90
+ child.once('error', error => {
91
+ reject(new Error(`update failed to start: ${error.message}`));
92
+ });
93
+ // 'close' fires once the stdio streams have ended, so a final line the
94
+ // child left without its newline still reaches the panel; a bare '\n'
95
+ // flushes any pending partial without adding a blank row.
96
+ child.once('close', (code, signal) => {
97
+ split('\n');
98
+ resolve(code ?? (signal === null ? 0 : 1));
99
+ });
100
+ });
101
+ }
@@ -0,0 +1,105 @@
1
+ /** Installed dsh-code version exposed by the terminal header. */
2
+ import { readFileSync, realpathSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ /** Read one package manifest version without making terminal startup depend on it. */
6
+ function readPackageVersion(manifest = new URL('../package.json', import.meta.url)) {
7
+ try {
8
+ const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
9
+ return typeof parsed.version === 'string' && parsed.version.length > 0 ? parsed.version : '0.0.0';
10
+ }
11
+ catch {
12
+ return '0.0.0';
13
+ }
14
+ }
15
+ /** Version of the installed dsh-code package. */
16
+ export const DSH_CODE_VERSION = readPackageVersion();
17
+ /** The harness host package: the dsh CLI whose process runs the TUI plugin. */
18
+ const DSH_HOST_PACKAGE_NAME = '@deepseek-ai/dsh';
19
+ /** Parent levels above the host entry allowed to hold its package manifest. */
20
+ const DSH_HOST_WALK_LIMIT = 4;
21
+ /** Read `@deepseek-ai/dsh`'s version from a directory's package.json, or undefined. */
22
+ function readHostVersion(directory) {
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
25
+ if (parsed.name === DSH_HOST_PACKAGE_NAME && typeof parsed.version === 'string' && parsed.version.length > 0) {
26
+ return parsed.version;
27
+ }
28
+ }
29
+ catch {
30
+ // No readable host manifest at this level.
31
+ }
32
+ return undefined;
33
+ }
34
+ /** Climb from an entry file toward a host package.json. */
35
+ function walkFrom(entry) {
36
+ let directory = dirname(resolve(entry));
37
+ for (let depth = 0; depth < DSH_HOST_WALK_LIMIT; depth += 1) {
38
+ const version = readHostVersion(directory);
39
+ if (version !== undefined)
40
+ return version;
41
+ const parent = dirname(directory);
42
+ if (parent === directory)
43
+ return undefined;
44
+ directory = parent;
45
+ }
46
+ return undefined;
47
+ }
48
+ /**
49
+ * Resolve the host package via Node's module lookup from an entry file.
50
+ * Covers global `bin/dsh` shims that are not inside the package tree.
51
+ */
52
+ function resolveFromNode(from) {
53
+ try {
54
+ const manifest = createRequire(from).resolve(`${DSH_HOST_PACKAGE_NAME}/package.json`);
55
+ return readHostVersion(dirname(manifest));
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ /**
62
+ * Resolve the running dsh CLI host's version from its entry file
63
+ * (`process.argv[1]`, e.g. `.../@deepseek-ai/dsh/lib/bin.js` or a PATH
64
+ * symlink `.../bin/dsh` → that file). Only a manifest literally named
65
+ * `@deepseek-ai/dsh` counts, so an unrelated entry (vitest, a plain node
66
+ * script) resolves to undefined instead of faking a kernel version.
67
+ */
68
+ export function resolveDshHostVersion(entry = process.argv[1]) {
69
+ if (entry === undefined || entry === '')
70
+ return undefined;
71
+ const fromWalk = walkFrom(entry);
72
+ if (fromWalk !== undefined)
73
+ return fromWalk;
74
+ try {
75
+ const real = realpathSync(entry);
76
+ if (real !== resolve(entry)) {
77
+ const fromReal = walkFrom(real);
78
+ if (fromReal !== undefined)
79
+ return fromReal;
80
+ }
81
+ }
82
+ catch {
83
+ // Broken or missing symlink: fall through to Node resolution.
84
+ }
85
+ return resolveFromNode(resolve(entry));
86
+ }
87
+ let cachedDshKernelVersion;
88
+ let dshKernelVersionResolved = false;
89
+ /**
90
+ * The dsh kernel version the TUI runs on, memoized after the first probe: the
91
+ * host process never changes within a run, and the header reads this on every
92
+ * Static replay.
93
+ */
94
+ export function dshKernelVersion() {
95
+ if (!dshKernelVersionResolved) {
96
+ cachedDshKernelVersion = resolveDshHostVersion();
97
+ dshKernelVersionResolved = true;
98
+ }
99
+ return cachedDshKernelVersion;
100
+ }
101
+ /** Test-only: forget the memoized kernel version so a new argv can be probed. */
102
+ export function _resetDshKernelVersionForTests() {
103
+ cachedDshKernelVersion = undefined;
104
+ dshKernelVersionResolved = false;
105
+ }
@@ -0,0 +1,20 @@
1
+ // GENERATED by scripts/gen-whale-glyph.ts — do not edit by hand. Rerun the
2
+ // generator after changing the FishLogo path. Half-block rendering of the
3
+ // DeepSeek fish logo (figma I39:24057;88:8943 fillGeometry, exact extract;
4
+ // native 23.16x17.04 → 26 columns × 8 half-block rows).
5
+ // Blank cells are part of the glyph's fixed 26-column grid: pad, never trim.
6
+ /** Half-block whale glyph rows; render with the brand color. */
7
+ export const WHALE_GLYPH = [
8
+ ' ▄▄▄▄▄▄▄▄█ ▄█▄ ▄',
9
+ ' ▄▄██████████▄▄ ▀███▄████',
10
+ '▄███████████████▄ ███▀▀▀ ',
11
+ '██ ▀▀█████▄▀██████ ',
12
+ '██▄ ▀████▄▄████ ',
13
+ ' ██▄ ▀██████▀ ',
14
+ ' ▀██▄▄ ██▄ ▀███▄▄ ',
15
+ ' ▀▀███████▀▀ ▀▀▀ ',
16
+ ];
17
+ /** Fixed glyph width in terminal columns. */
18
+ export const WHALE_GLYPH_COLUMNS = 26;
19
+ /** Fixed glyph height in half-block rows. */
20
+ export const WHALE_GLYPH_ROWS = 8;
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 unlinearity
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.