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.js CHANGED
@@ -538,6 +538,17 @@ var WebSocketManager = class {
538
538
  data: { terminal_id: terminalId, cols, rows }
539
539
  });
540
540
  }
541
+ /**
542
+ * Start a pending streaming command
543
+ * Used in two-phase streaming flow: HTTP request creates pending command,
544
+ * then this signal triggers execution after client has subscribed.
545
+ */
546
+ startCommand(cmdId) {
547
+ this.sendRaw({
548
+ type: "command:start",
549
+ data: { cmd_id: cmdId }
550
+ });
551
+ }
541
552
  on(event, handler) {
542
553
  if (!this.eventHandlers.has(event)) {
543
554
  this.eventHandlers.set(event, /* @__PURE__ */ new Set());
@@ -728,7 +739,7 @@ var TerminalCommand = class {
728
739
  */
729
740
  async run(command, options) {
730
741
  const response = await this.runHandler(command, options?.background);
731
- const cmd2 = new Command({
742
+ const cmd = new Command({
732
743
  cmdId: response.data.cmd_id || "",
733
744
  terminalId: this.terminalId,
734
745
  command: response.data.command,
@@ -739,9 +750,9 @@ var TerminalCommand = class {
739
750
  durationMs: response.data.duration_ms,
740
751
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
741
752
  });
742
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
743
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
744
- return cmd2;
753
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
754
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
755
+ return cmd;
745
756
  }
746
757
  /**
747
758
  * List all commands executed in this terminal
@@ -750,7 +761,7 @@ var TerminalCommand = class {
750
761
  async list() {
751
762
  const response = await this.listHandler();
752
763
  return response.data.commands.map((item) => {
753
- const cmd2 = new Command({
764
+ const cmd = new Command({
754
765
  cmdId: item.cmd_id,
755
766
  terminalId: this.terminalId,
756
767
  command: item.command,
@@ -764,9 +775,9 @@ var TerminalCommand = class {
764
775
  startedAt: item.started_at,
765
776
  finishedAt: item.finished_at
766
777
  });
767
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
768
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
769
- return cmd2;
778
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
779
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
780
+ return cmd;
770
781
  });
771
782
  }
772
783
  /**
@@ -776,7 +787,7 @@ var TerminalCommand = class {
776
787
  */
777
788
  async retrieve(cmdId) {
778
789
  const response = await this.retrieveHandler(cmdId);
779
- const cmd2 = new Command({
790
+ const cmd = new Command({
780
791
  cmdId: response.data.cmd_id,
781
792
  terminalId: this.terminalId,
782
793
  command: response.data.command,
@@ -788,9 +799,9 @@ var TerminalCommand = class {
788
799
  startedAt: response.data.started_at,
789
800
  finishedAt: response.data.finished_at
790
801
  });
791
- cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
792
- cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
793
- return cmd2;
802
+ cmd.setWaitHandler((timeout) => this.waitHandler(cmd.id, timeout));
803
+ cmd.setRetrieveHandler(() => this.retrieveHandler(cmd.id));
804
+ return cmd;
794
805
  }
795
806
  };
796
807
 
@@ -1011,6 +1022,7 @@ var TerminalInstance = class {
1011
1022
  throw new Error("Destroy handler not set");
1012
1023
  }
1013
1024
  await this._destroyHandler();
1025
+ this._status = "stopped";
1014
1026
  this.cleanup();
1015
1027
  }
1016
1028
  /**
@@ -1810,6 +1822,8 @@ var Run = class {
1810
1822
  * @param options - Execution options
1811
1823
  * @param options.shell - Shell to use (optional)
1812
1824
  * @param options.background - Run in background (optional)
1825
+ * @param options.cwd - Working directory for the command (optional)
1826
+ * @param options.env - Environment variables (optional)
1813
1827
  * @returns Command execution result with stdout, stderr, exit code, and duration
1814
1828
  */
1815
1829
  async command(command, options) {
@@ -1914,7 +1928,8 @@ var Sandbox = class {
1914
1928
  headers: config.headers || {},
1915
1929
  timeout: config.timeout || 3e4,
1916
1930
  protocol: config.protocol || "binary",
1917
- metadata: config.metadata
1931
+ metadata: config.metadata,
1932
+ destroyHandler: config.destroyHandler
1918
1933
  };
1919
1934
  this.WebSocketImpl = config.WebSocket || globalThis.WebSocket;
1920
1935
  if (!this.WebSocketImpl) {
@@ -1935,7 +1950,7 @@ var Sandbox = class {
1935
1950
  await this.writeFile(path, content);
1936
1951
  },
1937
1952
  mkdir: async (path) => {
1938
- await this.runCommand((0, import_cmd.mkdir)(path));
1953
+ await this.runCommand((0, import_cmd.escapeArgs)((0, import_cmd.mkdir)(path)));
1939
1954
  },
1940
1955
  readdir: async (path) => {
1941
1956
  const response = await this.listFiles(path);
@@ -1947,7 +1962,7 @@ var Sandbox = class {
1947
1962
  }));
1948
1963
  },
1949
1964
  exists: async (path) => {
1950
- const result = await this.runCommand(import_cmd.test.exists(path));
1965
+ const result = await this.runCommand((0, import_cmd.escapeArgs)(import_cmd.test.exists(path)));
1951
1966
  return result.exitCode === 0;
1952
1967
  },
1953
1968
  remove: async (path) => {
@@ -1972,7 +1987,13 @@ var Sandbox = class {
1972
1987
  };
1973
1988
  },
1974
1989
  command: async (command, options) => {
1975
- const result = await this.runCommandRequest({ command, shell: options?.shell, background: options?.background });
1990
+ const result = await this.runCommandRequest({
1991
+ command,
1992
+ shell: options?.shell,
1993
+ background: options?.background,
1994
+ cwd: options?.cwd,
1995
+ env: options?.env
1996
+ });
1976
1997
  return {
1977
1998
  stdout: result.data.stdout,
1978
1999
  stderr: result.data.stderr,
@@ -2286,12 +2307,14 @@ API request failed (${response.status}): ${error}`
2286
2307
  });
2287
2308
  }
2288
2309
  /**
2289
- * Execute a shell command (POST /run/command)
2310
+ * Execute a command and get the result
2311
+ * Lower-level method that returns the raw API response
2290
2312
  *
2291
- * @param options - Command options
2292
- * @param options.command - The command to execute
2313
+ * @param options.command - Command to execute
2293
2314
  * @param options.shell - Shell to use (optional)
2294
2315
  * @param options.background - Run in background (optional)
2316
+ * @param options.cwd - Working directory for the command (optional)
2317
+ * @param options.env - Environment variables (optional)
2295
2318
  * @returns Command execution result
2296
2319
  *
2297
2320
  * @example
@@ -2329,18 +2352,24 @@ API request failed (${response.status}): ${error}`
2329
2352
  * Get file metadata (without content)
2330
2353
  */
2331
2354
  async getFile(path) {
2332
- return this.request(`/files/${encodeURIComponent(path)}`);
2355
+ return this.request(`/files/${this.encodeFilePath(path)}`);
2356
+ }
2357
+ /**
2358
+ * Encode a file path for use in URLs
2359
+ * Strips leading slash and encodes each segment separately to preserve path structure
2360
+ */
2361
+ encodeFilePath(path) {
2362
+ const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2363
+ const segments = pathWithoutLeadingSlash.split("/");
2364
+ return segments.map((s) => encodeURIComponent(s)).join("/");
2333
2365
  }
2334
2366
  /**
2335
2367
  * Read file content
2336
2368
  */
2337
2369
  async readFile(path) {
2338
2370
  const params = new URLSearchParams({ content: "true" });
2339
- const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2340
- const segments = pathWithoutLeadingSlash.split("/");
2341
- const encodedPath = segments.map((s) => encodeURIComponent(s)).join("/");
2342
2371
  const response = await this.request(
2343
- `/files/${encodedPath}?${params}`
2372
+ `/files/${this.encodeFilePath(path)}?${params}`
2344
2373
  );
2345
2374
  return response.data.content || "";
2346
2375
  }
@@ -2357,7 +2386,7 @@ API request failed (${response.status}): ${error}`
2357
2386
  * Delete a file or directory
2358
2387
  */
2359
2388
  async deleteFile(path) {
2360
- return this.request(`/files/${encodeURIComponent(path)}`, {
2389
+ return this.request(`/files/${this.encodeFilePath(path)}`, {
2361
2390
  method: "DELETE"
2362
2391
  });
2363
2392
  }
@@ -2376,7 +2405,7 @@ API request failed (${response.status}): ${error}`
2376
2405
  headers["Authorization"] = `Bearer ${this._token}`;
2377
2406
  }
2378
2407
  const response = await fetch(
2379
- `${this.config.sandboxUrl}/files/${encodeURIComponent(path)}`,
2408
+ `${this.config.sandboxUrl}/files/${this.encodeFilePath(path)}`,
2380
2409
  {
2381
2410
  method: "HEAD",
2382
2411
  headers,
@@ -2888,29 +2917,104 @@ API request failed (${response.status}): ${error}`
2888
2917
  return this.run.code(code, language ? { language } : void 0);
2889
2918
  }
2890
2919
  /**
2891
- * Execute shell command in the sandbox (convenience method)
2920
+ * Execute shell command in the sandbox
2892
2921
  *
2893
- * Delegates to sandbox.run.command() - prefer using that directly for new code.
2922
+ * Sends clean command string to server - no preprocessing or shell wrapping.
2923
+ * The server handles shell invocation, working directory, and backgrounding.
2894
2924
  *
2895
- * @param command - The command to execute (string or array form)
2896
- * @param argsOrOptions - Arguments array or options object
2897
- * @param maybeOptions - Options when using (command, args, options) form
2925
+ * @param command - The command to execute (raw string, e.g., "npm install")
2926
+ * @param options - Execution options
2927
+ * @param options.background - Run in background (server uses goroutines)
2928
+ * @param options.cwd - Working directory (server uses cmd.Dir)
2929
+ * @param options.env - Environment variables (server uses cmd.Env)
2930
+ * @param options.onStdout - Callback for streaming stdout data
2931
+ * @param options.onStderr - Callback for streaming stderr data
2898
2932
  * @returns Command execution result
2933
+ *
2934
+ * @example
2935
+ * ```typescript
2936
+ * // Simple command
2937
+ * await sandbox.runCommand('ls -la')
2938
+ *
2939
+ * // With working directory
2940
+ * await sandbox.runCommand('npm install', { cwd: '/app' })
2941
+ *
2942
+ * // Background with env vars
2943
+ * await sandbox.runCommand('node server.js', {
2944
+ * background: true,
2945
+ * env: { PORT: '3000' }
2946
+ * })
2947
+ *
2948
+ * // With streaming output
2949
+ * await sandbox.runCommand('npm install', {
2950
+ * onStdout: (data) => console.log(data),
2951
+ * onStderr: (data) => console.error(data),
2952
+ * })
2953
+ * ```
2899
2954
  */
2900
- async runCommand(commandOrArray, argsOrOptions, maybeOptions) {
2901
- let commandParts;
2902
- let options;
2903
- if (Array.isArray(commandOrArray)) {
2904
- commandParts = commandOrArray;
2905
- options = argsOrOptions;
2906
- } else {
2907
- const args = Array.isArray(argsOrOptions) ? argsOrOptions : [];
2908
- commandParts = [commandOrArray, ...args];
2909
- options = Array.isArray(argsOrOptions) ? maybeOptions : argsOrOptions;
2955
+ async runCommand(command, options) {
2956
+ const hasStreamingCallbacks = options?.onStdout || options?.onStderr;
2957
+ if (!hasStreamingCallbacks) {
2958
+ return this.run.command(command, options);
2910
2959
  }
2911
- const finalCommand = (0, import_cmd.cmd)(commandParts, options);
2912
- const fullCommand = (0, import_cmd.escapeArgs)(finalCommand);
2913
- return this.run.command(fullCommand, { background: options?.background });
2960
+ const ws = await this.ensureWebSocket();
2961
+ const result = await this.runCommandRequest({
2962
+ command,
2963
+ stream: true,
2964
+ cwd: options?.cwd,
2965
+ env: options?.env
2966
+ });
2967
+ const { cmd_id, channel } = result.data;
2968
+ if (!cmd_id || !channel) {
2969
+ throw new Error("Server did not return streaming channel info");
2970
+ }
2971
+ ws.subscribe(channel);
2972
+ let stdout = "";
2973
+ let stderr = "";
2974
+ let exitCode = 0;
2975
+ let resolvePromise = null;
2976
+ const cleanup = () => {
2977
+ ws.off("command:stdout", handleStdout);
2978
+ ws.off("command:stderr", handleStderr);
2979
+ ws.off("command:exit", handleExit);
2980
+ ws.unsubscribe(channel);
2981
+ };
2982
+ const handleStdout = (msg) => {
2983
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2984
+ stdout += msg.data.output;
2985
+ options?.onStdout?.(msg.data.output);
2986
+ }
2987
+ };
2988
+ const handleStderr = (msg) => {
2989
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2990
+ stderr += msg.data.output;
2991
+ options?.onStderr?.(msg.data.output);
2992
+ }
2993
+ };
2994
+ const handleExit = (msg) => {
2995
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2996
+ exitCode = msg.data.exit_code;
2997
+ cleanup();
2998
+ if (resolvePromise) {
2999
+ resolvePromise({ stdout, stderr, exitCode, durationMs: 0 });
3000
+ }
3001
+ }
3002
+ };
3003
+ ws.on("command:stdout", handleStdout);
3004
+ ws.on("command:stderr", handleStderr);
3005
+ ws.on("command:exit", handleExit);
3006
+ ws.startCommand(cmd_id);
3007
+ if (options?.background) {
3008
+ return {
3009
+ stdout: "",
3010
+ stderr: "",
3011
+ exitCode: 0,
3012
+ durationMs: 0
3013
+ };
3014
+ }
3015
+ return new Promise((resolve) => {
3016
+ resolvePromise = resolve;
3017
+ });
2914
3018
  }
2915
3019
  /**
2916
3020
  * Get server information
@@ -2963,9 +3067,15 @@ API request failed (${response.status}): ${error}`
2963
3067
  }
2964
3068
  /**
2965
3069
  * Destroy the sandbox (Sandbox interface method)
3070
+ *
3071
+ * If a destroyHandler was provided (e.g., from gateway), calls it to destroy
3072
+ * the sandbox on the backend. Otherwise, only disconnects the WebSocket.
2966
3073
  */
2967
3074
  async destroy() {
2968
3075
  await this.disconnect();
3076
+ if (this.config.destroyHandler) {
3077
+ await this.config.destroyHandler();
3078
+ }
2969
3079
  }
2970
3080
  /**
2971
3081
  * Disconnect WebSocket
@@ -3404,9 +3514,17 @@ function buildConfigExample(provider, authOptions) {
3404
3514
  return options.join("\n\n");
3405
3515
  }
3406
3516
  function createConfigFromExplicit(config) {
3407
- if (!config.apiKey) {
3517
+ const computesdkApiKey = config.computesdkApiKey || config.apiKey;
3518
+ if (!computesdkApiKey) {
3408
3519
  throw new Error(
3409
- `Missing ComputeSDK API key. The 'apiKey' field is required.
3520
+ `Missing ComputeSDK API key. Set 'computesdkApiKey' in your config.
3521
+
3522
+ Example:
3523
+ compute.setConfig({
3524
+ provider: 'e2b',
3525
+ computesdkApiKey: process.env.COMPUTESDK_API_KEY,
3526
+ e2b: { apiKey: process.env.E2B_API_KEY }
3527
+ })
3410
3528
 
3411
3529
  Get your API key at: https://computesdk.com/dashboard`
3412
3530
  );
@@ -3414,7 +3532,7 @@ Get your API key at: https://computesdk.com/dashboard`
3414
3532
  validateProviderConfig(config);
3415
3533
  const providerHeaders = buildProviderHeaders2(config);
3416
3534
  return {
3417
- apiKey: config.apiKey,
3535
+ apiKey: computesdkApiKey,
3418
3536
  gatewayUrl: config.gatewayUrl || GATEWAY_URL,
3419
3537
  provider: config.provider,
3420
3538
  providerHeaders
@@ -3530,7 +3648,12 @@ var ComputeManager = class {
3530
3648
  ...name && { name },
3531
3649
  ...namespace && { namespace }
3532
3650
  },
3533
- WebSocket: globalThis.WebSocket
3651
+ WebSocket: globalThis.WebSocket,
3652
+ destroyHandler: async () => {
3653
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3654
+ method: "DELETE"
3655
+ });
3656
+ }
3534
3657
  });
3535
3658
  await waitForComputeReady(sandbox);
3536
3659
  return sandbox;
@@ -3551,7 +3674,12 @@ var ComputeManager = class {
3551
3674
  provider,
3552
3675
  token: token || config.apiKey,
3553
3676
  metadata,
3554
- WebSocket: globalThis.WebSocket
3677
+ WebSocket: globalThis.WebSocket,
3678
+ destroyHandler: async () => {
3679
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3680
+ method: "DELETE"
3681
+ });
3682
+ }
3555
3683
  });
3556
3684
  await waitForComputeReady(sandbox);
3557
3685
  return sandbox;
@@ -3601,7 +3729,12 @@ var ComputeManager = class {
3601
3729
  name: result.data.name,
3602
3730
  namespace: result.data.namespace
3603
3731
  },
3604
- WebSocket: globalThis.WebSocket
3732
+ WebSocket: globalThis.WebSocket,
3733
+ destroyHandler: async () => {
3734
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3735
+ method: "DELETE"
3736
+ });
3737
+ }
3605
3738
  });
3606
3739
  await waitForComputeReady(sandbox);
3607
3740
  return sandbox;
@@ -3632,7 +3765,12 @@ var ComputeManager = class {
3632
3765
  name,
3633
3766
  namespace
3634
3767
  },
3635
- WebSocket: globalThis.WebSocket
3768
+ WebSocket: globalThis.WebSocket,
3769
+ destroyHandler: async () => {
3770
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3771
+ method: "DELETE"
3772
+ });
3773
+ }
3636
3774
  });
3637
3775
  await waitForComputeReady(sandbox);
3638
3776
  return sandbox;
@@ -3673,7 +3811,7 @@ var ComputeManager = class {
3673
3811
 
3674
3812
  Options:
3675
3813
  1. Zero-config: Set COMPUTESDK_API_KEY and provider credentials (e.g., E2B_API_KEY)
3676
- 2. Explicit: Call compute.setConfig({ provider: "e2b", apiKey: "...", e2b: { apiKey: "..." } })
3814
+ 2. Explicit: Call compute.setConfig({ provider: "e2b", computesdkApiKey: "...", e2b: { apiKey: "..." } })
3677
3815
  3. Use provider directly: import { e2b } from '@computesdk/e2b'
3678
3816
 
3679
3817
  Docs: https://computesdk.com/docs/quickstart`