@computesdk/cli 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1103 -147
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as readline from "readline";
|
|
|
23
23
|
import * as path2 from "path";
|
|
24
24
|
import * as os from "os";
|
|
25
25
|
import * as fs from "fs";
|
|
26
|
-
import
|
|
26
|
+
import pc4 from "picocolors";
|
|
27
27
|
import { compute } from "computesdk";
|
|
28
28
|
async function startREPL(sandbox, provider) {
|
|
29
29
|
void provider;
|
|
@@ -34,8 +34,8 @@ async function startREPL(sandbox, provider) {
|
|
|
34
34
|
await runSession(ctx);
|
|
35
35
|
}
|
|
36
36
|
function buildPrompt(sandboxId) {
|
|
37
|
-
if (!sandboxId) return
|
|
38
|
-
return
|
|
37
|
+
if (!sandboxId) return pc4.gray("[no sandbox] ") + pc4.bold("$ ");
|
|
38
|
+
return pc4.cyan(`[${sandboxId}] `) + pc4.bold("$ ");
|
|
39
39
|
}
|
|
40
40
|
function runSession(ctx) {
|
|
41
41
|
const rl = readline.createInterface({
|
|
@@ -80,13 +80,13 @@ function runSession(ctx) {
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
} else if (ctx.sandboxes.size === 0) {
|
|
83
|
-
console.log(
|
|
83
|
+
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
84
84
|
} else {
|
|
85
85
|
const sandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
86
86
|
await executeCommand(sandbox, trimmed);
|
|
87
87
|
}
|
|
88
88
|
} catch (error) {
|
|
89
|
-
console.log(
|
|
89
|
+
console.log(pc4.red(`\u2717 ${error.message}`));
|
|
90
90
|
}
|
|
91
91
|
if (!resolved) {
|
|
92
92
|
rl.prompt();
|
|
@@ -104,13 +104,13 @@ async function executeCommand(sandbox, command) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
if (result.stderr) {
|
|
107
|
-
process.stderr.write(
|
|
107
|
+
process.stderr.write(pc4.red(result.stderr));
|
|
108
108
|
if (!result.stderr.endsWith("\n")) {
|
|
109
109
|
console.log();
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
if (result.exitCode !== 0) {
|
|
113
|
-
console.log(
|
|
113
|
+
console.log(pc4.gray(`exit code: ${result.exitCode}`));
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
async function handleSpecialCommand(ctx, input, rl) {
|
|
@@ -124,67 +124,67 @@ async function handleSpecialCommand(ctx, input, rl) {
|
|
|
124
124
|
case "info":
|
|
125
125
|
case "i": {
|
|
126
126
|
if (ctx.sandboxes.size === 0) {
|
|
127
|
-
console.log(
|
|
127
|
+
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
128
128
|
return null;
|
|
129
129
|
}
|
|
130
130
|
const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
131
131
|
console.log();
|
|
132
|
-
console.log(
|
|
133
|
-
console.log(` ID: ${
|
|
132
|
+
console.log(pc4.bold(" Sandbox Info"));
|
|
133
|
+
console.log(` ID: ${pc4.cyan(activeSandbox.sandboxId)}`);
|
|
134
134
|
console.log();
|
|
135
135
|
return null;
|
|
136
136
|
}
|
|
137
137
|
case "url":
|
|
138
138
|
case "u": {
|
|
139
139
|
if (ctx.sandboxes.size === 0) {
|
|
140
|
-
console.log(
|
|
140
|
+
console.log(pc4.yellow("No active sandbox. Use /new to create one."));
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
const activeSandbox = ctx.sandboxes.get(ctx.activeSandboxId);
|
|
144
144
|
if (args.length === 0) {
|
|
145
|
-
console.log(
|
|
145
|
+
console.log(pc4.gray("Usage: /url <port>"));
|
|
146
146
|
} else {
|
|
147
147
|
const port = parseInt(args[0], 10);
|
|
148
148
|
if (isNaN(port)) {
|
|
149
|
-
console.log(
|
|
149
|
+
console.log(pc4.red("Invalid port number"));
|
|
150
150
|
} else {
|
|
151
151
|
const url = await activeSandbox.getUrl({ port });
|
|
152
|
-
console.log(
|
|
152
|
+
console.log(pc4.cyan(url));
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
case "new": {
|
|
158
|
-
console.log(
|
|
158
|
+
console.log(pc4.gray(" Creating new sandbox..."));
|
|
159
159
|
const newSandbox = await compute.sandbox.create();
|
|
160
160
|
ctx.sandboxes.set(newSandbox.sandboxId, newSandbox);
|
|
161
161
|
ctx.activeSandboxId = newSandbox.sandboxId;
|
|
162
162
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
163
|
-
console.log(` Sandbox ready: ${
|
|
164
|
-
console.log(` ${
|
|
163
|
+
console.log(` Sandbox ready: ${pc4.cyan(newSandbox.sandboxId)}`);
|
|
164
|
+
console.log(` ${pc4.gray(`Active sandboxes: ${ctx.sandboxes.size}`)}`);
|
|
165
165
|
return null;
|
|
166
166
|
}
|
|
167
167
|
case "destroy": {
|
|
168
168
|
if (args.length === 0) {
|
|
169
|
-
console.log(
|
|
170
|
-
console.log(
|
|
169
|
+
console.log(pc4.gray("Usage: /destroy <sandbox-id> or /destroy --all"));
|
|
170
|
+
console.log(pc4.gray("Use /list to see all sandboxes"));
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
173
173
|
if (args[0] === "--all") {
|
|
174
174
|
if (ctx.sandboxes.size === 0) {
|
|
175
|
-
console.log(
|
|
175
|
+
console.log(pc4.yellow(" No sandboxes to destroy."));
|
|
176
176
|
return null;
|
|
177
177
|
}
|
|
178
178
|
const count = ctx.sandboxes.size;
|
|
179
|
-
console.log(
|
|
179
|
+
console.log(pc4.gray(` Destroying ${count} sandbox${count > 1 ? "es" : ""}...`));
|
|
180
180
|
await Promise.all(
|
|
181
181
|
Array.from(ctx.sandboxes.values()).map((s) => s.destroy())
|
|
182
182
|
);
|
|
183
183
|
ctx.sandboxes.clear();
|
|
184
184
|
ctx.activeSandboxId = "";
|
|
185
185
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
186
|
-
console.log(` Destroyed ${
|
|
187
|
-
console.log(
|
|
186
|
+
console.log(` Destroyed ${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
188
|
return null;
|
|
189
189
|
}
|
|
190
190
|
const targetId = args[0];
|
|
@@ -194,13 +194,13 @@ async function handleSpecialCommand(ctx, input, rl) {
|
|
|
194
194
|
if (matches.length === 1) {
|
|
195
195
|
return destroySandbox(ctx, matches[0], rl);
|
|
196
196
|
} else if (matches.length > 1) {
|
|
197
|
-
console.log(
|
|
197
|
+
console.log(pc4.yellow(" Ambiguous ID. Matches:"));
|
|
198
198
|
for (const id of matches) {
|
|
199
|
-
console.log(` ${
|
|
199
|
+
console.log(` ${pc4.cyan(id)}`);
|
|
200
200
|
}
|
|
201
201
|
} else {
|
|
202
|
-
console.log(
|
|
203
|
-
console.log(
|
|
202
|
+
console.log(pc4.red(` Sandbox not found: ${targetId}`));
|
|
203
|
+
console.log(pc4.gray(" Use /list to see all sandboxes"));
|
|
204
204
|
}
|
|
205
205
|
return null;
|
|
206
206
|
}
|
|
@@ -209,10 +209,10 @@ async function handleSpecialCommand(ctx, input, rl) {
|
|
|
209
209
|
case "list":
|
|
210
210
|
case "ls": {
|
|
211
211
|
console.log();
|
|
212
|
-
console.log(
|
|
212
|
+
console.log(pc4.bold(" Sandboxes"));
|
|
213
213
|
for (const id of ctx.sandboxes.keys()) {
|
|
214
|
-
const marker = id === ctx.activeSandboxId ?
|
|
215
|
-
console.log(` ${marker} ${
|
|
214
|
+
const marker = id === ctx.activeSandboxId ? pc4.green(" \u25CF") : " ";
|
|
215
|
+
console.log(` ${marker} ${pc4.cyan(id)}`);
|
|
216
216
|
}
|
|
217
217
|
console.log();
|
|
218
218
|
return null;
|
|
@@ -220,30 +220,30 @@ async function handleSpecialCommand(ctx, input, rl) {
|
|
|
220
220
|
case "switch":
|
|
221
221
|
case "sw": {
|
|
222
222
|
if (args.length === 0) {
|
|
223
|
-
console.log(
|
|
224
|
-
console.log(
|
|
223
|
+
console.log(pc4.gray("Usage: /switch <sandbox-id>"));
|
|
224
|
+
console.log(pc4.gray("Use /list to see all sandboxes"));
|
|
225
225
|
return null;
|
|
226
226
|
}
|
|
227
227
|
const switchId = args[0];
|
|
228
228
|
if (ctx.sandboxes.has(switchId)) {
|
|
229
229
|
ctx.activeSandboxId = switchId;
|
|
230
230
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
231
|
-
console.log(` Switched to ${
|
|
231
|
+
console.log(` Switched to ${pc4.cyan(switchId)}`);
|
|
232
232
|
return null;
|
|
233
233
|
}
|
|
234
234
|
const matches = Array.from(ctx.sandboxes.keys()).filter((id) => id.startsWith(switchId));
|
|
235
235
|
if (matches.length === 1) {
|
|
236
236
|
ctx.activeSandboxId = matches[0];
|
|
237
237
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
238
|
-
console.log(` Switched to ${
|
|
238
|
+
console.log(` Switched to ${pc4.cyan(matches[0])}`);
|
|
239
239
|
} else if (matches.length > 1) {
|
|
240
|
-
console.log(
|
|
240
|
+
console.log(pc4.yellow(" Ambiguous ID. Matches:"));
|
|
241
241
|
for (const id of matches) {
|
|
242
|
-
console.log(` ${
|
|
242
|
+
console.log(` ${pc4.cyan(id)}`);
|
|
243
243
|
}
|
|
244
244
|
} else {
|
|
245
|
-
console.log(
|
|
246
|
-
console.log(
|
|
245
|
+
console.log(pc4.red(` Sandbox not found: ${switchId}`));
|
|
246
|
+
console.log(pc4.gray(" Use /list to see all sandboxes"));
|
|
247
247
|
}
|
|
248
248
|
return null;
|
|
249
249
|
}
|
|
@@ -252,51 +252,51 @@ async function handleSpecialCommand(ctx, input, rl) {
|
|
|
252
252
|
case "q":
|
|
253
253
|
return "exit";
|
|
254
254
|
default:
|
|
255
|
-
console.log(
|
|
256
|
-
console.log(
|
|
255
|
+
console.log(pc4.yellow(`Unknown command: /${cmd}`));
|
|
256
|
+
console.log(pc4.gray("Type /help for available commands"));
|
|
257
257
|
return null;
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
async function destroySandbox(ctx, sandboxId, rl) {
|
|
261
261
|
const target = ctx.sandboxes.get(sandboxId);
|
|
262
|
-
console.log(
|
|
262
|
+
console.log(pc4.gray(` Destroying ${sandboxId}...`));
|
|
263
263
|
await target.destroy();
|
|
264
264
|
ctx.sandboxes.delete(sandboxId);
|
|
265
|
-
console.log(` Destroyed ${
|
|
265
|
+
console.log(` Destroyed ${pc4.cyan(sandboxId)}`);
|
|
266
266
|
if (ctx.activeSandboxId === sandboxId && ctx.sandboxes.size > 0) {
|
|
267
267
|
ctx.activeSandboxId = ctx.sandboxes.keys().next().value;
|
|
268
268
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
269
|
-
console.log(` Switched to ${
|
|
269
|
+
console.log(` Switched to ${pc4.cyan(ctx.activeSandboxId)}`);
|
|
270
270
|
} else if (ctx.sandboxes.size === 0) {
|
|
271
271
|
ctx.activeSandboxId = "";
|
|
272
272
|
rl.setPrompt(buildPrompt(ctx.activeSandboxId));
|
|
273
|
-
console.log(
|
|
273
|
+
console.log(pc4.yellow(" No sandboxes remaining. Use /new to create one or /exit to quit."));
|
|
274
274
|
}
|
|
275
275
|
return null;
|
|
276
276
|
}
|
|
277
277
|
function showHelp() {
|
|
278
278
|
console.log();
|
|
279
|
-
console.log(
|
|
280
|
-
console.log(
|
|
279
|
+
console.log(pc4.bold(" Shell Mode"));
|
|
280
|
+
console.log(pc4.gray(" Just type commands - they run in the active sandbox"));
|
|
281
281
|
console.log();
|
|
282
|
-
console.log(
|
|
283
|
-
console.log(
|
|
284
|
-
console.log(
|
|
282
|
+
console.log(pc4.white(" ls -la"));
|
|
283
|
+
console.log(pc4.white(" cat /etc/os-release"));
|
|
284
|
+
console.log(pc4.white(" npm install lodash"));
|
|
285
285
|
console.log();
|
|
286
|
-
console.log(
|
|
286
|
+
console.log(pc4.bold(" Sandbox Commands") + pc4.gray(" (prefix with /)"));
|
|
287
287
|
console.log();
|
|
288
|
-
console.log(
|
|
289
|
-
console.log(
|
|
290
|
-
console.log(
|
|
291
|
-
console.log(
|
|
292
|
-
console.log(
|
|
288
|
+
console.log(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
293
|
console.log();
|
|
294
|
-
console.log(
|
|
294
|
+
console.log(pc4.bold(" Other Commands"));
|
|
295
295
|
console.log();
|
|
296
|
-
console.log(
|
|
297
|
-
console.log(
|
|
298
|
-
console.log(
|
|
299
|
-
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
300
|
console.log();
|
|
301
301
|
}
|
|
302
302
|
function loadHistory(rl) {
|
|
@@ -343,7 +343,7 @@ import * as fs3 from "fs";
|
|
|
343
343
|
import * as path4 from "path";
|
|
344
344
|
import * as os3 from "os";
|
|
345
345
|
import * as p3 from "@clack/prompts";
|
|
346
|
-
import
|
|
346
|
+
import pc7 from "picocolors";
|
|
347
347
|
function shellEscape(arg) {
|
|
348
348
|
if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg)) return arg;
|
|
349
349
|
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
@@ -382,7 +382,7 @@ async function workspaceCreate(options) {
|
|
|
382
382
|
}
|
|
383
383
|
existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
|
|
384
384
|
saveState(state);
|
|
385
|
-
p3.log.info(`Attaching to existing workspace: ${
|
|
385
|
+
p3.log.info(`Attaching to existing workspace: ${pc7.cyan(id)}`);
|
|
386
386
|
return existing;
|
|
387
387
|
} catch {
|
|
388
388
|
delete state.workspaces[id];
|
|
@@ -394,9 +394,9 @@ async function workspaceCreate(options) {
|
|
|
394
394
|
let sandbox;
|
|
395
395
|
try {
|
|
396
396
|
sandbox = await compute3.sandbox.create();
|
|
397
|
-
spinner4.stop(`Sandbox ready: ${
|
|
397
|
+
spinner4.stop(`Sandbox ready: ${pc7.cyan(sandbox.sandboxId)}`);
|
|
398
398
|
} catch (error) {
|
|
399
|
-
spinner4.stop(
|
|
399
|
+
spinner4.stop(pc7.red("Failed to create sandbox"));
|
|
400
400
|
throw error;
|
|
401
401
|
}
|
|
402
402
|
spinner4.start("Bootstrapping workspace (installing gh CLI, cloning repo)...");
|
|
@@ -436,12 +436,12 @@ async function workspaceCreate(options) {
|
|
|
436
436
|
};
|
|
437
437
|
state.workspaces[id] = workspace;
|
|
438
438
|
saveState(state);
|
|
439
|
-
p3.log.success(`Workspace ready: ${
|
|
440
|
-
p3.log.info(`Sandbox: ${
|
|
441
|
-
p3.log.info(`Worktree: ${
|
|
439
|
+
p3.log.success(`Workspace ready: ${pc7.cyan(id)}`);
|
|
440
|
+
p3.log.info(`Sandbox: ${pc7.dim(sandbox.sandboxId)}`);
|
|
441
|
+
p3.log.info(`Worktree: ${pc7.dim(worktreePath)}`);
|
|
442
442
|
return workspace;
|
|
443
443
|
} catch (error) {
|
|
444
|
-
spinner4.stop(
|
|
444
|
+
spinner4.stop(pc7.red("Bootstrap failed"));
|
|
445
445
|
try {
|
|
446
446
|
await sandbox.destroy();
|
|
447
447
|
} catch {
|
|
@@ -466,9 +466,9 @@ async function workspaceAttach(id) {
|
|
|
466
466
|
}
|
|
467
467
|
workspace.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
|
|
468
468
|
saveState(state);
|
|
469
|
-
p3.log.info(`Attaching to workspace: ${
|
|
470
|
-
p3.log.info(`Worktree: ${
|
|
471
|
-
console.log(
|
|
469
|
+
p3.log.info(`Attaching to workspace: ${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
472
|
try {
|
|
473
473
|
await startREPL(sandbox, workspace.provider);
|
|
474
474
|
} catch (error) {
|
|
@@ -485,19 +485,19 @@ async function sandboxList() {
|
|
|
485
485
|
return;
|
|
486
486
|
}
|
|
487
487
|
console.log();
|
|
488
|
-
console.log(
|
|
488
|
+
console.log(pc7.bold("Sandboxes"));
|
|
489
489
|
console.log();
|
|
490
490
|
for (const ws of workspaces) {
|
|
491
491
|
const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
|
|
492
492
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
493
|
-
const status = age > 7 ?
|
|
494
|
-
console.log(` ${status} ${
|
|
495
|
-
console.log(` Workspace: ${
|
|
493
|
+
const status = age > 7 ? 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
496
|
console.log(` Provider: ${ws.provider}`);
|
|
497
|
-
console.log(` Last active: ${
|
|
497
|
+
console.log(` Last active: ${pc7.gray(ageStr)}`);
|
|
498
498
|
console.log();
|
|
499
499
|
}
|
|
500
|
-
console.log(
|
|
500
|
+
console.log(pc7.gray('Tip: Use "compute workspaces" to see full workspace details'));
|
|
501
501
|
}
|
|
502
502
|
async function workspaceList() {
|
|
503
503
|
const state = loadState();
|
|
@@ -508,15 +508,15 @@ async function workspaceList() {
|
|
|
508
508
|
return;
|
|
509
509
|
}
|
|
510
510
|
console.log();
|
|
511
|
-
console.log(
|
|
511
|
+
console.log(pc7.bold("Workspaces"));
|
|
512
512
|
console.log();
|
|
513
513
|
for (const ws of workspaces) {
|
|
514
514
|
const age = Math.round((Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24));
|
|
515
515
|
const ageStr = age === 0 ? "today" : `${age}d ago`;
|
|
516
|
-
console.log(` ${
|
|
517
|
-
console.log(` Sandbox: ${
|
|
518
|
-
console.log(` Path: ${
|
|
519
|
-
console.log(` Last used: ${
|
|
516
|
+
console.log(` ${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
520
|
console.log();
|
|
521
521
|
}
|
|
522
522
|
}
|
|
@@ -542,7 +542,7 @@ async function workspaceDestroy(id) {
|
|
|
542
542
|
p3.log.error(`Workspace not found: ${id}`);
|
|
543
543
|
return;
|
|
544
544
|
}
|
|
545
|
-
p3.log.info(`Destroying workspace: ${
|
|
545
|
+
p3.log.info(`Destroying workspace: ${pc7.cyan(id)}`);
|
|
546
546
|
try {
|
|
547
547
|
await compute3.sandbox.destroy(workspace.sandboxId);
|
|
548
548
|
delete state.workspaces[id];
|
|
@@ -586,7 +586,7 @@ init_esm_shims();
|
|
|
586
586
|
import "dotenv/config";
|
|
587
587
|
import { Command } from "commander";
|
|
588
588
|
import * as p4 from "@clack/prompts";
|
|
589
|
-
import
|
|
589
|
+
import pc8 from "picocolors";
|
|
590
590
|
import { readFileSync as readFileSync5 } from "fs";
|
|
591
591
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
592
592
|
import { dirname as dirname2, join as join5 } from "path";
|
|
@@ -842,13 +842,35 @@ var ActionsClient = class {
|
|
|
842
842
|
if (value === void 0) continue;
|
|
843
843
|
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
844
844
|
}
|
|
845
|
-
const res = await this.fetchImpl(url, {
|
|
845
|
+
const res = await this.fetchImpl(url, {
|
|
846
|
+
headers: { ...this.headers(), accept: "text/event-stream" }
|
|
847
|
+
});
|
|
846
848
|
if (!res.ok) {
|
|
847
849
|
await this.parse(res);
|
|
848
850
|
}
|
|
849
851
|
if (!res.body) throw new Error("SSE response has no body");
|
|
850
852
|
yield* readSseEvents(res.body);
|
|
851
853
|
}
|
|
854
|
+
/**
|
|
855
|
+
* `sse()` with the frame's `event:` name preserved — for streams whose
|
|
856
|
+
* events are distinguished by name rather than payload shape (process
|
|
857
|
+
* follow: stdout/stderr share a `{chunk}` payload).
|
|
858
|
+
*/
|
|
859
|
+
async *sseNamed(path5, params) {
|
|
860
|
+
const url = new URL(`${this.auth.baseUrl}${path5}`);
|
|
861
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
862
|
+
if (value === void 0) continue;
|
|
863
|
+
for (const v of Array.isArray(value) ? value : [value]) url.searchParams.append(key, v);
|
|
864
|
+
}
|
|
865
|
+
const res = await this.fetchImpl(url, {
|
|
866
|
+
headers: { ...this.headers(), accept: "text/event-stream" }
|
|
867
|
+
});
|
|
868
|
+
if (!res.ok) {
|
|
869
|
+
await this.parse(res);
|
|
870
|
+
}
|
|
871
|
+
if (!res.body) throw new Error("SSE response has no body");
|
|
872
|
+
yield* readSseNamedEvents(res.body);
|
|
873
|
+
}
|
|
852
874
|
/** Download bytes following redirects (artifact URLs 302 to storage). */
|
|
853
875
|
async download(path5) {
|
|
854
876
|
const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
|
|
@@ -862,7 +884,7 @@ var ActionsClient = class {
|
|
|
862
884
|
return new Uint8Array(await res.arrayBuffer());
|
|
863
885
|
}
|
|
864
886
|
headers() {
|
|
865
|
-
return { authorization: `Bearer ${this.auth.apiKey}
|
|
887
|
+
return { authorization: `Bearer ${this.auth.apiKey}`, accept: "application/json" };
|
|
866
888
|
}
|
|
867
889
|
async parse(res) {
|
|
868
890
|
const body = await res.json().catch(() => void 0);
|
|
@@ -900,6 +922,34 @@ async function* readSseEvents(body) {
|
|
|
900
922
|
reader.releaseLock();
|
|
901
923
|
}
|
|
902
924
|
}
|
|
925
|
+
async function* readSseNamedEvents(body) {
|
|
926
|
+
const reader = body.getReader();
|
|
927
|
+
const decoder = new TextDecoder();
|
|
928
|
+
let buffer = "";
|
|
929
|
+
try {
|
|
930
|
+
for (; ; ) {
|
|
931
|
+
const { done, value } = await reader.read();
|
|
932
|
+
if (done) break;
|
|
933
|
+
buffer += decoder.decode(value, { stream: true });
|
|
934
|
+
let idx;
|
|
935
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
936
|
+
const frame = buffer.slice(0, idx);
|
|
937
|
+
buffer = buffer.slice(idx + 2);
|
|
938
|
+
let event = "message";
|
|
939
|
+
const dataLines = [];
|
|
940
|
+
for (const line of frame.split("\n")) {
|
|
941
|
+
if (line.startsWith("event: ")) event = line.slice(7);
|
|
942
|
+
else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
|
|
943
|
+
}
|
|
944
|
+
if (dataLines.length > 0) {
|
|
945
|
+
yield { event, data: JSON.parse(dataLines.join("\n")) };
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
} finally {
|
|
950
|
+
reader.releaseLock();
|
|
951
|
+
}
|
|
952
|
+
}
|
|
903
953
|
function encodeWatchCursor(id, jobId, step, offset) {
|
|
904
954
|
const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
|
|
905
955
|
return `${id}:${jobId}:${stepPart}:${offset}`;
|
|
@@ -981,6 +1031,22 @@ function sanitizePathPart(name) {
|
|
|
981
1031
|
async function client(opts) {
|
|
982
1032
|
return new ActionsClient(await resolveActionsAuth(opts));
|
|
983
1033
|
}
|
|
1034
|
+
function assertSecretValueHost(auth, what) {
|
|
1035
|
+
if (!isTrustedActionsHost(auth.baseUrl)) {
|
|
1036
|
+
throw new ActionsCliError(
|
|
1037
|
+
"untrusted_host",
|
|
1038
|
+
`Refusing to send or read ${what} at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
async function secretValueClient(opts, what) {
|
|
1043
|
+
const auth = await resolveActionsAuth(opts);
|
|
1044
|
+
assertSecretValueHost(auth, what);
|
|
1045
|
+
return new ActionsClient(auth);
|
|
1046
|
+
}
|
|
1047
|
+
async function vaultValueClient(opts) {
|
|
1048
|
+
return secretValueClient(opts, "vault values");
|
|
1049
|
+
}
|
|
984
1050
|
function output(opts, data, render) {
|
|
985
1051
|
if (opts.json) {
|
|
986
1052
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
@@ -1373,6 +1439,7 @@ function dispatchBody(workflow, opts) {
|
|
|
1373
1439
|
if (opts.providerRegion !== void 0 && opts.provider === void 0) {
|
|
1374
1440
|
throw new ActionsCliError("invalid_argument", "--provider-region requires --provider.");
|
|
1375
1441
|
}
|
|
1442
|
+
const maxPrice = maxPriceFields(opts.maxBid, opts.maxBidPer);
|
|
1376
1443
|
return {
|
|
1377
1444
|
workflowId: workflow.id,
|
|
1378
1445
|
ref,
|
|
@@ -1381,14 +1448,66 @@ function dispatchBody(workflow, opts) {
|
|
|
1381
1448
|
...opts.provider !== void 0 && {
|
|
1382
1449
|
provider: opts.provider,
|
|
1383
1450
|
...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
|
|
1384
|
-
}
|
|
1451
|
+
},
|
|
1452
|
+
...maxPrice !== void 0 && maxPrice
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function maxPriceFields(maxBid, maxBidPer) {
|
|
1456
|
+
if (maxBidPer !== void 0 && maxBid === void 0) {
|
|
1457
|
+
throw new ActionsCliError("invalid_argument", "--max-bid-per requires --max-bid.");
|
|
1458
|
+
}
|
|
1459
|
+
if (maxBid === void 0) return void 0;
|
|
1460
|
+
const usd = Number(maxBid);
|
|
1461
|
+
if (!/^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(maxBid) || !Number.isFinite(usd) || usd <= 0) {
|
|
1462
|
+
throw new ActionsCliError("invalid_argument", `Invalid --max-bid "${maxBid}". Expected a positive dollar amount (e.g. 0.12).`);
|
|
1463
|
+
}
|
|
1464
|
+
const per = maxBidPer ?? "second";
|
|
1465
|
+
if (per !== "second" && per !== "minute" && per !== "hour") {
|
|
1466
|
+
throw new ActionsCliError("invalid_argument", `--max-bid-per must be second, minute, or hour, got "${maxBidPer}".`);
|
|
1467
|
+
}
|
|
1468
|
+
return { maxPriceUsd: usd, maxPricePer: per };
|
|
1469
|
+
}
|
|
1470
|
+
function parseVaultKind(kind) {
|
|
1471
|
+
if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
|
|
1472
|
+
throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
|
|
1473
|
+
}
|
|
1474
|
+
function vaultPath(path5, params) {
|
|
1475
|
+
const search = new URLSearchParams();
|
|
1476
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1477
|
+
if (value !== void 0) search.set(key, value);
|
|
1478
|
+
}
|
|
1479
|
+
const query = search.toString();
|
|
1480
|
+
return query ? `${path5}?${query}` : path5;
|
|
1481
|
+
}
|
|
1482
|
+
function vaultSetBody(name, value, opts) {
|
|
1483
|
+
if (value === "") throw new ActionsCliError("invalid_argument", "The value is empty.");
|
|
1484
|
+
if (opts.revealable && opts.kind === "variable") {
|
|
1485
|
+
throw new ActionsCliError("invalid_argument", "--revealable applies to secrets; variables are always readable.");
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
name,
|
|
1489
|
+
kind: opts.kind,
|
|
1490
|
+
value,
|
|
1491
|
+
...opts.revealable && { revealable: true },
|
|
1492
|
+
...opts.description !== void 0 && { description: opts.description },
|
|
1493
|
+
...opts.labels !== void 0 && opts.labels.length > 0 && { labels: opts.labels }
|
|
1385
1494
|
};
|
|
1386
1495
|
}
|
|
1496
|
+
function formatVaultRow(item) {
|
|
1497
|
+
const flags = [
|
|
1498
|
+
item.kind,
|
|
1499
|
+
...item.revealable && item.kind === "secret" ? ["revealable"] : [],
|
|
1500
|
+
`v${item.version}`,
|
|
1501
|
+
...item.source === "organization" ? ["inherited"] : [],
|
|
1502
|
+
...item.overridesOrganization ? ["overrides org"] : []
|
|
1503
|
+
];
|
|
1504
|
+
return `${pc.cyan(safeTerm(item.name))} ${pc.dim(flags.join(", "))} ${pc.dim(`updated ${safeTerm(item.updatedAt)}`)}`;
|
|
1505
|
+
}
|
|
1387
1506
|
function registerActionsCommands(program2) {
|
|
1388
1507
|
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
|
|
1389
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");
|
|
1390
1509
|
common(
|
|
1391
|
-
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>)")
|
|
1510
|
+
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)")
|
|
1392
1511
|
).action(async (repo, opts) => {
|
|
1393
1512
|
try {
|
|
1394
1513
|
const c = await client(opts);
|
|
@@ -1714,6 +1833,74 @@ function registerActionsCommands(program2) {
|
|
|
1714
1833
|
fail(e, opts);
|
|
1715
1834
|
}
|
|
1716
1835
|
});
|
|
1836
|
+
const vault = actions.command("vault").description("The org vault: secrets and variables (owner/admin key)");
|
|
1837
|
+
const vaultScope = (cmd) => common(cmd).option("--repo <owner/repo>", "scope to a repo's own items instead of the org's").option("--kind <kind>", "secret or variable", "secret");
|
|
1838
|
+
vaultScope(
|
|
1839
|
+
vault.command("ls").description("List names and metadata (never values)")
|
|
1840
|
+
).action(async (opts) => {
|
|
1841
|
+
try {
|
|
1842
|
+
const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind) });
|
|
1843
|
+
const data = await (await client(opts)).get(path5);
|
|
1844
|
+
output(opts, data, (d) => {
|
|
1845
|
+
for (const item of d.items) console.log(formatVaultRow(item));
|
|
1846
|
+
});
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
fail(e, opts);
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1851
|
+
vaultScope(
|
|
1852
|
+
vault.command("set").description("Store a value, read from stdin (or --from-file) and sent exactly as read").argument("<name>", "item name, e.g. NPM_TOKEN").option("--from-file <path>", "read the value from a file instead of stdin").option("--revealable", "let the value be read back with `vault get` (secrets; fixed at creation)").option("--description <text>", "what the item is for").option("--labels <labels...>", "labels (fixed at creation)")
|
|
1853
|
+
).action(async (name, opts) => {
|
|
1854
|
+
try {
|
|
1855
|
+
if (opts.fromFile === void 0 && process.stdin.isTTY) {
|
|
1856
|
+
throw new ActionsCliError(
|
|
1857
|
+
"invalid_argument",
|
|
1858
|
+
'Pipe the value on stdin (e.g. printf %s "$VALUE" | compute actions vault set NAME) or pass --from-file.'
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
const value = readFileSync(opts.fromFile ?? 0, "utf8");
|
|
1862
|
+
const kind = parseVaultKind(opts.kind);
|
|
1863
|
+
const body = vaultSetBody(name, value, { ...opts, kind });
|
|
1864
|
+
const result = await (await vaultValueClient(opts)).put(
|
|
1865
|
+
vaultPath("/api/v1/vault", { repo: opts.repo }),
|
|
1866
|
+
body
|
|
1867
|
+
);
|
|
1868
|
+
output(opts, result, (r) => {
|
|
1869
|
+
const where = opts.repo ? safeTerm(opts.repo) : "org";
|
|
1870
|
+
console.log(`saved ${pc.cyan(safeTerm(r.item.name))} ${pc.dim(`(${r.item.kind}, v${r.item.version}, ${where})`)}`);
|
|
1871
|
+
if (/\r?\n$/.test(value)) {
|
|
1872
|
+
console.error(pc.yellow("note: the stored value ends with a newline; use printf %s rather than echo to avoid one."));
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
} catch (e) {
|
|
1876
|
+
fail(e, opts);
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
vaultScope(
|
|
1880
|
+
vault.command("get").description("Print a value: any variable, or a secret created --revealable").argument("<name>", "item name")
|
|
1881
|
+
).action(async (name, opts) => {
|
|
1882
|
+
try {
|
|
1883
|
+
const kind = parseVaultKind(opts.kind);
|
|
1884
|
+
const result = await (await vaultValueClient(opts)).post(
|
|
1885
|
+
vaultPath("/api/v1/vault/reveal", { repo: opts.repo }),
|
|
1886
|
+
{ name, kind }
|
|
1887
|
+
);
|
|
1888
|
+
output(opts, result, (r) => process.stdout.write(r.value));
|
|
1889
|
+
} catch (e) {
|
|
1890
|
+
fail(e, opts);
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
vaultScope(
|
|
1894
|
+
vault.command("rm").description("Delete an item (with --repo, only the repo's override)").argument("<name>", "item name")
|
|
1895
|
+
).action(async (name, opts) => {
|
|
1896
|
+
try {
|
|
1897
|
+
const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind), name });
|
|
1898
|
+
const result = await (await client(opts)).del(path5);
|
|
1899
|
+
output(opts, result, (r) => console.log(`deleted ${pc.cyan(safeTerm(r.name))} ${pc.dim(`(${r.kind})`)}`));
|
|
1900
|
+
} catch (e) {
|
|
1901
|
+
fail(e, opts);
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1717
1904
|
common(
|
|
1718
1905
|
actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
|
|
1719
1906
|
).action(async (runId, opts) => {
|
|
@@ -1804,6 +1991,773 @@ function registerActionsCommands(program2) {
|
|
|
1804
1991
|
});
|
|
1805
1992
|
}
|
|
1806
1993
|
|
|
1994
|
+
// src/market.ts
|
|
1995
|
+
init_esm_shims();
|
|
1996
|
+
import pc2 from "picocolors";
|
|
1997
|
+
var USD_RE = /^\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
1998
|
+
var RATE_UNITS = ["second", "minute", "hour"];
|
|
1999
|
+
function parseRate(price, per) {
|
|
2000
|
+
if (price === void 0) {
|
|
2001
|
+
throw new ActionsCliError("invalid_argument", "A price is required \u2014 pass --price <usd>.");
|
|
2002
|
+
}
|
|
2003
|
+
const usd = Number(price);
|
|
2004
|
+
if (!USD_RE.test(price) || !Number.isFinite(usd) || usd <= 0) {
|
|
2005
|
+
throw new ActionsCliError(
|
|
2006
|
+
"invalid_argument",
|
|
2007
|
+
`Invalid --price "${price}". Expected a positive dollar amount (e.g. 0.12).`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
const unit = per ?? "second";
|
|
2011
|
+
if (!RATE_UNITS.includes(unit)) {
|
|
2012
|
+
throw new ActionsCliError(
|
|
2013
|
+
"invalid_argument",
|
|
2014
|
+
`--per must be second, minute, or hour, got "${per}".`
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
return { usd, per: unit };
|
|
2018
|
+
}
|
|
2019
|
+
function parseCapacity(value) {
|
|
2020
|
+
if (value === void 0) return void 0;
|
|
2021
|
+
const n = Number(value);
|
|
2022
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
2023
|
+
throw new ActionsCliError(
|
|
2024
|
+
"invalid_argument",
|
|
2025
|
+
`Invalid --capacity "${value}". Expected a positive integer.`
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
return n;
|
|
2029
|
+
}
|
|
2030
|
+
function parseExpiresIn(value) {
|
|
2031
|
+
if (value === void 0) return void 0;
|
|
2032
|
+
const hours = Number(value);
|
|
2033
|
+
if (!Number.isFinite(hours) || hours <= 0) {
|
|
2034
|
+
throw new ActionsCliError(
|
|
2035
|
+
"invalid_argument",
|
|
2036
|
+
`Invalid --expires-in "${value}". Expected a positive number of hours.`
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
return hours;
|
|
2040
|
+
}
|
|
2041
|
+
function sellBody(opts) {
|
|
2042
|
+
const rate = parseRate(opts.price, opts.per);
|
|
2043
|
+
const maxConcurrent = parseCapacity(opts.capacity);
|
|
2044
|
+
const expiresInHours = parseExpiresIn(opts.expiresIn);
|
|
2045
|
+
if (opts.renew && expiresInHours === void 0) {
|
|
2046
|
+
throw new ActionsCliError(
|
|
2047
|
+
"invalid_argument",
|
|
2048
|
+
"--renew requires --expires-in \u2014 a standing listing never expires."
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
return {
|
|
2052
|
+
size: opts.size,
|
|
2053
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
2054
|
+
usd: rate.usd,
|
|
2055
|
+
per: rate.per,
|
|
2056
|
+
...maxConcurrent !== void 0 && { maxConcurrent },
|
|
2057
|
+
...expiresInHours !== void 0 && { expiresInHours },
|
|
2058
|
+
...opts.renew && { rollover: true }
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function listingPatchBody(listingId, change) {
|
|
2062
|
+
const body = { askId: listingId };
|
|
2063
|
+
if (change.price !== void 0 || change.per !== void 0) {
|
|
2064
|
+
const rate = parseRate(change.price, change.per);
|
|
2065
|
+
body.usd = rate.usd;
|
|
2066
|
+
body.per = rate.per;
|
|
2067
|
+
}
|
|
2068
|
+
const maxConcurrent = parseCapacity(change.capacity);
|
|
2069
|
+
if (maxConcurrent !== void 0) body.maxConcurrent = maxConcurrent;
|
|
2070
|
+
if (change.status !== void 0) body.status = change.status;
|
|
2071
|
+
if (body.usd === void 0 && body.maxConcurrent === void 0 && body.status === void 0) {
|
|
2072
|
+
throw new ActionsCliError(
|
|
2073
|
+
"invalid_argument",
|
|
2074
|
+
"Nothing to change \u2014 pass --price, --capacity, or a status command."
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
return body;
|
|
2078
|
+
}
|
|
2079
|
+
function credentialEnvVar(providerId, fieldName) {
|
|
2080
|
+
const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
2081
|
+
return `${snake(providerId)}_${snake(fieldName)}`;
|
|
2082
|
+
}
|
|
2083
|
+
function credentialValues(providerId, fields, overrides, env = process.env) {
|
|
2084
|
+
const declared = new Set(fields.map((f) => f.name));
|
|
2085
|
+
const unknown = Object.keys(overrides).filter((name) => !declared.has(name));
|
|
2086
|
+
if (unknown.length > 0) {
|
|
2087
|
+
throw new ActionsCliError(
|
|
2088
|
+
"invalid_argument",
|
|
2089
|
+
`${providerId} takes no credential field "${unknown.join('", "')}" \u2014 expected one of ${fields.map((f) => f.name).join(", ")}.`
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
const values = {};
|
|
2093
|
+
const fromFlag = [];
|
|
2094
|
+
const missing = [];
|
|
2095
|
+
const envNames = {};
|
|
2096
|
+
for (const field of fields) {
|
|
2097
|
+
envNames[field.name] = credentialEnvVar(providerId, field.name);
|
|
2098
|
+
const fromOverride = overrides[field.name];
|
|
2099
|
+
if (fromOverride !== void 0 && fromOverride.trim() !== "") {
|
|
2100
|
+
values[field.name] = fromOverride.trim();
|
|
2101
|
+
fromFlag.push(field.name);
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
const fromEnv = env[envNames[field.name]];
|
|
2105
|
+
if (fromEnv !== void 0 && fromEnv.trim() !== "") {
|
|
2106
|
+
values[field.name] = fromEnv.trim();
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
if (field.required !== false) missing.push(field);
|
|
2110
|
+
}
|
|
2111
|
+
return { values, missing, fromFlag, envNames };
|
|
2112
|
+
}
|
|
2113
|
+
function credentialBody(fields, values) {
|
|
2114
|
+
if (fields.length === 1) {
|
|
2115
|
+
return { key: values[fields[0].name] };
|
|
2116
|
+
}
|
|
2117
|
+
return { fields: values };
|
|
2118
|
+
}
|
|
2119
|
+
function formatUsd(usd) {
|
|
2120
|
+
return `$${usd.toFixed(6).replace(/\.?0+$/, "")}`;
|
|
2121
|
+
}
|
|
2122
|
+
function formatRate(usd, per) {
|
|
2123
|
+
return `${formatUsd(usd)}/${per}`;
|
|
2124
|
+
}
|
|
2125
|
+
function formatListingStatus(ask) {
|
|
2126
|
+
let label;
|
|
2127
|
+
if (ask.status === "withdrawn") label = pc2.gray("withdrawn");
|
|
2128
|
+
else if (ask.status === "paused") label = pc2.yellow("paused");
|
|
2129
|
+
else if (!ask.live) label = pc2.gray("expired");
|
|
2130
|
+
else label = pc2.green("live");
|
|
2131
|
+
if (ask.pendingUsd !== null && ask.pendingPer !== null) {
|
|
2132
|
+
label += pc2.dim(` \u2192 ${formatRate(ask.pendingUsd, ask.pendingPer)} next window`);
|
|
2133
|
+
}
|
|
2134
|
+
return label;
|
|
2135
|
+
}
|
|
2136
|
+
function formatListingRow(ask) {
|
|
2137
|
+
const expiry = ask.expiresAt ? ask.rollover ? `renews ${ask.expiresAt}` : `expires ${ask.expiresAt}` : "standing";
|
|
2138
|
+
return [
|
|
2139
|
+
ask.id,
|
|
2140
|
+
formatListingStatus(ask),
|
|
2141
|
+
safeTerm(ask.size),
|
|
2142
|
+
safeTerm(ask.region ?? "-"),
|
|
2143
|
+
formatRate(ask.usd, ask.per),
|
|
2144
|
+
pc2.dim(`\xD7${ask.maxConcurrent}`),
|
|
2145
|
+
pc2.dim(expiry)
|
|
2146
|
+
].join(" ");
|
|
2147
|
+
}
|
|
2148
|
+
function formatProviderStatus(p5) {
|
|
2149
|
+
const lines = [];
|
|
2150
|
+
lines.push(`${pc2.bold(safeTerm(p5.name))} ${pc2.dim(`selling ${safeTerm(p5.provider)}`)}`);
|
|
2151
|
+
lines.push(`id: ${p5.id}`);
|
|
2152
|
+
lines.push(
|
|
2153
|
+
`credential: ${p5.executorCredentialConnected ? pc2.green("connected") : pc2.yellow("not connected \u2014 your listings cannot fill until `compute market credential connect`")}`
|
|
2154
|
+
);
|
|
2155
|
+
if (p5.revokedAt) lines.push(pc2.red(`revoked: ${p5.revokedAt}`));
|
|
2156
|
+
lines.push(
|
|
2157
|
+
`sizes: ${p5.sizes.map((s) => `${s.name} (${s.label})`).join(", ") || "-"}`
|
|
2158
|
+
);
|
|
2159
|
+
lines.push(`regions: ${p5.regions.join(", ") || "-"}`);
|
|
2160
|
+
if (p5.credentialFields.length > 0) {
|
|
2161
|
+
lines.push(
|
|
2162
|
+
`credential fields: ${p5.credentialFields.map((f) => `${f.name}${f.secret ? " (secret)" : ""}`).join(", ")}`
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
lines.push(`created: ${p5.createdAt}`);
|
|
2166
|
+
return lines.join("\n");
|
|
2167
|
+
}
|
|
2168
|
+
function formatBook(book) {
|
|
2169
|
+
const lines = [];
|
|
2170
|
+
lines.push(pc2.bold("sellers' asking prices"));
|
|
2171
|
+
if (book.asks.length === 0) lines.push(pc2.dim(" no listings are live"));
|
|
2172
|
+
for (const ask of book.asks) {
|
|
2173
|
+
const where = ask.region ? safeTerm(ask.region) : "any region";
|
|
2174
|
+
const expiry = ask.expiresAt ? pc2.dim(ask.rollover ? ` renews ${ask.expiresAt}` : ` until ${ask.expiresAt}`) : "";
|
|
2175
|
+
lines.push(
|
|
2176
|
+
` ${formatRate(ask.usd, ask.per)} ${safeTerm(ask.providerName)} ${safeTerm(ask.size)} ${where} up to ${ask.maxConcurrent} at once${expiry}`
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
lines.push("");
|
|
2180
|
+
lines.push(pc2.bold("buyer offers"));
|
|
2181
|
+
if (book.bids.length === 0) lines.push(pc2.dim(" no open offers"));
|
|
2182
|
+
for (const bid of book.bids) {
|
|
2183
|
+
const where = bid.region ? safeTerm(bid.region) : "anywhere";
|
|
2184
|
+
lines.push(
|
|
2185
|
+
` pays up to ${formatRate(bid.maxUsd, bid.per)} ${safeTerm(bid.size)} ${where} ${pc2.dim(`posted ${bid.createdAt}`)}`
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
lines.push("");
|
|
2189
|
+
lines.push(pc2.bold("recent sales"));
|
|
2190
|
+
if (book.fills.length === 0) lines.push(pc2.dim(" nothing has sold yet"));
|
|
2191
|
+
for (const fill of book.fills) {
|
|
2192
|
+
const statusNote = fill.status === "replaced" ? pc2.yellow(" replaced") : fill.status === "closed" ? pc2.gray(" closed") : "";
|
|
2193
|
+
lines.push(
|
|
2194
|
+
` ${pc2.dim(fill.id)} ${formatRate(fill.usd, fill.per)} ${safeTerm(fill.provider)} ${safeTerm(fill.size)} ${safeTerm(fill.region ?? "-")} ${pc2.dim(fill.createdAt)}${statusNote}`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
return lines.join("\n");
|
|
2198
|
+
}
|
|
2199
|
+
function formatReplacedFill(fill) {
|
|
2200
|
+
const settled = formatUsd(fill.settledMicroUsd / 1e6);
|
|
2201
|
+
const job = fill.jobRequeued ? pc2.dim(" \u2014 job re-places on the buyer's next provider") : "";
|
|
2202
|
+
return `replaced ${pc2.cyan(fill.fillId)} settled ${settled}${job}`;
|
|
2203
|
+
}
|
|
2204
|
+
function replaceTarget(listingId, fillId) {
|
|
2205
|
+
if (fillId !== void 0) {
|
|
2206
|
+
return {
|
|
2207
|
+
path: `/api/v1/market/fills/${encodeURIComponent(fillId)}/replace`,
|
|
2208
|
+
scope: "fill"
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
if (listingId === void 0) {
|
|
2212
|
+
throw new ActionsCliError(
|
|
2213
|
+
"invalid_argument",
|
|
2214
|
+
"Nothing to replace \u2014 pass a listing ID or --fill <fill-id>."
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
return {
|
|
2218
|
+
path: `/api/v1/market/listings/${encodeURIComponent(listingId)}/replace`,
|
|
2219
|
+
scope: "listing"
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
function formatSettlementRow(s) {
|
|
2223
|
+
const period = `${s.periodStart.slice(0, 10)} \u2192 ${s.periodEnd.slice(0, 10)}`;
|
|
2224
|
+
const paid = s.paidAt ? pc2.green(`paid ${s.paidAt.slice(0, 10)}`) : s.invoiceRef ? pc2.yellow(`invoiced ${safeTerm(s.invoiceRef)}`) : pc2.dim("unpaid");
|
|
2225
|
+
return [
|
|
2226
|
+
pc2.dim(period),
|
|
2227
|
+
`${s.vcpuSeconds.toLocaleString("en-US")} vCPU\xB7s`,
|
|
2228
|
+
`gross ${formatUsd(s.grossUsd)}`,
|
|
2229
|
+
`net ${formatUsd(s.netUsd)}`,
|
|
2230
|
+
paid
|
|
2231
|
+
].join(" ");
|
|
2232
|
+
}
|
|
2233
|
+
function registerMarketCommands(program2) {
|
|
2234
|
+
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");
|
|
2236
|
+
common(
|
|
2237
|
+
market.command("listings").alias("list").description("Your listings \u2014 live, paused, expired, and withdrawn")
|
|
2238
|
+
).action(async (opts) => {
|
|
2239
|
+
try {
|
|
2240
|
+
const { asks } = await (await client(opts)).get(
|
|
2241
|
+
"/api/v1/market/asks"
|
|
2242
|
+
);
|
|
2243
|
+
output(opts, asks, (rows) => {
|
|
2244
|
+
if (rows.length === 0) {
|
|
2245
|
+
console.log("No listings yet \u2014 post one with `compute market sell`.");
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
for (const ask of rows) console.log(formatListingRow(ask));
|
|
2249
|
+
});
|
|
2250
|
+
} catch (e) {
|
|
2251
|
+
fail(e, opts);
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
common(
|
|
2255
|
+
market.command("sell").description("Post a listing: sell capacity at a price").requiredOption("--price <usd>", "price per unit of time (e.g. 0.12)").option("--per <unit>", "time unit the price is per: second, minute, or hour (default: second)").option("--size <tier>", "size tier to sell (default: medium, else the provider's first tier)").option("--region <region>", "region to sell in (default: anywhere the provider runs)").option("--capacity <n>", "max simultaneous fills (default: 1)").option("--expires-in <hours>", "delist after this many hours (default: standing)").option("--renew", "re-list for the same window each time it expires (requires --expires-in)")
|
|
2256
|
+
).action(async (opts) => {
|
|
2257
|
+
try {
|
|
2258
|
+
const c = await client(opts);
|
|
2259
|
+
let size = opts.size;
|
|
2260
|
+
if (size === void 0) {
|
|
2261
|
+
const { provider } = await c.get(
|
|
2262
|
+
"/api/v1/market/provider"
|
|
2263
|
+
);
|
|
2264
|
+
const preset = provider.sizes.find((s) => s.name === "medium") ?? provider.sizes[0];
|
|
2265
|
+
if (!preset) {
|
|
2266
|
+
throw new ActionsCliError(
|
|
2267
|
+
"invalid_argument",
|
|
2268
|
+
`--size is required \u2014 ${provider.provider} advertises no size tiers.`
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
size = preset.name;
|
|
2272
|
+
console.error(pc2.dim(`no --size given \u2014 listing at "${size}"`));
|
|
2273
|
+
}
|
|
2274
|
+
const result = await c.post(
|
|
2275
|
+
"/api/v1/market/asks",
|
|
2276
|
+
sellBody({ ...opts, size })
|
|
2277
|
+
);
|
|
2278
|
+
output(opts, result, (r) => {
|
|
2279
|
+
console.log(`listed ${pc2.cyan(r.ask.id)}`);
|
|
2280
|
+
console.log(formatListingRow(r.ask));
|
|
2281
|
+
});
|
|
2282
|
+
} catch (e) {
|
|
2283
|
+
fail(e, opts);
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
common(
|
|
2287
|
+
market.command("price").description("Reprice a listing").argument("<listing-id>", "listing ID").requiredOption("--price <usd>", "new price per unit of time (e.g. 0.08)").option("--per <unit>", "time unit the price is per: second, minute, or hour (default: second)")
|
|
2288
|
+
).action(async (listingId, opts) => {
|
|
2289
|
+
try {
|
|
2290
|
+
const result = await (await client(opts)).post(
|
|
2291
|
+
"/api/v1/market/asks",
|
|
2292
|
+
listingPatchBody(listingId, { price: opts.price, per: opts.per })
|
|
2293
|
+
);
|
|
2294
|
+
output(opts, result, (r) => {
|
|
2295
|
+
const rate = r.ask.pendingUsd !== null && r.ask.pendingPer !== null ? `${formatRate(r.ask.pendingUsd, r.ask.pendingPer)} ${pc2.dim("(applies next window)")}` : formatRate(r.ask.usd, r.ask.per);
|
|
2296
|
+
console.log(`repriced ${pc2.cyan(r.ask.id)} ${rate}`);
|
|
2297
|
+
});
|
|
2298
|
+
} catch (e) {
|
|
2299
|
+
fail(e, opts);
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
const setStatus = async (listingId, status, opts) => {
|
|
2303
|
+
try {
|
|
2304
|
+
const result = await (await client(opts)).post(
|
|
2305
|
+
"/api/v1/market/asks",
|
|
2306
|
+
listingPatchBody(listingId, { status })
|
|
2307
|
+
);
|
|
2308
|
+
output(opts, result, (r) => {
|
|
2309
|
+
console.log(`${status === "live" ? "resumed" : "paused"} ${pc2.cyan(r.ask.id)}`);
|
|
2310
|
+
});
|
|
2311
|
+
} catch (e) {
|
|
2312
|
+
fail(e, opts);
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
common(
|
|
2316
|
+
market.command("pause").description("Pause a listing \u2014 it stays posted but cannot fill").argument("<listing-id>", "listing ID")
|
|
2317
|
+
).action((listingId, opts) => setStatus(listingId, "paused", opts));
|
|
2318
|
+
common(
|
|
2319
|
+
market.command("resume").description("Resume a paused listing").argument("<listing-id>", "listing ID")
|
|
2320
|
+
).action((listingId, opts) => setStatus(listingId, "live", opts));
|
|
2321
|
+
common(
|
|
2322
|
+
market.command("withdraw").description("Withdraw a listing \u2014 it leaves the book for good").argument("<listing-id>", "listing ID")
|
|
2323
|
+
).action(async (listingId, opts) => {
|
|
2324
|
+
try {
|
|
2325
|
+
const result = await (await client(opts)).del(
|
|
2326
|
+
`/api/v1/market/asks?id=${encodeURIComponent(listingId)}`
|
|
2327
|
+
);
|
|
2328
|
+
output(opts, result, (r) => {
|
|
2329
|
+
console.log(`withdrew ${pc2.cyan(r.ask.id)}`);
|
|
2330
|
+
});
|
|
2331
|
+
} catch (e) {
|
|
2332
|
+
fail(e, opts);
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
common(
|
|
2336
|
+
market.command("replace").description(
|
|
2337
|
+
"Take capacity back: evict a sale, or pause a listing and evict every sale on it.\nAn evicted Actions job restarts on the buyer's next provider; the buyer pays\nonly for the seconds the sale actually ran."
|
|
2338
|
+
).argument("[listing-id]", "listing to pause and evict every sale on").option("--fill <fill-id>", "evict one sale without touching the listing")
|
|
2339
|
+
).action(
|
|
2340
|
+
async (listingId, opts) => {
|
|
2341
|
+
try {
|
|
2342
|
+
const target = replaceTarget(listingId, opts.fill);
|
|
2343
|
+
const c = await client(opts);
|
|
2344
|
+
if (target.scope === "fill") {
|
|
2345
|
+
const result = await c.post(target.path, {});
|
|
2346
|
+
output(opts, result, (r) => console.log(formatReplacedFill(r.fill)));
|
|
2347
|
+
} else {
|
|
2348
|
+
const result = await c.post(target.path, {});
|
|
2349
|
+
output(opts, result, (r) => {
|
|
2350
|
+
console.log(`paused ${pc2.cyan(r.listing.id)}`);
|
|
2351
|
+
if (r.fills.length === 0) {
|
|
2352
|
+
console.log(pc2.dim(" no live sales to evict"));
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
for (const f of r.fills) {
|
|
2356
|
+
if (f.ok) console.log(formatReplacedFill(f.fill));
|
|
2357
|
+
else console.log(pc2.red(` could not evict a sale \u2014 ${f.error}`));
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
if (result.fills.some((f) => !f.ok)) process.exitCode = 1;
|
|
2361
|
+
}
|
|
2362
|
+
} catch (e) {
|
|
2363
|
+
fail(e, opts);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2367
|
+
common(
|
|
2368
|
+
market.command("book").description("Live prices: sellers' asking prices, buyer offers, recent sales")
|
|
2369
|
+
).action(async (opts) => {
|
|
2370
|
+
try {
|
|
2371
|
+
const book = await (await client(opts)).get(
|
|
2372
|
+
"/api/v1/market/book"
|
|
2373
|
+
);
|
|
2374
|
+
output(opts, book, (b) => console.log(formatBook(b)));
|
|
2375
|
+
} catch (e) {
|
|
2376
|
+
fail(e, opts);
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
common(
|
|
2380
|
+
market.command("settlements").description("Your monthly payout reports")
|
|
2381
|
+
).action(async (opts) => {
|
|
2382
|
+
try {
|
|
2383
|
+
const { settlements } = await (await client(opts)).get("/api/v1/market/settlements");
|
|
2384
|
+
output(opts, settlements, (rows) => {
|
|
2385
|
+
if (rows.length === 0) {
|
|
2386
|
+
console.log("No settlements yet.");
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
for (const s of rows) console.log(formatSettlementRow(s));
|
|
2390
|
+
});
|
|
2391
|
+
} catch (e) {
|
|
2392
|
+
fail(e, opts);
|
|
2393
|
+
}
|
|
2394
|
+
});
|
|
2395
|
+
common(
|
|
2396
|
+
market.command("status").description("Who you are on the market: seller identity, credential, sizes, regions")
|
|
2397
|
+
).action(async (opts) => {
|
|
2398
|
+
try {
|
|
2399
|
+
const { provider } = await (await client(opts)).get("/api/v1/market/provider");
|
|
2400
|
+
output(opts, provider, (p5) => console.log(formatProviderStatus(p5)));
|
|
2401
|
+
} catch (e) {
|
|
2402
|
+
fail(e, opts);
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
const credential = market.command("credential").description("Connect the credential your listings fill under");
|
|
2406
|
+
common(
|
|
2407
|
+
credential.command("connect").description(
|
|
2408
|
+
"Save the executor credential your sold capacity runs under. Each field is read from\na <PROVIDER>_<FIELD> env var (e.g. TENSORLAKE_API_KEY) \u2014 never pass secrets in argv;\n--field name=value overrides a field (visible in shell history)"
|
|
2409
|
+
).option("--field <pairs...>", "credential fields as name=value (prefer env vars for secrets)")
|
|
2410
|
+
).action(async (opts) => {
|
|
2411
|
+
try {
|
|
2412
|
+
const c = await secretValueClient(opts, "credential values");
|
|
2413
|
+
const { provider } = await c.get(
|
|
2414
|
+
"/api/v1/market/provider"
|
|
2415
|
+
);
|
|
2416
|
+
if (provider.credentialFields.length === 0) {
|
|
2417
|
+
throw new ActionsCliError(
|
|
2418
|
+
"invalid_argument",
|
|
2419
|
+
`${provider.provider} authenticates with the deployment's own identity \u2014 there is no credential to connect.`
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
const resolved = credentialValues(
|
|
2423
|
+
provider.provider,
|
|
2424
|
+
provider.credentialFields,
|
|
2425
|
+
parseInputs(opts.field)
|
|
2426
|
+
);
|
|
2427
|
+
if (resolved.missing.length > 0) {
|
|
2428
|
+
const hints = resolved.missing.map((f) => ` export ${resolved.envNames[f.name]}=\u2026 # ${f.label}`).join("\n");
|
|
2429
|
+
throw new ActionsCliError(
|
|
2430
|
+
"invalid_argument",
|
|
2431
|
+
`Missing credential field${resolved.missing.length === 1 ? "" : "s"}. Set:
|
|
2432
|
+
${hints}`
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
if (resolved.fromFlag.some((name) => provider.credentialFields.find((f) => f.name === name)?.secret)) {
|
|
2436
|
+
console.error(
|
|
2437
|
+
pc2.yellow("note: a secret passed via --field stays in your shell history \u2014 prefer the env vars.")
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
const result = await c.post(
|
|
2441
|
+
"/api/v1/market/provider/credential",
|
|
2442
|
+
credentialBody(provider.credentialFields, resolved.values)
|
|
2443
|
+
);
|
|
2444
|
+
output(opts, result, (r) => {
|
|
2445
|
+
console.log(`connected ${pc2.cyan(safeTerm(r.provider.name))} credential saved`);
|
|
2446
|
+
});
|
|
2447
|
+
} catch (e) {
|
|
2448
|
+
fail(e, opts);
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
common(
|
|
2452
|
+
credential.command("disconnect").description("Remove the stored credential \u2014 listings stop filling until a new one connects")
|
|
2453
|
+
).action(async (opts) => {
|
|
2454
|
+
try {
|
|
2455
|
+
const result = await (await client(opts)).del(
|
|
2456
|
+
"/api/v1/market/provider/credential"
|
|
2457
|
+
);
|
|
2458
|
+
output(opts, result, (r) => {
|
|
2459
|
+
console.log(`disconnected ${pc2.cyan(safeTerm(r.provider.name))} credential removed`);
|
|
2460
|
+
});
|
|
2461
|
+
} catch (e) {
|
|
2462
|
+
fail(e, opts);
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/sandboxes.ts
|
|
2468
|
+
init_esm_shims();
|
|
2469
|
+
import pc3 from "picocolors";
|
|
2470
|
+
function shellQuote(arg) {
|
|
2471
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
2472
|
+
}
|
|
2473
|
+
function printSandbox(s) {
|
|
2474
|
+
const attach = "attach" in s ? s.attach : void 0;
|
|
2475
|
+
console.log(`${pc3.cyan(s.id)} ${s.status} ${s.provider ?? "\u2014"} ${s.providerSandboxId ?? "\u2014"}`);
|
|
2476
|
+
console.log(` label: ${s.label ?? "\u2014"} commands: ${s.commandCount} created: ${s.createdAt}`);
|
|
2477
|
+
if (s.destroyError) console.log(` destroyError: ${pc3.red(s.destroyError)}`);
|
|
2478
|
+
if (attach !== void 0) {
|
|
2479
|
+
console.log(
|
|
2480
|
+
attach === null ? " attach: none (market fill or ambient provider \u2014 platform drive only)" : ` attach: ${attach.provider} / ${attach.providerSandboxId}`
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function printProcess(p5) {
|
|
2485
|
+
const exit = p5.status === "exited" ? `exit=${p5.exitCode ?? "?"}${p5.signal ? ` (${p5.signal})` : ""}` : "";
|
|
2486
|
+
console.log(`${pc3.cyan(p5.jobId)} ${p5.status}${exit ? " " + exit : ""} ${p5.command}`);
|
|
2487
|
+
}
|
|
2488
|
+
function registerSandboxesCommands(program2) {
|
|
2489
|
+
const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
|
|
2490
|
+
cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
|
|
2491
|
+
try {
|
|
2492
|
+
const c = await client(opts);
|
|
2493
|
+
const resources = {};
|
|
2494
|
+
if (opts.cpus) resources.cpus = Number(opts.cpus);
|
|
2495
|
+
if (opts.memoryMb) resources.memoryMb = Number(opts.memoryMb);
|
|
2496
|
+
if (opts.diskMb) resources.ephemeralDiskMb = Number(opts.diskMb);
|
|
2497
|
+
const body = {};
|
|
2498
|
+
if (opts.order) body.providerOrder = opts.order.split(",").map((s) => s.trim());
|
|
2499
|
+
if (opts.label) body.label = opts.label;
|
|
2500
|
+
if (opts.image) body.image = opts.image;
|
|
2501
|
+
if (opts.snapshotId) body.snapshotId = opts.snapshotId;
|
|
2502
|
+
if (opts.timeoutMs) body.timeoutMs = Number(opts.timeoutMs);
|
|
2503
|
+
if (opts.secret && opts.secret.length > 0) body.secrets = opts.secret;
|
|
2504
|
+
if (Object.keys(resources).length > 0) body.resources = resources;
|
|
2505
|
+
const res = await c.post("/api/v1/sandboxes", body);
|
|
2506
|
+
output(opts, res.sandbox, printSandbox);
|
|
2507
|
+
} catch (e) {
|
|
2508
|
+
fail(e, opts);
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
cmd.command("list").description("List sandboxes").option("--status <status>", "creating | running | destroyed").option("--limit <n>", "page size").option("--cursor <cursor>", "page cursor from a previous response").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (opts) => {
|
|
2512
|
+
try {
|
|
2513
|
+
const c = await client(opts);
|
|
2514
|
+
const res = await c.get(
|
|
2515
|
+
"/api/v1/sandboxes",
|
|
2516
|
+
{ status: opts.status, limit: opts.limit, cursor: opts.cursor }
|
|
2517
|
+
);
|
|
2518
|
+
output(opts, res, (page) => {
|
|
2519
|
+
page.sandboxes.forEach(printSandbox);
|
|
2520
|
+
if (page.nextCursor) console.log(pc3.dim(`nextCursor: ${page.nextCursor}`));
|
|
2521
|
+
});
|
|
2522
|
+
} catch (e) {
|
|
2523
|
+
fail(e, opts);
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
cmd.command("get <sandboxId>").description("Show one sandbox (includes the BYOK attach descriptor)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2527
|
+
try {
|
|
2528
|
+
const c = await client(opts);
|
|
2529
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}`);
|
|
2530
|
+
output(opts, res.sandbox, printSandbox);
|
|
2531
|
+
} catch (e) {
|
|
2532
|
+
fail(e, opts);
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
cmd.command("destroy <sandboxId>").description("Destroy a sandbox (verified teardown + cost settle)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2536
|
+
try {
|
|
2537
|
+
const c = await client(opts);
|
|
2538
|
+
const res = await c.del(`/api/v1/sandboxes/${sandboxId}`);
|
|
2539
|
+
output(opts, res.sandbox, printSandbox);
|
|
2540
|
+
} catch (e) {
|
|
2541
|
+
fail(e, opts);
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
|
|
2545
|
+
try {
|
|
2546
|
+
const c = await client(opts);
|
|
2547
|
+
const res = await c.post(
|
|
2548
|
+
`/api/v1/sandboxes/${sandboxId}/commands`,
|
|
2549
|
+
{
|
|
2550
|
+
command: command.map(shellQuote).join(" "),
|
|
2551
|
+
...opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
output(opts, res, (r) => {
|
|
2555
|
+
process.stdout.write(r.stdout);
|
|
2556
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
2557
|
+
if (!r.stdout) console.log(`exit ${r.exitCode} (${r.durationMs}ms)`);
|
|
2558
|
+
});
|
|
2559
|
+
if (res.exitCode !== 0) process.exitCode = res.exitCode;
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
fail(e, opts);
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
|
|
2565
|
+
try {
|
|
2566
|
+
const c = await client(opts);
|
|
2567
|
+
const res = await c.post(
|
|
2568
|
+
`/api/v1/sandboxes/${sandboxId}/processes`,
|
|
2569
|
+
{
|
|
2570
|
+
command: command.map(shellQuote).join(" "),
|
|
2571
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
2572
|
+
...opts.env && opts.env.length > 0 ? { env: parseInputs(opts.env) } : {}
|
|
2573
|
+
}
|
|
2574
|
+
);
|
|
2575
|
+
output(opts, res.process, printProcess);
|
|
2576
|
+
} catch (e) {
|
|
2577
|
+
fail(e, opts);
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
cmd.command("ps <sandboxId>").description("List the sandbox's tracked processes").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2581
|
+
try {
|
|
2582
|
+
const c = await client(opts);
|
|
2583
|
+
const res = await c.get(
|
|
2584
|
+
`/api/v1/sandboxes/${sandboxId}/processes`
|
|
2585
|
+
);
|
|
2586
|
+
output(opts, res.processes, (list) => list.forEach(printProcess));
|
|
2587
|
+
} catch (e) {
|
|
2588
|
+
fail(e, opts);
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
cmd.command("logs <sandboxId> <jobId>").description("Show a process's buffered output (--follow streams until exit)").option("-f, --follow", "stream output until the process exits").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2592
|
+
try {
|
|
2593
|
+
const c = await client(opts);
|
|
2594
|
+
if (opts.follow) {
|
|
2595
|
+
await followProcess(c, sandboxId, jobId);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const res = await c.get(
|
|
2599
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2600
|
+
);
|
|
2601
|
+
output(opts, res.process, (p5) => {
|
|
2602
|
+
if (p5.stdout) process.stdout.write(p5.stdout);
|
|
2603
|
+
if (p5.stderr) process.stderr.write(p5.stderr);
|
|
2604
|
+
if (!p5.stdout && !p5.stderr) printProcess(p5);
|
|
2605
|
+
});
|
|
2606
|
+
} catch (e) {
|
|
2607
|
+
fail(e, opts);
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
cmd.command("wait <sandboxId> <jobId>").description("Block until a process exits").option("--timeout-ms <ms>", "bound the wait (job keeps running on timeout)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2611
|
+
try {
|
|
2612
|
+
const c = await client(opts);
|
|
2613
|
+
const res = await c.post(
|
|
2614
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/wait`,
|
|
2615
|
+
opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2616
|
+
);
|
|
2617
|
+
output(opts, res.process, printProcess);
|
|
2618
|
+
if (res.process.status === "exited") {
|
|
2619
|
+
process.exitCode = res.process.exitCode ?? 128;
|
|
2620
|
+
}
|
|
2621
|
+
} catch (e) {
|
|
2622
|
+
fail(e, opts);
|
|
2623
|
+
}
|
|
2624
|
+
});
|
|
2625
|
+
cmd.command("kill <sandboxId> <jobId>").description("Signal a process (default SIGTERM)").option("--signal <signal>", "signal name or number, e.g. SIGKILL or 9").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2626
|
+
try {
|
|
2627
|
+
const c = await client(opts);
|
|
2628
|
+
const res = await c.post(
|
|
2629
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/kill`,
|
|
2630
|
+
opts.signal ? { signal: opts.signal } : {}
|
|
2631
|
+
);
|
|
2632
|
+
output(opts, res.process, printProcess);
|
|
2633
|
+
} catch (e) {
|
|
2634
|
+
fail(e, opts);
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
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, path5, opts) => {
|
|
2638
|
+
try {
|
|
2639
|
+
const c = await client(opts);
|
|
2640
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2641
|
+
path: path5 ?? "/"
|
|
2642
|
+
});
|
|
2643
|
+
output(opts, res, (view) => {
|
|
2644
|
+
if (view.type === "directory") {
|
|
2645
|
+
for (const e of view.entries) {
|
|
2646
|
+
console.log(`${e.type === "directory" ? pc3.blue("d") : " "} ${e.name}${e.size !== void 0 ? ` ${pc3.dim(String(e.size))}` : ""}`);
|
|
2647
|
+
}
|
|
2648
|
+
} else {
|
|
2649
|
+
console.log(`${view.path} file ${view.content.length} chars`);
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
} catch (e) {
|
|
2653
|
+
fail(e, opts);
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
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, path5, opts) => {
|
|
2657
|
+
try {
|
|
2658
|
+
const c = await client(opts);
|
|
2659
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path: path5 });
|
|
2660
|
+
output(opts, res, (view) => {
|
|
2661
|
+
if (view.type !== "file") {
|
|
2662
|
+
console.error(pc3.red(`${view.path} is a directory`));
|
|
2663
|
+
process.exit(1);
|
|
2664
|
+
}
|
|
2665
|
+
process.stdout.write(view.content);
|
|
2666
|
+
if (!view.content.endsWith("\n")) process.stdout.write("\n");
|
|
2667
|
+
});
|
|
2668
|
+
} catch (e) {
|
|
2669
|
+
fail(e, opts);
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
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, path5, opts) => {
|
|
2673
|
+
try {
|
|
2674
|
+
let content = opts.content;
|
|
2675
|
+
if (content === void 0 && opts.file) {
|
|
2676
|
+
const { readFile } = await import("fs/promises");
|
|
2677
|
+
content = await readFile(opts.file, "utf8");
|
|
2678
|
+
}
|
|
2679
|
+
if (content === void 0 && !process.stdin.isTTY) {
|
|
2680
|
+
content = await new Promise((resolve) => {
|
|
2681
|
+
let data = "";
|
|
2682
|
+
process.stdin.setEncoding("utf8");
|
|
2683
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
2684
|
+
process.stdin.on("end", () => resolve(data));
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
if (content === void 0) {
|
|
2688
|
+
console.error(pc3.red("Pass --content, --file, or pipe stdin"));
|
|
2689
|
+
process.exit(1);
|
|
2690
|
+
}
|
|
2691
|
+
const c = await client(opts);
|
|
2692
|
+
const res = await c.post(
|
|
2693
|
+
`/api/v1/sandboxes/${sandboxId}/files`,
|
|
2694
|
+
{ path: path5, content }
|
|
2695
|
+
);
|
|
2696
|
+
output(opts, res, (r) => console.log(`wrote ${r.path}`));
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
fail(e, opts);
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
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, path5, opts) => {
|
|
2702
|
+
try {
|
|
2703
|
+
const c = await client(opts);
|
|
2704
|
+
const res = await c.post(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2705
|
+
path: path5,
|
|
2706
|
+
mkdir: true
|
|
2707
|
+
});
|
|
2708
|
+
output(opts, res, (r) => console.log(`created ${r.path}`));
|
|
2709
|
+
} catch (e) {
|
|
2710
|
+
fail(e, opts);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
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, path5, opts) => {
|
|
2714
|
+
try {
|
|
2715
|
+
const c = await client(opts);
|
|
2716
|
+
const res = await c.del(
|
|
2717
|
+
`/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path5)}`
|
|
2718
|
+
);
|
|
2719
|
+
output(opts, res, (r) => console.log(`removed ${r.path}`));
|
|
2720
|
+
} catch (e) {
|
|
2721
|
+
fail(e, opts);
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
cmd.command("url <sandboxId>").description("Resolve the provider's public URL for a port").requiredOption("--port <n>", "port the service listens on").option("--protocol <protocol>", "protocol (default provider-chosen)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2725
|
+
try {
|
|
2726
|
+
const c = await client(opts);
|
|
2727
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/urls`, {
|
|
2728
|
+
port: opts.port,
|
|
2729
|
+
protocol: opts.protocol
|
|
2730
|
+
});
|
|
2731
|
+
output(opts, res, (r) => console.log(safeTerm(r.url)));
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
fail(e, opts);
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
async function followProcess(c, sandboxId, jobId) {
|
|
2738
|
+
let exitCode = 0;
|
|
2739
|
+
for await (const { event, data } of c.sseNamed(
|
|
2740
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2741
|
+
)) {
|
|
2742
|
+
const payload = data;
|
|
2743
|
+
if (event === "stdout") process.stdout.write(String(payload.chunk ?? ""));
|
|
2744
|
+
else if (event === "stderr") process.stderr.write(String(payload.chunk ?? ""));
|
|
2745
|
+
else if (event === "exit") {
|
|
2746
|
+
exitCode = typeof payload.exitCode === "number" ? payload.exitCode : 128;
|
|
2747
|
+
return finish();
|
|
2748
|
+
} else if (event === "error") {
|
|
2749
|
+
console.error(pc3.red(`Error: ${String(payload.error ?? "stream error")}`));
|
|
2750
|
+
return finish(1);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
console.error(pc3.yellow("stream ended before the process exited"));
|
|
2754
|
+
return finish(2);
|
|
2755
|
+
function finish(code) {
|
|
2756
|
+
if (code !== void 0) exitCode = code;
|
|
2757
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
1807
2761
|
// src/index.ts
|
|
1808
2762
|
init_repl();
|
|
1809
2763
|
|
|
@@ -1816,7 +2770,7 @@ import * as fs2 from "fs";
|
|
|
1816
2770
|
import * as path3 from "path";
|
|
1817
2771
|
import * as os2 from "os";
|
|
1818
2772
|
import * as p from "@clack/prompts";
|
|
1819
|
-
import
|
|
2773
|
+
import pc5 from "picocolors";
|
|
1820
2774
|
function getCredentialsDir() {
|
|
1821
2775
|
return path3.join(os2.homedir(), ".computesdk");
|
|
1822
2776
|
}
|
|
@@ -1874,7 +2828,7 @@ function openBrowser(url) {
|
|
|
1874
2828
|
if (err) {
|
|
1875
2829
|
p.log.warn("Could not open browser automatically.");
|
|
1876
2830
|
p.log.info(`Open this URL manually:
|
|
1877
|
-
${
|
|
2831
|
+
${pc5.cyan(url)}`);
|
|
1878
2832
|
}
|
|
1879
2833
|
});
|
|
1880
2834
|
}
|
|
@@ -1945,7 +2899,7 @@ async function resolveApiKey(options) {
|
|
|
1945
2899
|
const stored = loadStoredCredentials();
|
|
1946
2900
|
if (stored) {
|
|
1947
2901
|
p.log.info(
|
|
1948
|
-
`Using stored credentials from ${
|
|
2902
|
+
`Using stored credentials from ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
1949
2903
|
);
|
|
1950
2904
|
return stored;
|
|
1951
2905
|
}
|
|
@@ -1959,14 +2913,14 @@ async function resolveApiKey(options) {
|
|
|
1959
2913
|
try {
|
|
1960
2914
|
storeCredentials(apiKey);
|
|
1961
2915
|
p.log.success(
|
|
1962
|
-
`Credentials saved to ${
|
|
2916
|
+
`Credentials saved to ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
1963
2917
|
);
|
|
1964
2918
|
} catch {
|
|
1965
2919
|
p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
|
|
1966
2920
|
}
|
|
1967
2921
|
return apiKey;
|
|
1968
2922
|
} catch (error) {
|
|
1969
|
-
spinner4.stop(
|
|
2923
|
+
spinner4.stop(pc5.red("Authentication failed"));
|
|
1970
2924
|
throw error;
|
|
1971
2925
|
}
|
|
1972
2926
|
}
|
|
@@ -1974,7 +2928,7 @@ async function resolveApiKey(options) {
|
|
|
1974
2928
|
// src/setup.ts
|
|
1975
2929
|
init_esm_shims();
|
|
1976
2930
|
import * as p2 from "@clack/prompts";
|
|
1977
|
-
import
|
|
2931
|
+
import pc6 from "picocolors";
|
|
1978
2932
|
import { compute as compute2 } from "computesdk";
|
|
1979
2933
|
async function ensureAuth(options) {
|
|
1980
2934
|
try {
|
|
@@ -1982,8 +2936,8 @@ async function ensureAuth(options) {
|
|
|
1982
2936
|
process.env.COMPUTESDK_API_KEY = apiKey;
|
|
1983
2937
|
} catch (error) {
|
|
1984
2938
|
p2.log.error(error.message);
|
|
1985
|
-
p2.log.info(`Get your API key at ${
|
|
1986
|
-
p2.outro(
|
|
2939
|
+
p2.log.info(`Get your API key at ${pc6.cyan("https://console.computesdk.com")}`);
|
|
2940
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
1987
2941
|
process.exit(1);
|
|
1988
2942
|
}
|
|
1989
2943
|
}
|
|
@@ -2012,7 +2966,7 @@ async function resolveProvider(providerFlag, options) {
|
|
|
2012
2966
|
if (available.length === 0) {
|
|
2013
2967
|
p2.log.error("No provider credentials detected");
|
|
2014
2968
|
p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
|
|
2015
|
-
p2.outro(
|
|
2969
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
2016
2970
|
process.exit(1);
|
|
2017
2971
|
}
|
|
2018
2972
|
if (available.length === 1) {
|
|
@@ -2056,24 +3010,24 @@ program.name("compute").description("Cloud-native workspace management").version
|
|
|
2056
3010
|
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
|
|
2057
3011
|
if (opts.logout) {
|
|
2058
3012
|
console.log();
|
|
2059
|
-
p4.intro(
|
|
3013
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2060
3014
|
clearStoredCredentials();
|
|
2061
3015
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2062
|
-
p4.outro(
|
|
3016
|
+
p4.outro(pc8.green("Done!"));
|
|
2063
3017
|
process.exit(0);
|
|
2064
3018
|
}
|
|
2065
3019
|
if (opts.login) {
|
|
2066
3020
|
console.log();
|
|
2067
|
-
p4.intro(
|
|
3021
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2068
3022
|
await ensureAuth({ forceLogin: true });
|
|
2069
|
-
p4.outro(
|
|
3023
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2070
3024
|
process.exit(0);
|
|
2071
3025
|
}
|
|
2072
3026
|
program.help();
|
|
2073
3027
|
});
|
|
2074
3028
|
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) => {
|
|
2075
3029
|
console.log();
|
|
2076
|
-
p4.intro(
|
|
3030
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2077
3031
|
await ensureAuth();
|
|
2078
3032
|
if (!repo) {
|
|
2079
3033
|
const state = loadWorkspaceState();
|
|
@@ -2094,7 +3048,7 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2094
3048
|
message: "Select workspace to attach (or create new)",
|
|
2095
3049
|
options: [
|
|
2096
3050
|
...choices,
|
|
2097
|
-
{ value: "__new__", label:
|
|
3051
|
+
{ value: "__new__", label: pc8.gray("+ Create new workspace") }
|
|
2098
3052
|
]
|
|
2099
3053
|
});
|
|
2100
3054
|
if (p4.isCancel(selected)) {
|
|
@@ -2135,15 +3089,15 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2135
3089
|
branch: options.branch,
|
|
2136
3090
|
provider
|
|
2137
3091
|
});
|
|
2138
|
-
spinner4.stop(`Workspace ready: ${
|
|
3092
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2139
3093
|
if (options.attach !== false) {
|
|
2140
3094
|
console.log();
|
|
2141
3095
|
await workspaceAttach(workspace.id);
|
|
2142
3096
|
} else {
|
|
2143
|
-
p4.outro(
|
|
3097
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2144
3098
|
}
|
|
2145
3099
|
} catch (error) {
|
|
2146
|
-
spinner4.stop(
|
|
3100
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2147
3101
|
p4.log.error(error.message);
|
|
2148
3102
|
process.exit(1);
|
|
2149
3103
|
}
|
|
@@ -2152,7 +3106,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
|
|
|
2152
3106
|
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) => {
|
|
2153
3107
|
if (!opts.silent) {
|
|
2154
3108
|
console.log();
|
|
2155
|
-
p4.intro(
|
|
3109
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2156
3110
|
}
|
|
2157
3111
|
await ensureAuth();
|
|
2158
3112
|
const provider = await resolveProvider(opts.provider);
|
|
@@ -2164,7 +3118,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2164
3118
|
const sandbox = await compute4.sandbox.create(
|
|
2165
3119
|
buildCreateOptions(opts)
|
|
2166
3120
|
);
|
|
2167
|
-
spinner4.stop(`Sandbox ready: ${
|
|
3121
|
+
spinner4.stop(`Sandbox ready: ${pc8.cyan(sandbox.sandboxId)}`);
|
|
2168
3122
|
const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2169
3123
|
await saveSandbox2({
|
|
2170
3124
|
id: sandbox.sandboxId,
|
|
@@ -2177,7 +3131,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2177
3131
|
await startREPL(sandbox, provider);
|
|
2178
3132
|
}
|
|
2179
3133
|
} catch (error) {
|
|
2180
|
-
spinner4.stop(
|
|
3134
|
+
spinner4.stop(pc8.red("Failed to create sandbox"));
|
|
2181
3135
|
p4.log.error(error.message);
|
|
2182
3136
|
process.exit(1);
|
|
2183
3137
|
}
|
|
@@ -2206,14 +3160,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2206
3160
|
});
|
|
2207
3161
|
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) => {
|
|
2208
3162
|
console.log();
|
|
2209
|
-
p4.intro(
|
|
3163
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2210
3164
|
const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2211
3165
|
const state = loadState2();
|
|
2212
3166
|
if (!sandboxIds || sandboxIds.length === 0) {
|
|
2213
3167
|
const workspaces = Object.values(state.workspaces);
|
|
2214
3168
|
if (workspaces.length === 0) {
|
|
2215
3169
|
p4.log.info("No sandboxes to destroy.");
|
|
2216
|
-
p4.outro(
|
|
3170
|
+
p4.outro(pc8.green("Done!"));
|
|
2217
3171
|
return;
|
|
2218
3172
|
}
|
|
2219
3173
|
const choices = workspaces.map((ws) => {
|
|
@@ -2221,7 +3175,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2221
3175
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2222
3176
|
);
|
|
2223
3177
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2224
|
-
const status = age > 7 ?
|
|
3178
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2225
3179
|
return {
|
|
2226
3180
|
value: ws.sandboxId,
|
|
2227
3181
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2249,7 +3203,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2249
3203
|
for (const result of results) {
|
|
2250
3204
|
if (result.status === "fulfilled") {
|
|
2251
3205
|
const sandboxId = result.value;
|
|
2252
|
-
console.log(
|
|
3206
|
+
console.log(pc8.green(`Destroyed ${sandboxId}`));
|
|
2253
3207
|
const workspaceId = Object.keys(state.workspaces).find(
|
|
2254
3208
|
(key) => state.workspaces[key].sandboxId === sandboxId
|
|
2255
3209
|
);
|
|
@@ -2257,19 +3211,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2257
3211
|
delete state.workspaces[workspaceId];
|
|
2258
3212
|
}
|
|
2259
3213
|
} else {
|
|
2260
|
-
console.error(
|
|
3214
|
+
console.error(pc8.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
|
|
2261
3215
|
}
|
|
2262
3216
|
}
|
|
2263
3217
|
saveState2(state);
|
|
2264
3218
|
});
|
|
2265
3219
|
sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
|
|
2266
3220
|
console.log();
|
|
2267
|
-
p4.intro(
|
|
3221
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2268
3222
|
await sandboxList();
|
|
2269
3223
|
});
|
|
2270
3224
|
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) => {
|
|
2271
3225
|
console.log();
|
|
2272
|
-
p4.intro(
|
|
3226
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2273
3227
|
await ensureAuth();
|
|
2274
3228
|
const provider = await resolveProvider(opts.provider);
|
|
2275
3229
|
configureCompute(provider);
|
|
@@ -2280,7 +3234,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2280
3234
|
if (workspaces.length === 0) {
|
|
2281
3235
|
p4.log.info("No sandboxes found.");
|
|
2282
3236
|
p4.log.info("Create one with: compute sandbox create");
|
|
2283
|
-
p4.outro(
|
|
3237
|
+
p4.outro(pc8.green("Done!"));
|
|
2284
3238
|
return;
|
|
2285
3239
|
}
|
|
2286
3240
|
const choices = workspaces.map((ws) => {
|
|
@@ -2288,7 +3242,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2288
3242
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2289
3243
|
);
|
|
2290
3244
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2291
|
-
const status = age > 7 ?
|
|
3245
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2292
3246
|
return {
|
|
2293
3247
|
value: ws.sandboxId,
|
|
2294
3248
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2308,7 +3262,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2308
3262
|
spinner4.start("Connecting...");
|
|
2309
3263
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2310
3264
|
if (!sandbox) {
|
|
2311
|
-
spinner4.stop(
|
|
3265
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2312
3266
|
process.exit(1);
|
|
2313
3267
|
}
|
|
2314
3268
|
spinner4.stop("");
|
|
@@ -2320,7 +3274,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2320
3274
|
configureCompute(provider);
|
|
2321
3275
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2322
3276
|
if (!sandbox) {
|
|
2323
|
-
console.error(
|
|
3277
|
+
console.error(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2324
3278
|
process.exit(1);
|
|
2325
3279
|
}
|
|
2326
3280
|
const cmd = buildExecCommand(command, opts);
|
|
@@ -2335,7 +3289,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2335
3289
|
});
|
|
2336
3290
|
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) => {
|
|
2337
3291
|
console.log();
|
|
2338
|
-
p4.intro(
|
|
3292
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2339
3293
|
await ensureAuth();
|
|
2340
3294
|
const provider = await resolveProvider(opts.provider);
|
|
2341
3295
|
configureCompute(provider);
|
|
@@ -2346,7 +3300,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2346
3300
|
if (workspaces.length === 0) {
|
|
2347
3301
|
p4.log.info("No sandboxes found.");
|
|
2348
3302
|
p4.log.info("Create one with: compute sandbox create");
|
|
2349
|
-
p4.outro(
|
|
3303
|
+
p4.outro(pc8.green("Done!"));
|
|
2350
3304
|
return;
|
|
2351
3305
|
}
|
|
2352
3306
|
const choices = workspaces.map((ws) => {
|
|
@@ -2354,7 +3308,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2354
3308
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2355
3309
|
);
|
|
2356
3310
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2357
|
-
const status = age > 7 ?
|
|
3311
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2358
3312
|
return {
|
|
2359
3313
|
value: ws.sandboxId,
|
|
2360
3314
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2374,7 +3328,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2374
3328
|
spinner4.start("Connecting...");
|
|
2375
3329
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2376
3330
|
if (!sandbox) {
|
|
2377
|
-
spinner4.stop(
|
|
3331
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2378
3332
|
process.exit(1);
|
|
2379
3333
|
}
|
|
2380
3334
|
spinner4.stop("");
|
|
@@ -2383,11 +3337,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2383
3337
|
var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
|
|
2384
3338
|
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) => {
|
|
2385
3339
|
console.log();
|
|
2386
|
-
p4.intro(
|
|
3340
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2387
3341
|
await ensureAuth();
|
|
2388
3342
|
if (!repo) {
|
|
2389
3343
|
p4.log.info("Usage: compute workspace create owner/repo --branch main");
|
|
2390
|
-
p4.outro(
|
|
3344
|
+
p4.outro(pc8.green("Done!"));
|
|
2391
3345
|
return;
|
|
2392
3346
|
}
|
|
2393
3347
|
const provider = await resolveProvider(options.provider, { interactive: true });
|
|
@@ -2400,33 +3354,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
|
|
|
2400
3354
|
branch: options.branch,
|
|
2401
3355
|
provider
|
|
2402
3356
|
});
|
|
2403
|
-
spinner4.stop(`Workspace ready: ${
|
|
3357
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2404
3358
|
if (options.attach !== false) {
|
|
2405
3359
|
console.log();
|
|
2406
3360
|
await workspaceAttach(workspace.id);
|
|
2407
3361
|
} else {
|
|
2408
|
-
p4.outro(
|
|
3362
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2409
3363
|
}
|
|
2410
3364
|
} catch (error) {
|
|
2411
|
-
spinner4.stop(
|
|
3365
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2412
3366
|
p4.log.error(error.message);
|
|
2413
3367
|
process.exit(1);
|
|
2414
3368
|
}
|
|
2415
3369
|
});
|
|
2416
3370
|
workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
|
|
2417
3371
|
console.log();
|
|
2418
|
-
p4.intro(
|
|
3372
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2419
3373
|
await workspaceList();
|
|
2420
3374
|
});
|
|
2421
3375
|
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) => {
|
|
2422
3376
|
console.log();
|
|
2423
|
-
p4.intro(
|
|
3377
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2424
3378
|
if (!id) {
|
|
2425
3379
|
const state = loadWorkspaceState();
|
|
2426
3380
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2427
3381
|
if (workspaceIds.length === 0) {
|
|
2428
3382
|
p4.log.info("No workspaces to destroy.");
|
|
2429
|
-
p4.outro(
|
|
3383
|
+
p4.outro(pc8.green("Done!"));
|
|
2430
3384
|
return;
|
|
2431
3385
|
}
|
|
2432
3386
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2437,7 +3391,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2437
3391
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2438
3392
|
return {
|
|
2439
3393
|
value: key,
|
|
2440
|
-
label: `${
|
|
3394
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2441
3395
|
};
|
|
2442
3396
|
});
|
|
2443
3397
|
const selected = await p4.multiselect({
|
|
@@ -2457,14 +3411,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2457
3411
|
});
|
|
2458
3412
|
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) => {
|
|
2459
3413
|
console.log();
|
|
2460
|
-
p4.intro(
|
|
3414
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2461
3415
|
if (!id) {
|
|
2462
3416
|
const state = loadWorkspaceState();
|
|
2463
3417
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2464
3418
|
if (workspaceIds.length === 0) {
|
|
2465
3419
|
p4.log.info("No workspaces found.");
|
|
2466
3420
|
p4.log.info("Create one with: compute create owner/repo");
|
|
2467
|
-
p4.outro(
|
|
3421
|
+
p4.outro(pc8.green("Done!"));
|
|
2468
3422
|
return;
|
|
2469
3423
|
}
|
|
2470
3424
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2475,7 +3429,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
|
|
|
2475
3429
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2476
3430
|
return {
|
|
2477
3431
|
value: key,
|
|
2478
|
-
label: `${
|
|
3432
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2479
3433
|
};
|
|
2480
3434
|
});
|
|
2481
3435
|
const selected = await p4.select({
|
|
@@ -2500,7 +3454,7 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2500
3454
|
buildCreateOptions(opts)
|
|
2501
3455
|
);
|
|
2502
3456
|
} catch (error) {
|
|
2503
|
-
console.error(
|
|
3457
|
+
console.error(pc8.red(`Failed to create sandbox: ${error.message}`));
|
|
2504
3458
|
process.exit(1);
|
|
2505
3459
|
}
|
|
2506
3460
|
try {
|
|
@@ -2523,37 +3477,39 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2523
3477
|
} catch {
|
|
2524
3478
|
}
|
|
2525
3479
|
}
|
|
2526
|
-
console.error(
|
|
3480
|
+
console.error(pc8.red(error.message));
|
|
2527
3481
|
process.exit(1);
|
|
2528
3482
|
}
|
|
2529
3483
|
});
|
|
2530
3484
|
program.command("providers").description("List all providers and their configuration status").action(async () => {
|
|
2531
3485
|
console.log();
|
|
2532
|
-
p4.intro(
|
|
3486
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2533
3487
|
const statuses = getProviderStatus();
|
|
2534
3488
|
console.log();
|
|
2535
3489
|
for (const status of statuses) {
|
|
2536
|
-
const icon = status.ready ?
|
|
2537
|
-
const name = status.ready ?
|
|
2538
|
-
const detail = status.ready ?
|
|
3490
|
+
const icon = status.ready ? pc8.green("\u25CF") : pc8.gray("\u25CB");
|
|
3491
|
+
const name = status.ready ? pc8.white(status.name) : pc8.gray(status.name);
|
|
3492
|
+
const detail = status.ready ? pc8.green("ready") : pc8.gray(`missing: ${status.missing.join(", ")}`);
|
|
2539
3493
|
console.log(` ${icon} ${name} ${detail}`);
|
|
2540
3494
|
}
|
|
2541
3495
|
console.log();
|
|
2542
3496
|
});
|
|
2543
3497
|
program.command("login").description("Authenticate with ComputeSDK").action(async () => {
|
|
2544
3498
|
console.log();
|
|
2545
|
-
p4.intro(
|
|
3499
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2546
3500
|
await ensureAuth({ forceLogin: true });
|
|
2547
|
-
p4.outro(
|
|
3501
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2548
3502
|
});
|
|
2549
3503
|
program.command("logout").description("Clear stored credentials").action(() => {
|
|
2550
3504
|
console.log();
|
|
2551
|
-
p4.intro(
|
|
3505
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2552
3506
|
clearStoredCredentials();
|
|
2553
3507
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2554
|
-
p4.outro(
|
|
3508
|
+
p4.outro(pc8.green("Done!"));
|
|
2555
3509
|
});
|
|
2556
3510
|
registerActionsCommands(program);
|
|
3511
|
+
registerMarketCommands(program);
|
|
3512
|
+
registerSandboxesCommands(program);
|
|
2557
3513
|
program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
|
|
2558
3514
|
});
|
|
2559
3515
|
function buildCreateOptions(opts) {
|
|
@@ -2631,7 +3587,7 @@ async function main() {
|
|
|
2631
3587
|
await program.parseAsync(process.argv);
|
|
2632
3588
|
}
|
|
2633
3589
|
main().catch((error) => {
|
|
2634
|
-
console.error(
|
|
3590
|
+
console.error(pc8.red(`
|
|
2635
3591
|
Error: ${error.message}`));
|
|
2636
3592
|
process.exit(1);
|
|
2637
3593
|
});
|