@computesdk/workbench 0.1.0 → 1.0.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.
package/README.md CHANGED
@@ -67,7 +67,8 @@ drwxr-xr-x 2 user user 4096 Dec 12 19:01 repo
67
67
 
68
68
  ### Workbench Commands
69
69
 
70
- - `provider <name>` - Switch provider (e2b, railway, etc.)
70
+ - `provider <name>` - Switch provider via gateway (e.g., `provider e2b`)
71
+ - `provider direct <name>` - Switch to direct provider connection (e.g., `provider direct e2b`)
71
72
  - `providers` - List all providers with status
72
73
  - `restart` - Restart current sandbox
73
74
  - `destroy` - Destroy current sandbox
@@ -76,6 +77,14 @@ drwxr-xr-x 2 user user 4096 Dec 12 19:01 repo
76
77
  - `help` - Show this help
77
78
  - `exit` - Exit workbench
78
79
 
80
+ **Provider Modes:**
81
+ - **Gateway mode (default)**: Routes through ComputeSDK API, zero-config setup
82
+ - Example: `provider e2b` uses E2B via gateway
83
+ - Requires: `COMPUTESDK_API_KEY` + provider credentials
84
+ - **Direct mode**: Connects directly to provider, requires provider packages
85
+ - Example: `provider direct e2b` connects directly to E2B
86
+ - Requires: Provider package installed (`@computesdk/e2b`) + provider credentials
87
+
79
88
  ### Running Commands
80
89
 
81
90
  Just type any `@computesdk/cmd` function. Tab autocomplete works!
@@ -102,6 +111,9 @@ mkdir('/app/src')
102
111
  ls('/home')
103
112
  cat('/home/file.txt')
104
113
  cp('/src', '/dest', { recursive: true })
114
+ rm('/file.txt') // Remove file
115
+ rm.rf('/directory') // Force remove anything
116
+ rm.auto('/path') // Smart remove (auto-detects file vs directory)
105
117
  ```
106
118
 
107
119
  **Network:**
@@ -114,19 +126,39 @@ wget('https://file.com/download.zip')
114
126
 
115
127
  ## Provider Switching
116
128
 
117
- Switch between providers on the fly:
129
+ ### Gateway Mode (Default - Zero Config)
130
+
131
+ Switch between providers via the gateway:
118
132
 
119
133
  ```
134
+ workbench> provider e2b
135
+ ✅ Switched to e2b (via gateway)
136
+
137
+ workbench> npm.install('express')
138
+ ⏳ Creating sandbox with e2b (via gateway)...
139
+ ✅ Sandbox ready (1.2s)
140
+ Running: npm install express
141
+ ✅ Completed (3.2s)
142
+
120
143
  workbench> provider railway
121
144
  Destroy current sandbox? (y/N): y
122
- Destroyed e2b sandbox
123
- ⏳ Creating sandbox with railway...
124
- ✅ Sandbox ready (2.1s)
125
- Switched to railway
126
-
127
- workbench> npm.install('lodash')
128
- Running: npm install lodash
129
- ✅ Completed (2.8s)
145
+ Switched to railway (via gateway)
146
+ ```
147
+
148
+ ### Direct Mode (Advanced)
149
+
150
+ Connect directly to providers (requires provider packages):
151
+
152
+ ```
153
+ workbench> provider direct e2b
154
+ ✅ Switched to e2b (direct)
155
+
156
+ workbench> pwd()
157
+ ⏳ Creating sandbox with e2b (direct)...
158
+ ✅ Sandbox ready (2.5s)
159
+ Running: pwd
160
+ /home
161
+ ✅ Completed (0.1s)
130
162
  ```
131
163
 
132
164
  ## Tab Autocomplete
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.3_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/esm_shims.js
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ var getFilename = () => fileURLToPath(import.meta.url);
7
+ var getDirname = () => path.dirname(getFilename());
8
+ var __dirname = /* @__PURE__ */ getDirname();
9
+
3
10
  // src/bin/workbench.ts
4
11
  import { config } from "dotenv";
5
12
 
@@ -10,7 +17,8 @@ function createState() {
10
17
  currentSandbox: null,
11
18
  sandboxCreatedAt: null,
12
19
  availableProviders: [],
13
- forceGatewayMode: false,
20
+ useDirectMode: false,
21
+ // Default to gateway mode
14
22
  verbose: false
15
23
  };
16
24
  }
@@ -79,16 +87,22 @@ var c = {
79
87
  blue: (text) => `${colors.blue}${text}${colors.reset}`,
80
88
  magenta: (text) => `${colors.magenta}${text}${colors.reset}`
81
89
  };
82
- function showWelcome(availableProviders, currentProvider) {
90
+ function showWelcome(availableProviders, currentProvider, useDirectMode) {
83
91
  console.log(c.bold(c.cyan("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")));
84
92
  console.log(c.bold(c.cyan("\u2551 ComputeSDK Workbench \u2551")));
85
93
  console.log(c.bold(c.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n")));
86
94
  if (availableProviders.length > 0) {
87
- console.log(`Providers available: ${availableProviders.join(", ")}`);
95
+ const backendProviders = availableProviders.filter((p) => p !== "gateway");
96
+ console.log(`Providers available: ${backendProviders.join(", ")}`);
88
97
  if (currentProvider) {
89
- const mode = currentProvider === "gateway" ? "\u{1F310} gateway mode" : "\u{1F517} direct mode";
90
- console.log(`Current provider: ${c.green(currentProvider)} (${mode})
98
+ if (useDirectMode) {
99
+ console.log(`Current provider: ${c.green(currentProvider)} (\u{1F517} direct mode)
100
+ `);
101
+ } else {
102
+ const backendProvider = currentProvider === "gateway" ? backendProviders[0] || "auto" : currentProvider;
103
+ console.log(`Current provider: ${c.green(backendProvider)} (\u{1F310} via gateway)
91
104
  `);
105
+ }
92
106
  } else {
93
107
  console.log(`
94
108
  ${c.dim('Tip: Use "provider <name>" to select a provider')}
@@ -104,58 +118,6 @@ ${c.dim('Tip: Use "provider <name>" to select a provider')}
104
118
  }
105
119
  console.log(c.dim('Type "help" for available commands\n'));
106
120
  }
107
- function showHelp() {
108
- console.log(`
109
- ${c.bold("Workbench Commands:")}
110
- ${c.cyan("provider <name>")} Switch provider (gateway, e2b, railway, etc.)
111
- ${c.cyan("providers")} List all providers with status
112
- ${c.cyan("mode")} Show current mode (gateway vs direct)
113
- ${c.cyan("mode gateway")} Force gateway mode
114
- ${c.cyan("mode direct")} Force direct mode (auto-detect provider)
115
- ${c.cyan("restart")} Restart current sandbox
116
- ${c.cyan("destroy")} Destroy current sandbox
117
- ${c.cyan("info")} Show sandbox info
118
- ${c.cyan("env")} Show environment/credentials status
119
- ${c.cyan("verbose")} Toggle verbose output (show full results)
120
- ${c.cyan("help")} Show this help
121
- ${c.cyan("exit")} or ${c.cyan(".exit")} Exit workbench
122
-
123
- ${c.bold("Provider Modes:")}
124
- ${c.cyan("gateway")} \u{1F310} Routes through ComputeSDK API (COMPUTESDK_API_KEY)
125
- ${c.cyan("e2b, railway, etc.")} \u{1F517} Direct connection to provider (requires provider package)
126
-
127
- ${c.bold("Running Commands:")}
128
- Just type any ${c.cyan("@computesdk/cmd")} function:
129
- ${c.dim('npm.install("express")')}
130
- ${c.dim('git.clone("https://github.com/user/repo")')}
131
- ${c.dim('python("script.py")')}
132
- ${c.dim('mkdir("/app/src")')}
133
- ${c.dim('ls("/home")')}
134
-
135
- ${c.green("\u2728 Tab autocomplete works for all functions!")}
136
-
137
- ${c.bold("Background Execution:")}
138
- Run commands in the background (returns immediately):
139
- ${c.dim('sh("sleep 10", { background: true })')}
140
- ${c.dim('sh("npm start", { background: true })')}
141
-
142
- ${c.bold("Examples:")}
143
- ${c.dim("# Install a package")}
144
- ${c.cyan('npm.install("express")')}
145
-
146
- ${c.dim("# Clone a repo")}
147
- ${c.cyan('git.clone("https://github.com/user/repo")')}
148
-
149
- ${c.dim("# Run Python code")}
150
- ${c.cyan(`python("-c", "print('hello')")`)}
151
-
152
- ${c.dim("# Start a server in background")}
153
- ${c.cyan('sh("python -m http.server 8000", { background: true })')}
154
-
155
- ${c.dim("# Switch providers")}
156
- ${c.cyan("provider railway")}
157
- `);
158
- }
159
121
  function showInfo(state) {
160
122
  if (!state.currentSandbox) {
161
123
  console.log(c.yellow("\nNo active sandbox\n"));
@@ -214,6 +176,64 @@ function logSuccess(message, duration) {
214
176
  const durationStr = duration ? ` (${formatDuration(duration)})` : "";
215
177
  console.log(c.green(`\u2705 ${message}${durationStr}`));
216
178
  }
179
+ function showHelp() {
180
+ console.log(`
181
+ ${c.bold("ComputeSDK Workbench Commands")}
182
+
183
+ ${c.bold("Provider Management:")}
184
+ ${c.cyan("provider <name>")} Switch to provider via gateway (default)
185
+ ${c.dim("Example: provider e2b")}
186
+ ${c.cyan("provider direct <name>")} Connect directly to provider
187
+ ${c.dim("Example: provider direct e2b")}
188
+ ${c.cyan("providers")} List all providers with status
189
+ ${c.cyan("mode <gateway|direct>")} Toggle default mode for next sandbox
190
+
191
+ ${c.bold("Provider Modes:")}
192
+ ${c.bold("Gateway (default)")}: Routes through ComputeSDK API, zero-config
193
+ \u2022 Requires: COMPUTESDK_API_KEY + provider credentials
194
+ \u2022 Usage: ${c.cyan("provider e2b")}
195
+
196
+ ${c.bold("Direct")}: Connects directly to provider, requires packages
197
+ \u2022 Requires: Provider package installed + credentials
198
+ \u2022 Usage: ${c.cyan("provider direct e2b")}
199
+
200
+ ${c.bold("Sandbox Management:")}
201
+ ${c.cyan("restart")} Restart current sandbox
202
+ ${c.cyan("destroy")} Destroy current sandbox
203
+ ${c.cyan("info")} Show sandbox info (provider, uptime)
204
+
205
+ ${c.bold("Environment:")}
206
+ ${c.cyan("env")} Show environment/credentials status
207
+ ${c.cyan("verbose")} Toggle verbose output mode
208
+
209
+ ${c.bold("Help:")}
210
+ ${c.cyan("help")} Show this help message
211
+ ${c.cyan("exit")} or ${c.cyan(".exit")} Exit workbench
212
+
213
+ ${c.bold("Running Commands:")}
214
+ Type any @computesdk/cmd function and it will run automatically:
215
+
216
+ ${c.dim("Package Managers:")}
217
+ ${c.cyan('npm.install("express")')}
218
+ ${c.cyan('pip.install("requests")')}
219
+
220
+ ${c.dim("Git:")}
221
+ ${c.cyan('git.clone("https://github.com/user/repo")')}
222
+ ${c.cyan("git.status()")}
223
+
224
+ ${c.dim("Filesystem:")}
225
+ ${c.cyan('ls("/home")')}
226
+ ${c.cyan('cat("/etc/hosts")')}
227
+ ${c.cyan('rm.rf("/tmp")')} ${c.dim("// Force remove")}
228
+ ${c.cyan('rm.auto("/path")')} ${c.dim("// Smart remove")}
229
+
230
+ ${c.bold("Background Execution:")}
231
+ ${c.cyan('sh("npm start", { background: true })')}
232
+ ${c.cyan('sh("python -m http.server 8000", { background: true })')}
233
+
234
+ ${c.dim("Press Tab for autocomplete on all commands!")}
235
+ `);
236
+ }
217
237
  function logError(message) {
218
238
  console.log(c.red(`\u274C ${message}`));
219
239
  }
@@ -395,12 +415,44 @@ async function loadProvider(providerName) {
395
415
  }
396
416
  function getProviderConfig(providerName) {
397
417
  const config2 = {};
398
- const requiredVars = PROVIDER_ENV_VARS[providerName];
399
- for (const varName of requiredVars) {
400
- const value = process.env[varName];
401
- if (value) {
402
- config2[varName] = value;
403
- }
418
+ switch (providerName) {
419
+ case "e2b":
420
+ if (process.env.E2B_API_KEY) config2.apiKey = process.env.E2B_API_KEY;
421
+ break;
422
+ case "railway":
423
+ if (process.env.RAILWAY_API_KEY) config2.apiKey = process.env.RAILWAY_API_KEY;
424
+ if (process.env.RAILWAY_PROJECT_ID) config2.projectId = process.env.RAILWAY_PROJECT_ID;
425
+ if (process.env.RAILWAY_ENVIRONMENT_ID) config2.environmentId = process.env.RAILWAY_ENVIRONMENT_ID;
426
+ break;
427
+ case "daytona":
428
+ if (process.env.DAYTONA_API_KEY) config2.apiKey = process.env.DAYTONA_API_KEY;
429
+ break;
430
+ case "modal":
431
+ if (process.env.MODAL_TOKEN_ID) config2.tokenId = process.env.MODAL_TOKEN_ID;
432
+ if (process.env.MODAL_TOKEN_SECRET) config2.tokenSecret = process.env.MODAL_TOKEN_SECRET;
433
+ break;
434
+ case "runloop":
435
+ if (process.env.RUNLOOP_API_KEY) config2.apiKey = process.env.RUNLOOP_API_KEY;
436
+ break;
437
+ case "vercel":
438
+ if (process.env.VERCEL_TOKEN) config2.token = process.env.VERCEL_TOKEN;
439
+ if (process.env.VERCEL_TEAM_ID) config2.teamId = process.env.VERCEL_TEAM_ID;
440
+ if (process.env.VERCEL_PROJECT_ID) config2.projectId = process.env.VERCEL_PROJECT_ID;
441
+ break;
442
+ case "cloudflare":
443
+ if (process.env.CLOUDFLARE_API_TOKEN) config2.apiToken = process.env.CLOUDFLARE_API_TOKEN;
444
+ if (process.env.CLOUDFLARE_ACCOUNT_ID) config2.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
445
+ break;
446
+ case "codesandbox":
447
+ if (process.env.CSB_API_KEY) config2.apiKey = process.env.CSB_API_KEY;
448
+ break;
449
+ case "blaxel":
450
+ if (process.env.BL_API_KEY) config2.apiKey = process.env.BL_API_KEY;
451
+ if (process.env.BL_WORKSPACE) config2.workspace = process.env.BL_WORKSPACE;
452
+ break;
453
+ case "gateway":
454
+ if (process.env.COMPUTESDK_API_KEY) config2.apiKey = process.env.COMPUTESDK_API_KEY;
455
+ break;
404
456
  }
405
457
  return config2;
406
458
  }
@@ -428,23 +480,36 @@ async function ensureSandbox(state) {
428
480
  await createSandbox(state);
429
481
  }
430
482
  async function createSandbox(state) {
431
- const providerName = state.currentProvider || autoDetectProvider(state.forceGatewayMode);
483
+ const providerName = state.currentProvider || autoDetectProvider(false);
484
+ const useDirect = state.useDirectMode;
432
485
  if (!providerName) {
433
486
  logError('No provider configured. Run "env" to see setup instructions.');
434
487
  throw new Error("No provider available");
435
488
  }
436
- if (!isProviderReady(providerName)) {
437
- logError(`Provider ${providerName} is not fully configured.`);
438
- console.log(getProviderSetupHelp(providerName));
439
- throw new Error("Provider not ready");
489
+ let modeLabel;
490
+ let actualProviderName;
491
+ if (useDirect) {
492
+ modeLabel = `${providerName} (direct)`;
493
+ actualProviderName = providerName;
494
+ if (!isProviderReady(providerName)) {
495
+ logError(`Provider ${providerName} is not fully configured for direct mode.`);
496
+ console.log(getProviderSetupHelp(providerName));
497
+ throw new Error("Provider not ready");
498
+ }
499
+ } else {
500
+ modeLabel = `${providerName} (via gateway)`;
501
+ actualProviderName = "gateway";
502
+ if (!isProviderReady("gateway")) {
503
+ logError("Gateway mode requires COMPUTESDK_API_KEY");
504
+ console.log(getProviderSetupHelp("gateway"));
505
+ throw new Error("Gateway not ready");
506
+ }
440
507
  }
441
- const spinner = new Spinner(`Creating sandbox with ${providerName}...`).start();
508
+ const spinner = new Spinner(`Creating sandbox with ${modeLabel}...`).start();
442
509
  const startTime = Date.now();
443
510
  try {
444
511
  let compute;
445
- if (providerName === "gateway") {
446
- compute = createCompute();
447
- } else {
512
+ if (useDirect) {
448
513
  const providerModule = await loadProvider(providerName);
449
514
  const providerFactory = providerModule[providerName];
450
515
  if (!providerFactory) {
@@ -454,6 +519,43 @@ async function createSandbox(state) {
454
519
  compute = createCompute({
455
520
  defaultProvider: providerFactory(config2)
456
521
  });
522
+ } else {
523
+ const gatewayModule = await import("computesdk");
524
+ const gatewayFactory = gatewayModule.gateway;
525
+ const providerConfig = getProviderConfig(providerName);
526
+ const providerHeaders = {};
527
+ switch (providerName) {
528
+ case "e2b":
529
+ if (providerConfig.apiKey) providerHeaders["X-E2B-API-Key"] = providerConfig.apiKey;
530
+ break;
531
+ case "railway":
532
+ if (providerConfig.apiKey) providerHeaders["X-Railway-API-Key"] = providerConfig.apiKey;
533
+ if (providerConfig.projectId) providerHeaders["X-Railway-Project-ID"] = providerConfig.projectId;
534
+ if (providerConfig.environmentId) providerHeaders["X-Railway-Environment-ID"] = providerConfig.environmentId;
535
+ break;
536
+ case "daytona":
537
+ if (providerConfig.apiKey) providerHeaders["X-Daytona-API-Key"] = providerConfig.apiKey;
538
+ break;
539
+ case "modal":
540
+ if (providerConfig.tokenId) providerHeaders["X-Modal-Token-ID"] = providerConfig.tokenId;
541
+ if (providerConfig.tokenSecret) providerHeaders["X-Modal-Token-Secret"] = providerConfig.tokenSecret;
542
+ break;
543
+ case "vercel":
544
+ if (providerConfig.token) providerHeaders["X-Vercel-Token"] = providerConfig.token;
545
+ if (providerConfig.teamId) providerHeaders["X-Vercel-Team-ID"] = providerConfig.teamId;
546
+ if (providerConfig.projectId) providerHeaders["X-Vercel-Project-ID"] = providerConfig.projectId;
547
+ break;
548
+ }
549
+ const config2 = {
550
+ apiKey: process.env.COMPUTESDK_API_KEY,
551
+ provider: providerName,
552
+ // Tell gateway which backend to use
553
+ providerHeaders
554
+ // Pass provider credentials via headers
555
+ };
556
+ compute = createCompute({
557
+ defaultProvider: gatewayFactory(config2)
558
+ });
457
559
  }
458
560
  const result = await compute.sandbox.create();
459
561
  const duration = Date.now() - startTime;
@@ -468,6 +570,12 @@ async function createSandbox(state) {
468
570
  Install it with: ${c.cyan(`npm install @computesdk/${providerName}`)}
469
571
  `);
470
572
  }
573
+ if (error instanceof Error) {
574
+ logError(`Error: ${error.message}`);
575
+ if (error.stack) {
576
+ console.log(c.dim(error.stack));
577
+ }
578
+ }
471
579
  throw error;
472
580
  }
473
581
  }
@@ -523,68 +631,100 @@ async function runCommand(state, command) {
523
631
  throw error;
524
632
  }
525
633
  }
526
- async function switchProvider(state, newProvider) {
527
- if (!isValidProvider(newProvider)) {
528
- logError(`Unknown provider: ${newProvider}`);
634
+ async function switchProvider(state, mode, providerName) {
635
+ let useDirect = false;
636
+ let actualProvider = mode;
637
+ if (mode === "direct") {
638
+ if (!providerName) {
639
+ logError("Usage: provider direct <name>");
640
+ console.log("Example: provider direct e2b");
641
+ return;
642
+ }
643
+ useDirect = true;
644
+ actualProvider = providerName;
645
+ } else if (mode === "gateway") {
646
+ if (!providerName) {
647
+ logError("Usage: provider gateway <name>");
648
+ console.log("Example: provider gateway e2b");
649
+ return;
650
+ }
651
+ useDirect = false;
652
+ actualProvider = providerName;
653
+ }
654
+ if (actualProvider === "gateway") {
655
+ actualProvider = autoDetectProvider(false) || "e2b";
656
+ }
657
+ if (!isValidProvider(actualProvider)) {
658
+ logError(`Unknown provider: ${actualProvider}`);
529
659
  console.log(`Available providers: e2b, railway, daytona, modal, runloop, vercel, cloudflare, codesandbox, blaxel`);
530
660
  return;
531
661
  }
532
- if (!isProviderReady(newProvider)) {
533
- logError(`Provider ${newProvider} is not fully configured.`);
534
- console.log(getProviderSetupHelp(newProvider));
662
+ if (!useDirect && !isProviderReady("gateway")) {
663
+ logError("Gateway mode requires COMPUTESDK_API_KEY");
664
+ console.log(getProviderSetupHelp("gateway"));
665
+ return;
666
+ }
667
+ if (useDirect && !isProviderReady(actualProvider)) {
668
+ logError(`Provider ${actualProvider} is not fully configured for direct mode.`);
669
+ console.log(getProviderSetupHelp(actualProvider));
535
670
  return;
536
671
  }
537
672
  if (hasSandbox(state)) {
538
673
  const shouldDestroy = await confirm("Destroy current sandbox?");
539
674
  if (shouldDestroy) {
540
675
  await destroySandbox(state);
541
- state.currentProvider = newProvider;
542
- logSuccess(`Switched to ${newProvider}`);
676
+ state.currentProvider = actualProvider;
677
+ state.useDirectMode = useDirect;
678
+ const modeStr = useDirect ? `${actualProvider} (direct)` : `${actualProvider} (via gateway)`;
679
+ logSuccess(`Switched to ${modeStr}`);
543
680
  } else {
544
681
  logWarning("Keeping current sandbox. Provider remains unchanged.");
545
682
  }
546
683
  } else {
547
- state.currentProvider = newProvider;
548
- logSuccess(`Switched to ${newProvider}`);
684
+ state.currentProvider = actualProvider;
685
+ state.useDirectMode = useDirect;
686
+ const modeStr = useDirect ? `${actualProvider} (direct)` : `${actualProvider} (via gateway)`;
687
+ logSuccess(`Switched to ${modeStr}`);
549
688
  }
550
689
  }
551
690
  function createProviderCommand(state) {
552
- return async function provider(name) {
553
- if (!name) {
691
+ return async function provider(mode, providerName) {
692
+ if (!mode) {
554
693
  if (state.currentProvider) {
694
+ const modeStr = state.useDirectMode ? "direct" : "via gateway";
555
695
  console.log(`
556
- Current provider: ${c.green(state.currentProvider)}
696
+ Current provider: ${c.green(state.currentProvider)} (${modeStr})
557
697
  `);
558
698
  } else {
559
699
  console.log(c.yellow("\nNo provider selected\n"));
560
700
  }
561
701
  return;
562
702
  }
563
- await switchProvider(state, name);
703
+ await switchProvider(state, mode, providerName);
564
704
  };
565
705
  }
566
706
  async function toggleMode(state, mode) {
567
- const newMode = mode || (state.forceGatewayMode ? "direct" : "gateway");
568
- if (newMode === "gateway") {
569
- state.forceGatewayMode = true;
570
- logSuccess("Switched to gateway mode \u{1F310}");
571
- console.log(c.dim("Next sandbox will use gateway (requires COMPUTESDK_API_KEY)\n"));
572
- if (hasSandbox(state) && state.currentProvider !== "gateway") {
573
- console.log(c.yellow("Current sandbox is in direct mode."));
574
- console.log(c.dim('Run "restart" to switch to gateway mode\n'));
575
- }
576
- } else {
577
- state.forceGatewayMode = false;
707
+ const newMode = mode || (state.useDirectMode ? "gateway" : "direct");
708
+ if (newMode === "direct") {
709
+ state.useDirectMode = true;
578
710
  logSuccess("Switched to direct mode \u{1F517}");
579
711
  console.log(c.dim("Next sandbox will use direct provider packages\n"));
580
- if (hasSandbox(state) && state.currentProvider === "gateway") {
712
+ if (hasSandbox(state) && !state.useDirectMode) {
581
713
  console.log(c.yellow("Current sandbox is in gateway mode."));
582
714
  console.log(c.dim('Run "restart" to switch to direct mode\n'));
583
715
  }
716
+ } else {
717
+ state.useDirectMode = false;
718
+ logSuccess("Switched to gateway mode \u{1F310}");
719
+ console.log(c.dim("Next sandbox will use gateway (requires COMPUTESDK_API_KEY)\n"));
720
+ if (hasSandbox(state) && state.useDirectMode) {
721
+ console.log(c.yellow("Current sandbox is in direct mode."));
722
+ console.log(c.dim('Run "restart" to switch to gateway mode\n'));
723
+ }
584
724
  }
585
725
  }
586
726
  function showMode(state) {
587
- const mode = state.forceGatewayMode || state.currentProvider === "gateway" ? "gateway" : "direct";
727
+ const mode = state.useDirectMode ? "direct" : "gateway";
588
728
  const icon = mode === "gateway" ? "\u{1F310}" : "\u{1F517}";
589
729
  console.log(`
590
730
  Current mode: ${c.green(mode)} ${icon}`);
@@ -594,7 +734,7 @@ Current mode: ${c.green(mode)} ${icon}`);
594
734
  console.log(c.dim("Direct connection to providers (requires provider packages)"));
595
735
  }
596
736
  console.log(`
597
- Toggle with: ${c.cyan("mode gateway")} or ${c.cyan("mode direct")}
737
+ Switch with: ${c.cyan("provider e2b")} (gateway) or ${c.cyan("provider direct e2b")} (direct)
598
738
  `);
599
739
  }
600
740
  function toggleVerbose(state) {
@@ -642,7 +782,7 @@ function isCommand(value) {
642
782
  }
643
783
 
644
784
  // src/cli/repl.ts
645
- import * as path from "path";
785
+ import * as path2 from "path";
646
786
  import * as os from "os";
647
787
  function createREPL(state) {
648
788
  const replServer = repl.start({
@@ -769,10 +909,18 @@ function setupSmartEvaluator(replServer, state) {
769
909
  const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose"]);
770
910
  replServer.eval = function(cmd3, context, filename, callback) {
771
911
  const trimmedCmd = cmd3.trim();
772
- const providerMatch = trimmedCmd.match(/^provider\s+(\w+)$/);
912
+ const providerMatch = trimmedCmd.match(/^provider(?:\s+(direct|gateway))?\s+(\w+)$/);
773
913
  if (providerMatch) {
774
- const providerName = providerMatch[1];
775
- const providerCmd = `await provider('${providerName}')`;
914
+ const mode = providerMatch[1] || null;
915
+ const providerName = providerMatch[2];
916
+ const providerCmd = mode ? `await provider('${mode}', '${providerName}')` : `await provider('${providerName}')`;
917
+ originalEval.call(this, providerCmd, context, filename, callback);
918
+ return;
919
+ }
920
+ const providerOnlyMatch = trimmedCmd.match(/^provider\s+(direct|gateway)$/);
921
+ if (providerOnlyMatch) {
922
+ const mode = providerOnlyMatch[1];
923
+ const providerCmd = `await provider('${mode}')`;
776
924
  originalEval.call(this, providerCmd, context, filename, callback);
777
925
  return;
778
926
  }
@@ -828,12 +976,17 @@ function setupAutocomplete(replServer, state) {
828
976
  };
829
977
  replServer.completer = function(line, callback) {
830
978
  const trimmed = line.trim();
831
- if (!trimmed.includes(" ") && !trimmed.includes(".")) {
979
+ if (!line.includes(" ") && !line.includes(".")) {
832
980
  const commands = Object.keys(workbenchCommands);
833
981
  const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
834
982
  if (originalCompleter) {
835
- originalCompleter.call(replServer, line, (err, [contextHits, partial]) => {
836
- if (err) {
983
+ originalCompleter.call(replServer, line, (err, result) => {
984
+ if (err || !result) {
985
+ callback(null, [hits, trimmed]);
986
+ return;
987
+ }
988
+ const [contextHits, partial] = result;
989
+ if (!Array.isArray(contextHits)) {
837
990
  callback(null, [hits, trimmed]);
838
991
  return;
839
992
  }
@@ -845,25 +998,32 @@ function setupAutocomplete(replServer, state) {
845
998
  callback(null, [hits.length ? hits : commands, trimmed]);
846
999
  return;
847
1000
  }
848
- const parts = trimmed.split(/\s+/);
849
- if (parts.length === 2 && !trimmed.includes(".")) {
850
- const [command, partial] = parts;
1001
+ if (line.includes(" ") && !line.includes(".")) {
1002
+ const parts = line.split(" ");
1003
+ const command = parts[0].trim();
1004
+ const partial = parts.slice(1).join(" ").trim();
851
1005
  const suggestions = workbenchCommands[command];
852
1006
  if (suggestions && suggestions.length > 0) {
853
1007
  const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
854
- callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), trimmed]);
1008
+ callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
855
1009
  return;
856
1010
  }
857
1011
  }
858
1012
  if (originalCompleter) {
859
- originalCompleter.call(replServer, line, callback);
1013
+ originalCompleter.call(replServer, line, (err, result) => {
1014
+ if (err || !result) {
1015
+ callback(null, [[], line]);
1016
+ return;
1017
+ }
1018
+ callback(null, result);
1019
+ });
860
1020
  } else {
861
1021
  callback(null, [[], line]);
862
1022
  }
863
1023
  };
864
1024
  }
865
1025
  function setupHistory(replServer) {
866
- const historyFile = path.join(os.homedir(), ".computesdk_workbench_history");
1026
+ const historyFile = path2.join(os.homedir(), ".computesdk_workbench_history");
867
1027
  replServer.setupHistory(historyFile, (err) => {
868
1028
  if (err) {
869
1029
  }
@@ -874,8 +1034,16 @@ function setupHistory(replServer) {
874
1034
  async function startWorkbench() {
875
1035
  const state = createState();
876
1036
  state.availableProviders = getAvailableProviders();
877
- state.currentProvider = autoDetectProvider();
878
- showWelcome(state.availableProviders, state.currentProvider);
1037
+ const detectedProvider = autoDetectProvider();
1038
+ if (detectedProvider === "gateway") {
1039
+ const backendProviders = state.availableProviders.filter((p) => p !== "gateway");
1040
+ state.currentProvider = backendProviders[0] || "e2b";
1041
+ state.useDirectMode = false;
1042
+ } else {
1043
+ state.currentProvider = detectedProvider;
1044
+ state.useDirectMode = false;
1045
+ }
1046
+ showWelcome(state.availableProviders, state.currentProvider, state.useDirectMode);
879
1047
  const replServer = createREPL(state);
880
1048
  replServer.on("exit", async () => {
881
1049
  await cleanupOnExit(state, replServer);
@@ -884,8 +1052,22 @@ async function startWorkbench() {
884
1052
  }
885
1053
 
886
1054
  // src/bin/workbench.ts
887
- import * as path2 from "path";
888
- config({ path: path2.join(process.cwd(), ".env") });
1055
+ import * as path3 from "path";
1056
+ import * as fs from "fs";
1057
+ var possibleEnvPaths = [
1058
+ path3.join(process.cwd(), ".env"),
1059
+ // Current directory
1060
+ path3.join(process.cwd(), "../../.env"),
1061
+ // Monorepo root (if running from packages/workbench)
1062
+ path3.join(__dirname, "../../.env")
1063
+ // Relative to this script
1064
+ ];
1065
+ for (const envPath of possibleEnvPaths) {
1066
+ if (fs.existsSync(envPath)) {
1067
+ config({ path: envPath });
1068
+ break;
1069
+ }
1070
+ }
889
1071
  startWorkbench().catch((error) => {
890
1072
  console.error("Fatal error:", error);
891
1073
  process.exit(1);