@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/dist/index.js CHANGED
@@ -5,7 +5,8 @@ function createState() {
5
5
  currentSandbox: null,
6
6
  sandboxCreatedAt: null,
7
7
  availableProviders: [],
8
- forceGatewayMode: false,
8
+ useDirectMode: false,
9
+ // Default to gateway mode
9
10
  verbose: false
10
11
  };
11
12
  }
@@ -74,16 +75,22 @@ var c = {
74
75
  blue: (text) => `${colors.blue}${text}${colors.reset}`,
75
76
  magenta: (text) => `${colors.magenta}${text}${colors.reset}`
76
77
  };
77
- function showWelcome(availableProviders, currentProvider) {
78
+ function showWelcome(availableProviders, currentProvider, useDirectMode) {
78
79
  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")));
79
80
  console.log(c.bold(c.cyan("\u2551 ComputeSDK Workbench \u2551")));
80
81
  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")));
81
82
  if (availableProviders.length > 0) {
82
- console.log(`Providers available: ${availableProviders.join(", ")}`);
83
+ const backendProviders = availableProviders.filter((p) => p !== "gateway");
84
+ console.log(`Providers available: ${backendProviders.join(", ")}`);
83
85
  if (currentProvider) {
84
- const mode = currentProvider === "gateway" ? "\u{1F310} gateway mode" : "\u{1F517} direct mode";
85
- console.log(`Current provider: ${c.green(currentProvider)} (${mode})
86
+ if (useDirectMode) {
87
+ console.log(`Current provider: ${c.green(currentProvider)} (\u{1F517} direct mode)
86
88
  `);
89
+ } else {
90
+ const backendProvider = currentProvider === "gateway" ? backendProviders[0] || "auto" : currentProvider;
91
+ console.log(`Current provider: ${c.green(backendProvider)} (\u{1F310} via gateway)
92
+ `);
93
+ }
87
94
  } else {
88
95
  console.log(`
89
96
  ${c.dim('Tip: Use "provider <name>" to select a provider')}
@@ -99,58 +106,6 @@ ${c.dim('Tip: Use "provider <name>" to select a provider')}
99
106
  }
100
107
  console.log(c.dim('Type "help" for available commands\n'));
101
108
  }
102
- function showHelp() {
103
- console.log(`
104
- ${c.bold("Workbench Commands:")}
105
- ${c.cyan("provider <name>")} Switch provider (gateway, e2b, railway, etc.)
106
- ${c.cyan("providers")} List all providers with status
107
- ${c.cyan("mode")} Show current mode (gateway vs direct)
108
- ${c.cyan("mode gateway")} Force gateway mode
109
- ${c.cyan("mode direct")} Force direct mode (auto-detect provider)
110
- ${c.cyan("restart")} Restart current sandbox
111
- ${c.cyan("destroy")} Destroy current sandbox
112
- ${c.cyan("info")} Show sandbox info
113
- ${c.cyan("env")} Show environment/credentials status
114
- ${c.cyan("verbose")} Toggle verbose output (show full results)
115
- ${c.cyan("help")} Show this help
116
- ${c.cyan("exit")} or ${c.cyan(".exit")} Exit workbench
117
-
118
- ${c.bold("Provider Modes:")}
119
- ${c.cyan("gateway")} \u{1F310} Routes through ComputeSDK API (COMPUTESDK_API_KEY)
120
- ${c.cyan("e2b, railway, etc.")} \u{1F517} Direct connection to provider (requires provider package)
121
-
122
- ${c.bold("Running Commands:")}
123
- Just type any ${c.cyan("@computesdk/cmd")} function:
124
- ${c.dim('npm.install("express")')}
125
- ${c.dim('git.clone("https://github.com/user/repo")')}
126
- ${c.dim('python("script.py")')}
127
- ${c.dim('mkdir("/app/src")')}
128
- ${c.dim('ls("/home")')}
129
-
130
- ${c.green("\u2728 Tab autocomplete works for all functions!")}
131
-
132
- ${c.bold("Background Execution:")}
133
- Run commands in the background (returns immediately):
134
- ${c.dim('sh("sleep 10", { background: true })')}
135
- ${c.dim('sh("npm start", { background: true })')}
136
-
137
- ${c.bold("Examples:")}
138
- ${c.dim("# Install a package")}
139
- ${c.cyan('npm.install("express")')}
140
-
141
- ${c.dim("# Clone a repo")}
142
- ${c.cyan('git.clone("https://github.com/user/repo")')}
143
-
144
- ${c.dim("# Run Python code")}
145
- ${c.cyan(`python("-c", "print('hello')")`)}
146
-
147
- ${c.dim("# Start a server in background")}
148
- ${c.cyan('sh("python -m http.server 8000", { background: true })')}
149
-
150
- ${c.dim("# Switch providers")}
151
- ${c.cyan("provider railway")}
152
- `);
153
- }
154
109
  function showInfo(state) {
155
110
  if (!state.currentSandbox) {
156
111
  console.log(c.yellow("\nNo active sandbox\n"));
@@ -209,6 +164,64 @@ function logSuccess(message, duration) {
209
164
  const durationStr = duration ? ` (${formatDuration(duration)})` : "";
210
165
  console.log(c.green(`\u2705 ${message}${durationStr}`));
211
166
  }
167
+ function showHelp() {
168
+ console.log(`
169
+ ${c.bold("ComputeSDK Workbench Commands")}
170
+
171
+ ${c.bold("Provider Management:")}
172
+ ${c.cyan("provider <name>")} Switch to provider via gateway (default)
173
+ ${c.dim("Example: provider e2b")}
174
+ ${c.cyan("provider direct <name>")} Connect directly to provider
175
+ ${c.dim("Example: provider direct e2b")}
176
+ ${c.cyan("providers")} List all providers with status
177
+ ${c.cyan("mode <gateway|direct>")} Toggle default mode for next sandbox
178
+
179
+ ${c.bold("Provider Modes:")}
180
+ ${c.bold("Gateway (default)")}: Routes through ComputeSDK API, zero-config
181
+ \u2022 Requires: COMPUTESDK_API_KEY + provider credentials
182
+ \u2022 Usage: ${c.cyan("provider e2b")}
183
+
184
+ ${c.bold("Direct")}: Connects directly to provider, requires packages
185
+ \u2022 Requires: Provider package installed + credentials
186
+ \u2022 Usage: ${c.cyan("provider direct e2b")}
187
+
188
+ ${c.bold("Sandbox Management:")}
189
+ ${c.cyan("restart")} Restart current sandbox
190
+ ${c.cyan("destroy")} Destroy current sandbox
191
+ ${c.cyan("info")} Show sandbox info (provider, uptime)
192
+
193
+ ${c.bold("Environment:")}
194
+ ${c.cyan("env")} Show environment/credentials status
195
+ ${c.cyan("verbose")} Toggle verbose output mode
196
+
197
+ ${c.bold("Help:")}
198
+ ${c.cyan("help")} Show this help message
199
+ ${c.cyan("exit")} or ${c.cyan(".exit")} Exit workbench
200
+
201
+ ${c.bold("Running Commands:")}
202
+ Type any @computesdk/cmd function and it will run automatically:
203
+
204
+ ${c.dim("Package Managers:")}
205
+ ${c.cyan('npm.install("express")')}
206
+ ${c.cyan('pip.install("requests")')}
207
+
208
+ ${c.dim("Git:")}
209
+ ${c.cyan('git.clone("https://github.com/user/repo")')}
210
+ ${c.cyan("git.status()")}
211
+
212
+ ${c.dim("Filesystem:")}
213
+ ${c.cyan('ls("/home")')}
214
+ ${c.cyan('cat("/etc/hosts")')}
215
+ ${c.cyan('rm.rf("/tmp")')} ${c.dim("// Force remove")}
216
+ ${c.cyan('rm.auto("/path")')} ${c.dim("// Smart remove")}
217
+
218
+ ${c.bold("Background Execution:")}
219
+ ${c.cyan('sh("npm start", { background: true })')}
220
+ ${c.cyan('sh("python -m http.server 8000", { background: true })')}
221
+
222
+ ${c.dim("Press Tab for autocomplete on all commands!")}
223
+ `);
224
+ }
212
225
  function logError(message) {
213
226
  console.log(c.red(`\u274C ${message}`));
214
227
  }
@@ -390,12 +403,44 @@ async function loadProvider(providerName) {
390
403
  }
391
404
  function getProviderConfig(providerName) {
392
405
  const config = {};
393
- const requiredVars = PROVIDER_ENV_VARS[providerName];
394
- for (const varName of requiredVars) {
395
- const value = process.env[varName];
396
- if (value) {
397
- config[varName] = value;
398
- }
406
+ switch (providerName) {
407
+ case "e2b":
408
+ if (process.env.E2B_API_KEY) config.apiKey = process.env.E2B_API_KEY;
409
+ break;
410
+ case "railway":
411
+ if (process.env.RAILWAY_API_KEY) config.apiKey = process.env.RAILWAY_API_KEY;
412
+ if (process.env.RAILWAY_PROJECT_ID) config.projectId = process.env.RAILWAY_PROJECT_ID;
413
+ if (process.env.RAILWAY_ENVIRONMENT_ID) config.environmentId = process.env.RAILWAY_ENVIRONMENT_ID;
414
+ break;
415
+ case "daytona":
416
+ if (process.env.DAYTONA_API_KEY) config.apiKey = process.env.DAYTONA_API_KEY;
417
+ break;
418
+ case "modal":
419
+ if (process.env.MODAL_TOKEN_ID) config.tokenId = process.env.MODAL_TOKEN_ID;
420
+ if (process.env.MODAL_TOKEN_SECRET) config.tokenSecret = process.env.MODAL_TOKEN_SECRET;
421
+ break;
422
+ case "runloop":
423
+ if (process.env.RUNLOOP_API_KEY) config.apiKey = process.env.RUNLOOP_API_KEY;
424
+ break;
425
+ case "vercel":
426
+ if (process.env.VERCEL_TOKEN) config.token = process.env.VERCEL_TOKEN;
427
+ if (process.env.VERCEL_TEAM_ID) config.teamId = process.env.VERCEL_TEAM_ID;
428
+ if (process.env.VERCEL_PROJECT_ID) config.projectId = process.env.VERCEL_PROJECT_ID;
429
+ break;
430
+ case "cloudflare":
431
+ if (process.env.CLOUDFLARE_API_TOKEN) config.apiToken = process.env.CLOUDFLARE_API_TOKEN;
432
+ if (process.env.CLOUDFLARE_ACCOUNT_ID) config.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
433
+ break;
434
+ case "codesandbox":
435
+ if (process.env.CSB_API_KEY) config.apiKey = process.env.CSB_API_KEY;
436
+ break;
437
+ case "blaxel":
438
+ if (process.env.BL_API_KEY) config.apiKey = process.env.BL_API_KEY;
439
+ if (process.env.BL_WORKSPACE) config.workspace = process.env.BL_WORKSPACE;
440
+ break;
441
+ case "gateway":
442
+ if (process.env.COMPUTESDK_API_KEY) config.apiKey = process.env.COMPUTESDK_API_KEY;
443
+ break;
399
444
  }
400
445
  return config;
401
446
  }
@@ -423,23 +468,36 @@ async function ensureSandbox(state) {
423
468
  await createSandbox(state);
424
469
  }
425
470
  async function createSandbox(state) {
426
- const providerName = state.currentProvider || autoDetectProvider(state.forceGatewayMode);
471
+ const providerName = state.currentProvider || autoDetectProvider(false);
472
+ const useDirect = state.useDirectMode;
427
473
  if (!providerName) {
428
474
  logError('No provider configured. Run "env" to see setup instructions.');
429
475
  throw new Error("No provider available");
430
476
  }
431
- if (!isProviderReady(providerName)) {
432
- logError(`Provider ${providerName} is not fully configured.`);
433
- console.log(getProviderSetupHelp(providerName));
434
- throw new Error("Provider not ready");
477
+ let modeLabel;
478
+ let actualProviderName;
479
+ if (useDirect) {
480
+ modeLabel = `${providerName} (direct)`;
481
+ actualProviderName = providerName;
482
+ if (!isProviderReady(providerName)) {
483
+ logError(`Provider ${providerName} is not fully configured for direct mode.`);
484
+ console.log(getProviderSetupHelp(providerName));
485
+ throw new Error("Provider not ready");
486
+ }
487
+ } else {
488
+ modeLabel = `${providerName} (via gateway)`;
489
+ actualProviderName = "gateway";
490
+ if (!isProviderReady("gateway")) {
491
+ logError("Gateway mode requires COMPUTESDK_API_KEY");
492
+ console.log(getProviderSetupHelp("gateway"));
493
+ throw new Error("Gateway not ready");
494
+ }
435
495
  }
436
- const spinner = new Spinner(`Creating sandbox with ${providerName}...`).start();
496
+ const spinner = new Spinner(`Creating sandbox with ${modeLabel}...`).start();
437
497
  const startTime = Date.now();
438
498
  try {
439
499
  let compute;
440
- if (providerName === "gateway") {
441
- compute = createCompute();
442
- } else {
500
+ if (useDirect) {
443
501
  const providerModule = await loadProvider(providerName);
444
502
  const providerFactory = providerModule[providerName];
445
503
  if (!providerFactory) {
@@ -449,6 +507,43 @@ async function createSandbox(state) {
449
507
  compute = createCompute({
450
508
  defaultProvider: providerFactory(config)
451
509
  });
510
+ } else {
511
+ const gatewayModule = await import("computesdk");
512
+ const gatewayFactory = gatewayModule.gateway;
513
+ const providerConfig = getProviderConfig(providerName);
514
+ const providerHeaders = {};
515
+ switch (providerName) {
516
+ case "e2b":
517
+ if (providerConfig.apiKey) providerHeaders["X-E2B-API-Key"] = providerConfig.apiKey;
518
+ break;
519
+ case "railway":
520
+ if (providerConfig.apiKey) providerHeaders["X-Railway-API-Key"] = providerConfig.apiKey;
521
+ if (providerConfig.projectId) providerHeaders["X-Railway-Project-ID"] = providerConfig.projectId;
522
+ if (providerConfig.environmentId) providerHeaders["X-Railway-Environment-ID"] = providerConfig.environmentId;
523
+ break;
524
+ case "daytona":
525
+ if (providerConfig.apiKey) providerHeaders["X-Daytona-API-Key"] = providerConfig.apiKey;
526
+ break;
527
+ case "modal":
528
+ if (providerConfig.tokenId) providerHeaders["X-Modal-Token-ID"] = providerConfig.tokenId;
529
+ if (providerConfig.tokenSecret) providerHeaders["X-Modal-Token-Secret"] = providerConfig.tokenSecret;
530
+ break;
531
+ case "vercel":
532
+ if (providerConfig.token) providerHeaders["X-Vercel-Token"] = providerConfig.token;
533
+ if (providerConfig.teamId) providerHeaders["X-Vercel-Team-ID"] = providerConfig.teamId;
534
+ if (providerConfig.projectId) providerHeaders["X-Vercel-Project-ID"] = providerConfig.projectId;
535
+ break;
536
+ }
537
+ const config = {
538
+ apiKey: process.env.COMPUTESDK_API_KEY,
539
+ provider: providerName,
540
+ // Tell gateway which backend to use
541
+ providerHeaders
542
+ // Pass provider credentials via headers
543
+ };
544
+ compute = createCompute({
545
+ defaultProvider: gatewayFactory(config)
546
+ });
452
547
  }
453
548
  const result = await compute.sandbox.create();
454
549
  const duration = Date.now() - startTime;
@@ -463,6 +558,12 @@ async function createSandbox(state) {
463
558
  Install it with: ${c.cyan(`npm install @computesdk/${providerName}`)}
464
559
  `);
465
560
  }
561
+ if (error instanceof Error) {
562
+ logError(`Error: ${error.message}`);
563
+ if (error.stack) {
564
+ console.log(c.dim(error.stack));
565
+ }
566
+ }
466
567
  throw error;
467
568
  }
468
569
  }
@@ -518,68 +619,100 @@ async function runCommand(state, command) {
518
619
  throw error;
519
620
  }
520
621
  }
521
- async function switchProvider(state, newProvider) {
522
- if (!isValidProvider(newProvider)) {
523
- logError(`Unknown provider: ${newProvider}`);
622
+ async function switchProvider(state, mode, providerName) {
623
+ let useDirect = false;
624
+ let actualProvider = mode;
625
+ if (mode === "direct") {
626
+ if (!providerName) {
627
+ logError("Usage: provider direct <name>");
628
+ console.log("Example: provider direct e2b");
629
+ return;
630
+ }
631
+ useDirect = true;
632
+ actualProvider = providerName;
633
+ } else if (mode === "gateway") {
634
+ if (!providerName) {
635
+ logError("Usage: provider gateway <name>");
636
+ console.log("Example: provider gateway e2b");
637
+ return;
638
+ }
639
+ useDirect = false;
640
+ actualProvider = providerName;
641
+ }
642
+ if (actualProvider === "gateway") {
643
+ actualProvider = autoDetectProvider(false) || "e2b";
644
+ }
645
+ if (!isValidProvider(actualProvider)) {
646
+ logError(`Unknown provider: ${actualProvider}`);
524
647
  console.log(`Available providers: e2b, railway, daytona, modal, runloop, vercel, cloudflare, codesandbox, blaxel`);
525
648
  return;
526
649
  }
527
- if (!isProviderReady(newProvider)) {
528
- logError(`Provider ${newProvider} is not fully configured.`);
529
- console.log(getProviderSetupHelp(newProvider));
650
+ if (!useDirect && !isProviderReady("gateway")) {
651
+ logError("Gateway mode requires COMPUTESDK_API_KEY");
652
+ console.log(getProviderSetupHelp("gateway"));
653
+ return;
654
+ }
655
+ if (useDirect && !isProviderReady(actualProvider)) {
656
+ logError(`Provider ${actualProvider} is not fully configured for direct mode.`);
657
+ console.log(getProviderSetupHelp(actualProvider));
530
658
  return;
531
659
  }
532
660
  if (hasSandbox(state)) {
533
661
  const shouldDestroy = await confirm("Destroy current sandbox?");
534
662
  if (shouldDestroy) {
535
663
  await destroySandbox(state);
536
- state.currentProvider = newProvider;
537
- logSuccess(`Switched to ${newProvider}`);
664
+ state.currentProvider = actualProvider;
665
+ state.useDirectMode = useDirect;
666
+ const modeStr = useDirect ? `${actualProvider} (direct)` : `${actualProvider} (via gateway)`;
667
+ logSuccess(`Switched to ${modeStr}`);
538
668
  } else {
539
669
  logWarning("Keeping current sandbox. Provider remains unchanged.");
540
670
  }
541
671
  } else {
542
- state.currentProvider = newProvider;
543
- logSuccess(`Switched to ${newProvider}`);
672
+ state.currentProvider = actualProvider;
673
+ state.useDirectMode = useDirect;
674
+ const modeStr = useDirect ? `${actualProvider} (direct)` : `${actualProvider} (via gateway)`;
675
+ logSuccess(`Switched to ${modeStr}`);
544
676
  }
545
677
  }
546
678
  function createProviderCommand(state) {
547
- return async function provider(name) {
548
- if (!name) {
679
+ return async function provider(mode, providerName) {
680
+ if (!mode) {
549
681
  if (state.currentProvider) {
682
+ const modeStr = state.useDirectMode ? "direct" : "via gateway";
550
683
  console.log(`
551
- Current provider: ${c.green(state.currentProvider)}
684
+ Current provider: ${c.green(state.currentProvider)} (${modeStr})
552
685
  `);
553
686
  } else {
554
687
  console.log(c.yellow("\nNo provider selected\n"));
555
688
  }
556
689
  return;
557
690
  }
558
- await switchProvider(state, name);
691
+ await switchProvider(state, mode, providerName);
559
692
  };
560
693
  }
561
694
  async function toggleMode(state, mode) {
562
- const newMode = mode || (state.forceGatewayMode ? "direct" : "gateway");
563
- if (newMode === "gateway") {
564
- state.forceGatewayMode = true;
565
- logSuccess("Switched to gateway mode \u{1F310}");
566
- console.log(c.dim("Next sandbox will use gateway (requires COMPUTESDK_API_KEY)\n"));
567
- if (hasSandbox(state) && state.currentProvider !== "gateway") {
568
- console.log(c.yellow("Current sandbox is in direct mode."));
569
- console.log(c.dim('Run "restart" to switch to gateway mode\n'));
570
- }
571
- } else {
572
- state.forceGatewayMode = false;
695
+ const newMode = mode || (state.useDirectMode ? "gateway" : "direct");
696
+ if (newMode === "direct") {
697
+ state.useDirectMode = true;
573
698
  logSuccess("Switched to direct mode \u{1F517}");
574
699
  console.log(c.dim("Next sandbox will use direct provider packages\n"));
575
- if (hasSandbox(state) && state.currentProvider === "gateway") {
700
+ if (hasSandbox(state) && !state.useDirectMode) {
576
701
  console.log(c.yellow("Current sandbox is in gateway mode."));
577
702
  console.log(c.dim('Run "restart" to switch to direct mode\n'));
578
703
  }
704
+ } else {
705
+ state.useDirectMode = false;
706
+ logSuccess("Switched to gateway mode \u{1F310}");
707
+ console.log(c.dim("Next sandbox will use gateway (requires COMPUTESDK_API_KEY)\n"));
708
+ if (hasSandbox(state) && state.useDirectMode) {
709
+ console.log(c.yellow("Current sandbox is in direct mode."));
710
+ console.log(c.dim('Run "restart" to switch to gateway mode\n'));
711
+ }
579
712
  }
580
713
  }
581
714
  function showMode(state) {
582
- const mode = state.forceGatewayMode || state.currentProvider === "gateway" ? "gateway" : "direct";
715
+ const mode = state.useDirectMode ? "direct" : "gateway";
583
716
  const icon = mode === "gateway" ? "\u{1F310}" : "\u{1F517}";
584
717
  console.log(`
585
718
  Current mode: ${c.green(mode)} ${icon}`);
@@ -589,7 +722,7 @@ Current mode: ${c.green(mode)} ${icon}`);
589
722
  console.log(c.dim("Direct connection to providers (requires provider packages)"));
590
723
  }
591
724
  console.log(`
592
- Toggle with: ${c.cyan("mode gateway")} or ${c.cyan("mode direct")}
725
+ Switch with: ${c.cyan("provider e2b")} (gateway) or ${c.cyan("provider direct e2b")} (direct)
593
726
  `);
594
727
  }
595
728
  function toggleVerbose(state) {
@@ -764,10 +897,18 @@ function setupSmartEvaluator(replServer, state) {
764
897
  const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose"]);
765
898
  replServer.eval = function(cmd3, context, filename, callback) {
766
899
  const trimmedCmd = cmd3.trim();
767
- const providerMatch = trimmedCmd.match(/^provider\s+(\w+)$/);
900
+ const providerMatch = trimmedCmd.match(/^provider(?:\s+(direct|gateway))?\s+(\w+)$/);
768
901
  if (providerMatch) {
769
- const providerName = providerMatch[1];
770
- const providerCmd = `await provider('${providerName}')`;
902
+ const mode = providerMatch[1] || null;
903
+ const providerName = providerMatch[2];
904
+ const providerCmd = mode ? `await provider('${mode}', '${providerName}')` : `await provider('${providerName}')`;
905
+ originalEval.call(this, providerCmd, context, filename, callback);
906
+ return;
907
+ }
908
+ const providerOnlyMatch = trimmedCmd.match(/^provider\s+(direct|gateway)$/);
909
+ if (providerOnlyMatch) {
910
+ const mode = providerOnlyMatch[1];
911
+ const providerCmd = `await provider('${mode}')`;
771
912
  originalEval.call(this, providerCmd, context, filename, callback);
772
913
  return;
773
914
  }
@@ -823,12 +964,17 @@ function setupAutocomplete(replServer, state) {
823
964
  };
824
965
  replServer.completer = function(line, callback) {
825
966
  const trimmed = line.trim();
826
- if (!trimmed.includes(" ") && !trimmed.includes(".")) {
967
+ if (!line.includes(" ") && !line.includes(".")) {
827
968
  const commands = Object.keys(workbenchCommands);
828
969
  const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
829
970
  if (originalCompleter) {
830
- originalCompleter.call(replServer, line, (err, [contextHits, partial]) => {
831
- if (err) {
971
+ originalCompleter.call(replServer, line, (err, result) => {
972
+ if (err || !result) {
973
+ callback(null, [hits, trimmed]);
974
+ return;
975
+ }
976
+ const [contextHits, partial] = result;
977
+ if (!Array.isArray(contextHits)) {
832
978
  callback(null, [hits, trimmed]);
833
979
  return;
834
980
  }
@@ -840,18 +986,25 @@ function setupAutocomplete(replServer, state) {
840
986
  callback(null, [hits.length ? hits : commands, trimmed]);
841
987
  return;
842
988
  }
843
- const parts = trimmed.split(/\s+/);
844
- if (parts.length === 2 && !trimmed.includes(".")) {
845
- const [command, partial] = parts;
989
+ if (line.includes(" ") && !line.includes(".")) {
990
+ const parts = line.split(" ");
991
+ const command = parts[0].trim();
992
+ const partial = parts.slice(1).join(" ").trim();
846
993
  const suggestions = workbenchCommands[command];
847
994
  if (suggestions && suggestions.length > 0) {
848
995
  const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
849
- callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), trimmed]);
996
+ callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
850
997
  return;
851
998
  }
852
999
  }
853
1000
  if (originalCompleter) {
854
- originalCompleter.call(replServer, line, callback);
1001
+ originalCompleter.call(replServer, line, (err, result) => {
1002
+ if (err || !result) {
1003
+ callback(null, [[], line]);
1004
+ return;
1005
+ }
1006
+ callback(null, result);
1007
+ });
855
1008
  } else {
856
1009
  callback(null, [[], line]);
857
1010
  }
@@ -869,8 +1022,16 @@ function setupHistory(replServer) {
869
1022
  async function startWorkbench() {
870
1023
  const state = createState();
871
1024
  state.availableProviders = getAvailableProviders();
872
- state.currentProvider = autoDetectProvider();
873
- showWelcome(state.availableProviders, state.currentProvider);
1025
+ const detectedProvider = autoDetectProvider();
1026
+ if (detectedProvider === "gateway") {
1027
+ const backendProviders = state.availableProviders.filter((p) => p !== "gateway");
1028
+ state.currentProvider = backendProviders[0] || "e2b";
1029
+ state.useDirectMode = false;
1030
+ } else {
1031
+ state.currentProvider = detectedProvider;
1032
+ state.useDirectMode = false;
1033
+ }
1034
+ showWelcome(state.availableProviders, state.currentProvider, state.useDirectMode);
874
1035
  const replServer = createREPL(state);
875
1036
  replServer.on("exit", async () => {
876
1037
  await cleanupOnExit(state, replServer);