run402 4.96.0 → 4.98.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 (225) hide show
  1. package/README.md +3 -3
  2. package/cli.mjs +1 -7
  3. package/core-dist/binding-file.js +28 -1
  4. package/core-dist/wallet.js +3 -3
  5. package/lib/auth.mjs +1 -2
  6. package/lib/buzz-doctor.mjs +21 -806
  7. package/lib/buzz-doctor.test.mjs +7 -6
  8. package/lib/buzz.mjs +1 -1
  9. package/lib/buzz.test.mjs +13 -2
  10. package/lib/command-manifest.mjs +3 -6
  11. package/lib/credentials.mjs +4 -4
  12. package/lib/deploy-v2.mjs +27 -27
  13. package/lib/doctor.mjs +72 -811
  14. package/lib/errors.mjs +1 -1
  15. package/lib/events.mjs +1 -1
  16. package/lib/grants.mjs +148 -46
  17. package/lib/init.mjs +35 -576
  18. package/lib/logs.mjs +1 -1
  19. package/lib/org-context.mjs +39 -376
  20. package/lib/orgs.mjs +22 -106
  21. package/lib/projects.mjs +2 -3
  22. package/lib/repos.mjs +6 -6
  23. package/lib/rooms.mjs +1 -2
  24. package/lib/sdk-errors.mjs +26 -0
  25. package/lib/sites.mjs +5 -5
  26. package/lib/status.mjs +14 -154
  27. package/lib/up.mjs +1 -2
  28. package/lib/vault-scaffold.mjs +2 -3
  29. package/lib/vault-target.mjs +14 -141
  30. package/lib/wallet-context.mjs +61 -227
  31. package/lib/wallets.mjs +47 -310
  32. package/package.json +1 -2
  33. package/sdk/core-dist/binding-file.js +28 -1
  34. package/sdk/core-dist/wallet.js +3 -3
  35. package/sdk/dist/errors.d.ts +25 -3
  36. package/sdk/dist/errors.d.ts.map +1 -1
  37. package/sdk/dist/errors.js +46 -4
  38. package/sdk/dist/errors.js.map +1 -1
  39. package/sdk/dist/grant-key-credentials.d.ts +38 -0
  40. package/sdk/dist/grant-key-credentials.d.ts.map +1 -0
  41. package/sdk/dist/grant-key-credentials.js +42 -0
  42. package/sdk/dist/grant-key-credentials.js.map +1 -0
  43. package/sdk/dist/index.d.ts +25 -17
  44. package/sdk/dist/index.d.ts.map +1 -1
  45. package/sdk/dist/index.js +18 -14
  46. package/sdk/dist/index.js.map +1 -1
  47. package/sdk/dist/kernel.d.ts +34 -0
  48. package/sdk/dist/kernel.d.ts.map +1 -1
  49. package/sdk/dist/kernel.js +15 -1
  50. package/sdk/dist/kernel.js.map +1 -1
  51. package/sdk/dist/namespaces/admin.d.ts.map +1 -1
  52. package/sdk/dist/namespaces/admin.js +8 -0
  53. package/sdk/dist/namespaces/admin.js.map +1 -1
  54. package/sdk/dist/namespaces/agent.d.ts +2 -2
  55. package/sdk/dist/namespaces/agent.d.ts.map +1 -1
  56. package/sdk/dist/namespaces/agent.js +4 -0
  57. package/sdk/dist/namespaces/agent.js.map +1 -1
  58. package/sdk/dist/namespaces/agent.types.d.ts +5 -0
  59. package/sdk/dist/namespaces/agent.types.d.ts.map +1 -1
  60. package/sdk/dist/namespaces/apps.d.ts.map +1 -1
  61. package/sdk/dist/namespaces/apps.js +2 -0
  62. package/sdk/dist/namespaces/apps.js.map +1 -1
  63. package/sdk/dist/namespaces/auth.d.ts.map +1 -1
  64. package/sdk/dist/namespaces/auth.js +9 -0
  65. package/sdk/dist/namespaces/auth.js.map +1 -1
  66. package/sdk/dist/namespaces/billing.d.ts +1 -1
  67. package/sdk/dist/namespaces/billing.js +1 -1
  68. package/sdk/dist/namespaces/branches.d.ts.map +1 -1
  69. package/sdk/dist/namespaces/branches.js +2 -0
  70. package/sdk/dist/namespaces/branches.js.map +1 -1
  71. package/sdk/dist/namespaces/buzz.js +1 -1
  72. package/sdk/dist/namespaces/buzz.js.map +1 -1
  73. package/sdk/dist/namespaces/ci.d.ts.map +1 -1
  74. package/sdk/dist/namespaces/ci.js +2 -0
  75. package/sdk/dist/namespaces/ci.js.map +1 -1
  76. package/sdk/dist/namespaces/credentials.d.ts +4 -27
  77. package/sdk/dist/namespaces/credentials.d.ts.map +1 -1
  78. package/sdk/dist/namespaces/credentials.js +33 -4
  79. package/sdk/dist/namespaces/credentials.js.map +1 -1
  80. package/sdk/dist/namespaces/deploy.js +5 -5
  81. package/sdk/dist/namespaces/deploy.js.map +1 -1
  82. package/sdk/dist/namespaces/domains.d.ts +1 -1
  83. package/sdk/dist/namespaces/domains.js +1 -1
  84. package/sdk/dist/namespaces/errors.d.ts +1 -1
  85. package/sdk/dist/namespaces/errors.js +2 -2
  86. package/sdk/dist/namespaces/escalations.d.ts +2 -2
  87. package/sdk/dist/namespaces/escalations.d.ts.map +1 -1
  88. package/sdk/dist/namespaces/escalations.js +4 -2
  89. package/sdk/dist/namespaces/escalations.js.map +1 -1
  90. package/sdk/dist/namespaces/escalations.types.d.ts +3 -3
  91. package/sdk/dist/namespaces/escalations.types.d.ts.map +1 -1
  92. package/sdk/dist/namespaces/events.d.ts +1 -1
  93. package/sdk/dist/namespaces/events.js +1 -1
  94. package/sdk/dist/namespaces/functions.js +1 -1
  95. package/sdk/dist/namespaces/functions.js.map +1 -1
  96. package/sdk/dist/namespaces/grants.d.ts +43 -9
  97. package/sdk/dist/namespaces/grants.d.ts.map +1 -1
  98. package/sdk/dist/namespaces/grants.js +103 -10
  99. package/sdk/dist/namespaces/grants.js.map +1 -1
  100. package/sdk/dist/namespaces/grants.types.d.ts +120 -15
  101. package/sdk/dist/namespaces/grants.types.d.ts.map +1 -1
  102. package/sdk/dist/namespaces/grants.types.js +12 -3
  103. package/sdk/dist/namespaces/grants.types.js.map +1 -1
  104. package/sdk/dist/namespaces/identity-links.types.d.ts +2 -2
  105. package/sdk/dist/namespaces/identity-links.types.d.ts.map +1 -1
  106. package/sdk/dist/namespaces/live.d.ts +0 -16
  107. package/sdk/dist/namespaces/live.d.ts.map +1 -1
  108. package/sdk/dist/namespaces/live.js +19 -0
  109. package/sdk/dist/namespaces/live.js.map +1 -1
  110. package/sdk/dist/namespaces/me.d.ts.map +1 -1
  111. package/sdk/dist/namespaces/me.js +3 -0
  112. package/sdk/dist/namespaces/me.js.map +1 -1
  113. package/sdk/dist/namespaces/org.d.ts +1 -1
  114. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  115. package/sdk/dist/namespaces/org.js +4 -1
  116. package/sdk/dist/namespaces/org.js.map +1 -1
  117. package/sdk/dist/namespaces/projects.d.ts +1 -1
  118. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  119. package/sdk/dist/namespaces/projects.js +6 -1
  120. package/sdk/dist/namespaces/projects.js.map +1 -1
  121. package/sdk/dist/namespaces/repos.d.ts.map +1 -1
  122. package/sdk/dist/namespaces/repos.js +7 -2
  123. package/sdk/dist/namespaces/repos.js.map +1 -1
  124. package/sdk/dist/namespaces/rooms.d.ts +3 -3
  125. package/sdk/dist/namespaces/rooms.d.ts.map +1 -1
  126. package/sdk/dist/namespaces/rooms.js +6 -3
  127. package/sdk/dist/namespaces/rooms.js.map +1 -1
  128. package/sdk/dist/namespaces/session.d.ts.map +1 -1
  129. package/sdk/dist/namespaces/session.js +18 -0
  130. package/sdk/dist/namespaces/session.js.map +1 -1
  131. package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
  132. package/sdk/dist/namespaces/transfers.js +3 -0
  133. package/sdk/dist/namespaces/transfers.js.map +1 -1
  134. package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
  135. package/sdk/dist/namespaces/wallets.js +2 -0
  136. package/sdk/dist/namespaces/wallets.js.map +1 -1
  137. package/sdk/dist/namespaces/write-approval.d.ts.map +1 -1
  138. package/sdk/dist/namespaces/write-approval.js +3 -0
  139. package/sdk/dist/namespaces/write-approval.js.map +1 -1
  140. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  141. package/sdk/dist/node/actions-node.js +3 -0
  142. package/sdk/dist/node/actions-node.js.map +1 -1
  143. package/sdk/dist/node/buzz-doctor-contract.d.ts +155 -0
  144. package/sdk/dist/node/buzz-doctor-contract.d.ts.map +1 -0
  145. package/sdk/dist/node/buzz-doctor-contract.js +385 -0
  146. package/sdk/dist/node/buzz-doctor-contract.js.map +1 -0
  147. package/sdk/dist/node/buzz-doctor.d.ts +155 -0
  148. package/sdk/dist/node/buzz-doctor.d.ts.map +1 -0
  149. package/sdk/dist/node/buzz-doctor.js +810 -0
  150. package/sdk/dist/node/buzz-doctor.js.map +1 -0
  151. package/sdk/dist/node/credentials.d.ts +18 -13
  152. package/sdk/dist/node/credentials.d.ts.map +1 -1
  153. package/sdk/dist/node/credentials.js +13 -13
  154. package/sdk/dist/node/credentials.js.map +1 -1
  155. package/sdk/dist/node/diagnostics.d.ts +45 -0
  156. package/sdk/dist/node/diagnostics.d.ts.map +1 -0
  157. package/sdk/dist/node/diagnostics.js +67 -0
  158. package/sdk/dist/node/diagnostics.js.map +1 -0
  159. package/sdk/dist/node/doctor.d.ts +101 -0
  160. package/sdk/dist/node/doctor.d.ts.map +1 -0
  161. package/sdk/dist/node/doctor.js +618 -0
  162. package/sdk/dist/node/doctor.js.map +1 -0
  163. package/sdk/dist/node/index.d.ts +61 -9
  164. package/sdk/dist/node/index.d.ts.map +1 -1
  165. package/sdk/dist/node/index.js +37 -2
  166. package/sdk/dist/node/index.js.map +1 -1
  167. package/sdk/dist/node/init.d.ts +137 -0
  168. package/sdk/dist/node/init.d.ts.map +1 -0
  169. package/sdk/dist/node/init.js +507 -0
  170. package/sdk/dist/node/init.js.map +1 -0
  171. package/sdk/dist/node/lightning-wallet.d.ts +61 -0
  172. package/sdk/dist/node/lightning-wallet.d.ts.map +1 -0
  173. package/sdk/dist/node/lightning-wallet.js +126 -0
  174. package/sdk/dist/node/lightning-wallet.js.map +1 -0
  175. package/sdk/dist/node/local-actions.d.ts +23 -0
  176. package/sdk/dist/node/local-actions.d.ts.map +1 -0
  177. package/sdk/dist/node/local-actions.js +38 -0
  178. package/sdk/dist/node/local-actions.js.map +1 -0
  179. package/sdk/dist/node/org-context.d.ts +169 -0
  180. package/sdk/dist/node/org-context.d.ts.map +1 -0
  181. package/sdk/dist/node/org-context.js +403 -0
  182. package/sdk/dist/node/org-context.js.map +1 -0
  183. package/sdk/dist/node/status.d.ts +62 -0
  184. package/sdk/dist/node/status.d.ts.map +1 -0
  185. package/sdk/dist/node/status.js +132 -0
  186. package/sdk/dist/node/status.js.map +1 -0
  187. package/sdk/dist/node/vault-target.d.ts +49 -0
  188. package/sdk/dist/node/vault-target.d.ts.map +1 -0
  189. package/sdk/dist/node/vault-target.js +109 -0
  190. package/sdk/dist/node/vault-target.js.map +1 -0
  191. package/sdk/dist/node/wallets.d.ts +242 -0
  192. package/sdk/dist/node/wallets.d.ts.map +1 -0
  193. package/sdk/dist/node/wallets.js +484 -0
  194. package/sdk/dist/node/wallets.js.map +1 -0
  195. package/sdk/dist/project-auth-classification.d.ts +8 -8
  196. package/sdk/dist/project-auth-classification.d.ts.map +1 -1
  197. package/sdk/dist/project-auth-classification.js +7 -7
  198. package/sdk/dist/project-auth-classification.js.map +1 -1
  199. package/sdk/dist/scoped.d.ts +6 -14
  200. package/sdk/dist/scoped.d.ts.map +1 -1
  201. package/sdk/dist/scoped.js +10 -21
  202. package/sdk/dist/scoped.js.map +1 -1
  203. package/sdk/dist/secret-gate.d.ts +377 -0
  204. package/sdk/dist/secret-gate.d.ts.map +1 -0
  205. package/sdk/dist/secret-gate.js +273 -0
  206. package/sdk/dist/secret-gate.js.map +1 -0
  207. package/vault-surface.json +1 -1
  208. package/fixtures/buzz-v0.5.2-cli-capabilities.json +0 -92
  209. package/fixtures/run402-buzz-doctor-v1-contract.json +0 -140
  210. package/lib/buzz-doctor-contract.mjs +0 -97
  211. package/lib/buzz-doctor-contract.test.mjs +0 -134
  212. package/lib/delegates.mjs +0 -189
  213. package/lib/lightning-wallet.mjs +0 -115
  214. package/sdk/dist/delegate-credentials.d.ts +0 -37
  215. package/sdk/dist/delegate-credentials.d.ts.map +0 -1
  216. package/sdk/dist/delegate-credentials.js +0 -41
  217. package/sdk/dist/delegate-credentials.js.map +0 -1
  218. package/sdk/dist/namespaces/delegates.d.ts +0 -50
  219. package/sdk/dist/namespaces/delegates.d.ts.map +0 -1
  220. package/sdk/dist/namespaces/delegates.js +0 -99
  221. package/sdk/dist/namespaces/delegates.js.map +0 -1
  222. package/sdk/dist/namespaces/delegates.types.d.ts +0 -88
  223. package/sdk/dist/namespaces/delegates.types.d.ts.map +0 -1
  224. package/sdk/dist/namespaces/delegates.types.js +0 -10
  225. package/sdk/dist/namespaces/delegates.types.js.map +0 -1
@@ -1,51 +1,49 @@
1
1
  /**
2
- * Active-wallet (profile) resolution for the CLI edge.
2
+ * The `--wallet` edge of the CLI: what has to happen before any subcommand
3
+ * module (and so before `cli/lib/config.mjs` snapshots credential paths) is
4
+ * loaded, and how a wallet-selection refusal is reported.
3
5
  *
4
- * Runs at the top of cli.mjs BEFORE any subcommand module (and therefore
5
- * before cli/lib/config.mjs snapshots its paths) is loaded. Resolves which
6
- * named wallet a command operates on, sets `process.env.RUN402_WALLET` so all
7
- * core path functions resolve under it. Core itself stays env-only — the
8
- * `--wallet` flag and the per-directory `.run402.json` binding are translated
9
- * into the env var here, at the edge.
10
- *
11
- * Precedence (highest first):
12
- * 1. --wallet <name> / --profile <name> (flag; under `repos`, only --wallet —
13
- * `repos create|mirror|recover --profile <name>` is the AWS credential profile)
14
- * 2. RUN402_WALLET / RUN402_PROFILE (env)
15
- * 3. nearest .run402.local.json/.run402.json (directory binding, walk up)
16
- * 4. config.json active_wallet (global `wallets use`)
17
- * 5. "default" (root wallet)
18
- *
19
- * The flag is also the conflict resolver: when env and binding name different
20
- * wallets and no flag is given, that is a hard error (not a silent pick).
6
+ * The selection itself — the precedence chain (flag, RUN402_WALLET, the
7
+ * nearest .run402(.local).json binding, the global `wallets use` default,
8
+ * `default`), the env-vs-binding conflict, and the fail-closed existence
9
+ * check — is the Node SDK's (`resolveWalletSelection`, `assertWalletExists`,
10
+ * `selectWallet`). This module adds only:
11
+ * - reading `--wallet` / `--profile` off argv (`splitWalletFlag`);
12
+ * - which commands must keep working while selection is ambiguous or names
13
+ * a wallet that does not exist yet (the wallet-management group, `init`,
14
+ * `doctor`);
15
+ * - translating a `WalletSelectionError` into `fail()` for the CLI, while
16
+ * `git-remote-run402` (which must never `process.exit()` mid-stream)
17
+ * catches the same error and reports it through its own channel;
18
+ * - the opt-in stderr provenance line.
21
19
  */
22
20
 
23
- import { existsSync, rmSync, writeFileSync } from "node:fs";
24
21
  import { join } from "node:path";
25
22
  import { fail } from "./sdk-errors.mjs";
26
- import { isValidProfileName } from "../core-dist/config.js";
27
- import { getDefaultWallet, profileExists, readMeta, profileDir } from "../core-dist/profiles.js";
23
+ import { profileDir, readMeta } from "../core-dist/profiles.js";
28
24
  import { readWallet } from "../core-dist/wallet.js";
29
- import { describeRejectedValue } from "../core-dist/redact.js";
30
- // The binding file is a CHECKOUT-LEVEL CONTRACT read by more than one surface,
31
- // so its reader lives in core — `run402-mcp` ships core/dist but not cli/, and
32
- // two readers of one file format is exactly the drift worth not having.
33
- // Re-exported here under the names the rest of the CLI already imports.
34
25
  import {
35
26
  findBindingKey,
36
27
  bindingFilePath,
37
28
  readBindingFile,
38
- BINDING_FILE,
29
+ updateBindingFile,
39
30
  } from "../core-dist/binding-file.js";
31
+ import {
32
+ WalletSelectionError,
33
+ assertWalletExists,
34
+ findWalletBinding,
35
+ resolveWalletSelection,
36
+ selectWallet,
37
+ } from "#sdk/node";
40
38
 
41
- export { findBindingKey, bindingFilePath, readBindingFile };
39
+ export { findBindingKey, bindingFilePath, readBindingFile, updateBindingFile, WalletSelectionError };
42
40
 
43
41
  const DEFAULT = "default";
44
42
  const GLOBAL_FLAGS = new Set(["--wallet", "--profile"]);
45
43
  // `repos` owns `--profile` itself (the AWS credential profile for a BYO
46
44
  // destination, a mirror, or a recovery source), so under that command only
47
45
  // `--wallet` selects the wallet; RUN402_WALLET and the directory binding still
48
- // apply. Stripping `--profile` there made the documented s3 flags unreachable.
46
+ // apply.
49
47
  const OWNS_PROFILE_FLAG = new Set(["repos"]);
50
48
  // The `wallets` group is the management + escape surface — it must work even
51
49
  // when selection is ambiguous (so you can `wallets unbind`), and it validates
@@ -57,10 +55,10 @@ const EXISTENCE_EXEMPT = new Set(["wallets", "init", "doctor"]);
57
55
  /**
58
56
  * Split the global --wallet/--profile flag (and its value) out of argv so the
59
57
  * subcommand never sees it — except `--profile` under a command that owns the
60
- * flag (OWNS_PROFILE_FLAG), which is passed through untouched. Pure: no core imports, no side effects. Returns
58
+ * flag (OWNS_PROFILE_FLAG), which is passed through untouched. Pure. Returns
61
59
  * the cleaned argv and the selected flag (`{ flag, value }` or null). Last
62
- * occurrence wins. A missing value is left as `value: undefined` for
63
- * resolveWallet to reject with a precise error.
60
+ * occurrence wins. A missing value is left as `value: undefined` for the
61
+ * resolver to reject with a precise error.
64
62
  */
65
63
  export function splitWalletFlag(rawArgv = []) {
66
64
  const argv = [];
@@ -99,201 +97,46 @@ export function splitWalletFlag(rawArgv = []) {
99
97
  return { argv, walletFlag: flag };
100
98
  }
101
99
 
102
- /**
103
- * MERGE keys into a directory's binding file. A `null` value removes its key;
104
- * a file left with no keys is deleted rather than committed empty.
105
- *
106
- * The file is shared by tiers (`wallet` from `wallets bind`, `org`/`room` from
107
- * `orgs bind`) and unknown keys are preserved, so one tier can never clobber
108
- * another's binding — which a whole-file write did until this existed.
109
- */
110
- export function updateBindingFile(dir, patch) {
111
- const file = bindingFilePath(dir);
112
- const next = { ...readBindingFile(dir) };
113
- for (const [k, v] of Object.entries(patch)) {
114
- if (v === null || v === undefined) delete next[k];
115
- else next[k] = v;
116
- }
117
- if (Object.keys(next).length === 0) {
118
- const existed = existsSync(file);
119
- if (existed) rmSync(file, { force: true });
120
- return { file, contents: null, removed: existed };
121
- }
122
- writeFileSync(file, JSON.stringify(next, null, 2) + "\n");
123
- return { file, contents: next, removed: false };
124
- }
125
-
126
100
  /** Nearest wallet binding walking up from `startDir` to the filesystem root. */
127
101
  export function findBinding(startDir) {
128
- const hit = findBindingKey(startDir, "wallet");
129
- return hit ? { wallet: hit.value, file: hit.file } : null;
102
+ return findWalletBinding(startDir);
130
103
  }
131
104
 
132
105
  /**
133
- * Thrown by the `*Core` functions below instead of calling `fail()` directly
134
- * — carries the exact `{code, message, hint, details}` shape `fail()`
135
- * expects, so a caller that CAN safely call `process.exit()` (the CLI, at
136
- * the top of `cli.mjs`, before any protocol stream has started) does
137
- * `fail(err)` verbatim, and a caller that CANNOT (`git-remote-run402` — see
138
- * that file's own header: `process.exit()` mid-stream can truncate a
139
- * pending stdout write on a pipe) catches it and reports through its own
140
- * non-exiting error path instead.
106
+ * The SDK's selection for command `cmd` (a non-CLI caller passes none), throwing
107
+ * {@link WalletSelectionError}. `git-remote-run402` composes this directly so a
108
+ * refusal never exits mid-protocol.
141
109
  */
142
- export class WalletSelectionError extends Error {
143
- constructor({ code, message, hint, details }) {
144
- super(message);
145
- this.name = "WalletSelectionError";
146
- this.code = code;
147
- this.hint = hint;
148
- this.details = details;
149
- }
150
- }
151
-
152
- // A rejected name is a value we know nothing about — the most likely
153
- // mistake is a typo, but a live Base-mainnet private key pasted into
154
- // RUN402_WALLET (a NAME field, not a key field) is a demonstrated case, and
155
- // echoing the raw value prints it to a terminal, a log, and a session
156
- // transcript. Route it
157
- // through describeRejectedValue() so a short/plain typo still shows in
158
- // full (that's what makes the error useful) while anything long or
159
- // hex-shaped enough to be a secret is redacted instead.
160
- function assertValidNameCore(name, origin) {
161
- if (name === DEFAULT || isValidProfileName(name)) return;
162
- throw new WalletSelectionError({
163
- code: "BAD_WALLET_NAME",
164
- message: `Invalid wallet name ${JSON.stringify(describeRejectedValue(name))} (from ${origin}).`,
165
- hint: "Wallet names must match /^[a-z0-9][a-z0-9_-]{0,63}$/ (lowercase letters, digits, '_' and '-'). If a private key or other secret ended up here, it does not belong in a NAME field — see `run402 wallets import` — and should be treated as compromised.",
166
- details: { name: describeRejectedValue(name), origin },
167
- });
110
+ export function resolveWalletCore({ walletFlag, env = {}, cwd = process.cwd(), cmd } = {}) {
111
+ return resolveWalletSelection({ walletFlag, env, cwd, allowConflict: CONFLICT_EXEMPT.has(cmd) });
168
112
  }
169
113
 
170
- function assertValidName(name, origin) {
171
- try {
172
- assertValidNameCore(name, origin);
173
- } catch (err) {
174
- if (err instanceof WalletSelectionError) fail(err);
175
- throw err;
176
- }
114
+ /** Fail closed on a missing wallet unless `cmd` creates or manages wallets; throws {@link WalletSelectionError}. */
115
+ export function enforceWalletExistsCore(resolved, cmd) {
116
+ if (EXISTENCE_EXEMPT.has(cmd)) return;
117
+ assertWalletExists(resolved);
177
118
  }
178
119
 
179
- /**
180
- * Pure precedence resolution + conflict detection — no flag layer, never
181
- * prompts, never calls `fail()`/`process.exit()`. Returns
182
- * `{ name, source, sourceDetail }` or throws {@link WalletSelectionError}.
183
- *
184
- * Shared verbatim by `resolveWallet` below (the CLI wrapper: `--wallet` flag
185
- * layered on top, failures routed through `fail()`) AND by
186
- * `git-remote-run402` (no flag layer — the helper has no argv flags at all
187
- * — failures routed through its own protocol-safe error reporting). Calling
188
- * `getSdk()` directly instead would run no wallet selection at all, so a
189
- * `.run402.json` binding — and even the global `wallets use` default —
190
- * would silently never reach the helper; only the `RUN402_WALLET` env layer
191
- * would work.
192
- *
193
- * Precedence beneath an optional flag (highest first): `RUN402_WALLET` /
194
- * `RUN402_PROFILE` env > nearest `.run402.local.json`/`.run402.json` binding
195
- * (walked from `cwd`, which callers choose deliberately — see this file's
196
- * own module doc and `git-remote-run402`'s "WHICH REPOSITORY" note: the
197
- * binding walk must start from the REPOSITORY directory when one is
198
- * resolvable, cwd only for repository-free commands) > `wallets use` global
199
- * default > `"default"`. env-vs-binding disagreement is a hard error unless
200
- * `cmd` is conflict-exempt (`wallets`) — same rule for every caller.
201
- */
202
- export function resolveWalletCore({ walletFlag, env = {}, cwd = process.cwd(), cmd } = {}) {
203
- if (walletFlag) {
204
- if (walletFlag.value === undefined || walletFlag.value === "") {
205
- throw new WalletSelectionError({ code: "BAD_FLAG", message: `${walletFlag.flag} requires a value`, details: { flag: walletFlag.flag } });
206
- }
207
- assertValidNameCore(walletFlag.value, walletFlag.flag);
208
- return { name: walletFlag.value, source: "flag", sourceDetail: walletFlag.flag };
209
- }
210
-
211
- const envRaw = env.RUN402_WALLET ?? env.RUN402_PROFILE;
212
- const envName = typeof envRaw === "string" && envRaw.trim() ? envRaw.trim() : null;
213
- const binding = findBinding(cwd);
214
-
215
- if (envName && binding && envName !== binding.wallet && !CONFLICT_EXEMPT.has(cmd)) {
216
- // This conflict check runs BEFORE assertValidNameCore below, so an
217
- // unvalidated (possibly secret-shaped)
218
- // RUN402_WALLET reaches here first whenever a directory binding exists
219
- // (routine per this repo's own fleet-coordination convention). Redact
220
- // the env side the same way the format check would; `binding.wallet`
221
- // comes from a committed .run402.json, which by convention never holds
222
- // a secret (`wallets bind` validates the name before writing it).
223
- throw new WalletSelectionError({
224
- code: "WALLET_SELECTION_CONFLICT",
225
- message: `Ambiguous wallet: RUN402_WALLET=${describeRejectedValue(envName)} but ${binding.file} selects '${binding.wallet}'.`,
226
- hint: "Resolve with one of: pass --wallet <name>, unset RUN402_WALLET, or run402 wallets unbind.",
227
- details: { env_wallet: describeRejectedValue(envName), binding_wallet: binding.wallet, binding_file: binding.file },
228
- });
229
- }
230
-
231
- if (envName) {
232
- assertValidNameCore(envName, "RUN402_WALLET");
233
- return { name: envName, source: "env", sourceDetail: "RUN402_WALLET" };
234
- }
235
- if (binding) {
236
- assertValidNameCore(binding.wallet, binding.file);
237
- return { name: binding.wallet, source: "binding", sourceDetail: binding.file };
238
- }
239
- const def = getDefaultWallet();
240
- if (def && def !== DEFAULT) return { name: def, source: "config", sourceDetail: "wallets use" };
241
- return { name: DEFAULT, source: "default", sourceDetail: null };
120
+ function failSelection(err) {
121
+ if (err instanceof WalletSelectionError) fail(err);
122
+ throw err;
242
123
  }
243
124
 
244
- /** CLI wrapper over {@link resolveWalletCore}: identical resolution, `fail()` on error. */
125
+ /** {@link resolveWalletCore}, `fail()` on a refusal. */
245
126
  export function resolveWallet(opts) {
246
127
  try {
247
128
  return resolveWalletCore(opts);
248
129
  } catch (err) {
249
- if (err instanceof WalletSelectionError) fail(err);
250
- throw err;
130
+ return failSelection(err);
251
131
  }
252
132
  }
253
133
 
254
- function looksLikeAddress(s) {
255
- return typeof s === "string" && /^0x[a-fA-F0-9]{40}$/.test(s);
256
- }
257
-
258
- /**
259
- * Fail-closed check that a non-default, non-exempt selection names a wallet
260
- * that actually exists locally — pure, throws {@link WalletSelectionError}.
261
- * `cmd` is `undefined` for a non-CLI caller (`git-remote-run402`), which
262
- * matches neither `EXISTENCE_EXEMPT` entry, so the check always runs there.
263
- */
264
- export function enforceWalletExistsCore({ name, source }, cmd) {
265
- if (name === DEFAULT) return;
266
- if (EXISTENCE_EXEMPT.has(cmd)) return;
267
- if (profileExists(name)) return;
268
- // `name` already passed the format check (assertValidNameCore), which
269
- // constrains it to lowercase/digits/'_'/'-' — but a bare 64-hex private
270
- // key with no "0x" prefix satisfies that charset too, so this "not found"
271
- // path can still see raw key material. looksLikeAddress() is safe to echo
272
- // (an address is public); anything else goes through describeRejectedValue(),
273
- // which leaves a short/plain name untouched but redacts anything shaped
274
- // like a secret — so only the untouched case gets a "create it" command
275
- // that's actually safe (and sensible) to suggest re-typing.
276
- const shown = describeRejectedValue(name);
277
- const hint = looksLikeAddress(name)
278
- ? `'${name}' looks like an address. For billing use: run402 billing ... --wallet-address ${name}`
279
- : shown === name
280
- ? `Run 'run402 wallets list' to see wallets, or 'run402 wallets new ${name}' to create it.`
281
- : "Run 'run402 wallets list' to see wallets. This value was not shown because it looks like a secret rather than a wallet name — if a private key or other credential landed here, treat it as compromised.";
282
- throw new WalletSelectionError({
283
- code: "WALLET_NOT_FOUND",
284
- message: `No local wallet named '${shown}'.`,
285
- hint,
286
- details: { wallet: shown, source },
287
- });
288
- }
289
-
290
- /** CLI wrapper over {@link enforceWalletExistsCore}: identical check, `fail()` on error. */
134
+ /** {@link enforceWalletExistsCore}, `fail()` on a refusal. */
291
135
  export function enforceWalletExists(resolved, cmd) {
292
136
  try {
293
137
  enforceWalletExistsCore(resolved, cmd);
294
138
  } catch (err) {
295
- if (err instanceof WalletSelectionError) fail(err);
296
- throw err;
139
+ failSelection(err);
297
140
  }
298
141
  }
299
142
 
@@ -328,31 +171,22 @@ export function emitProvenance({ name, source, sourceDetail }, { cmd, quiet, sho
328
171
  }
329
172
 
330
173
  /**
331
- * Orchestrate edge resolution: resolve → fail-closed → publish to the env so
332
- * core paths resolve → provenance. Returns the resolved selection.
174
+ * Resolve, fail closed, and publish the selection to the environment (the
175
+ * SDK's `selectWallet`), then the optional provenance line.
333
176
  */
334
177
  export function applyWalletSelection({ walletFlag, cmd, cwd = process.cwd(), env = process.env, quiet = false } = {}) {
335
- // Capture the pre-resolution signals so `wallets current` can report
336
- // provenance and any env-vs-binding divergence (it can't recompute them once
337
- // we overwrite RUN402_WALLET below).
338
- const envRaw = env.RUN402_WALLET ?? env.RUN402_PROFILE;
339
- const envName = typeof envRaw === "string" && envRaw.trim() ? envRaw.trim() : null;
340
- const binding = findBinding(cwd);
341
-
342
- const resolved = resolveWallet({ walletFlag, env, cwd, cmd });
343
- enforceWalletExists(resolved, cmd);
344
-
345
- // Publish to the env so all core path functions resolve under this wallet.
346
- env.RUN402_WALLET = resolved.name;
347
- env.RUN402_ACTIVE_WALLET_JSON = JSON.stringify({
348
- name: resolved.name,
349
- source: resolved.source,
350
- sourceDetail: resolved.sourceDetail,
351
- binding: binding ? { wallet: binding.wallet, file: binding.file } : null,
352
- envName,
353
- diverged: !!(envName && binding && envName !== binding.wallet),
354
- });
355
-
178
+ let resolved;
179
+ try {
180
+ resolved = selectWallet({
181
+ walletFlag,
182
+ env,
183
+ cwd,
184
+ allowConflict: CONFLICT_EXEMPT.has(cmd),
185
+ allowMissing: EXISTENCE_EXEMPT.has(cmd),
186
+ });
187
+ } catch (err) {
188
+ failSelection(err);
189
+ }
356
190
  emitProvenance(resolved, {
357
191
  cmd,
358
192
  quiet,