run402 4.8.0 → 4.10.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.
Files changed (64) hide show
  1. package/cli.mjs +20 -8
  2. package/lib/admin.mjs +18 -25
  3. package/lib/agent.mjs +2 -2
  4. package/lib/ai.mjs +2 -2
  5. package/lib/allowance.mjs +2 -2
  6. package/lib/apps.mjs +69 -51
  7. package/lib/archives.mjs +2 -7
  8. package/lib/argparse.mjs +131 -3
  9. package/lib/asset-wire.mjs +59 -0
  10. package/lib/assets.mjs +14 -12
  11. package/lib/auth.mjs +2 -2
  12. package/lib/billing.mjs +17 -13
  13. package/lib/branches.mjs +22 -23
  14. package/lib/cache.mjs +2 -6
  15. package/lib/cdn.mjs +2 -2
  16. package/lib/ci.mjs +4 -1
  17. package/lib/cloud.mjs +27 -28
  18. package/lib/command-manifest.mjs +352 -0
  19. package/lib/contracts.mjs +49 -11
  20. package/lib/core.mjs +3 -13
  21. package/lib/credentials.mjs +3 -13
  22. package/lib/deploy-v2.mjs +21 -8
  23. package/lib/deploy.mjs +2 -5
  24. package/lib/doctor.mjs +32 -4
  25. package/lib/domains.mjs +2 -2
  26. package/lib/email.mjs +2 -2
  27. package/lib/functions.mjs +100 -55
  28. package/lib/grants.mjs +42 -22
  29. package/lib/image.mjs +2 -2
  30. package/lib/jobs.mjs +18 -11
  31. package/lib/message.mjs +2 -2
  32. package/lib/notifications.mjs +6 -12
  33. package/lib/operator.mjs +2 -7
  34. package/lib/org.mjs +88 -49
  35. package/lib/projects.mjs +68 -39
  36. package/lib/secrets.mjs +99 -41
  37. package/lib/service.mjs +3 -2
  38. package/lib/sites.mjs +2 -2
  39. package/lib/snapshots.mjs +22 -24
  40. package/lib/subdomains.mjs +2 -2
  41. package/lib/tier.mjs +2 -2
  42. package/lib/transfer.mjs +2 -1
  43. package/lib/up.mjs +12 -6
  44. package/lib/wallets.mjs +6 -9
  45. package/lib/webhook-secret.mjs +3 -8
  46. package/lib/webhooks.mjs +2 -2
  47. package/package.json +1 -1
  48. package/sdk/dist/actions.d.ts +20 -1
  49. package/sdk/dist/actions.d.ts.map +1 -1
  50. package/sdk/dist/actions.js.map +1 -1
  51. package/sdk/dist/namespaces/projects.d.ts +2 -1
  52. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  53. package/sdk/dist/namespaces/projects.js +8 -12
  54. package/sdk/dist/namespaces/projects.js.map +1 -1
  55. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  56. package/sdk/dist/node/actions-node.js +114 -8
  57. package/sdk/dist/node/actions-node.js.map +1 -1
  58. package/sdk/dist/node/deploy-manifest.d.ts +36 -0
  59. package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
  60. package/sdk/dist/node/deploy-manifest.js +112 -3
  61. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  62. package/sdk/dist/node/index.d.ts +1 -1
  63. package/sdk/dist/node/index.d.ts.map +1 -1
  64. package/sdk/dist/node/index.js.map +1 -1
@@ -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,7 +6,9 @@ import {
6
6
  flagValue,
7
7
  normalizeArgv,
8
8
  positionalArgs,
9
+ resolveProjectSelector,
9
10
  validateEvmAddress,
11
+ failUnknownSubcommand,
10
12
  } from "./argparse.mjs";
11
13
 
12
14
  const HELP = `run402 contracts — KMS-backed Ethereum signers for smart-contract calls
@@ -51,6 +53,9 @@ const SUB_HELP = {
51
53
  "provision-signer": `run402 contracts provision-signer — Provision a KMS-backed signer
52
54
 
53
55
  Usage:
56
+ run402 contracts provision-signer --chain <chain> [--project <id>] [options]
57
+
58
+ Legacy (still supported):
54
59
  run402 contracts provision-signer <project_id> --chain <chain> [options]
55
60
 
56
61
  Arguments:
@@ -64,16 +69,25 @@ Options:
64
69
  "set-recovery": `run402 contracts set-recovery — Set or clear the signer recovery address
65
70
 
66
71
  Usage:
72
+ run402 contracts set-recovery <signer_id> [--project <id>] [options]
73
+
74
+ Legacy (still supported):
67
75
  run402 contracts set-recovery <project_id> <signer_id> [options]
68
76
  `,
69
77
  "set-alert": `run402 contracts set-alert — Set the low-balance alert threshold
70
78
 
71
79
  Usage:
80
+ run402 contracts set-alert <signer_id> --threshold-wei <n> [--project <id>]
81
+
82
+ Legacy (still supported):
72
83
  run402 contracts set-alert <project_id> <signer_id> --threshold-wei <n>
73
84
  `,
74
85
  call: `run402 contracts call — Submit a contract write call
75
86
 
76
87
  Usage:
88
+ run402 contracts call <signer_id> --to 0x... --abi <json> [--project <id>]
89
+
90
+ Legacy (still supported):
77
91
  run402 contracts call <project_id> <signer_id> --to 0x... --abi <json>
78
92
  --fn <name> --args <json> [options]
79
93
  `,
@@ -84,6 +98,9 @@ Returns the deterministic CREATE address synchronously — known from (signer, n
84
98
  before the tx confirms. Cost: chain gas at-cost + $0.000005 KMS sign fee.
85
99
 
86
100
  Usage:
101
+ run402 contracts deploy <signer_id> --bytecode 0x... [--project <id>] [options]
102
+
103
+ Legacy (still supported):
87
104
  run402 contracts deploy <project_id> <signer_id> --bytecode 0x... [options]
88
105
 
89
106
  Options:
@@ -108,16 +125,25 @@ Usage:
108
125
  drain: `run402 contracts drain — Drain native balance to a destination address
109
126
 
110
127
  Usage:
128
+ run402 contracts drain <signer_id> --to 0x... --confirm [--project <id>]
129
+
130
+ Legacy (still supported):
111
131
  run402 contracts drain <project_id> <signer_id> --to 0x... --confirm
112
132
  `,
113
133
  delete: `run402 contracts delete — Schedule the KMS key for deletion
114
134
 
115
135
  Usage:
136
+ run402 contracts delete <signer_id> --confirm [--project <id>]
137
+
138
+ Legacy (still supported):
116
139
  run402 contracts delete <project_id> <signer_id> --confirm
117
140
  `,
118
141
  "get-signer": `run402 contracts get-signer — Get signer metadata + live balance
119
142
 
120
143
  Usage:
144
+ run402 contracts get-signer <signer_id> [--project <id>]
145
+
146
+ Legacy (still supported):
121
147
  run402 contracts get-signer <project_id> <signer_id>
122
148
 
123
149
  Arguments:
@@ -130,6 +156,9 @@ Examples:
130
156
  "list-signers": `run402 contracts list-signers — List all KMS signers for a project
131
157
 
132
158
  Usage:
159
+ run402 contracts list-signers [--project <id>]
160
+
161
+ Legacy (still supported):
133
162
  run402 contracts list-signers <project_id>
134
163
 
135
164
  Arguments:
@@ -144,6 +173,9 @@ Examples:
144
173
  status: `run402 contracts status — Get a contract call's status and receipt
145
174
 
146
175
  Usage:
176
+ run402 contracts status <call_id> [--project <id>]
177
+
178
+ Legacy (still supported):
147
179
  run402 contracts status <project_id> <call_id>
148
180
 
149
181
  Arguments:
@@ -470,19 +502,25 @@ async function deleteSigner(projectId, signerId, args) {
470
502
  export async function run(sub, args) {
471
503
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
472
504
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
505
+ // Project selection (CLI-wide convention): `--project <id>` wins, else a
506
+ // legacy leading `prj_...` positional, else the active project. The signer /
507
+ // call id stays positional (`cwlt_...` / `ccall_...`). Computed lazily so
508
+ // unknown subcommands (and the anonymous `read`) never trip project
509
+ // resolution.
510
+ const select = () => resolveProjectSelector(normalizeArgv(Array.isArray(args) ? args : []));
473
511
  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;
512
+ case "provision-signer": { const { projectId, rest } = select(); await provisionSigner(projectId, rest); break; }
513
+ case "get-signer": { const { projectId, rest } = select(); await getSigner(projectId, rest[0], rest.slice(1)); break; }
514
+ case "list-signers": { const { projectId, rest } = select(); await listSigners(projectId, rest); break; }
515
+ case "set-recovery": { const { projectId, rest } = select(); await setRecovery(projectId, rest[0], rest.slice(1)); break; }
516
+ case "set-alert": { const { projectId, rest } = select(); await setAlert(projectId, rest[0], rest.slice(1)); break; }
517
+ case "call": { const { projectId, rest } = select(); await call(projectId, rest[0], rest.slice(1)); break; }
518
+ case "deploy": { const { projectId, rest } = select(); await deploy(projectId, rest[0], rest.slice(1)); break; }
481
519
  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;
520
+ case "status": { const { projectId, rest } = select(); await status(projectId, rest[0], rest.slice(1)); break; }
521
+ case "drain": { const { projectId, rest } = select(); await drain(projectId, rest[0], rest.slice(1)); break; }
522
+ case "delete": { const { projectId, rest } = select(); await deleteSigner(projectId, rest[0], rest.slice(1)); break; }
485
523
  default:
486
- fail({ code: "UNKNOWN_SUBCOMMAND", message: `Unknown contracts subcommand: ${sub}`, hint: "Run `run402 contracts --help` for usage.", details: { command: "contracts", subcommand: sub } });
524
+ failUnknownSubcommand("contracts", sub);
487
525
  }
488
526
  }
package/lib/core.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { importArchiveToCore } from "#sdk/node";
2
2
  import { fail, reportSdkError } from "./sdk-errors.mjs";
3
- import { assertKnownFlags, flagValue, hasHelp, normalizeArgv, positionalArgs } from "./argparse.mjs";
3
+ import { assertKnownFlags, flagValue, hasHelp, normalizeArgv, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
4
4
 
5
5
  const HELP = `run402 core — Local Run402 Core commands
6
6
 
@@ -29,21 +29,11 @@ export async function run(sub, args = []) {
29
29
  return;
30
30
  }
31
31
  if (sub !== "projects") {
32
- fail({
33
- code: "UNKNOWN_SUBCOMMAND",
34
- message: `Unknown core subcommand: ${sub}`,
35
- hint: "Run `run402 core --help` for usage.",
36
- details: { command: "core", subcommand: sub },
37
- });
32
+ failUnknownSubcommand("core", sub);
38
33
  }
39
34
  const action = args[0];
40
35
  if (action === "import" || action === "apply") return importProject(args.slice(1));
41
- fail({
42
- code: "UNKNOWN_SUBCOMMAND",
43
- message: `Unknown core projects subcommand: ${action}`,
44
- hint: "Run `run402 core projects --help` for usage.",
45
- details: { command: "core projects", subcommand: action },
46
- });
36
+ failUnknownSubcommand("core projects", action, { extraSubcommands: ["apply"] });
47
37
  }
48
38
 
49
39
  async function importProject(rawArgs) {
@@ -8,7 +8,7 @@ import {
8
8
  removeProject,
9
9
  saveProject,
10
10
  } from "./config.mjs";
11
- import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
11
+ import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
12
12
  import { fail } from "./sdk-errors.mjs";
13
13
 
14
14
  const HELP = `run402 credentials — Manage local credential material
@@ -245,12 +245,7 @@ async function runProjectKeys(sub, args) {
245
245
  case "export": await exportKey(args); break;
246
246
  case "remove": await remove(args); break;
247
247
  default:
248
- fail({
249
- code: "UNKNOWN_SUBCOMMAND",
250
- message: `Unknown credentials project-keys subcommand: ${sub}`,
251
- hint: "Run `run402 credentials project-keys --help` for usage.",
252
- details: { command: "credentials project-keys", subcommand: sub },
253
- });
248
+ failUnknownSubcommand("credentials project-keys", sub);
254
249
  }
255
250
  }
256
251
 
@@ -264,10 +259,5 @@ export async function run(sub, args = []) {
264
259
  await runProjectKeys(projectKeySub, rest);
265
260
  return;
266
261
  }
267
- fail({
268
- code: "UNKNOWN_SUBCOMMAND",
269
- message: `Unknown credentials subcommand: ${sub}`,
270
- hint: "Run `run402 credentials --help` for usage.",
271
- details: { command: "credentials", subcommand: sub },
272
- });
262
+ failUnknownSubcommand("credentials", sub);
273
263
  }