@computesdk/cli 0.1.2 → 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
@@ -18,95 +18,310 @@ var init_esm_shims = __esm({
18
18
  }
19
19
  });
20
20
 
21
- // src/pty.ts
22
- async function startPTY(sandbox, options = {}) {
23
- const terminal = await sandbox.terminal.create({ pty: true, shell: "/bin/bash" });
24
- await new Promise((resolve) => setTimeout(resolve, 100));
25
- if (process.stdin.isTTY) {
26
- process.stdin.setRawMode(true);
27
- }
28
- process.stdin.resume();
29
- let exiting = false;
30
- let resolvePromise;
31
- if (options.initialCommand) {
32
- terminal.write(options.initialCommand + "\n");
33
- await new Promise((resolve) => setTimeout(resolve, 50));
34
- }
35
- const ws = terminal._ws;
36
- if (ws) {
37
- ws.on("close", () => {
38
- if (!exiting) {
39
- doExit();
21
+ // src/repl.ts
22
+ import * as readline from "readline";
23
+ import * as path2 from "path";
24
+ import * as os from "os";
25
+ import * as fs from "fs";
26
+ import pc from "picocolors";
27
+ import { compute } from "computesdk";
28
+ async function startREPL(sandbox, provider) {
29
+ void provider;
30
+ const ctx = {
31
+ sandboxes: /* @__PURE__ */ new Map([[sandbox.sandboxId, sandbox]]),
32
+ activeSandboxId: sandbox.sandboxId
33
+ };
34
+ await runSession(ctx);
35
+ }
36
+ function buildPrompt(sandboxId) {
37
+ if (!sandboxId) return pc.gray("[no sandbox] ") + pc.bold("$ ");
38
+ return pc.cyan(`[${sandboxId}] `) + pc.bold("$ ");
39
+ }
40
+ function runSession(ctx) {
41
+ const rl = readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout,
44
+ prompt: buildPrompt(ctx.activeSandboxId),
45
+ terminal: true,
46
+ historySize: 1e3
47
+ });
48
+ loadHistory(rl);
49
+ return new Promise((resolve) => {
50
+ let resolved = false;
51
+ const done = (result) => {
52
+ if (resolved) return;
53
+ resolved = true;
54
+ rl.close();
55
+ resolve(result);
56
+ };
57
+ rl.on("SIGINT", () => {
58
+ console.log();
59
+ rl.prompt();
60
+ });
61
+ rl.on("close", () => {
62
+ done("exit");
63
+ });
64
+ rl.on("line", async (line) => {
65
+ const trimmed = line.trim();
66
+ if (!trimmed) {
67
+ rl.prompt();
68
+ return;
69
+ }
70
+ if (trimmed === ".exit") {
71
+ done("exit");
72
+ return;
73
+ }
74
+ saveToHistory(trimmed);
75
+ try {
76
+ if (trimmed.startsWith("/")) {
77
+ const result = await handleSpecialCommand(ctx, trimmed, rl);
78
+ if (result === "exit") {
79
+ done(result);
80
+ return;
81
+ }
82
+ } else if (ctx.sandboxes.size === 0) {
83
+ console.log(pc.yellow("No active sandbox. Use /new to create one."));
84
+ } else {
85
+ const sandbox = ctx.sandboxes.get(ctx.activeSandboxId);
86
+ await executeCommand(sandbox, trimmed);
87
+ }
88
+ } catch (error) {
89
+ console.log(pc.red(`\u2717 ${error.message}`));
90
+ }
91
+ if (!resolved) {
92
+ rl.prompt();
40
93
  }
41
94
  });
95
+ rl.prompt();
96
+ });
97
+ }
98
+ async function executeCommand(sandbox, command) {
99
+ const result = await sandbox.runCommand(command);
100
+ if (result.stdout) {
101
+ process.stdout.write(result.stdout);
102
+ if (!result.stdout.endsWith("\n")) {
103
+ console.log();
104
+ }
42
105
  }
43
- terminal.on("output", (data) => {
44
- if (!exiting) {
45
- process.stdout.write(data);
106
+ if (result.stderr) {
107
+ process.stderr.write(pc.red(result.stderr));
108
+ if (!result.stderr.endsWith("\n")) {
109
+ console.log();
46
110
  }
47
- });
48
- terminal.on("error", (error) => {
49
- if (!error || error === "undefined") {
50
- if (!exiting) {
51
- doExit();
111
+ }
112
+ if (result.exitCode !== 0) {
113
+ console.log(pc.gray(`exit code: ${result.exitCode}`));
114
+ }
115
+ }
116
+ async function handleSpecialCommand(ctx, input, rl) {
117
+ const [cmd, ...args] = input.slice(1).split(/\s+/);
118
+ switch (cmd.toLowerCase()) {
119
+ case "help":
120
+ case "h":
121
+ case "?":
122
+ showHelp();
123
+ return null;
124
+ case "info":
125
+ case "i": {
126
+ if (ctx.sandboxes.size === 0) {
127
+ console.log(pc.yellow("No active sandbox. Use /new to create one."));
128
+ return null;
52
129
  }
53
- return;
130
+ const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
131
+ console.log();
132
+ console.log(pc.bold(" Sandbox Info"));
133
+ console.log(` ID: ${pc.cyan(activeSandbox.sandboxId)}`);
134
+ console.log();
135
+ return null;
54
136
  }
55
- process.stderr.write(`\r
56
- Terminal error: ${error}\r
57
- `);
58
- });
59
- const onData = (data) => {
60
- terminal.write(data.toString());
61
- };
62
- process.stdin.on("data", onData);
63
- const onResize = () => {
64
- if (process.stdout.columns && process.stdout.rows) {
65
- try {
66
- terminal.resize(process.stdout.columns, process.stdout.rows);
67
- } catch {
137
+ case "url":
138
+ case "u": {
139
+ if (ctx.sandboxes.size === 0) {
140
+ console.log(pc.yellow("No active sandbox. Use /new to create one."));
141
+ return null;
68
142
  }
143
+ const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
144
+ if (args.length === 0) {
145
+ console.log(pc.gray("Usage: /url <port>"));
146
+ } else {
147
+ const port = parseInt(args[0], 10);
148
+ if (isNaN(port)) {
149
+ console.log(pc.red("Invalid port number"));
150
+ } else {
151
+ const url = await activeSandbox.getUrl({ port });
152
+ console.log(pc.cyan(url));
153
+ }
154
+ }
155
+ return null;
69
156
  }
70
- };
71
- process.stdout.on("resize", onResize);
72
- onResize();
73
- async function doExit() {
74
- if (exiting) return;
75
- exiting = true;
76
- cleanup();
77
- try {
78
- await terminal.destroy();
79
- } catch {
157
+ case "new": {
158
+ console.log(pc.gray(" Creating new sandbox..."));
159
+ const newSandbox = await compute.sandbox.create();
160
+ ctx.sandboxes.set(newSandbox.sandboxId, newSandbox);
161
+ ctx.activeSandboxId = newSandbox.sandboxId;
162
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
163
+ console.log(` Sandbox ready: ${pc.cyan(newSandbox.sandboxId)}`);
164
+ console.log(` ${pc.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
165
+ return null;
166
+ }
167
+ case "destroy": {
168
+ if (args.length === 0) {
169
+ console.log(pc.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
170
+ console.log(pc.gray("Use /list to see all sandboxes"));
171
+ return null;
172
+ }
173
+ if (args[0] === "--all") {
174
+ if (ctx.sandboxes.size === 0) {
175
+ console.log(pc.yellow(" No sandboxes to destroy."));
176
+ return null;
177
+ }
178
+ const count = ctx.sandboxes.size;
179
+ console.log(pc.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
180
+ await Promise.all(
181
+ Array.from(ctx.sandboxes.values()).map((s) => s.destroy())
182
+ );
183
+ ctx.sandboxes.clear();
184
+ ctx.activeSandboxId = "";
185
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
186
+ console.log(` Destroyed ${pc.cyan(String(count))} sandbox${count > 1 ? "es" : ""}`);
187
+ console.log(pc.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
188
+ return null;
189
+ }
190
+ const targetId = args[0];
191
+ const target = ctx.sandboxes.get(targetId);
192
+ if (!target) {
193
+ const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(targetId));
194
+ if (matches.length === 1) {
195
+ return destroySandbox(ctx, matches[0], rl);
196
+ } else if (matches.length > 1) {
197
+ console.log(pc.yellow(" Ambiguous ID. Matches:"));
198
+ for (const id of matches) {
199
+ console.log(` ${pc.cyan(id)}`);
200
+ }
201
+ } else {
202
+ console.log(pc.red(` Sandbox not found: ${targetId}`));
203
+ console.log(pc.gray(" Use /list to see all sandboxes"));
204
+ }
205
+ return null;
206
+ }
207
+ return destroySandbox(ctx, targetId, rl);
80
208
  }
81
- if (resolvePromise) {
82
- resolvePromise();
209
+ case "list":
210
+ case "ls": {
211
+ console.log();
212
+ console.log(pc.bold(" Sandboxes"));
213
+ for (const id of ctx.sandboxes.keys()) {
214
+ const marker = id === ctx.activeSandboxId ? pc.green(" \u25CF") : " ";
215
+ console.log(` ${marker} ${pc.cyan(id)}`);
216
+ }
217
+ console.log();
218
+ return null;
83
219
  }
84
- }
85
- function cleanup() {
86
- process.stdin.removeListener("data", onData);
87
- process.stdout.removeListener("resize", onResize);
88
- if (process.stdin.isTTY) {
89
- process.stdin.setRawMode(false);
220
+ case "switch":
221
+ case "sw": {
222
+ if (args.length === 0) {
223
+ console.log(pc.gray("Usage: /switch <sandbox-id>"));
224
+ console.log(pc.gray("Use /list to see all sandboxes"));
225
+ return null;
226
+ }
227
+ const switchId = args[0];
228
+ if (ctx.sandboxes.has(switchId)) {
229
+ ctx.activeSandboxId = switchId;
230
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
231
+ console.log(` Switched to ${pc.cyan(switchId)}`);
232
+ return null;
233
+ }
234
+ const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(switchId));
235
+ if (matches.length === 1) {
236
+ ctx.activeSandboxId = matches[0];
237
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
238
+ console.log(` Switched to ${pc.cyan(matches[0])}`);
239
+ } else if (matches.length > 1) {
240
+ console.log(pc.yellow(" Ambiguous ID. Matches:"));
241
+ for (const id of matches) {
242
+ console.log(` ${pc.cyan(id)}`);
243
+ }
244
+ } else {
245
+ console.log(pc.red(` Sandbox not found: ${switchId}`));
246
+ console.log(pc.gray(" Use /list to see all sandboxes"));
247
+ }
248
+ return null;
90
249
  }
91
- process.stdin.pause();
250
+ case "exit":
251
+ case "quit":
252
+ case "q":
253
+ return "exit";
254
+ default:
255
+ console.log(pc.yellow(`Unknown command: /${cmd}`));
256
+ console.log(pc.gray("Type /help for available commands"));
257
+ return null;
92
258
  }
93
- return new Promise((resolve) => {
94
- resolvePromise = resolve;
95
- terminal.on("destroyed", () => {
96
- if (!exiting) {
97
- cleanup();
98
- resolve();
259
+ }
260
+ async function destroySandbox(ctx, sandboxId, rl) {
261
+ const target = ctx.sandboxes.get(sandboxId);
262
+ console.log(pc.gray(` Destroying ${sandboxId}...`));
263
+ await target.destroy();
264
+ ctx.sandboxes.delete(sandboxId);
265
+ console.log(` Destroyed ${pc.cyan(sandboxId)}`);
266
+ if (ctx.activeSandboxId === sandboxId && ctx.sandboxes.size > 0) {
267
+ ctx.activeSandboxId = ctx.sandboxes.keys().next().value;
268
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
269
+ console.log(` Switched to ${pc.cyan(ctx.activeSandboxId)}`);
270
+ } else if (ctx.sandboxes.size === 0) {
271
+ ctx.activeSandboxId = "";
272
+ rl.setPrompt(buildPrompt(ctx.activeSandboxId));
273
+ console.log(pc.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
274
+ }
275
+ return null;
276
+ }
277
+ function showHelp() {
278
+ console.log();
279
+ console.log(pc.bold(" Shell Mode"));
280
+ console.log(pc.gray(" Just type commands - they run in the active sandbox"));
281
+ console.log();
282
+ console.log(pc.white(" ls -la"));
283
+ console.log(pc.white(" cat /etc/os-release"));
284
+ console.log(pc.white(" npm install lodash"));
285
+ console.log();
286
+ console.log(pc.bold(" Sandbox Commands") + pc.gray(" (prefix with /)"));
287
+ console.log();
288
+ console.log(pc.cyan(" /new") + pc.gray(" Create a new sandbox and switch to it"));
289
+ console.log(pc.cyan(" /list") + pc.gray(" List all sandboxes"));
290
+ console.log(pc.cyan(" /switch <id>") + pc.gray(" Switch active sandbox"));
291
+ console.log(pc.cyan(" /destroy <id>") + pc.gray(" Destroy a sandbox by ID"));
292
+ console.log(pc.cyan(" /destroy --all") + pc.gray(" Destroy all sandboxes"));
293
+ console.log();
294
+ console.log(pc.bold(" Other Commands"));
295
+ console.log();
296
+ console.log(pc.cyan(" /info") + pc.gray(" Show active sandbox info"));
297
+ console.log(pc.cyan(" /url <port>") + pc.gray(" Get public URL for port"));
298
+ console.log(pc.cyan(" /help") + pc.gray(" Show this help"));
299
+ console.log(pc.cyan(" /exit") + pc.gray(" Exit the REPL"));
300
+ console.log();
301
+ }
302
+ function loadHistory(rl) {
303
+ try {
304
+ if (fs.existsSync(HISTORY_FILE)) {
305
+ const history = fs.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter((line) => line.trim()).reverse();
306
+ for (const line of history.slice(0, 1e3)) {
307
+ rl.history?.push(line);
99
308
  }
100
- });
101
- process.stdin.on("end", () => {
102
- doExit();
103
- });
104
- });
309
+ }
310
+ } catch {
311
+ }
105
312
  }
106
- var init_pty = __esm({
107
- "src/pty.ts"() {
313
+ function saveToHistory(line) {
314
+ try {
315
+ fs.appendFileSync(HISTORY_FILE, line + "\n");
316
+ } catch {
317
+ }
318
+ }
319
+ var HISTORY_FILE;
320
+ var init_repl = __esm({
321
+ "src/repl.ts"() {
108
322
  "use strict";
109
323
  init_esm_shims();
324
+ HISTORY_FILE = path2.join(os.homedir(), ".create_compute_history");
110
325
  }
111
326
  });
112
327
 
@@ -123,33 +338,33 @@ __export(workspace_cli_exports, {
123
338
  workspaceDestroy: () => workspaceDestroy,
124
339
  workspaceList: () => workspaceList
125
340
  });
126
- import { compute as compute2 } from "computesdk";
127
- import * as fs2 from "fs";
128
- import * as path3 from "path";
129
- import * as os2 from "os";
341
+ import { compute as compute3 } from "computesdk";
342
+ import * as fs3 from "fs";
343
+ import * as path4 from "path";
344
+ import * as os3 from "os";
130
345
  import * as p3 from "@clack/prompts";
131
- import pc3 from "picocolors";
346
+ import pc4 from "picocolors";
132
347
  function shellEscape(arg) {
133
348
  if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg)) return arg;
134
349
  return `'${arg.replace(/'/g, "'\\''")}'`;
135
350
  }
136
351
  function loadState() {
137
352
  try {
138
- if (!fs2.existsSync(WORKSPACE_STATE_FILE)) {
353
+ if (!fs3.existsSync(WORKSPACE_STATE_FILE)) {
139
354
  return { workspaces: {} };
140
355
  }
141
- const raw = fs2.readFileSync(WORKSPACE_STATE_FILE, "utf-8");
356
+ const raw = fs3.readFileSync(WORKSPACE_STATE_FILE, "utf-8");
142
357
  return JSON.parse(raw);
143
358
  } catch {
144
359
  return { workspaces: {} };
145
360
  }
146
361
  }
147
362
  function saveState(state) {
148
- const dir = path3.dirname(WORKSPACE_STATE_FILE);
149
- if (!fs2.existsSync(dir)) {
150
- fs2.mkdirSync(dir, { recursive: true, mode: 448 });
363
+ const dir = path4.dirname(WORKSPACE_STATE_FILE);
364
+ if (!fs3.existsSync(dir)) {
365
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
151
366
  }
152
- fs2.writeFileSync(WORKSPACE_STATE_FILE, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
367
+ fs3.writeFileSync(WORKSPACE_STATE_FILE, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
153
368
  }
154
369
  function getWorkspaceId(repo, branch) {
155
370
  return `${repo}:${branch}`;
@@ -161,13 +376,13 @@ async function workspaceCreate(options) {
161
376
  if (state.workspaces[id]) {
162
377
  const existing = state.workspaces[id];
163
378
  try {
164
- const sandbox2 = await compute2.sandbox.getById(existing.sandboxId);
379
+ const sandbox2 = await compute3.sandbox.getById(existing.sandboxId);
165
380
  if (!sandbox2) {
166
381
  throw new Error("Sandbox not found");
167
382
  }
168
383
  existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
169
384
  saveState(state);
170
- p3.log.info(`Attaching to existing workspace: ${pc3.cyan(id)}`);
385
+ p3.log.info(`Attaching to existing workspace: ${pc4.cyan(id)}`);
171
386
  return existing;
172
387
  } catch {
173
388
  delete state.workspaces[id];
@@ -178,14 +393,14 @@ async function workspaceCreate(options) {
178
393
  spinner4.start("Provisioning via ComputeSDK...");
179
394
  let sandbox;
180
395
  try {
181
- sandbox = await compute2.sandbox.create();
182
- spinner4.stop(`Sandbox ready: ${pc3.cyan(sandbox.sandboxId)}`);
396
+ sandbox = await compute3.sandbox.create();
397
+ spinner4.stop(`Sandbox ready: ${pc4.cyan(sandbox.sandboxId)}`);
183
398
  } catch (error) {
184
- spinner4.stop(pc3.red("Failed to create sandbox"));
399
+ spinner4.stop(pc4.red("Failed to create sandbox"));
185
400
  throw error;
186
401
  }
187
402
  spinner4.start("Bootstrapping workspace (installing gh CLI, cloning repo)...");
188
- const bootstrapScript = fs2.readFileSync(
403
+ const bootstrapScript = fs3.readFileSync(
189
404
  new URL("./bootstrap.sh", import.meta.url),
190
405
  "utf-8"
191
406
  );
@@ -200,7 +415,7 @@ async function workspaceCreate(options) {
200
415
  );
201
416
  const output = result.stdout || "";
202
417
  const jsonMatch = output.match(/\{[\s\S]*\}/);
203
- let worktreePath = `/workspaces/${path3.basename(repo)}-${branch}`;
418
+ let worktreePath = `/workspaces/${path4.basename(repo)}-${branch}`;
204
419
  if (jsonMatch) {
205
420
  try {
206
421
  const bootstrapInfo = JSON.parse(jsonMatch[0]);
@@ -221,12 +436,12 @@ async function workspaceCreate(options) {
221
436
  };
222
437
  state.workspaces[id] = workspace;
223
438
  saveState(state);
224
- p3.log.success(`Workspace ready: ${pc3.cyan(id)}`);
225
- p3.log.info(`Sandbox: ${pc3.dim(sandbox.sandboxId)}`);
226
- p3.log.info(`Worktree: ${pc3.dim(worktreePath)}`);
439
+ p3.log.success(`Workspace ready: ${pc4.cyan(id)}`);
440
+ p3.log.info(`Sandbox: ${pc4.dim(sandbox.sandboxId)}`);
441
+ p3.log.info(`Worktree: ${pc4.dim(worktreePath)}`);
227
442
  return workspace;
228
443
  } catch (error) {
229
- spinner4.stop(pc3.red("Bootstrap failed"));
444
+ spinner4.stop(pc4.red("Bootstrap failed"));
230
445
  try {
231
446
  await sandbox.destroy();
232
447
  } catch {
@@ -242,7 +457,7 @@ async function workspaceAttach(id) {
242
457
  p3.log.info(`Run: compute workspace create --repo owner/repo --branch ${id.split(":")[1] || "main"}`);
243
458
  process.exit(1);
244
459
  }
245
- const sandbox = await compute2.sandbox.getById(workspace.sandboxId);
460
+ const sandbox = await compute3.sandbox.getById(workspace.sandboxId);
246
461
  if (!sandbox) {
247
462
  p3.log.error(`Sandbox no longer exists: ${workspace.sandboxId}`);
248
463
  delete state.workspaces[id];
@@ -251,12 +466,11 @@ async function workspaceAttach(id) {
251
466
  }
252
467
  workspace.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
253
468
  saveState(state);
254
- p3.log.info(`Attaching to workspace: ${pc3.cyan(id)}`);
255
- p3.log.info(`Changing to: ${pc3.dim(workspace.worktreePath)}`);
256
- console.log(pc3.gray("Starting shell..."));
469
+ p3.log.info(`Attaching to workspace: ${pc4.cyan(id)}`);
470
+ p3.log.info(`Worktree: ${pc4.dim(workspace.worktreePath)}`);
471
+ console.log(pc4.gray(`Tip: commands run at sandbox root; use { cwd: "${workspace.worktreePath}" } on runCommand to scope them.`));
257
472
  try {
258
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
259
- await startPTY(sandbox, { initialCommand: `cd ${shellEscape(workspace.worktreePath)}` });
473
+ await startREPL(sandbox, workspace.provider);
260
474
  } catch (error) {
261
475
  p3.log.error(`Failed to attach: ${error.message}`);
262
476
  process.exit(1);
@@ -271,19 +485,19 @@ async function sandboxList() {
271
485
  return;
272
486
  }
273
487
  console.log();
274
- console.log(pc3.bold("Sandboxes"));
488
+ console.log(pc4.bold("Sandboxes"));
275
489
  console.log();
276
490
  for (const ws of workspaces) {
277
491
  const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
278
492
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
279
- const status = age > 7 ? pc3.red("stale") : age > 1 ? pc3.yellow("idle") : pc3.green("active");
280
- console.log(` ${status} ${pc3.cyan(ws.sandboxId)}`);
281
- console.log(` Workspace: ${pc3.dim(ws.id)}`);
493
+ const status = age > 7 ? pc4.red("stale") : age > 1 ? pc4.yellow("idle") : pc4.green("active");
494
+ console.log(` ${status} ${pc4.cyan(ws.sandboxId)}`);
495
+ console.log(` Workspace: ${pc4.dim(ws.id)}`);
282
496
  console.log(` Provider: ${ws.provider}`);
283
- console.log(` Last active: ${pc3.gray(ageStr)}`);
497
+ console.log(` Last active: ${pc4.gray(ageStr)}`);
284
498
  console.log();
285
499
  }
286
- console.log(pc3.gray('Tip: Use "compute workspaces" to see full workspace details'));
500
+ console.log(pc4.gray('Tip: Use "compute workspaces" to see full workspace details'));
287
501
  }
288
502
  async function workspaceList() {
289
503
  const state = loadState();
@@ -294,15 +508,15 @@ async function workspaceList() {
294
508
  return;
295
509
  }
296
510
  console.log();
297
- console.log(pc3.bold("Workspaces"));
511
+ console.log(pc4.bold("Workspaces"));
298
512
  console.log();
299
513
  for (const ws of workspaces) {
300
514
  const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
301
515
  const ageStr = age === 0 ? "today" : `${age}d ago`;
302
- console.log(` ${pc3.cyan(ws.id)}`);
303
- console.log(` Sandbox: ${pc3.dim(ws.sandboxId)} (${ws.provider})`);
304
- console.log(` Path: ${pc3.dim(ws.worktreePath)}`);
305
- console.log(` Last used: ${pc3.gray(ageStr)}`);
516
+ console.log(` ${pc4.cyan(ws.id)}`);
517
+ console.log(` Sandbox: ${pc4.dim(ws.sandboxId)} (${ws.provider})`);
518
+ console.log(` Path: ${pc4.dim(ws.worktreePath)}`);
519
+ console.log(` Last used: ${pc4.gray(ageStr)}`);
306
520
  console.log();
307
521
  }
308
522
  }
@@ -328,9 +542,9 @@ async function workspaceDestroy(id) {
328
542
  p3.log.error(`Workspace not found: ${id}`);
329
543
  return;
330
544
  }
331
- p3.log.info(`Destroying workspace: ${pc3.cyan(id)}`);
545
+ p3.log.info(`Destroying workspace: ${pc4.cyan(id)}`);
332
546
  try {
333
- await compute2.sandbox.destroy(workspace.sandboxId);
547
+ await compute3.sandbox.destroy(workspace.sandboxId);
334
548
  delete state.workspaces[id];
335
549
  saveState(state);
336
550
  p3.log.success("Workspace destroyed");
@@ -361,8 +575,8 @@ var init_workspace_cli = __esm({
361
575
  "src/workspace-cli.ts"() {
362
576
  "use strict";
363
577
  init_esm_shims();
364
- init_pty();
365
- WORKSPACE_STATE_FILE = path3.join(os2.homedir(), ".computesdk", "workspaces.json");
578
+ init_repl();
579
+ WORKSPACE_STATE_FILE = path4.join(os3.homedir(), ".computesdk", "workspaces.json");
366
580
  loadWorkspaceState = loadState;
367
581
  }
368
582
  });
@@ -372,11 +586,11 @@ init_esm_shims();
372
586
  import "dotenv/config";
373
587
  import { Command } from "commander";
374
588
  import * as p4 from "@clack/prompts";
375
- import pc4 from "picocolors";
376
- import { readFileSync as readFileSync3 } from "fs";
589
+ import pc5 from "picocolors";
590
+ import { readFileSync as readFileSync4 } from "fs";
377
591
  import { fileURLToPath as fileURLToPath2 } from "url";
378
- import { dirname as dirname2, join as join3 } from "path";
379
- import { compute as compute3 } from "computesdk";
592
+ import { dirname as dirname2, join as join4 } from "path";
593
+ import { compute as compute4 } from "computesdk";
380
594
 
381
595
  // src/providers.ts
382
596
  init_esm_shims();
@@ -449,23 +663,23 @@ function buildProviderConfig(provider) {
449
663
  }
450
664
 
451
665
  // src/index.ts
452
- init_pty();
666
+ init_repl();
453
667
 
454
668
  // src/auth.ts
455
669
  init_esm_shims();
456
670
  import * as http from "http";
457
671
  import * as crypto from "crypto";
458
672
  import { exec } from "child_process";
459
- import * as fs from "fs";
460
- import * as path2 from "path";
461
- import * as os from "os";
673
+ import * as fs2 from "fs";
674
+ import * as path3 from "path";
675
+ import * as os2 from "os";
462
676
  import * as p from "@clack/prompts";
463
- import pc from "picocolors";
677
+ import pc2 from "picocolors";
464
678
  function getCredentialsDir() {
465
- return path2.join(os.homedir(), ".computesdk");
679
+ return path3.join(os2.homedir(), ".computesdk");
466
680
  }
467
681
  function getCredentialsFile() {
468
- return path2.join(getCredentialsDir(), "credentials.json");
682
+ return path3.join(getCredentialsDir(), "credentials.json");
469
683
  }
470
684
  var AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
471
685
  var CONSOLE_AUTH_URL = "https://console.computesdk.com/cli/auth";
@@ -481,10 +695,10 @@ var SUCCESS_HTML = `<!DOCTYPE html>
481
695
  </html>`;
482
696
  function loadStoredCredentials() {
483
697
  try {
484
- if (!fs.existsSync(getCredentialsFile())) {
698
+ if (!fs2.existsSync(getCredentialsFile())) {
485
699
  return null;
486
700
  }
487
- const raw = fs.readFileSync(getCredentialsFile(), "utf-8");
701
+ const raw = fs2.readFileSync(getCredentialsFile(), "utf-8");
488
702
  const data = JSON.parse(raw);
489
703
  if (typeof data.apiKey === "string" && data.apiKey.length > 0) {
490
704
  return data.apiKey;
@@ -495,10 +709,10 @@ function loadStoredCredentials() {
495
709
  }
496
710
  }
497
711
  function storeCredentials(apiKey) {
498
- if (!fs.existsSync(getCredentialsDir())) {
499
- fs.mkdirSync(getCredentialsDir(), { recursive: true, mode: 448 });
712
+ if (!fs2.existsSync(getCredentialsDir())) {
713
+ fs2.mkdirSync(getCredentialsDir(), { recursive: true, mode: 448 });
500
714
  }
501
- fs.writeFileSync(
715
+ fs2.writeFileSync(
502
716
  getCredentialsFile(),
503
717
  JSON.stringify({ apiKey }, null, 2) + "\n",
504
718
  { mode: 384 }
@@ -506,8 +720,8 @@ function storeCredentials(apiKey) {
506
720
  }
507
721
  function clearStoredCredentials() {
508
722
  try {
509
- if (fs.existsSync(getCredentialsFile())) {
510
- fs.unlinkSync(getCredentialsFile());
723
+ if (fs2.existsSync(getCredentialsFile())) {
724
+ fs2.unlinkSync(getCredentialsFile());
511
725
  }
512
726
  } catch {
513
727
  }
@@ -518,7 +732,7 @@ function openBrowser(url) {
518
732
  if (err) {
519
733
  p.log.warn("Could not open browser automatically.");
520
734
  p.log.info(`Open this URL manually:
521
- ${pc.cyan(url)}`);
735
+ ${pc2.cyan(url)}`);
522
736
  }
523
737
  });
524
738
  }
@@ -589,7 +803,7 @@ async function resolveApiKey(options) {
589
803
  const stored = loadStoredCredentials();
590
804
  if (stored) {
591
805
  p.log.info(
592
- `Using stored credentials from ${pc.dim("~/.computesdk/credentials.json")}`
806
+ `Using stored credentials from ${pc2.dim("~/.computesdk/credentials.json")}`
593
807
  );
594
808
  return stored;
595
809
  }
@@ -603,14 +817,14 @@ async function resolveApiKey(options) {
603
817
  try {
604
818
  storeCredentials(apiKey);
605
819
  p.log.success(
606
- `Credentials saved to ${pc.dim("~/.computesdk/credentials.json")}`
820
+ `Credentials saved to ${pc2.dim("~/.computesdk/credentials.json")}`
607
821
  );
608
822
  } catch {
609
823
  p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
610
824
  }
611
825
  return apiKey;
612
826
  } catch (error) {
613
- spinner4.stop(pc.red("Authentication failed"));
827
+ spinner4.stop(pc2.red("Authentication failed"));
614
828
  throw error;
615
829
  }
616
830
  }
@@ -618,16 +832,16 @@ async function resolveApiKey(options) {
618
832
  // src/setup.ts
619
833
  init_esm_shims();
620
834
  import * as p2 from "@clack/prompts";
621
- import pc2 from "picocolors";
622
- import { compute } from "computesdk";
835
+ import pc3 from "picocolors";
836
+ import { compute as compute2 } from "computesdk";
623
837
  async function ensureAuth(options) {
624
838
  try {
625
839
  const apiKey = await resolveApiKey({ forceLogin: options?.forceLogin });
626
840
  process.env.COMPUTESDK_API_KEY = apiKey;
627
841
  } catch (error) {
628
842
  p2.log.error(error.message);
629
- p2.log.info(`Get your API key at ${pc2.cyan("https://console.computesdk.com")}`);
630
- p2.outro(pc2.red("Setup incomplete"));
843
+ p2.log.info(`Get your API key at ${pc3.cyan("https://console.computesdk.com")}`);
844
+ p2.outro(pc3.red("Setup incomplete"));
631
845
  process.exit(1);
632
846
  }
633
847
  }
@@ -656,7 +870,7 @@ async function resolveProvider(providerFlag, options) {
656
870
  if (available.length === 0) {
657
871
  p2.log.error("No provider credentials detected");
658
872
  p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
659
- p2.outro(pc2.red("Setup incomplete"));
873
+ p2.outro(pc3.red("Setup incomplete"));
660
874
  process.exit(1);
661
875
  }
662
876
  if (available.length === 1) {
@@ -683,7 +897,7 @@ async function resolveProvider(providerFlag, options) {
683
897
  }
684
898
  function configureCompute(provider) {
685
899
  const config = buildProviderConfig(provider);
686
- compute.setConfig(config);
900
+ compute2.setConfig(config);
687
901
  }
688
902
 
689
903
  // src/index.ts
@@ -691,7 +905,7 @@ init_workspace_cli();
691
905
  var __filename2 = fileURLToPath2(import.meta.url);
692
906
  var __dirname2 = dirname2(__filename2);
693
907
  var packageJson = JSON.parse(
694
- readFileSync3(join3(__dirname2, "..", "package.json"), "utf-8")
908
+ readFileSync4(join4(__dirname2, "..", "package.json"), "utf-8")
695
909
  );
696
910
  var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
697
911
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
@@ -700,24 +914,24 @@ program.name("compute").description("Cloud-native workspace management").version
700
914
  program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
701
915
  if (opts.logout) {
702
916
  console.log();
703
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
917
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
704
918
  clearStoredCredentials();
705
919
  p4.log.success("Logged out. Stored credentials removed.");
706
- p4.outro(pc4.green("Done!"));
920
+ p4.outro(pc5.green("Done!"));
707
921
  process.exit(0);
708
922
  }
709
923
  if (opts.login) {
710
924
  console.log();
711
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
925
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
712
926
  await ensureAuth({ forceLogin: true });
713
- p4.outro(pc4.green("Authenticated!"));
927
+ p4.outro(pc5.green("Authenticated!"));
714
928
  process.exit(0);
715
929
  }
716
930
  program.help();
717
931
  });
718
932
  program.command("create").description("Create a new workspace (same as create-compute)").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
719
933
  console.log();
720
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
934
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
721
935
  await ensureAuth();
722
936
  if (!repo) {
723
937
  const state = loadWorkspaceState();
@@ -738,7 +952,7 @@ program.command("create").description("Create a new workspace (same as create-co
738
952
  message: "Select workspace to attach (or create new)",
739
953
  options: [
740
954
  ...choices,
741
- { value: "__new__", label: pc4.gray("+ Create new workspace") }
955
+ { value: "__new__", label: pc5.gray("+ Create new workspace") }
742
956
  ]
743
957
  });
744
958
  if (p4.isCancel(selected)) {
@@ -779,24 +993,24 @@ program.command("create").description("Create a new workspace (same as create-co
779
993
  branch: options.branch,
780
994
  provider
781
995
  });
782
- spinner4.stop(`Workspace ready: ${pc4.cyan(workspace.id)}`);
996
+ spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
783
997
  if (options.attach !== false) {
784
998
  console.log();
785
999
  await workspaceAttach(workspace.id);
786
1000
  } else {
787
- p4.outro(pc4.green("Workspace created!"));
1001
+ p4.outro(pc5.green("Workspace created!"));
788
1002
  }
789
1003
  } catch (error) {
790
- spinner4.stop(pc4.red("Failed to create workspace"));
1004
+ spinner4.stop(pc5.red("Failed to create workspace"));
791
1005
  p4.log.error(error.message);
792
1006
  process.exit(1);
793
1007
  }
794
1008
  });
795
1009
  var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw sandboxes (VMs without git/worktree)");
796
- sandboxCmd.command("create").description("Create a new sandbox").option("-p, --provider <provider>", "provider to use").option("--timeout <duration>", "sandbox timeout (e.g. 5m, 1h)").option("--name <name>", "sandbox name for findOrCreate").option("--connect", "start interactive shell after creation").option("--silent", "only output the sandbox ID").action(async (opts) => {
1010
+ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --provider <provider>", "provider to use").option("--timeout <duration>", "sandbox timeout (e.g. 5m, 1h)").option("--name <name>", "sandbox name").option("--connect", "drop into the REPL after creation").option("--silent", "only output the sandbox ID").action(async (opts) => {
797
1011
  if (!opts.silent) {
798
1012
  console.log();
799
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1013
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
800
1014
  }
801
1015
  await ensureAuth();
802
1016
  const provider = await resolveProvider(opts.provider);
@@ -805,10 +1019,10 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
805
1019
  const spinner4 = p4.spinner();
806
1020
  spinner4.start("Creating sandbox...");
807
1021
  try {
808
- const sandbox = await compute3.sandbox.create(
1022
+ const sandbox = await compute4.sandbox.create(
809
1023
  buildCreateOptions(opts)
810
1024
  );
811
- spinner4.stop(`Sandbox ready: ${pc4.cyan(sandbox.sandboxId)}`);
1025
+ spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
812
1026
  const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
813
1027
  await saveSandbox2({
814
1028
  id: sandbox.sandboxId,
@@ -818,19 +1032,16 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
818
1032
  lastUsed: (/* @__PURE__ */ new Date()).toISOString()
819
1033
  });
820
1034
  if (opts.connect) {
821
- console.log(pc4.gray(" Entering shell... (.exit or Ctrl+D to return)"));
822
- console.log();
823
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
824
- await startPTY(sandbox);
1035
+ await startREPL(sandbox, provider);
825
1036
  }
826
1037
  } catch (error) {
827
- spinner4.stop(pc4.red("Failed to create sandbox"));
1038
+ spinner4.stop(pc5.red("Failed to create sandbox"));
828
1039
  p4.log.error(error.message);
829
1040
  process.exit(1);
830
1041
  }
831
1042
  } else {
832
1043
  try {
833
- const sandbox = await compute3.sandbox.create(
1044
+ const sandbox = await compute4.sandbox.create(
834
1045
  buildCreateOptions(opts)
835
1046
  );
836
1047
  process.stdout.write(sandbox.sandboxId);
@@ -843,9 +1054,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
843
1054
  lastUsed: (/* @__PURE__ */ new Date()).toISOString()
844
1055
  });
845
1056
  if (opts.connect) {
846
- console.log();
847
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
848
- await startPTY(sandbox);
1057
+ await startREPL(sandbox, provider);
849
1058
  }
850
1059
  } catch (error) {
851
1060
  process.stderr.write(error.message + "\n");
@@ -855,14 +1064,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
855
1064
  });
856
1065
  sandboxCmd.command("destroy").description("Destroy one or more sandboxes").aliases(["rm", "stop"]).argument("[sandbox_id...]", "sandbox IDs to destroy (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxIds, opts) => {
857
1066
  console.log();
858
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1067
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
859
1068
  const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
860
1069
  const state = loadState2();
861
1070
  if (!sandboxIds || sandboxIds.length === 0) {
862
1071
  const workspaces = Object.values(state.workspaces);
863
1072
  if (workspaces.length === 0) {
864
1073
  p4.log.info("No sandboxes to destroy.");
865
- p4.outro(pc4.green("Done!"));
1074
+ p4.outro(pc5.green("Done!"));
866
1075
  return;
867
1076
  }
868
1077
  const choices = workspaces.map((ws) => {
@@ -870,7 +1079,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
870
1079
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
871
1080
  );
872
1081
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
873
- const status = age > 7 ? pc4.red("stale") : age > 1 ? pc4.yellow("idle") : pc4.green("active");
1082
+ const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
874
1083
  return {
875
1084
  value: ws.sandboxId,
876
1085
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -891,14 +1100,14 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
891
1100
  configureCompute(provider);
892
1101
  const results = await Promise.allSettled(
893
1102
  sandboxIds.map(async (id) => {
894
- await compute3.sandbox.destroy(id);
1103
+ await compute4.sandbox.destroy(id);
895
1104
  return id;
896
1105
  })
897
1106
  );
898
1107
  for (const result of results) {
899
1108
  if (result.status === "fulfilled") {
900
1109
  const sandboxId = result.value;
901
- console.log(pc4.green(`Destroyed ${sandboxId}`));
1110
+ console.log(pc5.green(`Destroyed ${sandboxId}`));
902
1111
  const workspaceId = Object.keys(state.workspaces).find(
903
1112
  (key) => state.workspaces[key].sandboxId === sandboxId
904
1113
  );
@@ -906,19 +1115,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
906
1115
  delete state.workspaces[workspaceId];
907
1116
  }
908
1117
  } else {
909
- console.error(pc4.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
1118
+ console.error(pc5.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
910
1119
  }
911
1120
  }
912
1121
  saveState2(state);
913
1122
  });
914
1123
  sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
915
1124
  console.log();
916
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1125
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
917
1126
  await sandboxList();
918
1127
  });
919
- sandboxCmd.command("connect").description("Connect to a sandbox via PTY (same as compute connect)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
1128
+ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; same as compute connect)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
920
1129
  console.log();
921
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1130
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
922
1131
  await ensureAuth();
923
1132
  const provider = await resolveProvider(opts.provider);
924
1133
  configureCompute(provider);
@@ -929,7 +1138,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox via PTY (same as
929
1138
  if (workspaces.length === 0) {
930
1139
  p4.log.info("No sandboxes found.");
931
1140
  p4.log.info("Create one with: compute sandbox create");
932
- p4.outro(pc4.green("Done!"));
1141
+ p4.outro(pc5.green("Done!"));
933
1142
  return;
934
1143
  }
935
1144
  const choices = workspaces.map((ws) => {
@@ -937,7 +1146,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox via PTY (same as
937
1146
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
938
1147
  );
939
1148
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
940
- const status = age > 7 ? pc4.red("stale") : age > 1 ? pc4.yellow("idle") : pc4.green("active");
1149
+ const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
941
1150
  return {
942
1151
  value: ws.sandboxId,
943
1152
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -955,22 +1164,21 @@ sandboxCmd.command("connect").description("Connect to a sandbox via PTY (same as
955
1164
  }
956
1165
  const spinner4 = p4.spinner();
957
1166
  spinner4.start("Connecting...");
958
- const sandbox = await compute3.sandbox.getById(sandboxId);
1167
+ const sandbox = await compute4.sandbox.getById(sandboxId);
959
1168
  if (!sandbox) {
960
- spinner4.stop(pc4.red(`Sandbox not found: ${sandboxId}`));
1169
+ spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
961
1170
  process.exit(1);
962
1171
  }
963
1172
  spinner4.stop("");
964
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
965
- await startPTY(sandbox);
1173
+ await startREPL(sandbox, provider);
966
1174
  });
967
1175
  sandboxCmd.command("exec").description("Execute a command in a sandbox").argument("<sandbox_id>", "sandbox ID").argument("<command...>", "command to run").option("-p, --provider <provider>", "provider to use").option("-e, --env <key=value...>", "environment variables").option("-w, --workdir <directory>", "working directory").passThroughOptions().action(async (sandboxId, command, opts) => {
968
1176
  await ensureAuth();
969
1177
  const provider = await resolveProvider(opts.provider);
970
1178
  configureCompute(provider);
971
- const sandbox = await compute3.sandbox.getById(sandboxId);
1179
+ const sandbox = await compute4.sandbox.getById(sandboxId);
972
1180
  if (!sandbox) {
973
- console.error(pc4.red(`Sandbox not found: ${sandboxId}`));
1181
+ console.error(pc5.red(`Sandbox not found: ${sandboxId}`));
974
1182
  process.exit(1);
975
1183
  }
976
1184
  const cmd = buildExecCommand(command, opts);
@@ -983,9 +1191,9 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
983
1191
  }
984
1192
  process.exit(result.exitCode);
985
1193
  });
986
- program.command("connect").description("Connect to a sandbox via PTY").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
1194
+ program.command("connect").description("Connect to a sandbox (opens REPL)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
987
1195
  console.log();
988
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1196
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
989
1197
  await ensureAuth();
990
1198
  const provider = await resolveProvider(opts.provider);
991
1199
  configureCompute(provider);
@@ -996,7 +1204,7 @@ program.command("connect").description("Connect to a sandbox via PTY").aliases([
996
1204
  if (workspaces.length === 0) {
997
1205
  p4.log.info("No sandboxes found.");
998
1206
  p4.log.info("Create one with: compute sandbox create");
999
- p4.outro(pc4.green("Done!"));
1207
+ p4.outro(pc5.green("Done!"));
1000
1208
  return;
1001
1209
  }
1002
1210
  const choices = workspaces.map((ws) => {
@@ -1004,7 +1212,7 @@ program.command("connect").description("Connect to a sandbox via PTY").aliases([
1004
1212
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1005
1213
  );
1006
1214
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1007
- const status = age > 7 ? pc4.red("stale") : age > 1 ? pc4.yellow("idle") : pc4.green("active");
1215
+ const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
1008
1216
  return {
1009
1217
  value: ws.sandboxId,
1010
1218
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1022,23 +1230,22 @@ program.command("connect").description("Connect to a sandbox via PTY").aliases([
1022
1230
  }
1023
1231
  const spinner4 = p4.spinner();
1024
1232
  spinner4.start("Connecting...");
1025
- const sandbox = await compute3.sandbox.getById(sandboxId);
1233
+ const sandbox = await compute4.sandbox.getById(sandboxId);
1026
1234
  if (!sandbox) {
1027
- spinner4.stop(pc4.red(`Sandbox not found: ${sandboxId}`));
1235
+ spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
1028
1236
  process.exit(1);
1029
1237
  }
1030
1238
  spinner4.stop("");
1031
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
1032
- await startPTY(sandbox);
1239
+ await startREPL(sandbox, provider);
1033
1240
  });
1034
1241
  var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
1035
1242
  workspaceCmd.command("create").description("Create a new workspace").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
1036
1243
  console.log();
1037
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1244
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1038
1245
  await ensureAuth();
1039
1246
  if (!repo) {
1040
1247
  p4.log.info("Usage: compute workspace create owner/repo --branch main");
1041
- p4.outro(pc4.green("Done!"));
1248
+ p4.outro(pc5.green("Done!"));
1042
1249
  return;
1043
1250
  }
1044
1251
  const provider = await resolveProvider(options.provider, { interactive: true });
@@ -1051,33 +1258,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
1051
1258
  branch: options.branch,
1052
1259
  provider
1053
1260
  });
1054
- spinner4.stop(`Workspace ready: ${pc4.cyan(workspace.id)}`);
1261
+ spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
1055
1262
  if (options.attach !== false) {
1056
1263
  console.log();
1057
1264
  await workspaceAttach(workspace.id);
1058
1265
  } else {
1059
- p4.outro(pc4.green("Workspace created!"));
1266
+ p4.outro(pc5.green("Workspace created!"));
1060
1267
  }
1061
1268
  } catch (error) {
1062
- spinner4.stop(pc4.red("Failed to create workspace"));
1269
+ spinner4.stop(pc5.red("Failed to create workspace"));
1063
1270
  p4.log.error(error.message);
1064
1271
  process.exit(1);
1065
1272
  }
1066
1273
  });
1067
1274
  workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
1068
1275
  console.log();
1069
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1276
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1070
1277
  await workspaceList();
1071
1278
  });
1072
1279
  workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").argument("[id]", "Workspace ID (repo:branch format, optional - will show picker if not provided)").action(async (id) => {
1073
1280
  console.log();
1074
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1281
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1075
1282
  if (!id) {
1076
1283
  const state = loadWorkspaceState();
1077
1284
  const workspaceIds = Object.keys(state.workspaces);
1078
1285
  if (workspaceIds.length === 0) {
1079
1286
  p4.log.info("No workspaces to destroy.");
1080
- p4.outro(pc4.green("Done!"));
1287
+ p4.outro(pc5.green("Done!"));
1081
1288
  return;
1082
1289
  }
1083
1290
  const choices = workspaceIds.map((key) => {
@@ -1088,7 +1295,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1088
1295
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1089
1296
  return {
1090
1297
  value: key,
1091
- label: `${pc4.cyan(key)} (${ws.provider}, last used ${ageStr})`
1298
+ label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
1092
1299
  };
1093
1300
  });
1094
1301
  const selected = await p4.multiselect({
@@ -1108,14 +1315,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1108
1315
  });
1109
1316
  workspaceCmd.command("connect").alias("attach").description("Connect to an existing workspace (with git context)").argument("[id]", "Workspace ID (optional - will show picker if not provided)").action(async (id) => {
1110
1317
  console.log();
1111
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1318
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1112
1319
  if (!id) {
1113
1320
  const state = loadWorkspaceState();
1114
1321
  const workspaceIds = Object.keys(state.workspaces);
1115
1322
  if (workspaceIds.length === 0) {
1116
1323
  p4.log.info("No workspaces found.");
1117
1324
  p4.log.info("Create one with: compute create owner/repo");
1118
- p4.outro(pc4.green("Done!"));
1325
+ p4.outro(pc5.green("Done!"));
1119
1326
  return;
1120
1327
  }
1121
1328
  const choices = workspaceIds.map((key) => {
@@ -1126,7 +1333,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
1126
1333
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1127
1334
  return {
1128
1335
  value: key,
1129
- label: `${pc4.cyan(key)} (${ws.provider}, last used ${ageStr})`
1336
+ label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
1130
1337
  };
1131
1338
  });
1132
1339
  const selected = await p4.select({
@@ -1147,11 +1354,11 @@ program.command("run").description("Create a sandbox, run a command, then option
1147
1354
  configureCompute(provider);
1148
1355
  let sandbox;
1149
1356
  try {
1150
- sandbox = await compute3.sandbox.create(
1357
+ sandbox = await compute4.sandbox.create(
1151
1358
  buildCreateOptions(opts)
1152
1359
  );
1153
1360
  } catch (error) {
1154
- console.error(pc4.red(`Failed to create sandbox: ${error.message}`));
1361
+ console.error(pc5.red(`Failed to create sandbox: ${error.message}`));
1155
1362
  process.exit(1);
1156
1363
  }
1157
1364
  try {
@@ -1174,35 +1381,35 @@ program.command("run").description("Create a sandbox, run a command, then option
1174
1381
  } catch {
1175
1382
  }
1176
1383
  }
1177
- console.error(pc4.red(error.message));
1384
+ console.error(pc5.red(error.message));
1178
1385
  process.exit(1);
1179
1386
  }
1180
1387
  });
1181
1388
  program.command("providers").description("List all providers and their configuration status").action(async () => {
1182
1389
  console.log();
1183
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1390
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1184
1391
  const statuses = getProviderStatus();
1185
1392
  console.log();
1186
1393
  for (const status of statuses) {
1187
- const icon = status.ready ? pc4.green("\u25CF") : pc4.gray("\u25CB");
1188
- const name = status.ready ? pc4.white(status.name) : pc4.gray(status.name);
1189
- const detail = status.ready ? pc4.green("ready") : pc4.gray(`missing: ${status.missing.join(", ")}`);
1394
+ const icon = status.ready ? pc5.green("\u25CF") : pc5.gray("\u25CB");
1395
+ const name = status.ready ? pc5.white(status.name) : pc5.gray(status.name);
1396
+ const detail = status.ready ? pc5.green("ready") : pc5.gray(`missing: ${status.missing.join(", ")}`);
1190
1397
  console.log(` ${icon} ${name} ${detail}`);
1191
1398
  }
1192
1399
  console.log();
1193
1400
  });
1194
1401
  program.command("login").description("Authenticate with ComputeSDK").action(async () => {
1195
1402
  console.log();
1196
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1403
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1197
1404
  await ensureAuth({ forceLogin: true });
1198
- p4.outro(pc4.green("Authenticated!"));
1405
+ p4.outro(pc5.green("Authenticated!"));
1199
1406
  });
1200
1407
  program.command("logout").description("Clear stored credentials").action(() => {
1201
1408
  console.log();
1202
- p4.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
1409
+ p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1203
1410
  clearStoredCredentials();
1204
1411
  p4.log.success("Logged out. Stored credentials removed.");
1205
- p4.outro(pc4.green("Done!"));
1412
+ p4.outro(pc5.green("Done!"));
1206
1413
  });
1207
1414
  function buildCreateOptions(opts) {
1208
1415
  const options = {};
@@ -1271,7 +1478,7 @@ function parseDuration(input) {
1271
1478
  return total;
1272
1479
  }
1273
1480
  program.parseAsync(process.argv).catch((error) => {
1274
- console.error(pc4.red(`
1481
+ console.error(pc5.red(`
1275
1482
  Error: ${error.message}`));
1276
1483
  process.exit(1);
1277
1484
  });