@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 +952 -176
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
|
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
|
|
38
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
133
|
-
console.log(` ID: ${
|
|
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(
|
|
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(
|
|
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(
|
|
149
|
+
console.log(pc2.red("Invalid port number"));
|
|
150
150
|
} else {
|
|
151
151
|
const url = await activeSandbox.getUrl({ port });
|
|
152
|
-
console.log(
|
|
152
|
+
console.log(pc2.cyan(url));
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
case "new": {
|
|
158
|
-
console.log(
|
|
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: ${
|
|
164
|
-
console.log(` ${
|
|
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(
|
|
170
|
-
console.log(
|
|
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(
|
|
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(
|
|
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 ${
|
|
187
|
-
console.log(
|
|
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(
|
|
197
|
+
console.log(pc2.yellow(" Ambiguous ID. Matches:"));
|
|
198
198
|
for (const id of matches) {
|
|
199
|
-
console.log(` ${
|
|
199
|
+
console.log(` ${pc2.cyan(id)}`);
|
|
200
200
|
}
|
|
201
201
|
} else {
|
|
202
|
-
console.log(
|
|
203
|
-
console.log(
|
|
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(
|
|
212
|
+
console.log(pc2.bold(" Sandboxes"));
|
|
213
213
|
for (const id of ctx.sandboxes.keys()) {
|
|
214
|
-
const marker = id === ctx.activeSandboxId ?
|
|
215
|
-
console.log(` ${marker} ${
|
|
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(
|
|
224
|
-
console.log(
|
|
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 ${
|
|
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 ${
|
|
238
|
+
console.log(` Switched to ${pc2.cyan(matches[0])}`);
|
|
239
239
|
} else if (matches.length > 1) {
|
|
240
|
-
console.log(
|
|
240
|
+
console.log(pc2.yellow(" Ambiguous ID. Matches:"));
|
|
241
241
|
for (const id of matches) {
|
|
242
|
-
console.log(` ${
|
|
242
|
+
console.log(` ${pc2.cyan(id)}`);
|
|
243
243
|
}
|
|
244
244
|
} else {
|
|
245
|
-
console.log(
|
|
246
|
-
console.log(
|
|
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(
|
|
256
|
-
console.log(
|
|
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(
|
|
262
|
+
console.log(pc2.gray(` Destroying ${sandboxId}...`));
|
|
263
263
|
await target.destroy();
|
|
264
264
|
ctx.sandboxes.delete(sandboxId);
|
|
265
|
-
console.log(` Destroyed ${
|
|
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 ${
|
|
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(
|
|
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(
|
|
280
|
-
console.log(
|
|
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(
|
|
283
|
-
console.log(
|
|
284
|
-
console.log(
|
|
282
|
+
console.log(pc2.white(" ls -la"));
|
|
283
|
+
console.log(pc2.white(" cat /etc/os-release"));
|
|
284
|
+
console.log(pc2.white(" npm install lodash"));
|
|
285
285
|
console.log();
|
|
286
|
-
console.log(
|
|
286
|
+
console.log(pc2.bold(" Sandbox Commands") + pc2.gray(" (prefix with /)"));
|
|
287
287
|
console.log();
|
|
288
|
-
console.log(
|
|
289
|
-
console.log(
|
|
290
|
-
console.log(
|
|
291
|
-
console.log(
|
|
292
|
-
console.log(
|
|
288
|
+
console.log(pc2.cyan(" /new") + pc2.gray(" Create a new sandbox and switch to it"));
|
|
289
|
+
console.log(pc2.cyan(" /list") + pc2.gray(" List all sandboxes"));
|
|
290
|
+
console.log(pc2.cyan(" /switch <id>") + pc2.gray(" Switch active sandbox"));
|
|
291
|
+
console.log(pc2.cyan(" /destroy <id>") + pc2.gray(" Destroy a sandbox by ID"));
|
|
292
|
+
console.log(pc2.cyan(" /destroy --all") + pc2.gray(" Destroy all sandboxes"));
|
|
293
293
|
console.log();
|
|
294
|
-
console.log(
|
|
294
|
+
console.log(pc2.bold(" Other Commands"));
|
|
295
295
|
console.log();
|
|
296
|
-
console.log(
|
|
297
|
-
console.log(
|
|
298
|
-
console.log(
|
|
299
|
-
console.log(
|
|
296
|
+
console.log(pc2.cyan(" /info") + pc2.gray(" Show active sandbox info"));
|
|
297
|
+
console.log(pc2.cyan(" /url <port>") + pc2.gray(" Get public URL for port"));
|
|
298
|
+
console.log(pc2.cyan(" /help") + pc2.gray(" Show this help"));
|
|
299
|
+
console.log(pc2.cyan(" /exit") + pc2.gray(" Exit the REPL"));
|
|
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
|
|
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: ${
|
|
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: ${
|
|
397
|
+
spinner4.stop(`Sandbox ready: ${pc5.cyan(sandbox.sandboxId)}`);
|
|
398
398
|
} catch (error) {
|
|
399
|
-
spinner4.stop(
|
|
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
|
|
417
|
-
const jsonMatch =
|
|
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: ${
|
|
440
|
-
p3.log.info(`Sandbox: ${
|
|
441
|
-
p3.log.info(`Worktree: ${
|
|
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(
|
|
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: ${
|
|
470
|
-
p3.log.info(`Worktree: ${
|
|
471
|
-
console.log(
|
|
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(
|
|
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 ?
|
|
494
|
-
console.log(` ${status} ${
|
|
495
|
-
console.log(` Workspace: ${
|
|
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: ${
|
|
497
|
+
console.log(` Last active: ${pc5.gray(ageStr)}`);
|
|
498
498
|
console.log();
|
|
499
499
|
}
|
|
500
|
-
console.log(
|
|
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(
|
|
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(` ${
|
|
517
|
-
console.log(` Sandbox: ${
|
|
518
|
-
console.log(` Path: ${
|
|
519
|
-
console.log(` Last used: ${
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
"
|
|
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 (
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
657
|
-
|
|
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
|
|
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
|
-
${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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
|
|
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 ${
|
|
844
|
-
p2.outro(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1690
|
+
p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
|
|
926
1691
|
await ensureAuth({ forceLogin: true });
|
|
927
|
-
p4.outro(
|
|
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(
|
|
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:
|
|
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: ${
|
|
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(
|
|
1766
|
+
p4.outro(pc6.green("Workspace created!"));
|
|
1002
1767
|
}
|
|
1003
1768
|
} catch (error) {
|
|
1004
|
-
spinner4.stop(
|
|
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(
|
|
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: ${
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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: ${
|
|
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(
|
|
2031
|
+
p4.outro(pc6.green("Workspace created!"));
|
|
1267
2032
|
}
|
|
1268
2033
|
} catch (error) {
|
|
1269
|
-
spinner4.stop(
|
|
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(
|
|
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(
|
|
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(
|
|
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: `${
|
|
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(
|
|
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(
|
|
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: `${
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
1395
|
-
const name = status.ready ?
|
|
1396
|
-
const detail = status.ready ?
|
|
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(
|
|
2168
|
+
p4.intro(pc6.cyan(`@computesdk/cli v${VERSION}`));
|
|
1404
2169
|
await ensureAuth({ forceLogin: true });
|
|
1405
|
-
p4.outro(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1481
|
-
|
|
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
|
});
|