run402 4.1.0 → 4.2.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/cli.mjs CHANGED
@@ -27,7 +27,7 @@ Commands:
27
27
  credentials Manage local credential material (project-keys)
28
28
  allowance Manage your agent allowance (create, fund, balance, status)
29
29
  tier Manage tier subscription (status, set)
30
- projects Manage projects (provision, list, query, inspect, delete)
30
+ projects Manage projects (provision, list, get, sql, delete)
31
31
  apply Alias for deploy apply; supports --rehearse for migration rehearsal
32
32
  snapshots Create/list/restore/delete project data snapshots
33
33
  branches Create/list/renew/delete contained project branches
package/lib/allowance.mjs CHANGED
@@ -242,7 +242,17 @@ async function balance() {
242
242
  }, null, 2));
243
243
  }
244
244
 
245
- async function exportAddr() {
245
+ async function exportAddr(args = []) {
246
+ const parsedArgs = normalizeArgv(args);
247
+ assertKnownFlags(parsedArgs, [], []);
248
+ const bare = positionalArgs(parsedArgs, []);
249
+ if (bare.length > 0) {
250
+ fail({
251
+ code: "BAD_USAGE",
252
+ message: `Unexpected argument for allowance export: ${bare[0]}`,
253
+ details: { argument: bare[0] },
254
+ });
255
+ }
246
256
  try {
247
257
  const address = await getSdk().allowance.export();
248
258
  console.log(JSON.stringify({ address }));
@@ -333,7 +343,7 @@ export async function run(sub, args) {
333
343
  case "create": await create(); break;
334
344
  case "fund": await fund(); break;
335
345
  case "balance": await balance(); break;
336
- case "export": await exportAddr(); break;
346
+ case "export": await exportAddr(args); break;
337
347
  case "checkout": await checkout(args); break;
338
348
  case "history": await history(args); break;
339
349
  default:
package/lib/billing.mjs CHANGED
@@ -9,7 +9,7 @@ Usage:
9
9
 
10
10
  Subcommands:
11
11
  create-email <email> Create an email organization
12
- link-wallet <org_id> <wallet> Link a wallet to an email organization
12
+ link-wallet <org_id> <wallet_address> Link a wallet to an email organization
13
13
  checkout <identifier> --product <p> Create an org checkout
14
14
  auto-recharge <org_id> <on|off> [--threshold <n>]
15
15
  balance <identifier> Balance by organization id (UUID), wallet (0x...), or email
@@ -114,11 +114,11 @@ Examples:
114
114
  "link-wallet": `run402 billing link-wallet — Link a wallet to an email organization
115
115
 
116
116
  Usage:
117
- run402 billing link-wallet <org_id> <wallet>
117
+ run402 billing link-wallet <org_id> <wallet_address>
118
118
 
119
119
  Arguments:
120
120
  <org_id> Organization ID (e.g. org_abc123)
121
- <wallet> Wallet address (0x...) to link
121
+ <wallet_address> Wallet address (0x...) to link
122
122
 
123
123
  Notes:
124
124
  - Tier and quotas are per-organization. Linking a wallet merges its
@@ -242,8 +242,8 @@ async function linkWallet(args) {
242
242
  if (!organizationId || !wallet) {
243
243
  fail({
244
244
  code: "BAD_USAGE",
245
- message: "Missing <org_id> and/or <wallet>.",
246
- hint: "run402 billing link-wallet <org_id> <wallet>",
245
+ message: "Missing <org_id> and/or <wallet_address>.",
246
+ hint: "run402 billing link-wallet <org_id> <wallet_address>",
247
247
  });
248
248
  }
249
249
  try {
@@ -305,7 +305,7 @@ async function balance(args) {
305
305
  fail({
306
306
  code: "BAD_USAGE",
307
307
  message: "Missing <identifier>.",
308
- hint: "run402 billing balance <org-id | wallet | email>",
308
+ hint: "run402 billing balance <org_id | wallet | email>",
309
309
  });
310
310
  }
311
311
  try {
@@ -329,7 +329,7 @@ async function history(args) {
329
329
  fail({
330
330
  code: "BAD_USAGE",
331
331
  message: "Missing <identifier>.",
332
- hint: "run402 billing history <org-id | wallet | email> [--limit <n>] [--after <cursor>]",
332
+ hint: "run402 billing history <org_id | wallet | email> [--limit <n>] [--after <cursor>]",
333
333
  });
334
334
  }
335
335
  const limit = parsedArgs.includes("--limit")
package/lib/doctor.mjs CHANGED
@@ -56,6 +56,13 @@ Exit codes:
56
56
  1 — one or more checks failed (details in output)
57
57
  `;
58
58
 
59
+ function redactAllowanceForDiagnostics(allowance) {
60
+ if (!allowance || typeof allowance !== "object") return allowance;
61
+ const safe = { ...allowance };
62
+ delete safe.privateKey;
63
+ return safe;
64
+ }
65
+
59
66
  export async function run(sub, args = []) {
60
67
  const all = [sub, ...args].filter(Boolean);
61
68
  if (all.includes("--help") || all.includes("-h")) {
@@ -113,8 +120,9 @@ export async function run(sub, args = []) {
113
120
  value: {
114
121
  rail: allowance.rail,
115
122
  // Don't surface amounts or addresses unless --verbose; agents
116
- // checking for config presence don't need wallet details.
117
- ...(verbose && { details: allowance }),
123
+ // checking for config presence don't need wallet details. Never
124
+ // include keystore secrets in diagnostics, even in verbose mode.
125
+ ...(verbose && { details: redactAllowanceForDiagnostics(allowance) }),
118
126
  },
119
127
  });
120
128
  } else {
@@ -182,19 +190,34 @@ export async function run(sub, args = []) {
182
190
  const sdk = getSdk();
183
191
  const tier = await sdk.tier.status();
184
192
  const tierName = tier?.tier ?? null;
185
- if (tierName && tierName !== "past_due" && tierName !== "frozen" && tierName !== "dormant") {
193
+ const lifecycle = tier?.organization_lifecycle_state ?? null;
194
+ const active = tier?.active === true;
195
+ if (tierName && active && lifecycle === "active") {
186
196
  checks.push({
187
197
  name: "tier",
188
198
  status: "ok",
189
- value: { tier: tierName },
199
+ value: { tier: tierName, active, organization_lifecycle_state: lifecycle },
190
200
  });
191
201
  } else {
202
+ const status = lifecycle && lifecycle !== "active"
203
+ ? lifecycle
204
+ : tierName && !active
205
+ ? "inactive"
206
+ : tierName && lifecycle === null
207
+ ? "unknown"
208
+ : tierName ?? "missing";
192
209
  checks.push({
193
210
  name: "tier",
194
- status: tierName ?? "missing",
195
- ...(tierName && {
196
- hint: "Run 'run402 tier set prototype' to subscribe (or upgrade).",
197
- }),
211
+ status,
212
+ value: {
213
+ tier: tierName,
214
+ active,
215
+ organization_lifecycle_state: lifecycle,
216
+ lease_expires_at: tier?.lease_expires_at ?? null,
217
+ },
218
+ hint: lifecycle === null && tierName
219
+ ? "Tier resolved, but organization lifecycle could not be determined. Check `run402 tier status` before assuming the account is healthy."
220
+ : "Run 'run402 tier set prototype' to subscribe, renew, or reactivate the tier.",
198
221
  });
199
222
  }
200
223
  } catch (err) {
package/lib/org.mjs CHANGED
@@ -15,23 +15,25 @@ const HELP = `run402 org — organizations: create, label, membership, invites
15
15
  Usage:
16
16
  run402 org create [--name <label>]
17
17
  run402 org list
18
- run402 org get <org>
19
- run402 org rename <org> <display_name> (or: --clear to remove the label)
18
+ 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
21
  run402 org whoami
21
- run402 org audit <org> [--limit N] [--after <cursor>] [--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>
22
+ run402 org audit <org_id> [--limit N] [--after <cursor>] [--before <cursor>]
23
+ 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>
27
+ 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
30
 
30
31
  Subcommands:
31
32
  create Create an empty org on the prototype tier (you become owner)
32
33
  list Orgs you are a member of
33
34
  get Read one org (label + tier/lease + your role)
34
35
  rename Set or clear an org's display label (owner-only)
36
+ payout-wallet Set or clear the tenant route payout wallet (admin+)
35
37
  whoami Resolved principal + org memberships (GET /agent/v1/whoami)
36
38
  member Manage members (list, add, role, rm) — mutations require owner
37
39
  invite Manage email invites (list, create, rm) — mutations require owner
@@ -40,7 +42,7 @@ Subcommands:
40
42
  Notes:
41
43
  - A wallet AUTHENTICATES; an org owns projects. Membership/role authorizes.
42
44
  - Roles: ${ROLE_LIST}. Member/invite changes need an active owner.
43
- - create/rename/member/invite are step-up gated for control-plane sessions.
45
+ - create/rename/payout-wallet/member/invite are step-up gated for control-plane sessions.
44
46
  - Removing/demoting the org's only active owner fails with 409 LAST_OWNER.
45
47
  - JSON in, JSON out.
46
48
 
@@ -50,6 +52,8 @@ Examples:
50
52
  run402 org get org_abc
51
53
  run402 org rename org_abc "New Name"
52
54
  run402 org rename org_abc --clear
55
+ run402 org payout-wallet org_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
56
+ run402 org payout-wallet org_abc --clear
53
57
  run402 org member add org_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
54
58
  run402 org invite create org_abc dev@example.com --role developer
55
59
  run402 org audit org_abc --limit 50
@@ -75,7 +79,7 @@ Usage:
75
79
  get: `run402 org get — read one org (label + tier/lease + your role)
76
80
 
77
81
  Usage:
78
- run402 org get <org>
82
+ run402 org get <org_id>
79
83
 
80
84
  Any active member may read. A non-member (including a guessed id) gets the same
81
85
  non-revealing 403.
@@ -83,11 +87,25 @@ non-revealing 403.
83
87
  rename: `run402 org rename — set or clear an org's display label (owner-only)
84
88
 
85
89
  Usage:
86
- run402 org rename <org> <display_name>
87
- run402 org rename <org> --clear
90
+ run402 org rename <org_id> <display_name>
91
+ run402 org rename <org_id> --clear
88
92
 
89
93
  Owner-only + step-up gated. Pass --clear (or an empty display_name) to remove
90
94
  the label. Output includes the updated tier and lease timestamps.
95
+ `,
96
+ "payout-wallet": `run402 org payout-wallet — set or clear the tenant route payout wallet
97
+
98
+ Usage:
99
+ run402 org payout-wallet <org_id> <wallet_address>
100
+ run402 org payout-wallet <org_id> --clear
101
+
102
+ Admin/owner-only + step-up gated. The wallet must already be active and linked
103
+ to the same org. This wallet receives x402 settlement for function web routes
104
+ that declare pricing.pay_to = "org_default_payout". Pass --clear to remove the
105
+ explicit default; a single active org wallet may still resolve automatically.
106
+
107
+ The JSON response includes recovery.status, active_wallet_count, and
108
+ next_actions for PAYOUT_WALLET_REQUIRED / PAYOUT_WALLET_AMBIGUOUS setup.
91
109
  `,
92
110
  whoami: `run402 org whoami — resolved principal + org memberships
93
111
 
@@ -101,10 +119,10 @@ for local wallet/profile state use 'run402 status'.
101
119
  member: `run402 org member — manage org members
102
120
 
103
121
  Usage:
104
- run402 org member list <org>
105
- run402 org member add <org> <wallet> [--role <role>]
106
- run402 org member role <org> <principal_id> <role>
107
- run402 org member rm <org> <principal_id>
122
+ 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>
108
126
 
109
127
  Roles: ${ROLE_LIST} (add defaults to developer). Mutations require an active owner.
110
128
  Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
@@ -112,9 +130,9 @@ Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
112
130
  invite: `run402 org invite — manage email invites
113
131
 
114
132
  Usage:
115
- run402 org invite list <org>
116
- run402 org invite create <org> <email> [--role <role>] [--ttl-hours N]
117
- run402 org invite rm <org> <principal_id>
133
+ 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>
118
136
 
119
137
  An invite is claimed at the recipient's first login. Mutations require an active owner
120
138
  (plus step-up when driven by a control-plane session).
@@ -122,7 +140,7 @@ An invite is claimed at the recipient's first login. Mutations require an active
122
140
  audit: `run402 org audit — control-plane audit trail
123
141
 
124
142
  Usage:
125
- run402 org audit <org> [--limit N] [--after <cursor>] [--before <cursor>]
143
+ run402 org audit <org_id> [--limit N] [--after <cursor>] [--before <cursor>]
126
144
 
127
145
  Requires an admin+ membership on the org. Newest-first. Page forward with --after
128
146
  (next_cursor from a prior page); --before is the legacy cursor. Returns
@@ -171,7 +189,7 @@ async function get(args) {
171
189
  const a = normalizeArgv(args);
172
190
  assertKnownFlags(a, ["--help", "-h"]);
173
191
  const [org] = requirePositionalCount(a, [], {
174
- min: 1, max: 1, command: "run402 org get <org>", missing: "Missing <org>.",
192
+ min: 1, max: 1, command: "run402 org get <org_id>", missing: "Missing <org_id>.",
175
193
  });
176
194
  try {
177
195
  console.log(JSON.stringify(await getSdk().org(org).get(), null, 2));
@@ -187,8 +205,8 @@ async function rename(args) {
187
205
  const positionals = requirePositionalCount(a, [], {
188
206
  min: clear ? 1 : 2,
189
207
  max: clear ? 1 : 2,
190
- command: "run402 org rename <org> <display_name>",
191
- missing: clear ? "Missing <org>." : "Missing <org> and/or <display_name> (or pass --clear).",
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).",
192
210
  });
193
211
  const org = positionals[0];
194
212
  const displayName = clear ? null : positionals[1];
@@ -199,6 +217,25 @@ async function rename(args) {
199
217
  }
200
218
  }
201
219
 
220
+ async function payoutWallet(args) {
221
+ const a = normalizeArgv(args);
222
+ assertKnownFlags(a, ["--clear", "--help", "-h"]);
223
+ 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).",
229
+ });
230
+ const org = positionals[0];
231
+ const walletAddress = clear ? null : positionals[1];
232
+ try {
233
+ console.log(JSON.stringify(await getSdk().org(org).setPayoutWallet({ walletAddress }), null, 2));
234
+ } catch (err) {
235
+ reportSdkError(err);
236
+ }
237
+ }
238
+
202
239
  async function audit(args) {
203
240
  const a = normalizeArgv(args);
204
241
  const valueFlags = ["--limit", "--after", "--before"];
@@ -206,8 +243,8 @@ async function audit(args) {
206
243
  const [org] = requirePositionalCount(a, valueFlags, {
207
244
  min: 1,
208
245
  max: 1,
209
- command: "run402 org audit <org>",
210
- missing: "Missing <org>.",
246
+ command: "run402 org audit <org_id>",
247
+ missing: "Missing <org_id>.",
211
248
  });
212
249
  const limitFlag = flagValue(a, "--limit");
213
250
  const after = flagValue(a, "--after");
@@ -243,7 +280,7 @@ async function runMember(args) {
243
280
  const a = normalizeArgv(rest);
244
281
  assertKnownFlags(a, ["--help", "-h"]);
245
282
  const [org] = requirePositionalCount(a, [], {
246
- min: 1, max: 1, command: "run402 org member list <org>", missing: "Missing <org>.",
283
+ min: 1, max: 1, command: "run402 org member list <org_id>", missing: "Missing <org_id>.",
247
284
  });
248
285
  try {
249
286
  console.log(JSON.stringify({ members: await getSdk().org(org).members.list() }, null, 2));
@@ -258,8 +295,8 @@ async function runMember(args) {
258
295
  assertKnownFlags(a, ["--role", "--help", "-h"], ["--role"]);
259
296
  const role = flagValue(a, "--role");
260
297
  const [org, wallet] = requirePositionalCount(a, ["--role"], {
261
- min: 2, max: 2, command: "run402 org member add <org> <wallet> [--role <role>]",
262
- missing: "Missing <org> and/or <wallet>.",
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>.",
263
300
  });
264
301
  try {
265
302
  const res = await getSdk().org(org).members.add({ wallet, role: role || undefined });
@@ -274,8 +311,8 @@ async function runMember(args) {
274
311
  const a = normalizeArgv(rest);
275
312
  assertKnownFlags(a, ["--help", "-h"]);
276
313
  const [org, principalId, role] = requirePositionalCount(a, [], {
277
- min: 3, max: 3, command: "run402 org member role <org> <principal_id> <role>",
278
- missing: "Missing <org>, <principal_id>, and/or <role>.",
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>.",
279
316
  });
280
317
  try {
281
318
  console.log(JSON.stringify(await getSdk().org(org).members.setRole(principalId, { role }), null, 2));
@@ -289,8 +326,8 @@ async function runMember(args) {
289
326
  const a = normalizeArgv(rest);
290
327
  assertKnownFlags(a, ["--help", "-h"]);
291
328
  const [org, principalId] = requirePositionalCount(a, [], {
292
- min: 2, max: 2, command: "run402 org member rm <org> <principal_id>",
293
- missing: "Missing <org> and/or <principal_id>.",
329
+ min: 2, max: 2, command: "run402 org member rm <org_id> <principal_id>",
330
+ missing: "Missing <org_id> and/or <principal_id>.",
294
331
  });
295
332
  try {
296
333
  console.log(JSON.stringify(await getSdk().org(org).members.revoke(principalId), null, 2));
@@ -321,7 +358,7 @@ async function runInvite(args) {
321
358
  const a = normalizeArgv(rest);
322
359
  assertKnownFlags(a, ["--help", "-h"]);
323
360
  const [org] = requirePositionalCount(a, [], {
324
- min: 1, max: 1, command: "run402 org invite list <org>", missing: "Missing <org>.",
361
+ min: 1, max: 1, command: "run402 org invite list <org_id>", missing: "Missing <org_id>.",
325
362
  });
326
363
  try {
327
364
  console.log(JSON.stringify({ invites: await getSdk().org(org).invites.list() }, null, 2));
@@ -338,8 +375,8 @@ async function runInvite(args) {
338
375
  const role = flagValue(a, "--role");
339
376
  const ttlFlag = flagValue(a, "--ttl-hours");
340
377
  const [org, email] = requirePositionalCount(a, valueFlags, {
341
- min: 2, max: 2, command: "run402 org invite create <org> <email> [--role <role>]",
342
- missing: "Missing <org> and/or <email>.",
378
+ min: 2, max: 2, command: "run402 org invite create <org_id> <email> [--role <role>]",
379
+ missing: "Missing <org_id> and/or <email>.",
343
380
  });
344
381
  const inviteTtlHours = ttlFlag === null ? undefined : parseIntegerFlag("--ttl-hours", ttlFlag, { min: 1, max: 8760 });
345
382
  try {
@@ -355,8 +392,8 @@ async function runInvite(args) {
355
392
  const a = normalizeArgv(rest);
356
393
  assertKnownFlags(a, ["--help", "-h"]);
357
394
  const [org, principalId] = requirePositionalCount(a, [], {
358
- min: 2, max: 2, command: "run402 org invite rm <org> <principal_id>",
359
- missing: "Missing <org> and/or <principal_id>.",
395
+ min: 2, max: 2, command: "run402 org invite rm <org_id> <principal_id>",
396
+ missing: "Missing <org_id> and/or <principal_id>.",
360
397
  });
361
398
  try {
362
399
  console.log(JSON.stringify(await getSdk().org(org).invites.revoke(principalId), null, 2));
@@ -393,6 +430,7 @@ export async function run(sub, args) {
393
430
  case "list": await list(args); break;
394
431
  case "get": await get(args); break;
395
432
  case "rename": await rename(args); break;
433
+ case "payout-wallet": await payoutWallet(args); break;
396
434
  case "whoami": await whoami(args); break;
397
435
  case "audit": await audit(args); break;
398
436
  default:
package/lib/projects.mjs CHANGED
@@ -18,6 +18,7 @@ Subcommands:
18
18
  use <id> Set the active project (used as default for other commands)
19
19
  list [--org <id>] [--all] List your projects from the server (name, site_url, custom domains, org_id, active marker)
20
20
  rename <id> --name <label> Rename a project (fix an auto-generated name)
21
+ tenant-payments [project_id] [--status <s>] List redacted tenant x402 payments for priced routes
21
22
  get [id] Authoritative server read: status, org, tier, active deploy, mailbox, usage vs limits (live; no keys)
22
23
  current Show the active project pointer and validation status
23
24
  sql [id] "<query>" [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
@@ -44,6 +45,7 @@ Examples:
44
45
  run402 projects list --org 11111111-2222-3333-4444-555555555555
45
46
  run402 projects list --all
46
47
  run402 projects rename prj_abc123 --name "My Site"
48
+ run402 projects tenant-payments prj_abc123 --status settled
47
49
  run402 projects get prj_abc123
48
50
  run402 projects current
49
51
  run402 projects sql prj_abc123 "SELECT * FROM users LIMIT 5"
@@ -150,6 +152,25 @@ Notes:
150
152
  Examples:
151
153
  run402 projects rename prj_abc123 --name "My Site"
152
154
  `,
155
+ "tenant-payments": `run402 projects tenant-payments — List tenant x402 payments
156
+
157
+ Usage:
158
+ run402 projects tenant-payments [project_id] [--status <settling|settled|settle_failed|ambiguous>] [--limit N] [--after <cursor>]
159
+
160
+ Arguments:
161
+ [project_id] Project ID (defaults to the active project if omitted)
162
+
163
+ Options:
164
+ --status <s> Optional payment status filter
165
+ --limit N Page size (server default 50, max 200)
166
+ --after <cursor> Opaque cursor from a previous next_cursor
167
+
168
+ Notes:
169
+ - Requires project.tenant_payments.read: org developer+ or a read-scoped
170
+ project grant/delegate.
171
+ - Output is the gateway's redacted payment page. Raw X-PAYMENT headers,
172
+ authorization hashes, and internal metadata are never returned.
173
+ `,
153
174
  current: `run402 projects current — Inspect active project selection
154
175
 
155
176
  Usage:
@@ -544,6 +565,34 @@ async function rename(projectId, args = []) {
544
565
  }
545
566
  }
546
567
 
568
+ async function tenantPayments(projectId, args = []) {
569
+ const status = flagValue(args, "--status") ?? undefined;
570
+ const after = flagValue(args, "--after") ?? undefined;
571
+ const limitFlag = flagValue(args, "--limit");
572
+ const limit = limitFlag === null ? undefined : Number(limitFlag);
573
+ if (limitFlag !== null && (!Number.isInteger(limit) || limit <= 0 || limit > 200)) {
574
+ fail({
575
+ code: "BAD_FLAG",
576
+ message: `--limit must be an integer from 1 to 200; got ${limitFlag}.`,
577
+ details: { flag: "--limit", value: limitFlag },
578
+ });
579
+ }
580
+ const allowed = new Set(["settling", "settled", "settle_failed", "ambiguous"]);
581
+ if (status !== undefined && !allowed.has(status)) {
582
+ fail({
583
+ code: "BAD_FLAG",
584
+ message: `--status must be one of settling, settled, settle_failed, ambiguous; got ${status}.`,
585
+ details: { flag: "--status", value: status },
586
+ });
587
+ }
588
+ try {
589
+ const data = await getSdk().projects.listTenantPayments(projectId, { status, after, limit });
590
+ console.log(JSON.stringify(data, null, 2));
591
+ } catch (err) {
592
+ reportSdkError(err);
593
+ }
594
+ }
595
+
547
596
  async function get(projectId) {
548
597
  try {
549
598
  const data = await getSdk().projects.get(projectId);
@@ -793,6 +842,7 @@ const FLAGS_BY_SUB = {
793
842
  },
794
843
  list: { known: ["--org", "--all"], values: ["--org"] },
795
844
  rename: { known: ["--name"], values: ["--name"] },
845
+ "tenant-payments": { known: ["--status", "--limit", "--after"], values: ["--status", "--limit", "--after"] },
796
846
  sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
797
847
  costs: { known: ["--window"], values: ["--window"] },
798
848
  "apply-expose": { known: ["--file"], values: ["--file"] },
@@ -839,6 +889,7 @@ export async function run(sub, args) {
839
889
  case "list": await list(args); break;
840
890
  case "current": await current(); break;
841
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; }
842
893
  case "get": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await get(projectId); break; }
843
894
  case "info": commandMoved("info"); break;
844
895
  case "keys": commandMoved("keys"); break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "4.1.0",
3
+ "version": "4.2.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": {
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { isCiSessionCredentials } from "../ci-credentials.js";
22
22
  import { assertCiDeployableSpec } from "./ci.js";
23
- import { ROUTE_HTTP_METHODS, normalizeDeployResolveRequest, } from "./deploy.types.js";
23
+ import { ROUTE_PRICING_NETWORKS, ROUTE_HTTP_METHODS, normalizeDeployResolveRequest, } from "./deploy.types.js";
24
24
  import { ApiError, LocalError, NetworkError, PaymentRequired, Run402DeployError, Unauthorized, isTransferFreezeError, } from "../errors.js";
25
25
  import { assertAssetMetadata, assertExifPolicy, } from "./assets-validation.js";
26
26
  // ─── Constants ───────────────────────────────────────────────────────────────
@@ -2073,10 +2073,12 @@ const SITE_PUBLIC_PATHS_FIELDS = new Set(["mode", "replace"]);
2073
2073
  const PUBLIC_STATIC_PATH_FIELDS = new Set(["asset", "cache_class"]);
2074
2074
  const SUBDOMAINS_SPEC_FIELDS = new Set(["set", "add", "remove"]);
2075
2075
  const ROUTES_SPEC_FIELDS = new Set(["replace"]);
2076
- const ROUTE_ENTRY_FIELDS = new Set(["pattern", "methods", "target", "acknowledge_readonly"]);
2076
+ const ROUTE_ENTRY_FIELDS = new Set(["pattern", "methods", "target", "pricing", "acknowledge_readonly"]);
2077
2077
  const FUNCTION_ROUTE_TARGET_FIELDS = new Set(["type", "name"]);
2078
2078
  const STATIC_ROUTE_TARGET_FIELDS = new Set(["type", "file"]);
2079
2079
  const ROUTE_METHOD_SET = new Set(ROUTE_HTTP_METHODS);
2080
+ const ROUTE_PRICING_FIELDS = new Set(["mode", "amount_usd_micros", "pay_to", "networks"]);
2081
+ const ROUTE_PRICING_NETWORK_SET = new Set(ROUTE_PRICING_NETWORKS);
2080
2082
  const I18N_SPEC_FIELDS = new Set(["defaultLocale", "locales", "detect", "unknownLocalePolicy"]);
2081
2083
  const I18N_LOCALE_TAG_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2082
2084
  const I18N_COOKIE_NAME_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -2493,11 +2495,48 @@ function validateRouteEntry(route, resource) {
2493
2495
  }
2494
2496
  }
2495
2497
  const targetType = validateRouteTarget(entry.target, `${resource}.target`);
2498
+ validateRoutePricing(entry.pricing, resource, targetType);
2496
2499
  validateRouteReadOnlyAcknowledgement(entry, targetType, resource);
2497
2500
  if (targetType === "static") {
2498
2501
  validateStaticRouteEntry(entry, resource);
2499
2502
  }
2500
2503
  }
2504
+ function validateRoutePricing(pricing, resource, targetType) {
2505
+ if (pricing === undefined)
2506
+ return;
2507
+ if (targetType !== "function") {
2508
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing is only supported on function route targets`, `${resource}.pricing`);
2509
+ }
2510
+ const obj = requireObject(pricing, `${resource}.pricing`);
2511
+ validateKnownFields(obj, `${resource}.pricing`, ROUTE_PRICING_FIELDS);
2512
+ if (obj.mode !== "always") {
2513
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.mode must be "always"`, `${resource}.pricing.mode`);
2514
+ }
2515
+ if (typeof obj.amount_usd_micros !== "number" || !Number.isSafeInteger(obj.amount_usd_micros) || obj.amount_usd_micros <= 0) {
2516
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.amount_usd_micros must be a positive safe JSON integer`, `${resource}.pricing.amount_usd_micros`);
2517
+ }
2518
+ if (obj.pay_to !== "org_default_payout") {
2519
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.pay_to must be "org_default_payout"`, `${resource}.pricing.pay_to`);
2520
+ }
2521
+ if (obj.networks === undefined)
2522
+ return;
2523
+ if (!Array.isArray(obj.networks)) {
2524
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.networks must be an array`, `${resource}.pricing.networks`);
2525
+ }
2526
+ if (obj.networks.length === 0) {
2527
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.networks must not be empty; omit networks for production mainnet only`, `${resource}.pricing.networks`);
2528
+ }
2529
+ const seen = new Set();
2530
+ for (const network of obj.networks) {
2531
+ if (typeof network !== "string" || !ROUTE_PRICING_NETWORK_SET.has(network)) {
2532
+ throw invalidRouteSpec(`Unsupported route pricing network ${JSON.stringify(network)} at ReleaseSpec.${resource}.pricing.networks. Supported networks: ${ROUTE_PRICING_NETWORKS.join(", ")}`, `${resource}.pricing.networks`);
2533
+ }
2534
+ if (seen.has(network)) {
2535
+ throw invalidRouteSpec(`ReleaseSpec.${resource}.pricing.networks contains duplicate network ${JSON.stringify(network)}`, `${resource}.pricing.networks`);
2536
+ }
2537
+ seen.add(network);
2538
+ }
2539
+ }
2501
2540
  function validateRouteReadOnlyAcknowledgement(entry, targetType, resource) {
2502
2541
  if (entry.acknowledge_readonly === undefined)
2503
2542
  return;