@computesdk/cli 1.0.8 → 1.0.9

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 pc from "picocolors";
26
+ import pc2 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 pc.gray("[no sandbox] ") + pc.bold("$ ");
38
- return pc.cyan(`[${sandboxId}] `) + pc.bold("$ ");
37
+ if (!sandboxId) return pc2.gray("[no sandbox] ") + pc2.bold("$ ");
38
+ return pc2.cyan(`[${sandboxId}] `) + pc2.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(pc.yellow("No active sandbox. Use /new to create one."));
83
+ console.log(pc2.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(pc.red(`\u2717 ${error.message}`));
89
+ console.log(pc2.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(pc.red(result.stderr));
107
+ process.stderr.write(pc2.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(pc.gray(`exit code: ${result.exitCode}`));
113
+ console.log(pc2.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(pc.yellow("No active sandbox. Use /new to create one."));
127
+ console.log(pc2.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(pc.bold(" Sandbox Info"));
133
- console.log(` ID: ${pc.cyan(activeSandbox.sandboxId)}`);
132
+ console.log(pc2.bold(" Sandbox Info"));
133
+ console.log(` ID: ${pc2.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(pc.yellow("No active sandbox. Use /new to create one."));
140
+ console.log(pc2.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(pc.gray("Usage: /url <port>"));
145
+ console.log(pc2.gray("Usage: /url <port>"));
146
146
  } else {
147
147
  const port = parseInt(args[0], 10);
148
148
  if (isNaN(port)) {
149
- console.log(pc.red("Invalid port number"));
149
+ console.log(pc2.red("Invalid port number"));
150
150
  } else {
151
151
  const url = await activeSandbox.getUrl({ port });
152
- console.log(pc.cyan(url));
152
+ console.log(pc2.cyan(url));
153
153
  }
154
154
  }
155
155
  return null;
156
156
  }
157
157
  case "new": {
158
- console.log(pc.gray(" Creating new sandbox..."));
158
+ console.log(pc2.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: ${pc.cyan(newSandbox.sandboxId)}`);
164
- console.log(` ${pc.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
163
+ console.log(` Sandbox ready: ${pc2.cyan(newSandbox.sandboxId)}`);
164
+ console.log(` ${pc2.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(pc.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
170
- console.log(pc.gray("Use /list to see all sandboxes"));
169
+ console.log(pc2.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
170
+ console.log(pc2.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(pc.yellow(" No sandboxes to destroy."));
175
+ console.log(pc2.yellow(" No sandboxes to destroy."));
176
176
  return null;
177
177
  }
178
178
  const count = ctx.sandboxes.size;
179
- console.log(pc.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
179
+ console.log(pc2.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 ${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."));
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."));
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(pc.yellow(" Ambiguous ID. Matches:"));
197
+ console.log(pc2.yellow(" Ambiguous ID. Matches:"));
198
198
  for (const id of matches) {
199
- console.log(` ${pc.cyan(id)}`);
199
+ console.log(` ${pc2.cyan(id)}`);
200
200
  }
201
201
  } else {
202
- console.log(pc.red(` Sandbox not found: ${targetId}`));
203
- console.log(pc.gray(" Use /list to see all sandboxes"));
202
+ console.log(pc2.red(` Sandbox not found: ${targetId}`));
203
+ console.log(pc2.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(pc.bold(" Sandboxes"));
212
+ console.log(pc2.bold(" Sandboxes"));
213
213
  for (const id of ctx.sandboxes.keys()) {
214
- const marker = id === ctx.activeSandboxId ? pc.green(" \u25CF") : " ";
215
- console.log(` ${marker} ${pc.cyan(id)}`);
214
+ const marker = id === ctx.activeSandboxId ? pc2.green(" \u25CF") : " ";
215
+ console.log(` ${marker} ${pc2.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(pc.gray("Usage: /switch <sandbox-id>"));
224
- console.log(pc.gray("Use /list to see all sandboxes"));
223
+ console.log(pc2.gray("Usage: /switch <sandbox-id>"));
224
+ console.log(pc2.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 ${pc.cyan(switchId)}`);
231
+ console.log(` Switched to ${pc2.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 ${pc.cyan(matches[0])}`);
238
+ console.log(` Switched to ${pc2.cyan(matches[0])}`);
239
239
  } else if (matches.length > 1) {
240
- console.log(pc.yellow(" Ambiguous ID. Matches:"));
240
+ console.log(pc2.yellow(" Ambiguous ID. Matches:"));
241
241
  for (const id of matches) {
242
- console.log(` ${pc.cyan(id)}`);
242
+ console.log(` ${pc2.cyan(id)}`);
243
243
  }
244
244
  } else {
245
- console.log(pc.red(` Sandbox not found: ${switchId}`));
246
- console.log(pc.gray(" Use /list to see all sandboxes"));
245
+ console.log(pc2.red(` Sandbox not found: ${switchId}`));
246
+ console.log(pc2.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(pc.yellow(`Unknown command: /${cmd}`));
256
- console.log(pc.gray("Type /help for available commands"));
255
+ console.log(pc2.yellow(`Unknown command: /${cmd}`));
256
+ console.log(pc2.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(pc.gray(` Destroying ${sandboxId}...`));
262
+ console.log(pc2.gray(` Destroying ${sandboxId}...`));
263
263
  await target.destroy();
264
264
  ctx.sandboxes.delete(sandboxId);
265
- console.log(` Destroyed ${pc.cyan(sandboxId)}`);
265
+ console.log(` Destroyed ${pc2.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 ${pc.cyan(ctx.activeSandboxId)}`);
269
+ console.log(` Switched to ${pc2.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(pc.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
273
+ console.log(pc2.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(pc.bold(" Shell Mode"));
280
- console.log(pc.gray(" Just type commands - they run in the active sandbox"));
279
+ console.log(pc2.bold(" Shell Mode"));
280
+ console.log(pc2.gray(" Just type commands - they run in the active sandbox"));
281
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"));
282
+ console.log(pc2.white(" ls -la"));
283
+ console.log(pc2.white(" cat /etc/os-release"));
284
+ console.log(pc2.white(" npm install lodash"));
285
285
  console.log();
286
- console.log(pc.bold(" Sandbox Commands") + pc.gray(" (prefix with /)"));
286
+ console.log(pc2.bold(" Sandbox Commands") + pc2.gray(" (prefix with /)"));
287
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"));
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"));
293
293
  console.log();
294
- console.log(pc.bold(" Other Commands"));
294
+ console.log(pc2.bold(" Other Commands"));
295
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"));
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"));
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 pc4 from "picocolors";
346
+ import pc5 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: ${pc4.cyan(id)}`);
385
+ p3.log.info(`Attaching to existing workspace: ${pc5.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: ${pc4.cyan(sandbox.sandboxId)}`);
397
+ spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
398
398
  } catch (error) {
399
- spinner4.stop(pc4.red("Failed to create sandbox"));
399
+ spinner4.stop(pc5.red("Failed to create sandbox"));
400
400
  throw error;
401
401
  }
402
402
  spinner4.start("Bootstrapping workspace (installing gh CLI, cloning repo)...");
@@ -413,8 +413,8 @@ async function workspaceCreate(options) {
413
413
  { timeout: 3e5 }
414
414
  // 5 min timeout for auth
415
415
  );
416
- const output = result.stdout || "";
417
- const jsonMatch = output.match(/\{[\s\S]*\}/);
416
+ const output2 = result.stdout || "";
417
+ const jsonMatch = output2.match(/\{[\s\S]*\}/);
418
418
  let worktreePath = `/workspaces/${path4.basename(repo)}-${branch}`;
419
419
  if (jsonMatch) {
420
420
  try {
@@ -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: ${pc4.cyan(id)}`);
440
- p3.log.info(`Sandbox: ${pc4.dim(sandbox.sandboxId)}`);
441
- p3.log.info(`Worktree: ${pc4.dim(worktreePath)}`);
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)}`);
442
442
  return workspace;
443
443
  } catch (error) {
444
- spinner4.stop(pc4.red("Bootstrap failed"));
444
+ spinner4.stop(pc5.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: ${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.`));
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.`));
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(pc4.bold("Sandboxes"));
488
+ console.log(pc5.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 ? 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)}`);
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)}`);
496
496
  console.log(` Provider: ${ws.provider}`);
497
- console.log(` Last active: ${pc4.gray(ageStr)}`);
497
+ console.log(` Last active: ${pc5.gray(ageStr)}`);
498
498
  console.log();
499
499
  }
500
- console.log(pc4.gray('Tip: Use "compute workspaces" to see full workspace details'));
500
+ console.log(pc5.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(pc4.bold("Workspaces"));
511
+ console.log(pc5.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(` ${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)}`);
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)}`);
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: ${pc4.cyan(id)}`);
545
+ p3.log.info(`Destroying workspace: ${pc5.cyan(id)}`);
546
546
  try {
547
547
  await compute3.sandbox.destroy(workspace.sandboxId);
548
548
  delete state.workspaces[id];
@@ -586,38 +586,63 @@ init_esm_shims();
586
586
  import "dotenv/config";
587
587
  import { Command } from "commander";
588
588
  import * as p4 from "@clack/prompts";
589
- import pc5 from "picocolors";
589
+ import pc6 from "picocolors";
590
590
  import { readFileSync as readFileSync4 } from "fs";
591
591
  import { fileURLToPath as fileURLToPath2 } from "url";
592
- import { dirname as dirname2, join as join4 } from "path";
592
+ import { dirname as dirname2, join as join5 } from "path";
593
593
  import { compute as compute4 } from "computesdk";
594
594
 
595
595
  // src/providers.ts
596
596
  init_esm_shims();
597
- import {
598
- PROVIDER_NAMES,
599
- isProviderAuthComplete,
600
- getMissingEnvVars,
601
- getProviderConfigFromEnv
602
- } from "computesdk";
603
- var PROVIDERS_NEEDING_CREDS = [
604
- "e2b",
605
- "railway",
606
- "modal",
607
- "vercel",
608
- "daytona",
609
- "render",
610
- "namespace",
611
- "blaxel",
612
- "codesandbox"
613
- ];
597
+ var PROVIDER_ENV = {
598
+ e2b: { authOptions: [["E2B_API_KEY"]], configKeys: { E2B_API_KEY: "apiKey" } },
599
+ railway: { authOptions: [["RAILWAY_API_TOKEN"]], configKeys: { RAILWAY_API_TOKEN: "token" } },
600
+ modal: {
601
+ authOptions: [["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"]],
602
+ configKeys: { MODAL_TOKEN_ID: "tokenId", MODAL_TOKEN_SECRET: "tokenSecret" }
603
+ },
604
+ vercel: {
605
+ authOptions: [["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"], ["VERCEL_OIDC_TOKEN"]],
606
+ configKeys: {
607
+ VERCEL_TOKEN: "token",
608
+ VERCEL_TEAM_ID: "teamId",
609
+ VERCEL_PROJECT_ID: "projectId"
610
+ }
611
+ },
612
+ daytona: { authOptions: [["DAYTONA_API_KEY"]], configKeys: { DAYTONA_API_KEY: "apiKey" } },
613
+ namespace: {
614
+ authOptions: [["NSC_TOKEN"], ["NSC_TOKEN_FILE"]],
615
+ configKeys: { NSC_TOKEN: "token", NSC_TOKEN_FILE: "tokenFile" }
616
+ },
617
+ blaxel: {
618
+ authOptions: [["BL_API_KEY", "BL_WORKSPACE"]],
619
+ configKeys: { BL_API_KEY: "apiKey", BL_WORKSPACE: "workspace" }
620
+ },
621
+ codesandbox: { authOptions: [["CSB_API_KEY"]], configKeys: { CSB_API_KEY: "apiKey" } },
622
+ render: { authOptions: [["RENDER_API_KEY"]], configKeys: { RENDER_API_KEY: "apiKey" } }
623
+ };
624
+ var PROVIDER_NAMES = Object.keys(PROVIDER_ENV);
625
+ function missingEnvVars(provider) {
626
+ const spec = PROVIDER_ENV[provider];
627
+ if (!spec) return [];
628
+ let best = null;
629
+ for (const option of spec.authOptions) {
630
+ const missing = option.filter((name) => !process.env[name]);
631
+ if (missing.length === 0) return [];
632
+ if (!best || missing.length < best.length) best = missing;
633
+ }
634
+ return best ?? [];
635
+ }
636
+ function isProviderAuthComplete(provider) {
637
+ return missingEnvVars(provider).length === 0;
638
+ }
614
639
  function detectAvailableProviders() {
615
640
  const available = [];
616
641
  if (process.env.COMPUTESDK_API_KEY) {
617
642
  available.push("computesdk");
618
643
  }
619
644
  for (const provider of PROVIDER_NAMES) {
620
- if (PROVIDERS_NEEDING_CREDS.includes(provider) && isProviderAuthComplete(provider)) {
645
+ if (isProviderAuthComplete(provider)) {
621
646
  available.push(provider);
622
647
  }
623
648
  }
@@ -632,13 +657,11 @@ function getProviderStatus() {
632
657
  missing: hasGatewayKey ? [] : ["COMPUTESDK_API_KEY"]
633
658
  });
634
659
  for (const provider of PROVIDER_NAMES) {
635
- if (PROVIDERS_NEEDING_CREDS.includes(provider)) {
636
- statuses.push({
637
- name: provider,
638
- ready: isProviderAuthComplete(provider),
639
- missing: getMissingEnvVars(provider)
640
- });
641
- }
660
+ statuses.push({
661
+ name: provider,
662
+ ready: isProviderAuthComplete(provider),
663
+ missing: missingEnvVars(provider)
664
+ });
642
665
  }
643
666
  return statuses;
644
667
  }
@@ -652,9 +675,15 @@ function buildProviderConfig(provider) {
652
675
  config.computesdk = {
653
676
  computesdk_api_key: process.env.COMPUTESDK_API_KEY
654
677
  };
655
- }
656
- if (provider !== "computesdk") {
657
- const providerConfig = getProviderConfigFromEnv(provider);
678
+ return config;
679
+ }
680
+ const spec = PROVIDER_ENV[provider];
681
+ if (spec) {
682
+ const providerConfig = {};
683
+ for (const [envVar, key] of Object.entries(spec.configKeys)) {
684
+ const value = process.env[envVar];
685
+ if (value) providerConfig[key] = value;
686
+ }
658
687
  if (Object.keys(providerConfig).length > 0) {
659
688
  config[provider] = providerConfig;
660
689
  }
@@ -662,6 +691,572 @@ function buildProviderConfig(provider) {
662
691
  return config;
663
692
  }
664
693
 
694
+ // src/actions.ts
695
+ init_esm_shims();
696
+ import pc from "picocolors";
697
+ import { mkdirSync, writeFileSync } from "fs";
698
+ import { basename, join } from "path";
699
+
700
+ // src/actions-client.ts
701
+ init_esm_shims();
702
+ var DEFAULT_BASE_URL = "https://platform.computesdk.com";
703
+ var ActionsApiError = class extends Error {
704
+ constructor(status, message, body) {
705
+ super(message);
706
+ this.status = status;
707
+ this.body = body;
708
+ this.name = "ActionsApiError";
709
+ }
710
+ };
711
+ var ActionsClient = class {
712
+ constructor(auth, fetchImpl = fetch) {
713
+ this.auth = auth;
714
+ this.fetchImpl = fetchImpl;
715
+ this.orgPromise = null;
716
+ }
717
+ get baseUrl() {
718
+ return this.auth.baseUrl;
719
+ }
720
+ /** The credential's org — fetched once, needed for dashboard URLs. */
721
+ org() {
722
+ this.orgPromise ?? (this.orgPromise = this.get("/api/v1/actions/org"));
723
+ return this.orgPromise;
724
+ }
725
+ async get(path5, params) {
726
+ const url = new URL(`${this.auth.baseUrl}${path5}`);
727
+ for (const [key, value] of Object.entries(params ?? {})) {
728
+ if (value === void 0) continue;
729
+ for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
730
+ }
731
+ const res = await this.fetchImpl(url, { headers: this.headers() });
732
+ return this.parse(res);
733
+ }
734
+ async post(path5, body = {}) {
735
+ const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
736
+ method: "POST",
737
+ headers: { ...this.headers(), "content-type": "application/json" },
738
+ body: JSON.stringify(body)
739
+ });
740
+ return this.parse(res);
741
+ }
742
+ /**
743
+ * One `data:` JSON payload per SSE event. `next()` resolves to the payload,
744
+ * or null when the stream ends. Call `close()` to abort.
745
+ */
746
+ async *sse(path5, params) {
747
+ const url = new URL(`${this.auth.baseUrl}${path5}`);
748
+ for (const [key, value] of Object.entries(params ?? {})) {
749
+ if (value === void 0) continue;
750
+ for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
751
+ }
752
+ const res = await this.fetchImpl(url, { headers: this.headers() });
753
+ if (!res.ok) {
754
+ await this.parse(res);
755
+ }
756
+ if (!res.body) throw new Error("SSE response has no body");
757
+ yield* readSseEvents(res.body);
758
+ }
759
+ /** Download bytes following redirects (artifact URLs 302 to storage). */
760
+ async download(path5) {
761
+ const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
762
+ headers: this.headers(),
763
+ redirect: "follow"
764
+ });
765
+ if (!res.ok) {
766
+ const body = await res.json().catch(() => void 0);
767
+ throw new ActionsApiError(res.status, errorMessage(body, res.status), body);
768
+ }
769
+ return new Uint8Array(await res.arrayBuffer());
770
+ }
771
+ headers() {
772
+ return { authorization: `Bearer ${this.auth.apiKey}` };
773
+ }
774
+ async parse(res) {
775
+ const body = await res.json().catch(() => void 0);
776
+ if (!res.ok) throw new ActionsApiError(res.status, errorMessage(body, res.status), body);
777
+ return body;
778
+ }
779
+ };
780
+ function errorMessage(body, status) {
781
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
782
+ return body.error;
783
+ }
784
+ return `Request failed with status ${status}`;
785
+ }
786
+ async function* readSseEvents(body) {
787
+ const reader = body.getReader();
788
+ const decoder = new TextDecoder();
789
+ let buffer = "";
790
+ try {
791
+ for (; ; ) {
792
+ const { done, value } = await reader.read();
793
+ if (done) break;
794
+ buffer += decoder.decode(value, { stream: true });
795
+ let idx;
796
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
797
+ const event = buffer.slice(0, idx);
798
+ buffer = buffer.slice(idx + 2);
799
+ for (const line of event.split("\n")) {
800
+ if (line.startsWith("data: ")) {
801
+ yield JSON.parse(line.slice(6));
802
+ }
803
+ }
804
+ }
805
+ }
806
+ } finally {
807
+ reader.releaseLock();
808
+ }
809
+ }
810
+ function encodeWatchCursor(id, jobId, step, offset) {
811
+ const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
812
+ return `${id}:${jobId}:${stepPart}:${offset}`;
813
+ }
814
+ function resolveActionsAuth(opts) {
815
+ const apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY;
816
+ if (!apiKey) {
817
+ throw new Error(
818
+ "No API key. Set COMPUTE_API_KEY or pass --api-key."
819
+ );
820
+ }
821
+ const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
822
+ DEFAULT_BASE_URL).replace(/\/+$/, "");
823
+ if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
824
+ throw new Error(
825
+ `Refusing to send the API key to ${baseUrl} \u2014 it is not a computesdk.com or localhost host. If this is a self-hosted/dev deployment you trust, pass --allow-untrusted-host.`
826
+ );
827
+ }
828
+ return { apiKey, baseUrl };
829
+ }
830
+ function isTrustedActionsHost(baseUrl) {
831
+ let host;
832
+ try {
833
+ host = new URL(baseUrl).hostname;
834
+ } catch {
835
+ return false;
836
+ }
837
+ return host === "computesdk.com" || host.endsWith(".computesdk.com") || host === "localhost" || host === "127.0.0.1" || host === "::1";
838
+ }
839
+
840
+ // src/actions.ts
841
+ function sanitizePathPart(name) {
842
+ const base = basename(name);
843
+ if (base === "" || base === "." || base === "..") return "artifact";
844
+ return base;
845
+ }
846
+ function client(opts) {
847
+ return new ActionsClient(resolveActionsAuth(opts));
848
+ }
849
+ function output(opts, data, render) {
850
+ if (opts.json) {
851
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
852
+ } else {
853
+ render(data);
854
+ }
855
+ }
856
+ async function listRunJobs(c, runId) {
857
+ try {
858
+ const run = await c.get(`/api/v1/actions/runs/${runId}`);
859
+ return run.jobs;
860
+ } catch (e) {
861
+ if (!(e instanceof ActionsApiError) || e.status !== 404) throw e;
862
+ }
863
+ const state = await c.get(`/api/v1/actions/runs/${runId}/state`);
864
+ return state.jobs.map((j) => ({
865
+ id: j.id,
866
+ name: j.id.slice(0, 8),
867
+ // the live-state route does not carry job names
868
+ needs: [],
869
+ workflowJobId: null,
870
+ matrix: null,
871
+ state: j.state,
872
+ provider: j.provider,
873
+ region: j.region,
874
+ sandboxId: j.sandboxId,
875
+ placementAttempts: j.placementAttempts,
876
+ durationMs: null,
877
+ steps: []
878
+ }));
879
+ }
880
+ function fail(error) {
881
+ if (error instanceof ActionsApiError) {
882
+ console.error(pc.red(`Error (${error.status}): ${error.message}`));
883
+ } else {
884
+ console.error(pc.red(`Error: ${error.message}`));
885
+ }
886
+ process.exit(1);
887
+ }
888
+ function formatDuration(ms) {
889
+ if (ms === null || ms === void 0) return "-";
890
+ if (ms < 1e3) return `${ms}ms`;
891
+ const s = Math.round(ms / 1e3);
892
+ if (s < 60) return `${s}s`;
893
+ const m = Math.floor(s / 60);
894
+ if (m < 60) return `${m}m${s % 60}s`;
895
+ const h = Math.floor(m / 60);
896
+ return `${h}h${m % 60}m`;
897
+ }
898
+ function shortSha(sha) {
899
+ return sha.slice(0, 8);
900
+ }
901
+ function conclusionLabel(conclusion) {
902
+ switch (conclusion) {
903
+ case "passed":
904
+ return pc.green("passed");
905
+ case "failed":
906
+ return pc.red("failed");
907
+ case "running":
908
+ return pc.yellow("running");
909
+ case "cancelled":
910
+ return pc.gray("cancelled");
911
+ case "none":
912
+ return pc.yellow("queued");
913
+ default:
914
+ return conclusion;
915
+ }
916
+ }
917
+ function formatRunRow(run) {
918
+ const num = run.runNumber === null ? "-" : `#${run.runNumber}`;
919
+ return [
920
+ run.id,
921
+ pc.dim(num),
922
+ run.workflowName || run.workflowPath,
923
+ conclusionLabel(run.conclusion),
924
+ pc.dim(`${run.repoFullName}@${run.ref}`),
925
+ shortSha(run.headSha),
926
+ formatDuration(run.durationMs),
927
+ run.startedAt
928
+ ].join(" ");
929
+ }
930
+ function formatRunDetail(run, runUrl) {
931
+ const lines = [];
932
+ lines.push(`${pc.bold(run.workflowName || run.workflowPath)} ${conclusionLabel(run.conclusion)}`);
933
+ lines.push(pc.dim(`run ${run.id}${run.runNumber === null ? "" : ` #${run.runNumber}`}`));
934
+ lines.push(`${run.repoFullName} @ ${run.ref} ${shortSha(run.headSha)} ${run.event}${run.prNumber ? ` PR #${run.prNumber}` : ""}`);
935
+ lines.push(`title: ${run.title}`);
936
+ lines.push(`started: ${run.startedAt} duration: ${formatDuration(run.durationMs)}`);
937
+ if (run.cancellationReason) lines.push(`cancelled: ${run.cancellationReason}`);
938
+ if (run.supersededByRunId) lines.push(`superseded by: ${run.supersededByRunId}`);
939
+ if (runUrl) lines.push(`url: ${runUrl}`);
940
+ if (run.jobs.length > 0) {
941
+ lines.push("");
942
+ lines.push(pc.bold("jobs"));
943
+ for (const job of run.jobs) {
944
+ const placement = job.provider ? `${job.provider}${job.region ? `:${job.region}` : ""}` : "-";
945
+ lines.push(` ${job.id} ${job.name} ${conclusionLabel(job.state)} ${placement} ${formatDuration(job.durationMs)}`);
946
+ for (const attempt of job.placementAttempts) {
947
+ lines.push(pc.dim(` placement failed on ${attempt.provider}${attempt.region ? `:${attempt.region}` : ""}: ${attempt.error}`));
948
+ }
949
+ for (const step of job.steps) {
950
+ const exit = step.exitCode === null ? "" : ` (exit ${step.exitCode})`;
951
+ lines.push(pc.dim(` ${step.name} ${step.state}${exit} ${formatDuration(step.durationMs)}`));
952
+ }
953
+ }
954
+ }
955
+ return lines.join("\n");
956
+ }
957
+ function parseInputs(pairs) {
958
+ const inputs = {};
959
+ for (const pair of pairs ?? []) {
960
+ const eq = pair.indexOf("=");
961
+ if (eq === -1) {
962
+ throw new Error(`Invalid --inputs entry "${pair}". Expected key=value.`);
963
+ }
964
+ inputs[pair.slice(0, eq)] = pair.slice(eq + 1);
965
+ }
966
+ return inputs;
967
+ }
968
+ function matchWorkflow(workflows, selector) {
969
+ return workflows.find((w) => w.path === selector) ?? workflows.find((w) => w.name === selector) ?? workflows.find((w) => w.id === selector);
970
+ }
971
+ function matchJob(jobs, selector) {
972
+ return jobs.find((j) => j.id === selector) ?? jobs.find((j) => j.name === selector);
973
+ }
974
+ async function readWholeJobLog(client2, jobId, step) {
975
+ const segments = [];
976
+ let offset = 0;
977
+ let slice;
978
+ do {
979
+ slice = await client2.get(`/api/v1/actions/jobs/${jobId}/logs`, {
980
+ offset: String(offset),
981
+ step
982
+ });
983
+ segments.push(...slice.segments);
984
+ offset = slice.nextOffset;
985
+ } while (slice.truncated);
986
+ return { ...slice, segments };
987
+ }
988
+ async function* followJobLog(client2, jobId, step) {
989
+ let offset = 0;
990
+ for (; ; ) {
991
+ let terminal = false;
992
+ for await (const raw of client2.sse(`/api/v1/actions/jobs/${jobId}/logs`, {
993
+ offset: String(offset),
994
+ follow: "1",
995
+ step
996
+ })) {
997
+ const slice = raw;
998
+ offset = slice.nextOffset;
999
+ if (slice.segments.length > 0 || slice.state !== "running") yield slice;
1000
+ if (slice.state !== "running" && slice.state !== "pending" && !slice.truncated) {
1001
+ terminal = true;
1002
+ }
1003
+ }
1004
+ if (terminal) return;
1005
+ }
1006
+ }
1007
+ function parseStep(step) {
1008
+ if (step === void 0) return null;
1009
+ if (step === "runner") return "runner";
1010
+ const n = Number(step);
1011
+ if (!Number.isInteger(n) || n < 0) {
1012
+ throw new Error(`Invalid --step "${step}". Expected a step ordinal or "runner".`);
1013
+ }
1014
+ return n;
1015
+ }
1016
+ async function* followRunLogs(client2, runId, jobs, step) {
1017
+ const cursorStep = parseStep(step);
1018
+ const offsets = new Map(jobs.map((j) => [j.id, 0]));
1019
+ const byCursor = new Map(jobs.map((j, i) => [`j${i}`, j]));
1020
+ for (; ; ) {
1021
+ let done = false;
1022
+ const watch = jobs.map(
1023
+ (j, i) => encodeWatchCursor(`j${i}`, j.id, cursorStep, offsets.get(j.id) ?? 0)
1024
+ );
1025
+ for await (const raw of client2.sse(`/api/v1/actions/runs/${runId}/stream`, { watch })) {
1026
+ const msg = raw;
1027
+ if (msg.type === "log") {
1028
+ const job = byCursor.get(msg.cursor);
1029
+ if (job) {
1030
+ offsets.set(msg.slice.jobId, msg.slice.nextOffset);
1031
+ yield { job, slice: msg.slice };
1032
+ }
1033
+ } else if (msg.type === "done") {
1034
+ done = true;
1035
+ break;
1036
+ } else if (msg.type === "reconnect") {
1037
+ break;
1038
+ }
1039
+ }
1040
+ if (done) return;
1041
+ await new Promise((r) => setTimeout(r, 1e3));
1042
+ }
1043
+ }
1044
+ function registerActionsCommands(program2) {
1045
+ const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
1046
+ 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 the API key to a non-computesdk, non-localhost --base-url").option("--json", "print machine-readable JSON");
1047
+ common(
1048
+ actions.command("dispatch").description("Dispatch a workflow_dispatch run").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")
1049
+ ).action(async (repo, opts) => {
1050
+ try {
1051
+ const c = client(opts);
1052
+ const { workflows } = await c.get(
1053
+ "/api/v1/actions/workflows",
1054
+ { repo }
1055
+ );
1056
+ const workflow = matchWorkflow(workflows, opts.workflow);
1057
+ if (!workflow) {
1058
+ const choices = workflows.map((w) => `${w.path} (${w.name})`).join(", ") || "none";
1059
+ throw new Error(
1060
+ `No dispatchable workflow "${opts.workflow}" in ${repo}. Available: ${choices}`
1061
+ );
1062
+ }
1063
+ const ref = opts.ref ?? workflow.refs[0];
1064
+ if (!ref) throw new Error("No --ref given and the workflow has no watched refs.");
1065
+ const result = await c.post(
1066
+ "/api/v1/actions/dispatch",
1067
+ { workflowId: workflow.id, ref, inputs: parseInputs(opts.inputs) }
1068
+ );
1069
+ const org = await c.org();
1070
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
1071
+ output(opts, { ...result, url }, (r) => {
1072
+ console.log(`${r.created ? "dispatched" : "already running"} ${pc.cyan(r.runId)}`);
1073
+ console.log(`sha: ${r.headSha}`);
1074
+ console.log(`url: ${r.url}`);
1075
+ });
1076
+ } catch (e) {
1077
+ fail(e);
1078
+ }
1079
+ });
1080
+ common(
1081
+ actions.command("runs").description("List recent runs for a repo").argument("<repo>", "repository in owner/repo format").option("--status <status...>", "filter by conclusion: passed, failed, running, cancelled, none").option("--branch <branch...>", "filter by branch/ref")
1082
+ ).action(async (repo, opts) => {
1083
+ try {
1084
+ const { runs } = await client(opts).get(
1085
+ "/api/v1/actions/runs",
1086
+ { repo, status: opts.status, branch: opts.branch }
1087
+ );
1088
+ output(opts, runs, (rs) => {
1089
+ if (rs.length === 0) {
1090
+ console.log("No runs found.");
1091
+ return;
1092
+ }
1093
+ for (const run of rs) console.log(formatRunRow(run));
1094
+ });
1095
+ } catch (e) {
1096
+ fail(e);
1097
+ }
1098
+ });
1099
+ common(
1100
+ actions.command("run").description("Show a run: jobs, provider:region placement, placement attempts").argument("<run-id>", "run ID")
1101
+ ).action(async (runId, opts) => {
1102
+ try {
1103
+ const c = client(opts);
1104
+ const run = await c.get(`/api/v1/actions/runs/${runId}`);
1105
+ const org = await c.org();
1106
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
1107
+ output(opts, { ...run, url }, (r) => console.log(formatRunDetail(r, r.url)));
1108
+ } catch (e) {
1109
+ fail(e);
1110
+ }
1111
+ });
1112
+ common(
1113
+ actions.command("logs").description("Print or follow a run's job logs").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--step <step>", 'step ordinal or "runner" (default: whole job)').option("--follow", "stream logs while the run is live")
1114
+ ).action(async (runId, opts) => {
1115
+ try {
1116
+ const c = client(opts);
1117
+ let jobs = await listRunJobs(c, runId);
1118
+ if (opts.job) {
1119
+ const job = matchJob(jobs, opts.job);
1120
+ if (!job) {
1121
+ throw new Error(
1122
+ `No job "${opts.job}" in run ${runId}. Jobs: ${jobs.map((j) => j.name).join(", ") || "none"}`
1123
+ );
1124
+ }
1125
+ jobs = [job];
1126
+ }
1127
+ if (jobs.length === 0) {
1128
+ if (opts.json) output(opts, { segments: [] }, () => {
1129
+ });
1130
+ else console.error("Run has no jobs yet.");
1131
+ return;
1132
+ }
1133
+ if (!opts.follow) {
1134
+ for (const job of jobs) {
1135
+ const slice = await readWholeJobLog(c, job.id, opts.step);
1136
+ if (opts.json) {
1137
+ process.stdout.write(JSON.stringify({ jobId: job.id, name: job.name, slice }) + "\n");
1138
+ } else {
1139
+ if (jobs.length > 1) console.log(pc.bold(`\u2500\u2500 ${job.name} \u2500\u2500`));
1140
+ for (const seg of slice.segments) process.stdout.write(seg.text);
1141
+ }
1142
+ }
1143
+ return;
1144
+ }
1145
+ if (jobs.length === 1) {
1146
+ for await (const slice of followJobLog(c, jobs[0].id, opts.step)) {
1147
+ if (opts.json) {
1148
+ process.stdout.write(JSON.stringify(slice) + "\n");
1149
+ } else {
1150
+ for (const seg of slice.segments) process.stdout.write(seg.text);
1151
+ }
1152
+ }
1153
+ return;
1154
+ }
1155
+ for await (const { job, slice } of followRunLogs(c, runId, jobs, opts.step)) {
1156
+ if (opts.json) {
1157
+ process.stdout.write(JSON.stringify({ jobId: job.id, name: job.name, slice }) + "\n");
1158
+ } else {
1159
+ for (const seg of slice.segments) {
1160
+ const text2 = seg.text.replace(/\n/g, `
1161
+ [${job.name}] `);
1162
+ process.stdout.write(`[${job.name}] ${text2}`);
1163
+ }
1164
+ }
1165
+ }
1166
+ } catch (e) {
1167
+ fail(e);
1168
+ }
1169
+ });
1170
+ common(
1171
+ actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
1172
+ ).action(async (runId, opts) => {
1173
+ try {
1174
+ const result = await client(opts).post(
1175
+ `/api/v1/actions/runs/${runId}/cancel`
1176
+ );
1177
+ output(opts, result, (r) => {
1178
+ console.log(r.cancelled ? `Cancelled ${runId}` : `Run ${runId} was already finished`);
1179
+ });
1180
+ } catch (e) {
1181
+ fail(e);
1182
+ }
1183
+ });
1184
+ common(
1185
+ actions.command("rerun").description("Re-run a finished run at the same commit").argument("<run-id>", "run ID")
1186
+ ).action(async (runId, opts) => {
1187
+ try {
1188
+ const c = client(opts);
1189
+ const result = await c.post(
1190
+ `/api/v1/actions/runs/${runId}/rerun`
1191
+ );
1192
+ const org = await c.org();
1193
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
1194
+ output(opts, { ...result, url }, (r) => {
1195
+ console.log(`${r.created ? "rerun dispatched" : "rerun already running"} ${pc.cyan(r.runId)}`);
1196
+ console.log(`url: ${r.url}`);
1197
+ });
1198
+ } catch (e) {
1199
+ fail(e);
1200
+ }
1201
+ });
1202
+ common(
1203
+ actions.command("artifacts").description("List or download a run's artifacts").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--out <dir>", "download artifacts into this directory instead of listing")
1204
+ ).action(async (runId, opts) => {
1205
+ try {
1206
+ const c = client(opts);
1207
+ let jobs = await listRunJobs(c, runId);
1208
+ if (opts.job) {
1209
+ const job = matchJob(jobs, opts.job);
1210
+ if (!job) throw new Error(`No job "${opts.job}" in run ${runId}.`);
1211
+ jobs = [job];
1212
+ }
1213
+ const entries = [];
1214
+ for (const job of jobs) {
1215
+ const artifacts = await c.get(
1216
+ `/api/v1/actions/jobs/${job.id}/artifacts`
1217
+ );
1218
+ entries.push({ job, artifacts });
1219
+ }
1220
+ if (opts.out) {
1221
+ const written = [];
1222
+ for (const { job, artifacts } of entries) {
1223
+ const jobDir = join(opts.out, sanitizePathPart(job.name || job.id));
1224
+ mkdirSync(jobDir, { recursive: true });
1225
+ for (const artifact of artifacts) {
1226
+ if (artifact.expired || artifact.skippedReason) continue;
1227
+ const bytes = await c.download(
1228
+ `/api/v1/actions/jobs/${job.id}/artifacts/${artifact.id}`
1229
+ );
1230
+ const file = join(jobDir, sanitizePathPart(artifact.fileName || artifact.name));
1231
+ writeFileSync(file, bytes);
1232
+ written.push({ jobId: job.id, id: artifact.id, file });
1233
+ }
1234
+ }
1235
+ output(opts, written, (ws) => {
1236
+ for (const w of ws) console.log(`wrote ${w.file}`);
1237
+ if (ws.length === 0) console.log("No downloadable artifacts.");
1238
+ });
1239
+ return;
1240
+ }
1241
+ output(opts, entries.map((e) => ({ jobId: e.job.id, job: e.job.name, artifacts: e.artifacts })), (es) => {
1242
+ let any = false;
1243
+ for (const { job, artifacts } of es) {
1244
+ for (const a of artifacts) {
1245
+ any = true;
1246
+ const flags = a.expired ? " expired" : a.skippedReason ? ` skipped: ${a.skippedReason}` : "";
1247
+ console.log(
1248
+ `${job} ${a.id} ${a.name} ${a.contentType} ${a.byteLength}B${flags}`
1249
+ );
1250
+ }
1251
+ }
1252
+ if (!any) console.log("No artifacts.");
1253
+ });
1254
+ } catch (e) {
1255
+ fail(e);
1256
+ }
1257
+ });
1258
+ }
1259
+
665
1260
  // src/index.ts
666
1261
  init_repl();
667
1262
 
@@ -674,7 +1269,7 @@ import * as fs2 from "fs";
674
1269
  import * as path3 from "path";
675
1270
  import * as os2 from "os";
676
1271
  import * as p from "@clack/prompts";
677
- import pc2 from "picocolors";
1272
+ import pc3 from "picocolors";
678
1273
  function getCredentialsDir() {
679
1274
  return path3.join(os2.homedir(), ".computesdk");
680
1275
  }
@@ -732,7 +1327,7 @@ function openBrowser(url) {
732
1327
  if (err) {
733
1328
  p.log.warn("Could not open browser automatically.");
734
1329
  p.log.info(`Open this URL manually:
735
- ${pc2.cyan(url)}`);
1330
+ ${pc3.cyan(url)}`);
736
1331
  }
737
1332
  });
738
1333
  }
@@ -803,7 +1398,7 @@ async function resolveApiKey(options) {
803
1398
  const stored = loadStoredCredentials();
804
1399
  if (stored) {
805
1400
  p.log.info(
806
- `Using stored credentials from ${pc2.dim("~/.computesdk/credentials.json")}`
1401
+ `Using stored credentials from ${pc3.dim("~/.computesdk/credentials.json")}`
807
1402
  );
808
1403
  return stored;
809
1404
  }
@@ -817,14 +1412,14 @@ async function resolveApiKey(options) {
817
1412
  try {
818
1413
  storeCredentials(apiKey);
819
1414
  p.log.success(
820
- `Credentials saved to ${pc2.dim("~/.computesdk/credentials.json")}`
1415
+ `Credentials saved to ${pc3.dim("~/.computesdk/credentials.json")}`
821
1416
  );
822
1417
  } catch {
823
1418
  p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
824
1419
  }
825
1420
  return apiKey;
826
1421
  } catch (error) {
827
- spinner4.stop(pc2.red("Authentication failed"));
1422
+ spinner4.stop(pc3.red("Authentication failed"));
828
1423
  throw error;
829
1424
  }
830
1425
  }
@@ -832,7 +1427,7 @@ async function resolveApiKey(options) {
832
1427
  // src/setup.ts
833
1428
  init_esm_shims();
834
1429
  import * as p2 from "@clack/prompts";
835
- import pc3 from "picocolors";
1430
+ import pc4 from "picocolors";
836
1431
  import { compute as compute2 } from "computesdk";
837
1432
  async function ensureAuth(options) {
838
1433
  try {
@@ -840,8 +1435,8 @@ async function ensureAuth(options) {
840
1435
  process.env.COMPUTESDK_API_KEY = apiKey;
841
1436
  } catch (error) {
842
1437
  p2.log.error(error.message);
843
- p2.log.info(`Get your API key at ${pc3.cyan("https://console.computesdk.com")}`);
844
- p2.outro(pc3.red("Setup incomplete"));
1438
+ p2.log.info(`Get your API key at ${pc4.cyan("https://console.computesdk.com")}`);
1439
+ p2.outro(pc4.red("Setup incomplete"));
845
1440
  process.exit(1);
846
1441
  }
847
1442
  }
@@ -870,7 +1465,7 @@ async function resolveProvider(providerFlag, options) {
870
1465
  if (available.length === 0) {
871
1466
  p2.log.error("No provider credentials detected");
872
1467
  p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
873
- p2.outro(pc3.red("Setup incomplete"));
1468
+ p2.outro(pc4.red("Setup incomplete"));
874
1469
  process.exit(1);
875
1470
  }
876
1471
  if (available.length === 1) {
@@ -905,7 +1500,7 @@ init_workspace_cli();
905
1500
  var __filename2 = fileURLToPath2(import.meta.url);
906
1501
  var __dirname2 = dirname2(__filename2);
907
1502
  var packageJson = JSON.parse(
908
- readFileSync4(join4(__dirname2, "..", "package.json"), "utf-8")
1503
+ readFileSync4(join5(__dirname2, "..", "package.json"), "utf-8")
909
1504
  );
910
1505
  var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
911
1506
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
@@ -914,24 +1509,24 @@ program.name("compute").description("Cloud-native workspace management").version
914
1509
  program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
915
1510
  if (opts.logout) {
916
1511
  console.log();
917
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1512
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
918
1513
  clearStoredCredentials();
919
1514
  p4.log.success("Logged out. Stored credentials removed.");
920
- p4.outro(pc5.green("Done!"));
1515
+ p4.outro(pc6.green("Done!"));
921
1516
  process.exit(0);
922
1517
  }
923
1518
  if (opts.login) {
924
1519
  console.log();
925
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1520
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
926
1521
  await ensureAuth({ forceLogin: true });
927
- p4.outro(pc5.green("Authenticated!"));
1522
+ p4.outro(pc6.green("Authenticated!"));
928
1523
  process.exit(0);
929
1524
  }
930
1525
  program.help();
931
1526
  });
932
1527
  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) => {
933
1528
  console.log();
934
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1529
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
935
1530
  await ensureAuth();
936
1531
  if (!repo) {
937
1532
  const state = loadWorkspaceState();
@@ -952,7 +1547,7 @@ program.command("create").description("Create a new workspace (same as create-co
952
1547
  message: "Select workspace to attach (or create new)",
953
1548
  options: [
954
1549
  ...choices,
955
- { value: "__new__", label: pc5.gray("+ Create new workspace") }
1550
+ { value: "__new__", label: pc6.gray("+ Create new workspace") }
956
1551
  ]
957
1552
  });
958
1553
  if (p4.isCancel(selected)) {
@@ -993,15 +1588,15 @@ program.command("create").description("Create a new workspace (same as create-co
993
1588
  branch: options.branch,
994
1589
  provider
995
1590
  });
996
- spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
1591
+ spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
997
1592
  if (options.attach !== false) {
998
1593
  console.log();
999
1594
  await workspaceAttach(workspace.id);
1000
1595
  } else {
1001
- p4.outro(pc5.green("Workspace created!"));
1596
+ p4.outro(pc6.green("Workspace created!"));
1002
1597
  }
1003
1598
  } catch (error) {
1004
- spinner4.stop(pc5.red("Failed to create workspace"));
1599
+ spinner4.stop(pc6.red("Failed to create workspace"));
1005
1600
  p4.log.error(error.message);
1006
1601
  process.exit(1);
1007
1602
  }
@@ -1010,7 +1605,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
1010
1605
  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) => {
1011
1606
  if (!opts.silent) {
1012
1607
  console.log();
1013
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1608
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1014
1609
  }
1015
1610
  await ensureAuth();
1016
1611
  const provider = await resolveProvider(opts.provider);
@@ -1022,7 +1617,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1022
1617
  const sandbox = await compute4.sandbox.create(
1023
1618
  buildCreateOptions(opts)
1024
1619
  );
1025
- spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
1620
+ spinner4.stop(`Sandbox ready: ${pc6.cyan(sandbox.sandboxId)}`);
1026
1621
  const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
1027
1622
  await saveSandbox2({
1028
1623
  id: sandbox.sandboxId,
@@ -1035,7 +1630,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1035
1630
  await startREPL(sandbox, provider);
1036
1631
  }
1037
1632
  } catch (error) {
1038
- spinner4.stop(pc5.red("Failed to create sandbox"));
1633
+ spinner4.stop(pc6.red("Failed to create sandbox"));
1039
1634
  p4.log.error(error.message);
1040
1635
  process.exit(1);
1041
1636
  }
@@ -1064,14 +1659,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1064
1659
  });
1065
1660
  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) => {
1066
1661
  console.log();
1067
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1662
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1068
1663
  const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
1069
1664
  const state = loadState2();
1070
1665
  if (!sandboxIds || sandboxIds.length === 0) {
1071
1666
  const workspaces = Object.values(state.workspaces);
1072
1667
  if (workspaces.length === 0) {
1073
1668
  p4.log.info("No sandboxes to destroy.");
1074
- p4.outro(pc5.green("Done!"));
1669
+ p4.outro(pc6.green("Done!"));
1075
1670
  return;
1076
1671
  }
1077
1672
  const choices = workspaces.map((ws) => {
@@ -1079,7 +1674,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1079
1674
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1080
1675
  );
1081
1676
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1082
- const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
1677
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1083
1678
  return {
1084
1679
  value: ws.sandboxId,
1085
1680
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1107,7 +1702,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1107
1702
  for (const result of results) {
1108
1703
  if (result.status === "fulfilled") {
1109
1704
  const sandboxId = result.value;
1110
- console.log(pc5.green(`Destroyed ${sandboxId}`));
1705
+ console.log(pc6.green(`Destroyed ${sandboxId}`));
1111
1706
  const workspaceId = Object.keys(state.workspaces).find(
1112
1707
  (key) => state.workspaces[key].sandboxId === sandboxId
1113
1708
  );
@@ -1115,19 +1710,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1115
1710
  delete state.workspaces[workspaceId];
1116
1711
  }
1117
1712
  } else {
1118
- console.error(pc5.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
1713
+ console.error(pc6.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
1119
1714
  }
1120
1715
  }
1121
1716
  saveState2(state);
1122
1717
  });
1123
1718
  sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
1124
1719
  console.log();
1125
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1720
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1126
1721
  await sandboxList();
1127
1722
  });
1128
1723
  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) => {
1129
1724
  console.log();
1130
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1725
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1131
1726
  await ensureAuth();
1132
1727
  const provider = await resolveProvider(opts.provider);
1133
1728
  configureCompute(provider);
@@ -1138,7 +1733,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1138
1733
  if (workspaces.length === 0) {
1139
1734
  p4.log.info("No sandboxes found.");
1140
1735
  p4.log.info("Create one with: compute sandbox create");
1141
- p4.outro(pc5.green("Done!"));
1736
+ p4.outro(pc6.green("Done!"));
1142
1737
  return;
1143
1738
  }
1144
1739
  const choices = workspaces.map((ws) => {
@@ -1146,7 +1741,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1146
1741
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1147
1742
  );
1148
1743
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1149
- const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
1744
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1150
1745
  return {
1151
1746
  value: ws.sandboxId,
1152
1747
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1166,7 +1761,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1166
1761
  spinner4.start("Connecting...");
1167
1762
  const sandbox = await compute4.sandbox.getById(sandboxId);
1168
1763
  if (!sandbox) {
1169
- spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
1764
+ spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
1170
1765
  process.exit(1);
1171
1766
  }
1172
1767
  spinner4.stop("");
@@ -1178,7 +1773,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
1178
1773
  configureCompute(provider);
1179
1774
  const sandbox = await compute4.sandbox.getById(sandboxId);
1180
1775
  if (!sandbox) {
1181
- console.error(pc5.red(`Sandbox not found: ${sandboxId}`));
1776
+ console.error(pc6.red(`Sandbox not found: ${sandboxId}`));
1182
1777
  process.exit(1);
1183
1778
  }
1184
1779
  const cmd = buildExecCommand(command, opts);
@@ -1193,7 +1788,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
1193
1788
  });
1194
1789
  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) => {
1195
1790
  console.log();
1196
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1791
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1197
1792
  await ensureAuth();
1198
1793
  const provider = await resolveProvider(opts.provider);
1199
1794
  configureCompute(provider);
@@ -1204,7 +1799,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1204
1799
  if (workspaces.length === 0) {
1205
1800
  p4.log.info("No sandboxes found.");
1206
1801
  p4.log.info("Create one with: compute sandbox create");
1207
- p4.outro(pc5.green("Done!"));
1802
+ p4.outro(pc6.green("Done!"));
1208
1803
  return;
1209
1804
  }
1210
1805
  const choices = workspaces.map((ws) => {
@@ -1212,7 +1807,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1212
1807
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1213
1808
  );
1214
1809
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1215
- const status = age > 7 ? pc5.red("stale") : age > 1 ? pc5.yellow("idle") : pc5.green("active");
1810
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1216
1811
  return {
1217
1812
  value: ws.sandboxId,
1218
1813
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1232,7 +1827,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1232
1827
  spinner4.start("Connecting...");
1233
1828
  const sandbox = await compute4.sandbox.getById(sandboxId);
1234
1829
  if (!sandbox) {
1235
- spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
1830
+ spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
1236
1831
  process.exit(1);
1237
1832
  }
1238
1833
  spinner4.stop("");
@@ -1241,11 +1836,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1241
1836
  var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
1242
1837
  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) => {
1243
1838
  console.log();
1244
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1839
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1245
1840
  await ensureAuth();
1246
1841
  if (!repo) {
1247
1842
  p4.log.info("Usage: compute workspace create owner/repo --branch main");
1248
- p4.outro(pc5.green("Done!"));
1843
+ p4.outro(pc6.green("Done!"));
1249
1844
  return;
1250
1845
  }
1251
1846
  const provider = await resolveProvider(options.provider, { interactive: true });
@@ -1258,33 +1853,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
1258
1853
  branch: options.branch,
1259
1854
  provider
1260
1855
  });
1261
- spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
1856
+ spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
1262
1857
  if (options.attach !== false) {
1263
1858
  console.log();
1264
1859
  await workspaceAttach(workspace.id);
1265
1860
  } else {
1266
- p4.outro(pc5.green("Workspace created!"));
1861
+ p4.outro(pc6.green("Workspace created!"));
1267
1862
  }
1268
1863
  } catch (error) {
1269
- spinner4.stop(pc5.red("Failed to create workspace"));
1864
+ spinner4.stop(pc6.red("Failed to create workspace"));
1270
1865
  p4.log.error(error.message);
1271
1866
  process.exit(1);
1272
1867
  }
1273
1868
  });
1274
1869
  workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
1275
1870
  console.log();
1276
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1871
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1277
1872
  await workspaceList();
1278
1873
  });
1279
1874
  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) => {
1280
1875
  console.log();
1281
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1876
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1282
1877
  if (!id) {
1283
1878
  const state = loadWorkspaceState();
1284
1879
  const workspaceIds = Object.keys(state.workspaces);
1285
1880
  if (workspaceIds.length === 0) {
1286
1881
  p4.log.info("No workspaces to destroy.");
1287
- p4.outro(pc5.green("Done!"));
1882
+ p4.outro(pc6.green("Done!"));
1288
1883
  return;
1289
1884
  }
1290
1885
  const choices = workspaceIds.map((key) => {
@@ -1295,7 +1890,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1295
1890
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1296
1891
  return {
1297
1892
  value: key,
1298
- label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
1893
+ label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
1299
1894
  };
1300
1895
  });
1301
1896
  const selected = await p4.multiselect({
@@ -1315,14 +1910,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1315
1910
  });
1316
1911
  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) => {
1317
1912
  console.log();
1318
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1913
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1319
1914
  if (!id) {
1320
1915
  const state = loadWorkspaceState();
1321
1916
  const workspaceIds = Object.keys(state.workspaces);
1322
1917
  if (workspaceIds.length === 0) {
1323
1918
  p4.log.info("No workspaces found.");
1324
1919
  p4.log.info("Create one with: compute create owner/repo");
1325
- p4.outro(pc5.green("Done!"));
1920
+ p4.outro(pc6.green("Done!"));
1326
1921
  return;
1327
1922
  }
1328
1923
  const choices = workspaceIds.map((key) => {
@@ -1333,7 +1928,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
1333
1928
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1334
1929
  return {
1335
1930
  value: key,
1336
- label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
1931
+ label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
1337
1932
  };
1338
1933
  });
1339
1934
  const selected = await p4.select({
@@ -1358,7 +1953,7 @@ program.command("run").description("Create a sandbox, run a command, then option
1358
1953
  buildCreateOptions(opts)
1359
1954
  );
1360
1955
  } catch (error) {
1361
- console.error(pc5.red(`Failed to create sandbox: ${error.message}`));
1956
+ console.error(pc6.red(`Failed to create sandbox: ${error.message}`));
1362
1957
  process.exit(1);
1363
1958
  }
1364
1959
  try {
@@ -1381,35 +1976,38 @@ program.command("run").description("Create a sandbox, run a command, then option
1381
1976
  } catch {
1382
1977
  }
1383
1978
  }
1384
- console.error(pc5.red(error.message));
1979
+ console.error(pc6.red(error.message));
1385
1980
  process.exit(1);
1386
1981
  }
1387
1982
  });
1388
1983
  program.command("providers").description("List all providers and their configuration status").action(async () => {
1389
1984
  console.log();
1390
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1985
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1391
1986
  const statuses = getProviderStatus();
1392
1987
  console.log();
1393
1988
  for (const status of statuses) {
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(", ")}`);
1989
+ const icon = status.ready ? pc6.green("\u25CF") : pc6.gray("\u25CB");
1990
+ const name = status.ready ? pc6.white(status.name) : pc6.gray(status.name);
1991
+ const detail = status.ready ? pc6.green("ready") : pc6.gray(`missing: ${status.missing.join(", ")}`);
1397
1992
  console.log(` ${icon} ${name} ${detail}`);
1398
1993
  }
1399
1994
  console.log();
1400
1995
  });
1401
1996
  program.command("login").description("Authenticate with ComputeSDK").action(async () => {
1402
1997
  console.log();
1403
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1998
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1404
1999
  await ensureAuth({ forceLogin: true });
1405
- p4.outro(pc5.green("Authenticated!"));
2000
+ p4.outro(pc6.green("Authenticated!"));
1406
2001
  });
1407
2002
  program.command("logout").description("Clear stored credentials").action(() => {
1408
2003
  console.log();
1409
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2004
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1410
2005
  clearStoredCredentials();
1411
2006
  p4.log.success("Logged out. Stored credentials removed.");
1412
- p4.outro(pc5.green("Done!"));
2007
+ p4.outro(pc6.green("Done!"));
2008
+ });
2009
+ registerActionsCommands(program);
2010
+ program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
1413
2011
  });
1414
2012
  function buildCreateOptions(opts) {
1415
2013
  const options = {};
@@ -1477,8 +2075,16 @@ function parseDuration(input) {
1477
2075
  }
1478
2076
  return total;
1479
2077
  }
1480
- program.parseAsync(process.argv).catch((error) => {
1481
- console.error(pc5.red(`
2078
+ async function main() {
2079
+ if (process.argv[2] === "bench" || process.argv[2] === "benchmark") {
2080
+ const { run: benchRun } = await import("@benchsdk/runner");
2081
+ await benchRun(process.argv.slice(3));
2082
+ return;
2083
+ }
2084
+ await program.parseAsync(process.argv);
2085
+ }
2086
+ main().catch((error) => {
2087
+ console.error(pc6.red(`
1482
2088
  Error: ${error.message}`));
1483
2089
  process.exit(1);
1484
2090
  });