run402 2.19.1 → 2.21.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
@@ -28,6 +28,7 @@ Commands:
28
28
  admin Platform-admin operations (lease-perpetual, archive, reactivate)
29
29
  deploy Unified deploy operations (requires active tier)
30
30
  ci Link GitHub Actions OIDC deploy bindings
31
+ transfer Two-party project transfer (init, preview, list, accept, cancel)
31
32
  jobs Submit and inspect fixed platform-managed jobs
32
33
  functions Manage serverless functions (deploy, invoke, logs, list, delete)
33
34
  secrets Manage project secrets (set, list, delete)
@@ -141,6 +142,11 @@ switch (cmd) {
141
142
  await run(sub, rest);
142
143
  break;
143
144
  }
145
+ case "transfer": {
146
+ const { run } = await import("./lib/transfer.mjs");
147
+ await run(sub, rest);
148
+ break;
149
+ }
144
150
  case "jobs": {
145
151
  const { run } = await import("./lib/jobs.mjs");
146
152
  await run(sub, rest);
package/lib/email.mjs CHANGED
@@ -20,7 +20,7 @@ Subcommands:
20
20
  Fetch raw RFC-822 bytes (inbound only)
21
21
  reply <message_id> --html "..." [--text "..."] [--subject "..."] [--from-name "..."] [--project <id>]
22
22
  Reply to an inbound message (threads via In-Reply-To)
23
- delete [<mailbox_id>] --confirm [--project <id>]
23
+ delete [<slug|mailbox_id>] --confirm [--project <id>]
24
24
  Delete the project's mailbox (irreversible)
25
25
  webhooks <action> [args...] Manage webhooks (see below)
26
26
 
@@ -38,6 +38,13 @@ Send modes:
38
38
  Raw HTML: --subject "..." --html "..." [--text "..."] (both --subject and --html required)
39
39
  Both modes support: --from-name "Display Name" --project <id>
40
40
 
41
+ Choosing a mailbox:
42
+ --mailbox <slug|id> Target a specific mailbox. Accepted by send, list, get,
43
+ get-raw, reply, info, and webhooks. Required when the
44
+ project has more than one mailbox; omit it only when the
45
+ project has exactly one. (delete takes the target as its
46
+ positional <slug|mailbox_id>.)
47
+
41
48
  Templates:
42
49
  project_invite — requires --var project_name=... --var invite_url=...
43
50
  magic_link — requires --var project_name=... --var link_url=... --var expires_in=...
@@ -58,7 +65,7 @@ Examples:
58
65
  run402 email webhooks register --url https://example.com/hook --events delivery,bounced
59
66
 
60
67
  Notes:
61
- - One mailbox per project
68
+ - Up to 5 mailboxes per project — pass --mailbox <slug|id> to pick one
62
69
  - Single recipient per send (no CC/BCC)
63
70
  - Slug: 3-63 chars, lowercase alphanumeric + hyphens, no consecutive hyphens
64
71
  - --project defaults to the active project
@@ -82,12 +89,13 @@ Options:
82
89
  --html "..." HTML body (raw HTML mode; required with --subject)
83
90
  --text "..." Plain-text body (raw HTML mode; optional)
84
91
  --from-name "..." Display name for the From header
92
+ --mailbox <slug|id> Target mailbox (required if the project has more than one)
85
93
  --project <id> Project ID (defaults to the active project)
86
94
  `,
87
95
  list: `run402 email list — List messages in the mailbox
88
96
 
89
97
  Usage:
90
- run402 email list [--limit <n>] [--after <cursor>] [--project <id>]
98
+ run402 email list [--mailbox <slug|id>] [--limit <n>] [--after <cursor>] [--project <id>]
91
99
  `,
92
100
  reply: `run402 email reply — Reply to an inbound message (threaded via In-Reply-To)
93
101
 
@@ -97,7 +105,7 @@ Usage:
97
105
  delete: `run402 email delete — Delete the project's mailbox (irreversible)
98
106
 
99
107
  Usage:
100
- run402 email delete [<mailbox_id>] --confirm [--project <id>]
108
+ run402 email delete [<slug|mailbox_id>] --confirm [--project <id>]
101
109
  `,
102
110
  info: `run402 email info — Show mailbox info (ID, address, slug)
103
111
 
@@ -131,7 +139,7 @@ Options:
131
139
  --project <id> Project ID (defaults to the active project)
132
140
 
133
141
  Notes:
134
- - One mailbox per project
142
+ - Up to 5 mailboxes per project (create distinct slugs, e.g. sign, support)
135
143
 
136
144
  Examples:
137
145
  run402 email create my-app
@@ -229,7 +237,7 @@ async function create(args) {
229
237
  }
230
238
 
231
239
  async function send(args) {
232
- const valueFlags = ["--template", "--to", "--subject", "--html", "--text", "--from-name", "--project", "--vars", "--var"];
240
+ const valueFlags = ["--template", "--to", "--subject", "--html", "--text", "--from-name", "--project", "--vars", "--var", "--mailbox"];
233
241
  validateArgs(args, valueFlags);
234
242
  const template = strictFlagValue(args, "--template");
235
243
  const to = strictFlagValue(args, "--to");
@@ -237,6 +245,7 @@ async function send(args) {
237
245
  const html = strictFlagValue(args, "--html");
238
246
  const text = strictFlagValue(args, "--text");
239
247
  const fromName = strictFlagValue(args, "--from-name");
248
+ const mailbox = strictFlagValue(args, "--mailbox");
240
249
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
241
250
  const variables = parseVars(args);
242
251
 
@@ -253,6 +262,7 @@ async function send(args) {
253
262
  html: html ?? undefined,
254
263
  text: text ?? undefined,
255
264
  from_name: fromName ?? undefined,
265
+ mailbox: mailbox ?? undefined,
256
266
  });
257
267
  console.log(JSON.stringify({ message_id: data.message_id, to: data.to, template: data.template, subject: data.subject, sent: true }));
258
268
  } catch (err) {
@@ -261,15 +271,17 @@ async function send(args) {
261
271
  }
262
272
 
263
273
  async function list(args) {
264
- const valueFlags = ["--project", "--limit", "--after"];
274
+ const valueFlags = ["--project", "--limit", "--after", "--mailbox"];
265
275
  validateArgs(args, valueFlags);
266
276
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
267
277
  const limit = strictFlagValue(args, "--limit");
268
278
  const after = strictFlagValue(args, "--after");
279
+ const mailbox = strictFlagValue(args, "--mailbox");
269
280
  try {
270
281
  const data = await getSdk().email.list(projectId, {
271
282
  limit: limit ? parseIntegerFlag("--limit", limit) : undefined,
272
283
  after: after ?? undefined,
284
+ mailbox: mailbox ?? undefined,
273
285
  });
274
286
  console.log(JSON.stringify(data, null, 2));
275
287
  } catch (err) {
@@ -278,9 +290,11 @@ async function list(args) {
278
290
  }
279
291
 
280
292
  async function get(args) {
281
- validateArgs(args, ["--project"]);
282
- const messageId = positionalArgs(args, ["--project"])[0] ?? null;
293
+ const valueFlags = ["--project", "--mailbox"];
294
+ validateArgs(args, valueFlags);
295
+ const messageId = positionalArgs(args, valueFlags)[0] ?? null;
283
296
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
297
+ const mailbox = strictFlagValue(args, "--mailbox");
284
298
  if (!messageId) {
285
299
  fail({
286
300
  code: "BAD_USAGE",
@@ -289,7 +303,7 @@ async function get(args) {
289
303
  });
290
304
  }
291
305
  try {
292
- const data = await getSdk().email.get(projectId, messageId);
306
+ const data = await getSdk().email.get(projectId, messageId, { mailbox: mailbox ?? undefined });
293
307
  console.log(JSON.stringify(data, null, 2));
294
308
  } catch (err) {
295
309
  reportSdkError(err);
@@ -297,10 +311,11 @@ async function get(args) {
297
311
  }
298
312
 
299
313
  async function getRaw(args) {
300
- const valueFlags = ["--project", "--output"];
314
+ const valueFlags = ["--project", "--output", "--mailbox"];
301
315
  validateArgs(args, valueFlags);
302
316
  const messageId = positionalArgs(args, valueFlags)[0] ?? null;
303
317
  const outputFile = strictFlagValue(args, "--output");
318
+ const mailbox = strictFlagValue(args, "--mailbox");
304
319
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
305
320
  if (!messageId) {
306
321
  fail({
@@ -311,7 +326,7 @@ async function getRaw(args) {
311
326
  }
312
327
 
313
328
  try {
314
- const result = await getSdk().email.getRaw(projectId, messageId);
329
+ const result = await getSdk().email.getRaw(projectId, messageId, { mailbox: mailbox ?? undefined });
315
330
  const buf = Buffer.from(result.bytes);
316
331
 
317
332
  if (outputFile) {
@@ -327,13 +342,14 @@ async function getRaw(args) {
327
342
  }
328
343
 
329
344
  async function reply(args) {
330
- const valueFlags = ["--project", "--html", "--text", "--subject", "--from-name"];
345
+ const valueFlags = ["--project", "--html", "--text", "--subject", "--from-name", "--mailbox"];
331
346
  validateArgs(args, valueFlags);
332
347
  const messageId = positionalArgs(args, valueFlags)[0] ?? null;
333
348
  const html = strictFlagValue(args, "--html");
334
349
  const text = strictFlagValue(args, "--text");
335
350
  const subjectOverride = strictFlagValue(args, "--subject");
336
351
  const fromName = strictFlagValue(args, "--from-name");
352
+ const mailbox = strictFlagValue(args, "--mailbox");
337
353
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
338
354
 
339
355
  if (!messageId) {
@@ -352,7 +368,7 @@ async function reply(args) {
352
368
 
353
369
  try {
354
370
  // Fetch the original message to derive the reply-to address and subject.
355
- const original = await getSdk().email.get(projectId, messageId);
371
+ const original = await getSdk().email.get(projectId, messageId, { mailbox: mailbox ?? undefined });
356
372
  const replyTo = original.from || original.from_address || original.sender || null;
357
373
  if (!replyTo) {
358
374
  fail({
@@ -374,6 +390,7 @@ async function reply(args) {
374
390
  text: text ?? undefined,
375
391
  from_name: fromName ?? undefined,
376
392
  in_reply_to: messageId,
393
+ mailbox: mailbox ?? undefined,
377
394
  });
378
395
  console.log(JSON.stringify({ message_id: data.message_id, to: data.to, subject: replySubject, in_reply_to: messageId, sent: true }));
379
396
  } catch (err) {
@@ -403,10 +420,12 @@ async function deleteMailbox(args) {
403
420
  }
404
421
 
405
422
  async function status(args) {
406
- validateArgs(args, ["--project"]);
423
+ const valueFlags = ["--project", "--mailbox"];
424
+ validateArgs(args, valueFlags);
407
425
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
426
+ const mailbox = strictFlagValue(args, "--mailbox");
408
427
  try {
409
- const mb = await getSdk().email.getMailbox(projectId);
428
+ const mb = await getSdk().email.getMailbox(projectId, mailbox ?? undefined);
410
429
  console.log(JSON.stringify({ mailbox_id: mb.mailbox_id, address: mb.address, slug: mb.slug }));
411
430
  } catch (err) {
412
431
  reportSdkError(err);
@@ -0,0 +1,266 @@
1
+ import { allowanceAuthHeaders, resolveProjectId } from "./config.mjs";
2
+ import { getSdk } from "./sdk.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
+ import {
5
+ assertKnownFlags,
6
+ flagValue,
7
+ hasHelp,
8
+ normalizeArgv,
9
+ parseIntegerFlag,
10
+ positionalArgs,
11
+ } from "./argparse.mjs";
12
+
13
+ const HELP = `run402 transfer — Two-party project transfer (v1.59)
14
+
15
+ Usage:
16
+ run402 transfer init --to <wallet> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>]
17
+ run402 transfer preview <transfer_id>
18
+ run402 transfer list [--incoming | --outgoing] [--limit N] [--offset N]
19
+ run402 transfer accept <transfer_id>
20
+ run402 transfer cancel <transfer_id> [--reason <text>]
21
+
22
+ Subcommands:
23
+ init Initiate a transfer to a new owner (you must currently own the project)
24
+ preview Fetch the safe review document (either party may view)
25
+ list List pending transfers (incoming by default, or --outgoing)
26
+ accept Accept an incoming transfer (your wallet must be the to_wallet)
27
+ cancel Cancel a pending transfer (either party may cancel)
28
+
29
+ Notes:
30
+ - Owner-side mutations on a project with a pending transfer return 409
31
+ PROJECT_HAS_PENDING_TRANSFER. Cancel the transfer or wait 72h for expiry.
32
+ - Phase 1A supports only billing_policy=migrate (default).
33
+ - Secret VALUES are inherited by the recipient on accept; rotation is advised.
34
+ - GitHub repo ownership is NOT transferred — handle that out of band.
35
+ `;
36
+
37
+ const SUB_HELP = {
38
+ init: `run402 transfer init — Initiate a project transfer
39
+
40
+ Usage:
41
+ run402 transfer init --to <wallet> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>]
42
+
43
+ Options:
44
+ --project <id> Project id (defaults to the active project)
45
+ --to <wallet> Recipient wallet (required). Any case; lowercased server-side.
46
+ --billing-policy <p> Billing policy. Phase 1A only allows 'migrate' (default).
47
+ --message <text> Optional note shown to the recipient in preview + emails.
48
+ --kysigned <record_id> Optional KySigned record id (Phase 1A: informational only).
49
+
50
+ Notes:
51
+ - Caller's wallet must currently own the project (gateway re-checks fresh DB).
52
+ - Owner-side mutations on the project are frozen until accept/cancel/expiry.
53
+ - The project lease stays with your billing account; it is NOT refunded.
54
+ `,
55
+ preview: `run402 transfer preview — Fetch the preview document
56
+
57
+ Usage:
58
+ run402 transfer preview <transfer_id>
59
+
60
+ Returns project name, custom domains, subdomains, function names, secret NAMES
61
+ (values are never returned), CI bindings that will be revoked on accept, and
62
+ billing implications. Either the from_wallet or the to_wallet may preview.
63
+ `,
64
+ list: `run402 transfer list — List pending transfers
65
+
66
+ Usage:
67
+ run402 transfer list [--incoming | --outgoing] [--limit N] [--offset N]
68
+
69
+ Options:
70
+ --incoming List transfers OFFERED TO your wallet (default).
71
+ --outgoing List transfers INITIATED BY your wallet.
72
+ --limit N Page size (default 50).
73
+ --offset N Pagination offset (default 0).
74
+ `,
75
+ accept: `run402 transfer accept — Accept an incoming transfer
76
+
77
+ Usage:
78
+ run402 transfer accept <transfer_id>
79
+
80
+ Your wallet must equal the transfer's to_wallet. The accept transaction
81
+ atomically: flips ownership, revokes the previous owner's CI bindings on the
82
+ project, enqueues notifications to both parties, and stamps a
83
+ 'secrets_rotation_advised' advisory on the project.
84
+ `,
85
+ cancel: `run402 transfer cancel — Cancel a pending transfer
86
+
87
+ Usage:
88
+ run402 transfer cancel <transfer_id> [--reason <text>]
89
+
90
+ Either the from_wallet or the to_wallet may cancel. Already-processed
91
+ transfers return 409 TRANSFER_ALREADY_PROCESSED.
92
+ `,
93
+ };
94
+
95
+ const BILLING_POLICIES = new Set(["migrate"]);
96
+
97
+ async function init(args) {
98
+ const parsedArgs = normalizeArgv(args);
99
+ const valueFlags = ["--project", "--to", "--billing-policy", "--message", "--kysigned"];
100
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
101
+ const extra = positionalArgs(parsedArgs, valueFlags);
102
+ if (extra.length > 0) {
103
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for transfer init: ${extra[0]}` });
104
+ }
105
+ const toWallet = flagValue(parsedArgs, "--to");
106
+ if (!toWallet) {
107
+ fail({ code: "BAD_USAGE", message: "Missing --to <wallet>" });
108
+ }
109
+ const projectFlag = flagValue(parsedArgs, "--project");
110
+ const projectId = await resolveProjectId(projectFlag);
111
+ if (!projectId) {
112
+ fail({ code: "NO_PROJECT", message: "No project id. Pass --project <id> or set an active project with 'run402 projects use <id>'." });
113
+ }
114
+ const billingPolicy = flagValue(parsedArgs, "--billing-policy");
115
+ if (billingPolicy !== null && !BILLING_POLICIES.has(billingPolicy)) {
116
+ fail({
117
+ code: "BAD_FLAG",
118
+ message: `Unsupported --billing-policy: ${billingPolicy}. Phase 1A allows only 'migrate'.`,
119
+ details: { flag: "--billing-policy", value: billingPolicy, allowed: [...BILLING_POLICIES] },
120
+ });
121
+ }
122
+ const message = flagValue(parsedArgs, "--message");
123
+ const kysigned = flagValue(parsedArgs, "--kysigned");
124
+
125
+ allowanceAuthHeaders(`/projects/v1/${projectId}/transfers`);
126
+
127
+ try {
128
+ const res = await getSdk().admin.transfers.initiate({
129
+ projectId,
130
+ toWallet,
131
+ billingPolicy: billingPolicy ?? undefined,
132
+ message: message ?? undefined,
133
+ kysignedRecordId: kysigned ?? undefined,
134
+ });
135
+ console.log(JSON.stringify(res, null, 2));
136
+ } catch (err) {
137
+ reportSdkError(err);
138
+ }
139
+ }
140
+
141
+ async function preview(args) {
142
+ const parsedArgs = normalizeArgv(args);
143
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
144
+ const positionals = positionalArgs(parsedArgs);
145
+ if (positionals.length !== 1) {
146
+ fail({ code: "BAD_USAGE", message: "Usage: run402 transfer preview <transfer_id>" });
147
+ }
148
+ const transferId = positionals[0];
149
+ allowanceAuthHeaders(`/agent/v1/transfers/${transferId}`);
150
+
151
+ try {
152
+ const data = await getSdk().admin.transfers.preview(transferId);
153
+ console.log(JSON.stringify(data, null, 2));
154
+ } catch (err) {
155
+ reportSdkError(err);
156
+ }
157
+ }
158
+
159
+ async function list(args) {
160
+ const parsedArgs = normalizeArgv(args);
161
+ const valueFlags = ["--limit", "--offset"];
162
+ assertKnownFlags(parsedArgs, [...valueFlags, "--incoming", "--outgoing", "--help", "-h"], valueFlags);
163
+ const extra = positionalArgs(parsedArgs, valueFlags);
164
+ if (extra.length > 0) {
165
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for transfer list: ${extra[0]}` });
166
+ }
167
+ const incoming = parsedArgs.includes("--incoming");
168
+ const outgoing = parsedArgs.includes("--outgoing");
169
+ if (incoming && outgoing) {
170
+ fail({ code: "BAD_USAGE", message: "Cannot pass both --incoming and --outgoing." });
171
+ }
172
+ const direction = outgoing ? "outgoing" : "incoming";
173
+
174
+ const limitFlag = flagValue(parsedArgs, "--limit");
175
+ const offsetFlag = flagValue(parsedArgs, "--offset");
176
+ const limit =
177
+ limitFlag === null
178
+ ? undefined
179
+ : parseIntegerFlag("--limit", limitFlag, { min: 1, max: 1000 });
180
+ const offset =
181
+ offsetFlag === null
182
+ ? undefined
183
+ : parseIntegerFlag("--offset", offsetFlag, { min: 0 });
184
+
185
+ allowanceAuthHeaders(`/agent/v1/transfers/${direction}`);
186
+
187
+ try {
188
+ const data =
189
+ direction === "incoming"
190
+ ? await getSdk().admin.transfers.listIncoming({ limit, offset })
191
+ : await getSdk().admin.transfers.listOutgoing({ limit, offset });
192
+ console.log(JSON.stringify({ direction, transfers: data }, null, 2));
193
+ } catch (err) {
194
+ reportSdkError(err);
195
+ }
196
+ }
197
+
198
+ async function accept(args) {
199
+ const parsedArgs = normalizeArgv(args);
200
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
201
+ const positionals = positionalArgs(parsedArgs);
202
+ if (positionals.length !== 1) {
203
+ fail({ code: "BAD_USAGE", message: "Usage: run402 transfer accept <transfer_id>" });
204
+ }
205
+ const transferId = positionals[0];
206
+ allowanceAuthHeaders(`/agent/v1/transfers/${transferId}/accept`);
207
+
208
+ try {
209
+ const data = await getSdk().admin.transfers.accept(transferId);
210
+ console.log(JSON.stringify(data, null, 2));
211
+ } catch (err) {
212
+ reportSdkError(err);
213
+ }
214
+ }
215
+
216
+ async function cancel(args) {
217
+ const parsedArgs = normalizeArgv(args);
218
+ const valueFlags = ["--reason"];
219
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
220
+ const positionals = positionalArgs(parsedArgs, valueFlags);
221
+ if (positionals.length !== 1) {
222
+ fail({ code: "BAD_USAGE", message: "Usage: run402 transfer cancel <transfer_id> [--reason <text>]" });
223
+ }
224
+ const transferId = positionals[0];
225
+ const reason = flagValue(parsedArgs, "--reason");
226
+ allowanceAuthHeaders(`/agent/v1/transfers/${transferId}/cancel`);
227
+
228
+ try {
229
+ const data = await getSdk().admin.transfers.cancel(transferId, reason ?? undefined);
230
+ console.log(JSON.stringify(data, null, 2));
231
+ } catch (err) {
232
+ reportSdkError(err);
233
+ }
234
+ }
235
+
236
+ export async function run(sub, args) {
237
+ if (!sub || sub === "--help" || sub === "-h") {
238
+ console.log(HELP);
239
+ process.exit(0);
240
+ }
241
+ if (Array.isArray(args) && hasHelp(args) && SUB_HELP[sub]) {
242
+ console.log(SUB_HELP[sub]);
243
+ process.exit(0);
244
+ }
245
+ switch (sub) {
246
+ case "init":
247
+ await init(args);
248
+ return;
249
+ case "preview":
250
+ await preview(args);
251
+ return;
252
+ case "list":
253
+ await list(args);
254
+ return;
255
+ case "accept":
256
+ await accept(args);
257
+ return;
258
+ case "cancel":
259
+ await cancel(args);
260
+ return;
261
+ default:
262
+ console.error(`Unknown subcommand: ${sub}\n`);
263
+ console.log(HELP);
264
+ process.exit(1);
265
+ }
266
+ }
package/lib/webhooks.mjs CHANGED
@@ -9,14 +9,16 @@ Usage:
9
9
  run402 email webhooks <action> [args...]
10
10
 
11
11
  Actions:
12
- list [--project <id>] List webhooks
13
- get <webhook_id> [--project <id>] Get a webhook
14
- delete <webhook_id> [--project <id>] Delete a webhook
15
- update <webhook_id> [--url <url>] [--events <e1,e2>] Update a webhook
16
- register --url <url> --events <e1,e2> [--project <id>] Register a new webhook
12
+ list [--mailbox <slug|id>] [--project <id>] List webhooks
13
+ get <webhook_id> [--mailbox <slug|id>] [--project <id>] Get a webhook
14
+ delete <webhook_id> [--mailbox <slug|id>] [--project <id>] Delete a webhook
15
+ update <webhook_id> [--url <url>] [--events <e1,e2>] [--mailbox <slug|id>] Update a webhook
16
+ register --url <url> --events <e1,e2> [--mailbox <slug|id>] [--project <id>] Register a new webhook
17
17
 
18
18
  Valid events: delivery, bounced, complained, reply_received
19
19
 
20
+ Pass --mailbox <slug|id> to target a specific mailbox when the project has more than one.
21
+
20
22
  Examples:
21
23
  run402 email webhooks list
22
24
  run402 email webhooks register --url https://example.com/hook --events delivery,bounced
@@ -67,10 +69,12 @@ function validateArgs(args, knownFlags, flagsWithValues = knownFlags) {
67
69
  }
68
70
 
69
71
  async function list(args) {
70
- validateArgs(args, ["--project"]);
72
+ const valueFlags = ["--project", "--mailbox"];
73
+ validateArgs(args, valueFlags);
71
74
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
75
+ const mailbox = strictFlagValue(args, "--mailbox");
72
76
  try {
73
- const data = await getSdk().email.webhooks.list(projectId);
77
+ const data = await getSdk().email.webhooks.list(projectId, { mailbox: mailbox ?? undefined });
74
78
  console.log(JSON.stringify(data, null, 2));
75
79
  } catch (err) {
76
80
  reportSdkError(err);
@@ -78,9 +82,11 @@ async function list(args) {
78
82
  }
79
83
 
80
84
  async function get(args) {
81
- validateArgs(args, ["--project"]);
82
- const webhookId = positionalArgs(args, ["--project"])[0] ?? null;
85
+ const valueFlags = ["--project", "--mailbox"];
86
+ validateArgs(args, valueFlags);
87
+ const webhookId = positionalArgs(args, valueFlags)[0] ?? null;
83
88
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
89
+ const mailbox = strictFlagValue(args, "--mailbox");
84
90
  if (!webhookId) {
85
91
  fail({
86
92
  code: "BAD_USAGE",
@@ -89,7 +95,7 @@ async function get(args) {
89
95
  });
90
96
  }
91
97
  try {
92
- const data = await getSdk().email.webhooks.get(projectId, webhookId);
98
+ const data = await getSdk().email.webhooks.get(projectId, webhookId, { mailbox: mailbox ?? undefined });
93
99
  console.log(JSON.stringify(data, null, 2));
94
100
  } catch (err) {
95
101
  reportSdkError(err);
@@ -97,9 +103,11 @@ async function get(args) {
97
103
  }
98
104
 
99
105
  async function del(args) {
100
- validateArgs(args, ["--project"]);
101
- const webhookId = positionalArgs(args, ["--project"])[0] ?? null;
106
+ const valueFlags = ["--project", "--mailbox"];
107
+ validateArgs(args, valueFlags);
108
+ const webhookId = positionalArgs(args, valueFlags)[0] ?? null;
102
109
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
110
+ const mailbox = strictFlagValue(args, "--mailbox");
103
111
  if (!webhookId) {
104
112
  fail({
105
113
  code: "BAD_USAGE",
@@ -108,7 +116,7 @@ async function del(args) {
108
116
  });
109
117
  }
110
118
  try {
111
- await getSdk().email.webhooks.delete(projectId, webhookId);
119
+ await getSdk().email.webhooks.delete(projectId, webhookId, { mailbox: mailbox ?? undefined });
112
120
  console.log(JSON.stringify({ webhook_id: webhookId, project_id: projectId, deleted: true }));
113
121
  } catch (err) {
114
122
  reportSdkError(err);
@@ -116,11 +124,12 @@ async function del(args) {
116
124
  }
117
125
 
118
126
  async function update(args) {
119
- const valueFlags = ["--project", "--url", "--events"];
127
+ const valueFlags = ["--project", "--url", "--events", "--mailbox"];
120
128
  validateArgs(args, valueFlags);
121
129
  const webhookId = positionalArgs(args, valueFlags)[0] ?? null;
122
130
  const url = strictFlagValue(args, "--url");
123
131
  const eventsRaw = strictFlagValue(args, "--events");
132
+ const mailbox = strictFlagValue(args, "--mailbox");
124
133
  const projectId = resolveProjectId(strictFlagValue(args, "--project"));
125
134
  if (!webhookId) {
126
135
  fail({
@@ -142,6 +151,7 @@ async function update(args) {
142
151
  const data = await getSdk().email.webhooks.update(projectId, webhookId, {
143
152
  url: url ?? undefined,
144
153
  events: eventsRaw ? eventsRaw.split(",").map((e) => e.trim()) : undefined,
154
+ mailbox: mailbox ?? undefined,
145
155
  });
146
156
  console.log(JSON.stringify(data));
147
157
  } catch (err) {
@@ -150,10 +160,11 @@ async function update(args) {
150
160
  }
151
161
 
152
162
  async function register(args) {
153
- const valueFlags = ["--project", "--url", "--events"];
163
+ const valueFlags = ["--project", "--url", "--events", "--mailbox"];
154
164
  validateArgs(args, valueFlags);
155
165
  const url = strictFlagValue(args, "--url");
156
166
  const eventsRaw = strictFlagValue(args, "--events");
167
+ const mailbox = strictFlagValue(args, "--mailbox");
157
168
  const projectOpt = strictFlagValue(args, "--project");
158
169
  const projectId = resolveProjectId(projectOpt);
159
170
 
@@ -178,7 +189,7 @@ async function register(args) {
178
189
 
179
190
  const events = eventsRaw.split(",").map((e) => e.trim());
180
191
  try {
181
- const data = await getSdk().email.webhooks.register(projectId, { url, events });
192
+ const data = await getSdk().email.webhooks.register(projectId, { url, events, mailbox: mailbox ?? undefined });
182
193
  console.log(JSON.stringify(data));
183
194
  } catch (err) {
184
195
  reportSdkError(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "2.19.1",
3
+ "version": "2.21.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": {
@@ -15,7 +15,7 @@
15
15
  * (or the exported `is*` guards) to branch on errors safely across SDK copies
16
16
  * and realms — value comparison, no class-identity dependency.
17
17
  */
18
- export type Run402ErrorKind = "payment_required" | "project_not_found" | "unauthorized" | "api_error" | "network_error" | "local_error" | "deploy_error";
18
+ export type Run402ErrorKind = "payment_required" | "project_not_found" | "unauthorized" | "api_error" | "network_error" | "local_error" | "deploy_error" | "transfer_freeze";
19
19
  /**
20
20
  * Quota-denial scope discriminator (v1.46+). Indicates whether a quota-related
21
21
  * denial was enforced against the pooled billing-account total (`"account"`)
@@ -187,6 +187,33 @@ export declare class Run402DeployError extends Run402Error {
187
187
  });
188
188
  toJSON(): Record<string, unknown>;
189
189
  }
190
+ /**
191
+ * Project has a pending transfer (v1.59+). Gateway returns 409 with
192
+ * `code: "PROJECT_HAS_PENDING_TRANSFER"` from the transfer-freeze middleware
193
+ * mounted on owner-side mutations (deploy, secrets, custom domains, function
194
+ * CRUD, scheduled-function changes, mailbox config, CI bindings, project
195
+ * rename, etc.). The pending transfer must be accepted, cancelled, or
196
+ * allowed to expire (72h) before owner-side mutations resume.
197
+ *
198
+ * The error carries `transferId` (when the gateway resolved it) and
199
+ * `cancelPath` lifted from `next_actions[].path`, so callers can present an
200
+ * actionable resolution path. `previewPath` mirrors the view-transfer
201
+ * next_action when present.
202
+ */
203
+ export declare class TransferFreezeError extends Run402Error {
204
+ static readonly DEFAULT_CODE = "PROJECT_HAS_PENDING_TRANSFER";
205
+ static readonly DEFAULT_CATEGORY = "validation";
206
+ static readonly DEFAULT_RETRYABLE = false;
207
+ readonly kind: "transfer_freeze";
208
+ /** The pending transfer id when the gateway resolved one. */
209
+ readonly transferId: string | null;
210
+ /** API path to cancel the pending transfer (e.g. `/agent/v1/transfers/<id>/cancel`). */
211
+ readonly cancelPath: string | null;
212
+ /** API path to view the pending transfer preview. */
213
+ readonly previewPath: string | null;
214
+ readonly projectId: string | null;
215
+ constructor(message: string, status: number, body: unknown, context: string);
216
+ }
190
217
  /** True if `e` is any {@link Run402Error} subclass instance, regardless of which SDK copy created it. */
191
218
  export declare function isRun402Error(e: unknown): e is Run402Error;
192
219
  /** True if `e` is a {@link PaymentRequired}. Survives duplicate SDK copies and realms. */
@@ -203,6 +230,8 @@ export declare function isNetworkError(e: unknown): e is NetworkError;
203
230
  export declare function isLocalError(e: unknown): e is LocalError;
204
231
  /** True if `e` is a {@link Run402DeployError}. */
205
232
  export declare function isDeployError(e: unknown): e is Run402DeployError;
233
+ /** True if `e` is a {@link TransferFreezeError}. */
234
+ export declare function isTransferFreezeError(e: unknown): e is TransferFreezeError;
206
235
  /**
207
236
  * Extract the v1.46+ quota-denial scope from an error. Returns `"account"`
208
237
  * for pooled denials, `"project"` for the orphan fallback, or `undefined`