run402 4.93.2 → 4.95.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 (316) hide show
  1. package/README.md +61 -59
  2. package/cli.mjs +40 -47
  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 +2 -2
  11. package/lib/admin.mjs +6 -6
  12. package/lib/agent.mjs +20 -18
  13. package/lib/ai.mjs +5 -5
  14. package/lib/approve.mjs +81 -0
  15. package/lib/apps.mjs +12 -12
  16. package/lib/archives.mjs +371 -8
  17. package/lib/argparse.mjs +5 -5
  18. package/lib/assets.mjs +14 -14
  19. package/lib/auth.mjs +34 -34
  20. package/lib/billing.mjs +6 -6
  21. package/lib/branches.mjs +7 -7
  22. package/lib/buzz-doctor.mjs +2 -2
  23. package/lib/buzz-notifications.mjs +5 -5
  24. package/lib/buzz.mjs +3 -3
  25. package/lib/buzz.test.mjs +1 -1
  26. package/lib/cache.mjs +1 -1
  27. package/lib/cdn.mjs +2 -2
  28. package/lib/ci.mjs +12 -12
  29. package/lib/claims.mjs +2 -2
  30. package/lib/cold-start.mjs +21 -21
  31. package/lib/command-manifest.mjs +55 -70
  32. package/lib/config.mjs +28 -28
  33. package/lib/contacts.mjs +16 -16
  34. package/lib/contracts.mjs +12 -12
  35. package/lib/credentials.mjs +33 -33
  36. package/lib/daemon-path.mjs +1 -1
  37. package/lib/delegates.mjs +14 -14
  38. package/lib/deliveries.mjs +6 -6
  39. package/lib/deploy-rehearse-discovery.test.mjs +7 -7
  40. package/lib/deploy-v2.mjs +241 -193
  41. package/lib/deploy.mjs +21 -53
  42. package/lib/dev.mjs +3 -3
  43. package/lib/doctor-source-scan.mjs +1 -1
  44. package/lib/doctor.mjs +63 -63
  45. package/lib/domains.mjs +27 -27
  46. package/lib/email.mjs +38 -38
  47. package/lib/errors.mjs +40 -24
  48. package/lib/escalations.mjs +5 -5
  49. package/lib/events.mjs +32 -25
  50. package/lib/feedback.mjs +12 -12
  51. package/lib/functions.mjs +34 -34
  52. package/lib/gitvault-daemon.mjs +1 -1
  53. package/lib/gitvault-scaffold.mjs +2 -2
  54. package/lib/gitvault.mjs +2 -2
  55. package/lib/grants.mjs +7 -7
  56. package/lib/image.mjs +10 -10
  57. package/lib/init.mjs +69 -69
  58. package/lib/jobs.mjs +15 -15
  59. package/lib/lightning-wallet.mjs +28 -28
  60. package/lib/live.mjs +1 -1
  61. package/lib/login.mjs +207 -0
  62. package/lib/logout.mjs +53 -0
  63. package/lib/logs.mjs +8 -8
  64. package/lib/messages.mjs +5 -5
  65. package/lib/next-actions.mjs +16 -16
  66. package/lib/org-context.mjs +13 -13
  67. package/lib/{org.mjs → orgs.mjs} +215 -167
  68. package/lib/pay.mjs +1 -1
  69. package/lib/projects.mjs +70 -84
  70. package/lib/redeem.mjs +7 -8
  71. package/lib/remote-helper-session.mjs +12 -12
  72. package/lib/repos.mjs +83 -83
  73. package/lib/rooms.mjs +105 -69
  74. package/lib/sdk-errors.mjs +10 -5
  75. package/lib/sdk.mjs +3 -3
  76. package/lib/secrets.mjs +13 -13
  77. package/lib/service.mjs +5 -5
  78. package/lib/session-proofs.mjs +21 -0
  79. package/lib/sign-in.mjs +296 -0
  80. package/lib/sites.mjs +22 -22
  81. package/lib/snapshots.mjs +10 -10
  82. package/lib/stats.mjs +2 -2
  83. package/lib/status.mjs +25 -25
  84. package/lib/subdomains.mjs +30 -30
  85. package/lib/subscriptions.mjs +10 -10
  86. package/lib/tier.mjs +29 -22
  87. package/lib/transfer.mjs +59 -86
  88. package/lib/up.mjs +14 -14
  89. package/lib/wallet-context.mjs +4 -4
  90. package/lib/wallet-context.test.mjs +3 -3
  91. package/lib/wallets.mjs +197 -32
  92. package/lib/webhook-secret.mjs +6 -6
  93. package/lib/webhooks.mjs +8 -8
  94. package/lib/whoami.mjs +102 -0
  95. package/package.json +1 -1
  96. package/sdk/core-dist/config.js +52 -18
  97. package/sdk/core-dist/control-plane-session.js +31 -6
  98. package/sdk/core-dist/keystore.js +7 -7
  99. package/sdk/core-dist/profiles.js +9 -7
  100. package/sdk/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
  101. package/{core-dist/allowance.js → sdk/core-dist/wallet.js} +20 -20
  102. package/{core-dist/write-auth-session.js → sdk/core-dist/write-approvals.js} +19 -19
  103. package/sdk/dist/actions.d.ts +4 -4
  104. package/sdk/dist/actions.d.ts.map +1 -1
  105. package/sdk/dist/app-up.d.ts +2 -2
  106. package/sdk/dist/app-up.d.ts.map +1 -1
  107. package/sdk/dist/config.d.ts +1 -2
  108. package/sdk/dist/config.d.ts.map +1 -1
  109. package/sdk/dist/config.js.map +1 -1
  110. package/sdk/dist/control-plane-credentials.d.ts +8 -8
  111. package/sdk/dist/control-plane-credentials.js +7 -7
  112. package/sdk/dist/credentials.d.ts +20 -20
  113. package/sdk/dist/credentials.d.ts.map +1 -1
  114. package/sdk/dist/credentials.js +1 -1
  115. package/sdk/dist/errors.d.ts +26 -28
  116. package/sdk/dist/errors.d.ts.map +1 -1
  117. package/sdk/dist/errors.js +35 -38
  118. package/sdk/dist/errors.js.map +1 -1
  119. package/sdk/dist/index.d.ts +29 -16
  120. package/sdk/dist/index.d.ts.map +1 -1
  121. package/sdk/dist/index.js +30 -17
  122. package/sdk/dist/index.js.map +1 -1
  123. package/sdk/dist/kernel.d.ts +1 -1
  124. package/sdk/dist/kernel.d.ts.map +1 -1
  125. package/sdk/dist/kernel.js +10 -9
  126. package/sdk/dist/kernel.js.map +1 -1
  127. package/sdk/dist/namespaces/admin.d.ts +52 -121
  128. package/sdk/dist/namespaces/admin.d.ts.map +1 -1
  129. package/sdk/dist/namespaces/admin.js +41 -45
  130. package/sdk/dist/namespaces/admin.js.map +1 -1
  131. package/sdk/dist/namespaces/agent.d.ts +2 -2
  132. package/sdk/dist/namespaces/agent.d.ts.map +1 -1
  133. package/sdk/dist/namespaces/agent.js +2 -2
  134. package/sdk/dist/namespaces/agent.js.map +1 -1
  135. package/sdk/dist/namespaces/ai.d.ts +2 -2
  136. package/sdk/dist/namespaces/ai.js +3 -3
  137. package/sdk/dist/namespaces/ai.js.map +1 -1
  138. package/sdk/dist/namespaces/assets.js +1 -1
  139. package/sdk/dist/namespaces/assets.js.map +1 -1
  140. package/sdk/dist/namespaces/billing.d.ts +6 -3
  141. package/sdk/dist/namespaces/billing.d.ts.map +1 -1
  142. package/sdk/dist/namespaces/billing.js.map +1 -1
  143. package/sdk/dist/namespaces/buzz-notifications.types.d.ts +1 -1
  144. package/sdk/dist/namespaces/buzz.d.ts +2 -2
  145. package/sdk/dist/namespaces/buzz.d.ts.map +1 -1
  146. package/sdk/dist/namespaces/buzz.js +2 -2
  147. package/sdk/dist/namespaces/buzz.js.map +1 -1
  148. package/sdk/dist/namespaces/buzz.types.d.ts +2 -2
  149. package/sdk/dist/namespaces/buzz.types.d.ts.map +1 -1
  150. package/sdk/dist/namespaces/ci.js +3 -3
  151. package/sdk/dist/namespaces/ci.js.map +1 -1
  152. package/sdk/dist/namespaces/credentials.types.d.ts +1 -1
  153. package/sdk/dist/namespaces/deploy.d.ts +1 -1
  154. package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
  155. package/sdk/dist/namespaces/deploy.js +31 -32
  156. package/sdk/dist/namespaces/deploy.js.map +1 -1
  157. package/sdk/dist/namespaces/deploy.types.d.ts +6 -6
  158. package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
  159. package/sdk/dist/namespaces/domains.d.ts +10 -2
  160. package/sdk/dist/namespaces/domains.d.ts.map +1 -1
  161. package/sdk/dist/namespaces/domains.js +10 -5
  162. package/sdk/dist/namespaces/domains.js.map +1 -1
  163. package/sdk/dist/namespaces/errors.js +1 -1
  164. package/sdk/dist/namespaces/escalations.d.ts +1 -1
  165. package/sdk/dist/namespaces/escalations.js +1 -1
  166. package/sdk/dist/namespaces/escalations.types.d.ts +2 -2
  167. package/sdk/dist/namespaces/escalations.types.d.ts.map +1 -1
  168. package/sdk/dist/namespaces/events.types.d.ts +1 -1
  169. package/sdk/dist/namespaces/functions.d.ts +1 -1
  170. package/sdk/dist/namespaces/functions.js +2 -2
  171. package/sdk/dist/namespaces/functions.js.map +1 -1
  172. package/sdk/dist/namespaces/gitvault.crypto.d.ts +1 -1
  173. package/sdk/dist/namespaces/gitvault.d.ts +26 -26
  174. package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
  175. package/sdk/dist/namespaces/gitvault.js +113 -113
  176. package/sdk/dist/namespaces/gitvault.js.map +1 -1
  177. package/sdk/dist/namespaces/me.d.ts +120 -0
  178. package/sdk/dist/namespaces/me.d.ts.map +1 -0
  179. package/sdk/dist/namespaces/me.js +34 -0
  180. package/sdk/dist/namespaces/me.js.map +1 -0
  181. package/sdk/dist/namespaces/org.d.ts +108 -6
  182. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  183. package/sdk/dist/namespaces/org.js +63 -7
  184. package/sdk/dist/namespaces/org.js.map +1 -1
  185. package/sdk/dist/namespaces/org.types.d.ts +29 -9
  186. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  187. package/sdk/dist/namespaces/pay.js +3 -3
  188. package/sdk/dist/namespaces/pay.js.map +1 -1
  189. package/sdk/dist/namespaces/projects.d.ts +9 -9
  190. package/sdk/dist/namespaces/projects.js +15 -15
  191. package/sdk/dist/namespaces/projects.js.map +1 -1
  192. package/sdk/dist/namespaces/projects.types.d.ts +28 -12
  193. package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
  194. package/sdk/dist/namespaces/rooms.d.ts +7 -7
  195. package/sdk/dist/namespaces/rooms.js +17 -17
  196. package/sdk/dist/namespaces/rooms.js.map +1 -1
  197. package/sdk/dist/namespaces/rooms.types.d.ts +9 -9
  198. package/sdk/dist/namespaces/rooms.types.d.ts.map +1 -1
  199. package/sdk/dist/namespaces/service.d.ts +1 -1
  200. package/sdk/dist/namespaces/service.js +1 -1
  201. package/sdk/dist/namespaces/{operator-session.d.ts → session.d.ts} +135 -51
  202. package/sdk/dist/namespaces/session.d.ts.map +1 -0
  203. package/sdk/dist/namespaces/{operator-session.js → session.js} +118 -30
  204. package/sdk/dist/namespaces/session.js.map +1 -0
  205. package/sdk/dist/namespaces/subdomains.d.ts +5 -5
  206. package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
  207. package/sdk/dist/namespaces/subdomains.js +6 -6
  208. package/sdk/dist/namespaces/subdomains.js.map +1 -1
  209. package/sdk/dist/namespaces/tier.d.ts +44 -21
  210. package/sdk/dist/namespaces/tier.d.ts.map +1 -1
  211. package/sdk/dist/namespaces/tier.js +7 -7
  212. package/sdk/dist/namespaces/tier.js.map +1 -1
  213. package/sdk/dist/namespaces/transfers.d.ts +119 -85
  214. package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
  215. package/sdk/dist/namespaces/transfers.js +31 -38
  216. package/sdk/dist/namespaces/transfers.js.map +1 -1
  217. package/sdk/dist/namespaces/vouchers.d.ts +9 -10
  218. package/sdk/dist/namespaces/vouchers.d.ts.map +1 -1
  219. package/sdk/dist/namespaces/vouchers.js +4 -5
  220. package/sdk/dist/namespaces/vouchers.js.map +1 -1
  221. package/sdk/dist/namespaces/wallets.d.ts +72 -3
  222. package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
  223. package/sdk/dist/namespaces/wallets.js +141 -3
  224. package/sdk/dist/namespaces/wallets.js.map +1 -1
  225. package/sdk/dist/namespaces/write-approval.d.ts +101 -0
  226. package/sdk/dist/namespaces/write-approval.d.ts.map +1 -0
  227. package/sdk/dist/namespaces/write-approval.js +62 -0
  228. package/sdk/dist/namespaces/write-approval.js.map +1 -0
  229. package/sdk/dist/node/actions-node.d.ts +3 -3
  230. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  231. package/sdk/dist/node/actions-node.js +44 -44
  232. package/sdk/dist/node/actions-node.js.map +1 -1
  233. package/sdk/dist/node/assets-node.js +6 -6
  234. package/sdk/dist/node/assets-node.js.map +1 -1
  235. package/sdk/dist/node/{bearer-claim-key.d.ts → bearer-redeem-key.d.ts} +12 -12
  236. package/sdk/dist/node/bearer-redeem-key.d.ts.map +1 -0
  237. package/sdk/dist/node/{bearer-claim-key.js → bearer-redeem-key.js} +21 -21
  238. package/sdk/dist/node/bearer-redeem-key.js.map +1 -0
  239. package/sdk/dist/node/ci.d.ts +1 -1
  240. package/sdk/dist/node/ci.d.ts.map +1 -1
  241. package/sdk/dist/node/ci.js +6 -6
  242. package/sdk/dist/node/ci.js.map +1 -1
  243. package/sdk/dist/node/credentials.d.ts +18 -17
  244. package/sdk/dist/node/credentials.d.ts.map +1 -1
  245. package/sdk/dist/node/credentials.js +29 -28
  246. package/sdk/dist/node/credentials.js.map +1 -1
  247. package/sdk/dist/node/deploy-manifest.d.ts +3 -5
  248. package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
  249. package/sdk/dist/node/deploy-manifest.js +3 -12
  250. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  251. package/sdk/dist/node/funding-recovery.d.ts +1 -1
  252. package/sdk/dist/node/funding-recovery.d.ts.map +1 -1
  253. package/sdk/dist/node/funding-recovery.js +1 -1
  254. package/sdk/dist/node/funding-recovery.js.map +1 -1
  255. package/sdk/dist/node/gitvault-apply.d.ts +1 -1
  256. package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
  257. package/sdk/dist/node/gitvault-apply.js +1 -1
  258. package/sdk/dist/node/gitvault-apply.js.map +1 -1
  259. package/sdk/dist/node/gitvault-handoff.d.ts +22 -22
  260. package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
  261. package/sdk/dist/node/gitvault-handoff.js +45 -45
  262. package/sdk/dist/node/gitvault-handoff.js.map +1 -1
  263. package/sdk/dist/node/gitvault-prewarm.js +7 -7
  264. package/sdk/dist/node/gitvault-prewarm.js.map +1 -1
  265. package/sdk/dist/node/gitvault-restore.d.ts +1 -1
  266. package/sdk/dist/node/gitvault-restore.js +1 -1
  267. package/sdk/dist/node/index.d.ts +16 -16
  268. package/sdk/dist/node/index.d.ts.map +1 -1
  269. package/sdk/dist/node/index.js +15 -15
  270. package/sdk/dist/node/index.js.map +1 -1
  271. package/sdk/dist/node/manifest-export.js +2 -2
  272. package/sdk/dist/node/manifest-export.js.map +1 -1
  273. package/sdk/dist/node/org-adopt.d.ts +49 -0
  274. package/sdk/dist/node/org-adopt.d.ts.map +1 -0
  275. package/sdk/dist/node/org-adopt.js +77 -0
  276. package/sdk/dist/node/org-adopt.js.map +1 -0
  277. package/sdk/dist/node/paid-fetch.d.ts +7 -7
  278. package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
  279. package/sdk/dist/node/paid-fetch.js +54 -54
  280. package/sdk/dist/node/paid-fetch.js.map +1 -1
  281. package/sdk/dist/node/preflight.d.ts +1 -1
  282. package/sdk/dist/node/preflight.d.ts.map +1 -1
  283. package/sdk/dist/node/preflight.js +1 -1
  284. package/sdk/dist/node/preflight.js.map +1 -1
  285. package/sdk/dist/node/sites-node.js +1 -1
  286. package/sdk/dist/node/sites-node.js.map +1 -1
  287. package/sdk/dist/node/source-scan.d.ts +1 -1
  288. package/sdk/dist/node/source-scan.js +2 -2
  289. package/sdk/dist/node/source-scan.js.map +1 -1
  290. package/sdk/dist/scoped.d.ts +11 -11
  291. package/sdk/dist/scoped.d.ts.map +1 -1
  292. package/sdk/dist/scoped.js +5 -5
  293. package/sdk/dist/scoped.js.map +1 -1
  294. package/core-dist/operator-session.js +0 -154
  295. package/lib/allowance.mjs +0 -355
  296. package/lib/cloud.mjs +0 -256
  297. package/lib/core.mjs +0 -123
  298. package/lib/operator-proofs.mjs +0 -22
  299. package/lib/operator.mjs +0 -771
  300. package/sdk/core-dist/operator-session.js +0 -154
  301. package/sdk/dist/namespaces/allowance.d.ts +0 -79
  302. package/sdk/dist/namespaces/allowance.d.ts.map +0 -1
  303. package/sdk/dist/namespaces/allowance.js +0 -149
  304. package/sdk/dist/namespaces/allowance.js.map +0 -1
  305. package/sdk/dist/namespaces/operator-session.d.ts.map +0 -1
  306. package/sdk/dist/namespaces/operator-session.js.map +0 -1
  307. package/sdk/dist/namespaces/operator.d.ts +0 -359
  308. package/sdk/dist/namespaces/operator.d.ts.map +0 -1
  309. package/sdk/dist/namespaces/operator.js +0 -272
  310. package/sdk/dist/namespaces/operator.js.map +0 -1
  311. package/sdk/dist/node/bearer-claim-key.d.ts.map +0 -1
  312. package/sdk/dist/node/bearer-claim-key.js.map +0 -1
  313. package/sdk/dist/node/operator-claim.d.ts +0 -49
  314. package/sdk/dist/node/operator-claim.d.ts.map +0 -1
  315. package/sdk/dist/node/operator-claim.js +0 -77
  316. package/sdk/dist/node/operator-claim.js.map +0 -1
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Shared machinery for a person's command-line sign-in: `run402 login`,
3
+ * `run402 logout`, `run402 whoami`, and `run402 approve`.
4
+ *
5
+ * One sign-in session (wire token class `control_plane_session`), bound to the
6
+ * principal and graded by how it was minted:
7
+ * - `run402 login` — loopback PKCE (RFC 8252): the browser runs the passkey
8
+ * ceremony and redirects to a 127.0.0.1 listener. Grade `loopback`: full and
9
+ * step-up-able.
10
+ * - `run402 login --device` — RFC 8628 device authorization for a terminal
11
+ * with no browser. Grade `device`: read-only (every mutation answers
12
+ * `SESSION_READ_ONLY`).
13
+ *
14
+ * Provisioning, deploying, and writing secrets without a wallet additionally
15
+ * need a **write approval** — passkey-signed, scoped to one (action, target),
16
+ * minted by `run402 approve` and sent as `X-Run402-Write-Approval`. A write
17
+ * approval never satisfies step-up.
18
+ *
19
+ * The session and approvals are cached at the BASE config dir (principal-
20
+ * scoped, shared across named wallets), mode 0600. Agent-first: JSON to stdout;
21
+ * the URL and code a person must open go to stderr.
22
+ */
23
+
24
+ import { spawn } from "node:child_process";
25
+ import { createServer } from "node:http";
26
+ import { randomBytes, createHash } from "node:crypto";
27
+ import { fail, reportSdkError } from "./sdk-errors.mjs";
28
+ import { getSdk } from "./sdk.mjs";
29
+ import { isWriteApprovalRequired } from "#sdk/node";
30
+ import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
31
+ import {
32
+ saveApproval,
33
+ hashControlPlaneSession,
34
+ approvalFromTokenResponse,
35
+ } from "../core-dist/write-approvals.js";
36
+ import { getApiBase } from "../core-dist/config.js";
37
+
38
+ /** Gateway write-approval capabilities → required target scope. */
39
+ export const APPROVAL_ACTIONS = {
40
+ "org.project.create": "org",
41
+ "project.deploy": "project",
42
+ "project.secret.write": "project",
43
+ };
44
+
45
+ /** True when `url` is same-origin as the API base (confirm_url validation). */
46
+ function sameApiOrigin(url) {
47
+ try {
48
+ return new URL(url).origin === new URL(getApiBase()).origin;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /** Best-effort, cross-platform browser open. Never throws. */
55
+ export function openBrowser(url) {
56
+ try {
57
+ let cmd;
58
+ let cmdArgs;
59
+ if (process.platform === "darwin") {
60
+ cmd = "open";
61
+ cmdArgs = [url];
62
+ } else if (process.platform === "win32") {
63
+ cmd = "cmd";
64
+ cmdArgs = ["/c", "start", "", url];
65
+ } else {
66
+ cmd = "xdg-open";
67
+ cmdArgs = [url];
68
+ }
69
+ const child = spawn(cmd, cmdArgs, { stdio: "ignore", detached: true });
70
+ child.on("error", () => {}); // ignore: the URL is also printed to stderr
71
+ child.unref();
72
+ } catch {
73
+ // Best-effort only — the person can always copy the printed URL.
74
+ }
75
+ }
76
+
77
+ /** Output shape for a cached sign-in session. NEVER includes the token. */
78
+ export function sessionView(session, nowMs = Date.now()) {
79
+ return {
80
+ logged_in: true,
81
+ grade: session.grade,
82
+ read_only: session.grade === "device",
83
+ principal_id: session.principal_id || null,
84
+ amr: session.amr,
85
+ expires_at: new Date(session.expires_at).toISOString(),
86
+ expires_in_seconds: Math.max(0, Math.round((session.expires_at - nowMs) / 1000)),
87
+ };
88
+ }
89
+
90
+ const base64url = (buf) => buf.toString("base64url");
91
+
92
+ /** Generate PKCE (S256) + CSRF state + replay nonce for the loopback flow. */
93
+ export function pkce() {
94
+ const codeVerifier = base64url(randomBytes(32));
95
+ const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
96
+ return { codeVerifier, codeChallenge, state: base64url(randomBytes(16)), nonce: base64url(randomBytes(16)) };
97
+ }
98
+
99
+ /**
100
+ * Start a 127.0.0.1 loopback server (RFC 8252) that captures exactly one
101
+ * redirect. Returns the bound port (via `ready`), a promise for the auth code,
102
+ * and a `close()`. State is validated here to reject CSRF before exchange.
103
+ */
104
+ export function startLoopbackServer({ expectedState, timeoutMs }) {
105
+ let resolveCode;
106
+ let rejectCode;
107
+ const codePromise = new Promise((res, rej) => {
108
+ resolveCode = res;
109
+ rejectCode = rej;
110
+ });
111
+ let timer;
112
+ // Track live sockets: server.close() alone stops NEW connections but leaves
113
+ // the browser's keep-alive socket open, which keeps Node's event loop alive
114
+ // and hangs the CLI after a successful login. close() must destroy them.
115
+ const sockets = new Set();
116
+ const server = createServer((req, res) => {
117
+ let u;
118
+ try {
119
+ u = new URL(req.url, "http://127.0.0.1");
120
+ } catch {
121
+ res.writeHead(400).end("bad request");
122
+ return;
123
+ }
124
+ if (u.pathname !== "/callback") {
125
+ res.writeHead(404).end("not found");
126
+ return;
127
+ }
128
+ const code = u.searchParams.get("code");
129
+ const gotState = u.searchParams.get("state");
130
+ const errParam = u.searchParams.get("error");
131
+ // `connection: close` so the browser does not keep the socket alive.
132
+ res.writeHead(200, { "content-type": "text/html", connection: "close" });
133
+ res.end(
134
+ "<!doctype html><html><body style=\"font-family:system-ui;padding:3rem\">" +
135
+ "<h2>run402 - you're signed in.</h2><p>You can close this window and return to your terminal.</p></body></html>",
136
+ );
137
+ // Do NOT tear down here — let the response flush. The caller calls close()
138
+ // once it has the code (or on any failure path).
139
+ if (errParam) rejectCode(new Error(`authorization error: ${errParam}`));
140
+ else if (!code) rejectCode(new Error("no authorization code on the loopback redirect"));
141
+ else if (gotState !== expectedState) rejectCode(new Error("state mismatch on the loopback redirect (possible CSRF) - aborted"));
142
+ else resolveCode(code);
143
+ });
144
+ server.on("connection", (s) => {
145
+ sockets.add(s);
146
+ s.once("close", () => sockets.delete(s));
147
+ });
148
+ function close() {
149
+ clearTimeout(timer);
150
+ for (const s of sockets) {
151
+ try {
152
+ s.destroy();
153
+ } catch {
154
+ // already gone
155
+ }
156
+ }
157
+ try {
158
+ server.close();
159
+ } catch {
160
+ // already closing
161
+ }
162
+ }
163
+ timer = setTimeout(() => {
164
+ close();
165
+ rejectCode(new Error("timed out waiting for browser approval"));
166
+ }, timeoutMs);
167
+ server.on("error", (e) => {
168
+ close();
169
+ rejectCode(e);
170
+ });
171
+ const ready = new Promise((res, rej) => {
172
+ server.once("error", rej);
173
+ server.listen(0, "127.0.0.1", () => res(server.address().port));
174
+ });
175
+ return { ready, codePromise, close };
176
+ }
177
+
178
+ /**
179
+ * Run the write-approval ceremony (loopback + passkey) and cache the minted
180
+ * approval. Requires a live, write-capable sign-in session. Returns the cached
181
+ * approval (or exits non-zero via fail/reportSdkError). Reused by
182
+ * `run402 approve` AND the TTY auto-approve path in provision/deploy.
183
+ */
184
+ export async function mintApproval({ action, orgId, projectId, noOpen = false }) {
185
+ const cp = loadLiveControlPlaneSession();
186
+ if (!cp) {
187
+ return fail({
188
+ code: "LOGIN_REQUIRED",
189
+ message: "No sign-in session. Run 'run402 login' first, then approve.",
190
+ hint: "A write approval is minted on top of your sign-in session.",
191
+ next_actions: [{ type: "authenticate", command: "run402 login" }],
192
+ });
193
+ }
194
+ if (cp.grade === "device") {
195
+ return fail({
196
+ code: "SESSION_READ_ONLY",
197
+ message: "This sign-in session came from 'run402 login --device' and can only read; it cannot mint a write approval.",
198
+ hint: "Run 'run402 login' (the browser passkey sign-in), then approve.",
199
+ next_actions: [{ type: "authenticate", command: "run402 login" }],
200
+ });
201
+ }
202
+ const sdk = getSdk();
203
+ const { codeVerifier, codeChallenge, state } = pkce();
204
+ const { ready, codePromise, close } = startLoopbackServer({ expectedState: state, timeoutMs: 300_000 });
205
+
206
+ let port;
207
+ try {
208
+ port = await ready;
209
+ } catch (err) {
210
+ close();
211
+ return fail({ code: "APPROVE_FAILED", message: `Could not start the loopback server: ${err.message}` });
212
+ }
213
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
214
+
215
+ let challenge;
216
+ try {
217
+ challenge = await sdk.writeApproval.requestChallenge({
218
+ action,
219
+ orgId: orgId ?? undefined,
220
+ projectId: projectId ?? undefined,
221
+ cliRedirectUri: redirectUri,
222
+ codeChallenge,
223
+ state,
224
+ token: cp.control_plane_session_token,
225
+ });
226
+ } catch (err) {
227
+ close();
228
+ return reportSdkError(err);
229
+ }
230
+
231
+ const confirmUrl = challenge?.confirm_url;
232
+ if (!confirmUrl || !sameApiOrigin(confirmUrl)) {
233
+ close();
234
+ return fail({
235
+ code: "APPROVE_BAD_CONFIRM_URL",
236
+ message: "The gateway returned a confirm_url that is not same-origin as the API base; aborting.",
237
+ });
238
+ }
239
+ const targetLabel = orgId ? `org ${orgId}` : projectId ? `project ${projectId}` : "(no target)";
240
+ process.stderr.write(
241
+ `\nTo approve '${action}' for ${targetLabel}, open and approve with your passkey:\n ${confirmUrl}\n\n`,
242
+ );
243
+ if (!noOpen && process.stderr.isTTY) {
244
+ openBrowser(confirmUrl);
245
+ process.stderr.write("(opening your browser…)\n\n");
246
+ }
247
+ process.stderr.write("Waiting for passkey approval…\n");
248
+
249
+ let code;
250
+ try {
251
+ code = await codePromise;
252
+ } catch (err) {
253
+ close();
254
+ return fail({ code: "APPROVE_FAILED", message: err.message, hint: "Run 'run402 approve' to try again." });
255
+ }
256
+
257
+ let token;
258
+ try {
259
+ token = await sdk.writeApproval.exchangeClaimCode({ code, codeVerifier, state });
260
+ } catch (err) {
261
+ close();
262
+ return reportSdkError(err);
263
+ }
264
+ close();
265
+
266
+ const approval = approvalFromTokenResponse(token, {
267
+ action,
268
+ target: { org_id: orgId ?? undefined, project_id: projectId ?? undefined },
269
+ apiOrigin: new URL(getApiBase()).origin,
270
+ controlPlaneSessionHash: hashControlPlaneSession(cp.control_plane_session_token),
271
+ controlPlanePrincipalId: cp.principal_id || "",
272
+ });
273
+ saveApproval(approval);
274
+ process.stderr.write(`\nApproved '${action}' for ${targetLabel}.\n`);
275
+ return approval;
276
+ }
277
+
278
+ /**
279
+ * Run a write action; on `WriteApprovalRequiredError` AND an interactive TTY
280
+ * (CLI), run the scoped approval ceremony and retry once. In MCP / CI /
281
+ * non-TTY the typed error is rethrown unchanged — the agent relays the resolved
282
+ * `run402 approve …` command rather than a browser opening unexpectedly.
283
+ */
284
+ export async function withAutoApprove(fn) {
285
+ try {
286
+ return await fn();
287
+ } catch (err) {
288
+ if (!isWriteApprovalRequired(err) || !process.stderr.isTTY) throw err;
289
+ const action = err.capability;
290
+ const target = err.target || {};
291
+ if (!action) throw err;
292
+ process.stderr.write(`\nThis '${action}' needs a one-time write approval. Opening the browser to approve…\n`);
293
+ await mintApproval({ action, orgId: target.org_id ?? undefined, projectId: target.project_id ?? undefined });
294
+ return await fn(); // retry once with the now-cached approval
295
+ }
296
+ }
package/lib/sites.mjs CHANGED
@@ -2,7 +2,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"
2
2
  import { tmpdir } from "os";
3
3
  import { dirname, join, resolve } from "path";
4
4
  import { fileSetFromDir, delegateTokenFromEnv } from "#sdk/node";
5
- import { allowanceAuthHeaders, resolveProjectId, updateProject } from "./config.mjs";
5
+ import { walletAuthHeaders, resolveProjectId, updateProject } from "./config.mjs";
6
6
  import { resolveFilePathsInManifest } from "./manifest.mjs";
7
7
  import { getSdk } from "./sdk.mjs";
8
8
  import { reportSdkError, fail } from "./sdk-errors.mjs";
@@ -13,8 +13,8 @@ const SMALL_DIR_THRESHOLD = 5;
13
13
  const HELP = `run402 sites - Deploy and manage static sites
14
14
 
15
15
  Usage:
16
- run402 sites deploy --manifest <file> [--project <id>]
17
- run402 sites deploy-dir <path> --project <id>
16
+ run402 sites deploy --manifest <file> [--project <project_id>]
17
+ run402 sites deploy-dir <path> --project <project_id>
18
18
  cat manifest.json | run402 sites deploy
19
19
 
20
20
  Subcommands:
@@ -23,12 +23,12 @@ Subcommands:
23
23
 
24
24
  Options (deploy):
25
25
  --manifest <file> Path to manifest JSON file (or read from stdin)
26
- --project <id> Project ID (defaults to active project)
26
+ --project <project_id> Project ID (defaults to active project)
27
27
  --help, -h Show this help message
28
28
 
29
29
  Options (deploy-dir):
30
30
  <path> Positional: local directory to deploy
31
- --project <id> Project ID (defaults to active project)
31
+ --project <project_id> Project ID (defaults to active project)
32
32
  --quiet Suppress progress events on stderr
33
33
  --dry-run Plan-only: print the diff envelope and exit
34
34
  --confirm-prune Required when <path> has fewer files than the
@@ -58,25 +58,25 @@ Notes:
58
58
  (CAS-backed): only bytes the gateway doesn't already have are uploaded.
59
59
  Re-deploys of an unchanged tree make no S3 PUTs.
60
60
  - To check status of an in-flight deploy, use 'run402 deploy events <op>'
61
- or 'run402 deploy list --project <id>'.
61
+ or 'run402 deploy list --project <project_id>'.
62
62
  - deploy-dir walks the directory, skips .git / node_modules / .DS_Store,
63
63
  and auto-detects binary files. Symlinks are rejected.
64
64
  - Progress events are emitted as JSON-line objects on stderr by default
65
65
  (one object per line). Final result envelope goes to stdout. Pass --quiet
66
66
  to silence stderr.
67
- - Free with active tier - requires allowance auth
67
+ - Free with active tier - requires SIWX auth
68
68
  `;
69
69
 
70
70
  const SUB_HELP = {
71
71
  deploy: `run402 sites deploy - Deploy a static site from a manifest
72
72
 
73
73
  Usage:
74
- run402 sites deploy --manifest <file> [--project <id>]
75
- cat manifest.json | run402 sites deploy [--project <id>]
74
+ run402 sites deploy --manifest <file> [--project <project_id>]
75
+ cat manifest.json | run402 sites deploy [--project <project_id>]
76
76
 
77
77
  Options:
78
78
  --manifest <file> Path to manifest JSON file (or read from stdin)
79
- --project <id> Project ID (defaults to the active project)
79
+ --project <project_id> Project ID (defaults to the active project)
80
80
 
81
81
  Manifest format (JSON):
82
82
  {
@@ -90,7 +90,7 @@ Manifest format (JSON):
90
90
 
91
91
  Notes:
92
92
  - Must include at least index.html in the files array
93
- - Free with active tier - requires allowance auth
93
+ - Free with active tier - requires SIWX auth
94
94
 
95
95
  Examples:
96
96
  run402 sites deploy --manifest site.json
@@ -99,14 +99,14 @@ Examples:
99
99
  "deploy-dir": `run402 sites deploy-dir - Deploy a static site from a local directory
100
100
 
101
101
  Usage:
102
- run402 sites deploy-dir <path> [--project <id>] [--quiet]
102
+ run402 sites deploy-dir <path> [--project <project_id>] [--quiet]
103
103
  [--dry-run] [--confirm-prune]
104
104
 
105
105
  Arguments:
106
106
  <path> Local directory to deploy (positional, required)
107
107
 
108
108
  Options:
109
- --project <id> Project ID (defaults to the active project)
109
+ --project <project_id> Project ID (defaults to the active project)
110
110
  --quiet Suppress progress events on stderr (events are on by
111
111
  default — see Progress events below)
112
112
  --dry-run Plan the deploy and print a JSON envelope describing
@@ -139,7 +139,7 @@ Progress events:
139
139
  Notes:
140
140
  - Re-deploying an unchanged tree makes no S3 PUTs (returns immediately
141
141
  with bytes_uploaded: 0)
142
- - Free with active tier - requires allowance auth
142
+ - Free with active tier - requires SIWX auth
143
143
 
144
144
  Examples:
145
145
  run402 sites deploy-dir ./dist --project prj_abc
@@ -196,7 +196,7 @@ async function deploy(args) {
196
196
  fail({
197
197
  code: "BAD_USAGE",
198
198
  message: `Unexpected argument for sites deploy: ${extra[0]}`,
199
- hint: "Use `run402 sites deploy --manifest <file> [--project <id>]`.",
199
+ hint: "Use `run402 sites deploy --manifest <file> [--project <project_id>]`.",
200
200
  });
201
201
  }
202
202
  const opts = { manifest: null, project: undefined, target: undefined, quiet: false };
@@ -219,10 +219,10 @@ async function deploy(args) {
219
219
  const manifest = JSON.parse(raw);
220
220
  if (opts.manifest) resolveFilePathsInManifest(manifest, dirname(resolve(opts.manifest)));
221
221
 
222
- // Preserve the aggressive early exit when no allowance is configured.
222
+ // Preserve the aggressive early exit when no local wallet is configured.
223
223
  // A delegate holds `deploy` and /apply/v1/plans accepts it; refusing locally
224
224
  // would tell a wallet-less holder to run `run402 init` (see 4.11.2).
225
- if (!delegateTokenFromEnv()) allowanceAuthHeaders("/apply/v1/plans");
225
+ if (!delegateTokenFromEnv()) walletAuthHeaders("/apply/v1/plans");
226
226
 
227
227
  const stage = stageFilesToTempDir(manifest.files || []);
228
228
  try {
@@ -252,7 +252,7 @@ async function deployDir(args) {
252
252
  fail({
253
253
  code: "BAD_USAGE",
254
254
  message: `Unexpected argument for sites deploy-dir: ${positionals[1]}`,
255
- hint: "Use `run402 sites deploy-dir <path> --project <id>`.",
255
+ hint: "Use `run402 sites deploy-dir <path> --project <project_id>`.",
256
256
  });
257
257
  }
258
258
  const opts = {
@@ -282,16 +282,16 @@ async function deployDir(args) {
282
282
  fail({
283
283
  code: "BAD_USAGE",
284
284
  message: "Missing <path>.",
285
- hint: "run402 sites deploy-dir <path> --project <id>",
285
+ hint: "run402 sites deploy-dir <path> --project <project_id>",
286
286
  });
287
287
  }
288
288
  const projectId = resolveProjectId(opts.project);
289
289
  if (opts.target !== undefined) failUnsupportedTarget();
290
290
 
291
- // Preserve the aggressive early exit when no allowance is configured.
291
+ // Preserve the aggressive early exit when no local wallet is configured.
292
292
  // A delegate holds `deploy` and /apply/v1/plans accepts it; refusing locally
293
293
  // would tell a wallet-less holder to run `run402 init` (see 4.11.2).
294
- if (!delegateTokenFromEnv()) allowanceAuthHeaders("/apply/v1/plans");
294
+ if (!delegateTokenFromEnv()) walletAuthHeaders("/apply/v1/plans");
295
295
 
296
296
  let fileSet;
297
297
  try {
@@ -325,7 +325,7 @@ async function deployDir(args) {
325
325
  if (opts.dryRun) {
326
326
  try {
327
327
  const { plan } = await getSdk()._applyEngine.plan({
328
- project: projectId,
328
+ project_id: projectId,
329
329
  site: { replace: fileSet },
330
330
  }, { dryRun: true });
331
331
  console.log(JSON.stringify({
package/lib/snapshots.mjs CHANGED
@@ -15,14 +15,14 @@ import {
15
15
  const HELP = `run402 snapshots — Project database restore points
16
16
 
17
17
  Usage:
18
- run402 snapshots create [--project <id>] [--json]
19
- run402 snapshots list [--project <id>] [--kind <kind>] [--limit <n>] [--after <cursor>] [--json]
20
- run402 snapshots get <snapshot-id> [--project <id>] [--json]
21
- run402 snapshots restore <snapshot-id> [--project <id>] [--include-auth] [--confirm <token>] [--json]
22
- run402 snapshots delete <snapshot-id> [--project <id>] [--json]
18
+ run402 snapshots create [--project <project_id>] [--json]
19
+ run402 snapshots list [--project <project_id>] [--kind <kind>] [--limit <n>] [--after <cursor>] [--json]
20
+ run402 snapshots get <snapshot_id> [--project <project_id>] [--json]
21
+ run402 snapshots restore <snapshot_id> [--project <project_id>] [--include-auth] [--confirm <token>] [--json]
22
+ run402 snapshots delete <snapshot_id> [--project <project_id>] [--json]
23
23
 
24
24
  Legacy (still supported): a leading prj_... positional selects the project,
25
- e.g. run402 snapshots restore prj_abc123 <snapshot-id>. --project defaults to
25
+ e.g. run402 snapshots restore prj_abc123 <snapshot_id>. --project defaults to
26
26
  the active project.
27
27
 
28
28
  Restore is a two-step handshake. First call without --confirm to get a
@@ -82,7 +82,7 @@ async function list(args) {
82
82
  }
83
83
 
84
84
  async function get(args) {
85
- const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots get [project-id] <snapshot-id>");
85
+ const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots get [project_id] <snapshot_id>");
86
86
  try {
87
87
  const snapshot = await getSdk().snapshots.get(projectId, snapshotId);
88
88
  console.log(JSON.stringify({ snapshot }, null, 2));
@@ -92,7 +92,7 @@ async function get(args) {
92
92
  }
93
93
 
94
94
  async function restore(args) {
95
- const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots restore [project-id] <snapshot-id> [--confirm <token>]");
95
+ const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots restore [project_id] <snapshot_id> [--confirm <token>]");
96
96
  const includeAuth = args.includes("--include-auth");
97
97
  const confirm = flagValue(args, "--confirm");
98
98
  try {
@@ -115,7 +115,7 @@ async function restore(args) {
115
115
  }
116
116
 
117
117
  async function deleteSnapshot(args) {
118
- const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots delete [project-id] <snapshot-id>");
118
+ const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots delete [project_id] <snapshot_id>");
119
119
  try {
120
120
  await getSdk().snapshots.delete(projectId, snapshotId);
121
121
  console.log(JSON.stringify({ ok: true, project_id: projectId, snapshot_id: snapshotId, deleted: true }, null, 2));
@@ -125,7 +125,7 @@ async function deleteSnapshot(args) {
125
125
  }
126
126
 
127
127
  function resolveProjectAndSnapshot(args, usage) {
128
- // Canonical: `<snapshot-id> [--project <id>]`; legacy `<prj_id> <snapshot-id>`
128
+ // Canonical: `<snapshot_id> [--project <project_id>]`; legacy `<prj_id> <snapshot_id>`
129
129
  // still works (requireRestPositional keeps a lone snapshot id that happens to
130
130
  // start with prj_ from being eaten as the project selector).
131
131
  const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES, requireRestPositional: true });
package/lib/stats.mjs CHANGED
@@ -8,7 +8,7 @@
8
8
  * line per request to stderr, automatically, for every SDK caller (CLI, MCP,
9
9
  * `git-remote-run402`). This module is the thin CLI-edge reader of that
10
10
  * per-instance accumulator: it turns `sdk.stats()` into the `stats` envelope
11
- * field every `repos` verb's result and `deploy apply`'s final result carry
11
+ * field every `repos` verb's result and `deploy`'s final result carry
12
12
  * (always on), plus the `-v`/`--verbose` stderr summary line.
13
13
  *
14
14
  * `sdk.stats()` reflects only calls made through the ONE `Run402` instance
@@ -47,7 +47,7 @@ function writeVerboseStatsLine(sdk) {
47
47
  /**
48
48
  * One stderr summary line, only when verbose was requested. Coexists with
49
49
  * `--human`. `verbose` is either normalized argv (checked for `-v`/
50
- * `--verbose`) or a plain boolean, for callers (e.g. `deploy apply`) that
50
+ * `--verbose`) or a plain boolean, for callers (e.g. `deploy`) that
51
51
  * already parsed their own flags into an options object.
52
52
  */
53
53
  export function printVerboseStats(verbose, sdk) {
package/lib/status.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readRemoteStatus } from "#sdk/node";
2
2
  import {
3
- readAllowance,
3
+ readWallet,
4
4
  loadKeyStore,
5
5
  getActiveProjectId,
6
6
  apiBase,
@@ -21,8 +21,8 @@ Usage:
21
21
  Displays:
22
22
  - Wallet identity (local_label, server_label, address)
23
23
  - Payment rail (x402 | mpp)
24
- - Balances (on_chain_usd_micros + on_chain_token, prepaid_credit_usd_micros, held_usd_micros)
25
- - Tier subscription (name, status, expiry)
24
+ - Balances (on_chain_usd_micros + on_chain_token, allowance_usd_micros, held_usd_micros)
25
+ - Tier and lease (name, status, expiry)
26
26
  - Projects (from server, with fallback to local keystore)
27
27
  - Active project ID
28
28
  - Active API target
@@ -31,11 +31,11 @@ Output is JSON by default. --json is accepted as a compatibility no-op.
31
31
  --human renders a compact human summary instead (wallet, API target, tier,
32
32
  active project, balance, and the next action); it cannot be combined with
33
33
  --json.
34
- Run402 Cloud status requires an allowance; Core target status can still report
34
+ Run402 Cloud status requires a wallet; Core target status can still report
35
35
  local project state without one.
36
36
  `;
37
37
 
38
- // USDC / pathUSD constants (match allowance.mjs)
38
+ // USDC / pathUSD constants (match wallet.mjs)
39
39
  const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
40
40
  const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
41
41
  const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
@@ -105,13 +105,13 @@ export async function run(args = []) {
105
105
  hint: "JSON is the default; drop --json to keep it, or keep --human alone for the rendered view.",
106
106
  });
107
107
  }
108
- const allowance = readAllowance();
108
+ const localWallet = readWallet();
109
109
  const target = {
110
110
  api_base: apiBase(),
111
111
  api_base_source: apiBaseSource(),
112
112
  kind: apiTargetKind(),
113
113
  };
114
- if (!allowance) {
114
+ if (!localWallet) {
115
115
  const store = loadKeyStore();
116
116
  const bare = {
117
117
  wallet: null,
@@ -128,16 +128,16 @@ export async function run(args = []) {
128
128
  return;
129
129
  }
130
130
 
131
- const wallet = allowance.address.toLowerCase();
132
- const rail = allowance.rail || "x402";
131
+ const wallet = localWallet.address.toLowerCase();
132
+ const rail = localWallet.rail || "x402";
133
133
 
134
- // Parallel API calls: tier + billing balance + server-side projects + on-chain wallet balance.
134
+ // Parallel API calls: tier + allowance + server-side projects + on-chain wallet balance.
135
135
  // projects.list() is the membership-scoped named inventory (project-findability);
136
- // SIWX wallet auth is signed from the allowance. Best-effort — a missing
137
- // allowance yields null and we fall back to the local keystore below.
136
+ // SIWX wallet auth is signed from the wallet. Best-effort — a missing
137
+ // wallet yields null and we fall back to the local keystore below.
138
138
  const [status, walletBalance] = await Promise.all([
139
139
  readRemoteStatus(getSdk(), wallet),
140
- readWalletBalanceUsdMicros(rail, allowance.address),
140
+ readWalletBalanceUsdMicros(rail, localWallet.address),
141
141
  ]);
142
142
  const { tier, billing, remote } = status;
143
143
 
@@ -155,18 +155,18 @@ export async function run(args = []) {
155
155
  const walletName = getActiveProfile();
156
156
  const walletMeta = readMeta(walletName);
157
157
 
158
- // Balances are grouped under one object so the on-chain and
159
- // prepaid-credit numbers are unambiguous and rail-legible.
158
+ // Balances are grouped under one object so the on-chain (wallet) and
159
+ // allowance numbers are unambiguous and rail-legible.
160
160
  // - on_chain_usd_micros / on_chain_token: on-chain USDC (x402) or pathUSD
161
- // (mpp), null if the RPC read failed
162
- // - prepaid_credit_usd_micros / held_usd_micros: Run402-held credits,
163
- // rail-independent, null when no organization exists
161
+ // (mpp) in the wallet, null if the RPC read failed
162
+ // - allowance_usd_micros / held_usd_micros: the organization's Run402-held
163
+ // allowance, rail-independent, null when no organization exists
164
164
  const hasBilling = billing && billing.exists !== false;
165
165
  const result = {
166
166
  wallet: {
167
167
  local_label: walletName,
168
168
  server_label: walletMeta?.label ?? null,
169
- address: allowance.address,
169
+ address: localWallet.address,
170
170
  },
171
171
  rail,
172
172
  remote_status: status.availability,
@@ -175,7 +175,7 @@ export async function run(args = []) {
175
175
  balances: {
176
176
  on_chain_usd_micros: walletBalance,
177
177
  on_chain_token: rail === "mpp" ? "pathUSD" : "USDC",
178
- prepaid_credit_usd_micros: hasBilling ? billing.available_usd_micros : null,
178
+ allowance_usd_micros: hasBilling ? billing.allowance_usd_micros : null,
179
179
  held_usd_micros: hasBilling ? (billing.held_usd_micros ?? 0) : null,
180
180
  },
181
181
  tier: tier && tier.tier
@@ -205,7 +205,7 @@ function usdFromMicros(micros) {
205
205
 
206
206
  /**
207
207
  * The one next action for the human view, derived from the same state the
208
- * JSON carries. Mirrors `init`: `run402 up -y` subscribes the prototype tier
208
+ * JSON carries. Mirrors `init`: `run402 up -y` sets the prototype tier
209
209
  * itself as part of the first deploy, so a tier-less account gets ONE command.
210
210
  */
211
211
  function statusNextAction(result) {
@@ -214,7 +214,7 @@ function statusNextAction(result) {
214
214
  }
215
215
  if (result.remote_status?.tier?.state === "unavailable") return "Check connectivity/authentication and retry run402 status; tier state is unavailable.";
216
216
  if (!result.tier) {
217
- return "run402 up -y (subscribes the prototype tier, free on testnet, as part of the first deploy; or: run402 tier set prototype)";
217
+ return "run402 up -y (sets the prototype tier, free on testnet, as part of the first deploy; or: run402 tier set prototype)";
218
218
  }
219
219
  if (!result.active_project) {
220
220
  return "run402 up --name <name> -y (or select an existing project: run402 projects use <project_id>)";
@@ -230,7 +230,7 @@ function statusNextAction(result) {
230
230
  export function formatStatusHuman(result) {
231
231
  const lines = [];
232
232
  if (!result.wallet) {
233
- lines.push("Wallet: none (no allowance on this machine)");
233
+ lines.push("Wallet: none (no local wallet on this machine)");
234
234
  } else {
235
235
  const label = result.wallet.server_label
236
236
  ? `${result.wallet.local_label} (${result.wallet.server_label})`
@@ -262,8 +262,8 @@ export function formatStatusHuman(result) {
262
262
  const parts = [];
263
263
  const onChain = usdFromMicros(result.balances.on_chain_usd_micros);
264
264
  if (onChain) parts.push(`${onChain} ${result.balances.on_chain_token} on-chain`);
265
- const credit = usdFromMicros(result.balances.prepaid_credit_usd_micros);
266
- if (credit) parts.push(`${credit} prepaid credit`);
265
+ const allowance = usdFromMicros(result.balances.allowance_usd_micros);
266
+ if (allowance) parts.push(`${allowance} allowance`);
267
267
  if (parts.length) lines.push(`Balance: ${parts.join(", ")}`);
268
268
  }
269
269
  lines.push(`Next: ${statusNextAction(result)}`);