run402 1.69.4 → 1.69.6

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 (47) hide show
  1. package/lib/agent.mjs +37 -11
  2. package/lib/ai.mjs +16 -15
  3. package/lib/allowance.mjs +23 -7
  4. package/lib/apps.mjs +92 -40
  5. package/lib/argparse.mjs +60 -1
  6. package/lib/auth.mjs +62 -1
  7. package/lib/billing.mjs +83 -25
  8. package/lib/blob.mjs +2 -2
  9. package/lib/cdn.mjs +21 -8
  10. package/lib/contracts.mjs +123 -33
  11. package/lib/domains.mjs +22 -11
  12. package/lib/email.mjs +68 -61
  13. package/lib/image.mjs +17 -8
  14. package/lib/message.mjs +4 -1
  15. package/lib/secrets.mjs +29 -12
  16. package/lib/sender-domain.mjs +32 -14
  17. package/lib/sites.mjs +39 -16
  18. package/lib/subdomains.mjs +31 -35
  19. package/lib/tier.mjs +26 -4
  20. package/lib/webhooks.mjs +46 -33
  21. package/package.json +1 -1
  22. package/sdk/dist/errors.d.ts +4 -1
  23. package/sdk/dist/errors.d.ts.map +1 -1
  24. package/sdk/dist/errors.js +16 -2
  25. package/sdk/dist/errors.js.map +1 -1
  26. package/sdk/dist/namespaces/ai.d.ts.map +1 -1
  27. package/sdk/dist/namespaces/ai.js +7 -2
  28. package/sdk/dist/namespaces/ai.js.map +1 -1
  29. package/sdk/dist/namespaces/billing.d.ts.map +1 -1
  30. package/sdk/dist/namespaces/billing.js +20 -17
  31. package/sdk/dist/namespaces/billing.js.map +1 -1
  32. package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
  33. package/sdk/dist/namespaces/contracts.js +21 -4
  34. package/sdk/dist/namespaces/contracts.js.map +1 -1
  35. package/sdk/dist/namespaces/deploy.js +9 -0
  36. package/sdk/dist/namespaces/deploy.js.map +1 -1
  37. package/sdk/dist/namespaces/functions.d.ts.map +1 -1
  38. package/sdk/dist/namespaces/functions.js +20 -0
  39. package/sdk/dist/namespaces/functions.js.map +1 -1
  40. package/sdk/dist/retry.d.ts +2 -1
  41. package/sdk/dist/retry.d.ts.map +1 -1
  42. package/sdk/dist/retry.js +2 -1
  43. package/sdk/dist/retry.js.map +1 -1
  44. package/sdk/dist/validation.d.ts +3 -0
  45. package/sdk/dist/validation.d.ts.map +1 -1
  46. package/sdk/dist/validation.js +15 -0
  47. package/sdk/dist/validation.js.map +1 -1
package/lib/agent.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { allowanceAuthHeaders } 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
+ import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, validateWebhookUrl } from "./argparse.mjs";
5
5
 
6
6
  const HELP = `run402 agent — Manage agent identity
7
7
 
@@ -76,12 +76,20 @@ contact email. Requires assurance_level=email_verified first.
76
76
  };
77
77
 
78
78
  async function contact(args) {
79
- let name = null, email = null, webhook = null;
80
- for (let i = 0; i < args.length; i++) {
81
- if (args[i] === "--name" && args[i + 1]) name = args[++i];
82
- if (args[i] === "--email" && args[i + 1]) email = args[++i];
83
- if (args[i] === "--webhook" && args[i + 1]) webhook = args[++i];
79
+ const parsedArgs = normalizeArgv(args);
80
+ const valueFlags = ["--name", "--email", "--webhook"];
81
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
82
+ const extra = positionalArgs(parsedArgs, valueFlags);
83
+ if (extra.length > 0) {
84
+ fail({
85
+ code: "BAD_USAGE",
86
+ message: `Unexpected argument for agent contact: ${extra[0]}`,
87
+ hint: "Use `run402 agent contact --name <name> [--email <email>] [--webhook <url>]`.",
88
+ });
84
89
  }
90
+ const name = flagValue(parsedArgs, "--name");
91
+ const email = flagValue(parsedArgs, "--email");
92
+ const webhook = flagValue(parsedArgs, "--webhook");
85
93
  if (!name) {
86
94
  fail({ code: "BAD_USAGE", message: "Missing --name <name>" });
87
95
  }
@@ -104,7 +112,13 @@ async function contact(args) {
104
112
  }
105
113
  }
106
114
 
107
- async function status() {
115
+ async function status(args = []) {
116
+ const parsedArgs = normalizeArgv(args);
117
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
118
+ const extra = positionalArgs(parsedArgs);
119
+ if (extra.length > 0) {
120
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for agent status: ${extra[0]}` });
121
+ }
108
122
  allowanceAuthHeaders("/agent/v1/contact/status");
109
123
 
110
124
  try {
@@ -115,7 +129,13 @@ async function status() {
115
129
  }
116
130
  }
117
131
 
118
- async function verifyEmail() {
132
+ async function verifyEmail(args = []) {
133
+ const parsedArgs = normalizeArgv(args);
134
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
135
+ const extra = positionalArgs(parsedArgs);
136
+ if (extra.length > 0) {
137
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for agent verify-email: ${extra[0]}` });
138
+ }
119
139
  allowanceAuthHeaders("/agent/v1/contact/verify-email");
120
140
 
121
141
  try {
@@ -127,7 +147,13 @@ async function verifyEmail() {
127
147
  }
128
148
 
129
149
  async function passkey(args) {
130
- const action = args[0];
150
+ const parsedArgs = normalizeArgv(args);
151
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
152
+ const positionals = positionalArgs(parsedArgs);
153
+ const action = positionals[0];
154
+ if (positionals.length > 1) {
155
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for agent passkey: ${positionals[1]}` });
156
+ }
131
157
  if (action !== "enroll") {
132
158
  fail({ code: "BAD_USAGE", message: "Usage: run402 agent passkey enroll" });
133
159
  }
@@ -152,10 +178,10 @@ export async function run(sub, args) {
152
178
  await contact(args);
153
179
  return;
154
180
  case "status":
155
- await status();
181
+ await status(args);
156
182
  return;
157
183
  case "verify-email":
158
- await verifyEmail();
184
+ await verifyEmail(args);
159
185
  return;
160
186
  case "passkey":
161
187
  await passkey(args);
package/lib/ai.mjs CHANGED
@@ -1,7 +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 { resolvePositionalProject } from "./argparse.mjs";
4
+ import { assertKnownFlags, flagValue, normalizeArgv, resolvePositionalProject } from "./argparse.mjs";
5
5
 
6
6
  const HELP = `run402 ai — AI translation and moderation tools
7
7
 
@@ -103,20 +103,19 @@ Examples:
103
103
  `,
104
104
  };
105
105
 
106
- function parseFlag(args, flag) {
107
- for (let i = 0; i < args.length; i++) {
108
- if (args[i] === flag && args[i + 1]) return args[i + 1];
109
- }
110
- return null;
111
- }
112
-
113
106
  // translate has value-bearing flags (--to, --from, --context, --project) that
114
107
  // must not be mistaken for positional bare args when prefix-matching.
115
108
  const TRANSLATE_VALUE_FLAGS = ["--to", "--from", "--context", "--project"];
116
109
 
117
110
  async function translate(args) {
111
+ args = normalizeArgv(args);
112
+ assertKnownFlags(args, TRANSLATE_VALUE_FLAGS, TRANSLATE_VALUE_FLAGS);
113
+
118
114
  // --project <id> wins over positional, mirroring previous behavior.
119
- const projectOpt = parseFlag(args, "--project");
115
+ const projectOpt = flagValue(args, "--project");
116
+ const to = flagValue(args, "--to");
117
+ const from = flagValue(args, "--from");
118
+ const context = flagValue(args, "--context");
120
119
  let projectId;
121
120
  let rest;
122
121
  if (projectOpt) {
@@ -139,10 +138,6 @@ async function translate(args) {
139
138
  break;
140
139
  }
141
140
 
142
- const to = parseFlag(args, "--to");
143
- const from = parseFlag(args, "--from");
144
- const context = parseFlag(args, "--context");
145
-
146
141
  if (!text) {
147
142
  fail({
148
143
  code: "BAD_USAGE",
@@ -165,7 +160,10 @@ async function translate(args) {
165
160
  const MODERATE_VALUE_FLAGS = ["--project"];
166
161
 
167
162
  async function moderate(args) {
168
- const projectOpt = parseFlag(args, "--project");
163
+ args = normalizeArgv(args);
164
+ assertKnownFlags(args, MODERATE_VALUE_FLAGS, MODERATE_VALUE_FLAGS);
165
+
166
+ const projectOpt = flagValue(args, "--project");
169
167
  let projectId;
170
168
  let rest;
171
169
  if (projectOpt) {
@@ -203,7 +201,10 @@ async function moderate(args) {
203
201
  }
204
202
 
205
203
  async function usage(args) {
206
- const projectOpt = parseFlag(args, "--project");
204
+ args = normalizeArgv(args);
205
+ assertKnownFlags(args, MODERATE_VALUE_FLAGS, MODERATE_VALUE_FLAGS);
206
+
207
+ const projectOpt = flagValue(args, "--project");
207
208
  let projectId;
208
209
  if (projectOpt) {
209
210
  projectId = resolveProjectId(projectOpt);
package/lib/allowance.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readAllowance, saveAllowance, ALLOWANCE_FILE } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
3
  import { reportSdkError, fail } from "./sdk-errors.mjs";
4
+ import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
4
5
 
5
6
  const HELP = `run402 allowance — Manage your agent allowance
6
7
 
@@ -258,17 +259,25 @@ async function checkout(args) {
258
259
  hint: "Run: run402 allowance create",
259
260
  });
260
261
  }
261
- let amount = null;
262
- for (let i = 0; i < args.length; i++) {
263
- if (args[i] === "--amount" && args[i + 1]) amount = parseInt(args[++i], 10);
262
+ const parsedArgs = normalizeArgv(args);
263
+ assertKnownFlags(parsedArgs, ["--amount", "--help", "-h"], ["--amount"]);
264
+ const bare = positionalArgs(parsedArgs, ["--amount"]);
265
+ if (bare.length > 0) {
266
+ fail({
267
+ code: "BAD_USAGE",
268
+ message: `Unexpected argument for allowance checkout: ${bare[0]}`,
269
+ details: { argument: bare[0] },
270
+ });
264
271
  }
265
- if (!amount) {
272
+ const amountRaw = flagValue(parsedArgs, "--amount");
273
+ if (amountRaw === null) {
266
274
  fail({
267
275
  code: "BAD_USAGE",
268
276
  message: "Missing --amount <usd_micros>",
269
277
  hint: "e.g. --amount 5000000 for $5",
270
278
  });
271
279
  }
280
+ const amount = parseIntegerFlag("--amount", amountRaw, { min: 1 });
272
281
  try {
273
282
  const data = await getSdk().billing.createCheckout(w.address, amount);
274
283
  console.log(JSON.stringify(data, null, 2));
@@ -286,10 +295,17 @@ async function history(args) {
286
295
  hint: "Run: run402 allowance create",
287
296
  });
288
297
  }
289
- let limit = 20;
290
- for (let i = 0; i < args.length; i++) {
291
- if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i], 10);
298
+ const parsedArgs = normalizeArgv(args);
299
+ assertKnownFlags(parsedArgs, ["--limit", "--help", "-h"], ["--limit"]);
300
+ const bare = positionalArgs(parsedArgs, ["--limit"]);
301
+ if (bare.length > 0) {
302
+ fail({
303
+ code: "BAD_USAGE",
304
+ message: `Unexpected argument for allowance history: ${bare[0]}`,
305
+ details: { argument: bare[0] },
306
+ });
292
307
  }
308
+ const limit = parseIntegerFlag("--limit", flagValue(parsedArgs, "--limit"), { min: 1, def: 20 });
293
309
  try {
294
310
  const data = await getSdk().billing.history(w.address, limit);
295
311
  console.log(JSON.stringify(data, null, 2));
package/lib/apps.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { allowanceAuthHeaders, saveProject } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
3
  import { reportSdkError, fail } from "./sdk-errors.mjs";
4
+ import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
4
5
 
5
6
  const HELP = `run402 apps — Browse and manage the app marketplace
6
7
 
@@ -9,7 +10,7 @@ Usage:
9
10
 
10
11
  Subcommands:
11
12
  browse [--tag <tag>] Browse public apps
12
- fork <version_id> <name> [--tier <tier>] [--subdomain <name>]
13
+ fork <version_id> <name> [--subdomain <name>]
13
14
  Fork a published app into your own project
14
15
  publish <id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
15
16
  Publish a project as an app
@@ -22,7 +23,7 @@ Subcommands:
22
23
  Examples:
23
24
  run402 apps browse
24
25
  run402 apps browse --tag auth
25
- run402 apps fork ver_abc123 my-todo --tier prototype
26
+ run402 apps fork ver_abc123 my-todo
26
27
  run402 apps publish prj_abc123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
27
28
  run402 apps versions prj_abc123
28
29
  run402 apps inspect ver_abc123
@@ -54,12 +55,11 @@ Arguments:
54
55
  <name> Name for the forked project
55
56
 
56
57
  Options:
57
- --tier <tier> Tier for the new project (default: prototype)
58
58
  --subdomain <name> Claim a subdomain for the forked project
59
59
 
60
60
  Examples:
61
61
  run402 apps fork ver_abc123 my-todo
62
- run402 apps fork ver_abc123 my-todo --tier hobby --subdomain todo-v2
62
+ run402 apps fork ver_abc123 my-todo --subdomain todo-v2
63
63
  `,
64
64
  publish: `run402 apps publish — Publish a project as an app
65
65
 
@@ -137,9 +137,16 @@ Examples:
137
137
  };
138
138
 
139
139
  async function browse(args) {
140
+ const parsedArgs = normalizeArgv(args);
141
+ const valueFlags = ["--tag"];
142
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
143
+ const extra = positionalArgs(parsedArgs, valueFlags);
144
+ if (extra.length > 0) {
145
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps browse: ${extra[0]}` });
146
+ }
140
147
  const tags = [];
141
- for (let i = 0; i < args.length; i++) {
142
- if (args[i] === "--tag" && args[i + 1]) tags.push(args[++i]);
148
+ for (let i = 0; i < parsedArgs.length; i++) {
149
+ if (parsedArgs[i] === "--tag") tags.push(parsedArgs[++i]);
143
150
  }
144
151
  try {
145
152
  const data = await getSdk().apps.browse(tags.length > 0 ? tags : undefined);
@@ -150,18 +157,24 @@ async function browse(args) {
150
157
  }
151
158
 
152
159
  async function fork(versionId, name, args) {
153
- const opts = { tier: "prototype", subdomain: undefined };
154
- for (let i = 0; i < args.length; i++) {
155
- if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
156
- if (args[i] === "--subdomain" && args[i + 1]) opts.subdomain = args[++i];
160
+ const parsedArgs = normalizeArgv([versionId, name, ...args].filter((arg) => arg !== undefined));
161
+ const valueFlags = ["--subdomain"];
162
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
163
+ const positionals = positionalArgs(parsedArgs, valueFlags);
164
+ if (positionals.length < 2) {
165
+ fail({ code: "BAD_USAGE", message: "Missing <version_id> and/or <name>." });
157
166
  }
167
+ if (positionals.length > 2) {
168
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps fork: ${positionals[2]}` });
169
+ }
170
+ const opts = { subdomain: flagValue(parsedArgs, "--subdomain") ?? undefined };
158
171
  // Preserve the aggressive early exit when no allowance is configured.
159
172
  allowanceAuthHeaders("/fork/v1");
160
173
 
161
174
  try {
162
175
  const data = await getSdk().apps.fork({
163
- versionId,
164
- name,
176
+ versionId: positionals[0],
177
+ name: positionals[1],
165
178
  subdomain: opts.subdomain,
166
179
  });
167
180
 
@@ -181,15 +194,24 @@ async function fork(versionId, name, args) {
181
194
  }
182
195
 
183
196
  async function publish(projectId, args) {
184
- const opts = { description: undefined, tags: undefined, visibility: undefined, forkAllowed: undefined };
185
- for (let i = 0; i < args.length; i++) {
186
- if (args[i] === "--description" && args[i + 1]) opts.description = args[++i];
187
- if (args[i] === "--tags" && args[i + 1]) opts.tags = args[++i].split(",");
188
- if (args[i] === "--visibility" && args[i + 1]) opts.visibility = args[++i];
189
- if (args[i] === "--fork-allowed") opts.forkAllowed = true;
197
+ const parsedArgs = normalizeArgv([projectId, ...args].filter((arg) => arg !== undefined));
198
+ const valueFlags = ["--description", "--tags", "--visibility"];
199
+ assertKnownFlags(parsedArgs, [...valueFlags, "--fork-allowed", "--help", "-h"], valueFlags);
200
+ const positionals = positionalArgs(parsedArgs, valueFlags);
201
+ if (positionals.length < 1) {
202
+ fail({ code: "BAD_USAGE", message: "Missing <id>." });
190
203
  }
204
+ if (positionals.length > 1) {
205
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps publish: ${positionals[1]}` });
206
+ }
207
+ const opts = { description: undefined, tags: undefined, visibility: undefined, forkAllowed: undefined };
208
+ opts.description = flagValue(parsedArgs, "--description") ?? undefined;
209
+ opts.tags = flagValue(parsedArgs, "--tags")?.split(",");
210
+ opts.visibility = flagValue(parsedArgs, "--visibility") ?? undefined;
211
+ if (opts.visibility) assertAllowedValue(opts.visibility, ["public", "unlisted", "private"], "--visibility");
212
+ if (parsedArgs.includes("--fork-allowed")) opts.forkAllowed = true;
191
213
  try {
192
- const data = await getSdk().apps.publish(projectId, {
214
+ const data = await getSdk().apps.publish(positionals[0], {
193
215
  description: opts.description,
194
216
  tags: opts.tags,
195
217
  visibility: opts.visibility,
@@ -201,21 +223,30 @@ async function publish(projectId, args) {
201
223
  }
202
224
  }
203
225
 
204
- async function versions(projectId) {
226
+ async function versions(projectId, args = []) {
227
+ const parsedArgs = normalizeArgv([projectId, ...args].filter((arg) => arg !== undefined));
228
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
229
+ const positionals = positionalArgs(parsedArgs);
230
+ if (positionals.length !== 1) {
231
+ fail({ code: "BAD_USAGE", message: positionals.length === 0 ? "Missing <id>." : `Unexpected argument for apps versions: ${positionals[1]}` });
232
+ }
205
233
  try {
206
- const data = await getSdk().apps.listVersions(projectId);
234
+ const data = await getSdk().apps.listVersions(positionals[0]);
207
235
  console.log(JSON.stringify(data, null, 2));
208
236
  } catch (err) {
209
237
  reportSdkError(err);
210
238
  }
211
239
  }
212
240
 
213
- async function inspect(versionId) {
214
- if (!versionId) {
215
- fail({ code: "BAD_USAGE", message: "Missing version ID" });
241
+ async function inspect(versionId, args = []) {
242
+ const parsedArgs = normalizeArgv([versionId, ...args].filter((arg) => arg !== undefined));
243
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
244
+ const positionals = positionalArgs(parsedArgs);
245
+ if (positionals.length !== 1) {
246
+ fail({ code: "BAD_USAGE", message: positionals.length === 0 ? "Missing version ID" : `Unexpected argument for apps inspect: ${positionals[1]}` });
216
247
  }
217
248
  try {
218
- const data = await getSdk().apps.getApp(versionId);
249
+ const data = await getSdk().apps.getApp(positionals[0]);
219
250
  console.log(JSON.stringify(data, null, 2));
220
251
  } catch (err) {
221
252
  reportSdkError(err);
@@ -223,26 +254,47 @@ async function inspect(versionId) {
223
254
  }
224
255
 
225
256
  async function update(projectId, versionId, args) {
226
- const opts = {};
227
- for (let i = 0; i < args.length; i++) {
228
- if (args[i] === "--description" && args[i + 1]) opts.description = args[++i];
229
- if (args[i] === "--tags" && args[i + 1]) opts.tags = args[++i].split(",");
230
- if (args[i] === "--visibility" && args[i + 1]) opts.visibility = args[++i];
231
- if (args[i] === "--fork-allowed") opts.fork_allowed = true;
232
- if (args[i] === "--no-fork") opts.fork_allowed = false;
257
+ const parsedArgs = normalizeArgv([projectId, versionId, ...args].filter((arg) => arg !== undefined));
258
+ const valueFlags = ["--description", "--tags", "--visibility"];
259
+ assertKnownFlags(parsedArgs, [...valueFlags, "--fork-allowed", "--no-fork", "--help", "-h"], valueFlags);
260
+ const positionals = positionalArgs(parsedArgs, valueFlags);
261
+ if (positionals.length < 2) {
262
+ fail({ code: "BAD_USAGE", message: "Missing <project_id> and/or <version_id>." });
263
+ }
264
+ if (positionals.length > 2) {
265
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps update: ${positionals[2]}` });
266
+ }
267
+ if (parsedArgs.includes("--fork-allowed") && parsedArgs.includes("--no-fork")) {
268
+ fail({ code: "BAD_USAGE", message: "Provide either --fork-allowed or --no-fork, not both." });
233
269
  }
270
+ const opts = {};
271
+ opts.description = flagValue(parsedArgs, "--description") ?? undefined;
272
+ opts.tags = flagValue(parsedArgs, "--tags")?.split(",");
273
+ opts.visibility = flagValue(parsedArgs, "--visibility") ?? undefined;
274
+ if (opts.visibility) assertAllowedValue(opts.visibility, ["public", "unlisted", "private"], "--visibility");
275
+ if (parsedArgs.includes("--fork-allowed")) opts.fork_allowed = true;
276
+ if (parsedArgs.includes("--no-fork")) opts.fork_allowed = false;
234
277
  try {
235
- await getSdk().apps.updateVersion(projectId, versionId, opts);
236
- console.log(JSON.stringify({ status: "ok", project_id: projectId, version_id: versionId }));
278
+ await getSdk().apps.updateVersion(positionals[0], positionals[1], opts);
279
+ console.log(JSON.stringify({ status: "ok", project_id: positionals[0], version_id: positionals[1] }));
237
280
  } catch (err) {
238
281
  reportSdkError(err);
239
282
  }
240
283
  }
241
284
 
242
- async function deleteVersion(projectId, versionId) {
285
+ async function deleteVersion(projectId, versionId, args = []) {
286
+ const parsedArgs = normalizeArgv([projectId, versionId, ...args].filter((arg) => arg !== undefined));
287
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
288
+ const positionals = positionalArgs(parsedArgs);
289
+ if (positionals.length < 2) {
290
+ fail({ code: "BAD_USAGE", message: "Missing <project_id> and/or <version_id>." });
291
+ }
292
+ if (positionals.length > 2) {
293
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps delete: ${positionals[2]}` });
294
+ }
243
295
  try {
244
- await getSdk().apps.deleteVersion(projectId, versionId);
245
- console.log(JSON.stringify({ status: "ok", message: `Version ${versionId} deleted.` }));
296
+ await getSdk().apps.deleteVersion(positionals[0], positionals[1]);
297
+ console.log(JSON.stringify({ status: "ok", message: `Version ${positionals[1]} deleted.` }));
246
298
  } catch (err) {
247
299
  reportSdkError(err);
248
300
  }
@@ -255,10 +307,10 @@ export async function run(sub, args) {
255
307
  case "browse": await browse(args); break;
256
308
  case "fork": await fork(args[0], args[1], args.slice(2)); break;
257
309
  case "publish": await publish(args[0], args.slice(1)); break;
258
- case "versions": await versions(args[0]); break;
259
- case "inspect": await inspect(args[0]); break;
310
+ case "versions": await versions(args[0], args.slice(1)); break;
311
+ case "inspect": await inspect(args[0], args.slice(1)); break;
260
312
  case "update": await update(args[0], args[1], args.slice(2)); break;
261
- case "delete": await deleteVersion(args[0], args[1]); break;
313
+ case "delete": await deleteVersion(args[0], args[1], args.slice(2)); break;
262
314
  default:
263
315
  console.error(`Unknown subcommand: ${sub}\n`);
264
316
  console.log(HELP);
package/lib/argparse.mjs CHANGED
@@ -25,6 +25,13 @@ export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = [
25
25
  for (let i = 0; i < args.length; i++) {
26
26
  const arg = args[i];
27
27
  if (valueFlags.has(arg)) {
28
+ if (i + 1 >= args.length || (typeof args[i + 1] === "string" && args[i + 1].startsWith("--"))) {
29
+ fail({
30
+ code: "BAD_FLAG",
31
+ message: `${arg} requires a value`,
32
+ details: { flag: arg },
33
+ });
34
+ }
28
35
  i += 1;
29
36
  continue;
30
37
  }
@@ -47,7 +54,7 @@ export function failUnknownFlag(flag, knownFlags = []) {
47
54
  export function flagValue(args, flag) {
48
55
  const idx = args.indexOf(flag);
49
56
  if (idx === -1) return null;
50
- if (idx + 1 >= args.length) {
57
+ if (idx + 1 >= args.length || (typeof args[idx + 1] === "string" && args[idx + 1].startsWith("--"))) {
51
58
  fail({
52
59
  code: "BAD_FLAG",
53
60
  message: `${flag} requires a value`,
@@ -75,6 +82,13 @@ export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_I
75
82
  });
76
83
  }
77
84
  const n = Number.parseInt(raw, 10);
85
+ if (!Number.isSafeInteger(n)) {
86
+ fail({
87
+ code: "BAD_FLAG",
88
+ message: `${name} must be a safe integer, got: ${raw}`,
89
+ details: { flag: name, value: raw },
90
+ });
91
+ }
78
92
  if (n < min) {
79
93
  fail({
80
94
  code: "BAD_FLAG",
@@ -92,6 +106,26 @@ export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_I
92
106
  return n;
93
107
  }
94
108
 
109
+ export function assertAllowedValue(value, allowed, fieldName) {
110
+ if (!allowed.includes(value)) {
111
+ fail({
112
+ code: "BAD_FLAG",
113
+ message: `${fieldName} must be one of: ${allowed.join(", ")}`,
114
+ details: { field: fieldName, value, allowed },
115
+ });
116
+ }
117
+ }
118
+
119
+ export function validateEvmAddress(value, fieldName = "address") {
120
+ if (typeof value !== "string" || !/^0x[a-fA-F0-9]{40}$/.test(value)) {
121
+ fail({
122
+ code: "BAD_FLAG",
123
+ message: `${fieldName} must be a 0x-prefixed 20-byte EVM address`,
124
+ details: { field: fieldName, value },
125
+ });
126
+ }
127
+ }
128
+
95
129
  export function failBadProjectId(value) {
96
130
  fail({
97
131
  code: "BAD_PROJECT_ID",
@@ -199,6 +233,31 @@ export function positionalArgs(args = [], flagsWithValues = []) {
199
233
  return out;
200
234
  }
201
235
 
236
+ export function requirePositionalCount(args = [], flagsWithValues = [], opts = {}) {
237
+ const {
238
+ min = 0,
239
+ max = min,
240
+ command = "command",
241
+ missing = "Missing required argument.",
242
+ } = opts;
243
+ const pos = positionalArgs(args, flagsWithValues);
244
+ if (pos.length < min) {
245
+ fail({
246
+ code: "BAD_USAGE",
247
+ message: missing,
248
+ hint: command,
249
+ });
250
+ }
251
+ if (pos.length > max) {
252
+ fail({
253
+ code: "BAD_USAGE",
254
+ message: `Unexpected argument for ${command}: ${pos[max]}`,
255
+ hint: `Use \`${command}\`.`,
256
+ });
257
+ }
258
+ return pos;
259
+ }
260
+
202
261
  // Resolve a positional project_id argument with active-project fallback (GH-102, GH-187).
203
262
  // If the first positional starts with "prj_", treat it as the project id and
204
263
  // strip it from the rest. Otherwise, fall through to the active project from
package/lib/auth.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 { assertKnownFlags, hasHelp, normalizeArgv } from "./argparse.mjs";
4
5
 
5
6
  const HELP = `run402 auth — Manage project user authentication
6
7
 
@@ -230,6 +231,61 @@ Examples:
230
231
  `,
231
232
  };
232
233
 
234
+ const AUTH_FLAGS = {
235
+ "magic-link": {
236
+ known: ["--email", "--redirect", "--intent", "--state", "--project", "--help", "-h"],
237
+ values: ["--email", "--redirect", "--intent", "--state", "--project"],
238
+ },
239
+ "create-user": {
240
+ known: ["--email", "--admin", "--invite", "--redirect", "--state", "--project", "--help", "-h"],
241
+ values: ["--email", "--admin", "--redirect", "--state", "--project"],
242
+ },
243
+ "invite-user": {
244
+ known: ["--email", "--redirect", "--admin", "--state", "--project", "--help", "-h"],
245
+ values: ["--email", "--redirect", "--admin", "--state", "--project"],
246
+ },
247
+ verify: {
248
+ known: ["--token", "--project", "--help", "-h"],
249
+ values: ["--token", "--project"],
250
+ },
251
+ "set-password": {
252
+ known: ["--token", "--new", "--current", "--project", "--help", "-h"],
253
+ values: ["--token", "--new", "--current", "--project"],
254
+ },
255
+ settings: {
256
+ known: ["--allow-password-set", "--preferred", "--public-signup", "--require-admin-passkey", "--project", "--help", "-h"],
257
+ values: ["--allow-password-set", "--preferred", "--public-signup", "--require-admin-passkey", "--project"],
258
+ },
259
+ "passkey-register-options": {
260
+ known: ["--token", "--app-origin", "--project", "--help", "-h"],
261
+ values: ["--token", "--app-origin", "--project"],
262
+ },
263
+ "passkey-register-verify": {
264
+ known: ["--token", "--challenge", "--response", "--label", "--project", "--help", "-h"],
265
+ values: ["--token", "--challenge", "--response", "--label", "--project"],
266
+ },
267
+ "passkey-login-options": {
268
+ known: ["--app-origin", "--email", "--project", "--help", "-h"],
269
+ values: ["--app-origin", "--email", "--project"],
270
+ },
271
+ "passkey-login-verify": {
272
+ known: ["--challenge", "--response", "--project", "--help", "-h"],
273
+ values: ["--challenge", "--response", "--project"],
274
+ },
275
+ passkeys: {
276
+ known: ["--token", "--project", "--help", "-h"],
277
+ values: ["--token", "--project"],
278
+ },
279
+ "delete-passkey": {
280
+ known: ["--token", "--id", "--project", "--help", "-h"],
281
+ values: ["--token", "--id", "--project"],
282
+ },
283
+ providers: {
284
+ known: ["--project", "--help", "-h"],
285
+ values: ["--project"],
286
+ },
287
+ };
288
+
233
289
  function parseFlag(args, flag) {
234
290
  for (let i = 0; i < args.length; i++) {
235
291
  if (args[i] === flag && args[i + 1]) return args[i + 1];
@@ -557,10 +613,15 @@ async function providers(args) {
557
613
 
558
614
  export async function run(sub, args) {
559
615
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
560
- if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
616
+ args = normalizeArgv(args);
617
+ if (Array.isArray(args) && hasHelp(args)) {
561
618
  console.log(SUB_HELP[sub] || HELP);
562
619
  process.exit(0);
563
620
  }
621
+ const flagSpec = AUTH_FLAGS[sub];
622
+ if (flagSpec) {
623
+ assertKnownFlags(args, flagSpec.known, flagSpec.values);
624
+ }
564
625
  switch (sub) {
565
626
  case "magic-link": await magicLink(args); break;
566
627
  case "verify": await verify(args); break;