run402 2.41.0 → 2.42.0
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/lib/operator.mjs +103 -13
- package/lib/org.mjs +150 -67
- package/lib/projects.mjs +20 -5
- package/package.json +1 -1
- package/sdk/dist/control-plane-credentials.d.ts +1 -1
- package/sdk/dist/control-plane-credentials.js +1 -1
- package/sdk/dist/index.d.ts +16 -7
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +19 -8
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/operator.d.ts +101 -0
- package/sdk/dist/namespaces/operator.d.ts.map +1 -1
- package/sdk/dist/namespaces/operator.js +60 -1
- package/sdk/dist/namespaces/operator.js.map +1 -1
- package/sdk/dist/namespaces/org.d.ts +73 -35
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +122 -71
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/org.types.d.ts +51 -17
- package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.types.js +7 -4
- package/sdk/dist/namespaces/org.types.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +2 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +8 -0
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/node/index.d.ts +3 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +2 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/operator-claim.d.ts +49 -0
- package/sdk/dist/node/operator-claim.d.ts.map +1 -0
- package/sdk/dist/node/operator-claim.js +77 -0
- package/sdk/dist/node/operator-claim.js.map +1 -0
package/lib/operator.mjs
CHANGED
|
@@ -23,7 +23,8 @@ import { createServer } from "node:http";
|
|
|
23
23
|
import { randomBytes, createHash } from "node:crypto";
|
|
24
24
|
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
25
25
|
import { getSdk } from "./sdk.mjs";
|
|
26
|
-
import {
|
|
26
|
+
import { claimWalletOrg, isStepUpRequired } from "#sdk/node";
|
|
27
|
+
import { normalizeArgv, hasHelp, assertKnownFlags, flagValue } from "./argparse.mjs";
|
|
27
28
|
import {
|
|
28
29
|
saveOperatorSession,
|
|
29
30
|
clearOperatorSession,
|
|
@@ -55,19 +56,25 @@ Usage:
|
|
|
55
56
|
run402 operator overview
|
|
56
57
|
run402 operator whoami
|
|
57
58
|
run402 operator logout
|
|
59
|
+
run402 operator claim-wallet-org [--org <id>] [--name <label>]
|
|
58
60
|
|
|
59
61
|
Subcommands:
|
|
60
|
-
login
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
overview
|
|
64
|
-
whoami
|
|
65
|
-
logout
|
|
62
|
+
login Sign in via the browser. Default = device-flow READ session (powers
|
|
63
|
+
'overview'). --loopback = write-capable control-plane session
|
|
64
|
+
(aws-sso-style, passkey-fresh). --step-up = re-mint a fresh write session.
|
|
65
|
+
overview Account view across ALL wallets controlling your email (requires login)
|
|
66
|
+
whoami Show the cached session(s) — local, no network
|
|
67
|
+
logout Revoke the session server-side and clear the local cache(s)
|
|
68
|
+
claim-wallet-org Claim an org owned by your wallet's agent into your human identity
|
|
69
|
+
(ownership transfer). Dual proof: your write-capable session
|
|
70
|
+
('login --loopback' first) + a fresh signature from the active wallet.
|
|
66
71
|
|
|
67
72
|
Options:
|
|
68
73
|
--no-open (login) Do not auto-open the browser; just print the URL.
|
|
69
74
|
--loopback (login) Use the loopback-PKCE write login instead of the device flow.
|
|
70
75
|
--step-up (login) Re-mint a fresh write session (implies --loopback).
|
|
76
|
+
--org <id> (claim-wallet-org) Pick which org to claim (only when the wallet owns several).
|
|
77
|
+
--name <s> (claim-wallet-org) Optional label to set on the claimed org.
|
|
71
78
|
|
|
72
79
|
Notes:
|
|
73
80
|
- The session is cached at the base config dir, shared across named wallets.
|
|
@@ -147,6 +154,10 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
147
154
|
rejectCode = rej;
|
|
148
155
|
});
|
|
149
156
|
let timer;
|
|
157
|
+
// Track live sockets: server.close() alone stops NEW connections but leaves
|
|
158
|
+
// the browser's keep-alive socket open, which keeps Node's event loop alive
|
|
159
|
+
// and hangs the CLI after a successful login. close() must destroy them.
|
|
160
|
+
const sockets = new Set();
|
|
150
161
|
const server = createServer((req, res) => {
|
|
151
162
|
let u;
|
|
152
163
|
try {
|
|
@@ -162,19 +173,32 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
162
173
|
const code = u.searchParams.get("code");
|
|
163
174
|
const gotState = u.searchParams.get("state");
|
|
164
175
|
const errParam = u.searchParams.get("error");
|
|
165
|
-
|
|
176
|
+
// `connection: close` so the browser does not keep the socket alive.
|
|
177
|
+
res.writeHead(200, { "content-type": "text/html", connection: "close" });
|
|
166
178
|
res.end(
|
|
167
179
|
"<!doctype html><html><body style=\"font-family:system-ui;padding:3rem\">" +
|
|
168
180
|
"<h2>run402 - you're signed in.</h2><p>You can close this window and return to your terminal.</p></body></html>",
|
|
169
181
|
);
|
|
170
|
-
|
|
182
|
+
// Do NOT tear down here — let the response flush. The caller calls close()
|
|
183
|
+
// once it has the code (or on any failure path).
|
|
171
184
|
if (errParam) rejectCode(new Error(`authorization error: ${errParam}`));
|
|
172
185
|
else if (!code) rejectCode(new Error("no authorization code on the loopback redirect"));
|
|
173
186
|
else if (gotState !== expectedState) rejectCode(new Error("state mismatch on the loopback redirect (possible CSRF) - aborted"));
|
|
174
187
|
else resolveCode(code);
|
|
175
188
|
});
|
|
176
|
-
|
|
189
|
+
server.on("connection", (s) => {
|
|
190
|
+
sockets.add(s);
|
|
191
|
+
s.once("close", () => sockets.delete(s));
|
|
192
|
+
});
|
|
193
|
+
function close() {
|
|
177
194
|
clearTimeout(timer);
|
|
195
|
+
for (const s of sockets) {
|
|
196
|
+
try {
|
|
197
|
+
s.destroy();
|
|
198
|
+
} catch {
|
|
199
|
+
// already gone
|
|
200
|
+
}
|
|
201
|
+
}
|
|
178
202
|
try {
|
|
179
203
|
server.close();
|
|
180
204
|
} catch {
|
|
@@ -182,18 +206,18 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
182
206
|
}
|
|
183
207
|
}
|
|
184
208
|
timer = setTimeout(() => {
|
|
185
|
-
|
|
209
|
+
close();
|
|
186
210
|
rejectCode(new Error("timed out waiting for browser approval"));
|
|
187
211
|
}, timeoutMs);
|
|
188
212
|
server.on("error", (e) => {
|
|
189
|
-
|
|
213
|
+
close();
|
|
190
214
|
rejectCode(e);
|
|
191
215
|
});
|
|
192
216
|
const ready = new Promise((res, rej) => {
|
|
193
217
|
server.once("error", rej);
|
|
194
218
|
server.listen(0, "127.0.0.1", () => res(server.address().port));
|
|
195
219
|
});
|
|
196
|
-
return { ready, codePromise, close
|
|
220
|
+
return { ready, codePromise, close };
|
|
197
221
|
}
|
|
198
222
|
|
|
199
223
|
/**
|
|
@@ -242,8 +266,13 @@ async function loopbackLogin(args, { stepUp }) {
|
|
|
242
266
|
try {
|
|
243
267
|
session = await sdk.operator.exchangeCliToken({ code, codeVerifier, redirectUri, state });
|
|
244
268
|
} catch (err) {
|
|
269
|
+
close();
|
|
245
270
|
return reportSdkError(err);
|
|
246
271
|
}
|
|
272
|
+
// The loopback server has done its job. Tear it down (destroying any
|
|
273
|
+
// keep-alive socket) so Node's event loop drains and the CLI exits instead
|
|
274
|
+
// of hanging until Ctrl+C.
|
|
275
|
+
close();
|
|
247
276
|
|
|
248
277
|
const cached = controlPlaneSessionFromTokenResponse(session);
|
|
249
278
|
saveControlPlaneSession(cached);
|
|
@@ -423,6 +452,64 @@ async function whoami(args) {
|
|
|
423
452
|
process.exitCode = 1;
|
|
424
453
|
}
|
|
425
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Claim a wallet-owned org into your human (console) identity — an ownership
|
|
457
|
+
* transfer (v1.82). Dual proof: your write-capable control-plane session (run
|
|
458
|
+
* 'operator login --loopback' first) PLUS a fresh SIWX signature from the active
|
|
459
|
+
* wallet over a server nonce. The Node convenience runs the whole dance
|
|
460
|
+
* (read session → challenge → sign → submit). Step-up failures and the
|
|
461
|
+
* multi-org `select_org` round-trip are surfaced here, not thrown to the user.
|
|
462
|
+
*/
|
|
463
|
+
async function claimWalletOrgCmd(args) {
|
|
464
|
+
assertKnownFlags(args, ["--help", "-h", "--org", "--name"], ["--org", "--name"]);
|
|
465
|
+
const orgId = flagValue(args, "--org");
|
|
466
|
+
const name = flagValue(args, "--name");
|
|
467
|
+
try {
|
|
468
|
+
const result = await claimWalletOrg(getSdk(), {
|
|
469
|
+
orgId: orgId ?? undefined,
|
|
470
|
+
displayName: name ?? undefined,
|
|
471
|
+
});
|
|
472
|
+
// CLI output contract (cli-output-shape): no top-level `status` envelope.
|
|
473
|
+
// The gateway's discriminator becomes an explicit `claimed` boolean.
|
|
474
|
+
if (result && result.status === "select_org") {
|
|
475
|
+
console.log(
|
|
476
|
+
JSON.stringify(
|
|
477
|
+
{
|
|
478
|
+
claimed: false,
|
|
479
|
+
selectable_orgs: result.selectable_orgs,
|
|
480
|
+
hint: "Your wallet's agent owns more than one org. Re-run with --org <id> to claim one.",
|
|
481
|
+
},
|
|
482
|
+
null,
|
|
483
|
+
2,
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
console.log(
|
|
489
|
+
JSON.stringify(
|
|
490
|
+
{
|
|
491
|
+
claimed: true,
|
|
492
|
+
org_id: result.org_id,
|
|
493
|
+
display_name: result.display_name,
|
|
494
|
+
role: result.role,
|
|
495
|
+
already_owned: result.already_owned ?? false,
|
|
496
|
+
},
|
|
497
|
+
null,
|
|
498
|
+
2,
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (isStepUpRequired(err)) {
|
|
503
|
+
return fail({
|
|
504
|
+
code: "STEP_UP_REQUIRED",
|
|
505
|
+
message: "Claiming a wallet org needs a fresh passkey step-up.",
|
|
506
|
+
hint: "Run 'run402 operator login --step-up', then re-run 'run402 operator claim-wallet-org'.",
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
reportSdkError(err);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
426
513
|
export async function run(sub, args = []) {
|
|
427
514
|
args = normalizeArgv(args);
|
|
428
515
|
if (!sub || sub === "--help" || sub === "-h" || hasHelp(args)) {
|
|
@@ -442,6 +529,9 @@ export async function run(sub, args = []) {
|
|
|
442
529
|
case "whoami":
|
|
443
530
|
await whoami(args);
|
|
444
531
|
break;
|
|
532
|
+
case "claim-wallet-org":
|
|
533
|
+
await claimWalletOrgCmd(args);
|
|
534
|
+
break;
|
|
445
535
|
default:
|
|
446
536
|
fail({
|
|
447
537
|
code: "BAD_USAGE",
|
package/lib/org.mjs
CHANGED
|
@@ -10,64 +10,99 @@ import {
|
|
|
10
10
|
|
|
11
11
|
const ROLE_LIST = "owner | admin | developer | billing | viewer";
|
|
12
12
|
|
|
13
|
-
const HELP = `run402 org —
|
|
13
|
+
const HELP = `run402 org — organizations: create, label, membership, invites
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
|
-
run402 org
|
|
16
|
+
run402 org create [--name <label>]
|
|
17
17
|
run402 org list
|
|
18
|
-
run402 org
|
|
19
|
-
run402 org
|
|
20
|
-
run402 org
|
|
21
|
-
run402 org
|
|
22
|
-
run402 org member
|
|
23
|
-
run402 org
|
|
24
|
-
run402 org
|
|
25
|
-
run402 org
|
|
18
|
+
run402 org get <org>
|
|
19
|
+
run402 org rename <org> <display_name> (or: --clear to remove the label)
|
|
20
|
+
run402 org whoami
|
|
21
|
+
run402 org audit <org> [--limit N] [--before <cursor>]
|
|
22
|
+
run402 org member list <org>
|
|
23
|
+
run402 org member add <org> <wallet> [--role <role>]
|
|
24
|
+
run402 org member role <org> <principal_id> <role>
|
|
25
|
+
run402 org member rm <org> <principal_id>
|
|
26
|
+
run402 org invite list <org>
|
|
27
|
+
run402 org invite create <org> <email> [--role <role>] [--ttl-hours N]
|
|
28
|
+
run402 org invite rm <org> <principal_id>
|
|
26
29
|
|
|
27
30
|
Subcommands:
|
|
28
|
-
|
|
31
|
+
create Create an empty org on the prototype tier (you become owner)
|
|
29
32
|
list Orgs you are a member of
|
|
33
|
+
get Read one org (label + tier + your role)
|
|
34
|
+
rename Set or clear an org's display label (owner-only)
|
|
35
|
+
whoami Resolved principal + org memberships (GET /agent/v1/whoami)
|
|
30
36
|
member Manage members (list, add, role, rm) — mutations require owner
|
|
31
37
|
invite Manage email invites (list, create, rm) — mutations require owner
|
|
32
38
|
audit Control-plane audit trail for an org (admin+)
|
|
33
39
|
|
|
34
40
|
Notes:
|
|
35
|
-
- A wallet AUTHENTICATES;
|
|
41
|
+
- A wallet AUTHENTICATES; an org owns projects. Membership/role authorizes.
|
|
36
42
|
- Roles: ${ROLE_LIST}. Member/invite changes need an active owner.
|
|
43
|
+
- create/rename/member/invite are step-up gated for control-plane sessions.
|
|
37
44
|
- Removing/demoting the org's only active owner fails with 409 LAST_OWNER.
|
|
38
45
|
- JSON in, JSON out.
|
|
39
46
|
|
|
40
47
|
Examples:
|
|
41
|
-
run402 org
|
|
42
|
-
run402 org
|
|
43
|
-
run402 org
|
|
44
|
-
run402 org
|
|
45
|
-
run402 org
|
|
46
|
-
run402 org
|
|
48
|
+
run402 org create --name "Kychee"
|
|
49
|
+
run402 org list
|
|
50
|
+
run402 org get org_abc
|
|
51
|
+
run402 org rename org_abc "New Name"
|
|
52
|
+
run402 org rename org_abc --clear
|
|
53
|
+
run402 org member add org_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
|
|
54
|
+
run402 org invite create org_abc dev@example.com --role developer
|
|
55
|
+
run402 org audit org_abc --limit 50
|
|
47
56
|
`;
|
|
48
57
|
|
|
49
58
|
const SUB_HELP = {
|
|
50
|
-
|
|
59
|
+
create: `run402 org create — create an empty org (prototype tier; you become owner)
|
|
51
60
|
|
|
52
61
|
Usage:
|
|
53
|
-
run402 org
|
|
62
|
+
run402 org create [--name <label>]
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
The label is an optional free-text name (non-unique, not an id). Omit for an
|
|
65
|
+
unlabeled org. There is no tier at create — paid tiers are a separate flow.
|
|
66
|
+
Step-up gated for control-plane sessions; the free-org cap may apply.
|
|
58
67
|
`,
|
|
59
68
|
list: `run402 org list — orgs you are a member of
|
|
60
69
|
|
|
61
70
|
Usage:
|
|
62
71
|
run402 org list
|
|
72
|
+
`,
|
|
73
|
+
get: `run402 org get — read one org (label + tier + your role)
|
|
74
|
+
|
|
75
|
+
Usage:
|
|
76
|
+
run402 org get <org>
|
|
77
|
+
|
|
78
|
+
Any active member may read. A non-member (including a guessed id) gets the same
|
|
79
|
+
non-revealing 403.
|
|
80
|
+
`,
|
|
81
|
+
rename: `run402 org rename — set or clear an org's display label (owner-only)
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
run402 org rename <org> <display_name>
|
|
85
|
+
run402 org rename <org> --clear
|
|
86
|
+
|
|
87
|
+
Owner-only + step-up gated. Pass --clear (or an empty display_name) to remove
|
|
88
|
+
the label.
|
|
89
|
+
`,
|
|
90
|
+
whoami: `run402 org whoami — resolved principal + org memberships
|
|
91
|
+
|
|
92
|
+
Usage:
|
|
93
|
+
run402 org whoami
|
|
94
|
+
|
|
95
|
+
Calls GET /agent/v1/whoami. Returns the control-plane principal (id/type/displayName/createdAt),
|
|
96
|
+
authenticator_id, and every org membership (org_id, display_name, role, status). REMOTE identity;
|
|
97
|
+
for local wallet/profile state use 'run402 status'.
|
|
63
98
|
`,
|
|
64
99
|
member: `run402 org member — manage org members
|
|
65
100
|
|
|
66
101
|
Usage:
|
|
67
|
-
run402 org member list <
|
|
68
|
-
run402 org member add <
|
|
69
|
-
run402 org member role <
|
|
70
|
-
run402 org member rm <
|
|
102
|
+
run402 org member list <org>
|
|
103
|
+
run402 org member add <org> <wallet> [--role <role>]
|
|
104
|
+
run402 org member role <org> <principal_id> <role>
|
|
105
|
+
run402 org member rm <org> <principal_id>
|
|
71
106
|
|
|
72
107
|
Roles: ${ROLE_LIST} (add defaults to developer). Mutations require an active owner.
|
|
73
108
|
Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
|
|
@@ -75,9 +110,9 @@ Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
|
|
|
75
110
|
invite: `run402 org invite — manage email invites
|
|
76
111
|
|
|
77
112
|
Usage:
|
|
78
|
-
run402 org invite list <
|
|
79
|
-
run402 org invite create <
|
|
80
|
-
run402 org invite rm <
|
|
113
|
+
run402 org invite list <org>
|
|
114
|
+
run402 org invite create <org> <email> [--role <role>] [--ttl-hours N]
|
|
115
|
+
run402 org invite rm <org> <principal_id>
|
|
81
116
|
|
|
82
117
|
An invite is claimed at the recipient's first login. Mutations require an active owner
|
|
83
118
|
(plus step-up when driven by a control-plane session).
|
|
@@ -85,31 +120,76 @@ An invite is claimed at the recipient's first login. Mutations require an active
|
|
|
85
120
|
audit: `run402 org audit — control-plane audit trail
|
|
86
121
|
|
|
87
122
|
Usage:
|
|
88
|
-
run402 org audit <
|
|
123
|
+
run402 org audit <org> [--limit N] [--before <cursor>]
|
|
89
124
|
|
|
90
125
|
Requires an admin+ membership on the org. Newest-first; page with --before.
|
|
91
126
|
`,
|
|
92
127
|
};
|
|
93
128
|
|
|
94
|
-
// ── Top-level:
|
|
129
|
+
// ── Top-level: create / list / get / rename / whoami / audit ────────────────────
|
|
130
|
+
|
|
131
|
+
async function create(args) {
|
|
132
|
+
const a = normalizeArgv(args);
|
|
133
|
+
const valueFlags = ["--name"];
|
|
134
|
+
assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
|
|
135
|
+
requirePositionalCount(a, valueFlags, { min: 0, max: 0, command: "run402 org create [--name <label>]" });
|
|
136
|
+
const name = flagValue(a, "--name");
|
|
137
|
+
try {
|
|
138
|
+
console.log(JSON.stringify(await getSdk().orgs.create({ displayName: name ?? undefined }), null, 2));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
reportSdkError(err);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function list(args) {
|
|
145
|
+
const a = normalizeArgv(args);
|
|
146
|
+
assertKnownFlags(a, ["--help", "-h"]);
|
|
147
|
+
requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 org list" });
|
|
148
|
+
try {
|
|
149
|
+
console.log(JSON.stringify({ orgs: await getSdk().orgs.list() }, null, 2));
|
|
150
|
+
} catch (err) {
|
|
151
|
+
reportSdkError(err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
95
154
|
|
|
96
155
|
async function whoami(args) {
|
|
97
156
|
const a = normalizeArgv(args);
|
|
98
157
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
99
158
|
requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 org whoami" });
|
|
100
159
|
try {
|
|
101
|
-
console.log(JSON.stringify(await getSdk().
|
|
160
|
+
console.log(JSON.stringify(await getSdk().orgs.whoami(), null, 2));
|
|
102
161
|
} catch (err) {
|
|
103
162
|
reportSdkError(err);
|
|
104
163
|
}
|
|
105
164
|
}
|
|
106
165
|
|
|
107
|
-
async function
|
|
166
|
+
async function get(args) {
|
|
108
167
|
const a = normalizeArgv(args);
|
|
109
168
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
110
|
-
requirePositionalCount(a, [], {
|
|
169
|
+
const [org] = requirePositionalCount(a, [], {
|
|
170
|
+
min: 1, max: 1, command: "run402 org get <org>", missing: "Missing <org>.",
|
|
171
|
+
});
|
|
111
172
|
try {
|
|
112
|
-
console.log(JSON.stringify(
|
|
173
|
+
console.log(JSON.stringify(await getSdk().org(org).get(), null, 2));
|
|
174
|
+
} catch (err) {
|
|
175
|
+
reportSdkError(err);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function rename(args) {
|
|
180
|
+
const a = normalizeArgv(args);
|
|
181
|
+
assertKnownFlags(a, ["--clear", "--help", "-h"]);
|
|
182
|
+
const clear = a.includes("--clear");
|
|
183
|
+
const positionals = requirePositionalCount(a, [], {
|
|
184
|
+
min: clear ? 1 : 2,
|
|
185
|
+
max: clear ? 1 : 2,
|
|
186
|
+
command: "run402 org rename <org> <display_name>",
|
|
187
|
+
missing: clear ? "Missing <org>." : "Missing <org> and/or <display_name> (or pass --clear).",
|
|
188
|
+
});
|
|
189
|
+
const org = positionals[0];
|
|
190
|
+
const displayName = clear ? null : positionals[1];
|
|
191
|
+
try {
|
|
192
|
+
console.log(JSON.stringify(await getSdk().org(org).rename(displayName), null, 2));
|
|
113
193
|
} catch (err) {
|
|
114
194
|
reportSdkError(err);
|
|
115
195
|
}
|
|
@@ -119,17 +199,17 @@ async function audit(args) {
|
|
|
119
199
|
const a = normalizeArgv(args);
|
|
120
200
|
const valueFlags = ["--limit", "--before"];
|
|
121
201
|
assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
|
|
122
|
-
const [
|
|
202
|
+
const [org] = requirePositionalCount(a, valueFlags, {
|
|
123
203
|
min: 1,
|
|
124
204
|
max: 1,
|
|
125
|
-
command: "run402 org audit <
|
|
126
|
-
missing: "Missing <
|
|
205
|
+
command: "run402 org audit <org>",
|
|
206
|
+
missing: "Missing <org>.",
|
|
127
207
|
});
|
|
128
208
|
const limitFlag = flagValue(a, "--limit");
|
|
129
209
|
const before = flagValue(a, "--before");
|
|
130
210
|
const limit = limitFlag === null ? undefined : parseIntegerFlag("--limit", limitFlag, { min: 1, max: 1000 });
|
|
131
211
|
try {
|
|
132
|
-
const events = await getSdk().org.audit(
|
|
212
|
+
const events = await getSdk().org(org).audit({ limit, before: before ?? undefined });
|
|
133
213
|
console.log(JSON.stringify({ events }, null, 2));
|
|
134
214
|
} catch (err) {
|
|
135
215
|
reportSdkError(err);
|
|
@@ -153,11 +233,11 @@ async function runMember(args) {
|
|
|
153
233
|
if (memberAction === "list") {
|
|
154
234
|
const a = normalizeArgv(rest);
|
|
155
235
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
156
|
-
const [
|
|
157
|
-
min: 1, max: 1, command: "run402 org member list <
|
|
236
|
+
const [org] = requirePositionalCount(a, [], {
|
|
237
|
+
min: 1, max: 1, command: "run402 org member list <org>", missing: "Missing <org>.",
|
|
158
238
|
});
|
|
159
239
|
try {
|
|
160
|
-
console.log(JSON.stringify({ members: await getSdk().org.members.list(
|
|
240
|
+
console.log(JSON.stringify({ members: await getSdk().org(org).members.list() }, null, 2));
|
|
161
241
|
} catch (err) {
|
|
162
242
|
reportSdkError(err);
|
|
163
243
|
}
|
|
@@ -168,12 +248,12 @@ async function runMember(args) {
|
|
|
168
248
|
const a = normalizeArgv(rest);
|
|
169
249
|
assertKnownFlags(a, ["--role", "--help", "-h"], ["--role"]);
|
|
170
250
|
const role = flagValue(a, "--role");
|
|
171
|
-
const [
|
|
172
|
-
min: 2, max: 2, command: "run402 org member add <
|
|
173
|
-
missing: "Missing <
|
|
251
|
+
const [org, wallet] = requirePositionalCount(a, ["--role"], {
|
|
252
|
+
min: 2, max: 2, command: "run402 org member add <org> <wallet> [--role <role>]",
|
|
253
|
+
missing: "Missing <org> and/or <wallet>.",
|
|
174
254
|
});
|
|
175
255
|
try {
|
|
176
|
-
const res = await getSdk().org.members.add(
|
|
256
|
+
const res = await getSdk().org(org).members.add({ wallet, role: role || undefined });
|
|
177
257
|
console.log(JSON.stringify(res, null, 2));
|
|
178
258
|
} catch (err) {
|
|
179
259
|
reportSdkError(err);
|
|
@@ -184,12 +264,12 @@ async function runMember(args) {
|
|
|
184
264
|
if (memberAction === "role") {
|
|
185
265
|
const a = normalizeArgv(rest);
|
|
186
266
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
187
|
-
const [
|
|
188
|
-
min: 3, max: 3, command: "run402 org member role <
|
|
189
|
-
missing: "Missing <
|
|
267
|
+
const [org, principalId, role] = requirePositionalCount(a, [], {
|
|
268
|
+
min: 3, max: 3, command: "run402 org member role <org> <principal_id> <role>",
|
|
269
|
+
missing: "Missing <org>, <principal_id>, and/or <role>.",
|
|
190
270
|
});
|
|
191
271
|
try {
|
|
192
|
-
console.log(JSON.stringify(await getSdk().org.members.setRole(
|
|
272
|
+
console.log(JSON.stringify(await getSdk().org(org).members.setRole(principalId, role), null, 2));
|
|
193
273
|
} catch (err) {
|
|
194
274
|
reportSdkError(err);
|
|
195
275
|
}
|
|
@@ -199,12 +279,12 @@ async function runMember(args) {
|
|
|
199
279
|
if (memberAction === "rm") {
|
|
200
280
|
const a = normalizeArgv(rest);
|
|
201
281
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
202
|
-
const [
|
|
203
|
-
min: 2, max: 2, command: "run402 org member rm <
|
|
204
|
-
missing: "Missing <
|
|
282
|
+
const [org, principalId] = requirePositionalCount(a, [], {
|
|
283
|
+
min: 2, max: 2, command: "run402 org member rm <org> <principal_id>",
|
|
284
|
+
missing: "Missing <org> and/or <principal_id>.",
|
|
205
285
|
});
|
|
206
286
|
try {
|
|
207
|
-
console.log(JSON.stringify(await getSdk().org.members.revoke(
|
|
287
|
+
console.log(JSON.stringify(await getSdk().org(org).members.revoke(principalId), null, 2));
|
|
208
288
|
} catch (err) {
|
|
209
289
|
reportSdkError(err);
|
|
210
290
|
}
|
|
@@ -231,11 +311,11 @@ async function runInvite(args) {
|
|
|
231
311
|
if (inviteAction === "list") {
|
|
232
312
|
const a = normalizeArgv(rest);
|
|
233
313
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
234
|
-
const [
|
|
235
|
-
min: 1, max: 1, command: "run402 org invite list <
|
|
314
|
+
const [org] = requirePositionalCount(a, [], {
|
|
315
|
+
min: 1, max: 1, command: "run402 org invite list <org>", missing: "Missing <org>.",
|
|
236
316
|
});
|
|
237
317
|
try {
|
|
238
|
-
console.log(JSON.stringify({ invites: await getSdk().org.invites.list(
|
|
318
|
+
console.log(JSON.stringify({ invites: await getSdk().org(org).invites.list() }, null, 2));
|
|
239
319
|
} catch (err) {
|
|
240
320
|
reportSdkError(err);
|
|
241
321
|
}
|
|
@@ -248,13 +328,13 @@ async function runInvite(args) {
|
|
|
248
328
|
assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
|
|
249
329
|
const role = flagValue(a, "--role");
|
|
250
330
|
const ttlFlag = flagValue(a, "--ttl-hours");
|
|
251
|
-
const [
|
|
252
|
-
min: 2, max: 2, command: "run402 org invite create <
|
|
253
|
-
missing: "Missing <
|
|
331
|
+
const [org, email] = requirePositionalCount(a, valueFlags, {
|
|
332
|
+
min: 2, max: 2, command: "run402 org invite create <org> <email> [--role <role>]",
|
|
333
|
+
missing: "Missing <org> and/or <email>.",
|
|
254
334
|
});
|
|
255
335
|
const inviteTtlHours = ttlFlag === null ? undefined : parseIntegerFlag("--ttl-hours", ttlFlag, { min: 1, max: 8760 });
|
|
256
336
|
try {
|
|
257
|
-
const res = await getSdk().org.invites.create(
|
|
337
|
+
const res = await getSdk().org(org).invites.create({ email, role: role || "developer", inviteTtlHours });
|
|
258
338
|
console.log(JSON.stringify(res, null, 2));
|
|
259
339
|
} catch (err) {
|
|
260
340
|
reportSdkError(err);
|
|
@@ -265,12 +345,12 @@ async function runInvite(args) {
|
|
|
265
345
|
if (inviteAction === "rm") {
|
|
266
346
|
const a = normalizeArgv(rest);
|
|
267
347
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
268
|
-
const [
|
|
269
|
-
min: 2, max: 2, command: "run402 org invite rm <
|
|
270
|
-
missing: "Missing <
|
|
348
|
+
const [org, principalId] = requirePositionalCount(a, [], {
|
|
349
|
+
min: 2, max: 2, command: "run402 org invite rm <org> <principal_id>",
|
|
350
|
+
missing: "Missing <org> and/or <principal_id>.",
|
|
271
351
|
});
|
|
272
352
|
try {
|
|
273
|
-
console.log(JSON.stringify(await getSdk().org.invites.revoke(
|
|
353
|
+
console.log(JSON.stringify(await getSdk().org(org).invites.revoke(principalId), null, 2));
|
|
274
354
|
} catch (err) {
|
|
275
355
|
reportSdkError(err);
|
|
276
356
|
}
|
|
@@ -300,8 +380,11 @@ export async function run(sub, args) {
|
|
|
300
380
|
process.exit(0);
|
|
301
381
|
}
|
|
302
382
|
switch (sub) {
|
|
303
|
-
case "
|
|
383
|
+
case "create": await create(args); break;
|
|
304
384
|
case "list": await list(args); break;
|
|
385
|
+
case "get": await get(args); break;
|
|
386
|
+
case "rename": await rename(args); break;
|
|
387
|
+
case "whoami": await whoami(args); break;
|
|
305
388
|
case "audit": await audit(args); break;
|
|
306
389
|
default:
|
|
307
390
|
console.error(`Unknown subcommand: ${sub}\n`);
|
package/lib/projects.mjs
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
|
|
12
12
|
Subcommands:
|
|
13
13
|
quote Show pricing tiers
|
|
14
|
-
provision [--tier <tier>] [--name <n>] Provision a new Postgres project (pays via x402)
|
|
14
|
+
provision [--tier <tier>] [--name <n>] [--org <id>] Provision a new Postgres project (pays via x402)
|
|
15
15
|
use <id> Set the active project (used as default for other commands)
|
|
16
16
|
list List all your projects (IDs, URLs, active marker)
|
|
17
17
|
info [id] Show project details: REST URL, keys
|
|
@@ -79,11 +79,13 @@ const SUB_HELP = {
|
|
|
79
79
|
provision: `run402 projects provision — Provision a new Postgres project
|
|
80
80
|
|
|
81
81
|
Usage:
|
|
82
|
-
run402 projects provision [--tier <tier>] [--name <name>]
|
|
82
|
+
run402 projects provision [--tier <tier>] [--name <name>] [--org <id>]
|
|
83
83
|
|
|
84
84
|
Options:
|
|
85
85
|
--tier <tier> Tier for the new project (default: prototype)
|
|
86
86
|
--name <name> Human-readable name for the project
|
|
87
|
+
--org <id> Provision into an EXISTING org (needs developer+ on it).
|
|
88
|
+
Omit for the cold-start path. Tier is org-governed.
|
|
87
89
|
|
|
88
90
|
Notes:
|
|
89
91
|
- Payment is automatic via x402; requires a funded allowance
|
|
@@ -93,6 +95,7 @@ Examples:
|
|
|
93
95
|
run402 projects provision
|
|
94
96
|
run402 projects provision --tier prototype
|
|
95
97
|
run402 projects provision --tier hobby --name my-app
|
|
98
|
+
run402 projects provision --org org_abc123
|
|
96
99
|
`,
|
|
97
100
|
sql: `run402 projects sql — Run a SQL query against a project's database
|
|
98
101
|
|
|
@@ -184,12 +187,24 @@ async function quote() {
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
async function provision(args) {
|
|
187
|
-
const opts = { tier: "prototype", name: undefined };
|
|
190
|
+
const opts = { tier: "prototype", name: undefined, orgId: undefined };
|
|
188
191
|
for (let i = 0; i < args.length; i++) {
|
|
189
192
|
if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
|
|
190
193
|
// Use !== undefined so an empty-string value is captured (and rejected
|
|
191
194
|
// below) rather than silently dropped by the falsy-check pattern (GH-176).
|
|
192
195
|
if (args[i] === "--name" && args[i + 1] !== undefined) opts.name = args[++i];
|
|
196
|
+
// --org targets an EXISTING org (v1.82); caller needs developer+ on it.
|
|
197
|
+
// Omitted = cold-start. Tier is org-governed, so --tier is irrelevant here
|
|
198
|
+
// (the gateway ignores a client-supplied tier in all cases).
|
|
199
|
+
if (args[i] === "--org" && args[i + 1] !== undefined) opts.orgId = args[++i];
|
|
200
|
+
}
|
|
201
|
+
if (opts.orgId === "") {
|
|
202
|
+
fail({
|
|
203
|
+
code: "BAD_USAGE",
|
|
204
|
+
message: "--org must not be empty.",
|
|
205
|
+
details: { field: "--org" },
|
|
206
|
+
hint: "Pass an org id (run402 org list), or omit --org for the cold-start path.",
|
|
207
|
+
});
|
|
193
208
|
}
|
|
194
209
|
// Validate --name when provided. Omitted --name lets the server pick a
|
|
195
210
|
// default. The same envelope should also be enforced server-side (GH-176).
|
|
@@ -225,7 +240,7 @@ async function provision(args) {
|
|
|
225
240
|
|
|
226
241
|
const activeBefore = getActiveProjectId();
|
|
227
242
|
try {
|
|
228
|
-
const data = await getSdk().projects.provision({ tier: opts.tier, name: opts.name });
|
|
243
|
+
const data = await getSdk().projects.provision({ tier: opts.tier, name: opts.name, orgId: opts.orgId });
|
|
229
244
|
const activeAfter = getActiveProjectId();
|
|
230
245
|
const out = { ...data };
|
|
231
246
|
if (activeBefore && activeAfter && activeBefore !== activeAfter) {
|
|
@@ -555,7 +570,7 @@ async function deleteProject(projectId, args = []) {
|
|
|
555
570
|
}
|
|
556
571
|
|
|
557
572
|
const FLAGS_BY_SUB = {
|
|
558
|
-
provision: { known: ["--tier", "--name"], values: ["--tier", "--name"] },
|
|
573
|
+
provision: { known: ["--tier", "--name", "--org"], values: ["--tier", "--name", "--org"] },
|
|
559
574
|
sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
|
|
560
575
|
costs: { known: ["--window"], values: ["--window"] },
|
|
561
576
|
"apply-expose": { known: ["--file"], values: ["--file"] },
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* / `passkeyVerify` / the loopback-PKCE `exchangeCliToken`, then:
|
|
10
10
|
*
|
|
11
11
|
* const r = run402({ credentials: controlPlaneSessionCredentials({ token }) });
|
|
12
|
-
* await r.
|
|
12
|
+
* await r.orgs.whoami(); // resolves the principal + memberships
|
|
13
13
|
*
|
|
14
14
|
* High-stakes writes still require a fresh passkey — an `email`/`oauth` session
|
|
15
15
|
* gets {@link StepUpRequiredError}; run the step-up ceremony
|