promigence 0.1.0-beta.2 → 0.1.0-beta.3
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/account.js +31 -1
- package/dist/bin.js +4 -2
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +117 -8
- package/dist/handlers.js +42 -0
- package/dist/io.d.ts +1 -0
- package/dist/io.js +9 -1
- package/dist/login.js +4 -0
- package/dist/routes.js +1 -0
- package/dist/sdk/client.d.ts +43 -0
- package/dist/sdk/client.js +34 -1
- package/dist/sdk/core.d.ts +1 -1
- package/dist/sdk/core.js +1 -1
- package/dist/sdk/models.gen.d.ts +97 -2
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +8 -1
- package/dist/timing.js +71 -0
- package/llms.txt +4 -3
- package/package.json +1 -1
package/dist/account.js
CHANGED
|
@@ -87,7 +87,7 @@ const status = async (ctx) => {
|
|
|
87
87
|
};
|
|
88
88
|
const me = async (ctx) => {
|
|
89
89
|
const c = client(ctx);
|
|
90
|
-
const info = await c.me();
|
|
90
|
+
const [info, outcomes] = await Promise.all([c.me(), c.outcomes().catch(() => undefined)]);
|
|
91
91
|
const conc = await concurrencyOf(c, info);
|
|
92
92
|
const plan = planName(info);
|
|
93
93
|
const trial = info.trial;
|
|
@@ -124,12 +124,42 @@ const me = async (ctx) => {
|
|
|
124
124
|
].filter(Boolean);
|
|
125
125
|
if (shape.length)
|
|
126
126
|
lines.push(` limits: ${shape.join(" · ")}`);
|
|
127
|
+
if (outcomes && outcomes.sandboxes > 0) {
|
|
128
|
+
const [head, ...rest] = outcomeLines(outcomes);
|
|
129
|
+
lines.push(` ${head}:`, ...rest.map((l) => ` ${l}`));
|
|
130
|
+
}
|
|
127
131
|
return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
|
|
128
132
|
me: info,
|
|
133
|
+
...(outcomes ? { outcomes } : {}),
|
|
129
134
|
...(plan ? { plan } : {}),
|
|
130
135
|
...(conc ? { concurrency: { in_use: conc.in_use, limit: conc.limit, source: conc.source, ...(conc.at_least ? { at_least: true } : {}) } } : {}),
|
|
131
136
|
}, next), lines, 0, ctx.pointer);
|
|
132
137
|
};
|
|
138
|
+
export function outcomeLines(o) {
|
|
139
|
+
const usd = (n) => `$${n.toFixed(2)}`;
|
|
140
|
+
const lines = [`this month (since ${o.since.slice(0, 10)})`];
|
|
141
|
+
if (o.sandboxes <= 0) {
|
|
142
|
+
lines.push(" nothing ran yet this month");
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
lines.push(` runs ${o.runs.toLocaleString("en-US")}`);
|
|
146
|
+
const ready = o.ready_ms_p50 ?? null;
|
|
147
|
+
lines.push(` sandboxes ${o.sandboxes.toLocaleString("en-US")}${ready === null ? "" : `, ready in ${ms(ready)} (median)`}`);
|
|
148
|
+
if (o.failures_ours > 0)
|
|
149
|
+
lines.push(` failures of ours ${o.failures_ours}, not billed${o.failures_ours_usd > 0 ? ` (${usd(o.failures_ours_usd)} given back)` : ""}`);
|
|
150
|
+
const speedup = o.batch_speedup ?? null;
|
|
151
|
+
const saved = o.batch_saved_s ?? null;
|
|
152
|
+
if (speedup !== null && saved !== null) {
|
|
153
|
+
lines.push(` many at once ${o.batch_runs} runs, ${speedup.toFixed(1)}x faster than one after another, ${duration(saved * 1000)} not spent waiting`);
|
|
154
|
+
}
|
|
155
|
+
if (o.kept_failures > 0)
|
|
156
|
+
lines.push(` kept failures ${o.kept_failures} you can still reopen`);
|
|
157
|
+
const hours = o.paused_hours ?? null;
|
|
158
|
+
const paidNot = o.paused_usd ?? null;
|
|
159
|
+
if (hours !== null)
|
|
160
|
+
lines.push(` paused, not billed ${hours.toFixed(1)} h${paidNot === null ? "" : ` (${usd(paidNot)} you did not pay)`}`);
|
|
161
|
+
return lines;
|
|
162
|
+
}
|
|
133
163
|
const billingUsage = async (ctx) => {
|
|
134
164
|
const limit = limitOf(ctx, 20, 1000);
|
|
135
165
|
const wantLedger = bool(ctx, "--ledger");
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { GLOBAL_FLAGS, LISTED_COMMANDS, REQUEST_TIMEOUT_DEFAULT, SCHEMA, VERSION, findCommand, nearestFlag, notReleasedEnvelope, notReleasedLines, renderCommandHelp, renderHelp, unknownFlags, } from "./surface.js";
|
|
3
|
-
import { HANDLERS, client } from "./commands.js";
|
|
3
|
+
import { HANDLERS, client, serverLocation } from "./commands.js";
|
|
4
|
+
import { maybeFarNotice } from "./timing.js";
|
|
4
5
|
import { refineError } from "./errfix.js";
|
|
5
6
|
import { EXTRA_HANDLERS } from "./handlers.js";
|
|
6
7
|
import { BUNDLE_HANDLERS } from "./bundle.js";
|
|
@@ -17,7 +18,7 @@ import { RUNNERS_HANDLERS } from "./runners.js";
|
|
|
17
18
|
import { PRICING_HANDLERS } from "./pricing.js";
|
|
18
19
|
import { GITHUB_HANDLERS } from "./github.js";
|
|
19
20
|
import { GITLAB_HANDLERS } from "./gitlab.js";
|
|
20
|
-
import { emit, errorEnvelope, errorTextLines, parseDuration, toPromigenceError, usageError as usageFailure, USAGE_DOCS, write, } from "./io.js";
|
|
21
|
+
import { emit, errorEnvelope, errorTextLines, parseDuration, setBeforeExit, toPromigenceError, usageError as usageFailure, USAGE_DOCS, write, } from "./io.js";
|
|
21
22
|
import { setCaller } from "./sdk/caller.js";
|
|
22
23
|
import { sh } from "./term.js";
|
|
23
24
|
import { parse, positionalsBeforeTail, takesTail } from "./argv.js";
|
|
@@ -286,6 +287,7 @@ async function main(argv) {
|
|
|
286
287
|
if (ctx.apiKey !== undefined && cmd.name !== "auth login" && cmd.name !== "doctor") {
|
|
287
288
|
process.stderr.write("promigence: warning: --api-key puts the key on the command line, where `ps` and shell history can read it; prefer PROMIGENCE_API_KEY or `promigence auth login --from-env VAR`.\n");
|
|
288
289
|
}
|
|
290
|
+
setBeforeExit(() => maybeFarNotice({ json: ctx.mode === "json", location: () => serverLocation(ctx) }));
|
|
289
291
|
try {
|
|
290
292
|
checkFlags(cmd, p, argv);
|
|
291
293
|
ctx.requestTimeoutMs = requestTimeoutMs(p);
|
package/dist/commands.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare function clientOpts(ctx: Ctx): {
|
|
|
24
24
|
cliErrors: true;
|
|
25
25
|
};
|
|
26
26
|
export declare function client(ctx: Ctx): PromigenceClient;
|
|
27
|
+
export declare function serverLocation(ctx: Ctx): Promise<string | undefined>;
|
|
27
28
|
export declare const nexts: (...items: Array<[string, string]>) => NextHint[];
|
|
28
29
|
export declare const KEPT_BY_HOLDERS = "sandboxes that were already given its value keep it until they end; it stays masked in their output";
|
|
29
30
|
export declare function waitBound(ctx: Ctx): number;
|
|
@@ -52,6 +53,7 @@ export declare function snapshotSource(s: {
|
|
|
52
53
|
commit?: string;
|
|
53
54
|
verification?: Record<string, unknown>;
|
|
54
55
|
}): string;
|
|
56
|
+
export declare function readyLine(ourMs: number | undefined, totalMs: number): string;
|
|
55
57
|
export declare function sandboxIdLines(sandboxes: ReadonlyArray<Pick<SandboxRef, "sandbox_id" | "state">>, cap?: number): string[];
|
|
56
58
|
type EnvError = {
|
|
57
59
|
code: string;
|
|
@@ -67,6 +69,7 @@ export interface ExecOutcome {
|
|
|
67
69
|
error?: EnvError;
|
|
68
70
|
}
|
|
69
71
|
export declare function execOutcome(res: ExecResponse, sandboxId: string, timeoutMs?: number, words?: "cli" | "mcp"): ExecOutcome;
|
|
72
|
+
export declare function execTimingLine(exitCode: number | null | undefined, wallMs: unknown, totalMs: number, isTTY?: boolean, env?: NodeJS.ProcessEnv): string;
|
|
70
73
|
export declare function endNotice(res: ExecResponse, timeoutMs?: number): string;
|
|
71
74
|
export declare const MAX_OUTPUT_BYTES_PIPED: number;
|
|
72
75
|
export declare function truncationNotice(kept: "first" | "last", maxOutputBytes: number | undefined): string;
|
package/dist/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync,
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, dirname, join, resolve } from "node:path";
|
|
6
6
|
import { DEFAULT_API_URL, DEFAULT_SNAPSHOT, ERRORS_DOCS_URL, MAX_OUTPUT_BYTES_DEFAULT, CONTEXT_GIT_RE, PromigenceClient, checkRepoUrl, withoutPin, PromigenceError, SpendCapError, TimeoutError, checkImageRef, contextRelative, credentialsPath, FILE_PUT_CHUNK_BYTES, imageSnapshot, isWaiting, execOutputBytes, packContext, readCredentials, } from "./sdk/index.js";
|
|
7
|
+
import { fmtMs, recordTiming } from "./timing.js";
|
|
7
8
|
import { capacityLine, queuedClause, queuedLines, statusLine } from "./capacity.js";
|
|
8
9
|
import { DevcontainerError, detectOne, load, planJson, planLines, planSetup, resolveGiven, translate, workspaceRootFor, wrapCommand, } from "./devcontainer.js";
|
|
9
10
|
import { detect, ecosystemOfCommands, imageCreateCommand, readConfig } from "./init.js";
|
|
@@ -52,7 +53,11 @@ export function clientOpts(ctx) {
|
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
export function client(ctx) {
|
|
55
|
-
return new PromigenceClient({ ...clientOpts(ctx), onNotice: recordNotice });
|
|
56
|
+
return new PromigenceClient({ ...clientOpts(ctx), onNotice: recordNotice, onTiming: recordTiming });
|
|
57
|
+
}
|
|
58
|
+
export async function serverLocation(ctx) {
|
|
59
|
+
const h = await client(ctx).health({ timeoutMs: 3000 }).catch(() => undefined);
|
|
60
|
+
return typeof h?.location === "string" && h.location !== "" ? h.location : undefined;
|
|
56
61
|
}
|
|
57
62
|
export const nexts = (...items) => items.map(([cmd, why]) => ({ cmd, why }));
|
|
58
63
|
export const KEPT_BY_HOLDERS = "sandboxes that were already given its value keep it until they end; it stays masked in their output";
|
|
@@ -1048,13 +1053,16 @@ const fork = async (ctx) => {
|
|
|
1048
1053
|
env.error = failure;
|
|
1049
1054
|
return emit(ctx.mode, env, [
|
|
1050
1055
|
...(replayed ? [replayed] : []),
|
|
1051
|
-
`${up} of ${opts.count} sandbox${opts.count === 1 ? "" : "es"} ready in ${waited.waitedMs}
|
|
1056
|
+
`${up} of ${opts.count} sandbox${opts.count === 1 ? "" : "es"} ready in ${readyLine(report?.fork?.ready_ms?.p50, waited.waitedMs)} (run ${runId})`,
|
|
1052
1057
|
...sandboxIdLines(waited.sandboxes),
|
|
1053
1058
|
...(report && bool(ctx, "--verbose") ? [` ${forkLine(report.fork)}`, ` ${totalsLine(report.totals)}`] : []),
|
|
1054
1059
|
...(report && exit !== 0 ? failureLines(report, new Map(), false) : []),
|
|
1055
1060
|
` next: ${execNext[0].cmd}`,
|
|
1056
1061
|
], exit, ctx.pointer);
|
|
1057
1062
|
};
|
|
1063
|
+
export function readyLine(ourMs, totalMs) {
|
|
1064
|
+
return ourMs === undefined ? `${totalMs} ms` : `${fmtMs(ourMs)} on our side · ${fmtMs(totalMs)} total`;
|
|
1065
|
+
}
|
|
1058
1066
|
export function sandboxIdLines(sandboxes, cap = 10) {
|
|
1059
1067
|
const lines = sandboxes.slice(0, cap).map((s) => ` ${s.sandbox_id} ${s.state}`);
|
|
1060
1068
|
if (sandboxes.length > cap)
|
|
@@ -1135,6 +1143,91 @@ function wantsStream(ctx) {
|
|
|
1135
1143
|
return true;
|
|
1136
1144
|
return Boolean(process.stdout.isTTY);
|
|
1137
1145
|
}
|
|
1146
|
+
let followKill;
|
|
1147
|
+
const FOLLOW_MAX_OUTPUT = 8 * 1024 * 1024;
|
|
1148
|
+
const FOLLOW_RECONNECT_MS = 10 * 60_000;
|
|
1149
|
+
async function followExec(c, sandboxId, execOpts) {
|
|
1150
|
+
let rec;
|
|
1151
|
+
try {
|
|
1152
|
+
rec = await c.sandboxes.execBackground(sandboxId, {
|
|
1153
|
+
argv: execOpts.argv,
|
|
1154
|
+
...(execOpts.secrets ? { secrets: execOpts.secrets } : {}),
|
|
1155
|
+
...(execOpts.timeoutMs === undefined ? {} : { timeoutMs: execOpts.timeoutMs }),
|
|
1156
|
+
maxOutputBytes: execOpts.maxOutputBytes ?? FOLLOW_MAX_OUTPUT,
|
|
1157
|
+
follow: true,
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
catch (err) {
|
|
1161
|
+
const e = toPromigenceError(err);
|
|
1162
|
+
if (e.code === "usage" && /follow/.test(e.message))
|
|
1163
|
+
return undefined;
|
|
1164
|
+
throw err;
|
|
1165
|
+
}
|
|
1166
|
+
const eid = rec.exec_id;
|
|
1167
|
+
followKill = () => Promise.race([
|
|
1168
|
+
c.sandboxes.execs.forget(sandboxId, eid).then(() => undefined, () => undefined),
|
|
1169
|
+
new Promise((r) => setTimeout(r, 2000)),
|
|
1170
|
+
]);
|
|
1171
|
+
const offset = { stdout: 0, stderr: 0 };
|
|
1172
|
+
let last = rec;
|
|
1173
|
+
let lostAt;
|
|
1174
|
+
const say = (s) => write(process.stderr, s);
|
|
1175
|
+
const pump = async (fd) => {
|
|
1176
|
+
for (let backoff = 500;;) {
|
|
1177
|
+
try {
|
|
1178
|
+
const r = await c.sandboxes.execs.read(sandboxId, eid, fd, { offset: offset[fd], waitMs: 25_000, untilOutput: true });
|
|
1179
|
+
if (lostAt !== undefined) {
|
|
1180
|
+
lostAt = undefined;
|
|
1181
|
+
await say("promigence: reconnected; the command's output continues from where it was\n");
|
|
1182
|
+
}
|
|
1183
|
+
backoff = 500;
|
|
1184
|
+
if (r.skipped > 0) {
|
|
1185
|
+
await say(`\npromigence: ${r.skipped} bytes of the command's ${fd} were not kept while this connection was down (the newest 8 MiB are)\n`);
|
|
1186
|
+
}
|
|
1187
|
+
if (r.data.length)
|
|
1188
|
+
await writeBytes(fd === "stdout" ? process.stdout : process.stderr, r.data);
|
|
1189
|
+
offset[fd] = r.offset + r.skipped + r.data.length;
|
|
1190
|
+
last = r.record;
|
|
1191
|
+
if (r.eof || (r.record.state !== "running" && r.data.length === 0))
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
const e = toPromigenceError(err);
|
|
1196
|
+
if (e.code !== "unreachable" && e.code !== "timeout")
|
|
1197
|
+
throw err;
|
|
1198
|
+
if (lostAt === undefined) {
|
|
1199
|
+
lostAt = Date.now();
|
|
1200
|
+
await say(`\npromigence: connection lost; the command keeps running in the sandbox. Reconnecting… ` +
|
|
1201
|
+
`(or later: ${sh `promigence exec output ${eid} --sandbox ${sandboxId} --follow`})\n`);
|
|
1202
|
+
}
|
|
1203
|
+
else if (Date.now() - lostAt > FOLLOW_RECONNECT_MS) {
|
|
1204
|
+
throw err;
|
|
1205
|
+
}
|
|
1206
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
1207
|
+
backoff = Math.min(backoff * 2, 5_000);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
try {
|
|
1212
|
+
await Promise.all([pump("stdout"), pump("stderr")]);
|
|
1213
|
+
}
|
|
1214
|
+
finally {
|
|
1215
|
+
followKill = undefined;
|
|
1216
|
+
}
|
|
1217
|
+
const fin = last.state === "running" || !last.exit ? await c.sandboxes.execs.get(sandboxId, eid, { waitMs: 5_000 }) : last;
|
|
1218
|
+
await c.sandboxes.execs.forget(sandboxId, eid).catch(() => undefined);
|
|
1219
|
+
const x = fin.exit;
|
|
1220
|
+
const signal = typeof x?.signal === "number" ? x.signal : undefined;
|
|
1221
|
+
return {
|
|
1222
|
+
exec_id: eid,
|
|
1223
|
+
exit_code: x?.exit_code ?? null,
|
|
1224
|
+
...(signal === undefined ? {} : { signal }),
|
|
1225
|
+
...(x?.oom_killed ? { reason: "out_of_memory" } : signal !== undefined ? { reason: "signal" } : {}),
|
|
1226
|
+
...(typeof x?.wall_ms === "number" ? { wall_ms: x.wall_ms } : {}),
|
|
1227
|
+
...(typeof x?.cpu_ms === "number" ? { cpu_ms: x.cpu_ms } : {}),
|
|
1228
|
+
truncated: false,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1138
1231
|
async function streamExec(ctx, c, sandboxId, execOpts) {
|
|
1139
1232
|
const ndjson = ctx.mode === "json";
|
|
1140
1233
|
const dec = { stdout: new TextDecoder(), stderr: new TextDecoder() };
|
|
@@ -1195,6 +1288,11 @@ async function emitChild(res, exit, notice) {
|
|
|
1195
1288
|
await write(process.stderr, notice);
|
|
1196
1289
|
return exitWith(exit);
|
|
1197
1290
|
}
|
|
1291
|
+
export function execTimingLine(exitCode, wallMs, totalMs, isTTY = Boolean(process.stderr.isTTY), env = process.env) {
|
|
1292
|
+
if (!isTTY || env["PROMIGENCE_QUIET"] === "1" || typeof wallMs !== "number" || !Number.isFinite(wallMs))
|
|
1293
|
+
return "";
|
|
1294
|
+
return `promigence: exit ${exitCode ?? "none"} · ran ${fmtMs(wallMs)} on our side · ${fmtMs(totalMs)} round trip\n`;
|
|
1295
|
+
}
|
|
1198
1296
|
export function endNotice(res, timeoutMs) {
|
|
1199
1297
|
const sig = typeof res["signal"] === "number" ? ` (signal ${res["signal"]})` : "";
|
|
1200
1298
|
switch (res.reason) {
|
|
@@ -1284,6 +1382,10 @@ const exec = async (ctx) => {
|
|
|
1284
1382
|
execOpts.maxOutputBytes = MAX_OUTPUT_BYTES_PIPED;
|
|
1285
1383
|
}
|
|
1286
1384
|
const interrupted = (sig) => {
|
|
1385
|
+
const ending = followKill?.() ?? Promise.resolve();
|
|
1386
|
+
void ending.finally(() => interruptedEnvelope(sig));
|
|
1387
|
+
};
|
|
1388
|
+
const interruptedEnvelope = (sig) => {
|
|
1287
1389
|
const err = new PromigenceError({
|
|
1288
1390
|
code: "aborted_by_client",
|
|
1289
1391
|
message: `interrupted by ${sig}; the command in the sandbox ends with this connection`,
|
|
@@ -1297,9 +1399,14 @@ const exec = async (ctx) => {
|
|
|
1297
1399
|
};
|
|
1298
1400
|
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"])
|
|
1299
1401
|
process.once(sig, () => interrupted(sig));
|
|
1300
|
-
const
|
|
1402
|
+
const execStarted = performance.now();
|
|
1403
|
+
const followed = streaming && ctx.mode === "text" && process.env["PROMIGENCE_EXEC_FOLLOW"] !== "0"
|
|
1404
|
+
? await followExec(c, sandboxId, execOpts)
|
|
1405
|
+
: undefined;
|
|
1406
|
+
const res = followed ?? (streaming
|
|
1301
1407
|
? await streamExec(ctx, c, sandboxId, execOpts)
|
|
1302
|
-
: asText(await c.sandboxes.exec(sandboxId, { ...execOpts, encoding: "base64" }));
|
|
1408
|
+
: asText(await c.sandboxes.exec(sandboxId, { ...execOpts, encoding: "base64" })));
|
|
1409
|
+
const execTotalMs = performance.now() - execStarted;
|
|
1303
1410
|
const outcome = execOutcome(res, sandboxId, execOpts.timeoutMs);
|
|
1304
1411
|
const passthrough = !bool(ctx, "--no-passthrough");
|
|
1305
1412
|
let exit = passthrough ? (outcome.exitCode ?? 4) : (outcome.exitCode === 0 ? 0 : 4);
|
|
@@ -1309,13 +1416,15 @@ const exec = async (ctx) => {
|
|
|
1309
1416
|
const notice = (res.truncated ? truncationNotice(streaming ? "first" : "last", execOpts.maxOutputBytes) : "")
|
|
1310
1417
|
+ endNotice(res, execOpts.timeoutMs);
|
|
1311
1418
|
const raw = streaming || typeof res.stdout_b64 === "string";
|
|
1312
|
-
const
|
|
1419
|
+
const timed = execTimingLine(outcome.exitCode, res.wall_ms, execTotalMs);
|
|
1420
|
+
const all = notice + timed;
|
|
1421
|
+
const gap = all && raw && process.stdout.isTTY && !endsWithNewline(res) ? "\n" : "";
|
|
1313
1422
|
if (streaming) {
|
|
1314
|
-
if (
|
|
1315
|
-
await write(process.stderr, gap +
|
|
1423
|
+
if (all)
|
|
1424
|
+
await write(process.stderr, gap + all);
|
|
1316
1425
|
return exitWith(exit);
|
|
1317
1426
|
}
|
|
1318
|
-
return emitChild(res, exit,
|
|
1427
|
+
return emitChild(res, exit, all ? gap + all : undefined);
|
|
1319
1428
|
}
|
|
1320
1429
|
const next = nexts(...(res.truncated
|
|
1321
1430
|
? [[`${sh `promigence exec --sandbox ${sandboxId} --max-output 32m --`} ${argv.map(shellArg).join(" ")}`, "re-run with a larger --max-output"]]
|
package/dist/handlers.js
CHANGED
|
@@ -12,6 +12,7 @@ import { detect, gitFacts, imageCreateCommand, renderConfig, stripRemoteCredenti
|
|
|
12
12
|
import { TIERS } from "./generated.js";
|
|
13
13
|
import { GOLDEN_PATH, SCHEMA, VERSION } from "./surface.js";
|
|
14
14
|
import { jsonLine, sh } from "./term.js";
|
|
15
|
+
import { fmtMs, median, where } from "./timing.js";
|
|
15
16
|
const SECTION_NAMES = new Set(["core", "commands", "errors", "cost"]);
|
|
16
17
|
const agentGuide = async (ctx) => {
|
|
17
18
|
const format = str(ctx, "--format") ?? (ctx.explicitMode === "json" ? "json" : "md");
|
|
@@ -323,6 +324,46 @@ async function apiChecks(ctx, checks) {
|
|
|
323
324
|
checks.push(fail("auth_accepted", `${e.code}: ${e.message}`, e.fix, e.cliExit ?? 7));
|
|
324
325
|
}
|
|
325
326
|
}
|
|
327
|
+
const ping = async (ctx) => {
|
|
328
|
+
const n = Number(process.env["PROMIGENCE_PING_COUNT"] ?? "5");
|
|
329
|
+
if (!Number.isInteger(n) || n < 1 || n > 50)
|
|
330
|
+
throw usageError("PROMIGENCE_PING_COUNT takes 1 to 50", "Unset it for the default of five.");
|
|
331
|
+
const c = client(ctx);
|
|
332
|
+
const rounds = [];
|
|
333
|
+
let location;
|
|
334
|
+
for (let i = 0; i < n; i++) {
|
|
335
|
+
const h = await c.health({ timeoutMs: 10_000 });
|
|
336
|
+
if (typeof h.location === "string" && h.location)
|
|
337
|
+
location = h.location;
|
|
338
|
+
const tm = c.lastTiming;
|
|
339
|
+
if (tm)
|
|
340
|
+
rounds.push({ totalMs: tm.totalMs, ourMs: tm.ourMs });
|
|
341
|
+
}
|
|
342
|
+
const totals = rounds.map((r) => r.totalMs);
|
|
343
|
+
const ours = rounds.map((r) => r.ourMs).filter((x) => x !== null);
|
|
344
|
+
const nets = rounds.filter((r) => r.ourMs !== null).map((r) => Math.max(0, r.totalMs - r.ourMs));
|
|
345
|
+
const rt = median(totals);
|
|
346
|
+
const our = median(ours);
|
|
347
|
+
const net = median(nets);
|
|
348
|
+
const r1 = (x) => (x === undefined ? null : Math.round(x * 10) / 10);
|
|
349
|
+
const fields = {
|
|
350
|
+
location: location ?? null,
|
|
351
|
+
round_trips: n,
|
|
352
|
+
round_trip_ms: { median: r1(rt), min: r1(Math.min(...totals)), max: r1(Math.max(...totals)) },
|
|
353
|
+
our_side_ms: r1(our),
|
|
354
|
+
network_ms: r1(net),
|
|
355
|
+
};
|
|
356
|
+
const lines = [
|
|
357
|
+
`promigence ping: ${where(location)}`,
|
|
358
|
+
` round trip ${rt === undefined ? "—" : fmtMs(rt)} (median of ${n}; ${fmtMs(Math.min(...totals))} to ${fmtMs(Math.max(...totals))})`,
|
|
359
|
+
` our side ${our === undefined ? "— (not said by this service)" : fmtMs(our)}`,
|
|
360
|
+
` your network ${net === undefined ? "—" : fmtMs(net)}`,
|
|
361
|
+
...(net !== undefined && net >= 150
|
|
362
|
+
? [" Times marked 'on our side' are the platform's own; the rest is the distance to our servers."]
|
|
363
|
+
: []),
|
|
364
|
+
];
|
|
365
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, fields, nexts(["promigence doctor", "check the client, your key and the API"])), lines, 0, ctx.pointer);
|
|
366
|
+
};
|
|
326
367
|
const doctor = async (ctx) => {
|
|
327
368
|
const checks = await clientChecks(ctx);
|
|
328
369
|
await apiChecks(ctx, checks);
|
|
@@ -466,5 +507,6 @@ export const EXTRA_HANDLERS = {
|
|
|
466
507
|
init,
|
|
467
508
|
examples,
|
|
468
509
|
doctor,
|
|
510
|
+
ping,
|
|
469
511
|
"exec output": execOutput,
|
|
470
512
|
};
|
package/dist/io.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function markGuideSeen(): void;
|
|
|
6
6
|
export declare function isGoneReader(code: string | undefined): boolean;
|
|
7
7
|
export declare function writeBytes(stream: NodeJS.WriteStream, data: Uint8Array): Promise<void>;
|
|
8
8
|
export declare function write(stream: NodeJS.WriteStream, text: string): Promise<void>;
|
|
9
|
+
export declare function setBeforeExit(fn: (() => Promise<unknown>) | undefined): void;
|
|
9
10
|
export declare function exitWith(code: number): Promise<never>;
|
|
10
11
|
export declare function recordNotice(n: AccountNotice): void;
|
|
11
12
|
export declare function noticeLine(n: AccountNotice): string;
|
package/dist/io.js
CHANGED
|
@@ -53,7 +53,15 @@ function flush(stream) {
|
|
|
53
53
|
stream.write("", () => resolve());
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
let beforeExit;
|
|
57
|
+
export function setBeforeExit(fn) {
|
|
58
|
+
beforeExit = fn;
|
|
59
|
+
}
|
|
56
60
|
export async function exitWith(code) {
|
|
61
|
+
const hook = beforeExit;
|
|
62
|
+
beforeExit = undefined;
|
|
63
|
+
if (hook)
|
|
64
|
+
await hook().catch(() => undefined);
|
|
57
65
|
await flush(process.stdout);
|
|
58
66
|
await flush(process.stderr);
|
|
59
67
|
process.exit(code);
|
|
@@ -238,7 +246,7 @@ export function forkLine(fork) {
|
|
|
238
246
|
return "fork: (no timings yet)";
|
|
239
247
|
const p = (name, v) => v === undefined ? `${name} —` : `${name} p50 ${ms(v.p50)} p95 ${ms(v.p95)}${v.p99 === undefined ? "" : ` p99 ${ms(v.p99)}`}`;
|
|
240
248
|
return [
|
|
241
|
-
`fork n=${fork.n}`,
|
|
249
|
+
`fork n=${fork.n} (on our side)`,
|
|
242
250
|
p("queued", fork.queued_ms),
|
|
243
251
|
p("ready", fork.ready_ms),
|
|
244
252
|
p("e2e", fork.e2e_ms),
|
package/dist/login.js
CHANGED
|
@@ -809,6 +809,7 @@ async function signInAndStore(ctx, isSignup) {
|
|
|
809
809
|
}
|
|
810
810
|
const consoleHome = viaConsole ? `${new URL(viaConsole).origin}/home` : undefined;
|
|
811
811
|
const credit = await offerAfterLogin(ctx, anon.apiUrl, session.api_key, consoleHome);
|
|
812
|
+
const location = await anon.health({ timeoutMs: 3000 }).then((h) => (typeof h.location === "string" && h.location ? h.location : undefined)).catch(() => undefined);
|
|
812
813
|
const next = nexts(["promigence me", "your org, limits and spend"], ["promigence examples", "what you can fork with no build (the built-in base)"]);
|
|
813
814
|
const fields = {
|
|
814
815
|
credentials: path, store: stored.store, mode: stored.store === "file" ? "0600" : null, created: session.created,
|
|
@@ -820,6 +821,8 @@ async function signInAndStore(ctx, isSignup) {
|
|
|
820
821
|
if (session.referral)
|
|
821
822
|
fields["referral"] = session.referral;
|
|
822
823
|
fields["credit_offer"] = credit.field;
|
|
824
|
+
if (location)
|
|
825
|
+
fields["location"] = location;
|
|
823
826
|
if (warnings.length)
|
|
824
827
|
fields["warnings"] = warnings;
|
|
825
828
|
const rf = session.referral;
|
|
@@ -831,6 +834,7 @@ async function signInAndStore(ctx, isSignup) {
|
|
|
831
834
|
return emit(ctx.mode, okEnvelope(ctx.cmd.name, fields, next), [
|
|
832
835
|
`promigence login: ${session.created ? "account created" : "signed in"} as ${session.user.email} (${session.user.provider})`,
|
|
833
836
|
` org: ${session.org.name} (${session.org.org_id})`,
|
|
837
|
+
...(location ? [` Your sandboxes run on our servers on the ${location}.`] : []),
|
|
834
838
|
` key: ${session.key?.prefix ?? ""}… "${session.key?.name ?? keyName}" stored in ${storedWhere(stored)}`,
|
|
835
839
|
...(session.key?.expires_at ? [` key expires: ${session.key.expires_at.slice(0, 10)} (sign in again before then; \`promigence keys list\` shows it)`] : []),
|
|
836
840
|
...(stored.note ? [` note: ${stored.note}`] : []),
|
package/dist/routes.js
CHANGED
|
@@ -43,6 +43,7 @@ export const ROUTE_COMMANDS = {
|
|
|
43
43
|
"GET /pricing": "pricing",
|
|
44
44
|
"GET /billing": "billing usage",
|
|
45
45
|
"GET /me/ledger": "billing usage",
|
|
46
|
+
"GET /me/outcomes": "me",
|
|
46
47
|
"POST /billing/checkout": "billing card",
|
|
47
48
|
"POST /billing/portal": "billing portal",
|
|
48
49
|
"GET /referrals": "referrals",
|
package/dist/sdk/client.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ export interface Capacity {
|
|
|
83
83
|
queued_message?: string;
|
|
84
84
|
}
|
|
85
85
|
export type Outcome = M.Outcome;
|
|
86
|
+
/** `GET /me/outcomes`: what the organization got this month, measured. */
|
|
87
|
+
export type Outcomes = M.Outcomes;
|
|
86
88
|
export interface Percentiles {
|
|
87
89
|
p50: number;
|
|
88
90
|
p95: number;
|
|
@@ -631,6 +633,8 @@ export interface HealthResponse {
|
|
|
631
633
|
build_sha?: string;
|
|
632
634
|
/** Whether the service can start new sandboxes right now. */
|
|
633
635
|
serving?: boolean;
|
|
636
|
+
/** Where sandboxes run, in plain words (for example "US East Coast"); absent when not said. */
|
|
637
|
+
location?: string;
|
|
634
638
|
[extra: string]: unknown;
|
|
635
639
|
}
|
|
636
640
|
/** One service's row in `GET /status` (`api`, `sandboxes`): `up`, `degraded` or `down`. */
|
|
@@ -1213,6 +1217,10 @@ export interface BackgroundExecRecord {
|
|
|
1213
1217
|
stdout_total?: number;
|
|
1214
1218
|
stderr_total?: number;
|
|
1215
1219
|
truncated?: boolean;
|
|
1220
|
+
/** Started with `follow`: the newest output is kept, and the handle goes 10 minutes after it ends. */
|
|
1221
|
+
follow?: boolean;
|
|
1222
|
+
/** A range read of a `follow` command: bytes dropped between the asked offset and the first held. */
|
|
1223
|
+
skipped?: number;
|
|
1216
1224
|
exit?: {
|
|
1217
1225
|
exit_code: number | null;
|
|
1218
1226
|
signal: number | null;
|
|
@@ -1421,6 +1429,11 @@ export interface PromigenceClientOpts {
|
|
|
1421
1429
|
* on `client.notice`.
|
|
1422
1430
|
*/
|
|
1423
1431
|
onNotice?: (notice: AccountNotice) => void;
|
|
1432
|
+
/**
|
|
1433
|
+
* Called with every answer's timing ([`Timing`]): the round trip this process saw and how long
|
|
1434
|
+
* the answer took on Promigence's side. A CLI uses it to say how far away the caller is.
|
|
1435
|
+
*/
|
|
1436
|
+
onTiming?: (timing: Timing) => void;
|
|
1424
1437
|
}
|
|
1425
1438
|
/** `GET /sandboxes/:id/network`. */
|
|
1426
1439
|
export interface SandboxNetwork {
|
|
@@ -1857,6 +1870,17 @@ interface RequestOpts {
|
|
|
1857
1870
|
/** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
|
|
1858
1871
|
headers?: Record<string, string>;
|
|
1859
1872
|
}
|
|
1873
|
+
/** One answer's timing ([`PromigenceClient.lastTiming`]). */
|
|
1874
|
+
export interface Timing {
|
|
1875
|
+
method: string;
|
|
1876
|
+
path: string;
|
|
1877
|
+
/** The round trip as the client saw it, in ms, until the answer's headers arrived. */
|
|
1878
|
+
totalMs: number;
|
|
1879
|
+
/** How long the answer took on Promigence's side, in ms; `null` when the service did not say. */
|
|
1880
|
+
ourMs: number | null;
|
|
1881
|
+
}
|
|
1882
|
+
/** The `app` span of a `Server-Timing` header (`app;dur=12.35`), in ms; `null` without one. */
|
|
1883
|
+
export declare function serverAppMs(header: string | null | undefined): number | null;
|
|
1860
1884
|
export declare class PromigenceClient {
|
|
1861
1885
|
#private;
|
|
1862
1886
|
readonly apiUrl: string;
|
|
@@ -1879,9 +1903,16 @@ export declare class PromigenceClient {
|
|
|
1879
1903
|
requests: number;
|
|
1880
1904
|
retries: number;
|
|
1881
1905
|
};
|
|
1906
|
+
/**
|
|
1907
|
+
* The most recent answer's timing: `totalMs` is the whole round trip as this process saw it,
|
|
1908
|
+
* until the answer's headers arrived; `ourMs` is how long the answer took on Promigence's side
|
|
1909
|
+
* (`null` when the service did not say). `totalMs − ourMs` is the network between you and us.
|
|
1910
|
+
*/
|
|
1911
|
+
lastTiming: Timing | null;
|
|
1882
1912
|
/** The latest account notice any response carried (`Promigence-Notice`), if one did. */
|
|
1883
1913
|
notice: AccountNotice | undefined;
|
|
1884
1914
|
private readonly onNotice;
|
|
1915
|
+
private readonly onTiming;
|
|
1885
1916
|
private readonly noticesSeen;
|
|
1886
1917
|
constructor(opts?: PromigenceClientOpts);
|
|
1887
1918
|
/** Read `Promigence-Notice` off a response (its raw value); tell the caller once per distinct notice. */
|
|
@@ -1975,6 +2006,13 @@ export declare class PromigenceClient {
|
|
|
1975
2006
|
me(opts?: {
|
|
1976
2007
|
signal?: AbortSignal;
|
|
1977
2008
|
}): Promise<MeResponse>;
|
|
2009
|
+
/**
|
|
2010
|
+
* `GET /me/outcomes?period=month` — what this organization (with a project's key: that project) got
|
|
2011
|
+
* this month, measured from its own runs and sandboxes. A figure with nothing behind it is `null`.
|
|
2012
|
+
*/
|
|
2013
|
+
outcomes(opts?: {
|
|
2014
|
+
signal?: AbortSignal;
|
|
2015
|
+
}): Promise<Outcomes>;
|
|
1978
2016
|
readonly billing: {
|
|
1979
2017
|
/** `GET /billing` — plan, card, period, usage, trial, upcoming invoice. */
|
|
1980
2018
|
usage: () => Promise<BillingUsage>;
|
|
@@ -2245,6 +2283,7 @@ export declare class PromigenceClient {
|
|
|
2245
2283
|
*/
|
|
2246
2284
|
execBackground: (sandboxId: string, opts: Omit<ExecOpts, "terminal" | "killOnExit" | "signal"> & {
|
|
2247
2285
|
stdin?: boolean;
|
|
2286
|
+
follow?: boolean;
|
|
2248
2287
|
}) => Promise<BackgroundExecRecord>;
|
|
2249
2288
|
/** Background execs of one sandbox (`GET|POST|DELETE /sandboxes/:id/execs/:exec_id…`). */
|
|
2250
2289
|
execs: {
|
|
@@ -2262,10 +2301,14 @@ export declare class PromigenceClient {
|
|
|
2262
2301
|
read: (sandboxId: string, execId: string, stream: "stdout" | "stderr", opts?: {
|
|
2263
2302
|
offset?: number;
|
|
2264
2303
|
len?: number;
|
|
2304
|
+
waitMs?: number;
|
|
2305
|
+
untilOutput?: boolean;
|
|
2306
|
+
signal?: AbortSignal;
|
|
2265
2307
|
}) => Promise<{
|
|
2266
2308
|
data: Uint8Array;
|
|
2267
2309
|
offset: number;
|
|
2268
2310
|
eof: boolean;
|
|
2311
|
+
skipped: number;
|
|
2269
2312
|
record: BackgroundExecRecord;
|
|
2270
2313
|
}>;
|
|
2271
2314
|
/**
|
package/dist/sdk/client.js
CHANGED
|
@@ -904,6 +904,16 @@ function waitQuery(waitMs) {
|
|
|
904
904
|
: "";
|
|
905
905
|
}
|
|
906
906
|
const notReleased = (what) => Promise.reject(new NotReleasedError(what));
|
|
907
|
+
export function serverAppMs(header) {
|
|
908
|
+
if (!header)
|
|
909
|
+
return null;
|
|
910
|
+
for (const part of header.split(",")) {
|
|
911
|
+
const m = /^\s*app\s*;(?:.*;)?\s*dur=([0-9]+(?:\.[0-9]+)?)/.exec(part);
|
|
912
|
+
if (m)
|
|
913
|
+
return Number(m[1]);
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
907
917
|
function headerOf(res, name) {
|
|
908
918
|
return res instanceof H2Response ? res.header(name) : res.headers.get(name);
|
|
909
919
|
}
|
|
@@ -920,8 +930,10 @@ export class PromigenceClient {
|
|
|
920
930
|
cliErrors;
|
|
921
931
|
caller;
|
|
922
932
|
stats = { requests: 0, retries: 0 };
|
|
933
|
+
lastTiming = null;
|
|
923
934
|
notice;
|
|
924
935
|
onNotice;
|
|
936
|
+
onTiming;
|
|
925
937
|
noticesSeen = new Set();
|
|
926
938
|
constructor(opts = {}) {
|
|
927
939
|
traceEvent("client:ctor");
|
|
@@ -940,6 +952,7 @@ export class PromigenceClient {
|
|
|
940
952
|
this.caller = resolveCaller(opts.caller);
|
|
941
953
|
this.userAgent = userAgentWithCaller(opts.userAgent ?? `promigence/${VERSION} node/${process.versions.node}`, this.caller);
|
|
942
954
|
this.onNotice = opts.onNotice;
|
|
955
|
+
this.onTiming = opts.onTiming;
|
|
943
956
|
this.cliErrors = opts.cliErrors === true;
|
|
944
957
|
traceEvent("client:ready");
|
|
945
958
|
}
|
|
@@ -1045,6 +1058,7 @@ export class PromigenceClient {
|
|
|
1045
1058
|
for (let attempt = 0;; attempt++) {
|
|
1046
1059
|
let res;
|
|
1047
1060
|
this.stats.requests++;
|
|
1061
|
+
const sentAt = performance.now();
|
|
1048
1062
|
try {
|
|
1049
1063
|
if (this.h2) {
|
|
1050
1064
|
const init = {
|
|
@@ -1074,6 +1088,13 @@ export class PromigenceClient {
|
|
|
1074
1088
|
res = await this.fetchImpl(url, init);
|
|
1075
1089
|
}
|
|
1076
1090
|
mark(tr, "res");
|
|
1091
|
+
this.lastTiming = {
|
|
1092
|
+
method: opts.method,
|
|
1093
|
+
path: opts.path,
|
|
1094
|
+
totalMs: performance.now() - sentAt,
|
|
1095
|
+
ourMs: serverAppMs(headerOf(res, "server-timing")),
|
|
1096
|
+
};
|
|
1097
|
+
this.onTiming?.(this.lastTiming);
|
|
1077
1098
|
}
|
|
1078
1099
|
catch (err) {
|
|
1079
1100
|
const e = err;
|
|
@@ -1391,6 +1412,9 @@ export class PromigenceClient {
|
|
|
1391
1412
|
me(opts = {}) {
|
|
1392
1413
|
return this.request({ method: "GET", path: "/me", ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
1393
1414
|
}
|
|
1415
|
+
outcomes(opts = {}) {
|
|
1416
|
+
return this.request({ method: "GET", path: "/me/outcomes?period=month", ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
1417
|
+
}
|
|
1394
1418
|
billing = {
|
|
1395
1419
|
usage: () => this.request({ method: "GET", path: "/billing" }),
|
|
1396
1420
|
ledger: async (limit = 100) => (await this.billing.ledgerPage({ limit })).items,
|
|
@@ -1966,6 +1990,8 @@ export class PromigenceClient {
|
|
|
1966
1990
|
body["secrets"] = opts.secrets;
|
|
1967
1991
|
if (opts.stdin)
|
|
1968
1992
|
body["stdin"] = true;
|
|
1993
|
+
if (opts.follow)
|
|
1994
|
+
body["follow"] = true;
|
|
1969
1995
|
const timeoutMs = commandTimeoutMs(opts.timeoutMs);
|
|
1970
1996
|
if (timeoutMs !== undefined)
|
|
1971
1997
|
body["timeout_ms"] = timeoutMs;
|
|
@@ -1998,11 +2024,18 @@ export class PromigenceClient {
|
|
|
1998
2024
|
const q = [`stream=${stream}`, `offset=${Math.max(0, Math.floor(opts.offset ?? 0))}`];
|
|
1999
2025
|
if (opts.len !== undefined)
|
|
2000
2026
|
q.push(`len=${Math.max(0, Math.floor(opts.len))}`);
|
|
2027
|
+
const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
|
|
2028
|
+
if (wait)
|
|
2029
|
+
q.push(`wait_ms=${wait}`);
|
|
2030
|
+
if (wait && opts.untilOutput)
|
|
2031
|
+
q.push("until=output");
|
|
2001
2032
|
const r = await this.request({
|
|
2002
2033
|
method: "GET",
|
|
2003
2034
|
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}?${q.join("&")}`,
|
|
2035
|
+
...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
|
|
2036
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
2004
2037
|
});
|
|
2005
|
-
return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), record: r };
|
|
2038
|
+
return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), skipped: r.skipped ?? 0, record: r };
|
|
2006
2039
|
},
|
|
2007
2040
|
stdin: (sandboxId, execId, opts = {}) => {
|
|
2008
2041
|
if (opts.data === undefined && !opts.eof) {
|
package/dist/sdk/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Promigence SDK — constants, error hierarchy and shared types. */
|
|
2
|
-
export declare const VERSION = "0.1.0-beta.
|
|
2
|
+
export declare const VERSION = "0.1.0-beta.3";
|
|
3
3
|
export declare const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
/** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
|
|
5
5
|
export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
package/dist/sdk/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
2
|
+
export const VERSION = "0.1.0-beta.3";
|
|
3
3
|
export const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
5
5
|
export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
|
package/dist/sdk/models.gen.d.ts
CHANGED
|
@@ -146,6 +146,11 @@ export interface BackgroundExec {
|
|
|
146
146
|
* How the command ended; only once `state` is `exited`.
|
|
147
147
|
*/
|
|
148
148
|
exit?: BackgroundExecExit | null;
|
|
149
|
+
/**
|
|
150
|
+
* Started with `follow`: the newest output is kept, and the handle is freed 10 minutes
|
|
151
|
+
* after the command ends.
|
|
152
|
+
*/
|
|
153
|
+
follow?: boolean | null;
|
|
149
154
|
/**
|
|
150
155
|
* Present when the sandbox pauses itself when idle: after this command ends, the idle rule
|
|
151
156
|
* waits up to this many seconds for its result to be read before it pauses the sandbox (a
|
|
@@ -166,6 +171,11 @@ export interface BackgroundExec {
|
|
|
166
171
|
len?: number | null;
|
|
167
172
|
offset?: number | null;
|
|
168
173
|
sandbox_id: string;
|
|
174
|
+
/**
|
|
175
|
+
* A `follow` command only: bytes between the `offset` asked for and the first byte still
|
|
176
|
+
* held, dropped to keep the newest output; `data` then starts at `offset + skipped`.
|
|
177
|
+
*/
|
|
178
|
+
skipped?: number | null;
|
|
169
179
|
/**
|
|
170
180
|
* Unix nanoseconds.
|
|
171
181
|
*/
|
|
@@ -1018,6 +1028,14 @@ export interface ExecCreate {
|
|
|
1018
1028
|
* Extra environment variables.
|
|
1019
1029
|
*/
|
|
1020
1030
|
env?: Record<string, string>;
|
|
1031
|
+
/**
|
|
1032
|
+
* With `background`: the command is read as it runs, by a caller that may lose its
|
|
1033
|
+
* connection and come back. The newest `max_output_bytes` of each stream are kept (8 MiB by
|
|
1034
|
+
* default; older bytes are dropped, and a read that asks for them says how many in
|
|
1035
|
+
* `skipped`), and the handle is freed 10 minutes after the command ends, read or not. The
|
|
1036
|
+
* command never stops because a caller went away.
|
|
1037
|
+
*/
|
|
1038
|
+
follow?: boolean;
|
|
1021
1039
|
/**
|
|
1022
1040
|
* End the sandbox when the command exits (default: the value of `terminal`).
|
|
1023
1041
|
*/
|
|
@@ -1904,6 +1922,11 @@ export interface Health {
|
|
|
1904
1922
|
* The commit this build came from; empty for a local build.
|
|
1905
1923
|
*/
|
|
1906
1924
|
build_sha: string;
|
|
1925
|
+
/**
|
|
1926
|
+
* Where sandboxes run, in plain words (for example "US East Coast"). Absent when the service
|
|
1927
|
+
* does not say.
|
|
1928
|
+
*/
|
|
1929
|
+
location?: string | null;
|
|
1907
1930
|
ok: boolean;
|
|
1908
1931
|
/**
|
|
1909
1932
|
* Whether new sandboxes can start right now. Answered to a request made with a key; absent
|
|
@@ -2325,6 +2348,16 @@ export interface Me {
|
|
|
2325
2348
|
*/
|
|
2326
2349
|
key?: KeyRef | null;
|
|
2327
2350
|
limits: MeLimits;
|
|
2351
|
+
/**
|
|
2352
|
+
* Where sandboxes run, in plain words (for example "US East Coast"), as `GET /v1/health` says
|
|
2353
|
+
* it. Absent when the service does not say.
|
|
2354
|
+
*/
|
|
2355
|
+
location?: string | null;
|
|
2356
|
+
/**
|
|
2357
|
+
* The same place as a short code for an icon: `us-east`, `us-west`, `us-midwest`, `eu-west` or
|
|
2358
|
+
* `india`. Only beside `location`; a code you do not know is fine to ignore.
|
|
2359
|
+
*/
|
|
2360
|
+
location_code?: string | null;
|
|
2328
2361
|
/**
|
|
2329
2362
|
* A notice about a coming change to the trial rules; `null` when there is none.
|
|
2330
2363
|
*/
|
|
@@ -2627,6 +2660,67 @@ export interface OtherUsage {
|
|
|
2627
2660
|
entries: number;
|
|
2628
2661
|
}
|
|
2629
2662
|
export type Outcome = "completed" | "task_failed" | "env_failed" | "snapshot_failed" | "aborted_by_client" | "expired" | "cap_killed" | "not_started" | "superseded" | "grader_failed" | "preempted";
|
|
2663
|
+
/**
|
|
2664
|
+
* `GET /v1/me/outcomes?period=month` — what the organization (with a project's key: that project)
|
|
2665
|
+
* got this period, measured from its own runs and sandboxes. A figure with nothing behind it is
|
|
2666
|
+
* `null`; counts are `0`.
|
|
2667
|
+
*/
|
|
2668
|
+
export interface Outcomes {
|
|
2669
|
+
/**
|
|
2670
|
+
* Finished runs of two or more sandboxes this period.
|
|
2671
|
+
*/
|
|
2672
|
+
batch_runs: number;
|
|
2673
|
+
/**
|
|
2674
|
+
* For those runs: seconds not spent waiting, the sum of their sandboxes' own times minus
|
|
2675
|
+
* the runs' wall time; `null` without such a run.
|
|
2676
|
+
*/
|
|
2677
|
+
batch_saved_s?: number | null;
|
|
2678
|
+
/**
|
|
2679
|
+
* For those runs: the sum of their sandboxes' own times divided by the runs' wall time
|
|
2680
|
+
* (how many times faster than one after another); `null` without such a run.
|
|
2681
|
+
*/
|
|
2682
|
+
batch_speedup?: number | null;
|
|
2683
|
+
/**
|
|
2684
|
+
* Sandboxes that ended on a failure on our side and were not billed for it.
|
|
2685
|
+
*/
|
|
2686
|
+
failures_ours: number;
|
|
2687
|
+
/**
|
|
2688
|
+
* What was given back for those failures, in USD (`0` when they ended before any charge).
|
|
2689
|
+
*/
|
|
2690
|
+
failures_ours_usd: number;
|
|
2691
|
+
/**
|
|
2692
|
+
* Failed sandboxes kept to reopen that can still be reopened.
|
|
2693
|
+
*/
|
|
2694
|
+
kept_failures: number;
|
|
2695
|
+
/**
|
|
2696
|
+
* Hours this period's sandboxes spent paused, which is not billed; `null` if none paused.
|
|
2697
|
+
*/
|
|
2698
|
+
paused_hours?: number | null;
|
|
2699
|
+
/**
|
|
2700
|
+
* What those paused hours would have cost running, in USD; `null` if none paused.
|
|
2701
|
+
*/
|
|
2702
|
+
paused_usd?: number | null;
|
|
2703
|
+
/**
|
|
2704
|
+
* `month`: the calendar month so far, UTC.
|
|
2705
|
+
*/
|
|
2706
|
+
period: string;
|
|
2707
|
+
/**
|
|
2708
|
+
* The median milliseconds until a sandbox was ready to take work; `null` with no sandbox.
|
|
2709
|
+
*/
|
|
2710
|
+
ready_ms_p50?: number | null;
|
|
2711
|
+
/**
|
|
2712
|
+
* Runs started this period.
|
|
2713
|
+
*/
|
|
2714
|
+
runs: number;
|
|
2715
|
+
/**
|
|
2716
|
+
* Sandboxes started this period.
|
|
2717
|
+
*/
|
|
2718
|
+
sandboxes: number;
|
|
2719
|
+
/**
|
|
2720
|
+
* When the period began (RFC 3339, UTC).
|
|
2721
|
+
*/
|
|
2722
|
+
since: string;
|
|
2723
|
+
}
|
|
2630
2724
|
/**
|
|
2631
2725
|
* What a parked sandbox occupies on disk.
|
|
2632
2726
|
*/
|
|
@@ -4256,8 +4350,9 @@ export interface SandboxView {
|
|
|
4256
4350
|
*/
|
|
4257
4351
|
billable_seconds?: number | null;
|
|
4258
4352
|
/**
|
|
4259
|
-
*
|
|
4260
|
-
* Present on `GET /v1/sandboxes/{id}`
|
|
4353
|
+
* What it has cost in USD at its run's rates: once it has ended, the run report's `cost`; while
|
|
4354
|
+
* it is live, the seconds billed so far (running time only). Present on `GET /v1/sandboxes/{id}`
|
|
4355
|
+
* once it has started; absent from list rows.
|
|
4261
4356
|
*/
|
|
4262
4357
|
cost_usd?: number | null;
|
|
4263
4358
|
/**
|
package/dist/surface.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const DESCRIPTION = "Promigence \u2014 verified, reproducible, fo
|
|
|
6
6
|
export declare const GOLDEN_PATH = "promigence run --snapshot promigence/base --count 2 --cap 1 --json -- python3 -c 'print(6*7)'";
|
|
7
7
|
export declare const WAIT_TIMEOUT_DEFAULT = "100s";
|
|
8
8
|
export declare const REQUEST_TIMEOUT_DEFAULT = "30s";
|
|
9
|
-
export declare const LIVE_COMMANDS: readonly ["login", "signup", "logout", "auth login", "keys list", "keys create", "keys revoke", "org list", "org create", "org switch", "org members", "org invite", "org invites", "org uninvite", "org resend", "org role", "org remove", "org leave", "org join", "org audit", "org rename", "org delete", "project list", "project create", "project rename", "project delete", "project cap", "project move", "me", "billing card", "doctor", "init", "examples", "templates", "templates show", "agent-guide", "mcp", "snapshot create", "snapshot status", "snapshot list", "snapshot inspect", "snapshot verify", "snapshot export", "fork", "exec", "exec output", "exec ps", "exec kill", "files put", "files get", "files ls", "run", "run quote", "run report", "run status", "run cancel", "run kill", "run replay", "run open", "run captures", "sandbox kill", "sandbox shell", "sandbox ssh", "sandbox network", "port expose", "port list", "port revoke", "secrets set", "bench burst", "status", "billing usage", "billing portal", "snapshot delete", "snapshot builds", "snapshot alias", "run list", "sandbox list", "sandbox info", "sandbox pause", "sandbox resume", "sandbox extend", "sandbox probe", "files rm", "secrets list", "secrets delete", "image resolve", "sandbox snapshot", "files search", "files mkdir", "files mv", "files cp", "files chmod", "exec stdin", "sandbox autopause", "agent start", "referrals", "referrals apply", "credits", "credits claim", "runners github", "runners gitlab", "pricing", "github connect", "github status", "github disconnect", "github untrack", "repos", "gitlab connect", "gitlab status", "gitlab disconnect", "gitlab untrack"];
|
|
9
|
+
export declare const LIVE_COMMANDS: readonly ["login", "signup", "logout", "auth login", "keys list", "keys create", "keys revoke", "org list", "org create", "org switch", "org members", "org invite", "org invites", "org uninvite", "org resend", "org role", "org remove", "org leave", "org join", "org audit", "org rename", "org delete", "project list", "project create", "project rename", "project delete", "project cap", "project move", "me", "billing card", "doctor", "ping", "init", "examples", "templates", "templates show", "agent-guide", "mcp", "snapshot create", "snapshot status", "snapshot list", "snapshot inspect", "snapshot verify", "snapshot export", "fork", "exec", "exec output", "exec ps", "exec kill", "files put", "files get", "files ls", "run", "run quote", "run report", "run status", "run cancel", "run kill", "run replay", "run open", "run captures", "sandbox kill", "sandbox shell", "sandbox ssh", "sandbox network", "port expose", "port list", "port revoke", "secrets set", "bench burst", "status", "billing usage", "billing portal", "snapshot delete", "snapshot builds", "snapshot alias", "run list", "sandbox list", "sandbox info", "sandbox pause", "sandbox resume", "sandbox extend", "sandbox probe", "files rm", "secrets list", "secrets delete", "image resolve", "sandbox snapshot", "files search", "files mkdir", "files mv", "files cp", "files chmod", "exec stdin", "sandbox autopause", "agent start", "referrals", "referrals apply", "credits", "credits claim", "runners github", "runners gitlab", "pricing", "github connect", "github status", "github disconnect", "github untrack", "repos", "gitlab connect", "gitlab status", "gitlab disconnect", "gitlab untrack"];
|
|
10
10
|
export declare const GUIDE_COMMON_ERRORS: readonly ["auth", "not_found", "not_verified", "cap_exceeded", "timeout", "nonzero_exit"];
|
|
11
11
|
export type Group = "Setup" | "Snapshots" | "Runs" | "Sandboxes" | "Ops";
|
|
12
12
|
export type CommandStatus = "planned" | "released";
|
package/dist/surface.js
CHANGED
|
@@ -10,7 +10,7 @@ export const LIVE_COMMANDS = [
|
|
|
10
10
|
"login", "signup", "logout", "auth login", "keys list", "keys create", "keys revoke",
|
|
11
11
|
"org list", "org create", "org switch", "org members", "org invite", "org invites", "org uninvite", "org resend", "org role", "org remove", "org leave", "org join", "org audit", "org rename", "org delete",
|
|
12
12
|
"project list", "project create", "project rename", "project delete", "project cap", "project move",
|
|
13
|
-
"me", "billing card", "doctor", "init", "examples", "templates", "templates show", "agent-guide", "mcp",
|
|
13
|
+
"me", "billing card", "doctor", "ping", "init", "examples", "templates", "templates show", "agent-guide", "mcp",
|
|
14
14
|
"snapshot create", "snapshot status", "snapshot list", "snapshot inspect", "snapshot verify",
|
|
15
15
|
"snapshot export",
|
|
16
16
|
"fork", "exec", "exec output", "exec ps", "exec kill", "files put", "files get", "files ls",
|
|
@@ -322,6 +322,13 @@ export const COMMANDS = [
|
|
|
322
322
|
flags: [json],
|
|
323
323
|
next: ["promigence examples"],
|
|
324
324
|
},
|
|
325
|
+
{
|
|
326
|
+
name: "ping", group: "Setup", status: "released",
|
|
327
|
+
helpRow: { key: "doctor", summary: "" },
|
|
328
|
+
summary: "your distance to our servers",
|
|
329
|
+
flags: [json],
|
|
330
|
+
next: ["promigence doctor"],
|
|
331
|
+
},
|
|
325
332
|
{
|
|
326
333
|
name: "init", group: "Setup", status: "released",
|
|
327
334
|
summary: "write promigence.toml",
|
package/dist/timing.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const seen = [];
|
|
5
|
+
export function recordTiming(t) {
|
|
6
|
+
seen.push(t);
|
|
7
|
+
}
|
|
8
|
+
export function timings() {
|
|
9
|
+
return seen.filter((t) => t.ourMs !== null);
|
|
10
|
+
}
|
|
11
|
+
export function networkMs(list = timings()) {
|
|
12
|
+
let best;
|
|
13
|
+
for (const t of list) {
|
|
14
|
+
if (t.ourMs === null)
|
|
15
|
+
continue;
|
|
16
|
+
const n = Math.max(0, t.totalMs - t.ourMs);
|
|
17
|
+
if (best === undefined || n < best)
|
|
18
|
+
best = n;
|
|
19
|
+
}
|
|
20
|
+
return best;
|
|
21
|
+
}
|
|
22
|
+
export function ourSideLine(ourMs, totalMs) {
|
|
23
|
+
const ours = `${fmtMs(ourMs)} on our side`;
|
|
24
|
+
return totalMs === undefined ? ours : `${ours} · ${fmtMs(totalMs)} round trip`;
|
|
25
|
+
}
|
|
26
|
+
export function fmtMs(ms) {
|
|
27
|
+
return ms >= 10 ? `${Math.round(ms)} ms` : `${Math.round(ms * 10) / 10} ms`;
|
|
28
|
+
}
|
|
29
|
+
export function where(location) {
|
|
30
|
+
return location ? `our servers on the ${location}` : "our servers";
|
|
31
|
+
}
|
|
32
|
+
export const FAR_MS = 150;
|
|
33
|
+
export function farMarker(env = process.env) {
|
|
34
|
+
const base = env["XDG_CONFIG_HOME"] ? join(env["XDG_CONFIG_HOME"], "promigence") : join(homedir(), ".config", "promigence");
|
|
35
|
+
return join(base, "far-notice-shown");
|
|
36
|
+
}
|
|
37
|
+
export function farNoticeText(ms, location) {
|
|
38
|
+
return `promigence: You're about ${Math.round(ms)} ms from ${where(location)}. ` +
|
|
39
|
+
"Times marked 'on our side' are the platform's own; the rest is your network. " +
|
|
40
|
+
"(Shown once. `promigence ping` measures it again.)\n";
|
|
41
|
+
}
|
|
42
|
+
export async function maybeFarNotice(opts) {
|
|
43
|
+
const env = opts.env ?? process.env;
|
|
44
|
+
if (opts.json || env["PROMIGENCE_QUIET"] === "1")
|
|
45
|
+
return undefined;
|
|
46
|
+
if (!(opts.isTTY ?? process.stderr.isTTY))
|
|
47
|
+
return undefined;
|
|
48
|
+
const ms = networkMs(opts.list ?? timings());
|
|
49
|
+
if (ms === undefined || ms < FAR_MS)
|
|
50
|
+
return undefined;
|
|
51
|
+
const marker = farMarker(env);
|
|
52
|
+
if (existsSync(marker))
|
|
53
|
+
return undefined;
|
|
54
|
+
const loc = await opts.location().catch(() => undefined);
|
|
55
|
+
const text = farNoticeText(ms, loc);
|
|
56
|
+
(opts.write ?? ((s) => process.stderr.write(s)))(text);
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(join(marker, ".."), { recursive: true, mode: 0o700 });
|
|
59
|
+
writeFileSync(marker, `${new Date().toISOString()} ${Math.round(ms)}\n`, { mode: 0o600 });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
}
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
65
|
+
export function median(xs) {
|
|
66
|
+
if (!xs.length)
|
|
67
|
+
return undefined;
|
|
68
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
69
|
+
const m = s.length >> 1;
|
|
70
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
71
|
+
}
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Promigence — promigence@0.1.0-beta.
|
|
1
|
+
# Promigence — promigence@0.1.0-beta.3
|
|
2
2
|
|
|
3
3
|
> Verified, reproducible, forkable execution environments for agents. The CLI is the
|
|
4
4
|
> `promigence` command (package `promigence`, which is also the TypeScript SDK).
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
- `promigence me` — plan, concurrency in use, trial credit, spend, limits
|
|
37
37
|
- `promigence billing card` — add a card: prints a checkout URL (trial credits kept)
|
|
38
38
|
- `promigence doctor` — check client, auth and API
|
|
39
|
+
- `promigence ping` — your distance to our servers
|
|
39
40
|
- `promigence init` — write promigence.toml
|
|
40
41
|
- `promigence examples` — what forks with no build (no auth)
|
|
41
42
|
- `promigence templates` — the templates: ready-made starting points to fork (no auth)
|
|
@@ -125,7 +126,7 @@
|
|
|
125
126
|
|
|
126
127
|
---
|
|
127
128
|
|
|
128
|
-
# Promigence — agent guide (promigence@0.1.0-beta.
|
|
129
|
+
# Promigence — agent guide (promigence@0.1.0-beta.3)
|
|
129
130
|
|
|
130
131
|
Promigence is an **environment runtime**: verified, reproducible, forkable execution
|
|
131
132
|
environments, billed per second (vCPU + memory). The model is one line:
|
|
@@ -195,4 +196,4 @@ Highest-precedence code wins: 2 > 5 > 6 > 7/8/9/10 > 11 > 3 > 12 > 4 > 1 > 0.
|
|
|
195
196
|
· `promigence <command> --help` · https://promigence.ai
|
|
196
197
|
Same surface as MCP tools: `promigence mcp` (stdio; `claude mcp add promigence -- promigence mcp`).
|
|
197
198
|
|
|
198
|
-
Live in v0.1.0-beta.
|
|
199
|
+
Live in v0.1.0-beta.3: login · signup · logout · auth login · keys list/create/revoke · org list/create/switch/members/invite/invites/uninvite/resend/role/remove/leave/join/audit/rename/delete · project list/create/rename/delete/cap/move · me · billing card/usage/portal · doctor · ping · init · examples · templates +show · agent-guide · mcp · snapshot create/status/list/inspect/verify/export/delete/builds/alias · image resolve · github connect/status/disconnect/untrack · repos · gitlab connect/status/disconnect/untrack · run +quote/report/status/cancel/kill/replay/open/captures/list · fork · exec +output/ps/stdin/kill · files put/get/ls/search/mkdir/mv/cp/chmod/rm · agent start · sandbox kill/network/shell/ssh/list/pause/resume/snapshot/info/extend/autopause/probe · port expose/list/revoke · secrets set/list/delete · bench burst · runners github/gitlab · status · referrals +apply · pricing · credits +claim
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promigence",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
4
|
"description": "The fastest sandboxes for AI agents and code. Snapshot an environment once, fork it as often as you need, run commands in every copy, and get a report that tells environment failures from task failures. Billed per second, with a quote before a run and a hard spend cap. One package: the promigence command and the TypeScript SDK.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.promigence.ai",
|