@computesdk/cli 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +990 -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,773 @@ 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.destroyError) console.log(` destroyError: ${pc3.red(s.destroyError)}`);
|
|
2478
|
+
if (attach !== void 0) {
|
|
2479
|
+
console.log(
|
|
2480
|
+
attach === null ? " attach: none (market fill or ambient provider \u2014 platform drive only)" : ` attach: ${attach.provider} / ${attach.providerSandboxId}`
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function printProcess(p5) {
|
|
2485
|
+
const exit = p5.status === "exited" ? `exit=${p5.exitCode ?? "?"}${p5.signal ? ` (${p5.signal})` : ""}` : "";
|
|
2486
|
+
console.log(`${pc3.cyan(p5.jobId)} ${p5.status}${exit ? " " + exit : ""} ${p5.command}`);
|
|
2487
|
+
}
|
|
2488
|
+
function registerSandboxesCommands(program2) {
|
|
2489
|
+
const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
|
|
2490
|
+
cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
|
|
2491
|
+
try {
|
|
2492
|
+
const c = await client(opts);
|
|
2493
|
+
const resources = {};
|
|
2494
|
+
if (opts.cpus) resources.cpus = Number(opts.cpus);
|
|
2495
|
+
if (opts.memoryMb) resources.memoryMb = Number(opts.memoryMb);
|
|
2496
|
+
if (opts.diskMb) resources.ephemeralDiskMb = Number(opts.diskMb);
|
|
2497
|
+
const body = {};
|
|
2498
|
+
if (opts.order) body.providerOrder = opts.order.split(",").map((s) => s.trim());
|
|
2499
|
+
if (opts.label) body.label = opts.label;
|
|
2500
|
+
if (opts.image) body.image = opts.image;
|
|
2501
|
+
if (opts.snapshotId) body.snapshotId = opts.snapshotId;
|
|
2502
|
+
if (opts.timeoutMs) body.timeoutMs = Number(opts.timeoutMs);
|
|
2503
|
+
if (opts.secret && opts.secret.length > 0) body.secrets = opts.secret;
|
|
2504
|
+
if (Object.keys(resources).length > 0) body.resources = resources;
|
|
2505
|
+
const res = await c.post("/api/v1/sandboxes", body);
|
|
2506
|
+
output(opts, res.sandbox, printSandbox);
|
|
2507
|
+
} catch (e) {
|
|
2508
|
+
fail(e, opts);
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
cmd.command("list").description("List sandboxes").option("--status <status>", "creating | running | destroyed").option("--limit <n>", "page size").option("--cursor <cursor>", "page cursor from a previous response").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (opts) => {
|
|
2512
|
+
try {
|
|
2513
|
+
const c = await client(opts);
|
|
2514
|
+
const res = await c.get(
|
|
2515
|
+
"/api/v1/sandboxes",
|
|
2516
|
+
{ status: opts.status, limit: opts.limit, cursor: opts.cursor }
|
|
2517
|
+
);
|
|
2518
|
+
output(opts, res, (page) => {
|
|
2519
|
+
page.sandboxes.forEach(printSandbox);
|
|
2520
|
+
if (page.nextCursor) console.log(pc3.dim(`nextCursor: ${page.nextCursor}`));
|
|
2521
|
+
});
|
|
2522
|
+
} catch (e) {
|
|
2523
|
+
fail(e, opts);
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
cmd.command("get <sandboxId>").description("Show one sandbox (includes the BYOK attach descriptor)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2527
|
+
try {
|
|
2528
|
+
const c = await client(opts);
|
|
2529
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}`);
|
|
2530
|
+
output(opts, res.sandbox, printSandbox);
|
|
2531
|
+
} catch (e) {
|
|
2532
|
+
fail(e, opts);
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
cmd.command("destroy <sandboxId>").description("Destroy a sandbox (verified teardown + cost settle)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2536
|
+
try {
|
|
2537
|
+
const c = await client(opts);
|
|
2538
|
+
const res = await c.del(`/api/v1/sandboxes/${sandboxId}`);
|
|
2539
|
+
output(opts, res.sandbox, printSandbox);
|
|
2540
|
+
} catch (e) {
|
|
2541
|
+
fail(e, opts);
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
|
|
2545
|
+
try {
|
|
2546
|
+
const c = await client(opts);
|
|
2547
|
+
const res = await c.post(
|
|
2548
|
+
`/api/v1/sandboxes/${sandboxId}/commands`,
|
|
2549
|
+
{
|
|
2550
|
+
command: command.map(shellQuote).join(" "),
|
|
2551
|
+
...opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
output(opts, res, (r) => {
|
|
2555
|
+
process.stdout.write(r.stdout);
|
|
2556
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
2557
|
+
if (!r.stdout) console.log(`exit ${r.exitCode} (${r.durationMs}ms)`);
|
|
2558
|
+
});
|
|
2559
|
+
if (res.exitCode !== 0) process.exitCode = res.exitCode;
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
fail(e, opts);
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
|
|
2565
|
+
try {
|
|
2566
|
+
const c = await client(opts);
|
|
2567
|
+
const res = await c.post(
|
|
2568
|
+
`/api/v1/sandboxes/${sandboxId}/processes`,
|
|
2569
|
+
{
|
|
2570
|
+
command: command.map(shellQuote).join(" "),
|
|
2571
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
2572
|
+
...opts.env && opts.env.length > 0 ? { env: parseInputs(opts.env) } : {}
|
|
2573
|
+
}
|
|
2574
|
+
);
|
|
2575
|
+
output(opts, res.process, printProcess);
|
|
2576
|
+
} catch (e) {
|
|
2577
|
+
fail(e, opts);
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
cmd.command("ps <sandboxId>").description("List the sandbox's tracked processes").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2581
|
+
try {
|
|
2582
|
+
const c = await client(opts);
|
|
2583
|
+
const res = await c.get(
|
|
2584
|
+
`/api/v1/sandboxes/${sandboxId}/processes`
|
|
2585
|
+
);
|
|
2586
|
+
output(opts, res.processes, (list) => list.forEach(printProcess));
|
|
2587
|
+
} catch (e) {
|
|
2588
|
+
fail(e, opts);
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
cmd.command("logs <sandboxId> <jobId>").description("Show a process's buffered output (--follow streams until exit)").option("-f, --follow", "stream output until the process exits").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2592
|
+
try {
|
|
2593
|
+
const c = await client(opts);
|
|
2594
|
+
if (opts.follow) {
|
|
2595
|
+
await followProcess(c, sandboxId, jobId);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const res = await c.get(
|
|
2599
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2600
|
+
);
|
|
2601
|
+
output(opts, res.process, (p5) => {
|
|
2602
|
+
if (p5.stdout) process.stdout.write(p5.stdout);
|
|
2603
|
+
if (p5.stderr) process.stderr.write(p5.stderr);
|
|
2604
|
+
if (!p5.stdout && !p5.stderr) printProcess(p5);
|
|
2605
|
+
});
|
|
2606
|
+
} catch (e) {
|
|
2607
|
+
fail(e, opts);
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
cmd.command("wait <sandboxId> <jobId>").description("Block until a process exits").option("--timeout-ms <ms>", "bound the wait (job keeps running on timeout)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2611
|
+
try {
|
|
2612
|
+
const c = await client(opts);
|
|
2613
|
+
const res = await c.post(
|
|
2614
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/wait`,
|
|
2615
|
+
opts.timeoutMs ? { timeoutMs: Number(opts.timeoutMs) } : {}
|
|
2616
|
+
);
|
|
2617
|
+
output(opts, res.process, printProcess);
|
|
2618
|
+
if (res.process.status === "exited") {
|
|
2619
|
+
process.exitCode = res.process.exitCode ?? 128;
|
|
2620
|
+
}
|
|
2621
|
+
} catch (e) {
|
|
2622
|
+
fail(e, opts);
|
|
2623
|
+
}
|
|
2624
|
+
});
|
|
2625
|
+
cmd.command("kill <sandboxId> <jobId>").description("Signal a process (default SIGTERM)").option("--signal <signal>", "signal name or number, e.g. SIGKILL or 9").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, jobId, opts) => {
|
|
2626
|
+
try {
|
|
2627
|
+
const c = await client(opts);
|
|
2628
|
+
const res = await c.post(
|
|
2629
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}/kill`,
|
|
2630
|
+
opts.signal ? { signal: opts.signal } : {}
|
|
2631
|
+
);
|
|
2632
|
+
output(opts, res.process, printProcess);
|
|
2633
|
+
} catch (e) {
|
|
2634
|
+
fail(e, opts);
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
cmd.command("ls <sandboxId> [path]").description("List a directory (or stat a path) inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2638
|
+
try {
|
|
2639
|
+
const c = await client(opts);
|
|
2640
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2641
|
+
path: path5 ?? "/"
|
|
2642
|
+
});
|
|
2643
|
+
output(opts, res, (view) => {
|
|
2644
|
+
if (view.type === "directory") {
|
|
2645
|
+
for (const e of view.entries) {
|
|
2646
|
+
console.log(`${e.type === "directory" ? pc3.blue("d") : " "} ${e.name}${e.size !== void 0 ? ` ${pc3.dim(String(e.size))}` : ""}`);
|
|
2647
|
+
}
|
|
2648
|
+
} else {
|
|
2649
|
+
console.log(`${view.path} file ${view.content.length} chars`);
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
} catch (e) {
|
|
2653
|
+
fail(e, opts);
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
cmd.command("cat <sandboxId> <path>").description("Print a file's contents").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2657
|
+
try {
|
|
2658
|
+
const c = await client(opts);
|
|
2659
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/files`, { path: path5 });
|
|
2660
|
+
output(opts, res, (view) => {
|
|
2661
|
+
if (view.type !== "file") {
|
|
2662
|
+
console.error(pc3.red(`${view.path} is a directory`));
|
|
2663
|
+
process.exit(1);
|
|
2664
|
+
}
|
|
2665
|
+
process.stdout.write(view.content);
|
|
2666
|
+
if (!view.content.endsWith("\n")) process.stdout.write("\n");
|
|
2667
|
+
});
|
|
2668
|
+
} catch (e) {
|
|
2669
|
+
fail(e, opts);
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
cmd.command("write <sandboxId> <path>").description("Write a file inside the sandbox (--content or --file <local>, else stdin)").option("--content <text>", "file contents").option("--file <local>", "read contents from a local file").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2673
|
+
try {
|
|
2674
|
+
let content = opts.content;
|
|
2675
|
+
if (content === void 0 && opts.file) {
|
|
2676
|
+
const { readFile } = await import("fs/promises");
|
|
2677
|
+
content = await readFile(opts.file, "utf8");
|
|
2678
|
+
}
|
|
2679
|
+
if (content === void 0 && !process.stdin.isTTY) {
|
|
2680
|
+
content = await new Promise((resolve) => {
|
|
2681
|
+
let data = "";
|
|
2682
|
+
process.stdin.setEncoding("utf8");
|
|
2683
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
2684
|
+
process.stdin.on("end", () => resolve(data));
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
if (content === void 0) {
|
|
2688
|
+
console.error(pc3.red("Pass --content, --file, or pipe stdin"));
|
|
2689
|
+
process.exit(1);
|
|
2690
|
+
}
|
|
2691
|
+
const c = await client(opts);
|
|
2692
|
+
const res = await c.post(
|
|
2693
|
+
`/api/v1/sandboxes/${sandboxId}/files`,
|
|
2694
|
+
{ path: path5, content }
|
|
2695
|
+
);
|
|
2696
|
+
output(opts, res, (r) => console.log(`wrote ${r.path}`));
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
fail(e, opts);
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
cmd.command("mkdir <sandboxId> <path>").description("Create a directory (parents included)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2702
|
+
try {
|
|
2703
|
+
const c = await client(opts);
|
|
2704
|
+
const res = await c.post(`/api/v1/sandboxes/${sandboxId}/files`, {
|
|
2705
|
+
path: path5,
|
|
2706
|
+
mkdir: true
|
|
2707
|
+
});
|
|
2708
|
+
output(opts, res, (r) => console.log(`created ${r.path}`));
|
|
2709
|
+
} catch (e) {
|
|
2710
|
+
fail(e, opts);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
cmd.command("rm <sandboxId> <path>").description("Remove a file or directory inside the sandbox").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, path5, opts) => {
|
|
2714
|
+
try {
|
|
2715
|
+
const c = await client(opts);
|
|
2716
|
+
const res = await c.del(
|
|
2717
|
+
`/api/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path5)}`
|
|
2718
|
+
);
|
|
2719
|
+
output(opts, res, (r) => console.log(`removed ${r.path}`));
|
|
2720
|
+
} catch (e) {
|
|
2721
|
+
fail(e, opts);
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
cmd.command("url <sandboxId>").description("Resolve the provider's public URL for a port").requiredOption("--port <n>", "port the service listens on").option("--protocol <protocol>", "protocol (default provider-chosen)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, opts) => {
|
|
2725
|
+
try {
|
|
2726
|
+
const c = await client(opts);
|
|
2727
|
+
const res = await c.get(`/api/v1/sandboxes/${sandboxId}/urls`, {
|
|
2728
|
+
port: opts.port,
|
|
2729
|
+
protocol: opts.protocol
|
|
2730
|
+
});
|
|
2731
|
+
output(opts, res, (r) => console.log(safeTerm(r.url)));
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
fail(e, opts);
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
async function followProcess(c, sandboxId, jobId) {
|
|
2738
|
+
let exitCode = 0;
|
|
2739
|
+
for await (const { event, data } of c.sseNamed(
|
|
2740
|
+
`/api/v1/sandboxes/${sandboxId}/processes/${jobId}`
|
|
2741
|
+
)) {
|
|
2742
|
+
const payload = data;
|
|
2743
|
+
if (event === "stdout") process.stdout.write(String(payload.chunk ?? ""));
|
|
2744
|
+
else if (event === "stderr") process.stderr.write(String(payload.chunk ?? ""));
|
|
2745
|
+
else if (event === "exit") {
|
|
2746
|
+
exitCode = typeof payload.exitCode === "number" ? payload.exitCode : 128;
|
|
2747
|
+
return finish();
|
|
2748
|
+
} else if (event === "error") {
|
|
2749
|
+
console.error(pc3.red(`Error: ${String(payload.error ?? "stream error")}`));
|
|
2750
|
+
return finish(1);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
console.error(pc3.yellow("stream ended before the process exited"));
|
|
2754
|
+
return finish(2);
|
|
2755
|
+
function finish(code) {
|
|
2756
|
+
if (code !== void 0) exitCode = code;
|
|
2757
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
1924
2761
|
// src/index.ts
|
|
1925
2762
|
init_repl();
|
|
1926
2763
|
|
|
@@ -1933,7 +2770,7 @@ import * as fs2 from "fs";
|
|
|
1933
2770
|
import * as path3 from "path";
|
|
1934
2771
|
import * as os2 from "os";
|
|
1935
2772
|
import * as p from "@clack/prompts";
|
|
1936
|
-
import
|
|
2773
|
+
import pc5 from "picocolors";
|
|
1937
2774
|
function getCredentialsDir() {
|
|
1938
2775
|
return path3.join(os2.homedir(), ".computesdk");
|
|
1939
2776
|
}
|
|
@@ -1991,7 +2828,7 @@ function openBrowser(url) {
|
|
|
1991
2828
|
if (err) {
|
|
1992
2829
|
p.log.warn("Could not open browser automatically.");
|
|
1993
2830
|
p.log.info(`Open this URL manually:
|
|
1994
|
-
${
|
|
2831
|
+
${pc5.cyan(url)}`);
|
|
1995
2832
|
}
|
|
1996
2833
|
});
|
|
1997
2834
|
}
|
|
@@ -2062,7 +2899,7 @@ async function resolveApiKey(options) {
|
|
|
2062
2899
|
const stored = loadStoredCredentials();
|
|
2063
2900
|
if (stored) {
|
|
2064
2901
|
p.log.info(
|
|
2065
|
-
`Using stored credentials from ${
|
|
2902
|
+
`Using stored credentials from ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
2066
2903
|
);
|
|
2067
2904
|
return stored;
|
|
2068
2905
|
}
|
|
@@ -2076,14 +2913,14 @@ async function resolveApiKey(options) {
|
|
|
2076
2913
|
try {
|
|
2077
2914
|
storeCredentials(apiKey);
|
|
2078
2915
|
p.log.success(
|
|
2079
|
-
`Credentials saved to ${
|
|
2916
|
+
`Credentials saved to ${pc5.dim("~/.computesdk/credentials.json")}`
|
|
2080
2917
|
);
|
|
2081
2918
|
} catch {
|
|
2082
2919
|
p.log.warn("Could not save credentials to disk. You may need to authenticate again next time.");
|
|
2083
2920
|
}
|
|
2084
2921
|
return apiKey;
|
|
2085
2922
|
} catch (error) {
|
|
2086
|
-
spinner4.stop(
|
|
2923
|
+
spinner4.stop(pc5.red("Authentication failed"));
|
|
2087
2924
|
throw error;
|
|
2088
2925
|
}
|
|
2089
2926
|
}
|
|
@@ -2091,7 +2928,7 @@ async function resolveApiKey(options) {
|
|
|
2091
2928
|
// src/setup.ts
|
|
2092
2929
|
init_esm_shims();
|
|
2093
2930
|
import * as p2 from "@clack/prompts";
|
|
2094
|
-
import
|
|
2931
|
+
import pc6 from "picocolors";
|
|
2095
2932
|
import { compute as compute2 } from "computesdk";
|
|
2096
2933
|
async function ensureAuth(options) {
|
|
2097
2934
|
try {
|
|
@@ -2099,8 +2936,8 @@ async function ensureAuth(options) {
|
|
|
2099
2936
|
process.env.COMPUTESDK_API_KEY = apiKey;
|
|
2100
2937
|
} catch (error) {
|
|
2101
2938
|
p2.log.error(error.message);
|
|
2102
|
-
p2.log.info(`Get your API key at ${
|
|
2103
|
-
p2.outro(
|
|
2939
|
+
p2.log.info(`Get your API key at ${pc6.cyan("https://console.computesdk.com")}`);
|
|
2940
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
2104
2941
|
process.exit(1);
|
|
2105
2942
|
}
|
|
2106
2943
|
}
|
|
@@ -2129,7 +2966,7 @@ async function resolveProvider(providerFlag, options) {
|
|
|
2129
2966
|
if (available.length === 0) {
|
|
2130
2967
|
p2.log.error("No provider credentials detected");
|
|
2131
2968
|
p2.log.info("Set one of: E2B_API_KEY, MODAL_TOKEN_ID, RAILWAY_API_KEY, etc.");
|
|
2132
|
-
p2.outro(
|
|
2969
|
+
p2.outro(pc6.red("Setup incomplete"));
|
|
2133
2970
|
process.exit(1);
|
|
2134
2971
|
}
|
|
2135
2972
|
if (available.length === 1) {
|
|
@@ -2173,24 +3010,24 @@ program.name("compute").description("Cloud-native workspace management").version
|
|
|
2173
3010
|
program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
|
|
2174
3011
|
if (opts.logout) {
|
|
2175
3012
|
console.log();
|
|
2176
|
-
p4.intro(
|
|
3013
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2177
3014
|
clearStoredCredentials();
|
|
2178
3015
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2179
|
-
p4.outro(
|
|
3016
|
+
p4.outro(pc8.green("Done!"));
|
|
2180
3017
|
process.exit(0);
|
|
2181
3018
|
}
|
|
2182
3019
|
if (opts.login) {
|
|
2183
3020
|
console.log();
|
|
2184
|
-
p4.intro(
|
|
3021
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2185
3022
|
await ensureAuth({ forceLogin: true });
|
|
2186
|
-
p4.outro(
|
|
3023
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2187
3024
|
process.exit(0);
|
|
2188
3025
|
}
|
|
2189
3026
|
program.help();
|
|
2190
3027
|
});
|
|
2191
3028
|
program.command("create").description("Create a new workspace (same as create-compute)").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
|
|
2192
3029
|
console.log();
|
|
2193
|
-
p4.intro(
|
|
3030
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2194
3031
|
await ensureAuth();
|
|
2195
3032
|
if (!repo) {
|
|
2196
3033
|
const state = loadWorkspaceState();
|
|
@@ -2211,7 +3048,7 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2211
3048
|
message: "Select workspace to attach (or create new)",
|
|
2212
3049
|
options: [
|
|
2213
3050
|
...choices,
|
|
2214
|
-
{ value: "__new__", label:
|
|
3051
|
+
{ value: "__new__", label: pc8.gray("+ Create new workspace") }
|
|
2215
3052
|
]
|
|
2216
3053
|
});
|
|
2217
3054
|
if (p4.isCancel(selected)) {
|
|
@@ -2252,15 +3089,15 @@ program.command("create").description("Create a new workspace (same as create-co
|
|
|
2252
3089
|
branch: options.branch,
|
|
2253
3090
|
provider
|
|
2254
3091
|
});
|
|
2255
|
-
spinner4.stop(`Workspace ready: ${
|
|
3092
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2256
3093
|
if (options.attach !== false) {
|
|
2257
3094
|
console.log();
|
|
2258
3095
|
await workspaceAttach(workspace.id);
|
|
2259
3096
|
} else {
|
|
2260
|
-
p4.outro(
|
|
3097
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2261
3098
|
}
|
|
2262
3099
|
} catch (error) {
|
|
2263
|
-
spinner4.stop(
|
|
3100
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2264
3101
|
p4.log.error(error.message);
|
|
2265
3102
|
process.exit(1);
|
|
2266
3103
|
}
|
|
@@ -2269,7 +3106,7 @@ var sandboxCmd = program.command("sandbox").alias("sb").description("Manage raw
|
|
|
2269
3106
|
sandboxCmd.command("create").description("Create a new sandbox").option("-p, --provider <provider>", "provider to use").option("--timeout <duration>", "sandbox timeout (e.g. 5m, 1h)").option("--name <name>", "sandbox name").option("--connect", "drop into the REPL after creation").option("--silent", "only output the sandbox ID").action(async (opts) => {
|
|
2270
3107
|
if (!opts.silent) {
|
|
2271
3108
|
console.log();
|
|
2272
|
-
p4.intro(
|
|
3109
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2273
3110
|
}
|
|
2274
3111
|
await ensureAuth();
|
|
2275
3112
|
const provider = await resolveProvider(opts.provider);
|
|
@@ -2281,7 +3118,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2281
3118
|
const sandbox = await compute4.sandbox.create(
|
|
2282
3119
|
buildCreateOptions(opts)
|
|
2283
3120
|
);
|
|
2284
|
-
spinner4.stop(`Sandbox ready: ${
|
|
3121
|
+
spinner4.stop(`Sandbox ready: ${pc8.cyan(sandbox.sandboxId)}`);
|
|
2285
3122
|
const { saveSandbox: saveSandbox2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2286
3123
|
await saveSandbox2({
|
|
2287
3124
|
id: sandbox.sandboxId,
|
|
@@ -2294,7 +3131,7 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2294
3131
|
await startREPL(sandbox, provider);
|
|
2295
3132
|
}
|
|
2296
3133
|
} catch (error) {
|
|
2297
|
-
spinner4.stop(
|
|
3134
|
+
spinner4.stop(pc8.red("Failed to create sandbox"));
|
|
2298
3135
|
p4.log.error(error.message);
|
|
2299
3136
|
process.exit(1);
|
|
2300
3137
|
}
|
|
@@ -2323,14 +3160,14 @@ sandboxCmd.command("create").description("Create a new sandbox").option("-p, --p
|
|
|
2323
3160
|
});
|
|
2324
3161
|
sandboxCmd.command("destroy").description("Destroy one or more sandboxes").aliases(["rm", "stop"]).argument("[sandbox_id...]", "sandbox IDs to destroy (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxIds, opts) => {
|
|
2325
3162
|
console.log();
|
|
2326
|
-
p4.intro(
|
|
3163
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2327
3164
|
const { loadState: loadState2, saveState: saveState2 } = await Promise.resolve().then(() => (init_workspace_cli(), workspace_cli_exports));
|
|
2328
3165
|
const state = loadState2();
|
|
2329
3166
|
if (!sandboxIds || sandboxIds.length === 0) {
|
|
2330
3167
|
const workspaces = Object.values(state.workspaces);
|
|
2331
3168
|
if (workspaces.length === 0) {
|
|
2332
3169
|
p4.log.info("No sandboxes to destroy.");
|
|
2333
|
-
p4.outro(
|
|
3170
|
+
p4.outro(pc8.green("Done!"));
|
|
2334
3171
|
return;
|
|
2335
3172
|
}
|
|
2336
3173
|
const choices = workspaces.map((ws) => {
|
|
@@ -2338,7 +3175,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2338
3175
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2339
3176
|
);
|
|
2340
3177
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2341
|
-
const status = age > 7 ?
|
|
3178
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2342
3179
|
return {
|
|
2343
3180
|
value: ws.sandboxId,
|
|
2344
3181
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2366,7 +3203,7 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2366
3203
|
for (const result of results) {
|
|
2367
3204
|
if (result.status === "fulfilled") {
|
|
2368
3205
|
const sandboxId = result.value;
|
|
2369
|
-
console.log(
|
|
3206
|
+
console.log(pc8.green(`Destroyed ${sandboxId}`));
|
|
2370
3207
|
const workspaceId = Object.keys(state.workspaces).find(
|
|
2371
3208
|
(key) => state.workspaces[key].sandboxId === sandboxId
|
|
2372
3209
|
);
|
|
@@ -2374,19 +3211,19 @@ sandboxCmd.command("destroy").description("Destroy one or more sandboxes").alias
|
|
|
2374
3211
|
delete state.workspaces[workspaceId];
|
|
2375
3212
|
}
|
|
2376
3213
|
} else {
|
|
2377
|
-
console.error(
|
|
3214
|
+
console.error(pc8.red(`Failed to destroy: ${result.reason?.message || result.reason}`));
|
|
2378
3215
|
}
|
|
2379
3216
|
}
|
|
2380
3217
|
saveState2(state);
|
|
2381
3218
|
});
|
|
2382
3219
|
sandboxCmd.command("list").alias("ls").description("List all sandboxes").action(async () => {
|
|
2383
3220
|
console.log();
|
|
2384
|
-
p4.intro(
|
|
3221
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2385
3222
|
await sandboxList();
|
|
2386
3223
|
});
|
|
2387
3224
|
sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; same as compute connect)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
|
|
2388
3225
|
console.log();
|
|
2389
|
-
p4.intro(
|
|
3226
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2390
3227
|
await ensureAuth();
|
|
2391
3228
|
const provider = await resolveProvider(opts.provider);
|
|
2392
3229
|
configureCompute(provider);
|
|
@@ -2397,7 +3234,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2397
3234
|
if (workspaces.length === 0) {
|
|
2398
3235
|
p4.log.info("No sandboxes found.");
|
|
2399
3236
|
p4.log.info("Create one with: compute sandbox create");
|
|
2400
|
-
p4.outro(
|
|
3237
|
+
p4.outro(pc8.green("Done!"));
|
|
2401
3238
|
return;
|
|
2402
3239
|
}
|
|
2403
3240
|
const choices = workspaces.map((ws) => {
|
|
@@ -2405,7 +3242,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2405
3242
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2406
3243
|
);
|
|
2407
3244
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2408
|
-
const status = age > 7 ?
|
|
3245
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2409
3246
|
return {
|
|
2410
3247
|
value: ws.sandboxId,
|
|
2411
3248
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2425,7 +3262,7 @@ sandboxCmd.command("connect").description("Connect to a sandbox (opens REPL; sam
|
|
|
2425
3262
|
spinner4.start("Connecting...");
|
|
2426
3263
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2427
3264
|
if (!sandbox) {
|
|
2428
|
-
spinner4.stop(
|
|
3265
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2429
3266
|
process.exit(1);
|
|
2430
3267
|
}
|
|
2431
3268
|
spinner4.stop("");
|
|
@@ -2437,7 +3274,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2437
3274
|
configureCompute(provider);
|
|
2438
3275
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2439
3276
|
if (!sandbox) {
|
|
2440
|
-
console.error(
|
|
3277
|
+
console.error(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2441
3278
|
process.exit(1);
|
|
2442
3279
|
}
|
|
2443
3280
|
const cmd = buildExecCommand(command, opts);
|
|
@@ -2452,7 +3289,7 @@ sandboxCmd.command("exec").description("Execute a command in a sandbox").argumen
|
|
|
2452
3289
|
});
|
|
2453
3290
|
program.command("connect").description("Connect to a sandbox (opens REPL)").aliases(["ssh", "shell"]).argument("[sandbox_id]", "sandbox ID (optional - will show picker if not provided)").option("-p, --provider <provider>", "provider to use").action(async (sandboxId, opts) => {
|
|
2454
3291
|
console.log();
|
|
2455
|
-
p4.intro(
|
|
3292
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2456
3293
|
await ensureAuth();
|
|
2457
3294
|
const provider = await resolveProvider(opts.provider);
|
|
2458
3295
|
configureCompute(provider);
|
|
@@ -2463,7 +3300,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2463
3300
|
if (workspaces.length === 0) {
|
|
2464
3301
|
p4.log.info("No sandboxes found.");
|
|
2465
3302
|
p4.log.info("Create one with: compute sandbox create");
|
|
2466
|
-
p4.outro(
|
|
3303
|
+
p4.outro(pc8.green("Done!"));
|
|
2467
3304
|
return;
|
|
2468
3305
|
}
|
|
2469
3306
|
const choices = workspaces.map((ws) => {
|
|
@@ -2471,7 +3308,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2471
3308
|
(Date.now() - new Date(ws.lastUsed).getTime()) / (1e3 * 60 * 60 * 24)
|
|
2472
3309
|
);
|
|
2473
3310
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2474
|
-
const status = age > 7 ?
|
|
3311
|
+
const status = age > 7 ? pc8.red("stale") : age > 1 ? pc8.yellow("idle") : pc8.green("active");
|
|
2475
3312
|
return {
|
|
2476
3313
|
value: ws.sandboxId,
|
|
2477
3314
|
label: `${status} ${ws.sandboxId} (${ws.provider}, last active ${ageStr})`
|
|
@@ -2491,7 +3328,7 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2491
3328
|
spinner4.start("Connecting...");
|
|
2492
3329
|
const sandbox = await compute4.sandbox.getById(sandboxId);
|
|
2493
3330
|
if (!sandbox) {
|
|
2494
|
-
spinner4.stop(
|
|
3331
|
+
spinner4.stop(pc8.red(`Sandbox not found: ${sandboxId}`));
|
|
2495
3332
|
process.exit(1);
|
|
2496
3333
|
}
|
|
2497
3334
|
spinner4.stop("");
|
|
@@ -2500,11 +3337,11 @@ program.command("connect").description("Connect to a sandbox (opens REPL)").alia
|
|
|
2500
3337
|
var workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces (sandbox + git + worktree)");
|
|
2501
3338
|
workspaceCmd.command("create").description("Create a new workspace").argument("[repo]", "Repository in owner/repo format").option("-b, --branch <branch>", "Branch name", "main").option("-p, --provider <provider>", "provider to use").option("--no-attach", "Create workspace but do not attach").action(async (repo, options) => {
|
|
2502
3339
|
console.log();
|
|
2503
|
-
p4.intro(
|
|
3340
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2504
3341
|
await ensureAuth();
|
|
2505
3342
|
if (!repo) {
|
|
2506
3343
|
p4.log.info("Usage: compute workspace create owner/repo --branch main");
|
|
2507
|
-
p4.outro(
|
|
3344
|
+
p4.outro(pc8.green("Done!"));
|
|
2508
3345
|
return;
|
|
2509
3346
|
}
|
|
2510
3347
|
const provider = await resolveProvider(options.provider, { interactive: true });
|
|
@@ -2517,33 +3354,33 @@ workspaceCmd.command("create").description("Create a new workspace").argument("[
|
|
|
2517
3354
|
branch: options.branch,
|
|
2518
3355
|
provider
|
|
2519
3356
|
});
|
|
2520
|
-
spinner4.stop(`Workspace ready: ${
|
|
3357
|
+
spinner4.stop(`Workspace ready: ${pc8.cyan(workspace.id)}`);
|
|
2521
3358
|
if (options.attach !== false) {
|
|
2522
3359
|
console.log();
|
|
2523
3360
|
await workspaceAttach(workspace.id);
|
|
2524
3361
|
} else {
|
|
2525
|
-
p4.outro(
|
|
3362
|
+
p4.outro(pc8.green("Workspace created!"));
|
|
2526
3363
|
}
|
|
2527
3364
|
} catch (error) {
|
|
2528
|
-
spinner4.stop(
|
|
3365
|
+
spinner4.stop(pc8.red("Failed to create workspace"));
|
|
2529
3366
|
p4.log.error(error.message);
|
|
2530
3367
|
process.exit(1);
|
|
2531
3368
|
}
|
|
2532
3369
|
});
|
|
2533
3370
|
workspaceCmd.command("list").alias("ls").description("List all workspaces").action(async () => {
|
|
2534
3371
|
console.log();
|
|
2535
|
-
p4.intro(
|
|
3372
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2536
3373
|
await workspaceList();
|
|
2537
3374
|
});
|
|
2538
3375
|
workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").argument("[id]", "Workspace ID (repo:branch format, optional - will show picker if not provided)").action(async (id) => {
|
|
2539
3376
|
console.log();
|
|
2540
|
-
p4.intro(
|
|
3377
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2541
3378
|
if (!id) {
|
|
2542
3379
|
const state = loadWorkspaceState();
|
|
2543
3380
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2544
3381
|
if (workspaceIds.length === 0) {
|
|
2545
3382
|
p4.log.info("No workspaces to destroy.");
|
|
2546
|
-
p4.outro(
|
|
3383
|
+
p4.outro(pc8.green("Done!"));
|
|
2547
3384
|
return;
|
|
2548
3385
|
}
|
|
2549
3386
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2554,7 +3391,7 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2554
3391
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2555
3392
|
return {
|
|
2556
3393
|
value: key,
|
|
2557
|
-
label: `${
|
|
3394
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2558
3395
|
};
|
|
2559
3396
|
});
|
|
2560
3397
|
const selected = await p4.multiselect({
|
|
@@ -2574,14 +3411,14 @@ workspaceCmd.command("destroy").alias("rm").description("Destroy a workspace").a
|
|
|
2574
3411
|
});
|
|
2575
3412
|
workspaceCmd.command("connect").alias("attach").description("Connect to an existing workspace (with git context)").argument("[id]", "Workspace ID (optional - will show picker if not provided)").action(async (id) => {
|
|
2576
3413
|
console.log();
|
|
2577
|
-
p4.intro(
|
|
3414
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2578
3415
|
if (!id) {
|
|
2579
3416
|
const state = loadWorkspaceState();
|
|
2580
3417
|
const workspaceIds = Object.keys(state.workspaces);
|
|
2581
3418
|
if (workspaceIds.length === 0) {
|
|
2582
3419
|
p4.log.info("No workspaces found.");
|
|
2583
3420
|
p4.log.info("Create one with: compute create owner/repo");
|
|
2584
|
-
p4.outro(
|
|
3421
|
+
p4.outro(pc8.green("Done!"));
|
|
2585
3422
|
return;
|
|
2586
3423
|
}
|
|
2587
3424
|
const choices = workspaceIds.map((key) => {
|
|
@@ -2592,7 +3429,7 @@ workspaceCmd.command("connect").alias("attach").description("Connect to an exist
|
|
|
2592
3429
|
const ageStr = age === 0 ? "today" : age === 1 ? "1 day ago" : `${age} days ago`;
|
|
2593
3430
|
return {
|
|
2594
3431
|
value: key,
|
|
2595
|
-
label: `${
|
|
3432
|
+
label: `${pc8.cyan(key)} (${ws.provider}, last used ${ageStr})`
|
|
2596
3433
|
};
|
|
2597
3434
|
});
|
|
2598
3435
|
const selected = await p4.select({
|
|
@@ -2617,7 +3454,7 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2617
3454
|
buildCreateOptions(opts)
|
|
2618
3455
|
);
|
|
2619
3456
|
} catch (error) {
|
|
2620
|
-
console.error(
|
|
3457
|
+
console.error(pc8.red(`Failed to create sandbox: ${error.message}`));
|
|
2621
3458
|
process.exit(1);
|
|
2622
3459
|
}
|
|
2623
3460
|
try {
|
|
@@ -2640,37 +3477,39 @@ program.command("run").description("Create a sandbox, run a command, then option
|
|
|
2640
3477
|
} catch {
|
|
2641
3478
|
}
|
|
2642
3479
|
}
|
|
2643
|
-
console.error(
|
|
3480
|
+
console.error(pc8.red(error.message));
|
|
2644
3481
|
process.exit(1);
|
|
2645
3482
|
}
|
|
2646
3483
|
});
|
|
2647
3484
|
program.command("providers").description("List all providers and their configuration status").action(async () => {
|
|
2648
3485
|
console.log();
|
|
2649
|
-
p4.intro(
|
|
3486
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2650
3487
|
const statuses = getProviderStatus();
|
|
2651
3488
|
console.log();
|
|
2652
3489
|
for (const status of statuses) {
|
|
2653
|
-
const icon = status.ready ?
|
|
2654
|
-
const name = status.ready ?
|
|
2655
|
-
const detail = status.ready ?
|
|
3490
|
+
const icon = status.ready ? pc8.green("\u25CF") : pc8.gray("\u25CB");
|
|
3491
|
+
const name = status.ready ? pc8.white(status.name) : pc8.gray(status.name);
|
|
3492
|
+
const detail = status.ready ? pc8.green("ready") : pc8.gray(`missing: ${status.missing.join(", ")}`);
|
|
2656
3493
|
console.log(` ${icon} ${name} ${detail}`);
|
|
2657
3494
|
}
|
|
2658
3495
|
console.log();
|
|
2659
3496
|
});
|
|
2660
3497
|
program.command("login").description("Authenticate with ComputeSDK").action(async () => {
|
|
2661
3498
|
console.log();
|
|
2662
|
-
p4.intro(
|
|
3499
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2663
3500
|
await ensureAuth({ forceLogin: true });
|
|
2664
|
-
p4.outro(
|
|
3501
|
+
p4.outro(pc8.green("Authenticated!"));
|
|
2665
3502
|
});
|
|
2666
3503
|
program.command("logout").description("Clear stored credentials").action(() => {
|
|
2667
3504
|
console.log();
|
|
2668
|
-
p4.intro(
|
|
3505
|
+
p4.intro(pc8.cyan(`@computesdk/cli v${VERSION}`));
|
|
2669
3506
|
clearStoredCredentials();
|
|
2670
3507
|
p4.log.success("Logged out. Stored credentials removed.");
|
|
2671
|
-
p4.outro(
|
|
3508
|
+
p4.outro(pc8.green("Done!"));
|
|
2672
3509
|
});
|
|
2673
3510
|
registerActionsCommands(program);
|
|
3511
|
+
registerMarketCommands(program);
|
|
3512
|
+
registerSandboxesCommands(program);
|
|
2674
3513
|
program.command("bench").description("Benchmarks platform CLI (run, auth, org, benchmarks, runs, results, artifacts, export, ...)").allowUnknownOption().helpOption(false).action(() => {
|
|
2675
3514
|
});
|
|
2676
3515
|
function buildCreateOptions(opts) {
|
|
@@ -2748,7 +3587,7 @@ async function main() {
|
|
|
2748
3587
|
await program.parseAsync(process.argv);
|
|
2749
3588
|
}
|
|
2750
3589
|
main().catch((error) => {
|
|
2751
|
-
console.error(
|
|
3590
|
+
console.error(pc8.red(`
|
|
2752
3591
|
Error: ${error.message}`));
|
|
2753
3592
|
process.exit(1);
|
|
2754
3593
|
});
|