computesdk 1.10.2 → 1.11.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.
package/dist/index.mjs CHANGED
@@ -485,6 +485,17 @@ var WebSocketManager = class {
485
485
  data: { terminal_id: terminalId, cols, rows }
486
486
  });
487
487
  }
488
+ /**
489
+ * Start a pending streaming command
490
+ * Used in two-phase streaming flow: HTTP request creates pending command,
491
+ * then this signal triggers execution after client has subscribed.
492
+ */
493
+ startCommand(cmdId) {
494
+ this.sendRaw({
495
+ type: "command:start",
496
+ data: { cmd_id: cmdId }
497
+ });
498
+ }
488
499
  on(event, handler) {
489
500
  if (!this.eventHandlers.has(event)) {
490
501
  this.eventHandlers.set(event, /* @__PURE__ */ new Set());
@@ -675,7 +686,7 @@ var TerminalCommand = class {
675
686
  */
676
687
  async run(command, options) {
677
688
  const response = await this.runHandler(command, options?.background);
678
- const cmd2 = new Command({
689
+ const cmd = new Command({
679
690
  cmdId: response.data.cmd_id || "",
680
691
  terminalId: this.terminalId,
681
692
  command: response.data.command,
@@ -686,9 +697,9 @@ var TerminalCommand = class {
686
697
  durationMs: response.data.duration_ms,
687
698
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
688
699
  });
689
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
690
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
691
- return cmd2;
700
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
701
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
702
+ return cmd;
692
703
  }
693
704
  /**
694
705
  * List all commands executed in this terminal
@@ -697,7 +708,7 @@ var TerminalCommand = class {
697
708
  async list() {
698
709
  const response = await this.listHandler();
699
710
  return response.data.commands.map((item) => {
700
- const cmd2 = new Command({
711
+ const cmd = new Command({
701
712
  cmdId: item.cmd_id,
702
713
  terminalId: this.terminalId,
703
714
  command: item.command,
@@ -711,9 +722,9 @@ var TerminalCommand = class {
711
722
  startedAt: item.started_at,
712
723
  finishedAt: item.finished_at
713
724
  });
714
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
715
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
716
- return cmd2;
725
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
726
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
727
+ return cmd;
717
728
  });
718
729
  }
719
730
  /**
@@ -723,7 +734,7 @@ var TerminalCommand = class {
723
734
  */
724
735
  async retrieve(cmdId) {
725
736
  const response = await this.retrieveHandler(cmdId);
726
- const cmd2 = new Command({
737
+ const cmd = new Command({
727
738
  cmdId: response.data.cmd_id,
728
739
  terminalId: this.terminalId,
729
740
  command: response.data.command,
@@ -735,9 +746,9 @@ var TerminalCommand = class {
735
746
  startedAt: response.data.started_at,
736
747
  finishedAt: response.data.finished_at
737
748
  });
738
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
739
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
740
- return cmd2;
749
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
750
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
751
+ return cmd;
741
752
  }
742
753
  };
743
754
 
@@ -958,6 +969,7 @@ var TerminalInstance = class {
958
969
  throw new Error("Destroy handler not set");
959
970
  }
960
971
  await this._destroyHandler();
972
+ this._status = "stopped";
961
973
  this.cleanup();
962
974
  }
963
975
  /**
@@ -1268,7 +1280,7 @@ var SignalService = class {
1268
1280
  };
1269
1281
 
1270
1282
  // src/client/index.ts
1271
- import { cmd, escapeArgs, mkdir, test } from "@computesdk/cmd";
1283
+ import { escapeArgs, mkdir, test } from "@computesdk/cmd";
1272
1284
 
1273
1285
  // src/client/resources/terminal.ts
1274
1286
  var Terminal = class {
@@ -1757,6 +1769,8 @@ var Run = class {
1757
1769
  * @param options - Execution options
1758
1770
  * @param options.shell - Shell to use (optional)
1759
1771
  * @param options.background - Run in background (optional)
1772
+ * @param options.cwd - Working directory for the command (optional)
1773
+ * @param options.env - Environment variables (optional)
1760
1774
  * @returns Command execution result with stdout, stderr, exit code, and duration
1761
1775
  */
1762
1776
  async command(command, options) {
@@ -1861,7 +1875,8 @@ var Sandbox = class {
1861
1875
  headers: config.headers || {},
1862
1876
  timeout: config.timeout || 3e4,
1863
1877
  protocol: config.protocol || "binary",
1864
- metadata: config.metadata
1878
+ metadata: config.metadata,
1879
+ destroyHandler: config.destroyHandler
1865
1880
  };
1866
1881
  this.WebSocketImpl = config.WebSocket || globalThis.WebSocket;
1867
1882
  if (!this.WebSocketImpl) {
@@ -1882,7 +1897,7 @@ var Sandbox = class {
1882
1897
  await this.writeFile(path, content);
1883
1898
  },
1884
1899
  mkdir: async (path) => {
1885
- await this.runCommand(mkdir(path));
1900
+ await this.runCommand(escapeArgs(mkdir(path)));
1886
1901
  },
1887
1902
  readdir: async (path) => {
1888
1903
  const response = await this.listFiles(path);
@@ -1894,7 +1909,7 @@ var Sandbox = class {
1894
1909
  }));
1895
1910
  },
1896
1911
  exists: async (path) => {
1897
- const result = await this.runCommand(test.exists(path));
1912
+ const result = await this.runCommand(escapeArgs(test.exists(path)));
1898
1913
  return result.exitCode === 0;
1899
1914
  },
1900
1915
  remove: async (path) => {
@@ -1919,7 +1934,13 @@ var Sandbox = class {
1919
1934
  };
1920
1935
  },
1921
1936
  command: async (command, options) => {
1922
- const result = await this.runCommandRequest({ command, shell: options?.shell, background: options?.background });
1937
+ const result = await this.runCommandRequest({
1938
+ command,
1939
+ shell: options?.shell,
1940
+ background: options?.background,
1941
+ cwd: options?.cwd,
1942
+ env: options?.env
1943
+ });
1923
1944
  return {
1924
1945
  stdout: result.data.stdout,
1925
1946
  stderr: result.data.stderr,
@@ -2233,12 +2254,14 @@ API request failed (${response.status}): ${error}`
2233
2254
  });
2234
2255
  }
2235
2256
  /**
2236
- * Execute a shell command (POST /run/command)
2257
+ * Execute a command and get the result
2258
+ * Lower-level method that returns the raw API response
2237
2259
  *
2238
- * @param options - Command options
2239
- * @param options.command - The command to execute
2260
+ * @param options.command - Command to execute
2240
2261
  * @param options.shell - Shell to use (optional)
2241
2262
  * @param options.background - Run in background (optional)
2263
+ * @param options.cwd - Working directory for the command (optional)
2264
+ * @param options.env - Environment variables (optional)
2242
2265
  * @returns Command execution result
2243
2266
  *
2244
2267
  * @example
@@ -2276,18 +2299,24 @@ API request failed (${response.status}): ${error}`
2276
2299
  * Get file metadata (without content)
2277
2300
  */
2278
2301
  async getFile(path) {
2279
- return this.request(`/files/${encodeURIComponent(path)}`);
2302
+ return this.request(`/files/${this.encodeFilePath(path)}`);
2303
+ }
2304
+ /**
2305
+ * Encode a file path for use in URLs
2306
+ * Strips leading slash and encodes each segment separately to preserve path structure
2307
+ */
2308
+ encodeFilePath(path) {
2309
+ const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2310
+ const segments = pathWithoutLeadingSlash.split("/");
2311
+ return segments.map((s) => encodeURIComponent(s)).join("/");
2280
2312
  }
2281
2313
  /**
2282
2314
  * Read file content
2283
2315
  */
2284
2316
  async readFile(path) {
2285
2317
  const params = new URLSearchParams({ content: "true" });
2286
- const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2287
- const segments = pathWithoutLeadingSlash.split("/");
2288
- const encodedPath = segments.map((s) => encodeURIComponent(s)).join("/");
2289
2318
  const response = await this.request(
2290
- `/files/${encodedPath}?${params}`
2319
+ `/files/${this.encodeFilePath(path)}?${params}`
2291
2320
  );
2292
2321
  return response.data.content || "";
2293
2322
  }
@@ -2304,7 +2333,7 @@ API request failed (${response.status}): ${error}`
2304
2333
  * Delete a file or directory
2305
2334
  */
2306
2335
  async deleteFile(path) {
2307
- return this.request(`/files/${encodeURIComponent(path)}`, {
2336
+ return this.request(`/files/${this.encodeFilePath(path)}`, {
2308
2337
  method: "DELETE"
2309
2338
  });
2310
2339
  }
@@ -2323,7 +2352,7 @@ API request failed (${response.status}): ${error}`
2323
2352
  headers["Authorization"] = `Bearer ${this._token}`;
2324
2353
  }
2325
2354
  const response = await fetch(
2326
- `${this.config.sandboxUrl}/files/${encodeURIComponent(path)}`,
2355
+ `${this.config.sandboxUrl}/files/${this.encodeFilePath(path)}`,
2327
2356
  {
2328
2357
  method: "HEAD",
2329
2358
  headers,
@@ -2835,29 +2864,104 @@ API request failed (${response.status}): ${error}`
2835
2864
  return this.run.code(code, language ? { language } : void 0);
2836
2865
  }
2837
2866
  /**
2838
- * Execute shell command in the sandbox (convenience method)
2867
+ * Execute shell command in the sandbox
2839
2868
  *
2840
- * Delegates to sandbox.run.command() - prefer using that directly for new code.
2869
+ * Sends clean command string to server - no preprocessing or shell wrapping.
2870
+ * The server handles shell invocation, working directory, and backgrounding.
2841
2871
  *
2842
- * @param command - The command to execute (string or array form)
2843
- * @param argsOrOptions - Arguments array or options object
2844
- * @param maybeOptions - Options when using (command, args, options) form
2872
+ * @param command - The command to execute (raw string, e.g., "npm install")
2873
+ * @param options - Execution options
2874
+ * @param options.background - Run in background (server uses goroutines)
2875
+ * @param options.cwd - Working directory (server uses cmd.Dir)
2876
+ * @param options.env - Environment variables (server uses cmd.Env)
2877
+ * @param options.onStdout - Callback for streaming stdout data
2878
+ * @param options.onStderr - Callback for streaming stderr data
2845
2879
  * @returns Command execution result
2880
+ *
2881
+ * @example
2882
+ * ```typescript
2883
+ * // Simple command
2884
+ * await sandbox.runCommand('ls -la')
2885
+ *
2886
+ * // With working directory
2887
+ * await sandbox.runCommand('npm install', { cwd: '/app' })
2888
+ *
2889
+ * // Background with env vars
2890
+ * await sandbox.runCommand('node server.js', {
2891
+ * background: true,
2892
+ * env: { PORT: '3000' }
2893
+ * })
2894
+ *
2895
+ * // With streaming output
2896
+ * await sandbox.runCommand('npm install', {
2897
+ * onStdout: (data) => console.log(data),
2898
+ * onStderr: (data) => console.error(data),
2899
+ * })
2900
+ * ```
2846
2901
  */
2847
- async runCommand(commandOrArray, argsOrOptions, maybeOptions) {
2848
- let commandParts;
2849
- let options;
2850
- if (Array.isArray(commandOrArray)) {
2851
- commandParts = commandOrArray;
2852
- options = argsOrOptions;
2853
- } else {
2854
- const args = Array.isArray(argsOrOptions) ? argsOrOptions : [];
2855
- commandParts = [commandOrArray, ...args];
2856
- options = Array.isArray(argsOrOptions) ? maybeOptions : argsOrOptions;
2902
+ async runCommand(command, options) {
2903
+ const hasStreamingCallbacks = options?.onStdout || options?.onStderr;
2904
+ if (!hasStreamingCallbacks) {
2905
+ return this.run.command(command, options);
2857
2906
  }
2858
- const finalCommand = cmd(commandParts, options);
2859
- const fullCommand = escapeArgs(finalCommand);
2860
- return this.run.command(fullCommand, { background: options?.background });
2907
+ const ws = await this.ensureWebSocket();
2908
+ const result = await this.runCommandRequest({
2909
+ command,
2910
+ stream: true,
2911
+ cwd: options?.cwd,
2912
+ env: options?.env
2913
+ });
2914
+ const { cmd_id, channel } = result.data;
2915
+ if (!cmd_id || !channel) {
2916
+ throw new Error("Server did not return streaming channel info");
2917
+ }
2918
+ ws.subscribe(channel);
2919
+ let stdout = "";
2920
+ let stderr = "";
2921
+ let exitCode = 0;
2922
+ let resolvePromise = null;
2923
+ const cleanup = () => {
2924
+ ws.off("command:stdout", handleStdout);
2925
+ ws.off("command:stderr", handleStderr);
2926
+ ws.off("command:exit", handleExit);
2927
+ ws.unsubscribe(channel);
2928
+ };
2929
+ const handleStdout = (msg) => {
2930
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2931
+ stdout += msg.data.output;
2932
+ options?.onStdout?.(msg.data.output);
2933
+ }
2934
+ };
2935
+ const handleStderr = (msg) => {
2936
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2937
+ stderr += msg.data.output;
2938
+ options?.onStderr?.(msg.data.output);
2939
+ }
2940
+ };
2941
+ const handleExit = (msg) => {
2942
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2943
+ exitCode = msg.data.exit_code;
2944
+ cleanup();
2945
+ if (resolvePromise) {
2946
+ resolvePromise({ stdout, stderr, exitCode, durationMs: 0 });
2947
+ }
2948
+ }
2949
+ };
2950
+ ws.on("command:stdout", handleStdout);
2951
+ ws.on("command:stderr", handleStderr);
2952
+ ws.on("command:exit", handleExit);
2953
+ ws.startCommand(cmd_id);
2954
+ if (options?.background) {
2955
+ return {
2956
+ stdout: "",
2957
+ stderr: "",
2958
+ exitCode: 0,
2959
+ durationMs: 0
2960
+ };
2961
+ }
2962
+ return new Promise((resolve) => {
2963
+ resolvePromise = resolve;
2964
+ });
2861
2965
  }
2862
2966
  /**
2863
2967
  * Get server information
@@ -2910,9 +3014,15 @@ API request failed (${response.status}): ${error}`
2910
3014
  }
2911
3015
  /**
2912
3016
  * Destroy the sandbox (Sandbox interface method)
3017
+ *
3018
+ * If a destroyHandler was provided (e.g., from gateway), calls it to destroy
3019
+ * the sandbox on the backend. Otherwise, only disconnects the WebSocket.
2913
3020
  */
2914
3021
  async destroy() {
2915
3022
  await this.disconnect();
3023
+ if (this.config.destroyHandler) {
3024
+ await this.config.destroyHandler();
3025
+ }
2916
3026
  }
2917
3027
  /**
2918
3028
  * Disconnect WebSocket
@@ -3351,9 +3461,17 @@ function buildConfigExample(provider, authOptions) {
3351
3461
  return options.join("\n\n");
3352
3462
  }
3353
3463
  function createConfigFromExplicit(config) {
3354
- if (!config.apiKey) {
3464
+ const computesdkApiKey = config.computesdkApiKey || config.apiKey;
3465
+ if (!computesdkApiKey) {
3355
3466
  throw new Error(
3356
- `Missing ComputeSDK API key. The 'apiKey' field is required.
3467
+ `Missing ComputeSDK API key. Set 'computesdkApiKey' in your config.
3468
+
3469
+ Example:
3470
+ compute.setConfig({
3471
+ provider: 'e2b',
3472
+ computesdkApiKey: process.env.COMPUTESDK_API_KEY,
3473
+ e2b: { apiKey: process.env.E2B_API_KEY }
3474
+ })
3357
3475
 
3358
3476
  Get your API key at: https://computesdk.com/dashboard`
3359
3477
  );
@@ -3361,7 +3479,7 @@ Get your API key at: https://computesdk.com/dashboard`
3361
3479
  validateProviderConfig(config);
3362
3480
  const providerHeaders = buildProviderHeaders2(config);
3363
3481
  return {
3364
- apiKey: config.apiKey,
3482
+ apiKey: computesdkApiKey,
3365
3483
  gatewayUrl: config.gatewayUrl || GATEWAY_URL,
3366
3484
  provider: config.provider,
3367
3485
  providerHeaders
@@ -3477,7 +3595,12 @@ var ComputeManager = class {
3477
3595
  ...name && { name },
3478
3596
  ...namespace && { namespace }
3479
3597
  },
3480
- WebSocket: globalThis.WebSocket
3598
+ WebSocket: globalThis.WebSocket,
3599
+ destroyHandler: async () => {
3600
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3601
+ method: "DELETE"
3602
+ });
3603
+ }
3481
3604
  });
3482
3605
  await waitForComputeReady(sandbox);
3483
3606
  return sandbox;
@@ -3498,7 +3621,12 @@ var ComputeManager = class {
3498
3621
  provider,
3499
3622
  token: token || config.apiKey,
3500
3623
  metadata,
3501
- WebSocket: globalThis.WebSocket
3624
+ WebSocket: globalThis.WebSocket,
3625
+ destroyHandler: async () => {
3626
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3627
+ method: "DELETE"
3628
+ });
3629
+ }
3502
3630
  });
3503
3631
  await waitForComputeReady(sandbox);
3504
3632
  return sandbox;
@@ -3548,7 +3676,12 @@ var ComputeManager = class {
3548
3676
  name: result.data.name,
3549
3677
  namespace: result.data.namespace
3550
3678
  },
3551
- WebSocket: globalThis.WebSocket
3679
+ WebSocket: globalThis.WebSocket,
3680
+ destroyHandler: async () => {
3681
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3682
+ method: "DELETE"
3683
+ });
3684
+ }
3552
3685
  });
3553
3686
  await waitForComputeReady(sandbox);
3554
3687
  return sandbox;
@@ -3579,7 +3712,12 @@ var ComputeManager = class {
3579
3712
  name,
3580
3713
  namespace
3581
3714
  },
3582
- WebSocket: globalThis.WebSocket
3715
+ WebSocket: globalThis.WebSocket,
3716
+ destroyHandler: async () => {
3717
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3718
+ method: "DELETE"
3719
+ });
3720
+ }
3583
3721
  });
3584
3722
  await waitForComputeReady(sandbox);
3585
3723
  return sandbox;
@@ -3620,7 +3758,7 @@ var ComputeManager = class {
3620
3758
 
3621
3759
  Options:
3622
3760
  1. Zero-config: Set COMPUTESDK_API_KEY and provider credentials (e.g., E2B_API_KEY)
3623
- 2. Explicit: Call compute.setConfig({ provider: "e2b", apiKey: "...", e2b: { apiKey: "..." } })
3761
+ 2. Explicit: Call compute.setConfig({ provider: "e2b", computesdkApiKey: "...", e2b: { apiKey: "..." } })
3624
3762
  3. Use provider directly: import { e2b } from '@computesdk/e2b'
3625
3763
 
3626
3764
  Docs: https://computesdk.com/docs/quickstart`