run402 1.69.5 → 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.
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/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`,
@@ -99,6 +106,26 @@ export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_I
99
106
  return n;
100
107
  }
101
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
+
102
129
  export function failBadProjectId(value) {
103
130
  fail({
104
131
  code: "BAD_PROJECT_ID",
@@ -206,6 +233,31 @@ export function positionalArgs(args = [], flagsWithValues = []) {
206
233
  return out;
207
234
  }
208
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
+
209
261
  // Resolve a positional project_id argument with active-project fallback (GH-102, GH-187).
210
262
  // If the first positional starts with "prj_", treat it as the project id and
211
263
  // strip it from the rest. Otherwise, fall through to the active project from
package/lib/billing.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getSdk } from "./sdk.mjs";
2
2
  import { reportSdkError, fail } from "./sdk-errors.mjs";
3
- import { parseIntegerFlag } from "./argparse.mjs";
3
+ import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
4
4
 
5
5
  const HELP = `run402 billing — Email billing accounts, Stripe tier checkout, email packs
6
6
 
@@ -122,13 +122,6 @@ Examples:
122
122
  `,
123
123
  };
124
124
 
125
- function parseFlag(args, flag) {
126
- for (let i = 0; i < args.length; i++) {
127
- if (args[i] === flag && args[i + 1]) return args[i + 1];
128
- }
129
- return null;
130
- }
131
-
132
125
  function requireSingleBillingIdentifier(email, wallet) {
133
126
  if (email && wallet) {
134
127
  fail({
@@ -147,7 +140,13 @@ function requireSingleBillingIdentifier(email, wallet) {
147
140
  }
148
141
 
149
142
  async function createEmail(args) {
150
- const email = args[0];
143
+ const parsedArgs = normalizeArgv(args);
144
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
145
+ const positionals = positionalArgs(parsedArgs);
146
+ const email = positionals[0];
147
+ if (positionals.length > 1) {
148
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing create-email: ${positionals[1]}` });
149
+ }
151
150
  if (!email) {
152
151
  fail({
153
152
  code: "BAD_USAGE",
@@ -164,8 +163,14 @@ async function createEmail(args) {
164
163
  }
165
164
 
166
165
  async function linkWallet(args) {
167
- const accountId = args[0];
168
- const wallet = args[1];
166
+ const parsedArgs = normalizeArgv(args);
167
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
168
+ const positionals = positionalArgs(parsedArgs);
169
+ const accountId = positionals[0];
170
+ const wallet = positionals[1];
171
+ if (positionals.length > 2) {
172
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing link-wallet: ${positionals[2]}` });
173
+ }
169
174
  if (!accountId || !wallet) {
170
175
  fail({
171
176
  code: "BAD_USAGE",
@@ -182,7 +187,14 @@ async function linkWallet(args) {
182
187
  }
183
188
 
184
189
  async function tierCheckout(args) {
185
- const tier = args[0];
190
+ const parsedArgs = normalizeArgv(args);
191
+ const valueFlags = ["--email", "--wallet"];
192
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
193
+ const positionals = positionalArgs(parsedArgs, valueFlags);
194
+ const tier = positionals[0];
195
+ if (positionals.length > 1) {
196
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing tier-checkout: ${positionals[1]}` });
197
+ }
186
198
  if (!tier) {
187
199
  fail({
188
200
  code: "BAD_USAGE",
@@ -190,8 +202,8 @@ async function tierCheckout(args) {
190
202
  hint: "run402 billing tier-checkout <tier> [--email <e> | --wallet <w>]",
191
203
  });
192
204
  }
193
- const email = parseFlag(args, "--email");
194
- const wallet = parseFlag(args, "--wallet");
205
+ const email = flagValue(parsedArgs, "--email");
206
+ const wallet = flagValue(parsedArgs, "--wallet");
195
207
  requireSingleBillingIdentifier(email, wallet);
196
208
  try {
197
209
  const data = await getSdk().billing.tierCheckout(tier, { email: email ?? undefined, wallet: wallet ?? undefined });
@@ -202,8 +214,15 @@ async function tierCheckout(args) {
202
214
  }
203
215
 
204
216
  async function buyPack(args) {
205
- const email = parseFlag(args, "--email");
206
- const wallet = parseFlag(args, "--wallet");
217
+ const parsedArgs = normalizeArgv(args);
218
+ const valueFlags = ["--email", "--wallet"];
219
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
220
+ const extra = positionalArgs(parsedArgs, valueFlags);
221
+ if (extra.length > 0) {
222
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing buy-email-pack: ${extra[0]}` });
223
+ }
224
+ const email = flagValue(parsedArgs, "--email");
225
+ const wallet = flagValue(parsedArgs, "--wallet");
207
226
  requireSingleBillingIdentifier(email, wallet);
208
227
  try {
209
228
  const data = await getSdk().billing.buyEmailPack({ email: email ?? undefined, wallet: wallet ?? undefined });
@@ -214,8 +233,15 @@ async function buyPack(args) {
214
233
  }
215
234
 
216
235
  async function autoRecharge(args) {
217
- const accountId = args[0];
218
- const state = args[1];
236
+ const parsedArgs = normalizeArgv(args);
237
+ const valueFlags = ["--threshold"];
238
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
239
+ const positionals = positionalArgs(parsedArgs, valueFlags);
240
+ const accountId = positionals[0];
241
+ const state = positionals[1];
242
+ if (positionals.length > 2) {
243
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing auto-recharge: ${positionals[2]}` });
244
+ }
219
245
  if (!accountId || !state || !["on", "off"].includes(state)) {
220
246
  fail({
221
247
  code: "BAD_USAGE",
@@ -223,8 +249,8 @@ async function autoRecharge(args) {
223
249
  hint: "run402 billing auto-recharge <account_id> <on|off> [--threshold <n>]",
224
250
  });
225
251
  }
226
- const thresholdStr = parseFlag(args, "--threshold");
227
- const threshold = args.includes("--threshold")
252
+ const thresholdStr = flagValue(parsedArgs, "--threshold");
253
+ const threshold = parsedArgs.includes("--threshold")
228
254
  ? parseIntegerFlag("--threshold", thresholdStr, { min: 0 })
229
255
  : undefined;
230
256
  try {
@@ -240,7 +266,13 @@ async function autoRecharge(args) {
240
266
  }
241
267
 
242
268
  async function balance(args) {
243
- const id = args[0];
269
+ const parsedArgs = normalizeArgv(args);
270
+ assertKnownFlags(parsedArgs, ["--help", "-h"]);
271
+ const positionals = positionalArgs(parsedArgs);
272
+ const id = positionals[0];
273
+ if (positionals.length > 1) {
274
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing balance: ${positionals[1]}` });
275
+ }
244
276
  if (!id) {
245
277
  fail({
246
278
  code: "BAD_USAGE",
@@ -257,7 +289,14 @@ async function balance(args) {
257
289
  }
258
290
 
259
291
  async function history(args) {
260
- const id = args[0];
292
+ const parsedArgs = normalizeArgv(args);
293
+ const valueFlags = ["--limit"];
294
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
295
+ const positionals = positionalArgs(parsedArgs, valueFlags);
296
+ const id = positionals[0];
297
+ if (positionals.length > 1) {
298
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing history: ${positionals[1]}` });
299
+ }
261
300
  if (!id) {
262
301
  fail({
263
302
  code: "BAD_USAGE",
@@ -265,9 +304,11 @@ async function history(args) {
265
304
  hint: "run402 billing history <email-or-wallet> [--limit <n>]",
266
305
  });
267
306
  }
268
- const limit = parseFlag(args, "--limit") || "50";
307
+ const limit = parsedArgs.includes("--limit")
308
+ ? parseIntegerFlag("--limit", flagValue(parsedArgs, "--limit"), { min: 1 })
309
+ : 50;
269
310
  try {
270
- const data = await getSdk().billing.getHistory(id, Number(limit));
311
+ const data = await getSdk().billing.getHistory(id, limit);
271
312
  console.log(JSON.stringify(data, null, 2));
272
313
  } catch (err) {
273
314
  reportSdkError(err);
package/lib/blob.mjs CHANGED
@@ -438,8 +438,8 @@ async function put(projectId, argv) {
438
438
  const resolvedId = resolveProjectId(opts.project);
439
439
 
440
440
  if (opts.positional.length === 0) die("At least one file path is required");
441
- if (opts.immutable && opts.positional.length > 1 && opts.key && !opts.key.endsWith("/")) {
442
- die("--key with --immutable across multiple files requires a directory prefix (ending with /)");
441
+ if (opts.positional.length > 1 && opts.key && !opts.key.endsWith("/")) {
442
+ die("--key across multiple files requires a directory prefix (ending with /)");
443
443
  }
444
444
 
445
445
  const results = [];
package/lib/cdn.mjs CHANGED
@@ -16,6 +16,7 @@
16
16
  import { resolveProjectId } from "./config.mjs";
17
17
  import { getSdk } from "./sdk.mjs";
18
18
  import { reportSdkError, fail } from "./sdk-errors.mjs";
19
+ import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
19
20
 
20
21
  const HELP = `run402 cdn — CloudFront CDN diagnostics for public blob URLs
21
22
 
@@ -73,14 +74,19 @@ function die(msg, exit_code = 1) {
73
74
  }
74
75
 
75
76
  function parseArgs(args) {
76
- const opts = { positional: [] };
77
- for (let i = 0; i < args.length; i++) {
78
- const a = args[i];
79
- if (a === "--sha") opts.sha = args[++i];
80
- else if (a === "--timeout") opts.timeout = Number(args[++i]);
81
- else if (a === "--project") opts.project = args[++i];
82
- else if (a.startsWith("--")) die(`Unknown option: ${a}`);
83
- else opts.positional.push(a);
77
+ const normalized = normalizeArgv(args);
78
+ const valueFlags = ["--sha", "--timeout", "--project"];
79
+ assertKnownFlags(normalized, [...valueFlags, "--help", "-h"], valueFlags);
80
+ const opts = {
81
+ positional: positionalArgs(normalized, valueFlags),
82
+ sha: flagValue(normalized, "--sha"),
83
+ timeout: normalized.includes("--timeout")
84
+ ? parseIntegerFlag("--timeout", flagValue(normalized, "--timeout"), { min: 1 })
85
+ : undefined,
86
+ project: flagValue(normalized, "--project"),
87
+ };
88
+ if (opts.positional.length > 1) {
89
+ die(`Unexpected argument for cdn wait-fresh: ${opts.positional[1]}`);
84
90
  }
85
91
  return opts;
86
92
  }
@@ -92,6 +98,13 @@ async function waitFresh(projectId, argv) {
92
98
  if (opts.positional.length === 0) die("URL required");
93
99
  const url = opts.positional[0];
94
100
  if (!opts.sha) die("--sha is required");
101
+ if (!/^[a-fA-F0-9]{64}$/.test(opts.sha)) {
102
+ fail({
103
+ code: "BAD_FLAG",
104
+ message: "--sha must be a 64-character hex SHA-256 digest",
105
+ details: { flag: "--sha", value: opts.sha },
106
+ });
107
+ }
95
108
 
96
109
  const timeoutMs = (opts.timeout ?? 60) * 1000;
97
110
  try {