run402 1.54.1 → 1.54.2

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.
Files changed (43) hide show
  1. package/lib/ai.mjs +6 -6
  2. package/lib/apps.mjs +9 -9
  3. package/lib/argparse.mjs +147 -0
  4. package/lib/auth.mjs +2 -2
  5. package/lib/blob.mjs +55 -22
  6. package/lib/functions.mjs +62 -29
  7. package/lib/projects.mjs +60 -29
  8. package/lib/secrets.mjs +6 -6
  9. package/lib/status.mjs +4 -1
  10. package/lib/subdomains.mjs +2 -2
  11. package/package.json +1 -1
  12. package/sdk/dist/index.d.ts +1 -0
  13. package/sdk/dist/index.d.ts.map +1 -1
  14. package/sdk/dist/index.js +5 -0
  15. package/sdk/dist/index.js.map +1 -1
  16. package/sdk/dist/namespaces/auth.d.ts +7 -0
  17. package/sdk/dist/namespaces/auth.d.ts.map +1 -1
  18. package/sdk/dist/namespaces/auth.js +24 -0
  19. package/sdk/dist/namespaces/auth.js.map +1 -1
  20. package/sdk/dist/namespaces/billing.d.ts +3 -0
  21. package/sdk/dist/namespaces/billing.d.ts.map +1 -1
  22. package/sdk/dist/namespaces/billing.js +6 -0
  23. package/sdk/dist/namespaces/billing.js.map +1 -1
  24. package/sdk/dist/namespaces/contracts.d.ts +3 -0
  25. package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
  26. package/sdk/dist/namespaces/contracts.js +6 -0
  27. package/sdk/dist/namespaces/contracts.js.map +1 -1
  28. package/sdk/dist/namespaces/email.d.ts +4 -0
  29. package/sdk/dist/namespaces/email.d.ts.map +1 -1
  30. package/sdk/dist/namespaces/email.js +8 -0
  31. package/sdk/dist/namespaces/email.js.map +1 -1
  32. package/sdk/dist/namespaces/projects.d.ts +14 -0
  33. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  34. package/sdk/dist/namespaces/projects.js +72 -0
  35. package/sdk/dist/namespaces/projects.js.map +1 -1
  36. package/sdk/dist/namespaces/sender-domain.d.ts +2 -0
  37. package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -1
  38. package/sdk/dist/namespaces/sender-domain.js +4 -0
  39. package/sdk/dist/namespaces/sender-domain.js.map +1 -1
  40. package/sdk/dist/scoped.d.ts +8 -1
  41. package/sdk/dist/scoped.d.ts.map +1 -1
  42. package/sdk/dist/scoped.js +21 -0
  43. package/sdk/dist/scoped.js.map +1 -1
package/lib/ai.mjs CHANGED
@@ -13,10 +13,10 @@ Subcommands:
13
13
  usage <project_id>
14
14
 
15
15
  Examples:
16
- run402 ai translate proj-001 "Hello world" --to es
17
- run402 ai translate proj-001 "Hello" --to ja --from en --context "formal business email"
18
- run402 ai moderate proj-001 "content to check"
19
- run402 ai usage proj-001
16
+ run402 ai translate prj_abc123 "Hello world" --to es
17
+ run402 ai translate prj_abc123 "Hello" --to ja --from en --context "formal business email"
18
+ run402 ai moderate prj_abc123 "content to check"
19
+ run402 ai usage prj_abc123
20
20
 
21
21
  Notes:
22
22
  - translate requires the AI Translation add-on on the project
@@ -44,8 +44,8 @@ Notes:
44
44
  - Counts against the project's translation word quota
45
45
 
46
46
  Examples:
47
- run402 ai translate proj-001 "Hello world" --to es
48
- run402 ai translate proj-001 "Hello" --to ja --from en \\
47
+ run402 ai translate prj_abc123 "Hello world" --to es
48
+ run402 ai translate prj_abc123 "Hello" --to ja --from en \\
49
49
  --context "formal business email"
50
50
  `,
51
51
  };
package/lib/apps.mjs CHANGED
@@ -23,11 +23,11 @@ Examples:
23
23
  run402 apps browse
24
24
  run402 apps browse --tag auth
25
25
  run402 apps fork ver_abc123 my-todo --tier prototype
26
- run402 apps publish proj123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
27
- run402 apps versions proj123
26
+ run402 apps publish prj_abc123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
27
+ run402 apps versions prj_abc123
28
28
  run402 apps inspect ver_abc123
29
- run402 apps update proj123 ver_abc123 --description "Updated" --tags todo
30
- run402 apps delete proj123 ver_abc123
29
+ run402 apps update prj_abc123 ver_abc123 --description "Updated" --tags todo
30
+ run402 apps delete prj_abc123 ver_abc123
31
31
  `;
32
32
 
33
33
  const SUB_HELP = {
@@ -76,8 +76,8 @@ Options:
76
76
  --fork-allowed Allow other users to fork this app
77
77
 
78
78
  Examples:
79
- run402 apps publish proj123 --description "Todo app" --tags todo,auth
80
- run402 apps publish proj123 --visibility public --fork-allowed
79
+ run402 apps publish prj_abc123 --description "Todo app" --tags todo,auth
80
+ run402 apps publish prj_abc123 --visibility public --fork-allowed
81
81
  `,
82
82
  update: `run402 apps update — Update a published version's metadata
83
83
 
@@ -96,9 +96,9 @@ Options:
96
96
  --no-fork Disable forking for this version
97
97
 
98
98
  Examples:
99
- run402 apps update proj123 ver_abc123 --description "Updated"
100
- run402 apps update proj123 ver_abc123 --tags todo,auth --fork-allowed
101
- run402 apps update proj123 ver_abc123 --no-fork
99
+ run402 apps update prj_abc123 ver_abc123 --description "Updated"
100
+ run402 apps update prj_abc123 ver_abc123 --tags todo,auth --fork-allowed
101
+ run402 apps update prj_abc123 ver_abc123 --no-fork
102
102
  `,
103
103
  };
104
104
 
@@ -0,0 +1,147 @@
1
+ import { fail } from "./sdk-errors.mjs";
2
+
3
+ export function normalizeArgv(argv = []) {
4
+ const out = [];
5
+ for (const arg of argv ?? []) {
6
+ if (typeof arg === "string" && arg.startsWith("--") && arg.includes("=")) {
7
+ const eq = arg.indexOf("=");
8
+ out.push(arg.slice(0, eq), arg.slice(eq + 1));
9
+ } else {
10
+ out.push(arg);
11
+ }
12
+ }
13
+ return out;
14
+ }
15
+
16
+ export function hasHelp(args = []) {
17
+ return args.includes("--help") || args.includes("-h");
18
+ }
19
+
20
+ export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
21
+ const known = new Set(knownFlags);
22
+ const valueFlags = new Set(flagsWithValues);
23
+ for (let i = 0; i < args.length; i++) {
24
+ const arg = args[i];
25
+ if (valueFlags.has(arg)) {
26
+ i += 1;
27
+ continue;
28
+ }
29
+ if (typeof arg !== "string" || !arg.startsWith("-") || arg === "-") continue;
30
+ if (known.has(arg)) continue;
31
+ failUnknownFlag(arg, known);
32
+ }
33
+ }
34
+
35
+ export function failUnknownFlag(flag, knownFlags = []) {
36
+ const known = [...knownFlags].filter((f) => typeof f === "string" && f.startsWith("-"));
37
+ const closest = closestFlag(flag, known);
38
+ fail({
39
+ code: "UNKNOWN_FLAG",
40
+ message: closest ? `Unknown flag: ${flag}. Did you mean ${closest}?` : `Unknown flag: ${flag}.`,
41
+ details: { flag, closest: closest ? [closest] : [] },
42
+ });
43
+ }
44
+
45
+ export function flagValue(args, flag) {
46
+ const idx = args.indexOf(flag);
47
+ if (idx === -1) return null;
48
+ if (idx + 1 >= args.length) {
49
+ fail({
50
+ code: "BAD_FLAG",
51
+ message: `${flag} requires a value`,
52
+ details: { flag },
53
+ });
54
+ }
55
+ return args[idx + 1];
56
+ }
57
+
58
+ export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_INFINITY, def } = {}) {
59
+ if (value === undefined || value === null) {
60
+ if (def !== undefined) return def;
61
+ fail({
62
+ code: "BAD_FLAG",
63
+ message: `${name} requires an integer value`,
64
+ details: { flag: name },
65
+ });
66
+ }
67
+ const raw = String(value);
68
+ if (!/^-?\d+$/.test(raw)) {
69
+ fail({
70
+ code: "BAD_FLAG",
71
+ message: `${name} must be an integer, got: ${raw}`,
72
+ details: { flag: name, value: raw },
73
+ });
74
+ }
75
+ const n = Number.parseInt(raw, 10);
76
+ if (n < min) {
77
+ fail({
78
+ code: "BAD_FLAG",
79
+ message: `${name} must be >= ${min}, got: ${n}`,
80
+ details: { flag: name, value: n, min },
81
+ });
82
+ }
83
+ if (n > max) {
84
+ fail({
85
+ code: "BAD_FLAG",
86
+ message: `${name} must be <= ${max}, got: ${n}`,
87
+ details: { flag: name, value: n, max },
88
+ });
89
+ }
90
+ return n;
91
+ }
92
+
93
+ export function failBadProjectId(value) {
94
+ fail({
95
+ code: "BAD_PROJECT_ID",
96
+ message: `Argument '${value}' is not a project id. Project IDs must start with 'prj_'.`,
97
+ hint: "Omit the project id to use the active project, or pass the full prj_... id.",
98
+ details: { value, expected_prefix: "prj_" },
99
+ });
100
+ }
101
+
102
+ export function positionalArgs(args = [], flagsWithValues = []) {
103
+ const valueFlags = new Set(flagsWithValues);
104
+ const out = [];
105
+ for (let i = 0; i < args.length; i++) {
106
+ const arg = args[i];
107
+ if (valueFlags.has(arg)) {
108
+ i += 1;
109
+ continue;
110
+ }
111
+ if (typeof arg === "string" && arg.startsWith("-")) continue;
112
+ out.push(arg);
113
+ }
114
+ return out;
115
+ }
116
+
117
+ function closestFlag(flag, candidates) {
118
+ let best = null;
119
+ let bestDistance = Number.POSITIVE_INFINITY;
120
+ for (const candidate of candidates) {
121
+ const d = levenshtein(flag, candidate);
122
+ if (d < bestDistance) {
123
+ best = candidate;
124
+ bestDistance = d;
125
+ }
126
+ }
127
+ if (!best) return null;
128
+ return bestDistance <= 3 ? best : null;
129
+ }
130
+
131
+ function levenshtein(a, b) {
132
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
133
+ const curr = Array.from({ length: b.length + 1 }, () => 0);
134
+ for (let i = 1; i <= a.length; i++) {
135
+ curr[0] = i;
136
+ for (let j = 1; j <= b.length; j++) {
137
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
138
+ curr[j] = Math.min(
139
+ curr[j - 1] + 1,
140
+ prev[j] + 1,
141
+ prev[j - 1] + cost,
142
+ );
143
+ }
144
+ for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
145
+ }
146
+ return prev[b.length];
147
+ }
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 abc123
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 abc123
108
+ run402 auth providers --project prj_abc123
109
109
  `,
110
110
  };
111
111
 
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 abc123
66
- run402 blob put ./dist/**/*.png --project abc123 --key assets/
67
- run402 blob put huge.bin --project abc123 --immutable
68
- run402 blob get images/logo.png --output /tmp/logo.png --project abc123
69
- run402 blob ls --project abc123 --prefix images/
70
- run402 blob rm images/logo.png --project abc123
71
- run402 blob sign images/logo.png --project abc123 --ttl 600
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 abc123
94
- run402 blob put ./dist/**/*.png --project abc123 --key assets/
95
- run402 blob put huge.bin --project abc123 --immutable --concurrency 8
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 abc123
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 abc123
124
- run402 blob ls --project abc123 --prefix images/ --limit 500
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 abc123
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 abc123 --ttl 600
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 parseArgs(args) {
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 = parseInt(args[++i], 10);
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 = parseInt(args[++i], 10);
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 = parseInt(args[++i], 10);
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) die(`Init failed: HTTP ${init.status}: ${JSON.stringify(init.body)}`);
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) die(`Complete failed: HTTP ${complete.status}: ${JSON.stringify(complete.body)}`);
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
- if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
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/functions.mjs CHANGED
@@ -2,6 +2,7 @@ import { readFileSync } 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";
5
+ import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag } from "./argparse.mjs";
5
6
 
6
7
  const HELP = `run402 functions — Manage serverless functions
7
8
 
@@ -21,18 +22,18 @@ Subcommands:
21
22
  delete <id> <name> Delete a function
22
23
 
23
24
  Examples:
24
- run402 functions deploy abc123 stripe-webhook --file handler.ts
25
- run402 functions deploy abc123 send-reminders --file remind.ts --schedule '*/15 * * * *'
26
- run402 functions deploy abc123 send-reminders --file remind.ts --schedule '' # remove schedule
27
- run402 functions invoke abc123 stripe-webhook --body '{"event":"test"}'
28
- run402 functions logs abc123 stripe-webhook --tail 100
29
- run402 functions logs abc123 stripe-webhook --since 2026-03-29T14:00:00Z
30
- run402 functions logs abc123 stripe-webhook --follow
31
- run402 functions update abc123 send-reminders --schedule '0 */4 * * *'
32
- run402 functions update abc123 send-reminders --schedule-remove
33
- run402 functions update abc123 my-func --timeout 15 --memory 256
34
- run402 functions list abc123
35
- run402 functions delete abc123 stripe-webhook
25
+ run402 functions deploy prj_abc123 stripe-webhook --file handler.ts
26
+ run402 functions deploy prj_abc123 send-reminders --file remind.ts --schedule '*/15 * * * *'
27
+ run402 functions deploy prj_abc123 send-reminders --file remind.ts --schedule '' # remove schedule
28
+ run402 functions invoke prj_abc123 stripe-webhook --body '{"event":"test"}'
29
+ run402 functions logs prj_abc123 stripe-webhook --tail 100
30
+ run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
31
+ run402 functions logs prj_abc123 stripe-webhook --follow
32
+ run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
33
+ run402 functions update prj_abc123 send-reminders --schedule-remove
34
+ run402 functions update prj_abc123 my-func --timeout 15 --memory 256
35
+ run402 functions list prj_abc123
36
+ run402 functions delete prj_abc123 stripe-webhook
36
37
 
37
38
  Notes:
38
39
  - Code must export a default async function: export default async (req: Request) => Response
@@ -77,10 +78,10 @@ Notes:
77
78
  notes such as bundle-size advisories
78
79
 
79
80
  Examples:
80
- run402 functions deploy abc123 stripe-webhook --file handler.ts
81
- run402 functions deploy abc123 send-reminders --file remind.ts \\
81
+ run402 functions deploy prj_abc123 stripe-webhook --file handler.ts
82
+ run402 functions deploy prj_abc123 send-reminders --file remind.ts \\
82
83
  --schedule '*/15 * * * *'
83
- run402 functions deploy abc123 send-reminders --file remind.ts --schedule ''
84
+ run402 functions deploy prj_abc123 send-reminders --file remind.ts --schedule ''
84
85
  `,
85
86
  invoke: `run402 functions invoke — Invoke a deployed function
86
87
 
@@ -96,8 +97,8 @@ Options:
96
97
  --body <json> Request body (ignored for GET/HEAD)
97
98
 
98
99
  Examples:
99
- run402 functions invoke abc123 stripe-webhook --body '{"event":"test"}'
100
- run402 functions invoke abc123 ping --method GET
100
+ run402 functions invoke prj_abc123 stripe-webhook --body '{"event":"test"}'
101
+ run402 functions invoke prj_abc123 ping --method GET
101
102
  `,
102
103
  logs: `run402 functions logs — Fetch or tail function logs
103
104
 
@@ -114,9 +115,9 @@ Options:
114
115
  --follow Poll every 3s and stream new entries (Ctrl-C to stop)
115
116
 
116
117
  Examples:
117
- run402 functions logs abc123 stripe-webhook --tail 100
118
- run402 functions logs abc123 stripe-webhook --since 2026-03-29T14:00:00Z
119
- run402 functions logs abc123 stripe-webhook --follow
118
+ run402 functions logs prj_abc123 stripe-webhook --tail 100
119
+ run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
120
+ run402 functions logs prj_abc123 stripe-webhook --follow
120
121
  `,
121
122
  update: `run402 functions update — Update function config without re-deploying
122
123
 
@@ -137,18 +138,20 @@ Notes:
137
138
  Must provide at least one of the options above.
138
139
 
139
140
  Examples:
140
- run402 functions update abc123 send-reminders --schedule '0 */4 * * *'
141
- run402 functions update abc123 send-reminders --schedule-remove
142
- run402 functions update abc123 my-func --timeout 15 --memory 256
141
+ run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
142
+ run402 functions update prj_abc123 send-reminders --schedule-remove
143
+ run402 functions update prj_abc123 my-func --timeout 15 --memory 256
143
144
  `,
144
145
  };
145
146
 
146
147
  async function deploy(projectId, name, args) {
148
+ assertRequiredProjectAndName(projectId, name, "run402 functions deploy <project_id> <name> --file <file>");
149
+ assertKnownFlags(args, ["--file", "--timeout", "--memory", "--deps", "--schedule", "--help", "-h"], ["--file", "--timeout", "--memory", "--deps", "--schedule"]);
147
150
  const opts = { file: null, timeout: undefined, memory: undefined, deps: undefined, schedule: undefined };
148
151
  for (let i = 0; i < args.length; i++) {
149
152
  if (args[i] === "--file" && args[i + 1]) opts.file = args[++i];
150
- if (args[i] === "--timeout" && args[i + 1]) opts.timeout = parseInt(args[++i]);
151
- if (args[i] === "--memory" && args[i + 1]) opts.memory = parseInt(args[++i]);
153
+ if (args[i] === "--timeout") opts.timeout = parseIntegerFlag("--timeout", args[++i], { min: 1 });
154
+ if (args[i] === "--memory") opts.memory = parseIntegerFlag("--memory", args[++i], { min: 1 });
152
155
  if (args[i] === "--deps" && args[i + 1]) opts.deps = args[++i].split(",");
153
156
  if (args[i] === "--schedule" && i + 1 < args.length) opts.schedule = args[++i];
154
157
  }
@@ -175,6 +178,8 @@ async function deploy(projectId, name, args) {
175
178
  }
176
179
 
177
180
  async function invoke(projectId, name, args) {
181
+ assertRequiredProjectAndName(projectId, name, "run402 functions invoke <project_id> <name> [--method <M>] [--body <json>]");
182
+ assertKnownFlags(args, ["--method", "--body", "--help", "-h"], ["--method", "--body"]);
178
183
  const opts = { method: "POST", body: undefined };
179
184
  for (let i = 0; i < args.length; i++) {
180
185
  if (args[i] === "--method" && args[i + 1]) opts.method = args[++i];
@@ -198,11 +203,13 @@ async function invoke(projectId, name, args) {
198
203
  }
199
204
 
200
205
  async function logs(projectId, name, args) {
206
+ assertRequiredProjectAndName(projectId, name, "run402 functions logs <project_id> <name> [--tail <n>]");
207
+ assertKnownFlags(args, ["--tail", "--since", "--follow", "--help", "-h"], ["--tail", "--since"]);
201
208
  let tail = 50;
202
209
  let since = undefined;
203
210
  let follow = false;
204
211
  for (let i = 0; i < args.length; i++) {
205
- if (args[i] === "--tail" && args[i + 1]) tail = parseInt(args[++i]);
212
+ if (args[i] === "--tail") tail = parseIntegerFlag("--tail", args[++i], { min: 1 });
206
213
  if (args[i] === "--since" && args[i + 1]) since = args[++i];
207
214
  if (args[i] === "--follow") follow = true;
208
215
  }
@@ -269,6 +276,8 @@ async function logs(projectId, name, args) {
269
276
  }
270
277
 
271
278
  async function update(projectId, name, args) {
279
+ assertRequiredProjectAndName(projectId, name, "run402 functions update <project_id> <name> [options]");
280
+ assertKnownFlags(args, ["--schedule", "--schedule-remove", "--timeout", "--memory", "--help", "-h"], ["--schedule", "--timeout", "--memory"]);
272
281
  let schedule = undefined;
273
282
  let scheduleRemove = false;
274
283
  let timeout = undefined;
@@ -276,8 +285,8 @@ async function update(projectId, name, args) {
276
285
  for (let i = 0; i < args.length; i++) {
277
286
  if (args[i] === "--schedule" && i + 1 < args.length) schedule = args[++i];
278
287
  if (args[i] === "--schedule-remove") scheduleRemove = true;
279
- if (args[i] === "--timeout" && args[i + 1]) timeout = parseInt(args[++i]);
280
- if (args[i] === "--memory" && args[i + 1]) memory = parseInt(args[++i]);
288
+ if (args[i] === "--timeout") timeout = parseIntegerFlag("--timeout", args[++i], { min: 1 });
289
+ if (args[i] === "--memory") memory = parseIntegerFlag("--memory", args[++i], { min: 1 });
281
290
  }
282
291
 
283
292
  const updateOpts = {};
@@ -305,6 +314,7 @@ async function update(projectId, name, args) {
305
314
  }
306
315
 
307
316
  async function list(projectId) {
317
+ assertRequiredProject(projectId, "run402 functions list <project_id>");
308
318
  try {
309
319
  const data = await getSdk().functions.list(projectId);
310
320
  console.log(JSON.stringify(data, null, 2));
@@ -314,6 +324,7 @@ async function list(projectId) {
314
324
  }
315
325
 
316
326
  async function deleteFunction(projectId, name) {
327
+ assertRequiredProjectAndName(projectId, name, "run402 functions delete <project_id> <name>");
317
328
  try {
318
329
  await getSdk().functions.delete(projectId, name);
319
330
  console.log(JSON.stringify({ status: "ok", message: `Function '${name}' deleted.` }));
@@ -324,7 +335,8 @@ async function deleteFunction(projectId, name) {
324
335
 
325
336
  export async function run(sub, args) {
326
337
  if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
327
- if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
338
+ args = normalizeArgv(args);
339
+ if (Array.isArray(args) && hasHelp(args)) {
328
340
  console.log(SUB_HELP[sub] || HELP);
329
341
  process.exit(0);
330
342
  }
@@ -341,3 +353,24 @@ export async function run(sub, args) {
341
353
  process.exit(1);
342
354
  }
343
355
  }
356
+
357
+ function assertRequiredProject(projectId, usage) {
358
+ if (!projectId || String(projectId).startsWith("-")) {
359
+ fail({
360
+ code: "BAD_USAGE",
361
+ message: "Missing <project_id>.",
362
+ hint: usage,
363
+ });
364
+ }
365
+ }
366
+
367
+ function assertRequiredProjectAndName(projectId, name, usage) {
368
+ assertRequiredProject(projectId, usage);
369
+ if (!name || String(name).startsWith("-")) {
370
+ fail({
371
+ code: "BAD_USAGE",
372
+ message: "Missing <name>.",
373
+ hint: usage,
374
+ });
375
+ }
376
+ }