gitx.do 0.0.3 → 0.1.1

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 (231) hide show
  1. package/README.md +319 -92
  2. package/dist/cli/commands/add.d.ts +176 -0
  3. package/dist/cli/commands/add.d.ts.map +1 -0
  4. package/dist/cli/commands/add.js +979 -0
  5. package/dist/cli/commands/add.js.map +1 -0
  6. package/dist/cli/commands/blame.d.ts +1 -1
  7. package/dist/cli/commands/blame.d.ts.map +1 -1
  8. package/dist/cli/commands/blame.js +1 -1
  9. package/dist/cli/commands/blame.js.map +1 -1
  10. package/dist/cli/commands/branch.d.ts +1 -1
  11. package/dist/cli/commands/branch.d.ts.map +1 -1
  12. package/dist/cli/commands/branch.js +2 -2
  13. package/dist/cli/commands/branch.js.map +1 -1
  14. package/dist/cli/commands/checkout.d.ts +73 -0
  15. package/dist/cli/commands/checkout.d.ts.map +1 -0
  16. package/dist/cli/commands/checkout.js +725 -0
  17. package/dist/cli/commands/checkout.js.map +1 -0
  18. package/dist/cli/commands/commit.d.ts.map +1 -1
  19. package/dist/cli/commands/commit.js +22 -2
  20. package/dist/cli/commands/commit.js.map +1 -1
  21. package/dist/cli/commands/diff.d.ts +4 -4
  22. package/dist/cli/commands/diff.d.ts.map +1 -1
  23. package/dist/cli/commands/diff.js +9 -8
  24. package/dist/cli/commands/diff.js.map +1 -1
  25. package/dist/cli/commands/log.d.ts +1 -1
  26. package/dist/cli/commands/log.d.ts.map +1 -1
  27. package/dist/cli/commands/log.js +1 -1
  28. package/dist/cli/commands/log.js.map +1 -1
  29. package/dist/cli/commands/merge.d.ts +106 -0
  30. package/dist/cli/commands/merge.d.ts.map +1 -0
  31. package/dist/cli/commands/merge.js +852 -0
  32. package/dist/cli/commands/merge.js.map +1 -0
  33. package/dist/cli/commands/review.d.ts +1 -1
  34. package/dist/cli/commands/review.d.ts.map +1 -1
  35. package/dist/cli/commands/review.js +26 -1
  36. package/dist/cli/commands/review.js.map +1 -1
  37. package/dist/cli/commands/stash.d.ts +157 -0
  38. package/dist/cli/commands/stash.d.ts.map +1 -0
  39. package/dist/cli/commands/stash.js +655 -0
  40. package/dist/cli/commands/stash.js.map +1 -0
  41. package/dist/cli/commands/status.d.ts.map +1 -1
  42. package/dist/cli/commands/status.js +1 -2
  43. package/dist/cli/commands/status.js.map +1 -1
  44. package/dist/cli/commands/web.d.ts.map +1 -1
  45. package/dist/cli/commands/web.js +3 -2
  46. package/dist/cli/commands/web.js.map +1 -1
  47. package/dist/cli/fs-adapter.d.ts.map +1 -1
  48. package/dist/cli/fs-adapter.js +3 -5
  49. package/dist/cli/fs-adapter.js.map +1 -1
  50. package/dist/cli/fsx-cli-adapter.d.ts +359 -0
  51. package/dist/cli/fsx-cli-adapter.d.ts.map +1 -0
  52. package/dist/cli/fsx-cli-adapter.js +619 -0
  53. package/dist/cli/fsx-cli-adapter.js.map +1 -0
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +68 -12
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/ui/components/DiffView.d.ts +7 -2
  58. package/dist/cli/ui/components/DiffView.d.ts.map +1 -1
  59. package/dist/cli/ui/components/DiffView.js.map +1 -1
  60. package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -2
  61. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -1
  62. package/dist/cli/ui/components/ErrorDisplay.js.map +1 -1
  63. package/dist/cli/ui/components/FuzzySearch.d.ts +8 -2
  64. package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -1
  65. package/dist/cli/ui/components/FuzzySearch.js.map +1 -1
  66. package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -2
  67. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -1
  68. package/dist/cli/ui/components/LoadingSpinner.js.map +1 -1
  69. package/dist/cli/ui/components/NavigationList.d.ts +7 -2
  70. package/dist/cli/ui/components/NavigationList.d.ts.map +1 -1
  71. package/dist/cli/ui/components/NavigationList.js.map +1 -1
  72. package/dist/cli/ui/components/ScrollableContent.d.ts +7 -2
  73. package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -1
  74. package/dist/cli/ui/components/ScrollableContent.js.map +1 -1
  75. package/dist/cli/ui/terminal-ui.d.ts +42 -9
  76. package/dist/cli/ui/terminal-ui.d.ts.map +1 -1
  77. package/dist/cli/ui/terminal-ui.js.map +1 -1
  78. package/dist/do/BashModule.d.ts +871 -0
  79. package/dist/do/BashModule.d.ts.map +1 -0
  80. package/dist/do/BashModule.js +1143 -0
  81. package/dist/do/BashModule.js.map +1 -0
  82. package/dist/do/FsModule.d.ts +612 -0
  83. package/dist/do/FsModule.d.ts.map +1 -0
  84. package/dist/do/FsModule.js +1120 -0
  85. package/dist/do/FsModule.js.map +1 -0
  86. package/dist/do/GitModule.d.ts +635 -0
  87. package/dist/do/GitModule.d.ts.map +1 -0
  88. package/dist/do/GitModule.js +784 -0
  89. package/dist/do/GitModule.js.map +1 -0
  90. package/dist/do/GitRepoDO.d.ts +281 -0
  91. package/dist/do/GitRepoDO.d.ts.map +1 -0
  92. package/dist/do/GitRepoDO.js +479 -0
  93. package/dist/do/GitRepoDO.js.map +1 -0
  94. package/dist/do/bash-ast.d.ts +246 -0
  95. package/dist/do/bash-ast.d.ts.map +1 -0
  96. package/dist/do/bash-ast.js +888 -0
  97. package/dist/do/bash-ast.js.map +1 -0
  98. package/dist/do/container-executor.d.ts +491 -0
  99. package/dist/do/container-executor.d.ts.map +1 -0
  100. package/dist/do/container-executor.js +731 -0
  101. package/dist/do/container-executor.js.map +1 -0
  102. package/dist/do/index.d.ts +53 -0
  103. package/dist/do/index.d.ts.map +1 -0
  104. package/dist/do/index.js +91 -0
  105. package/dist/do/index.js.map +1 -0
  106. package/dist/do/tiered-storage.d.ts +403 -0
  107. package/dist/do/tiered-storage.d.ts.map +1 -0
  108. package/dist/do/tiered-storage.js +689 -0
  109. package/dist/do/tiered-storage.js.map +1 -0
  110. package/dist/do/withBash.d.ts +231 -0
  111. package/dist/do/withBash.d.ts.map +1 -0
  112. package/dist/do/withBash.js +244 -0
  113. package/dist/do/withBash.js.map +1 -0
  114. package/dist/do/withFs.d.ts +237 -0
  115. package/dist/do/withFs.d.ts.map +1 -0
  116. package/dist/do/withFs.js +387 -0
  117. package/dist/do/withFs.js.map +1 -0
  118. package/dist/do/withGit.d.ts +180 -0
  119. package/dist/do/withGit.d.ts.map +1 -0
  120. package/dist/do/withGit.js +271 -0
  121. package/dist/do/withGit.js.map +1 -0
  122. package/dist/durable-object/object-store.d.ts +157 -15
  123. package/dist/durable-object/object-store.d.ts.map +1 -1
  124. package/dist/durable-object/object-store.js +435 -47
  125. package/dist/durable-object/object-store.js.map +1 -1
  126. package/dist/durable-object/schema.d.ts +12 -1
  127. package/dist/durable-object/schema.d.ts.map +1 -1
  128. package/dist/durable-object/schema.js +87 -2
  129. package/dist/durable-object/schema.js.map +1 -1
  130. package/dist/index.d.ts +84 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +34 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +22 -0
  135. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +1 -0
  136. package/dist/mcp/sandbox/miniflare-evaluator.js +140 -0
  137. package/dist/mcp/sandbox/miniflare-evaluator.js.map +1 -0
  138. package/dist/mcp/sandbox/object-store-proxy.d.ts +32 -0
  139. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +1 -0
  140. package/dist/mcp/sandbox/object-store-proxy.js +30 -0
  141. package/dist/mcp/sandbox/object-store-proxy.js.map +1 -0
  142. package/dist/mcp/sandbox/template.d.ts +17 -0
  143. package/dist/mcp/sandbox/template.d.ts.map +1 -0
  144. package/dist/mcp/sandbox/template.js +71 -0
  145. package/dist/mcp/sandbox/template.js.map +1 -0
  146. package/dist/mcp/sandbox.d.ts.map +1 -1
  147. package/dist/mcp/sandbox.js +16 -4
  148. package/dist/mcp/sandbox.js.map +1 -1
  149. package/dist/mcp/tools/do.d.ts +32 -0
  150. package/dist/mcp/tools/do.d.ts.map +1 -0
  151. package/dist/mcp/tools/do.js +117 -0
  152. package/dist/mcp/tools/do.js.map +1 -0
  153. package/dist/mcp/tools.d.ts.map +1 -1
  154. package/dist/mcp/tools.js +1258 -22
  155. package/dist/mcp/tools.js.map +1 -1
  156. package/dist/pack/delta.d.ts +8 -0
  157. package/dist/pack/delta.d.ts.map +1 -1
  158. package/dist/pack/delta.js +241 -30
  159. package/dist/pack/delta.js.map +1 -1
  160. package/dist/refs/branch.d.ts +38 -25
  161. package/dist/refs/branch.d.ts.map +1 -1
  162. package/dist/refs/branch.js +421 -94
  163. package/dist/refs/branch.js.map +1 -1
  164. package/dist/refs/storage.d.ts +77 -5
  165. package/dist/refs/storage.d.ts.map +1 -1
  166. package/dist/refs/storage.js +193 -43
  167. package/dist/refs/storage.js.map +1 -1
  168. package/dist/refs/tag.d.ts +44 -24
  169. package/dist/refs/tag.d.ts.map +1 -1
  170. package/dist/refs/tag.js +411 -70
  171. package/dist/refs/tag.js.map +1 -1
  172. package/dist/storage/backend.d.ts +425 -0
  173. package/dist/storage/backend.d.ts.map +1 -0
  174. package/dist/storage/backend.js +41 -0
  175. package/dist/storage/backend.js.map +1 -0
  176. package/dist/storage/fsx-adapter.d.ts +204 -0
  177. package/dist/storage/fsx-adapter.d.ts.map +1 -0
  178. package/dist/storage/fsx-adapter.js +518 -0
  179. package/dist/storage/fsx-adapter.js.map +1 -0
  180. package/dist/storage/r2-pack.d.ts.map +1 -1
  181. package/dist/storage/r2-pack.js +4 -1
  182. package/dist/storage/r2-pack.js.map +1 -1
  183. package/dist/tiered/cdc-pipeline.js +3 -3
  184. package/dist/tiered/cdc-pipeline.js.map +1 -1
  185. package/dist/tiered/migration.d.ts.map +1 -1
  186. package/dist/tiered/migration.js +4 -1
  187. package/dist/tiered/migration.js.map +1 -1
  188. package/dist/types/capability.d.ts +1385 -0
  189. package/dist/types/capability.d.ts.map +1 -0
  190. package/dist/types/capability.js +36 -0
  191. package/dist/types/capability.js.map +1 -0
  192. package/dist/types/index.d.ts +13 -0
  193. package/dist/types/index.d.ts.map +1 -0
  194. package/dist/types/index.js +18 -0
  195. package/dist/types/index.js.map +1 -0
  196. package/dist/types/interfaces.d.ts +673 -0
  197. package/dist/types/interfaces.d.ts.map +1 -0
  198. package/dist/types/interfaces.js +26 -0
  199. package/dist/types/interfaces.js.map +1 -0
  200. package/dist/types/objects.d.ts +182 -0
  201. package/dist/types/objects.d.ts.map +1 -1
  202. package/dist/types/objects.js +249 -4
  203. package/dist/types/objects.js.map +1 -1
  204. package/dist/types/storage.d.ts +114 -0
  205. package/dist/types/storage.d.ts.map +1 -1
  206. package/dist/types/storage.js +160 -1
  207. package/dist/types/storage.js.map +1 -1
  208. package/dist/types/worker-loader.d.ts +60 -0
  209. package/dist/types/worker-loader.d.ts.map +1 -0
  210. package/dist/types/worker-loader.js +62 -0
  211. package/dist/types/worker-loader.js.map +1 -0
  212. package/dist/utils/hash.d.ts +126 -80
  213. package/dist/utils/hash.d.ts.map +1 -1
  214. package/dist/utils/hash.js +191 -100
  215. package/dist/utils/hash.js.map +1 -1
  216. package/dist/utils/sha1.d.ts +206 -0
  217. package/dist/utils/sha1.d.ts.map +1 -1
  218. package/dist/utils/sha1.js +405 -0
  219. package/dist/utils/sha1.js.map +1 -1
  220. package/dist/wire/path-security.d.ts +157 -0
  221. package/dist/wire/path-security.d.ts.map +1 -0
  222. package/dist/wire/path-security.js +307 -0
  223. package/dist/wire/path-security.js.map +1 -0
  224. package/dist/wire/receive-pack.d.ts +7 -0
  225. package/dist/wire/receive-pack.d.ts.map +1 -1
  226. package/dist/wire/receive-pack.js +29 -1
  227. package/dist/wire/receive-pack.js.map +1 -1
  228. package/dist/wire/upload-pack.d.ts.map +1 -1
  229. package/dist/wire/upload-pack.js +4 -1
  230. package/dist/wire/upload-pack.js.map +1 -1
  231. package/package.json +10 -1
@@ -0,0 +1,731 @@
1
+ /**
2
+ * @fileoverview Cloudflare Container Executor Integration
3
+ *
4
+ * This module provides executor implementations for running bash commands
5
+ * in Cloudflare Containers. It supports multiple execution modes:
6
+ *
7
+ * - HTTP-based execution with the Sandbox SDK (exec/execStream)
8
+ * - WebSocket streaming for interactive sessions
9
+ * - Session isolation for multi-tenant environments
10
+ *
11
+ * @module do/container-executor
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { CloudflareContainerExecutor } from 'gitx.do/do'
16
+ *
17
+ * const executor = new CloudflareContainerExecutor({
18
+ * sandbox: env.Sandbox,
19
+ * sessionId: 'user-123',
20
+ * })
21
+ *
22
+ * const result = await executor.execute('ls -la')
23
+ * console.log(result.stdout)
24
+ * ```
25
+ */
26
+ // ============================================================================
27
+ // CloudflareContainerExecutor Class
28
+ // ============================================================================
29
+ /**
30
+ * CloudflareContainerExecutor - Execute bash commands in Cloudflare Containers.
31
+ *
32
+ * This executor implements the BashExecutor interface and supports multiple
33
+ * execution backends:
34
+ *
35
+ * 1. **Sandbox SDK**: Uses `exec()` and `execStream()` from @cloudflare/sandbox
36
+ * 2. **HTTP Exec**: Sends commands to an HTTP endpoint in the container
37
+ * 3. **WebSocket Streaming**: Connects to container via WebSocket for streaming
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * // Using Sandbox SDK
42
+ * const executor = new CloudflareContainerExecutor({
43
+ * sandbox: env.Sandbox,
44
+ * sessionId: 'session-123',
45
+ * })
46
+ *
47
+ * // Execute a command
48
+ * const result = await executor.execute('npm install')
49
+ *
50
+ * // Spawn for streaming
51
+ * const handle = await executor.spawn('npm', ['run', 'dev'], {
52
+ * onStdout: (chunk) => console.log(chunk),
53
+ * onStderr: (chunk) => console.error(chunk),
54
+ * })
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Using HTTP exec endpoint
60
+ * const executor = new CloudflareContainerExecutor({
61
+ * httpExecEndpoint: 'https://container.example.com/exec',
62
+ * sessionId: 'session-456',
63
+ * })
64
+ *
65
+ * const result = await executor.execute('ls -la')
66
+ * ```
67
+ */
68
+ export class CloudflareContainerExecutor {
69
+ sandbox;
70
+ container;
71
+ sessionId;
72
+ defaultCwd;
73
+ defaultTimeout;
74
+ defaultEnv;
75
+ wsEndpoint;
76
+ httpExecEndpoint;
77
+ fetchFn;
78
+ /**
79
+ * Session state for isolation.
80
+ */
81
+ session;
82
+ /**
83
+ * Process ID counter for spawn handles.
84
+ */
85
+ pidCounter = 1000;
86
+ /**
87
+ * Create a new CloudflareContainerExecutor.
88
+ *
89
+ * @param options - Configuration options
90
+ */
91
+ constructor(options = {}) {
92
+ this.sandbox = options.sandbox;
93
+ this.container = options.container;
94
+ this.sessionId = options.sessionId ?? crypto.randomUUID();
95
+ this.defaultCwd = options.cwd ?? '/';
96
+ this.defaultTimeout = options.timeout ?? 30000;
97
+ this.defaultEnv = options.env ?? {};
98
+ this.wsEndpoint = options.wsEndpoint;
99
+ this.httpExecEndpoint = options.httpExecEndpoint;
100
+ this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
101
+ // Initialize session state
102
+ this.session = {
103
+ id: this.sessionId,
104
+ cwd: this.defaultCwd,
105
+ env: { ...this.defaultEnv },
106
+ processes: new Map(),
107
+ lastActivity: Date.now(),
108
+ };
109
+ }
110
+ // ===========================================================================
111
+ // BashExecutor Interface
112
+ // ===========================================================================
113
+ /**
114
+ * Execute a command and return the result.
115
+ *
116
+ * @param command - The command to execute
117
+ * @param options - Execution options
118
+ * @returns Promise resolving to the execution result
119
+ */
120
+ async execute(command, options) {
121
+ this.session.lastActivity = Date.now();
122
+ const cwd = options?.cwd ?? this.session.cwd;
123
+ const timeout = options?.timeout ?? this.defaultTimeout;
124
+ const env = { ...this.session.env, ...options?.env };
125
+ // Try sandbox SDK first
126
+ if (this.sandbox) {
127
+ return this.executeViaSandbox(command, { cwd, timeout, env, stdin: options?.stdin });
128
+ }
129
+ // Try HTTP exec endpoint
130
+ if (this.httpExecEndpoint) {
131
+ return this.executeViaHttp(command, { cwd, timeout, env, stdin: options?.stdin });
132
+ }
133
+ // Try container fetch with custom exec endpoint
134
+ if (this.container) {
135
+ return this.executeViaContainer(command, { cwd, timeout, env, stdin: options?.stdin });
136
+ }
137
+ // No execution backend available
138
+ return {
139
+ command,
140
+ stdout: '',
141
+ stderr: 'No execution backend configured (sandbox, httpExecEndpoint, or container required)',
142
+ exitCode: 1,
143
+ };
144
+ }
145
+ /**
146
+ * Spawn a command for streaming execution.
147
+ *
148
+ * @param command - The command to spawn
149
+ * @param args - Command arguments
150
+ * @param options - Spawn options
151
+ * @returns Promise resolving to a spawn handle
152
+ */
153
+ async spawn(command, args, options) {
154
+ this.session.lastActivity = Date.now();
155
+ const fullCommand = args?.length ? `${command} ${args.join(' ')}` : command;
156
+ const cwd = options?.cwd ?? this.session.cwd;
157
+ const timeout = options?.timeout ?? this.defaultTimeout;
158
+ const env = { ...this.session.env, ...options?.env };
159
+ // Try sandbox SDK streaming
160
+ if (this.sandbox?.execStream) {
161
+ return this.spawnViaSandbox(fullCommand, { cwd, timeout, env }, options);
162
+ }
163
+ // Try WebSocket streaming
164
+ if (this.wsEndpoint) {
165
+ return this.spawnViaWebSocket(fullCommand, { cwd, timeout, env }, options);
166
+ }
167
+ // Try sandbox startProcess
168
+ if (this.sandbox?.startProcess) {
169
+ return this.spawnViaProcess(fullCommand, { cwd, timeout, env }, options);
170
+ }
171
+ // Fallback: execute and simulate streaming
172
+ return this.spawnViaExec(fullCommand, { cwd, timeout, env }, options);
173
+ }
174
+ // ===========================================================================
175
+ // Sandbox SDK Execution
176
+ // ===========================================================================
177
+ /**
178
+ * Execute a command using the Sandbox SDK.
179
+ */
180
+ async executeViaSandbox(command, options) {
181
+ try {
182
+ const result = await this.sandbox.exec(command, options);
183
+ return {
184
+ command,
185
+ stdout: result.stdout,
186
+ stderr: result.stderr,
187
+ exitCode: result.exitCode,
188
+ };
189
+ }
190
+ catch (error) {
191
+ return {
192
+ command,
193
+ stdout: '',
194
+ stderr: error instanceof Error ? error.message : String(error),
195
+ exitCode: 1,
196
+ };
197
+ }
198
+ }
199
+ /**
200
+ * Spawn a command using Sandbox SDK streaming.
201
+ */
202
+ async spawnViaSandbox(command, execOptions, spawnOptions) {
203
+ const pid = this.pidCounter++;
204
+ let abortController = null;
205
+ const streamResult = await this.sandbox.execStream(command, execOptions);
206
+ abortController = { abort: streamResult.abort };
207
+ // Process the stream
208
+ const reader = streamResult.stream.getReader();
209
+ const processStream = async () => {
210
+ let stdout = '';
211
+ let stderr = '';
212
+ let exitCode = 0;
213
+ try {
214
+ while (true) {
215
+ const { done, value } = await reader.read();
216
+ if (done)
217
+ break;
218
+ if (value.type === 'stdout') {
219
+ const data = String(value.data);
220
+ stdout += data;
221
+ spawnOptions?.onStdout?.(data);
222
+ }
223
+ else if (value.type === 'stderr') {
224
+ const data = String(value.data);
225
+ stderr += data;
226
+ spawnOptions?.onStderr?.(data);
227
+ }
228
+ else if (value.type === 'exit') {
229
+ exitCode = Number(value.data);
230
+ }
231
+ }
232
+ }
233
+ catch (error) {
234
+ stderr += error instanceof Error ? error.message : String(error);
235
+ exitCode = 1;
236
+ }
237
+ spawnOptions?.onExit?.(exitCode);
238
+ return { command, stdout, stderr, exitCode };
239
+ };
240
+ const donePromise = processStream();
241
+ const handle = {
242
+ pid,
243
+ done: donePromise,
244
+ kill: (_signal) => {
245
+ abortController?.abort();
246
+ },
247
+ write: (_data) => {
248
+ // Sandbox stream doesn't support stdin writing
249
+ },
250
+ closeStdin: () => {
251
+ // No-op for sandbox stream
252
+ },
253
+ };
254
+ return handle;
255
+ }
256
+ /**
257
+ * Spawn a command using Sandbox SDK startProcess.
258
+ */
259
+ async spawnViaProcess(command, execOptions, spawnOptions) {
260
+ const processHandle = await this.sandbox.startProcess(command, execOptions);
261
+ const pid = processHandle.pid;
262
+ // Track the process
263
+ this.session.processes.set(pid, processHandle);
264
+ // Set up exit handling
265
+ const donePromise = processHandle.exited.then(result => {
266
+ this.session.processes.delete(pid);
267
+ spawnOptions?.onExit?.(result.exitCode);
268
+ return {
269
+ command,
270
+ stdout: result.stdout,
271
+ stderr: result.stderr,
272
+ exitCode: result.exitCode,
273
+ };
274
+ });
275
+ const handle = {
276
+ pid,
277
+ done: donePromise,
278
+ kill: (signal) => {
279
+ processHandle.kill(signal);
280
+ },
281
+ write: (data) => {
282
+ processHandle.write(data);
283
+ },
284
+ closeStdin: () => {
285
+ processHandle.closeStdin();
286
+ },
287
+ };
288
+ return handle;
289
+ }
290
+ // ===========================================================================
291
+ // HTTP Exec Execution
292
+ // ===========================================================================
293
+ /**
294
+ * Execute a command via HTTP exec endpoint.
295
+ */
296
+ async executeViaHttp(command, options) {
297
+ try {
298
+ const response = await this.fetchFn(this.httpExecEndpoint, {
299
+ method: 'POST',
300
+ headers: {
301
+ 'Content-Type': 'application/json',
302
+ 'X-Session-Id': this.sessionId,
303
+ },
304
+ body: JSON.stringify({
305
+ command,
306
+ cwd: options.cwd,
307
+ env: options.env,
308
+ timeout: options.timeout,
309
+ stdin: options.stdin,
310
+ }),
311
+ });
312
+ if (!response.ok) {
313
+ const errorText = await response.text();
314
+ return {
315
+ command,
316
+ stdout: '',
317
+ stderr: `HTTP exec failed: ${response.status} ${errorText}`,
318
+ exitCode: 1,
319
+ };
320
+ }
321
+ const result = await response.json();
322
+ return {
323
+ command,
324
+ stdout: result.stdout ?? '',
325
+ stderr: result.stderr ?? '',
326
+ exitCode: result.exitCode ?? (result.success ? 0 : 1),
327
+ };
328
+ }
329
+ catch (error) {
330
+ return {
331
+ command,
332
+ stdout: '',
333
+ stderr: error instanceof Error ? error.message : String(error),
334
+ exitCode: 1,
335
+ };
336
+ }
337
+ }
338
+ // ===========================================================================
339
+ // Container Fetch Execution
340
+ // ===========================================================================
341
+ /**
342
+ * Execute a command via container fetch.
343
+ */
344
+ async executeViaContainer(command, options) {
345
+ try {
346
+ // Build the request to the container's exec endpoint
347
+ const container = this.container;
348
+ const request = new Request('http://container/exec', {
349
+ method: 'POST',
350
+ headers: {
351
+ 'Content-Type': 'application/json',
352
+ 'X-Session-Id': this.sessionId,
353
+ },
354
+ body: JSON.stringify({
355
+ command,
356
+ cwd: options.cwd,
357
+ env: options.env,
358
+ timeout: options.timeout,
359
+ stdin: options.stdin,
360
+ }),
361
+ });
362
+ const response = await container.fetch(request);
363
+ if (!response.ok) {
364
+ const errorText = await response.text();
365
+ return {
366
+ command,
367
+ stdout: '',
368
+ stderr: `Container exec failed: ${response.status} ${errorText}`,
369
+ exitCode: 1,
370
+ };
371
+ }
372
+ const result = await response.json();
373
+ return {
374
+ command,
375
+ stdout: result.stdout ?? '',
376
+ stderr: result.stderr ?? '',
377
+ exitCode: result.exitCode ?? (result.success ? 0 : 1),
378
+ };
379
+ }
380
+ catch (error) {
381
+ return {
382
+ command,
383
+ stdout: '',
384
+ stderr: error instanceof Error ? error.message : String(error),
385
+ exitCode: 1,
386
+ };
387
+ }
388
+ }
389
+ // ===========================================================================
390
+ // WebSocket Streaming
391
+ // ===========================================================================
392
+ /**
393
+ * Spawn a command via WebSocket connection.
394
+ */
395
+ async spawnViaWebSocket(command, execOptions, spawnOptions) {
396
+ const pid = this.pidCounter++;
397
+ let ws = null;
398
+ const { promise: donePromise, resolve: resolveDone } = withResolvers();
399
+ let stdout = '';
400
+ let stderr = '';
401
+ let exitCode = 0;
402
+ try {
403
+ // Build WebSocket URL with session and command info
404
+ const wsUrl = new URL(this.wsEndpoint);
405
+ wsUrl.searchParams.set('session', this.sessionId);
406
+ wsUrl.searchParams.set('command', command);
407
+ if (execOptions.cwd)
408
+ wsUrl.searchParams.set('cwd', execOptions.cwd);
409
+ // Create WebSocket connection
410
+ ws = new WebSocket(wsUrl.toString());
411
+ ws.addEventListener('open', () => {
412
+ // Send initial configuration
413
+ ws.send(JSON.stringify({
414
+ type: 'init',
415
+ env: execOptions.env,
416
+ timeout: execOptions.timeout,
417
+ }));
418
+ });
419
+ ws.addEventListener('message', (event) => {
420
+ try {
421
+ const message = JSON.parse(String(event.data));
422
+ switch (message.type) {
423
+ case 'stdout': {
424
+ const outData = String(message.data ?? '');
425
+ stdout += outData;
426
+ spawnOptions?.onStdout?.(outData);
427
+ break;
428
+ }
429
+ case 'stderr': {
430
+ const errData = String(message.data ?? '');
431
+ stderr += errData;
432
+ spawnOptions?.onStderr?.(errData);
433
+ break;
434
+ }
435
+ case 'exit':
436
+ exitCode = Number(message.data ?? 0);
437
+ ws?.close();
438
+ break;
439
+ case 'error':
440
+ stderr += String(message.data ?? '');
441
+ exitCode = 1;
442
+ ws?.close();
443
+ break;
444
+ }
445
+ }
446
+ catch {
447
+ // Handle non-JSON messages as stdout
448
+ const data = String(event.data);
449
+ stdout += data;
450
+ spawnOptions?.onStdout?.(data);
451
+ }
452
+ });
453
+ ws.addEventListener('close', () => {
454
+ spawnOptions?.onExit?.(exitCode);
455
+ resolveDone({ command, stdout, stderr, exitCode });
456
+ });
457
+ ws.addEventListener('error', (event) => {
458
+ stderr += `WebSocket error: ${event}`;
459
+ exitCode = 1;
460
+ });
461
+ }
462
+ catch (error) {
463
+ stderr = error instanceof Error ? error.message : String(error);
464
+ exitCode = 1;
465
+ resolveDone({ command, stdout, stderr, exitCode });
466
+ }
467
+ const handle = {
468
+ pid,
469
+ done: donePromise,
470
+ kill: (signal) => {
471
+ if (ws && ws.readyState === WebSocket.OPEN) {
472
+ ws.send(JSON.stringify({ type: 'signal', signal: signal ?? 'SIGTERM' }));
473
+ ws.close();
474
+ }
475
+ },
476
+ write: (data) => {
477
+ if (ws && ws.readyState === WebSocket.OPEN) {
478
+ ws.send(JSON.stringify({ type: 'stdin', data }));
479
+ }
480
+ },
481
+ closeStdin: () => {
482
+ if (ws && ws.readyState === WebSocket.OPEN) {
483
+ ws.send(JSON.stringify({ type: 'stdin_close' }));
484
+ }
485
+ },
486
+ };
487
+ return handle;
488
+ }
489
+ /**
490
+ * Spawn a command by executing and simulating streaming output.
491
+ */
492
+ async spawnViaExec(command, execOptions, spawnOptions) {
493
+ const pid = this.pidCounter++;
494
+ const executeAndStream = async () => {
495
+ const result = await this.execute(command, {
496
+ cwd: execOptions.cwd,
497
+ timeout: execOptions.timeout,
498
+ env: execOptions.env,
499
+ stdin: execOptions.stdin,
500
+ });
501
+ // Simulate streaming by emitting all output at once
502
+ if (result.stdout) {
503
+ spawnOptions?.onStdout?.(result.stdout);
504
+ }
505
+ if (result.stderr) {
506
+ spawnOptions?.onStderr?.(result.stderr);
507
+ }
508
+ spawnOptions?.onExit?.(result.exitCode);
509
+ return result;
510
+ };
511
+ const handle = {
512
+ pid,
513
+ done: executeAndStream(),
514
+ kill: () => {
515
+ // Cannot kill a non-streaming execution
516
+ },
517
+ write: () => {
518
+ // Cannot write to a non-streaming execution
519
+ },
520
+ closeStdin: () => {
521
+ // No-op
522
+ },
523
+ };
524
+ return handle;
525
+ }
526
+ // ===========================================================================
527
+ // Session Management
528
+ // ===========================================================================
529
+ /**
530
+ * Get the current session ID.
531
+ *
532
+ * @returns The session ID
533
+ */
534
+ getSessionId() {
535
+ return this.sessionId;
536
+ }
537
+ /**
538
+ * Get the current session state.
539
+ *
540
+ * @returns Copy of the session state
541
+ */
542
+ getSessionState() {
543
+ return {
544
+ id: this.session.id,
545
+ cwd: this.session.cwd,
546
+ env: { ...this.session.env },
547
+ lastActivity: this.session.lastActivity,
548
+ processCount: this.session.processes.size,
549
+ };
550
+ }
551
+ /**
552
+ * Update the session working directory.
553
+ *
554
+ * @param cwd - New working directory
555
+ */
556
+ setCwd(cwd) {
557
+ this.session.cwd = cwd;
558
+ }
559
+ /**
560
+ * Update session environment variables.
561
+ *
562
+ * @param env - Environment variables to set
563
+ */
564
+ setEnv(env) {
565
+ Object.assign(this.session.env, env);
566
+ }
567
+ /**
568
+ * Unset session environment variables.
569
+ *
570
+ * @param keys - Environment variable keys to unset
571
+ */
572
+ unsetEnv(keys) {
573
+ for (const key of keys) {
574
+ delete this.session.env[key];
575
+ }
576
+ }
577
+ /**
578
+ * Get the number of active processes.
579
+ *
580
+ * @returns Number of active processes
581
+ */
582
+ getActiveProcessCount() {
583
+ return this.session.processes.size;
584
+ }
585
+ /**
586
+ * Kill all active processes.
587
+ *
588
+ * @param signal - Signal to send (default: SIGTERM)
589
+ */
590
+ async killAll(signal = 'SIGTERM') {
591
+ const kills = Array.from(this.session.processes.values()).map(handle => handle.kill(signal));
592
+ await Promise.all(kills);
593
+ this.session.processes.clear();
594
+ }
595
+ // ===========================================================================
596
+ // File Operations (when sandbox supports it)
597
+ // ===========================================================================
598
+ /**
599
+ * Write a file to the container filesystem.
600
+ *
601
+ * @param path - File path
602
+ * @param content - File content
603
+ */
604
+ async writeFile(path, content) {
605
+ if (this.sandbox?.writeFile) {
606
+ await this.sandbox.writeFile(path, content);
607
+ }
608
+ else if (this.httpExecEndpoint) {
609
+ // Use HTTP endpoint for file writing
610
+ const bodyContent = typeof content === 'string' ? content : new Uint8Array(content).buffer;
611
+ await this.fetchFn(this.httpExecEndpoint.replace('/exec', '/file'), {
612
+ method: 'PUT',
613
+ headers: {
614
+ 'Content-Type': 'application/octet-stream',
615
+ 'X-Session-Id': this.sessionId,
616
+ 'X-File-Path': path,
617
+ },
618
+ body: bodyContent,
619
+ });
620
+ }
621
+ else {
622
+ throw new Error('File operations not supported by this executor');
623
+ }
624
+ }
625
+ /**
626
+ * Read a file from the container filesystem.
627
+ *
628
+ * @param path - File path
629
+ * @returns File content
630
+ */
631
+ async readFile(path) {
632
+ if (this.sandbox?.readFile) {
633
+ return await this.sandbox.readFile(path);
634
+ }
635
+ else if (this.httpExecEndpoint) {
636
+ // Use HTTP endpoint for file reading
637
+ const response = await this.fetchFn(this.httpExecEndpoint.replace('/exec', '/file'), {
638
+ method: 'GET',
639
+ headers: {
640
+ 'X-Session-Id': this.sessionId,
641
+ 'X-File-Path': path,
642
+ },
643
+ });
644
+ if (!response.ok) {
645
+ throw new Error(`File read failed: ${response.status}`);
646
+ }
647
+ return await response.text();
648
+ }
649
+ else {
650
+ throw new Error('File operations not supported by this executor');
651
+ }
652
+ }
653
+ }
654
+ // ============================================================================
655
+ // Factory Functions
656
+ // ============================================================================
657
+ /**
658
+ * Create a CloudflareContainerExecutor instance.
659
+ *
660
+ * @param options - Configuration options
661
+ * @returns A new CloudflareContainerExecutor instance
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * const executor = createContainerExecutor({
666
+ * sandbox: env.Sandbox,
667
+ * sessionId: 'user-123',
668
+ * })
669
+ * ```
670
+ */
671
+ export function createContainerExecutor(options = {}) {
672
+ return new CloudflareContainerExecutor(options);
673
+ }
674
+ /**
675
+ * Create an executor from a Sandbox binding.
676
+ *
677
+ * @param sandbox - Cloudflare Sandbox binding
678
+ * @param sessionId - Optional session ID for isolation
679
+ * @returns A new CloudflareContainerExecutor instance
680
+ */
681
+ export function createSandboxExecutor(sandbox, sessionId) {
682
+ return new CloudflareContainerExecutor({ sandbox, sessionId });
683
+ }
684
+ /**
685
+ * Create an executor from an HTTP exec endpoint.
686
+ *
687
+ * @param endpoint - HTTP exec endpoint URL
688
+ * @param sessionId - Optional session ID for isolation
689
+ * @returns A new CloudflareContainerExecutor instance
690
+ */
691
+ export function createHttpExecutor(endpoint, sessionId) {
692
+ return new CloudflareContainerExecutor({ httpExecEndpoint: endpoint, sessionId });
693
+ }
694
+ /**
695
+ * Create an executor from a WebSocket endpoint.
696
+ *
697
+ * @param endpoint - WebSocket endpoint URL
698
+ * @param sessionId - Optional session ID for isolation
699
+ * @returns A new CloudflareContainerExecutor instance
700
+ */
701
+ export function createWebSocketExecutor(endpoint, sessionId) {
702
+ return new CloudflareContainerExecutor({ wsEndpoint: endpoint, sessionId });
703
+ }
704
+ // ============================================================================
705
+ // Type Guards
706
+ // ============================================================================
707
+ /**
708
+ * Check if a value is a CloudflareContainerExecutor instance.
709
+ *
710
+ * @param value - Value to check
711
+ * @returns True if value is a CloudflareContainerExecutor
712
+ */
713
+ export function isContainerExecutor(value) {
714
+ return value instanceof CloudflareContainerExecutor;
715
+ }
716
+ // ============================================================================
717
+ // Helper Functions
718
+ // ============================================================================
719
+ /**
720
+ * Promise.withResolvers polyfill for older environments.
721
+ */
722
+ function withResolvers() {
723
+ let resolve;
724
+ let reject;
725
+ const promise = new Promise((res, rej) => {
726
+ resolve = res;
727
+ reject = rej;
728
+ });
729
+ return { promise, resolve: resolve, reject: reject };
730
+ }
731
+ //# sourceMappingURL=container-executor.js.map