dsh-edge 0.1.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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +183 -0
  4. package/README.zh.md +183 -0
  5. package/THIRD_PARTY_NOTICES.md +199 -0
  6. package/dist/_headers +3 -0
  7. package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/dist/assets/index-C-1AiF3k.js +112 -0
  67. package/dist/assets/index-CSGf6Qzd.css +1 -0
  68. package/dist/assets/langs/c-BIGW1oBm.js +2 -0
  69. package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
  70. package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
  71. package/dist/assets/langs/css-CLj8gQPS.js +2 -0
  72. package/dist/assets/langs/go-C27-OAKa.js +2 -0
  73. package/dist/assets/langs/html-CfGypltT.js +2 -0
  74. package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
  75. package/dist/assets/langs/java-CylS5w8V.js +2 -0
  76. package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
  77. package/dist/assets/langs/less-B1dDrJ26.js +2 -0
  78. package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
  79. package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
  80. package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
  81. package/dist/assets/langs/php-D-uCvoST.js +2 -0
  82. package/dist/assets/langs/python-B6aJPvgy.js +2 -0
  83. package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
  84. package/dist/assets/langs/rust-B1yitclQ.js +2 -0
  85. package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
  86. package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
  87. package/dist/assets/langs/swift-D82vCrfD.js +2 -0
  88. package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
  89. package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
  90. package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
  91. package/dist/assets/vendor-Cjbwl5VI.js +411 -0
  92. package/dist/assets/vendor-CjyC-hUb.css +1 -0
  93. package/dist/favicon.svg +8 -0
  94. package/dist/index.html +35 -0
  95. package/dist/manifest.webmanifest +16 -0
  96. package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
  97. package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
  98. package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
  99. package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
  100. package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
  101. package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
  102. package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
  103. package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
  104. package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
  105. package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
  106. package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
  107. package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
  108. package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
  109. package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
  110. package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
  111. package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
  112. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
  113. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
  114. package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
  115. package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
  116. package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
  117. package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
  118. package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
  119. package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
  120. package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
  121. package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
  122. package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
  123. package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
  124. package/package.json +97 -0
  125. package/scripts/assemble-web.mjs +230 -0
  126. package/scripts/bundle-size.d.mts +2 -0
  127. package/scripts/bundle-size.mjs +26 -0
  128. package/scripts/bundle.mjs +53 -0
  129. package/scripts/cli.d.mts +39 -0
  130. package/scripts/cli.mjs +496 -0
  131. package/scripts/install.d.mts +149 -0
  132. package/scripts/install.mjs +989 -0
  133. package/scripts/legal-files.mjs +24 -0
  134. package/scripts/verify-packed.mjs +32 -0
  135. package/scripts/wrangler-config.d.mts +18 -0
  136. package/scripts/wrangler-config.mjs +84 -0
  137. package/src/agent.ts +119 -0
  138. package/src/auth.ts +352 -0
  139. package/src/deepseek.ts +231 -0
  140. package/src/deployment.ts +147 -0
  141. package/src/direct-shell-core-empty.ts +10 -0
  142. package/src/direct-shell-protocol.ts +5 -0
  143. package/src/direct-shell.ts +341 -0
  144. package/src/do-session-persistence.ts +1064 -0
  145. package/src/edge-api.ts +796 -0
  146. package/src/edge-credentials.ts +56 -0
  147. package/src/edge-remotes.ts +41 -0
  148. package/src/edge-workspace-store.ts +339 -0
  149. package/src/http.ts +212 -0
  150. package/src/index.ts +196 -0
  151. package/src/instance.ts +1007 -0
  152. package/src/isolated-direct-shell-unavailable.ts +11 -0
  153. package/src/protocol.ts +39 -0
  154. package/src/release.ts +9 -0
  155. package/src/session-store.ts +1110 -0
  156. package/src/sse.ts +86 -0
  157. package/src/web-search.ts +38 -0
  158. package/src/workspace.ts +270 -0
  159. package/wrangler.jsonc +56 -0
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as prompt from '@clack/prompts'
4
+ import { realpathSync, writeSync } from 'node:fs'
5
+ import { Writable } from 'node:stream'
6
+ import { fileURLToPath } from 'node:url'
7
+ import edgePackage from '../package.json' with { type: 'json' }
8
+ import {
9
+ executeWrangler,
10
+ InstallCancelledError,
11
+ InstallerOutputError,
12
+ installEdge,
13
+ } from './install.mjs'
14
+
15
+ const INTERRUPT_EXIT_CODES = new Map([
16
+ ['SIGHUP', 129],
17
+ ['SIGINT', 130],
18
+ ['SIGTERM', 143],
19
+ ])
20
+ const OUTPUT_DRAIN_TIMEOUT_MS = 1_000
21
+
22
+ export class InstallInterruptedError extends InstallCancelledError {
23
+ constructor(signal) {
24
+ super()
25
+ this.exitCode = INTERRUPT_EXIT_CODES.get(signal)
26
+ this.signal = signal
27
+ }
28
+ }
29
+
30
+ export function parseCommand(args) {
31
+ if (args.length === 1 && (args[0] === 'install' || args[0] === 'upgrade')) return args[0]
32
+ if (args.length === 0 || (args.length === 1 && ['--help', '-h'].includes(args[0]))) return 'help'
33
+ if (args.length === 1 && ['--version', '-v'].includes(args[0])) return 'version'
34
+ throw new Error('Usage: dsh-edge <install|upgrade>')
35
+ }
36
+
37
+ export function createInstallerUi(
38
+ clack = prompt,
39
+ signal,
40
+ writeDescriptor = writeSync,
41
+ output,
42
+ writeRecovery,
43
+ command = 'install',
44
+ ) {
45
+ const withOutput = options => output === undefined ? options : { ...options, output }
46
+ const log = (writer, message) => output === undefined
47
+ ? writer(message)
48
+ : writer(message, { output })
49
+ const note = (message, title) => output === undefined
50
+ ? clack.note(message, title)
51
+ : clack.note(message, title, { output })
52
+ return {
53
+ intro: message => log(clack.intro, message),
54
+ step: message => log(clack.log.step, message),
55
+ async selectRuntime() {
56
+ return await requireAnswer(await clack.select(withOutput({
57
+ message: 'Choose a runtime',
58
+ signal,
59
+ initialValue: 'direct',
60
+ options: [
61
+ {
62
+ value: 'direct',
63
+ label: 'Free — Direct Shell',
64
+ hint: 'recommended; runs on Workers Free',
65
+ },
66
+ {
67
+ value: 'isolated',
68
+ label: 'Isolated — Dynamic Worker',
69
+ hint: 'requires Workers Paid (starting at $5/month)',
70
+ },
71
+ ],
72
+ })))
73
+ },
74
+ async selectAccount(choices) {
75
+ return await requireAnswer(await clack.select(withOutput({
76
+ message: 'Choose a Cloudflare account',
77
+ options: choices,
78
+ signal,
79
+ })))
80
+ },
81
+ async workerName(initialValue, validate) {
82
+ return await requireAnswer(await clack.text(withOutput({
83
+ message: 'Worker name',
84
+ initialValue,
85
+ signal,
86
+ validate,
87
+ })))
88
+ },
89
+ async workerConflict(workerName) {
90
+ return await requireAnswer(await clack.select(withOutput({
91
+ message: `${workerName} already exists`,
92
+ signal,
93
+ options: [
94
+ { value: 'rename', label: 'Choose another name' },
95
+ { value: 'update', label: 'Update this Worker', hint: 'keeps its Durable Object data' },
96
+ { value: 'cancel', label: 'Cancel' },
97
+ ],
98
+ })))
99
+ },
100
+ async selectOwnerSecretMode() {
101
+ return await requireAnswer(await clack.select(withOutput({
102
+ message: 'Set the owner access key',
103
+ initialValue: 'generate',
104
+ signal,
105
+ options: [
106
+ { value: 'generate', label: 'Generate a secure key', hint: 'recommended' },
107
+ { value: 'custom', label: 'Enter my own key' },
108
+ ],
109
+ })))
110
+ },
111
+ async ownerSecret(validate) {
112
+ return await requireAnswer(await clack.password(withOutput({
113
+ message: 'Owner access key',
114
+ mask: '•',
115
+ signal,
116
+ validate,
117
+ })))
118
+ },
119
+ async deepSeekKey(validate) {
120
+ return await requireAnswer(await clack.password(withOutput({
121
+ message: 'DeepSeek API key',
122
+ mask: '•',
123
+ signal,
124
+ validate,
125
+ })))
126
+ },
127
+ async confirm(summary) {
128
+ note([
129
+ `Runtime: ${summary.modeLabel}`,
130
+ `Account: ${summary.accountLabel}`,
131
+ `Worker: ${summary.workerName}`,
132
+ `Cost: ${summary.paid ? 'Workers Paid is required' : 'Works on Workers Free'}`,
133
+ ...(command === 'upgrade' ? ['Existing Durable Object data is preserved.', 'The entered secrets replace the active values.'] : []),
134
+ ].join('\n'), command === 'upgrade' ? 'Upgrade summary' : 'Installation summary')
135
+ return await requireAnswer(await clack.confirm(withOutput({
136
+ message: command === 'upgrade' ? 'Upgrade this instance?' : 'Install this instance?',
137
+ initialValue: true,
138
+ signal,
139
+ })))
140
+ },
141
+ async acceptTemporaryTerms() {
142
+ note([
143
+ 'Cloudflare Terms of Service: https://www.cloudflare.com/terms/',
144
+ 'Cloudflare Privacy Policy: https://www.cloudflare.com/privacypolicy/',
145
+ ].join('\n'), 'Temporary account terms')
146
+ return await requireAnswer(await clack.confirm(withOutput({
147
+ message: 'Accept these terms and create a temporary Cloudflare account?',
148
+ initialValue: false,
149
+ signal,
150
+ })))
151
+ },
152
+ cleanupFailure(message) {
153
+ log(clack.log.warn, message)
154
+ },
155
+ recovery(result) {
156
+ note(recoveryLines(result).join('\n'), 'Worker uploaded — recovery details')
157
+ },
158
+ outputFailureRecovery(result, failedStream) {
159
+ const lines = [
160
+ '',
161
+ 'Worker uploaded — recovery details',
162
+ ...recoveryLines(result),
163
+ '',
164
+ ]
165
+ if (writeRecovery !== undefined) {
166
+ return writeRecovery(failedStream, lines.join('\n'))
167
+ }
168
+ return writeAlternate(writeDescriptor, failedStream, lines)
169
+ },
170
+ success(result) {
171
+ const lines = [
172
+ `URL: ${result.publicUrl}`,
173
+ `Owner access key: ${result.ownerSecret}`,
174
+ ]
175
+ if (result.claimUrl !== undefined) {
176
+ lines.push(
177
+ `Claim URL: ${result.claimUrl}`,
178
+ '',
179
+ 'Next steps:',
180
+ '1. Claim this temporary account within 60 minutes to keep the Worker and its data.',
181
+ '2. Open the URL above.',
182
+ '3. Enter the owner access key when prompted.',
183
+ )
184
+ } else {
185
+ lines.push(
186
+ '',
187
+ 'Next steps:',
188
+ '1. Open the URL above.',
189
+ '2. Enter the owner access key when prompted.',
190
+ )
191
+ }
192
+ note(lines.join('\n'), command === 'upgrade' ? 'Worker upgraded' : 'Worker uploaded')
193
+ log(clack.outro, command === 'upgrade' ? 'Upgrade handoff complete.' : 'Installation handoff complete.')
194
+ },
195
+ }
196
+ }
197
+
198
+ function recoveryLines(result) {
199
+ const lines = [
200
+ `Worker: ${result.workerName}`,
201
+ `Active owner access key: ${result.ownerSecret}`,
202
+ ]
203
+ if (result.publicUrl !== undefined) lines.unshift(`URL: ${result.publicUrl}`)
204
+ if (result.claimUrl !== undefined) lines.push(`Claim URL: ${result.claimUrl}`)
205
+ lines.push('Save this key. Wrangler reported a successful upload, but the installer could not complete its handoff.')
206
+ return lines
207
+ }
208
+
209
+ function writeAlternate(writeDescriptor, failedStream, lines) {
210
+ const descriptor = failedStream === 'stdout' ? 2 : 1
211
+ try {
212
+ writeDescriptor(descriptor, lines.join('\n'))
213
+ return true
214
+ } catch {
215
+ // The alternate process stream is unavailable too; credential cleanup must continue.
216
+ return false
217
+ }
218
+ }
219
+
220
+ function requireAnswer(value) {
221
+ if (prompt.isCancel(value)) throw new InstallCancelledError()
222
+ return value
223
+ }
224
+
225
+ export async function runInstaller({
226
+ command = 'install',
227
+ install,
228
+ installEdgeImpl = installEdge,
229
+ installerUi,
230
+ outputDrainTimeoutMs = OUTPUT_DRAIN_TIMEOUT_MS,
231
+ uiFactory = (signal, output, writeRecovery) => createInstallerUi(
232
+ prompt,
233
+ signal,
234
+ writeSync,
235
+ output,
236
+ writeRecovery,
237
+ command,
238
+ ),
239
+ runtimeProcess = process,
240
+ stderr = process.stderr,
241
+ stdout = process.stdout,
242
+ wranglerRunner = executeWrangler,
243
+ } = {}) {
244
+ const controller = new AbortController()
245
+ let interruption
246
+ let outputFailure
247
+ let notifyOutputFailure
248
+ const outputFailureDetected = new Promise(resolve => {
249
+ notifyOutputFailure = resolve
250
+ })
251
+ let recoveryStream
252
+ const failOutput = (stream, error) => {
253
+ if (outputFailure !== undefined) return
254
+ outputFailure = new InstallerOutputError(stream, error)
255
+ recoveryStream ??= stream
256
+ notifyOutputFailure()
257
+ controller.abort(outputFailure)
258
+ }
259
+ const stdoutBoundary = createInstallerOutput(stdout, error => failOutput('stdout', error))
260
+ const stderrBoundary = createInstallerOutput(stderr, error => failOutput('stderr', error))
261
+ const cancelBlockedOutput = () => {
262
+ const interruptError = interruption ?? controller.signal.reason
263
+ if (!(interruptError instanceof InstallInterruptedError)) return
264
+ const stdoutBlocked = stdoutBoundary.cancel()
265
+ const stderrBlocked = stderrBoundary.cancel()
266
+ recoveryStream ??= stderrBlocked && !stdoutBlocked ? 'stderr' : 'stdout'
267
+ interruptError.outputFailureStream = recoveryStream
268
+ }
269
+ controller.signal.addEventListener('abort', cancelBlockedOutput)
270
+ const installOperation = install ?? (options => installEdgeImpl({
271
+ ...options,
272
+ runWrangler: (args, runOptions) => wranglerRunner(args, {
273
+ ...runOptions,
274
+ stderrDestination: stderrBoundary.output,
275
+ stdoutDestination: stdoutBoundary.output,
276
+ }),
277
+ }))
278
+ const writeRecovery = (failedStream, value) => {
279
+ const alternateBoundary = failedStream === 'stdout' ? stderrBoundary : stdoutBoundary
280
+ return alternateBoundary.write(value)
281
+ }
282
+ const baseUi = installerUi
283
+ ?? uiFactory(controller.signal, stdoutBoundary.output, writeRecovery)
284
+ let lastRecovery
285
+ let alternateRecoveryAttempted = false
286
+ let alternateRecoveryPending
287
+ const deliverAlternateRecovery = (result, stream) => {
288
+ if (alternateRecoveryAttempted) return
289
+ alternateRecoveryAttempted = true
290
+ try {
291
+ alternateRecoveryPending = Promise.resolve(baseUi.outputFailureRecovery(result, stream))
292
+ .then(delivered => delivered !== false, () => false)
293
+ } catch {
294
+ alternateRecoveryPending = Promise.resolve()
295
+ }
296
+ }
297
+ const ui = {
298
+ ...baseUi,
299
+ recovery(result) {
300
+ lastRecovery = result
301
+ if (recoveryStream === undefined) baseUi.recovery(result)
302
+ else deliverAlternateRecovery(result, recoveryStream)
303
+ },
304
+ outputFailureRecovery(result, stream) {
305
+ lastRecovery = result
306
+ recoveryStream ??= stream
307
+ deliverAlternateRecovery(result, stream)
308
+ },
309
+ success(result) {
310
+ lastRecovery = result
311
+ if (recoveryStream === undefined) baseUi.success(result)
312
+ else deliverAlternateRecovery(result, recoveryStream)
313
+ },
314
+ }
315
+ const handlers = new Map([...INTERRUPT_EXIT_CODES].map(([signal]) => [
316
+ signal,
317
+ () => {
318
+ interruption ??= new InstallInterruptedError(signal)
319
+ if (controller.signal.aborted) cancelBlockedOutput()
320
+ else controller.abort(interruption)
321
+ },
322
+ ]))
323
+ for (const [signal, handler] of handlers) runtimeProcess.on(signal, handler)
324
+ let result
325
+ let installError
326
+ try {
327
+ try {
328
+ result = await installOperation({ command, ui, signal: controller.signal })
329
+ } catch (error) {
330
+ installError = error
331
+ }
332
+ if (outputFailure === undefined) {
333
+ await Promise.race([
334
+ Promise.all([stdoutBoundary.settled(), stderrBoundary.settled()]),
335
+ outputFailureDetected,
336
+ ])
337
+ }
338
+ if (recoveryStream !== undefined && lastRecovery !== undefined) {
339
+ deliverAlternateRecovery(lastRecovery, recoveryStream)
340
+ }
341
+ const outputsSettled = Promise.all([stdoutBoundary.settled(), stderrBoundary.settled()])
342
+ if (outputFailure !== undefined) {
343
+ let timeout
344
+ const drainTimedOut = new Promise(resolve => {
345
+ timeout = setTimeout(() => resolve(true), outputDrainTimeoutMs)
346
+ })
347
+ try {
348
+ const timedOut = await Promise.race([
349
+ outputsSettled.then(() => false),
350
+ drainTimedOut,
351
+ ])
352
+ if (timedOut) {
353
+ stdoutBoundary.cancel()
354
+ stderrBoundary.cancel()
355
+ await outputsSettled
356
+ }
357
+ } finally {
358
+ clearTimeout(timeout)
359
+ }
360
+ } else {
361
+ await outputsSettled
362
+ }
363
+ await alternateRecoveryPending
364
+ const abortError = interruption ?? controller.signal.reason
365
+ if (abortError instanceof InstallInterruptedError) throw abortError
366
+ if (outputFailure !== undefined && abortError === outputFailure) throw outputFailure
367
+ if (installError !== undefined) throw installError
368
+ if (abortError instanceof Error) throw abortError
369
+ if (controller.signal.aborted) throw new Error('Installation aborted without an Error reason.')
370
+ return result
371
+ } finally {
372
+ for (const [signal, handler] of handlers) runtimeProcess.removeListener(signal, handler)
373
+ controller.signal.removeEventListener('abort', cancelBlockedOutput)
374
+ stdoutBoundary.dispose()
375
+ stderrBoundary.dispose()
376
+ }
377
+ }
378
+
379
+ function createInstallerOutput(destination, onFailure) {
380
+ let activeWrite
381
+ let failed = false
382
+ let pendingWrites = 0
383
+ const settleWaiters = new Set()
384
+ const settle = () => {
385
+ if (pendingWrites !== 0 || output.writableLength !== 0) return
386
+ for (const resolve of settleWaiters) resolve()
387
+ settleWaiters.clear()
388
+ }
389
+ const fail = (error) => {
390
+ if (failed) return
391
+ failed = true
392
+ onFailure(error instanceof Error ? error : new Error(String(error)))
393
+ activeWrite?.()
394
+ }
395
+ const closed = () => fail(new Error('Output destination closed during installation.'))
396
+ const output = new Writable({
397
+ write(chunk, encoding, callback) {
398
+ if (failed) {
399
+ callback()
400
+ queueMicrotask(settle)
401
+ return
402
+ }
403
+ pendingWrites += 1
404
+ let completed = false
405
+ const written = (error) => {
406
+ if (completed) return
407
+ completed = true
408
+ activeWrite = undefined
409
+ if (error !== undefined && error !== null) fail(error)
410
+ pendingWrites = Math.max(0, pendingWrites - 1)
411
+ callback()
412
+ queueMicrotask(settle)
413
+ }
414
+ activeWrite = written
415
+ try {
416
+ destination.write(chunk, encoding, written)
417
+ } catch (error) {
418
+ written(error)
419
+ }
420
+ },
421
+ })
422
+ Object.defineProperties(output, {
423
+ columns: { get: () => destination.columns },
424
+ isTTY: { get: () => destination.isTTY },
425
+ rows: { get: () => destination.rows },
426
+ })
427
+ destination.on('error', fail)
428
+ destination.on('close', closed)
429
+ return {
430
+ output,
431
+ write(value) {
432
+ return new Promise(resolve => {
433
+ if (failed) {
434
+ resolve(false)
435
+ return
436
+ }
437
+ output.write(value, () => resolve(!failed))
438
+ })
439
+ },
440
+ cancel() {
441
+ const blocked = pendingWrites !== 0 || output.writableLength !== 0
442
+ if (!blocked) return false
443
+ failed = true
444
+ destination.destroy()
445
+ activeWrite?.()
446
+ return true
447
+ },
448
+ settled() {
449
+ if (pendingWrites === 0 && output.writableLength === 0) return Promise.resolve()
450
+ return new Promise(resolve => settleWaiters.add(resolve))
451
+ },
452
+ dispose() {
453
+ destination.removeListener('error', fail)
454
+ destination.removeListener('close', closed)
455
+ },
456
+ }
457
+ }
458
+
459
+ async function main() {
460
+ const command = parseCommand(process.argv.slice(2))
461
+ if (command === 'help') {
462
+ process.stdout.write('Usage: dsh-edge <install|upgrade>\n\nCommands:\n install Create or update an instance\n upgrade Upgrade an existing instance without deleting its data\n')
463
+ return
464
+ }
465
+ if (command === 'version') {
466
+ process.stdout.write(`${edgePackage.version}\n`)
467
+ return
468
+ }
469
+ await runInstaller({ command })
470
+ }
471
+
472
+ if (process.argv[1] !== undefined
473
+ && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
474
+ main().catch((error) => {
475
+ if (error instanceof InstallCancelledError) {
476
+ if (error instanceof InstallInterruptedError) {
477
+ writeAlternate(
478
+ writeSync,
479
+ error.outputFailureStream ?? 'stdout',
480
+ ['', `Installation interrupted by ${error.signal}.`, ''],
481
+ )
482
+ process.exitCode = error.exitCode
483
+ } else {
484
+ prompt.cancel(error.message)
485
+ }
486
+ return
487
+ }
488
+ if (error instanceof InstallerOutputError) {
489
+ writeAlternate(writeSync, error.stream, ['', `Installation failed: ${error.message}`, ''])
490
+ process.exitCode = 1
491
+ return
492
+ }
493
+ prompt.cancel(error instanceof Error ? error.message : String(error))
494
+ process.exitCode = 1
495
+ })
496
+ }
@@ -0,0 +1,149 @@
1
+ export type RuntimeMode = 'direct' | 'isolated'
2
+ export type InstallerCommand = 'install' | 'upgrade'
3
+
4
+ export interface CloudflareAccount {
5
+ id: string
6
+ name: string
7
+ }
8
+
9
+ export interface CommandResult {
10
+ interrupted?: boolean
11
+ outputFailure?: InstallerOutputError
12
+ status: number | null
13
+ stdout: string
14
+ stderr: string
15
+ }
16
+
17
+ export interface InstallerUi {
18
+ intro(message: string): void
19
+ step(message: string): void
20
+ selectRuntime(): Promise<RuntimeMode>
21
+ selectAccount(choices: Array<{ value: string; label: string; hint?: string }>): Promise<string>
22
+ workerName(initialValue: string, validate: (value: string) => string | undefined): Promise<string>
23
+ workerConflict(workerName: string): Promise<'rename' | 'update' | 'cancel'>
24
+ selectOwnerSecretMode(): Promise<'generate' | 'custom'>
25
+ ownerSecret(validate: (value: string) => string | undefined): Promise<string>
26
+ deepSeekKey(validate: (value: string) => string | undefined): Promise<string>
27
+ confirm(summary: {
28
+ mode: RuntimeMode
29
+ modeLabel: string
30
+ accountLabel: string
31
+ workerName: string
32
+ paid: boolean
33
+ }): Promise<boolean>
34
+ acceptTemporaryTerms(): Promise<boolean>
35
+ cleanupFailure(message: string): void
36
+ recovery(result: InstallRecovery): void
37
+ outputFailureRecovery(
38
+ result: InstallRecovery,
39
+ failedStream: 'stderr' | 'stdout',
40
+ ): boolean | Promise<boolean> | void
41
+ success(result: InstallResult): void
42
+ }
43
+
44
+ export interface InstallRecovery {
45
+ claimUrl?: string
46
+ ownerSecret: string
47
+ publicUrl?: string
48
+ workerName: string
49
+ }
50
+
51
+ export interface InstallResult {
52
+ publicUrl: string
53
+ versionId?: string
54
+ account?: CloudflareAccount
55
+ claimUrl?: string
56
+ deploymentId: string
57
+ mode: RuntimeMode
58
+ ownerSecret: string
59
+ temporary: boolean
60
+ workerName: string
61
+ }
62
+
63
+ export const DEPLOYMENT_ID_DEFINE: string
64
+ export const DEFAULT_WORKER_NAME: string
65
+ export const LOGIN_PROFILE: string
66
+ export const RUNTIME_MODES: Readonly<Record<RuntimeMode, Readonly<{
67
+ environment: string
68
+ expectedShell: string
69
+ label: string
70
+ }>>>
71
+ export class InstallCancelledError extends Error {}
72
+ export class InstallerOutputError extends Error {
73
+ readonly stream: 'stderr' | 'stdout'
74
+ }
75
+ export function accountChoices(mode: RuntimeMode, accounts: CloudflareAccount[], command?: InstallerCommand): Array<{
76
+ value: string
77
+ label: string
78
+ hint?: string
79
+ }>
80
+ export function parseWhoami(source: string): { accounts: CloudflareAccount[]; email?: string }
81
+ export function validateWorkerName(value: string): string | undefined
82
+ export function validateOwnerSecret(value: string): string | undefined
83
+ export function validateDeepSeekKey(value: string): string | undefined
84
+ export function generateOwnerSecret(): string
85
+ export function wranglerEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv
86
+ export function unauthenticatedEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv
87
+ export function wranglerDeployArgs(options: {
88
+ deploymentId: string
89
+ mode: RuntimeMode
90
+ workerName: string
91
+ secretsFile: string
92
+ configFile: string
93
+ profile?: string
94
+ temporary?: boolean
95
+ }): string[]
96
+ export function parseDeploymentOutput(source: string): { publicUrl: string; versionId?: string }
97
+ export function parseClaimUrl(source: string): string | undefined
98
+ export function parseWorkerExistence(result: CommandResult): boolean
99
+ export function truncateUtf8Tail(value: string, maxBytes: number): string
100
+ export function createOutputForwarder(
101
+ source: NodeJS.ReadableStream,
102
+ destination: NodeJS.WritableStream,
103
+ onFailure: (error: Error) => void,
104
+ ): {
105
+ write(chunk: string): void
106
+ settled(): Promise<void>
107
+ cancel(): void
108
+ dispose(): void
109
+ }
110
+ export function createTerminalSanitizer(): {
111
+ push(chunk: string): string
112
+ }
113
+ export function resolveWranglerClose(options: {
114
+ outputFailure?: InstallerOutputError
115
+ processError?: unknown
116
+ signal?: AbortSignal
117
+ status: number | null
118
+ stderr: string
119
+ stdout: string
120
+ }): CommandResult
121
+ export function installEdge(options: {
122
+ command?: InstallerCommand
123
+ ui: InstallerUi
124
+ runWrangler?: (args: string[], options?: {
125
+ environment?: NodeJS.ProcessEnv
126
+ interactive?: boolean
127
+ capture?: boolean
128
+ signal?: AbortSignal
129
+ }) => Promise<CommandResult>
130
+ deploymentId?: string
131
+ environment?: NodeJS.ProcessEnv
132
+ createTemporaryDirectory?: () => Promise<string>
133
+ removePath?: typeof import('node:fs/promises').rm
134
+ signal?: AbortSignal
135
+ }): Promise<InstallResult>
136
+ export function wranglerProcessInvocation(args: string[], options?: {
137
+ nodeExecutable?: string
138
+ wranglerCli?: string
139
+ }): { command: string; args: string[] }
140
+ export function executeWrangler(args: string[], options?: {
141
+ environment?: NodeJS.ProcessEnv
142
+ interactive?: boolean
143
+ capture?: boolean
144
+ forceKillAfterDelay?: number
145
+ invocation?: { command: string; args: string[] }
146
+ signal?: AbortSignal
147
+ stderrDestination?: NodeJS.WritableStream
148
+ stdoutDestination?: NodeJS.WritableStream
149
+ }): Promise<CommandResult>