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