@computesdk/cli 1.0.13 → 1.0.15
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 +1080 -151
- 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,19 +1031,22 @@ function sanitizePathPart(name) {
|
|
|
981
1031
|
async function client(opts) {
|
|
982
1032
|
return new ActionsClient(await resolveActionsAuth(opts));
|
|
983
1033
|
}
|
|
984
|
-
function
|
|
1034
|
+
function assertSecretValueHost(auth, what) {
|
|
985
1035
|
if (!isTrustedActionsHost(auth.baseUrl)) {
|
|
986
1036
|
throw new ActionsCliError(
|
|
987
1037
|
"untrusted_host",
|
|
988
|
-
`Refusing to send or read
|
|
1038
|
+
`Refusing to send or read ${what} at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
|
|
989
1039
|
);
|
|
990
1040
|
}
|
|
991
1041
|
}
|
|
992
|
-
async function
|
|
1042
|
+
async function secretValueClient(opts, what) {
|
|
993
1043
|
const auth = await resolveActionsAuth(opts);
|
|
994
|
-
|
|
1044
|
+
assertSecretValueHost(auth, what);
|
|
995
1045
|
return new ActionsClient(auth);
|
|
996
1046
|
}
|
|
1047
|
+
async function vaultValueClient(opts) {
|
|
1048
|
+
return secretValueClient(opts, "vault values");
|
|
1049
|
+
}
|
|
997
1050
|
function output(opts, data, render) {
|
|
998
1051
|
if (opts.json) {
|
|
999
1052
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
@@ -1386,6 +1439,7 @@ function dispatchBody(workflow, opts) {
|
|
|
1386
1439
|
if (opts.providerRegion !== void 0 && opts.provider === void 0) {
|
|
1387
1440
|
throw new ActionsCliError("invalid_argument", "--provider-region requires --provider.");
|
|
1388
1441
|
}
|
|
1442
|
+
const maxPrice = maxPriceFields(opts.maxBid, opts.maxBidPer);
|
|
1389
1443
|
return {
|
|
1390
1444
|
workflowId: workflow.id,
|
|
1391
1445
|
ref,
|
|
@@ -1394,9 +1448,25 @@ function dispatchBody(workflow, opts) {
|
|
|
1394
1448
|
...opts.provider !== void 0 && {
|
|
1395
1449
|
provider: opts.provider,
|
|
1396
1450
|
...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
|
|
1397
|
-
}
|
|
1451
|
+
},
|
|
1452
|
+
...maxPrice !== void 0 && maxPrice
|
|
1398
1453
|
};
|
|
1399
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
|
+
}
|
|
1400
1470
|
function parseVaultKind(kind) {
|
|
1401
1471
|
if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
|
|
1402
1472
|
throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
|
|
@@ -1437,7 +1507,7 @@ function registerActionsCommands(program2) {
|
|
|
1437
1507
|
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
|
|
1438
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");
|
|
1439
1509
|
common(
|
|
1440
|
-
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)")
|
|
1441
1511
|
).action(async (repo, opts) => {
|
|
1442
1512
|
try {
|
|
1443
1513
|
const c = await client(opts);
|
|
@@ -1921,6 +1991,863 @@ function registerActionsCommands(program2) {
|
|
|
1921
1991
|
});
|
|
1922
1992
|
}
|
|
1923
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.image !== void 0) {
|
|
2478
|
+
console.log(` image: ${s.image ?? "\u2014"}${s.requestedImage !== null && s.requestedImage !== s.image ? ` ${pc3.dim(`(requested: ${s.requestedImage})`)}` : ""}`);
|
|
2479
|
+
}
|
|
2480
|
+
if (s.destroyError) console.log(` destroyError: ${pc3.red(s.destroyError)}`);
|
|
2481
|
+
if (attach !== void 0) {
|
|
2482
|
+
console.log(
|
|
2483
|
+
attach === null ? " attach: none (market fill or ambient provider \u2014 platform drive only)" : ` attach: ${attach.provider} / ${attach.providerSandboxId}${attach.region ? ` (${attach.region})` : ""}`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function printProcess(p5) {
|
|
2488
|
+
const exit = p5.status === "exited" ? `exit=${p5.exitCode ?? "?"}${p5.signal ? ` (${p5.signal})` : ""}` : "";
|
|
2489
|
+
const stdin = p5.stdin && p5.status === "running" ? " [stdin open]" : "";
|
|
2490
|
+
console.log(`${pc3.cyan(p5.jobId)} ${p5.status}${exit ? " " + exit : ""}${stdin} ${p5.command}`);
|
|
2491
|
+
}
|
|
2492
|
+
function printSnapshot(s) {
|
|
2493
|
+
const state = s.deletedAt !== null ? ` ${pc3.dim(`(deleted ${s.deletedAt})`)}` : "";
|
|
2494
|
+
console.log(`${pc3.cyan(s.id)} ${s.providerSnapshotId}${state}`);
|
|
2495
|
+
console.log(
|
|
2496
|
+
` provider: ${s.provider}${s.region ? ` (${s.region})` : ""} label: ${s.label ?? "\u2014"} created: ${s.createdAt}${s.expiresAt ? ` expires: ${s.expiresAt}` : ""}`
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
function registerSandboxesCommands(program2) {
|
|
2500
|
+
const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
|
|
2501
|
+
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) => {
|
|
2502
|
+
try {
|
|
2503
|
+
const c = await client(opts);
|
|
2504
|
+
const resources = {};
|
|
2505
|
+
if (opts.cpus) resources.cpus = Number(opts.cpus);
|
|
2506
|
+
if (opts.memoryMb) resources.memoryMb = Number(opts.memoryMb);
|
|
2507
|
+
if (opts.diskMb) resources.ephemeralDiskMb = Number(opts.diskMb);
|
|
2508
|
+
const body = {};
|
|
2509
|
+
if (opts.order) body.providerOrder = opts.order.split(",").map((s) => s.trim());
|
|
2510
|
+
if (opts.label) body.label = opts.label;
|
|
2511
|
+
if (opts.image) body.image = opts.image;
|
|
2512
|
+
if (opts.snapshotId) body.snapshotId = opts.snapshotId;
|
|
2513
|
+
if (opts.timeoutMs) body.timeoutMs = Number(opts.timeoutMs);
|
|
2514
|
+
if (opts.secret && opts.secret.length > 0) body.secrets = opts.secret;
|
|
2515
|
+
if (Object.keys(resources).length > 0) body.resources = resources;
|
|
2516
|
+
const res = await c.post("/api/v1/sandboxes", body);
|
|
2517
|
+
output(opts, res.sandbox, printSandbox);
|
|
2518
|
+
} catch (e) {
|
|
2519
|
+
fail(e, opts);
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
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) => {
|
|
2523
|
+
try {
|
|
2524
|
+
const c = await client(opts);
|
|
2525
|
+
const res = await c.get(
|
|
2526
|
+
"/api/v1/sandboxes",
|
|
2527
|
+
{ status: opts.status, limit: opts.limit, cursor: opts.cursor }
|
|
2528
|
+
);
|
|
2529
|
+
output(opts, res, (page) => {
|
|
2530
|
+
page.sandboxes.forEach(printSandbox);
|
|
2531
|
+
if (page.nextCursor) console.log(pc3.dim(`nextCursor: ${page.nextCursor}`));
|
|
2532
|
+
});
|
|
2533
|
+
} catch (e) {
|
|
2534
|
+
fail(e, opts);
|
|
2535
|
+
}
|
|
2536
|
+
});
|
|
2537
|
+
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) => {
|
|
2538
|
+
try {
|
|
2539
|
+
const c = await client(opts);
|
|
2540
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}`);
|
|
2541
|
+
output(opts, res.sandbox, printSandbox);
|
|
2542
|
+
} catch (e) {
|
|
2543
|
+
fail(e, opts);
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
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) => {
|
|
2547
|
+
try {
|
|
2548
|
+
const c = await client(opts);
|
|
2549
|
+
const res = await c.del(`/api/v1/sandboxes/${sandboxId}`);
|
|
2550
|
+
output(opts, res.sandbox, printSandbox);
|
|
2551
|
+
} catch (e) {
|
|
2552
|
+
fail(e, opts);
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
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) => {
|
|
2556
|
+
try {
|
|
2557
|
+
const c = await client(opts);
|
|
2558
|
+
const res = await c.post(
|
|
2559
|
+
`/api/v1/sandboxes/${sandboxId}/commands`,
|
|
2560
|
+
{
|
|
2561
|
+
command: command.map(shellQuote).join(" "),
|
|
2562
|
+
...opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2563
|
+
}
|
|
2564
|
+
);
|
|
2565
|
+
output(opts, res, (r) => {
|
|
2566
|
+
process.stdout.write(r.stdout);
|
|
2567
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
2568
|
+
if (!r.stdout) console.log(`exit ${r.exitCode} (${r.durationMs}ms)`);
|
|
2569
|
+
});
|
|
2570
|
+
if (res.exitCode !== 0) process.exitCode = res.exitCode;
|
|
2571
|
+
} catch (e) {
|
|
2572
|
+
fail(e, opts);
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
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("--stdin", "keep the process's stdin pipe open for `stdin`/`close-stdin`").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
|
|
2576
|
+
try {
|
|
2577
|
+
const c = await client(opts);
|
|
2578
|
+
const res = await c.post(
|
|
2579
|
+
`/api/v1/sandboxes/${sandboxId}/processes`,
|
|
2580
|
+
{
|
|
2581
|
+
command: command.map(shellQuote).join(" "),
|
|
2582
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
2583
|
+
...opts.env && opts.env.length > 0 ? { env: parseInputs(opts.env) } : {},
|
|
2584
|
+
...opts.stdin ? { stdin: true } : {}
|
|
2585
|
+
}
|
|
2586
|
+
);
|
|
2587
|
+
output(opts, res.process, printProcess);
|
|
2588
|
+
} catch (e) {
|
|
2589
|
+
fail(e, opts);
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
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) => {
|
|
2593
|
+
try {
|
|
2594
|
+
const c = await client(opts);
|
|
2595
|
+
const res = await c.get(
|
|
2596
|
+
`/api/v1/sandboxes/${sandboxId}/processes`
|
|
2597
|
+
);
|
|
2598
|
+
output(opts, res.processes, (list) => list.forEach(printProcess));
|
|
2599
|
+
} catch (e) {
|
|
2600
|
+
fail(e, opts);
|
|
2601
|
+
}
|
|
2602
|
+
});
|
|
2603
|
+
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) => {
|
|
2604
|
+
try {
|
|
2605
|
+
const c = await client(opts);
|
|
2606
|
+
if (opts.follow) {
|
|
2607
|
+
await followProcess(c, sandboxId, jobId);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const res = await c.get(
|
|
2611
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2612
|
+
);
|
|
2613
|
+
output(opts, res.process, (p5) => {
|
|
2614
|
+
if (p5.stdout) process.stdout.write(p5.stdout);
|
|
2615
|
+
if (p5.stderr) process.stderr.write(p5.stderr);
|
|
2616
|
+
if (!p5.stdout && !p5.stderr) printProcess(p5);
|
|
2617
|
+
});
|
|
2618
|
+
} catch (e) {
|
|
2619
|
+
fail(e, opts);
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
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) => {
|
|
2623
|
+
try {
|
|
2624
|
+
const c = await client(opts);
|
|
2625
|
+
const res = await c.post(
|
|
2626
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/wait`,
|
|
2627
|
+
opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2628
|
+
);
|
|
2629
|
+
output(opts, res.process, printProcess);
|
|
2630
|
+
if (res.process.status === "exited") {
|
|
2631
|
+
process.exitCode = res.process.exitCode ?? 128;
|
|
2632
|
+
}
|
|
2633
|
+
} catch (e) {
|
|
2634
|
+
fail(e, opts);
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
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) => {
|
|
2638
|
+
try {
|
|
2639
|
+
const c = await client(opts);
|
|
2640
|
+
const res = await c.post(
|
|
2641
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/kill`,
|
|
2642
|
+
opts.signal ? { signal: opts.signal } : {}
|
|
2643
|
+
);
|
|
2644
|
+
output(opts, res.process, printProcess);
|
|
2645
|
+
} catch (e) {
|
|
2646
|
+
fail(e, opts);
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
cmd.command("stdin <sandboxId> <jobId>").description("Write to a process's stdin pipe (spawn with --stdin; --data, --file, or piped)").option("--data <text>", "data to write").option("--file <local>", "read data from a local file").option("--base64", "base64-encode the input before sending (binary-safe)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2650
|
+
try {
|
|
2651
|
+
let data = opts.data;
|
|
2652
|
+
if (data === void 0 && opts.file) {
|
|
2653
|
+
const { readFile } = await import("fs/promises");
|
|
2654
|
+
data = opts.base64 ? (await readFile(opts.file)).toString("base64") : await readFile(opts.file, "utf8");
|
|
2655
|
+
}
|
|
2656
|
+
if (data === void 0 && !process.stdin.isTTY) {
|
|
2657
|
+
const piped = await new Promise((resolve) => {
|
|
2658
|
+
const chunks = [];
|
|
2659
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
2660
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks)));
|
|
2661
|
+
});
|
|
2662
|
+
data = opts.base64 ? piped.toString("base64") : piped.toString("utf8");
|
|
2663
|
+
}
|
|
2664
|
+
if (data === void 0 || data === "") {
|
|
2665
|
+
console.error(pc3.red("Pass --data, --file, or pipe stdin"));
|
|
2666
|
+
process.exit(1);
|
|
2667
|
+
}
|
|
2668
|
+
if (opts.base64 && opts.data !== void 0) {
|
|
2669
|
+
data = Buffer.from(data).toString("base64");
|
|
2670
|
+
}
|
|
2671
|
+
const c = await client(opts);
|
|
2672
|
+
const res = await c.post(
|
|
2673
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/stdin`,
|
|
2674
|
+
{ data, ...opts.base64 ? { encoding: "base64" } : {} }
|
|
2675
|
+
);
|
|
2676
|
+
output(opts, res, () => console.log(`wrote ${data.length} chars to ${jobId}`));
|
|
2677
|
+
} catch (e) {
|
|
2678
|
+
fail(e, opts);
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
cmd.command("close-stdin <sandboxId> <jobId>").description("Close a process's stdin pipe").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2682
|
+
try {
|
|
2683
|
+
const c = await client(opts);
|
|
2684
|
+
const res = await c.post(
|
|
2685
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/close-stdin`,
|
|
2686
|
+
{}
|
|
2687
|
+
);
|
|
2688
|
+
output(opts, res, () => console.log(`closed stdin on ${jobId}`));
|
|
2689
|
+
} catch (e) {
|
|
2690
|
+
fail(e, opts);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
cmd.command("ls <sandboxId> [path]").description("List a directory (or stat a path) inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2694
|
+
try {
|
|
2695
|
+
const c = await client(opts);
|
|
2696
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2697
|
+
path: path5 ?? "/"
|
|
2698
|
+
});
|
|
2699
|
+
output(opts, res, (view) => {
|
|
2700
|
+
if (view.type === "directory") {
|
|
2701
|
+
for (const e of view.entries) {
|
|
2702
|
+
console.log(`${e.type === "directory" ? pc3.blue("d") : " "} ${e.name}${e.size !== void 0 ? ` ${pc3.dim(String(e.size))}` : ""}`);
|
|
2703
|
+
}
|
|
2704
|
+
} else {
|
|
2705
|
+
console.log(`${view.path} file ${view.content.length} chars`);
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
} catch (e) {
|
|
2709
|
+
fail(e, opts);
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
2712
|
+
cmd.command("cat <sandboxId> <path>").description("Print a file's contents").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2713
|
+
try {
|
|
2714
|
+
const c = await client(opts);
|
|
2715
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path: path5 });
|
|
2716
|
+
output(opts, res, (view) => {
|
|
2717
|
+
if (view.type !== "file") {
|
|
2718
|
+
console.error(pc3.red(`${view.path} is a directory`));
|
|
2719
|
+
process.exit(1);
|
|
2720
|
+
}
|
|
2721
|
+
process.stdout.write(view.content);
|
|
2722
|
+
if (!view.content.endsWith("\n")) process.stdout.write("\n");
|
|
2723
|
+
});
|
|
2724
|
+
} catch (e) {
|
|
2725
|
+
fail(e, opts);
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
cmd.command("write <sandboxId> <path>").description("Write a file inside the sandbox (--content or --file <local>, else stdin)").option("--content <text>", "file contents").option("--file <local>", "read contents from a local file").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2729
|
+
try {
|
|
2730
|
+
let content = opts.content;
|
|
2731
|
+
if (content === void 0 && opts.file) {
|
|
2732
|
+
const { readFile } = await import("fs/promises");
|
|
2733
|
+
content = await readFile(opts.file, "utf8");
|
|
2734
|
+
}
|
|
2735
|
+
if (content === void 0 && !process.stdin.isTTY) {
|
|
2736
|
+
content = await new Promise((resolve) => {
|
|
2737
|
+
let data = "";
|
|
2738
|
+
process.stdin.setEncoding("utf8");
|
|
2739
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
2740
|
+
process.stdin.on("end", () => resolve(data));
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
if (content === void 0) {
|
|
2744
|
+
console.error(pc3.red("Pass --content, --file, or pipe stdin"));
|
|
2745
|
+
process.exit(1);
|
|
2746
|
+
}
|
|
2747
|
+
const c = await client(opts);
|
|
2748
|
+
const res = await c.post(
|
|
2749
|
+
`/api/v1/sandboxes/${sandboxId}/files`,
|
|
2750
|
+
{ path: path5, content }
|
|
2751
|
+
);
|
|
2752
|
+
output(opts, res, (r) => console.log(`wrote ${r.path}`));
|
|
2753
|
+
} catch (e) {
|
|
2754
|
+
fail(e, opts);
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
cmd.command("mkdir <sandboxId> <path>").description("Create a directory (parents included)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2758
|
+
try {
|
|
2759
|
+
const c = await client(opts);
|
|
2760
|
+
const res = await c.post(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2761
|
+
path: path5,
|
|
2762
|
+
mkdir: true
|
|
2763
|
+
});
|
|
2764
|
+
output(opts, res, (r) => console.log(`created ${r.path}`));
|
|
2765
|
+
} catch (e) {
|
|
2766
|
+
fail(e, opts);
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
cmd.command("rm <sandboxId> <path>").description("Remove a file or directory inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2770
|
+
try {
|
|
2771
|
+
const c = await client(opts);
|
|
2772
|
+
const res = await c.del(
|
|
2773
|
+
`/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path5)}`
|
|
2774
|
+
);
|
|
2775
|
+
output(opts, res, (r) => console.log(`removed ${r.path}`));
|
|
2776
|
+
} catch (e) {
|
|
2777
|
+
fail(e, opts);
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
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) => {
|
|
2781
|
+
try {
|
|
2782
|
+
const c = await client(opts);
|
|
2783
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/urls`, {
|
|
2784
|
+
port: opts.port,
|
|
2785
|
+
protocol: opts.protocol
|
|
2786
|
+
});
|
|
2787
|
+
output(opts, res, (r) => console.log(safeTerm(r.url)));
|
|
2788
|
+
} catch (e) {
|
|
2789
|
+
fail(e, opts);
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
cmd.command("snapshots <sandboxId>").description("List the sandbox's snapshots").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2793
|
+
try {
|
|
2794
|
+
const c = await client(opts);
|
|
2795
|
+
const res = await c.get(
|
|
2796
|
+
`/api/v1/sandboxes/${sandboxId}/snapshots`
|
|
2797
|
+
);
|
|
2798
|
+
output(opts, res.snapshots, (list) => list.forEach(printSnapshot));
|
|
2799
|
+
} catch (e) {
|
|
2800
|
+
fail(e, opts);
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
cmd.command("snapshot <sandboxId>").description("Snapshot a running sandbox through its provider").option("--label <label>", "label for the snapshot").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2804
|
+
try {
|
|
2805
|
+
const c = await client(opts);
|
|
2806
|
+
const res = await c.post(
|
|
2807
|
+
`/api/v1/sandboxes/${sandboxId}/snapshots`,
|
|
2808
|
+
opts.label ? { label: opts.label } : {}
|
|
2809
|
+
);
|
|
2810
|
+
output(opts, res.snapshot, printSnapshot);
|
|
2811
|
+
} catch (e) {
|
|
2812
|
+
fail(e, opts);
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
cmd.command("snapshot-delete <sandboxId> <snapshotId>").description("Delete a snapshot's provider artifact").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, snapshotId, opts) => {
|
|
2816
|
+
try {
|
|
2817
|
+
const c = await client(opts);
|
|
2818
|
+
const res = await c.del(
|
|
2819
|
+
`/api/v1/sandboxes/${sandboxId}/snapshots/${snapshotId}`
|
|
2820
|
+
);
|
|
2821
|
+
output(opts, res, () => console.log(`deleted snapshot ${snapshotId}`));
|
|
2822
|
+
} catch (e) {
|
|
2823
|
+
fail(e, opts);
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
async function followProcess(c, sandboxId, jobId) {
|
|
2828
|
+
let exitCode = 0;
|
|
2829
|
+
for await (const { event, data } of c.sseNamed(
|
|
2830
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2831
|
+
)) {
|
|
2832
|
+
const payload = data;
|
|
2833
|
+
if (event === "stdout") process.stdout.write(String(payload.chunk ?? ""));
|
|
2834
|
+
else if (event === "stderr") process.stderr.write(String(payload.chunk ?? ""));
|
|
2835
|
+
else if (event === "exit") {
|
|
2836
|
+
exitCode = typeof payload.exitCode === "number" ? payload.exitCode : 128;
|
|
2837
|
+
return finish();
|
|
2838
|
+
} else if (event === "error") {
|
|
2839
|
+
console.error(pc3.red(`Error: ${String(payload.error ?? "stream error")}`));
|
|
2840
|
+
return finish(1);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
console.error(pc3.yellow("stream ended before the process exited"));
|
|
2844
|
+
return finish(2);
|
|
2845
|
+
function finish(code) {
|
|
2846
|
+
if (code !== void 0) exitCode = code;
|
|
2847
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
|
|
1924
2851
|
// src/index.ts
|
|
1925
2852
|
init_repl();
|
|
1926
2853
|
|
|
@@ -1933,7 +2860,7 @@ import * as fs2 from "fs";
|
|
|
1933
2860
|
import * as path3 from "path";
|
|
1934
2861
|
import * as os2 from "os";
|
|
1935
2862
|
import * as p from "@clack/prompts";
|
|
1936
|
-
import
|
|
2863
|
+
import pc5 from "picocolors";
|
|
1937
2864
|
function getCredentialsDir() {
|
|
1938
2865
|
return path3.join(os2.homedir(), ".computesdk");
|
|
1939
2866
|
}
|
|
@@ -1991,7 +2918,7 @@ function openBrowser(url) {
|
|
|
1991
2918
|
if (err) {
|
|
1992
2919
|
p.log.warn("Could not open browser automatically.");
|
|
1993
2920
|
p.log.info(`Open this URL manually:
|
|
1994
|
-
${
|
|
2921
|
+
${pc5.cyan(url)}`);
|
|
1995
2922
|
}
|
|
1996
2923
|
});
|
|
1997
2924
|
}
|
|
@@ -2062,7 +2989,7 @@ async function resolveApiKey(options) {
|
|
|
2062
2989
|
const stored = loadStoredCredentials();
|
|
2063
2990
|
if (stored) {
|
|
2064
2991
|
p.log.info(
|
|
2065
|
-
`Using stored credentials from ${
|
|
2992
|
+
`Using stored credentials from ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
2066
2993
|
);
|
|
2067
2994
|
return stored;
|
|
2068
2995
|
}
|
|
@@ -2076,14 +3003,14 @@ async function resolveApiKey(options) {
|
|
|
2076
3003
|
try {
|
|
2077
3004
|
storeCredentials(apiKey);
|
|
2078
3005
|
p.log.success(
|
|
2079
|
-
`Credentials saved to ${
|
|
3006
|
+
`Credentials saved to ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
2080
3007
|
);
|
|
2081
3008
|
} catch {
|
|
2082
3009
|
p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
|
|
2083
3010
|
}
|
|
2084
3011
|
return apiKey;
|
|
2085
3012
|
} catch (error) {
|
|
2086
|
-
spinner4.stop(
|
|
3013
|
+
spinner4.stop(pc5.red("Authentication failed"));
|
|
2087
3014
|
throw error;
|
|
2088
3015
|
}
|
|
2089
3016
|
}
|
|
@@ -2091,7 +3018,7 @@ async function resolveApiKey(options) {
|
|
|
2091
3018
|
// src/setup.ts
|
|
2092
3019
|
init_esm_shims();
|
|
2093
3020
|
import * as p2 from "@clack/prompts";
|
|
2094
|
-
import
|
|
3021
|
+
import pc6 from "picocolors";
|
|
2095
3022
|
import { compute as compute2 } from "computesdk";
|
|
2096
3023
|
async function ensureAuth(options) {
|
|
2097
3024
|
try {
|
|
@@ -2099,8 +3026,8 @@ async function ensureAuth(options) {
|
|
|
2099
3026
|
process.env.COMPUTESDK_API_KEY = apiKey;
|
|
2100
3027
|
} catch (error) {
|
|
2101
3028
|
p2.log.error(error.message);
|
|
2102
|
-
p2.log.info(`Get your API key at ${
|
|
2103
|
-
p2.outro(
|
|
3029
|
+
p2.log.info(`Get your API key at ${pc6.cyan("https://console.computesdk.com")}`);
|
|
3030
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
2104
3031
|
process.exit(1);
|
|
2105
3032
|
}
|
|
2106
3033
|
}
|
|
@@ -2129,7 +3056,7 @@ async function resolveProvider(providerFlag, options) {
|
|
|
2129
3056
|
if (available.length === 0) {
|
|
2130
3057
|
p2.log.error("No provider credentials detected");
|
|
2131
3058
|
p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
|
|
2132
|
-
p2.outro(
|
|
3059
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
2133
3060
|
process.exit(1);
|
|
2134
3061
|
}
|
|
2135
3062
|
if (available.length === 1) {
|
|
@@ -2173,24 +3100,24 @@ program.name("compute").description("Cloud-native workspace management").version
|
|
|
2173
3100
|
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
|
|
2174
3101
|
if (opts.logout) {
|
|
2175
3102
|
console.log();
|
|
2176
|
-
p4.intro(
|
|
3103
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2177
3104
|
clearStoredCredentials();
|
|
2178
3105
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2179
|
-
p4.outro(
|
|
3106
|
+
p4.outro(pc8.green("Done!"));
|
|
2180
3107
|
process.exit(0);
|
|
2181
3108
|
}
|
|
2182
3109
|
if (opts.login) {
|
|
2183
3110
|
console.log();
|
|
2184
|
-
p4.intro(
|
|
3111
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2185
3112
|
await ensureAuth({ forceLogin: true });
|
|
2186
|
-
p4.outro(
|
|
3113
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2187
3114
|
process.exit(0);
|
|
2188
3115
|
}
|
|
2189
3116
|
program.help();
|
|
2190
3117
|
});
|
|
2191
3118
|
program.command("create").description("Create a new workspace (same as create-compute)").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
|
|
2192
3119
|
console.log();
|
|
2193
|
-
p4.intro(
|
|
3120
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2194
3121
|
await ensureAuth();
|
|
2195
3122
|
if (!repo) {
|
|
2196
3123
|
const state = loadWorkspaceState();
|
|
@@ -2211,7 +3138,7 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2211
3138
|
message: "Select workspace to attach (or create new)",
|
|
2212
3139
|
options: [
|
|
2213
3140
|
...choices,
|
|
2214
|
-
{ value: "__new__", label:
|
|
3141
|
+
{ value: "__new__", label: pc8.gray("+ Create new workspace") }
|
|
2215
3142
|
]
|
|
2216
3143
|
});
|
|
2217
3144
|
if (p4.isCancel(selected)) {
|
|
@@ -2252,15 +3179,15 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2252
3179
|
branch: options.branch,
|
|
2253
3180
|
provider
|
|
2254
3181
|
});
|
|
2255
|
-
spinner4.stop(`Workspace ready: ${
|
|
3182
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2256
3183
|
if (options.attach !== false) {
|
|
2257
3184
|
console.log();
|
|
2258
3185
|
await workspaceAttach(workspace.id);
|
|
2259
3186
|
} else {
|
|
2260
|
-
p4.outro(
|
|
3187
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2261
3188
|
}
|
|
2262
3189
|
} catch (error) {
|
|
2263
|
-
spinner4.stop(
|
|
3190
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2264
3191
|
p4.log.error(error.message);
|
|
2265
3192
|
process.exit(1);
|
|
2266
3193
|
}
|
|
@@ -2269,7 +3196,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
|
|
|
2269
3196
|
sandboxCmd.command("create").description("Create a new sandbox").option("-p, --provider <provider>", "provider to use").option("--timeout <duration>", "sandbox timeout (e.g. 5m, 1h)").option("--name <name>", "sandbox name").option("--connect", "drop into the REPL after creation").option("--silent", "only output the sandbox ID").action(async (opts) => {
|
|
2270
3197
|
if (!opts.silent) {
|
|
2271
3198
|
console.log();
|
|
2272
|
-
p4.intro(
|
|
3199
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2273
3200
|
}
|
|
2274
3201
|
await ensureAuth();
|
|
2275
3202
|
const provider = await resolveProvider(opts.provider);
|
|
@@ -2281,7 +3208,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2281
3208
|
const sandbox = await compute4.sandbox.create(
|
|
2282
3209
|
buildCreateOptions(opts)
|
|
2283
3210
|
);
|
|
2284
|
-
spinner4.stop(`Sandbox ready: ${
|
|
3211
|
+
spinner4.stop(`Sandbox ready: ${pc8.cyan(sandbox.sandboxId)}`);
|
|
2285
3212
|
const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2286
3213
|
await saveSandbox2({
|
|
2287
3214
|
id: sandbox.sandboxId,
|
|
@@ -2294,7 +3221,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2294
3221
|
await startREPL(sandbox, provider);
|
|
2295
3222
|
}
|
|
2296
3223
|
} catch (error) {
|
|
2297
|
-
spinner4.stop(
|
|
3224
|
+
spinner4.stop(pc8.red("Failed to create sandbox"));
|
|
2298
3225
|
p4.log.error(error.message);
|
|
2299
3226
|
process.exit(1);
|
|
2300
3227
|
}
|
|
@@ -2323,14 +3250,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2323
3250
|
});
|
|
2324
3251
|
sandboxCmd.command("destroy").description("Destroy one or more sandboxes").aliases(["rm", "stop"]).argument("[sandbox_id...]", "sandbox IDs to destroy (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxIds, opts) => {
|
|
2325
3252
|
console.log();
|
|
2326
|
-
p4.intro(
|
|
3253
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2327
3254
|
const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2328
3255
|
const state = loadState2();
|
|
2329
3256
|
if (!sandboxIds || sandboxIds.length === 0) {
|
|
2330
3257
|
const workspaces = Object.values(state.workspaces);
|
|
2331
3258
|
if (workspaces.length === 0) {
|
|
2332
3259
|
p4.log.info("No sandboxes to destroy.");
|
|
2333
|
-
p4.outro(
|
|
3260
|
+
p4.outro(pc8.green("Done!"));
|
|
2334
3261
|
return;
|
|
2335
3262
|
}
|
|
2336
3263
|
const choices = workspaces.map((ws) => {
|
|
@@ -2338,7 +3265,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2338
3265
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2339
3266
|
);
|
|
2340
3267
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2341
|
-
const status = age > 7 ?
|
|
3268
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2342
3269
|
return {
|
|
2343
3270
|
value: ws.sandboxId,
|
|
2344
3271
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2366,7 +3293,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2366
3293
|
for (const result of results) {
|
|
2367
3294
|
if (result.status === "fulfilled") {
|
|
2368
3295
|
const sandboxId = result.value;
|
|
2369
|
-
console.log(
|
|
3296
|
+
console.log(pc8.green(`Destroyed ${sandboxId}`));
|
|
2370
3297
|
const workspaceId = Object.keys(state.workspaces).find(
|
|
2371
3298
|
(key) => state.workspaces[key].sandboxId === sandboxId
|
|
2372
3299
|
);
|
|
@@ -2374,19 +3301,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2374
3301
|
delete state.workspaces[workspaceId];
|
|
2375
3302
|
}
|
|
2376
3303
|
} else {
|
|
2377
|
-
console.error(
|
|
3304
|
+
console.error(pc8.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
|
|
2378
3305
|
}
|
|
2379
3306
|
}
|
|
2380
3307
|
saveState2(state);
|
|
2381
3308
|
});
|
|
2382
3309
|
sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
|
|
2383
3310
|
console.log();
|
|
2384
|
-
p4.intro(
|
|
3311
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2385
3312
|
await sandboxList();
|
|
2386
3313
|
});
|
|
2387
3314
|
sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; same as compute connect)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
|
|
2388
3315
|
console.log();
|
|
2389
|
-
p4.intro(
|
|
3316
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2390
3317
|
await ensureAuth();
|
|
2391
3318
|
const provider = await resolveProvider(opts.provider);
|
|
2392
3319
|
configureCompute(provider);
|
|
@@ -2397,7 +3324,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2397
3324
|
if (workspaces.length === 0) {
|
|
2398
3325
|
p4.log.info("No sandboxes found.");
|
|
2399
3326
|
p4.log.info("Create one with: compute sandbox create");
|
|
2400
|
-
p4.outro(
|
|
3327
|
+
p4.outro(pc8.green("Done!"));
|
|
2401
3328
|
return;
|
|
2402
3329
|
}
|
|
2403
3330
|
const choices = workspaces.map((ws) => {
|
|
@@ -2405,7 +3332,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2405
3332
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2406
3333
|
);
|
|
2407
3334
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2408
|
-
const status = age > 7 ?
|
|
3335
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2409
3336
|
return {
|
|
2410
3337
|
value: ws.sandboxId,
|
|
2411
3338
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2425,7 +3352,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2425
3352
|
spinner4.start("Connecting...");
|
|
2426
3353
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2427
3354
|
if (!sandbox) {
|
|
2428
|
-
spinner4.stop(
|
|
3355
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2429
3356
|
process.exit(1);
|
|
2430
3357
|
}
|
|
2431
3358
|
spinner4.stop("");
|
|
@@ -2437,7 +3364,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2437
3364
|
configureCompute(provider);
|
|
2438
3365
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2439
3366
|
if (!sandbox) {
|
|
2440
|
-
console.error(
|
|
3367
|
+
console.error(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2441
3368
|
process.exit(1);
|
|
2442
3369
|
}
|
|
2443
3370
|
const cmd = buildExecCommand(command, opts);
|
|
@@ -2452,7 +3379,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2452
3379
|
});
|
|
2453
3380
|
program.command("connect").description("Connect to a sandbox (opens REPL)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
|
|
2454
3381
|
console.log();
|
|
2455
|
-
p4.intro(
|
|
3382
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2456
3383
|
await ensureAuth();
|
|
2457
3384
|
const provider = await resolveProvider(opts.provider);
|
|
2458
3385
|
configureCompute(provider);
|
|
@@ -2463,7 +3390,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2463
3390
|
if (workspaces.length === 0) {
|
|
2464
3391
|
p4.log.info("No sandboxes found.");
|
|
2465
3392
|
p4.log.info("Create one with: compute sandbox create");
|
|
2466
|
-
p4.outro(
|
|
3393
|
+
p4.outro(pc8.green("Done!"));
|
|
2467
3394
|
return;
|
|
2468
3395
|
}
|
|
2469
3396
|
const choices = workspaces.map((ws) => {
|
|
@@ -2471,7 +3398,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2471
3398
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2472
3399
|
);
|
|
2473
3400
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2474
|
-
const status = age > 7 ?
|
|
3401
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2475
3402
|
return {
|
|
2476
3403
|
value: ws.sandboxId,
|
|
2477
3404
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2491,7 +3418,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2491
3418
|
spinner4.start("Connecting...");
|
|
2492
3419
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2493
3420
|
if (!sandbox) {
|
|
2494
|
-
spinner4.stop(
|
|
3421
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2495
3422
|
process.exit(1);
|
|
2496
3423
|
}
|
|
2497
3424
|
spinner4.stop("");
|
|
@@ -2500,11 +3427,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2500
3427
|
var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
|
|
2501
3428
|
workspaceCmd.command("create").description("Create a new workspace").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
|
|
2502
3429
|
console.log();
|
|
2503
|
-
p4.intro(
|
|
3430
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2504
3431
|
await ensureAuth();
|
|
2505
3432
|
if (!repo) {
|
|
2506
3433
|
p4.log.info("Usage: compute workspace create owner/repo --branch main");
|
|
2507
|
-
p4.outro(
|
|
3434
|
+
p4.outro(pc8.green("Done!"));
|
|
2508
3435
|
return;
|
|
2509
3436
|
}
|
|
2510
3437
|
const provider = await resolveProvider(options.provider, { interactive: true });
|
|
@@ -2517,33 +3444,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
|
|
|
2517
3444
|
branch: options.branch,
|
|
2518
3445
|
provider
|
|
2519
3446
|
});
|
|
2520
|
-
spinner4.stop(`Workspace ready: ${
|
|
3447
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2521
3448
|
if (options.attach !== false) {
|
|
2522
3449
|
console.log();
|
|
2523
3450
|
await workspaceAttach(workspace.id);
|
|
2524
3451
|
} else {
|
|
2525
|
-
p4.outro(
|
|
3452
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2526
3453
|
}
|
|
2527
3454
|
} catch (error) {
|
|
2528
|
-
spinner4.stop(
|
|
3455
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2529
3456
|
p4.log.error(error.message);
|
|
2530
3457
|
process.exit(1);
|
|
2531
3458
|
}
|
|
2532
3459
|
});
|
|
2533
3460
|
workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
|
|
2534
3461
|
console.log();
|
|
2535
|
-
p4.intro(
|
|
3462
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2536
3463
|
await workspaceList();
|
|
2537
3464
|
});
|
|
2538
3465
|
workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").argument("[id]", "Workspace ID (repo:branch format, optional - will show picker if not provided)").action(async (id) => {
|
|
2539
3466
|
console.log();
|
|
2540
|
-
p4.intro(
|
|
3467
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2541
3468
|
if (!id) {
|
|
2542
3469
|
const state = loadWorkspaceState();
|
|
2543
3470
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2544
3471
|
if (workspaceIds.length === 0) {
|
|
2545
3472
|
p4.log.info("No workspaces to destroy.");
|
|
2546
|
-
p4.outro(
|
|
3473
|
+
p4.outro(pc8.green("Done!"));
|
|
2547
3474
|
return;
|
|
2548
3475
|
}
|
|
2549
3476
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2554,7 +3481,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2554
3481
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2555
3482
|
return {
|
|
2556
3483
|
value: key,
|
|
2557
|
-
label: `${
|
|
3484
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2558
3485
|
};
|
|
2559
3486
|
});
|
|
2560
3487
|
const selected = await p4.multiselect({
|
|
@@ -2574,14 +3501,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2574
3501
|
});
|
|
2575
3502
|
workspaceCmd.command("connect").alias("attach").description("Connect to an existing workspace (with git context)").argument("[id]", "Workspace ID (optional - will show picker if not provided)").action(async (id) => {
|
|
2576
3503
|
console.log();
|
|
2577
|
-
p4.intro(
|
|
3504
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2578
3505
|
if (!id) {
|
|
2579
3506
|
const state = loadWorkspaceState();
|
|
2580
3507
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2581
3508
|
if (workspaceIds.length === 0) {
|
|
2582
3509
|
p4.log.info("No workspaces found.");
|
|
2583
3510
|
p4.log.info("Create one with: compute create owner/repo");
|
|
2584
|
-
p4.outro(
|
|
3511
|
+
p4.outro(pc8.green("Done!"));
|
|
2585
3512
|
return;
|
|
2586
3513
|
}
|
|
2587
3514
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2592,7 +3519,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
|
|
|
2592
3519
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2593
3520
|
return {
|
|
2594
3521
|
value: key,
|
|
2595
|
-
label: `${
|
|
3522
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2596
3523
|
};
|
|
2597
3524
|
});
|
|
2598
3525
|
const selected = await p4.select({
|
|
@@ -2617,7 +3544,7 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2617
3544
|
buildCreateOptions(opts)
|
|
2618
3545
|
);
|
|
2619
3546
|
} catch (error) {
|
|
2620
|
-
console.error(
|
|
3547
|
+
console.error(pc8.red(`Failed to create sandbox: ${error.message}`));
|
|
2621
3548
|
process.exit(1);
|
|
2622
3549
|
}
|
|
2623
3550
|
try {
|
|
@@ -2640,37 +3567,39 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2640
3567
|
} catch {
|
|
2641
3568
|
}
|
|
2642
3569
|
}
|
|
2643
|
-
console.error(
|
|
3570
|
+
console.error(pc8.red(error.message));
|
|
2644
3571
|
process.exit(1);
|
|
2645
3572
|
}
|
|
2646
3573
|
});
|
|
2647
3574
|
program.command("providers").description("List all providers and their configuration status").action(async () => {
|
|
2648
3575
|
console.log();
|
|
2649
|
-
p4.intro(
|
|
3576
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2650
3577
|
const statuses = getProviderStatus();
|
|
2651
3578
|
console.log();
|
|
2652
3579
|
for (const status of statuses) {
|
|
2653
|
-
const icon = status.ready ?
|
|
2654
|
-
const name = status.ready ?
|
|
2655
|
-
const detail = status.ready ?
|
|
3580
|
+
const icon = status.ready ? pc8.green("\u25CF") : pc8.gray("\u25CB");
|
|
3581
|
+
const name = status.ready ? pc8.white(status.name) : pc8.gray(status.name);
|
|
3582
|
+
const detail = status.ready ? pc8.green("ready") : pc8.gray(`missing: ${status.missing.join(", ")}`);
|
|
2656
3583
|
console.log(` ${icon} ${name} ${detail}`);
|
|
2657
3584
|
}
|
|
2658
3585
|
console.log();
|
|
2659
3586
|
});
|
|
2660
3587
|
program.command("login").description("Authenticate with ComputeSDK").action(async () => {
|
|
2661
3588
|
console.log();
|
|
2662
|
-
p4.intro(
|
|
3589
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2663
3590
|
await ensureAuth({ forceLogin: true });
|
|
2664
|
-
p4.outro(
|
|
3591
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2665
3592
|
});
|
|
2666
3593
|
program.command("logout").description("Clear stored credentials").action(() => {
|
|
2667
3594
|
console.log();
|
|
2668
|
-
p4.intro(
|
|
3595
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2669
3596
|
clearStoredCredentials();
|
|
2670
3597
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2671
|
-
p4.outro(
|
|
3598
|
+
p4.outro(pc8.green("Done!"));
|
|
2672
3599
|
});
|
|
2673
3600
|
registerActionsCommands(program);
|
|
3601
|
+
registerMarketCommands(program);
|
|
3602
|
+
registerSandboxesCommands(program);
|
|
2674
3603
|
program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
|
|
2675
3604
|
});
|
|
2676
3605
|
function buildCreateOptions(opts) {
|
|
@@ -2748,7 +3677,7 @@ async function main() {
|
|
|
2748
3677
|
await program.parseAsync(process.argv);
|
|
2749
3678
|
}
|
|
2750
3679
|
main().catch((error) => {
|
|
2751
|
-
console.error(
|
|
3680
|
+
console.error(pc8.red(`
|
|
2752
3681
|
Error: ${error.message}`));
|
|
2753
3682
|
process.exit(1);
|
|
2754
3683
|
});
|