run402 1.36.0 → 1.37.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/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 +307 -9
- 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/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