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/lib/cloud.mjs CHANGED
@@ -3,14 +3,18 @@ import { dirname, resolve } from "node:path";
3
3
 
4
4
  import { getSdk } from "./sdk.mjs";
5
5
  import { fail, reportSdkError } from "./sdk-errors.mjs";
6
- import { assertAllowedValue, assertKnownFlags, flagValue, hasHelp, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
6
+ import { assertAllowedValue, assertKnownFlags, flagValue, hasHelp, normalizeArgv, parseIntegerFlag, positionalArgs, resolveProjectSelector } from "./argparse.mjs";
7
7
 
8
8
  const HELP = `run402 cloud — Run402 Cloud portability commands
9
9
 
10
10
  Usage:
11
- run402 cloud archives create <project-id> [options]
12
- run402 cloud archives download <project-id> <archive-id> --output <file> [--json]
13
- run402 cloud archives status <project-id> <archive-id> [--json]
11
+ run402 cloud archives create [--project <id>] [options]
12
+ run402 cloud archives download <archive-id> --output <file> [--project <id>] [--json]
13
+ run402 cloud archives status <archive-id> [--project <id>] [--json]
14
+
15
+ Legacy (still supported): a leading prj_... positional selects the project,
16
+ e.g. run402 cloud archives download <project-id> <archive-id> --output <file>.
17
+ --project defaults to the active project.
14
18
 
15
19
  Canonical agent path:
16
20
  run402 cloud archives create prj_... \\
@@ -31,6 +35,7 @@ Options for create:
31
35
  `;
32
36
 
33
37
  const FLAG_VALUES = [
38
+ "--project",
34
39
  "--scope",
35
40
  "--auth",
36
41
  "--consistency",
@@ -80,10 +85,10 @@ export async function run(sub, args = []) {
80
85
  async function create(rawArgs) {
81
86
  const args = normalizeArgv(rawArgs);
82
87
  assertKnownFlags(args, [...FLAGS], FLAG_VALUES);
83
- const pos = positionalArgs(args, FLAG_VALUES);
84
- const projectId = pos[0];
85
- if (!projectId) {
86
- fail({ code: "BAD_PROJECT_ID", message: "Missing project id." });
88
+ const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
89
+ const extraPos = positionalArgs(rest, FLAG_VALUES);
90
+ if (extraPos.length > 0) {
91
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for cloud archives create: ${extraPos[0]}` });
87
92
  }
88
93
  const scope = flagValue(args, "--scope") ?? "portable-runtime-v1";
89
94
  const auth = flagValue(args, "--auth") ?? "stubs";
@@ -170,12 +175,13 @@ async function create(rawArgs) {
170
175
 
171
176
  async function download(rawArgs) {
172
177
  const args = normalizeArgv(rawArgs);
173
- assertKnownFlags(args, ["--output", "--json", "--help", "-h"], ["--output"]);
174
- const pos = positionalArgs(args, ["--output"]);
175
- const [projectId, archiveId] = pos;
178
+ assertKnownFlags(args, ["--project", "--output", "--json", "--help", "-h"], ["--project", "--output"]);
179
+ const { projectId, rest } = resolveProjectSelector(args, { valueFlags: ["--project", "--output"] });
180
+ const pos = positionalArgs(rest, ["--project", "--output"]);
181
+ const [archiveId] = pos;
176
182
  const output = flagValue(args, "--output");
177
- if (!projectId || !archiveId || !output) {
178
- fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives download <project-id> <archive-id> --output <file> [--json]" });
183
+ if (!archiveId || pos.length > 1 || !output) {
184
+ fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives download <archive-id> --output <file> [--project <id>] [--json]" });
179
185
  }
180
186
  try {
181
187
  const download = await getSdk().archives.download(projectId, archiveId);
@@ -200,11 +206,12 @@ async function download(rawArgs) {
200
206
 
201
207
  async function status(rawArgs) {
202
208
  const args = normalizeArgv(rawArgs);
203
- assertKnownFlags(args, ["--json", "--help", "-h"], []);
204
- const pos = positionalArgs(args, []);
205
- const [projectId, archiveId] = pos;
206
- if (!projectId || !archiveId) {
207
- fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives status <project-id> <archive-id> [--json]" });
209
+ assertKnownFlags(args, ["--project", "--json", "--help", "-h"], ["--project"]);
210
+ const { projectId, rest } = resolveProjectSelector(args, { valueFlags: ["--project"] });
211
+ const pos = positionalArgs(rest, ["--project"]);
212
+ const [archiveId] = pos;
213
+ if (!archiveId || pos.length > 1) {
214
+ fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives status <archive-id> [--project <id>] [--json]" });
208
215
  }
209
216
  try {
210
217
  const archive = await getSdk().archives.get(projectId, archiveId);
@@ -0,0 +1,352 @@
1
+ /**
2
+ * COMMAND_MANIFEST — data-only registry of every user-facing CLI command.
3
+ *
4
+ * The cli-conventions-gate test derives EVERYTHING from this file:
5
+ * - structural invariants (≤1 positional attribute per canonical form,
6
+ * legacyPositionalProject ⇒ projectScoped, ...);
7
+ * - behavioral `--project` acceptance for every projectScoped entry;
8
+ * - behavioral `--json` acceptance for every entry;
9
+ * - completeness against cli.mjs's dispatch switch.
10
+ *
11
+ * Entry shape:
12
+ * path argv words, e.g. ["secrets", "set"]
13
+ * positionals canonical positional ATTRIBUTES (post-conventions).
14
+ * A variadic list of the SAME kind counts as one.
15
+ * projectScoped command operates on a project and accepts
16
+ * `--project <id>` (precedence: --project > legacy
17
+ * leading prj_ positional > active project)
18
+ * legacyPositionalProject still accepts the legacy leading `prj_...`
19
+ * positional project selector (compat, no warning)
20
+ * minimalArgs args (beyond path) that satisfy LOCAL validation
21
+ * for the gate's behavioral flag-acceptance runs.
22
+ * Placeholders the gate substitutes at runtime:
23
+ * __FIXTURE_FILE__ an existing regular file
24
+ * __OUT_FILE__ a writable output file path
25
+ * __SCRATCH_DIR__ an existing scratch directory
26
+ * runStyle how the gate invokes the module (default "sub"):
27
+ * "sub" run(path[1], [...path.slice(2), ...argv])
28
+ * "flat" run([...path.slice(1), ...argv])
29
+ * "merged" run(argv[0], argv.slice(1))
30
+ * "deployV2" runDeployV2(path[1], [...path.slice(2), ...argv])
31
+ * skipBehavioral optional string reason — the command is interactive,
32
+ * long-running, or has cwd side effects that make an
33
+ * in-process behavioral run unsafe. Structural
34
+ * invariants still apply.
35
+ *
36
+ * Keep it honest: if a command's canonical form cannot satisfy the ≤1
37
+ * positional rule, fix the command (add flag alternatives), don't fudge the
38
+ * manifest.
39
+ */
40
+
41
+ const p = (name, { required = true, variadic = false } = {}) => ({ name, required, variadic });
42
+
43
+ export const COMMAND_MANIFEST = [
44
+ // ── up / init / status (flat runners) ────────────────────────────────────
45
+ { path: ["up"], positionals: [p("source", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--check", "-y"], runStyle: "flat", skipBehavioral: "orchestrates a full provision/build/deploy against the real cwd" },
46
+ { path: ["up", "verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "flat", skipBehavioral: "polls live edge coherence" },
47
+ { path: ["init"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], runStyle: "flat", skipBehavioral: "creates a wallet and polls funding" },
48
+ { path: ["status"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], runStyle: "flat" },
49
+
50
+ // ── wallets ──────────────────────────────────────────────────────────────
51
+ { path: ["wallets", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
52
+ { path: ["wallets", "current"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
53
+ { path: ["wallets", "new"], positionals: [p("name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["gate-test-wallet"] },
54
+ { path: ["wallets", "use"], positionals: [p("name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["default"] },
55
+ { path: ["wallets", "rename"], positionals: [p("old_name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["no-such-wallet", "--to", "still-no-such"] },
56
+ { path: ["wallets", "bind"], positionals: [p("name", { required: false })], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
57
+ { path: ["wallets", "unbind"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
58
+ { path: ["wallets", "import"], positionals: [p("name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["gate-import-wallet", "--key", "__FIXTURE_FILE__"] },
59
+ { path: ["wallets", "rm"], positionals: [p("name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["no-such-wallet", "--yes"] },
60
+
61
+ // ── credentials (project-keys group) ─────────────────────────────────────
62
+ { path: ["credentials", "project-keys", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
63
+ { path: ["credentials", "project-keys", "status"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
64
+ { path: ["credentials", "project-keys", "import"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--service-key-env", "RUN402_GATE_FAKE_SERVICE_KEY"] },
65
+ { path: ["credentials", "project-keys", "export"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--reveal"] },
66
+ { path: ["credentials", "project-keys", "remove"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
67
+
68
+ // ── allowance ────────────────────────────────────────────────────────────
69
+ { path: ["allowance", "status"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
70
+ { path: ["allowance", "create"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], skipBehavioral: "creates a real local wallet key outside the gate's scratch profile" },
71
+ { path: ["allowance", "fund"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], skipBehavioral: "polls on-chain funding with sleeps" },
72
+ { path: ["allowance", "balance"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
73
+ { path: ["allowance", "export"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
74
+ { path: ["allowance", "checkout"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--amount", "5000000"] },
75
+ { path: ["allowance", "history"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
76
+
77
+ // ── tier ─────────────────────────────────────────────────────────────────
78
+ { path: ["tier", "status"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
79
+ { path: ["tier", "set"], positionals: [p("tier")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["prototype"] },
80
+
81
+ // ── projects ─────────────────────────────────────────────────────────────
82
+ { path: ["projects", "quote"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
83
+ { path: ["projects", "provision"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
84
+ { path: ["projects", "use"], positionals: [p("project_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["prj_test123"] },
85
+ { path: ["projects", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
86
+ { path: ["projects", "current"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
87
+ { path: ["projects", "rename"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["--name", "Gate Name"] },
88
+ { path: ["projects", "tenant-payments"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
89
+ { path: ["projects", "get"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
90
+ { path: ["projects", "sql"], positionals: [p("query", { required: false })], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["SELECT 1"] },
91
+ { path: ["projects", "rest"], positionals: [p("table")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["users"] },
92
+ { path: ["projects", "usage"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
93
+ { path: ["projects", "costs"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
94
+ { path: ["projects", "schema"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
95
+ { path: ["projects", "apply-expose"], positionals: [p("manifest_json", { required: false })], projectScoped: true, legacyPositionalProject: true, minimalArgs: ['{"version":"1","tables":[]}'] },
96
+ { path: ["projects", "validate-expose"], positionals: [p("manifest_json", { required: false })], projectScoped: true, legacyPositionalProject: true, minimalArgs: ['{"version":"1","tables":[]}'] },
97
+ { path: ["projects", "get-expose"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
98
+ { path: ["projects", "delete"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
99
+ { path: ["projects", "promote-user"], positionals: [p("email")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["user@example.com"] },
100
+ { path: ["projects", "demote-user"], positionals: [p("email")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["user@example.com"] },
101
+ { path: ["projects", "export"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
102
+
103
+ // ── snapshots ────────────────────────────────────────────────────────────
104
+ { path: ["snapshots", "create"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
105
+ { path: ["snapshots", "list"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
106
+ { path: ["snapshots", "get"], positionals: [p("snapshot_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["snap_gate1"] },
107
+ { path: ["snapshots", "restore"], positionals: [p("snapshot_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["snap_gate1"] },
108
+ { path: ["snapshots", "delete"], positionals: [p("snapshot_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["snap_gate1"] },
109
+
110
+ // ── branches ─────────────────────────────────────────────────────────────
111
+ { path: ["branches", "create"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
112
+ { path: ["branches", "list"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
113
+ { path: ["branches", "renew"], positionals: [p("branch_project_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["prj_branch1"] },
114
+ { path: ["branches", "delete"], positionals: [p("branch_project_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["prj_branch1"] },
115
+
116
+ // ── admin ────────────────────────────────────────────────────────────────
117
+ { path: ["admin", "lease-perpetual"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--enable"] },
118
+ { path: ["admin", "archive"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["--reason", "gate"] },
119
+ { path: ["admin", "reactivate"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
120
+
121
+ // ── cloud / archives / core ──────────────────────────────────────────────
122
+ { path: ["cloud", "archives", "create"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
123
+ { path: ["cloud", "archives", "download"], positionals: [p("archive_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["arc_gate1", "--output", "__OUT_FILE__"] },
124
+ { path: ["cloud", "archives", "status"], positionals: [p("archive_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["arc_gate1"] },
125
+ { path: ["archives", "inspect"], positionals: [p("archive_path")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["__FIXTURE_FILE__"] },
126
+ { path: ["archives", "verify"], positionals: [p("archive_path")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["__FIXTURE_FILE__"] },
127
+ { path: ["core", "projects", "import"], positionals: [p("archive_path")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["__FIXTURE_FILE__", "--name", "gate-import"] },
128
+
129
+ // ── deploy (unified deploy v2) ───────────────────────────────────────────
130
+ { path: ["deploy", "apply"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--spec", "{}", "--check"], runStyle: "deployV2" },
131
+ { path: ["deploy", "rehearse"], positionals: [p("plan_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["plan_gate1"], runStyle: "deployV2" },
132
+ { path: ["deploy", "promote"], positionals: [p("release_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["rel_gate1"], runStyle: "deployV2" },
133
+ { path: ["deploy", "resume"], positionals: [p("operation_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["op_gate1"], runStyle: "deployV2" },
134
+ { path: ["deploy", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "deployV2" },
135
+ { path: ["deploy", "events"], positionals: [p("operation_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["op_gate1"], runStyle: "deployV2" },
136
+ { path: ["deploy", "verify"], positionals: [p("operation_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["op_gate1"], runStyle: "deployV2" },
137
+ { path: ["deploy", "release", "get"], positionals: [p("release_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["rel_gate1"], runStyle: "deployV2" },
138
+ { path: ["deploy", "release", "active"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "deployV2" },
139
+ { path: ["deploy", "release", "diff"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--from", "empty", "--to", "active"], runStyle: "deployV2" },
140
+ { path: ["deploy", "diagnose"], positionals: [p("url")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["https://example.com/"], runStyle: "deployV2" },
141
+ { path: ["deploy", "resolve"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--url", "https://example.com/"], runStyle: "deployV2" },
142
+
143
+ // ── ci ───────────────────────────────────────────────────────────────────
144
+ { path: ["ci", "link"], positionals: [p("provider")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["github"] },
145
+ { path: ["ci", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
146
+ { path: ["ci", "revoke"], positionals: [p("binding_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["bnd_gate1"] },
147
+ { path: ["ci", "set-asset-scopes"], positionals: [p("binding_id_and_scopes", { variadic: true })], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["bnd_gate1", "astro/*"] },
148
+
149
+ // ── transfer ─────────────────────────────────────────────────────────────
150
+ { path: ["transfer", "init"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--to", "0x1111111111111111111111111111111111111111"] },
151
+ { path: ["transfer", "preview"], positionals: [p("transfer_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["tr_gate1"] },
152
+ { path: ["transfer", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
153
+ { path: ["transfer", "accept"], positionals: [p("transfer_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["tr_gate1"] },
154
+ { path: ["transfer", "claim"], positionals: [p("transfer_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["tr_gate1"] },
155
+ { path: ["transfer", "cancel"], positionals: [p("transfer_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["tr_gate1"] },
156
+
157
+ // ── org ──────────────────────────────────────────────────────────────────
158
+ { path: ["org", "create"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
159
+ { path: ["org", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
160
+ { path: ["org", "get"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1"] },
161
+ { path: ["org", "rename"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--name", "Gate"] },
162
+ { path: ["org", "payout-wallet"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--wallet", "0x1111111111111111111111111111111111111111"] },
163
+ { path: ["org", "whoami"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
164
+ { path: ["org", "audit"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1"] },
165
+ { path: ["org", "member", "list"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1"] },
166
+ { path: ["org", "member", "add"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--wallet", "0x1111111111111111111111111111111111111111"] },
167
+ { path: ["org", "member", "role"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--principal", "prn_gate1", "--role", "viewer"] },
168
+ { path: ["org", "member", "rm"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--principal", "prn_gate1"] },
169
+ { path: ["org", "invite", "list"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1"] },
170
+ { path: ["org", "invite", "create"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--email", "gate@example.com"] },
171
+ { path: ["org", "invite", "rm"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--principal", "prn_gate1"] },
172
+
173
+ // ── grants ───────────────────────────────────────────────────────────────
174
+ { path: ["grants", "create"], positionals: [p("wallet")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["0x1111111111111111111111111111111111111111", "--capability", "deploy"] },
175
+ { path: ["grants", "revoke"], positionals: [p("grant_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["grt_gate1"] },
176
+
177
+ // ── events / errors (flat, merged runners) ───────────────────────────────
178
+ { path: ["events"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
179
+ { path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
180
+
181
+ // ── jobs ─────────────────────────────────────────────────────────────────
182
+ { path: ["jobs", "submit"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--file", "__FIXTURE_FILE__"] },
183
+ { path: ["jobs", "get"], positionals: [p("job_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["job_gate1"] },
184
+ { path: ["jobs", "logs"], positionals: [p("job_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["job_gate1"] },
185
+ { path: ["jobs", "cancel"], positionals: [p("job_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["job_gate1"] },
186
+ { path: ["jobs", "purge"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
187
+ { path: ["jobs", "artifacts", "get"], positionals: [p("job_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["job_gate1", "--file", "result.json", "--output", "__OUT_FILE__"] },
188
+
189
+ // ── functions ────────────────────────────────────────────────────────────
190
+ { path: ["functions", "deploy"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn", "--file", "__FIXTURE_FILE__"] },
191
+ { path: ["functions", "invoke"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn"] },
192
+ { path: ["functions", "logs"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn"] },
193
+ { path: ["functions", "update"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn", "--timeout", "5"] },
194
+ { path: ["functions", "rebuild"], positionals: [p("name", { required: false })], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn"] },
195
+ { path: ["functions", "list"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
196
+ { path: ["functions", "delete"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn"] },
197
+ { path: ["functions", "runs", "create"], positionals: [p("function_name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn", "--event-type", "gate.test", "--idempotency-key", "gate:1"] },
198
+ { path: ["functions", "runs", "list"], positionals: [p("function_name")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["gate-fn"] },
199
+ { path: ["functions", "runs", "get"], positionals: [p("run_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["fnrun_gate1"] },
200
+ { path: ["functions", "runs", "logs"], positionals: [p("run_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["fnrun_gate1"] },
201
+ { path: ["functions", "runs", "cancel"], positionals: [p("run_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["fnrun_gate1"] },
202
+ { path: ["functions", "runs", "redrive"], positionals: [p("run_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["fnrun_gate1"] },
203
+
204
+ // ── secrets ──────────────────────────────────────────────────────────────
205
+ { path: ["secrets", "set"], positionals: [p("key")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["MY_KEY", "--value", "v"] },
206
+ { path: ["secrets", "list"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
207
+ { path: ["secrets", "delete"], positionals: [p("key")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["MY_KEY"] },
208
+
209
+ // ── assets ───────────────────────────────────────────────────────────────
210
+ { path: ["assets", "put"], positionals: [p("file", { variadic: true })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["__FIXTURE_FILE__"] },
211
+ { path: ["assets", "get"], positionals: [p("key")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate.txt", "--output", "__OUT_FILE__"] },
212
+ { path: ["assets", "ls"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
213
+ { path: ["assets", "rm"], positionals: [p("key")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate.txt"] },
214
+ { path: ["assets", "sign"], positionals: [p("key")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate.txt"] },
215
+ { path: ["assets", "diagnose"], positionals: [p("url")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["https://app.run402.com/_blob/gate.txt"] },
216
+
217
+ // ── cdn ──────────────────────────────────────────────────────────────────
218
+ { path: ["cdn", "wait-fresh"], positionals: [p("url")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["https://example.com/a.png", "--sha", "a".repeat(64), "--timeout", "1"] },
219
+
220
+ // ── sites ────────────────────────────────────────────────────────────────
221
+ { path: ["sites", "deploy"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--manifest", "__FIXTURE_FILE__"] },
222
+ { path: ["sites", "deploy-dir"], positionals: [p("dir")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["__SCRATCH_DIR__"] },
223
+
224
+ // ── subdomains ───────────────────────────────────────────────────────────
225
+ { path: ["subdomains", "claim"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate-sub"] },
226
+ { path: ["subdomains", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
227
+ { path: ["subdomains", "delete"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate-sub", "--confirm"] },
228
+
229
+ // ── domains ──────────────────────────────────────────────────────────────
230
+ { path: ["domains", "connect"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com", "--web"] },
231
+ { path: ["domains", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
232
+ { path: ["domains", "status"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
233
+ { path: ["domains", "dns"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
234
+ { path: ["domains", "check"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
235
+ { path: ["domains", "apply"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
236
+ { path: ["domains", "repair"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
237
+ { path: ["domains", "test-receive"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com", "--to", "inbox"] },
238
+ { path: ["domains", "wait"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com", "--timeout-ms", "1", "--interval-ms", "1"] },
239
+ { path: ["domains", "activate"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com"] },
240
+ { path: ["domains", "disconnect"], positionals: [p("domain")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["example.com", "--confirm"] },
241
+
242
+ // ── apps ─────────────────────────────────────────────────────────────────
243
+ { path: ["apps", "browse"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
244
+ { path: ["apps", "fork"], positionals: [p("version_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["ver_gate1", "--name", "gate-fork"] },
245
+ { path: ["apps", "publish"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
246
+ { path: ["apps", "versions"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
247
+ { path: ["apps", "inspect"], positionals: [p("version_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["ver_gate1"] },
248
+ { path: ["apps", "update"], positionals: [p("version_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["ver_gate1", "--description", "gate"] },
249
+ { path: ["apps", "delete"], positionals: [p("version_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["ver_gate1"] },
250
+
251
+ // ── ai / image ───────────────────────────────────────────────────────────
252
+ { path: ["ai", "translate"], positionals: [p("text")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["hello", "--to", "fr"] },
253
+ { path: ["ai", "moderate"], positionals: [p("text")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["hello"] },
254
+ { path: ["ai", "usage"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
255
+ { path: ["image", "generate"], positionals: [p("prompt")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["a gate mascot"] },
256
+
257
+ // ── email (+ nested webhooks group) ──────────────────────────────────────
258
+ { path: ["email", "create"], positionals: [p("slug")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["gate-slug"] },
259
+ { path: ["email", "mailboxes"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
260
+ { path: ["email", "defaults"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
261
+ { path: ["email", "update"], positionals: [p("mailbox", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--footer-policy", "none"] },
262
+ { path: ["email", "info"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
263
+ { path: ["email", "send"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--to", "gate@example.com", "--subject", "gate", "--html", "<p>gate</p>"] },
264
+ { path: ["email", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
265
+ { path: ["email", "get"], positionals: [p("message_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["msg_gate1"] },
266
+ { path: ["email", "get-raw"], positionals: [p("message_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["msg_gate1", "--output", "__OUT_FILE__"] },
267
+ { path: ["email", "reply"], positionals: [p("message_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["msg_gate1", "--html", "<p>gate</p>"] },
268
+ { path: ["email", "delete"], positionals: [p("mailbox", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--confirm"] },
269
+ { path: ["email", "webhooks", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
270
+ { path: ["email", "webhooks", "get"], positionals: [p("webhook_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["whk_gate1"] },
271
+ { path: ["email", "webhooks", "delete"], positionals: [p("webhook_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["whk_gate1"] },
272
+ { path: ["email", "webhooks", "update"], positionals: [p("webhook_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["whk_gate1", "--url", "https://example.com/hook"] },
273
+ { path: ["email", "webhooks", "register"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--url", "https://example.com/hook", "--events", "email.received"] },
274
+ { path: ["email", "webhooks", "deliveries"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
275
+ { path: ["email", "webhooks", "redrive"], positionals: [p("delivery_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["dlv_gate1"] },
276
+
277
+ // ── message / agent / operator ───────────────────────────────────────────
278
+ { path: ["message", "send"], positionals: [p("words", { variadic: true })], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["hello", "from", "the", "gate"] },
279
+ { path: ["agent", "contact"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--name", "gate-agent"] },
280
+ { path: ["agent", "status"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
281
+ { path: ["agent", "verify-email"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
282
+ { path: ["agent", "passkey"], positionals: [p("action")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["enroll"] },
283
+ { path: ["operator", "login"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], skipBehavioral: "opens a browser / loopback listener" },
284
+ { path: ["operator", "logout"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
285
+ { path: ["operator", "overview"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
286
+ { path: ["operator", "whoami"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
287
+ { path: ["operator", "claim-wallet-org"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
288
+ { path: ["operator", "approve"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--action", "org.project.create", "--org", "org_gate1", "--no-open"] },
289
+
290
+ // ── auth ─────────────────────────────────────────────────────────────────
291
+ { path: ["auth", "magic-link"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--email", "gate@example.com", "--redirect", "https://example.com/cb"] },
292
+ { path: ["auth", "verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1"] },
293
+ { path: ["auth", "create-user"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--email", "gate@example.com"] },
294
+ { path: ["auth", "invite-user"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--email", "gate@example.com", "--redirect", "https://example.com/cb"] },
295
+ { path: ["auth", "set-password"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1", "--new", "gate-password"] },
296
+ { path: ["auth", "settings"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
297
+ { path: ["auth", "passkey-register-options"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1", "--app-origin", "https://example.com"] },
298
+ { path: ["auth", "passkey-register-verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1", "--challenge", "ch_gate1", "--response", "{}"] },
299
+ { path: ["auth", "passkey-login-options"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--app-origin", "https://example.com"] },
300
+ { path: ["auth", "passkey-login-verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--challenge", "ch_gate1", "--response", "{}"] },
301
+ { path: ["auth", "passkeys"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1"] },
302
+ { path: ["auth", "delete-passkey"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--token", "tok_gate1", "--id", "pk_gate1"] },
303
+ { path: ["auth", "providers"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [] },
304
+
305
+ // ── billing ──────────────────────────────────────────────────────────────
306
+ { path: ["billing", "create-email"], positionals: [p("email")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["gate@example.com"] },
307
+ { path: ["billing", "link-wallet"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["00000000-0000-4000-8000-000000000001", "--wallet", "0x1111111111111111111111111111111111111111"] },
308
+ { path: ["billing", "checkout"], positionals: [p("identifier")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["00000000-0000-4000-8000-000000000001", "--product", "email-pack"] },
309
+ { path: ["billing", "auto-recharge"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["00000000-0000-4000-8000-000000000001", "--state", "on"] },
310
+ { path: ["billing", "balance"], positionals: [p("identifier")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["gate@example.com"] },
311
+ { path: ["billing", "history"], positionals: [p("identifier")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["gate@example.com"] },
312
+
313
+ // ── contracts ────────────────────────────────────────────────────────────
314
+ { path: ["contracts", "provision-signer"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["--chain", "base-sepolia", "--yes"] },
315
+ { path: ["contracts", "get-signer"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1"] },
316
+ { path: ["contracts", "list-signers"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
317
+ { path: ["contracts", "set-recovery"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--clear"] },
318
+ { path: ["contracts", "set-alert"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--threshold-wei", "1"] },
319
+ { path: ["contracts", "call"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--to", "0x4444444444444444444444444444444444444444", "--abi", "[]", "--fn", "noop", "--args", "[]"] },
320
+ { path: ["contracts", "deploy"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--bytecode", "0x00"] },
321
+ { path: ["contracts", "read"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--chain", "base-sepolia", "--to", "0x4444444444444444444444444444444444444444", "--abi", "[]", "--fn", "noop", "--args", "[]"] },
322
+ { path: ["contracts", "status"], positionals: [p("call_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["ccall_gate1"] },
323
+ { path: ["contracts", "drain"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--to", "0x4444444444444444444444444444444444444444", "--confirm"] },
324
+ { path: ["contracts", "delete"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--confirm"] },
325
+
326
+ // ── service / cache / doctor / notifications / webhook-secret / logs ─────
327
+ { path: ["service", "status"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
328
+ { path: ["service", "health"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
329
+ { path: ["cache", "inspect"], positionals: [p("url")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["https://example.com/"] },
330
+ { path: ["cache", "invalidate"], positionals: [p("url", { required: false })], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--all", "--host", "example.com"] },
331
+ { path: ["doctor"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--no-scan"], runStyle: "merged" },
332
+ { path: ["notifications", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
333
+ { path: ["notifications", "get"], positionals: [p("notification_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["ntf_gate1"] },
334
+ { path: ["notifications", "preferences"], positionals: [p("set_kv", { required: false, variadic: true })], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
335
+ { path: ["notifications", "test"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
336
+ { path: ["notifications", "channels", "connect"], positionals: [p("channel_type")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["telegram"], skipBehavioral: "polls interactively for the Telegram connect handshake" },
337
+ { path: ["notifications", "channels", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
338
+ { path: ["notifications", "channels", "revoke"], positionals: [p("binding_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["bnd_gate1"] },
339
+ { path: ["notifications", "rules", "add"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--binding", "bnd_gate1"] },
340
+ { path: ["notifications", "rules", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
341
+ { path: ["notifications", "rules", "rm"], positionals: [p("rule_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["rul_gate1"] },
342
+ { path: ["webhook-secret", "rotate"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
343
+ { path: ["logs"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--request-id", "req_gate123"], runStyle: "merged" },
344
+ ];
345
+
346
+ // Families deliberately absent from the manifest, consumed by the gate's
347
+ // completeness check against cli.mjs's dispatch switch.
348
+ export const SKIPPED_FAMILIES = {
349
+ "apply": "pure alias for `deploy apply` (covered by the deploy family)",
350
+ "sender-domain": "removed command — every subcommand errors with COMMAND_REMOVED",
351
+ "dev": "interactive wrapper that spawns `astro dev`",
352
+ };
package/lib/contracts.mjs CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  flagValue,
7
7
  normalizeArgv,
8
8
  positionalArgs,
9
+ resolveProjectSelector,
9
10
  validateEvmAddress,
10
11
  } from "./argparse.mjs";
11
12
 
@@ -51,6 +52,9 @@ const SUB_HELP = {
51
52
  "provision-signer": `run402 contracts provision-signer — Provision a KMS-backed signer
52
53
 
53
54
  Usage:
55
+ run402 contracts provision-signer --chain <chain> [--project <id>] [options]
56
+
57
+ Legacy (still supported):
54
58
  run402 contracts provision-signer <project_id> --chain <chain> [options]
55
59
 
56
60
  Arguments:
@@ -64,16 +68,25 @@ Options:
64
68
  "set-recovery": `run402 contracts set-recovery — Set or clear the signer recovery address
65
69
 
66
70
  Usage:
71
+ run402 contracts set-recovery <signer_id> [--project <id>] [options]
72
+
73
+ Legacy (still supported):
67
74
  run402 contracts set-recovery <project_id> <signer_id> [options]
68
75
  `,
69
76
  "set-alert": `run402 contracts set-alert — Set the low-balance alert threshold
70
77
 
71
78
  Usage:
79
+ run402 contracts set-alert <signer_id> --threshold-wei <n> [--project <id>]
80
+
81
+ Legacy (still supported):
72
82
  run402 contracts set-alert <project_id> <signer_id> --threshold-wei <n>
73
83
  `,
74
84
  call: `run402 contracts call — Submit a contract write call
75
85
 
76
86
  Usage:
87
+ run402 contracts call <signer_id> --to 0x... --abi <json> [--project <id>]
88
+
89
+ Legacy (still supported):
77
90
  run402 contracts call <project_id> <signer_id> --to 0x... --abi <json>
78
91
  --fn <name> --args <json> [options]
79
92
  `,
@@ -84,6 +97,9 @@ Returns the deterministic CREATE address synchronously — known from (signer, n
84
97
  before the tx confirms. Cost: chain gas at-cost + $0.000005 KMS sign fee.
85
98
 
86
99
  Usage:
100
+ run402 contracts deploy <signer_id> --bytecode 0x... [--project <id>] [options]
101
+
102
+ Legacy (still supported):
87
103
  run402 contracts deploy <project_id> <signer_id> --bytecode 0x... [options]
88
104
 
89
105
  Options:
@@ -108,16 +124,25 @@ Usage:
108
124
  drain: `run402 contracts drain — Drain native balance to a destination address
109
125
 
110
126
  Usage:
127
+ run402 contracts drain <signer_id> --to 0x... --confirm [--project <id>]
128
+
129
+ Legacy (still supported):
111
130
  run402 contracts drain <project_id> <signer_id> --to 0x... --confirm
112
131
  `,
113
132
  delete: `run402 contracts delete — Schedule the KMS key for deletion
114
133
 
115
134
  Usage:
135
+ run402 contracts delete <signer_id> --confirm [--project <id>]
136
+
137
+ Legacy (still supported):
116
138
  run402 contracts delete <project_id> <signer_id> --confirm
117
139
  `,
118
140
  "get-signer": `run402 contracts get-signer — Get signer metadata + live balance
119
141
 
120
142
  Usage:
143
+ run402 contracts get-signer <signer_id> [--project <id>]
144
+
145
+ Legacy (still supported):
121
146
  run402 contracts get-signer <project_id> <signer_id>
122
147
 
123
148
  Arguments:
@@ -130,6 +155,9 @@ Examples:
130
155
  "list-signers": `run402 contracts list-signers — List all KMS signers for a project
131
156
 
132
157
  Usage:
158
+ run402 contracts list-signers [--project <id>]
159
+
160
+ Legacy (still supported):
133
161
  run402 contracts list-signers <project_id>
134
162
 
135
163
  Arguments:
@@ -144,6 +172,9 @@ Examples:
144
172
  status: `run402 contracts status — Get a contract call's status and receipt
145
173
 
146
174
  Usage:
175
+ run402 contracts status <call_id> [--project <id>]
176
+
177
+ Legacy (still supported):
147
178
  run402 contracts status <project_id> <call_id>
148
179
 
149
180
  Arguments:
@@ -470,18 +501,24 @@ async function deleteSigner(projectId, signerId, args) {
470
501
  export async function run(sub, args) {
471
502
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
472
503
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
504
+ // Project selection (CLI-wide convention): `--project <id>` wins, else a
505
+ // legacy leading `prj_...` positional, else the active project. The signer /
506
+ // call id stays positional (`cwlt_...` / `ccall_...`). Computed lazily so
507
+ // unknown subcommands (and the anonymous `read`) never trip project
508
+ // resolution.
509
+ const select = () => resolveProjectSelector(normalizeArgv(Array.isArray(args) ? args : []));
473
510
  switch (sub) {
474
- case "provision-signer": await provisionSigner(args[0], args.slice(1)); break;
475
- case "get-signer": await getSigner(args[0], args[1], args.slice(2)); break;
476
- case "list-signers": await listSigners(args[0], args.slice(1)); break;
477
- case "set-recovery": await setRecovery(args[0], args[1], args.slice(2)); break;
478
- case "set-alert": await setAlert(args[0], args[1], args.slice(2)); break;
479
- case "call": await call(args[0], args[1], args.slice(2)); break;
480
- case "deploy": await deploy(args[0], args[1], args.slice(2)); break;
511
+ case "provision-signer": { const { projectId, rest } = select(); await provisionSigner(projectId, rest); break; }
512
+ case "get-signer": { const { projectId, rest } = select(); await getSigner(projectId, rest[0], rest.slice(1)); break; }
513
+ case "list-signers": { const { projectId, rest } = select(); await listSigners(projectId, rest); break; }
514
+ case "set-recovery": { const { projectId, rest } = select(); await setRecovery(projectId, rest[0], rest.slice(1)); break; }
515
+ case "set-alert": { const { projectId, rest } = select(); await setAlert(projectId, rest[0], rest.slice(1)); break; }
516
+ case "call": { const { projectId, rest } = select(); await call(projectId, rest[0], rest.slice(1)); break; }
517
+ case "deploy": { const { projectId, rest } = select(); await deploy(projectId, rest[0], rest.slice(1)); break; }
481
518
  case "read": await read(args); break;
482
- case "status": await status(args[0], args[1], args.slice(2)); break;
483
- case "drain": await drain(args[0], args[1], args.slice(2)); break;
484
- case "delete": await deleteSigner(args[0], args[1], args.slice(2)); break;
519
+ case "status": { const { projectId, rest } = select(); await status(projectId, rest[0], rest.slice(1)); break; }
520
+ case "drain": { const { projectId, rest } = select(); await drain(projectId, rest[0], rest.slice(1)); break; }
521
+ case "delete": { const { projectId, rest } = select(); await deleteSigner(projectId, rest[0], rest.slice(1)); break; }
485
522
  default:
486
523
  fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown contracts subcommand: ${sub}`, hint: "Run `run402 contracts --help` for usage.", details: { command: "contracts", subcommand: sub } });
487
524
  }