@computesdk/cli 1.0.16 → 2.1.0
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/README.md +60 -0
- package/dist/index.d.ts +1 -19
- package/dist/index.js +199 -1493
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,599 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
-
};
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.3_typescript@5.8.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js
|
|
13
|
-
import path from "path";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
var init_esm_shims = __esm({
|
|
16
|
-
"../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.3_typescript@5.8.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
-
"use strict";
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// src/repl.ts
|
|
22
|
-
import * as readline from "readline";
|
|
23
|
-
import * as path2 from "path";
|
|
24
|
-
import * as os from "os";
|
|
25
|
-
import * as fs from "fs";
|
|
26
|
-
import pc4 from "picocolors";
|
|
27
|
-
import { compute } from "computesdk";
|
|
28
|
-
async function startREPL(sandbox, provider) {
|
|
29
|
-
void provider;
|
|
30
|
-
const ctx = {
|
|
31
|
-
sandboxes: /* @__PURE__ */ new Map([[sandbox.sandboxId, sandbox]]),
|
|
32
|
-
activeSandboxId: sandbox.sandboxId
|
|
33
|
-
};
|
|
34
|
-
await runSession(ctx);
|
|
35
|
-
}
|
|
36
|
-
function buildPrompt(sandboxId) {
|
|
37
|
-
if (!sandboxId) return pc4.gray("[no sandbox] ") + pc4.bold("$ ");
|
|
38
|
-
return pc4.cyan(`[${sandboxId}] `) + pc4.bold("$ ");
|
|
39
|
-
}
|
|
40
|
-
function runSession(ctx) {
|
|
41
|
-
const rl = readline.createInterface({
|
|
42
|
-
input: process.stdin,
|
|
43
|
-
output: process.stdout,
|
|
44
|
-
prompt: buildPrompt(ctx.activeSandboxId),
|
|
45
|
-
terminal: true,
|
|
46
|
-
historySize: 1e3
|
|
47
|
-
});
|
|
48
|
-
loadHistory(rl);
|
|
49
|
-
return new Promise((resolve) => {
|
|
50
|
-
let resolved = false;
|
|
51
|
-
const done = (result) => {
|
|
52
|
-
if (resolved) return;
|
|
53
|
-
resolved = true;
|
|
54
|
-
rl.close();
|
|
55
|
-
resolve(result);
|
|
56
|
-
};
|
|
57
|
-
rl.on("SIGINT", () => {
|
|
58
|
-
console.log();
|
|
59
|
-
rl.prompt();
|
|
60
|
-
});
|
|
61
|
-
rl.on("close", () => {
|
|
62
|
-
done("exit");
|
|
63
|
-
});
|
|
64
|
-
rl.on("line", async (line) => {
|
|
65
|
-
const trimmed = line.trim();
|
|
66
|
-
if (!trimmed) {
|
|
67
|
-
rl.prompt();
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (trimmed === ".exit") {
|
|
71
|
-
done("exit");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
saveToHistory(trimmed);
|
|
75
|
-
try {
|
|
76
|
-
if (trimmed.startsWith("/")) {
|
|
77
|
-
const result = await handleSpecialCommand(ctx, trimmed, rl);
|
|
78
|
-
if (result === "exit") {
|
|
79
|
-
done(result);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
} else if (ctx.sandboxes.size === 0) {
|
|
83
|
-
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
84
|
-
} else {
|
|
85
|
-
const sandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
86
|
-
await executeCommand(sandbox, trimmed);
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.log(pc4.red(`\u2717 ${error.message}`));
|
|
90
|
-
}
|
|
91
|
-
if (!resolved) {
|
|
92
|
-
rl.prompt();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
rl.prompt();
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
async function executeCommand(sandbox, command) {
|
|
99
|
-
const result = await sandbox.runCommand(command);
|
|
100
|
-
if (result.stdout) {
|
|
101
|
-
process.stdout.write(result.stdout);
|
|
102
|
-
if (!result.stdout.endsWith("\n")) {
|
|
103
|
-
console.log();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (result.stderr) {
|
|
107
|
-
process.stderr.write(pc4.red(result.stderr));
|
|
108
|
-
if (!result.stderr.endsWith("\n")) {
|
|
109
|
-
console.log();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (result.exitCode !== 0) {
|
|
113
|
-
console.log(pc4.gray(`exit code: ${result.exitCode}`));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
async function handleSpecialCommand(ctx, input, rl) {
|
|
117
|
-
const [cmd, ...args] = input.slice(1).split(/\s+/);
|
|
118
|
-
switch (cmd.toLowerCase()) {
|
|
119
|
-
case "help":
|
|
120
|
-
case "h":
|
|
121
|
-
case "?":
|
|
122
|
-
showHelp();
|
|
123
|
-
return null;
|
|
124
|
-
case "info":
|
|
125
|
-
case "i": {
|
|
126
|
-
if (ctx.sandboxes.size === 0) {
|
|
127
|
-
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
131
|
-
console.log();
|
|
132
|
-
console.log(pc4.bold(" Sandbox Info"));
|
|
133
|
-
console.log(` ID: ${pc4.cyan(activeSandbox.sandboxId)}`);
|
|
134
|
-
console.log();
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
case "url":
|
|
138
|
-
case "u": {
|
|
139
|
-
if (ctx.sandboxes.size === 0) {
|
|
140
|
-
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
144
|
-
if (args.length === 0) {
|
|
145
|
-
console.log(pc4.gray("Usage: /url <port>"));
|
|
146
|
-
} else {
|
|
147
|
-
const port = parseInt(args[0], 10);
|
|
148
|
-
if (isNaN(port)) {
|
|
149
|
-
console.log(pc4.red("Invalid port number"));
|
|
150
|
-
} else {
|
|
151
|
-
const url = await activeSandbox.getUrl({ port });
|
|
152
|
-
console.log(pc4.cyan(url));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
case "new": {
|
|
158
|
-
console.log(pc4.gray(" Creating new sandbox..."));
|
|
159
|
-
const newSandbox = await compute.sandbox.create();
|
|
160
|
-
ctx.sandboxes.set(newSandbox.sandboxId, newSandbox);
|
|
161
|
-
ctx.activeSandboxId = newSandbox.sandboxId;
|
|
162
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
163
|
-
console.log(` Sandbox ready: ${pc4.cyan(newSandbox.sandboxId)}`);
|
|
164
|
-
console.log(` ${pc4.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
case "destroy": {
|
|
168
|
-
if (args.length === 0) {
|
|
169
|
-
console.log(pc4.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
|
|
170
|
-
console.log(pc4.gray("Use /list to see all sandboxes"));
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if (args[0] === "--all") {
|
|
174
|
-
if (ctx.sandboxes.size === 0) {
|
|
175
|
-
console.log(pc4.yellow(" No sandboxes to destroy."));
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
const count = ctx.sandboxes.size;
|
|
179
|
-
console.log(pc4.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
|
|
180
|
-
await Promise.all(
|
|
181
|
-
Array.from(ctx.sandboxes.values()).map((s) => s.destroy())
|
|
182
|
-
);
|
|
183
|
-
ctx.sandboxes.clear();
|
|
184
|
-
ctx.activeSandboxId = "";
|
|
185
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
186
|
-
console.log(` Destroyed ${pc4.cyan(String(count))} sandbox${count > 1 ? "es" : ""}`);
|
|
187
|
-
console.log(pc4.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const targetId = args[0];
|
|
191
|
-
const target = ctx.sandboxes.get(targetId);
|
|
192
|
-
if (!target) {
|
|
193
|
-
const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(targetId));
|
|
194
|
-
if (matches.length === 1) {
|
|
195
|
-
return destroySandbox(ctx, matches[0], rl);
|
|
196
|
-
} else if (matches.length > 1) {
|
|
197
|
-
console.log(pc4.yellow(" Ambiguous ID. Matches:"));
|
|
198
|
-
for (const id of matches) {
|
|
199
|
-
console.log(` ${pc4.cyan(id)}`);
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
console.log(pc4.red(` Sandbox not found: ${targetId}`));
|
|
203
|
-
console.log(pc4.gray(" Use /list to see all sandboxes"));
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
return destroySandbox(ctx, targetId, rl);
|
|
208
|
-
}
|
|
209
|
-
case "list":
|
|
210
|
-
case "ls": {
|
|
211
|
-
console.log();
|
|
212
|
-
console.log(pc4.bold(" Sandboxes"));
|
|
213
|
-
for (const id of ctx.sandboxes.keys()) {
|
|
214
|
-
const marker = id === ctx.activeSandboxId ? pc4.green(" \u25CF") : " ";
|
|
215
|
-
console.log(` ${marker} ${pc4.cyan(id)}`);
|
|
216
|
-
}
|
|
217
|
-
console.log();
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
case "switch":
|
|
221
|
-
case "sw": {
|
|
222
|
-
if (args.length === 0) {
|
|
223
|
-
console.log(pc4.gray("Usage: /switch <sandbox-id>"));
|
|
224
|
-
console.log(pc4.gray("Use /list to see all sandboxes"));
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
const switchId = args[0];
|
|
228
|
-
if (ctx.sandboxes.has(switchId)) {
|
|
229
|
-
ctx.activeSandboxId = switchId;
|
|
230
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
231
|
-
console.log(` Switched to ${pc4.cyan(switchId)}`);
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(switchId));
|
|
235
|
-
if (matches.length === 1) {
|
|
236
|
-
ctx.activeSandboxId = matches[0];
|
|
237
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
238
|
-
console.log(` Switched to ${pc4.cyan(matches[0])}`);
|
|
239
|
-
} else if (matches.length > 1) {
|
|
240
|
-
console.log(pc4.yellow(" Ambiguous ID. Matches:"));
|
|
241
|
-
for (const id of matches) {
|
|
242
|
-
console.log(` ${pc4.cyan(id)}`);
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
console.log(pc4.red(` Sandbox not found: ${switchId}`));
|
|
246
|
-
console.log(pc4.gray(" Use /list to see all sandboxes"));
|
|
247
|
-
}
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
case "exit":
|
|
251
|
-
case "quit":
|
|
252
|
-
case "q":
|
|
253
|
-
return "exit";
|
|
254
|
-
default:
|
|
255
|
-
console.log(pc4.yellow(`Unknown command: /${cmd}`));
|
|
256
|
-
console.log(pc4.gray("Type /help for available commands"));
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
async function destroySandbox(ctx, sandboxId, rl) {
|
|
261
|
-
const target = ctx.sandboxes.get(sandboxId);
|
|
262
|
-
console.log(pc4.gray(` Destroying ${sandboxId}...`));
|
|
263
|
-
await target.destroy();
|
|
264
|
-
ctx.sandboxes.delete(sandboxId);
|
|
265
|
-
console.log(` Destroyed ${pc4.cyan(sandboxId)}`);
|
|
266
|
-
if (ctx.activeSandboxId === sandboxId && ctx.sandboxes.size > 0) {
|
|
267
|
-
ctx.activeSandboxId = ctx.sandboxes.keys().next().value;
|
|
268
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
269
|
-
console.log(` Switched to ${pc4.cyan(ctx.activeSandboxId)}`);
|
|
270
|
-
} else if (ctx.sandboxes.size === 0) {
|
|
271
|
-
ctx.activeSandboxId = "";
|
|
272
|
-
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
273
|
-
console.log(pc4.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
|
|
274
|
-
}
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
function showHelp() {
|
|
278
|
-
console.log();
|
|
279
|
-
console.log(pc4.bold(" Shell Mode"));
|
|
280
|
-
console.log(pc4.gray(" Just type commands - they run in the active sandbox"));
|
|
281
|
-
console.log();
|
|
282
|
-
console.log(pc4.white(" ls -la"));
|
|
283
|
-
console.log(pc4.white(" cat /etc/os-release"));
|
|
284
|
-
console.log(pc4.white(" npm install lodash"));
|
|
285
|
-
console.log();
|
|
286
|
-
console.log(pc4.bold(" Sandbox Commands") + pc4.gray(" (prefix with /)"));
|
|
287
|
-
console.log();
|
|
288
|
-
console.log(pc4.cyan(" /new") + pc4.gray(" Create a new sandbox and switch to it"));
|
|
289
|
-
console.log(pc4.cyan(" /list") + pc4.gray(" List all sandboxes"));
|
|
290
|
-
console.log(pc4.cyan(" /switch <id>") + pc4.gray(" Switch active sandbox"));
|
|
291
|
-
console.log(pc4.cyan(" /destroy <id>") + pc4.gray(" Destroy a sandbox by ID"));
|
|
292
|
-
console.log(pc4.cyan(" /destroy --all") + pc4.gray(" Destroy all sandboxes"));
|
|
293
|
-
console.log();
|
|
294
|
-
console.log(pc4.bold(" Other Commands"));
|
|
295
|
-
console.log();
|
|
296
|
-
console.log(pc4.cyan(" /info") + pc4.gray(" Show active sandbox info"));
|
|
297
|
-
console.log(pc4.cyan(" /url <port>") + pc4.gray(" Get public URL for port"));
|
|
298
|
-
console.log(pc4.cyan(" /help") + pc4.gray(" Show this help"));
|
|
299
|
-
console.log(pc4.cyan(" /exit") + pc4.gray(" Exit the REPL"));
|
|
300
|
-
console.log();
|
|
301
|
-
}
|
|
302
|
-
function loadHistory(rl) {
|
|
303
|
-
try {
|
|
304
|
-
if (fs.existsSync(HISTORY_FILE)) {
|
|
305
|
-
const history = fs.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter((line) => line.trim()).reverse();
|
|
306
|
-
for (const line of history.slice(0, 1e3)) {
|
|
307
|
-
rl.history?.push(line);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
} catch {
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function saveToHistory(line) {
|
|
314
|
-
try {
|
|
315
|
-
fs.appendFileSync(HISTORY_FILE, line + "\n");
|
|
316
|
-
} catch {
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
var HISTORY_FILE;
|
|
320
|
-
var init_repl = __esm({
|
|
321
|
-
"src/repl.ts"() {
|
|
322
|
-
"use strict";
|
|
323
|
-
init_esm_shims();
|
|
324
|
-
HISTORY_FILE = path2.join(os.homedir(), ".create_compute_history");
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// src/workspace-cli.ts
|
|
329
|
-
var workspace_cli_exports = {};
|
|
330
|
-
__export(workspace_cli_exports, {
|
|
331
|
-
loadState: () => loadState,
|
|
332
|
-
loadWorkspaceState: () => loadWorkspaceState,
|
|
333
|
-
sandboxList: () => sandboxList,
|
|
334
|
-
saveSandbox: () => saveSandbox,
|
|
335
|
-
saveState: () => saveState,
|
|
336
|
-
workspaceAttach: () => workspaceAttach,
|
|
337
|
-
workspaceCreate: () => workspaceCreate,
|
|
338
|
-
workspaceDestroy: () => workspaceDestroy,
|
|
339
|
-
workspaceList: () => workspaceList
|
|
340
|
-
});
|
|
341
|
-
import { compute as compute3 } from "computesdk";
|
|
342
|
-
import * as fs3 from "fs";
|
|
343
|
-
import * as path4 from "path";
|
|
344
|
-
import * as os3 from "os";
|
|
345
|
-
import * as p3 from "@clack/prompts";
|
|
346
|
-
import pc7 from "picocolors";
|
|
347
|
-
function shellEscape(arg) {
|
|
348
|
-
if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg)) return arg;
|
|
349
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
350
|
-
}
|
|
351
|
-
function loadState() {
|
|
352
|
-
try {
|
|
353
|
-
if (!fs3.existsSync(WORKSPACE_STATE_FILE)) {
|
|
354
|
-
return { workspaces: {} };
|
|
355
|
-
}
|
|
356
|
-
const raw = fs3.readFileSync(WORKSPACE_STATE_FILE, "utf-8");
|
|
357
|
-
return JSON.parse(raw);
|
|
358
|
-
} catch {
|
|
359
|
-
return { workspaces: {} };
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function saveState(state) {
|
|
363
|
-
const dir = path4.dirname(WORKSPACE_STATE_FILE);
|
|
364
|
-
if (!fs3.existsSync(dir)) {
|
|
365
|
-
fs3.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
366
|
-
}
|
|
367
|
-
fs3.writeFileSync(WORKSPACE_STATE_FILE, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
368
|
-
}
|
|
369
|
-
function getWorkspaceId(repo, branch) {
|
|
370
|
-
return `${repo}:${branch}`;
|
|
371
|
-
}
|
|
372
|
-
async function workspaceCreate(options) {
|
|
373
|
-
const { repo, branch, provider } = options;
|
|
374
|
-
const id = getWorkspaceId(repo, branch);
|
|
375
|
-
const state = loadState();
|
|
376
|
-
if (state.workspaces[id]) {
|
|
377
|
-
const existing = state.workspaces[id];
|
|
378
|
-
try {
|
|
379
|
-
const sandbox2 = await compute3.sandbox.getById(existing.sandboxId);
|
|
380
|
-
if (!sandbox2) {
|
|
381
|
-
throw new Error("Sandbox not found");
|
|
382
|
-
}
|
|
383
|
-
existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
|
|
384
|
-
saveState(state);
|
|
385
|
-
p3.log.info(`Attaching to existing workspace: ${pc7.cyan(id)}`);
|
|
386
|
-
return existing;
|
|
387
|
-
} catch {
|
|
388
|
-
delete state.workspaces[id];
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
p3.log.info("Creating sandbox...");
|
|
392
|
-
const spinner4 = p3.spinner();
|
|
393
|
-
spinner4.start("Provisioning via ComputeSDK...");
|
|
394
|
-
let sandbox;
|
|
395
|
-
try {
|
|
396
|
-
sandbox = await compute3.sandbox.create();
|
|
397
|
-
spinner4.stop(`Sandbox ready: ${pc7.cyan(sandbox.sandboxId)}`);
|
|
398
|
-
} catch (error) {
|
|
399
|
-
spinner4.stop(pc7.red("Failed to create sandbox"));
|
|
400
|
-
throw error;
|
|
401
|
-
}
|
|
402
|
-
spinner4.start("Bootstrapping workspace (installing gh CLI, cloning repo)...");
|
|
403
|
-
const bootstrapScript = fs3.readFileSync(
|
|
404
|
-
new URL("./bootstrap.sh", import.meta.url),
|
|
405
|
-
"utf-8"
|
|
406
|
-
);
|
|
407
|
-
try {
|
|
408
|
-
await sandbox.filesystem.writeFile("/tmp/bootstrap.sh", bootstrapScript);
|
|
409
|
-
await sandbox.runCommand("chmod +x /tmp/bootstrap.sh");
|
|
410
|
-
spinner4.stop("Running bootstrap (you may need to authenticate with GitHub)...");
|
|
411
|
-
const result = await sandbox.runCommand(
|
|
412
|
-
`/tmp/bootstrap.sh ${shellEscape(repo)} ${shellEscape(branch)}`,
|
|
413
|
-
{ timeout: 3e5 }
|
|
414
|
-
// 5 min timeout for auth
|
|
415
|
-
);
|
|
416
|
-
const output2 = result.stdout || "";
|
|
417
|
-
const jsonMatch = output2.match(/\{[\s\S]*\}/);
|
|
418
|
-
let worktreePath = `/workspaces/${path4.basename(repo)}-${branch}`;
|
|
419
|
-
if (jsonMatch) {
|
|
420
|
-
try {
|
|
421
|
-
const bootstrapInfo = JSON.parse(jsonMatch[0]);
|
|
422
|
-
worktreePath = bootstrapInfo.worktree_path || worktreePath;
|
|
423
|
-
} catch {
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const workspace = {
|
|
427
|
-
id,
|
|
428
|
-
repo,
|
|
429
|
-
branch,
|
|
430
|
-
sandboxId: sandbox.sandboxId,
|
|
431
|
-
provider: provider || "e2b",
|
|
432
|
-
// TODO: Get actual provider
|
|
433
|
-
worktreePath,
|
|
434
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
435
|
-
lastUsed: (/* @__PURE__ */ new Date()).toISOString()
|
|
436
|
-
};
|
|
437
|
-
state.workspaces[id] = workspace;
|
|
438
|
-
saveState(state);
|
|
439
|
-
p3.log.success(`Workspace ready: ${pc7.cyan(id)}`);
|
|
440
|
-
p3.log.info(`Sandbox: ${pc7.dim(sandbox.sandboxId)}`);
|
|
441
|
-
p3.log.info(`Worktree: ${pc7.dim(worktreePath)}`);
|
|
442
|
-
return workspace;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
spinner4.stop(pc7.red("Bootstrap failed"));
|
|
445
|
-
try {
|
|
446
|
-
await sandbox.destroy();
|
|
447
|
-
} catch {
|
|
448
|
-
}
|
|
449
|
-
throw error;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
async function workspaceAttach(id) {
|
|
453
|
-
const state = loadState();
|
|
454
|
-
const workspace = state.workspaces[id];
|
|
455
|
-
if (!workspace) {
|
|
456
|
-
p3.log.error(`Workspace not found: ${id}`);
|
|
457
|
-
p3.log.info(`Run: compute workspace create --repo owner/repo --branch ${id.split(":")[1] || "main"}`);
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
const sandbox = await compute3.sandbox.getById(workspace.sandboxId);
|
|
461
|
-
if (!sandbox) {
|
|
462
|
-
p3.log.error(`Sandbox no longer exists: ${workspace.sandboxId}`);
|
|
463
|
-
delete state.workspaces[id];
|
|
464
|
-
saveState(state);
|
|
465
|
-
process.exit(1);
|
|
466
|
-
}
|
|
467
|
-
workspace.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
|
|
468
|
-
saveState(state);
|
|
469
|
-
p3.log.info(`Attaching to workspace: ${pc7.cyan(id)}`);
|
|
470
|
-
p3.log.info(`Worktree: ${pc7.dim(workspace.worktreePath)}`);
|
|
471
|
-
console.log(pc7.gray(`Tip: commands run at sandbox root; use { cwd: "${workspace.worktreePath}" } on runCommand to scope them.`));
|
|
472
|
-
try {
|
|
473
|
-
await startREPL(sandbox, workspace.provider);
|
|
474
|
-
} catch (error) {
|
|
475
|
-
p3.log.error(`Failed to attach: ${error.message}`);
|
|
476
|
-
process.exit(1);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
async function sandboxList() {
|
|
480
|
-
const state = loadState();
|
|
481
|
-
const workspaces = Object.values(state.workspaces);
|
|
482
|
-
if (workspaces.length === 0) {
|
|
483
|
-
p3.log.info("No sandboxes found.");
|
|
484
|
-
p3.log.info("Create one with: compute owner/repo --branch main");
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
console.log();
|
|
488
|
-
console.log(pc7.bold("Sandboxes"));
|
|
489
|
-
console.log();
|
|
490
|
-
for (const ws of workspaces) {
|
|
491
|
-
const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
|
|
492
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
493
|
-
const status = age > 7 ? pc7.red("stale") : age > 1 ? pc7.yellow("idle") : pc7.green("active");
|
|
494
|
-
console.log(` ${status} ${pc7.cyan(ws.sandboxId)}`);
|
|
495
|
-
console.log(` Workspace: ${pc7.dim(ws.id)}`);
|
|
496
|
-
console.log(` Provider: ${ws.provider}`);
|
|
497
|
-
console.log(` Last active: ${pc7.gray(ageStr)}`);
|
|
498
|
-
console.log();
|
|
499
|
-
}
|
|
500
|
-
console.log(pc7.gray('Tip: Use "compute workspaces" to see full workspace details'));
|
|
501
|
-
}
|
|
502
|
-
async function workspaceList() {
|
|
503
|
-
const state = loadState();
|
|
504
|
-
const workspaces = Object.values(state.workspaces);
|
|
505
|
-
if (workspaces.length === 0) {
|
|
506
|
-
p3.log.info("No workspaces found.");
|
|
507
|
-
p3.log.info("Create one with: compute owner/repo --branch main");
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
console.log();
|
|
511
|
-
console.log(pc7.bold("Workspaces"));
|
|
512
|
-
console.log();
|
|
513
|
-
for (const ws of workspaces) {
|
|
514
|
-
const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
|
|
515
|
-
const ageStr = age === 0 ? "today" : `${age}d ago`;
|
|
516
|
-
console.log(` ${pc7.cyan(ws.id)}`);
|
|
517
|
-
console.log(` Sandbox: ${pc7.dim(ws.sandboxId)} (${ws.provider})`);
|
|
518
|
-
console.log(` Path: ${pc7.dim(ws.worktreePath)}`);
|
|
519
|
-
console.log(` Last used: ${pc7.gray(ageStr)}`);
|
|
520
|
-
console.log();
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async function saveSandbox(sandbox) {
|
|
524
|
-
const state = loadState();
|
|
525
|
-
state.workspaces[sandbox.id] = {
|
|
526
|
-
id: sandbox.id,
|
|
527
|
-
repo: "(no repo)",
|
|
528
|
-
branch: "(raw sandbox)",
|
|
529
|
-
sandboxId: sandbox.sandboxId,
|
|
530
|
-
provider: sandbox.provider,
|
|
531
|
-
worktreePath: "/",
|
|
532
|
-
createdAt: sandbox.createdAt,
|
|
533
|
-
lastUsed: sandbox.lastUsed
|
|
534
|
-
};
|
|
535
|
-
saveState(state);
|
|
536
|
-
}
|
|
537
|
-
async function workspaceDestroy(id) {
|
|
538
|
-
const state = loadState();
|
|
539
|
-
if (id) {
|
|
540
|
-
const workspace = state.workspaces[id];
|
|
541
|
-
if (!workspace) {
|
|
542
|
-
p3.log.error(`Workspace not found: ${id}`);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
p3.log.info(`Destroying workspace: ${pc7.cyan(id)}`);
|
|
546
|
-
try {
|
|
547
|
-
await compute3.sandbox.destroy(workspace.sandboxId);
|
|
548
|
-
delete state.workspaces[id];
|
|
549
|
-
saveState(state);
|
|
550
|
-
p3.log.success("Workspace destroyed");
|
|
551
|
-
} catch (error) {
|
|
552
|
-
p3.log.error(`Failed to destroy: ${error.message}`);
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
555
|
-
const choices = Object.values(state.workspaces).map((ws) => ({
|
|
556
|
-
value: ws.id,
|
|
557
|
-
label: `${ws.id} (${ws.provider}, last used ${Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24))}d ago)`
|
|
558
|
-
}));
|
|
559
|
-
if (choices.length === 0) {
|
|
560
|
-
p3.log.info("No workspaces to destroy");
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
const selected = await p3.select({
|
|
564
|
-
message: "Select workspace to destroy",
|
|
565
|
-
options: choices
|
|
566
|
-
});
|
|
567
|
-
if (p3.isCancel(selected)) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
await workspaceDestroy(selected);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
var WORKSPACE_STATE_FILE, loadWorkspaceState;
|
|
574
|
-
var init_workspace_cli = __esm({
|
|
575
|
-
"src/workspace-cli.ts"() {
|
|
576
|
-
"use strict";
|
|
577
|
-
init_esm_shims();
|
|
578
|
-
init_repl();
|
|
579
|
-
WORKSPACE_STATE_FILE = path4.join(os3.homedir(), ".computesdk", "workspaces.json");
|
|
580
|
-
loadWorkspaceState = loadState;
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
2
|
|
|
584
3
|
// src/index.ts
|
|
585
|
-
init_esm_shims();
|
|
586
4
|
import "dotenv/config";
|
|
587
5
|
import { Command } from "commander";
|
|
588
|
-
import * as
|
|
589
|
-
import
|
|
590
|
-
import { readFileSync as
|
|
591
|
-
import { fileURLToPath
|
|
592
|
-
import { dirname
|
|
593
|
-
import { compute as compute4 } from "computesdk";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import pc4 from "picocolors";
|
|
8
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname, join as join2 } from "path";
|
|
594
11
|
|
|
595
12
|
// src/providers.ts
|
|
596
|
-
init_esm_shims();
|
|
597
13
|
var PROVIDER_ENV = {
|
|
598
14
|
e2b: { authOptions: [["E2B_API_KEY"]], configKeys: { E2B_API_KEY: "apiKey" } },
|
|
599
15
|
railway: { authOptions: [["RAILWAY_API_TOKEN"]], configKeys: { RAILWAY_API_TOKEN: "token" } },
|
|
@@ -636,26 +52,8 @@ function missingEnvVars(provider) {
|
|
|
636
52
|
function isProviderAuthComplete(provider) {
|
|
637
53
|
return missingEnvVars(provider).length === 0;
|
|
638
54
|
}
|
|
639
|
-
function detectAvailableProviders() {
|
|
640
|
-
const available = [];
|
|
641
|
-
if (process.env.COMPUTESDK_API_KEY) {
|
|
642
|
-
available.push("computesdk");
|
|
643
|
-
}
|
|
644
|
-
for (const provider of PROVIDER_NAMES) {
|
|
645
|
-
if (isProviderAuthComplete(provider)) {
|
|
646
|
-
available.push(provider);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return available;
|
|
650
|
-
}
|
|
651
55
|
function getProviderStatus() {
|
|
652
56
|
const statuses = [];
|
|
653
|
-
const hasGatewayKey = !!process.env.COMPUTESDK_API_KEY;
|
|
654
|
-
statuses.push({
|
|
655
|
-
name: "computesdk",
|
|
656
|
-
ready: hasGatewayKey,
|
|
657
|
-
missing: hasGatewayKey ? [] : ["COMPUTESDK_API_KEY"]
|
|
658
|
-
});
|
|
659
57
|
for (const provider of PROVIDER_NAMES) {
|
|
660
58
|
statuses.push({
|
|
661
59
|
name: provider,
|
|
@@ -665,40 +63,13 @@ function getProviderStatus() {
|
|
|
665
63
|
}
|
|
666
64
|
return statuses;
|
|
667
65
|
}
|
|
668
|
-
function buildProviderConfig(provider) {
|
|
669
|
-
const config = {
|
|
670
|
-
provider,
|
|
671
|
-
apiKey: process.env.COMPUTESDK_API_KEY
|
|
672
|
-
// Gateway API key (top-level)
|
|
673
|
-
};
|
|
674
|
-
if (provider === "computesdk") {
|
|
675
|
-
config.computesdk = {
|
|
676
|
-
computesdk_api_key: process.env.COMPUTESDK_API_KEY
|
|
677
|
-
};
|
|
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
|
-
}
|
|
687
|
-
if (Object.keys(providerConfig).length > 0) {
|
|
688
|
-
config[provider] = providerConfig;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return config;
|
|
692
|
-
}
|
|
693
66
|
|
|
694
67
|
// src/actions.ts
|
|
695
|
-
init_esm_shims();
|
|
696
68
|
import pc from "picocolors";
|
|
697
69
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
698
70
|
import { basename, join } from "path";
|
|
699
71
|
|
|
700
72
|
// src/actions-client.ts
|
|
701
|
-
init_esm_shims();
|
|
702
73
|
var DEFAULT_BASE_URL = "https://platform.computesdk.com";
|
|
703
74
|
var ActionsApiError = class extends Error {
|
|
704
75
|
constructor(status, message, body) {
|
|
@@ -747,11 +118,12 @@ function errorDetails(body) {
|
|
|
747
118
|
function toErrorEnvelope(error) {
|
|
748
119
|
if (error instanceof ActionsApiError) {
|
|
749
120
|
const details = errorDetails(error.body);
|
|
121
|
+
const insufficientScope = error.message.includes("insufficient_scope") || typeof error.body === "string" && error.body.includes("insufficient_scope") || typeof error.body === "object" && error.body !== null && JSON.stringify(error.body).includes("insufficient_scope");
|
|
750
122
|
return {
|
|
751
123
|
ok: false,
|
|
752
124
|
error: {
|
|
753
125
|
code: codeForStatus(error.status),
|
|
754
|
-
message: error.message,
|
|
126
|
+
message: insufficientScope ? `${error.message} \u2014 run \`compute login\` to sign in again.` : error.message,
|
|
755
127
|
httpStatus: error.status,
|
|
756
128
|
retryable: error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504,
|
|
757
129
|
...details !== void 0 && { details }
|
|
@@ -792,8 +164,8 @@ var ActionsClient = class {
|
|
|
792
164
|
this.orgPromise ?? (this.orgPromise = this.get("/api/v1/actions/org"));
|
|
793
165
|
return this.orgPromise;
|
|
794
166
|
}
|
|
795
|
-
async get(
|
|
796
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
167
|
+
async get(path, params) {
|
|
168
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
797
169
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
798
170
|
if (value === void 0) continue;
|
|
799
171
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -801,32 +173,32 @@ var ActionsClient = class {
|
|
|
801
173
|
const res = await this.fetchImpl(url, { headers: this.headers() });
|
|
802
174
|
return this.parse(res);
|
|
803
175
|
}
|
|
804
|
-
async post(
|
|
805
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
176
|
+
async post(path, body = {}) {
|
|
177
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
806
178
|
method: "POST",
|
|
807
179
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
808
180
|
body: JSON.stringify(body)
|
|
809
181
|
});
|
|
810
182
|
return this.parse(res);
|
|
811
183
|
}
|
|
812
|
-
async put(
|
|
813
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
184
|
+
async put(path, body = {}) {
|
|
185
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
814
186
|
method: "PUT",
|
|
815
187
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
816
188
|
body: JSON.stringify(body)
|
|
817
189
|
});
|
|
818
190
|
return this.parse(res);
|
|
819
191
|
}
|
|
820
|
-
async patch(
|
|
821
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
192
|
+
async patch(path, body = {}) {
|
|
193
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
822
194
|
method: "PATCH",
|
|
823
195
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
824
196
|
body: JSON.stringify(body)
|
|
825
197
|
});
|
|
826
198
|
return this.parse(res);
|
|
827
199
|
}
|
|
828
|
-
async del(
|
|
829
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
200
|
+
async del(path) {
|
|
201
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
830
202
|
method: "DELETE",
|
|
831
203
|
headers: this.headers()
|
|
832
204
|
});
|
|
@@ -836,8 +208,8 @@ var ActionsClient = class {
|
|
|
836
208
|
* One `data:` JSON payload per SSE event. `next()` resolves to the payload,
|
|
837
209
|
* or null when the stream ends. Call `close()` to abort.
|
|
838
210
|
*/
|
|
839
|
-
async *sse(
|
|
840
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
211
|
+
async *sse(path, params) {
|
|
212
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
841
213
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
842
214
|
if (value === void 0) continue;
|
|
843
215
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -856,8 +228,8 @@ var ActionsClient = class {
|
|
|
856
228
|
* events are distinguished by name rather than payload shape (process
|
|
857
229
|
* follow: stdout/stderr share a `{chunk}` payload).
|
|
858
230
|
*/
|
|
859
|
-
async *sseNamed(
|
|
860
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
231
|
+
async *sseNamed(path, params) {
|
|
232
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
861
233
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
862
234
|
if (value === void 0) continue;
|
|
863
235
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -872,8 +244,8 @@ var ActionsClient = class {
|
|
|
872
244
|
yield* readSseNamedEvents(res.body);
|
|
873
245
|
}
|
|
874
246
|
/** Download bytes following redirects (artifact URLs 302 to storage). */
|
|
875
|
-
async download(
|
|
876
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
247
|
+
async download(path) {
|
|
248
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
877
249
|
headers: this.headers(),
|
|
878
250
|
redirect: "follow"
|
|
879
251
|
});
|
|
@@ -884,7 +256,12 @@ var ActionsClient = class {
|
|
|
884
256
|
return new Uint8Array(await res.arrayBuffer());
|
|
885
257
|
}
|
|
886
258
|
headers() {
|
|
887
|
-
|
|
259
|
+
const headers = {
|
|
260
|
+
authorization: `Bearer ${this.auth.apiKey}`,
|
|
261
|
+
accept: "application/json"
|
|
262
|
+
};
|
|
263
|
+
if (this.auth.orgSlug) headers["X-Org-Slug"] = this.auth.orgSlug;
|
|
264
|
+
return headers;
|
|
888
265
|
}
|
|
889
266
|
async parse(res) {
|
|
890
267
|
const body = await res.json().catch(() => void 0);
|
|
@@ -954,10 +331,11 @@ function encodeWatchCursor(id, jobId, step, offset) {
|
|
|
954
331
|
const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
|
|
955
332
|
return `${id}:${jobId}:${stepPart}:${offset}`;
|
|
956
333
|
}
|
|
957
|
-
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute
|
|
334
|
+
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute login`.";
|
|
958
335
|
async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAuth) {
|
|
959
336
|
const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
|
|
960
337
|
DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
338
|
+
let orgSlug = opts.org || process.env.COMPUTE_ORG || void 0;
|
|
961
339
|
if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
|
|
962
340
|
throw new ActionsCliError(
|
|
963
341
|
"untrusted_host",
|
|
@@ -992,12 +370,12 @@ async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAut
|
|
|
992
370
|
if (!apiKey) {
|
|
993
371
|
throw new ActionsCliError("no_credentials", `No API key. ${NO_CREDENTIALS_HINT}`);
|
|
994
372
|
}
|
|
995
|
-
return { apiKey, baseUrl };
|
|
373
|
+
return { apiKey, baseUrl, ...orgSlug && { orgSlug } };
|
|
996
374
|
}
|
|
997
375
|
async function resolveStoredPlatformAuth(opts) {
|
|
998
376
|
const { resolveAuth } = await import("@benchsdk/cli");
|
|
999
377
|
const auth = await resolveAuth({ baseUrl: opts.baseUrl });
|
|
1000
|
-
return { apiKey: auth.apiKey, token: auth.token };
|
|
378
|
+
return { apiKey: auth.apiKey, token: auth.token, orgSlug: auth.orgSlug, orgId: auth.orgId };
|
|
1001
379
|
}
|
|
1002
380
|
function isLoopbackHost(host) {
|
|
1003
381
|
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
@@ -1389,14 +767,14 @@ function formatRunHistory(history) {
|
|
|
1389
767
|
}
|
|
1390
768
|
return lines.join("\n");
|
|
1391
769
|
}
|
|
1392
|
-
function formatProviderRow(
|
|
1393
|
-
const icon =
|
|
1394
|
-
const name =
|
|
770
|
+
function formatProviderRow(p2) {
|
|
771
|
+
const icon = p2.usable ? pc.green("\u25CF") : pc.gray("\u25CB");
|
|
772
|
+
const name = p2.usable ? pc.white(p2.provider) : pc.gray(p2.provider);
|
|
1395
773
|
const flags = [
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
774
|
+
p2.credential,
|
|
775
|
+
p2.actCapable ? pc.green("act") : pc.gray("no-act"),
|
|
776
|
+
p2.regions.length > 0 ? `regions: ${p2.regions.join(",")}` : "no region choice",
|
|
777
|
+
p2.position === null ? pc.gray("not in provider order") : `order #${p2.position}`
|
|
1400
778
|
].join(" ");
|
|
1401
779
|
return ` ${icon} ${name} ${flags}`;
|
|
1402
780
|
}
|
|
@@ -1471,13 +849,13 @@ function parseVaultKind(kind) {
|
|
|
1471
849
|
if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
|
|
1472
850
|
throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
|
|
1473
851
|
}
|
|
1474
|
-
function vaultPath(
|
|
852
|
+
function vaultPath(path, params) {
|
|
1475
853
|
const search = new URLSearchParams();
|
|
1476
854
|
for (const [key, value] of Object.entries(params)) {
|
|
1477
855
|
if (value !== void 0) search.set(key, value);
|
|
1478
856
|
}
|
|
1479
857
|
const query = search.toString();
|
|
1480
|
-
return query ? `${
|
|
858
|
+
return query ? `${path}?${query}` : path;
|
|
1481
859
|
}
|
|
1482
860
|
function vaultSetBody(name, value, opts) {
|
|
1483
861
|
if (value === "") throw new ActionsCliError("invalid_argument", "The value is empty.");
|
|
@@ -1505,7 +883,7 @@ function formatVaultRow(item) {
|
|
|
1505
883
|
}
|
|
1506
884
|
function registerActionsCommands(program2) {
|
|
1507
885
|
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
|
|
1508
|
-
const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
|
|
886
|
+
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("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
|
|
1509
887
|
common(
|
|
1510
888
|
actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)").option("--max-bid <usd>", "max price per vCPU for a market fill (e.g. 0.12); unfilled jobs fall through to the next provider").option("--max-bid-per <unit>", "time unit --max-bid is priced in: second, minute, or hour (default: second)")
|
|
1511
889
|
).action(async (repo, opts) => {
|
|
@@ -1528,8 +906,8 @@ function registerActionsCommands(program2) {
|
|
|
1528
906
|
"/api/v1/actions/dispatch",
|
|
1529
907
|
body
|
|
1530
908
|
);
|
|
1531
|
-
const
|
|
1532
|
-
const url = `${c.baseUrl}/${
|
|
909
|
+
const org2 = await c.org();
|
|
910
|
+
const url = `${c.baseUrl}/${org2.slug}/actions/runs/${result.runId}`;
|
|
1533
911
|
output(opts, { ...result, url }, (r) => {
|
|
1534
912
|
console.log(`${r.created ? "dispatched" : "already running"} ${pc.cyan(r.runId)}`);
|
|
1535
913
|
console.log(`sha: ${r.headSha}`);
|
|
@@ -1587,8 +965,8 @@ function registerActionsCommands(program2) {
|
|
|
1587
965
|
try {
|
|
1588
966
|
const c = await client(opts);
|
|
1589
967
|
const run = await c.get(`/api/v1/actions/runs/${runId}`);
|
|
1590
|
-
const
|
|
1591
|
-
const url = `${c.baseUrl}/${
|
|
968
|
+
const org2 = await c.org();
|
|
969
|
+
const url = `${c.baseUrl}/${org2.slug}/actions/runs/${run.id}`;
|
|
1592
970
|
const summary = run.conclusion === "failed" ? await fetchRunSummary(c, runId) : null;
|
|
1593
971
|
output(opts, { ...run, url, summary }, (r) => {
|
|
1594
972
|
console.log(formatRunDetail(r, r.url));
|
|
@@ -1672,9 +1050,9 @@ function registerActionsCommands(program2) {
|
|
|
1672
1050
|
process.stdout.write(JSON.stringify({ jobId: job.id, name: job.name, slice }) + "\n");
|
|
1673
1051
|
} else {
|
|
1674
1052
|
for (const seg of slice.segments) {
|
|
1675
|
-
const
|
|
1053
|
+
const text = seg.text.replace(/\n/g, `
|
|
1676
1054
|
[${job.name}] `);
|
|
1677
|
-
process.stdout.write(`[${job.name}] ${
|
|
1055
|
+
process.stdout.write(`[${job.name}] ${text}`);
|
|
1678
1056
|
}
|
|
1679
1057
|
}
|
|
1680
1058
|
}
|
|
@@ -1687,7 +1065,7 @@ function registerActionsCommands(program2) {
|
|
|
1687
1065
|
try {
|
|
1688
1066
|
const data = await (await client(opts)).get("/api/v1/actions/providers");
|
|
1689
1067
|
output(opts, data, (d) => {
|
|
1690
|
-
for (const
|
|
1068
|
+
for (const p2 of d.providers) console.log(formatProviderRow(p2));
|
|
1691
1069
|
});
|
|
1692
1070
|
} catch (e) {
|
|
1693
1071
|
fail(e, opts);
|
|
@@ -1839,8 +1217,8 @@ function registerActionsCommands(program2) {
|
|
|
1839
1217
|
vault.command("ls").description("List names and metadata (never values)")
|
|
1840
1218
|
).action(async (opts) => {
|
|
1841
1219
|
try {
|
|
1842
|
-
const
|
|
1843
|
-
const data = await (await client(opts)).get(
|
|
1220
|
+
const path = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind) });
|
|
1221
|
+
const data = await (await client(opts)).get(path);
|
|
1844
1222
|
output(opts, data, (d) => {
|
|
1845
1223
|
for (const item of d.items) console.log(formatVaultRow(item));
|
|
1846
1224
|
});
|
|
@@ -1894,8 +1272,8 @@ function registerActionsCommands(program2) {
|
|
|
1894
1272
|
vault.command("rm").description("Delete an item (with --repo, only the repo's override)").argument("<name>", "item name")
|
|
1895
1273
|
).action(async (name, opts) => {
|
|
1896
1274
|
try {
|
|
1897
|
-
const
|
|
1898
|
-
const result = await (await client(opts)).del(
|
|
1275
|
+
const path = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind), name });
|
|
1276
|
+
const result = await (await client(opts)).del(path);
|
|
1899
1277
|
output(opts, result, (r) => console.log(`deleted ${pc.cyan(safeTerm(r.name))} ${pc.dim(`(${r.kind})`)}`));
|
|
1900
1278
|
} catch (e) {
|
|
1901
1279
|
fail(e, opts);
|
|
@@ -1923,8 +1301,8 @@ function registerActionsCommands(program2) {
|
|
|
1923
1301
|
const result = await c.post(
|
|
1924
1302
|
`/api/v1/actions/runs/${runId}/rerun`
|
|
1925
1303
|
);
|
|
1926
|
-
const
|
|
1927
|
-
const url = `${c.baseUrl}/${
|
|
1304
|
+
const org2 = await c.org();
|
|
1305
|
+
const url = `${c.baseUrl}/${org2.slug}/actions/runs/${result.runId}`;
|
|
1928
1306
|
output(opts, { ...result, url }, (r) => {
|
|
1929
1307
|
console.log(`${r.created ? "rerun dispatched" : "rerun already running"} ${pc.cyan(r.runId)}`);
|
|
1930
1308
|
console.log(`url: ${r.url}`);
|
|
@@ -1992,7 +1370,6 @@ function registerActionsCommands(program2) {
|
|
|
1992
1370
|
}
|
|
1993
1371
|
|
|
1994
1372
|
// src/market.ts
|
|
1995
|
-
init_esm_shims();
|
|
1996
1373
|
import pc2 from "picocolors";
|
|
1997
1374
|
var USD_RE = /^\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
1998
1375
|
var RATE_UNITS = ["second", "minute", "hour"];
|
|
@@ -2145,24 +1522,24 @@ function formatListingRow(ask) {
|
|
|
2145
1522
|
pc2.dim(expiry)
|
|
2146
1523
|
].join(" ");
|
|
2147
1524
|
}
|
|
2148
|
-
function formatProviderStatus(
|
|
1525
|
+
function formatProviderStatus(p2) {
|
|
2149
1526
|
const lines = [];
|
|
2150
|
-
lines.push(`${pc2.bold(safeTerm(
|
|
2151
|
-
lines.push(`id: ${
|
|
1527
|
+
lines.push(`${pc2.bold(safeTerm(p2.name))} ${pc2.dim(`selling ${safeTerm(p2.provider)}`)}`);
|
|
1528
|
+
lines.push(`id: ${p2.id}`);
|
|
2152
1529
|
lines.push(
|
|
2153
|
-
`credential: ${
|
|
1530
|
+
`credential: ${p2.executorCredentialConnected ? pc2.green("connected") : pc2.yellow("not connected \u2014 your listings cannot fill until `compute market credential connect`")}`
|
|
2154
1531
|
);
|
|
2155
|
-
if (
|
|
1532
|
+
if (p2.revokedAt) lines.push(pc2.red(`revoked: ${p2.revokedAt}`));
|
|
2156
1533
|
lines.push(
|
|
2157
|
-
`sizes: ${
|
|
1534
|
+
`sizes: ${p2.sizes.map((s) => `${s.name} (${s.label})`).join(", ") || "-"}`
|
|
2158
1535
|
);
|
|
2159
|
-
lines.push(`regions: ${
|
|
2160
|
-
if (
|
|
1536
|
+
lines.push(`regions: ${p2.regions.join(", ") || "-"}`);
|
|
1537
|
+
if (p2.credentialFields.length > 0) {
|
|
2161
1538
|
lines.push(
|
|
2162
|
-
`credential fields: ${
|
|
1539
|
+
`credential fields: ${p2.credentialFields.map((f) => `${f.name}${f.secret ? " (secret)" : ""}`).join(", ")}`
|
|
2163
1540
|
);
|
|
2164
1541
|
}
|
|
2165
|
-
lines.push(`created: ${
|
|
1542
|
+
lines.push(`created: ${p2.createdAt}`);
|
|
2166
1543
|
return lines.join("\n");
|
|
2167
1544
|
}
|
|
2168
1545
|
function formatBook(book) {
|
|
@@ -2232,7 +1609,7 @@ function formatSettlementRow(s) {
|
|
|
2232
1609
|
}
|
|
2233
1610
|
function registerMarketCommands(program2) {
|
|
2234
1611
|
const market = program2.command("market").description("Sell compute capacity on the compute market").configureOutput({ outputError: usageErrorOutput });
|
|
2235
|
-
const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
|
|
1612
|
+
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("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
|
|
2236
1613
|
common(
|
|
2237
1614
|
market.command("listings").alias("list").description("Your listings \u2014 live, paused, expired, and withdrawn")
|
|
2238
1615
|
).action(async (opts) => {
|
|
@@ -2397,7 +1774,7 @@ function registerMarketCommands(program2) {
|
|
|
2397
1774
|
).action(async (opts) => {
|
|
2398
1775
|
try {
|
|
2399
1776
|
const { provider } = await (await client(opts)).get("/api/v1/market/provider");
|
|
2400
|
-
output(opts, provider, (
|
|
1777
|
+
output(opts, provider, (p2) => console.log(formatProviderStatus(p2)));
|
|
2401
1778
|
} catch (e) {
|
|
2402
1779
|
fail(e, opts);
|
|
2403
1780
|
}
|
|
@@ -2465,7 +1842,6 @@ ${hints}`
|
|
|
2465
1842
|
}
|
|
2466
1843
|
|
|
2467
1844
|
// src/sandboxes.ts
|
|
2468
|
-
init_esm_shims();
|
|
2469
1845
|
import pc3 from "picocolors";
|
|
2470
1846
|
function shellQuote(arg) {
|
|
2471
1847
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
@@ -2484,10 +1860,10 @@ function printSandbox(s) {
|
|
|
2484
1860
|
);
|
|
2485
1861
|
}
|
|
2486
1862
|
}
|
|
2487
|
-
function printProcess(
|
|
2488
|
-
const exit =
|
|
2489
|
-
const stdin =
|
|
2490
|
-
console.log(`${pc3.cyan(
|
|
1863
|
+
function printProcess(p2) {
|
|
1864
|
+
const exit = p2.status === "exited" ? `exit=${p2.exitCode ?? "?"}${p2.signal ? ` (${p2.signal})` : ""}` : "";
|
|
1865
|
+
const stdin = p2.stdin && p2.status === "running" ? " [stdin open]" : "";
|
|
1866
|
+
console.log(`${pc3.cyan(p2.jobId)} ${p2.status}${exit ? " " + exit : ""}${stdin} ${p2.command}`);
|
|
2491
1867
|
}
|
|
2492
1868
|
function printSnapshot(s) {
|
|
2493
1869
|
const state = s.deletedAt !== null ? ` ${pc3.dim(`(deleted ${s.deletedAt})`)}` : "";
|
|
@@ -2610,10 +1986,10 @@ function registerSandboxesCommands(program2) {
|
|
|
2610
1986
|
const res = await c.get(
|
|
2611
1987
|
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2612
1988
|
);
|
|
2613
|
-
output(opts, res.process, (
|
|
2614
|
-
if (
|
|
2615
|
-
if (
|
|
2616
|
-
if (!
|
|
1989
|
+
output(opts, res.process, (p2) => {
|
|
1990
|
+
if (p2.stdout) process.stdout.write(p2.stdout);
|
|
1991
|
+
if (p2.stderr) process.stderr.write(p2.stderr);
|
|
1992
|
+
if (!p2.stdout && !p2.stderr) printProcess(p2);
|
|
2617
1993
|
});
|
|
2618
1994
|
} catch (e) {
|
|
2619
1995
|
fail(e, opts);
|
|
@@ -2690,11 +2066,11 @@ function registerSandboxesCommands(program2) {
|
|
|
2690
2066
|
fail(e, opts);
|
|
2691
2067
|
}
|
|
2692
2068
|
});
|
|
2693
|
-
cmd.command("ls <sandboxId> [path]").description("List a directory (or stat a path) inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId,
|
|
2069
|
+
cmd.command("ls <sandboxId> [path]").description("List a directory (or stat a path) inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path, opts) => {
|
|
2694
2070
|
try {
|
|
2695
2071
|
const c = await client(opts);
|
|
2696
2072
|
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2697
|
-
path:
|
|
2073
|
+
path: path ?? "/"
|
|
2698
2074
|
});
|
|
2699
2075
|
output(opts, res, (view) => {
|
|
2700
2076
|
if (view.type === "directory") {
|
|
@@ -2709,10 +2085,10 @@ function registerSandboxesCommands(program2) {
|
|
|
2709
2085
|
fail(e, opts);
|
|
2710
2086
|
}
|
|
2711
2087
|
});
|
|
2712
|
-
cmd.command("cat <sandboxId> <path>").description("Print a file's contents").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId,
|
|
2088
|
+
cmd.command("cat <sandboxId> <path>").description("Print a file's contents").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path, opts) => {
|
|
2713
2089
|
try {
|
|
2714
2090
|
const c = await client(opts);
|
|
2715
|
-
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path
|
|
2091
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path });
|
|
2716
2092
|
output(opts, res, (view) => {
|
|
2717
2093
|
if (view.type !== "file") {
|
|
2718
2094
|
console.error(pc3.red(`${view.path} is a directory`));
|
|
@@ -2725,7 +2101,7 @@ function registerSandboxesCommands(program2) {
|
|
|
2725
2101
|
fail(e, opts);
|
|
2726
2102
|
}
|
|
2727
2103
|
});
|
|
2728
|
-
cmd.command("write <sandboxId> <path>").description("Write a file inside the sandbox (--content or --file <local>, else stdin)").option("--content <text>", "file contents").option("--file <local>", "read contents from a local file").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId,
|
|
2104
|
+
cmd.command("write <sandboxId> <path>").description("Write a file inside the sandbox (--content or --file <local>, else stdin)").option("--content <text>", "file contents").option("--file <local>", "read contents from a local file").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path, opts) => {
|
|
2729
2105
|
try {
|
|
2730
2106
|
let content = opts.content;
|
|
2731
2107
|
if (content === void 0 && opts.file) {
|
|
@@ -2747,18 +2123,18 @@ function registerSandboxesCommands(program2) {
|
|
|
2747
2123
|
const c = await client(opts);
|
|
2748
2124
|
const res = await c.post(
|
|
2749
2125
|
`/api/v1/sandboxes/${sandboxId}/files`,
|
|
2750
|
-
{ path
|
|
2126
|
+
{ path, content }
|
|
2751
2127
|
);
|
|
2752
2128
|
output(opts, res, (r) => console.log(`wrote ${r.path}`));
|
|
2753
2129
|
} catch (e) {
|
|
2754
2130
|
fail(e, opts);
|
|
2755
2131
|
}
|
|
2756
2132
|
});
|
|
2757
|
-
cmd.command("mkdir <sandboxId> <path>").description("Create a directory (parents included)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId,
|
|
2133
|
+
cmd.command("mkdir <sandboxId> <path>").description("Create a directory (parents included)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path, opts) => {
|
|
2758
2134
|
try {
|
|
2759
2135
|
const c = await client(opts);
|
|
2760
2136
|
const res = await c.post(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2761
|
-
path
|
|
2137
|
+
path,
|
|
2762
2138
|
mkdir: true
|
|
2763
2139
|
});
|
|
2764
2140
|
output(opts, res, (r) => console.log(`created ${r.path}`));
|
|
@@ -2766,11 +2142,11 @@ function registerSandboxesCommands(program2) {
|
|
|
2766
2142
|
fail(e, opts);
|
|
2767
2143
|
}
|
|
2768
2144
|
});
|
|
2769
|
-
cmd.command("rm <sandboxId> <path>").description("Remove a file or directory inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId,
|
|
2145
|
+
cmd.command("rm <sandboxId> <path>").description("Remove a file or directory inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path, opts) => {
|
|
2770
2146
|
try {
|
|
2771
2147
|
const c = await client(opts);
|
|
2772
2148
|
const res = await c.del(
|
|
2773
|
-
`/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(
|
|
2149
|
+
`/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path)}`
|
|
2774
2150
|
);
|
|
2775
2151
|
output(opts, res, (r) => console.log(`removed ${r.path}`));
|
|
2776
2152
|
} catch (e) {
|
|
@@ -2823,6 +2199,9 @@ function registerSandboxesCommands(program2) {
|
|
|
2823
2199
|
fail(e, opts);
|
|
2824
2200
|
}
|
|
2825
2201
|
});
|
|
2202
|
+
for (const sub of cmd.commands) {
|
|
2203
|
+
sub.option("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)");
|
|
2204
|
+
}
|
|
2826
2205
|
}
|
|
2827
2206
|
async function followProcess(c, sandboxId, jobId) {
|
|
2828
2207
|
let exitCode = 0;
|
|
@@ -2849,825 +2228,155 @@ async function followProcess(c, sandboxId, jobId) {
|
|
|
2849
2228
|
}
|
|
2850
2229
|
|
|
2851
2230
|
// src/index.ts
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
import pc5 from "picocolors";
|
|
2864
|
-
function getCredentialsDir() {
|
|
2865
|
-
return path3.join(os2.homedir(), ".computesdk");
|
|
2866
|
-
}
|
|
2867
|
-
function getCredentialsFile() {
|
|
2868
|
-
return path3.join(getCredentialsDir(), "credentials.json");
|
|
2869
|
-
}
|
|
2870
|
-
var AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2871
|
-
var CONSOLE_AUTH_URL = "https://console.computesdk.com/cli/auth";
|
|
2872
|
-
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
2873
|
-
<html>
|
|
2874
|
-
<head><title>ComputeSDK CLI</title></head>
|
|
2875
|
-
<body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
2876
|
-
<div style="text-align: center;">
|
|
2877
|
-
<h1>Authenticated</h1>
|
|
2878
|
-
<p>You can close this tab and return to the terminal.</p>
|
|
2879
|
-
</div>
|
|
2880
|
-
</body>
|
|
2881
|
-
</html>`;
|
|
2882
|
-
function loadStoredCredentials() {
|
|
2883
|
-
try {
|
|
2884
|
-
if (!fs2.existsSync(getCredentialsFile())) {
|
|
2885
|
-
return null;
|
|
2886
|
-
}
|
|
2887
|
-
const raw = fs2.readFileSync(getCredentialsFile(), "utf-8");
|
|
2888
|
-
const data = JSON.parse(raw);
|
|
2889
|
-
if (typeof data.apiKey === "string" && data.apiKey.length > 0) {
|
|
2890
|
-
return data.apiKey;
|
|
2891
|
-
}
|
|
2892
|
-
return null;
|
|
2893
|
-
} catch {
|
|
2894
|
-
return null;
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
function storeCredentials(apiKey) {
|
|
2898
|
-
if (!fs2.existsSync(getCredentialsDir())) {
|
|
2899
|
-
fs2.mkdirSync(getCredentialsDir(), { recursive: true, mode: 448 });
|
|
2900
|
-
}
|
|
2901
|
-
fs2.writeFileSync(
|
|
2902
|
-
getCredentialsFile(),
|
|
2903
|
-
JSON.stringify({ apiKey }, null, 2) + "\n",
|
|
2904
|
-
{ mode: 384 }
|
|
2905
|
-
);
|
|
2906
|
-
}
|
|
2907
|
-
function clearStoredCredentials() {
|
|
2908
|
-
try {
|
|
2909
|
-
if (fs2.existsSync(getCredentialsFile())) {
|
|
2910
|
-
fs2.unlinkSync(getCredentialsFile());
|
|
2911
|
-
}
|
|
2912
|
-
} catch {
|
|
2913
|
-
}
|
|
2914
|
-
}
|
|
2915
|
-
function openBrowser(url) {
|
|
2916
|
-
const command = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
2917
|
-
exec(command, (err) => {
|
|
2918
|
-
if (err) {
|
|
2919
|
-
p.log.warn("Could not open browser automatically.");
|
|
2920
|
-
p.log.info(`Open this URL manually:
|
|
2921
|
-
${pc5.cyan(url)}`);
|
|
2922
|
-
}
|
|
2923
|
-
});
|
|
2231
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
2232
|
+
var __dirname2 = dirname(__filename2);
|
|
2233
|
+
var packageJson = JSON.parse(
|
|
2234
|
+
readFileSync2(join2(__dirname2, "..", "package.json"), "utf-8")
|
|
2235
|
+
);
|
|
2236
|
+
var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
|
|
2237
|
+
var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
|
|
2238
|
+
var program = new Command();
|
|
2239
|
+
program.name("compute").description("ComputeSDK CLI").version(VERSION).enablePositionalOptions().passThroughOptions();
|
|
2240
|
+
function envBaseUrl() {
|
|
2241
|
+
return process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL;
|
|
2924
2242
|
}
|
|
2925
|
-
function
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
return new Promise((resolve, reject) => {
|
|
2929
|
-
const server = http.createServer();
|
|
2930
|
-
const timeout = setTimeout(() => {
|
|
2931
|
-
server.close();
|
|
2932
|
-
reject(
|
|
2933
|
-
new Error("Authentication timed out after 5 minutes. Please try again.")
|
|
2934
|
-
);
|
|
2935
|
-
}, timeoutMs);
|
|
2936
|
-
server.on("request", (req, res) => {
|
|
2937
|
-
const url = new URL(req.url || "/", "http://localhost");
|
|
2938
|
-
if (req.method !== "GET" || url.pathname !== "/callback") {
|
|
2939
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2940
|
-
res.end("Not Found");
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
const receivedState = url.searchParams.get("state");
|
|
2944
|
-
const token = url.searchParams.get("token");
|
|
2945
|
-
if (receivedState !== state) {
|
|
2946
|
-
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
2947
|
-
res.end("Error: State mismatch. Please try authenticating again.");
|
|
2948
|
-
return;
|
|
2949
|
-
}
|
|
2950
|
-
if (!token) {
|
|
2951
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
2952
|
-
res.end("Error: No token received.");
|
|
2953
|
-
return;
|
|
2954
|
-
}
|
|
2955
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
2956
|
-
res.end(SUCCESS_HTML);
|
|
2957
|
-
clearTimeout(timeout);
|
|
2958
|
-
server.close();
|
|
2959
|
-
resolve(token);
|
|
2960
|
-
});
|
|
2961
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2962
|
-
const addr = server.address();
|
|
2963
|
-
if (!addr || typeof addr === "string") {
|
|
2964
|
-
clearTimeout(timeout);
|
|
2965
|
-
server.close();
|
|
2966
|
-
reject(new Error("Failed to start auth server"));
|
|
2967
|
-
return;
|
|
2968
|
-
}
|
|
2969
|
-
const port = addr.port;
|
|
2970
|
-
const authUrl = `${CONSOLE_AUTH_URL}?port=${port}&state=${state}`;
|
|
2971
|
-
if (options?.onServerReady) {
|
|
2972
|
-
options.onServerReady(port, state);
|
|
2973
|
-
}
|
|
2974
|
-
if (!options?.skipBrowserOpen) {
|
|
2975
|
-
openBrowser(authUrl);
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
server.on("error", (err) => {
|
|
2979
|
-
clearTimeout(timeout);
|
|
2980
|
-
reject(new Error(`Auth server error: ${err.message}`));
|
|
2981
|
-
});
|
|
2982
|
-
});
|
|
2243
|
+
async function benchAuth(opts = {}) {
|
|
2244
|
+
const { resolveAuth } = await import("@benchsdk/cli");
|
|
2245
|
+
return resolveAuth({ baseUrl: opts.baseUrl ?? envBaseUrl() });
|
|
2983
2246
|
}
|
|
2984
|
-
async function
|
|
2985
|
-
if (process.env.COMPUTESDK_API_KEY) {
|
|
2986
|
-
return process.env.COMPUTESDK_API_KEY;
|
|
2987
|
-
}
|
|
2988
|
-
if (!options?.forceLogin) {
|
|
2989
|
-
const stored = loadStoredCredentials();
|
|
2990
|
-
if (stored) {
|
|
2991
|
-
p.log.info(
|
|
2992
|
-
`Using stored credentials from ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
2993
|
-
);
|
|
2994
|
-
return stored;
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
p.log.info("No API key found. Starting browser authentication...");
|
|
2998
|
-
const spinner4 = p.spinner();
|
|
2999
|
-
spinner4.start("Waiting for browser authentication...");
|
|
2247
|
+
async function printSessionLine(options) {
|
|
3000
2248
|
try {
|
|
3001
|
-
const
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
2249
|
+
const { getMe } = await import("@benchsdk/cli");
|
|
2250
|
+
const me = await getMe(await benchAuth({ baseUrl: options?.baseUrl }));
|
|
2251
|
+
const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
|
|
2252
|
+
const who = me.user.email ?? me.user.name ?? me.user.id;
|
|
2253
|
+
if (active) {
|
|
3005
2254
|
p.log.success(
|
|
3006
|
-
`
|
|
2255
|
+
`Logged in as ${who} \u2014 active org: ${active.slug}. Change with \`compute org use <slug>\`.`
|
|
3007
2256
|
);
|
|
3008
|
-
}
|
|
3009
|
-
p.log.
|
|
2257
|
+
} else {
|
|
2258
|
+
p.log.success(`Logged in as ${who} \u2014 no active org`);
|
|
3010
2259
|
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
spinner4.stop(pc5.red("Authentication failed"));
|
|
3014
|
-
throw error;
|
|
2260
|
+
} catch {
|
|
2261
|
+
p.log.warn("Logged in, but could not fetch session info.");
|
|
3015
2262
|
}
|
|
3016
2263
|
}
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
import pc6 from "picocolors";
|
|
3022
|
-
import { compute as compute2 } from "computesdk";
|
|
3023
|
-
async function ensureAuth(options) {
|
|
3024
|
-
try {
|
|
3025
|
-
const apiKey = await resolveApiKey({ forceLogin: options?.forceLogin });
|
|
3026
|
-
process.env.COMPUTESDK_API_KEY = apiKey;
|
|
3027
|
-
} catch (error) {
|
|
3028
|
-
p2.log.error(error.message);
|
|
3029
|
-
p2.log.info(`Get your API key at ${pc6.cyan("https://console.computesdk.com")}`);
|
|
3030
|
-
p2.outro(pc6.red("Setup incomplete"));
|
|
3031
|
-
process.exit(1);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
async function resolveProvider(providerFlag, options) {
|
|
3035
|
-
const interactive = options?.interactive ?? false;
|
|
3036
|
-
const available = detectAvailableProviders();
|
|
3037
|
-
const providerStatus = getProviderStatus();
|
|
3038
|
-
if (providerFlag) {
|
|
3039
|
-
const match = providerStatus.find(
|
|
3040
|
-
(s) => s.name.toLowerCase() === providerFlag.toLowerCase()
|
|
3041
|
-
);
|
|
3042
|
-
if (!match) {
|
|
3043
|
-
p2.log.error(`Unknown provider: ${providerFlag}`);
|
|
3044
|
-
p2.log.info(`Available providers: ${providerStatus.map((s) => s.name).join(", ")}`);
|
|
3045
|
-
process.exit(1);
|
|
3046
|
-
}
|
|
3047
|
-
if (!match.ready) {
|
|
3048
|
-
p2.log.error(`Provider ${providerFlag} is not configured`);
|
|
3049
|
-
if (match.missing.length > 0) {
|
|
3050
|
-
p2.log.info(`Missing env vars: ${match.missing.join(", ")}`);
|
|
3051
|
-
}
|
|
3052
|
-
process.exit(1);
|
|
3053
|
-
}
|
|
3054
|
-
return match.name;
|
|
3055
|
-
}
|
|
3056
|
-
if (available.length === 0) {
|
|
3057
|
-
p2.log.error("No provider credentials detected");
|
|
3058
|
-
p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
|
|
3059
|
-
p2.outro(pc6.red("Setup incomplete"));
|
|
3060
|
-
process.exit(1);
|
|
3061
|
-
}
|
|
3062
|
-
if (available.length === 1) {
|
|
3063
|
-
return available[0];
|
|
3064
|
-
}
|
|
3065
|
-
if (!interactive) {
|
|
3066
|
-
p2.log.error("Multiple providers configured. Use --provider to specify one.");
|
|
3067
|
-
p2.log.info(`Available: ${available.join(", ")}`);
|
|
3068
|
-
process.exit(1);
|
|
3069
|
-
}
|
|
3070
|
-
const providerOptions = providerStatus.filter((status) => status.ready).map((status) => ({
|
|
3071
|
-
value: status.name,
|
|
3072
|
-
label: status.name
|
|
3073
|
-
}));
|
|
3074
|
-
const provider = await p2.select({
|
|
3075
|
-
message: "Select provider",
|
|
3076
|
-
options: providerOptions
|
|
2264
|
+
async function runLogin(options) {
|
|
2265
|
+
const { oauthLogin } = await import("@benchsdk/cli");
|
|
2266
|
+
await oauthLogin({
|
|
2267
|
+
baseUrl: options?.baseUrl ?? envBaseUrl()
|
|
3077
2268
|
});
|
|
3078
|
-
if (p2.isCancel(provider)) {
|
|
3079
|
-
p2.cancel("Cancelled");
|
|
3080
|
-
process.exit(0);
|
|
3081
|
-
}
|
|
3082
|
-
return provider;
|
|
3083
2269
|
}
|
|
3084
|
-
function
|
|
3085
|
-
const
|
|
3086
|
-
|
|
2270
|
+
async function runLogout() {
|
|
2271
|
+
const { clearCredentials } = await import("@benchsdk/cli");
|
|
2272
|
+
await clearCredentials();
|
|
3087
2273
|
}
|
|
3088
|
-
|
|
3089
|
-
// src/index.ts
|
|
3090
|
-
init_workspace_cli();
|
|
3091
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3092
|
-
var __dirname2 = dirname2(__filename2);
|
|
3093
|
-
var packageJson = JSON.parse(
|
|
3094
|
-
readFileSync5(join5(__dirname2, "..", "package.json"), "utf-8")
|
|
3095
|
-
);
|
|
3096
|
-
var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
|
|
3097
|
-
var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
|
|
3098
|
-
var program = new Command();
|
|
3099
|
-
program.name("compute").description("Cloud-native workspace management").version(VERSION).enablePositionalOptions().passThroughOptions();
|
|
3100
|
-
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
|
|
2274
|
+
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").option("--org <slug>", "organization slug override for this invocation (or $COMPUTE_ORG)").action(async (opts) => {
|
|
3101
2275
|
if (opts.logout) {
|
|
3102
2276
|
console.log();
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
2277
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2278
|
+
await runLogout();
|
|
2279
|
+
p.log.success("Logged out. Stored credentials removed.");
|
|
2280
|
+
p.outro(pc4.green("Done!"));
|
|
3107
2281
|
process.exit(0);
|
|
3108
2282
|
}
|
|
3109
2283
|
if (opts.login) {
|
|
3110
2284
|
console.log();
|
|
3111
|
-
|
|
3112
|
-
await
|
|
3113
|
-
|
|
2285
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2286
|
+
await runLogin();
|
|
2287
|
+
await printSessionLine();
|
|
2288
|
+
p.outro(pc4.green("Authenticated!"));
|
|
3114
2289
|
process.exit(0);
|
|
3115
2290
|
}
|
|
3116
2291
|
program.help();
|
|
3117
2292
|
});
|
|
3118
|
-
program.
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
await ensureAuth();
|
|
3122
|
-
if (!repo) {
|
|
3123
|
-
const state = loadWorkspaceState();
|
|
3124
|
-
const workspaceIds = Object.keys(state.workspaces);
|
|
3125
|
-
if (workspaceIds.length > 0) {
|
|
3126
|
-
const choices = workspaceIds.map((id) => {
|
|
3127
|
-
const ws = state.workspaces[id];
|
|
3128
|
-
const age = Math.round(
|
|
3129
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3130
|
-
);
|
|
3131
|
-
const ageStr = age === 0 ? "today" : `${age}d ago`;
|
|
3132
|
-
return {
|
|
3133
|
-
value: id,
|
|
3134
|
-
label: `${id} (${ws.provider}, ${ageStr})`
|
|
3135
|
-
};
|
|
3136
|
-
});
|
|
3137
|
-
const selected = await p4.select({
|
|
3138
|
-
message: "Select workspace to attach (or create new)",
|
|
3139
|
-
options: [
|
|
3140
|
-
...choices,
|
|
3141
|
-
{ value: "__new__", label: pc8.gray("+ Create new workspace") }
|
|
3142
|
-
]
|
|
3143
|
-
});
|
|
3144
|
-
if (p4.isCancel(selected)) {
|
|
3145
|
-
p4.cancel("Cancelled");
|
|
3146
|
-
process.exit(0);
|
|
3147
|
-
}
|
|
3148
|
-
if (selected !== "__new__") {
|
|
3149
|
-
await workspaceAttach(selected);
|
|
3150
|
-
return;
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3153
|
-
const repoInput = await p4.text({
|
|
3154
|
-
message: "Repository to work on?",
|
|
3155
|
-
placeholder: "owner/repo",
|
|
3156
|
-
validate: (value) => {
|
|
3157
|
-
if (!value || !value.includes("/")) {
|
|
3158
|
-
return "Please enter a repository in owner/repo format";
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3162
|
-
if (p4.isCancel(repoInput)) {
|
|
3163
|
-
p4.cancel("Cancelled");
|
|
3164
|
-
process.exit(0);
|
|
3165
|
-
}
|
|
3166
|
-
repo = repoInput;
|
|
3167
|
-
}
|
|
3168
|
-
if (!repo.includes("/")) {
|
|
3169
|
-
p4.log.error("Invalid repository format. Use: owner/repo");
|
|
3170
|
-
process.exit(1);
|
|
3171
|
-
}
|
|
3172
|
-
const provider = await resolveProvider(options.provider, { interactive: true });
|
|
3173
|
-
configureCompute(provider);
|
|
3174
|
-
const spinner4 = p4.spinner();
|
|
3175
|
-
spinner4.start("Creating workspace...");
|
|
3176
|
-
try {
|
|
3177
|
-
const workspace = await workspaceCreate({
|
|
3178
|
-
repo,
|
|
3179
|
-
branch: options.branch,
|
|
3180
|
-
provider
|
|
3181
|
-
});
|
|
3182
|
-
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
3183
|
-
if (options.attach !== false) {
|
|
3184
|
-
console.log();
|
|
3185
|
-
await workspaceAttach(workspace.id);
|
|
3186
|
-
} else {
|
|
3187
|
-
p4.outro(pc8.green("Workspace created!"));
|
|
3188
|
-
}
|
|
3189
|
-
} catch (error) {
|
|
3190
|
-
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
3191
|
-
p4.log.error(error.message);
|
|
3192
|
-
process.exit(1);
|
|
3193
|
-
}
|
|
3194
|
-
});
|
|
3195
|
-
var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw sandboxes (VMs without git/worktree)");
|
|
3196
|
-
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) => {
|
|
3197
|
-
if (!opts.silent) {
|
|
3198
|
-
console.log();
|
|
3199
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3200
|
-
}
|
|
3201
|
-
await ensureAuth();
|
|
3202
|
-
const provider = await resolveProvider(opts.provider);
|
|
3203
|
-
configureCompute(provider);
|
|
3204
|
-
if (!opts.silent) {
|
|
3205
|
-
const spinner4 = p4.spinner();
|
|
3206
|
-
spinner4.start("Creating sandbox...");
|
|
3207
|
-
try {
|
|
3208
|
-
const sandbox = await compute4.sandbox.create(
|
|
3209
|
-
buildCreateOptions(opts)
|
|
3210
|
-
);
|
|
3211
|
-
spinner4.stop(`Sandbox ready: ${pc8.cyan(sandbox.sandboxId)}`);
|
|
3212
|
-
const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
3213
|
-
await saveSandbox2({
|
|
3214
|
-
id: sandbox.sandboxId,
|
|
3215
|
-
sandboxId: sandbox.sandboxId,
|
|
3216
|
-
provider,
|
|
3217
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3218
|
-
lastUsed: (/* @__PURE__ */ new Date()).toISOString()
|
|
3219
|
-
});
|
|
3220
|
-
if (opts.connect) {
|
|
3221
|
-
await startREPL(sandbox, provider);
|
|
3222
|
-
}
|
|
3223
|
-
} catch (error) {
|
|
3224
|
-
spinner4.stop(pc8.red("Failed to create sandbox"));
|
|
3225
|
-
p4.log.error(error.message);
|
|
3226
|
-
process.exit(1);
|
|
3227
|
-
}
|
|
3228
|
-
} else {
|
|
3229
|
-
try {
|
|
3230
|
-
const sandbox = await compute4.sandbox.create(
|
|
3231
|
-
buildCreateOptions(opts)
|
|
3232
|
-
);
|
|
3233
|
-
process.stdout.write(sandbox.sandboxId);
|
|
3234
|
-
const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
3235
|
-
await saveSandbox2({
|
|
3236
|
-
id: sandbox.sandboxId,
|
|
3237
|
-
sandboxId: sandbox.sandboxId,
|
|
3238
|
-
provider,
|
|
3239
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3240
|
-
lastUsed: (/* @__PURE__ */ new Date()).toISOString()
|
|
3241
|
-
});
|
|
3242
|
-
if (opts.connect) {
|
|
3243
|
-
await startREPL(sandbox, provider);
|
|
3244
|
-
}
|
|
3245
|
-
} catch (error) {
|
|
3246
|
-
process.stderr.write(error.message + "\n");
|
|
3247
|
-
process.exit(1);
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
});
|
|
3251
|
-
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) => {
|
|
3252
|
-
console.log();
|
|
3253
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3254
|
-
const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
3255
|
-
const state = loadState2();
|
|
3256
|
-
if (!sandboxIds || sandboxIds.length === 0) {
|
|
3257
|
-
const workspaces = Object.values(state.workspaces);
|
|
3258
|
-
if (workspaces.length === 0) {
|
|
3259
|
-
p4.log.info("No sandboxes to destroy.");
|
|
3260
|
-
p4.outro(pc8.green("Done!"));
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
|
-
const choices = workspaces.map((ws) => {
|
|
3264
|
-
const age = Math.round(
|
|
3265
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3266
|
-
);
|
|
3267
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
3268
|
-
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
3269
|
-
return {
|
|
3270
|
-
value: ws.sandboxId,
|
|
3271
|
-
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
3272
|
-
};
|
|
3273
|
-
});
|
|
3274
|
-
const selected = await p4.multiselect({
|
|
3275
|
-
message: "Select sandboxes to destroy (space to select, enter to confirm)",
|
|
3276
|
-
options: choices
|
|
3277
|
-
});
|
|
3278
|
-
if (p4.isCancel(selected) || selected.length === 0) {
|
|
3279
|
-
p4.cancel("Cancelled");
|
|
3280
|
-
process.exit(0);
|
|
3281
|
-
}
|
|
3282
|
-
sandboxIds = selected;
|
|
3283
|
-
}
|
|
3284
|
-
await ensureAuth();
|
|
3285
|
-
const provider = await resolveProvider(opts.provider);
|
|
3286
|
-
configureCompute(provider);
|
|
3287
|
-
const results = await Promise.allSettled(
|
|
3288
|
-
sandboxIds.map(async (id) => {
|
|
3289
|
-
await compute4.sandbox.destroy(id);
|
|
3290
|
-
return id;
|
|
3291
|
-
})
|
|
3292
|
-
);
|
|
3293
|
-
for (const result of results) {
|
|
3294
|
-
if (result.status === "fulfilled") {
|
|
3295
|
-
const sandboxId = result.value;
|
|
3296
|
-
console.log(pc8.green(`Destroyed ${sandboxId}`));
|
|
3297
|
-
const workspaceId = Object.keys(state.workspaces).find(
|
|
3298
|
-
(key) => state.workspaces[key].sandboxId === sandboxId
|
|
3299
|
-
);
|
|
3300
|
-
if (workspaceId) {
|
|
3301
|
-
delete state.workspaces[workspaceId];
|
|
3302
|
-
}
|
|
3303
|
-
} else {
|
|
3304
|
-
console.error(pc8.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
saveState2(state);
|
|
3308
|
-
});
|
|
3309
|
-
sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
|
|
3310
|
-
console.log();
|
|
3311
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3312
|
-
await sandboxList();
|
|
3313
|
-
});
|
|
3314
|
-
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) => {
|
|
3315
|
-
console.log();
|
|
3316
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3317
|
-
await ensureAuth();
|
|
3318
|
-
const provider = await resolveProvider(opts.provider);
|
|
3319
|
-
configureCompute(provider);
|
|
3320
|
-
if (!sandboxId) {
|
|
3321
|
-
const { loadState: loadState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
3322
|
-
const state = loadState2();
|
|
3323
|
-
const workspaces = Object.values(state.workspaces);
|
|
3324
|
-
if (workspaces.length === 0) {
|
|
3325
|
-
p4.log.info("No sandboxes found.");
|
|
3326
|
-
p4.log.info("Create one with: compute sandbox create");
|
|
3327
|
-
p4.outro(pc8.green("Done!"));
|
|
3328
|
-
return;
|
|
3329
|
-
}
|
|
3330
|
-
const choices = workspaces.map((ws) => {
|
|
3331
|
-
const age = Math.round(
|
|
3332
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3333
|
-
);
|
|
3334
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
3335
|
-
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
3336
|
-
return {
|
|
3337
|
-
value: ws.sandboxId,
|
|
3338
|
-
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
3339
|
-
};
|
|
3340
|
-
});
|
|
3341
|
-
const selected = await p4.select({
|
|
3342
|
-
message: "Select sandbox to connect",
|
|
3343
|
-
options: choices
|
|
3344
|
-
});
|
|
3345
|
-
if (p4.isCancel(selected)) {
|
|
3346
|
-
p4.cancel("Cancelled");
|
|
3347
|
-
process.exit(0);
|
|
3348
|
-
}
|
|
3349
|
-
sandboxId = selected;
|
|
3350
|
-
}
|
|
3351
|
-
const spinner4 = p4.spinner();
|
|
3352
|
-
spinner4.start("Connecting...");
|
|
3353
|
-
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
3354
|
-
if (!sandbox) {
|
|
3355
|
-
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
3356
|
-
process.exit(1);
|
|
3357
|
-
}
|
|
3358
|
-
spinner4.stop("");
|
|
3359
|
-
await startREPL(sandbox, provider);
|
|
3360
|
-
});
|
|
3361
|
-
sandboxCmd.command("exec").description("Execute a command in a sandbox").argument("<sandbox_id>", "sandbox ID").argument("<command...>", "command to run").option("-p, --provider <provider>", "provider to use").option("-e, --env <key=value...>", "environment variables").option("-w, --workdir <directory>", "working directory").passThroughOptions().action(async (sandboxId, command, opts) => {
|
|
3362
|
-
await ensureAuth();
|
|
3363
|
-
const provider = await resolveProvider(opts.provider);
|
|
3364
|
-
configureCompute(provider);
|
|
3365
|
-
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
3366
|
-
if (!sandbox) {
|
|
3367
|
-
console.error(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
3368
|
-
process.exit(1);
|
|
3369
|
-
}
|
|
3370
|
-
const cmd = buildExecCommand(command, opts);
|
|
3371
|
-
const result = await sandbox.runCommand(cmd);
|
|
3372
|
-
if (result.stdout) {
|
|
3373
|
-
process.stdout.write(result.stdout);
|
|
3374
|
-
}
|
|
3375
|
-
if (result.stderr) {
|
|
3376
|
-
process.stderr.write(result.stderr);
|
|
3377
|
-
}
|
|
3378
|
-
process.exit(result.exitCode);
|
|
3379
|
-
});
|
|
3380
|
-
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) => {
|
|
3381
|
-
console.log();
|
|
3382
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3383
|
-
await ensureAuth();
|
|
3384
|
-
const provider = await resolveProvider(opts.provider);
|
|
3385
|
-
configureCompute(provider);
|
|
3386
|
-
if (!sandboxId) {
|
|
3387
|
-
const { loadState: loadState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
3388
|
-
const state = loadState2();
|
|
3389
|
-
const workspaces = Object.values(state.workspaces);
|
|
3390
|
-
if (workspaces.length === 0) {
|
|
3391
|
-
p4.log.info("No sandboxes found.");
|
|
3392
|
-
p4.log.info("Create one with: compute sandbox create");
|
|
3393
|
-
p4.outro(pc8.green("Done!"));
|
|
3394
|
-
return;
|
|
3395
|
-
}
|
|
3396
|
-
const choices = workspaces.map((ws) => {
|
|
3397
|
-
const age = Math.round(
|
|
3398
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3399
|
-
);
|
|
3400
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
3401
|
-
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
3402
|
-
return {
|
|
3403
|
-
value: ws.sandboxId,
|
|
3404
|
-
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
3405
|
-
};
|
|
3406
|
-
});
|
|
3407
|
-
const selected = await p4.select({
|
|
3408
|
-
message: "Select sandbox to connect",
|
|
3409
|
-
options: choices
|
|
3410
|
-
});
|
|
3411
|
-
if (p4.isCancel(selected)) {
|
|
3412
|
-
p4.cancel("Cancelled");
|
|
3413
|
-
process.exit(0);
|
|
3414
|
-
}
|
|
3415
|
-
sandboxId = selected;
|
|
3416
|
-
}
|
|
3417
|
-
const spinner4 = p4.spinner();
|
|
3418
|
-
spinner4.start("Connecting...");
|
|
3419
|
-
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
3420
|
-
if (!sandbox) {
|
|
3421
|
-
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
3422
|
-
process.exit(1);
|
|
3423
|
-
}
|
|
3424
|
-
spinner4.stop("");
|
|
3425
|
-
await startREPL(sandbox, provider);
|
|
3426
|
-
});
|
|
3427
|
-
var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
|
|
3428
|
-
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) => {
|
|
3429
|
-
console.log();
|
|
3430
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3431
|
-
await ensureAuth();
|
|
3432
|
-
if (!repo) {
|
|
3433
|
-
p4.log.info("Usage: compute workspace create owner/repo --branch main");
|
|
3434
|
-
p4.outro(pc8.green("Done!"));
|
|
3435
|
-
return;
|
|
3436
|
-
}
|
|
3437
|
-
const provider = await resolveProvider(options.provider, { interactive: true });
|
|
3438
|
-
configureCompute(provider);
|
|
3439
|
-
const spinner4 = p4.spinner();
|
|
3440
|
-
spinner4.start("Creating workspace...");
|
|
3441
|
-
try {
|
|
3442
|
-
const workspace = await workspaceCreate({
|
|
3443
|
-
repo,
|
|
3444
|
-
branch: options.branch,
|
|
3445
|
-
provider
|
|
3446
|
-
});
|
|
3447
|
-
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
3448
|
-
if (options.attach !== false) {
|
|
3449
|
-
console.log();
|
|
3450
|
-
await workspaceAttach(workspace.id);
|
|
3451
|
-
} else {
|
|
3452
|
-
p4.outro(pc8.green("Workspace created!"));
|
|
3453
|
-
}
|
|
3454
|
-
} catch (error) {
|
|
3455
|
-
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
3456
|
-
p4.log.error(error.message);
|
|
3457
|
-
process.exit(1);
|
|
3458
|
-
}
|
|
3459
|
-
});
|
|
3460
|
-
workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
|
|
3461
|
-
console.log();
|
|
3462
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3463
|
-
await workspaceList();
|
|
3464
|
-
});
|
|
3465
|
-
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) => {
|
|
3466
|
-
console.log();
|
|
3467
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3468
|
-
if (!id) {
|
|
3469
|
-
const state = loadWorkspaceState();
|
|
3470
|
-
const workspaceIds = Object.keys(state.workspaces);
|
|
3471
|
-
if (workspaceIds.length === 0) {
|
|
3472
|
-
p4.log.info("No workspaces to destroy.");
|
|
3473
|
-
p4.outro(pc8.green("Done!"));
|
|
3474
|
-
return;
|
|
3475
|
-
}
|
|
3476
|
-
const choices = workspaceIds.map((key) => {
|
|
3477
|
-
const ws = state.workspaces[key];
|
|
3478
|
-
const age = Math.round(
|
|
3479
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3480
|
-
);
|
|
3481
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
3482
|
-
return {
|
|
3483
|
-
value: key,
|
|
3484
|
-
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
3485
|
-
};
|
|
3486
|
-
});
|
|
3487
|
-
const selected = await p4.multiselect({
|
|
3488
|
-
message: "Select workspaces to destroy (space to select, enter to confirm)",
|
|
3489
|
-
options: choices
|
|
3490
|
-
});
|
|
3491
|
-
if (p4.isCancel(selected) || selected.length === 0) {
|
|
3492
|
-
p4.cancel("Cancelled");
|
|
3493
|
-
process.exit(0);
|
|
3494
|
-
}
|
|
3495
|
-
for (const workspaceId of selected) {
|
|
3496
|
-
await workspaceDestroy(workspaceId);
|
|
3497
|
-
}
|
|
3498
|
-
} else {
|
|
3499
|
-
await workspaceDestroy(id);
|
|
3500
|
-
}
|
|
3501
|
-
});
|
|
3502
|
-
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) => {
|
|
3503
|
-
console.log();
|
|
3504
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
3505
|
-
if (!id) {
|
|
3506
|
-
const state = loadWorkspaceState();
|
|
3507
|
-
const workspaceIds = Object.keys(state.workspaces);
|
|
3508
|
-
if (workspaceIds.length === 0) {
|
|
3509
|
-
p4.log.info("No workspaces found.");
|
|
3510
|
-
p4.log.info("Create one with: compute create owner/repo");
|
|
3511
|
-
p4.outro(pc8.green("Done!"));
|
|
3512
|
-
return;
|
|
3513
|
-
}
|
|
3514
|
-
const choices = workspaceIds.map((key) => {
|
|
3515
|
-
const ws = state.workspaces[key];
|
|
3516
|
-
const age = Math.round(
|
|
3517
|
-
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3518
|
-
);
|
|
3519
|
-
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
3520
|
-
return {
|
|
3521
|
-
value: key,
|
|
3522
|
-
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
3523
|
-
};
|
|
3524
|
-
});
|
|
3525
|
-
const selected = await p4.select({
|
|
3526
|
-
message: "Select workspace to connect",
|
|
3527
|
-
options: choices
|
|
3528
|
-
});
|
|
3529
|
-
if (p4.isCancel(selected)) {
|
|
3530
|
-
p4.cancel("Cancelled");
|
|
3531
|
-
process.exit(0);
|
|
3532
|
-
}
|
|
3533
|
-
id = selected;
|
|
3534
|
-
}
|
|
3535
|
-
await workspaceAttach(id);
|
|
3536
|
-
});
|
|
3537
|
-
program.command("run").description("Create a sandbox, run a command, then optionally destroy it").argument("<command...>", "command to run").option("-p, --provider <provider>", "provider to use").option("-e, --env <key=value...>", "environment variables").option("-w, --workdir <directory>", "working directory").passThroughOptions().option("--rm", "destroy sandbox after command finishes").option("--timeout <duration>", "sandbox timeout (e.g. 5m, 1h)").option("--name <name>", "sandbox name").action(async (command, opts) => {
|
|
3538
|
-
await ensureAuth();
|
|
3539
|
-
const provider = await resolveProvider(opts.provider);
|
|
3540
|
-
configureCompute(provider);
|
|
3541
|
-
let sandbox;
|
|
3542
|
-
try {
|
|
3543
|
-
sandbox = await compute4.sandbox.create(
|
|
3544
|
-
buildCreateOptions(opts)
|
|
3545
|
-
);
|
|
3546
|
-
} catch (error) {
|
|
3547
|
-
console.error(pc8.red(`Failed to create sandbox: ${error.message}`));
|
|
3548
|
-
process.exit(1);
|
|
3549
|
-
}
|
|
3550
|
-
try {
|
|
3551
|
-
const cmd = buildExecCommand(command, opts);
|
|
3552
|
-
const result = await sandbox.runCommand(cmd);
|
|
3553
|
-
if (result.stdout) {
|
|
3554
|
-
process.stdout.write(result.stdout);
|
|
3555
|
-
}
|
|
3556
|
-
if (result.stderr) {
|
|
3557
|
-
process.stderr.write(result.stderr);
|
|
3558
|
-
}
|
|
3559
|
-
if (opts.rm) {
|
|
3560
|
-
await sandbox.destroy();
|
|
3561
|
-
}
|
|
3562
|
-
process.exit(result.exitCode);
|
|
3563
|
-
} catch (error) {
|
|
3564
|
-
if (opts.rm) {
|
|
3565
|
-
try {
|
|
3566
|
-
await sandbox.destroy();
|
|
3567
|
-
} catch {
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
console.error(pc8.red(error.message));
|
|
3571
|
-
process.exit(1);
|
|
3572
|
-
}
|
|
2293
|
+
program.hook("preAction", (thisCommand) => {
|
|
2294
|
+
const org2 = thisCommand.opts().org;
|
|
2295
|
+
if (org2) process.env.COMPUTE_ORG = org2;
|
|
3573
2296
|
});
|
|
3574
2297
|
program.command("providers").description("List all providers and their configuration status").action(async () => {
|
|
3575
2298
|
console.log();
|
|
3576
|
-
|
|
2299
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
3577
2300
|
const statuses = getProviderStatus();
|
|
3578
2301
|
console.log();
|
|
3579
2302
|
for (const status of statuses) {
|
|
3580
|
-
const icon = status.ready ?
|
|
3581
|
-
const name = status.ready ?
|
|
3582
|
-
const detail = status.ready ?
|
|
2303
|
+
const icon = status.ready ? pc4.green("\u25CF") : pc4.gray("\u25CB");
|
|
2304
|
+
const name = status.ready ? pc4.white(status.name) : pc4.gray(status.name);
|
|
2305
|
+
const detail = status.ready ? pc4.green("ready") : pc4.gray(`missing: ${status.missing.join(", ")}`);
|
|
3583
2306
|
console.log(` ${icon} ${name} ${detail}`);
|
|
3584
2307
|
}
|
|
3585
2308
|
console.log();
|
|
3586
2309
|
});
|
|
3587
|
-
program.command("login").description("Authenticate with ComputeSDK").action(async () => {
|
|
2310
|
+
program.command("login").description("Authenticate with ComputeSDK (OAuth device flow)").option("--base-url <url>", "platform URL to authenticate against").action(async (opts) => {
|
|
3588
2311
|
console.log();
|
|
3589
|
-
|
|
3590
|
-
await
|
|
3591
|
-
|
|
2312
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2313
|
+
await runLogin({ baseUrl: opts.baseUrl });
|
|
2314
|
+
await printSessionLine({ baseUrl: opts.baseUrl });
|
|
2315
|
+
p.outro(pc4.green("Authenticated!"));
|
|
3592
2316
|
});
|
|
3593
|
-
program.command("logout").description("Clear stored credentials").action(() => {
|
|
2317
|
+
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
3594
2318
|
console.log();
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
2319
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2320
|
+
await runLogout();
|
|
2321
|
+
p.log.success("Logged out. Stored credentials removed.");
|
|
2322
|
+
p.outro(pc4.green("Done!"));
|
|
3599
2323
|
});
|
|
2324
|
+
async function printWhoami() {
|
|
2325
|
+
const { getMe } = await import("@benchsdk/cli");
|
|
2326
|
+
const me = await getMe(await benchAuth());
|
|
2327
|
+
const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
|
|
2328
|
+
console.log(me.user.email ?? me.user.name ?? me.user.id);
|
|
2329
|
+
console.log(
|
|
2330
|
+
`active org: ${active ? `${active.slug} (${active.id})` : "\u2014"}${me.organizations.length > 1 ? " \u2014 change with `compute org use <slug>`" : ""}`
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
var org = program.command("org").description("Manage the active organization");
|
|
2334
|
+
org.command("list").description("List your organizations").option("--json", "print machine-readable JSON").action(async (opts) => {
|
|
2335
|
+
const { listOrganizations, getMe } = await import("@benchsdk/cli");
|
|
2336
|
+
const auth = await benchAuth();
|
|
2337
|
+
const [organizations, me] = await Promise.all([listOrganizations(auth), getMe(auth)]);
|
|
2338
|
+
if (opts.json) {
|
|
2339
|
+
console.log(JSON.stringify(organizations, null, 2));
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
for (const o of organizations) {
|
|
2343
|
+
const marker = o.id === me.activeOrganizationId ? pc4.green("*") : " ";
|
|
2344
|
+
console.log(`${marker} ${pc4.cyan(o.slug)} ${o.name ?? ""} ${pc4.dim(o.id)}`);
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
org.command("use <slug>").description("Set the active organization (persisted to the stored login)").action(async (slug) => {
|
|
2348
|
+
const { loadCredentials, saveCredentials, createApiClient, setActiveOrganization } = await import("@benchsdk/cli");
|
|
2349
|
+
const credentials = await loadCredentials() ?? {};
|
|
2350
|
+
const { auth } = await createApiClient({ baseUrl: envBaseUrl() });
|
|
2351
|
+
const result = await setActiveOrganization(auth, slug);
|
|
2352
|
+
if (!result.organization) {
|
|
2353
|
+
throw new Error(`Could not set active organization to ${slug}`);
|
|
2354
|
+
}
|
|
2355
|
+
await saveCredentials({
|
|
2356
|
+
...credentials,
|
|
2357
|
+
baseUrl: auth.baseUrl,
|
|
2358
|
+
// An API-key login must not be rewritten as an OAuth one — keep its
|
|
2359
|
+
// key and kind, and only stamp token fields when this auth actually
|
|
2360
|
+
// is a token session.
|
|
2361
|
+
...auth.token ? {
|
|
2362
|
+
token: auth.token,
|
|
2363
|
+
refreshToken: auth.refreshToken,
|
|
2364
|
+
tokenExpiresAt: auth.tokenExpiresAt,
|
|
2365
|
+
refreshExpiresAt: auth.refreshExpiresAt,
|
|
2366
|
+
kind: "oauth"
|
|
2367
|
+
} : {},
|
|
2368
|
+
orgSlug: result.organization.slug,
|
|
2369
|
+
orgId: result.organization.id
|
|
2370
|
+
});
|
|
2371
|
+
console.log(`Active organization set to ${result.organization.slug} (${result.organization.id})`);
|
|
2372
|
+
});
|
|
2373
|
+
org.command("current").description("Show the current user and active organization").action(printWhoami);
|
|
2374
|
+
program.command("whoami").description("Show the current user and active organization").action(printWhoami);
|
|
3600
2375
|
registerActionsCommands(program);
|
|
3601
2376
|
registerMarketCommands(program);
|
|
3602
2377
|
registerSandboxesCommands(program);
|
|
3603
2378
|
program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
|
|
3604
2379
|
});
|
|
3605
|
-
function buildCreateOptions(opts) {
|
|
3606
|
-
const options = {};
|
|
3607
|
-
if (opts.timeout) {
|
|
3608
|
-
options.timeout = parseDuration(opts.timeout);
|
|
3609
|
-
}
|
|
3610
|
-
if (opts.name) {
|
|
3611
|
-
options.name = opts.name;
|
|
3612
|
-
}
|
|
3613
|
-
return options;
|
|
3614
|
-
}
|
|
3615
|
-
function shellEscape2(arg) {
|
|
3616
|
-
if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg)) return arg;
|
|
3617
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
3618
|
-
}
|
|
3619
|
-
function buildExecCommand(command, opts) {
|
|
3620
|
-
let cmd = command.map(shellEscape2).join(" ");
|
|
3621
|
-
if (opts.workdir) {
|
|
3622
|
-
cmd = `cd ${shellEscape2(opts.workdir)} && ${cmd}`;
|
|
3623
|
-
}
|
|
3624
|
-
if (opts.env && opts.env.length > 0) {
|
|
3625
|
-
const envPrefix = opts.env.map((entry) => {
|
|
3626
|
-
const eqIndex = entry.indexOf("=");
|
|
3627
|
-
if (eqIndex === -1) {
|
|
3628
|
-
throw new Error(
|
|
3629
|
-
`Invalid environment entry "${entry}". Expected format KEY=VALUE.`
|
|
3630
|
-
);
|
|
3631
|
-
}
|
|
3632
|
-
const key = entry.slice(0, eqIndex);
|
|
3633
|
-
const val = entry.slice(eqIndex + 1);
|
|
3634
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
3635
|
-
throw new Error(
|
|
3636
|
-
`Invalid environment variable name "${key}" in entry "${entry}".`
|
|
3637
|
-
);
|
|
3638
|
-
}
|
|
3639
|
-
return `${key}=${shellEscape2(val)}`;
|
|
3640
|
-
}).join(" ");
|
|
3641
|
-
cmd = `${envPrefix} ${cmd}`;
|
|
3642
|
-
}
|
|
3643
|
-
return cmd;
|
|
3644
|
-
}
|
|
3645
|
-
function parseDuration(input) {
|
|
3646
|
-
let total = 0;
|
|
3647
|
-
const pattern = /(\d+)\s*(s|m|h)/gi;
|
|
3648
|
-
let match;
|
|
3649
|
-
while ((match = pattern.exec(input)) !== null) {
|
|
3650
|
-
const value = parseInt(match[1], 10);
|
|
3651
|
-
const unit = match[2].toLowerCase();
|
|
3652
|
-
switch (unit) {
|
|
3653
|
-
case "s":
|
|
3654
|
-
total += value * 1e3;
|
|
3655
|
-
break;
|
|
3656
|
-
case "m":
|
|
3657
|
-
total += value * 60 * 1e3;
|
|
3658
|
-
break;
|
|
3659
|
-
case "h":
|
|
3660
|
-
total += value * 60 * 60 * 1e3;
|
|
3661
|
-
break;
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
if (total === 0) {
|
|
3665
|
-
const num = parseInt(input, 10);
|
|
3666
|
-
if (!isNaN(num)) return num * 1e3;
|
|
3667
|
-
throw new Error(`Invalid duration: ${input}. Use formats like 30s, 5m, 1h`);
|
|
3668
|
-
}
|
|
3669
|
-
return total;
|
|
3670
|
-
}
|
|
3671
2380
|
async function main() {
|
|
3672
2381
|
if (process.argv[2] === "bench" || process.argv[2] === "benchmark") {
|
|
3673
2382
|
const { run: benchRun } = await import("@benchsdk/runner");
|
|
@@ -3677,11 +2386,8 @@ async function main() {
|
|
|
3677
2386
|
await program.parseAsync(process.argv);
|
|
3678
2387
|
}
|
|
3679
2388
|
main().catch((error) => {
|
|
3680
|
-
console.error(
|
|
2389
|
+
console.error(pc4.red(`
|
|
3681
2390
|
Error: ${error.message}`));
|
|
3682
2391
|
process.exit(1);
|
|
3683
2392
|
});
|
|
3684
|
-
export {
|
|
3685
|
-
shellEscape2 as shellEscape
|
|
3686
|
-
};
|
|
3687
2393
|
//# sourceMappingURL=index.js.map
|