@computesdk/cli 1.0.16 → 2.0.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 +50 -0
- package/dist/index.d.ts +1 -19
- package/dist/index.js +104 -1482
- 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,18 +52,6 @@ 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
57
|
const hasGatewayKey = !!process.env.COMPUTESDK_API_KEY;
|
|
@@ -665,40 +69,13 @@ function getProviderStatus() {
|
|
|
665
69
|
}
|
|
666
70
|
return statuses;
|
|
667
71
|
}
|
|
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
72
|
|
|
694
73
|
// src/actions.ts
|
|
695
|
-
init_esm_shims();
|
|
696
74
|
import pc from "picocolors";
|
|
697
75
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
698
76
|
import { basename, join } from "path";
|
|
699
77
|
|
|
700
78
|
// src/actions-client.ts
|
|
701
|
-
init_esm_shims();
|
|
702
79
|
var DEFAULT_BASE_URL = "https://platform.computesdk.com";
|
|
703
80
|
var ActionsApiError = class extends Error {
|
|
704
81
|
constructor(status, message, body) {
|
|
@@ -747,11 +124,12 @@ function errorDetails(body) {
|
|
|
747
124
|
function toErrorEnvelope(error) {
|
|
748
125
|
if (error instanceof ActionsApiError) {
|
|
749
126
|
const details = errorDetails(error.body);
|
|
127
|
+
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
128
|
return {
|
|
751
129
|
ok: false,
|
|
752
130
|
error: {
|
|
753
131
|
code: codeForStatus(error.status),
|
|
754
|
-
message: error.message,
|
|
132
|
+
message: insufficientScope ? `${error.message} \u2014 run \`compute login\` to sign in again.` : error.message,
|
|
755
133
|
httpStatus: error.status,
|
|
756
134
|
retryable: error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504,
|
|
757
135
|
...details !== void 0 && { details }
|
|
@@ -792,8 +170,8 @@ var ActionsClient = class {
|
|
|
792
170
|
this.orgPromise ?? (this.orgPromise = this.get("/api/v1/actions/org"));
|
|
793
171
|
return this.orgPromise;
|
|
794
172
|
}
|
|
795
|
-
async get(
|
|
796
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
173
|
+
async get(path, params) {
|
|
174
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
797
175
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
798
176
|
if (value === void 0) continue;
|
|
799
177
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -801,32 +179,32 @@ var ActionsClient = class {
|
|
|
801
179
|
const res = await this.fetchImpl(url, { headers: this.headers() });
|
|
802
180
|
return this.parse(res);
|
|
803
181
|
}
|
|
804
|
-
async post(
|
|
805
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
182
|
+
async post(path, body = {}) {
|
|
183
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
806
184
|
method: "POST",
|
|
807
185
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
808
186
|
body: JSON.stringify(body)
|
|
809
187
|
});
|
|
810
188
|
return this.parse(res);
|
|
811
189
|
}
|
|
812
|
-
async put(
|
|
813
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
190
|
+
async put(path, body = {}) {
|
|
191
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
814
192
|
method: "PUT",
|
|
815
193
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
816
194
|
body: JSON.stringify(body)
|
|
817
195
|
});
|
|
818
196
|
return this.parse(res);
|
|
819
197
|
}
|
|
820
|
-
async patch(
|
|
821
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
198
|
+
async patch(path, body = {}) {
|
|
199
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
822
200
|
method: "PATCH",
|
|
823
201
|
headers: { ...this.headers(), "content-type": "application/json" },
|
|
824
202
|
body: JSON.stringify(body)
|
|
825
203
|
});
|
|
826
204
|
return this.parse(res);
|
|
827
205
|
}
|
|
828
|
-
async del(
|
|
829
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
206
|
+
async del(path) {
|
|
207
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
830
208
|
method: "DELETE",
|
|
831
209
|
headers: this.headers()
|
|
832
210
|
});
|
|
@@ -836,8 +214,8 @@ var ActionsClient = class {
|
|
|
836
214
|
* One `data:` JSON payload per SSE event. `next()` resolves to the payload,
|
|
837
215
|
* or null when the stream ends. Call `close()` to abort.
|
|
838
216
|
*/
|
|
839
|
-
async *sse(
|
|
840
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
217
|
+
async *sse(path, params) {
|
|
218
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
841
219
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
842
220
|
if (value === void 0) continue;
|
|
843
221
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -856,8 +234,8 @@ var ActionsClient = class {
|
|
|
856
234
|
* events are distinguished by name rather than payload shape (process
|
|
857
235
|
* follow: stdout/stderr share a `{chunk}` payload).
|
|
858
236
|
*/
|
|
859
|
-
async *sseNamed(
|
|
860
|
-
const url = new URL(`${this.auth.baseUrl}${
|
|
237
|
+
async *sseNamed(path, params) {
|
|
238
|
+
const url = new URL(`${this.auth.baseUrl}${path}`);
|
|
861
239
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
862
240
|
if (value === void 0) continue;
|
|
863
241
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
@@ -872,8 +250,8 @@ var ActionsClient = class {
|
|
|
872
250
|
yield* readSseNamedEvents(res.body);
|
|
873
251
|
}
|
|
874
252
|
/** Download bytes following redirects (artifact URLs 302 to storage). */
|
|
875
|
-
async download(
|
|
876
|
-
const res = await this.fetchImpl(`${this.auth.baseUrl}${
|
|
253
|
+
async download(path) {
|
|
254
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path}`, {
|
|
877
255
|
headers: this.headers(),
|
|
878
256
|
redirect: "follow"
|
|
879
257
|
});
|
|
@@ -954,7 +332,7 @@ function encodeWatchCursor(id, jobId, step, offset) {
|
|
|
954
332
|
const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
|
|
955
333
|
return `${id}:${jobId}:${stepPart}:${offset}`;
|
|
956
334
|
}
|
|
957
|
-
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute
|
|
335
|
+
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute login`.";
|
|
958
336
|
async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAuth) {
|
|
959
337
|
const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
|
|
960
338
|
DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
@@ -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.");
|
|
@@ -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);
|
|
@@ -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) {
|
|
@@ -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) {
|
|
@@ -2849,825 +2225,74 @@ async function followProcess(c, sandboxId, jobId) {
|
|
|
2849
2225
|
}
|
|
2850
2226
|
|
|
2851
2227
|
// src/index.ts
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
// src/auth.ts
|
|
2855
|
-
init_esm_shims();
|
|
2856
|
-
import * as http from "http";
|
|
2857
|
-
import * as crypto from "crypto";
|
|
2858
|
-
import { exec } from "child_process";
|
|
2859
|
-
import * as fs2 from "fs";
|
|
2860
|
-
import * as path3 from "path";
|
|
2861
|
-
import * as os2 from "os";
|
|
2862
|
-
import * as p from "@clack/prompts";
|
|
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
|
-
});
|
|
2924
|
-
}
|
|
2925
|
-
function runBrowserAuthFlow(options) {
|
|
2926
|
-
const timeoutMs = options?.timeoutMs ?? AUTH_TIMEOUT_MS;
|
|
2927
|
-
const state = crypto.randomBytes(32).toString("hex");
|
|
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
|
-
});
|
|
2983
|
-
}
|
|
2984
|
-
async function resolveApiKey(options) {
|
|
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...");
|
|
3000
|
-
try {
|
|
3001
|
-
const apiKey = await runBrowserAuthFlow();
|
|
3002
|
-
spinner4.stop("Authentication successful");
|
|
3003
|
-
try {
|
|
3004
|
-
storeCredentials(apiKey);
|
|
3005
|
-
p.log.success(
|
|
3006
|
-
`Credentials saved to ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
3007
|
-
);
|
|
3008
|
-
} catch {
|
|
3009
|
-
p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
|
|
3010
|
-
}
|
|
3011
|
-
return apiKey;
|
|
3012
|
-
} catch (error) {
|
|
3013
|
-
spinner4.stop(pc5.red("Authentication failed"));
|
|
3014
|
-
throw error;
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
|
-
// src/setup.ts
|
|
3019
|
-
init_esm_shims();
|
|
3020
|
-
import * as p2 from "@clack/prompts";
|
|
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
|
|
3077
|
-
});
|
|
3078
|
-
if (p2.isCancel(provider)) {
|
|
3079
|
-
p2.cancel("Cancelled");
|
|
3080
|
-
process.exit(0);
|
|
3081
|
-
}
|
|
3082
|
-
return provider;
|
|
3083
|
-
}
|
|
3084
|
-
function configureCompute(provider) {
|
|
3085
|
-
const config = buildProviderConfig(provider);
|
|
3086
|
-
compute2.setConfig(config);
|
|
3087
|
-
}
|
|
3088
|
-
|
|
3089
|
-
// src/index.ts
|
|
3090
|
-
init_workspace_cli();
|
|
3091
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3092
|
-
var __dirname2 = dirname2(__filename2);
|
|
2228
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
2229
|
+
var __dirname2 = dirname(__filename2);
|
|
3093
2230
|
var packageJson = JSON.parse(
|
|
3094
|
-
|
|
2231
|
+
readFileSync2(join2(__dirname2, "..", "package.json"), "utf-8")
|
|
3095
2232
|
);
|
|
3096
2233
|
var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
|
|
3097
2234
|
var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
|
|
3098
2235
|
var program = new Command();
|
|
3099
|
-
program.name("compute").description("
|
|
2236
|
+
program.name("compute").description("ComputeSDK CLI").version(VERSION).enablePositionalOptions().passThroughOptions();
|
|
2237
|
+
async function runLogin(options) {
|
|
2238
|
+
const { oauthLogin } = await import("@benchsdk/cli");
|
|
2239
|
+
await oauthLogin({
|
|
2240
|
+
baseUrl: options?.baseUrl ?? process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
async function runLogout() {
|
|
2244
|
+
const { clearCredentials } = await import("@benchsdk/cli");
|
|
2245
|
+
await clearCredentials();
|
|
2246
|
+
}
|
|
3100
2247
|
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
|
|
3101
2248
|
if (opts.logout) {
|
|
3102
2249
|
console.log();
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
2250
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2251
|
+
await runLogout();
|
|
2252
|
+
p.log.success("Logged out. Stored credentials removed.");
|
|
2253
|
+
p.outro(pc4.green("Done!"));
|
|
3107
2254
|
process.exit(0);
|
|
3108
2255
|
}
|
|
3109
2256
|
if (opts.login) {
|
|
3110
2257
|
console.log();
|
|
3111
|
-
|
|
3112
|
-
await
|
|
3113
|
-
|
|
2258
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2259
|
+
await runLogin();
|
|
2260
|
+
p.outro(pc4.green("Authenticated!"));
|
|
3114
2261
|
process.exit(0);
|
|
3115
2262
|
}
|
|
3116
2263
|
program.help();
|
|
3117
2264
|
});
|
|
3118
|
-
program.command("create").description("Create a new workspace (same as create-compute)").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
|
|
3119
|
-
console.log();
|
|
3120
|
-
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
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
|
-
}
|
|
3573
|
-
});
|
|
3574
2265
|
program.command("providers").description("List all providers and their configuration status").action(async () => {
|
|
3575
2266
|
console.log();
|
|
3576
|
-
|
|
2267
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
3577
2268
|
const statuses = getProviderStatus();
|
|
3578
2269
|
console.log();
|
|
3579
2270
|
for (const status of statuses) {
|
|
3580
|
-
const icon = status.ready ?
|
|
3581
|
-
const name = status.ready ?
|
|
3582
|
-
const detail = status.ready ?
|
|
2271
|
+
const icon = status.ready ? pc4.green("\u25CF") : pc4.gray("\u25CB");
|
|
2272
|
+
const name = status.ready ? pc4.white(status.name) : pc4.gray(status.name);
|
|
2273
|
+
const detail = status.ready ? pc4.green("ready") : pc4.gray(`missing: ${status.missing.join(", ")}`);
|
|
3583
2274
|
console.log(` ${icon} ${name} ${detail}`);
|
|
3584
2275
|
}
|
|
3585
2276
|
console.log();
|
|
3586
2277
|
});
|
|
3587
|
-
program.command("login").description("Authenticate with ComputeSDK").action(async () => {
|
|
2278
|
+
program.command("login").description("Authenticate with ComputeSDK (OAuth device flow)").option("--base-url <url>", "platform URL to authenticate against").action(async (opts) => {
|
|
3588
2279
|
console.log();
|
|
3589
|
-
|
|
3590
|
-
await
|
|
3591
|
-
|
|
2280
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2281
|
+
await runLogin({ baseUrl: opts.baseUrl });
|
|
2282
|
+
p.outro(pc4.green("Authenticated!"));
|
|
3592
2283
|
});
|
|
3593
|
-
program.command("logout").description("Clear stored credentials").action(() => {
|
|
2284
|
+
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
3594
2285
|
console.log();
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
2286
|
+
p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
|
|
2287
|
+
await runLogout();
|
|
2288
|
+
p.log.success("Logged out. Stored credentials removed.");
|
|
2289
|
+
p.outro(pc4.green("Done!"));
|
|
3599
2290
|
});
|
|
3600
2291
|
registerActionsCommands(program);
|
|
3601
2292
|
registerMarketCommands(program);
|
|
3602
2293
|
registerSandboxesCommands(program);
|
|
3603
2294
|
program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
|
|
3604
2295
|
});
|
|
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
2296
|
async function main() {
|
|
3672
2297
|
if (process.argv[2] === "bench" || process.argv[2] === "benchmark") {
|
|
3673
2298
|
const { run: benchRun } = await import("@benchsdk/runner");
|
|
@@ -3677,11 +2302,8 @@ async function main() {
|
|
|
3677
2302
|
await program.parseAsync(process.argv);
|
|
3678
2303
|
}
|
|
3679
2304
|
main().catch((error) => {
|
|
3680
|
-
console.error(
|
|
2305
|
+
console.error(pc4.red(`
|
|
3681
2306
|
Error: ${error.message}`));
|
|
3682
2307
|
process.exit(1);
|
|
3683
2308
|
});
|
|
3684
|
-
export {
|
|
3685
|
-
shellEscape2 as shellEscape
|
|
3686
|
-
};
|
|
3687
2309
|
//# sourceMappingURL=index.js.map
|