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 +6 -0
- package/lib/email.mjs +35 -16
- package/lib/transfer.mjs +266 -0
- package/lib/webhooks.mjs +27 -16
- package/package.json +1 -1
- package/sdk/dist/errors.d.ts +30 -1
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +54 -0
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +3 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +2 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +10 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts +7 -0
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +8 -0
- package/sdk/dist/namespaces/admin.js.map +1 -1
- package/sdk/dist/namespaces/email.d.ts +65 -16
- package/sdk/dist/namespaces/email.d.ts.map +1 -1
- package/sdk/dist/namespaces/email.js +105 -106
- package/sdk/dist/namespaces/email.js.map +1 -1
- package/sdk/dist/namespaces/tier.d.ts +60 -0
- package/sdk/dist/namespaces/tier.d.ts.map +1 -1
- package/sdk/dist/namespaces/tier.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts +199 -0
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -0
- package/sdk/dist/namespaces/transfers.js +115 -0
- package/sdk/dist/namespaces/transfers.js.map +1 -0
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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);
|
package/lib/transfer.mjs
ADDED
|
@@ -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>]
|
|
13
|
-
get <webhook_id> [--project <id>]
|
|
14
|
-
delete <webhook_id> [--project <id>]
|
|
15
|
-
update <webhook_id> [--url <url>] [--events <e1,e2>]
|
|
16
|
-
register --url <url> --events <e1,e2> [--project <id>]
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
package/sdk/dist/errors.d.ts
CHANGED
|
@@ -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`
|