@computesdk/cli 1.0.13 → 1.0.14

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
@@ -23,7 +23,7 @@ import * as readline from "readline";
23
23
  import * as path2 from "path";
24
24
  import * as os from "os";
25
25
  import * as fs from "fs";
26
- import pc2 from "picocolors";
26
+ import pc4 from "picocolors";
27
27
  import { compute } from "computesdk";
28
28
  async function startREPL(sandbox, provider) {
29
29
  void provider;
@@ -34,8 +34,8 @@ async function startREPL(sandbox, provider) {
34
34
  await runSession(ctx);
35
35
  }
36
36
  function buildPrompt(sandboxId) {
37
- if (!sandboxId) return pc2.gray("[no sandbox] ") + pc2.bold("$ ");
38
- return pc2.cyan(`[${sandboxId}] `) + pc2.bold("$ ");
37
+ if (!sandboxId) return pc4.gray("[no sandbox] ") + pc4.bold("$ ");
38
+ return pc4.cyan(`[${sandboxId}] `) + pc4.bold("$ ");
39
39
  }
40
40
  function runSession(ctx) {
41
41
  const rl = readline.createInterface({
@@ -80,13 +80,13 @@ function runSession(ctx) {
80
80
  return;
81
81
  }
82
82
  } else if (ctx.sandboxes.size === 0) {
83
- console.log(pc2.yellow("No active sandbox. Use /new to create one."));
83
+ console.log(pc4.yellow("No active sandbox. Use /new to create one."));
84
84
  } else {
85
85
  const sandbox = ctx.sandboxes.get(ctx.activeSandboxId);
86
86
  await executeCommand(sandbox, trimmed);
87
87
  }
88
88
  } catch (error) {
89
- console.log(pc2.red(`\u2717 ${error.message}`));
89
+ console.log(pc4.red(`\u2717 ${error.message}`));
90
90
  }
91
91
  if (!resolved) {
92
92
  rl.prompt();
@@ -104,13 +104,13 @@ async function executeCommand(sandbox, command) {
104
104
  }
105
105
  }
106
106
  if (result.stderr) {
107
- process.stderr.write(pc2.red(result.stderr));
107
+ process.stderr.write(pc4.red(result.stderr));
108
108
  if (!result.stderr.endsWith("\n")) {
109
109
  console.log();
110
110
  }
111
111
  }
112
112
  if (result.exitCode !== 0) {
113
- console.log(pc2.gray(`exit code: ${result.exitCode}`));
113
+ console.log(pc4.gray(`exit code: ${result.exitCode}`));
114
114
  }
115
115
  }
116
116
  async function handleSpecialCommand(ctx, input, rl) {
@@ -124,67 +124,67 @@ async function handleSpecialCommand(ctx, input, rl) {
124
124
  case "info":
125
125
  case "i": {
126
126
  if (ctx.sandboxes.size === 0) {
127
- console.log(pc2.yellow("No active sandbox. Use /new to create one."));
127
+ console.log(pc4.yellow("No active sandbox. Use /new to create one."));
128
128
  return null;
129
129
  }
130
130
  const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
131
131
  console.log();
132
- console.log(pc2.bold(" Sandbox Info"));
133
- console.log(` ID: ${pc2.cyan(activeSandbox.sandboxId)}`);
132
+ console.log(pc4.bold(" Sandbox Info"));
133
+ console.log(` ID: ${pc4.cyan(activeSandbox.sandboxId)}`);
134
134
  console.log();
135
135
  return null;
136
136
  }
137
137
  case "url":
138
138
  case "u": {
139
139
  if (ctx.sandboxes.size === 0) {
140
- console.log(pc2.yellow("No active sandbox. Use /new to create one."));
140
+ console.log(pc4.yellow("No active sandbox. Use /new to create one."));
141
141
  return null;
142
142
  }
143
143
  const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
144
144
  if (args.length === 0) {
145
- console.log(pc2.gray("Usage: /url <port>"));
145
+ console.log(pc4.gray("Usage: /url <port>"));
146
146
  } else {
147
147
  const port = parseInt(args[0], 10);
148
148
  if (isNaN(port)) {
149
- console.log(pc2.red("Invalid port number"));
149
+ console.log(pc4.red("Invalid port number"));
150
150
  } else {
151
151
  const url = await activeSandbox.getUrl({ port });
152
- console.log(pc2.cyan(url));
152
+ console.log(pc4.cyan(url));
153
153
  }
154
154
  }
155
155
  return null;
156
156
  }
157
157
  case "new": {
158
- console.log(pc2.gray(" Creating new sandbox..."));
158
+ console.log(pc4.gray(" Creating new sandbox..."));
159
159
  const newSandbox = await compute.sandbox.create();
160
160
  ctx.sandboxes.set(newSandbox.sandboxId, newSandbox);
161
161
  ctx.activeSandboxId = newSandbox.sandboxId;
162
162
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
163
- console.log(` Sandbox ready: ${pc2.cyan(newSandbox.sandboxId)}`);
164
- console.log(` ${pc2.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
163
+ console.log(` Sandbox ready: ${pc4.cyan(newSandbox.sandboxId)}`);
164
+ console.log(` ${pc4.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
165
165
  return null;
166
166
  }
167
167
  case "destroy": {
168
168
  if (args.length === 0) {
169
- console.log(pc2.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
170
- console.log(pc2.gray("Use /list to see all sandboxes"));
169
+ console.log(pc4.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
170
+ console.log(pc4.gray("Use /list to see all sandboxes"));
171
171
  return null;
172
172
  }
173
173
  if (args[0] === "--all") {
174
174
  if (ctx.sandboxes.size === 0) {
175
- console.log(pc2.yellow(" No sandboxes to destroy."));
175
+ console.log(pc4.yellow(" No sandboxes to destroy."));
176
176
  return null;
177
177
  }
178
178
  const count = ctx.sandboxes.size;
179
- console.log(pc2.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
179
+ console.log(pc4.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
180
180
  await Promise.all(
181
181
  Array.from(ctx.sandboxes.values()).map((s) => s.destroy())
182
182
  );
183
183
  ctx.sandboxes.clear();
184
184
  ctx.activeSandboxId = "";
185
185
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
186
- console.log(` Destroyed ${pc2.cyan(String(count))} sandbox${count > 1 ? "es" : ""}`);
187
- console.log(pc2.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
186
+ console.log(` Destroyed ${pc4.cyan(String(count))} sandbox${count > 1 ? "es" : ""}`);
187
+ console.log(pc4.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
188
188
  return null;
189
189
  }
190
190
  const targetId = args[0];
@@ -194,13 +194,13 @@ async function handleSpecialCommand(ctx, input, rl) {
194
194
  if (matches.length === 1) {
195
195
  return destroySandbox(ctx, matches[0], rl);
196
196
  } else if (matches.length > 1) {
197
- console.log(pc2.yellow(" Ambiguous ID. Matches:"));
197
+ console.log(pc4.yellow(" Ambiguous ID. Matches:"));
198
198
  for (const id of matches) {
199
- console.log(` ${pc2.cyan(id)}`);
199
+ console.log(` ${pc4.cyan(id)}`);
200
200
  }
201
201
  } else {
202
- console.log(pc2.red(` Sandbox not found: ${targetId}`));
203
- console.log(pc2.gray(" Use /list to see all sandboxes"));
202
+ console.log(pc4.red(` Sandbox not found: ${targetId}`));
203
+ console.log(pc4.gray(" Use /list to see all sandboxes"));
204
204
  }
205
205
  return null;
206
206
  }
@@ -209,10 +209,10 @@ async function handleSpecialCommand(ctx, input, rl) {
209
209
  case "list":
210
210
  case "ls": {
211
211
  console.log();
212
- console.log(pc2.bold(" Sandboxes"));
212
+ console.log(pc4.bold(" Sandboxes"));
213
213
  for (const id of ctx.sandboxes.keys()) {
214
- const marker = id === ctx.activeSandboxId ? pc2.green(" \u25CF") : " ";
215
- console.log(` ${marker} ${pc2.cyan(id)}`);
214
+ const marker = id === ctx.activeSandboxId ? pc4.green(" \u25CF") : " ";
215
+ console.log(` ${marker} ${pc4.cyan(id)}`);
216
216
  }
217
217
  console.log();
218
218
  return null;
@@ -220,30 +220,30 @@ async function handleSpecialCommand(ctx, input, rl) {
220
220
  case "switch":
221
221
  case "sw": {
222
222
  if (args.length === 0) {
223
- console.log(pc2.gray("Usage: /switch <sandbox-id>"));
224
- console.log(pc2.gray("Use /list to see all sandboxes"));
223
+ console.log(pc4.gray("Usage: /switch <sandbox-id>"));
224
+ console.log(pc4.gray("Use /list to see all sandboxes"));
225
225
  return null;
226
226
  }
227
227
  const switchId = args[0];
228
228
  if (ctx.sandboxes.has(switchId)) {
229
229
  ctx.activeSandboxId = switchId;
230
230
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
231
- console.log(` Switched to ${pc2.cyan(switchId)}`);
231
+ console.log(` Switched to ${pc4.cyan(switchId)}`);
232
232
  return null;
233
233
  }
234
234
  const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(switchId));
235
235
  if (matches.length === 1) {
236
236
  ctx.activeSandboxId = matches[0];
237
237
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
238
- console.log(` Switched to ${pc2.cyan(matches[0])}`);
238
+ console.log(` Switched to ${pc4.cyan(matches[0])}`);
239
239
  } else if (matches.length > 1) {
240
- console.log(pc2.yellow(" Ambiguous ID. Matches:"));
240
+ console.log(pc4.yellow(" Ambiguous ID. Matches:"));
241
241
  for (const id of matches) {
242
- console.log(` ${pc2.cyan(id)}`);
242
+ console.log(` ${pc4.cyan(id)}`);
243
243
  }
244
244
  } else {
245
- console.log(pc2.red(` Sandbox not found: ${switchId}`));
246
- console.log(pc2.gray(" Use /list to see all sandboxes"));
245
+ console.log(pc4.red(` Sandbox not found: ${switchId}`));
246
+ console.log(pc4.gray(" Use /list to see all sandboxes"));
247
247
  }
248
248
  return null;
249
249
  }
@@ -252,51 +252,51 @@ async function handleSpecialCommand(ctx, input, rl) {
252
252
  case "q":
253
253
  return "exit";
254
254
  default:
255
- console.log(pc2.yellow(`Unknown command: /${cmd}`));
256
- console.log(pc2.gray("Type /help for available commands"));
255
+ console.log(pc4.yellow(`Unknown command: /${cmd}`));
256
+ console.log(pc4.gray("Type /help for available commands"));
257
257
  return null;
258
258
  }
259
259
  }
260
260
  async function destroySandbox(ctx, sandboxId, rl) {
261
261
  const target = ctx.sandboxes.get(sandboxId);
262
- console.log(pc2.gray(` Destroying ${sandboxId}...`));
262
+ console.log(pc4.gray(` Destroying ${sandboxId}...`));
263
263
  await target.destroy();
264
264
  ctx.sandboxes.delete(sandboxId);
265
- console.log(` Destroyed ${pc2.cyan(sandboxId)}`);
265
+ console.log(` Destroyed ${pc4.cyan(sandboxId)}`);
266
266
  if (ctx.activeSandboxId === sandboxId && ctx.sandboxes.size > 0) {
267
267
  ctx.activeSandboxId = ctx.sandboxes.keys().next().value;
268
268
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
269
- console.log(` Switched to ${pc2.cyan(ctx.activeSandboxId)}`);
269
+ console.log(` Switched to ${pc4.cyan(ctx.activeSandboxId)}`);
270
270
  } else if (ctx.sandboxes.size === 0) {
271
271
  ctx.activeSandboxId = "";
272
272
  rl.setPrompt(buildPrompt(ctx.activeSandboxId));
273
- console.log(pc2.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
273
+ console.log(pc4.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
274
274
  }
275
275
  return null;
276
276
  }
277
277
  function showHelp() {
278
278
  console.log();
279
- console.log(pc2.bold(" Shell Mode"));
280
- console.log(pc2.gray(" Just type commands - they run in the active sandbox"));
279
+ console.log(pc4.bold(" Shell Mode"));
280
+ console.log(pc4.gray(" Just type commands - they run in the active sandbox"));
281
281
  console.log();
282
- console.log(pc2.white(" ls -la"));
283
- console.log(pc2.white(" cat /etc/os-release"));
284
- console.log(pc2.white(" npm install lodash"));
282
+ console.log(pc4.white(" ls -la"));
283
+ console.log(pc4.white(" cat /etc/os-release"));
284
+ console.log(pc4.white(" npm install lodash"));
285
285
  console.log();
286
- console.log(pc2.bold(" Sandbox Commands") + pc2.gray(" (prefix with /)"));
286
+ console.log(pc4.bold(" Sandbox Commands") + pc4.gray(" (prefix with /)"));
287
287
  console.log();
288
- console.log(pc2.cyan(" /new") + pc2.gray(" Create a new sandbox and switch to it"));
289
- console.log(pc2.cyan(" /list") + pc2.gray(" List all sandboxes"));
290
- console.log(pc2.cyan(" /switch <id>") + pc2.gray(" Switch active sandbox"));
291
- console.log(pc2.cyan(" /destroy <id>") + pc2.gray(" Destroy a sandbox by ID"));
292
- console.log(pc2.cyan(" /destroy --all") + pc2.gray(" Destroy all sandboxes"));
288
+ console.log(pc4.cyan(" /new") + pc4.gray(" Create a new sandbox and switch to it"));
289
+ console.log(pc4.cyan(" /list") + pc4.gray(" List all sandboxes"));
290
+ console.log(pc4.cyan(" /switch <id>") + pc4.gray(" Switch active sandbox"));
291
+ console.log(pc4.cyan(" /destroy <id>") + pc4.gray(" Destroy a sandbox by ID"));
292
+ console.log(pc4.cyan(" /destroy --all") + pc4.gray(" Destroy all sandboxes"));
293
293
  console.log();
294
- console.log(pc2.bold(" Other Commands"));
294
+ console.log(pc4.bold(" Other Commands"));
295
295
  console.log();
296
- console.log(pc2.cyan(" /info") + pc2.gray(" Show active sandbox info"));
297
- console.log(pc2.cyan(" /url <port>") + pc2.gray(" Get public URL for port"));
298
- console.log(pc2.cyan(" /help") + pc2.gray(" Show this help"));
299
- console.log(pc2.cyan(" /exit") + pc2.gray(" Exit the REPL"));
296
+ console.log(pc4.cyan(" /info") + pc4.gray(" Show active sandbox info"));
297
+ console.log(pc4.cyan(" /url <port>") + pc4.gray(" Get public URL for port"));
298
+ console.log(pc4.cyan(" /help") + pc4.gray(" Show this help"));
299
+ console.log(pc4.cyan(" /exit") + pc4.gray(" Exit the REPL"));
300
300
  console.log();
301
301
  }
302
302
  function loadHistory(rl) {
@@ -343,7 +343,7 @@ import * as fs3 from "fs";
343
343
  import * as path4 from "path";
344
344
  import * as os3 from "os";
345
345
  import * as p3 from "@clack/prompts";
346
- import pc5 from "picocolors";
346
+ import pc7 from "picocolors";
347
347
  function shellEscape(arg) {
348
348
  if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg)) return arg;
349
349
  return `'${arg.replace(/'/g, "'\\''")}'`;
@@ -382,7 +382,7 @@ async function workspaceCreate(options) {
382
382
  }
383
383
  existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
384
384
  saveState(state);
385
- p3.log.info(`Attaching to existing workspace: ${pc5.cyan(id)}`);
385
+ p3.log.info(`Attaching to existing workspace: ${pc7.cyan(id)}`);
386
386
  return existing;
387
387
  } catch {
388
388
  delete state.workspaces[id];
@@ -394,9 +394,9 @@ async function workspaceCreate(options) {
394
394
  let sandbox;
395
395
  try {
396
396
  sandbox = await compute3.sandbox.create();
397
- spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
397
+ spinner4.stop(`Sandbox ready: ${pc7.cyan(sandbox.sandboxId)}`);
398
398
  } catch (error) {
399
- spinner4.stop(pc5.red("Failed to create sandbox"));
399
+ spinner4.stop(pc7.red("Failed to create sandbox"));
400
400
  throw error;
401
401
  }
402
402
  spinner4.start("Bootstrapping workspace (installing gh CLI, cloning repo)...");
@@ -436,12 +436,12 @@ async function workspaceCreate(options) {
436
436
  };
437
437
  state.workspaces[id] = workspace;
438
438
  saveState(state);
439
- p3.log.success(`Workspace ready: ${pc5.cyan(id)}`);
440
- p3.log.info(`Sandbox: ${pc5.dim(sandbox.sandboxId)}`);
441
- p3.log.info(`Worktree: ${pc5.dim(worktreePath)}`);
439
+ p3.log.success(`Workspace ready: ${pc7.cyan(id)}`);
440
+ p3.log.info(`Sandbox: ${pc7.dim(sandbox.sandboxId)}`);
441
+ p3.log.info(`Worktree: ${pc7.dim(worktreePath)}`);
442
442
  return workspace;
443
443
  } catch (error) {
444
- spinner4.stop(pc5.red("Bootstrap failed"));
444
+ spinner4.stop(pc7.red("Bootstrap failed"));
445
445
  try {
446
446
  await sandbox.destroy();
447
447
  } catch {
@@ -466,9 +466,9 @@ async function workspaceAttach(id) {
466
466
  }
467
467
  workspace.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
468
468
  saveState(state);
469
- p3.log.info(`Attaching to workspace: ${pc5.cyan(id)}`);
470
- p3.log.info(`Worktree: ${pc5.dim(workspace.worktreePath)}`);
471
- console.log(pc5.gray(`Tip: commands run at sandbox root; use { cwd: "${workspace.worktreePath}" } on runCommand to scope them.`));
469
+ p3.log.info(`Attaching to workspace: ${pc7.cyan(id)}`);
470
+ p3.log.info(`Worktree: ${pc7.dim(workspace.worktreePath)}`);
471
+ console.log(pc7.gray(`Tip: commands run at sandbox root; use { cwd: "${workspace.worktreePath}" } on runCommand to scope them.`));
472
472
  try {
473
473
  await startREPL(sandbox, workspace.provider);
474
474
  } catch (error) {
@@ -485,19 +485,19 @@ async function sandboxList() {
485
485
  return;
486
486
  }
487
487
  console.log();
488
- console.log(pc5.bold("Sandboxes"));
488
+ console.log(pc7.bold("Sandboxes"));
489
489
  console.log();
490
490
  for (const ws of workspaces) {
491
491
  const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
492
492
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
493
- const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
494
- console.log(` ${status} ${pc5.cyan(ws.sandboxId)}`);
495
- console.log(` Workspace: ${pc5.dim(ws.id)}`);
493
+ const status = age > 7 ? pc7.red("stale") : age > 1 ? pc7.yellow("idle") : pc7.green("active");
494
+ console.log(` ${status} ${pc7.cyan(ws.sandboxId)}`);
495
+ console.log(` Workspace: ${pc7.dim(ws.id)}`);
496
496
  console.log(` Provider: ${ws.provider}`);
497
- console.log(` Last active: ${pc5.gray(ageStr)}`);
497
+ console.log(` Last active: ${pc7.gray(ageStr)}`);
498
498
  console.log();
499
499
  }
500
- console.log(pc5.gray('Tip: Use "compute workspaces" to see full workspace details'));
500
+ console.log(pc7.gray('Tip: Use "compute workspaces" to see full workspace details'));
501
501
  }
502
502
  async function workspaceList() {
503
503
  const state = loadState();
@@ -508,15 +508,15 @@ async function workspaceList() {
508
508
  return;
509
509
  }
510
510
  console.log();
511
- console.log(pc5.bold("Workspaces"));
511
+ console.log(pc7.bold("Workspaces"));
512
512
  console.log();
513
513
  for (const ws of workspaces) {
514
514
  const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
515
515
  const ageStr = age === 0 ? "today" : `${age}d ago`;
516
- console.log(` ${pc5.cyan(ws.id)}`);
517
- console.log(` Sandbox: ${pc5.dim(ws.sandboxId)} (${ws.provider})`);
518
- console.log(` Path: ${pc5.dim(ws.worktreePath)}`);
519
- console.log(` Last used: ${pc5.gray(ageStr)}`);
516
+ console.log(` ${pc7.cyan(ws.id)}`);
517
+ console.log(` Sandbox: ${pc7.dim(ws.sandboxId)} (${ws.provider})`);
518
+ console.log(` Path: ${pc7.dim(ws.worktreePath)}`);
519
+ console.log(` Last used: ${pc7.gray(ageStr)}`);
520
520
  console.log();
521
521
  }
522
522
  }
@@ -542,7 +542,7 @@ async function workspaceDestroy(id) {
542
542
  p3.log.error(`Workspace not found: ${id}`);
543
543
  return;
544
544
  }
545
- p3.log.info(`Destroying workspace: ${pc5.cyan(id)}`);
545
+ p3.log.info(`Destroying workspace: ${pc7.cyan(id)}`);
546
546
  try {
547
547
  await compute3.sandbox.destroy(workspace.sandboxId);
548
548
  delete state.workspaces[id];
@@ -586,7 +586,7 @@ init_esm_shims();
586
586
  import "dotenv/config";
587
587
  import { Command } from "commander";
588
588
  import * as p4 from "@clack/prompts";
589
- import pc6 from "picocolors";
589
+ import pc8 from "picocolors";
590
590
  import { readFileSync as readFileSync5 } from "fs";
591
591
  import { fileURLToPath as fileURLToPath2 } from "url";
592
592
  import { dirname as dirname2, join as join5 } from "path";
@@ -842,13 +842,35 @@ var ActionsClient = class {
842
842
  if (value === void 0) continue;
843
843
  for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
844
844
  }
845
- const res = await this.fetchImpl(url, { headers: this.headers() });
845
+ const res = await this.fetchImpl(url, {
846
+ headers: { ...this.headers(), accept: "text/event-stream" }
847
+ });
846
848
  if (!res.ok) {
847
849
  await this.parse(res);
848
850
  }
849
851
  if (!res.body) throw new Error("SSE response has no body");
850
852
  yield* readSseEvents(res.body);
851
853
  }
854
+ /**
855
+ * `sse()` with the frame's `event:` name preserved — for streams whose
856
+ * events are distinguished by name rather than payload shape (process
857
+ * follow: stdout/stderr share a `{chunk}` payload).
858
+ */
859
+ async *sseNamed(path5, params) {
860
+ const url = new URL(`${this.auth.baseUrl}${path5}`);
861
+ for (const [key, value] of Object.entries(params ?? {})) {
862
+ if (value === void 0) continue;
863
+ for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
864
+ }
865
+ const res = await this.fetchImpl(url, {
866
+ headers: { ...this.headers(), accept: "text/event-stream" }
867
+ });
868
+ if (!res.ok) {
869
+ await this.parse(res);
870
+ }
871
+ if (!res.body) throw new Error("SSE response has no body");
872
+ yield* readSseNamedEvents(res.body);
873
+ }
852
874
  /** Download bytes following redirects (artifact URLs 302 to storage). */
853
875
  async download(path5) {
854
876
  const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
@@ -862,7 +884,7 @@ var ActionsClient = class {
862
884
  return new Uint8Array(await res.arrayBuffer());
863
885
  }
864
886
  headers() {
865
- return { authorization: `Bearer ${this.auth.apiKey}` };
887
+ return { authorization: `Bearer ${this.auth.apiKey}`, accept: "application/json" };
866
888
  }
867
889
  async parse(res) {
868
890
  const body = await res.json().catch(() => void 0);
@@ -900,6 +922,34 @@ async function* readSseEvents(body) {
900
922
  reader.releaseLock();
901
923
  }
902
924
  }
925
+ async function* readSseNamedEvents(body) {
926
+ const reader = body.getReader();
927
+ const decoder = new TextDecoder();
928
+ let buffer = "";
929
+ try {
930
+ for (; ; ) {
931
+ const { done, value } = await reader.read();
932
+ if (done) break;
933
+ buffer += decoder.decode(value, { stream: true });
934
+ let idx;
935
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
936
+ const frame = buffer.slice(0, idx);
937
+ buffer = buffer.slice(idx + 2);
938
+ let event = "message";
939
+ const dataLines = [];
940
+ for (const line of frame.split("\n")) {
941
+ if (line.startsWith("event: ")) event = line.slice(7);
942
+ else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
943
+ }
944
+ if (dataLines.length > 0) {
945
+ yield { event, data: JSON.parse(dataLines.join("\n")) };
946
+ }
947
+ }
948
+ }
949
+ } finally {
950
+ reader.releaseLock();
951
+ }
952
+ }
903
953
  function encodeWatchCursor(id, jobId, step, offset) {
904
954
  const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
905
955
  return `${id}:${jobId}:${stepPart}:${offset}`;
@@ -981,19 +1031,22 @@ function sanitizePathPart(name) {
981
1031
  async function client(opts) {
982
1032
  return new ActionsClient(await resolveActionsAuth(opts));
983
1033
  }
984
- function assertVaultValueHost(auth) {
1034
+ function assertSecretValueHost(auth, what) {
985
1035
  if (!isTrustedActionsHost(auth.baseUrl)) {
986
1036
  throw new ActionsCliError(
987
1037
  "untrusted_host",
988
- `Refusing to send or read vault values at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
1038
+ `Refusing to send or read ${what} at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
989
1039
  );
990
1040
  }
991
1041
  }
992
- async function vaultValueClient(opts) {
1042
+ async function secretValueClient(opts, what) {
993
1043
  const auth = await resolveActionsAuth(opts);
994
- assertVaultValueHost(auth);
1044
+ assertSecretValueHost(auth, what);
995
1045
  return new ActionsClient(auth);
996
1046
  }
1047
+ async function vaultValueClient(opts) {
1048
+ return secretValueClient(opts, "vault values");
1049
+ }
997
1050
  function output(opts, data, render) {
998
1051
  if (opts.json) {
999
1052
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
@@ -1386,6 +1439,7 @@ function dispatchBody(workflow, opts) {
1386
1439
  if (opts.providerRegion !== void 0 && opts.provider === void 0) {
1387
1440
  throw new ActionsCliError("invalid_argument", "--provider-region requires --provider.");
1388
1441
  }
1442
+ const maxPrice = maxPriceFields(opts.maxBid, opts.maxBidPer);
1389
1443
  return {
1390
1444
  workflowId: workflow.id,
1391
1445
  ref,
@@ -1394,9 +1448,25 @@ function dispatchBody(workflow, opts) {
1394
1448
  ...opts.provider !== void 0 && {
1395
1449
  provider: opts.provider,
1396
1450
  ...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
1397
- }
1451
+ },
1452
+ ...maxPrice !== void 0 && maxPrice
1398
1453
  };
1399
1454
  }
1455
+ function maxPriceFields(maxBid, maxBidPer) {
1456
+ if (maxBidPer !== void 0 && maxBid === void 0) {
1457
+ throw new ActionsCliError("invalid_argument", "--max-bid-per requires --max-bid.");
1458
+ }
1459
+ if (maxBid === void 0) return void 0;
1460
+ const usd = Number(maxBid);
1461
+ if (!/^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(maxBid) || !Number.isFinite(usd) || usd <= 0) {
1462
+ throw new ActionsCliError("invalid_argument", `Invalid --max-bid "${maxBid}". Expected a positive dollar amount (e.g. 0.12).`);
1463
+ }
1464
+ const per = maxBidPer ?? "second";
1465
+ if (per !== "second" && per !== "minute" && per !== "hour") {
1466
+ throw new ActionsCliError("invalid_argument", `--max-bid-per must be second, minute, or hour, got "${maxBidPer}".`);
1467
+ }
1468
+ return { maxPriceUsd: usd, maxPricePer: per };
1469
+ }
1400
1470
  function parseVaultKind(kind) {
1401
1471
  if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
1402
1472
  throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
@@ -1437,7 +1507,7 @@ function registerActionsCommands(program2) {
1437
1507
  const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
1438
1508
  const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
1439
1509
  common(
1440
- actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)")
1510
+ actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)").option("--max-bid <usd>", "max price per vCPU for a market fill (e.g. 0.12); unfilled jobs fall through to the next provider").option("--max-bid-per <unit>", "time unit --max-bid is priced in: second, minute, or hour (default: second)")
1441
1511
  ).action(async (repo, opts) => {
1442
1512
  try {
1443
1513
  const c = await client(opts);
@@ -1921,6 +1991,773 @@ function registerActionsCommands(program2) {
1921
1991
  });
1922
1992
  }
1923
1993
 
1994
+ // src/market.ts
1995
+ init_esm_shims();
1996
+ import pc2 from "picocolors";
1997
+ var USD_RE = /^\d+(\.\d+)?([eE][+-]?\d+)?$/;
1998
+ var RATE_UNITS = ["second", "minute", "hour"];
1999
+ function parseRate(price, per) {
2000
+ if (price === void 0) {
2001
+ throw new ActionsCliError("invalid_argument", "A price is required \u2014 pass --price <usd>.");
2002
+ }
2003
+ const usd = Number(price);
2004
+ if (!USD_RE.test(price) || !Number.isFinite(usd) || usd <= 0) {
2005
+ throw new ActionsCliError(
2006
+ "invalid_argument",
2007
+ `Invalid --price "${price}". Expected a positive dollar amount (e.g. 0.12).`
2008
+ );
2009
+ }
2010
+ const unit = per ?? "second";
2011
+ if (!RATE_UNITS.includes(unit)) {
2012
+ throw new ActionsCliError(
2013
+ "invalid_argument",
2014
+ `--per must be second, minute, or hour, got "${per}".`
2015
+ );
2016
+ }
2017
+ return { usd, per: unit };
2018
+ }
2019
+ function parseCapacity(value) {
2020
+ if (value === void 0) return void 0;
2021
+ const n = Number(value);
2022
+ if (!Number.isInteger(n) || n <= 0) {
2023
+ throw new ActionsCliError(
2024
+ "invalid_argument",
2025
+ `Invalid --capacity "${value}". Expected a positive integer.`
2026
+ );
2027
+ }
2028
+ return n;
2029
+ }
2030
+ function parseExpiresIn(value) {
2031
+ if (value === void 0) return void 0;
2032
+ const hours = Number(value);
2033
+ if (!Number.isFinite(hours) || hours <= 0) {
2034
+ throw new ActionsCliError(
2035
+ "invalid_argument",
2036
+ `Invalid --expires-in "${value}". Expected a positive number of hours.`
2037
+ );
2038
+ }
2039
+ return hours;
2040
+ }
2041
+ function sellBody(opts) {
2042
+ const rate = parseRate(opts.price, opts.per);
2043
+ const maxConcurrent = parseCapacity(opts.capacity);
2044
+ const expiresInHours = parseExpiresIn(opts.expiresIn);
2045
+ if (opts.renew && expiresInHours === void 0) {
2046
+ throw new ActionsCliError(
2047
+ "invalid_argument",
2048
+ "--renew requires --expires-in \u2014 a standing listing never expires."
2049
+ );
2050
+ }
2051
+ return {
2052
+ size: opts.size,
2053
+ ...opts.region !== void 0 && { region: opts.region },
2054
+ usd: rate.usd,
2055
+ per: rate.per,
2056
+ ...maxConcurrent !== void 0 && { maxConcurrent },
2057
+ ...expiresInHours !== void 0 && { expiresInHours },
2058
+ ...opts.renew && { rollover: true }
2059
+ };
2060
+ }
2061
+ function listingPatchBody(listingId, change) {
2062
+ const body = { askId: listingId };
2063
+ if (change.price !== void 0 || change.per !== void 0) {
2064
+ const rate = parseRate(change.price, change.per);
2065
+ body.usd = rate.usd;
2066
+ body.per = rate.per;
2067
+ }
2068
+ const maxConcurrent = parseCapacity(change.capacity);
2069
+ if (maxConcurrent !== void 0) body.maxConcurrent = maxConcurrent;
2070
+ if (change.status !== void 0) body.status = change.status;
2071
+ if (body.usd === void 0 && body.maxConcurrent === void 0 && body.status === void 0) {
2072
+ throw new ActionsCliError(
2073
+ "invalid_argument",
2074
+ "Nothing to change \u2014 pass --price, --capacity, or a status command."
2075
+ );
2076
+ }
2077
+ return body;
2078
+ }
2079
+ function credentialEnvVar(providerId, fieldName) {
2080
+ const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
2081
+ return `${snake(providerId)}_${snake(fieldName)}`;
2082
+ }
2083
+ function credentialValues(providerId, fields, overrides, env = process.env) {
2084
+ const declared = new Set(fields.map((f) => f.name));
2085
+ const unknown = Object.keys(overrides).filter((name) => !declared.has(name));
2086
+ if (unknown.length > 0) {
2087
+ throw new ActionsCliError(
2088
+ "invalid_argument",
2089
+ `${providerId} takes no credential field "${unknown.join('", "')}" \u2014 expected one of ${fields.map((f) => f.name).join(", ")}.`
2090
+ );
2091
+ }
2092
+ const values = {};
2093
+ const fromFlag = [];
2094
+ const missing = [];
2095
+ const envNames = {};
2096
+ for (const field of fields) {
2097
+ envNames[field.name] = credentialEnvVar(providerId, field.name);
2098
+ const fromOverride = overrides[field.name];
2099
+ if (fromOverride !== void 0 && fromOverride.trim() !== "") {
2100
+ values[field.name] = fromOverride.trim();
2101
+ fromFlag.push(field.name);
2102
+ continue;
2103
+ }
2104
+ const fromEnv = env[envNames[field.name]];
2105
+ if (fromEnv !== void 0 && fromEnv.trim() !== "") {
2106
+ values[field.name] = fromEnv.trim();
2107
+ continue;
2108
+ }
2109
+ if (field.required !== false) missing.push(field);
2110
+ }
2111
+ return { values, missing, fromFlag, envNames };
2112
+ }
2113
+ function credentialBody(fields, values) {
2114
+ if (fields.length === 1) {
2115
+ return { key: values[fields[0].name] };
2116
+ }
2117
+ return { fields: values };
2118
+ }
2119
+ function formatUsd(usd) {
2120
+ return `$${usd.toFixed(6).replace(/\.?0+$/, "")}`;
2121
+ }
2122
+ function formatRate(usd, per) {
2123
+ return `${formatUsd(usd)}/${per}`;
2124
+ }
2125
+ function formatListingStatus(ask) {
2126
+ let label;
2127
+ if (ask.status === "withdrawn") label = pc2.gray("withdrawn");
2128
+ else if (ask.status === "paused") label = pc2.yellow("paused");
2129
+ else if (!ask.live) label = pc2.gray("expired");
2130
+ else label = pc2.green("live");
2131
+ if (ask.pendingUsd !== null && ask.pendingPer !== null) {
2132
+ label += pc2.dim(` \u2192 ${formatRate(ask.pendingUsd, ask.pendingPer)} next window`);
2133
+ }
2134
+ return label;
2135
+ }
2136
+ function formatListingRow(ask) {
2137
+ const expiry = ask.expiresAt ? ask.rollover ? `renews ${ask.expiresAt}` : `expires ${ask.expiresAt}` : "standing";
2138
+ return [
2139
+ ask.id,
2140
+ formatListingStatus(ask),
2141
+ safeTerm(ask.size),
2142
+ safeTerm(ask.region ?? "-"),
2143
+ formatRate(ask.usd, ask.per),
2144
+ pc2.dim(`\xD7${ask.maxConcurrent}`),
2145
+ pc2.dim(expiry)
2146
+ ].join(" ");
2147
+ }
2148
+ function formatProviderStatus(p5) {
2149
+ const lines = [];
2150
+ lines.push(`${pc2.bold(safeTerm(p5.name))} ${pc2.dim(`selling ${safeTerm(p5.provider)}`)}`);
2151
+ lines.push(`id: ${p5.id}`);
2152
+ lines.push(
2153
+ `credential: ${p5.executorCredentialConnected ? pc2.green("connected") : pc2.yellow("not connected \u2014 your listings cannot fill until `compute market credential connect`")}`
2154
+ );
2155
+ if (p5.revokedAt) lines.push(pc2.red(`revoked: ${p5.revokedAt}`));
2156
+ lines.push(
2157
+ `sizes: ${p5.sizes.map((s) => `${s.name} (${s.label})`).join(", ") || "-"}`
2158
+ );
2159
+ lines.push(`regions: ${p5.regions.join(", ") || "-"}`);
2160
+ if (p5.credentialFields.length > 0) {
2161
+ lines.push(
2162
+ `credential fields: ${p5.credentialFields.map((f) => `${f.name}${f.secret ? " (secret)" : ""}`).join(", ")}`
2163
+ );
2164
+ }
2165
+ lines.push(`created: ${p5.createdAt}`);
2166
+ return lines.join("\n");
2167
+ }
2168
+ function formatBook(book) {
2169
+ const lines = [];
2170
+ lines.push(pc2.bold("sellers' asking prices"));
2171
+ if (book.asks.length === 0) lines.push(pc2.dim(" no listings are live"));
2172
+ for (const ask of book.asks) {
2173
+ const where = ask.region ? safeTerm(ask.region) : "any region";
2174
+ const expiry = ask.expiresAt ? pc2.dim(ask.rollover ? ` renews ${ask.expiresAt}` : ` until ${ask.expiresAt}`) : "";
2175
+ lines.push(
2176
+ ` ${formatRate(ask.usd, ask.per)} ${safeTerm(ask.providerName)} ${safeTerm(ask.size)} ${where} up to ${ask.maxConcurrent} at once${expiry}`
2177
+ );
2178
+ }
2179
+ lines.push("");
2180
+ lines.push(pc2.bold("buyer offers"));
2181
+ if (book.bids.length === 0) lines.push(pc2.dim(" no open offers"));
2182
+ for (const bid of book.bids) {
2183
+ const where = bid.region ? safeTerm(bid.region) : "anywhere";
2184
+ lines.push(
2185
+ ` pays up to ${formatRate(bid.maxUsd, bid.per)} ${safeTerm(bid.size)} ${where} ${pc2.dim(`posted ${bid.createdAt}`)}`
2186
+ );
2187
+ }
2188
+ lines.push("");
2189
+ lines.push(pc2.bold("recent sales"));
2190
+ if (book.fills.length === 0) lines.push(pc2.dim(" nothing has sold yet"));
2191
+ for (const fill of book.fills) {
2192
+ const statusNote = fill.status === "replaced" ? pc2.yellow(" replaced") : fill.status === "closed" ? pc2.gray(" closed") : "";
2193
+ lines.push(
2194
+ ` ${pc2.dim(fill.id)} ${formatRate(fill.usd, fill.per)} ${safeTerm(fill.provider)} ${safeTerm(fill.size)} ${safeTerm(fill.region ?? "-")} ${pc2.dim(fill.createdAt)}${statusNote}`
2195
+ );
2196
+ }
2197
+ return lines.join("\n");
2198
+ }
2199
+ function formatReplacedFill(fill) {
2200
+ const settled = formatUsd(fill.settledMicroUsd / 1e6);
2201
+ const job = fill.jobRequeued ? pc2.dim(" \u2014 job re-places on the buyer's next provider") : "";
2202
+ return `replaced ${pc2.cyan(fill.fillId)} settled ${settled}${job}`;
2203
+ }
2204
+ function replaceTarget(listingId, fillId) {
2205
+ if (fillId !== void 0) {
2206
+ return {
2207
+ path: `/api/v1/market/fills/${encodeURIComponent(fillId)}/replace`,
2208
+ scope: "fill"
2209
+ };
2210
+ }
2211
+ if (listingId === void 0) {
2212
+ throw new ActionsCliError(
2213
+ "invalid_argument",
2214
+ "Nothing to replace \u2014 pass a listing ID or --fill <fill-id>."
2215
+ );
2216
+ }
2217
+ return {
2218
+ path: `/api/v1/market/listings/${encodeURIComponent(listingId)}/replace`,
2219
+ scope: "listing"
2220
+ };
2221
+ }
2222
+ function formatSettlementRow(s) {
2223
+ const period = `${s.periodStart.slice(0, 10)} \u2192 ${s.periodEnd.slice(0, 10)}`;
2224
+ const paid = s.paidAt ? pc2.green(`paid ${s.paidAt.slice(0, 10)}`) : s.invoiceRef ? pc2.yellow(`invoiced ${safeTerm(s.invoiceRef)}`) : pc2.dim("unpaid");
2225
+ return [
2226
+ pc2.dim(period),
2227
+ `${s.vcpuSeconds.toLocaleString("en-US")} vCPU\xB7s`,
2228
+ `gross ${formatUsd(s.grossUsd)}`,
2229
+ `net ${formatUsd(s.netUsd)}`,
2230
+ paid
2231
+ ].join(" ");
2232
+ }
2233
+ function registerMarketCommands(program2) {
2234
+ const market = program2.command("market").description("Sell compute capacity on the compute market").configureOutput({ outputError: usageErrorOutput });
2235
+ const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
2236
+ common(
2237
+ market.command("listings").alias("list").description("Your listings \u2014 live, paused, expired, and withdrawn")
2238
+ ).action(async (opts) => {
2239
+ try {
2240
+ const { asks } = await (await client(opts)).get(
2241
+ "/api/v1/market/asks"
2242
+ );
2243
+ output(opts, asks, (rows) => {
2244
+ if (rows.length === 0) {
2245
+ console.log("No listings yet \u2014 post one with `compute market sell`.");
2246
+ return;
2247
+ }
2248
+ for (const ask of rows) console.log(formatListingRow(ask));
2249
+ });
2250
+ } catch (e) {
2251
+ fail(e, opts);
2252
+ }
2253
+ });
2254
+ common(
2255
+ market.command("sell").description("Post a listing: sell capacity at a price").requiredOption("--price <usd>", "price per unit of time (e.g. 0.12)").option("--per <unit>", "time unit the price is per: second, minute, or hour (default: second)").option("--size <tier>", "size tier to sell (default: medium, else the provider's first tier)").option("--region <region>", "region to sell in (default: anywhere the provider runs)").option("--capacity <n>", "max simultaneous fills (default: 1)").option("--expires-in <hours>", "delist after this many hours (default: standing)").option("--renew", "re-list for the same window each time it expires (requires --expires-in)")
2256
+ ).action(async (opts) => {
2257
+ try {
2258
+ const c = await client(opts);
2259
+ let size = opts.size;
2260
+ if (size === void 0) {
2261
+ const { provider } = await c.get(
2262
+ "/api/v1/market/provider"
2263
+ );
2264
+ const preset = provider.sizes.find((s) => s.name === "medium") ?? provider.sizes[0];
2265
+ if (!preset) {
2266
+ throw new ActionsCliError(
2267
+ "invalid_argument",
2268
+ `--size is required \u2014 ${provider.provider} advertises no size tiers.`
2269
+ );
2270
+ }
2271
+ size = preset.name;
2272
+ console.error(pc2.dim(`no --size given \u2014 listing at "${size}"`));
2273
+ }
2274
+ const result = await c.post(
2275
+ "/api/v1/market/asks",
2276
+ sellBody({ ...opts, size })
2277
+ );
2278
+ output(opts, result, (r) => {
2279
+ console.log(`listed ${pc2.cyan(r.ask.id)}`);
2280
+ console.log(formatListingRow(r.ask));
2281
+ });
2282
+ } catch (e) {
2283
+ fail(e, opts);
2284
+ }
2285
+ });
2286
+ common(
2287
+ market.command("price").description("Reprice a listing").argument("<listing-id>", "listing ID").requiredOption("--price <usd>", "new price per unit of time (e.g. 0.08)").option("--per <unit>", "time unit the price is per: second, minute, or hour (default: second)")
2288
+ ).action(async (listingId, opts) => {
2289
+ try {
2290
+ const result = await (await client(opts)).post(
2291
+ "/api/v1/market/asks",
2292
+ listingPatchBody(listingId, { price: opts.price, per: opts.per })
2293
+ );
2294
+ output(opts, result, (r) => {
2295
+ const rate = r.ask.pendingUsd !== null && r.ask.pendingPer !== null ? `${formatRate(r.ask.pendingUsd, r.ask.pendingPer)} ${pc2.dim("(applies next window)")}` : formatRate(r.ask.usd, r.ask.per);
2296
+ console.log(`repriced ${pc2.cyan(r.ask.id)} ${rate}`);
2297
+ });
2298
+ } catch (e) {
2299
+ fail(e, opts);
2300
+ }
2301
+ });
2302
+ const setStatus = async (listingId, status, opts) => {
2303
+ try {
2304
+ const result = await (await client(opts)).post(
2305
+ "/api/v1/market/asks",
2306
+ listingPatchBody(listingId, { status })
2307
+ );
2308
+ output(opts, result, (r) => {
2309
+ console.log(`${status === "live" ? "resumed" : "paused"} ${pc2.cyan(r.ask.id)}`);
2310
+ });
2311
+ } catch (e) {
2312
+ fail(e, opts);
2313
+ }
2314
+ };
2315
+ common(
2316
+ market.command("pause").description("Pause a listing \u2014 it stays posted but cannot fill").argument("<listing-id>", "listing ID")
2317
+ ).action((listingId, opts) => setStatus(listingId, "paused", opts));
2318
+ common(
2319
+ market.command("resume").description("Resume a paused listing").argument("<listing-id>", "listing ID")
2320
+ ).action((listingId, opts) => setStatus(listingId, "live", opts));
2321
+ common(
2322
+ market.command("withdraw").description("Withdraw a listing \u2014 it leaves the book for good").argument("<listing-id>", "listing ID")
2323
+ ).action(async (listingId, opts) => {
2324
+ try {
2325
+ const result = await (await client(opts)).del(
2326
+ `/api/v1/market/asks?id=${encodeURIComponent(listingId)}`
2327
+ );
2328
+ output(opts, result, (r) => {
2329
+ console.log(`withdrew ${pc2.cyan(r.ask.id)}`);
2330
+ });
2331
+ } catch (e) {
2332
+ fail(e, opts);
2333
+ }
2334
+ });
2335
+ common(
2336
+ market.command("replace").description(
2337
+ "Take capacity back: evict a sale, or pause a listing and evict every sale on it.\nAn evicted Actions job restarts on the buyer's next provider; the buyer pays\nonly for the seconds the sale actually ran."
2338
+ ).argument("[listing-id]", "listing to pause and evict every sale on").option("--fill <fill-id>", "evict one sale without touching the listing")
2339
+ ).action(
2340
+ async (listingId, opts) => {
2341
+ try {
2342
+ const target = replaceTarget(listingId, opts.fill);
2343
+ const c = await client(opts);
2344
+ if (target.scope === "fill") {
2345
+ const result = await c.post(target.path, {});
2346
+ output(opts, result, (r) => console.log(formatReplacedFill(r.fill)));
2347
+ } else {
2348
+ const result = await c.post(target.path, {});
2349
+ output(opts, result, (r) => {
2350
+ console.log(`paused ${pc2.cyan(r.listing.id)}`);
2351
+ if (r.fills.length === 0) {
2352
+ console.log(pc2.dim(" no live sales to evict"));
2353
+ return;
2354
+ }
2355
+ for (const f of r.fills) {
2356
+ if (f.ok) console.log(formatReplacedFill(f.fill));
2357
+ else console.log(pc2.red(` could not evict a sale \u2014 ${f.error}`));
2358
+ }
2359
+ });
2360
+ if (result.fills.some((f) => !f.ok)) process.exitCode = 1;
2361
+ }
2362
+ } catch (e) {
2363
+ fail(e, opts);
2364
+ }
2365
+ }
2366
+ );
2367
+ common(
2368
+ market.command("book").description("Live prices: sellers' asking prices, buyer offers, recent sales")
2369
+ ).action(async (opts) => {
2370
+ try {
2371
+ const book = await (await client(opts)).get(
2372
+ "/api/v1/market/book"
2373
+ );
2374
+ output(opts, book, (b) => console.log(formatBook(b)));
2375
+ } catch (e) {
2376
+ fail(e, opts);
2377
+ }
2378
+ });
2379
+ common(
2380
+ market.command("settlements").description("Your monthly payout reports")
2381
+ ).action(async (opts) => {
2382
+ try {
2383
+ const { settlements } = await (await client(opts)).get("/api/v1/market/settlements");
2384
+ output(opts, settlements, (rows) => {
2385
+ if (rows.length === 0) {
2386
+ console.log("No settlements yet.");
2387
+ return;
2388
+ }
2389
+ for (const s of rows) console.log(formatSettlementRow(s));
2390
+ });
2391
+ } catch (e) {
2392
+ fail(e, opts);
2393
+ }
2394
+ });
2395
+ common(
2396
+ market.command("status").description("Who you are on the market: seller identity, credential, sizes, regions")
2397
+ ).action(async (opts) => {
2398
+ try {
2399
+ const { provider } = await (await client(opts)).get("/api/v1/market/provider");
2400
+ output(opts, provider, (p5) => console.log(formatProviderStatus(p5)));
2401
+ } catch (e) {
2402
+ fail(e, opts);
2403
+ }
2404
+ });
2405
+ const credential = market.command("credential").description("Connect the credential your listings fill under");
2406
+ common(
2407
+ credential.command("connect").description(
2408
+ "Save the executor credential your sold capacity runs under. Each field is read from\na <PROVIDER>_<FIELD> env var (e.g. TENSORLAKE_API_KEY) \u2014 never pass secrets in argv;\n--field name=value overrides a field (visible in shell history)"
2409
+ ).option("--field <pairs...>", "credential fields as name=value (prefer env vars for secrets)")
2410
+ ).action(async (opts) => {
2411
+ try {
2412
+ const c = await secretValueClient(opts, "credential values");
2413
+ const { provider } = await c.get(
2414
+ "/api/v1/market/provider"
2415
+ );
2416
+ if (provider.credentialFields.length === 0) {
2417
+ throw new ActionsCliError(
2418
+ "invalid_argument",
2419
+ `${provider.provider} authenticates with the deployment's own identity \u2014 there is no credential to connect.`
2420
+ );
2421
+ }
2422
+ const resolved = credentialValues(
2423
+ provider.provider,
2424
+ provider.credentialFields,
2425
+ parseInputs(opts.field)
2426
+ );
2427
+ if (resolved.missing.length > 0) {
2428
+ const hints = resolved.missing.map((f) => ` export ${resolved.envNames[f.name]}=\u2026 # ${f.label}`).join("\n");
2429
+ throw new ActionsCliError(
2430
+ "invalid_argument",
2431
+ `Missing credential field${resolved.missing.length === 1 ? "" : "s"}. Set:
2432
+ ${hints}`
2433
+ );
2434
+ }
2435
+ if (resolved.fromFlag.some((name) => provider.credentialFields.find((f) => f.name === name)?.secret)) {
2436
+ console.error(
2437
+ pc2.yellow("note: a secret passed via --field stays in your shell history \u2014 prefer the env vars.")
2438
+ );
2439
+ }
2440
+ const result = await c.post(
2441
+ "/api/v1/market/provider/credential",
2442
+ credentialBody(provider.credentialFields, resolved.values)
2443
+ );
2444
+ output(opts, result, (r) => {
2445
+ console.log(`connected ${pc2.cyan(safeTerm(r.provider.name))} credential saved`);
2446
+ });
2447
+ } catch (e) {
2448
+ fail(e, opts);
2449
+ }
2450
+ });
2451
+ common(
2452
+ credential.command("disconnect").description("Remove the stored credential \u2014 listings stop filling until a new one connects")
2453
+ ).action(async (opts) => {
2454
+ try {
2455
+ const result = await (await client(opts)).del(
2456
+ "/api/v1/market/provider/credential"
2457
+ );
2458
+ output(opts, result, (r) => {
2459
+ console.log(`disconnected ${pc2.cyan(safeTerm(r.provider.name))} credential removed`);
2460
+ });
2461
+ } catch (e) {
2462
+ fail(e, opts);
2463
+ }
2464
+ });
2465
+ }
2466
+
2467
+ // src/sandboxes.ts
2468
+ init_esm_shims();
2469
+ import pc3 from "picocolors";
2470
+ function shellQuote(arg) {
2471
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
2472
+ }
2473
+ function printSandbox(s) {
2474
+ const attach = "attach" in s ? s.attach : void 0;
2475
+ console.log(`${pc3.cyan(s.id)} ${s.status} ${s.provider ?? "\u2014"} ${s.providerSandboxId ?? "\u2014"}`);
2476
+ console.log(` label: ${s.label ?? "\u2014"} commands: ${s.commandCount} created: ${s.createdAt}`);
2477
+ if (s.destroyError) console.log(` destroyError: ${pc3.red(s.destroyError)}`);
2478
+ if (attach !== void 0) {
2479
+ console.log(
2480
+ attach === null ? " attach: none (market fill or ambient provider \u2014 platform drive only)" : ` attach: ${attach.provider} / ${attach.providerSandboxId}`
2481
+ );
2482
+ }
2483
+ }
2484
+ function printProcess(p5) {
2485
+ const exit = p5.status === "exited" ? `exit=${p5.exitCode ?? "?"}${p5.signal ? ` (${p5.signal})` : ""}` : "";
2486
+ console.log(`${pc3.cyan(p5.jobId)} ${p5.status}${exit ? " " + exit : ""} ${p5.command}`);
2487
+ }
2488
+ function registerSandboxesCommands(program2) {
2489
+ const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
2490
+ cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
2491
+ try {
2492
+ const c = await client(opts);
2493
+ const resources = {};
2494
+ if (opts.cpus) resources.cpus = Number(opts.cpus);
2495
+ if (opts.memoryMb) resources.memoryMb = Number(opts.memoryMb);
2496
+ if (opts.diskMb) resources.ephemeralDiskMb = Number(opts.diskMb);
2497
+ const body = {};
2498
+ if (opts.order) body.providerOrder = opts.order.split(",").map((s) => s.trim());
2499
+ if (opts.label) body.label = opts.label;
2500
+ if (opts.image) body.image = opts.image;
2501
+ if (opts.snapshotId) body.snapshotId = opts.snapshotId;
2502
+ if (opts.timeoutMs) body.timeoutMs = Number(opts.timeoutMs);
2503
+ if (opts.secret && opts.secret.length > 0) body.secrets = opts.secret;
2504
+ if (Object.keys(resources).length > 0) body.resources = resources;
2505
+ const res = await c.post("/api/v1/sandboxes", body);
2506
+ output(opts, res.sandbox, printSandbox);
2507
+ } catch (e) {
2508
+ fail(e, opts);
2509
+ }
2510
+ });
2511
+ cmd.command("list").description("List sandboxes").option("--status <status>", "creating | running | destroyed").option("--limit <n>", "page size").option("--cursor <cursor>", "page cursor from a previous response").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (opts) => {
2512
+ try {
2513
+ const c = await client(opts);
2514
+ const res = await c.get(
2515
+ "/api/v1/sandboxes",
2516
+ { status: opts.status, limit: opts.limit, cursor: opts.cursor }
2517
+ );
2518
+ output(opts, res, (page) => {
2519
+ page.sandboxes.forEach(printSandbox);
2520
+ if (page.nextCursor) console.log(pc3.dim(`nextCursor: ${page.nextCursor}`));
2521
+ });
2522
+ } catch (e) {
2523
+ fail(e, opts);
2524
+ }
2525
+ });
2526
+ cmd.command("get <sandboxId>").description("Show one sandbox (includes the BYOK attach descriptor)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
2527
+ try {
2528
+ const c = await client(opts);
2529
+ const res = await c.get(`/api/v1/sandboxes/${sandboxId}`);
2530
+ output(opts, res.sandbox, printSandbox);
2531
+ } catch (e) {
2532
+ fail(e, opts);
2533
+ }
2534
+ });
2535
+ cmd.command("destroy <sandboxId>").description("Destroy a sandbox (verified teardown + cost settle)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
2536
+ try {
2537
+ const c = await client(opts);
2538
+ const res = await c.del(`/api/v1/sandboxes/${sandboxId}`);
2539
+ output(opts, res.sandbox, printSandbox);
2540
+ } catch (e) {
2541
+ fail(e, opts);
2542
+ }
2543
+ });
2544
+ cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2545
+ try {
2546
+ const c = await client(opts);
2547
+ const res = await c.post(
2548
+ `/api/v1/sandboxes/${sandboxId}/commands`,
2549
+ {
2550
+ command: command.map(shellQuote).join(" "),
2551
+ ...opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
2552
+ }
2553
+ );
2554
+ output(opts, res, (r) => {
2555
+ process.stdout.write(r.stdout);
2556
+ if (r.stderr) process.stderr.write(r.stderr);
2557
+ if (!r.stdout) console.log(`exit ${r.exitCode} (${r.durationMs}ms)`);
2558
+ });
2559
+ if (res.exitCode !== 0) process.exitCode = res.exitCode;
2560
+ } catch (e) {
2561
+ fail(e, opts);
2562
+ }
2563
+ });
2564
+ cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2565
+ try {
2566
+ const c = await client(opts);
2567
+ const res = await c.post(
2568
+ `/api/v1/sandboxes/${sandboxId}/processes`,
2569
+ {
2570
+ command: command.map(shellQuote).join(" "),
2571
+ ...opts.cwd ? { cwd: opts.cwd } : {},
2572
+ ...opts.env && opts.env.length > 0 ? { env: parseInputs(opts.env) } : {}
2573
+ }
2574
+ );
2575
+ output(opts, res.process, printProcess);
2576
+ } catch (e) {
2577
+ fail(e, opts);
2578
+ }
2579
+ });
2580
+ cmd.command("ps <sandboxId>").description("List the sandbox's tracked processes").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
2581
+ try {
2582
+ const c = await client(opts);
2583
+ const res = await c.get(
2584
+ `/api/v1/sandboxes/${sandboxId}/processes`
2585
+ );
2586
+ output(opts, res.processes, (list) => list.forEach(printProcess));
2587
+ } catch (e) {
2588
+ fail(e, opts);
2589
+ }
2590
+ });
2591
+ cmd.command("logs <sandboxId> <jobId>").description("Show a process's buffered output (--follow streams until exit)").option("-f, --follow", "stream output until the process exits").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
2592
+ try {
2593
+ const c = await client(opts);
2594
+ if (opts.follow) {
2595
+ await followProcess(c, sandboxId, jobId);
2596
+ return;
2597
+ }
2598
+ const res = await c.get(
2599
+ `/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
2600
+ );
2601
+ output(opts, res.process, (p5) => {
2602
+ if (p5.stdout) process.stdout.write(p5.stdout);
2603
+ if (p5.stderr) process.stderr.write(p5.stderr);
2604
+ if (!p5.stdout && !p5.stderr) printProcess(p5);
2605
+ });
2606
+ } catch (e) {
2607
+ fail(e, opts);
2608
+ }
2609
+ });
2610
+ cmd.command("wait <sandboxId> <jobId>").description("Block until a process exits").option("--timeout-ms <ms>", "bound the wait (job keeps running on timeout)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
2611
+ try {
2612
+ const c = await client(opts);
2613
+ const res = await c.post(
2614
+ `/api/v1/sandboxes/${sandboxId}/processes/${jobId}/wait`,
2615
+ opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
2616
+ );
2617
+ output(opts, res.process, printProcess);
2618
+ if (res.process.status === "exited") {
2619
+ process.exitCode = res.process.exitCode ?? 128;
2620
+ }
2621
+ } catch (e) {
2622
+ fail(e, opts);
2623
+ }
2624
+ });
2625
+ cmd.command("kill <sandboxId> <jobId>").description("Signal a process (default SIGTERM)").option("--signal <signal>", "signal name or number, e.g. SIGKILL or 9").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
2626
+ try {
2627
+ const c = await client(opts);
2628
+ const res = await c.post(
2629
+ `/api/v1/sandboxes/${sandboxId}/processes/${jobId}/kill`,
2630
+ opts.signal ? { signal: opts.signal } : {}
2631
+ );
2632
+ output(opts, res.process, printProcess);
2633
+ } catch (e) {
2634
+ fail(e, opts);
2635
+ }
2636
+ });
2637
+ cmd.command("ls <sandboxId> [path]").description("List a directory (or stat a path) inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
2638
+ try {
2639
+ const c = await client(opts);
2640
+ const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, {
2641
+ path: path5 ?? "/"
2642
+ });
2643
+ output(opts, res, (view) => {
2644
+ if (view.type === "directory") {
2645
+ for (const e of view.entries) {
2646
+ console.log(`${e.type === "directory" ? pc3.blue("d") : " "} ${e.name}${e.size !== void 0 ? ` ${pc3.dim(String(e.size))}` : ""}`);
2647
+ }
2648
+ } else {
2649
+ console.log(`${view.path} file ${view.content.length} chars`);
2650
+ }
2651
+ });
2652
+ } catch (e) {
2653
+ fail(e, opts);
2654
+ }
2655
+ });
2656
+ cmd.command("cat <sandboxId> <path>").description("Print a file's contents").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
2657
+ try {
2658
+ const c = await client(opts);
2659
+ const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path: path5 });
2660
+ output(opts, res, (view) => {
2661
+ if (view.type !== "file") {
2662
+ console.error(pc3.red(`${view.path} is a directory`));
2663
+ process.exit(1);
2664
+ }
2665
+ process.stdout.write(view.content);
2666
+ if (!view.content.endsWith("\n")) process.stdout.write("\n");
2667
+ });
2668
+ } catch (e) {
2669
+ fail(e, opts);
2670
+ }
2671
+ });
2672
+ cmd.command("write <sandboxId> <path>").description("Write a file inside the sandbox (--content or --file <local>, else stdin)").option("--content <text>", "file contents").option("--file <local>", "read contents from a local file").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
2673
+ try {
2674
+ let content = opts.content;
2675
+ if (content === void 0 && opts.file) {
2676
+ const { readFile } = await import("fs/promises");
2677
+ content = await readFile(opts.file, "utf8");
2678
+ }
2679
+ if (content === void 0 && !process.stdin.isTTY) {
2680
+ content = await new Promise((resolve) => {
2681
+ let data = "";
2682
+ process.stdin.setEncoding("utf8");
2683
+ process.stdin.on("data", (chunk) => data += chunk);
2684
+ process.stdin.on("end", () => resolve(data));
2685
+ });
2686
+ }
2687
+ if (content === void 0) {
2688
+ console.error(pc3.red("Pass --content, --file, or pipe stdin"));
2689
+ process.exit(1);
2690
+ }
2691
+ const c = await client(opts);
2692
+ const res = await c.post(
2693
+ `/api/v1/sandboxes/${sandboxId}/files`,
2694
+ { path: path5, content }
2695
+ );
2696
+ output(opts, res, (r) => console.log(`wrote ${r.path}`));
2697
+ } catch (e) {
2698
+ fail(e, opts);
2699
+ }
2700
+ });
2701
+ cmd.command("mkdir <sandboxId> <path>").description("Create a directory (parents included)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
2702
+ try {
2703
+ const c = await client(opts);
2704
+ const res = await c.post(`/api/v1/sandboxes/${sandboxId}/files`, {
2705
+ path: path5,
2706
+ mkdir: true
2707
+ });
2708
+ output(opts, res, (r) => console.log(`created ${r.path}`));
2709
+ } catch (e) {
2710
+ fail(e, opts);
2711
+ }
2712
+ });
2713
+ cmd.command("rm <sandboxId> <path>").description("Remove a file or directory inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
2714
+ try {
2715
+ const c = await client(opts);
2716
+ const res = await c.del(
2717
+ `/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path5)}`
2718
+ );
2719
+ output(opts, res, (r) => console.log(`removed ${r.path}`));
2720
+ } catch (e) {
2721
+ fail(e, opts);
2722
+ }
2723
+ });
2724
+ cmd.command("url <sandboxId>").description("Resolve the provider's public URL for a port").requiredOption("--port <n>", "port the service listens on").option("--protocol <protocol>", "protocol (default provider-chosen)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
2725
+ try {
2726
+ const c = await client(opts);
2727
+ const res = await c.get(`/api/v1/sandboxes/${sandboxId}/urls`, {
2728
+ port: opts.port,
2729
+ protocol: opts.protocol
2730
+ });
2731
+ output(opts, res, (r) => console.log(safeTerm(r.url)));
2732
+ } catch (e) {
2733
+ fail(e, opts);
2734
+ }
2735
+ });
2736
+ }
2737
+ async function followProcess(c, sandboxId, jobId) {
2738
+ let exitCode = 0;
2739
+ for await (const { event, data } of c.sseNamed(
2740
+ `/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
2741
+ )) {
2742
+ const payload = data;
2743
+ if (event === "stdout") process.stdout.write(String(payload.chunk ?? ""));
2744
+ else if (event === "stderr") process.stderr.write(String(payload.chunk ?? ""));
2745
+ else if (event === "exit") {
2746
+ exitCode = typeof payload.exitCode === "number" ? payload.exitCode : 128;
2747
+ return finish();
2748
+ } else if (event === "error") {
2749
+ console.error(pc3.red(`Error: ${String(payload.error ?? "stream error")}`));
2750
+ return finish(1);
2751
+ }
2752
+ }
2753
+ console.error(pc3.yellow("stream ended before the process exited"));
2754
+ return finish(2);
2755
+ function finish(code) {
2756
+ if (code !== void 0) exitCode = code;
2757
+ if (exitCode !== 0) process.exitCode = exitCode;
2758
+ }
2759
+ }
2760
+
1924
2761
  // src/index.ts
1925
2762
  init_repl();
1926
2763
 
@@ -1933,7 +2770,7 @@ import * as fs2 from "fs";
1933
2770
  import * as path3 from "path";
1934
2771
  import * as os2 from "os";
1935
2772
  import * as p from "@clack/prompts";
1936
- import pc3 from "picocolors";
2773
+ import pc5 from "picocolors";
1937
2774
  function getCredentialsDir() {
1938
2775
  return path3.join(os2.homedir(), ".computesdk");
1939
2776
  }
@@ -1991,7 +2828,7 @@ function openBrowser(url) {
1991
2828
  if (err) {
1992
2829
  p.log.warn("Could not open browser automatically.");
1993
2830
  p.log.info(`Open this URL manually:
1994
- ${pc3.cyan(url)}`);
2831
+ ${pc5.cyan(url)}`);
1995
2832
  }
1996
2833
  });
1997
2834
  }
@@ -2062,7 +2899,7 @@ async function resolveApiKey(options) {
2062
2899
  const stored = loadStoredCredentials();
2063
2900
  if (stored) {
2064
2901
  p.log.info(
2065
- `Using stored credentials from ${pc3.dim("~/.computesdk/credentials.json")}`
2902
+ `Using stored credentials from ${pc5.dim("~/.computesdk/credentials.json")}`
2066
2903
  );
2067
2904
  return stored;
2068
2905
  }
@@ -2076,14 +2913,14 @@ async function resolveApiKey(options) {
2076
2913
  try {
2077
2914
  storeCredentials(apiKey);
2078
2915
  p.log.success(
2079
- `Credentials saved to ${pc3.dim("~/.computesdk/credentials.json")}`
2916
+ `Credentials saved to ${pc5.dim("~/.computesdk/credentials.json")}`
2080
2917
  );
2081
2918
  } catch {
2082
2919
  p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
2083
2920
  }
2084
2921
  return apiKey;
2085
2922
  } catch (error) {
2086
- spinner4.stop(pc3.red("Authentication failed"));
2923
+ spinner4.stop(pc5.red("Authentication failed"));
2087
2924
  throw error;
2088
2925
  }
2089
2926
  }
@@ -2091,7 +2928,7 @@ async function resolveApiKey(options) {
2091
2928
  // src/setup.ts
2092
2929
  init_esm_shims();
2093
2930
  import * as p2 from "@clack/prompts";
2094
- import pc4 from "picocolors";
2931
+ import pc6 from "picocolors";
2095
2932
  import { compute as compute2 } from "computesdk";
2096
2933
  async function ensureAuth(options) {
2097
2934
  try {
@@ -2099,8 +2936,8 @@ async function ensureAuth(options) {
2099
2936
  process.env.COMPUTESDK_API_KEY = apiKey;
2100
2937
  } catch (error) {
2101
2938
  p2.log.error(error.message);
2102
- p2.log.info(`Get your API key at ${pc4.cyan("https://console.computesdk.com")}`);
2103
- p2.outro(pc4.red("Setup incomplete"));
2939
+ p2.log.info(`Get your API key at ${pc6.cyan("https://console.computesdk.com")}`);
2940
+ p2.outro(pc6.red("Setup incomplete"));
2104
2941
  process.exit(1);
2105
2942
  }
2106
2943
  }
@@ -2129,7 +2966,7 @@ async function resolveProvider(providerFlag, options) {
2129
2966
  if (available.length === 0) {
2130
2967
  p2.log.error("No provider credentials detected");
2131
2968
  p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
2132
- p2.outro(pc4.red("Setup incomplete"));
2969
+ p2.outro(pc6.red("Setup incomplete"));
2133
2970
  process.exit(1);
2134
2971
  }
2135
2972
  if (available.length === 1) {
@@ -2173,24 +3010,24 @@ program.name("compute").description("Cloud-native workspace management").version
2173
3010
  program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
2174
3011
  if (opts.logout) {
2175
3012
  console.log();
2176
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3013
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2177
3014
  clearStoredCredentials();
2178
3015
  p4.log.success("Logged out. Stored credentials removed.");
2179
- p4.outro(pc6.green("Done!"));
3016
+ p4.outro(pc8.green("Done!"));
2180
3017
  process.exit(0);
2181
3018
  }
2182
3019
  if (opts.login) {
2183
3020
  console.log();
2184
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3021
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2185
3022
  await ensureAuth({ forceLogin: true });
2186
- p4.outro(pc6.green("Authenticated!"));
3023
+ p4.outro(pc8.green("Authenticated!"));
2187
3024
  process.exit(0);
2188
3025
  }
2189
3026
  program.help();
2190
3027
  });
2191
3028
  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) => {
2192
3029
  console.log();
2193
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3030
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2194
3031
  await ensureAuth();
2195
3032
  if (!repo) {
2196
3033
  const state = loadWorkspaceState();
@@ -2211,7 +3048,7 @@ program.command("create").description("Create a new workspace (same as create-co
2211
3048
  message: "Select workspace to attach (or create new)",
2212
3049
  options: [
2213
3050
  ...choices,
2214
- { value: "__new__", label: pc6.gray("+ Create new workspace") }
3051
+ { value: "__new__", label: pc8.gray("+ Create new workspace") }
2215
3052
  ]
2216
3053
  });
2217
3054
  if (p4.isCancel(selected)) {
@@ -2252,15 +3089,15 @@ program.command("create").description("Create a new workspace (same as create-co
2252
3089
  branch: options.branch,
2253
3090
  provider
2254
3091
  });
2255
- spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
3092
+ spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
2256
3093
  if (options.attach !== false) {
2257
3094
  console.log();
2258
3095
  await workspaceAttach(workspace.id);
2259
3096
  } else {
2260
- p4.outro(pc6.green("Workspace created!"));
3097
+ p4.outro(pc8.green("Workspace created!"));
2261
3098
  }
2262
3099
  } catch (error) {
2263
- spinner4.stop(pc6.red("Failed to create workspace"));
3100
+ spinner4.stop(pc8.red("Failed to create workspace"));
2264
3101
  p4.log.error(error.message);
2265
3102
  process.exit(1);
2266
3103
  }
@@ -2269,7 +3106,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
2269
3106
  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) => {
2270
3107
  if (!opts.silent) {
2271
3108
  console.log();
2272
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3109
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2273
3110
  }
2274
3111
  await ensureAuth();
2275
3112
  const provider = await resolveProvider(opts.provider);
@@ -2281,7 +3118,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
2281
3118
  const sandbox = await compute4.sandbox.create(
2282
3119
  buildCreateOptions(opts)
2283
3120
  );
2284
- spinner4.stop(`Sandbox ready: ${pc6.cyan(sandbox.sandboxId)}`);
3121
+ spinner4.stop(`Sandbox ready: ${pc8.cyan(sandbox.sandboxId)}`);
2285
3122
  const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
2286
3123
  await saveSandbox2({
2287
3124
  id: sandbox.sandboxId,
@@ -2294,7 +3131,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
2294
3131
  await startREPL(sandbox, provider);
2295
3132
  }
2296
3133
  } catch (error) {
2297
- spinner4.stop(pc6.red("Failed to create sandbox"));
3134
+ spinner4.stop(pc8.red("Failed to create sandbox"));
2298
3135
  p4.log.error(error.message);
2299
3136
  process.exit(1);
2300
3137
  }
@@ -2323,14 +3160,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
2323
3160
  });
2324
3161
  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) => {
2325
3162
  console.log();
2326
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3163
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2327
3164
  const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
2328
3165
  const state = loadState2();
2329
3166
  if (!sandboxIds || sandboxIds.length === 0) {
2330
3167
  const workspaces = Object.values(state.workspaces);
2331
3168
  if (workspaces.length === 0) {
2332
3169
  p4.log.info("No sandboxes to destroy.");
2333
- p4.outro(pc6.green("Done!"));
3170
+ p4.outro(pc8.green("Done!"));
2334
3171
  return;
2335
3172
  }
2336
3173
  const choices = workspaces.map((ws) => {
@@ -2338,7 +3175,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
2338
3175
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
2339
3176
  );
2340
3177
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
2341
- const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
3178
+ const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
2342
3179
  return {
2343
3180
  value: ws.sandboxId,
2344
3181
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -2366,7 +3203,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
2366
3203
  for (const result of results) {
2367
3204
  if (result.status === "fulfilled") {
2368
3205
  const sandboxId = result.value;
2369
- console.log(pc6.green(`Destroyed ${sandboxId}`));
3206
+ console.log(pc8.green(`Destroyed ${sandboxId}`));
2370
3207
  const workspaceId = Object.keys(state.workspaces).find(
2371
3208
  (key) => state.workspaces[key].sandboxId === sandboxId
2372
3209
  );
@@ -2374,19 +3211,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
2374
3211
  delete state.workspaces[workspaceId];
2375
3212
  }
2376
3213
  } else {
2377
- console.error(pc6.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
3214
+ console.error(pc8.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
2378
3215
  }
2379
3216
  }
2380
3217
  saveState2(state);
2381
3218
  });
2382
3219
  sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
2383
3220
  console.log();
2384
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3221
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2385
3222
  await sandboxList();
2386
3223
  });
2387
3224
  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) => {
2388
3225
  console.log();
2389
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3226
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2390
3227
  await ensureAuth();
2391
3228
  const provider = await resolveProvider(opts.provider);
2392
3229
  configureCompute(provider);
@@ -2397,7 +3234,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
2397
3234
  if (workspaces.length === 0) {
2398
3235
  p4.log.info("No sandboxes found.");
2399
3236
  p4.log.info("Create one with: compute sandbox create");
2400
- p4.outro(pc6.green("Done!"));
3237
+ p4.outro(pc8.green("Done!"));
2401
3238
  return;
2402
3239
  }
2403
3240
  const choices = workspaces.map((ws) => {
@@ -2405,7 +3242,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
2405
3242
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
2406
3243
  );
2407
3244
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
2408
- const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
3245
+ const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
2409
3246
  return {
2410
3247
  value: ws.sandboxId,
2411
3248
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -2425,7 +3262,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
2425
3262
  spinner4.start("Connecting...");
2426
3263
  const sandbox = await compute4.sandbox.getById(sandboxId);
2427
3264
  if (!sandbox) {
2428
- spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
3265
+ spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
2429
3266
  process.exit(1);
2430
3267
  }
2431
3268
  spinner4.stop("");
@@ -2437,7 +3274,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
2437
3274
  configureCompute(provider);
2438
3275
  const sandbox = await compute4.sandbox.getById(sandboxId);
2439
3276
  if (!sandbox) {
2440
- console.error(pc6.red(`Sandbox not found: ${sandboxId}`));
3277
+ console.error(pc8.red(`Sandbox not found: ${sandboxId}`));
2441
3278
  process.exit(1);
2442
3279
  }
2443
3280
  const cmd = buildExecCommand(command, opts);
@@ -2452,7 +3289,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
2452
3289
  });
2453
3290
  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) => {
2454
3291
  console.log();
2455
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3292
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2456
3293
  await ensureAuth();
2457
3294
  const provider = await resolveProvider(opts.provider);
2458
3295
  configureCompute(provider);
@@ -2463,7 +3300,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
2463
3300
  if (workspaces.length === 0) {
2464
3301
  p4.log.info("No sandboxes found.");
2465
3302
  p4.log.info("Create one with: compute sandbox create");
2466
- p4.outro(pc6.green("Done!"));
3303
+ p4.outro(pc8.green("Done!"));
2467
3304
  return;
2468
3305
  }
2469
3306
  const choices = workspaces.map((ws) => {
@@ -2471,7 +3308,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
2471
3308
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
2472
3309
  );
2473
3310
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
2474
- const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
3311
+ const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
2475
3312
  return {
2476
3313
  value: ws.sandboxId,
2477
3314
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -2491,7 +3328,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
2491
3328
  spinner4.start("Connecting...");
2492
3329
  const sandbox = await compute4.sandbox.getById(sandboxId);
2493
3330
  if (!sandbox) {
2494
- spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
3331
+ spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
2495
3332
  process.exit(1);
2496
3333
  }
2497
3334
  spinner4.stop("");
@@ -2500,11 +3337,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
2500
3337
  var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
2501
3338
  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) => {
2502
3339
  console.log();
2503
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3340
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2504
3341
  await ensureAuth();
2505
3342
  if (!repo) {
2506
3343
  p4.log.info("Usage: compute workspace create owner/repo --branch main");
2507
- p4.outro(pc6.green("Done!"));
3344
+ p4.outro(pc8.green("Done!"));
2508
3345
  return;
2509
3346
  }
2510
3347
  const provider = await resolveProvider(options.provider, { interactive: true });
@@ -2517,33 +3354,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
2517
3354
  branch: options.branch,
2518
3355
  provider
2519
3356
  });
2520
- spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
3357
+ spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
2521
3358
  if (options.attach !== false) {
2522
3359
  console.log();
2523
3360
  await workspaceAttach(workspace.id);
2524
3361
  } else {
2525
- p4.outro(pc6.green("Workspace created!"));
3362
+ p4.outro(pc8.green("Workspace created!"));
2526
3363
  }
2527
3364
  } catch (error) {
2528
- spinner4.stop(pc6.red("Failed to create workspace"));
3365
+ spinner4.stop(pc8.red("Failed to create workspace"));
2529
3366
  p4.log.error(error.message);
2530
3367
  process.exit(1);
2531
3368
  }
2532
3369
  });
2533
3370
  workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
2534
3371
  console.log();
2535
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3372
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2536
3373
  await workspaceList();
2537
3374
  });
2538
3375
  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) => {
2539
3376
  console.log();
2540
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3377
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2541
3378
  if (!id) {
2542
3379
  const state = loadWorkspaceState();
2543
3380
  const workspaceIds = Object.keys(state.workspaces);
2544
3381
  if (workspaceIds.length === 0) {
2545
3382
  p4.log.info("No workspaces to destroy.");
2546
- p4.outro(pc6.green("Done!"));
3383
+ p4.outro(pc8.green("Done!"));
2547
3384
  return;
2548
3385
  }
2549
3386
  const choices = workspaceIds.map((key) => {
@@ -2554,7 +3391,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
2554
3391
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
2555
3392
  return {
2556
3393
  value: key,
2557
- label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
3394
+ label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
2558
3395
  };
2559
3396
  });
2560
3397
  const selected = await p4.multiselect({
@@ -2574,14 +3411,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
2574
3411
  });
2575
3412
  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) => {
2576
3413
  console.log();
2577
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3414
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2578
3415
  if (!id) {
2579
3416
  const state = loadWorkspaceState();
2580
3417
  const workspaceIds = Object.keys(state.workspaces);
2581
3418
  if (workspaceIds.length === 0) {
2582
3419
  p4.log.info("No workspaces found.");
2583
3420
  p4.log.info("Create one with: compute create owner/repo");
2584
- p4.outro(pc6.green("Done!"));
3421
+ p4.outro(pc8.green("Done!"));
2585
3422
  return;
2586
3423
  }
2587
3424
  const choices = workspaceIds.map((key) => {
@@ -2592,7 +3429,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
2592
3429
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
2593
3430
  return {
2594
3431
  value: key,
2595
- label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
3432
+ label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
2596
3433
  };
2597
3434
  });
2598
3435
  const selected = await p4.select({
@@ -2617,7 +3454,7 @@ program.command("run").description("Create a sandbox, run a command, then option
2617
3454
  buildCreateOptions(opts)
2618
3455
  );
2619
3456
  } catch (error) {
2620
- console.error(pc6.red(`Failed to create sandbox: ${error.message}`));
3457
+ console.error(pc8.red(`Failed to create sandbox: ${error.message}`));
2621
3458
  process.exit(1);
2622
3459
  }
2623
3460
  try {
@@ -2640,37 +3477,39 @@ program.command("run").description("Create a sandbox, run a command, then option
2640
3477
  } catch {
2641
3478
  }
2642
3479
  }
2643
- console.error(pc6.red(error.message));
3480
+ console.error(pc8.red(error.message));
2644
3481
  process.exit(1);
2645
3482
  }
2646
3483
  });
2647
3484
  program.command("providers").description("List all providers and their configuration status").action(async () => {
2648
3485
  console.log();
2649
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3486
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2650
3487
  const statuses = getProviderStatus();
2651
3488
  console.log();
2652
3489
  for (const status of statuses) {
2653
- const icon = status.ready ? pc6.green("\u25CF") : pc6.gray("\u25CB");
2654
- const name = status.ready ? pc6.white(status.name) : pc6.gray(status.name);
2655
- const detail = status.ready ? pc6.green("ready") : pc6.gray(`missing: ${status.missing.join(", ")}`);
3490
+ const icon = status.ready ? pc8.green("\u25CF") : pc8.gray("\u25CB");
3491
+ const name = status.ready ? pc8.white(status.name) : pc8.gray(status.name);
3492
+ const detail = status.ready ? pc8.green("ready") : pc8.gray(`missing: ${status.missing.join(", ")}`);
2656
3493
  console.log(` ${icon} ${name} ${detail}`);
2657
3494
  }
2658
3495
  console.log();
2659
3496
  });
2660
3497
  program.command("login").description("Authenticate with ComputeSDK").action(async () => {
2661
3498
  console.log();
2662
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3499
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2663
3500
  await ensureAuth({ forceLogin: true });
2664
- p4.outro(pc6.green("Authenticated!"));
3501
+ p4.outro(pc8.green("Authenticated!"));
2665
3502
  });
2666
3503
  program.command("logout").description("Clear stored credentials").action(() => {
2667
3504
  console.log();
2668
- p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
3505
+ p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
2669
3506
  clearStoredCredentials();
2670
3507
  p4.log.success("Logged out. Stored credentials removed.");
2671
- p4.outro(pc6.green("Done!"));
3508
+ p4.outro(pc8.green("Done!"));
2672
3509
  });
2673
3510
  registerActionsCommands(program);
3511
+ registerMarketCommands(program);
3512
+ registerSandboxesCommands(program);
2674
3513
  program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
2675
3514
  });
2676
3515
  function buildCreateOptions(opts) {
@@ -2748,7 +3587,7 @@ async function main() {
2748
3587
  await program.parseAsync(process.argv);
2749
3588
  }
2750
3589
  main().catch((error) => {
2751
- console.error(pc6.red(`
3590
+ console.error(pc8.red(`
2752
3591
  Error: ${error.message}`));
2753
3592
  process.exit(1);
2754
3593
  });