@computesdk/workbench 2.0.0 → 2.0.2

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
@@ -20,10 +20,26 @@ function setSandbox(state, sandbox, provider) {
20
20
  state.currentSandbox = sandbox;
21
21
  state.currentProvider = provider;
22
22
  state.sandboxCreatedAt = /* @__PURE__ */ new Date();
23
+ updatePromptIfNeeded(state);
23
24
  }
24
25
  function clearSandbox(state) {
25
26
  state.currentSandbox = null;
26
27
  state.sandboxCreatedAt = null;
28
+ updatePromptIfNeeded(state);
29
+ }
30
+ function updatePromptIfNeeded(state) {
31
+ if (state._replServer) {
32
+ const prompt = getPrompt(state);
33
+ state._replServer.setPrompt(prompt);
34
+ }
35
+ }
36
+ function getPrompt(state) {
37
+ if (!state.currentSandbox) {
38
+ return "> ";
39
+ }
40
+ const provider = state.currentProvider || "unknown";
41
+ const sandboxId = state.currentSandbox.sandboxId || "";
42
+ return `${provider}:${sandboxId}> `;
27
43
  }
28
44
  function hasSandbox(state) {
29
45
  return state.currentSandbox !== null;
@@ -79,6 +95,7 @@ function showWelcome(availableProviders, currentProvider, useDirectMode) {
79
95
  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")));
80
96
  console.log(c.bold(c.cyan("\u2551 ComputeSDK Workbench \u2551")));
81
97
  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")));
98
+ console.log(c.dim("Prompt shows connection status: > (disconnected) or provider:sandbox> (connected)\n"));
82
99
  if (availableProviders.length > 0) {
83
100
  const backendProviders = availableProviders.filter((p) => p !== "gateway");
84
101
  console.log(`Providers available: ${backendProviders.join(", ")}`);
@@ -112,7 +129,12 @@ function showInfo(state) {
112
129
  return;
113
130
  }
114
131
  console.log("\n" + c.bold("Current Sandbox:"));
132
+ if (state.currentSandbox.sandboxId) {
133
+ console.log(` Sandbox ID: ${c.cyan(state.currentSandbox.sandboxId)}`);
134
+ }
115
135
  console.log(` Provider: ${c.green(state.currentProvider || "unknown")}`);
136
+ const modeLabel = state.useDirectMode ? "direct \u{1F517}" : "gateway \u{1F310}";
137
+ console.log(` Mode: ${c.blue(modeLabel)}`);
116
138
  console.log(` Created: ${state.sandboxCreatedAt?.toLocaleString() || "unknown"}`);
117
139
  console.log(` Uptime: ${formatUptime(state)}`);
118
140
  console.log("");
@@ -209,12 +231,28 @@ ${c.bold("Running Commands:")}
209
231
  ${c.cyan('git.clone("https://github.com/user/repo")')}
210
232
  ${c.cyan("git.status()")}
211
233
 
212
- ${c.dim("Filesystem:")}
234
+ ${c.dim("Filesystem (via commands):")}
213
235
  ${c.cyan('ls("/home")')}
214
236
  ${c.cyan('cat("/etc/hosts")')}
215
237
  ${c.cyan('rm.rf("/tmp")')} ${c.dim("// Force remove")}
216
238
  ${c.cyan('rm.auto("/path")')} ${c.dim("// Smart remove")}
217
239
 
240
+ ${c.dim("Filesystem (direct API):")}
241
+ ${c.cyan('filesystem.readFile("/etc/hosts")')}
242
+ ${c.cyan('filesystem.writeFile("/app/server.js", code)')}
243
+ ${c.cyan('filesystem.mkdir("/app")')}
244
+ ${c.cyan('filesystem.readdir("/home")')}
245
+ ${c.cyan('filesystem.exists("/path")')}
246
+ ${c.cyan('filesystem.remove("/file")')}
247
+
248
+ ${c.dim("Sandbox Methods:")}
249
+ ${c.cyan("getUrl({ port: 3000 })")} ${c.dim("// Get public URL")}
250
+ ${c.cyan(`runCode("console.log('hi')", "node")`)}
251
+ ${c.cyan("sandboxInfo()")} ${c.dim("// Get sandbox details")}
252
+ ${c.cyan("getInstance()")} ${c.dim("// Get native instance")}
253
+
254
+ ${c.dim('Note: No need to use "await" - promises are auto-awaited!')}
255
+
218
256
  ${c.dim("Compute CLI:")}
219
257
  ${c.cyan("compute.isSetup()")} ${c.dim("// Check if daemon is running")}
220
258
  ${c.cyan("compute.setup()")} ${c.dim("// Install + start daemon")}
@@ -244,47 +282,62 @@ function logWarning(message) {
244
282
  }
245
283
 
246
284
  // src/cli/providers.ts
285
+ import {
286
+ PROVIDER_AUTH as SHARED_PROVIDER_AUTH,
287
+ PROVIDER_NAMES as SHARED_PROVIDER_NAMES,
288
+ getProviderConfigFromEnv
289
+ } from "computesdk";
247
290
  var PROVIDER_NAMES = [
248
291
  "gateway",
249
- "e2b",
250
- "railway",
251
- "daytona",
252
- "modal",
253
- "runloop",
254
- "vercel",
255
- "cloudflare",
256
- "codesandbox",
257
- "blaxel"
292
+ ...SHARED_PROVIDER_NAMES
258
293
  ];
259
- var PROVIDER_ENV_VARS = {
260
- gateway: ["COMPUTESDK_API_KEY"],
261
- e2b: ["E2B_API_KEY"],
262
- railway: ["RAILWAY_API_KEY", "RAILWAY_PROJECT_ID", "RAILWAY_ENVIRONMENT_ID"],
263
- daytona: ["DAYTONA_API_KEY"],
264
- modal: ["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"],
265
- runloop: ["RUNLOOP_API_KEY"],
266
- vercel: ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"],
267
- cloudflare: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
268
- codesandbox: ["CSB_API_KEY"],
269
- blaxel: ["BL_API_KEY", "BL_WORKSPACE"]
294
+ var PROVIDER_AUTH = {
295
+ gateway: [["COMPUTESDK_API_KEY"]],
296
+ ...SHARED_PROVIDER_AUTH
270
297
  };
271
298
  function getProviderStatus(provider) {
299
+ const authOptions = PROVIDER_AUTH[provider];
272
300
  if (typeof process === "undefined") {
273
301
  return {
274
302
  name: provider,
275
303
  isComplete: false,
276
304
  present: [],
277
- missing: [...PROVIDER_ENV_VARS[provider]]
305
+ missing: [...authOptions[0]]
278
306
  };
279
307
  }
280
- const requiredVars = PROVIDER_ENV_VARS[provider];
281
- const present = requiredVars.filter((varName) => !!process.env?.[varName]);
282
- const missing = requiredVars.filter((varName) => !process.env?.[varName]);
308
+ const allVars = new Set(authOptions.flat());
309
+ const presentSet = /* @__PURE__ */ new Set();
310
+ for (const v of allVars) {
311
+ if (process.env?.[v]) presentSet.add(v);
312
+ }
313
+ let bestOption = null;
314
+ for (const option of authOptions) {
315
+ const missing = [];
316
+ let presentCount = 0;
317
+ for (const v of option) {
318
+ if (presentSet.has(v)) {
319
+ presentCount++;
320
+ } else {
321
+ missing.push(v);
322
+ }
323
+ }
324
+ if (missing.length === 0) {
325
+ return {
326
+ name: provider,
327
+ isComplete: true,
328
+ present: [...presentSet],
329
+ missing: []
330
+ };
331
+ }
332
+ if (!bestOption || presentCount > bestOption.presentCount) {
333
+ bestOption = { presentCount, missing };
334
+ }
335
+ }
283
336
  return {
284
337
  name: provider,
285
- isComplete: missing.length === 0,
286
- present: [...present],
287
- missing: [...missing]
338
+ isComplete: false,
339
+ present: [...presentSet],
340
+ missing: bestOption?.missing ?? []
288
341
  };
289
342
  }
290
343
  function getAvailableProviders() {
@@ -416,47 +469,12 @@ async function loadProvider(providerName) {
416
469
  }
417
470
  }
418
471
  function getProviderConfig(providerName) {
419
- const config = {};
420
- switch (providerName) {
421
- case "e2b":
422
- if (process.env.E2B_API_KEY) config.apiKey = process.env.E2B_API_KEY;
423
- break;
424
- case "railway":
425
- if (process.env.RAILWAY_API_KEY) config.apiKey = process.env.RAILWAY_API_KEY;
426
- if (process.env.RAILWAY_PROJECT_ID) config.projectId = process.env.RAILWAY_PROJECT_ID;
427
- if (process.env.RAILWAY_ENVIRONMENT_ID) config.environmentId = process.env.RAILWAY_ENVIRONMENT_ID;
428
- break;
429
- case "daytona":
430
- if (process.env.DAYTONA_API_KEY) config.apiKey = process.env.DAYTONA_API_KEY;
431
- break;
432
- case "modal":
433
- if (process.env.MODAL_TOKEN_ID) config.tokenId = process.env.MODAL_TOKEN_ID;
434
- if (process.env.MODAL_TOKEN_SECRET) config.tokenSecret = process.env.MODAL_TOKEN_SECRET;
435
- break;
436
- case "runloop":
437
- if (process.env.RUNLOOP_API_KEY) config.apiKey = process.env.RUNLOOP_API_KEY;
438
- break;
439
- case "vercel":
440
- if (process.env.VERCEL_TOKEN) config.token = process.env.VERCEL_TOKEN;
441
- if (process.env.VERCEL_TEAM_ID) config.teamId = process.env.VERCEL_TEAM_ID;
442
- if (process.env.VERCEL_PROJECT_ID) config.projectId = process.env.VERCEL_PROJECT_ID;
443
- break;
444
- case "cloudflare":
445
- if (process.env.CLOUDFLARE_API_TOKEN) config.apiToken = process.env.CLOUDFLARE_API_TOKEN;
446
- if (process.env.CLOUDFLARE_ACCOUNT_ID) config.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
447
- break;
448
- case "codesandbox":
449
- if (process.env.CSB_API_KEY) config.apiKey = process.env.CSB_API_KEY;
450
- break;
451
- case "blaxel":
452
- if (process.env.BL_API_KEY) config.apiKey = process.env.BL_API_KEY;
453
- if (process.env.BL_WORKSPACE) config.workspace = process.env.BL_WORKSPACE;
454
- break;
455
- case "gateway":
456
- if (process.env.COMPUTESDK_API_KEY) config.apiKey = process.env.COMPUTESDK_API_KEY;
457
- break;
472
+ if (providerName === "gateway") {
473
+ const config = {};
474
+ if (process.env.COMPUTESDK_API_KEY) config.apiKey = process.env.COMPUTESDK_API_KEY;
475
+ return config;
458
476
  }
459
- return config;
477
+ return getProviderConfigFromEnv(providerName);
460
478
  }
461
479
 
462
480
  // src/cli/commands.ts
@@ -630,9 +648,34 @@ async function runCommand(state, command) {
630
648
  } catch (error) {
631
649
  const duration = Date.now() - startTime;
632
650
  logError(`Failed ${c.dim(`(${formatDuration(duration)})`)} - ${error instanceof Error ? error.message : String(error)}`);
651
+ if (isStaleConnectionError(error)) {
652
+ clearSandbox(state);
653
+ logWarning("Sandbox connection lost. Next command will create a new sandbox.");
654
+ }
633
655
  throw error;
634
656
  }
635
657
  }
658
+ function isStaleConnectionError(error) {
659
+ if (!(error instanceof Error)) return false;
660
+ const message = error.message.toLowerCase();
661
+ const stalePhrases = [
662
+ "websocket",
663
+ "connection refused",
664
+ "connection reset",
665
+ "connection closed",
666
+ "socket hang up",
667
+ "econnrefused",
668
+ "econnreset",
669
+ "etimedout",
670
+ "not found",
671
+ "sandbox not found",
672
+ "unauthorized",
673
+ "401",
674
+ "403",
675
+ "404"
676
+ ];
677
+ return stalePhrases.some((phrase) => message.includes(phrase));
678
+ }
636
679
  async function switchProvider(state, mode, providerName) {
637
680
  let useDirect = false;
638
681
  let actualProvider = mode;
@@ -788,7 +831,8 @@ import * as path from "path";
788
831
  import * as os from "os";
789
832
  function createREPL(state) {
790
833
  const replServer = repl.start({
791
- prompt: "workbench> ",
834
+ prompt: "> ",
835
+ // Initial prompt, will be updated by state management
792
836
  useColors: true,
793
837
  terminal: true,
794
838
  useGlobal: false,
@@ -799,6 +843,7 @@ function createREPL(state) {
799
843
  setupSmartEvaluator(replServer, state);
800
844
  setupAutocomplete(replServer, state);
801
845
  setupHistory(replServer);
846
+ state._replServer = replServer;
802
847
  return replServer;
803
848
  }
804
849
  function injectCmdContext(replServer) {
@@ -906,10 +951,94 @@ function injectWorkbenchCommands(replServer, state) {
906
951
  };
907
952
  replServer.context.env = () => showEnv();
908
953
  replServer.context.help = showHelp;
954
+ replServer.context.getUrl = async (options) => {
955
+ const sandbox = state.currentSandbox;
956
+ if (!sandbox) {
957
+ throw new Error("No active sandbox. Run a command to auto-create one.");
958
+ }
959
+ return sandbox.getUrl(options);
960
+ };
961
+ replServer.context.sandboxInfo = async () => {
962
+ const sandbox = state.currentSandbox;
963
+ if (!sandbox) {
964
+ throw new Error("No active sandbox. Run a command to auto-create one.");
965
+ }
966
+ return sandbox.getInfo();
967
+ };
968
+ replServer.context.runCode = async (code, runtime) => {
969
+ const sandbox = state.currentSandbox;
970
+ if (!sandbox) {
971
+ throw new Error("No active sandbox. Run a command to auto-create one.");
972
+ }
973
+ return sandbox.runCode(code, runtime);
974
+ };
975
+ replServer.context.filesystem = {
976
+ get readFile() {
977
+ return async (path2) => {
978
+ const sandbox = state.currentSandbox;
979
+ if (!sandbox) {
980
+ throw new Error("No active sandbox. Run a command to auto-create one.");
981
+ }
982
+ return sandbox.filesystem.readFile(path2);
983
+ };
984
+ },
985
+ get writeFile() {
986
+ return async (path2, content) => {
987
+ const sandbox = state.currentSandbox;
988
+ if (!sandbox) {
989
+ throw new Error("No active sandbox. Run a command to auto-create one.");
990
+ }
991
+ return sandbox.filesystem.writeFile(path2, content);
992
+ };
993
+ },
994
+ get mkdir() {
995
+ return async (path2) => {
996
+ const sandbox = state.currentSandbox;
997
+ if (!sandbox) {
998
+ throw new Error("No active sandbox. Run a command to auto-create one.");
999
+ }
1000
+ return sandbox.filesystem.mkdir(path2);
1001
+ };
1002
+ },
1003
+ get readdir() {
1004
+ return async (path2) => {
1005
+ const sandbox = state.currentSandbox;
1006
+ if (!sandbox) {
1007
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1008
+ }
1009
+ return sandbox.filesystem.readdir(path2);
1010
+ };
1011
+ },
1012
+ get exists() {
1013
+ return async (path2) => {
1014
+ const sandbox = state.currentSandbox;
1015
+ if (!sandbox) {
1016
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1017
+ }
1018
+ return sandbox.filesystem.exists(path2);
1019
+ };
1020
+ },
1021
+ get remove() {
1022
+ return async (path2) => {
1023
+ const sandbox = state.currentSandbox;
1024
+ if (!sandbox) {
1025
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1026
+ }
1027
+ return sandbox.filesystem.remove(path2);
1028
+ };
1029
+ }
1030
+ };
1031
+ replServer.context.getInstance = () => {
1032
+ const sandbox = state.currentSandbox;
1033
+ if (!sandbox) {
1034
+ throw new Error("No active sandbox. Run a command to auto-create one.");
1035
+ }
1036
+ return sandbox.getInstance();
1037
+ };
909
1038
  }
910
1039
  function setupSmartEvaluator(replServer, state) {
911
1040
  const originalEval = replServer.eval;
912
- const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose"]);
1041
+ const workbenchCommands = /* @__PURE__ */ new Set(["help", "providers", "info", "env", "restart", "destroy", "mode", "verbose", "sandboxInfo"]);
913
1042
  replServer.eval = function(cmd3, context, filename, callback) {
914
1043
  const trimmedCmd = cmd3.trim();
915
1044
  const providerMatch = trimmedCmd.match(/^provider(?:\s+(direct|gateway))?\s+(\w+)$/);
@@ -968,6 +1097,15 @@ function setupSmartEvaluator(replServer, state) {
968
1097
  }
969
1098
  return;
970
1099
  }
1100
+ if (result && typeof result.then === "function") {
1101
+ try {
1102
+ const output = await result;
1103
+ callback(null, output);
1104
+ } catch (error) {
1105
+ callback(error, void 0);
1106
+ }
1107
+ return;
1108
+ }
971
1109
  callback(null, result);
972
1110
  });
973
1111
  };
@@ -986,52 +1124,81 @@ function setupAutocomplete(replServer, state) {
986
1124
  "help": [],
987
1125
  "verbose": [],
988
1126
  "exit": [],
989
- ".exit": []
1127
+ ".exit": [],
1128
+ // Sandbox methods
1129
+ "getUrl": [],
1130
+ "runCode": [],
1131
+ "sandboxInfo": [],
1132
+ "getInstance": []
1133
+ // Filesystem is an object, so it gets dot notation autocomplete automatically
990
1134
  };
991
1135
  replServer.completer = function(line, callback) {
992
- const trimmed = line.trim();
993
- if (!line.includes(" ") && !line.includes(".")) {
994
- const commands = Object.keys(workbenchCommands);
995
- const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
1136
+ try {
1137
+ const trimmed = line.trim();
1138
+ if (!line.includes(" ") && !line.includes(".")) {
1139
+ const commands = Object.keys(workbenchCommands);
1140
+ const hits = commands.filter((cmd3) => cmd3.startsWith(trimmed));
1141
+ if (originalCompleter) {
1142
+ originalCompleter.call(replServer, line, (err, result) => {
1143
+ if (err || !result) {
1144
+ callback(null, [hits, trimmed]);
1145
+ return;
1146
+ }
1147
+ if (!Array.isArray(result) || result.length !== 2) {
1148
+ callback(null, [hits, trimmed]);
1149
+ return;
1150
+ }
1151
+ const [contextHits, partial] = result;
1152
+ if (!Array.isArray(contextHits)) {
1153
+ callback(null, [hits, trimmed]);
1154
+ return;
1155
+ }
1156
+ const allHits = [.../* @__PURE__ */ new Set([...hits, ...contextHits])].sort();
1157
+ const completionPrefix = typeof partial === "string" ? partial : trimmed;
1158
+ callback(null, [allHits, completionPrefix]);
1159
+ });
1160
+ return;
1161
+ }
1162
+ callback(null, [hits.length ? hits : commands, trimmed]);
1163
+ return;
1164
+ }
1165
+ if (line.includes(" ") && !line.includes(".")) {
1166
+ const parts = line.split(" ");
1167
+ const command = parts[0].trim();
1168
+ const partial = parts.slice(1).join(" ").trim();
1169
+ const suggestions = workbenchCommands[command];
1170
+ if (suggestions !== void 0) {
1171
+ if (suggestions.length > 0) {
1172
+ const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
1173
+ callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
1174
+ } else {
1175
+ callback(null, [[], line]);
1176
+ }
1177
+ return;
1178
+ }
1179
+ }
996
1180
  if (originalCompleter) {
997
1181
  originalCompleter.call(replServer, line, (err, result) => {
998
1182
  if (err || !result) {
999
- callback(null, [hits, trimmed]);
1183
+ callback(null, [[], line]);
1000
1184
  return;
1001
1185
  }
1002
- const [contextHits, partial] = result;
1003
- if (!Array.isArray(contextHits)) {
1004
- callback(null, [hits, trimmed]);
1186
+ if (!Array.isArray(result) || result.length !== 2) {
1187
+ callback(null, [[], line]);
1005
1188
  return;
1006
1189
  }
1007
- const allHits = [.../* @__PURE__ */ new Set([...hits, ...contextHits])].sort();
1008
- callback(null, [allHits, partial]);
1190
+ const [completions, partial] = result;
1191
+ if (!Array.isArray(completions) || typeof partial !== "string") {
1192
+ callback(null, [[], line]);
1193
+ return;
1194
+ }
1195
+ callback(null, [completions, partial]);
1009
1196
  });
1010
- return;
1011
- }
1012
- callback(null, [hits.length ? hits : commands, trimmed]);
1013
- return;
1014
- }
1015
- if (line.includes(" ") && !line.includes(".")) {
1016
- const parts = line.split(" ");
1017
- const command = parts[0].trim();
1018
- const partial = parts.slice(1).join(" ").trim();
1019
- const suggestions = workbenchCommands[command];
1020
- if (suggestions && suggestions.length > 0) {
1021
- const hits = suggestions.filter((s) => s.startsWith(partial)).map((s) => `${command} ${s}`);
1022
- callback(null, [hits.length ? hits : suggestions.map((s) => `${command} ${s}`), line]);
1023
- return;
1197
+ } else {
1198
+ callback(null, [[], line]);
1024
1199
  }
1025
- }
1026
- if (originalCompleter) {
1027
- originalCompleter.call(replServer, line, (err, result) => {
1028
- if (err || !result) {
1029
- callback(null, [[], line]);
1030
- return;
1031
- }
1032
- callback(null, result);
1033
- });
1034
- } else {
1200
+ } catch (error) {
1201
+ console.error("Autocomplete error:", error);
1035
1202
  callback(null, [[], line]);
1036
1203
  }
1037
1204
  };