@computesdk/workbench 3.1.3 → 3.1.5
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 +61 -0
- package/dist/bin/workbench.js +308 -47
- package/dist/bin/workbench.js.map +1 -1
- package/dist/index.js +308 -47
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -316,6 +316,67 @@ BL_WORKSPACE=xxx
|
|
|
316
316
|
- **Auto-create**: First command automatically creates a sandbox
|
|
317
317
|
- **Stay in context**: Workbench maintains "current sandbox" - no IDs to track
|
|
318
318
|
|
|
319
|
+
## Debugging SDK Tests
|
|
320
|
+
|
|
321
|
+
The workbench is a full Node.js REPL with the ComputeSDK pre-loaded. You can reproduce any SDK test by calling the same methods interactively.
|
|
322
|
+
|
|
323
|
+
### SDK to Workbench Mapping
|
|
324
|
+
|
|
325
|
+
| SDK Test Code | Workbench Equivalent |
|
|
326
|
+
|---------------|---------------------|
|
|
327
|
+
| `sandbox.runCommand('echo hi')` | `runCommand('echo hi')` or `getInstance().runCommand(...)` |
|
|
328
|
+
| `sandbox.terminal.create()` | `terminal.create()` |
|
|
329
|
+
| `sandbox.filesystem.readFile(path)` | `filesystem.readFile(path)` |
|
|
330
|
+
| `sandbox.getInfo()` | `sandboxInfo()` |
|
|
331
|
+
| `sandbox.getUrl({ port })` | `getUrl({ port: 3000 })` |
|
|
332
|
+
|
|
333
|
+
### Example: Reproducing a Failing Test
|
|
334
|
+
|
|
335
|
+
If this test fails:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// From provider-compatibility.test.ts
|
|
339
|
+
it('command with streaming callbacks', async () => {
|
|
340
|
+
let stdoutCalled = false;
|
|
341
|
+
const result = await sandbox.runCommand('echo "hello"', {
|
|
342
|
+
onStdout: () => { stdoutCalled = true; },
|
|
343
|
+
});
|
|
344
|
+
expect(stdoutCalled).toBe(true);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Reproduce it in workbench:
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
> ls('/home') // Auto-creates sandbox
|
|
352
|
+
> const sandbox = getInstance()
|
|
353
|
+
> let stdoutCalled = false
|
|
354
|
+
> const result = await sandbox.runCommand('echo "hello"', {
|
|
355
|
+
onStdout: (data) => { console.log('STDOUT:', data); stdoutCalled = true }
|
|
356
|
+
})
|
|
357
|
+
> stdoutCalled // Should be true
|
|
358
|
+
> result
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Verbose Mode
|
|
362
|
+
|
|
363
|
+
Enable verbose mode to see full response objects and WebSocket debug info:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
> verbose() // Toggle on - shows full results and WebSocket frames
|
|
367
|
+
> ls('/home')
|
|
368
|
+
> verbose() // Toggle off
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Direct Shell Commands
|
|
372
|
+
|
|
373
|
+
Prefix with `$` to run shell commands directly (bypasses `@computesdk/cmd`):
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
> $echo "hello" | tr 'a-z' 'A-Z'
|
|
377
|
+
> $for i in 1 2 3; do echo $i; done
|
|
378
|
+
```
|
|
379
|
+
|
|
319
380
|
## License
|
|
320
381
|
|
|
321
382
|
MIT
|
package/dist/bin/workbench.js
CHANGED
|
@@ -42,6 +42,7 @@ function createState() {
|
|
|
42
42
|
useDirectMode: false,
|
|
43
43
|
// Default to gateway mode
|
|
44
44
|
verbose: false,
|
|
45
|
+
// Enabled automatically for local provider
|
|
45
46
|
compute: null
|
|
46
47
|
};
|
|
47
48
|
}
|
|
@@ -118,21 +119,27 @@ __export(output_exports, {
|
|
|
118
119
|
showInfo: () => showInfo,
|
|
119
120
|
showWelcome: () => showWelcome
|
|
120
121
|
});
|
|
121
|
-
function showWelcome(availableProviders, currentProvider, useDirectMode) {
|
|
122
|
+
function showWelcome(availableProviders, currentProvider, useDirectMode, localDaemonRunning = false) {
|
|
122
123
|
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")));
|
|
123
124
|
console.log(c.bold(c.cyan("\u2551 ComputeSDK Workbench \u2551")));
|
|
124
125
|
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")));
|
|
125
126
|
console.log(c.dim("Prompt shows connection status: > (disconnected) or provider:sandbox> (connected)\n"));
|
|
127
|
+
if (localDaemonRunning) {
|
|
128
|
+
console.log(c.green("Local daemon detected - auto-connecting...\n"));
|
|
129
|
+
}
|
|
126
130
|
if (availableProviders.length > 0) {
|
|
127
131
|
const backendProviders = availableProviders.filter((p) => p !== "gateway");
|
|
128
132
|
console.log(`Providers available: ${backendProviders.join(", ")}`);
|
|
129
133
|
if (currentProvider) {
|
|
130
|
-
if (
|
|
131
|
-
console.log(`Current provider: ${c.green(
|
|
134
|
+
if (currentProvider === "local") {
|
|
135
|
+
console.log(`Current provider: ${c.green("local")} (local daemon)
|
|
136
|
+
`);
|
|
137
|
+
} else if (useDirectMode) {
|
|
138
|
+
console.log(`Current provider: ${c.green(currentProvider)} (direct mode)
|
|
132
139
|
`);
|
|
133
140
|
} else {
|
|
134
141
|
const backendProvider = currentProvider === "gateway" ? backendProviders[0] || "auto" : currentProvider;
|
|
135
|
-
console.log(`Current provider: ${c.green(backendProvider)} (
|
|
142
|
+
console.log(`Current provider: ${c.green(backendProvider)} (via gateway)
|
|
136
143
|
`);
|
|
137
144
|
}
|
|
138
145
|
} else {
|
|
@@ -141,7 +148,7 @@ ${c.dim('Tip: Use "provider <name>" to select a provider')}
|
|
|
141
148
|
`);
|
|
142
149
|
}
|
|
143
150
|
} else {
|
|
144
|
-
console.log(c.yellow("
|
|
151
|
+
console.log(c.yellow("No providers detected.\n"));
|
|
145
152
|
console.log("To get started:");
|
|
146
153
|
console.log(" 1. Copy .env.example to .env");
|
|
147
154
|
console.log(" 2. Add your provider credentials");
|
|
@@ -204,10 +211,14 @@ ${c.bold("Provider Modes:")}
|
|
|
204
211
|
${c.bold("Sandbox Management:")}
|
|
205
212
|
${c.cyan("restart")} Restart current sandbox
|
|
206
213
|
${c.cyan("destroy")} Destroy current sandbox
|
|
207
|
-
${c.cyan("connect <url> [token]")} Connect to existing sandbox via URL
|
|
208
|
-
${c.dim("Example: connect https://sandbox-123.localhost:8080")}
|
|
209
|
-
${c.dim("Example: connect https://sandbox-123.localhost:8080 your_token")}
|
|
210
214
|
${c.cyan("info")} Show sandbox info (provider, uptime)
|
|
215
|
+
${c.cyan("connect <url> [token]")} Connect to sandbox via URL
|
|
216
|
+
|
|
217
|
+
${c.bold("Local Daemon:")}
|
|
218
|
+
${c.cyan("provider local")} Connect to local daemon's main sandbox
|
|
219
|
+
${c.cyan("provider local list")} List local sandboxes
|
|
220
|
+
${c.cyan("provider local <subdomain>")} Connect to specific local sandbox
|
|
221
|
+
${c.dim("Example: provider local separate-snail-qkktux")}
|
|
211
222
|
|
|
212
223
|
${c.bold("Environment:")}
|
|
213
224
|
${c.cyan("env")} Show environment/credentials status
|
|
@@ -262,6 +273,24 @@ ${c.bold("Running Commands:")}
|
|
|
262
273
|
${c.cyan("sandboxInfo()")} ${c.dim("// Get sandbox details")}
|
|
263
274
|
${c.cyan("getInstance()")} ${c.dim("// Get native instance")}
|
|
264
275
|
|
|
276
|
+
${c.dim("Terminal (PTY & Exec):")}
|
|
277
|
+
${c.cyan("terminal.create({ pty: true })")} ${c.dim("// Create PTY terminal")}
|
|
278
|
+
${c.cyan("terminal.create({ pty: false })")} ${c.dim("// Create exec terminal")}
|
|
279
|
+
${c.cyan("terminal.list()")} ${c.dim("// List all terminals")}
|
|
280
|
+
${c.cyan("terminal.retrieve(id)")} ${c.dim("// Get terminal by ID")}
|
|
281
|
+
${c.cyan("terminal.destroy(id)")} ${c.dim("// Close terminal by ID")}
|
|
282
|
+
|
|
283
|
+
${c.dim("PTY Terminal:")}
|
|
284
|
+
${c.cyan("term = terminal.create({ pty: true })")}
|
|
285
|
+
${c.cyan('term.on("output", (data) => console.log(data))')}
|
|
286
|
+
${c.cyan('term.write("echo hello\\n")')}
|
|
287
|
+
${c.cyan("term.destroy()")}
|
|
288
|
+
|
|
289
|
+
${c.dim("Exec Terminal:")}
|
|
290
|
+
${c.cyan("exec = terminal.create({ pty: false })")}
|
|
291
|
+
${c.cyan('cmd = exec.command.run("ls -la")')}
|
|
292
|
+
${c.cyan("cmd.stdout")} ${c.dim("// view output")}
|
|
293
|
+
|
|
265
294
|
${c.dim('Note: No need to use "await" - promises are auto-awaited!')}
|
|
266
295
|
|
|
267
296
|
${c.dim("Compute CLI:")}
|
|
@@ -566,12 +595,15 @@ var commands_exports = {};
|
|
|
566
595
|
__export(commands_exports, {
|
|
567
596
|
cleanupOnExit: () => cleanupOnExit,
|
|
568
597
|
confirmSandboxSwitch: () => confirmSandboxSwitch,
|
|
598
|
+
connectToLocal: () => connectToLocal,
|
|
569
599
|
connectToSandbox: () => connectToSandbox,
|
|
570
600
|
createSandbox: () => createSandbox,
|
|
571
601
|
defineProviderCommand: () => defineProviderCommand,
|
|
572
602
|
destroySandbox: () => destroySandbox,
|
|
573
603
|
ensureSandbox: () => ensureSandbox,
|
|
574
604
|
getComputeInstance: () => getComputeInstance,
|
|
605
|
+
isLocalDaemonRunning: () => isLocalDaemonRunning,
|
|
606
|
+
listLocalSandboxes: () => listLocalSandboxes,
|
|
575
607
|
restartSandbox: () => restartSandbox,
|
|
576
608
|
runCommand: () => runCommand,
|
|
577
609
|
showMode: () => showMode,
|
|
@@ -582,31 +614,58 @@ __export(commands_exports, {
|
|
|
582
614
|
});
|
|
583
615
|
import { createCompute } from "@computesdk/provider";
|
|
584
616
|
import { escapeArgs } from "@computesdk/cmd";
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
input: process.stdin,
|
|
590
|
-
output: process.stdout
|
|
591
|
-
});
|
|
617
|
+
async function confirm(question, defaultYes = false, _state) {
|
|
618
|
+
const promptSuffix = defaultYes ? "(Y/n)" : "(y/N)";
|
|
619
|
+
process.stdout.write(`${question} ${promptSuffix}: `);
|
|
620
|
+
if (process.stdin.isPaused()) {
|
|
592
621
|
process.stdin.resume();
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
622
|
+
}
|
|
623
|
+
return new Promise((resolve) => {
|
|
624
|
+
const wasRaw = process.stdin.isRaw;
|
|
625
|
+
if (process.stdin.isTTY) {
|
|
626
|
+
process.stdin.setRawMode(true);
|
|
627
|
+
}
|
|
628
|
+
const cleanup = (restoreRaw) => {
|
|
629
|
+
process.stdin.removeListener("data", onData);
|
|
630
|
+
if (process.stdin.isTTY && restoreRaw) {
|
|
631
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
const onData = (key) => {
|
|
635
|
+
const char = key.toString();
|
|
636
|
+
if (char === "") {
|
|
637
|
+
process.stdout.write("^C\n");
|
|
638
|
+
cleanup(true);
|
|
639
|
+
resolve(false);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (char === "\r" || char === "\n") {
|
|
643
|
+
process.stdout.write(defaultYes ? "Y\n" : "N\n");
|
|
644
|
+
cleanup(true);
|
|
598
645
|
resolve(defaultYes);
|
|
599
|
-
|
|
600
|
-
resolve(trimmed === "y" || trimmed === "yes");
|
|
646
|
+
return;
|
|
601
647
|
}
|
|
602
|
-
|
|
648
|
+
if (char === "y" || char === "Y") {
|
|
649
|
+
process.stdout.write("y\n");
|
|
650
|
+
cleanup(true);
|
|
651
|
+
resolve(true);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (char === "n" || char === "N") {
|
|
655
|
+
process.stdout.write("n\n");
|
|
656
|
+
cleanup(true);
|
|
657
|
+
resolve(false);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
process.stdin.on("data", onData);
|
|
603
662
|
});
|
|
604
663
|
}
|
|
605
664
|
async function confirmSandboxSwitch(state) {
|
|
606
665
|
if (!hasSandbox(state)) {
|
|
607
666
|
return true;
|
|
608
667
|
}
|
|
609
|
-
return await confirm("Switch to new sandbox?", true);
|
|
668
|
+
return await confirm("Switch to new sandbox?", true, state);
|
|
610
669
|
}
|
|
611
670
|
async function ensureSandbox(state) {
|
|
612
671
|
if (hasSandbox(state)) {
|
|
@@ -768,6 +827,14 @@ function isStaleConnectionError(error) {
|
|
|
768
827
|
return stalePhrases.some((phrase) => message.includes(phrase));
|
|
769
828
|
}
|
|
770
829
|
async function switchProvider(state, mode, providerName) {
|
|
830
|
+
if (mode === "local") {
|
|
831
|
+
if (providerName === "list") {
|
|
832
|
+
await listLocalSandboxes();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
await connectToLocal(state, providerName);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
771
838
|
let useDirect = false;
|
|
772
839
|
let actualProvider = mode;
|
|
773
840
|
if (mode === "direct") {
|
|
@@ -792,7 +859,7 @@ async function switchProvider(state, mode, providerName) {
|
|
|
792
859
|
}
|
|
793
860
|
if (!isValidProvider(actualProvider)) {
|
|
794
861
|
logError(`Unknown provider: ${actualProvider}`);
|
|
795
|
-
console.log(`Available providers: e2b, railway, daytona, modal, runloop, vercel, cloudflare, codesandbox, blaxel`);
|
|
862
|
+
console.log(`Available providers: e2b, railway, daytona, modal, runloop, vercel, cloudflare, codesandbox, blaxel, local`);
|
|
796
863
|
return;
|
|
797
864
|
}
|
|
798
865
|
if (!useDirect && !isProviderReady("gateway")) {
|
|
@@ -806,7 +873,7 @@ async function switchProvider(state, mode, providerName) {
|
|
|
806
873
|
return;
|
|
807
874
|
}
|
|
808
875
|
if (hasSandbox(state)) {
|
|
809
|
-
const shouldDestroy = await confirm("Destroy current sandbox?");
|
|
876
|
+
const shouldDestroy = await confirm("Destroy current sandbox?", false, state);
|
|
810
877
|
if (shouldDestroy) {
|
|
811
878
|
await destroySandbox(state);
|
|
812
879
|
state.currentProvider = actualProvider;
|
|
@@ -829,7 +896,7 @@ function defineProviderCommand(state) {
|
|
|
829
896
|
return async function provider(mode, providerName) {
|
|
830
897
|
if (!mode) {
|
|
831
898
|
if (state.currentProvider) {
|
|
832
|
-
const modeStr = state.useDirectMode ? "direct" : "via gateway";
|
|
899
|
+
const modeStr = state.useDirectMode ? "direct" : state.currentProvider === "local" ? "local daemon" : "via gateway";
|
|
833
900
|
console.log(`
|
|
834
901
|
Current provider: ${c.green(state.currentProvider)} (${modeStr})
|
|
835
902
|
`);
|
|
@@ -907,8 +974,8 @@ async function connectToSandbox(state, sandboxUrl, token) {
|
|
|
907
974
|
}
|
|
908
975
|
const cleanUrl = sandboxUrl.replace(/\/$/, "");
|
|
909
976
|
if (hasSandbox(state)) {
|
|
910
|
-
const
|
|
911
|
-
if (!
|
|
977
|
+
const shouldDisconnect = await confirm("Disconnect from current sandbox?", false, state);
|
|
978
|
+
if (!shouldDisconnect) {
|
|
912
979
|
logWarning("Keeping current sandbox. Connection cancelled.");
|
|
913
980
|
return;
|
|
914
981
|
}
|
|
@@ -954,6 +1021,125 @@ async function connectToSandbox(state, sandboxUrl, token) {
|
|
|
954
1021
|
throw error;
|
|
955
1022
|
}
|
|
956
1023
|
}
|
|
1024
|
+
async function readLocalConfig() {
|
|
1025
|
+
const os2 = await import("os");
|
|
1026
|
+
const fs2 = await import("fs/promises");
|
|
1027
|
+
const path4 = await import("path");
|
|
1028
|
+
const configPath = path4.join(os2.homedir(), ".compute", "config.json");
|
|
1029
|
+
try {
|
|
1030
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
1031
|
+
return JSON.parse(content);
|
|
1032
|
+
} catch {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async function isLocalDaemonRunning() {
|
|
1037
|
+
const os2 = await import("os");
|
|
1038
|
+
const fs2 = await import("fs/promises");
|
|
1039
|
+
const path4 = await import("path");
|
|
1040
|
+
const pidPath = path4.join(os2.homedir(), ".compute", "compute.pid");
|
|
1041
|
+
try {
|
|
1042
|
+
const pidContent = await fs2.readFile(pidPath, "utf-8");
|
|
1043
|
+
const pid = parseInt(pidContent.trim(), 10);
|
|
1044
|
+
process.kill(pid, 0);
|
|
1045
|
+
return true;
|
|
1046
|
+
} catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
async function listLocalSandboxes() {
|
|
1051
|
+
const config2 = await readLocalConfig();
|
|
1052
|
+
if (!config2) {
|
|
1053
|
+
logError("No local daemon config found at ~/.compute/config.json");
|
|
1054
|
+
console.log(c.dim('Run "compute start" to start the local daemon'));
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const isRunning = await isLocalDaemonRunning();
|
|
1058
|
+
console.log("");
|
|
1059
|
+
console.log(c.bold("Local Daemon Status:"), isRunning ? c.green("Running") : c.red("Stopped"));
|
|
1060
|
+
console.log("");
|
|
1061
|
+
if (!config2.sandboxes || config2.sandboxes.length === 0) {
|
|
1062
|
+
console.log(c.dim("No sandboxes found"));
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
console.log(c.bold("Sandboxes:"));
|
|
1066
|
+
for (const sandbox of config2.sandboxes) {
|
|
1067
|
+
const isMain = sandbox.subdomain === config2.main_subdomain;
|
|
1068
|
+
const mainLabel = isMain ? c.green(" (main)") : "";
|
|
1069
|
+
console.log(` ${c.cyan(sandbox.subdomain)}${mainLabel}`);
|
|
1070
|
+
console.log(c.dim(` https://${sandbox.subdomain}.sandbox.computesdk.com`));
|
|
1071
|
+
}
|
|
1072
|
+
console.log("");
|
|
1073
|
+
console.log(c.dim(`Connect with: local ${config2.main_subdomain}`));
|
|
1074
|
+
console.log("");
|
|
1075
|
+
}
|
|
1076
|
+
async function connectToLocal(state, subdomain) {
|
|
1077
|
+
const config2 = await readLocalConfig();
|
|
1078
|
+
if (!config2) {
|
|
1079
|
+
logError("No local daemon config found at ~/.compute/config.json");
|
|
1080
|
+
console.log(c.dim('Run "compute start" to start the local daemon'));
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const isRunning = await isLocalDaemonRunning();
|
|
1084
|
+
if (!isRunning) {
|
|
1085
|
+
logError("Local daemon is not running");
|
|
1086
|
+
console.log(c.dim('Run "compute start" to start the local daemon'));
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const targetSubdomain = subdomain || config2.main_subdomain;
|
|
1090
|
+
const sandbox = config2.sandboxes.find((s) => s.subdomain === targetSubdomain);
|
|
1091
|
+
if (!sandbox) {
|
|
1092
|
+
logError(`Sandbox "${targetSubdomain}" not found`);
|
|
1093
|
+
console.log(c.dim('Run "local list" to see available sandboxes'));
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const sandboxUrl = `https://${targetSubdomain}.sandbox.computesdk.com`;
|
|
1097
|
+
const token = config2.access_token;
|
|
1098
|
+
if (hasSandbox(state)) {
|
|
1099
|
+
const shouldDisconnect = await confirm("Disconnect from current sandbox?", false, state);
|
|
1100
|
+
if (!shouldDisconnect) {
|
|
1101
|
+
logWarning("Keeping current sandbox. Connection cancelled.");
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
clearSandbox(state);
|
|
1105
|
+
}
|
|
1106
|
+
const spinner = new Spinner(`Connecting to local sandbox ${targetSubdomain}...`).start();
|
|
1107
|
+
const startTime = Date.now();
|
|
1108
|
+
try {
|
|
1109
|
+
const { Sandbox } = await import("computesdk");
|
|
1110
|
+
let WebSocket;
|
|
1111
|
+
try {
|
|
1112
|
+
const wsModule = await import("ws");
|
|
1113
|
+
WebSocket = wsModule.default;
|
|
1114
|
+
} catch {
|
|
1115
|
+
spinner.fail('Failed to import "ws" module');
|
|
1116
|
+
logError("Please install ws: pnpm add ws");
|
|
1117
|
+
throw new Error('Missing "ws" dependency');
|
|
1118
|
+
}
|
|
1119
|
+
const sandboxInstance = new Sandbox({
|
|
1120
|
+
sandboxUrl,
|
|
1121
|
+
sandboxId: targetSubdomain,
|
|
1122
|
+
provider: "local",
|
|
1123
|
+
token,
|
|
1124
|
+
WebSocket
|
|
1125
|
+
});
|
|
1126
|
+
const info = await sandboxInstance.getInfo();
|
|
1127
|
+
const duration = Date.now() - startTime;
|
|
1128
|
+
setSandbox(state, sandboxInstance, "local");
|
|
1129
|
+
state.verbose = true;
|
|
1130
|
+
spinner.succeed(`Connected to local sandbox ${c.dim(`(${formatDuration(duration)})`)}`);
|
|
1131
|
+
console.log(c.dim(`Sandbox: ${targetSubdomain}`));
|
|
1132
|
+
console.log(c.dim(`URL: ${sandboxUrl}`));
|
|
1133
|
+
console.log(c.dim(`Verbose mode: enabled (for debugging)`));
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
const duration = Date.now() - startTime;
|
|
1136
|
+
spinner.fail(`Failed to connect ${c.dim(`(${formatDuration(duration)})`)}`);
|
|
1137
|
+
if (error instanceof Error) {
|
|
1138
|
+
logError(`Error: ${error.message}`);
|
|
1139
|
+
}
|
|
1140
|
+
throw error;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
957
1143
|
async function cleanupOnExit(state, replServer) {
|
|
958
1144
|
if (!hasSandbox(state)) {
|
|
959
1145
|
return;
|
|
@@ -966,7 +1152,7 @@ async function cleanupOnExit(state, replServer) {
|
|
|
966
1152
|
logWarning("Disconnecting from external sandbox (not destroying).");
|
|
967
1153
|
return;
|
|
968
1154
|
}
|
|
969
|
-
const shouldDestroy = await confirm("Destroy active sandbox?");
|
|
1155
|
+
const shouldDestroy = await confirm("Destroy active sandbox?", false, state);
|
|
970
1156
|
if (shouldDestroy) {
|
|
971
1157
|
await destroySandbox(state);
|
|
972
1158
|
} else {
|
|
@@ -1097,7 +1283,6 @@ function injectCmdContext(replServer) {
|
|
|
1097
1283
|
replServer.context.sha256sum = cmd.sha256sum;
|
|
1098
1284
|
replServer.context.sha1sum = cmd.sha1sum;
|
|
1099
1285
|
replServer.context.compute = cmd.compute;
|
|
1100
|
-
replServer.context.cmd = cmd.cmd;
|
|
1101
1286
|
replServer.context.shell = cmd.shell;
|
|
1102
1287
|
replServer.context.sh = cmd.sh;
|
|
1103
1288
|
replServer.context.bash = cmd.bash;
|
|
@@ -1361,6 +1546,59 @@ function injectWorkbenchCommands(replServer, state) {
|
|
|
1361
1546
|
};
|
|
1362
1547
|
}
|
|
1363
1548
|
};
|
|
1549
|
+
replServer.context.terminal = {
|
|
1550
|
+
get create() {
|
|
1551
|
+
return async (options) => {
|
|
1552
|
+
const sandbox = state.currentSandbox;
|
|
1553
|
+
if (!sandbox) {
|
|
1554
|
+
throw new Error("No active sandbox. Run a command to auto-create one.");
|
|
1555
|
+
}
|
|
1556
|
+
const term = await sandbox.terminal.create(options);
|
|
1557
|
+
if (state.verbose) {
|
|
1558
|
+
if (term._ws) {
|
|
1559
|
+
term._ws.config.debug = true;
|
|
1560
|
+
}
|
|
1561
|
+
term.on("output", (data) => {
|
|
1562
|
+
console.log("[terminal:output]", JSON.stringify(data));
|
|
1563
|
+
});
|
|
1564
|
+
term.on("error", (error) => {
|
|
1565
|
+
console.log("[terminal:error]", error);
|
|
1566
|
+
});
|
|
1567
|
+
term.on("destroyed", () => {
|
|
1568
|
+
console.log("[terminal:destroyed]");
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
return term;
|
|
1572
|
+
};
|
|
1573
|
+
},
|
|
1574
|
+
get list() {
|
|
1575
|
+
return async () => {
|
|
1576
|
+
const sandbox = state.currentSandbox;
|
|
1577
|
+
if (!sandbox) {
|
|
1578
|
+
throw new Error("No active sandbox. Run a command to auto-create one.");
|
|
1579
|
+
}
|
|
1580
|
+
return sandbox.terminal.list();
|
|
1581
|
+
};
|
|
1582
|
+
},
|
|
1583
|
+
get retrieve() {
|
|
1584
|
+
return async (id) => {
|
|
1585
|
+
const sandbox = state.currentSandbox;
|
|
1586
|
+
if (!sandbox) {
|
|
1587
|
+
throw new Error("No active sandbox. Run a command to auto-create one.");
|
|
1588
|
+
}
|
|
1589
|
+
return sandbox.terminal.retrieve(id);
|
|
1590
|
+
};
|
|
1591
|
+
},
|
|
1592
|
+
get destroy() {
|
|
1593
|
+
return async (id) => {
|
|
1594
|
+
const sandbox = state.currentSandbox;
|
|
1595
|
+
if (!sandbox) {
|
|
1596
|
+
throw new Error("No active sandbox. Run a command to auto-create one.");
|
|
1597
|
+
}
|
|
1598
|
+
return sandbox.terminal.destroy(id);
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1364
1602
|
replServer.context.getInstance = () => {
|
|
1365
1603
|
const sandbox = state.currentSandbox;
|
|
1366
1604
|
if (!sandbox) {
|
|
@@ -1372,8 +1610,15 @@ function injectWorkbenchCommands(replServer, state) {
|
|
|
1372
1610
|
function setupSmartEvaluator(replServer, state) {
|
|
1373
1611
|
const originalEval = replServer.eval;
|
|
1374
1612
|
const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose", "sandboxInfo", "connect"]);
|
|
1375
|
-
replServer.eval = function(
|
|
1376
|
-
const trimmedCmd =
|
|
1613
|
+
replServer.eval = function(cmd2, context, filename, callback) {
|
|
1614
|
+
const trimmedCmd = cmd2.trim();
|
|
1615
|
+
const providerLocalMatch = trimmedCmd.match(/^provider\s+local(?:\s+(\S+))?$/);
|
|
1616
|
+
if (providerLocalMatch) {
|
|
1617
|
+
const arg = providerLocalMatch[1];
|
|
1618
|
+
const providerCmd = arg ? `await provider('local', '${arg}')` : `await provider('local')`;
|
|
1619
|
+
originalEval.call(this, providerCmd, context, filename, callback);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1377
1622
|
const providerMatch = trimmedCmd.match(/^provider(?:\s+(direct|gateway))?\s+(\w+)$/);
|
|
1378
1623
|
if (providerMatch) {
|
|
1379
1624
|
const mode = providerMatch[1] || null;
|
|
@@ -1407,7 +1652,7 @@ function setupSmartEvaluator(replServer, state) {
|
|
|
1407
1652
|
runCommand(state, command).then((output) => callback(null, output)).catch((error) => callback(error, void 0));
|
|
1408
1653
|
return;
|
|
1409
1654
|
}
|
|
1410
|
-
originalEval.call(this,
|
|
1655
|
+
originalEval.call(this, cmd2, context, filename, async (err, result) => {
|
|
1411
1656
|
if (err) {
|
|
1412
1657
|
callback(err, void 0);
|
|
1413
1658
|
return;
|
|
@@ -1446,8 +1691,8 @@ function setupSmartEvaluator(replServer, state) {
|
|
|
1446
1691
|
function setupAutocomplete(replServer, state) {
|
|
1447
1692
|
const originalCompleter = replServer.completer;
|
|
1448
1693
|
const workbenchCommands = {
|
|
1449
|
-
"provider": [...PROVIDER_NAMES],
|
|
1450
|
-
//
|
|
1694
|
+
"provider": [...PROVIDER_NAMES, "local"],
|
|
1695
|
+
// Include 'local' as a provider option
|
|
1451
1696
|
"mode": ["gateway", "direct"],
|
|
1452
1697
|
"providers": [],
|
|
1453
1698
|
"restart": [],
|
|
@@ -1472,7 +1717,7 @@ function setupAutocomplete(replServer, state) {
|
|
|
1472
1717
|
const trimmed = line.trim();
|
|
1473
1718
|
if (!line.includes(" ") && !line.includes(".")) {
|
|
1474
1719
|
const commands = Object.keys(workbenchCommands);
|
|
1475
|
-
const hits = commands.filter((
|
|
1720
|
+
const hits = commands.filter((cmd2) => cmd2.startsWith(trimmed));
|
|
1476
1721
|
if (originalCompleter) {
|
|
1477
1722
|
originalCompleter.call(replServer, line, (err, result) => {
|
|
1478
1723
|
if (err || !result) {
|
|
@@ -1552,22 +1797,38 @@ init_providers();
|
|
|
1552
1797
|
init_commands();
|
|
1553
1798
|
async function startWorkbench() {
|
|
1554
1799
|
const state = createState();
|
|
1800
|
+
const localDaemonRunning = await isLocalDaemonRunning();
|
|
1555
1801
|
state.availableProviders = getAvailableProviders();
|
|
1802
|
+
if (localDaemonRunning) {
|
|
1803
|
+
state.availableProviders.push("local");
|
|
1804
|
+
}
|
|
1556
1805
|
const detectedProvider = autoDetectProvider();
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
if (hasGateway && backendProviders.length > 0) {
|
|
1560
|
-
state.currentProvider = backendProviders[0] || "e2b";
|
|
1806
|
+
if (localDaemonRunning) {
|
|
1807
|
+
state.currentProvider = "local";
|
|
1561
1808
|
state.useDirectMode = false;
|
|
1562
|
-
|
|
1563
|
-
state.currentProvider = backendProviders[0];
|
|
1564
|
-
state.useDirectMode = true;
|
|
1809
|
+
state.verbose = true;
|
|
1565
1810
|
} else {
|
|
1566
|
-
|
|
1567
|
-
state.
|
|
1811
|
+
const hasGateway = state.availableProviders.includes("gateway");
|
|
1812
|
+
const backendProviders = state.availableProviders.filter((p) => p !== "gateway" && p !== "local");
|
|
1813
|
+
if (hasGateway && backendProviders.length > 0) {
|
|
1814
|
+
state.currentProvider = backendProviders[0] || "e2b";
|
|
1815
|
+
state.useDirectMode = false;
|
|
1816
|
+
} else if (backendProviders.length > 0) {
|
|
1817
|
+
state.currentProvider = backendProviders[0];
|
|
1818
|
+
state.useDirectMode = true;
|
|
1819
|
+
} else {
|
|
1820
|
+
state.currentProvider = detectedProvider;
|
|
1821
|
+
state.useDirectMode = false;
|
|
1822
|
+
}
|
|
1568
1823
|
}
|
|
1569
|
-
showWelcome(state.availableProviders, state.currentProvider, state.useDirectMode);
|
|
1824
|
+
showWelcome(state.availableProviders, state.currentProvider, state.useDirectMode, localDaemonRunning);
|
|
1570
1825
|
const replServer = createREPL(state);
|
|
1826
|
+
if (localDaemonRunning) {
|
|
1827
|
+
try {
|
|
1828
|
+
await connectToLocal(state);
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1571
1832
|
replServer.on("exit", async () => {
|
|
1572
1833
|
await cleanupOnExit(state, replServer);
|
|
1573
1834
|
process.exit(0);
|