@computesdk/workbench 2.0.1 → 3.0.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.
@@ -32,10 +32,26 @@ function setSandbox(state, sandbox, provider) {
32
32
  state.currentSandbox = sandbox;
33
33
  state.currentProvider = provider;
34
34
  state.sandboxCreatedAt = /* @__PURE__ */ new Date();
35
+ updatePromptIfNeeded(state);
35
36
  }
36
37
  function clearSandbox(state) {
37
38
  state.currentSandbox = null;
38
39
  state.sandboxCreatedAt = null;
40
+ updatePromptIfNeeded(state);
41
+ }
42
+ function updatePromptIfNeeded(state) {
43
+ if (state._replServer) {
44
+ const prompt = getPrompt(state);
45
+ state._replServer.setPrompt(prompt);
46
+ }
47
+ }
48
+ function getPrompt(state) {
49
+ if (!state.currentSandbox) {
50
+ return "> ";
51
+ }
52
+ const provider = state.currentProvider || "unknown";
53
+ const sandboxId = state.currentSandbox.sandboxId || "";
54
+ return `${provider}:${sandboxId}> `;
39
55
  }
40
56
  function hasSandbox(state) {
41
57
  return state.currentSandbox !== null;
@@ -91,6 +107,7 @@ function showWelcome(availableProviders, currentProvider, useDirectMode) {
91
107
  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")));
92
108
  console.log(c.bold(c.cyan("\u2551 ComputeSDK Workbench \u2551")));
93
109
  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")));
110
+ console.log(c.dim("Prompt shows connection status: > (disconnected) or provider:sandbox> (connected)\n"));
94
111
  if (availableProviders.length > 0) {
95
112
  const backendProviders = availableProviders.filter((p) => p !== "gateway");
96
113
  console.log(`Providers available: ${backendProviders.join(", ")}`);
@@ -124,7 +141,12 @@ function showInfo(state) {
124
141
  return;
125
142
  }
126
143
  console.log("\n" + c.bold("Current Sandbox:"));
144
+ if (state.currentSandbox.sandboxId) {
145
+ console.log(` Sandbox ID: ${c.cyan(state.currentSandbox.sandboxId)}`);
146
+ }
127
147
  console.log(` Provider: ${c.green(state.currentProvider || "unknown")}`);
148
+ const modeLabel = state.useDirectMode ? "direct \u{1F517}" : "gateway \u{1F310}";
149
+ console.log(` Mode: ${c.blue(modeLabel)}`);
128
150
  console.log(` Created: ${state.sandboxCreatedAt?.toLocaleString() || "unknown"}`);
129
151
  console.log(` Uptime: ${formatUptime(state)}`);
130
152
  console.log("");
@@ -221,12 +243,28 @@ ${c.bold("Running Commands:")}
221
243
  ${c.cyan('git.clone("https://github.com/user/repo")')}
222
244
  ${c.cyan("git.status()")}
223
245
 
224
- ${c.dim("Filesystem:")}
246
+ ${c.dim("Filesystem (via commands):")}
225
247
  ${c.cyan('ls("/home")')}
226
248
  ${c.cyan('cat("/etc/hosts")')}
227
249
  ${c.cyan('rm.rf("/tmp")')} ${c.dim("// Force remove")}
228
250
  ${c.cyan('rm.auto("/path")')} ${c.dim("// Smart remove")}
229
251
 
252
+ ${c.dim("Filesystem (direct API):")}
253
+ ${c.cyan('filesystem.readFile("/etc/hosts")')}
254
+ ${c.cyan('filesystem.writeFile("/app/server.js", code)')}
255
+ ${c.cyan('filesystem.mkdir("/app")')}
256
+ ${c.cyan('filesystem.readdir("/home")')}
257
+ ${c.cyan('filesystem.exists("/path")')}
258
+ ${c.cyan('filesystem.remove("/file")')}
259
+
260
+ ${c.dim("Sandbox Methods:")}
261
+ ${c.cyan("getUrl({ port: 3000 })")} ${c.dim("// Get public URL")}
262
+ ${c.cyan(`runCode("console.log('hi')", "node")`)}
263
+ ${c.cyan("sandboxInfo()")} ${c.dim("// Get sandbox details")}
264
+ ${c.cyan("getInstance()")} ${c.dim("// Get native instance")}
265
+
266
+ ${c.dim('Note: No need to use "await" - promises are auto-awaited!')}
267
+
230
268
  ${c.dim("Compute CLI:")}
231
269
  ${c.cyan("compute.isSetup()")} ${c.dim("// Check if daemon is running")}
232
270
  ${c.cyan("compute.setup()")} ${c.dim("// Install + start daemon")}
@@ -256,47 +294,62 @@ function logWarning(message) {
256
294
  }
257
295
 
258
296
  // src/cli/providers.ts
297
+ import {
298
+ PROVIDER_AUTH as SHARED_PROVIDER_AUTH,
299
+ PROVIDER_NAMES as SHARED_PROVIDER_NAMES,
300
+ getProviderConfigFromEnv
301
+ } from "computesdk";
259
302
  var PROVIDER_NAMES = [
260
303
  "gateway",
261
- "e2b",
262
- "railway",
263
- "daytona",
264
- "modal",
265
- "runloop",
266
- "vercel",
267
- "cloudflare",
268
- "codesandbox",
269
- "blaxel"
304
+ ...SHARED_PROVIDER_NAMES
270
305
  ];
271
- var PROVIDER_ENV_VARS = {
272
- gateway: ["COMPUTESDK_API_KEY"],
273
- e2b: ["E2B_API_KEY"],
274
- railway: ["RAILWAY_API_KEY", "RAILWAY_PROJECT_ID", "RAILWAY_ENVIRONMENT_ID"],
275
- daytona: ["DAYTONA_API_KEY"],
276
- modal: ["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"],
277
- runloop: ["RUNLOOP_API_KEY"],
278
- vercel: ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"],
279
- cloudflare: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
280
- codesandbox: ["CSB_API_KEY"],
281
- blaxel: ["BL_API_KEY", "BL_WORKSPACE"]
306
+ var PROVIDER_AUTH = {
307
+ gateway: [["COMPUTESDK_API_KEY"]],
308
+ ...SHARED_PROVIDER_AUTH
282
309
  };
283
310
  function getProviderStatus(provider) {
311
+ const authOptions = PROVIDER_AUTH[provider];
284
312
  if (typeof process === "undefined") {
285
313
  return {
286
314
  name: provider,
287
315
  isComplete: false,
288
316
  present: [],
289
- missing: [...PROVIDER_ENV_VARS[provider]]
317
+ missing: [...authOptions[0]]
290
318
  };
291
319
  }
292
- const requiredVars = PROVIDER_ENV_VARS[provider];
293
- const present = requiredVars.filter((varName) => !!process.env?.[varName]);
294
- const missing = requiredVars.filter((varName) => !process.env?.[varName]);
320
+ const allVars = new Set(authOptions.flat());
321
+ const presentSet = /* @__PURE__ */ new Set();
322
+ for (const v of allVars) {
323
+ if (process.env?.[v]) presentSet.add(v);
324
+ }
325
+ let bestOption = null;
326
+ for (const option of authOptions) {
327
+ const missing = [];
328
+ let presentCount = 0;
329
+ for (const v of option) {
330
+ if (presentSet.has(v)) {
331
+ presentCount++;
332
+ } else {
333
+ missing.push(v);
334
+ }
335
+ }
336
+ if (missing.length === 0) {
337
+ return {
338
+ name: provider,
339
+ isComplete: true,
340
+ present: [...presentSet],
341
+ missing: []
342
+ };
343
+ }
344
+ if (!bestOption || presentCount > bestOption.presentCount) {
345
+ bestOption = { presentCount, missing };
346
+ }
347
+ }
295
348
  return {
296
349
  name: provider,
297
- isComplete: missing.length === 0,
298
- present: [...present],
299
- missing: [...missing]
350
+ isComplete: false,
351
+ present: [...presentSet],
352
+ missing: bestOption?.missing ?? []
300
353
  };
301
354
  }
302
355
  function getAvailableProviders() {
@@ -428,47 +481,12 @@ async function loadProvider(providerName) {
428
481
  }
429
482
  }
430
483
  function getProviderConfig(providerName) {
431
- const config2 = {};
432
- switch (providerName) {
433
- case "e2b":
434
- if (process.env.E2B_API_KEY) config2.apiKey = process.env.E2B_API_KEY;
435
- break;
436
- case "railway":
437
- if (process.env.RAILWAY_API_KEY) config2.apiKey = process.env.RAILWAY_API_KEY;
438
- if (process.env.RAILWAY_PROJECT_ID) config2.projectId = process.env.RAILWAY_PROJECT_ID;
439
- if (process.env.RAILWAY_ENVIRONMENT_ID) config2.environmentId = process.env.RAILWAY_ENVIRONMENT_ID;
440
- break;
441
- case "daytona":
442
- if (process.env.DAYTONA_API_KEY) config2.apiKey = process.env.DAYTONA_API_KEY;
443
- break;
444
- case "modal":
445
- if (process.env.MODAL_TOKEN_ID) config2.tokenId = process.env.MODAL_TOKEN_ID;
446
- if (process.env.MODAL_TOKEN_SECRET) config2.tokenSecret = process.env.MODAL_TOKEN_SECRET;
447
- break;
448
- case "runloop":
449
- if (process.env.RUNLOOP_API_KEY) config2.apiKey = process.env.RUNLOOP_API_KEY;
450
- break;
451
- case "vercel":
452
- if (process.env.VERCEL_TOKEN) config2.token = process.env.VERCEL_TOKEN;
453
- if (process.env.VERCEL_TEAM_ID) config2.teamId = process.env.VERCEL_TEAM_ID;
454
- if (process.env.VERCEL_PROJECT_ID) config2.projectId = process.env.VERCEL_PROJECT_ID;
455
- break;
456
- case "cloudflare":
457
- if (process.env.CLOUDFLARE_API_TOKEN) config2.apiToken = process.env.CLOUDFLARE_API_TOKEN;
458
- if (process.env.CLOUDFLARE_ACCOUNT_ID) config2.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
459
- break;
460
- case "codesandbox":
461
- if (process.env.CSB_API_KEY) config2.apiKey = process.env.CSB_API_KEY;
462
- break;
463
- case "blaxel":
464
- if (process.env.BL_API_KEY) config2.apiKey = process.env.BL_API_KEY;
465
- if (process.env.BL_WORKSPACE) config2.workspace = process.env.BL_WORKSPACE;
466
- break;
467
- case "gateway":
468
- if (process.env.COMPUTESDK_API_KEY) config2.apiKey = process.env.COMPUTESDK_API_KEY;
469
- break;
484
+ if (providerName === "gateway") {
485
+ const config2 = {};
486
+ if (process.env.COMPUTESDK_API_KEY) config2.apiKey = process.env.COMPUTESDK_API_KEY;
487
+ return config2;
470
488
  }
471
- return config2;
489
+ return getProviderConfigFromEnv(providerName);
472
490
  }
473
491
 
474
492
  // src/cli/commands.ts
@@ -642,9 +660,34 @@ async function runCommand(state, command) {
642
660
  } catch (error) {
643
661
  const duration = Date.now() - startTime;
644
662
  logError(`Failed ${c.dim(`(${formatDuration(duration)})`)} - ${error instanceof Error ? error.message : String(error)}`);
663
+ if (isStaleConnectionError(error)) {
664
+ clearSandbox(state);
665
+ logWarning("Sandbox connection lost. Next command will create a new sandbox.");
666
+ }
645
667
  throw error;
646
668
  }
647
669
  }
670
+ function isStaleConnectionError(error) {
671
+ if (!(error instanceof Error)) return false;
672
+ const message = error.message.toLowerCase();
673
+ const stalePhrases = [
674
+ "websocket",
675
+ "connection refused",
676
+ "connection reset",
677
+ "connection closed",
678
+ "socket hang up",
679
+ "econnrefused",
680
+ "econnreset",
681
+ "etimedout",
682
+ "not found",
683
+ "sandbox not found",
684
+ "unauthorized",
685
+ "401",
686
+ "403",
687
+ "404"
688
+ ];
689
+ return stalePhrases.some((phrase) => message.includes(phrase));
690
+ }
648
691
  async function switchProvider(state, mode, providerName) {
649
692
  let useDirect = false;
650
693
  let actualProvider = mode;
@@ -800,7 +843,8 @@ import * as path2 from "path";
800
843
  import * as os from "os";
801
844
  function createREPL(state) {
802
845
  const replServer = repl.start({
803
- prompt: "workbench> ",
846
+ prompt: "> ",
847
+ // Initial prompt, will be updated by state management
804
848
  useColors: true,
805
849
  terminal: true,
806
850
  useGlobal: false,
@@ -811,6 +855,7 @@ function createREPL(state) {
811
855
  setupSmartEvaluator(replServer, state);
812
856
  setupAutocomplete(replServer, state);
813
857
  setupHistory(replServer);
858
+ state._replServer = replServer;
814
859
  return replServer;
815
860
  }
816
861
  function injectCmdContext(replServer) {
@@ -918,10 +963,94 @@ function injectWorkbenchCommands(replServer, state) {
918
963
  };
919
964
  replServer.context.env = () => showEnv();
920
965
  replServer.context.help = showHelp;
966
+ replServer.context.getUrl = async (options) => {
967
+ const sandbox = state.currentSandbox;
968
+ if (!sandbox) {
969
+ throw new Error("No active sandbox. Run a command to auto-create one.");
970
+ }
971
+ return sandbox.getUrl(options);
972
+ };
973
+ replServer.context.sandboxInfo = async () => {
974
+ const sandbox = state.currentSandbox;
975
+ if (!sandbox) {
976
+ throw new Error("No active sandbox. Run a command to auto-create one.");
977
+ }
978
+ return sandbox.getInfo();
979
+ };
980
+ replServer.context.runCode = async (code, runtime) => {
981
+ const sandbox = state.currentSandbox;
982
+ if (!sandbox) {
983
+ throw new Error("No active sandbox. Run a command to auto-create one.");
984
+ }
985
+ return sandbox.runCode(code, runtime);
986
+ };
987
+ replServer.context.filesystem = {
988
+ get readFile() {
989
+ return async (path4) => {
990
+ const sandbox = state.currentSandbox;
991
+ if (!sandbox) {
992
+ throw new Error("No active sandbox. Run a command to auto-create one.");
993
+ }
994
+ return sandbox.filesystem.readFile(path4);
995
+ };
996
+ },
997
+ get writeFile() {
998
+ return async (path4, content) => {
999
+ const sandbox = state.currentSandbox;
1000
+ if (!sandbox) {
1001
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1002
+ }
1003
+ return sandbox.filesystem.writeFile(path4, content);
1004
+ };
1005
+ },
1006
+ get mkdir() {
1007
+ return async (path4) => {
1008
+ const sandbox = state.currentSandbox;
1009
+ if (!sandbox) {
1010
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1011
+ }
1012
+ return sandbox.filesystem.mkdir(path4);
1013
+ };
1014
+ },
1015
+ get readdir() {
1016
+ return async (path4) => {
1017
+ const sandbox = state.currentSandbox;
1018
+ if (!sandbox) {
1019
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1020
+ }
1021
+ return sandbox.filesystem.readdir(path4);
1022
+ };
1023
+ },
1024
+ get exists() {
1025
+ return async (path4) => {
1026
+ const sandbox = state.currentSandbox;
1027
+ if (!sandbox) {
1028
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1029
+ }
1030
+ return sandbox.filesystem.exists(path4);
1031
+ };
1032
+ },
1033
+ get remove() {
1034
+ return async (path4) => {
1035
+ const sandbox = state.currentSandbox;
1036
+ if (!sandbox) {
1037
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1038
+ }
1039
+ return sandbox.filesystem.remove(path4);
1040
+ };
1041
+ }
1042
+ };
1043
+ replServer.context.getInstance = () => {
1044
+ const sandbox = state.currentSandbox;
1045
+ if (!sandbox) {
1046
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1047
+ }
1048
+ return sandbox.getInstance();
1049
+ };
921
1050
  }
922
1051
  function setupSmartEvaluator(replServer, state) {
923
1052
  const originalEval = replServer.eval;
924
- const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose"]);
1053
+ const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose", "sandboxInfo"]);
925
1054
  replServer.eval = function(cmd3, context, filename, callback) {
926
1055
  const trimmedCmd = cmd3.trim();
927
1056
  const providerMatch = trimmedCmd.match(/^provider(?:\s+(direct|gateway))?\s+(\w+)$/);
@@ -980,6 +1109,15 @@ function setupSmartEvaluator(replServer, state) {
980
1109
  }
981
1110
  return;
982
1111
  }
1112
+ if (result && typeof result.then === "function") {
1113
+ try {
1114
+ const output = await result;
1115
+ callback(null, output);
1116
+ } catch (error) {
1117
+ callback(error, void 0);
1118
+ }
1119
+ return;
1120
+ }
983
1121
  callback(null, result);
984
1122
  });
985
1123
  };
@@ -998,52 +1136,81 @@ function setupAutocomplete(replServer, state) {
998
1136
  "help": [],
999
1137
  "verbose": [],
1000
1138
  "exit": [],
1001
- ".exit": []
1139
+ ".exit": [],
1140
+ // Sandbox methods
1141
+ "getUrl": [],
1142
+ "runCode": [],
1143
+ "sandboxInfo": [],
1144
+ "getInstance": []
1145
+ // Filesystem is an object, so it gets dot notation autocomplete automatically
1002
1146
  };
1003
1147
  replServer.completer = function(line, callback) {
1004
- const trimmed = line.trim();
1005
- if (!line.includes(" ") && !line.includes(".")) {
1006
- const commands = Object.keys(workbenchCommands);
1007
- const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
1148
+ try {
1149
+ const trimmed = line.trim();
1150
+ if (!line.includes(" ") && !line.includes(".")) {
1151
+ const commands = Object.keys(workbenchCommands);
1152
+ const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
1153
+ if (originalCompleter) {
1154
+ originalCompleter.call(replServer, line, (err, result) => {
1155
+ if (err || !result) {
1156
+ callback(null, [hits, trimmed]);
1157
+ return;
1158
+ }
1159
+ if (!Array.isArray(result) || result.length !== 2) {
1160
+ callback(null, [hits, trimmed]);
1161
+ return;
1162
+ }
1163
+ const [contextHits, partial] = result;
1164
+ if (!Array.isArray(contextHits)) {
1165
+ callback(null, [hits, trimmed]);
1166
+ return;
1167
+ }
1168
+ const allHits = [.../* @__PURE__ */ new Set([...hits, ...contextHits])].sort();
1169
+ const completionPrefix = typeof partial === "string" ? partial : trimmed;
1170
+ callback(null, [allHits, completionPrefix]);
1171
+ });
1172
+ return;
1173
+ }
1174
+ callback(null, [hits.length ? hits : commands, trimmed]);
1175
+ return;
1176
+ }
1177
+ if (line.includes(" ") && !line.includes(".")) {
1178
+ const parts = line.split(" ");
1179
+ const command = parts[0].trim();
1180
+ const partial = parts.slice(1).join(" ").trim();
1181
+ const suggestions = workbenchCommands[command];
1182
+ if (suggestions !== void 0) {
1183
+ if (suggestions.length > 0) {
1184
+ const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
1185
+ callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
1186
+ } else {
1187
+ callback(null, [[], line]);
1188
+ }
1189
+ return;
1190
+ }
1191
+ }
1008
1192
  if (originalCompleter) {
1009
1193
  originalCompleter.call(replServer, line, (err, result) => {
1010
1194
  if (err || !result) {
1011
- callback(null, [hits, trimmed]);
1195
+ callback(null, [[], line]);
1012
1196
  return;
1013
1197
  }
1014
- const [contextHits, partial] = result;
1015
- if (!Array.isArray(contextHits)) {
1016
- callback(null, [hits, trimmed]);
1198
+ if (!Array.isArray(result) || result.length !== 2) {
1199
+ callback(null, [[], line]);
1017
1200
  return;
1018
1201
  }
1019
- const allHits = [.../* @__PURE__ */ new Set([...hits, ...contextHits])].sort();
1020
- callback(null, [allHits, partial]);
1202
+ const [completions, partial] = result;
1203
+ if (!Array.isArray(completions) || typeof partial !== "string") {
1204
+ callback(null, [[], line]);
1205
+ return;
1206
+ }
1207
+ callback(null, [completions, partial]);
1021
1208
  });
1022
- return;
1023
- }
1024
- callback(null, [hits.length ? hits : commands, trimmed]);
1025
- return;
1026
- }
1027
- if (line.includes(" ") && !line.includes(".")) {
1028
- const parts = line.split(" ");
1029
- const command = parts[0].trim();
1030
- const partial = parts.slice(1).join(" ").trim();
1031
- const suggestions = workbenchCommands[command];
1032
- if (suggestions && suggestions.length > 0) {
1033
- const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
1034
- callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
1035
- return;
1209
+ } else {
1210
+ callback(null, [[], line]);
1036
1211
  }
1037
- }
1038
- if (originalCompleter) {
1039
- originalCompleter.call(replServer, line, (err, result) => {
1040
- if (err || !result) {
1041
- callback(null, [[], line]);
1042
- return;
1043
- }
1044
- callback(null, result);
1045
- });
1046
- } else {
1212
+ } catch (error) {
1213
+ console.error("Autocomplete error:", error);
1047
1214
  callback(null, [[], line]);
1048
1215
  }
1049
1216
  };