run402 1.54.1 → 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 +119 -43
- package/lib/apps.mjs +43 -9
- package/lib/argparse.mjs +234 -0
- package/lib/auth.mjs +17 -4
- package/lib/billing.mjs +35 -0
- package/lib/blob.mjs +55 -22
- 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 +104 -30
- package/lib/image.mjs +33 -1
- package/lib/message.mjs +50 -3
- package/lib/projects.mjs +75 -36
- package/lib/sdk-errors.mjs +2 -1
- package/lib/secrets.mjs +33 -6
- package/lib/sender-domain.mjs +78 -1
- package/lib/service.mjs +30 -1
- package/lib/status.mjs +4 -1
- package/lib/subdomains.mjs +31 -2
- 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/index.d.ts +1 -0
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +5 -0
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/auth.d.ts +7 -0
- package/sdk/dist/namespaces/auth.d.ts.map +1 -1
- package/sdk/dist/namespaces/auth.js +24 -0
- package/sdk/dist/namespaces/auth.js.map +1 -1
- package/sdk/dist/namespaces/billing.d.ts +3 -0
- package/sdk/dist/namespaces/billing.d.ts.map +1 -1
- package/sdk/dist/namespaces/billing.js +6 -0
- package/sdk/dist/namespaces/billing.js.map +1 -1
- package/sdk/dist/namespaces/contracts.d.ts +3 -0
- package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
- package/sdk/dist/namespaces/contracts.js +6 -0
- package/sdk/dist/namespaces/contracts.js.map +1 -1
- package/sdk/dist/namespaces/email.d.ts +4 -0
- package/sdk/dist/namespaces/email.d.ts.map +1 -1
- package/sdk/dist/namespaces/email.js +8 -0
- package/sdk/dist/namespaces/email.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +14 -0
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +72 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/sender-domain.d.ts +2 -0
- package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -1
- package/sdk/dist/namespaces/sender-domain.js +4 -0
- package/sdk/dist/namespaces/sender-domain.js.map +1 -1
- 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/sdk/dist/scoped.d.ts +8 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +21 -0
- package/sdk/dist/scoped.js.map +1 -1
package/lib/argparse.mjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { fail } from "./sdk-errors.mjs";
|
|
2
|
+
import { resolveProjectId } from "./config.mjs";
|
|
3
|
+
|
|
4
|
+
export function normalizeArgv(argv = []) {
|
|
5
|
+
const out = [];
|
|
6
|
+
for (const arg of argv ?? []) {
|
|
7
|
+
if (typeof arg === "string" && arg.startsWith("--") && arg.includes("=")) {
|
|
8
|
+
const eq = arg.indexOf("=");
|
|
9
|
+
out.push(arg.slice(0, eq), arg.slice(eq + 1));
|
|
10
|
+
} else {
|
|
11
|
+
out.push(arg);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasHelp(args = []) {
|
|
18
|
+
return args.includes("--help") || args.includes("-h");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
|
|
22
|
+
const known = new Set(knownFlags);
|
|
23
|
+
const valueFlags = new Set(flagsWithValues);
|
|
24
|
+
for (let i = 0; i < args.length; i++) {
|
|
25
|
+
const arg = args[i];
|
|
26
|
+
if (valueFlags.has(arg)) {
|
|
27
|
+
i += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (typeof arg !== "string" || !arg.startsWith("-") || arg === "-") continue;
|
|
31
|
+
if (known.has(arg)) continue;
|
|
32
|
+
failUnknownFlag(arg, known);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function failUnknownFlag(flag, knownFlags = []) {
|
|
37
|
+
const known = [...knownFlags].filter((f) => typeof f === "string" && f.startsWith("-"));
|
|
38
|
+
const closest = closestFlag(flag, known);
|
|
39
|
+
fail({
|
|
40
|
+
code: "UNKNOWN_FLAG",
|
|
41
|
+
message: closest ? `Unknown flag: ${flag}. Did you mean ${closest}?` : `Unknown flag: ${flag}.`,
|
|
42
|
+
details: { flag, closest: closest ? [closest] : [] },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function flagValue(args, flag) {
|
|
47
|
+
const idx = args.indexOf(flag);
|
|
48
|
+
if (idx === -1) return null;
|
|
49
|
+
if (idx + 1 >= args.length) {
|
|
50
|
+
fail({
|
|
51
|
+
code: "BAD_FLAG",
|
|
52
|
+
message: `${flag} requires a value`,
|
|
53
|
+
details: { flag },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return args[idx + 1];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_INFINITY, def } = {}) {
|
|
60
|
+
if (value === undefined || value === null) {
|
|
61
|
+
if (def !== undefined) return def;
|
|
62
|
+
fail({
|
|
63
|
+
code: "BAD_FLAG",
|
|
64
|
+
message: `${name} requires an integer value`,
|
|
65
|
+
details: { flag: name },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const raw = String(value);
|
|
69
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
70
|
+
fail({
|
|
71
|
+
code: "BAD_FLAG",
|
|
72
|
+
message: `${name} must be an integer, got: ${raw}`,
|
|
73
|
+
details: { flag: name, value: raw },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const n = Number.parseInt(raw, 10);
|
|
77
|
+
if (n < min) {
|
|
78
|
+
fail({
|
|
79
|
+
code: "BAD_FLAG",
|
|
80
|
+
message: `${name} must be >= ${min}, got: ${n}`,
|
|
81
|
+
details: { flag: name, value: n, min },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (n > max) {
|
|
85
|
+
fail({
|
|
86
|
+
code: "BAD_FLAG",
|
|
87
|
+
message: `${name} must be <= ${max}, got: ${n}`,
|
|
88
|
+
details: { flag: name, value: n, max },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return n;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function failBadProjectId(value) {
|
|
95
|
+
fail({
|
|
96
|
+
code: "BAD_PROJECT_ID",
|
|
97
|
+
message: `Argument '${value}' is not a project id. Project IDs must start with 'prj_'.`,
|
|
98
|
+
hint: "Omit the project id to use the active project, or pass the full prj_... id.",
|
|
99
|
+
details: { value, expected_prefix: "prj_" },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate a webhook URL: parse it locally and reject non-https:// schemes.
|
|
105
|
+
*
|
|
106
|
+
* Scope (GH-192): scheme-only validation. Reject `javascript:`, `file:`,
|
|
107
|
+
* `http:`, `data:`, `ftp:`, etc. before the request leaves the CLI process.
|
|
108
|
+
* Server-side SSRF defenses (private-IP filtering, DNS rebinding, IMDS
|
|
109
|
+
* blocking) live on the gateway, not here — this helper is the cheap
|
|
110
|
+
* client-side guard against the obvious classes.
|
|
111
|
+
*
|
|
112
|
+
* No-op when `url` is null/undefined/empty so callers can pass optional
|
|
113
|
+
* flag values directly. Required-vs-optional handling stays at the call
|
|
114
|
+
* site (e.g. `webhooks register` does its own missing-flag check first).
|
|
115
|
+
*
|
|
116
|
+
* On failure: `fail()` writes the canonical error envelope and exits 1.
|
|
117
|
+
*
|
|
118
|
+
* @param {string|null|undefined} url - The webhook URL to validate.
|
|
119
|
+
* @param {string} fieldName - The CLI flag name for the error envelope (e.g. "--url", "--webhook").
|
|
120
|
+
*/
|
|
121
|
+
export function validateWebhookUrl(url, fieldName = "--url") {
|
|
122
|
+
if (!url) return;
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = new URL(url);
|
|
126
|
+
} catch {
|
|
127
|
+
fail({
|
|
128
|
+
code: "BAD_WEBHOOK_URL",
|
|
129
|
+
message: `${fieldName} is not a valid URL: ${JSON.stringify(url)}`,
|
|
130
|
+
field: fieldName,
|
|
131
|
+
hint: "Webhook URL must be a fully-qualified https:// URL.",
|
|
132
|
+
details: { flag: fieldName, value: url },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (parsed.protocol !== "https:") {
|
|
136
|
+
fail({
|
|
137
|
+
code: "BAD_WEBHOOK_URL",
|
|
138
|
+
message: `${fieldName} must use https://, got ${parsed.protocol}`,
|
|
139
|
+
field: fieldName,
|
|
140
|
+
hint: "Webhook URLs must be https:// for transport security.",
|
|
141
|
+
details: { flag: fieldName, value: url, scheme: parsed.protocol },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function positionalArgs(args = [], flagsWithValues = []) {
|
|
147
|
+
const valueFlags = new Set(flagsWithValues);
|
|
148
|
+
const out = [];
|
|
149
|
+
for (let i = 0; i < args.length; i++) {
|
|
150
|
+
const arg = args[i];
|
|
151
|
+
if (valueFlags.has(arg)) {
|
|
152
|
+
i += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (typeof arg === "string" && arg.startsWith("-")) continue;
|
|
156
|
+
out.push(arg);
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Resolve a positional project_id argument with active-project fallback (GH-102, GH-187).
|
|
162
|
+
// If the first positional starts with "prj_", treat it as the project id and
|
|
163
|
+
// strip it from the rest. Otherwise, fall through to the active project from
|
|
164
|
+
// the keystore. Callers can tighten the legacy shorthand when a bare non-prj
|
|
165
|
+
// positional is more likely a mistyped project id than an argument for the
|
|
166
|
+
// active project.
|
|
167
|
+
//
|
|
168
|
+
// Options:
|
|
169
|
+
// rejectBareFirst: when true, error if the first positional
|
|
170
|
+
// is non-empty and doesn't start with "prj_".
|
|
171
|
+
// rejectBareFirstWhenFlagPresent: when one of these flags is present in
|
|
172
|
+
// args AND the first positional doesn't
|
|
173
|
+
// start with "prj_", error out.
|
|
174
|
+
// maxBarePositionals + valueFlags: when set, count the bare (non-flag)
|
|
175
|
+
// positionals using `positionalArgs(args,
|
|
176
|
+
// valueFlags)` and error if the count
|
|
177
|
+
// exceeds maxBarePositionals.
|
|
178
|
+
export function resolvePositionalProject(args, opts = {}) {
|
|
179
|
+
const first = Array.isArray(args) ? args[0] : undefined;
|
|
180
|
+
if (typeof first === "string" && first.startsWith("prj_")) {
|
|
181
|
+
return { projectId: first, rest: args.slice(1) };
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
typeof first === "string" &&
|
|
185
|
+
first.length > 0 &&
|
|
186
|
+
!first.startsWith("-") &&
|
|
187
|
+
Array.isArray(opts.rejectBareFirstWhenFlagPresent) &&
|
|
188
|
+
opts.rejectBareFirstWhenFlagPresent.some((flag) => args.includes(flag))
|
|
189
|
+
) {
|
|
190
|
+
failBadProjectId(first);
|
|
191
|
+
}
|
|
192
|
+
if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.rejectBareFirst) {
|
|
193
|
+
failBadProjectId(first);
|
|
194
|
+
}
|
|
195
|
+
if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.maxBarePositionals !== undefined) {
|
|
196
|
+
const bare = positionalArgs(args, opts.valueFlags ?? []);
|
|
197
|
+
if (bare.length > opts.maxBarePositionals) {
|
|
198
|
+
failBadProjectId(first);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function closestFlag(flag, candidates) {
|
|
205
|
+
let best = null;
|
|
206
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
207
|
+
for (const candidate of candidates) {
|
|
208
|
+
const d = levenshtein(flag, candidate);
|
|
209
|
+
if (d < bestDistance) {
|
|
210
|
+
best = candidate;
|
|
211
|
+
bestDistance = d;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!best) return null;
|
|
215
|
+
return bestDistance <= 3 ? best : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function levenshtein(a, b) {
|
|
219
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
220
|
+
const curr = Array.from({ length: b.length + 1 }, () => 0);
|
|
221
|
+
for (let i = 1; i <= a.length; i++) {
|
|
222
|
+
curr[0] = i;
|
|
223
|
+
for (let j = 1; j <= b.length; j++) {
|
|
224
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
225
|
+
curr[j] = Math.min(
|
|
226
|
+
curr[j - 1] + 1,
|
|
227
|
+
prev[j] + 1,
|
|
228
|
+
prev[j - 1] + cost,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
|
|
232
|
+
}
|
|
233
|
+
return prev[b.length];
|
|
234
|
+
}
|
package/lib/auth.mjs
CHANGED
|
@@ -93,7 +93,7 @@ Notes:
|
|
|
93
93
|
|
|
94
94
|
Examples:
|
|
95
95
|
run402 auth settings --allow-password-set true
|
|
96
|
-
run402 auth settings --allow-password-set false --project
|
|
96
|
+
run402 auth settings --allow-password-set false --project prj_abc123
|
|
97
97
|
`,
|
|
98
98
|
providers: `run402 auth providers — List available auth providers
|
|
99
99
|
|
|
@@ -105,7 +105,7 @@ Options:
|
|
|
105
105
|
|
|
106
106
|
Examples:
|
|
107
107
|
run402 auth providers
|
|
108
|
-
run402 auth providers --project
|
|
108
|
+
run402 auth providers --project prj_abc123
|
|
109
109
|
`,
|
|
110
110
|
};
|
|
111
111
|
|
|
@@ -187,10 +187,23 @@ async function settings(args) {
|
|
|
187
187
|
message: "Missing --allow-password-set <true|false>",
|
|
188
188
|
});
|
|
189
189
|
}
|
|
190
|
+
// Reject anything that isn't literally "true" or "false". Without this guard,
|
|
191
|
+
// the previous `=== "true"` coercion silently turned every other input
|
|
192
|
+
// (including "1", "yes", "TRUE", "bogus") into `false` and printed
|
|
193
|
+
// `{"status":"ok"}`, giving the user the OPPOSITE of what they likely
|
|
194
|
+
// intended for this security-adjacent flag. See GH-204.
|
|
195
|
+
if (allowPasswordSet !== "true" && allowPasswordSet !== "false") {
|
|
196
|
+
fail({
|
|
197
|
+
code: "BAD_FLAG",
|
|
198
|
+
message: "--allow-password-set must be 'true' or 'false'",
|
|
199
|
+
hint: "Use the literal strings 'true' or 'false'.",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
190
202
|
|
|
203
|
+
const allow = allowPasswordSet === "true";
|
|
191
204
|
try {
|
|
192
|
-
await getSdk().auth.settings(projectId, { allow_password_set:
|
|
193
|
-
console.log(JSON.stringify({ status: "ok", allow_password_set:
|
|
205
|
+
await getSdk().auth.settings(projectId, { allow_password_set: allow });
|
|
206
|
+
console.log(JSON.stringify({ status: "ok", allow_password_set: allow }));
|
|
194
207
|
} catch (err) {
|
|
195
208
|
reportSdkError(err);
|
|
196
209
|
}
|
package/lib/billing.mjs
CHANGED
|
@@ -84,6 +84,41 @@ Options:
|
|
|
84
84
|
Examples:
|
|
85
85
|
run402 billing history user@example.com
|
|
86
86
|
run402 billing history 0x1234... --limit 100
|
|
87
|
+
`,
|
|
88
|
+
balance: `run402 billing balance — Show balance for an email or wallet
|
|
89
|
+
|
|
90
|
+
Usage:
|
|
91
|
+
run402 billing balance <identifier>
|
|
92
|
+
|
|
93
|
+
Arguments:
|
|
94
|
+
<identifier> Email address or wallet (0x...)
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
run402 billing balance user@example.com
|
|
98
|
+
run402 billing balance 0x1234abcd...
|
|
99
|
+
`,
|
|
100
|
+
"create-email": `run402 billing create-email — Create an email billing account
|
|
101
|
+
|
|
102
|
+
Usage:
|
|
103
|
+
run402 billing create-email <email>
|
|
104
|
+
|
|
105
|
+
Arguments:
|
|
106
|
+
<email> Email address to register as a billing account
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
run402 billing create-email user@example.com
|
|
110
|
+
`,
|
|
111
|
+
"link-wallet": `run402 billing link-wallet — Link a wallet to an email billing account
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
run402 billing link-wallet <account_id> <wallet>
|
|
115
|
+
|
|
116
|
+
Arguments:
|
|
117
|
+
<account_id> Billing account ID (e.g. acct_abc123)
|
|
118
|
+
<wallet> Wallet address (0x...) to link
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
run402 billing link-wallet acct_abc123 0x1234abcd...
|
|
87
122
|
`,
|
|
88
123
|
};
|
|
89
124
|
|
package/lib/blob.mjs
CHANGED
|
@@ -37,6 +37,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
37
37
|
import { resolveProject, resolveProjectId, API } from "./config.mjs";
|
|
38
38
|
import { getSdk } from "./sdk.mjs";
|
|
39
39
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
40
|
+
import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag } from "./argparse.mjs";
|
|
40
41
|
|
|
41
42
|
const HELP = `run402 blob — Direct-to-S3 blob storage
|
|
42
43
|
|
|
@@ -62,13 +63,13 @@ Options:
|
|
|
62
63
|
--ttl <seconds> Signed-URL TTL (sign only; default 3600, max 604800)
|
|
63
64
|
|
|
64
65
|
Examples:
|
|
65
|
-
run402 blob put ./artifact.tgz --project
|
|
66
|
-
run402 blob put ./dist/**/*.png --project
|
|
67
|
-
run402 blob put huge.bin --project
|
|
68
|
-
run402 blob get images/logo.png --output /tmp/logo.png --project
|
|
69
|
-
run402 blob ls --project
|
|
70
|
-
run402 blob rm images/logo.png --project
|
|
71
|
-
run402 blob sign images/logo.png --project
|
|
66
|
+
run402 blob put ./artifact.tgz --project prj_abc123
|
|
67
|
+
run402 blob put ./dist/**/*.png --project prj_abc123 --key assets/
|
|
68
|
+
run402 blob put huge.bin --project prj_abc123 --immutable
|
|
69
|
+
run402 blob get images/logo.png --output /tmp/logo.png --project prj_abc123
|
|
70
|
+
run402 blob ls --project prj_abc123 --prefix images/
|
|
71
|
+
run402 blob rm images/logo.png --project prj_abc123
|
|
72
|
+
run402 blob sign images/logo.png --project prj_abc123 --ttl 600
|
|
72
73
|
`;
|
|
73
74
|
|
|
74
75
|
const SUB_HELP = {
|
|
@@ -90,9 +91,9 @@ Options:
|
|
|
90
91
|
--json Emit NDJSON progress events on stdout (for agent consumption)
|
|
91
92
|
|
|
92
93
|
Examples:
|
|
93
|
-
run402 blob put ./artifact.tgz --project
|
|
94
|
-
run402 blob put ./dist/**/*.png --project
|
|
95
|
-
run402 blob put huge.bin --project
|
|
94
|
+
run402 blob put ./artifact.tgz --project prj_abc123
|
|
95
|
+
run402 blob put ./dist/**/*.png --project prj_abc123 --key assets/
|
|
96
|
+
run402 blob put huge.bin --project prj_abc123 --immutable --concurrency 8
|
|
96
97
|
`,
|
|
97
98
|
get: `run402 blob get — Download a blob by key
|
|
98
99
|
|
|
@@ -107,7 +108,7 @@ Options:
|
|
|
107
108
|
--project <id> Project ID (defaults to active project)
|
|
108
109
|
|
|
109
110
|
Examples:
|
|
110
|
-
run402 blob get images/logo.png --output /tmp/logo.png --project
|
|
111
|
+
run402 blob get images/logo.png --output /tmp/logo.png --project prj_abc123
|
|
111
112
|
`,
|
|
112
113
|
ls: `run402 blob ls — List blob keys in a project
|
|
113
114
|
|
|
@@ -120,8 +121,8 @@ Options:
|
|
|
120
121
|
--limit <n> Max results (default 100, max 1000)
|
|
121
122
|
|
|
122
123
|
Examples:
|
|
123
|
-
run402 blob ls --project
|
|
124
|
-
run402 blob ls --project
|
|
124
|
+
run402 blob ls --project prj_abc123
|
|
125
|
+
run402 blob ls --project prj_abc123 --prefix images/ --limit 500
|
|
125
126
|
`,
|
|
126
127
|
rm: `run402 blob rm — Delete a blob
|
|
127
128
|
|
|
@@ -135,7 +136,7 @@ Options:
|
|
|
135
136
|
--project <id> Project ID (defaults to active project)
|
|
136
137
|
|
|
137
138
|
Examples:
|
|
138
|
-
run402 blob rm images/logo.png --project
|
|
139
|
+
run402 blob rm images/logo.png --project prj_abc123
|
|
139
140
|
`,
|
|
140
141
|
sign: `run402 blob sign — Create a presigned download URL for a blob
|
|
141
142
|
|
|
@@ -150,7 +151,7 @@ Options:
|
|
|
150
151
|
--ttl <seconds> Signed-URL TTL (default 3600, max 604800)
|
|
151
152
|
|
|
152
153
|
Examples:
|
|
153
|
-
run402 blob sign reports/2025-q4.pdf --project
|
|
154
|
+
run402 blob sign reports/2025-q4.pdf --project prj_abc123 --ttl 600
|
|
154
155
|
`,
|
|
155
156
|
diagnose: `run402 blob diagnose — Inspect the live CDN state for a public blob URL
|
|
156
157
|
|
|
@@ -186,7 +187,38 @@ function die(msg, exit_code = 1) {
|
|
|
186
187
|
fail({ code: "BAD_USAGE", message: msg, exit_code });
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
function
|
|
190
|
+
function dieApiFailure(prefix, http, body) {
|
|
191
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
192
|
+
const envelope = { status: "error", http, ...body };
|
|
193
|
+
if (!envelope.message && envelope.error) envelope.message = envelope.error;
|
|
194
|
+
console.error(JSON.stringify(envelope));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
fail({
|
|
198
|
+
message: `${prefix}: HTTP ${http}${typeof body === "string" && body ? `: ${body.slice(0, 500)}` : ""}`,
|
|
199
|
+
details: { http },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseArgs(rawArgs) {
|
|
204
|
+
const args = normalizeArgv(rawArgs);
|
|
205
|
+
const valueFlags = ["--project", "--key", "--concurrency", "--prefix", "--limit", "--output", "-o", "--ttl"];
|
|
206
|
+
assertKnownFlags(args, [
|
|
207
|
+
"--project",
|
|
208
|
+
"--key",
|
|
209
|
+
"--private",
|
|
210
|
+
"--immutable",
|
|
211
|
+
"--concurrency",
|
|
212
|
+
"--no-resume",
|
|
213
|
+
"--json",
|
|
214
|
+
"--prefix",
|
|
215
|
+
"--limit",
|
|
216
|
+
"--output",
|
|
217
|
+
"-o",
|
|
218
|
+
"--ttl",
|
|
219
|
+
"--help",
|
|
220
|
+
"-h",
|
|
221
|
+
], valueFlags);
|
|
190
222
|
const out = { positional: [], project: null, key: null, private: false, immutable: false,
|
|
191
223
|
concurrency: 4, resume: true, json: false, prefix: null, limit: null,
|
|
192
224
|
output: null, ttl: null };
|
|
@@ -196,13 +228,13 @@ function parseArgs(args) {
|
|
|
196
228
|
else if (a === "--key") out.key = args[++i];
|
|
197
229
|
else if (a === "--private") out.private = true;
|
|
198
230
|
else if (a === "--immutable") out.immutable = true;
|
|
199
|
-
else if (a === "--concurrency") out.concurrency =
|
|
231
|
+
else if (a === "--concurrency") out.concurrency = parseIntegerFlag("--concurrency", args[++i], { min: 1 });
|
|
200
232
|
else if (a === "--no-resume") out.resume = false;
|
|
201
233
|
else if (a === "--json") out.json = true;
|
|
202
234
|
else if (a === "--prefix") out.prefix = args[++i];
|
|
203
|
-
else if (a === "--limit") out.limit =
|
|
235
|
+
else if (a === "--limit") out.limit = parseIntegerFlag("--limit", args[++i], { min: 1, max: 1000 });
|
|
204
236
|
else if (a === "--output" || a === "-o") out.output = args[++i];
|
|
205
|
-
else if (a === "--ttl") out.ttl =
|
|
237
|
+
else if (a === "--ttl") out.ttl = parseIntegerFlag("--ttl", args[++i], { min: 1, max: 604800 });
|
|
206
238
|
else if (!a.startsWith("--")) out.positional.push(a);
|
|
207
239
|
}
|
|
208
240
|
return out;
|
|
@@ -284,7 +316,7 @@ async function putOne(project, filePath, opts) {
|
|
|
284
316
|
immutable: opts.immutable,
|
|
285
317
|
sha256,
|
|
286
318
|
});
|
|
287
|
-
if (init.status !== 201)
|
|
319
|
+
if (init.status !== 201) dieApiFailure("Init failed", init.status, init.body);
|
|
288
320
|
initRes = init.body;
|
|
289
321
|
saveState({
|
|
290
322
|
upload_id: initRes.upload_id,
|
|
@@ -330,7 +362,7 @@ async function putOne(project, filePath, opts) {
|
|
|
330
362
|
? { parts: etags.map((e, i) => ({ part_number: i + 1, etag: e.etag })) }
|
|
331
363
|
: {};
|
|
332
364
|
const complete = await apiFetch(`${API}/storage/v1/uploads/${state.upload_id}/complete`, "POST", project, body);
|
|
333
|
-
if (complete.status !== 200)
|
|
365
|
+
if (complete.status !== 200) dieApiFailure("Complete failed", complete.status, complete.body);
|
|
334
366
|
|
|
335
367
|
removeState(state.upload_id);
|
|
336
368
|
log(opts, { event: "done", ...complete.body });
|
|
@@ -565,7 +597,8 @@ export async function run(sub, args) {
|
|
|
565
597
|
console.log(HELP);
|
|
566
598
|
process.exit(0);
|
|
567
599
|
}
|
|
568
|
-
|
|
600
|
+
args = normalizeArgv(args);
|
|
601
|
+
if (Array.isArray(args) && hasHelp(args)) {
|
|
569
602
|
console.log(SUB_HELP[sub] || HELP);
|
|
570
603
|
process.exit(0);
|
|
571
604
|
}
|
package/lib/config.mjs
CHANGED
|
@@ -14,8 +14,27 @@ export const ALLOWANCE_FILE = getAllowancePath();
|
|
|
14
14
|
export const PROJECTS_FILE = getKeystorePath();
|
|
15
15
|
export const API = getApiBase();
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Wraps core's `readAllowance()` and converts the malformed-shape throw
|
|
19
|
+
* (GH-194) into the canonical CLI failure envelope. Without this guard, every
|
|
20
|
+
* CLI subcommand that touches the allowance leaks a Node stack trace and
|
|
21
|
+
* source paths the moment a user has a malformed `allowance.json`.
|
|
22
|
+
*
|
|
23
|
+
* The unparseable-JSON case still returns `null` (matching the historical
|
|
24
|
+
* "no_allowance" UX); only valid-JSON-but-wrong-shape becomes a structured
|
|
25
|
+
* error with `code: BAD_ALLOWANCE_FILE`.
|
|
26
|
+
*/
|
|
17
27
|
export function readAllowance() {
|
|
18
|
-
|
|
28
|
+
try {
|
|
29
|
+
return coreReadAllowance();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
fail({
|
|
32
|
+
code: "BAD_ALLOWANCE_FILE",
|
|
33
|
+
message: err?.message ?? "allowance.json is malformed",
|
|
34
|
+
hint: "Back up ~/.config/run402/allowance.json and run 'run402 init' to recreate it.",
|
|
35
|
+
details: { path: ALLOWANCE_FILE },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
export function saveAllowance(data) {
|
package/lib/contracts.mjs
CHANGED
|
@@ -83,6 +83,47 @@ Usage:
|
|
|
83
83
|
|
|
84
84
|
Usage:
|
|
85
85
|
run402 contracts delete <project_id> <wallet_id> --confirm
|
|
86
|
+
`,
|
|
87
|
+
"get-wallet": `run402 contracts get-wallet — Get wallet metadata + live balance
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
run402 contracts get-wallet <project_id> <wallet_id>
|
|
91
|
+
|
|
92
|
+
Arguments:
|
|
93
|
+
<project_id> Project ID that owns the wallet
|
|
94
|
+
<wallet_id> Wallet ID (e.g. cwlt_abc123)
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
run402 contracts get-wallet prj_abc123 cwlt_abc123
|
|
98
|
+
`,
|
|
99
|
+
"list-wallets": `run402 contracts list-wallets — List all KMS wallets for a project
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
run402 contracts list-wallets <project_id>
|
|
103
|
+
|
|
104
|
+
Arguments:
|
|
105
|
+
<project_id> Project ID to list wallets for
|
|
106
|
+
|
|
107
|
+
Notes:
|
|
108
|
+
- Includes deleted wallets
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
run402 contracts list-wallets prj_abc123
|
|
112
|
+
`,
|
|
113
|
+
status: `run402 contracts status — Get a contract call's status and receipt
|
|
114
|
+
|
|
115
|
+
Usage:
|
|
116
|
+
run402 contracts status <project_id> <call_id>
|
|
117
|
+
|
|
118
|
+
Arguments:
|
|
119
|
+
<project_id> Project ID that submitted the call
|
|
120
|
+
<call_id> Contract call ID returned from 'contracts call'
|
|
121
|
+
|
|
122
|
+
Notes:
|
|
123
|
+
- Returns status, gas used, gas cost (USD-micros), and receipt
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
run402 contracts status prj_abc123 ccall_abc123
|
|
86
127
|
`,
|
|
87
128
|
};
|
|
88
129
|
|