run402 1.36.0 → 1.36.1
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/lib/ai.mjs +27 -0
- package/lib/allowance.mjs +31 -0
- package/lib/apps.mjs +73 -0
- package/lib/auth.mjs +89 -2
- package/lib/billing.mjs +64 -0
- package/lib/blob.mjs +84 -1
- package/lib/contracts.mjs +139 -0
- package/lib/domains.mjs +1 -0
- package/lib/email.mjs +51 -0
- package/lib/functions.mjs +90 -1
- package/lib/init.mjs +16 -6
- package/lib/projects.mjs +111 -28
- package/lib/secrets.mjs +26 -1
- package/lib/sender-domain.mjs +1 -0
- package/lib/service.mjs +4 -4
- package/lib/sites.mjs +36 -0
- package/lib/storage.mjs +24 -0
- package/lib/subdomains.mjs +26 -0
- package/lib/tier.mjs +8 -1
- package/lib/webhooks.mjs +42 -0
- package/package.json +1 -1
package/lib/ai.mjs
CHANGED
|
@@ -22,6 +22,32 @@ Notes:
|
|
|
22
22
|
- usage shows translation word quota for the current billing period
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
+
const SUB_HELP = {
|
|
26
|
+
translate: `run402 ai translate — Translate text to another language
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
run402 ai translate <project_id> <text> --to <lang> [--from <lang>] [--context <hint>]
|
|
30
|
+
|
|
31
|
+
Arguments:
|
|
32
|
+
<project_id> Project ID (defaults to the active project if omitted)
|
|
33
|
+
<text> Text to translate (quote it to preserve spaces)
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
--to <lang> Target language code (required, e.g. es, ja, fr)
|
|
37
|
+
--from <lang> Source language code (optional; auto-detected if omitted)
|
|
38
|
+
--context <hint> Optional translation hint (e.g. "formal business email")
|
|
39
|
+
|
|
40
|
+
Notes:
|
|
41
|
+
- Requires the AI Translation add-on on the project
|
|
42
|
+
- Counts against the project's translation word quota
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
run402 ai translate proj-001 "Hello world" --to es
|
|
46
|
+
run402 ai translate proj-001 "Hello" --to ja --from en \\
|
|
47
|
+
--context "formal business email"
|
|
48
|
+
`,
|
|
49
|
+
};
|
|
50
|
+
|
|
25
51
|
function parseFlag(args, flag) {
|
|
26
52
|
for (let i = 0; i < args.length; i++) {
|
|
27
53
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -119,6 +145,7 @@ async function usage(args) {
|
|
|
119
145
|
|
|
120
146
|
export async function run(sub, args) {
|
|
121
147
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
148
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
122
149
|
switch (sub) {
|
|
123
150
|
case "translate": await translate(args); break;
|
|
124
151
|
case "moderate": await moderate(args); break;
|
package/lib/allowance.mjs
CHANGED
|
@@ -29,6 +29,33 @@ Examples:
|
|
|
29
29
|
run402 allowance history --limit 10
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
|
+
const SUB_HELP = {
|
|
33
|
+
checkout: `run402 allowance checkout — Create a billing checkout session
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
run402 allowance checkout --amount <usd_micros>
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--amount <n> Amount in USD micros (required; e.g. 5000000 for $5)
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
run402 allowance checkout --amount 5000000
|
|
43
|
+
run402 allowance checkout --amount 10000000
|
|
44
|
+
`,
|
|
45
|
+
history: `run402 allowance history — View billing transaction history
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
run402 allowance history [--limit <n>]
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
--limit <n> Max entries to return (default: 20)
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
run402 allowance history
|
|
55
|
+
run402 allowance history --limit 10
|
|
56
|
+
`,
|
|
57
|
+
};
|
|
58
|
+
|
|
32
59
|
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
33
60
|
const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
34
61
|
const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
@@ -213,6 +240,10 @@ export async function run(sub, args) {
|
|
|
213
240
|
console.log(HELP);
|
|
214
241
|
process.exit(0);
|
|
215
242
|
}
|
|
243
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
244
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
216
247
|
switch (sub) {
|
|
217
248
|
case "status": await status(); break;
|
|
218
249
|
case "create": await create(); break;
|
package/lib/apps.mjs
CHANGED
|
@@ -28,6 +28,78 @@ Examples:
|
|
|
28
28
|
run402 apps delete proj123 ver_abc123
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
|
+
const SUB_HELP = {
|
|
32
|
+
browse: `run402 apps browse — Browse public apps in the marketplace
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
run402 apps browse [--tag <tag>]
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--tag <tag> Filter by tag; repeat the flag to filter on multiple tags
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
run402 apps browse
|
|
42
|
+
run402 apps browse --tag auth
|
|
43
|
+
run402 apps browse --tag todo --tag auth
|
|
44
|
+
`,
|
|
45
|
+
fork: `run402 apps fork — Fork a published app into your own project
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
run402 apps fork <version_id> <name> [options]
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
<version_id> Published version ID (e.g. ver_abc123)
|
|
52
|
+
<name> Name for the forked project
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--tier <tier> Tier for the new project (default: prototype)
|
|
56
|
+
--subdomain <name> Claim a subdomain for the forked project
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
run402 apps fork ver_abc123 my-todo
|
|
60
|
+
run402 apps fork ver_abc123 my-todo --tier hobby --subdomain todo-v2
|
|
61
|
+
`,
|
|
62
|
+
publish: `run402 apps publish — Publish a project as an app
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
run402 apps publish <id> [options]
|
|
66
|
+
|
|
67
|
+
Arguments:
|
|
68
|
+
<id> Project ID to publish
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--description <d> Human-readable description of the app
|
|
72
|
+
--tags <t1,t2> Comma-separated list of tags
|
|
73
|
+
--visibility <v> Visibility: 'public' or 'private'
|
|
74
|
+
--fork-allowed Allow other users to fork this app
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
run402 apps publish proj123 --description "Todo app" --tags todo,auth
|
|
78
|
+
run402 apps publish proj123 --visibility public --fork-allowed
|
|
79
|
+
`,
|
|
80
|
+
update: `run402 apps update — Update a published version's metadata
|
|
81
|
+
|
|
82
|
+
Usage:
|
|
83
|
+
run402 apps update <project_id> <version_id> [options]
|
|
84
|
+
|
|
85
|
+
Arguments:
|
|
86
|
+
<project_id> Project ID that owns the version
|
|
87
|
+
<version_id> Published version ID to update
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
--description <d> New description
|
|
91
|
+
--tags <t1,t2> New comma-separated list of tags
|
|
92
|
+
--visibility <v> New visibility ('public' or 'private')
|
|
93
|
+
--fork-allowed Enable forking for this version
|
|
94
|
+
--no-fork Disable forking for this version
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
run402 apps update proj123 ver_abc123 --description "Updated"
|
|
98
|
+
run402 apps update proj123 ver_abc123 --tags todo,auth --fork-allowed
|
|
99
|
+
run402 apps update proj123 ver_abc123 --no-fork
|
|
100
|
+
`,
|
|
101
|
+
};
|
|
102
|
+
|
|
31
103
|
async function browse(args) {
|
|
32
104
|
let url = `${API}/apps/v1`;
|
|
33
105
|
const tags = [];
|
|
@@ -152,6 +224,7 @@ async function deleteVersion(projectId, versionId) {
|
|
|
152
224
|
|
|
153
225
|
export async function run(sub, args) {
|
|
154
226
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
227
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
155
228
|
switch (sub) {
|
|
156
229
|
case "browse": await browse(args); break;
|
|
157
230
|
case "fork": await fork(args[0], args[1], args.slice(2)); break;
|
package/lib/auth.mjs
CHANGED
|
@@ -29,6 +29,84 @@ Examples:
|
|
|
29
29
|
run402 auth providers
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
|
+
const SUB_HELP = {
|
|
33
|
+
"magic-link": `run402 auth magic-link — Send a passwordless login link
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
run402 auth magic-link --email <addr> --redirect <url> [options]
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--email <addr> Required: recipient email address
|
|
40
|
+
--redirect <url> Required: URL to redirect to after the user clicks
|
|
41
|
+
--project <id> Project ID (defaults to active project)
|
|
42
|
+
|
|
43
|
+
Notes:
|
|
44
|
+
Auto-creates the user on first use. Uses the project's anon_key.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
run402 auth magic-link --email user@example.com \\
|
|
48
|
+
--redirect https://myapp.run402.com/cb
|
|
49
|
+
`,
|
|
50
|
+
verify: `run402 auth verify — Exchange a magic-link token for session tokens
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
run402 auth verify --token <token> [options]
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--token <token> Required: the one-time magic-link token
|
|
57
|
+
--project <id> Project ID (defaults to active project)
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
Returns an access_token + refresh_token pair on success.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
run402 auth verify --token abc123def456
|
|
64
|
+
`,
|
|
65
|
+
"set-password": `run402 auth set-password — Change, reset, or set a user's password
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
run402 auth set-password --token <bearer> --new <password> [options]
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--token <bearer> Required: the user's access_token (Bearer token)
|
|
72
|
+
--new <password> Required: new password
|
|
73
|
+
--current <pwd> Current password (required when one is already set)
|
|
74
|
+
--project <id> Project ID (defaults to active project)
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
run402 auth set-password --token eyJ... --new "new-pass" \\
|
|
78
|
+
--current "old-pass"
|
|
79
|
+
`,
|
|
80
|
+
settings: `run402 auth settings — Update project auth settings
|
|
81
|
+
|
|
82
|
+
Usage:
|
|
83
|
+
run402 auth settings --allow-password-set <true|false> [options]
|
|
84
|
+
|
|
85
|
+
Options:
|
|
86
|
+
--allow-password-set <true|false> Required: toggle password-set flow
|
|
87
|
+
--project <id> Project ID (defaults to active project)
|
|
88
|
+
|
|
89
|
+
Notes:
|
|
90
|
+
Requires the project's service_key (admin-level).
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
run402 auth settings --allow-password-set true
|
|
94
|
+
run402 auth settings --allow-password-set false --project abc123
|
|
95
|
+
`,
|
|
96
|
+
providers: `run402 auth providers — List available auth providers
|
|
97
|
+
|
|
98
|
+
Usage:
|
|
99
|
+
run402 auth providers [options]
|
|
100
|
+
|
|
101
|
+
Options:
|
|
102
|
+
--project <id> Project ID (defaults to active project)
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
run402 auth providers
|
|
106
|
+
run402 auth providers --project abc123
|
|
107
|
+
`,
|
|
108
|
+
};
|
|
109
|
+
|
|
32
110
|
function parseFlag(args, flag) {
|
|
33
111
|
for (let i = 0; i < args.length; i++) {
|
|
34
112
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -90,6 +168,8 @@ async function setPassword(args) {
|
|
|
90
168
|
const accessToken = parseFlag(args, "--token");
|
|
91
169
|
const newPassword = parseFlag(args, "--new");
|
|
92
170
|
const currentPassword = parseFlag(args, "--current");
|
|
171
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
172
|
+
const p = findProject(projectId);
|
|
93
173
|
|
|
94
174
|
if (!accessToken) { console.error(JSON.stringify({ status: "error", message: "Missing --token <bearer_token>" })); process.exit(1); }
|
|
95
175
|
if (!newPassword) { console.error(JSON.stringify({ status: "error", message: "Missing --new <password>" })); process.exit(1); }
|
|
@@ -97,9 +177,16 @@ async function setPassword(args) {
|
|
|
97
177
|
const body = { new_password: newPassword };
|
|
98
178
|
if (currentPassword) body.current_password = currentPassword;
|
|
99
179
|
|
|
180
|
+
// /auth/v1/* is gated by apikeyAuth middleware: the `apikey` header must be
|
|
181
|
+
// the project's anon_key. `Authorization: Bearer <access_token>` stays as
|
|
182
|
+
// the user's identity so the server knows whose password to change.
|
|
100
183
|
const res = await fetch(`${API}/auth/v1/user/password`, {
|
|
101
184
|
method: "PUT",
|
|
102
|
-
headers: {
|
|
185
|
+
headers: {
|
|
186
|
+
"apikey": p.anon_key,
|
|
187
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
},
|
|
103
190
|
body: JSON.stringify(body),
|
|
104
191
|
});
|
|
105
192
|
const data = await res.json();
|
|
@@ -155,7 +242,7 @@ async function providers(args) {
|
|
|
155
242
|
export async function run(sub, args) {
|
|
156
243
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
157
244
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
158
|
-
console.log(HELP);
|
|
245
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
159
246
|
process.exit(0);
|
|
160
247
|
}
|
|
161
248
|
switch (sub) {
|
package/lib/billing.mjs
CHANGED
|
@@ -22,6 +22,69 @@ Examples:
|
|
|
22
22
|
run402 billing balance user@example.com
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
+
const SUB_HELP = {
|
|
26
|
+
"tier-checkout": `run402 billing tier-checkout — Create a Stripe tier checkout session
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
run402 billing tier-checkout <tier> [--email <e> | --wallet <w>]
|
|
30
|
+
|
|
31
|
+
Arguments:
|
|
32
|
+
<tier> Tier name (e.g. hobby, pro)
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--email <e> Email billing account to charge
|
|
36
|
+
--wallet <w> Wallet address (0x...) to associate with the checkout
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
run402 billing tier-checkout hobby --email user@example.com
|
|
40
|
+
run402 billing tier-checkout pro --wallet 0x1234...
|
|
41
|
+
`,
|
|
42
|
+
"buy-email-pack": `run402 billing buy-email-pack — Buy a $5 email pack (10,000 emails)
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
run402 billing buy-email-pack [--email <e> | --wallet <w>]
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--email <e> Email billing account to charge
|
|
49
|
+
--wallet <w> Wallet address (0x...) to associate with the purchase
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
run402 billing buy-email-pack --email user@example.com
|
|
53
|
+
run402 billing buy-email-pack --wallet 0x1234...
|
|
54
|
+
`,
|
|
55
|
+
"auto-recharge": `run402 billing auto-recharge — Toggle email-pack auto-recharge
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
run402 billing auto-recharge <account_id> <on|off> [--threshold <n>]
|
|
59
|
+
|
|
60
|
+
Arguments:
|
|
61
|
+
<account_id> Billing account ID
|
|
62
|
+
<on|off> Enable or disable auto-recharge
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
--threshold <n> Remaining-email threshold that triggers auto-recharge
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
run402 billing auto-recharge acct_abc on --threshold 2000
|
|
69
|
+
run402 billing auto-recharge acct_abc off
|
|
70
|
+
`,
|
|
71
|
+
history: `run402 billing history — Show ledger history for an email or wallet
|
|
72
|
+
|
|
73
|
+
Usage:
|
|
74
|
+
run402 billing history <identifier> [--limit <n>]
|
|
75
|
+
|
|
76
|
+
Arguments:
|
|
77
|
+
<identifier> Email address or wallet (0x...)
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--limit <n> Max entries to return (default: 50)
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
run402 billing history user@example.com
|
|
84
|
+
run402 billing history 0x1234... --limit 100
|
|
85
|
+
`,
|
|
86
|
+
};
|
|
87
|
+
|
|
25
88
|
function parseFlag(args, flag) {
|
|
26
89
|
for (let i = 0; i < args.length; i++) {
|
|
27
90
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -153,6 +216,7 @@ async function history(args) {
|
|
|
153
216
|
|
|
154
217
|
export async function run(sub, args) {
|
|
155
218
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
219
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
156
220
|
switch (sub) {
|
|
157
221
|
case "create-email": await createEmail(args); break;
|
|
158
222
|
case "link-wallet": await linkWallet(args); break;
|
package/lib/blob.mjs
CHANGED
|
@@ -68,6 +68,89 @@ Examples:
|
|
|
68
68
|
run402 blob sign images/logo.png --project abc123 --ttl 600
|
|
69
69
|
`;
|
|
70
70
|
|
|
71
|
+
const SUB_HELP = {
|
|
72
|
+
put: `run402 blob put — Upload one or more files to blob storage
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
run402 blob put <file> [files...] [options]
|
|
76
|
+
|
|
77
|
+
Arguments:
|
|
78
|
+
<file> Path to a file (or glob); pass multiple files to batch-upload
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
--project <id> Project ID (defaults to active project from 'run402 projects use')
|
|
82
|
+
--key <dest> Destination key; defaults to file basename. Use trailing '/' as prefix.
|
|
83
|
+
--private Upload as private (not served by CDN; apikey required to read)
|
|
84
|
+
--immutable Append content-hash suffix so overwrites produce distinct URLs
|
|
85
|
+
--concurrency N Concurrent part PUTs for multipart uploads (default 4)
|
|
86
|
+
--no-resume Ignore any cached resumable-upload state and start fresh
|
|
87
|
+
--json Emit NDJSON progress events on stdout (for agent consumption)
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
run402 blob put ./artifact.tgz --project abc123
|
|
91
|
+
run402 blob put ./dist/**/*.png --project abc123 --key assets/
|
|
92
|
+
run402 blob put huge.bin --project abc123 --immutable --concurrency 8
|
|
93
|
+
`,
|
|
94
|
+
get: `run402 blob get — Download a blob by key
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
run402 blob get <key> --output <file> [options]
|
|
98
|
+
|
|
99
|
+
Arguments:
|
|
100
|
+
<key> Blob key to download
|
|
101
|
+
|
|
102
|
+
Options:
|
|
103
|
+
--output <file> Local destination path (required)
|
|
104
|
+
--project <id> Project ID (defaults to active project)
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
run402 blob get images/logo.png --output /tmp/logo.png --project abc123
|
|
108
|
+
`,
|
|
109
|
+
ls: `run402 blob ls — List blob keys in a project
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
run402 blob ls [options]
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
--project <id> Project ID (defaults to active project)
|
|
116
|
+
--prefix <p> Only list keys starting with this prefix
|
|
117
|
+
--limit <n> Max results (default 100, max 1000)
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
run402 blob ls --project abc123
|
|
121
|
+
run402 blob ls --project abc123 --prefix images/ --limit 500
|
|
122
|
+
`,
|
|
123
|
+
rm: `run402 blob rm — Delete a blob
|
|
124
|
+
|
|
125
|
+
Usage:
|
|
126
|
+
run402 blob rm <key> [options]
|
|
127
|
+
|
|
128
|
+
Arguments:
|
|
129
|
+
<key> Blob key to delete
|
|
130
|
+
|
|
131
|
+
Options:
|
|
132
|
+
--project <id> Project ID (defaults to active project)
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
run402 blob rm images/logo.png --project abc123
|
|
136
|
+
`,
|
|
137
|
+
sign: `run402 blob sign — Create a presigned download URL for a blob
|
|
138
|
+
|
|
139
|
+
Usage:
|
|
140
|
+
run402 blob sign <key> [options]
|
|
141
|
+
|
|
142
|
+
Arguments:
|
|
143
|
+
<key> Blob key to sign
|
|
144
|
+
|
|
145
|
+
Options:
|
|
146
|
+
--project <id> Project ID (defaults to active project)
|
|
147
|
+
--ttl <seconds> Signed-URL TTL (default 3600, max 604800)
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
run402 blob sign reports/2025-q4.pdf --project abc123 --ttl 600
|
|
151
|
+
`,
|
|
152
|
+
};
|
|
153
|
+
|
|
71
154
|
const UPLOAD_STATE_DIR = join(homedir(), ".run402", "uploads");
|
|
72
155
|
|
|
73
156
|
function die(msg, code = 1) {
|
|
@@ -428,7 +511,7 @@ export async function run(sub, args) {
|
|
|
428
511
|
process.exit(0);
|
|
429
512
|
}
|
|
430
513
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
431
|
-
console.log(HELP);
|
|
514
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
432
515
|
process.exit(0);
|
|
433
516
|
}
|
|
434
517
|
const defaultProject = process.env.RUN402_PROJECT ?? null;
|
package/lib/contracts.mjs
CHANGED
|
@@ -36,6 +36,144 @@ Examples:
|
|
|
36
36
|
run402 contracts call proj_abc cwlt_xyz --to 0x1234... --abi '[{"type":"function","name":"ping","inputs":[],"outputs":[]}]' --fn ping --args '[]'
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
|
+
const SUB_HELP = {
|
|
40
|
+
"provision-wallet": `run402 contracts provision-wallet — Provision a KMS-backed wallet
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
run402 contracts provision-wallet <project_id> --chain <chain> [options]
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
<project_id> Target project ID
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--chain <chain> Required: base-mainnet or base-sepolia
|
|
50
|
+
--recovery 0x... Optional recovery address (can be set later)
|
|
51
|
+
--yes Skip confirmation when project already has a wallet
|
|
52
|
+
|
|
53
|
+
Pricing:
|
|
54
|
+
$0.04/day per wallet ($1.20/month). Creation requires $1.20 prepay
|
|
55
|
+
(30 days of rent). Non-custodial — see terms.html#non-custodial-kms-wallets.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
run402 contracts provision-wallet proj_abc --chain base-mainnet
|
|
59
|
+
run402 contracts provision-wallet proj_abc --chain base-sepolia --recovery 0xAbC...
|
|
60
|
+
`,
|
|
61
|
+
"set-recovery": `run402 contracts set-recovery — Set or clear the wallet recovery address
|
|
62
|
+
|
|
63
|
+
Usage:
|
|
64
|
+
run402 contracts set-recovery <project_id> <wallet_id> [options]
|
|
65
|
+
|
|
66
|
+
Arguments:
|
|
67
|
+
<project_id> Target project ID
|
|
68
|
+
<wallet_id> KMS wallet ID (cwlt_...)
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--address 0x... New recovery address
|
|
72
|
+
--clear Clear the recovery address (mutually exclusive with --address)
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
run402 contracts set-recovery proj_abc cwlt_xyz --address 0xAbC...
|
|
76
|
+
run402 contracts set-recovery proj_abc cwlt_xyz --clear
|
|
77
|
+
`,
|
|
78
|
+
"set-alert": `run402 contracts set-alert — Set the low-balance alert threshold
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
run402 contracts set-alert <project_id> <wallet_id> --threshold-wei <n>
|
|
82
|
+
|
|
83
|
+
Arguments:
|
|
84
|
+
<project_id> Target project ID
|
|
85
|
+
<wallet_id> KMS wallet ID (cwlt_...)
|
|
86
|
+
|
|
87
|
+
Options:
|
|
88
|
+
--threshold-wei <n> Required: alert threshold in wei
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
run402 contracts set-alert proj_abc cwlt_xyz --threshold-wei 1000000000000000
|
|
92
|
+
`,
|
|
93
|
+
call: `run402 contracts call — Submit a contract write call
|
|
94
|
+
|
|
95
|
+
Usage:
|
|
96
|
+
run402 contracts call <project_id> <wallet_id> --to 0x... --abi <json>
|
|
97
|
+
--fn <name> --args <json> [options]
|
|
98
|
+
|
|
99
|
+
Arguments:
|
|
100
|
+
<project_id> Target project ID
|
|
101
|
+
<wallet_id> KMS wallet ID (cwlt_...)
|
|
102
|
+
|
|
103
|
+
Options:
|
|
104
|
+
--to 0x... Required: contract address
|
|
105
|
+
--abi <json> Required: ABI fragment (JSON string)
|
|
106
|
+
--fn <name> Required: function name to invoke
|
|
107
|
+
--args <json> Required: function args (JSON array)
|
|
108
|
+
--value-wei <n> Native value to send (default 0)
|
|
109
|
+
--chain <chain> Chain override (default: base-mainnet)
|
|
110
|
+
--idempotency-key <k> Idempotency key for safe retries
|
|
111
|
+
|
|
112
|
+
Pricing:
|
|
113
|
+
Chain gas + $0.000005 KMS sign fee per call.
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
run402 contracts call proj_abc cwlt_xyz --to 0x1234... \\
|
|
117
|
+
--abi '[{"type":"function","name":"ping","inputs":[],"outputs":[]}]' \\
|
|
118
|
+
--fn ping --args '[]'
|
|
119
|
+
`,
|
|
120
|
+
read: `run402 contracts read — Read-only contract call (free)
|
|
121
|
+
|
|
122
|
+
Usage:
|
|
123
|
+
run402 contracts read --chain <chain> --to 0x... --abi <json>
|
|
124
|
+
--fn <name> --args <json>
|
|
125
|
+
|
|
126
|
+
Options:
|
|
127
|
+
--chain <chain> Required: base-mainnet or base-sepolia
|
|
128
|
+
--to 0x... Required: contract address
|
|
129
|
+
--abi <json> Required: ABI fragment (JSON string)
|
|
130
|
+
--fn <name> Required: function name to invoke
|
|
131
|
+
--args <json> Required: function args (JSON array)
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
run402 contracts read --chain base-mainnet --to 0x1234... \\
|
|
135
|
+
--abi '[{"type":"function","name":"balanceOf","inputs":[{"type":"address"}],"outputs":[{"type":"uint256"}]}]' \\
|
|
136
|
+
--fn balanceOf --args '["0xAbC..."]'
|
|
137
|
+
`,
|
|
138
|
+
drain: `run402 contracts drain — Drain native balance to a destination address
|
|
139
|
+
|
|
140
|
+
Usage:
|
|
141
|
+
run402 contracts drain <project_id> <wallet_id> --to 0x... --confirm
|
|
142
|
+
|
|
143
|
+
Arguments:
|
|
144
|
+
<project_id> Target project ID
|
|
145
|
+
<wallet_id> KMS wallet ID (cwlt_...)
|
|
146
|
+
|
|
147
|
+
Options:
|
|
148
|
+
--to 0x... Required: destination address
|
|
149
|
+
--confirm Required: explicit confirmation flag
|
|
150
|
+
|
|
151
|
+
Notes:
|
|
152
|
+
Works on suspended wallets. Cost: chain gas + $0.000005 KMS sign fee.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
run402 contracts drain proj_abc cwlt_xyz --to 0xAbC... --confirm
|
|
156
|
+
`,
|
|
157
|
+
delete: `run402 contracts delete — Schedule the KMS key for deletion
|
|
158
|
+
|
|
159
|
+
Usage:
|
|
160
|
+
run402 contracts delete <project_id> <wallet_id> --confirm
|
|
161
|
+
|
|
162
|
+
Arguments:
|
|
163
|
+
<project_id> Target project ID
|
|
164
|
+
<wallet_id> KMS wallet ID (cwlt_...)
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
--confirm Required: explicit confirmation flag
|
|
168
|
+
|
|
169
|
+
Notes:
|
|
170
|
+
Refused if wallet balance is greater than or equal to dust. Drain first.
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
run402 contracts delete proj_abc cwlt_xyz --confirm
|
|
174
|
+
`,
|
|
175
|
+
};
|
|
176
|
+
|
|
39
177
|
function parseFlag(args, flag) {
|
|
40
178
|
for (let i = 0; i < args.length; i++) {
|
|
41
179
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -243,6 +381,7 @@ async function deleteWallet(projectId, walletId, args) {
|
|
|
243
381
|
|
|
244
382
|
export async function run(sub, args) {
|
|
245
383
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
384
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
246
385
|
switch (sub) {
|
|
247
386
|
case "provision-wallet": await provisionWallet(args[0], args.slice(1)); break;
|
|
248
387
|
case "get-wallet": await getWallet(args[0], args[1]); break;
|
package/lib/domains.mjs
CHANGED
|
@@ -92,6 +92,7 @@ async function deleteDomain(args) {
|
|
|
92
92
|
|
|
93
93
|
export async function run(sub, args) {
|
|
94
94
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
95
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(HELP); process.exit(0); }
|
|
95
96
|
switch (sub) {
|
|
96
97
|
case "add": await add(args); break;
|
|
97
98
|
case "list": await list(args[0]); break;
|
package/lib/email.mjs
CHANGED
|
@@ -56,6 +56,56 @@ Notes:
|
|
|
56
56
|
- --project defaults to the active project
|
|
57
57
|
`;
|
|
58
58
|
|
|
59
|
+
const SUB_HELP = {
|
|
60
|
+
send: `run402 email send — Send an email (template or raw HTML)
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
run402 email send --to <email> --template <name> --var key=value [--var ...]
|
|
64
|
+
run402 email send --to <email> --subject "..." --html "..." [--text "..."]
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--to <email> Recipient email address (required; single recipient)
|
|
68
|
+
--template <name> Template name (template mode): project_invite, magic_link,
|
|
69
|
+
notification
|
|
70
|
+
--var key=value Template variable (repeatable; required keys vary by
|
|
71
|
+
template)
|
|
72
|
+
--subject "..." Subject line (raw HTML mode)
|
|
73
|
+
--html "..." HTML body (raw HTML mode)
|
|
74
|
+
--text "..." Plain-text body (raw HTML mode; optional)
|
|
75
|
+
--from-name "..." Display name for the From header
|
|
76
|
+
--project <id> Project ID (defaults to the active project)
|
|
77
|
+
|
|
78
|
+
Templates:
|
|
79
|
+
project_invite --var project_name=... --var invite_url=...
|
|
80
|
+
magic_link --var project_name=... --var link_url=... --var expires_in=...
|
|
81
|
+
notification --var project_name=... --var message=... (max 500 chars)
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
run402 email send --template project_invite --to user@example.com \\
|
|
85
|
+
--var project_name="My App" --var invite_url="https://example.com/invite/abc"
|
|
86
|
+
run402 email send --to user@example.com --subject "Welcome!" \\
|
|
87
|
+
--html "<h1>Hello</h1><p>Welcome aboard.</p>" --from-name "My App"
|
|
88
|
+
run402 email send --template notification --to admin@example.com \\
|
|
89
|
+
--var project_name="My App" --var message="Deploy complete"
|
|
90
|
+
`,
|
|
91
|
+
"get-raw": `run402 email get-raw — Fetch raw RFC-822 bytes for an inbound message
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
run402 email get-raw <message_id> [--output <file>] [--project <id>]
|
|
95
|
+
|
|
96
|
+
Arguments:
|
|
97
|
+
<message_id> Message ID to fetch (inbound messages only)
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--output <file> Write raw bytes to this file; omit to stream to stdout
|
|
101
|
+
--project <id> Project ID (defaults to the active project)
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
run402 email get-raw msg_abc123 --output reply.eml
|
|
105
|
+
run402 email get-raw msg_abc123 > reply.eml
|
|
106
|
+
`,
|
|
107
|
+
};
|
|
108
|
+
|
|
59
109
|
const SLUG_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
60
110
|
|
|
61
111
|
function parseFlag(args, flag) {
|
|
@@ -327,6 +377,7 @@ async function status(args) {
|
|
|
327
377
|
|
|
328
378
|
export async function run(sub, args) {
|
|
329
379
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
380
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h")) && sub !== "webhooks") { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
330
381
|
switch (sub) {
|
|
331
382
|
case "create": await create(args); break;
|
|
332
383
|
case "status": await status(args); break;
|
package/lib/functions.mjs
CHANGED
|
@@ -38,6 +38,95 @@ Notes:
|
|
|
38
38
|
- Deploy may require payment if the project lease has expired
|
|
39
39
|
`;
|
|
40
40
|
|
|
41
|
+
const SUB_HELP = {
|
|
42
|
+
deploy: `run402 functions deploy — Deploy a function to a project
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
run402 functions deploy <project_id> <name> --file <file> [options]
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
48
|
+
<project_id> Target project ID
|
|
49
|
+
<name> Function name (used in the invoke URL path)
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
--file <file> Required: path to the function source file
|
|
53
|
+
--timeout <s> Runtime timeout in seconds
|
|
54
|
+
--memory <mb> Memory in MB
|
|
55
|
+
--deps <pkg,...> Comma-separated npm deps to bundle
|
|
56
|
+
--schedule <cron> Cron schedule; pass '' to clear an existing schedule
|
|
57
|
+
|
|
58
|
+
Notes:
|
|
59
|
+
Code must export a default async function:
|
|
60
|
+
export default async (req: Request) => Response
|
|
61
|
+
Deploy may require payment if the project lease has expired.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
run402 functions deploy abc123 stripe-webhook --file handler.ts
|
|
65
|
+
run402 functions deploy abc123 send-reminders --file remind.ts \\
|
|
66
|
+
--schedule '*/15 * * * *'
|
|
67
|
+
run402 functions deploy abc123 send-reminders --file remind.ts --schedule ''
|
|
68
|
+
`,
|
|
69
|
+
invoke: `run402 functions invoke — Invoke a deployed function
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
run402 functions invoke <project_id> <name> [options]
|
|
73
|
+
|
|
74
|
+
Arguments:
|
|
75
|
+
<project_id> Target project ID
|
|
76
|
+
<name> Function name
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
--method <M> HTTP method (default POST)
|
|
80
|
+
--body <json> Request body (ignored for GET/HEAD)
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
run402 functions invoke abc123 stripe-webhook --body '{"event":"test"}'
|
|
84
|
+
run402 functions invoke abc123 ping --method GET
|
|
85
|
+
`,
|
|
86
|
+
logs: `run402 functions logs — Fetch or tail function logs
|
|
87
|
+
|
|
88
|
+
Usage:
|
|
89
|
+
run402 functions logs <project_id> <name> [options]
|
|
90
|
+
|
|
91
|
+
Arguments:
|
|
92
|
+
<project_id> Target project ID
|
|
93
|
+
<name> Function name
|
|
94
|
+
|
|
95
|
+
Options:
|
|
96
|
+
--tail <n> Number of most-recent entries (default 50)
|
|
97
|
+
--since <ts> ISO timestamp or epoch ms; only entries after this
|
|
98
|
+
--follow Poll every 3s and stream new entries (Ctrl-C to stop)
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
run402 functions logs abc123 stripe-webhook --tail 100
|
|
102
|
+
run402 functions logs abc123 stripe-webhook --since 2026-03-29T14:00:00Z
|
|
103
|
+
run402 functions logs abc123 stripe-webhook --follow
|
|
104
|
+
`,
|
|
105
|
+
update: `run402 functions update — Update function config without re-deploying
|
|
106
|
+
|
|
107
|
+
Usage:
|
|
108
|
+
run402 functions update <project_id> <name> [options]
|
|
109
|
+
|
|
110
|
+
Arguments:
|
|
111
|
+
<project_id> Target project ID
|
|
112
|
+
<name> Function name
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
--schedule <cron> New cron schedule (pass '' to clear)
|
|
116
|
+
--schedule-remove Explicitly remove the schedule
|
|
117
|
+
--timeout <s> Runtime timeout in seconds
|
|
118
|
+
--memory <mb> Memory in MB
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
Must provide at least one of the options above.
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
run402 functions update abc123 send-reminders --schedule '0 */4 * * *'
|
|
125
|
+
run402 functions update abc123 send-reminders --schedule-remove
|
|
126
|
+
run402 functions update abc123 my-func --timeout 15 --memory 256
|
|
127
|
+
`,
|
|
128
|
+
};
|
|
129
|
+
|
|
41
130
|
async function deploy(projectId, name, args) {
|
|
42
131
|
const p = findProject(projectId);
|
|
43
132
|
const opts = { file: null, timeout: undefined, memory: undefined, deps: undefined, schedule: undefined };
|
|
@@ -213,7 +302,7 @@ async function deleteFunction(projectId, name) {
|
|
|
213
302
|
export async function run(sub, args) {
|
|
214
303
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
215
304
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
216
|
-
console.log(HELP);
|
|
305
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
217
306
|
process.exit(0);
|
|
218
307
|
}
|
|
219
308
|
switch (sub) {
|
package/lib/init.mjs
CHANGED
|
@@ -112,16 +112,23 @@ export async function run(args = []) {
|
|
|
112
112
|
});
|
|
113
113
|
const data = await res.json();
|
|
114
114
|
if (data.result) {
|
|
115
|
-
// Tempo faucet is instant
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
// Tempo faucet is "instant" on-chain, but the client RPC read can be
|
|
116
|
+
// racy relative to faucet settlement — poll up to 30s (GH-81), mirroring
|
|
117
|
+
// the x402 path below.
|
|
118
|
+
for (let i = 0; i < 30; i++) {
|
|
119
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
120
|
+
try {
|
|
121
|
+
const raw = await client.readContract({ address: PATH_USD, abi: USDC_ABI, functionName: "balanceOf", args: [allowance.address] });
|
|
122
|
+
balance = Number(raw);
|
|
123
|
+
if (balance > 0) break;
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
120
126
|
saveAllowance({ ...allowance, funded: true, lastFaucet: new Date().toISOString() });
|
|
127
|
+
summary.allowance.funded = true;
|
|
121
128
|
if (balance > 0) {
|
|
122
129
|
line("Balance", `${(balance / 1e6).toFixed(2)} pathUSD (funded)`);
|
|
123
130
|
} else {
|
|
124
|
-
line("Balance", "faucet sent —
|
|
131
|
+
line("Balance", "faucet sent — not yet confirmed on-chain");
|
|
125
132
|
}
|
|
126
133
|
} else {
|
|
127
134
|
line("Balance", `faucet failed: ${data.error?.message || "unknown error"}`);
|
|
@@ -131,6 +138,7 @@ export async function run(args = []) {
|
|
|
131
138
|
}
|
|
132
139
|
} else {
|
|
133
140
|
line("Balance", `${(balance / 1e6).toFixed(2)} pathUSD`);
|
|
141
|
+
summary.allowance.funded = balance > 0;
|
|
134
142
|
}
|
|
135
143
|
summary.balance = { symbol: "pathUSD", usd_micros: balance };
|
|
136
144
|
} else {
|
|
@@ -162,6 +170,7 @@ export async function run(args = []) {
|
|
|
162
170
|
} catch {}
|
|
163
171
|
}
|
|
164
172
|
saveAllowance({ ...allowance, funded: true, lastFaucet: new Date().toISOString() });
|
|
173
|
+
summary.allowance.funded = true;
|
|
165
174
|
if (balance > 0) {
|
|
166
175
|
line("Balance", `${(balance / 1e6).toFixed(2)} USDC (funded)`);
|
|
167
176
|
} else {
|
|
@@ -174,6 +183,7 @@ export async function run(args = []) {
|
|
|
174
183
|
}
|
|
175
184
|
} else {
|
|
176
185
|
line("Balance", `${(balance / 1e6).toFixed(2)} USDC`);
|
|
186
|
+
summary.allowance.funded = balance > 0;
|
|
177
187
|
}
|
|
178
188
|
summary.balance = { symbol: "USDC", usd_micros: balance };
|
|
179
189
|
}
|
package/lib/projects.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { findProject, loadKeyStore, saveProject, removeProject, API, allowanceAuthHeaders, setActiveProjectId, getActiveProjectId } from "./config.mjs";
|
|
2
|
+
import { findProject, loadKeyStore, saveProject, removeProject, API, allowanceAuthHeaders, setActiveProjectId, getActiveProjectId, resolveProjectId } from "./config.mjs";
|
|
3
3
|
|
|
4
4
|
const HELP = `run402 projects — Manage your deployed Run402 projects
|
|
5
5
|
|
|
@@ -11,17 +11,17 @@ Subcommands:
|
|
|
11
11
|
provision [--tier <tier>] [--name <n>] Provision a new Postgres project (pays via x402)
|
|
12
12
|
use <id> Set the active project (used as default for other commands)
|
|
13
13
|
list List all your projects (IDs, URLs, active marker)
|
|
14
|
-
info
|
|
15
|
-
keys
|
|
16
|
-
sql
|
|
17
|
-
rest
|
|
18
|
-
usage
|
|
19
|
-
schema
|
|
20
|
-
rls
|
|
21
|
-
delete
|
|
22
|
-
pin
|
|
23
|
-
promote-user
|
|
24
|
-
demote-user
|
|
14
|
+
info [id] Show project details: REST URL, keys
|
|
15
|
+
keys [id] Print anon_key and service_key as JSON
|
|
16
|
+
sql [id] "<query>" [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
|
|
17
|
+
rest [id] <table> [params] Query a table via the REST API (PostgREST)
|
|
18
|
+
usage [id] Show compute/storage usage for a project
|
|
19
|
+
schema [id] Inspect the database schema
|
|
20
|
+
rls [id] <template> <tables_json> Apply Row-Level Security policies
|
|
21
|
+
delete [id] Immediately and irreversibly delete a project (cascade purge) and remove from local state
|
|
22
|
+
pin [id] Pin a project (prevents expiry/GC)
|
|
23
|
+
promote-user [id] <email> Promote a user to project_admin role
|
|
24
|
+
demote-user [id] <email> Demote a user from project_admin role
|
|
25
25
|
|
|
26
26
|
Examples:
|
|
27
27
|
run402 projects quote
|
|
@@ -41,8 +41,10 @@ Examples:
|
|
|
41
41
|
run402 projects delete abc123
|
|
42
42
|
|
|
43
43
|
Notes:
|
|
44
|
-
- <id> is the project_id shown in 'run402 projects list'
|
|
45
|
-
- Most commands that take <id> default to the active project
|
|
44
|
+
- <id> is the project_id shown in 'run402 projects list' (prefix: 'prj_')
|
|
45
|
+
- Most commands that take <id> default to the active project when omitted
|
|
46
|
+
(set it with 'run402 projects use <id>'). Project IDs start with 'prj_';
|
|
47
|
+
any first positional that doesn't is treated as the next argument instead.
|
|
46
48
|
- 'rest' uses PostgREST query syntax (table name + optional query string)
|
|
47
49
|
- 'provision' requires a funded allowance — payment is automatic via x402
|
|
48
50
|
- RLS templates (prefer user_owns_rows for user-scoped data):
|
|
@@ -52,6 +54,48 @@ Notes:
|
|
|
52
54
|
that includes "i_understand_this_is_unrestricted": true
|
|
53
55
|
`;
|
|
54
56
|
|
|
57
|
+
const SUB_HELP = {
|
|
58
|
+
provision: `run402 projects provision — Provision a new Postgres project
|
|
59
|
+
|
|
60
|
+
Usage:
|
|
61
|
+
run402 projects provision [--tier <tier>] [--name <name>]
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
--tier <tier> Tier for the new project (default: prototype)
|
|
65
|
+
--name <name> Human-readable name for the project
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
- Payment is automatic via x402; requires a funded allowance
|
|
69
|
+
- The new project becomes the active project after provisioning
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
run402 projects provision
|
|
73
|
+
run402 projects provision --tier prototype
|
|
74
|
+
run402 projects provision --tier hobby --name my-app
|
|
75
|
+
`,
|
|
76
|
+
sql: `run402 projects sql — Run a SQL query against a project's database
|
|
77
|
+
|
|
78
|
+
Usage:
|
|
79
|
+
run402 projects sql [id] "<query>" [options]
|
|
80
|
+
run402 projects sql [id] --file <path> [options]
|
|
81
|
+
|
|
82
|
+
Arguments:
|
|
83
|
+
[id] Project ID (defaults to the active project if omitted;
|
|
84
|
+
must start with 'prj_' — any other first arg is treated
|
|
85
|
+
as the query instead)
|
|
86
|
+
<query> Inline SQL query (quote it to preserve spaces)
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
--file <path> Read SQL from a file instead of an inline query
|
|
90
|
+
--params '<json>' JSON array of parameters for a parameterized query
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
run402 projects sql abc123 "SELECT * FROM users LIMIT 5"
|
|
94
|
+
run402 projects sql abc123 "SELECT * FROM users WHERE id = $1" --params '[42]'
|
|
95
|
+
run402 projects sql abc123 --file setup.sql
|
|
96
|
+
`,
|
|
97
|
+
};
|
|
98
|
+
|
|
55
99
|
async function quote() {
|
|
56
100
|
const res = await fetch(`${API}/tiers/v1`);
|
|
57
101
|
const data = await res.json();
|
|
@@ -73,8 +117,34 @@ async function provision(args) {
|
|
|
73
117
|
headers: { "Content-Type": "application/json", ...authHeaders },
|
|
74
118
|
body: JSON.stringify(body),
|
|
75
119
|
});
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
// Content-type aware parsing: gateways (ALB, CloudFront, etc.) return HTML on
|
|
121
|
+
// 502/504/etc., which would otherwise crash res.json() with SyntaxError (GH-84).
|
|
122
|
+
const contentType = res.headers.get("content-type") || "";
|
|
123
|
+
let data = null;
|
|
124
|
+
let parseError = null;
|
|
125
|
+
let bodyText = null;
|
|
126
|
+
if (contentType.includes("application/json")) {
|
|
127
|
+
try {
|
|
128
|
+
data = await res.json();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
parseError = e;
|
|
131
|
+
try { bodyText = await res.text(); } catch { bodyText = ""; }
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
try { bodyText = await res.text(); } catch { bodyText = ""; }
|
|
135
|
+
}
|
|
136
|
+
if (!res.ok || parseError || data === null) {
|
|
137
|
+
const err = { status: "error", http: res.status, content_type: contentType || null };
|
|
138
|
+
if (data && typeof data === "object") {
|
|
139
|
+
Object.assign(err, data);
|
|
140
|
+
} else {
|
|
141
|
+
const preview = typeof bodyText === "string" ? bodyText.slice(0, 500) : "";
|
|
142
|
+
err.body_preview = preview;
|
|
143
|
+
if (parseError) err.parse_error = "response body was not valid JSON";
|
|
144
|
+
}
|
|
145
|
+
console.error(JSON.stringify(err));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
78
148
|
// Save project credentials locally and set as active
|
|
79
149
|
if (data.project_id) {
|
|
80
150
|
saveProject(data.project_id, {
|
|
@@ -231,13 +301,26 @@ async function deleteProject(projectId) {
|
|
|
231
301
|
}
|
|
232
302
|
}
|
|
233
303
|
|
|
304
|
+
// Resolve a positional project_id argument with active-project fallback (GH-102).
|
|
305
|
+
// Heuristic: real project IDs start with "prj_". If args[0] is missing OR
|
|
306
|
+
// doesn't start with "prj_", fall back to the active project and return the
|
|
307
|
+
// full args array as remaining positionals. Otherwise consume args[0] as the
|
|
308
|
+
// project_id and return args.slice(1) as remaining positionals.
|
|
309
|
+
function resolvePositionalProject(args) {
|
|
310
|
+
const first = Array.isArray(args) ? args[0] : undefined;
|
|
311
|
+
if (typeof first === "string" && first.startsWith("prj_")) {
|
|
312
|
+
return { projectId: first, rest: args.slice(1) };
|
|
313
|
+
}
|
|
314
|
+
return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
|
|
315
|
+
}
|
|
316
|
+
|
|
234
317
|
export async function run(sub, args) {
|
|
235
318
|
if (!sub || sub === '--help' || sub === '-h') {
|
|
236
319
|
console.log(HELP);
|
|
237
320
|
process.exit(0);
|
|
238
321
|
}
|
|
239
322
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
240
|
-
console.log(HELP);
|
|
323
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
241
324
|
process.exit(0);
|
|
242
325
|
}
|
|
243
326
|
switch (sub) {
|
|
@@ -245,17 +328,17 @@ export async function run(sub, args) {
|
|
|
245
328
|
case "provision": await provision(args); break;
|
|
246
329
|
case "use": await use(args[0]); break;
|
|
247
330
|
case "list": await list(); break;
|
|
248
|
-
case "info": await info(
|
|
249
|
-
case "keys": await keys(
|
|
250
|
-
case "sql": await sqlCmd(
|
|
251
|
-
case "rest": await rest(
|
|
252
|
-
case "usage": await usage(
|
|
253
|
-
case "schema": await schema(
|
|
254
|
-
case "rls": await rls(
|
|
255
|
-
case "delete": await deleteProject(
|
|
256
|
-
case "pin":
|
|
257
|
-
case "promote-user": await promoteUser(
|
|
258
|
-
case "demote-user": await demoteUser(
|
|
331
|
+
case "info": { const { projectId } = resolvePositionalProject(args); await info(projectId); break; }
|
|
332
|
+
case "keys": { const { projectId } = resolvePositionalProject(args); await keys(projectId); break; }
|
|
333
|
+
case "sql": { const { projectId, rest } = resolvePositionalProject(args); await sqlCmd(projectId, rest); break; }
|
|
334
|
+
case "rest": { const { projectId, rest: restArgs } = resolvePositionalProject(args); await rest(projectId, restArgs[0], restArgs[1]); break; }
|
|
335
|
+
case "usage": { const { projectId } = resolvePositionalProject(args); await usage(projectId); break; }
|
|
336
|
+
case "schema": { const { projectId } = resolvePositionalProject(args); await schema(projectId); break; }
|
|
337
|
+
case "rls": { const { projectId, rest } = resolvePositionalProject(args); await rls(projectId, rest[0], rest[1]); break; }
|
|
338
|
+
case "delete": { const { projectId } = resolvePositionalProject(args); await deleteProject(projectId); break; }
|
|
339
|
+
case "pin": { const { projectId } = resolvePositionalProject(args); await pin(projectId); break; }
|
|
340
|
+
case "promote-user": { const { projectId, rest } = resolvePositionalProject(args); await promoteUser(projectId, rest[0]); break; }
|
|
341
|
+
case "demote-user": { const { projectId, rest } = resolvePositionalProject(args); await demoteUser(projectId, rest[0]); break; }
|
|
259
342
|
default:
|
|
260
343
|
console.error(`Unknown subcommand: ${sub}\n`);
|
|
261
344
|
console.log(HELP);
|
package/lib/secrets.mjs
CHANGED
|
@@ -22,6 +22,31 @@ Notes:
|
|
|
22
22
|
- Values are write-only — list returns keys with a value_hash (first 8 hex chars of SHA-256) for verifying the correct value was set
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
+
const SUB_HELP = {
|
|
26
|
+
set: `run402 secrets set — Set a secret on a project
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
run402 secrets set <id> <key> <value> [--file <path>]
|
|
30
|
+
run402 secrets set <id> <key> --file <path>
|
|
31
|
+
|
|
32
|
+
Arguments:
|
|
33
|
+
<id> Project ID (from 'run402 projects list')
|
|
34
|
+
<key> Secret key name (exposed as process.env.<key>)
|
|
35
|
+
<value> Inline secret value (omit if using --file)
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--file <path> Read the secret value from a file instead of inline
|
|
39
|
+
|
|
40
|
+
Notes:
|
|
41
|
+
- Secrets are injected as process.env in serverless functions
|
|
42
|
+
- Values are write-only; 'list' returns a value_hash for verification
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
run402 secrets set abc123 STRIPE_KEY sk-1234
|
|
46
|
+
run402 secrets set abc123 TLS_CERT --file cert.pem
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
|
|
25
50
|
async function set(projectId, key, args = []) {
|
|
26
51
|
const p = findProject(projectId);
|
|
27
52
|
let file = null;
|
|
@@ -69,7 +94,7 @@ async function deleteSecret(projectId, key) {
|
|
|
69
94
|
export async function run(sub, args) {
|
|
70
95
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
71
96
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
72
|
-
console.log(HELP);
|
|
97
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
73
98
|
process.exit(0);
|
|
74
99
|
}
|
|
75
100
|
switch (sub) {
|
package/lib/sender-domain.mjs
CHANGED
|
@@ -117,6 +117,7 @@ async function inboundToggle(action, args) {
|
|
|
117
117
|
|
|
118
118
|
export async function run(sub, args) {
|
|
119
119
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
120
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(HELP); process.exit(0); }
|
|
120
121
|
switch (sub) {
|
|
121
122
|
case "register": await register(args); break;
|
|
122
123
|
case "status": await status(args); break;
|
package/lib/service.mjs
CHANGED
|
@@ -17,15 +17,15 @@ async function fetchAndEmit(path) {
|
|
|
17
17
|
try {
|
|
18
18
|
res = await fetch(`${API}${path}`);
|
|
19
19
|
} catch (err) {
|
|
20
|
-
console.
|
|
21
|
-
|
|
20
|
+
console.error(JSON.stringify({ status: "error", message: err?.message || String(err) }));
|
|
21
|
+
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
const text = await res.text();
|
|
24
24
|
let body;
|
|
25
25
|
try { body = JSON.parse(text); } catch { body = text; }
|
|
26
26
|
if (!res.ok) {
|
|
27
|
-
console.
|
|
28
|
-
|
|
27
|
+
console.error(JSON.stringify({ status: "error", http: res.status, body }));
|
|
28
|
+
process.exit(1);
|
|
29
29
|
}
|
|
30
30
|
console.log(JSON.stringify(body, null, 2));
|
|
31
31
|
}
|
package/lib/sites.mjs
CHANGED
|
@@ -45,6 +45,41 @@ Notes:
|
|
|
45
45
|
- Free with active tier — requires allowance auth
|
|
46
46
|
`;
|
|
47
47
|
|
|
48
|
+
const SUB_HELP = {
|
|
49
|
+
deploy: `run402 sites deploy — Deploy a static site from a manifest
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
run402 sites deploy --manifest <file> [--project <id>] [--target <target>] [--inherit]
|
|
53
|
+
cat manifest.json | run402 sites deploy [--project <id>] [--target <target>]
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--manifest <file> Path to manifest JSON file (or read from stdin)
|
|
57
|
+
--project <id> Project ID (defaults to the active project)
|
|
58
|
+
--target <target> Deployment target (e.g. 'production')
|
|
59
|
+
--inherit Copy unchanged files from the previous deployment
|
|
60
|
+
(only upload changed files)
|
|
61
|
+
|
|
62
|
+
Manifest format (JSON):
|
|
63
|
+
{
|
|
64
|
+
"files": [
|
|
65
|
+
{ "file": "index.html", "data": "<html>...</html>" },
|
|
66
|
+
{ "file": "style.css", "path": "./dist/style.css" }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
Paths are resolved relative to the manifest file's directory.
|
|
70
|
+
Binary files are auto-detected and base64-encoded.
|
|
71
|
+
|
|
72
|
+
Notes:
|
|
73
|
+
- Must include at least index.html in the files array
|
|
74
|
+
- Free with active tier — requires allowance auth
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
run402 sites deploy --manifest site.json
|
|
78
|
+
run402 sites deploy --manifest site.json --target production --inherit
|
|
79
|
+
cat site.json | run402 sites deploy
|
|
80
|
+
`,
|
|
81
|
+
};
|
|
82
|
+
|
|
48
83
|
async function readStdin() {
|
|
49
84
|
const chunks = [];
|
|
50
85
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
@@ -96,6 +131,7 @@ async function status(args) {
|
|
|
96
131
|
|
|
97
132
|
export async function run(sub, args) {
|
|
98
133
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
134
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
99
135
|
switch (sub) {
|
|
100
136
|
case "deploy": await deploy(args); break;
|
|
101
137
|
case "status": await status(args); break;
|
package/lib/storage.mjs
CHANGED
|
@@ -25,6 +25,29 @@ Notes:
|
|
|
25
25
|
- Upload reads from --file or stdin if no --file is given
|
|
26
26
|
`;
|
|
27
27
|
|
|
28
|
+
const SUB_HELP = {
|
|
29
|
+
upload: `run402 storage upload — Upload a file to a project's storage bucket
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
run402 storage upload <id> <bucket> <path> [--file <local>] [--content-type <mime>]
|
|
33
|
+
echo "..." | run402 storage upload <id> <bucket> <path> [--content-type <mime>]
|
|
34
|
+
|
|
35
|
+
Arguments:
|
|
36
|
+
<id> Project ID (from 'run402 projects list')
|
|
37
|
+
<bucket> Target bucket name
|
|
38
|
+
<path> Destination path within the bucket
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--file <local> Local file to upload; if omitted, content is read from stdin
|
|
42
|
+
--content-type <mime> MIME type of the upload (default: text/plain)
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
run402 storage upload abc123 assets logo.png --file ./logo.png \\
|
|
46
|
+
--content-type image/png
|
|
47
|
+
echo "hello" | run402 storage upload abc123 data notes.txt
|
|
48
|
+
`,
|
|
49
|
+
};
|
|
50
|
+
|
|
28
51
|
async function readStdin() {
|
|
29
52
|
const chunks = [];
|
|
30
53
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
@@ -88,6 +111,7 @@ async function list(projectId, bucket) {
|
|
|
88
111
|
|
|
89
112
|
export async function run(sub, args) {
|
|
90
113
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
114
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
91
115
|
switch (sub) {
|
|
92
116
|
case "upload": await upload(args[0], args[1], args[2], args.slice(3)); break;
|
|
93
117
|
case "download": await download(args[0], args[1], args[2]); break;
|
package/lib/subdomains.mjs
CHANGED
|
@@ -24,6 +24,31 @@ Notes:
|
|
|
24
24
|
- Creates <name>.run402.com pointing to the deployment
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
|
+
const SUB_HELP = {
|
|
28
|
+
claim: `run402 subdomains claim — Claim a custom subdomain for a deployment
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
run402 subdomains claim <name> [--project <id>] [--deployment <id>]
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
<name> Subdomain name (3-63 chars, lowercase alphanumeric +
|
|
35
|
+
hyphens). Creates <name>.run402.com.
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--project <id> Project ID (defaults to the active project)
|
|
39
|
+
--deployment <id> Deployment ID to point at (defaults to the project's
|
|
40
|
+
last deployment)
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- Legacy syntax 'claim <deployment_id> <name>' is still supported
|
|
44
|
+
- Deploy a site first (or pass --deployment) so there is a target to claim
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
run402 subdomains claim myapp
|
|
48
|
+
run402 subdomains claim myapp --deployment dpl_abc123 --project proj123
|
|
49
|
+
`,
|
|
50
|
+
};
|
|
51
|
+
|
|
27
52
|
async function claim(positionalArgs, flagArgs) {
|
|
28
53
|
const opts = { project: null, deployment: null };
|
|
29
54
|
for (let i = 0; i < flagArgs.length; i++) {
|
|
@@ -85,6 +110,7 @@ async function list(projectId) {
|
|
|
85
110
|
|
|
86
111
|
export async function run(sub, args) {
|
|
87
112
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
113
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
88
114
|
switch (sub) {
|
|
89
115
|
case "claim": {
|
|
90
116
|
const positional = [];
|
package/lib/tier.mjs
CHANGED
|
@@ -29,7 +29,14 @@ async function status() {
|
|
|
29
29
|
const res = await fetch(`${API}/tiers/v1/status`, {
|
|
30
30
|
headers: { ...authHeaders },
|
|
31
31
|
});
|
|
32
|
-
const
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
let data;
|
|
34
|
+
try {
|
|
35
|
+
data = JSON.parse(text);
|
|
36
|
+
} catch {
|
|
37
|
+
console.error(JSON.stringify({ status: "error", http: res.status, message: "Non-JSON response from server", body: text.slice(0, 500) }));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
33
40
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
34
41
|
console.log(JSON.stringify(data, null, 2));
|
|
35
42
|
}
|
package/lib/webhooks.mjs
CHANGED
|
@@ -21,6 +21,47 @@ Examples:
|
|
|
21
21
|
run402 email webhooks delete whk_123
|
|
22
22
|
`;
|
|
23
23
|
|
|
24
|
+
const SUB_HELP = {
|
|
25
|
+
update: `run402 email webhooks update — Update an existing webhook
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
run402 email webhooks update <webhook_id> [--url <url>] [--events <e1,e2>] [--project <id>]
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
<webhook_id> Webhook ID to update
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--url <url> New delivery URL for the webhook
|
|
35
|
+
--events <e1,e2> Comma-separated event list to replace the current events
|
|
36
|
+
Valid: delivery, bounced, complained, reply_received
|
|
37
|
+
--project <id> Project ID (defaults to the active project)
|
|
38
|
+
|
|
39
|
+
Notes:
|
|
40
|
+
- Provide at least one of --url or --events
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
run402 email webhooks update whk_123 --url https://new.example.com/hook
|
|
44
|
+
run402 email webhooks update whk_123 --events delivery,bounced
|
|
45
|
+
`,
|
|
46
|
+
register: `run402 email webhooks register — Register a new webhook
|
|
47
|
+
|
|
48
|
+
Usage:
|
|
49
|
+
run402 email webhooks register --url <url> --events <e1,e2> [--project <id>]
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
--url <url> Delivery URL for the webhook (required)
|
|
53
|
+
--events <e1,e2> Comma-separated event list (required)
|
|
54
|
+
Valid: delivery, bounced, complained, reply_received
|
|
55
|
+
--project <id> Project ID (defaults to the active project)
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
run402 email webhooks register --url https://example.com/hook \\
|
|
59
|
+
--events delivery,bounced
|
|
60
|
+
run402 email webhooks register --url https://example.com/hook \\
|
|
61
|
+
--events reply_received --project proj123
|
|
62
|
+
`,
|
|
63
|
+
};
|
|
64
|
+
|
|
24
65
|
function parseFlag(args, flag) {
|
|
25
66
|
for (let i = 0; i < args.length; i++) {
|
|
26
67
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -207,6 +248,7 @@ async function register(args) {
|
|
|
207
248
|
|
|
208
249
|
export async function run(sub, args) {
|
|
209
250
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
251
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
210
252
|
switch (sub) {
|
|
211
253
|
case "list": await list(args); break;
|
|
212
254
|
case "get": await get(args); break;
|
package/package.json
CHANGED