run402 1.54.2 → 1.54.3
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 +18 -0
- package/core-dist/allowance-auth.js +5 -0
- package/core-dist/allowance.js +49 -1
- package/core-dist/config.js +35 -2
- package/core-dist/wallet-auth.js +62 -0
- package/core-dist/wallet.js +25 -0
- package/lib/agent.mjs +29 -1
- package/lib/ai.mjs +113 -37
- package/lib/apps.mjs +34 -0
- package/lib/argparse.mjs +87 -0
- package/lib/auth.mjs +15 -2
- package/lib/billing.mjs +35 -0
- package/lib/config.mjs +20 -1
- package/lib/contracts.mjs +41 -0
- package/lib/deploy.mjs +125 -58
- package/lib/domains.mjs +79 -5
- package/lib/email.mjs +34 -0
- package/lib/functions.mjs +42 -1
- package/lib/image.mjs +33 -1
- package/lib/message.mjs +50 -3
- package/lib/projects.mjs +39 -31
- package/lib/sdk-errors.mjs +2 -1
- package/lib/secrets.mjs +27 -0
- package/lib/sender-domain.mjs +78 -1
- package/lib/service.mjs +30 -1
- package/lib/subdomains.mjs +29 -0
- package/lib/tier.mjs +41 -1
- package/lib/webhooks.mjs +10 -0
- package/package.json +1 -1
- package/sdk/core-dist/allowance-auth.js +5 -0
- package/sdk/core-dist/allowance.js +49 -1
- package/sdk/core-dist/config.js +35 -2
- package/sdk/core-dist/wallet-auth.js +62 -0
- package/sdk/core-dist/wallet.js +25 -0
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +12 -1
- package/sdk/dist/node/paid-fetch.js.map +1 -1
package/lib/functions.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
1
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
2
2
|
import { findProject, API } from "./config.mjs";
|
|
3
3
|
import { getSdk } from "./sdk.mjs";
|
|
4
4
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
@@ -141,6 +141,29 @@ Examples:
|
|
|
141
141
|
run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
|
|
142
142
|
run402 functions update prj_abc123 send-reminders --schedule-remove
|
|
143
143
|
run402 functions update prj_abc123 my-func --timeout 15 --memory 256
|
|
144
|
+
`,
|
|
145
|
+
list: `run402 functions list — List all functions for a project
|
|
146
|
+
|
|
147
|
+
Usage:
|
|
148
|
+
run402 functions list <project_id>
|
|
149
|
+
|
|
150
|
+
Arguments:
|
|
151
|
+
<project_id> Target project ID
|
|
152
|
+
|
|
153
|
+
Examples:
|
|
154
|
+
run402 functions list prj_abc123
|
|
155
|
+
`,
|
|
156
|
+
delete: `run402 functions delete — Delete a function from a project
|
|
157
|
+
|
|
158
|
+
Usage:
|
|
159
|
+
run402 functions delete <project_id> <name>
|
|
160
|
+
|
|
161
|
+
Arguments:
|
|
162
|
+
<project_id> Target project ID
|
|
163
|
+
<name> Function name to delete
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
run402 functions delete prj_abc123 stripe-webhook
|
|
144
167
|
`,
|
|
145
168
|
};
|
|
146
169
|
|
|
@@ -158,6 +181,24 @@ async function deploy(projectId, name, args) {
|
|
|
158
181
|
if (!opts.file) {
|
|
159
182
|
fail({ code: "BAD_USAGE", message: "Missing --file <file>" });
|
|
160
183
|
}
|
|
184
|
+
if (!existsSync(opts.file)) {
|
|
185
|
+
fail({
|
|
186
|
+
code: "FILE_NOT_FOUND",
|
|
187
|
+
message: `File not found: ${opts.file}`,
|
|
188
|
+
field: "--file",
|
|
189
|
+
path: opts.file,
|
|
190
|
+
hint: "Check that --file points to an existing source file.",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const stat = statSync(opts.file);
|
|
194
|
+
if (!stat.isFile()) {
|
|
195
|
+
fail({
|
|
196
|
+
code: "NOT_A_FILE",
|
|
197
|
+
message: `--file points to a ${stat.isDirectory() ? "directory" : "non-regular file"}: ${opts.file}`,
|
|
198
|
+
field: "--file",
|
|
199
|
+
path: opts.file,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
161
202
|
const code = readFileSync(opts.file, "utf-8");
|
|
162
203
|
|
|
163
204
|
const deployOpts = { name, code };
|
package/lib/image.mjs
CHANGED
|
@@ -27,12 +27,44 @@ Notes:
|
|
|
27
27
|
- Use --output to save directly to a file instead of printing base64
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
|
+
const SUB_HELP = {
|
|
31
|
+
generate: `run402 image generate — Generate an AI image from a text prompt
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
run402 image generate "<prompt>" [options]
|
|
35
|
+
|
|
36
|
+
Arguments:
|
|
37
|
+
<prompt> Text prompt describing the image (quote it)
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--aspect <ratio> Image aspect ratio: square | landscape | portrait
|
|
41
|
+
(default: square)
|
|
42
|
+
--output <file> Save image to file (e.g. output.png). If omitted,
|
|
43
|
+
returns base64 JSON to stdout.
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
- Requires a funded allowance (run402 allowance create && run402 allowance fund)
|
|
47
|
+
- Payments are processed automatically via x402 micropayments
|
|
48
|
+
- Use --output to save directly to a file instead of printing base64
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
run402 image generate "a startup mascot, pixel art"
|
|
52
|
+
run402 image generate "futuristic city at night" --aspect landscape
|
|
53
|
+
run402 image generate "portrait of a cat CEO" --aspect portrait --output cat.png
|
|
54
|
+
`,
|
|
55
|
+
};
|
|
56
|
+
|
|
30
57
|
export async function run(sub, args) {
|
|
31
58
|
if (!sub || sub === '--help' || sub === '-h') {
|
|
32
59
|
console.log(HELP);
|
|
33
60
|
process.exit(0);
|
|
34
61
|
}
|
|
35
62
|
|
|
63
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
64
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
if (sub !== "generate") {
|
|
37
69
|
console.error(`Unknown subcommand: ${sub}\n`);
|
|
38
70
|
console.log(HELP);
|
|
@@ -43,7 +75,7 @@ export async function run(sub, args) {
|
|
|
43
75
|
let i = 0;
|
|
44
76
|
if (i < args.length && !args[i].startsWith("--")) opts.prompt = args[i++];
|
|
45
77
|
while (i < args.length) {
|
|
46
|
-
if (args[i] === "--help" || args[i] === "-h") { console.log(HELP); process.exit(0); }
|
|
78
|
+
if (args[i] === "--help" || args[i] === "-h") { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
47
79
|
else if (args[i] === "--aspect" && args[i + 1]) { opts.aspect = args[++i]; }
|
|
48
80
|
else if (args[i] === "--output" && args[i + 1]) { opts.output = args[++i]; }
|
|
49
81
|
i++;
|
package/lib/message.mjs
CHANGED
|
@@ -10,21 +10,68 @@ Usage:
|
|
|
10
10
|
Notes:
|
|
11
11
|
- Requires an active tier (run402 tier set <tier>)
|
|
12
12
|
- Requires an allowance (run402 allowance create)
|
|
13
|
+
- Messages are capped at 8 KB (8192 bytes UTF-8) to keep the developer
|
|
14
|
+
inbox useful and prevent payload-dump misuse. Trim or summarize long
|
|
15
|
+
content (e.g. stack traces) before sending.
|
|
13
16
|
|
|
14
17
|
Examples:
|
|
15
18
|
run402 message send "Hello from my agent!"
|
|
16
19
|
`;
|
|
17
20
|
|
|
21
|
+
// Cap message body at a Twitter-ish but engineer-generous size: enough for
|
|
22
|
+
// a few paragraphs and a stack-trace excerpt, small enough that a misbehaving
|
|
23
|
+
// agent script can't dump arbitrary content into the developer inbox in one
|
|
24
|
+
// call. UTF-8 bytes (not characters) — emoji and accented chars count as
|
|
25
|
+
// multiple bytes.
|
|
26
|
+
const MESSAGE_MAX_BYTES = 8192;
|
|
27
|
+
|
|
28
|
+
const SUB_HELP = {
|
|
29
|
+
send: `run402 message send — Send a message to Run402 developers
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
run402 message send <text>
|
|
33
|
+
|
|
34
|
+
Arguments:
|
|
35
|
+
<text> Message body (quote it; remaining args are joined with
|
|
36
|
+
spaces if multiple positional words are provided)
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- Requires an active tier (run402 tier set <tier>)
|
|
40
|
+
- Requires an allowance (run402 allowance create)
|
|
41
|
+
- Messages are capped at 8 KB (8192 bytes UTF-8) to keep the developer
|
|
42
|
+
inbox useful and prevent payload-dump misuse.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
run402 message send "Hello from my agent!"
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
48
|
+
|
|
18
49
|
async function send(text) {
|
|
19
|
-
if (!text) {
|
|
50
|
+
if (!text || typeof text !== "string") {
|
|
20
51
|
fail({ code: "BAD_USAGE", message: "Missing message text." });
|
|
21
52
|
}
|
|
53
|
+
// Cap check runs BEFORE the allowance check so oversized payloads surface
|
|
54
|
+
// a structured size error instead of being masked by a missing-allowance
|
|
55
|
+
// exit.
|
|
56
|
+
const bytes = Buffer.byteLength(text, "utf-8");
|
|
57
|
+
if (bytes > MESSAGE_MAX_BYTES) {
|
|
58
|
+
fail({
|
|
59
|
+
code: "MESSAGE_TOO_LONG",
|
|
60
|
+
message: `Message is ${bytes} bytes; maximum is ${MESSAGE_MAX_BYTES} bytes (~8 KB).`,
|
|
61
|
+
hint: "Trim or summarize the message.",
|
|
62
|
+
details: { bytes, max_bytes: MESSAGE_MAX_BYTES },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
22
65
|
// Preserve the aggressive early exit when no allowance is configured.
|
|
23
66
|
allowanceAuthHeaders("/message/v1");
|
|
24
67
|
|
|
25
68
|
try {
|
|
26
69
|
await getSdk().admin.sendMessage(text);
|
|
27
|
-
console.log(JSON.stringify({
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
status: "ok",
|
|
72
|
+
message: "Message sent to Run402 developers.",
|
|
73
|
+
bytes_sent: bytes,
|
|
74
|
+
}));
|
|
28
75
|
} catch (err) {
|
|
29
76
|
reportSdkError(err);
|
|
30
77
|
}
|
|
@@ -33,7 +80,7 @@ async function send(text) {
|
|
|
33
80
|
export async function run(sub, args) {
|
|
34
81
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
35
82
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
36
|
-
console.log(HELP);
|
|
83
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
37
84
|
process.exit(0);
|
|
38
85
|
}
|
|
39
86
|
if (sub !== "send") {
|
package/lib/projects.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
|
|
|
2
2
|
import { findProject, loadKeyStore, API, allowanceAuthHeaders, resolveProjectId, getActiveProjectId } from "./config.mjs";
|
|
3
3
|
import { getSdk } from "./sdk.mjs";
|
|
4
4
|
import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
|
|
5
|
-
import { assertKnownFlags, failBadProjectId, hasHelp, normalizeArgv, positionalArgs } from "./argparse.mjs";
|
|
5
|
+
import { assertKnownFlags, failBadProjectId, hasHelp, normalizeArgv, positionalArgs, resolvePositionalProject } from "./argparse.mjs";
|
|
6
6
|
|
|
7
7
|
const HELP = `run402 projects — Manage your deployed Run402 projects
|
|
8
8
|
|
|
@@ -123,7 +123,37 @@ async function provision(args) {
|
|
|
123
123
|
const opts = { tier: "prototype", name: undefined };
|
|
124
124
|
for (let i = 0; i < args.length; i++) {
|
|
125
125
|
if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
|
|
126
|
-
|
|
126
|
+
// Use !== undefined so an empty-string value is captured (and rejected
|
|
127
|
+
// below) rather than silently dropped by the falsy-check pattern (GH-176).
|
|
128
|
+
if (args[i] === "--name" && args[i + 1] !== undefined) opts.name = args[++i];
|
|
129
|
+
}
|
|
130
|
+
// Validate --name when provided. Omitted --name lets the server pick a
|
|
131
|
+
// default. The same envelope should also be enforced server-side (GH-176).
|
|
132
|
+
if (opts.name !== undefined) {
|
|
133
|
+
if (opts.name === "") {
|
|
134
|
+
fail({
|
|
135
|
+
code: "BAD_PROJECT_NAME",
|
|
136
|
+
message: "--name must not be empty.",
|
|
137
|
+
details: { field: "--name" },
|
|
138
|
+
hint: "Provide a 1-128 character name, or omit --name to use the server-assigned default.",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (opts.name.length > 128) {
|
|
142
|
+
fail({
|
|
143
|
+
code: "BAD_PROJECT_NAME",
|
|
144
|
+
message: `--name must be 1-128 characters, got ${opts.name.length}.`,
|
|
145
|
+
details: { field: "--name", length: opts.name.length, max: 128 },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// eslint-disable-next-line no-control-regex
|
|
149
|
+
if (/[\x00-\x1f\x7f]/.test(opts.name)) {
|
|
150
|
+
fail({
|
|
151
|
+
code: "BAD_PROJECT_NAME",
|
|
152
|
+
message: "--name contains control characters (newline, tab, etc).",
|
|
153
|
+
details: { field: "--name" },
|
|
154
|
+
hint: "Project names should be a single-line label.",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
127
157
|
}
|
|
128
158
|
// Preserve the aggressive early exit when no allowance is configured —
|
|
129
159
|
// gives the user a more specific prompt than the SDK's 401/402 path.
|
|
@@ -259,6 +289,13 @@ async function sqlCmd(projectId, args = []) {
|
|
|
259
289
|
}
|
|
260
290
|
|
|
261
291
|
async function rest(projectId, table, queryParams) {
|
|
292
|
+
if (!table) {
|
|
293
|
+
fail({
|
|
294
|
+
code: "BAD_USAGE",
|
|
295
|
+
message: "Missing <table> argument. Usage: run402 projects rest [id] <table> [\"<query>\"]",
|
|
296
|
+
hint: "Run 'run402 projects schema <id>' to list tables.",
|
|
297
|
+
});
|
|
298
|
+
}
|
|
262
299
|
const p = findProject(projectId);
|
|
263
300
|
const res = await fetch(`${API}/rest/v1/${table}${queryParams ? '?' + queryParams : ''}`, { headers: { "apikey": p.anon_key } });
|
|
264
301
|
const data = await res.json();
|
|
@@ -373,35 +410,6 @@ async function deleteProject(projectId, args = []) {
|
|
|
373
410
|
}
|
|
374
411
|
}
|
|
375
412
|
|
|
376
|
-
// Resolve a positional project_id argument with active-project fallback (GH-102).
|
|
377
|
-
// Callers can tighten the legacy shorthand when a bare non-prj positional is
|
|
378
|
-
// more likely a mistyped project id than an argument for the active project.
|
|
379
|
-
function resolvePositionalProject(args, opts = {}) {
|
|
380
|
-
const first = Array.isArray(args) ? args[0] : undefined;
|
|
381
|
-
if (typeof first === "string" && first.startsWith("prj_")) {
|
|
382
|
-
return { projectId: first, rest: args.slice(1) };
|
|
383
|
-
}
|
|
384
|
-
if (
|
|
385
|
-
typeof first === "string" &&
|
|
386
|
-
first.length > 0 &&
|
|
387
|
-
!first.startsWith("-") &&
|
|
388
|
-
Array.isArray(opts.rejectBareFirstWhenFlagPresent) &&
|
|
389
|
-
opts.rejectBareFirstWhenFlagPresent.some((flag) => args.includes(flag))
|
|
390
|
-
) {
|
|
391
|
-
failBadProjectId(first);
|
|
392
|
-
}
|
|
393
|
-
if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.rejectBareFirst) {
|
|
394
|
-
failBadProjectId(first);
|
|
395
|
-
}
|
|
396
|
-
if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.maxBarePositionals !== undefined) {
|
|
397
|
-
const bare = positionalArgs(args, opts.valueFlags ?? []);
|
|
398
|
-
if (bare.length > opts.maxBarePositionals) {
|
|
399
|
-
failBadProjectId(first);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
|
|
403
|
-
}
|
|
404
|
-
|
|
405
413
|
const FLAGS_BY_SUB = {
|
|
406
414
|
provision: { known: ["--tier", "--name"], values: ["--tier", "--name"] },
|
|
407
415
|
sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
|
package/lib/sdk-errors.mjs
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* validation: the call wasn't sent, so retrying is safe and won't help unless
|
|
30
30
|
* the user fixes input.
|
|
31
31
|
*/
|
|
32
|
-
export function fail({ message, code, hint, details, next_actions, retryable = false, safe_to_retry = true, exit_code = 1 } = {}) {
|
|
32
|
+
export function fail({ message, code, hint, details, next_actions, field, retryable = false, safe_to_retry = true, exit_code = 1 } = {}) {
|
|
33
33
|
const envelope = {
|
|
34
34
|
status: "error",
|
|
35
35
|
code: code ?? "BAD_USAGE",
|
|
@@ -39,6 +39,7 @@ export function fail({ message, code, hint, details, next_actions, retryable = f
|
|
|
39
39
|
};
|
|
40
40
|
if (hint !== undefined) envelope.hint = hint;
|
|
41
41
|
if (details !== undefined) envelope.details = details;
|
|
42
|
+
if (field !== undefined) envelope.field = field;
|
|
42
43
|
envelope.next_actions = Array.isArray(next_actions) ? next_actions : [];
|
|
43
44
|
envelope.trace_id = null;
|
|
44
45
|
console.error(JSON.stringify(envelope));
|
package/lib/secrets.mjs
CHANGED
|
@@ -45,6 +45,33 @@ Notes:
|
|
|
45
45
|
Examples:
|
|
46
46
|
run402 secrets set prj_abc123 STRIPE_KEY sk-1234
|
|
47
47
|
run402 secrets set prj_abc123 TLS_CERT --file cert.pem
|
|
48
|
+
`,
|
|
49
|
+
list: `run402 secrets list — List all secrets for a project
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
run402 secrets list <id>
|
|
53
|
+
|
|
54
|
+
Arguments:
|
|
55
|
+
<id> Project ID (from 'run402 projects list')
|
|
56
|
+
|
|
57
|
+
Notes:
|
|
58
|
+
- Returns secret keys with a value_hash (first 8 hex chars of SHA-256)
|
|
59
|
+
for verifying the correct value was set; raw values are write-only
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
run402 secrets list prj_abc123
|
|
63
|
+
`,
|
|
64
|
+
delete: `run402 secrets delete — Delete a secret from a project
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
run402 secrets delete <id> <key>
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
<id> Project ID (from 'run402 projects list')
|
|
71
|
+
<key> Secret key name to remove
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
run402 secrets delete prj_abc123 STRIPE_KEY
|
|
48
75
|
`,
|
|
49
76
|
};
|
|
50
77
|
|
package/lib/sender-domain.mjs
CHANGED
|
@@ -22,6 +22,83 @@ Examples:
|
|
|
22
22
|
run402 sender-domain inbound-disable kysigned.com
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
+
const SUB_HELP = {
|
|
26
|
+
register: `run402 sender-domain register — Register a custom sender domain
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
run402 sender-domain register <domain> [--project <id>]
|
|
30
|
+
|
|
31
|
+
Arguments:
|
|
32
|
+
<domain> Custom sender domain (e.g. kysigned.com)
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--project <id> Project ID (defaults to the active project)
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
- Returns DNS records (DKIM, SPF, DMARC) to add at your DNS provider
|
|
39
|
+
- Use 'run402 sender-domain status' to poll until verified
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
run402 sender-domain register kysigned.com
|
|
43
|
+
run402 sender-domain register kysigned.com --project prj_abc123
|
|
44
|
+
`,
|
|
45
|
+
status: `run402 sender-domain status — Check verification status of the project's sender domain
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
run402 sender-domain status [--project <id>]
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
--project <id> Project ID (defaults to the active project)
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
run402 sender-domain status
|
|
55
|
+
run402 sender-domain status --project prj_abc123
|
|
56
|
+
`,
|
|
57
|
+
remove: `run402 sender-domain remove — Remove the project's custom sender domain
|
|
58
|
+
|
|
59
|
+
Usage:
|
|
60
|
+
run402 sender-domain remove [--project <id>]
|
|
61
|
+
|
|
62
|
+
Options:
|
|
63
|
+
--project <id> Project ID (defaults to the active project)
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
run402 sender-domain remove
|
|
67
|
+
run402 sender-domain remove --project prj_abc123
|
|
68
|
+
`,
|
|
69
|
+
"inbound-enable": `run402 sender-domain inbound-enable — Enable inbound email for a sender domain
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
run402 sender-domain inbound-enable <domain> [--project <id>]
|
|
73
|
+
|
|
74
|
+
Arguments:
|
|
75
|
+
<domain> Custom sender domain to enable inbound on
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
--project <id> Project ID (defaults to the active project)
|
|
79
|
+
|
|
80
|
+
Notes:
|
|
81
|
+
- Requires the domain to be DKIM-verified first
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
run402 sender-domain inbound-enable kysigned.com
|
|
85
|
+
`,
|
|
86
|
+
"inbound-disable": `run402 sender-domain inbound-disable — Disable inbound email for a sender domain
|
|
87
|
+
|
|
88
|
+
Usage:
|
|
89
|
+
run402 sender-domain inbound-disable <domain> [--project <id>]
|
|
90
|
+
|
|
91
|
+
Arguments:
|
|
92
|
+
<domain> Custom sender domain to disable inbound on
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
--project <id> Project ID (defaults to the active project)
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
run402 sender-domain inbound-disable kysigned.com
|
|
99
|
+
`,
|
|
100
|
+
};
|
|
101
|
+
|
|
25
102
|
function parseFlag(args, flag) {
|
|
26
103
|
for (let i = 0; i < args.length; i++) {
|
|
27
104
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -106,7 +183,7 @@ async function inboundToggle(action, args) {
|
|
|
106
183
|
|
|
107
184
|
export async function run(sub, args) {
|
|
108
185
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
109
|
-
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(HELP); process.exit(0); }
|
|
186
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
110
187
|
switch (sub) {
|
|
111
188
|
case "register": await register(args); break;
|
|
112
189
|
case "status": await status(args); break;
|
package/lib/service.mjs
CHANGED
|
@@ -13,6 +13,35 @@ Notes:
|
|
|
13
13
|
balance, tier, projects), use 'run402 status'.
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
|
+
const SUB_HELP = {
|
|
17
|
+
status: `run402 service status — Public service availability report
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
run402 service status
|
|
21
|
+
|
|
22
|
+
Notes:
|
|
23
|
+
- Unauthenticated and free; no allowance required
|
|
24
|
+
- Returns uptime, supported capabilities, operator, and deployment info
|
|
25
|
+
- For account state (allowance, balance, tier, projects), use
|
|
26
|
+
'run402 status' instead
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
run402 service status
|
|
30
|
+
`,
|
|
31
|
+
health: `run402 service health — Service liveness check
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
run402 service health
|
|
35
|
+
|
|
36
|
+
Notes:
|
|
37
|
+
- Unauthenticated and free; no allowance required
|
|
38
|
+
- Returns per-dependency status and the deployed version
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
run402 service health
|
|
42
|
+
`,
|
|
43
|
+
};
|
|
44
|
+
|
|
16
45
|
async function status() {
|
|
17
46
|
try {
|
|
18
47
|
const data = await getSdk().service.status();
|
|
@@ -34,7 +63,7 @@ async function health() {
|
|
|
34
63
|
export async function run(sub, args) {
|
|
35
64
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
36
65
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
37
|
-
console.log(HELP);
|
|
66
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
38
67
|
process.exit(0);
|
|
39
68
|
}
|
|
40
69
|
switch (sub) {
|
package/lib/subdomains.mjs
CHANGED
|
@@ -48,6 +48,35 @@ Notes:
|
|
|
48
48
|
Examples:
|
|
49
49
|
run402 subdomains claim myapp
|
|
50
50
|
run402 subdomains claim myapp --deployment dpl_abc123 --project prj_abc123
|
|
51
|
+
`,
|
|
52
|
+
list: `run402 subdomains list — List subdomains claimed by a project
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
run402 subdomains list [<id>]
|
|
56
|
+
|
|
57
|
+
Arguments:
|
|
58
|
+
<id> Project ID (defaults to the active project)
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
run402 subdomains list
|
|
62
|
+
run402 subdomains list prj_abc123
|
|
63
|
+
`,
|
|
64
|
+
delete: `run402 subdomains delete — Release a claimed subdomain
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
run402 subdomains delete <name> --confirm [--project <id>]
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
<name> Subdomain name to release
|
|
71
|
+
|
|
72
|
+
Options:
|
|
73
|
+
--confirm Required: releasing a subdomain is irreversible and
|
|
74
|
+
makes it available for any other project to claim
|
|
75
|
+
--project <id> Project ID (defaults to the active project)
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
run402 subdomains delete myapp --confirm
|
|
79
|
+
run402 subdomains delete myapp --confirm --project prj_abc123
|
|
51
80
|
`,
|
|
52
81
|
};
|
|
53
82
|
|
package/lib/tier.mjs
CHANGED
|
@@ -24,6 +24,46 @@ Examples:
|
|
|
24
24
|
run402 tier set hobby
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
|
+
const SUB_HELP = {
|
|
28
|
+
status: `run402 tier status — Show current tier subscription state
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
run402 tier status
|
|
32
|
+
|
|
33
|
+
Notes:
|
|
34
|
+
- Returns the current tier name, status, and expiry
|
|
35
|
+
- Use 'run402 tier set <tier>' to subscribe, renew, or upgrade
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
run402 tier status
|
|
39
|
+
`,
|
|
40
|
+
set: `run402 tier set — Subscribe, renew, or upgrade your tier
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
run402 tier set <tier>
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
<tier> One of: prototype, hobby, team
|
|
47
|
+
|
|
48
|
+
Tiers:
|
|
49
|
+
prototype $0.10/7d (free with testnet faucet)
|
|
50
|
+
hobby $5/30d
|
|
51
|
+
team $20/30d
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
Server auto-detects action based on current allowance state:
|
|
55
|
+
- No tier or expired -> subscribe
|
|
56
|
+
- Same tier, active -> renew (extends from expiry)
|
|
57
|
+
- Higher tier -> upgrade (prorated refund to allowance)
|
|
58
|
+
- Lower tier, active -> rejected (wait for expiry)
|
|
59
|
+
Pays via x402 micropayments.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
run402 tier set prototype
|
|
63
|
+
run402 tier set hobby
|
|
64
|
+
`,
|
|
65
|
+
};
|
|
66
|
+
|
|
27
67
|
async function status() {
|
|
28
68
|
try {
|
|
29
69
|
const data = await getSdk().tier.status();
|
|
@@ -55,7 +95,7 @@ export async function run(sub, args) {
|
|
|
55
95
|
process.exit(0);
|
|
56
96
|
}
|
|
57
97
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
58
|
-
console.log(HELP);
|
|
98
|
+
console.log(SUB_HELP[sub] || HELP);
|
|
59
99
|
process.exit(0);
|
|
60
100
|
}
|
|
61
101
|
switch (sub) {
|
package/lib/webhooks.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveProjectId } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
+
import { validateWebhookUrl } from "./argparse.mjs";
|
|
4
5
|
|
|
5
6
|
const HELP = `run402 email webhooks — Manage mailbox webhooks
|
|
6
7
|
|
|
@@ -121,6 +122,11 @@ async function update(args) {
|
|
|
121
122
|
if (!url && !eventsRaw) {
|
|
122
123
|
fail({ code: "BAD_USAGE", message: "Provide at least --url or --events" });
|
|
123
124
|
}
|
|
125
|
+
// GH-192: scheme-only local validation. Server-side SSRF defenses are out
|
|
126
|
+
// of scope for the CLI (private-IP / DNS rebinding / IMDS belongs on the
|
|
127
|
+
// gateway). `validateWebhookUrl` is a no-op when `url` is null/undefined,
|
|
128
|
+
// so partial updates that change only `--events` still work.
|
|
129
|
+
validateWebhookUrl(url, "--url");
|
|
124
130
|
|
|
125
131
|
try {
|
|
126
132
|
const data = await getSdk().email.webhooks.update(projectId, webhookId, {
|
|
@@ -146,6 +152,10 @@ async function register(args) {
|
|
|
146
152
|
hint: "run402 email webhooks register --url <url> --events <e1,e2>",
|
|
147
153
|
});
|
|
148
154
|
}
|
|
155
|
+
// GH-192: validate scheme locally before any network call. Catches
|
|
156
|
+
// javascript:/file:/http:/data: schemes that the gateway would reject
|
|
157
|
+
// anyway, but with a friendlier round-trip-free error.
|
|
158
|
+
validateWebhookUrl(url, "--url");
|
|
149
159
|
if (!eventsRaw) {
|
|
150
160
|
fail({
|
|
151
161
|
code: "BAD_USAGE",
|
package/package.json
CHANGED
|
@@ -87,6 +87,11 @@ export function formatSIWEMessage(opts, address) {
|
|
|
87
87
|
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
88
88
|
*/
|
|
89
89
|
export function getAllowanceAuthHeaders(path, allowancePath) {
|
|
90
|
+
// GH-194: readAllowance throws on a malformed-shape allowance file. The
|
|
91
|
+
// CLI's higher-level readAllowance wrapper surfaces this as a structured
|
|
92
|
+
// BAD_ALLOWANCE_FILE envelope; here we preserve the public contract that
|
|
93
|
+
// this helper returns SIWxAuthHeaders | null. Re-throw so callers above
|
|
94
|
+
// the CLI's wrapper (e.g. SDK paid-fetch) can decide whether to swallow it.
|
|
90
95
|
const allowance = readAllowance(allowancePath);
|
|
91
96
|
if (!allowance || !allowance.address || !allowance.privateKey)
|
|
92
97
|
return null;
|