promigence 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +88 -0
- package/dist/account.js +850 -0
- package/dist/agent.js +374 -0
- package/dist/argv.js +159 -0
- package/dist/bin.js +332 -0
- package/dist/bundle.js +133 -0
- package/dist/capacity.js +69 -0
- package/dist/commands.d.ts +110 -0
- package/dist/commands.js +2675 -0
- package/dist/connectreq.js +125 -0
- package/dist/credfile.js +188 -0
- package/dist/credits.js +197 -0
- package/dist/devcontainer.d.ts +99 -0
- package/dist/devcontainer.js +744 -0
- package/dist/device.js +129 -0
- package/dist/dirsnap.js +378 -0
- package/dist/errfix.js +262 -0
- package/dist/generated.d.ts +35 -0
- package/dist/generated.js +307 -0
- package/dist/github.js +307 -0
- package/dist/gitlab.js +210 -0
- package/dist/guide.d.ts +19 -0
- package/dist/guide.js +366 -0
- package/dist/handlers.js +511 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +7 -0
- package/dist/init.d.ts +40 -0
- package/dist/init.js +352 -0
- package/dist/install.d.ts +23 -0
- package/dist/install.js +127 -0
- package/dist/io.d.ts +37 -0
- package/dist/io.js +318 -0
- package/dist/live.js +469 -0
- package/dist/login.js +1063 -0
- package/dist/mcp.js +889 -0
- package/dist/money.js +5 -0
- package/dist/orgs.js +475 -0
- package/dist/pricing.d.ts +52 -0
- package/dist/pricing.js +181 -0
- package/dist/report.js +132 -0
- package/dist/routes.js +135 -0
- package/dist/runners-github.js +257 -0
- package/dist/runners-gitlab.js +125 -0
- package/dist/runners-spec.js +137 -0
- package/dist/runners.js +1458 -0
- package/dist/sdk/blake3.d.ts +10 -0
- package/dist/sdk/blake3.js +111 -0
- package/dist/sdk/bundle.d.ts +96 -0
- package/dist/sdk/bundle.js +227 -0
- package/dist/sdk/ca-guard.js +166 -0
- package/dist/sdk/caller.d.ts +73 -0
- package/dist/sdk/caller.js +47 -0
- package/dist/sdk/client.d.ts +3015 -0
- package/dist/sdk/client.js +2583 -0
- package/dist/sdk/command-errors.d.ts +41 -0
- package/dist/sdk/command-errors.js +36 -0
- package/dist/sdk/context.d.ts +119 -0
- package/dist/sdk/context.js +588 -0
- package/dist/sdk/core.d.ts +285 -0
- package/dist/sdk/core.js +266 -0
- package/dist/sdk/credstore.d.ts +90 -0
- package/dist/sdk/credstore.js +206 -0
- package/dist/sdk/dispatcher.js +86 -0
- package/dist/sdk/github.d.ts +158 -0
- package/dist/sdk/github.js +78 -0
- package/dist/sdk/gitlab.d.ts +104 -0
- package/dist/sdk/gitlab.js +20 -0
- package/dist/sdk/handles.d.ts +82 -0
- package/dist/sdk/handles.js +135 -0
- package/dist/sdk/hint.js +7 -0
- package/dist/sdk/http2.js +1921 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +15 -0
- package/dist/sdk/keyed.gen.js +7 -0
- package/dist/sdk/models.gen.d.ts +6162 -0
- package/dist/sdk/models.gen.js +1 -0
- package/dist/sdk/proxy.js +249 -0
- package/dist/sdk/pty.d.ts +171 -0
- package/dist/sdk/pty.js +496 -0
- package/dist/sdk/sandbox.d.ts +537 -0
- package/dist/sdk/sandbox.js +725 -0
- package/dist/sdk/ssh.d.ts +25 -0
- package/dist/sdk/ssh.js +114 -0
- package/dist/sdk/trace.js +80 -0
- package/dist/sdk/transport-worker.js +466 -0
- package/dist/sdk/warm.js +40 -0
- package/dist/ssh.js +267 -0
- package/dist/surface.d.ts +95 -0
- package/dist/surface.js +1452 -0
- package/dist/templates.js +53 -0
- package/dist/term.js +50 -0
- package/dist/timing.js +71 -0
- package/dist/untar.js +324 -0
- package/dist/uploadguard.js +75 -0
- package/llms.txt +209 -0
- package/package.json +52 -0
package/dist/money.js
ADDED
package/dist/orgs.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
import { PromigenceClient, PromigenceError, credentialsPath, readCredentials } from "./sdk/index.js";
|
|
3
|
+
import { bool, clientOpts, nexts, str } from "./commands.js";
|
|
4
|
+
import { emit, errorEnvelope, errorTextLines, exitWith, okEnvelope, recordNotice, usageError, write } from "./io.js";
|
|
5
|
+
import { accessBody, accessNote, expiryBody, expiryLine, signIn, writeCredentials } from "./login.js";
|
|
6
|
+
import { checkCredentialsPath, loginMintedKey, storedWhere } from "./credfile.js";
|
|
7
|
+
import { sh, shellArg } from "./term.js";
|
|
8
|
+
function client(ctx) {
|
|
9
|
+
return new PromigenceClient({ ...clientOpts(ctx), onNotice: recordNotice });
|
|
10
|
+
}
|
|
11
|
+
async function personClient(ctx) {
|
|
12
|
+
const { idToken, nonce, apiUrl } = await signIn(ctx);
|
|
13
|
+
const base = globalThis.fetch;
|
|
14
|
+
const fetchWithNonce = (input, init) => {
|
|
15
|
+
const headers = new Headers(init?.headers);
|
|
16
|
+
if (nonce)
|
|
17
|
+
headers.set("promigence-login-nonce", nonce);
|
|
18
|
+
return base(input, { ...init, headers, redirect: "manual" });
|
|
19
|
+
};
|
|
20
|
+
return new PromigenceClient({ apiKey: idToken, apiUrl, onNotice: recordNotice, fetch: fetchWithNonce });
|
|
21
|
+
}
|
|
22
|
+
const enc = encodeURIComponent;
|
|
23
|
+
const orgTail = (ctx) => (str(ctx, "--org") ? ` --org ${shellArg(str(ctx, "--org"))}` : "");
|
|
24
|
+
async function scoped(ctx) {
|
|
25
|
+
const k = client(ctx);
|
|
26
|
+
const flag = str(ctx, "--org");
|
|
27
|
+
let me;
|
|
28
|
+
try {
|
|
29
|
+
me = await k.request({ method: "GET", path: "/me" });
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (flag)
|
|
33
|
+
return { c: await personClient(ctx), org: flag };
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
if (!flag || flag === me.org_id || flag === me.org)
|
|
37
|
+
return { c: k, org: flag ?? me.org_id };
|
|
38
|
+
return { c: await personClient(ctx), org: flag };
|
|
39
|
+
}
|
|
40
|
+
async function managing(ctx) {
|
|
41
|
+
let org = str(ctx, "--org");
|
|
42
|
+
if (!org) {
|
|
43
|
+
const me = await client(ctx).request({ method: "GET", path: "/me" });
|
|
44
|
+
org = me.org_id;
|
|
45
|
+
}
|
|
46
|
+
return { c: await personClient(ctx), org };
|
|
47
|
+
}
|
|
48
|
+
export async function projectKeyClient(ctx, project) {
|
|
49
|
+
const { c: person, org } = await managing(ctx);
|
|
50
|
+
const minting = person.request({
|
|
51
|
+
method: "POST", path: `/orgs/${enc(org)}/keys`, body: { name: `cli-keys@${hostname()}`.slice(0, 64), project, expires_in_minutes: 15 },
|
|
52
|
+
});
|
|
53
|
+
const keyClient = (k) => new PromigenceClient({ ...clientOpts(ctx), apiKey: k.api_key, apiUrl: person.apiUrl, onNotice: recordNotice });
|
|
54
|
+
const left = (id, why) => `the short-lived key ${id} this command minted in ${project} was not revoked (${why}); revoke it with \`promigence keys revoke ${shellArg(id)} --project ${shellArg(project)}${orgTail(ctx)}\``;
|
|
55
|
+
let revoking;
|
|
56
|
+
const revoke = (k) => (revoking ??= (async () => {
|
|
57
|
+
try {
|
|
58
|
+
await keyClient(k).request({ method: "DELETE", path: `/keys/${enc(k.key_id)}` });
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return left(k.key_id, e.message);
|
|
63
|
+
}
|
|
64
|
+
})());
|
|
65
|
+
let interrupted = false;
|
|
66
|
+
const onSignal = (sig) => {
|
|
67
|
+
if (interrupted) {
|
|
68
|
+
void write(process.stderr, `promigence ${ctx.cmd.name}: interrupted again; the short-lived key may still be live: check \`promigence keys list --project ${shellArg(project)}${orgTail(ctx)}\`\n`)
|
|
69
|
+
.then(() => exitWith(130));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
interrupted = true;
|
|
73
|
+
void (async () => {
|
|
74
|
+
let outcome;
|
|
75
|
+
try {
|
|
76
|
+
const k = await minting;
|
|
77
|
+
outcome = (await revoke(k)) ?? `the short-lived key ${k.key_id} was revoked`;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
outcome = "no short-lived key was made";
|
|
81
|
+
}
|
|
82
|
+
const check = `promigence keys list --project ${shellArg(project)}${orgTail(ctx)}`;
|
|
83
|
+
const err = new PromigenceError({ code: "aborted_by_client", message: `interrupted by ${sig}; ${outcome}`, fix: `Nothing to fix; \`${check}\` shows the keys that still work there.`, exitCode: 130 });
|
|
84
|
+
const next = nexts([check, "the keys that still work there"]);
|
|
85
|
+
await emit(ctx.mode, errorEnvelope(ctx.cmd.name, err, next), [`promigence ${ctx.cmd.name}: interrupted by ${sig} (exit 130)`, ` ${outcome}`], 130, false);
|
|
86
|
+
})();
|
|
87
|
+
};
|
|
88
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"].map((sig) => [sig, () => onSignal(sig)]);
|
|
89
|
+
for (const [sig, h] of signals)
|
|
90
|
+
process.on(sig, h);
|
|
91
|
+
const settle = async () => {
|
|
92
|
+
if (interrupted)
|
|
93
|
+
await new Promise(() => { });
|
|
94
|
+
for (const [sig, h] of signals)
|
|
95
|
+
process.off(sig, h);
|
|
96
|
+
};
|
|
97
|
+
let k;
|
|
98
|
+
try {
|
|
99
|
+
k = await minting;
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
await settle();
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
const done = async () => {
|
|
106
|
+
const warning = await revoke(k);
|
|
107
|
+
await settle();
|
|
108
|
+
return warning;
|
|
109
|
+
};
|
|
110
|
+
return { c: keyClient(k), keyId: k.key_id, org, done };
|
|
111
|
+
}
|
|
112
|
+
function arg(ctx, i, what, example) {
|
|
113
|
+
const v = ctx.args[i];
|
|
114
|
+
if (!v)
|
|
115
|
+
throw usageError(`${ctx.cmd.name} needs ${what}`, `Example: ${example}.`);
|
|
116
|
+
return v;
|
|
117
|
+
}
|
|
118
|
+
function role(ctx, v, fallback) {
|
|
119
|
+
const r = v ?? fallback;
|
|
120
|
+
if (r !== "owner" && r !== "member") {
|
|
121
|
+
throw usageError(`the role must be owner or member, not ${JSON.stringify(r ?? "")}`, `See 'promigence ${ctx.cmd.name} --help'.`);
|
|
122
|
+
}
|
|
123
|
+
return r;
|
|
124
|
+
}
|
|
125
|
+
const orgLine = (o) => ` ${o.default ? "*" : " "} ${o.name.padEnd(24)} ${o.org_id} ${o.role.padEnd(6)} ${o.members} member${o.members === 1 ? "" : "s"}, ${o.projects} project${o.projects === 1 ? "" : "s"}${o.card_on_file ? "" : " (no card)"}`;
|
|
126
|
+
const memberLine = (m) => ` ${m.email.padEnd(32)} ${m.user_id} ${m.role}${m.you ? " (you)" : ""}`;
|
|
127
|
+
const inviteLine = (i) => ` ${i.invite_id} ${i.email} as ${i.role} to ${i.org_name} from ${i.invited_by} ${i.created_at}`;
|
|
128
|
+
const projectLine = (p) => ` ${p.name.padEnd(24)} ${p.project_id} ${p.keys} key${p.keys === 1 ? "" : "s"}${p.month_spend_usd == null ? "" : ` $${p.month_spend_usd.toFixed(2)} in ${p.month}`}${p.monthly_cap_usd == null ? "" : ` cap $${p.monthly_cap_usd}/month`}${p.default ? " (default)" : ""}`;
|
|
129
|
+
const auditLine = (e) => ` ${String(e.seq).padStart(6)} ${e.at} ${e.action.padEnd(20)} ${e.actor} ${e.target_type}:${e.target_id}${e.outcome === "ok" ? "" : ` (${e.outcome})`}`;
|
|
130
|
+
const orgList = async (ctx) => {
|
|
131
|
+
const all = bool(ctx, "--all");
|
|
132
|
+
const c = all ? await personClient(ctx) : client(ctx);
|
|
133
|
+
const r = await c.request({ method: "GET", path: "/orgs" });
|
|
134
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r, all }, nexts(["promigence org switch <org>", "work in another organization"])), [
|
|
135
|
+
`promigence org list: ${r.orgs.length} organization${r.orgs.length === 1 ? "" : "s"} (* = default)${all ? "" : " — this key's; --all signs in and lists every one"}`,
|
|
136
|
+
...r.orgs.map(orgLine),
|
|
137
|
+
], 0, ctx.pointer);
|
|
138
|
+
};
|
|
139
|
+
const orgCreate = async (ctx) => {
|
|
140
|
+
const name = arg(ctx, 0, "a name", "promigence org create acme");
|
|
141
|
+
const o = await (await personClient(ctx)).request({ method: "POST", path: "/orgs", body: { name } });
|
|
142
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { org: o }, nexts([sh `promigence org switch ${o.name}`, "use it from this machine"])), [
|
|
143
|
+
`promigence org create: ${o.name} (${o.org_id}), you are its owner`,
|
|
144
|
+
" It has no trial credit (the trial is per person): add a card there before its first run.",
|
|
145
|
+
], 0, ctx.pointer);
|
|
146
|
+
};
|
|
147
|
+
const orgSwitch = async (ctx) => {
|
|
148
|
+
const ident = arg(ctx, 0, "an organization", "promigence org switch acme");
|
|
149
|
+
const stored = readCredentials().api_key;
|
|
150
|
+
const usingStored = ctx.apiKey === undefined && !process.env["PROMIGENCE_API_KEY"] && stored !== undefined;
|
|
151
|
+
const minted = loginMintedKey();
|
|
152
|
+
const mintedHere = usingStored && minted !== undefined && minted.apiKey === stored;
|
|
153
|
+
const old = client(ctx);
|
|
154
|
+
let oldKey;
|
|
155
|
+
if (mintedHere) {
|
|
156
|
+
try {
|
|
157
|
+
const me = await old.request({ method: "GET", path: "/me" });
|
|
158
|
+
oldKey = me.key?.service ? undefined : me.key?.key_id;
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
}
|
|
162
|
+
checkCredentialsPath(credentialsPath());
|
|
163
|
+
const person = await personClient(ctx);
|
|
164
|
+
const body = { name: `cli@${hostname()}`.slice(0, 64), default: true };
|
|
165
|
+
const project = str(ctx, "--project");
|
|
166
|
+
if (project)
|
|
167
|
+
body["project"] = project;
|
|
168
|
+
const k = await person.request({ method: "POST", path: `/orgs/${enc(ident)}/keys`, body });
|
|
169
|
+
const o = { org_id: k.org_id };
|
|
170
|
+
const saved = writeCredentials(k.api_key, person.apiUrl, k.key_id);
|
|
171
|
+
const path = saved.path;
|
|
172
|
+
const lines = [
|
|
173
|
+
`promigence org switch: now in ${o.org_id} (your default), project ${k.project_id}`,
|
|
174
|
+
` key ${k.key_id} (${k.prefix}…) stored in ${storedWhere(saved)}`,
|
|
175
|
+
...(saved.note ? [` note: ${saved.note}`] : []),
|
|
176
|
+
];
|
|
177
|
+
const warnings = [...saved.warnings];
|
|
178
|
+
let revoked = null;
|
|
179
|
+
if (oldKey && !bool(ctx, "--keep-old-key")) {
|
|
180
|
+
try {
|
|
181
|
+
await old.request({
|
|
182
|
+
method: "DELETE", path: `/keys/${enc(oldKey)}?reason=replaced`,
|
|
183
|
+
headers: { "promigence-replaced-by": k.api_key },
|
|
184
|
+
});
|
|
185
|
+
revoked = oldKey;
|
|
186
|
+
lines.push(` the previous key ${oldKey} was revoked`);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
warnings.push(`the previous key ${oldKey} was not revoked (${e.message}); revoke it with \`${sh `promigence keys revoke ${oldKey}`}\``);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if ((!usingStored || !mintedHere) && stored !== undefined) {
|
|
193
|
+
warnings.push(`the key that was stored in ${storedWhere(saved)} was replaced and still works; revoke it if nothing else uses it`);
|
|
194
|
+
}
|
|
195
|
+
if (process.env["PROMIGENCE_API_KEY"]) {
|
|
196
|
+
warnings.push("PROMIGENCE_API_KEY is set in this shell and wins over the stored key: unset it to use the new one");
|
|
197
|
+
}
|
|
198
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { org: o, key: { ...k, api_key: undefined }, credentials: path, store: saved.store, revoked, warnings }, nexts(["promigence me", "confirm where you are"])), [...lines, ...warnings.map((w) => ` warning: ${w}`)], 0, ctx.pointer);
|
|
199
|
+
};
|
|
200
|
+
const orgMembers = async (ctx) => {
|
|
201
|
+
const { c, org } = await scoped(ctx);
|
|
202
|
+
const r = await c.request({ method: "GET", path: `/orgs/${enc(org)}/members` });
|
|
203
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org invite <email>", "add someone"])), [
|
|
204
|
+
`promigence org members: ${r.members.length} in ${r.org_id}`,
|
|
205
|
+
...r.members.map(memberLine),
|
|
206
|
+
], 0, ctx.pointer);
|
|
207
|
+
};
|
|
208
|
+
const orgInvite = async (ctx) => {
|
|
209
|
+
const email = arg(ctx, 0, "an email address", "promigence org invite dev@example.com --role member");
|
|
210
|
+
const r0 = role(ctx, str(ctx, "--role"), "member");
|
|
211
|
+
const { c, org } = await managing(ctx);
|
|
212
|
+
const i = await c.request({
|
|
213
|
+
method: "POST", path: `/orgs/${enc(org)}/invites`, body: { email, role: r0 },
|
|
214
|
+
});
|
|
215
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { invite: i }, nexts(["promigence org invites", "pending invites"])), [
|
|
216
|
+
`promigence org invite: ${i.email} invited to ${i.org_name} as ${i.role} (${i.invite_id})`,
|
|
217
|
+
" They accept it signed in with that address, verified: `promigence org invites --mine`, then `promigence org join <id>`.",
|
|
218
|
+
], 0, ctx.pointer);
|
|
219
|
+
};
|
|
220
|
+
const orgInvites = async (ctx) => {
|
|
221
|
+
if (bool(ctx, "--mine"))
|
|
222
|
+
return inviteList(ctx);
|
|
223
|
+
const { c, org } = await scoped(ctx);
|
|
224
|
+
const r = await c.request({ method: "GET", path: `/orgs/${enc(org)}/invites` });
|
|
225
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org uninvite <invite_id>", "withdraw one"])), [
|
|
226
|
+
`promigence org invites: ${r.invites.length} pending`,
|
|
227
|
+
...r.invites.map(inviteLine),
|
|
228
|
+
], 0, ctx.pointer);
|
|
229
|
+
};
|
|
230
|
+
const orgUninvite = async (ctx) => {
|
|
231
|
+
const id = arg(ctx, 0, "an invite id", "promigence org uninvite inv_3e9f0c2a");
|
|
232
|
+
const { c, org } = await managing(ctx);
|
|
233
|
+
const r = await c.request({ method: "DELETE", path: `/orgs/${enc(org)}/invites/${enc(id)}` });
|
|
234
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org invites", "what is still pending"])), [
|
|
235
|
+
`promigence org uninvite: ${r.invite_id} revoked`,
|
|
236
|
+
], 0, ctx.pointer);
|
|
237
|
+
};
|
|
238
|
+
const orgResend = async (ctx) => {
|
|
239
|
+
const id = arg(ctx, 0, "an invite id", "promigence org resend inv_3e9f0c2a");
|
|
240
|
+
const { c, org } = await managing(ctx);
|
|
241
|
+
const i = await c.request({ method: "POST", path: `/orgs/${enc(org)}/invites/${enc(id)}/resend`, body: {} });
|
|
242
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { invite: i }, nexts(["promigence org invites", "pending invites"])), [
|
|
243
|
+
`promigence org resend: the invite to ${i.email} was sent again (${i.invite_id}); it now expires 14 days from today`,
|
|
244
|
+
], 0, ctx.pointer);
|
|
245
|
+
};
|
|
246
|
+
const orgRole = async (ctx) => {
|
|
247
|
+
const user = arg(ctx, 0, "a member's user id", "promigence org role usr_2b8e11 owner");
|
|
248
|
+
const r0 = role(ctx, arg(ctx, 1, "a role (owner or member)", "promigence org role usr_2b8e11 owner"));
|
|
249
|
+
const { c, org } = await managing(ctx);
|
|
250
|
+
const r = await c.request({
|
|
251
|
+
method: "PATCH", path: `/orgs/${enc(org)}/members/${enc(user)}`, body: { role: r0 },
|
|
252
|
+
});
|
|
253
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org members", "the result"])), [
|
|
254
|
+
`promigence org role: ${user} is now ${r0}`,
|
|
255
|
+
...r.members.map(memberLine),
|
|
256
|
+
], 0, ctx.pointer);
|
|
257
|
+
};
|
|
258
|
+
async function removeMember(ctx, org, user, c) {
|
|
259
|
+
const r = await c.request({
|
|
260
|
+
method: "DELETE", path: `/orgs/${enc(org)}/members/${enc(user)}`,
|
|
261
|
+
});
|
|
262
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org list", "where you still belong"])), [
|
|
263
|
+
`promigence ${ctx.cmd.name}: ${r.user_id} is no longer a member of ${r.org_id}; ${r.keys_revoked} of their key${r.keys_revoked === 1 ? "" : "s"} revoked`,
|
|
264
|
+
], 0, ctx.pointer);
|
|
265
|
+
}
|
|
266
|
+
const orgRemove = async (ctx) => {
|
|
267
|
+
const user = arg(ctx, 0, "a member's user id", "promigence org remove usr_2b8e11");
|
|
268
|
+
const { c, org } = await managing(ctx);
|
|
269
|
+
return removeMember(ctx, org, user, c);
|
|
270
|
+
};
|
|
271
|
+
const orgLeave = async (ctx) => {
|
|
272
|
+
const k = client(ctx);
|
|
273
|
+
let me;
|
|
274
|
+
try {
|
|
275
|
+
me = await k.request({ method: "GET", path: "/me" });
|
|
276
|
+
}
|
|
277
|
+
catch { }
|
|
278
|
+
const org = ctx.args[0] ?? me?.org_id;
|
|
279
|
+
if (!org)
|
|
280
|
+
throw usageError("org leave needs an organization", "Example: promigence org leave acme.");
|
|
281
|
+
const own = me !== undefined && (org === me.org_id || org === me.org) && Boolean(me.user?.user_id);
|
|
282
|
+
if (own) {
|
|
283
|
+
await write(process.stderr, " note: this is the organization of the key you are using; leaving revokes it. `promigence org switch <org>` first to keep working from here.\n");
|
|
284
|
+
return removeMember(ctx, org, me.user.user_id, k);
|
|
285
|
+
}
|
|
286
|
+
return removeMember(ctx, org, "me", await personClient(ctx));
|
|
287
|
+
};
|
|
288
|
+
function auditNeedsSignIn(ctx) {
|
|
289
|
+
if (bool(ctx, "--sign-in") || bool(ctx, "--no-browser") || process.env["PROMIGENCE_ID_TOKEN"])
|
|
290
|
+
return undefined;
|
|
291
|
+
const again = `promigence org audit${str(ctx, "--org") ? ` --org ${str(ctx, "--org")}` : ""}`;
|
|
292
|
+
const err = new PromigenceError({
|
|
293
|
+
code: "forbidden",
|
|
294
|
+
message: "reading the audit log needs you signed in as yourself, not an API key",
|
|
295
|
+
fix: `Run \`${again} --sign-in\` to sign in in your browser, or \`${again} --no-browser\` on a machine without one (you paste a code). A script can set PROMIGENCE_ID_TOKEN.`,
|
|
296
|
+
exitCode: 7,
|
|
297
|
+
});
|
|
298
|
+
const next = nexts([`${again} --sign-in`, "sign in in your browser and read the log"]);
|
|
299
|
+
return emit(ctx.mode, errorEnvelope(ctx.cmd.name, err, next), errorTextLines(ctx.cmd.name, err, 7, next), 7, false);
|
|
300
|
+
}
|
|
301
|
+
const orgAudit = async (ctx) => {
|
|
302
|
+
const refused = auditNeedsSignIn(ctx);
|
|
303
|
+
if (refused)
|
|
304
|
+
return refused;
|
|
305
|
+
const { c, org } = await managing(ctx);
|
|
306
|
+
const q = new URLSearchParams();
|
|
307
|
+
const before = str(ctx, "--before");
|
|
308
|
+
const limit = str(ctx, "--limit");
|
|
309
|
+
if (before)
|
|
310
|
+
q.set("before", before);
|
|
311
|
+
if (limit)
|
|
312
|
+
q.set("limit", limit);
|
|
313
|
+
const qs = q.toString() ? `?${q}` : "";
|
|
314
|
+
const r = await c.request({ method: "GET", path: `/orgs/${enc(org)}/audit${qs}` });
|
|
315
|
+
const full = r.entries.length >= Math.min(Number(limit ?? 100), 100);
|
|
316
|
+
const last = full ? r.entries.at(-1) : undefined;
|
|
317
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(last ? [`promigence org audit --before ${shellArg(last.seq)}${limit ? ` --limit ${shellArg(limit)}` : ""}${orgTail(ctx)}`, "older entries"] : [`promigence org members${orgTail(ctx)}`, "who can act here"])), [
|
|
318
|
+
`promigence org audit: ${r.entries.length} entr${r.entries.length === 1 ? "y" : "ies"} in ${r.org_id}, newest first`,
|
|
319
|
+
...r.entries.map(auditLine),
|
|
320
|
+
], 0, ctx.pointer);
|
|
321
|
+
};
|
|
322
|
+
const orgRename = async (ctx) => {
|
|
323
|
+
const ident = arg(ctx, 0, "an organization", "promigence org rename acme acme-labs");
|
|
324
|
+
const name = arg(ctx, 1, "the new name", "promigence org rename acme acme-labs");
|
|
325
|
+
const o = await (await personClient(ctx)).request({ method: "PATCH", path: `/orgs/${enc(ident)}`, body: { name } });
|
|
326
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { org: o }, nexts([sh `promigence org list --all`, "your organizations"])), [
|
|
327
|
+
`promigence org rename: ${o.org_id} is now ${o.name}`,
|
|
328
|
+
` Its id is unchanged. Anything that names it by its old name (--org, scripts) must use ${o.name} or ${o.org_id}.`,
|
|
329
|
+
], 0, ctx.pointer);
|
|
330
|
+
};
|
|
331
|
+
const orgDelete = async (ctx) => {
|
|
332
|
+
const ident = arg(ctx, 0, "an organization", "promigence org delete acme --confirm acme");
|
|
333
|
+
const confirm = str(ctx, "--confirm");
|
|
334
|
+
if (!confirm) {
|
|
335
|
+
throw usageError("org delete needs --confirm with the organization's name", `Example: ${sh `promigence org delete ${ident} --confirm <name>`}. Its keys stop working and its members leave it.`);
|
|
336
|
+
}
|
|
337
|
+
const c = await personClient(ctx);
|
|
338
|
+
const r = await c.request({
|
|
339
|
+
method: "DELETE", path: `/orgs/${enc(ident)}`, body: { confirm },
|
|
340
|
+
});
|
|
341
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org list --all", "where you still belong"])), [
|
|
342
|
+
`promigence org delete: ${r.org_id} deleted; ${r.keys_revoked} key${r.keys_revoked === 1 ? "" : "s"} revoked`,
|
|
343
|
+
...(r.members_rehomed ? [` ${r.members_rehomed} member${r.members_rehomed === 1 ? " had" : "s had"} no other organization and got a personal one`] : []),
|
|
344
|
+
], 0, ctx.pointer);
|
|
345
|
+
};
|
|
346
|
+
const inviteList = async (ctx) => {
|
|
347
|
+
const r = await (await personClient(ctx)).request({ method: "GET", path: "/invites" });
|
|
348
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence org join <invite_id>", "join"])), [
|
|
349
|
+
`promigence org invites --mine: ${r.invites.length} invite${r.invites.length === 1 ? "" : "s"} for your verified email`,
|
|
350
|
+
...r.invites.map(inviteLine),
|
|
351
|
+
], 0, ctx.pointer);
|
|
352
|
+
};
|
|
353
|
+
const inviteAccept = async (ctx) => {
|
|
354
|
+
const id = arg(ctx, 0, "an invite id", "promigence org join inv_3e9f0c2a");
|
|
355
|
+
if (bool(ctx, "--decline")) {
|
|
356
|
+
const r = await (await personClient(ctx)).request({ method: "POST", path: `/invites/${enc(id)}/decline`, body: {} });
|
|
357
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { declined: r.invite_id }, nexts(["promigence org invites --mine", "what else is waiting"])), [
|
|
358
|
+
`promigence org join --decline: ${r.invite_id} declined`,
|
|
359
|
+
], 0, ctx.pointer);
|
|
360
|
+
}
|
|
361
|
+
const o = await (await personClient(ctx)).request({ method: "POST", path: `/invites/${enc(id)}/accept`, body: {} });
|
|
362
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { org: o }, nexts([sh `promigence org switch ${o.name}`, "work there from this machine"])), [
|
|
363
|
+
`promigence org join: you are a${o.role === "owner" ? "n owner" : " member"} of ${o.name} (${o.org_id})`,
|
|
364
|
+
], 0, ctx.pointer);
|
|
365
|
+
};
|
|
366
|
+
const projectList = async (ctx) => {
|
|
367
|
+
const { c, org } = await scoped(ctx);
|
|
368
|
+
const month = str(ctx, "--month");
|
|
369
|
+
const q = month ? `?month=${encodeURIComponent(month)}` : "";
|
|
370
|
+
const r = await c.request({ method: "GET", path: `/orgs/${enc(org)}/projects${q}` });
|
|
371
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence project create <name>", "add one"])), [
|
|
372
|
+
`promigence project list: ${r.projects.length} in ${r.org_id}`,
|
|
373
|
+
...r.projects.map(projectLine),
|
|
374
|
+
], 0, ctx.pointer);
|
|
375
|
+
};
|
|
376
|
+
const projectCreate = async (ctx) => {
|
|
377
|
+
const name = arg(ctx, 0, "a name", "promigence project create evals");
|
|
378
|
+
const { c, org } = await scoped(ctx);
|
|
379
|
+
const p = await c.request({ method: "POST", path: `/orgs/${enc(org)}/projects`, body: { name } });
|
|
380
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { project: p }, nexts([sh `promigence keys create --project ${p.name}`, "a key for it"])), [
|
|
381
|
+
`promigence project create: ${p.name} (${p.project_id}) in ${p.org_id}`,
|
|
382
|
+
], 0, ctx.pointer);
|
|
383
|
+
};
|
|
384
|
+
const projectRename = async (ctx) => {
|
|
385
|
+
const from = arg(ctx, 0, "a project", "promigence project rename evals rl");
|
|
386
|
+
const to = arg(ctx, 1, "the new name", "promigence project rename evals rl");
|
|
387
|
+
const { c, org } = await scoped(ctx);
|
|
388
|
+
const p = await c.request({ method: "PATCH", path: `/orgs/${enc(org)}/projects/${enc(from)}`, body: { name: to } });
|
|
389
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { project: p }, nexts(["promigence project list", "the result"])), [
|
|
390
|
+
`promigence project rename: ${p.project_id} is now ${p.name}`,
|
|
391
|
+
], 0, ctx.pointer);
|
|
392
|
+
};
|
|
393
|
+
const projectDelete = async (ctx) => {
|
|
394
|
+
const ident = arg(ctx, 0, "a project", "promigence project delete evals");
|
|
395
|
+
const { c, org } = await managing(ctx);
|
|
396
|
+
const r = await c.request({ method: "DELETE", path: `/orgs/${enc(org)}/projects/${enc(ident)}` });
|
|
397
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts(["promigence project list", "what is left"])), [
|
|
398
|
+
`promigence project delete: ${r.project_id} deleted`,
|
|
399
|
+
], 0, ctx.pointer);
|
|
400
|
+
};
|
|
401
|
+
const projectCap = async (ctx) => {
|
|
402
|
+
const ident = arg(ctx, 0, "a project", "promigence project cap evals 500");
|
|
403
|
+
const raw = arg(ctx, 1, "a monthly cap in USD, or off", "promigence project cap evals 500");
|
|
404
|
+
let cap;
|
|
405
|
+
if (raw.toLowerCase() === "off")
|
|
406
|
+
cap = null;
|
|
407
|
+
else {
|
|
408
|
+
cap = /^\$?\d+(\.\d+)?$/.test(raw) ? Number(raw.replace(/^\$/, "")) : NaN;
|
|
409
|
+
if (!Number.isFinite(cap) || cap < 0) {
|
|
410
|
+
throw usageError(`the cap must be a number of USD or off, not ${JSON.stringify(raw)}`, "Example: promigence project cap evals 500.");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const { c, org } = await managing(ctx);
|
|
414
|
+
const p = await c.request({ method: "PATCH", path: `/orgs/${enc(org)}/projects/${enc(ident)}`, body: { monthly_cap_usd: cap } });
|
|
415
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { project: p }, nexts([`promigence project list${orgTail(ctx)}`, "every project's cap"])), [
|
|
416
|
+
p.monthly_cap_usd == null
|
|
417
|
+
? `promigence project cap: ${p.name} has no monthly cap`
|
|
418
|
+
: `promigence project cap: ${p.name} may bill at most $${p.monthly_cap_usd} a month (UTC); a run past it is refused`,
|
|
419
|
+
], 0, ctx.pointer);
|
|
420
|
+
};
|
|
421
|
+
const projectMove = async (ctx) => {
|
|
422
|
+
const ident = arg(ctx, 0, "a project", "promigence project move evals --to acme");
|
|
423
|
+
const to = str(ctx, "--to");
|
|
424
|
+
if (!to)
|
|
425
|
+
throw usageError("project move needs --to <org>", "Example: promigence project move evals --to acme.");
|
|
426
|
+
const { c, org } = await managing(ctx);
|
|
427
|
+
const r = await c.request({
|
|
428
|
+
method: "POST", path: `/orgs/${enc(org)}/projects/${enc(ident)}/move`, body: { to },
|
|
429
|
+
});
|
|
430
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, r, nexts([sh `promigence keys create --org ${r.to_org_id} --project ${ident}`, "a key for it there"])), [
|
|
431
|
+
`promigence project move: ${r.project_id} is now in ${r.to_org_id}, with ${r.snapshots_moved} snapshot${r.snapshots_moved === 1 ? "" : "s"} and ${r.secrets_moved} secret${r.secrets_moved === 1 ? "" : "s"}`,
|
|
432
|
+
` ${r.keys_revoked} key${r.keys_revoked === 1 ? " was" : "s were"} revoked; past runs stay with ${r.from_org_id}.`,
|
|
433
|
+
], 0, ctx.pointer);
|
|
434
|
+
};
|
|
435
|
+
export async function keysCreateInOrg(ctx) {
|
|
436
|
+
const { c, org } = bool(ctx, "--service") || str(ctx, "--project") ? await managing(ctx) : await scoped(ctx);
|
|
437
|
+
const body = { name: (str(ctx, "--name") ?? `cli@${hostname()}`).slice(0, 64) };
|
|
438
|
+
const project = str(ctx, "--project");
|
|
439
|
+
if (project)
|
|
440
|
+
body["project"] = project;
|
|
441
|
+
if (bool(ctx, "--service"))
|
|
442
|
+
body["service"] = true;
|
|
443
|
+
Object.assign(body, expiryBody(ctx), accessBody(ctx));
|
|
444
|
+
const k = await c.request({ method: "POST", path: `/orgs/${enc(org)}/keys`, body });
|
|
445
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { key: k }, nexts(["promigence keys list", "confirm it is there"])), [
|
|
446
|
+
`promigence keys create: ${k.key_id} "${k.name}" in ${k.org_id}, project ${k.project_id}${k.service ? " (service key)" : ""}${accessNote(k.access)}`,
|
|
447
|
+
` api_key: ${k.api_key}`,
|
|
448
|
+
" This is the only time the secret is shown. Store it now (e.g. in your CI secret store).",
|
|
449
|
+
...(k.service ? [] : [" It is yours: it stops working if you leave the organization. `--service` (owners) makes one that does not."]),
|
|
450
|
+
expiryLine(k.expires_at),
|
|
451
|
+
], 0, ctx.pointer);
|
|
452
|
+
}
|
|
453
|
+
export const ORG_HANDLERS = {
|
|
454
|
+
"org list": orgList,
|
|
455
|
+
"org create": orgCreate,
|
|
456
|
+
"org switch": orgSwitch,
|
|
457
|
+
"org members": orgMembers,
|
|
458
|
+
"org invite": orgInvite,
|
|
459
|
+
"org invites": orgInvites,
|
|
460
|
+
"org uninvite": orgUninvite,
|
|
461
|
+
"org role": orgRole,
|
|
462
|
+
"org remove": orgRemove,
|
|
463
|
+
"org leave": orgLeave,
|
|
464
|
+
"org join": inviteAccept,
|
|
465
|
+
"org audit": orgAudit,
|
|
466
|
+
"org rename": orgRename,
|
|
467
|
+
"org resend": orgResend,
|
|
468
|
+
"org delete": orgDelete,
|
|
469
|
+
"project list": projectList,
|
|
470
|
+
"project create": projectCreate,
|
|
471
|
+
"project rename": projectRename,
|
|
472
|
+
"project delete": projectDelete,
|
|
473
|
+
"project cap": projectCap,
|
|
474
|
+
"project move": projectMove,
|
|
475
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PromigenceClient, type PricingView } from "./sdk/index.js";
|
|
2
|
+
import { type Ctx, type Handler } from "./commands.js";
|
|
3
|
+
export declare const COMPILED_NOTE = "list prices at build time; could not reach the API";
|
|
4
|
+
export declare const PUBLISHED_NOTE = "published list prices; your organization's own need a key (promigence login)";
|
|
5
|
+
export interface PriceTier {
|
|
6
|
+
name: string;
|
|
7
|
+
vcpus: number;
|
|
8
|
+
mem_gib: number;
|
|
9
|
+
disk_gib: number;
|
|
10
|
+
per_second_usd: number;
|
|
11
|
+
per_hour_usd: number;
|
|
12
|
+
preemptible_per_second_usd?: number;
|
|
13
|
+
available?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface Prices {
|
|
16
|
+
source: "api" | "compiled" | "published";
|
|
17
|
+
note?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
view?: PricingView;
|
|
20
|
+
vcpu_second_usd: number;
|
|
21
|
+
gib_second_usd: number;
|
|
22
|
+
preemptible_vcpu_second_usd: number;
|
|
23
|
+
preemptible_gib_second_usd: number;
|
|
24
|
+
build_usd: number;
|
|
25
|
+
build_max_s: number;
|
|
26
|
+
granularity_seconds: number;
|
|
27
|
+
tiers: PriceTier[];
|
|
28
|
+
}
|
|
29
|
+
export declare function publishedPrices(): Prices;
|
|
30
|
+
export declare const DEFAULT_BUILD_TIER = "small";
|
|
31
|
+
export declare function compiledPrices(note?: string, reason?: string): Prices;
|
|
32
|
+
export declare function pricesFrom(view: PricingView): Prices;
|
|
33
|
+
export declare function livePricing(c: PromigenceClient, opts?: {
|
|
34
|
+
newAccount?: boolean;
|
|
35
|
+
strict?: boolean;
|
|
36
|
+
}): Promise<Prices>;
|
|
37
|
+
export interface BuildPrice {
|
|
38
|
+
most_usd: number;
|
|
39
|
+
per_second_usd: number;
|
|
40
|
+
max_s: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function buildPriceIn(p: Prices, tier?: string): BuildPrice;
|
|
43
|
+
export declare function buildPrice(c: PromigenceClient, tier?: string): Promise<BuildPrice>;
|
|
44
|
+
export declare function buildLimit(s: number): string;
|
|
45
|
+
export declare function buildPriceWords(p: BuildPrice, tier?: string): string;
|
|
46
|
+
export declare function quickClient(ctx: Ctx): PromigenceClient;
|
|
47
|
+
export declare function rate(n: number): string;
|
|
48
|
+
export declare function pricingLines(p: Prices, opts?: {
|
|
49
|
+
cardsOff?: boolean;
|
|
50
|
+
}): string[];
|
|
51
|
+
export declare function pricingJson(p: Prices): Record<string, unknown>;
|
|
52
|
+
export declare const PRICING_HANDLERS: Readonly<Record<string, Handler>>;
|