run402 2.41.1 → 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 +75 -7
- 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.
|
|
@@ -445,6 +452,64 @@ async function whoami(args) {
|
|
|
445
452
|
process.exitCode = 1;
|
|
446
453
|
}
|
|
447
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
|
+
|
|
448
513
|
export async function run(sub, args = []) {
|
|
449
514
|
args = normalizeArgv(args);
|
|
450
515
|
if (!sub || sub === "--help" || sub === "-h" || hasHelp(args)) {
|
|
@@ -464,6 +529,9 @@ export async function run(sub, args = []) {
|
|
|
464
529
|
case "whoami":
|
|
465
530
|
await whoami(args);
|
|
466
531
|
break;
|
|
532
|
+
case "claim-wallet-org":
|
|
533
|
+
await claimWalletOrgCmd(args);
|
|
534
|
+
break;
|
|
467
535
|
default:
|
|
468
536
|
fail({
|
|
469
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
|
|
@@ -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
|
package/sdk/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { Deploy } from "./namespaces/deploy.js";
|
|
|
30
30
|
import { Ci } from "./namespaces/ci.js";
|
|
31
31
|
import { Jobs } from "./namespaces/jobs.js";
|
|
32
32
|
import { Operator } from "./namespaces/operator.js";
|
|
33
|
-
import {
|
|
33
|
+
import { Orgs, ScopedOrg } from "./namespaces/org.js";
|
|
34
34
|
import { Grants } from "./namespaces/grants.js";
|
|
35
35
|
import type { ContentSource, FileSet } from "./namespaces/deploy.types.js";
|
|
36
36
|
import { ScopedRun402 } from "./scoped.js";
|
|
@@ -85,12 +85,13 @@ export declare class Run402 {
|
|
|
85
85
|
*/
|
|
86
86
|
readonly operator: Operator;
|
|
87
87
|
/**
|
|
88
|
-
* Org
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
88
|
+
* Org collection + identity (gateway v1.77+, first-class in v1.82):
|
|
89
|
+
* `r.orgs.create()` / `list()` / `whoami()`. For operations on a single org by
|
|
90
|
+
* id use the scoped sub-client {@link Run402.org} (`r.org(id).get()` /
|
|
91
|
+
* `rename()` / `members.*` / `invites.*` / `audit()`). Distinct from the local,
|
|
92
|
+
* network-free {@link Run402.whoami}.
|
|
92
93
|
*/
|
|
93
|
-
readonly
|
|
94
|
+
readonly orgs: Orgs;
|
|
94
95
|
/**
|
|
95
96
|
* Per-project capability grants for agent/CI principals. Also available
|
|
96
97
|
* project-scoped as `r.project(id).grants`.
|
|
@@ -128,6 +129,14 @@ export declare class Run402 {
|
|
|
128
129
|
* use {@link project} instead.
|
|
129
130
|
*/
|
|
130
131
|
useProject(id: string): Promise<ScopedRun402>;
|
|
132
|
+
/**
|
|
133
|
+
* Return an org-scoped sub-client with `id` pre-bound — the org analog of
|
|
134
|
+
* {@link Run402.project}. Instance operations (`get`, `rename`, `members.*`,
|
|
135
|
+
* `invites.*`, `audit`) drop their org-id argument. Synchronous: an org id is
|
|
136
|
+
* always explicit (there is no "active org" fallback). Collection/identity
|
|
137
|
+
* operations (`create`, `list`, `whoami`) live on {@link Run402.orgs}.
|
|
138
|
+
*/
|
|
139
|
+
org(id: string): ScopedOrg;
|
|
131
140
|
/**
|
|
132
141
|
* Identify the active wallet and project: `{ local_label, server_label,
|
|
133
142
|
* address, activeProject }`. `local_label` is the local wallet/profile
|
|
@@ -208,7 +217,7 @@ export type * from "./namespaces/jobs.js";
|
|
|
208
217
|
export type * from "./namespaces/operator.js";
|
|
209
218
|
export { OperatorSession } from "./namespaces/operator-session.js";
|
|
210
219
|
export type * from "./namespaces/operator-session.js";
|
|
211
|
-
export {
|
|
220
|
+
export { Orgs, ScopedOrg, OrgMembers, OrgInvites } from "./namespaces/org.js";
|
|
212
221
|
export type * from "./namespaces/org.types.js";
|
|
213
222
|
export { Grants } from "./namespaces/grants.js";
|
|
214
223
|
export type * from "./namespaces/grants.types.js";
|