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.
Files changed (35) hide show
  1. package/lib/operator.mjs +103 -13
  2. package/lib/org.mjs +150 -67
  3. package/lib/projects.mjs +20 -5
  4. package/package.json +1 -1
  5. package/sdk/dist/control-plane-credentials.d.ts +1 -1
  6. package/sdk/dist/control-plane-credentials.js +1 -1
  7. package/sdk/dist/index.d.ts +16 -7
  8. package/sdk/dist/index.d.ts.map +1 -1
  9. package/sdk/dist/index.js +19 -8
  10. package/sdk/dist/index.js.map +1 -1
  11. package/sdk/dist/namespaces/operator.d.ts +101 -0
  12. package/sdk/dist/namespaces/operator.d.ts.map +1 -1
  13. package/sdk/dist/namespaces/operator.js +60 -1
  14. package/sdk/dist/namespaces/operator.js.map +1 -1
  15. package/sdk/dist/namespaces/org.d.ts +73 -35
  16. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  17. package/sdk/dist/namespaces/org.js +122 -71
  18. package/sdk/dist/namespaces/org.js.map +1 -1
  19. package/sdk/dist/namespaces/org.types.d.ts +51 -17
  20. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  21. package/sdk/dist/namespaces/org.types.js +7 -4
  22. package/sdk/dist/namespaces/org.types.js.map +1 -1
  23. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  24. package/sdk/dist/namespaces/projects.js +2 -0
  25. package/sdk/dist/namespaces/projects.js.map +1 -1
  26. package/sdk/dist/namespaces/projects.types.d.ts +8 -0
  27. package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
  28. package/sdk/dist/node/index.d.ts +3 -1
  29. package/sdk/dist/node/index.d.ts.map +1 -1
  30. package/sdk/dist/node/index.js +2 -1
  31. package/sdk/dist/node/index.js.map +1 -1
  32. package/sdk/dist/node/operator-claim.d.ts +49 -0
  33. package/sdk/dist/node/operator-claim.d.ts.map +1 -0
  34. package/sdk/dist/node/operator-claim.js +77 -0
  35. 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 { normalizeArgv, hasHelp, assertKnownFlags } from "./argparse.mjs";
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 Sign in via the browser. Default = device-flow READ session (powers
61
- 'overview'). --loopback = write-capable control-plane session
62
- (aws-sso-style, passkey-fresh). --step-up = re-mint a fresh write session.
63
- overview Account view across ALL wallets controlling your email (requires login)
64
- whoami Show the cached session(s) — local, no network
65
- logout Revoke the session server-side and clear the local cache(s)
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
- res.writeHead(200, { "content-type": "text/html" });
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
- cleanup();
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
- function cleanup() {
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
- cleanup();
209
+ close();
186
210
  rejectCode(new Error("timed out waiting for browser approval"));
187
211
  }, timeoutMs);
188
212
  server.on("error", (e) => {
189
- cleanup();
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: cleanup };
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 — org-owned control plane: identity, membership, invites
13
+ const HELP = `run402 org — organizations: create, label, membership, invites
14
14
 
15
15
  Usage:
16
- run402 org whoami
16
+ run402 org create [--name <label>]
17
17
  run402 org list
18
- run402 org audit <billing_account> [--limit N] [--before <cursor>]
19
- run402 org member list <billing_account>
20
- run402 org member add <billing_account> <wallet> [--role <role>]
21
- run402 org member role <billing_account> <principal_id> <role>
22
- run402 org member rm <billing_account> <principal_id>
23
- run402 org invite list <billing_account>
24
- run402 org invite create <billing_account> <email> [--role <role>] [--ttl-hours N]
25
- run402 org invite rm <billing_account> <principal_id>
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
- whoami Resolved principal + org memberships (GET /agent/v1/whoami)
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; the org (billing account) owns projects. Membership/role authorizes.
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 whoami
42
- run402 org member list ba_abc
43
- run402 org member add ba_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
44
- run402 org member role ba_abc prn_xyz owner
45
- run402 org invite create ba_abc dev@example.com --role developer
46
- run402 org audit ba_abc --limit 50
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
- whoami: `run402 org whoamiresolved principal + org memberships
59
+ create: `run402 org createcreate an empty org (prototype tier; you become owner)
51
60
 
52
61
  Usage:
53
- run402 org whoami
62
+ run402 org create [--name <label>]
54
63
 
55
- Calls GET /agent/v1/whoami. Returns the control-plane principal (id/type/displayName/createdAt),
56
- authenticator_id, and every org membership with role + status. REMOTE identity; for local
57
- wallet/profile state use 'run402 status'.
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 <billing_account>
68
- run402 org member add <billing_account> <wallet> [--role <role>]
69
- run402 org member role <billing_account> <principal_id> <role>
70
- run402 org member rm <billing_account> <principal_id>
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 <billing_account>
79
- run402 org invite create <billing_account> <email> [--role <role>] [--ttl-hours N]
80
- run402 org invite rm <billing_account> <principal_id>
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 <billing_account> [--limit N] [--before <cursor>]
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: whoami / list / audit ───────────────────────────────────────────
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().org.whoami(), null, 2));
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 list(args) {
166
+ async function get(args) {
108
167
  const a = normalizeArgv(args);
109
168
  assertKnownFlags(a, ["--help", "-h"]);
110
- requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 org list" });
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({ orgs: await getSdk().org.list() }, null, 2));
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 [ba] = requirePositionalCount(a, valueFlags, {
202
+ const [org] = requirePositionalCount(a, valueFlags, {
123
203
  min: 1,
124
204
  max: 1,
125
- command: "run402 org audit <billing_account>",
126
- missing: "Missing <billing_account>.",
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(ba, { limit, before: before ?? undefined });
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 [ba] = requirePositionalCount(a, [], {
157
- min: 1, max: 1, command: "run402 org member list <billing_account>", missing: "Missing <billing_account>.",
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(ba) }, null, 2));
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 [ba, wallet] = requirePositionalCount(a, ["--role"], {
172
- min: 2, max: 2, command: "run402 org member add <billing_account> <wallet> [--role <role>]",
173
- missing: "Missing <billing_account> and/or <wallet>.",
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(ba, { wallet, role: role || undefined });
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 [ba, principalId, role] = requirePositionalCount(a, [], {
188
- min: 3, max: 3, command: "run402 org member role <billing_account> <principal_id> <role>",
189
- missing: "Missing <billing_account>, <principal_id>, and/or <role>.",
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(ba, principalId, role), null, 2));
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 [ba, principalId] = requirePositionalCount(a, [], {
203
- min: 2, max: 2, command: "run402 org member rm <billing_account> <principal_id>",
204
- missing: "Missing <billing_account> and/or <principal_id>.",
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(ba, principalId), null, 2));
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 [ba] = requirePositionalCount(a, [], {
235
- min: 1, max: 1, command: "run402 org invite list <billing_account>", missing: "Missing <billing_account>.",
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(ba) }, null, 2));
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 [ba, email] = requirePositionalCount(a, valueFlags, {
252
- min: 2, max: 2, command: "run402 org invite create <billing_account> <email> [--role <role>]",
253
- missing: "Missing <billing_account> and/or <email>.",
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(ba, { email, role: role || "developer", inviteTtlHours });
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 [ba, principalId] = requirePositionalCount(a, [], {
269
- min: 2, max: 2, command: "run402 org invite rm <billing_account> <principal_id>",
270
- missing: "Missing <billing_account> and/or <principal_id>.",
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(ba, principalId), null, 2));
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 "whoami": await whoami(args); break;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "2.41.0",
3
+ "version": "2.42.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.org.whoami(); // resolves the principal + memberships
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