run402 4.8.0 → 4.10.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 (64) hide show
  1. package/cli.mjs +20 -8
  2. package/lib/admin.mjs +18 -25
  3. package/lib/agent.mjs +2 -2
  4. package/lib/ai.mjs +2 -2
  5. package/lib/allowance.mjs +2 -2
  6. package/lib/apps.mjs +69 -51
  7. package/lib/archives.mjs +2 -7
  8. package/lib/argparse.mjs +131 -3
  9. package/lib/asset-wire.mjs +59 -0
  10. package/lib/assets.mjs +14 -12
  11. package/lib/auth.mjs +2 -2
  12. package/lib/billing.mjs +17 -13
  13. package/lib/branches.mjs +22 -23
  14. package/lib/cache.mjs +2 -6
  15. package/lib/cdn.mjs +2 -2
  16. package/lib/ci.mjs +4 -1
  17. package/lib/cloud.mjs +27 -28
  18. package/lib/command-manifest.mjs +352 -0
  19. package/lib/contracts.mjs +49 -11
  20. package/lib/core.mjs +3 -13
  21. package/lib/credentials.mjs +3 -13
  22. package/lib/deploy-v2.mjs +21 -8
  23. package/lib/deploy.mjs +2 -5
  24. package/lib/doctor.mjs +32 -4
  25. package/lib/domains.mjs +2 -2
  26. package/lib/email.mjs +2 -2
  27. package/lib/functions.mjs +100 -55
  28. package/lib/grants.mjs +42 -22
  29. package/lib/image.mjs +2 -2
  30. package/lib/jobs.mjs +18 -11
  31. package/lib/message.mjs +2 -2
  32. package/lib/notifications.mjs +6 -12
  33. package/lib/operator.mjs +2 -7
  34. package/lib/org.mjs +88 -49
  35. package/lib/projects.mjs +68 -39
  36. package/lib/secrets.mjs +99 -41
  37. package/lib/service.mjs +3 -2
  38. package/lib/sites.mjs +2 -2
  39. package/lib/snapshots.mjs +22 -24
  40. package/lib/subdomains.mjs +2 -2
  41. package/lib/tier.mjs +2 -2
  42. package/lib/transfer.mjs +2 -1
  43. package/lib/up.mjs +12 -6
  44. package/lib/wallets.mjs +6 -9
  45. package/lib/webhook-secret.mjs +3 -8
  46. package/lib/webhooks.mjs +2 -2
  47. package/package.json +1 -1
  48. package/sdk/dist/actions.d.ts +20 -1
  49. package/sdk/dist/actions.d.ts.map +1 -1
  50. package/sdk/dist/actions.js.map +1 -1
  51. package/sdk/dist/namespaces/projects.d.ts +2 -1
  52. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  53. package/sdk/dist/namespaces/projects.js +8 -12
  54. package/sdk/dist/namespaces/projects.js.map +1 -1
  55. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  56. package/sdk/dist/node/actions-node.js +114 -8
  57. package/sdk/dist/node/actions-node.js.map +1 -1
  58. package/sdk/dist/node/deploy-manifest.d.ts +36 -0
  59. package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
  60. package/sdk/dist/node/deploy-manifest.js +112 -3
  61. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  62. package/sdk/dist/node/index.d.ts +1 -1
  63. package/sdk/dist/node/index.d.ts.map +1 -1
  64. package/sdk/dist/node/index.js.map +1 -1
package/lib/image.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { writeFileSync } from "fs";
2
2
  import { getSdk } from "./sdk.mjs";
3
3
  import { reportSdkError, fail } from "./sdk-errors.mjs";
4
- import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
4
+ import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
5
5
 
6
6
  const HELP = `run402 image — Generate AI images via x402 micropayments
7
7
 
@@ -67,7 +67,7 @@ export async function run(sub, args) {
67
67
  }
68
68
 
69
69
  if (sub !== "generate") {
70
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown image subcommand: ${sub}`, hint: "Run `run402 image --help` for usage.", details: { command: "image", subcommand: sub } });
70
+ failUnknownSubcommand("image", sub);
71
71
  }
72
72
 
73
73
  const parsedArgs = normalizeArgv(args);
package/lib/jobs.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  positionalArgs,
15
15
  requirePositionalCount,
16
16
  validateRegularFile,
17
+ failUnknownSubcommand,
17
18
  } from "./argparse.mjs";
18
19
 
19
20
  const HELP = `run402 jobs — Submit and inspect platform-managed jobs
@@ -27,7 +28,7 @@ Subcommands:
27
28
  logs <job_id> Read job logs
28
29
  cancel <job_id> Cancel a queued or running job
29
30
  purge Purge all job runs for the project
30
- artifacts get <job_id> <file> Download a completed job's artifact
31
+ artifacts get <job_id> --file <name> Download a completed job's artifact (legacy: <job_id> <file>)
31
32
 
32
33
  Examples:
33
34
  run402 jobs submit --file job.json
@@ -120,6 +121,9 @@ recorded filenames on a given run.
120
121
  "artifacts get": `run402 jobs artifacts get — Download a completed job's artifact
121
122
 
122
123
  Usage:
124
+ run402 jobs artifacts get <job_id> --file <name> --output <path> [--project <id>]
125
+
126
+ Legacy (still supported):
123
127
  run402 jobs artifacts get <job_id> <file> --output <path> [--project <id>]
124
128
 
125
129
  Options:
@@ -355,24 +359,27 @@ async function artifactsGet(args = []) {
355
359
  process.exit(0);
356
360
  }
357
361
  const parsed = normalizeArgv(args);
358
- const valueFlags = ["--project", "--output", "-o"];
359
- assertKnownFlags(parsed, ["--project", "--output", "-o", "--help", "-h"], valueFlags);
362
+ const valueFlags = ["--project", "--output", "-o", "--file"];
363
+ assertKnownFlags(parsed, ["--project", "--output", "-o", "--file", "--help", "-h"], valueFlags);
364
+ const fileFlag = flagValue(parsed, "--file");
365
+ const expected = fileFlag ? 1 : 2;
360
366
  const positionals = positionalArgs(parsed, valueFlags);
361
- if (positionals.length < 2) {
367
+ if (positionals.length < expected) {
362
368
  fail({
363
369
  code: "BAD_USAGE",
364
370
  message: "Missing job_id and/or artifact filename.",
365
- hint: "Use `run402 jobs artifacts get <job_id> <file> --output <path>`.",
371
+ hint: "Use `run402 jobs artifacts get <job_id> --file <name> --output <path>`.",
366
372
  });
367
373
  }
368
- if (positionals.length > 2) {
374
+ if (positionals.length > expected) {
369
375
  fail({
370
376
  code: "BAD_USAGE",
371
- message: `Unexpected argument: ${positionals[2]}`,
372
- hint: "Use `run402 jobs artifacts get <job_id> <file> --output <path>`.",
377
+ message: `Unexpected argument: ${positionals[expected]}`,
378
+ hint: "Use `run402 jobs artifacts get <job_id> --file <name> --output <path>`.",
373
379
  });
374
380
  }
375
- const [jobId, filename] = positionals;
381
+ const jobId = positionals[0];
382
+ const filename = fileFlag ?? positionals[1];
376
383
  const output = flagValue(parsed, "--output") ?? flagValue(parsed, "-o");
377
384
  if (!output) {
378
385
  fail({
@@ -421,7 +428,7 @@ async function artifacts(args = []) {
421
428
  await artifactsGet(args.slice(1));
422
429
  return;
423
430
  }
424
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown jobs artifacts action: ${action}`, hint: "Run `run402 jobs artifacts --help` for usage.", details: { command: "jobs artifacts", subcommand: action } });
431
+ failUnknownSubcommand("jobs artifacts", action);
425
432
  }
426
433
 
427
434
  function splitJobIdArg(args = [], valueFlags = []) {
@@ -484,6 +491,6 @@ export async function run(sub, args = []) {
484
491
  await purge(args);
485
492
  break;
486
493
  default:
487
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown jobs subcommand: ${sub}`, hint: "Run `run402 jobs --help` for usage.", details: { command: "jobs", subcommand: sub } });
494
+ failUnknownSubcommand("jobs", sub);
488
495
  }
489
496
  }
package/lib/message.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { allowanceAuthHeaders } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
3
  import { reportSdkError, fail } from "./sdk-errors.mjs";
4
- import { assertKnownFlags, normalizeArgv } from "./argparse.mjs";
4
+ import { assertKnownFlags, normalizeArgv, failUnknownSubcommand } from "./argparse.mjs";
5
5
 
6
6
  const HELP = `run402 message — Send messages to Run402 developers
7
7
 
@@ -84,7 +84,7 @@ export async function run(sub, args) {
84
84
  process.exit(0);
85
85
  }
86
86
  if (sub !== "send") {
87
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown message subcommand: ${sub}`, hint: "Run `run402 message --help` for usage.", details: { command: "message", subcommand: sub } });
87
+ failUnknownSubcommand("message", sub);
88
88
  }
89
89
  const parsedArgs = normalizeArgv(args);
90
90
  assertKnownFlags(parsedArgs, ["--help", "-h"]);
@@ -40,6 +40,7 @@ import {
40
40
  normalizeArgv,
41
41
  positionalArgs,
42
42
  requirePositionalCount,
43
+ failUnknownSubcommand,
43
44
  } from "./argparse.mjs";
44
45
 
45
46
  const HELP = `run402 notifications — Operator health notifications + Telegram channel/rules
@@ -435,9 +436,8 @@ async function runChannels(args) {
435
436
  await channelsRevoke(rest);
436
437
  return;
437
438
  }
438
- fail({
439
- code: "BAD_USAGE",
440
- message: `Unknown 'notifications channels' action: ${channelsAction}. Try connect | list | revoke.`,
439
+ failUnknownSubcommand("notifications channels", channelsAction, {
440
+ hint: "Run `run402 notifications channels --help` for usage.",
441
441
  });
442
442
  }
443
443
 
@@ -539,9 +539,8 @@ async function runRules(args) {
539
539
  await rulesRm(rest);
540
540
  return;
541
541
  }
542
- fail({
543
- code: "BAD_USAGE",
544
- message: `Unknown 'notifications rules' action: ${rulesAction}. Try add | list | rm.`,
542
+ failUnknownSubcommand("notifications rules", rulesAction, {
543
+ hint: "Run `run402 notifications rules --help` for usage.",
545
544
  });
546
545
  }
547
546
 
@@ -586,12 +585,7 @@ export async function run(sub, args) {
586
585
  await test(args);
587
586
  return;
588
587
  default:
589
- fail({
590
- code: "UNKNOWN_SUBCOMMAND",
591
- message: `Unknown notifications subcommand: ${sub}`,
592
- hint: "Run `run402 notifications --help` for usage.",
593
- details: { command: "notifications", subcommand: sub },
594
- });
588
+ failUnknownSubcommand("notifications", sub);
595
589
  }
596
590
  }
597
591
 
package/lib/operator.mjs CHANGED
@@ -24,7 +24,7 @@ import { randomBytes, createHash } from "node:crypto";
24
24
  import { fail, reportSdkError } from "./sdk-errors.mjs";
25
25
  import { getSdk } from "./sdk.mjs";
26
26
  import { claimWalletOrg, isStepUpRequired, isOperatorApprovalRequired } from "#sdk/node";
27
- import { normalizeArgv, hasHelp, assertKnownFlags, flagValue } from "./argparse.mjs";
27
+ import { normalizeArgv, hasHelp, assertKnownFlags, flagValue, failUnknownSubcommand } from "./argparse.mjs";
28
28
  import {
29
29
  saveOperatorSession,
30
30
  clearOperatorSession,
@@ -766,11 +766,6 @@ export async function run(sub, args = []) {
766
766
  await status(args);
767
767
  break;
768
768
  default:
769
- fail({
770
- code: "UNKNOWN_SUBCOMMAND",
771
- message: `Unknown operator subcommand: ${sub}`,
772
- hint: "Run `run402 operator --help` for usage.",
773
- details: { command: "operator", subcommand: sub },
774
- });
769
+ failUnknownSubcommand("operator", sub, { extraSubcommands: ["status"] });
775
770
  }
776
771
  }
package/lib/org.mjs CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  flagValue,
7
7
  parseIntegerFlag,
8
8
  requirePositionalCount,
9
+ failUnknownSubcommand,
9
10
  } from "./argparse.mjs";
10
11
 
11
12
  const ROLE_LIST = "owner | admin | developer | billing | viewer";
@@ -16,17 +17,20 @@ Usage:
16
17
  run402 org create [--name <label>]
17
18
  run402 org list
18
19
  run402 org get <org_id>
19
- run402 org rename <org_id> <display_name> (or: --clear to remove the label)
20
- run402 org payout-wallet <org_id> <wallet_address> (or: --clear to remove the explicit default)
20
+ run402 org rename <org_id> --name <display_name> (or: --clear to remove the label)
21
+ run402 org payout-wallet <org_id> --wallet <wallet_address> (or: --clear to remove the explicit default)
21
22
  run402 org whoami
22
23
  run402 org audit <org_id> [--limit N] [--after <cursor>] [--before <cursor>]
23
24
  run402 org member list <org_id>
24
- run402 org member add <org_id> <wallet_address> [--role <role>]
25
- run402 org member role <org_id> <principal_id> <role>
26
- run402 org member rm <org_id> <principal_id>
25
+ run402 org member add <org_id> --wallet <wallet_address> [--role <role>]
26
+ run402 org member role <org_id> --principal <principal_id> --role <role>
27
+ run402 org member rm <org_id> --principal <principal_id>
27
28
  run402 org invite list <org_id>
28
- run402 org invite create <org_id> <email> [--role <role>] [--ttl-hours N]
29
- run402 org invite rm <org_id> <principal_id>
29
+ run402 org invite create <org_id> --email <email> [--role <role>] [--ttl-hours N]
30
+ run402 org invite rm <org_id> --principal <principal_id>
31
+
32
+ Legacy (still supported): the second attribute may also be passed positionally,
33
+ e.g. run402 org member add <org_id> <wallet_address>.
30
34
 
31
35
  Subcommands:
32
36
  create Create an empty org on the prototype tier (you become owner)
@@ -87,18 +91,24 @@ non-revealing 403.
87
91
  rename: `run402 org rename — set or clear an org's display label (owner-only)
88
92
 
89
93
  Usage:
90
- run402 org rename <org_id> <display_name>
94
+ run402 org rename <org_id> --name <display_name>
91
95
  run402 org rename <org_id> --clear
92
96
 
97
+ Legacy (still supported):
98
+ run402 org rename <org_id> <display_name>
99
+
93
100
  Owner-only + step-up gated. Pass --clear (or an empty display_name) to remove
94
101
  the label. Output includes the updated tier and lease timestamps.
95
102
  `,
96
103
  "payout-wallet": `run402 org payout-wallet — set or clear the tenant route payout wallet
97
104
 
98
105
  Usage:
99
- run402 org payout-wallet <org_id> <wallet_address>
106
+ run402 org payout-wallet <org_id> --wallet <wallet_address>
100
107
  run402 org payout-wallet <org_id> --clear
101
108
 
109
+ Legacy (still supported):
110
+ run402 org payout-wallet <org_id> <wallet_address>
111
+
102
112
  Admin/owner-only + step-up gated. The wallet must already be active and linked
103
113
  to the same org. This wallet receives x402 settlement for function web routes
104
114
  that declare pricing.pay_to = "org_default_payout". Pass --clear to remove the
@@ -120,9 +130,9 @@ for local wallet/profile state use 'run402 status'.
120
130
 
121
131
  Usage:
122
132
  run402 org member list <org_id>
123
- run402 org member add <org_id> <wallet_address> [--role <role>]
124
- run402 org member role <org_id> <principal_id> <role>
125
- run402 org member rm <org_id> <principal_id>
133
+ run402 org member add <org_id> --wallet <wallet_address> [--role <role>]
134
+ run402 org member role <org_id> --principal <principal_id> --role <role>
135
+ run402 org member rm <org_id> --principal <principal_id>
126
136
 
127
137
  Roles: ${ROLE_LIST} (add defaults to developer). Mutations require an active owner.
128
138
  Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
@@ -131,8 +141,11 @@ Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
131
141
 
132
142
  Usage:
133
143
  run402 org invite list <org_id>
134
- run402 org invite create <org_id> <email> [--role <role>] [--ttl-hours N]
135
- run402 org invite rm <org_id> <principal_id>
144
+ run402 org invite create <org_id> --email <email> [--role <role>] [--ttl-hours N]
145
+ run402 org invite rm <org_id> --principal <principal_id>
146
+
147
+ Legacy (still supported): the second attribute may also be passed positionally,
148
+ e.g. run402 org member add <org_id> <wallet_address>.
136
149
 
137
150
  An invite is claimed at the recipient's first login. Mutations require an active owner
138
151
  (plus step-up when driven by a control-plane session).
@@ -200,16 +213,18 @@ async function get(args) {
200
213
 
201
214
  async function rename(args) {
202
215
  const a = normalizeArgv(args);
203
- assertKnownFlags(a, ["--clear", "--help", "-h"]);
216
+ assertKnownFlags(a, ["--name", "--clear", "--help", "-h"], ["--name"]);
204
217
  const clear = a.includes("--clear");
205
- const positionals = requirePositionalCount(a, [], {
206
- min: clear ? 1 : 2,
207
- max: clear ? 1 : 2,
208
- command: "run402 org rename <org_id> <display_name>",
209
- missing: clear ? "Missing <org_id>." : "Missing <org_id> and/or <display_name> (or pass --clear).",
218
+ const nameFlag = flagValue(a, "--name");
219
+ const single = clear || nameFlag !== null;
220
+ const positionals = requirePositionalCount(a, ["--name"], {
221
+ min: single ? 1 : 2,
222
+ max: single ? 1 : 2,
223
+ command: "run402 org rename <org_id> --name <display_name>",
224
+ missing: single ? "Missing <org_id>." : "Missing <org_id> and/or <display_name> (use --name, or pass --clear).",
210
225
  });
211
226
  const org = positionals[0];
212
- const displayName = clear ? null : positionals[1];
227
+ const displayName = clear ? null : (nameFlag ?? positionals[1]);
213
228
  try {
214
229
  console.log(JSON.stringify(await getSdk().org(org).rename(displayName), null, 2));
215
230
  } catch (err) {
@@ -219,16 +234,18 @@ async function rename(args) {
219
234
 
220
235
  async function payoutWallet(args) {
221
236
  const a = normalizeArgv(args);
222
- assertKnownFlags(a, ["--clear", "--help", "-h"]);
237
+ assertKnownFlags(a, ["--wallet", "--clear", "--help", "-h"], ["--wallet"]);
223
238
  const clear = a.includes("--clear");
224
- const positionals = requirePositionalCount(a, [], {
225
- min: clear ? 1 : 2,
226
- max: clear ? 1 : 2,
227
- command: "run402 org payout-wallet <org_id> <wallet_address>",
228
- missing: clear ? "Missing <org_id>." : "Missing <org_id> and/or <wallet_address> (or pass --clear).",
239
+ const walletFlag = flagValue(a, "--wallet");
240
+ const single = clear || walletFlag !== null;
241
+ const positionals = requirePositionalCount(a, ["--wallet"], {
242
+ min: single ? 1 : 2,
243
+ max: single ? 1 : 2,
244
+ command: "run402 org payout-wallet <org_id> --wallet <wallet_address>",
245
+ missing: single ? "Missing <org_id>." : "Missing <org_id> and/or <wallet_address> (use --wallet, or pass --clear).",
229
246
  });
230
247
  const org = positionals[0];
231
- const walletAddress = clear ? null : positionals[1];
248
+ const walletAddress = clear ? null : (walletFlag ?? positionals[1]);
232
249
  try {
233
250
  console.log(JSON.stringify(await getSdk().org(org).setPayoutWallet({ walletAddress }), null, 2));
234
251
  } catch (err) {
@@ -292,12 +309,16 @@ async function runMember(args) {
292
309
 
293
310
  if (memberAction === "add") {
294
311
  const a = normalizeArgv(rest);
295
- assertKnownFlags(a, ["--role", "--help", "-h"], ["--role"]);
312
+ assertKnownFlags(a, ["--role", "--wallet", "--help", "-h"], ["--role", "--wallet"]);
296
313
  const role = flagValue(a, "--role");
297
- const [org, wallet] = requirePositionalCount(a, ["--role"], {
298
- min: 2, max: 2, command: "run402 org member add <org_id> <wallet_address> [--role <role>]",
299
- missing: "Missing <org_id> and/or <wallet_address>.",
314
+ const walletFlag = flagValue(a, "--wallet");
315
+ const count = walletFlag ? 1 : 2;
316
+ const pos = requirePositionalCount(a, ["--role", "--wallet"], {
317
+ min: count, max: count, command: "run402 org member add <org_id> --wallet <wallet_address> [--role <role>]",
318
+ missing: walletFlag ? "Missing <org_id>." : "Missing <org_id> and/or <wallet_address> (use --wallet).",
300
319
  });
320
+ const org = pos[0];
321
+ const wallet = walletFlag ?? pos[1];
301
322
  try {
302
323
  const res = await getSdk().org(org).members.add({ wallet, role: role || undefined });
303
324
  console.log(JSON.stringify(res, null, 2));
@@ -309,11 +330,17 @@ async function runMember(args) {
309
330
 
310
331
  if (memberAction === "role") {
311
332
  const a = normalizeArgv(rest);
312
- assertKnownFlags(a, ["--help", "-h"]);
313
- const [org, principalId, role] = requirePositionalCount(a, [], {
314
- min: 3, max: 3, command: "run402 org member role <org_id> <principal_id> <role>",
315
- missing: "Missing <org_id>, <principal_id>, and/or <role>.",
333
+ assertKnownFlags(a, ["--principal", "--role", "--help", "-h"], ["--principal", "--role"]);
334
+ const principalFlag = flagValue(a, "--principal");
335
+ const roleFlag = flagValue(a, "--role");
336
+ const count = 3 - (principalFlag ? 1 : 0) - (roleFlag ? 1 : 0);
337
+ const pos = requirePositionalCount(a, ["--principal", "--role"], {
338
+ min: count, max: count, command: "run402 org member role <org_id> --principal <principal_id> --role <role>",
339
+ missing: "Missing <org_id>, <principal_id> (--principal), and/or <role> (--role).",
316
340
  });
341
+ const org = pos[0];
342
+ const principalId = principalFlag ?? pos[1];
343
+ const role = roleFlag ?? (principalFlag ? pos[1] : pos[2]);
317
344
  try {
318
345
  console.log(JSON.stringify(await getSdk().org(org).members.setRole(principalId, { role }), null, 2));
319
346
  } catch (err) {
@@ -324,11 +351,15 @@ async function runMember(args) {
324
351
 
325
352
  if (memberAction === "rm") {
326
353
  const a = normalizeArgv(rest);
327
- assertKnownFlags(a, ["--help", "-h"]);
328
- const [org, principalId] = requirePositionalCount(a, [], {
329
- min: 2, max: 2, command: "run402 org member rm <org_id> <principal_id>",
330
- missing: "Missing <org_id> and/or <principal_id>.",
354
+ assertKnownFlags(a, ["--principal", "--help", "-h"], ["--principal"]);
355
+ const principalFlag = flagValue(a, "--principal");
356
+ const count = principalFlag ? 1 : 2;
357
+ const pos = requirePositionalCount(a, ["--principal"], {
358
+ min: count, max: count, command: "run402 org member rm <org_id> --principal <principal_id>",
359
+ missing: principalFlag ? "Missing <org_id>." : "Missing <org_id> and/or <principal_id> (use --principal).",
331
360
  });
361
+ const org = pos[0];
362
+ const principalId = principalFlag ?? pos[1];
332
363
  try {
333
364
  console.log(JSON.stringify(await getSdk().org(org).members.revoke(principalId), null, 2));
334
365
  } catch (err) {
@@ -370,14 +401,18 @@ async function runInvite(args) {
370
401
 
371
402
  if (inviteAction === "create") {
372
403
  const a = normalizeArgv(rest);
373
- const valueFlags = ["--role", "--ttl-hours"];
404
+ const valueFlags = ["--role", "--ttl-hours", "--email"];
374
405
  assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
375
406
  const role = flagValue(a, "--role");
376
407
  const ttlFlag = flagValue(a, "--ttl-hours");
377
- const [org, email] = requirePositionalCount(a, valueFlags, {
378
- min: 2, max: 2, command: "run402 org invite create <org_id> <email> [--role <role>]",
379
- missing: "Missing <org_id> and/or <email>.",
408
+ const emailFlag = flagValue(a, "--email");
409
+ const count = emailFlag ? 1 : 2;
410
+ const pos = requirePositionalCount(a, valueFlags, {
411
+ min: count, max: count, command: "run402 org invite create <org_id> --email <email> [--role <role>]",
412
+ missing: emailFlag ? "Missing <org_id>." : "Missing <org_id> and/or <email> (use --email).",
380
413
  });
414
+ const org = pos[0];
415
+ const email = emailFlag ?? pos[1];
381
416
  const inviteTtlHours = ttlFlag === null ? undefined : parseIntegerFlag("--ttl-hours", ttlFlag, { min: 1, max: 8760 });
382
417
  try {
383
418
  const res = await getSdk().org(org).invites.create({ email, role: role || "developer", inviteTtlHours });
@@ -390,11 +425,15 @@ async function runInvite(args) {
390
425
 
391
426
  if (inviteAction === "rm") {
392
427
  const a = normalizeArgv(rest);
393
- assertKnownFlags(a, ["--help", "-h"]);
394
- const [org, principalId] = requirePositionalCount(a, [], {
395
- min: 2, max: 2, command: "run402 org invite rm <org_id> <principal_id>",
396
- missing: "Missing <org_id> and/or <principal_id>.",
428
+ assertKnownFlags(a, ["--principal", "--help", "-h"], ["--principal"]);
429
+ const principalFlag = flagValue(a, "--principal");
430
+ const count = principalFlag ? 1 : 2;
431
+ const pos = requirePositionalCount(a, ["--principal"], {
432
+ min: count, max: count, command: "run402 org invite rm <org_id> --principal <principal_id>",
433
+ missing: principalFlag ? "Missing <org_id>." : "Missing <org_id> and/or <principal_id> (use --principal).",
397
434
  });
435
+ const org = pos[0];
436
+ const principalId = principalFlag ?? pos[1];
398
437
  try {
399
438
  console.log(JSON.stringify(await getSdk().org(org).invites.revoke(principalId), null, 2));
400
439
  } catch (err) {
@@ -434,6 +473,6 @@ export async function run(sub, args) {
434
473
  case "whoami": await whoami(args); break;
435
474
  case "audit": await audit(args); break;
436
475
  default:
437
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown org subcommand: ${sub}`, hint: "Run `run402 org --help` for usage.", details: { command: "org", subcommand: sub } });
476
+ failUnknownSubcommand("org", sub);
438
477
  }
439
478
  }
package/lib/projects.mjs CHANGED
@@ -5,7 +5,7 @@ import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.
5
5
  import { withAutoApprove } from "./operator.mjs";
6
6
  import { getSdk } from "./sdk.mjs";
7
7
  import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
8
- import { assertKnownFlags, failBadProjectId, flagValue, hasHelp, normalizeArgv, positionalArgs, resolvePositionalProject, validateRegularFile } from "./argparse.mjs";
8
+ import { assertKnownFlags, failBadProjectId, flagValue, hasHelp, normalizeArgv, positionalArgs, resolveProjectSelector, validateRegularFile, failUnknownSubcommand } from "./argparse.mjs";
9
9
 
10
10
  const HELP = `run402 projects — Manage your deployed Run402 projects
11
11
 
@@ -21,8 +21,8 @@ Subcommands:
21
21
  tenant-payments [project_id] [--status <s>] List redacted tenant x402 payments for priced routes
22
22
  get [id] Authoritative server read: status, org, tier, active deploy, mailbox, usage vs limits (live; no keys)
23
23
  current Show the active project pointer and validation status
24
- sql [id] "<query>" [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
25
- rest [id] <table> [params] Query a table via the REST API (PostgREST)
24
+ sql "<query>" [--project <id>] [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
25
+ rest <table> [--query "<params>"] [--project <id>] Query a table via the REST API (PostgREST)
26
26
  usage [id] Show compute/storage usage for a project
27
27
  costs [id] [--window <w>] Show admin-only per-project revenue/cost/margin
28
28
  schema [id] Inspect the database schema
@@ -210,23 +210,25 @@ Examples:
210
210
  sql: `run402 projects sql — Run a SQL query against a project's database
211
211
 
212
212
  Usage:
213
- run402 projects sql [id] "<query>" [options]
214
- run402 projects sql [id] --file <path> [options]
213
+ run402 projects sql "<query>" [--project <id>] [options]
214
+ run402 projects sql --file <path> [--project <id>] [options]
215
+
216
+ Legacy (still supported):
217
+ run402 projects sql <prj_id> "<query>" [options]
215
218
 
216
219
  Arguments:
217
- [id] Project ID (defaults to the active project if omitted;
218
- must start with 'prj_' — any other first arg is treated
219
- as the query instead)
220
220
  <query> Inline SQL query (quote it to preserve spaces)
221
221
 
222
222
  Options:
223
+ --project <id> Project ID (defaults to the active project; a leading
224
+ prj_... positional is also still accepted)
223
225
  --file <path> Read SQL from a file instead of an inline query
224
226
  --params '<json>' JSON array of parameters for a parameterized query
225
227
 
226
228
  Examples:
227
- run402 projects sql prj_abc123 "SELECT * FROM users LIMIT 5"
228
- run402 projects sql prj_abc123 "SELECT * FROM users WHERE id = $1" --params '[42]'
229
- run402 projects sql prj_abc123 --file setup.sql
229
+ run402 projects sql "SELECT * FROM users LIMIT 5" --project prj_abc123
230
+ run402 projects sql "SELECT * FROM users WHERE id = $1" --params '[42]'
231
+ run402 projects sql --file setup.sql --project prj_abc123
230
232
  `,
231
233
  costs: `run402 projects costs — Show admin-only per-project finance
232
234
 
@@ -411,6 +413,12 @@ async function applyExpose(projectId, args = []) {
411
413
 
412
414
  async function validateExpose(args = []) {
413
415
  let projectId = null;
416
+ let projectFlag = null;
417
+ if (args.includes("--project")) {
418
+ projectFlag = flagValue(args, "--project");
419
+ const idx = args.indexOf("--project");
420
+ args = [...args.slice(0, idx), ...args.slice(idx + 2)];
421
+ }
414
422
  let file = null;
415
423
  let inline = null;
416
424
  let migrationFile = null;
@@ -473,8 +481,15 @@ async function validateExpose(args = []) {
473
481
  });
474
482
  }
475
483
 
484
+ if (projectFlag !== null && projectId !== null && projectFlag !== projectId) {
485
+ fail({
486
+ code: "BAD_USAGE",
487
+ message: `Conflicting project ids: --project ${projectFlag} vs positional ${projectId}`,
488
+ details: { project_flag: projectFlag, positional: projectId },
489
+ });
490
+ }
476
491
  const activeProjectId = getActiveProjectId();
477
- const project = projectId || activeProjectId || undefined;
492
+ const project = projectFlag || projectId || activeProjectId || undefined;
478
493
  if (!project) allowanceAuthHeaders("/projects/v1/expose/validate");
479
494
  const migration = migrationFile ? readFileSync(migrationFile, "utf-8") : migrationSql;
480
495
 
@@ -697,6 +712,10 @@ function toCliExposeValidationResult(data) {
697
712
  return { has_errors: hasErrors, ...rest };
698
713
  }
699
714
 
715
+ // The SDK now returns the gateway SQL envelope verbatim (snake_case
716
+ // row_count) — the wire shape is uniform end-to-end. This shim survives one
717
+ // release as belt-and-braces against a stale local SDK build that still
718
+ // camelCases, then can be deleted.
700
719
  function toCliSqlResult(data) {
701
720
  if (!data || typeof data !== "object" || Array.isArray(data)) return data;
702
721
  if ("row_count" in data) return data;
@@ -705,12 +724,15 @@ function toCliSqlResult(data) {
705
724
  return { ...rest, row_count: rowCount };
706
725
  }
707
726
 
708
- async function rest(projectId, table, queryParams) {
727
+ async function rest(projectId, restArgs = []) {
728
+ const positionals = positionalArgs(restArgs, FLAGS_BY_SUB.rest.values);
729
+ const table = positionals[0];
730
+ const queryParams = flagValue(restArgs, "--query") ?? positionals[1];
709
731
  if (!table) {
710
732
  fail({
711
733
  code: "BAD_USAGE",
712
- message: "Missing <table> argument. Usage: run402 projects rest [id] <table> [\"<query>\"]",
713
- hint: "Run 'run402 projects schema <id>' to list tables.",
734
+ message: "Missing <table> argument. Usage: run402 projects rest <table> [--query \"<query>\"] [--project <id>]",
735
+ hint: "Run 'run402 projects schema' to list tables.",
714
736
  });
715
737
  }
716
738
  try {
@@ -841,19 +863,26 @@ const FLAGS_BY_SUB = {
841
863
  values: ["--tier", "--name", "--org", "--idempotency-key"],
842
864
  },
843
865
  list: { known: ["--org", "--all"], values: ["--org"] },
844
- rename: { known: ["--name"], values: ["--name"] },
845
- "tenant-payments": { known: ["--status", "--limit", "--after"], values: ["--status", "--limit", "--after"] },
846
- sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
847
- costs: { known: ["--window"], values: ["--window"] },
848
- "apply-expose": { known: ["--file"], values: ["--file"] },
866
+ rename: { known: ["--project", "--name"], values: ["--project", "--name"] },
867
+ "tenant-payments": { known: ["--project", "--status", "--limit", "--after"], values: ["--project", "--status", "--limit", "--after"] },
868
+ get: { known: ["--project"], values: ["--project"] },
869
+ rest: { known: ["--project", "--query"], values: ["--project", "--query"] },
870
+ usage: { known: ["--project"], values: ["--project"] },
871
+ schema: { known: ["--project"], values: ["--project"] },
872
+ "get-expose": { known: ["--project"], values: ["--project"] },
873
+ "promote-user": { known: ["--project"], values: ["--project"] },
874
+ "demote-user": { known: ["--project"], values: ["--project"] },
875
+ sql: { known: ["--project", "--file", "--params"], values: ["--project", "--file", "--params"] },
876
+ costs: { known: ["--project", "--window"], values: ["--project", "--window"] },
877
+ "apply-expose": { known: ["--project", "--file"], values: ["--project", "--file"] },
849
878
  "validate-expose": {
850
- known: ["--file", "--migration-file", "--migration-sql"],
851
- values: ["--file", "--migration-file", "--migration-sql"],
879
+ known: ["--project", "--file", "--migration-file", "--migration-sql"],
880
+ values: ["--project", "--file", "--migration-file", "--migration-sql"],
852
881
  },
853
- delete: { known: ["--confirm"], values: [] },
882
+ delete: { known: ["--project", "--confirm"], values: ["--project"] },
854
883
  export: {
855
- known: ["--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout", "--wait", "--json", "--json-stream"],
856
- values: ["--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout"],
884
+ known: ["--project", "--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout", "--wait", "--json", "--json-stream"],
885
+ values: ["--project", "--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout"],
857
886
  },
858
887
  };
859
888
 
@@ -888,28 +917,28 @@ export async function run(sub, args) {
888
917
  case "use": await use(args[0]); break;
889
918
  case "list": await list(args); break;
890
919
  case "current": await current(); break;
891
- case "rename": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.rename.values }); await rename(projectId, rest); break; }
892
- case "tenant-payments": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB["tenant-payments"].values }); await tenantPayments(projectId, rest); break; }
893
- case "get": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await get(projectId); break; }
920
+ case "rename": { const { projectId, rest } = resolveProjectSelector(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.rename.values }); await rename(projectId, rest); break; }
921
+ case "tenant-payments": { const { projectId, rest } = resolveProjectSelector(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB["tenant-payments"].values }); await tenantPayments(projectId, rest); break; }
922
+ case "get": { const { projectId } = resolveProjectSelector(args, { rejectBareFirst: true }); await get(projectId); break; }
894
923
  case "info": commandMoved("info"); break;
895
924
  case "keys": commandMoved("keys"); break;
896
- case "sql": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB.sql.values, rejectBareFirstWhenFlagPresent: ["--file"] }); await sqlCmd(projectId, rest); break; }
897
- case "rest": { const { projectId, rest: restArgs } = resolvePositionalProject(args); await rest(projectId, restArgs[0], restArgs[1]); break; }
898
- case "usage": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await usage(projectId); break; }
899
- case "costs": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.costs.values }); await costs(projectId, rest); break; }
900
- case "schema": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await schema(projectId); break; }
901
- case "apply-expose": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB["apply-expose"].values, rejectBareFirstWhenFlagPresent: ["--file"] }); await applyExpose(projectId, rest); break; }
925
+ case "sql": { const { projectId, rest } = resolveProjectSelector(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB.sql.values, rejectBareFirstWhenFlagPresent: ["--file"] }); await sqlCmd(projectId, rest); break; }
926
+ case "rest": { const { projectId, rest: restArgs } = resolveProjectSelector(args, { valueFlags: FLAGS_BY_SUB.rest.values }); await rest(projectId, restArgs); break; }
927
+ case "usage": { const { projectId } = resolveProjectSelector(args, { rejectBareFirst: true }); await usage(projectId); break; }
928
+ case "costs": { const { projectId, rest } = resolveProjectSelector(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.costs.values }); await costs(projectId, rest); break; }
929
+ case "schema": { const { projectId } = resolveProjectSelector(args, { rejectBareFirst: true }); await schema(projectId); break; }
930
+ case "apply-expose": { const { projectId, rest } = resolveProjectSelector(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB["apply-expose"].values, rejectBareFirstWhenFlagPresent: ["--file"] }); await applyExpose(projectId, rest); break; }
902
931
  case "validate-expose": await validateExpose(args); break;
903
- case "get-expose": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await getExpose(projectId); break; }
904
- case "delete": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true }); await deleteProject(projectId, rest); break; }
932
+ case "get-expose": { const { projectId } = resolveProjectSelector(args, { rejectBareFirst: true }); await getExpose(projectId); break; }
933
+ case "delete": { const { projectId, rest } = resolveProjectSelector(args, { rejectBareFirst: true }); await deleteProject(projectId, rest); break; }
905
934
  case "export": {
906
935
  const { run } = await import("./cloud.mjs");
907
936
  await run("archives", ["create", ...args]);
908
937
  break;
909
938
  }
910
- case "promote-user": { const { projectId, rest } = resolvePositionalProject(args); await promoteUser(projectId, rest[0]); break; }
911
- case "demote-user": { const { projectId, rest } = resolvePositionalProject(args); await demoteUser(projectId, rest[0]); break; }
939
+ case "promote-user": { const { projectId, rest } = resolveProjectSelector(args); await promoteUser(projectId, rest[0]); break; }
940
+ case "demote-user": { const { projectId, rest } = resolveProjectSelector(args); await demoteUser(projectId, rest[0]); break; }
912
941
  default:
913
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown projects subcommand: ${sub}`, hint: "Run `run402 projects --help` for usage.", details: { command: "projects", subcommand: sub } });
942
+ failUnknownSubcommand("projects", sub);
914
943
  }
915
944
  }