run402 4.7.0 → 4.9.0

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 CHANGED
@@ -79,15 +79,15 @@ Examples:
79
79
  run402 allowance fund
80
80
  run402 deploy apply --manifest app.json
81
81
  run402 apply --manifest app.json --rehearse --json
82
- run402 snapshots list prj_...
83
- run402 branches create prj_... --ttl-days 7 --json
84
- run402 cloud archives create prj_... --wait --output ./project.r402ar --json
82
+ run402 snapshots list --project prj_...
83
+ run402 branches create --project prj_... --ttl-days 7 --json
84
+ run402 cloud archives create --project prj_... --wait --output ./project.r402ar --json
85
85
  run402 core projects import ./project.r402ar --name imported-project --env-file ./required.env --json
86
86
  run402 jobs submit --file job.json
87
87
  run402 projects list
88
- run402 projects sql <project_id> "SELECT * FROM users LIMIT 5"
89
- run402 functions deploy <project_id> my-fn --file handler.ts
90
- run402 secrets set <project_id> API_KEY sk-1234
88
+ run402 projects sql "SELECT * FROM users LIMIT 5" --project <project_id>
89
+ run402 functions deploy my-fn --file handler.ts --project <project_id>
90
+ run402 secrets set API_KEY --value sk-1234 --project <project_id>
91
91
  run402 image generate "a startup mascot, pixel art" --output logo.png
92
92
 
93
93
  Getting started:
package/lib/admin.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getSdk } from "./sdk.mjs";
2
2
  import { reportSdkError, fail } from "./sdk-errors.mjs";
3
- import { assertKnownFlags, hasHelp, normalizeArgv } from "./argparse.mjs";
3
+ import { assertKnownFlags, hasHelp, normalizeArgv, resolveProjectSelector } from "./argparse.mjs";
4
4
 
5
5
  const HELP = `run402 admin — Platform-admin operations (v1.57+)
6
6
 
@@ -17,12 +17,12 @@ Subcommands:
17
17
  (\`reactivated: true\` in the response).
18
18
  Replaces the v1.56 per-project pin.
19
19
 
20
- archive <project_id> [--reason "..."] Moderate-archive a single project. Sets
20
+ archive [--project <id>] [--reason "..."] Moderate-archive a single project. Sets
21
21
  projects.archived_at = NOW(). Independent
22
22
  of organization lifecycle; the rest of the
23
23
  organization's projects keep serving.
24
24
 
25
- reactivate <project_id> Un-archive a project (flips archived_at
25
+ reactivate [--project <id>] Un-archive a project (flips archived_at
26
26
  back to NULL). In v1.57 this no longer
27
27
  touches organization-level lifecycle — to
28
28
  reactivate a grace-state account, use
@@ -65,6 +65,9 @@ Examples:
65
65
  archive: `run402 admin archive — Moderate-archive a single project
66
66
 
67
67
  Usage:
68
+ run402 admin archive [--project <id>] [--reason "..."]
69
+
70
+ Legacy (still supported):
68
71
  run402 admin archive <project_id> [--reason "..."]
69
72
 
70
73
  Options:
@@ -84,6 +87,9 @@ Examples:
84
87
  reactivate: `run402 admin reactivate — Un-archive a project
85
88
 
86
89
  Usage:
90
+ run402 admin reactivate [--project <id>]
91
+
92
+ Legacy (still supported):
87
93
  run402 admin reactivate <project_id>
88
94
 
89
95
  Notes:
@@ -100,8 +106,8 @@ Examples:
100
106
 
101
107
  const FLAGS_BY_SUB = {
102
108
  "lease-perpetual": { known: ["--enable", "--disable"], values: [] },
103
- archive: { known: ["--reason"], values: ["--reason"] },
104
- reactivate: { known: [], values: [] },
109
+ archive: { known: ["--project", "--reason"], values: ["--project", "--reason"] },
110
+ reactivate: { known: ["--project"], values: ["--project"] },
105
111
  };
106
112
 
107
113
  function validateFlags(sub, args) {
@@ -137,18 +143,12 @@ async function leasePerpetual(args) {
137
143
  }
138
144
 
139
145
  async function archive(args) {
140
- const projectId = args.find((a) => typeof a === "string" && !a.startsWith("--"));
141
- if (!projectId) {
142
- fail({
143
- code: "BAD_USAGE",
144
- message: "Missing <project_id>.",
145
- hint: "run402 admin archive <project_id> [--reason \"...\"]",
146
- });
147
- }
146
+ // Canonical: --project <id>; legacy leading prj_... positional kept.
147
+ const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAGS_BY_SUB.archive.values });
148
148
  let reason;
149
- for (let i = 0; i < args.length; i++) {
150
- if (args[i] === "--reason" && args[i + 1] !== undefined) {
151
- reason = args[++i];
149
+ for (let i = 0; i < rest.length; i++) {
150
+ if (rest[i] === "--reason" && rest[i + 1] !== undefined) {
151
+ reason = rest[++i];
152
152
  }
153
153
  }
154
154
  try {
@@ -160,14 +160,7 @@ async function archive(args) {
160
160
  }
161
161
 
162
162
  async function reactivate(args) {
163
- const projectId = args.find((a) => typeof a === "string" && !a.startsWith("--"));
164
- if (!projectId) {
165
- fail({
166
- code: "BAD_USAGE",
167
- message: "Missing <project_id>.",
168
- hint: "run402 admin reactivate <project_id>",
169
- });
170
- }
163
+ const { projectId } = resolveProjectSelector(args, { valueFlags: FLAGS_BY_SUB.reactivate.values });
171
164
  try {
172
165
  const data = await getSdk().admin.reactivateProject(projectId);
173
166
  console.log(JSON.stringify(data, null, 2));
package/lib/apps.mjs CHANGED
@@ -1,7 +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
+ import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs, resolveProjectSelector } from "./argparse.mjs";
5
5
 
6
6
  const HELP = `run402 apps — Browse and manage the app marketplace
7
7
 
@@ -10,15 +10,20 @@ Usage:
10
10
 
11
11
  Subcommands:
12
12
  browse [--tag <tag>] Browse public apps
13
- fork <version_id> <name> [--subdomain <name>]
13
+ fork <version_id> --name <name> [--subdomain <name>]
14
14
  Fork a published app into your own project
15
- publish <id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
15
+ publish [--project <id>] [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
16
16
  Publish a project as an app
17
- versions <id> List published versions of a project
17
+ versions [--project <id>] List published versions of a project
18
18
  inspect <version_id> Inspect a published app version
19
- update <project_id> <version_id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed] [--no-fork]
19
+ update <version_id> [--project <id>] [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed] [--no-fork]
20
20
  Update a published version
21
- delete <project_id> <version_id> Delete a published version
21
+ delete <version_id> [--project <id>] Delete a published version
22
+
23
+ Legacy (still supported): the old positional forms keep working, e.g.
24
+ run402 apps fork <version_id> <name>
25
+ run402 apps publish <project_id> / versions <project_id>
26
+ run402 apps update <project_id> <version_id> / delete <project_id> <version_id>
22
27
 
23
28
  Examples:
24
29
  run402 apps browse
@@ -48,13 +53,16 @@ Examples:
48
53
  fork: `run402 apps fork — Fork a published app into your own project
49
54
 
50
55
  Usage:
56
+ run402 apps fork <version_id> --name <name> [options]
57
+
58
+ Legacy (still supported):
51
59
  run402 apps fork <version_id> <name> [options]
52
60
 
53
61
  Arguments:
54
62
  <version_id> Published version ID (e.g. ver_abc123)
55
- <name> Name for the forked project
56
63
 
57
64
  Options:
65
+ --name <name> Name for the forked project (alternative to the legacy positional)
58
66
  --subdomain <name> Claim a subdomain for the forked project
59
67
 
60
68
  Examples:
@@ -64,10 +72,13 @@ Examples:
64
72
  publish: `run402 apps publish — Publish a project as an app
65
73
 
66
74
  Usage:
67
- run402 apps publish <id> [options]
75
+ run402 apps publish [--project <id>] [options]
68
76
 
69
- Arguments:
70
- <id> Project ID to publish
77
+ Legacy (still supported):
78
+ run402 apps publish <project_id> [options]
79
+
80
+ Options (project):
81
+ --project <id> Project ID to publish (defaults to the active project)
71
82
 
72
83
  Options:
73
84
  --description <d> Human-readable description of the app
@@ -82,10 +93,12 @@ Examples:
82
93
  update: `run402 apps update — Update a published version's metadata
83
94
 
84
95
  Usage:
96
+ run402 apps update <version_id> [--project <id>] [options]
97
+
98
+ Legacy (still supported):
85
99
  run402 apps update <project_id> <version_id> [options]
86
100
 
87
101
  Arguments:
88
- <project_id> Project ID that owns the version
89
102
  <version_id> Published version ID to update
90
103
 
91
104
  Options:
@@ -114,10 +127,10 @@ Examples:
114
127
  versions: `run402 apps versions — List published versions of a project
115
128
 
116
129
  Usage:
117
- run402 apps versions <id>
130
+ run402 apps versions [--project <id>]
118
131
 
119
- Arguments:
120
- <id> Project ID (e.g. prj_abc123)
132
+ Legacy (still supported):
133
+ run402 apps versions <project_id>
121
134
 
122
135
  Examples:
123
136
  run402 apps versions prj_abc123
@@ -125,10 +138,12 @@ Examples:
125
138
  delete: `run402 apps delete — Delete a published version
126
139
 
127
140
  Usage:
141
+ run402 apps delete <version_id> [--project <id>]
142
+
143
+ Legacy (still supported):
128
144
  run402 apps delete <project_id> <version_id>
129
145
 
130
146
  Arguments:
131
- <project_id> Project ID that owns the version
132
147
  <version_id> Published version ID to delete
133
148
 
134
149
  Examples:
@@ -158,14 +173,16 @@ async function browse(args) {
158
173
 
159
174
  async function fork(versionId, name, args) {
160
175
  const parsedArgs = normalizeArgv([versionId, name, ...args].filter((arg) => arg !== undefined));
161
- const valueFlags = ["--subdomain"];
176
+ const valueFlags = ["--subdomain", "--name"];
162
177
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
178
+ const nameFlag = flagValue(parsedArgs, "--name");
179
+ const expected = nameFlag ? 1 : 2;
163
180
  const positionals = positionalArgs(parsedArgs, valueFlags);
164
- if (positionals.length < 2) {
165
- fail({ code: "BAD_USAGE", message: "Missing <version_id> and/or <name>." });
181
+ if (positionals.length < expected) {
182
+ fail({ code: "BAD_USAGE", message: nameFlag ? "Missing <version_id>." : "Missing <version_id> and/or <name>." });
166
183
  }
167
- if (positionals.length > 2) {
168
- fail({ code: "BAD_USAGE", message: `Unexpected argument for apps fork: ${positionals[2]}` });
184
+ if (positionals.length > expected) {
185
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps fork: ${positionals[expected]}` });
169
186
  }
170
187
  const opts = { subdomain: flagValue(parsedArgs, "--subdomain") ?? undefined };
171
188
  // Preserve the aggressive early exit when no allowance is configured.
@@ -174,7 +191,7 @@ async function fork(versionId, name, args) {
174
191
  try {
175
192
  const data = await getSdk().apps.fork({
176
193
  versionId: positionals[0],
177
- name: positionals[1],
194
+ name: nameFlag ?? positionals[1],
178
195
  subdomain: opts.subdomain,
179
196
  });
180
197
 
@@ -195,14 +212,12 @@ async function fork(versionId, name, args) {
195
212
 
196
213
  async function publish(projectId, args) {
197
214
  const parsedArgs = normalizeArgv([projectId, ...args].filter((arg) => arg !== undefined));
198
- const valueFlags = ["--description", "--tags", "--visibility"];
215
+ const valueFlags = ["--project", "--description", "--tags", "--visibility"];
199
216
  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>." });
203
- }
204
- if (positionals.length > 1) {
205
- fail({ code: "BAD_USAGE", message: `Unexpected argument for apps publish: ${positionals[1]}` });
217
+ const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags });
218
+ const positionals = positionalArgs(rest, valueFlags);
219
+ if (positionals.length > 0) {
220
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps publish: ${positionals[0]}` });
206
221
  }
207
222
  const opts = { description: undefined, tags: undefined, visibility: undefined, forkAllowed: undefined };
208
223
  opts.description = flagValue(parsedArgs, "--description") ?? undefined;
@@ -211,7 +226,7 @@ async function publish(projectId, args) {
211
226
  if (opts.visibility) assertAllowedValue(opts.visibility, ["public", "unlisted", "private"], "--visibility");
212
227
  if (parsedArgs.includes("--fork-allowed")) opts.forkAllowed = true;
213
228
  try {
214
- const data = await getSdk().apps.publish(positionals[0], {
229
+ const data = await getSdk().apps.publish(resolvedProject, {
215
230
  description: opts.description,
216
231
  tags: opts.tags,
217
232
  visibility: opts.visibility,
@@ -225,13 +240,14 @@ async function publish(projectId, args) {
225
240
 
226
241
  async function versions(projectId, args = []) {
227
242
  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]}` });
243
+ assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
244
+ const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags: ["--project"] });
245
+ const positionals = positionalArgs(rest, ["--project"]);
246
+ if (positionals.length > 0) {
247
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps versions: ${positionals[0]}` });
232
248
  }
233
249
  try {
234
- const data = await getSdk().apps.listVersions(positionals[0]);
250
+ const data = await getSdk().apps.listVersions(resolvedProject);
235
251
  console.log(JSON.stringify(data, null, 2));
236
252
  } catch (err) {
237
253
  reportSdkError(err);
@@ -255,14 +271,15 @@ async function inspect(versionId, args = []) {
255
271
 
256
272
  async function update(projectId, versionId, args) {
257
273
  const parsedArgs = normalizeArgv([projectId, versionId, ...args].filter((arg) => arg !== undefined));
258
- const valueFlags = ["--description", "--tags", "--visibility"];
274
+ const valueFlags = ["--project", "--description", "--tags", "--visibility"];
259
275
  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>." });
276
+ const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags });
277
+ const positionals = positionalArgs(rest, valueFlags);
278
+ if (positionals.length < 1) {
279
+ fail({ code: "BAD_USAGE", message: "Missing <version_id>." });
263
280
  }
264
- if (positionals.length > 2) {
265
- fail({ code: "BAD_USAGE", message: `Unexpected argument for apps update: ${positionals[2]}` });
281
+ if (positionals.length > 1) {
282
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps update: ${positionals[1]}` });
266
283
  }
267
284
  if (parsedArgs.includes("--fork-allowed") && parsedArgs.includes("--no-fork")) {
268
285
  fail({ code: "BAD_USAGE", message: "Provide either --fork-allowed or --no-fork, not both." });
@@ -275,8 +292,8 @@ async function update(projectId, versionId, args) {
275
292
  if (parsedArgs.includes("--fork-allowed")) opts.fork_allowed = true;
276
293
  if (parsedArgs.includes("--no-fork")) opts.fork_allowed = false;
277
294
  try {
278
- await getSdk().apps.updateVersion(positionals[0], positionals[1], opts);
279
- console.log(JSON.stringify({ project_id: positionals[0], version_id: positionals[1], updated: true }));
295
+ await getSdk().apps.updateVersion(resolvedProject, positionals[0], opts);
296
+ console.log(JSON.stringify({ project_id: resolvedProject, version_id: positionals[0], updated: true }));
280
297
  } catch (err) {
281
298
  reportSdkError(err);
282
299
  }
@@ -284,17 +301,18 @@ async function update(projectId, versionId, args) {
284
301
 
285
302
  async function deleteVersion(projectId, versionId, args = []) {
286
303
  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>." });
304
+ assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
305
+ const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags: ["--project"] });
306
+ const positionals = positionalArgs(rest, ["--project"]);
307
+ if (positionals.length < 1) {
308
+ fail({ code: "BAD_USAGE", message: "Missing <version_id>." });
291
309
  }
292
- if (positionals.length > 2) {
293
- fail({ code: "BAD_USAGE", message: `Unexpected argument for apps delete: ${positionals[2]}` });
310
+ if (positionals.length > 1) {
311
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for apps delete: ${positionals[1]}` });
294
312
  }
295
313
  try {
296
- await getSdk().apps.deleteVersion(positionals[0], positionals[1]);
297
- console.log(JSON.stringify({ project_id: positionals[0], version_id: positionals[1], deleted: true }));
314
+ await getSdk().apps.deleteVersion(resolvedProject, positionals[0]);
315
+ console.log(JSON.stringify({ project_id: resolvedProject, version_id: positionals[0], deleted: true }));
298
316
  } catch (err) {
299
317
  reportSdkError(err);
300
318
  }
package/lib/argparse.mjs CHANGED
@@ -19,8 +19,14 @@ export function hasHelp(args = []) {
19
19
  return args.includes("--help") || args.includes("-h");
20
20
  }
21
21
 
22
+ // CLI-wide convention: every command accepts `--json`. Where stdout is already
23
+ // JSON (the vast majority of commands) it is a no-op; commands with a human
24
+ // default switch on it explicitly. Baking it into the baseline known set here
25
+ // makes the convention self-maintaining for future commands.
26
+ const ALWAYS_KNOWN_FLAGS = ["--json"];
27
+
22
28
  export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
23
- const known = new Set(knownFlags);
29
+ const known = new Set([...knownFlags, ...ALWAYS_KNOWN_FLAGS]);
24
30
  const valueFlags = new Set(flagsWithValues);
25
31
  for (let i = 0; i < args.length; i++) {
26
32
  const arg = args[i];
@@ -301,6 +307,67 @@ export function resolvePositionalProject(args, opts = {}) {
301
307
  return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
302
308
  }
303
309
 
310
+ // Resolve the project id for a project-scoped command from BOTH accepted
311
+ // forms — the canonical `--project <id>` flag and the legacy leading
312
+ // positional `prj_...` — with active-project fallback (CLI-wide convention).
313
+ //
314
+ // Precedence:
315
+ // 1. explicit `--project <id>` (canonical)
316
+ // 2. a leading positional starting with "prj_" (legacy compat, no warning)
317
+ // 3. the active project (`resolveProjectId(null)`)
318
+ //
319
+ // If BOTH a `--project` flag and a leading `prj_` positional are present and
320
+ // they DIFFER, fail with BAD_USAGE ("Conflicting project ids"). If they agree,
321
+ // the duplicate positional is consumed silently.
322
+ //
323
+ // Returns { projectId, rest } where `rest` is args with the project selector
324
+ // tokens (the flag+value pair and/or the leading positional) removed.
325
+ //
326
+ // Options (in addition to every `resolvePositionalProject` option, which this
327
+ // helper delegates to for the no-flag path):
328
+ // requireRestPositional: only treat a leading `prj_` positional as the
329
+ // project selector when at least one more bare positional follows (counted
330
+ // with opts.valueFlags). Needed by commands whose OWN attribute is itself a
331
+ // project id (e.g. `branches renew <branch-project-id>`).
332
+ export function resolveProjectSelector(args, opts = {}) {
333
+ const list = Array.isArray(args) ? [...args] : [];
334
+ let flagProject = null;
335
+ const flagIdx = list.indexOf("--project");
336
+ if (flagIdx !== -1) {
337
+ flagProject = flagValue(list, "--project");
338
+ list.splice(flagIdx, 2);
339
+ }
340
+
341
+ const first = list[0];
342
+ let leadingIsProject = typeof first === "string" && first.startsWith("prj_");
343
+ if (leadingIsProject && opts.requireRestPositional) {
344
+ const remainder = positionalArgs(list.slice(1), opts.valueFlags ?? []);
345
+ if (remainder.length === 0) leadingIsProject = false;
346
+ }
347
+
348
+ if (flagProject !== null) {
349
+ if (leadingIsProject) {
350
+ if (first !== flagProject) {
351
+ fail({
352
+ code: "BAD_USAGE",
353
+ message: `Conflicting project ids: --project ${flagProject} vs positional ${first}`,
354
+ hint: "Pass the project once — prefer --project <id>.",
355
+ details: { project_flag: flagProject, positional: first },
356
+ });
357
+ }
358
+ list.shift();
359
+ }
360
+ return { projectId: flagProject, rest: list };
361
+ }
362
+
363
+ if (!leadingIsProject && typeof first === "string" && first.startsWith("prj_")) {
364
+ // A lone prj_ positional deliberately NOT consumed as the selector
365
+ // (requireRestPositional): it is the command's own attribute.
366
+ return { projectId: resolveProjectId(null), rest: list };
367
+ }
368
+ return resolvePositionalProject(list, opts);
369
+ }
370
+
304
371
  function closestFlag(flag, candidates) {
305
372
  let best = null;
306
373
  let bestDistance = Number.POSITIVE_INFINITY;
package/lib/assets.mjs CHANGED
@@ -54,7 +54,7 @@ Options:
54
54
  --exif-policy keep|strip v1.50: EXIF retention policy for image uploads (default keep).
55
55
  --stream NDJSON per-file progress events (for agent consumption).
56
56
  Without --stream, only the final results array is
57
- printed. --json is a deprecated alias for --stream.
57
+ printed. --json is an accepted alias for --stream.
58
58
  --prefix <p> Prefix filter (ls only)
59
59
  --limit <n> Max results (ls only; default 100, max 1000)
60
60
  --sort <key> v1.50 ls only: key:asc | createdAt:asc | createdAt:desc (default key:asc).
@@ -103,7 +103,7 @@ Options:
103
103
  from the stored bytes and the image_exif response field.
104
104
  --stream Emit NDJSON per-file progress events on stdout (for
105
105
  agent consumption). Default: emit only the final
106
- results array (also JSON). --json is a deprecated
106
+ results array (also JSON). --json is an accepted
107
107
  alias for --stream.
108
108
 
109
109
  Examples:
@@ -256,12 +256,10 @@ function parseArgs(rawArgs) {
256
256
  else if (a === "--concurrency") out.concurrency = parseIntegerFlag("--concurrency", args[++i], { min: 1 });
257
257
  else if (a === "--no-resume") out.resume = false;
258
258
  else if (a === "--stream") out.stream = true;
259
- else if (a === "--json") {
260
- out.stream = true;
261
- process.stderr.write(
262
- "# warning: `--json` on `assets put` is deprecated and will be removed in a future release. Use `--stream` (alias means the same thing — NDJSON progress events on stdout).\n",
263
- );
264
- }
259
+ // `--json` and `--stream` are permanent aliases on `assets put` (both mean
260
+ // NDJSON progress events on stdout); elsewhere `--json` is a no-op since
261
+ // stdout is already JSON.
262
+ else if (a === "--json") { out.stream = true; }
265
263
  else if (a === "--prefix") out.prefix = args[++i];
266
264
  else if (a === "--limit") out.limit = parseIntegerFlag("--limit", args[++i], { min: 1, max: 1000 });
267
265
  else if (a === "--output" || a === "-o") out.output = args[++i];
package/lib/billing.mjs CHANGED
@@ -232,18 +232,20 @@ async function createEmail(args) {
232
232
 
233
233
  async function linkWallet(args) {
234
234
  const parsedArgs = normalizeArgv(args);
235
- assertKnownFlags(parsedArgs, ["--help", "-h"]);
236
- const positionals = positionalArgs(parsedArgs);
235
+ assertKnownFlags(parsedArgs, ["--wallet", "--help", "-h"], ["--wallet"]);
236
+ const walletFlag = flagValue(parsedArgs, "--wallet");
237
+ const positionals = positionalArgs(parsedArgs, ["--wallet"]);
237
238
  const organizationId = positionals[0];
238
- const wallet = positionals[1];
239
- if (positionals.length > 2) {
240
- fail({ code: "BAD_USAGE", message: `Unexpected argument for billing link-wallet: ${positionals[2]}` });
239
+ const wallet = walletFlag ?? positionals[1];
240
+ const max = walletFlag ? 1 : 2;
241
+ if (positionals.length > max) {
242
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing link-wallet: ${positionals[max]}` });
241
243
  }
242
244
  if (!organizationId || !wallet) {
243
245
  fail({
244
246
  code: "BAD_USAGE",
245
247
  message: "Missing <org_id> and/or <wallet_address>.",
246
- hint: "run402 billing link-wallet <org_id> <wallet_address>",
248
+ hint: "run402 billing link-wallet <org_id> --wallet <wallet_address>",
247
249
  });
248
250
  }
249
251
  try {
@@ -262,19 +264,21 @@ async function linkWallet(args) {
262
264
 
263
265
  async function autoRecharge(args) {
264
266
  const parsedArgs = normalizeArgv(args);
265
- const valueFlags = ["--threshold"];
267
+ const valueFlags = ["--threshold", "--state"];
266
268
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
269
+ const stateFlag = flagValue(parsedArgs, "--state");
267
270
  const positionals = positionalArgs(parsedArgs, valueFlags);
268
271
  const organizationId = positionals[0];
269
- const state = positionals[1];
270
- if (positionals.length > 2) {
271
- fail({ code: "BAD_USAGE", message: `Unexpected argument for billing auto-recharge: ${positionals[2]}` });
272
+ const state = stateFlag ?? positionals[1];
273
+ const max = stateFlag ? 1 : 2;
274
+ if (positionals.length > max) {
275
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for billing auto-recharge: ${positionals[max]}` });
272
276
  }
273
277
  if (!organizationId || !state || !["on", "off"].includes(state)) {
274
278
  fail({
275
279
  code: "BAD_USAGE",
276
280
  message: "Missing <org_id> and/or <on|off>.",
277
- hint: "run402 billing auto-recharge <org_id> <on|off> [--threshold <n>]",
281
+ hint: "run402 billing auto-recharge <org_id> --state <on|off> [--threshold <n>]",
278
282
  });
279
283
  }
280
284
  const thresholdStr = flagValue(parsedArgs, "--threshold");
package/lib/branches.mjs CHANGED
@@ -8,19 +8,23 @@ import {
8
8
  normalizeArgv,
9
9
  parseIntegerFlag,
10
10
  positionalArgs,
11
+ resolveProjectSelector,
11
12
  } from "./argparse.mjs";
12
- import { resolveProjectId } from "./config.mjs";
13
13
 
14
14
  const HELP = `run402 branches — Contained project data branches
15
15
 
16
16
  Usage:
17
- run402 branches create [project-id] [--from-snapshot <snapshot-id>] [--name <label>] [--email-mode sandbox|off] [--enable-cron] [--ttl-days <n>] [--json]
18
- run402 branches list [project-id] [--json]
19
- run402 branches renew [project-id] <branch-project-id> [--ttl-days <n>] [--json]
20
- run402 branches delete [project-id] <branch-project-id> [--json]
17
+ run402 branches create [--project <id>] [--from-snapshot <snapshot-id>] [--name <label>] [--email-mode sandbox|off] [--enable-cron] [--ttl-days <n>] [--json]
18
+ run402 branches list [--project <id>] [--json]
19
+ run402 branches renew <branch-project-id> [--project <id>] [--ttl-days <n>] [--json]
20
+ run402 branches delete <branch-project-id> [--project <id>] [--json]
21
+
22
+ Legacy (still supported): a leading prj_... parent-project positional,
23
+ e.g. run402 branches renew prj_parent prj_branch. --project defaults to the
24
+ active project.
21
25
  `;
22
26
 
23
- const FLAG_VALUES = ["--from-snapshot", "--name", "--email-mode", "--ttl-days"];
27
+ const FLAG_VALUES = ["--project", "--from-snapshot", "--name", "--email-mode", "--ttl-days"];
24
28
  const FLAGS = new Set([...FLAG_VALUES, "--enable-cron", "--json", "--help", "-h"]);
25
29
 
26
30
  export async function run(sub, args = []) {
@@ -47,7 +51,7 @@ export async function run(sub, args = []) {
47
51
  }
48
52
 
49
53
  async function create(args) {
50
- const projectId = resolveOptionalProject(positionalArgs(args, FLAG_VALUES)[0]);
54
+ const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
51
55
  const emailMode = flagValue(args, "--email-mode") ?? undefined;
52
56
  if (emailMode !== undefined) assertAllowedValue(emailMode, ["sandbox", "off"], "--email-mode");
53
57
  const ttlDaysFlag = flagValue(args, "--ttl-days");
@@ -67,7 +71,7 @@ async function create(args) {
67
71
  }
68
72
 
69
73
  async function list(args) {
70
- const projectId = resolveOptionalProject(positionalArgs(args, FLAG_VALUES)[0]);
74
+ const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
71
75
  try {
72
76
  const result = await getSdk().branches.list(projectId);
73
77
  console.log(JSON.stringify({ project_id: projectId, ...result }, null, 2));
@@ -100,14 +104,13 @@ async function deleteBranch(args) {
100
104
  }
101
105
  }
102
106
 
103
- function resolveOptionalProject(value) {
104
- if (value && String(value).startsWith("prj_")) return value;
105
- return resolveProjectId(null);
106
- }
107
-
108
107
  function resolveProjectAndBranch(args, usage) {
109
- const pos = positionalArgs(args, FLAG_VALUES);
110
- if (pos.length === 1) return { projectId: resolveProjectId(null), branchProjectId: pos[0] };
111
- if (pos.length === 2 && pos[0].startsWith("prj_")) return { projectId: pos[0], branchProjectId: pos[1] };
112
- fail({ code: "BAD_USAGE", message: `Usage: ${usage}` });
108
+ // Canonical: `<branch-project-id> [--project <parent-id>]`. Branch ids are
109
+ // themselves prj_..., so a leading prj_ positional is only treated as the
110
+ // PARENT project when a second positional follows (requireRestPositional)
111
+ // the legacy `renew prj_parent prj_branch` form.
112
+ const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES, requireRestPositional: true });
113
+ const pos = positionalArgs(rest, FLAG_VALUES);
114
+ if (pos.length !== 1) fail({ code: "BAD_USAGE", message: `Usage: ${usage}` });
115
+ return { projectId, branchProjectId: pos[0] };
113
116
  }
package/lib/ci.mjs CHANGED
@@ -96,6 +96,8 @@ function parseFlags(args, allowed, { repeatable = new Set() } = {}) {
96
96
  positional.push(arg);
97
97
  continue;
98
98
  }
99
+ // CLI-wide convention: --json is accepted everywhere (stdout is already JSON).
100
+ if (arg === "--json") continue;
99
101
  if (!allowed.has(arg)) {
100
102
  fail({
101
103
  code: "BAD_USAGE",