run402 4.93.1 → 4.94.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 (259) hide show
  1. package/README.md +43 -41
  2. package/cli.mjs +34 -29
  3. package/core-dist/config.js +52 -18
  4. package/core-dist/control-plane-session.js +31 -6
  5. package/core-dist/keystore.js +7 -7
  6. package/core-dist/profiles.js +9 -7
  7. package/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
  8. package/{sdk/core-dist/allowance.js → core-dist/wallet.js} +20 -20
  9. package/{sdk/core-dist/write-auth-session.js → core-dist/write-approvals.js} +19 -19
  10. package/gitvault-surface.json +1 -1
  11. package/lib/admin.mjs +1 -1
  12. package/lib/agent.mjs +20 -18
  13. package/lib/approve.mjs +81 -0
  14. package/lib/apps.mjs +3 -3
  15. package/lib/assets.mjs +2 -2
  16. package/lib/billing.mjs +6 -6
  17. package/lib/buzz-doctor.mjs +1 -1
  18. package/lib/buzz-notifications.mjs +1 -1
  19. package/lib/buzz.mjs +1 -1
  20. package/lib/buzz.test.mjs +1 -1
  21. package/lib/ci.mjs +4 -4
  22. package/lib/cold-start.mjs +21 -21
  23. package/lib/command-manifest.mjs +20 -34
  24. package/lib/config.mjs +24 -24
  25. package/lib/contacts.mjs +13 -13
  26. package/lib/contracts.mjs +1 -1
  27. package/lib/credentials.mjs +2 -2
  28. package/lib/daemon-path.mjs +1 -1
  29. package/lib/delegates.mjs +1 -1
  30. package/lib/deliveries.mjs +4 -4
  31. package/lib/deploy-rehearse-discovery.test.mjs +7 -7
  32. package/lib/deploy-v2.mjs +232 -184
  33. package/lib/deploy.mjs +21 -53
  34. package/lib/doctor-source-scan.mjs +1 -1
  35. package/lib/doctor.mjs +57 -57
  36. package/lib/escalations.mjs +1 -1
  37. package/lib/feedback.mjs +10 -10
  38. package/lib/gitvault-daemon.mjs +1 -1
  39. package/lib/image.mjs +3 -3
  40. package/lib/init.mjs +66 -66
  41. package/lib/lightning-wallet.mjs +28 -28
  42. package/lib/login.mjs +207 -0
  43. package/lib/logout.mjs +53 -0
  44. package/lib/next-actions.mjs +15 -15
  45. package/lib/org.mjs +100 -13
  46. package/lib/pay.mjs +1 -1
  47. package/lib/projects.mjs +26 -29
  48. package/lib/redeem.mjs +7 -8
  49. package/lib/remote-helper-session.mjs +12 -12
  50. package/lib/repos.mjs +62 -62
  51. package/lib/rooms.mjs +103 -67
  52. package/lib/sdk-errors.mjs +9 -4
  53. package/lib/sdk.mjs +3 -3
  54. package/lib/service.mjs +5 -5
  55. package/lib/session-proofs.mjs +21 -0
  56. package/lib/sign-in.mjs +296 -0
  57. package/lib/sites.mjs +8 -8
  58. package/lib/stats.mjs +2 -2
  59. package/lib/status.mjs +25 -25
  60. package/lib/subdomains.mjs +19 -19
  61. package/lib/subscriptions.mjs +8 -8
  62. package/lib/tier.mjs +29 -22
  63. package/lib/transfer.mjs +58 -85
  64. package/lib/up.mjs +7 -7
  65. package/lib/wallet-context.mjs +3 -3
  66. package/lib/wallet-context.test.mjs +3 -3
  67. package/lib/wallets.mjs +196 -31
  68. package/lib/webhook-secret.mjs +6 -6
  69. package/lib/whoami.mjs +75 -0
  70. package/package.json +1 -1
  71. package/sdk/core-dist/config.js +52 -18
  72. package/sdk/core-dist/control-plane-session.js +31 -6
  73. package/sdk/core-dist/keystore.js +7 -7
  74. package/sdk/core-dist/profiles.js +9 -7
  75. package/sdk/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
  76. package/{core-dist/allowance.js → sdk/core-dist/wallet.js} +20 -20
  77. package/{core-dist/write-auth-session.js → sdk/core-dist/write-approvals.js} +19 -19
  78. package/sdk/dist/actions.d.ts +1 -1
  79. package/sdk/dist/actions.d.ts.map +1 -1
  80. package/sdk/dist/control-plane-credentials.d.ts +8 -8
  81. package/sdk/dist/control-plane-credentials.js +7 -7
  82. package/sdk/dist/credentials.d.ts +19 -19
  83. package/sdk/dist/credentials.d.ts.map +1 -1
  84. package/sdk/dist/credentials.js +1 -1
  85. package/sdk/dist/errors.d.ts +24 -26
  86. package/sdk/dist/errors.d.ts.map +1 -1
  87. package/sdk/dist/errors.js +34 -37
  88. package/sdk/dist/errors.js.map +1 -1
  89. package/sdk/dist/index.d.ts +29 -16
  90. package/sdk/dist/index.d.ts.map +1 -1
  91. package/sdk/dist/index.js +29 -16
  92. package/sdk/dist/index.js.map +1 -1
  93. package/sdk/dist/kernel.d.ts +1 -1
  94. package/sdk/dist/kernel.d.ts.map +1 -1
  95. package/sdk/dist/kernel.js +10 -9
  96. package/sdk/dist/kernel.js.map +1 -1
  97. package/sdk/dist/namespaces/admin.d.ts +51 -120
  98. package/sdk/dist/namespaces/admin.d.ts.map +1 -1
  99. package/sdk/dist/namespaces/admin.js +41 -45
  100. package/sdk/dist/namespaces/admin.js.map +1 -1
  101. package/sdk/dist/namespaces/agent.d.ts +2 -2
  102. package/sdk/dist/namespaces/agent.d.ts.map +1 -1
  103. package/sdk/dist/namespaces/agent.js +2 -2
  104. package/sdk/dist/namespaces/agent.js.map +1 -1
  105. package/sdk/dist/namespaces/ai.d.ts +1 -1
  106. package/sdk/dist/namespaces/ai.js +1 -1
  107. package/sdk/dist/namespaces/billing.d.ts +6 -3
  108. package/sdk/dist/namespaces/billing.d.ts.map +1 -1
  109. package/sdk/dist/namespaces/billing.js.map +1 -1
  110. package/sdk/dist/namespaces/buzz-notifications.types.d.ts +1 -1
  111. package/sdk/dist/namespaces/buzz.d.ts +2 -2
  112. package/sdk/dist/namespaces/buzz.d.ts.map +1 -1
  113. package/sdk/dist/namespaces/buzz.js +2 -2
  114. package/sdk/dist/namespaces/buzz.js.map +1 -1
  115. package/sdk/dist/namespaces/buzz.types.d.ts +2 -2
  116. package/sdk/dist/namespaces/buzz.types.d.ts.map +1 -1
  117. package/sdk/dist/namespaces/credentials.types.d.ts +1 -1
  118. package/sdk/dist/namespaces/deploy.d.ts +1 -1
  119. package/sdk/dist/namespaces/deploy.js +5 -5
  120. package/sdk/dist/namespaces/deploy.js.map +1 -1
  121. package/sdk/dist/namespaces/deploy.types.d.ts +4 -4
  122. package/sdk/dist/namespaces/escalations.d.ts +1 -1
  123. package/sdk/dist/namespaces/escalations.js +1 -1
  124. package/sdk/dist/namespaces/escalations.types.d.ts +2 -2
  125. package/sdk/dist/namespaces/escalations.types.d.ts.map +1 -1
  126. package/sdk/dist/namespaces/events.types.d.ts +1 -1
  127. package/sdk/dist/namespaces/functions.d.ts +1 -1
  128. package/sdk/dist/namespaces/functions.js +1 -1
  129. package/sdk/dist/namespaces/gitvault.crypto.d.ts +1 -1
  130. package/sdk/dist/namespaces/gitvault.d.ts +24 -24
  131. package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
  132. package/sdk/dist/namespaces/gitvault.js +109 -109
  133. package/sdk/dist/namespaces/gitvault.js.map +1 -1
  134. package/sdk/dist/namespaces/me.d.ts +120 -0
  135. package/sdk/dist/namespaces/me.d.ts.map +1 -0
  136. package/sdk/dist/namespaces/me.js +34 -0
  137. package/sdk/dist/namespaces/me.js.map +1 -0
  138. package/sdk/dist/namespaces/org.d.ts +108 -6
  139. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  140. package/sdk/dist/namespaces/org.js +63 -7
  141. package/sdk/dist/namespaces/org.js.map +1 -1
  142. package/sdk/dist/namespaces/org.types.d.ts +29 -9
  143. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  144. package/sdk/dist/namespaces/pay.js +3 -3
  145. package/sdk/dist/namespaces/pay.js.map +1 -1
  146. package/sdk/dist/namespaces/projects.d.ts +9 -9
  147. package/sdk/dist/namespaces/projects.js +15 -15
  148. package/sdk/dist/namespaces/projects.js.map +1 -1
  149. package/sdk/dist/namespaces/projects.types.d.ts +28 -12
  150. package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
  151. package/sdk/dist/namespaces/rooms.d.ts +7 -7
  152. package/sdk/dist/namespaces/rooms.js +17 -17
  153. package/sdk/dist/namespaces/rooms.js.map +1 -1
  154. package/sdk/dist/namespaces/rooms.types.d.ts +9 -9
  155. package/sdk/dist/namespaces/rooms.types.d.ts.map +1 -1
  156. package/sdk/dist/namespaces/service.d.ts +1 -1
  157. package/sdk/dist/namespaces/service.js +1 -1
  158. package/sdk/dist/namespaces/{operator-session.d.ts → session.d.ts} +135 -51
  159. package/sdk/dist/namespaces/session.d.ts.map +1 -0
  160. package/sdk/dist/namespaces/{operator-session.js → session.js} +118 -30
  161. package/sdk/dist/namespaces/session.js.map +1 -0
  162. package/sdk/dist/namespaces/subdomains.d.ts +5 -5
  163. package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
  164. package/sdk/dist/namespaces/subdomains.js +6 -6
  165. package/sdk/dist/namespaces/subdomains.js.map +1 -1
  166. package/sdk/dist/namespaces/tier.d.ts +43 -20
  167. package/sdk/dist/namespaces/tier.d.ts.map +1 -1
  168. package/sdk/dist/namespaces/tier.js +7 -7
  169. package/sdk/dist/namespaces/tier.js.map +1 -1
  170. package/sdk/dist/namespaces/transfers.d.ts +74 -71
  171. package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
  172. package/sdk/dist/namespaces/transfers.js +31 -38
  173. package/sdk/dist/namespaces/transfers.js.map +1 -1
  174. package/sdk/dist/namespaces/vouchers.d.ts +9 -10
  175. package/sdk/dist/namespaces/vouchers.d.ts.map +1 -1
  176. package/sdk/dist/namespaces/vouchers.js +4 -5
  177. package/sdk/dist/namespaces/vouchers.js.map +1 -1
  178. package/sdk/dist/namespaces/wallets.d.ts +72 -3
  179. package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
  180. package/sdk/dist/namespaces/wallets.js +141 -3
  181. package/sdk/dist/namespaces/wallets.js.map +1 -1
  182. package/sdk/dist/namespaces/write-approval.d.ts +101 -0
  183. package/sdk/dist/namespaces/write-approval.d.ts.map +1 -0
  184. package/sdk/dist/namespaces/write-approval.js +62 -0
  185. package/sdk/dist/namespaces/write-approval.js.map +1 -0
  186. package/sdk/dist/node/actions-node.d.ts +3 -3
  187. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  188. package/sdk/dist/node/actions-node.js +36 -36
  189. package/sdk/dist/node/actions-node.js.map +1 -1
  190. package/sdk/dist/node/{bearer-claim-key.d.ts → bearer-redeem-key.d.ts} +12 -12
  191. package/sdk/dist/node/bearer-redeem-key.d.ts.map +1 -0
  192. package/sdk/dist/node/{bearer-claim-key.js → bearer-redeem-key.js} +21 -21
  193. package/sdk/dist/node/bearer-redeem-key.js.map +1 -0
  194. package/sdk/dist/node/ci.d.ts +1 -1
  195. package/sdk/dist/node/ci.d.ts.map +1 -1
  196. package/sdk/dist/node/ci.js +6 -6
  197. package/sdk/dist/node/ci.js.map +1 -1
  198. package/sdk/dist/node/credentials.d.ts +17 -16
  199. package/sdk/dist/node/credentials.d.ts.map +1 -1
  200. package/sdk/dist/node/credentials.js +28 -27
  201. package/sdk/dist/node/credentials.js.map +1 -1
  202. package/sdk/dist/node/funding-recovery.d.ts +1 -1
  203. package/sdk/dist/node/funding-recovery.d.ts.map +1 -1
  204. package/sdk/dist/node/funding-recovery.js +1 -1
  205. package/sdk/dist/node/funding-recovery.js.map +1 -1
  206. package/sdk/dist/node/gitvault-apply.d.ts +1 -1
  207. package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
  208. package/sdk/dist/node/gitvault-handoff.d.ts +22 -22
  209. package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
  210. package/sdk/dist/node/gitvault-handoff.js +45 -45
  211. package/sdk/dist/node/gitvault-handoff.js.map +1 -1
  212. package/sdk/dist/node/gitvault-prewarm.js +7 -7
  213. package/sdk/dist/node/gitvault-prewarm.js.map +1 -1
  214. package/sdk/dist/node/gitvault-restore.d.ts +1 -1
  215. package/sdk/dist/node/gitvault-restore.js +1 -1
  216. package/sdk/dist/node/index.d.ts +16 -16
  217. package/sdk/dist/node/index.d.ts.map +1 -1
  218. package/sdk/dist/node/index.js +15 -15
  219. package/sdk/dist/node/index.js.map +1 -1
  220. package/sdk/dist/node/org-adopt.d.ts +49 -0
  221. package/sdk/dist/node/org-adopt.d.ts.map +1 -0
  222. package/sdk/dist/node/org-adopt.js +77 -0
  223. package/sdk/dist/node/org-adopt.js.map +1 -0
  224. package/sdk/dist/node/paid-fetch.d.ts +7 -7
  225. package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
  226. package/sdk/dist/node/paid-fetch.js +54 -54
  227. package/sdk/dist/node/paid-fetch.js.map +1 -1
  228. package/sdk/dist/node/preflight.d.ts +1 -1
  229. package/sdk/dist/node/preflight.d.ts.map +1 -1
  230. package/sdk/dist/node/preflight.js +1 -1
  231. package/sdk/dist/node/preflight.js.map +1 -1
  232. package/sdk/dist/node/source-scan.d.ts +1 -1
  233. package/sdk/dist/node/source-scan.js +2 -2
  234. package/sdk/dist/node/source-scan.js.map +1 -1
  235. package/sdk/dist/scoped.d.ts +3 -3
  236. package/sdk/dist/scoped.d.ts.map +1 -1
  237. package/sdk/dist/scoped.js +2 -2
  238. package/sdk/dist/scoped.js.map +1 -1
  239. package/core-dist/operator-session.js +0 -154
  240. package/lib/allowance.mjs +0 -355
  241. package/lib/operator-proofs.mjs +0 -22
  242. package/lib/operator.mjs +0 -771
  243. package/sdk/core-dist/operator-session.js +0 -154
  244. package/sdk/dist/namespaces/allowance.d.ts +0 -79
  245. package/sdk/dist/namespaces/allowance.d.ts.map +0 -1
  246. package/sdk/dist/namespaces/allowance.js +0 -149
  247. package/sdk/dist/namespaces/allowance.js.map +0 -1
  248. package/sdk/dist/namespaces/operator-session.d.ts.map +0 -1
  249. package/sdk/dist/namespaces/operator-session.js.map +0 -1
  250. package/sdk/dist/namespaces/operator.d.ts +0 -359
  251. package/sdk/dist/namespaces/operator.d.ts.map +0 -1
  252. package/sdk/dist/namespaces/operator.js +0 -272
  253. package/sdk/dist/namespaces/operator.js.map +0 -1
  254. package/sdk/dist/node/bearer-claim-key.d.ts.map +0 -1
  255. package/sdk/dist/node/bearer-claim-key.js.map +0 -1
  256. package/sdk/dist/node/operator-claim.d.ts +0 -49
  257. package/sdk/dist/node/operator-claim.d.ts.map +0 -1
  258. package/sdk/dist/node/operator-claim.js +0 -77
  259. package/sdk/dist/node/operator-claim.js.map +0 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Command-line interface for [Run402](https://run402.com) — provision Postgres databases, deploy static sites, run Node 22 serverless functions, host content-addressed CDN assets, send email, sign on-chain. Paid autonomously with x402 (USDC on Base) or MPP (pathUSD on Tempo, or sats over Bitcoin Lightning). **Prototype tier is free on testnet.**
6
6
 
7
- The CLI lets an agent act as its own first-class Run402 principal rather than borrowing a human account. Identity records who invoked an action; organization roles, grants, delegates, freshness, and spend policy determine what that principal may do. People remain first-class owners and collaborators without becoming routine CLI operators.
7
+ The CLI lets an agent act as its own first-class Run402 principal rather than borrowing a human account. Identity records who invoked an action; organization roles, grants, delegates, freshness, and spend policy determine what that principal may do. People remain first-class owners and members without having to run the CLI themselves.
8
8
 
9
9
  For the full CLI reference (every flag, every subcommand) start at **<https://docs.run402.com/llms-cli.txt>** — the index holds the first-deploy contract and links one fetchable `/llms-cli-<slice>.txt` per topic (deploy, commands, repos, orgs, functions, assets, ops, errors, frontend, platform); <https://docs.run402.com/llms-cli-full.txt> is the whole reference as one document.
10
10
 
@@ -25,8 +25,8 @@ npx -y run402@latest <command>
25
25
  ## 30-second start
26
26
 
27
27
  ```bash
28
- run402 up --name my-app -y # bootstrap allowance/tier/project/link, then deploy
29
- run402 subdomains claim my-app # → https://my-app.run402.com
28
+ run402 up --name my-app -y # bootstrap wallet/tier/project/link, then deploy
29
+ run402 subdomains add my-app # → https://my-app.run402.com
30
30
  ```
31
31
 
32
32
  That's a real Postgres database + a deployed static site, paid for autonomously with testnet USDC.
@@ -51,13 +51,13 @@ run402 up verify --project prj_...
51
51
  run402 up --nested -y # app root inside another repo: its own nested repo + run402 remote
52
52
  ```
53
53
 
54
- `up` is a thin CLI shim over the Node SDK action runner. It discovers `run402.json`, `run402.deploy.json`, then `app.json`. A `run402.json` carrying the app schema/app markers uses the app-install graph; a release-shaped `run402.json` is normalized through the same ReleaseSpec path as `deploy apply` for compatibility. It validates the deploy input before any mutation, resolves the project as `--project` → `.run402/project.json` → manifest `project_id` → approved creation from `--name` → approved active-project fallback, then applies the manifest. `--name` is creation/link metadata only; it is not a manifest field and never renames an existing project. When everything is already configured, plain `run402 up` deploys. Non-interactive recursive prerequisites/local writes require `-y/--yes`.
54
+ `up` is a thin CLI shim over the Node SDK action runner. It discovers `run402.json`, `run402.deploy.json`, then `app.json`. A `run402.json` carrying the app schema/app markers uses the app-install graph; a release-shaped `run402.json` is normalized through the same ReleaseSpec path as `run402 deploy` for compatibility. It validates the deploy input before any mutation, resolves the project as `--project` → `.run402/project.json` → manifest `project_id` → approved creation from `--name` → approved active-project fallback, then applies the manifest. `--name` is creation/link metadata only; it is not a manifest field and never renames an existing project. When everything is already configured, plain `run402 up` deploys. Non-interactive recursive prerequisites/local writes require `-y/--yes`.
55
55
 
56
56
  For app manifests with `verify.http[]`, `up` runs HTTP checks after deploy. Fresh Run402 edge sentinel misses (`x-run402-edge` or sentinel JSON bodies) become `propagation_pending` with diagnostics instead of hard failure while the host binding is still converging. Use `--propagation-budget-s <seconds>` to tune the default 120 second wait, `--no-propagation-wait` to return immediately, and `run402 up verify` to rerun the same checks without upload, deploy, project creation, or resource mutation.
57
57
 
58
- For typed `run402.deploy.ts` configs, pass `--manifest` explicitly because TypeScript/JavaScript configs execute local code. Use `--check` for local-only import/normalize/file validation, `--print-spec` to inspect the normalized `ReleaseSpec`, `--plan` for a gateway-reviewed non-deploying plan, and `--require-plan <plan_id>` to apply only that reviewed intent. Warning flags are not used with `--require-plan`; the reviewed plan binds the exact warning/destructive set. Run402 Core skips Cloud allowance/tier prerequisites and fails closed when no Core project is selected.
58
+ For typed `run402.deploy.ts` configs, pass `--manifest` explicitly because TypeScript/JavaScript configs execute local code. Use `--check` for local-only import/normalize/file validation, `--print-spec` to inspect the normalized `ReleaseSpec`, `--plan` for a gateway-reviewed non-deploying plan, and `--require-plan <plan_id>` to apply only that reviewed intent. Warning flags are not used with `--require-plan`; the reviewed plan binds the exact warning/destructive set. Run402 Core skips Cloud wallet/tier prerequisites and fails closed when no Core project is selected.
59
59
 
60
- Every mode (and `deploy apply`) verifies the files the manifest references before any gateway call: migration `sql_path`/`sql_file`, function `source`/`files`, site `{ path }` entries and `dir()` targets, `assets.put` sources. A missing one is `MANIFEST_FILE_MISSING` (`details.missing[]` = `{ field_path, path, kind }`, one `create_file` next action per file). With no manifest in the working directory, `UP_MANIFEST_REQUIRED` looks one directory down (`details.nearby_manifests[]`, a leading `run_in_directory` next action such as `run402 up --check --dir <dir>`); `--manifest <path>` to a missing file is a typed `MANIFEST_NOT_FOUND` with a `create_manifest` next action. An app root that lies inside another repository is never scaffolded into it: the skip carries a `create_nested_repo` next action, and `--nested` gives the app its own nested repository (`git init -b main`, the `run402` remote, the first push) plus exactly one line in the enclosing repository's local `.git/info/exclude`, nothing else touched. `up` also sets this principal's display name when it has none: `RUN402_AGENT_NAME` (overrides an existing name), else the detected client (`claude-code`, `codex`, `cursor`, `grok`, or `RUN402_CLIENT=<name>` for one with no marker), and `result.identity` reports `detected` plus `detection: { applied, reason }` either way.
60
+ Every mode (and `run402 deploy`) verifies the files the manifest references before any gateway call: migration `sql_path`/`sql_file`, function `source`/`files`, site `{ path }` entries and `dir()` targets, `assets.put` sources. A missing one is `MANIFEST_FILE_MISSING` (`details.missing[]` = `{ field_path, path, kind }`, one `create_file` next action per file). With no manifest in the working directory, `UP_MANIFEST_REQUIRED` looks one directory down (`details.nearby_manifests[]`, a leading `run_in_directory` next action such as `run402 up --check --dir <dir>`); `--manifest <path>` to a missing file is a typed `MANIFEST_NOT_FOUND` with a `create_manifest` next action. An app root that lies inside another repository is never scaffolded into it: the skip carries a `create_nested_repo` next action, and `--nested` gives the app its own nested repository (`git init -b main`, the `run402` remote, the first push) plus exactly one line in the enclosing repository's local `.git/info/exclude`, nothing else touched. `up` also sets this principal's display name when it has none: `RUN402_AGENT_NAME` (overrides an existing name), else the detected client (`claude-code`, `codex`, `cursor`, `grok`, or `RUN402_CLIENT=<name>` for one with no marker), and `result.identity` reports `detected` plus `detection: { applied, reason }` either way.
61
61
 
62
62
  ### repos — host-blind encrypted git repos (zero deploy ceremony)
63
63
 
@@ -78,19 +78,19 @@ run402 repos delete --project prj_xyz --force # refuses without --force while th
78
78
 
79
79
  A hosted git remote, encrypted before it leaves the machine — no deploy, no manifest, no app. `origin` is claimed additively — an existing `origin` is never touched, the run402 remote falls back to `run402` instead. A directory inside another repository is never scaffolded into that repository: the skip carries a `create_nested_repo` next action, `--nested` makes it its own nested repository (one line appended to the enclosing repository's local `.git/info/exclude`, nothing else touched), and `create` prints a `git push` next action only for a remote it actually added. Every mutating `repos` verb (`create`, `rename`, `delete`, `snapshot`, `policy`, `mirror`, `gc`, `handoff`, `resume`, `invite`, `join`) is CLI/OpenClaw-only by design — no MCP tool exists or will exist for them (one-shot recovery receipts, immutable generations, irreversible delete, single-use bearer keys). Three READ-ONLY tools do exist — `repos_view`, `repos_list_heads`, `repos_fsck` — teaching only `repos` spellings. See `run402 repos --help` for the full tiered surface (common: `create`/`view`/`list`; occasional: `snapshot`/`mirror`/`recover`/`handoff`/`resume`/`invite`/`join`; lifecycle: `rename`/`delete`; maintenance: `fsck`/`gc`/`access`/`policy`), and the CLI reference's `repos` section for the terminal-loss statement and the progressive backup warning.
80
80
 
81
- **Handoff / resume.** `run402 repos handoff [--note-file <path>]` captures the actual working tree (staged/unstaged/untracked, like `git stash push -u`) into the vault and mints a single-use bearer key (`kgh1_…`, printed to stdout exactly once); `run402 repos resume <kgh1_…>` claims it on another machine, clones fresh, and reapplies the exact dirty state with `git stash apply --index`. No shared keystore, no shared allowance — the key is the whole handoff. On a wallet with no active tier, `resume` first folds the same cold-start chain `create` does (allowance → faucet → one x402 prototype payment); `--no-init` opts out, and the claim itself never waits on it. Sensitive untracked files (`.env`, `*.pem`, SSH/AWS/GPG dirs, …) are excluded from capture by default (`--include-sensitive <glob>` re-admits one). See the reference `run402.com/llms-cli.txt` for the full note-schema and flag reference.
81
+ **Handoff / resume.** `run402 repos handoff [--note-file <path>]` captures the actual working tree (staged/unstaged/untracked, like `git stash push -u`) into the vault and mints a single-use bearer key (`kgh1_…`, printed to stdout exactly once); `run402 repos resume <kgh1_…>` claims it on another machine, clones fresh, and reapplies the exact dirty state with `git stash apply --index`. No shared keystore, no shared wallet — the key is the whole handoff. On a wallet with no active tier, `resume` first folds the same cold-start chain `create` does (wallet → faucet → one x402 prototype payment); `--no-init` opts out, and the claim itself never waits on it. Sensitive untracked files (`.env`, `*.pem`, SSH/AWS/GPG dirs, …) are excluded from capture by default (`--include-sensitive <glob>` re-admits one). See the reference `run402.com/llms-cli.txt` for the full note-schema and flag reference.
82
82
 
83
83
  **Invite / join.** The second claim kind beside handoff/resume: a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `run402 repos invite [--room <key>] [--note-file <path>]` captures the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — registers the inviter's presence in a coordination room (the project's default room, or `--room <key>` for a named org room), mints a single-use bearer key (`kgi1_…`, printed to stdout exactly once), and posts ONE room message naming the checkpoint (never the key). `run402 repos join <kgi1_…>` folds the SAME cold-start chain `resume` does before the claim, clones fresh, restores the exact dirty state, pins the invite's room locally, registers its own presence, posts ONE arrival message, and reports the inviter (name, labels, liveness), the room's other live presences, and the last few messages. `run402 messages wait` is the room's blocking read from there — the agent's ear, never an error on silence. Neither `invite` nor `join` has an MCP tool, same reasoning as `handoff`/`resume`.
84
84
 
85
- **Named addressing.** `run402 org slug <slug>` (owner-only, small one-time fee) claims an org's globally-unique, address-form slug, after which `run402::<slug>/<name>` addresses any repo under it — `git push` to a name that doesn't exist yet push-to-creates it. `run402 repos rename <name> [--project <id>]` claims the per-org-unique `<name>` half explicitly (no fee); `repos create` claims one automatically, best-effort, when the org already has a slug. Also CLI/SDK-only — no MCP tool.
85
+ **Named addressing.** `run402 org slug <slug>` (owner-only, small one-time fee) sets an org's globally-unique, address-form slug, after which `run402::<slug>/<name>` addresses any repo under it — `git push` to a name that doesn't exist yet push-to-creates it. `run402 repos rename <name> [--project <id>]` claims the per-org-unique `<name>` half explicitly (no fee); `repos create` claims one automatically, best-effort, when the org already has a slug. Also CLI/SDK-only — no MCP tool.
86
86
 
87
- ### Allowance
87
+ ### Wallet
88
88
 
89
89
  ```bash
90
- run402 allowance create # generate the local allowance
91
- run402 allowance fund # request testnet USDC from the faucet
92
- run402 allowance balance # mainnet + testnet + billing balance
93
- run402 allowance export # print {"address":"0x..."} for funding
90
+ run402 wallets new default # generate the local wallet without funding it
91
+ run402 wallets fund # request testnet USDC from the faucet
92
+ run402 wallets balance # mainnet + testnet USDC, plus the organization's allowance
93
+ run402 wallets current # the active wallet's address (for funding), rail, and file path
94
94
  ```
95
95
 
96
96
  ### Promo codes
@@ -100,9 +100,9 @@ run402 redeem R402-K8F3-Q2W9 # any time, before or after `run402 init`
100
100
  run402 init --voucher R402-K8F3-Q2W9 # same redemption, folded into setup
101
101
  ```
102
102
 
103
- A promo code credits the organization with prepaid credit that settles tiers and
103
+ A promo code adds to the organization's allowance, which settles tiers and
104
104
  priced calls with no on-chain payment. Case-insensitive, hyphens optional.
105
- Retrying is safe — a repeat returns the original result and never credits twice.
105
+ Retrying is safe — a repeat returns the original result and never adds twice.
106
106
  A bad code never blocks `init`; it warns and records `voucher_error` in the JSON
107
107
  summary.
108
108
 
@@ -155,14 +155,14 @@ run402 projects schema <id> # introspect tables + R
155
155
 
156
156
  ```bash
157
157
  run402 sites deploy-dir ./dist # incremental upload (plan/commit transport)
158
- run402 deploy apply --manifest app.json # one-call full stack deploy
159
- run402 apply --manifest app.json --rehearse --json
158
+ run402 deploy --manifest app.json # deploy only (`run402 up` does missing setup first, then this)
159
+ run402 deploy status op_... --project prj_...
160
160
  run402 deploy verify op_... --project prj_... --wait
161
- run402 deploy release active # inspect current-live release inventory
162
- run402 deploy release diff --from empty --to active
163
- run402 deploy diagnose --project prj_123 https://example.com/events --method GET
164
- run402 deploy resolve --project prj_123 --url https://example.com/events?utm=x#hero --method GET
165
- run402 subdomains claim my-app # → my-app.run402.com (auto-reassigns on next deploy)
161
+ run402 deploy releases active # inspect current-live release inventory
162
+ run402 deploy releases diff --from empty --to active
163
+ run402 deploy resolve https://example.com/events --project prj_123 --method GET
164
+ run402 deploy resolve --url https://example.com/events?utm=x#hero --project prj_123 --method GET
165
+ run402 subdomains add my-app # → my-app.run402.com (auto-reassigns on next deploy)
166
166
  ```
167
167
 
168
168
  For pointer-swap recovery, verify the returned operation before declaring the public rollback complete:
@@ -175,13 +175,13 @@ run402 deploy verify --operation OPERATION_ID --wait
175
175
  Promote success means the origin pointer is active; mutable public URLs may still be converging. The result's `edge.state` reports convergence, and `edge.verify_url` is the direct operation-scoped HTTP verification endpoint.
176
176
 
177
177
  `deploy-dir` hashes each file client-side and only uploads bytes the gateway doesn't already have. Re-deploying an unchanged tree returns immediately with `bytes_uploaded: 0`. Progress events stream to stderr.
178
- Release inspection commands print `{ release: ... }` or `{ diff: ... }` (raw payload, no envelope — see the "Output Contract" section in [llms-cli.txt](llms-cli.txt)); use them after deploys to compare release inventory without starting another mutation. `deploy verify` prints the canonical edge-coherence report and exits 2 when the report is valid but not yet coherent. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata`; diffs include `static_assets` counters such as unchanged/changed/added/removed and CAS byte reuse. `deploy diagnose` / `deploy resolve --url` print URL-first diagnostics with `would_serve`, `diagnostic_status`, `match`, warnings, `edge_propagation`, and next steps; host misses are successful diagnostic calls with `would_serve: false`. Stable-host resolve fields can include `authorization_result`, `cas_object`, `response_variant`, `allow`, `route_pattern`, `target_type`, `target_name`, `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`).
178
+ Release inspection commands print `{ release: ... }` or `{ diff: ... }` (raw payload, no envelope — see the "Output Contract" section in [llms-cli.txt](llms-cli.txt)); use them after deploys to compare release inventory without starting another mutation. `deploy verify` prints the canonical edge-coherence report and exits 2 when the report is valid but not yet coherent. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata`; diffs include `static_assets` counters such as unchanged/changed/added/removed and CAS byte reuse. `deploy resolve <url>` (or `--url` / `--host` + `--path`) prints URL diagnostics with `would_serve`, `diagnostic_status`, `match`, warnings, `edge_propagation`, and next steps; host misses are successful diagnostic calls with `would_serve: false`. Stable-host resolve fields can include `authorization_result`, `cas_object`, `response_variant`, `allow`, `route_pattern`, `target_type`, `target_name`, `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`).
179
179
 
180
- For database-bearing changes, use `run402 apply --manifest app.json --rehearse --json` before commit. It creates a contained branch, applies the candidate plan there, runs checks, and exits nonzero on a failed rehearsal. The automatic rehearsal inside `up` / `apply` reports `rehearsal: { status: "skipped", reason }` when there is nothing to protect: `no_live_release` on a first deploy, `no_migrations`, or `migrations_unchanged` when every migration is already applied with an identical checksum (a page-only redeploy that still carries its migrations ships in seconds). `PUBLIC_ROUTED_FUNCTION` is informational (`requires_confirmation: false`) and needs no `--allow-warning`; only warnings with `requires_confirmation: true` do. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; temporary data branches live under `run402 branches create|list|renew|delete`.
180
+ For database-bearing changes, rehearsal happens before commit without a flag: `run402 deploy --manifest app.json --json` (or `run402 up`) creates a contained branch, applies the candidate plan there, runs checks, and stops on a failed rehearsal; `--no-rehearse` skips it and `run402 deploy rehearse <plan_id>` rehearses a persisted plan by hand. The automatic rehearsal inside `up` / `deploy` reports `rehearsal: { status: "skipped", reason }` when there is nothing to protect: `no_live_release` on a first deploy, `no_migrations`, or `migrations_unchanged` when every migration is already applied with an identical checksum (a page-only redeploy that still carries its migrations ships in seconds). `PUBLIC_ROUTED_FUNCTION` is informational (`requires_confirmation: false`) and needs no `--allow-warning`; only warnings with `requires_confirmation: true` do. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; temporary data branches live under `run402 branches create|list|renew|delete`.
181
181
 
182
182
  ### GitHub Actions OIDC deploys
183
183
 
184
- Link once from a local shell that has your Run402 allowance, then commit the generated workflow and manifest:
184
+ Link once from a local shell that has your Run402 wallet, then commit the generated workflow and manifest:
185
185
 
186
186
  ```bash
187
187
  run402 ci link github --project prj_... --manifest run402.deploy.json
@@ -203,10 +203,10 @@ jobs:
203
203
  steps:
204
204
  - uses: actions/checkout@v4
205
205
  - name: Deploy to run402
206
- run: npx --yes run402@3.7.5 deploy apply --manifest 'run402.deploy.json' --project 'prj_...'
206
+ run: npx --yes run402@3.7.5 deploy --manifest 'run402.deploy.json' --project 'prj_...'
207
207
  ```
208
208
 
209
- CI deploys can ship `site`, `functions`, `database`, and absent/current `base` changes. Route declarations are allowed only when the binding was linked with covering `--route-scope` patterns (`/admin` exact, `/api/*` final wildcard); no scopes means no CI route authority. Keep secrets, domains, subdomains, checks, non-current base changes, and out-of-scope routes in a local `run402 deploy apply` where the full allowance-backed authority is present.
209
+ CI deploys can ship `site`, `functions`, `database`, and absent/current `base` changes. Route declarations are allowed only when the binding was linked with covering `--route-scope` patterns (`/admin` exact, `/api/*` final wildcard); no scopes means no CI route authority. Keep secrets, domains, subdomains, checks, non-current base changes, and out-of-scope routes in a local `run402 deploy` where the full wallet-backed authority is present.
210
210
 
211
211
  ### Storage (paste-and-go CDN assets)
212
212
 
@@ -253,7 +253,7 @@ import { db, adminDb, auth, email, ai } from "@run402/functions";
253
253
 
254
254
  ### Same-origin web routes
255
255
 
256
- `run402 deploy apply` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `site.embedding.frame_ancestors` (catalog keys, e.g. `["localhost"]`, never raw origins) lets the named embedders put the site in an iframe: the gateway sends `Content-Security-Policy: frame-ancestors http://localhost:* http://127.0.0.1:*` and drops `X-Frame-Options` on every response of the host; omitted on a later apply carries the previous declaration forward, `null` returns to deny, unknown keys fail `INVALID_SPEC` naming the valid ones, and `/_run402/config.json` discloses the effective policy cross-origin so an embedder can check before framing. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Recipe — static home page + SPA shell: a root alias `{ "pattern": "/", "target": { "type": "static", "file": "home.html" } }` (with `home.html` at the site root) serves real static bytes at `GET /` (`route_static_alias`) while unmatched app routes keep the `index.html` shell (`spa_fallback`); expect non-blocking `STATIC_ALIAS_SHADOWS_STATIC_PATH` (warn) / `STATIC_ALIAS_DUPLICATE_CANONICAL_URL` (info) plan lints and verify with `run402 deploy resolve --url https://<your-site>/ --method GET`. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
256
+ `run402 deploy` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `site.embedding.frame_ancestors` (catalog keys, e.g. `["localhost"]`, never raw origins) lets the named embedders put the site in an iframe: the gateway sends `Content-Security-Policy: frame-ancestors http://localhost:* http://127.0.0.1:*` and drops `X-Frame-Options` on every response of the host; omitted on a later apply carries the previous declaration forward, `null` returns to deny, unknown keys fail `INVALID_SPEC` naming the valid ones, and `/_run402/config.json` discloses the effective policy cross-origin so an embedder can check before framing. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Recipe — static home page + SPA shell: a root alias `{ "pattern": "/", "target": { "type": "static", "file": "home.html" } }` (with `home.html` at the site root) serves real static bytes at `GET /` (`route_static_alias`) while unmatched app routes keep the `index.html` shell (`spa_fallback`); expect non-blocking `STATIC_ALIAS_SHADOWS_STATIC_PATH` (warn) / `STATIC_ALIAS_DUPLICATE_CANONICAL_URL` (info) plan lints and verify with `run402 deploy resolve --url https://<your-site>/ --method GET`. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
257
257
 
258
258
  ### Secrets
259
259
 
@@ -261,7 +261,7 @@ import { db, adminDb, auth, email, ai } from "@run402/functions";
261
261
  run402 secrets set <id> OPENAI_API_KEY --file ./.secrets/openai-key
262
262
  printf %s "$OPENAI_API_KEY" | run402 secrets set <id> OPENAI_API_KEY --stdin
263
263
  run402 secrets list <id>
264
- run402 deploy apply --manifest run402.deploy.json # manifest uses secrets.require, not values
264
+ run402 deploy --manifest run402.deploy.json # manifest uses secrets.require, not values
265
265
  ```
266
266
 
267
267
  Secret values are write-only. `list` returns keys and timestamps only; deploy manifests should declare dependencies with `secrets.require` and never contain values.
@@ -289,7 +289,7 @@ run402 email send --to user@example.com --subject "Welcome" --html "<h1>Hi</h1>"
289
289
  run402 email send --to user@example.com --template notification --var project_name="My App"
290
290
  ```
291
291
 
292
- For Run402 Core, use the same commands after `run402 init --api-base=http://my-core:4020`. The Core gateway operator must configure an outbound provider such as SES first; `run402 email mailboxes` surfaces `provider_readiness`, `can_send`, `send_blocked_reason`, and `next_actions` when setup is missing. Core's first email slice supports raw outbound mail with attachments; managed templates, inbound reply handling, sender-domain automation, and delivery operations may remain Cloud-only until the Core gateway adds those capabilities.
292
+ For Run402 Core, use the same commands after `run402 init --api-base=http://my-core:4020`. Whoever runs the Core gateway must configure an outbound provider such as SES first; `run402 email mailboxes` surfaces `provider_readiness`, `can_send`, `send_blocked_reason`, and `next_actions` when setup is missing. Core's first email slice supports raw outbound mail with attachments; managed templates, inbound reply handling, sender-domain automation, and delivery operations may remain Cloud-only until the Core gateway adds those capabilities.
293
293
 
294
294
  ### Image generation
295
295
 
@@ -323,18 +323,18 @@ run402 doctor # { ok, blocking[], warnings[], checks[] }
323
323
  run402 doctor --refresh # live npm check for a newer run402
324
324
  ```
325
325
 
326
- `ok` is structural: `true` exactly when `blocking[]` is empty. Every check carries `severity: "blocking" | "advisory" | "info"`; advisory findings (an unbound operator passkey, a degraded recovery posture on another org, a stale CLI, gitvault gaps) land in `warnings[]` without changing `ok` or the exit code. The `tier` check's `status` is a fixed vocabulary (`ok | inactive | frozen | past_due | dormant | purged | missing | unknown | error`, never a tier name; see `value.tier` / `value.lifecycle`), and a wallet whose own org holds no tier but can reach another org's projects reports `missing` as advisory (`TIER_MISSING_ON_OWN_ORG`).
326
+ `ok` is structural: `true` exactly when `blocking[]` is empty. Every check carries `severity: "blocking" | "advisory" | "info"`; advisory findings (an unbound contact passkey, a degraded recovery posture on another org, a stale CLI, gitvault gaps) land in `warnings[]` without changing `ok` or the exit code. The `tier` check's `status` is a fixed vocabulary (`ok | inactive | frozen | past_due | dormant | purged | missing | unknown | error`, never a tier name; see `value.tier` / `value.lifecycle`), and a wallet whose own org holds no tier but can reach another org's projects reports `missing` as advisory (`TIER_MISSING_ON_OWN_ORG`).
327
327
 
328
328
  ## State
329
329
 
330
330
  Local state lives at:
331
331
 
332
332
  - `~/.config/run402/projects.json` (`0600`) — project credentials (`anon_key`, `service_key`, `tier`, `lease_expires_at`)
333
- - `~/.config/run402/allowance.json` (`0600`) — wallet for x402 signing
333
+ - `~/.config/run402/wallet.json` (`0600`) — wallet for x402 signing
334
334
  - `~/.config/run402/config.json` (`0600`) — global default wallet pointer (`active_wallet`)
335
- - `~/.config/run402/profiles/<name>/` (`0700`) — named wallets, each with its own `allowance.json` + `projects.json` + non-secret `meta.json`
335
+ - `~/.config/run402/profiles/<name>/` (`0700`) — named wallets, each with its own `wallet.json` + `projects.json` + non-secret `meta.json`
336
336
 
337
- Override the base directory with `RUN402_CONFIG_DIR` or the allowance file with `RUN402_ALLOWANCE_PATH`. Override the API base with `RUN402_API_BASE`.
337
+ Override the base directory with `RUN402_CONFIG_DIR` or the wallet file with `RUN402_WALLET_PATH`. Override the API base with `RUN402_API_BASE`.
338
338
 
339
339
  ### Named wallets (profiles)
340
340
 
@@ -346,16 +346,18 @@ Hold several wallets on one machine and select between them:
346
346
 
347
347
  The CLI handles all x402 / MPP payment signing automatically — never ask the human for a private key or set up payment libraries by hand.
348
348
 
349
- ### Operator (human / email session)
349
+ ### Sign-in session (a person)
350
350
 
351
- The **operator** is YOU, the human, identified by email — distinct from the agent (your wallet). One browser login spans every wallet that verified your email, so the overview is a cross-wallet union. For a single wallet's account state, use `run402 status`.
351
+ A person signs in; the agent is your wallet (`run402 status` shows the wallet's own organization state). There is one sign-in session, graded by how it was minted.
352
352
 
353
- - `run402 operator login` — browser-delegated sign-in (device-authorization, RFC 8628, like `aws sso login`): magic-link or passkey in the browser, no WebAuthn in the CLI. Caches an email-scoped session at the base config dir (shared across named wallets).
354
- - `run402 operator overview` — account view across ALL wallets controlling your email (requires login; never falls back to a single wallet).
355
- - `run402 operator whoami` — show the cached session (email, wallets, expiry); local, no network.
356
- - `run402 operator logout` — revoke the session server-side and clear the local cache.
353
+ - `run402 login` — browser passkey sign-in over loopback PKCE (RFC 8252, like `aws sso login`): the CLI listens on `127.0.0.1`, the browser runs the ceremony, and the session (grade `loopback`: full, step-up-able) is cached at the base config dir (shared across named wallets). Signing in again is the step-up.
354
+ - `run402 login --device` — RFC 8628 device code for a terminal with no browser; approve it in the console. The session is grade `device`: read-only.
355
+ - `run402 whoami` — principal, memberships, `session.grade`, and the write approvals cached on this machine.
356
+ - `run402 approve --action <capability> (--org <org_id> | --project <project_id>)` — passkey-signed write approval for one action on one target; a person without a wallet needs it to provision, deploy, or write secrets (an interactive `provision`/`deploy` opens it for you).
357
+ - `run402 logout` — revoke the session server-side and clear it and every write approval locally.
358
+ - `run402 org list` — your orgs joined with the account overview (tier, lifecycle, quotas, allowance, advisories).
357
359
 
358
- Not exposed as MCP tools by design — MCP authenticates as the agent (wallet), and the human session must not be handed to it.
360
+ Not exposed as MCP tools by design — MCP authenticates as the agent (wallet), and a person's session must not become its ambient authority.
359
361
 
360
362
  ## Active project (sticky default)
361
363
 
package/cli.mjs CHANGED
@@ -31,20 +31,18 @@ Usage:
31
31
  Commands, grouped by when you reach for them:
32
32
 
33
33
  SET UP — get an agent funded and entitled
34
- init Set up allowance, funding, and check tier status (x402 default)
34
+ init Set up the local wallet, funding, and check tier status (x402 default)
35
35
  init mpp Set up with MPP payment rail (Tempo Moderato testnet)
36
- wallets Manage multiple named wallets (list, new, use, rename, bind, import)
37
- allowance Manage your agent allowance (create, fund, balance, status)
38
- tier Manage tier subscription (status, set)
39
- redeem Redeem a promo code for run402 credit
36
+ wallets Manage local wallets (list, current, new, use, fund, balance, import)
37
+ tier Manage your tier and its lease (status, set)
38
+ redeem Redeem a promo code into your organization's allowance
40
39
  pay Call an arbitrary x402-priced URL with a bounded payment
41
40
 
42
41
  BUILD & SHIP — the app and everything it serves
43
42
  up Provision/link/deploy the current app with SDK orchestration
44
43
  projects Manage projects (provision, list, get, sql, delete)
45
44
  repos Host-blind encrypted git repos: create/view/list, snapshot/mirror/recover, fsck/gc/access/policy (repo aliases repos)
46
- deploy Unified deploy operations (requires active tier)
47
- apply Alias for deploy apply; supports --rehearse for migration rehearsal
45
+ deploy Deploy a release; subcommands inspect and steer deploys (requires active tier)
48
46
  functions Manage serverless functions (deploy, invoke, logs, list, delete)
49
47
  secrets Manage project secrets (set, list, delete)
50
48
  sites Deploy static sites
@@ -57,7 +55,7 @@ OPERATE — what happened, and is it healthy
57
55
  live Change hints for live tables as NDJSON (SSE; --once for one held read)
58
56
  errors Grouped error fingerprints + a promote/revert verdict (release-baselined)
59
57
  logs Fetch function logs by request id (--request-id req_...)
60
- status Show full account state (allowance, balance, tier, projects)
58
+ status Show full account state (wallet, allowance, tier, projects)
61
59
  doctor Health and config diagnostics (JSON by default; includes --buzz preflight)
62
60
  service Run402 service health and availability (status, health)
63
61
  snapshots Create/list/restore/delete project data snapshots
@@ -78,13 +76,16 @@ AUTHORITY — who may act, and with what credential
78
76
  identity Public proof-backed external agent identity links
79
77
  auth Manage project user authentication (magic link, passwords, settings)
80
78
  ci Link GitHub Actions OIDC deploy bindings
81
- operator Operator (human/email) session — login, then overview across your wallets
79
+ login Sign in as a person (browser passkey; --device for a read-only session)
80
+ logout End your sign-in session and clear it from this machine
81
+ whoami Who this CLI acts as: principal, memberships, session grade
82
+ approve Write approval for one action on one target (provision, deploy, secrets)
82
83
 
83
84
  DELIVER — reach a human when something happens
84
85
  deliveries Did a notification actually land (list, get)
85
86
  contacts Where a human is reachable — paging ladder + Telegram (list, add, connect, rm, preferences)
86
87
  subscriptions Which events go where (add, list, rm)
87
- webhook-secret Rotate the operator webhook signing secret
88
+ webhook-secret Rotate your webhook signing secret
88
89
  email Send template-based emails from your project
89
90
 
90
91
  PLATFORM — everything else, and the things still finding a home
@@ -92,7 +93,7 @@ PLATFORM — everything else, and the things still finding a home
92
93
  billing Email organizations, Stripe tier checkout, email packs
93
94
  contracts KMS signers ($0.04/day rental + $0.000005/sign)
94
95
  jobs Submit and inspect platform-managed jobs
95
- transfer Two-party project transfer (init, preview, list, accept, cancel)
96
+ transfer Project transfer to a wallet, an email, or an owned org (init, preview, list, accept, cancel)
96
97
  cloud Cloud portability archive export (archives create/download/status)
97
98
  archives Inspect and verify portable project archives locally
98
99
  gitvault RETIRED — moved to repos (repo-surface-consolidation); every spelling answers COMMAND_MOVED/COMMAND_REMOVED
@@ -115,11 +116,10 @@ Run 'run402 <command> --help' for detailed usage of each command.
115
116
 
116
117
  Examples:
117
118
  run402 up --name my-app -y
118
- run402 allowance create
119
- run402 allowance fund
119
+ run402 init
120
+ run402 wallets fund
120
121
  run402 pay https://seller.example/resource --max-usd 0.05
121
- run402 deploy apply --manifest app.json
122
- run402 apply --manifest app.json --rehearse --json
122
+ run402 deploy --manifest app.json
123
123
  run402 snapshots list --project prj_...
124
124
  run402 branches create --project prj_... --ttl-days 7 --json
125
125
  run402 cloud archives create --project prj_... --wait --output ./project.r402ar --json
@@ -134,7 +134,7 @@ Examples:
134
134
  Getting started:
135
135
  run402 init Set up with x402 (Base Sepolia)
136
136
  run402 init mpp Set up with MPP (Tempo Moderato)
137
- run402 tier set prototype Subscribe to a tier
137
+ run402 tier set prototype Set a tier (prototype is free)
138
138
  run402 up --name my-app -y Create and deploy this app (explicit destination)
139
139
  run402 ci link github --project prj_... --manifest run402.deploy.json
140
140
  `;
@@ -219,11 +219,6 @@ switch (cmd) {
219
219
  await run(sub, rest);
220
220
  break;
221
221
  }
222
- case "allowance": {
223
- const { run } = await import("./lib/allowance.mjs");
224
- await run(sub, rest);
225
- break;
226
- }
227
222
  case "tier": {
228
223
  const { run } = await import("./lib/tier.mjs");
229
224
  await run(sub, rest);
@@ -247,11 +242,6 @@ switch (cmd) {
247
242
  await run(sub, rest);
248
243
  break;
249
244
  }
250
- case "apply": {
251
- const { runDeployV2 } = await import("./lib/deploy-v2.mjs");
252
- await runDeployV2("apply", [sub, ...rest].filter(Boolean));
253
- break;
254
- }
255
245
  case "snapshots": {
256
246
  const { run } = await import("./lib/snapshots.mjs");
257
247
  await run(sub, rest);
@@ -450,9 +440,24 @@ switch (cmd) {
450
440
  await run(sub, rest);
451
441
  break;
452
442
  }
453
- case "operator": {
454
- const { run } = await import("./lib/operator.mjs");
455
- await run(sub, rest);
443
+ case "login": {
444
+ const { run } = await import("./lib/login.mjs");
445
+ await run([sub, ...rest].filter(Boolean));
446
+ break;
447
+ }
448
+ case "logout": {
449
+ const { run } = await import("./lib/logout.mjs");
450
+ await run([sub, ...rest].filter(Boolean));
451
+ break;
452
+ }
453
+ case "whoami": {
454
+ const { run } = await import("./lib/whoami.mjs");
455
+ await run([sub, ...rest].filter(Boolean));
456
+ break;
457
+ }
458
+ case "approve": {
459
+ const { run } = await import("./lib/approve.mjs");
460
+ await run([sub, ...rest].filter(Boolean));
456
461
  break;
457
462
  }
458
463
  case "source-access": {
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { dirname, join } from "node:path";
3
- import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { describeRejectedValue } from "./redact.js";
6
6
  export const DEFAULT_API_BASE = "https://api.run402.com";
@@ -118,7 +118,7 @@ export function getProfilesDir() {
118
118
  * The effective config directory for the *active* wallet. The `default` wallet
119
119
  * resolves to the base dir (zero migration for existing single-wallet
120
120
  * installs); any named wallet resolves to `{base}/profiles/<name>`. Because
121
- * keystore, allowance, and meta paths all derive from this, switching the
121
+ * keystore, wallet, and meta paths all derive from this, switching the
122
122
  * profile env var moves the whole wallet bundle atomically — and the SDK/MCP
123
123
  * inherit profile selection for free.
124
124
  */
@@ -232,27 +232,61 @@ export function isCoreApiTarget() {
232
232
  return true;
233
233
  return false;
234
234
  }
235
- export function getAllowancePath() {
236
- if (process.env.RUN402_ALLOWANCE_PATH)
237
- return process.env.RUN402_ALLOWANCE_PATH;
238
- const dir = getConfigDir();
239
- const newPath = join(dir, "allowance.json");
240
- const oldPath = join(dir, "wallet.json");
241
- // Auto-migrate from wallet.json → allowance.json. renameSync preserves the
242
- // source file's mode, so a legacy world-readable wallet.json (mode 0644)
243
- // would otherwise carry that mode forward and leave the private key
244
- // world-readable on a shared machine. Tighten to 0600 after the rename.
245
- if (!existsSync(newPath) && existsSync(oldPath)) {
246
- mkdirSync(dir, { recursive: true });
247
- renameSync(oldPath, newPath);
235
+ /**
236
+ * The file that holds a wallet's key, in the base config dir (the `default`
237
+ * wallet) and in each `profiles/<name>/` directory.
238
+ */
239
+ export const WALLET_FILE_NAME = "wallet.json";
240
+ /** The name the wallet key file had before it was `wallet.json`. */
241
+ const PREVIOUS_WALLET_FILE_NAME = "allowance.json";
242
+ const walletFilesMovedFor = new Set();
243
+ /**
244
+ * One-time local move of the wallet key file to its current name: in the base
245
+ * config dir and every `profiles/<name>/` directory, `allowance.json` is
246
+ * renamed to `wallet.json` (an atomic same-directory rename) when
247
+ * `wallet.json` is absent, then tightened to 0600. A directory that already
248
+ * holds `wallet.json` is left untouched. Runs once per base dir per process;
249
+ * nothing reads the previous name afterwards.
250
+ */
251
+ export function moveWalletFiles() {
252
+ const base = getConfigBaseDir();
253
+ if (walletFilesMovedFor.has(base))
254
+ return;
255
+ walletFilesMovedFor.add(base);
256
+ const dirs = [base];
257
+ try {
258
+ for (const entry of readdirSync(join(base, "profiles"), { withFileTypes: true })) {
259
+ if (entry.isDirectory())
260
+ dirs.push(join(base, "profiles", entry.name));
261
+ }
262
+ }
263
+ catch {
264
+ // No profiles directory yet.
265
+ }
266
+ for (const dir of dirs) {
267
+ const from = join(dir, PREVIOUS_WALLET_FILE_NAME);
268
+ const to = join(dir, WALLET_FILE_NAME);
269
+ if (existsSync(to) || !existsSync(from))
270
+ continue;
271
+ try {
272
+ renameSync(from, to);
273
+ }
274
+ catch {
275
+ continue;
276
+ }
248
277
  try {
249
- chmodSync(newPath, 0o600);
278
+ chmodSync(to, 0o600);
250
279
  }
251
280
  catch {
252
281
  // Best-effort (e.g. Windows / exotic filesystems). Read-time self-heal
253
- // in readAllowance() is the backstop.
282
+ // in readWallet() is the backstop.
254
283
  }
255
284
  }
256
- return newPath;
285
+ }
286
+ export function getWalletPath() {
287
+ if (process.env.RUN402_WALLET_PATH)
288
+ return process.env.RUN402_WALLET_PATH;
289
+ moveWalletFiles();
290
+ return join(getConfigDir(), WALLET_FILE_NAME);
257
291
  }
258
292
  //# sourceMappingURL=config.js.map
@@ -10,6 +10,14 @@ export function getControlPlaneSessionPath() {
10
10
  return (process.env.RUN402_CONTROL_PLANE_SESSION_PATH ||
11
11
  join(getConfigBaseDir(), "control-plane-session.json"));
12
12
  }
13
+ /**
14
+ * Read a wire grade. An unknown or absent value is treated as `device` (the
15
+ * read-only grade) so a cache the client cannot classify never claims more
16
+ * authority than the gateway will grant.
17
+ */
18
+ function toGrade(raw) {
19
+ return raw === "browser" || raw === "loopback" || raw === "device" ? raw : "device";
20
+ }
13
21
  /** Tighten 0600 if group/other-readable, warning once. Best-effort, POSIX-only. */
14
22
  function selfHealPermissions(p) {
15
23
  if (process.platform === "win32")
@@ -50,20 +58,20 @@ export function readControlPlaneSession(path) {
50
58
  return null;
51
59
  }
52
60
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
53
- throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402 operator login --loopback' to recreate it.");
61
+ throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402 login' to recreate it.");
54
62
  }
55
63
  const data = parsed;
56
64
  if (typeof data.control_plane_session_token !== "string" ||
57
65
  data.control_plane_session_token.length === 0) {
58
- throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402 operator login --loopback' to refresh it.");
66
+ throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402 login' to refresh it.");
59
67
  }
60
68
  if (typeof data.expires_at !== "number" || !Number.isFinite(data.expires_at)) {
61
- throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402 operator login --loopback' to refresh it.");
69
+ throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402 login' to refresh it.");
62
70
  }
63
71
  return {
64
72
  control_plane_session_token: data.control_plane_session_token,
65
73
  token_type: typeof data.token_type === "string" ? data.token_type : "Bearer",
66
- provenance: typeof data.provenance === "string" ? data.provenance : "loopback_pkce",
74
+ grade: toGrade(data.grade),
67
75
  principal_id: typeof data.principal_id === "string" ? data.principal_id : "",
68
76
  amr: Array.isArray(data.amr) ? data.amr.filter((a) => typeof a === "string") : [],
69
77
  expires_at: data.expires_at,
@@ -79,7 +87,7 @@ export function saveControlPlaneSession(data, path) {
79
87
  renameSync(tmp, p);
80
88
  chmodSync(p, 0o600);
81
89
  }
82
- /** Delete the cached control-plane session — local half of `operator logout`. Idempotent. */
90
+ /** Delete the cached sign-in session — local half of `run402 logout`. Idempotent. */
83
91
  export function clearControlPlaneSession(path) {
84
92
  const p = path ?? getControlPlaneSessionPath();
85
93
  try {
@@ -105,10 +113,27 @@ export function controlPlaneSessionFromTokenResponse(resp, nowMs = Date.now()) {
105
113
  return {
106
114
  control_plane_session_token: resp.control_plane_session_token,
107
115
  token_type: resp.token_type ?? "Bearer",
108
- provenance: resp.provenance ?? "loopback_pkce",
116
+ grade: toGrade(resp.grade),
109
117
  principal_id: resp.principal_id ?? "",
110
118
  amr: Array.isArray(resp.amr) ? resp.amr.filter((a) => typeof a === "string") : [],
111
119
  expires_at: nowMs + (typeof resp.expires_in === "number" ? resp.expires_in : 0) * 1000,
112
120
  };
113
121
  }
122
+ /**
123
+ * Delete the session caches of the retired email-scoped session
124
+ * (`{base}/operator-session.json`) and the retired approval cache file
125
+ * (`{base}/write-auth-session.json`). Neither is read any more; `run402 login`
126
+ * and `run402 logout` call this so a stale token does not linger on disk.
127
+ * Best-effort and idempotent.
128
+ */
129
+ export function clearRetiredSessionCaches() {
130
+ for (const name of ["operator-session.json", "write-auth-session.json"]) {
131
+ try {
132
+ rmSync(join(getConfigBaseDir(), name), { force: true });
133
+ }
134
+ catch {
135
+ // Best-effort: a failed unlink never blocks login or logout.
136
+ }
137
+ }
138
+ }
114
139
  //# sourceMappingURL=control-plane-session.js.map
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { getLegacyProjectsPath, getProjectCredentialsPath } from "./config.js";
5
5
  import { clearActiveProjectId as clearProfileActiveProjectId, getActiveProjectId as getProfileActiveProjectId, recordMigration, setActiveProjectId as setProfileActiveProjectId, } from "./profile-state.js";
6
- import { readAllowance } from "./allowance.js";
6
+ import { readWallet } from "./wallet.js";
7
7
  function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
8
8
  const lockDir = path + ".lock";
9
9
  mkdirSync(dirname(path), { recursive: true });
@@ -99,7 +99,7 @@ function migrateLegacyProjectsJson(targetPath) {
99
99
  // Scoped the same way every other write in this module now is (see
100
100
  // `currentPrincipal`'s doc comment) — an unscoped write here is exactly
101
101
  // what poisons the "unknown"-principal bucket for good on a machine that
102
- // migrates before its wallet allowance exists yet.
102
+ // migrates before its wallet exists yet.
103
103
  setProfileActiveProjectId(legacy.active_project_id, undefined, { principal: currentPrincipal() });
104
104
  }
105
105
  recordMigration("projects_json_import", {
@@ -174,7 +174,7 @@ export function removeProject(projectId, path) {
174
174
  }
175
175
  /**
176
176
  * The active-project scope's `principal`, derived from the CURRENT wallet's
177
- * allowance — matching exactly what `NodeCredentialsProvider.setActiveProject`
177
+ * wallet — matching exactly what `NodeCredentialsProvider.setActiveProject`
178
178
  * (sdk/src/node/credentials.ts) writes after `projects.provision` /
179
179
  * `projects.use`. Without this, every reader here used the profile-state
180
180
  * module's own default (empty) scope, which resolves to a FIXED
@@ -185,7 +185,7 @@ export function removeProject(projectId, path) {
185
185
  * `profile-state.ts#getActiveProjectId` happens to agree), but once the
186
186
  * "unknown" bucket is EVER populated by any principal-less write — the
187
187
  * one-time legacy `projects.json` migration below, or any provision/`use`
188
- * call made before this machine had a wallet allowance — it never gets
188
+ * call made before this machine had a wallet — it never gets
189
189
  * updated again (real wallet operations write to the principal-keyed
190
190
  * bucket, not "unknown") and PERMANENTLY shadows every later wallet-scoped
191
191
  * activation for every caller that reads through this module: `resolveProjectId`
@@ -197,13 +197,13 @@ export function removeProject(projectId, path) {
197
197
  * stale "unknown"-bucket entry first and never falls through to the
198
198
  * freshly-updated flat value.
199
199
  *
200
- * Best-effort: an unreadable/malformed allowance degrades to `null` (the
200
+ * Best-effort: an unreadable/malformed wallet degrades to `null` (the
201
201
  * "unknown" bucket callers tolerate), never a
202
- * throw — this is a read-scoping concern, not an allowance-validity one.
202
+ * throw — this is a read-scoping concern, not a wallet-validity one.
203
203
  */
204
204
  function currentPrincipal() {
205
205
  try {
206
- return readAllowance()?.address ?? null;
206
+ return readWallet()?.address ?? null;
207
207
  }
208
208
  catch {
209
209
  return null;