@computesdk/cli 1.0.8 → 1.0.10

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,742 @@ 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
+ async put(path5, body = {}) {
743
+ const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
744
+ method: "PUT",
745
+ headers: { ...this.headers(), "content-type": "application/json" },
746
+ body: JSON.stringify(body)
747
+ });
748
+ return this.parse(res);
749
+ }
750
+ async del(path5) {
751
+ const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
752
+ method: "DELETE",
753
+ headers: this.headers()
754
+ });
755
+ return this.parse(res);
756
+ }
757
+ /**
758
+ * One `data:` JSON payload per SSE event. `next()` resolves to the payload,
759
+ * or null when the stream ends. Call `close()` to abort.
760
+ */
761
+ async *sse(path5, params) {
762
+ const url = new URL(`${this.auth.baseUrl}${path5}`);
763
+ for (const [key, value] of Object.entries(params ?? {})) {
764
+ if (value === void 0) continue;
765
+ for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
766
+ }
767
+ const res = await this.fetchImpl(url, { headers: this.headers() });
768
+ if (!res.ok) {
769
+ await this.parse(res);
770
+ }
771
+ if (!res.body) throw new Error("SSE response has no body");
772
+ yield* readSseEvents(res.body);
773
+ }
774
+ /** Download bytes following redirects (artifact URLs 302 to storage). */
775
+ async download(path5) {
776
+ const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
777
+ headers: this.headers(),
778
+ redirect: "follow"
779
+ });
780
+ if (!res.ok) {
781
+ const body = await res.json().catch(() => void 0);
782
+ throw new ActionsApiError(res.status, errorMessage(body, res.status), body);
783
+ }
784
+ return new Uint8Array(await res.arrayBuffer());
785
+ }
786
+ headers() {
787
+ return { authorization: `Bearer ${this.auth.apiKey}` };
788
+ }
789
+ async parse(res) {
790
+ const body = await res.json().catch(() => void 0);
791
+ if (!res.ok) throw new ActionsApiError(res.status, errorMessage(body, res.status), body);
792
+ return body;
793
+ }
794
+ };
795
+ function errorMessage(body, status) {
796
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
797
+ return body.error;
798
+ }
799
+ return `Request failed with status ${status}`;
800
+ }
801
+ async function* readSseEvents(body) {
802
+ const reader = body.getReader();
803
+ const decoder = new TextDecoder();
804
+ let buffer = "";
805
+ try {
806
+ for (; ; ) {
807
+ const { done, value } = await reader.read();
808
+ if (done) break;
809
+ buffer += decoder.decode(value, { stream: true });
810
+ let idx;
811
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
812
+ const event = buffer.slice(0, idx);
813
+ buffer = buffer.slice(idx + 2);
814
+ for (const line of event.split("\n")) {
815
+ if (line.startsWith("data: ")) {
816
+ yield JSON.parse(line.slice(6));
817
+ }
818
+ }
819
+ }
820
+ }
821
+ } finally {
822
+ reader.releaseLock();
823
+ }
824
+ }
825
+ function encodeWatchCursor(id, jobId, step, offset) {
826
+ const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
827
+ return `${id}:${jobId}:${stepPart}:${offset}`;
828
+ }
829
+ function resolveActionsAuth(opts) {
830
+ const apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY;
831
+ if (!apiKey) {
832
+ throw new Error(
833
+ "No API key. Set COMPUTE_API_KEY or pass --api-key."
834
+ );
835
+ }
836
+ const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
837
+ DEFAULT_BASE_URL).replace(/\/+$/, "");
838
+ if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
839
+ throw new Error(
840
+ `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.`
841
+ );
842
+ }
843
+ return { apiKey, baseUrl };
844
+ }
845
+ function isTrustedActionsHost(baseUrl) {
846
+ let host;
847
+ try {
848
+ host = new URL(baseUrl).hostname;
849
+ } catch {
850
+ return false;
851
+ }
852
+ return host === "computesdk.com" || host.endsWith(".computesdk.com") || host === "localhost" || host === "127.0.0.1" || host === "::1";
853
+ }
854
+
855
+ // src/actions.ts
856
+ function sanitizePathPart(name) {
857
+ const base = basename(name);
858
+ if (base === "" || base === "." || base === "..") return "artifact";
859
+ return base;
860
+ }
861
+ function client(opts) {
862
+ return new ActionsClient(resolveActionsAuth(opts));
863
+ }
864
+ function output(opts, data, render) {
865
+ if (opts.json) {
866
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
867
+ } else {
868
+ render(data);
869
+ }
870
+ }
871
+ async function listRunJobs(c, runId) {
872
+ try {
873
+ const run = await c.get(`/api/v1/actions/runs/${runId}`);
874
+ return run.jobs;
875
+ } catch (e) {
876
+ if (!(e instanceof ActionsApiError) || e.status !== 404) throw e;
877
+ }
878
+ const state = await c.get(`/api/v1/actions/runs/${runId}/state`);
879
+ return state.jobs.map((j) => ({
880
+ id: j.id,
881
+ name: j.id.slice(0, 8),
882
+ // the live-state route does not carry job names
883
+ needs: [],
884
+ workflowJobId: null,
885
+ matrix: null,
886
+ state: j.state,
887
+ provider: j.provider,
888
+ region: j.region,
889
+ sandboxId: j.sandboxId,
890
+ placementAttempts: j.placementAttempts,
891
+ durationMs: null,
892
+ steps: []
893
+ }));
894
+ }
895
+ function fail(error) {
896
+ if (error instanceof ActionsApiError) {
897
+ console.error(pc.red(`Error (${error.status}): ${error.message}`));
898
+ } else {
899
+ console.error(pc.red(`Error: ${error.message}`));
900
+ }
901
+ process.exit(1);
902
+ }
903
+ function formatDuration(ms) {
904
+ if (ms === null || ms === void 0) return "-";
905
+ if (ms < 1e3) return `${ms}ms`;
906
+ const s = Math.round(ms / 1e3);
907
+ if (s < 60) return `${s}s`;
908
+ const m = Math.floor(s / 60);
909
+ if (m < 60) return `${m}m${s % 60}s`;
910
+ const h = Math.floor(m / 60);
911
+ return `${h}h${m % 60}m`;
912
+ }
913
+ function shortSha(sha) {
914
+ return sha.slice(0, 8);
915
+ }
916
+ function conclusionLabel(conclusion) {
917
+ switch (conclusion) {
918
+ case "passed":
919
+ return pc.green("passed");
920
+ case "failed":
921
+ return pc.red("failed");
922
+ case "running":
923
+ return pc.yellow("running");
924
+ case "cancelled":
925
+ return pc.gray("cancelled");
926
+ case "none":
927
+ return pc.yellow("queued");
928
+ default:
929
+ return conclusion;
930
+ }
931
+ }
932
+ function formatRunRow(run) {
933
+ const num = run.runNumber === null ? "-" : `#${run.runNumber}`;
934
+ return [
935
+ run.id,
936
+ pc.dim(num),
937
+ run.workflowName || run.workflowPath,
938
+ conclusionLabel(run.conclusion),
939
+ pc.dim(`${run.repoFullName}@${run.ref}`),
940
+ shortSha(run.headSha),
941
+ formatDuration(run.durationMs),
942
+ run.startedAt
943
+ ].join(" ");
944
+ }
945
+ function formatRunDetail(run, runUrl) {
946
+ const lines = [];
947
+ lines.push(`${pc.bold(run.workflowName || run.workflowPath)} ${conclusionLabel(run.conclusion)}`);
948
+ lines.push(pc.dim(`run ${run.id}${run.runNumber === null ? "" : ` #${run.runNumber}`}`));
949
+ lines.push(`${run.repoFullName} @ ${run.ref} ${shortSha(run.headSha)} ${run.event}${run.prNumber ? ` PR #${run.prNumber}` : ""}`);
950
+ lines.push(`title: ${run.title}`);
951
+ lines.push(`started: ${run.startedAt} duration: ${formatDuration(run.durationMs)}`);
952
+ if (run.cancellationReason) lines.push(`cancelled: ${run.cancellationReason}`);
953
+ if (run.providerOverride) lines.push(`dispatched to: ${run.providerOverride}`);
954
+ if (run.supersededByRunId) lines.push(`superseded by: ${run.supersededByRunId}`);
955
+ if (runUrl) lines.push(`url: ${runUrl}`);
956
+ if (run.jobs.length > 0) {
957
+ lines.push("");
958
+ lines.push(pc.bold("jobs"));
959
+ for (const job of run.jobs) {
960
+ const placement = job.provider ? `${job.provider}${job.region ? `:${job.region}` : ""}` : "-";
961
+ lines.push(` ${job.id} ${job.name} ${conclusionLabel(job.state)} ${placement} ${formatDuration(job.durationMs)}`);
962
+ for (const attempt of job.placementAttempts) {
963
+ lines.push(pc.dim(` placement failed on ${attempt.provider}${attempt.region ? `:${attempt.region}` : ""}: ${attempt.error}`));
964
+ }
965
+ for (const step of job.steps) {
966
+ const exit = step.exitCode === null ? "" : ` (exit ${step.exitCode})`;
967
+ lines.push(pc.dim(` ${step.name} ${step.state}${exit} ${formatDuration(step.durationMs)}`));
968
+ }
969
+ }
970
+ }
971
+ return lines.join("\n");
972
+ }
973
+ function parseInputs(pairs) {
974
+ const inputs = {};
975
+ for (const pair of pairs ?? []) {
976
+ const eq = pair.indexOf("=");
977
+ if (eq === -1) {
978
+ throw new Error(`Invalid --inputs entry "${pair}". Expected key=value.`);
979
+ }
980
+ inputs[pair.slice(0, eq)] = pair.slice(eq + 1);
981
+ }
982
+ return inputs;
983
+ }
984
+ function matchWorkflow(workflows, selector) {
985
+ return workflows.find((w) => w.path === selector) ?? workflows.find((w) => w.name === selector) ?? workflows.find((w) => w.id === selector);
986
+ }
987
+ function matchJob(jobs, selector) {
988
+ return jobs.find((j) => j.id === selector) ?? jobs.find((j) => j.name === selector);
989
+ }
990
+ async function readWholeJobLog(client2, jobId, step) {
991
+ const segments = [];
992
+ let offset = 0;
993
+ let slice;
994
+ do {
995
+ slice = await client2.get(`/api/v1/actions/jobs/${jobId}/logs`, {
996
+ offset: String(offset),
997
+ step
998
+ });
999
+ segments.push(...slice.segments);
1000
+ offset = slice.nextOffset;
1001
+ } while (slice.truncated);
1002
+ return { ...slice, segments };
1003
+ }
1004
+ async function* followJobLog(client2, jobId, step) {
1005
+ let offset = 0;
1006
+ for (; ; ) {
1007
+ let terminal = false;
1008
+ for await (const raw of client2.sse(`/api/v1/actions/jobs/${jobId}/logs`, {
1009
+ offset: String(offset),
1010
+ follow: "1",
1011
+ step
1012
+ })) {
1013
+ const slice = raw;
1014
+ offset = slice.nextOffset;
1015
+ if (slice.segments.length > 0 || slice.state !== "running") yield slice;
1016
+ if (slice.state !== "running" && slice.state !== "pending" && !slice.truncated) {
1017
+ terminal = true;
1018
+ }
1019
+ }
1020
+ if (terminal) return;
1021
+ }
1022
+ }
1023
+ function parseStep(step) {
1024
+ if (step === void 0) return null;
1025
+ if (step === "runner") return "runner";
1026
+ const n = Number(step);
1027
+ if (!Number.isInteger(n) || n < 0) {
1028
+ throw new Error(`Invalid --step "${step}". Expected a step ordinal or "runner".`);
1029
+ }
1030
+ return n;
1031
+ }
1032
+ async function* followRunLogs(client2, runId, jobs, step) {
1033
+ const cursorStep = parseStep(step);
1034
+ const offsets = new Map(jobs.map((j) => [j.id, 0]));
1035
+ const byCursor = new Map(jobs.map((j, i) => [`j${i}`, j]));
1036
+ for (; ; ) {
1037
+ let done = false;
1038
+ const watch = jobs.map(
1039
+ (j, i) => encodeWatchCursor(`j${i}`, j.id, cursorStep, offsets.get(j.id) ?? 0)
1040
+ );
1041
+ for await (const raw of client2.sse(`/api/v1/actions/runs/${runId}/stream`, { watch })) {
1042
+ const msg = raw;
1043
+ if (msg.type === "log") {
1044
+ const job = byCursor.get(msg.cursor);
1045
+ if (job) {
1046
+ offsets.set(msg.slice.jobId, msg.slice.nextOffset);
1047
+ yield { job, slice: msg.slice };
1048
+ }
1049
+ } else if (msg.type === "done") {
1050
+ done = true;
1051
+ break;
1052
+ } else if (msg.type === "reconnect") {
1053
+ break;
1054
+ }
1055
+ }
1056
+ if (done) return;
1057
+ await new Promise((r) => setTimeout(r, 1e3));
1058
+ }
1059
+ }
1060
+ function safeTerm(s) {
1061
+ return s.replace(/[\u0000-\u001F\u007F-\u009F]/g, "\uFFFD");
1062
+ }
1063
+ function formatRunHistory(history) {
1064
+ const lines = [];
1065
+ const parts = Object.entries(history.conclusions).map(([c, n]) => `${n} ${c}`).join(", ");
1066
+ lines.push(`last ${history.runCount} runs: ${parts || "none"}`);
1067
+ if (history.jobs.length > 0) {
1068
+ lines.push("");
1069
+ lines.push(pc.bold("jobs"));
1070
+ for (const job of history.jobs) {
1071
+ const rate = job.runs > 0 ? Math.round(job.failedRuns / job.runs * 100) : 0;
1072
+ const platform = job.failedBeforeSteps > 0 ? pc.dim(` (${job.failedBeforeSteps} failed before steps)`) : "";
1073
+ const count = `${job.failedRuns}/${job.runs} runs failed`;
1074
+ const label = job.failedRuns > 0 ? pc.red(count) : pc.green(count);
1075
+ lines.push(` ${safeTerm(job.job)} ${label} ${rate}%${platform}`);
1076
+ for (const step of job.steps) {
1077
+ lines.push(pc.dim(` step "${safeTerm(step.step)}" failed in ${step.failedRuns} run${step.failedRuns === 1 ? "" : "s"}`));
1078
+ }
1079
+ }
1080
+ }
1081
+ const failed = history.runs.filter(
1082
+ (r) => r.conclusion === "failed" || r.failedJobs.length > 0
1083
+ );
1084
+ if (failed.length > 0) {
1085
+ lines.push("");
1086
+ lines.push(pc.bold("failed runs"));
1087
+ for (const run of failed) {
1088
+ const blame = run.failedJobs.length > 0 ? run.failedJobs.map((j) => safeTerm(j.step === null ? j.job : `${j.job}:${j.step}`)).join(", ") : pc.dim("(no failed job recorded)");
1089
+ lines.push(` ${shortSha(run.headSha)} ${pc.dim(run.startedAt)} ${blame}`);
1090
+ }
1091
+ }
1092
+ return lines.join("\n");
1093
+ }
1094
+ function formatProviderRow(p5) {
1095
+ const icon = p5.usable ? pc.green("\u25CF") : pc.gray("\u25CB");
1096
+ const name = p5.usable ? pc.white(p5.provider) : pc.gray(p5.provider);
1097
+ const flags = [
1098
+ p5.credential,
1099
+ p5.actCapable ? pc.green("act") : pc.gray("no-act"),
1100
+ p5.regions.length > 0 ? `regions: ${p5.regions.join(",")}` : "no region choice",
1101
+ p5.position === null ? pc.gray("not in provider order") : `order #${p5.position}`
1102
+ ].join(" ");
1103
+ return ` ${icon} ${name} ${flags}`;
1104
+ }
1105
+ function formatVerifyResult(r) {
1106
+ const ok = r.verified === true;
1107
+ const head = ok ? pc.green("verified") : pc.red("not verified");
1108
+ const detail = r.key.statusDetail ? pc.dim(` ${r.key.statusDetail}`) : "";
1109
+ const act = r.key.actCapable ? pc.green(" act-capable") : "";
1110
+ return `${head} ${r.key.provider}${act}${detail}`;
1111
+ }
1112
+ function registerActionsCommands(program2) {
1113
+ const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
1114
+ 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");
1115
+ common(
1116
+ 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").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>)")
1117
+ ).action(async (repo, opts) => {
1118
+ try {
1119
+ const c = client(opts);
1120
+ const { workflows } = await c.get(
1121
+ "/api/v1/actions/workflows",
1122
+ { repo }
1123
+ );
1124
+ const workflow = matchWorkflow(workflows, opts.workflow);
1125
+ if (!workflow) {
1126
+ const choices = workflows.map((w) => `${w.path} (${w.name})`).join(", ") || "none";
1127
+ throw new Error(
1128
+ `No dispatchable workflow "${opts.workflow}" in ${repo}. Available: ${choices}`
1129
+ );
1130
+ }
1131
+ const ref = opts.ref ?? workflow.refs[0];
1132
+ if (!ref) throw new Error("No --ref given and the workflow has no watched refs.");
1133
+ if (opts.providerRegion !== void 0 && opts.provider === void 0) {
1134
+ throw new Error("--provider-region requires --provider.");
1135
+ }
1136
+ const result = await c.post(
1137
+ "/api/v1/actions/dispatch",
1138
+ {
1139
+ workflowId: workflow.id,
1140
+ ref,
1141
+ inputs: parseInputs(opts.inputs),
1142
+ ...opts.provider !== void 0 && {
1143
+ provider: opts.provider,
1144
+ ...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
1145
+ }
1146
+ }
1147
+ );
1148
+ const org = await c.org();
1149
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
1150
+ output(opts, { ...result, url }, (r) => {
1151
+ console.log(`${r.created ? "dispatched" : "already running"} ${pc.cyan(r.runId)}`);
1152
+ console.log(`sha: ${r.headSha}`);
1153
+ console.log(`url: ${r.url}`);
1154
+ });
1155
+ } catch (e) {
1156
+ fail(e);
1157
+ }
1158
+ });
1159
+ common(
1160
+ 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")
1161
+ ).action(async (repo, opts) => {
1162
+ try {
1163
+ const { runs } = await client(opts).get(
1164
+ "/api/v1/actions/runs",
1165
+ { repo, status: opts.status, branch: opts.branch }
1166
+ );
1167
+ output(opts, runs, (rs) => {
1168
+ if (rs.length === 0) {
1169
+ console.log("No runs found.");
1170
+ return;
1171
+ }
1172
+ for (const run of rs) console.log(formatRunRow(run));
1173
+ });
1174
+ } catch (e) {
1175
+ fail(e);
1176
+ }
1177
+ });
1178
+ common(
1179
+ actions.command("history").description("Recent run history for a workflow \u2014 per-job/step failure rates (is this failure flaky?)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--branch <branch...>", "narrow the window to branches/refs").option("--job <job>", "narrow the rollup to one job name or workflow job id").option("--limit <n>", "runs in the window (default 10, max 50)")
1180
+ ).action(async (repo, opts) => {
1181
+ try {
1182
+ const c = client(opts);
1183
+ const { workflows } = await c.get(
1184
+ "/api/v1/actions/workflows",
1185
+ { repo }
1186
+ );
1187
+ const workflow = matchWorkflow(workflows, opts.workflow) ?? workflows.find((w) => w.path === opts.workflow);
1188
+ const workflowPath = workflow?.path ?? opts.workflow;
1189
+ const history = await c.get("/api/v1/actions/history", {
1190
+ repo,
1191
+ workflow: workflowPath,
1192
+ branch: opts.branch,
1193
+ job: opts.job,
1194
+ limit: opts.limit
1195
+ });
1196
+ output(opts, history, (h) => console.log(formatRunHistory(h)));
1197
+ } catch (e) {
1198
+ fail(e);
1199
+ }
1200
+ });
1201
+ common(
1202
+ actions.command("run").description("Show a run: jobs, provider:region placement, placement attempts").argument("<run-id>", "run ID")
1203
+ ).action(async (runId, opts) => {
1204
+ try {
1205
+ const c = client(opts);
1206
+ const run = await c.get(`/api/v1/actions/runs/${runId}`);
1207
+ const org = await c.org();
1208
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
1209
+ output(opts, { ...run, url }, (r) => console.log(formatRunDetail(r, r.url)));
1210
+ } catch (e) {
1211
+ fail(e);
1212
+ }
1213
+ });
1214
+ common(
1215
+ 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")
1216
+ ).action(async (runId, opts) => {
1217
+ try {
1218
+ const c = client(opts);
1219
+ let jobs = await listRunJobs(c, runId);
1220
+ if (opts.job) {
1221
+ const job = matchJob(jobs, opts.job);
1222
+ if (!job) {
1223
+ throw new Error(
1224
+ `No job "${opts.job}" in run ${runId}. Jobs: ${jobs.map((j) => j.name).join(", ") || "none"}`
1225
+ );
1226
+ }
1227
+ jobs = [job];
1228
+ }
1229
+ if (jobs.length === 0) {
1230
+ if (opts.json) output(opts, { segments: [] }, () => {
1231
+ });
1232
+ else console.error("Run has no jobs yet.");
1233
+ return;
1234
+ }
1235
+ if (!opts.follow) {
1236
+ for (const job of jobs) {
1237
+ const slice = await readWholeJobLog(c, job.id, opts.step);
1238
+ if (opts.json) {
1239
+ process.stdout.write(JSON.stringify({ jobId: job.id, name: job.name, slice }) + "\n");
1240
+ } else {
1241
+ if (jobs.length > 1) console.log(pc.bold(`\u2500\u2500 ${job.name} \u2500\u2500`));
1242
+ for (const seg of slice.segments) process.stdout.write(seg.text);
1243
+ }
1244
+ }
1245
+ return;
1246
+ }
1247
+ if (jobs.length === 1) {
1248
+ for await (const slice of followJobLog(c, jobs[0].id, opts.step)) {
1249
+ if (opts.json) {
1250
+ process.stdout.write(JSON.stringify(slice) + "\n");
1251
+ } else {
1252
+ for (const seg of slice.segments) process.stdout.write(seg.text);
1253
+ }
1254
+ }
1255
+ return;
1256
+ }
1257
+ for await (const { job, slice } of followRunLogs(c, runId, jobs, opts.step)) {
1258
+ if (opts.json) {
1259
+ process.stdout.write(JSON.stringify({ jobId: job.id, name: job.name, slice }) + "\n");
1260
+ } else {
1261
+ for (const seg of slice.segments) {
1262
+ const text2 = seg.text.replace(/\n/g, `
1263
+ [${job.name}] `);
1264
+ process.stdout.write(`[${job.name}] ${text2}`);
1265
+ }
1266
+ }
1267
+ }
1268
+ } catch (e) {
1269
+ fail(e);
1270
+ }
1271
+ });
1272
+ const providers = actions.command("providers").description("List the org's registered compute providers (regions, act/usable status)");
1273
+ common(providers).action(async (opts) => {
1274
+ try {
1275
+ const data = await client(opts).get("/api/v1/actions/providers");
1276
+ output(opts, data, (d) => {
1277
+ for (const p5 of d.providers) console.log(formatProviderRow(p5));
1278
+ });
1279
+ } catch (e) {
1280
+ fail(e);
1281
+ }
1282
+ });
1283
+ common(
1284
+ providers.command("configure").description("Save the org's credential for a provider (owner/admin API key)").argument("<provider>", "provider id (e.g. tensorlake, blaxel)").option("--key <key>", "provider API key (single-field credentials)").option("--field <pair...>", "credential fields as name=value (multi-field providers)").option("--verify", "run the placement probe after saving")
1285
+ ).action(async (provider, opts) => {
1286
+ try {
1287
+ const c = client(opts);
1288
+ let body;
1289
+ if (opts.field !== void 0) {
1290
+ if (opts.key !== void 0) throw new Error("Pass either --key or --field, not both.");
1291
+ body = { fields: parseInputs(opts.field) };
1292
+ } else if (opts.key !== void 0) {
1293
+ body = { key: opts.key };
1294
+ } else {
1295
+ throw new Error("Pass --key <key> or --field name=value.");
1296
+ }
1297
+ const saved = await c.put(
1298
+ `/api/v1/actions/providers/${provider}/key`,
1299
+ body
1300
+ );
1301
+ const verification = opts.verify ? await c.post(
1302
+ `/api/v1/actions/providers/${provider}/verify`
1303
+ ) : void 0;
1304
+ output(opts, verification ? { key: saved.key, verification } : saved, () => {
1305
+ console.log(`saved ${saved.key.provider} ${pc.dim(saved.key.keyHint)} ${saved.key.status}`);
1306
+ if (verification) console.log(formatVerifyResult(verification));
1307
+ });
1308
+ if (verification?.verified === false) process.exitCode = 1;
1309
+ } catch (e) {
1310
+ fail(e);
1311
+ }
1312
+ });
1313
+ common(
1314
+ providers.command("verify").description('Probe the stored provider key with a real sandbox ("Test" in Settings)').argument("<provider>", "provider id")
1315
+ ).action(async (provider, opts) => {
1316
+ try {
1317
+ const result = await client(opts).post(
1318
+ `/api/v1/actions/providers/${provider}/verify`
1319
+ );
1320
+ output(opts, result, (r) => console.log(formatVerifyResult(r)));
1321
+ if (result.verified === false) process.exitCode = 1;
1322
+ } catch (e) {
1323
+ fail(e);
1324
+ }
1325
+ });
1326
+ common(
1327
+ providers.command("remove").description("Delete the org's stored credential for a provider").argument("<provider>", "provider id")
1328
+ ).action(async (provider, opts) => {
1329
+ try {
1330
+ const result = await client(opts).del(
1331
+ `/api/v1/actions/providers/${provider}/key`
1332
+ );
1333
+ output(opts, result, (r) => {
1334
+ console.log(r.deleted ? `deleted ${r.provider}` : `no key stored for ${r.provider}`);
1335
+ });
1336
+ } catch (e) {
1337
+ fail(e);
1338
+ }
1339
+ });
1340
+ common(
1341
+ actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
1342
+ ).action(async (runId, opts) => {
1343
+ try {
1344
+ const result = await client(opts).post(
1345
+ `/api/v1/actions/runs/${runId}/cancel`
1346
+ );
1347
+ output(opts, result, (r) => {
1348
+ console.log(r.cancelled ? `Cancelled ${runId}` : `Run ${runId} was already finished`);
1349
+ });
1350
+ } catch (e) {
1351
+ fail(e);
1352
+ }
1353
+ });
1354
+ common(
1355
+ actions.command("rerun").description("Re-run a finished run at the same commit").argument("<run-id>", "run ID")
1356
+ ).action(async (runId, opts) => {
1357
+ try {
1358
+ const c = client(opts);
1359
+ const result = await c.post(
1360
+ `/api/v1/actions/runs/${runId}/rerun`
1361
+ );
1362
+ const org = await c.org();
1363
+ const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
1364
+ output(opts, { ...result, url }, (r) => {
1365
+ console.log(`${r.created ? "rerun dispatched" : "rerun already running"} ${pc.cyan(r.runId)}`);
1366
+ console.log(`url: ${r.url}`);
1367
+ });
1368
+ } catch (e) {
1369
+ fail(e);
1370
+ }
1371
+ });
1372
+ common(
1373
+ 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")
1374
+ ).action(async (runId, opts) => {
1375
+ try {
1376
+ const c = client(opts);
1377
+ let jobs = await listRunJobs(c, runId);
1378
+ if (opts.job) {
1379
+ const job = matchJob(jobs, opts.job);
1380
+ if (!job) throw new Error(`No job "${opts.job}" in run ${runId}.`);
1381
+ jobs = [job];
1382
+ }
1383
+ const entries = [];
1384
+ for (const job of jobs) {
1385
+ const artifacts = await c.get(
1386
+ `/api/v1/actions/jobs/${job.id}/artifacts`
1387
+ );
1388
+ entries.push({ job, artifacts });
1389
+ }
1390
+ if (opts.out) {
1391
+ const written = [];
1392
+ for (const { job, artifacts } of entries) {
1393
+ const jobDir = join(opts.out, sanitizePathPart(job.name || job.id));
1394
+ mkdirSync(jobDir, { recursive: true });
1395
+ for (const artifact of artifacts) {
1396
+ if (artifact.expired || artifact.skippedReason) continue;
1397
+ const bytes = await c.download(
1398
+ `/api/v1/actions/jobs/${job.id}/artifacts/${artifact.id}`
1399
+ );
1400
+ const file = join(jobDir, sanitizePathPart(artifact.fileName || artifact.name));
1401
+ writeFileSync(file, bytes);
1402
+ written.push({ jobId: job.id, id: artifact.id, file });
1403
+ }
1404
+ }
1405
+ output(opts, written, (ws) => {
1406
+ for (const w of ws) console.log(`wrote ${w.file}`);
1407
+ if (ws.length === 0) console.log("No downloadable artifacts.");
1408
+ });
1409
+ return;
1410
+ }
1411
+ output(opts, entries.map((e) => ({ jobId: e.job.id, job: e.job.name, artifacts: e.artifacts })), (es) => {
1412
+ let any = false;
1413
+ for (const { job, artifacts } of es) {
1414
+ for (const a of artifacts) {
1415
+ any = true;
1416
+ const flags = a.expired ? " expired" : a.skippedReason ? ` skipped: ${a.skippedReason}` : "";
1417
+ console.log(
1418
+ `${job} ${a.id} ${a.name} ${a.contentType} ${a.byteLength}B${flags}`
1419
+ );
1420
+ }
1421
+ }
1422
+ if (!any) console.log("No artifacts.");
1423
+ });
1424
+ } catch (e) {
1425
+ fail(e);
1426
+ }
1427
+ });
1428
+ }
1429
+
665
1430
  // src/index.ts
666
1431
  init_repl();
667
1432
 
@@ -674,7 +1439,7 @@ import * as fs2 from "fs";
674
1439
  import * as path3 from "path";
675
1440
  import * as os2 from "os";
676
1441
  import * as p from "@clack/prompts";
677
- import pc2 from "picocolors";
1442
+ import pc3 from "picocolors";
678
1443
  function getCredentialsDir() {
679
1444
  return path3.join(os2.homedir(), ".computesdk");
680
1445
  }
@@ -732,7 +1497,7 @@ function openBrowser(url) {
732
1497
  if (err) {
733
1498
  p.log.warn("Could not open browser automatically.");
734
1499
  p.log.info(`Open this URL manually:
735
- ${pc2.cyan(url)}`);
1500
+ ${pc3.cyan(url)}`);
736
1501
  }
737
1502
  });
738
1503
  }
@@ -803,7 +1568,7 @@ async function resolveApiKey(options) {
803
1568
  const stored = loadStoredCredentials();
804
1569
  if (stored) {
805
1570
  p.log.info(
806
- `Using stored credentials from ${pc2.dim("~/.computesdk/credentials.json")}`
1571
+ `Using stored credentials from ${pc3.dim("~/.computesdk/credentials.json")}`
807
1572
  );
808
1573
  return stored;
809
1574
  }
@@ -817,14 +1582,14 @@ async function resolveApiKey(options) {
817
1582
  try {
818
1583
  storeCredentials(apiKey);
819
1584
  p.log.success(
820
- `Credentials saved to ${pc2.dim("~/.computesdk/credentials.json")}`
1585
+ `Credentials saved to ${pc3.dim("~/.computesdk/credentials.json")}`
821
1586
  );
822
1587
  } catch {
823
1588
  p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
824
1589
  }
825
1590
  return apiKey;
826
1591
  } catch (error) {
827
- spinner4.stop(pc2.red("Authentication failed"));
1592
+ spinner4.stop(pc3.red("Authentication failed"));
828
1593
  throw error;
829
1594
  }
830
1595
  }
@@ -832,7 +1597,7 @@ async function resolveApiKey(options) {
832
1597
  // src/setup.ts
833
1598
  init_esm_shims();
834
1599
  import * as p2 from "@clack/prompts";
835
- import pc3 from "picocolors";
1600
+ import pc4 from "picocolors";
836
1601
  import { compute as compute2 } from "computesdk";
837
1602
  async function ensureAuth(options) {
838
1603
  try {
@@ -840,8 +1605,8 @@ async function ensureAuth(options) {
840
1605
  process.env.COMPUTESDK_API_KEY = apiKey;
841
1606
  } catch (error) {
842
1607
  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"));
1608
+ p2.log.info(`Get your API key at ${pc4.cyan("https://console.computesdk.com")}`);
1609
+ p2.outro(pc4.red("Setup incomplete"));
845
1610
  process.exit(1);
846
1611
  }
847
1612
  }
@@ -870,7 +1635,7 @@ async function resolveProvider(providerFlag, options) {
870
1635
  if (available.length === 0) {
871
1636
  p2.log.error("No provider credentials detected");
872
1637
  p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
873
- p2.outro(pc3.red("Setup incomplete"));
1638
+ p2.outro(pc4.red("Setup incomplete"));
874
1639
  process.exit(1);
875
1640
  }
876
1641
  if (available.length === 1) {
@@ -905,7 +1670,7 @@ init_workspace_cli();
905
1670
  var __filename2 = fileURLToPath2(import.meta.url);
906
1671
  var __dirname2 = dirname2(__filename2);
907
1672
  var packageJson = JSON.parse(
908
- readFileSync4(join4(__dirname2, "..", "package.json"), "utf-8")
1673
+ readFileSync4(join5(__dirname2, "..", "package.json"), "utf-8")
909
1674
  );
910
1675
  var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
911
1676
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
@@ -914,24 +1679,24 @@ program.name("compute").description("Cloud-native workspace management").version
914
1679
  program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
915
1680
  if (opts.logout) {
916
1681
  console.log();
917
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1682
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
918
1683
  clearStoredCredentials();
919
1684
  p4.log.success("Logged out. Stored credentials removed.");
920
- p4.outro(pc5.green("Done!"));
1685
+ p4.outro(pc6.green("Done!"));
921
1686
  process.exit(0);
922
1687
  }
923
1688
  if (opts.login) {
924
1689
  console.log();
925
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1690
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
926
1691
  await ensureAuth({ forceLogin: true });
927
- p4.outro(pc5.green("Authenticated!"));
1692
+ p4.outro(pc6.green("Authenticated!"));
928
1693
  process.exit(0);
929
1694
  }
930
1695
  program.help();
931
1696
  });
932
1697
  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
1698
  console.log();
934
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1699
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
935
1700
  await ensureAuth();
936
1701
  if (!repo) {
937
1702
  const state = loadWorkspaceState();
@@ -952,7 +1717,7 @@ program.command("create").description("Create a new workspace (same as create-co
952
1717
  message: "Select workspace to attach (or create new)",
953
1718
  options: [
954
1719
  ...choices,
955
- { value: "__new__", label: pc5.gray("+ Create new workspace") }
1720
+ { value: "__new__", label: pc6.gray("+ Create new workspace") }
956
1721
  ]
957
1722
  });
958
1723
  if (p4.isCancel(selected)) {
@@ -993,15 +1758,15 @@ program.command("create").description("Create a new workspace (same as create-co
993
1758
  branch: options.branch,
994
1759
  provider
995
1760
  });
996
- spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
1761
+ spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
997
1762
  if (options.attach !== false) {
998
1763
  console.log();
999
1764
  await workspaceAttach(workspace.id);
1000
1765
  } else {
1001
- p4.outro(pc5.green("Workspace created!"));
1766
+ p4.outro(pc6.green("Workspace created!"));
1002
1767
  }
1003
1768
  } catch (error) {
1004
- spinner4.stop(pc5.red("Failed to create workspace"));
1769
+ spinner4.stop(pc6.red("Failed to create workspace"));
1005
1770
  p4.log.error(error.message);
1006
1771
  process.exit(1);
1007
1772
  }
@@ -1010,7 +1775,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
1010
1775
  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
1776
  if (!opts.silent) {
1012
1777
  console.log();
1013
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1778
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1014
1779
  }
1015
1780
  await ensureAuth();
1016
1781
  const provider = await resolveProvider(opts.provider);
@@ -1022,7 +1787,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1022
1787
  const sandbox = await compute4.sandbox.create(
1023
1788
  buildCreateOptions(opts)
1024
1789
  );
1025
- spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
1790
+ spinner4.stop(`Sandbox ready: ${pc6.cyan(sandbox.sandboxId)}`);
1026
1791
  const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
1027
1792
  await saveSandbox2({
1028
1793
  id: sandbox.sandboxId,
@@ -1035,7 +1800,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1035
1800
  await startREPL(sandbox, provider);
1036
1801
  }
1037
1802
  } catch (error) {
1038
- spinner4.stop(pc5.red("Failed to create sandbox"));
1803
+ spinner4.stop(pc6.red("Failed to create sandbox"));
1039
1804
  p4.log.error(error.message);
1040
1805
  process.exit(1);
1041
1806
  }
@@ -1064,14 +1829,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
1064
1829
  });
1065
1830
  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
1831
  console.log();
1067
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1832
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1068
1833
  const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
1069
1834
  const state = loadState2();
1070
1835
  if (!sandboxIds || sandboxIds.length === 0) {
1071
1836
  const workspaces = Object.values(state.workspaces);
1072
1837
  if (workspaces.length === 0) {
1073
1838
  p4.log.info("No sandboxes to destroy.");
1074
- p4.outro(pc5.green("Done!"));
1839
+ p4.outro(pc6.green("Done!"));
1075
1840
  return;
1076
1841
  }
1077
1842
  const choices = workspaces.map((ws) => {
@@ -1079,7 +1844,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1079
1844
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1080
1845
  );
1081
1846
  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");
1847
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1083
1848
  return {
1084
1849
  value: ws.sandboxId,
1085
1850
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1107,7 +1872,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1107
1872
  for (const result of results) {
1108
1873
  if (result.status === "fulfilled") {
1109
1874
  const sandboxId = result.value;
1110
- console.log(pc5.green(`Destroyed ${sandboxId}`));
1875
+ console.log(pc6.green(`Destroyed ${sandboxId}`));
1111
1876
  const workspaceId = Object.keys(state.workspaces).find(
1112
1877
  (key) => state.workspaces[key].sandboxId === sandboxId
1113
1878
  );
@@ -1115,19 +1880,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
1115
1880
  delete state.workspaces[workspaceId];
1116
1881
  }
1117
1882
  } else {
1118
- console.error(pc5.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
1883
+ console.error(pc6.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
1119
1884
  }
1120
1885
  }
1121
1886
  saveState2(state);
1122
1887
  });
1123
1888
  sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
1124
1889
  console.log();
1125
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1890
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1126
1891
  await sandboxList();
1127
1892
  });
1128
1893
  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
1894
  console.log();
1130
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1895
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1131
1896
  await ensureAuth();
1132
1897
  const provider = await resolveProvider(opts.provider);
1133
1898
  configureCompute(provider);
@@ -1138,7 +1903,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1138
1903
  if (workspaces.length === 0) {
1139
1904
  p4.log.info("No sandboxes found.");
1140
1905
  p4.log.info("Create one with: compute sandbox create");
1141
- p4.outro(pc5.green("Done!"));
1906
+ p4.outro(pc6.green("Done!"));
1142
1907
  return;
1143
1908
  }
1144
1909
  const choices = workspaces.map((ws) => {
@@ -1146,7 +1911,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1146
1911
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1147
1912
  );
1148
1913
  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");
1914
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1150
1915
  return {
1151
1916
  value: ws.sandboxId,
1152
1917
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1166,7 +1931,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
1166
1931
  spinner4.start("Connecting...");
1167
1932
  const sandbox = await compute4.sandbox.getById(sandboxId);
1168
1933
  if (!sandbox) {
1169
- spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
1934
+ spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
1170
1935
  process.exit(1);
1171
1936
  }
1172
1937
  spinner4.stop("");
@@ -1178,7 +1943,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
1178
1943
  configureCompute(provider);
1179
1944
  const sandbox = await compute4.sandbox.getById(sandboxId);
1180
1945
  if (!sandbox) {
1181
- console.error(pc5.red(`Sandbox not found: ${sandboxId}`));
1946
+ console.error(pc6.red(`Sandbox not found: ${sandboxId}`));
1182
1947
  process.exit(1);
1183
1948
  }
1184
1949
  const cmd = buildExecCommand(command, opts);
@@ -1193,7 +1958,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
1193
1958
  });
1194
1959
  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
1960
  console.log();
1196
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
1961
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1197
1962
  await ensureAuth();
1198
1963
  const provider = await resolveProvider(opts.provider);
1199
1964
  configureCompute(provider);
@@ -1204,7 +1969,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1204
1969
  if (workspaces.length === 0) {
1205
1970
  p4.log.info("No sandboxes found.");
1206
1971
  p4.log.info("Create one with: compute sandbox create");
1207
- p4.outro(pc5.green("Done!"));
1972
+ p4.outro(pc6.green("Done!"));
1208
1973
  return;
1209
1974
  }
1210
1975
  const choices = workspaces.map((ws) => {
@@ -1212,7 +1977,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1212
1977
  (Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
1213
1978
  );
1214
1979
  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");
1980
+ const status = age > 7 ? pc6.red("stale") : age > 1 ? pc6.yellow("idle") : pc6.green("active");
1216
1981
  return {
1217
1982
  value: ws.sandboxId,
1218
1983
  label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
@@ -1232,7 +1997,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1232
1997
  spinner4.start("Connecting...");
1233
1998
  const sandbox = await compute4.sandbox.getById(sandboxId);
1234
1999
  if (!sandbox) {
1235
- spinner4.stop(pc5.red(`Sandbox not found: ${sandboxId}`));
2000
+ spinner4.stop(pc6.red(`Sandbox not found: ${sandboxId}`));
1236
2001
  process.exit(1);
1237
2002
  }
1238
2003
  spinner4.stop("");
@@ -1241,11 +2006,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
1241
2006
  var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
1242
2007
  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
2008
  console.log();
1244
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2009
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1245
2010
  await ensureAuth();
1246
2011
  if (!repo) {
1247
2012
  p4.log.info("Usage: compute workspace create owner/repo --branch main");
1248
- p4.outro(pc5.green("Done!"));
2013
+ p4.outro(pc6.green("Done!"));
1249
2014
  return;
1250
2015
  }
1251
2016
  const provider = await resolveProvider(options.provider, { interactive: true });
@@ -1258,33 +2023,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
1258
2023
  branch: options.branch,
1259
2024
  provider
1260
2025
  });
1261
- spinner4.stop(`Workspace ready: ${pc5.cyan(workspace.id)}`);
2026
+ spinner4.stop(`Workspace ready: ${pc6.cyan(workspace.id)}`);
1262
2027
  if (options.attach !== false) {
1263
2028
  console.log();
1264
2029
  await workspaceAttach(workspace.id);
1265
2030
  } else {
1266
- p4.outro(pc5.green("Workspace created!"));
2031
+ p4.outro(pc6.green("Workspace created!"));
1267
2032
  }
1268
2033
  } catch (error) {
1269
- spinner4.stop(pc5.red("Failed to create workspace"));
2034
+ spinner4.stop(pc6.red("Failed to create workspace"));
1270
2035
  p4.log.error(error.message);
1271
2036
  process.exit(1);
1272
2037
  }
1273
2038
  });
1274
2039
  workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
1275
2040
  console.log();
1276
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2041
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1277
2042
  await workspaceList();
1278
2043
  });
1279
2044
  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
2045
  console.log();
1281
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2046
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1282
2047
  if (!id) {
1283
2048
  const state = loadWorkspaceState();
1284
2049
  const workspaceIds = Object.keys(state.workspaces);
1285
2050
  if (workspaceIds.length === 0) {
1286
2051
  p4.log.info("No workspaces to destroy.");
1287
- p4.outro(pc5.green("Done!"));
2052
+ p4.outro(pc6.green("Done!"));
1288
2053
  return;
1289
2054
  }
1290
2055
  const choices = workspaceIds.map((key) => {
@@ -1295,7 +2060,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1295
2060
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1296
2061
  return {
1297
2062
  value: key,
1298
- label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
2063
+ label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
1299
2064
  };
1300
2065
  });
1301
2066
  const selected = await p4.multiselect({
@@ -1315,14 +2080,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
1315
2080
  });
1316
2081
  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
2082
  console.log();
1318
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2083
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1319
2084
  if (!id) {
1320
2085
  const state = loadWorkspaceState();
1321
2086
  const workspaceIds = Object.keys(state.workspaces);
1322
2087
  if (workspaceIds.length === 0) {
1323
2088
  p4.log.info("No workspaces found.");
1324
2089
  p4.log.info("Create one with: compute create owner/repo");
1325
- p4.outro(pc5.green("Done!"));
2090
+ p4.outro(pc6.green("Done!"));
1326
2091
  return;
1327
2092
  }
1328
2093
  const choices = workspaceIds.map((key) => {
@@ -1333,7 +2098,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
1333
2098
  const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
1334
2099
  return {
1335
2100
  value: key,
1336
- label: `${pc5.cyan(key)} (${ws.provider}, last used ${ageStr})`
2101
+ label: `${pc6.cyan(key)} (${ws.provider}, last used ${ageStr})`
1337
2102
  };
1338
2103
  });
1339
2104
  const selected = await p4.select({
@@ -1358,7 +2123,7 @@ program.command("run").description("Create a sandbox, run a command, then option
1358
2123
  buildCreateOptions(opts)
1359
2124
  );
1360
2125
  } catch (error) {
1361
- console.error(pc5.red(`Failed to create sandbox: ${error.message}`));
2126
+ console.error(pc6.red(`Failed to create sandbox: ${error.message}`));
1362
2127
  process.exit(1);
1363
2128
  }
1364
2129
  try {
@@ -1381,35 +2146,38 @@ program.command("run").description("Create a sandbox, run a command, then option
1381
2146
  } catch {
1382
2147
  }
1383
2148
  }
1384
- console.error(pc5.red(error.message));
2149
+ console.error(pc6.red(error.message));
1385
2150
  process.exit(1);
1386
2151
  }
1387
2152
  });
1388
2153
  program.command("providers").description("List all providers and their configuration status").action(async () => {
1389
2154
  console.log();
1390
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2155
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1391
2156
  const statuses = getProviderStatus();
1392
2157
  console.log();
1393
2158
  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(", ")}`);
2159
+ const icon = status.ready ? pc6.green("\u25CF") : pc6.gray("\u25CB");
2160
+ const name = status.ready ? pc6.white(status.name) : pc6.gray(status.name);
2161
+ const detail = status.ready ? pc6.green("ready") : pc6.gray(`missing: ${status.missing.join(", ")}`);
1397
2162
  console.log(` ${icon} ${name} ${detail}`);
1398
2163
  }
1399
2164
  console.log();
1400
2165
  });
1401
2166
  program.command("login").description("Authenticate with ComputeSDK").action(async () => {
1402
2167
  console.log();
1403
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2168
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1404
2169
  await ensureAuth({ forceLogin: true });
1405
- p4.outro(pc5.green("Authenticated!"));
2170
+ p4.outro(pc6.green("Authenticated!"));
1406
2171
  });
1407
2172
  program.command("logout").description("Clear stored credentials").action(() => {
1408
2173
  console.log();
1409
- p4.intro(pc5.cyan(`@computesdk/cli v${VERSION}`));
2174
+ p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
1410
2175
  clearStoredCredentials();
1411
2176
  p4.log.success("Logged out. Stored credentials removed.");
1412
- p4.outro(pc5.green("Done!"));
2177
+ p4.outro(pc6.green("Done!"));
2178
+ });
2179
+ registerActionsCommands(program);
2180
+ program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
1413
2181
  });
1414
2182
  function buildCreateOptions(opts) {
1415
2183
  const options = {};
@@ -1477,8 +2245,16 @@ function parseDuration(input) {
1477
2245
  }
1478
2246
  return total;
1479
2247
  }
1480
- program.parseAsync(process.argv).catch((error) => {
1481
- console.error(pc5.red(`
2248
+ async function main() {
2249
+ if (process.argv[2] === "bench" || process.argv[2] === "benchmark") {
2250
+ const { run: benchRun } = await import("@benchsdk/runner");
2251
+ await benchRun(process.argv.slice(3));
2252
+ return;
2253
+ }
2254
+ await program.parseAsync(process.argv);
2255
+ }
2256
+ main().catch((error) => {
2257
+ console.error(pc6.red(`
1482
2258
  Error: ${error.message}`));
1483
2259
  process.exit(1);
1484
2260
  });