@lamplitisles/dsh-nanocodex 0.1.0-beta.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 (276) hide show
  1. package/LICENSE +65 -0
  2. package/NOTICE +2 -0
  3. package/README.md +182 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/cordis.patch.yml +52 -0
  6. package/dist/index.d.ts +435 -0
  7. package/dist/index.js +3405 -0
  8. package/dist/standard-preset.d.ts +7 -0
  9. package/dist/standard-preset.js +25 -0
  10. package/engine-release.json +14 -0
  11. package/package.json +119 -0
  12. package/presets/standard/agent.cordis.yml +6 -0
  13. package/presets/standard/preset.yml +2 -0
  14. package/vendor/nanocodex/README.md +1344 -0
  15. package/vendor/nanocodex/actions/events.d.mts +10 -0
  16. package/vendor/nanocodex/actions/events.mjs +163 -0
  17. package/vendor/nanocodex/actions/index.d.mts +13 -0
  18. package/vendor/nanocodex/actions/index.mjs +35 -0
  19. package/vendor/nanocodex/actions/session.d.mts +66 -0
  20. package/vendor/nanocodex/actions/session.mjs +59 -0
  21. package/vendor/nanocodex/actions/turn.d.mts +59 -0
  22. package/vendor/nanocodex/actions/turn.mjs +37 -0
  23. package/vendor/nanocodex/actions/voice.d.mts +22 -0
  24. package/vendor/nanocodex/actions/voice.mjs +50 -0
  25. package/vendor/nanocodex/browser/Agent.d.mts +55 -0
  26. package/vendor/nanocodex/browser/Agent.mjs +11 -0
  27. package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
  28. package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
  29. package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
  30. package/vendor/nanocodex/browser/Transport.d.mts +74 -0
  31. package/vendor/nanocodex/browser/Transport.mjs +61 -0
  32. package/vendor/nanocodex/browser/Voice.d.mts +71 -0
  33. package/vendor/nanocodex/browser/Voice.mjs +291 -0
  34. package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
  35. package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
  36. package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
  37. package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
  38. package/vendor/nanocodex/browser/config.d.mts +36 -0
  39. package/vendor/nanocodex/browser/config.mjs +439 -0
  40. package/vendor/nanocodex/browser/engine.mjs +13 -0
  41. package/vendor/nanocodex/browser/harness.mjs +55 -0
  42. package/vendor/nanocodex/browser/host.d.mts +83 -0
  43. package/vendor/nanocodex/browser/host.mjs +555 -0
  44. package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
  45. package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
  46. package/vendor/nanocodex/browser/index.d.mts +92 -0
  47. package/vendor/nanocodex/browser/index.mjs +23 -0
  48. package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
  49. package/vendor/nanocodex/browser/workspace.d.mts +16 -0
  50. package/vendor/nanocodex/browser/workspace.mjs +107 -0
  51. package/vendor/nanocodex/cloud/Client.d.mts +83 -0
  52. package/vendor/nanocodex/cloud/Client.mjs +231 -0
  53. package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
  54. package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
  55. package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
  56. package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
  57. package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
  58. package/vendor/nanocodex/cloud/Errors.mjs +39 -0
  59. package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
  60. package/vendor/nanocodex/cloud/Principal.mjs +88 -0
  61. package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
  62. package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
  63. package/vendor/nanocodex/cloud/Transport.mjs +454 -0
  64. package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
  65. package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
  66. package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
  67. package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
  68. package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
  69. package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
  70. package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
  71. package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
  72. package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
  73. package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
  74. package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
  75. package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
  76. package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
  77. package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
  78. package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
  79. package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
  80. package/vendor/nanocodex/cloud/index.d.mts +30 -0
  81. package/vendor/nanocodex/cloud/index.mjs +9 -0
  82. package/vendor/nanocodex/cloud/internal.mjs +533 -0
  83. package/vendor/nanocodex/cloud/mercator.mjs +108 -0
  84. package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
  85. package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
  86. package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
  87. package/vendor/nanocodex/cloud/server/index.mjs +1 -0
  88. package/vendor/nanocodex/cloud/types.d.mts +211 -0
  89. package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
  90. package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
  91. package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
  92. package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
  93. package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
  94. package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
  95. package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
  96. package/vendor/nanocodex/cloudflare/index.mjs +6 -0
  97. package/vendor/nanocodex/host/Agent.d.mts +55 -0
  98. package/vendor/nanocodex/host/Agent.mjs +1 -0
  99. package/vendor/nanocodex/host/index.d.mts +82 -0
  100. package/vendor/nanocodex/host/index.mjs +10 -0
  101. package/vendor/nanocodex/index.d.mts +81 -0
  102. package/vendor/nanocodex/index.mjs +12 -0
  103. package/vendor/nanocodex/internal.mjs +1275 -0
  104. package/vendor/nanocodex/managed/Agent.d.mts +380 -0
  105. package/vendor/nanocodex/managed/Agent.mjs +1694 -0
  106. package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
  107. package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
  108. package/vendor/nanocodex/managed/README.md +241 -0
  109. package/vendor/nanocodex/managed/Voice.mjs +202 -0
  110. package/vendor/nanocodex/managed/index.d.mts +20 -0
  111. package/vendor/nanocodex/managed/index.mjs +2 -0
  112. package/vendor/nanocodex/managed/internal.mjs +119 -0
  113. package/vendor/nanocodex/node/Agent.d.mts +50 -0
  114. package/vendor/nanocodex/node/Agent.mjs +203 -0
  115. package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
  116. package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
  117. package/vendor/nanocodex/node/Transport.d.mts +44 -0
  118. package/vendor/nanocodex/node/Transport.mjs +46 -0
  119. package/vendor/nanocodex/node/host.mjs +731 -0
  120. package/vendor/nanocodex/node/index.d.mts +73 -0
  121. package/vendor/nanocodex/node/index.mjs +17 -0
  122. package/vendor/nanocodex/node/workspace.d.mts +10 -0
  123. package/vendor/nanocodex/node/workspace.mjs +198 -0
  124. package/vendor/nanocodex/package.json +200 -0
  125. package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
  126. package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
  127. package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
  128. package/vendor/nanocodex/pkg-node/package.json +1 -0
  129. package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
  130. package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
  131. package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
  132. package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
  133. package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
  134. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
  135. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
  136. package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
  137. package/vendor/nanocodex/pkg-web/package.json +1 -0
  138. package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
  139. package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
  140. package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
  141. package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
  142. package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
  143. package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
  144. package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
  145. package/vendor/nanocodex/runtime/durability.mjs +153 -0
  146. package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
  147. package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
  148. package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
  149. package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
  150. package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
  151. package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
  152. package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
  153. package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
  154. package/vendor/nanocodex/runtime/subagents.mjs +54 -0
  155. package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
  156. package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
  157. package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
  158. package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
  159. package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
  160. package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
  161. package/vendor/nanocodex/runtime/utf8.mjs +1 -0
  162. package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
  163. package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
  164. package/vendor/nanocodex/runtime/workspace.mjs +1 -0
  165. package/vendor/nanocodex/tools/Tools.d.mts +56 -0
  166. package/vendor/nanocodex/tools/Tools.mjs +136 -0
  167. package/vendor/nanocodex/tools/artifact.d.mts +1 -0
  168. package/vendor/nanocodex/tools/artifact.mjs +1 -0
  169. package/vendor/nanocodex/tools/attachment.mjs +1 -0
  170. package/vendor/nanocodex/tools/bash.d.mts +1 -0
  171. package/vendor/nanocodex/tools/bash.mjs +1 -0
  172. package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
  173. package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
  174. package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
  175. package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
  176. package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
  177. package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
  178. package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
  179. package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
  180. package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
  181. package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
  182. package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  183. package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
  184. package/vendor/nanocodex/tools/browser/index.mjs +174 -0
  185. package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
  186. package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
  187. package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
  188. package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
  189. package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
  190. package/vendor/nanocodex/tools/dataset.d.mts +1 -0
  191. package/vendor/nanocodex/tools/dataset.mjs +1 -0
  192. package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
  193. package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
  194. package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
  195. package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
  196. package/vendor/nanocodex/tools/index.d.mts +49 -0
  197. package/vendor/nanocodex/tools/index.mjs +19 -0
  198. package/vendor/nanocodex/tools/namedTool.mjs +1 -0
  199. package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
  200. package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
  201. package/vendor/nanocodex/tools/ssh.d.mts +1 -0
  202. package/vendor/nanocodex/tools/ssh.mjs +1 -0
  203. package/vendor/nanocodex/tools/standard.mjs +1 -0
  204. package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
  205. package/vendor/nanocodex/types.d.mts +663 -0
  206. package/vendor/nanocodex/wasm.d.mts +4 -0
  207. package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
  208. package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
  209. package/vendor/nanocodex/worker/index.d.mts +18 -0
  210. package/vendor/nanocodex/worker/index.mjs +5 -0
  211. package/vendor/nanocodex-tools/README.md +65 -0
  212. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
  213. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
  214. package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
  215. package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
  216. package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
  217. package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
  218. package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
  219. package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
  220. package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
  221. package/vendor/nanocodex-tools/dist/index.js +14 -0
  222. package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
  223. package/vendor/nanocodex-tools/dist/memory.js +411 -0
  224. package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
  225. package/vendor/nanocodex-tools/dist/namespace.js +338 -0
  226. package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
  227. package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
  228. package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
  229. package/vendor/nanocodex-tools/dist/runtime.js +37 -0
  230. package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
  231. package/vendor/nanocodex-tools/dist/session.js +286 -0
  232. package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
  233. package/vendor/nanocodex-tools/dist/shell.js +553 -0
  234. package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
  235. package/vendor/nanocodex-tools/dist/ssh.js +66 -0
  236. package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
  237. package/vendor/nanocodex-tools/dist/workspace.js +237 -0
  238. package/vendor/nanocodex-tools/package.json +122 -0
  239. package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
  240. package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
  241. package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
  242. package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
  243. package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
  244. package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
  245. package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
  246. package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
  247. package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
  248. package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
  249. package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
  250. package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
  251. package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
  252. package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
  253. package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
  254. package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
  255. package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
  256. package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
  257. package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
  258. package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
  259. package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
  260. package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
  261. package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
  262. package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
  263. package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
  264. package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
  265. package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
  266. package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
  267. package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
  268. package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
  269. package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
  270. package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
  271. package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
  272. package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
  273. package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
  274. package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
  275. package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
  276. package/vendor/nanocodex-tools/tools/x.mjs +91 -0
@@ -0,0 +1,503 @@
1
+ import { Buffer } from "buffer";
2
+ import {
3
+ CancellationTokenSource,
4
+ CommandRequestMessage,
5
+ SshAuthenticationType,
6
+ SshAlgorithms,
7
+ SshClientSession,
8
+ SshSessionConfiguration,
9
+ } from "@microsoft/dev-tunnels-ssh";
10
+ import { importKey, exportPrivateKey, exportPublicKey } from "@microsoft/dev-tunnels-ssh-keys";
11
+ import { defineCommand } from "just-bash/browser";
12
+
13
+ const DEFAULT_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
14
+ const DEFAULT_MAX_INPUT_BYTES = 64 * 1024;
15
+
16
+ /**
17
+ * Creates non-interactive SSH over a host-owned byte stream. IdentityRef is a
18
+ * separate host boundary: the reference is forwarded, but its key never is.
19
+ */
20
+ export function createSshCommand(options) {
21
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
22
+ throw new TypeError("SSH options must be an object");
23
+ }
24
+ if (options.transport !== "tcp" && options.transport !== "websocket") {
25
+ throw new TypeError("SSH transport must be tcp or websocket");
26
+ }
27
+ if (typeof options.openStream !== "function") {
28
+ throw new TypeError("SSH openStream is required");
29
+ }
30
+
31
+ const capabilities = Object.freeze({
32
+ identityFile: typeof options.readIdentity === "function",
33
+ identityReference: typeof options.executeWithIdentityReference === "function",
34
+ passwordReference: typeof options.resolvePassword === "function",
35
+ });
36
+ return defineCommand("ssh", async (args, context) => {
37
+ if (args[0] === "--help") return ok(`${usage(options.transport, capabilities)}\n`);
38
+ const parsed = parseArguments(args, options.transport, capabilities);
39
+ if ("error" in parsed) return fail(`${parsed.error}\n`, 2);
40
+ const stdin = String(context.stdin ?? "");
41
+ if (Buffer.byteLength(stdin, "utf8") > DEFAULT_MAX_INPUT_BYTES)
42
+ return fail("ssh: stdin exceeded the 64 KiB byte limit\n", 2);
43
+ try {
44
+ if (parsed.identityReference) {
45
+ return await options.executeWithIdentityReference({
46
+ identityReference: parsed.identityReference,
47
+ endpoint: parsed.endpoint,
48
+ username: parsed.username,
49
+ commandArgs: parsed.commandArgs,
50
+ ...(stdin ? { stdin } : {}),
51
+ }, context);
52
+ }
53
+ return await executeSsh(parsed, options, context);
54
+ } catch (error) {
55
+ return fail(`ssh: ${error instanceof Error ? error.message : String(error)}\n`, 255);
56
+ }
57
+ });
58
+ }
59
+
60
+ /** Adapts a WHATWG byte-stream socket, including Cloudflare TCP sockets, to SSH. */
61
+ export function createWebStreamSshStream(socket, signal) {
62
+ if (!socket?.readable || !socket?.writable || typeof socket.close !== "function") {
63
+ throw new TypeError("SSH byte-stream socket is required");
64
+ }
65
+ return new WebByteStreamSshStream(socket, signal);
66
+ }
67
+
68
+ class WebByteStreamSshStream {
69
+ #socket;
70
+ #reader;
71
+ #writer;
72
+ #listeners = new Set();
73
+ #buffer = Buffer.alloc(0);
74
+ #disposed = false;
75
+ #ended = false;
76
+ #finished = false;
77
+ #closeEvent = {};
78
+ #signal;
79
+ #abort;
80
+
81
+ constructor(socket, signal) {
82
+ this.#socket = socket;
83
+ this.#reader = socket.readable.getReader();
84
+ this.#writer = socket.writable.getWriter();
85
+ this.#signal = signal;
86
+ this.#abort = signal === undefined ? undefined : () => void this.#shutdown(signal.reason);
87
+ signal?.addEventListener("abort", this.#abort, { once: true });
88
+ void socket.closed?.then(
89
+ () => this.#finish(),
90
+ (error) => this.#finish(asError(error)),
91
+ );
92
+ }
93
+
94
+ get isDisposed() {
95
+ return this.#disposed;
96
+ }
97
+
98
+ closed(listener) {
99
+ if (this.#finished) {
100
+ queueMicrotask(() => listener(this.#closeEvent));
101
+ return Object.freeze({ dispose() {} });
102
+ }
103
+ this.#listeners.add(listener);
104
+ return Object.freeze({ dispose: () => this.#listeners.delete(listener) });
105
+ }
106
+
107
+ async read(count, cancellation) {
108
+ if (this.#disposed) throw new Error("SSH socket stream is disposed");
109
+ while (this.#buffer.byteLength === 0 && !this.#ended) {
110
+ const result = await cancellable(this.#reader.read(), cancellation);
111
+ if (result.done) this.#ended = true;
112
+ else this.#buffer = Buffer.from(result.value);
113
+ }
114
+ if (this.#buffer.byteLength === 0) return null;
115
+ const value = this.#buffer.subarray(0, count);
116
+ this.#buffer = this.#buffer.subarray(value.byteLength);
117
+ return value;
118
+ }
119
+
120
+ async write(data, cancellation) {
121
+ if (this.#disposed) throw new Error("SSH socket stream is disposed");
122
+ await cancellable(this.#writer.write(data), cancellation);
123
+ }
124
+
125
+ async close(error) {
126
+ await this.#shutdown(error);
127
+ }
128
+
129
+ dispose() {
130
+ void this.#shutdown();
131
+ }
132
+
133
+ async #shutdown(error) {
134
+ if (this.#disposed) return;
135
+ this.#disposed = true;
136
+ this.#signal?.removeEventListener("abort", this.#abort);
137
+ try {
138
+ await this.#socket.close();
139
+ } finally {
140
+ this.#reader.releaseLock();
141
+ this.#writer.releaseLock();
142
+ this.#finish(error === undefined ? undefined : asError(error));
143
+ }
144
+ }
145
+
146
+ #finish(error) {
147
+ if (this.#finished) return;
148
+ this.#ended = true;
149
+ this.#finished = true;
150
+ this.#closeEvent = error === undefined ? {} : { error };
151
+ for (const listener of this.#listeners) listener(this.#closeEvent);
152
+ this.#listeners.clear();
153
+ }
154
+ }
155
+
156
+ async function executeSsh(args, options, context) {
157
+ const cancellation = new CancellationTokenSource();
158
+ const abort = () => cancellation.cancel();
159
+ context.signal?.addEventListener("abort", abort, { once: true });
160
+ let privateKey;
161
+ let stream;
162
+ const session = new SshClientSession(new SshSessionConfiguration());
163
+ const authentication = session.onAuthenticating((event) => {
164
+ if (event.authenticationType !== SshAuthenticationType.serverPublicKey || !event.publicKey) {
165
+ event.authenticationPromise = Promise.resolve(null);
166
+ return;
167
+ }
168
+ event.authenticationPromise = authenticateHost(
169
+ event.publicKey,
170
+ args.hostKeySha256,
171
+ args.acceptUnknownHost,
172
+ );
173
+ });
174
+ try {
175
+ if (args.identityFile) {
176
+ const keySource = await options.readIdentity(args.identityFile, context);
177
+ privateKey = await importKey(keySource);
178
+ normalizeRsaPublicKeyBlob(privateKey);
179
+ }
180
+ stream = await options.openStream(args.endpoint, context.signal);
181
+ await session.connect(stream, cancellation.token);
182
+ const credentials = privateKey
183
+ ? { username: args.username, publicKeys: [privateKey] }
184
+ : {
185
+ username: args.username,
186
+ password: await options.resolvePassword(args.passwordReference),
187
+ };
188
+ const serverAuthenticated = await session.authenticateServer(cancellation.token);
189
+ if (!serverAuthenticated) throw new Error("server host-key authentication failed");
190
+
191
+ // Some OpenSSH servers enforce the protocol ordering strictly and ignore a
192
+ // user-authentication request sent before accepting the ssh-userauth service.
193
+ await session.requestService("ssh-userauth", cancellation.token);
194
+ if (!session.activateService("ssh-userauth")) {
195
+ throw new Error("server did not activate SSH user authentication");
196
+ }
197
+ const authenticated = await session.authenticateClient(credentials, cancellation.token);
198
+ if (!authenticated) throw new Error("authentication failed");
199
+
200
+ const channel = await session.openChannel("session", cancellation.token);
201
+ let stdout = "";
202
+ let stderr = "";
203
+ let outputBytes = 0;
204
+ let outputFailure;
205
+ const maxOutputBytes = positiveInteger(
206
+ options.maxOutputBytes,
207
+ DEFAULT_MAX_OUTPUT_BYTES,
208
+ "SSH maxOutputBytes",
209
+ );
210
+ channel.onDataReceived((data) => {
211
+ outputBytes += data.length;
212
+ if (outputBytes > maxOutputBytes) {
213
+ outputFailure ??= new Error("remote output exceeded the SSH byte limit");
214
+ void channel.close();
215
+ return;
216
+ }
217
+ stdout += data.toString("utf8");
218
+ channel.adjustWindow(data.length);
219
+ });
220
+ channel.onExtendedDataReceived((event) => {
221
+ outputBytes += event.data.length;
222
+ if (outputBytes > maxOutputBytes) {
223
+ outputFailure ??= new Error("remote output exceeded the SSH byte limit");
224
+ void channel.close();
225
+ return;
226
+ }
227
+ stderr += event.data.toString("utf8");
228
+ channel.adjustWindow(event.data.length);
229
+ });
230
+ let channelClosed = false;
231
+ const closed = new Promise((resolve) => channel.onClosed(result => { channelClosed = true; resolve(result); }));
232
+ const request = new CommandRequestMessage();
233
+ request.command = args.command;
234
+ request.wantReply = true;
235
+ if (!await channel.request(request, cancellation.token)) {
236
+ throw new Error("remote server rejected the command");
237
+ }
238
+ try {
239
+ const stdin = Buffer.from(String(context.stdin ?? ""), "utf8");
240
+ if (!channelClosed && stdin.length) await channel.send(stdin, cancellation.token);
241
+ // The SSH library emits CHANNEL_EOF for an empty send. Input must close
242
+ // independently of output so commands such as `cat` can finish.
243
+ if (!channelClosed) await channel.send(Buffer.alloc(0), cancellation.token);
244
+ } catch (error) {
245
+ if (!channelClosed) throw error;
246
+ }
247
+ const result = await closed;
248
+ if (outputFailure) throw outputFailure;
249
+ if (result.error) throw result.error;
250
+ if (result.exitSignal) stderr += `ssh: remote command exited on ${result.exitSignal}\n`;
251
+ return { stdout, stderr, exitCode: result.exitStatus ?? (result.exitSignal ? 128 : 0) };
252
+ } finally {
253
+ context.signal?.removeEventListener("abort", abort);
254
+ authentication.dispose();
255
+ privateKey?.dispose();
256
+ session.dispose();
257
+ stream?.dispose();
258
+ cancellation.dispose();
259
+ }
260
+ }
261
+
262
+ function normalizeRsaPublicKeyBlob(key) {
263
+ if (key?.keyAlgorithmName !== "ssh-rsa" || typeof key.getPublicKeyBytes !== "function") return;
264
+ const getPublicKeyBytes = key.getPublicKeyBytes.bind(key);
265
+ // RFC 8332 changes the signature algorithm name, but the public-key blob
266
+ // remains the RFC 4253 `ssh-rsa` encoding. The dependency otherwise writes
267
+ // `rsa-sha2-*` into both fields, which strict OpenSSH servers reject.
268
+ key.getPublicKeyBytes = async () => {
269
+ try {
270
+ return await getPublicKeyBytes("ssh-rsa");
271
+ } catch (error) {
272
+ // Node 24 cannot export the public KeyObject produced by the dependency's
273
+ // private-key importer as PKCS#1, although its public JWK remains valid.
274
+ // Encode that public material directly instead of rejecting a valid key.
275
+ const jwk = key.publicKey?.export?.({ format: "jwk" });
276
+ if (jwk?.kty !== "RSA" || typeof jwk.e !== "string" || typeof jwk.n !== "string") {
277
+ throw error;
278
+ }
279
+ return Buffer.concat([
280
+ sshLengthPrefixed(Buffer.from("ssh-rsa")),
281
+ sshLengthPrefixed(sshMpint(jwk.e)),
282
+ sshLengthPrefixed(sshMpint(jwk.n)),
283
+ ]);
284
+ }
285
+ };
286
+ }
287
+
288
+ function sshMpint(base64url) {
289
+ const value = Buffer.from(base64url, "base64url");
290
+ let offset = 0;
291
+ while (offset + 1 < value.length && value[offset] === 0) offset += 1;
292
+ const normalized = value.subarray(offset);
293
+ return normalized[0] >= 0x80 ? Buffer.concat([Buffer.from([0]), normalized]) : normalized;
294
+ }
295
+
296
+ function sshLengthPrefixed(value) {
297
+ const length = Buffer.allocUnsafe(4);
298
+ length.writeUInt32BE(value.length);
299
+ return Buffer.concat([length, value]);
300
+ }
301
+
302
+ async function authenticateHost(publicKey, expected, acceptUnknown) {
303
+ if (acceptUnknown) return {};
304
+ const bytes = await publicKey.getPublicKeyBytes();
305
+ if (!bytes || !expected) return null;
306
+ const keyBytes = new Uint8Array(bytes.byteLength);
307
+ keyBytes.set(bytes);
308
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", keyBytes));
309
+ const actual = Buffer.from(digest).toString("base64").replace(/=+$/u, "");
310
+ const normalized = expected.replace(/^SHA256:/u, "").replace(/=+$/u, "");
311
+ return actual === normalized ? {} : null;
312
+ }
313
+
314
+ function parseArguments(args, transport, capabilities) {
315
+ if (!args.length) return { error: usage(transport, capabilities) };
316
+ let username = "";
317
+ let identityFile = "";
318
+ let identityReference = "";
319
+ let passwordReference = "";
320
+ let hostKeySha256;
321
+ let acceptUnknownHost = false;
322
+ let port = 22;
323
+ let index = 0;
324
+ for (; index < args.length; index += 1) {
325
+ const arg = args[index];
326
+ if (arg === "--") {
327
+ index += 1;
328
+ break;
329
+ }
330
+ if (!arg.startsWith("-")) break;
331
+ if (arg === "-l" || arg === "-i" || arg === "-o" || arg === "-p") {
332
+ const value = args[++index];
333
+ if (!value) return { error: `ssh: ${arg} requires an argument` };
334
+ if (arg === "-l") username = value;
335
+ else if (arg === "-i") {
336
+ if (!capabilities.identityFile) return { error: "ssh: identity files are unavailable" };
337
+ identityFile = value;
338
+ } else if (arg === "-p") {
339
+ if (transport !== "tcp") return { error: "ssh: -p is unavailable for WebSocket endpoints" };
340
+ port = Number(value);
341
+ if (!Number.isInteger(port) || port < 1 || port > 65_535) {
342
+ return { error: "ssh: port must be an integer from 1 through 65535" };
343
+ }
344
+ } else if (value === "StrictHostKeyChecking=no") acceptUnknownHost = true;
345
+ else if (value.startsWith("HostKeySHA256=")) {
346
+ hostKeySha256 = value.slice("HostKeySHA256=".length);
347
+ } else if (value.startsWith("PasswordRef=")) {
348
+ if (!capabilities.passwordReference) return { error: "ssh: password references are unavailable" };
349
+ passwordReference = value.slice("PasswordRef=".length);
350
+ if (!passwordReference) return { error: "ssh: PasswordRef cannot be empty" };
351
+ } else if (value.startsWith("IdentityRef=")) {
352
+ if (!capabilities.identityReference) return { error: "ssh: brokered identity references are unavailable" };
353
+ identityReference = value.slice("IdentityRef=".length);
354
+ if (!identityReference) return { error: "ssh: IdentityRef cannot be empty" };
355
+ } else return { error: `ssh: unsupported option '${value}'` };
356
+ continue;
357
+ }
358
+ return { error: `ssh: unsupported option '${arg}'` };
359
+ }
360
+
361
+ const target = args[index++];
362
+ if (!target) return { error: usage(transport, capabilities) };
363
+ let endpoint;
364
+ if (transport === "websocket") {
365
+ try { endpoint = new URL(target); } catch {
366
+ return { error: "ssh: endpoint must be a ws:// or wss:// URL" };
367
+ }
368
+ if (!["ws:", "wss:"].includes(endpoint.protocol) || endpoint.username || endpoint.password) {
369
+ return { error: "ssh: endpoint must be a credential-free ws:// or wss:// URL" };
370
+ }
371
+ } else {
372
+ const parsedTarget = parseTcpTarget(target, username, port);
373
+ if ("error" in parsedTarget) return parsedTarget;
374
+ endpoint = parsedTarget.endpoint;
375
+ username = parsedTarget.username;
376
+ }
377
+
378
+ if (!username) return { error: "ssh: provide USER@HOST or -l USER" };
379
+ const authentication = [identityFile, identityReference, passwordReference].filter(Boolean);
380
+ if (authentication.length === 0) {
381
+ return { error: "ssh: provide -i PRIVATE_KEY, -o IdentityRef=REFERENCE, or -o PasswordRef=REFERENCE" };
382
+ }
383
+ if (authentication.length > 1) return { error: "ssh: authentication options are mutually exclusive" };
384
+ if (identityReference && (hostKeySha256 || acceptUnknownHost)) {
385
+ return { error: "ssh: IdentityRef uses broker-owned host-key verification" };
386
+ }
387
+ if (!identityReference && !hostKeySha256 && !acceptUnknownHost) {
388
+ return { error: "ssh: provide -o HostKeySHA256=SHA256:... or explicitly -o StrictHostKeyChecking=no" };
389
+ }
390
+ if (args[index] === "--") index += 1;
391
+ const commandArgs = args.slice(index);
392
+ if (!commandArgs.length) {
393
+ return { error: "ssh: an explicit remote command is required (interactive PTYs are unavailable)" };
394
+ }
395
+ return {
396
+ endpoint,
397
+ username,
398
+ identityFile,
399
+ identityReference,
400
+ passwordReference,
401
+ commandArgs,
402
+ command: commandArgs.map(shellQuote).join(" "),
403
+ hostKeySha256,
404
+ acceptUnknownHost,
405
+ };
406
+ }
407
+
408
+ function parseTcpTarget(target, configuredUsername, port) {
409
+ if (/\s/u.test(target) || target.includes("://")) {
410
+ return { error: "ssh: target must be [USER@]HOST" };
411
+ }
412
+ const separator = target.lastIndexOf("@");
413
+ const targetUsername = separator === -1 ? "" : target.slice(0, separator);
414
+ let hostname = separator === -1 ? target : target.slice(separator + 1);
415
+ if (targetUsername && configuredUsername && targetUsername !== configuredUsername) {
416
+ return { error: "ssh: target username conflicts with -l" };
417
+ }
418
+ if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
419
+ if (!hostname || hostname.includes("@") || (!hostname.includes(":") && !/^[A-Za-z0-9.-]+$/u.test(hostname))) {
420
+ return { error: "ssh: target must be [USER@]HOST" };
421
+ }
422
+ return {
423
+ endpoint: Object.freeze({ hostname, port }),
424
+ username: configuredUsername || targetUsername,
425
+ };
426
+ }
427
+
428
+ function shellQuote(value) {
429
+ return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
430
+ }
431
+
432
+ function usage(transport, capabilities) {
433
+ const endpoint = transport === "tcp" ? "[USER@]HOST" : "wss://SSH-GATEWAY";
434
+ const port = transport === "tcp" ? " [-p PORT]" : "";
435
+ const authentication = [
436
+ ...(capabilities.identityFile ? ["-i PRIVATE_KEY"] : []),
437
+ ...(capabilities.identityReference ? ["-o IdentityRef=REFERENCE"] : []),
438
+ ...(capabilities.passwordReference ? ["-o PasswordRef=REFERENCE"] : []),
439
+ ].join(" | ");
440
+ const verification = capabilities.identityReference
441
+ ? "Brokered IdentityRef records own host verification; other authentication requires HostKeySHA256 or an explicit opt-out."
442
+ : "Provide HostKeySHA256 or explicitly disable strict host checking.";
443
+ const transportNotice = transport === "websocket"
444
+ ? "browsers cannot open TCP port 22; the endpoint must carry raw SSH over WebSocket."
445
+ : "Cloudflare Workers can open direct outbound TCP; brokered identities execute inside private egress.";
446
+ return [
447
+ `usage: ssh${port} [-l USER] (${authentication}) ${endpoint} -- COMMAND [ARG...]`,
448
+ "SSH is non-interactive: PTYs are unavailable; stdin is bounded to 64 KiB.",
449
+ verification,
450
+ transportNotice,
451
+ ].join("\n");
452
+ }
453
+
454
+ function fail(stderr, exitCode) {
455
+ return { stdout: "", stderr, exitCode };
456
+ }
457
+
458
+ function ok(stdout) {
459
+ return { stdout, stderr: "", exitCode: 0 };
460
+ }
461
+
462
+ function cancellable(promise, cancellation) {
463
+ if (cancellation?.isCancellationRequested) {
464
+ return Promise.reject(new Error("SSH command cancelled"));
465
+ }
466
+ if (cancellation?.onCancellationRequested === undefined) return promise;
467
+ return new Promise((resolve, reject) => {
468
+ let subscription;
469
+ subscription = cancellation.onCancellationRequested(() => {
470
+ subscription?.dispose();
471
+ reject(new Error("SSH command cancelled"));
472
+ });
473
+ promise.then(
474
+ (value) => { subscription.dispose(); resolve(value); },
475
+ (error) => { subscription.dispose(); reject(error); },
476
+ );
477
+ });
478
+ }
479
+
480
+ function asError(value) {
481
+ return value instanceof Error ? value : new Error(String(value));
482
+ }
483
+
484
+ function positiveInteger(value, fallback, name) {
485
+ if (value === undefined) return fallback;
486
+ if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive integer`);
487
+ return value;
488
+ }
489
+
490
+ /** Generate an identity inside a credential-owning host; never expose its private key in tool output. */
491
+ export async function createSshKeyPair() {
492
+ const key = await SshAlgorithms.publicKey.ecdsaSha2Nistp256.generateKeyPair();
493
+ try {
494
+ return { privateKey: await exportPrivateKey(key, null, 5), publicKey: (await exportPublicKey(key)).trim() };
495
+ } finally { key.dispose(); }
496
+ }
497
+
498
+ /** Derive the authorized_keys line from a PEM identity without exporting private material. */
499
+ export async function sshPublicKey(privateKey) {
500
+ const key = await importKey(privateKey);
501
+ try { return (await exportPublicKey(key)).trim(); }
502
+ finally { key.dispose(); }
503
+ }