@run402/sdk 4.97.0 → 4.99.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 (138) hide show
  1. package/README.md +2 -2
  2. package/core-dist/binding-file.d.ts +14 -0
  3. package/core-dist/binding-file.js +28 -1
  4. package/core-dist/wallet.js +3 -3
  5. package/dist/errors.d.ts +24 -2
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/errors.js +44 -2
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +18 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +12 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/kernel.d.ts +34 -0
  14. package/dist/kernel.d.ts.map +1 -1
  15. package/dist/kernel.js +15 -1
  16. package/dist/kernel.js.map +1 -1
  17. package/dist/namespaces/admin.d.ts.map +1 -1
  18. package/dist/namespaces/admin.js +8 -0
  19. package/dist/namespaces/admin.js.map +1 -1
  20. package/dist/namespaces/agent.d.ts +2 -2
  21. package/dist/namespaces/agent.d.ts.map +1 -1
  22. package/dist/namespaces/agent.js +4 -0
  23. package/dist/namespaces/agent.js.map +1 -1
  24. package/dist/namespaces/agent.types.d.ts +5 -0
  25. package/dist/namespaces/agent.types.d.ts.map +1 -1
  26. package/dist/namespaces/apps.d.ts.map +1 -1
  27. package/dist/namespaces/apps.js +2 -0
  28. package/dist/namespaces/apps.js.map +1 -1
  29. package/dist/namespaces/auth.d.ts.map +1 -1
  30. package/dist/namespaces/auth.js +9 -0
  31. package/dist/namespaces/auth.js.map +1 -1
  32. package/dist/namespaces/branches.d.ts.map +1 -1
  33. package/dist/namespaces/branches.js +2 -0
  34. package/dist/namespaces/branches.js.map +1 -1
  35. package/dist/namespaces/ci.d.ts.map +1 -1
  36. package/dist/namespaces/ci.js +2 -0
  37. package/dist/namespaces/ci.js.map +1 -1
  38. package/dist/namespaces/credentials.d.ts +0 -23
  39. package/dist/namespaces/credentials.d.ts.map +1 -1
  40. package/dist/namespaces/credentials.js +29 -0
  41. package/dist/namespaces/credentials.js.map +1 -1
  42. package/dist/namespaces/escalations.d.ts.map +1 -1
  43. package/dist/namespaces/escalations.js +2 -0
  44. package/dist/namespaces/escalations.js.map +1 -1
  45. package/dist/namespaces/grants.d.ts.map +1 -1
  46. package/dist/namespaces/grants.js +4 -0
  47. package/dist/namespaces/grants.js.map +1 -1
  48. package/dist/namespaces/live.d.ts +0 -16
  49. package/dist/namespaces/live.d.ts.map +1 -1
  50. package/dist/namespaces/live.js +19 -0
  51. package/dist/namespaces/live.js.map +1 -1
  52. package/dist/namespaces/me.d.ts.map +1 -1
  53. package/dist/namespaces/me.js +3 -0
  54. package/dist/namespaces/me.js.map +1 -1
  55. package/dist/namespaces/org.d.ts.map +1 -1
  56. package/dist/namespaces/org.js +3 -0
  57. package/dist/namespaces/org.js.map +1 -1
  58. package/dist/namespaces/projects.d.ts.map +1 -1
  59. package/dist/namespaces/projects.js +5 -0
  60. package/dist/namespaces/projects.js.map +1 -1
  61. package/dist/namespaces/repos.d.ts.map +1 -1
  62. package/dist/namespaces/repos.js +5 -0
  63. package/dist/namespaces/repos.js.map +1 -1
  64. package/dist/namespaces/rooms.d.ts.map +1 -1
  65. package/dist/namespaces/rooms.js +3 -0
  66. package/dist/namespaces/rooms.js.map +1 -1
  67. package/dist/namespaces/session.d.ts.map +1 -1
  68. package/dist/namespaces/session.js +18 -0
  69. package/dist/namespaces/session.js.map +1 -1
  70. package/dist/namespaces/transfers.d.ts.map +1 -1
  71. package/dist/namespaces/transfers.js +3 -0
  72. package/dist/namespaces/transfers.js.map +1 -1
  73. package/dist/namespaces/wallets.d.ts.map +1 -1
  74. package/dist/namespaces/wallets.js +2 -0
  75. package/dist/namespaces/wallets.js.map +1 -1
  76. package/dist/namespaces/write-approval.d.ts.map +1 -1
  77. package/dist/namespaces/write-approval.js +3 -0
  78. package/dist/namespaces/write-approval.js.map +1 -1
  79. package/dist/node/actions-node.d.ts.map +1 -1
  80. package/dist/node/actions-node.js +3 -0
  81. package/dist/node/actions-node.js.map +1 -1
  82. package/dist/node/buzz-doctor-contract.d.ts +155 -0
  83. package/dist/node/buzz-doctor-contract.d.ts.map +1 -0
  84. package/dist/node/buzz-doctor-contract.js +385 -0
  85. package/dist/node/buzz-doctor-contract.js.map +1 -0
  86. package/dist/node/buzz-doctor.d.ts +155 -0
  87. package/dist/node/buzz-doctor.d.ts.map +1 -0
  88. package/dist/node/buzz-doctor.js +810 -0
  89. package/dist/node/buzz-doctor.js.map +1 -0
  90. package/dist/node/credentials.d.ts +9 -4
  91. package/dist/node/credentials.d.ts.map +1 -1
  92. package/dist/node/credentials.js +1 -1
  93. package/dist/node/credentials.js.map +1 -1
  94. package/dist/node/diagnostics.d.ts +45 -0
  95. package/dist/node/diagnostics.d.ts.map +1 -0
  96. package/dist/node/diagnostics.js +67 -0
  97. package/dist/node/diagnostics.js.map +1 -0
  98. package/dist/node/doctor.d.ts +101 -0
  99. package/dist/node/doctor.d.ts.map +1 -0
  100. package/dist/node/doctor.js +618 -0
  101. package/dist/node/doctor.js.map +1 -0
  102. package/dist/node/index.d.ts +57 -5
  103. package/dist/node/index.d.ts.map +1 -1
  104. package/dist/node/index.js +36 -1
  105. package/dist/node/index.js.map +1 -1
  106. package/dist/node/init.d.ts +137 -0
  107. package/dist/node/init.d.ts.map +1 -0
  108. package/dist/node/init.js +507 -0
  109. package/dist/node/init.js.map +1 -0
  110. package/dist/node/lightning-wallet.d.ts +61 -0
  111. package/dist/node/lightning-wallet.d.ts.map +1 -0
  112. package/dist/node/lightning-wallet.js +126 -0
  113. package/dist/node/lightning-wallet.js.map +1 -0
  114. package/dist/node/local-actions.d.ts +23 -0
  115. package/dist/node/local-actions.d.ts.map +1 -0
  116. package/dist/node/local-actions.js +38 -0
  117. package/dist/node/local-actions.js.map +1 -0
  118. package/dist/node/org-context.d.ts +169 -0
  119. package/dist/node/org-context.d.ts.map +1 -0
  120. package/dist/node/org-context.js +403 -0
  121. package/dist/node/org-context.js.map +1 -0
  122. package/dist/node/status.d.ts +62 -0
  123. package/dist/node/status.d.ts.map +1 -0
  124. package/dist/node/status.js +132 -0
  125. package/dist/node/status.js.map +1 -0
  126. package/dist/node/vault-target.d.ts +49 -0
  127. package/dist/node/vault-target.d.ts.map +1 -0
  128. package/dist/node/vault-target.js +109 -0
  129. package/dist/node/vault-target.js.map +1 -0
  130. package/dist/node/wallets.d.ts +242 -0
  131. package/dist/node/wallets.d.ts.map +1 -0
  132. package/dist/node/wallets.js +484 -0
  133. package/dist/node/wallets.js.map +1 -0
  134. package/dist/secret-gate.d.ts +377 -0
  135. package/dist/secret-gate.d.ts.map +1 -0
  136. package/dist/secret-gate.js +273 -0
  137. package/dist/secret-gate.js.map +1 -0
  138. package/package.json +2 -1
@@ -0,0 +1,810 @@
1
+ /**
2
+ * `r.buzz.doctor()` — the zero-mutation Buzz setup preflight
3
+ * (`run402.buzz-doctor.v1`), owned by the Node SDK: the managed session's
4
+ * shell and Node runtime, the Run402 CLI, the managed Buzz CLI sidecar and
5
+ * its capabilities, the public agent identity, the Run402 API and console
6
+ * origins (measured with `r.diagnostics.probeOrigin`), the Buzz relay (a
7
+ * DNS-pinned NIP-11 read), and the explicit wallet profile. Every check's
8
+ * status is `ok | warning | blocked`; a blocked or warning check carries
9
+ * exactly one repair action from the frozen matrix below.
10
+ *
11
+ * `run402 doctor --buzz` is a shim over this. The Run402 CLI facts (its
12
+ * version, how it was installed, whether it is current) are the calling
13
+ * program's own, supplied as hooks; without them the `run402_cli` check
14
+ * reports the CLI unavailable.
15
+ */
16
+ /* eslint-disable @typescript-eslint/no-explicit-any */
17
+ import { spawnSync } from "node:child_process";
18
+ import { accessSync, constants as fsConstants, existsSync, realpathSync } from "node:fs";
19
+ import { lookup as dnsLookup } from "node:dns/promises";
20
+ import { request as httpsRequest } from "node:https";
21
+ import { delimiter, extname, join } from "node:path";
22
+ import ipaddr from "ipaddr.js";
23
+ import { bech32 } from "@scure/base";
24
+ import { getActiveProfile as coreGetActiveProfile, getApiBase } from "../../core-dist/config.js";
25
+ import { profileExists, readMeta } from "../../core-dist/profiles.js";
26
+ import { LocalError } from "../errors.js";
27
+ import { Buzz } from "../namespaces/buzz.js";
28
+ import { BUZZ_CLI_CAPABILITIES, BUZZ_DOCTOR_CHECK_ORDER, BUZZ_DOCTOR_CONTRACT, BUZZ_DOCTOR_CONTRACT_ID, } from "./buzz-doctor-contract.js";
29
+ import { Diagnostics } from "./diagnostics.js";
30
+ const BUZZ_CAPABILITIES = BUZZ_CLI_CAPABILITIES;
31
+ export const BUZZ_DOCTOR_TIMEOUT_MS = 3_000;
32
+ export const BUZZ_DOCTOR_MAX_RESPONSE_BYTES = 64 * 1024;
33
+ export const BUZZ_DOCTOR_MIN_NODE_MAJOR = 22;
34
+ // v4.17.2 is the first client whose Buzz doctor keeps a safely contained relay
35
+ // availability failure warning-only for founder-agent setup. Older clients can
36
+ // expose the same command surface while enforcing the wrong setup semantics.
37
+ export const BUZZ_DOCTOR_MIN_RUN402_VERSION = "4.17.2";
38
+ const SPEND_NONE = Object.freeze({ currency: "USD", max_amount: "0" });
39
+ const SAFE_TOKEN = /^[A-Za-z0-9_./:=@+,-]+$/;
40
+ export const BUZZ_DOCTOR_REPAIR_MATRIX = Object.freeze({
41
+ BUZZ_PREFLIGHT_SHELL_UNAVAILABLE: { type: "repair_buzz_agent_runtime", surface: "buzz_settings" },
42
+ BUZZ_PREFLIGHT_NODE_UNAVAILABLE: { type: "repair_buzz_node_runtime", surface: "buzz_settings" },
43
+ BUZZ_PREFLIGHT_NODE_INCOMPATIBLE: { type: "repair_buzz_node_runtime", surface: "buzz_settings" },
44
+ BUZZ_PREFLIGHT_RUN402_UNAVAILABLE: { type: "install_run402_cli", surface: "shell" },
45
+ BUZZ_PREFLIGHT_RUN402_INCOMPATIBLE: { type: ["upgrade_run402_cli", "inspect_run402_cli", "install_run402_cli"], surface: "shell" },
46
+ BUZZ_PREFLIGHT_RUN402_UPDATE_AVAILABLE: { type: "upgrade_run402_cli", surface: "shell" },
47
+ BUZZ_PREFLIGHT_BUZZ_CLI_UNAVAILABLE: { type: "repair_buzz_cli_sidecar", surface: "buzz_settings" },
48
+ BUZZ_PREFLIGHT_BUZZ_CLI_INCOMPATIBLE: { type: "repair_buzz_cli_sidecar", surface: "buzz_settings" },
49
+ BUZZ_AGENT_TARGET_REQUIRED: { type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat" },
50
+ BUZZ_AGENT_TARGET_UNVERIFIED: { type: ["publish_buzz_public_profile", "repair_buzz_cli_sidecar"], surface: ["buzz_chat", "buzz_settings"] },
51
+ BUZZ_AGENT_TARGET_MISMATCH: { type: "select_expected_buzz_agent", surface: "buzz_chat" },
52
+ BUZZ_PREFLIGHT_API_UNREACHABLE: { type: "restore_run402_api_access", surface: "buzz_settings" },
53
+ BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE: { type: "restore_run402_console_access", surface: "buzz_settings" },
54
+ BUZZ_PREFLIGHT_RELAY_UNSAFE: { type: "repair_buzz_relay_url", surface: "buzz_settings" },
55
+ BUZZ_PREFLIGHT_RELAY_UNREACHABLE: {
56
+ type: ["repair_buzz_relay_tls", "repair_buzz_relay_dns", "repair_buzz_relay_service", "restore_buzz_relay_access"],
57
+ surface: "buzz_settings",
58
+ },
59
+ BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED: { type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat" },
60
+ BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND: { type: "create_buzz_wallet_profile", surface: "shell" },
61
+ BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH: { type: "rerun_buzz_doctor", surface: "shell" },
62
+ });
63
+ export function normalizeNostrSubject(value) {
64
+ if (typeof value !== "string")
65
+ return null;
66
+ const normalized = value.trim().toLowerCase();
67
+ if (/^[0-9a-f]{64}$/.test(normalized))
68
+ return normalized;
69
+ if (!normalized.startsWith("npub1"))
70
+ return null;
71
+ try {
72
+ const decoded = bech32.decode(normalized);
73
+ if (decoded.prefix !== "npub")
74
+ return null;
75
+ const bytes = Uint8Array.from(bech32.fromWords(decoded.words));
76
+ return bytes.length === 32 ? Buffer.from(bytes).toString("hex") : null;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ const defaultDiagnostics = new Diagnostics();
83
+ export async function buildBuzzDoctorReport({ expectedSubjectHex = null, run402Cli, env = process.env, argv = process.argv, execPath = process.execPath, cliExecutable = process.argv[1], cwd = process.cwd(), platform = process.platform, now = Date.now(), runCommand = defaultRunCommand, lookup = dnsLookup, pinnedRelayRead = defaultPinnedRelayRead, originProbe = (url, { timeoutMs }) => defaultDiagnostics.probeOrigin(url, { timeoutMs }), getActiveProfile = coreGetActiveProfile, profileExistsImpl = profileExists, readMetaImpl = readMeta, apiOrigin = getApiBase(), consoleOrigin = "https://console.run402.com", } = {}) {
84
+ const shellPath = configuredShell(env, platform);
85
+ const nodeExecutable = safeRealpath(execPath) ?? execPath ?? null;
86
+ const run402Executable = safeRealpath(cliExecutable) ?? cliExecutable ?? null;
87
+ const walletProfile = getActiveProfile();
88
+ const relay = parseRelayOrigin(env.BUZZ_RELAY_URL);
89
+ const binding = {
90
+ contract_id: BUZZ_DOCTOR_CONTRACT_ID,
91
+ expected_subject_hex: expectedSubjectHex,
92
+ wallet_profile: walletProfile,
93
+ node_executable: nodeExecutable,
94
+ run402_executable: run402Executable,
95
+ relay_origin: relay?.origin ?? null,
96
+ };
97
+ const sessionShell = checkSessionShell({ shellPath, platform, runCommand });
98
+ const nodeRuntime = checkNodeRuntime({ nodeExecutable, runCommand });
99
+ const run402CliCheck = await checkRun402Cli({ cwd, env, argv, run402Executable: run402Cli ? run402Executable : null, hooks: run402Cli });
100
+ const buzzExecutable = findExecutable("buzz", env, platform);
101
+ const buzzCli = checkBuzzCli({ buzzExecutable, runCommand });
102
+ const [buzzAgentTarget, run402Api, run402Console, buzzRelay, walletProfileCheck] = await Promise.all([
103
+ checkBuzzAgentTarget({ expectedSubjectHex, buzzExecutable, buzzCli, runCommand }),
104
+ checkOrigin("run402_api", apiOrigin, "BUZZ_PREFLIGHT_API_UNREACHABLE", originProbe, apiRepair(apiOrigin)),
105
+ checkOrigin("run402_console", consoleOrigin, "BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE", originProbe, consoleRepair()),
106
+ checkBuzzRelay({ relay, lookup, pinnedRelayRead }),
107
+ Promise.resolve(checkWalletProfile({ walletProfile, env, profileExistsImpl, readMetaImpl, expectedSubjectHex })),
108
+ ]);
109
+ const checks = [
110
+ sessionShell,
111
+ nodeRuntime,
112
+ run402CliCheck,
113
+ buzzCli,
114
+ buzzAgentTarget,
115
+ run402Api,
116
+ run402Console,
117
+ buzzRelay,
118
+ walletProfileCheck,
119
+ ];
120
+ if (checks.some((check, index) => check.name !== BUZZ_DOCTOR_CHECK_ORDER[index])) {
121
+ throw new LocalError("Buzz doctor check order drifted from the frozen contract", "running the Buzz preflight");
122
+ }
123
+ return {
124
+ ok: checks.every((check) => check.status !== "blocked"),
125
+ mode: "buzz",
126
+ contract_id: BUZZ_DOCTOR_CONTRACT_ID,
127
+ generated_at: new Date(now).toISOString(),
128
+ mutation_state: BUZZ_DOCTOR_CONTRACT.zero_mutation.mutation_state,
129
+ binding,
130
+ checks,
131
+ telemetry: {
132
+ status: env.RUN402_TELEMETRY === "0" ? "disabled" : "not_configured",
133
+ queued: false,
134
+ },
135
+ };
136
+ }
137
+ function checkSessionShell({ shellPath, platform, runCommand }) {
138
+ if (!shellPath)
139
+ return blocked("session_shell", "BUZZ_PREFLIGHT_SHELL_UNAVAILABLE", "The managed session has no configured shell executable.", shellRepair());
140
+ const args = platform === "win32" ? ["/d", "/s", "/c", "exit", "0"] : ["-c", ":"];
141
+ const result = runCommand(shellPath, args, { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false });
142
+ if (!commandPassed(result)) {
143
+ return blocked("session_shell", "BUZZ_PREFLIGHT_SHELL_UNAVAILABLE", "The configured shell cannot execute a bounded no-op.", shellRepair(), {
144
+ shell: safePath(shellPath),
145
+ failure: commandFailureClass(result),
146
+ });
147
+ }
148
+ return ok("session_shell", { shell: safePath(shellPath) });
149
+ }
150
+ function checkNodeRuntime({ nodeExecutable, runCommand }) {
151
+ if (!nodeExecutable)
152
+ return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_UNAVAILABLE", "The managed session has no Node executable.", nodeRepair());
153
+ const result = runCommand(nodeExecutable, ["--version"], { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false });
154
+ if (!commandPassed(result)) {
155
+ return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_UNAVAILABLE", "The exact Node executable cannot be spawned.", nodeRepair(), {
156
+ executable: safePath(nodeExecutable),
157
+ failure: commandFailureClass(result),
158
+ });
159
+ }
160
+ const version = String(result.stdout ?? "").trim();
161
+ const major = Number(version.match(/^v?(\d+)/)?.[1]);
162
+ if (!Number.isSafeInteger(major) || major < BUZZ_DOCTOR_MIN_NODE_MAJOR) {
163
+ return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_INCOMPATIBLE", `Node ${BUZZ_DOCTOR_MIN_NODE_MAJOR}+ is required by this Run402 package.`, nodeRepair(), {
164
+ executable: safePath(nodeExecutable),
165
+ version: version || null,
166
+ required_major: BUZZ_DOCTOR_MIN_NODE_MAJOR,
167
+ });
168
+ }
169
+ return ok("node_runtime", { executable: safePath(nodeExecutable), version, major, compatibility_source: "version" });
170
+ }
171
+ async function checkRun402Cli({ cwd, env, argv, run402Executable, hooks }) {
172
+ if (!run402Executable || !hooks) {
173
+ return blocked("run402_cli", "BUZZ_PREFLIGHT_RUN402_UNAVAILABLE", "The Run402 CLI executable is unavailable.", run402InstallRepair());
174
+ }
175
+ const updateCheck = hooks.updateCheck ?? (async () => null);
176
+ const version = hooks.currentVersion();
177
+ const install = hooks.detectInstall({ cwd, env, argv, execPath: run402Executable });
178
+ if (compareMajorMinorPatch(version, BUZZ_DOCTOR_MIN_RUN402_VERSION) < 0) {
179
+ return blocked("run402_cli", "BUZZ_PREFLIGHT_RUN402_INCOMPATIBLE", "The executing Run402 CLI predates the Buzz doctor contract.", run402RepairForInstall(install, cwd), {
180
+ version,
181
+ minimum_version: BUZZ_DOCTOR_MIN_RUN402_VERSION,
182
+ executable: safePath(run402Executable),
183
+ install_context: install.kind,
184
+ });
185
+ }
186
+ let update = null;
187
+ try {
188
+ update = await updateCheck({ refresh: false, cwd, env, argv, execPath: run402Executable, current: version });
189
+ }
190
+ catch {
191
+ update = null;
192
+ }
193
+ const value = {
194
+ version,
195
+ executable: safePath(run402Executable),
196
+ install_context: install.kind,
197
+ install_confidence: install.confidence,
198
+ package_manager: install.package_manager,
199
+ compatibility_source: "version_and_capability_probe",
200
+ required_capabilities: ["doctor --buzz", "doctor --buzz-agent", "wallets current", "whoami", "identity link nostr"],
201
+ };
202
+ if (update?.status === "warning") {
203
+ const rawAction = update?.value?.next_actions?.[0];
204
+ return warning("run402_cli", "BUZZ_PREFLIGHT_RUN402_UPDATE_AVAILABLE", "A newer compatible Run402 CLI is available; setup may continue.", normalizeUpgradeAction(rawAction, install, cwd), value);
205
+ }
206
+ return ok("run402_cli", value);
207
+ }
208
+ function checkBuzzCli({ buzzExecutable, runCommand }) {
209
+ if (!buzzExecutable) {
210
+ return blocked("buzz_cli", "BUZZ_PREFLIGHT_BUZZ_CLI_UNAVAILABLE", "The managed Buzz CLI sidecar is unavailable.", buzzSidecarRepair());
211
+ }
212
+ const observations = [];
213
+ for (const probe of BUZZ_CAPABILITIES.help_probes) {
214
+ const args = probe.argv.slice(1);
215
+ const result = runCommand(buzzExecutable, args, { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false });
216
+ const output = String(result?.stdout ?? "");
217
+ const compatible = commandPassed(result) && probe.stdout_contains.every((fragment) => output.includes(fragment));
218
+ observations.push({ capability: args.join(" "), compatible });
219
+ if (!compatible) {
220
+ return blocked("buzz_cli", "BUZZ_PREFLIGHT_BUZZ_CLI_INCOMPATIBLE", "The managed Buzz CLI lacks a released capability required by setup.", buzzSidecarRepair(), {
221
+ executable: safePath(buzzExecutable),
222
+ fixture_id: BUZZ_CAPABILITIES.fixture_id,
223
+ compatibility_source: "capability_probe",
224
+ observations,
225
+ });
226
+ }
227
+ }
228
+ return ok("buzz_cli", {
229
+ executable: safePath(buzzExecutable),
230
+ fixture_id: BUZZ_CAPABILITIES.fixture_id,
231
+ install_context: "managed_buzz_sidecar",
232
+ compatibility_source: "capability_probe",
233
+ version_status: "version_unknown_capabilities_ok",
234
+ observations,
235
+ });
236
+ }
237
+ async function checkBuzzAgentTarget({ expectedSubjectHex, buzzExecutable, buzzCli, runCommand }) {
238
+ if (!expectedSubjectHex) {
239
+ return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_REQUIRED", "Buzz setup requires the intended agent's public npub or hex subject.", targetRequiredRepair());
240
+ }
241
+ if (buzzCli.status === "blocked" || !buzzExecutable) {
242
+ return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The active Buzz agent cannot be proven until the managed Buzz CLI is repaired.", buzzSidecarRepair(), {
243
+ expected_subject_hex: expectedSubjectHex,
244
+ observed_subject_hex: null,
245
+ });
246
+ }
247
+ const result = runCommand(buzzExecutable, ["users", "get"], { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false });
248
+ if (!commandPassed(result)) {
249
+ return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The released Buzz public self query did not return a verifiable profile.", targetUnverifiedRepair(), {
250
+ expected_subject_hex: expectedSubjectHex,
251
+ observed_subject_hex: null,
252
+ failure: commandFailureClass(result),
253
+ });
254
+ }
255
+ let parsed;
256
+ try {
257
+ parsed = JSON.parse(String(result.stdout ?? ""));
258
+ }
259
+ catch {
260
+ parsed = null;
261
+ }
262
+ const observed = Array.isArray(parsed) && parsed.length === 1 ? normalizeNostrSubject(parsed[0]?.pubkey) : null;
263
+ if (!observed) {
264
+ return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The released Buzz public self query returned no single public signer profile.", targetUnverifiedRepair(), {
265
+ expected_subject_hex: expectedSubjectHex,
266
+ observed_subject_hex: null,
267
+ });
268
+ }
269
+ if (observed !== expectedSubjectHex) {
270
+ return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_MISMATCH", "The active Buzz CLI identity is not the explicitly requested managed agent.", targetMismatchRepair(), {
271
+ expected_subject_hex: expectedSubjectHex,
272
+ observed_subject_hex: observed,
273
+ });
274
+ }
275
+ return ok("buzz_agent_target", { expected_subject_hex: expectedSubjectHex, observed_subject_hex: observed, observation: "buzz users get" });
276
+ }
277
+ async function checkOrigin(name, origin, code, originProbe, repair) {
278
+ try {
279
+ const url = new URL("/status", origin);
280
+ if (name === "run402_console")
281
+ url.pathname = "/";
282
+ const result = await originProbe(url, { timeoutMs: BUZZ_DOCTOR_TIMEOUT_MS, maxBytes: BUZZ_DOCTOR_MAX_RESPONSE_BYTES });
283
+ if (!result?.reachable)
284
+ throw new ProbeFailure(result?.error ?? `http_${result?.status ?? "unknown"}`);
285
+ return ok(name, { origin: new URL(origin).origin, elapsed_ms: result.elapsed_ms ?? null });
286
+ }
287
+ catch (error) {
288
+ return blocked(name, code, `${name === "run402_api" ? "Run402 API" : "Run402 console"} reachability failed.`, repair, {
289
+ origin: safeOrigin(origin),
290
+ failure: safeFailure(error),
291
+ });
292
+ }
293
+ }
294
+ async function checkBuzzRelay({ relay, lookup, pinnedRelayRead }) {
295
+ if (!relay) {
296
+ return blocked("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNSAFE", "BUZZ_RELAY_URL must be a credential-free public wss:// origin.", relayUnsafeRepair());
297
+ }
298
+ try {
299
+ const records = await lookup(relay.hostname, { all: true, verbatim: true });
300
+ if (!Array.isArray(records) || records.length === 0)
301
+ throw new ProbeFailure("dns_empty");
302
+ for (const record of records) {
303
+ if (!isPublicAddress(record.address))
304
+ throw new UnsafeRelayError("dns_non_public");
305
+ }
306
+ const result = await pinnedRelayRead(relay, records[0], {
307
+ timeoutMs: BUZZ_DOCTOR_TIMEOUT_MS,
308
+ maxBytes: BUZZ_DOCTOR_MAX_RESPONSE_BYTES,
309
+ });
310
+ if (!result?.ok)
311
+ throw new ProbeFailure(result?.failure ?? `http_${result?.status ?? "unknown"}`);
312
+ return ok("buzz_relay", {
313
+ origin: relay.origin,
314
+ elapsed_ms: result.elapsed_ms ?? null,
315
+ compatibility_source: "nip11_public_read",
316
+ address_count: records.length,
317
+ });
318
+ }
319
+ catch (error) {
320
+ if (error instanceof UnsafeRelayError) {
321
+ return blocked("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNSAFE", "The configured Buzz relay is not a safe public destination.", relayUnsafeRepair(), {
322
+ origin: relay.origin,
323
+ failure: error.message,
324
+ });
325
+ }
326
+ const failure = safeFailure(error);
327
+ return warning("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNREACHABLE", "The configured Buzz relay is safe to probe but unavailable; founder-agent setup may continue without community installation or enrollment.", relayAvailabilityRepair(relay.origin, failure), {
328
+ origin: relay.origin,
329
+ failure,
330
+ community_operations: "unavailable",
331
+ });
332
+ }
333
+ }
334
+ function checkWalletProfile({ walletProfile, env, profileExistsImpl, readMetaImpl, expectedSubjectHex }) {
335
+ const context = parseWalletContext(env.RUN402_ACTIVE_WALLET_JSON);
336
+ if (!walletProfile || walletProfile === "default") {
337
+ return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED", "Buzz setup requires a unique named Run402 wallet profile.", targetRequiredRepair(expectedSubjectHex));
338
+ }
339
+ if (context?.source !== "flag" || context?.sourceDetail !== "--wallet") {
340
+ return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH", "The Run402 profile was not selected by the explicit --wallet argument.", rerunDoctorRepair(walletProfile, expectedSubjectHex), {
341
+ profile_label: walletProfile,
342
+ selection_source: context?.source ?? "unknown",
343
+ selection_source_detail: context?.sourceDetail ?? null,
344
+ });
345
+ }
346
+ if (!profileExistsImpl(walletProfile)) {
347
+ return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND", `The dedicated Run402 profile '${walletProfile}' does not exist yet.`, walletCreateRepair(walletProfile), {
348
+ profile_label: walletProfile,
349
+ profile_type: "eoa_wallet",
350
+ initialized: false,
351
+ selection_source: "explicit_argument",
352
+ });
353
+ }
354
+ const meta = readMetaImpl(walletProfile);
355
+ if (meta?.name && meta.name !== walletProfile) {
356
+ return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH", "The selected profile metadata does not match the explicit profile label.", rerunDoctorRepair(walletProfile, expectedSubjectHex), {
357
+ profile_label: walletProfile,
358
+ observed_label: meta.name,
359
+ initialized: true,
360
+ selection_source: "explicit_argument",
361
+ });
362
+ }
363
+ return ok("wallet_profile", {
364
+ profile_label: walletProfile,
365
+ profile_type: "eoa_wallet",
366
+ rail: meta?.rail ?? null,
367
+ initialized: true,
368
+ selection_source: "explicit_argument",
369
+ });
370
+ }
371
+ export function defaultPinnedRelayRead(relay, record, { timeoutMs = BUZZ_DOCTOR_TIMEOUT_MS, maxBytes = BUZZ_DOCTOR_MAX_RESPONSE_BYTES } = {}) {
372
+ return new Promise((resolve, reject) => {
373
+ const started = Date.now();
374
+ const request = httpsRequest({
375
+ protocol: "https:",
376
+ hostname: relay.hostname,
377
+ port: relay.port || 443,
378
+ path: "/",
379
+ method: "GET",
380
+ servername: relay.hostname,
381
+ headers: { accept: "application/nostr+json,application/json" },
382
+ lookup: pinnedLookup(record, relay.hostname),
383
+ timeout: timeoutMs,
384
+ }, (response) => {
385
+ const statusCode = response.statusCode ?? 0;
386
+ if (statusCode >= 300 && statusCode < 400) {
387
+ response.destroy();
388
+ reject(new Error("redirect_rejected"));
389
+ return;
390
+ }
391
+ const chunks = [];
392
+ let bytes = 0;
393
+ response.on("data", (chunk) => {
394
+ bytes += chunk.length;
395
+ if (bytes > maxBytes) {
396
+ response.destroy(new Error("response_too_large"));
397
+ return;
398
+ }
399
+ chunks.push(chunk);
400
+ });
401
+ response.on("end", () => {
402
+ if (statusCode < 200 || statusCode >= 300) {
403
+ reject(new Error(`http_${statusCode}`));
404
+ return;
405
+ }
406
+ try {
407
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
408
+ if (!body || typeof body !== "object" || Array.isArray(body))
409
+ throw new ProbeFailure("nip11_invalid");
410
+ resolve({ ok: true, status: statusCode, elapsed_ms: Date.now() - started });
411
+ }
412
+ catch (error) {
413
+ reject(error);
414
+ }
415
+ });
416
+ });
417
+ request.on("timeout", () => request.destroy(new Error("timeout")));
418
+ request.on("error", reject);
419
+ request.end();
420
+ });
421
+ }
422
+ export function isPublicAddress(address) {
423
+ try {
424
+ return ipaddr.parse(address).range() === "unicast";
425
+ }
426
+ catch {
427
+ return false;
428
+ }
429
+ }
430
+ export function pinnedLookup(record, expectedHostname) {
431
+ return (hostname, options, callback) => {
432
+ if (hostname !== expectedHostname) {
433
+ callback(new Error("relay_hostname_changed"));
434
+ return;
435
+ }
436
+ // Node 20+ sockets (autoSelectFamily) ask with `{ all: true }` and expect
437
+ // an array of records; answering in the single-address form there makes
438
+ // the connect fail with "Invalid IP address: undefined" — i.e. every relay
439
+ // looked unreachable from a current Node.
440
+ if (options && typeof options === "object" && options.all) {
441
+ callback(null, [{ address: record.address, family: record.family }]);
442
+ return;
443
+ }
444
+ callback(null, record.address, record.family);
445
+ };
446
+ }
447
+ function defaultRunCommand(command, args, options = {}) {
448
+ return spawnSync(command, args, {
449
+ encoding: "utf8",
450
+ shell: false,
451
+ maxBuffer: BUZZ_DOCTOR_MAX_RESPONSE_BYTES,
452
+ timeout: BUZZ_DOCTOR_TIMEOUT_MS,
453
+ ...options,
454
+ });
455
+ }
456
+ function findExecutable(name, env, platform) {
457
+ const pathValue = typeof env.PATH === "string" ? env.PATH : "";
458
+ const extensions = platform === "win32"
459
+ ? String(env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean)
460
+ : [""];
461
+ for (const directory of pathValue.split(delimiter).filter(Boolean)) {
462
+ for (const extension of extensions) {
463
+ const candidate = join(directory, platform === "win32" && !extname(name) ? `${name}${extension}` : name);
464
+ try {
465
+ accessSync(candidate, platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK);
466
+ return safeRealpath(candidate) ?? candidate;
467
+ }
468
+ catch { /* keep searching */ }
469
+ }
470
+ }
471
+ return null;
472
+ }
473
+ function configuredShell(env, platform) {
474
+ if (platform === "win32")
475
+ return env.ComSpec || env.COMSPEC || null;
476
+ return typeof env.SHELL === "string" && env.SHELL.trim() ? env.SHELL.trim() : null;
477
+ }
478
+ function parseRelayOrigin(raw) {
479
+ if (typeof raw !== "string" || raw.trim() === "")
480
+ return null;
481
+ try {
482
+ const url = new URL(raw);
483
+ if (url.protocol !== "wss:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash)
484
+ return null;
485
+ if (url.hostname === "localhost" || ipaddr.isValid(url.hostname))
486
+ return null;
487
+ return { origin: url.origin, hostname: url.hostname, port: url.port ? Number(url.port) : 443 };
488
+ }
489
+ catch {
490
+ return null;
491
+ }
492
+ }
493
+ function blocked(name, code, message, action, value) {
494
+ assertRepairMatches(code, action);
495
+ return { name, status: "blocked", code, ...(value ? { value } : {}), message, next_actions: [action] };
496
+ }
497
+ function warning(name, code, message, action, value) {
498
+ assertRepairMatches(code, action);
499
+ return { name, status: "warning", code, ...(value ? { value } : {}), message, next_actions: [action] };
500
+ }
501
+ function ok(name, value) {
502
+ return { name, status: "ok", ...(value ? { value } : {}) };
503
+ }
504
+ function shellAction({ type, argv, why, cwd, safeToAutoExecute = false, requiresApproval = true }) {
505
+ return {
506
+ type,
507
+ surface: "shell",
508
+ command: shellCommand(argv),
509
+ argv,
510
+ ...(cwd ? { cwd } : {}),
511
+ why,
512
+ safe_to_auto_execute: safeToAutoExecute,
513
+ requires_approval: requiresApproval,
514
+ destructive: false,
515
+ idempotent: true,
516
+ spend_impact: SPEND_NONE,
517
+ };
518
+ }
519
+ function nonShellAction({ type, surface, command, why }) {
520
+ return {
521
+ type,
522
+ surface,
523
+ command,
524
+ why,
525
+ safe_to_auto_execute: false,
526
+ requires_approval: true,
527
+ destructive: false,
528
+ idempotent: true,
529
+ spend_impact: SPEND_NONE,
530
+ };
531
+ }
532
+ function shellRepair() {
533
+ return nonShellAction({
534
+ type: "repair_buzz_agent_runtime",
535
+ surface: "buzz_settings",
536
+ command: "Open Buzz Desktop > Settings > Agents, select this agent, choose a runtime with command execution enabled, restart the agent, then rerun Run402 setup.",
537
+ why: "Run402 setup needs the managed agent's configured shell before it can diagnose or mutate anything.",
538
+ });
539
+ }
540
+ function nodeRepair() {
541
+ return nonShellAction({
542
+ type: "repair_buzz_node_runtime",
543
+ surface: "buzz_settings",
544
+ command: "Open Buzz Desktop > Settings > Updates, install the available Buzz update, restart this agent, then rerun Run402 setup.",
545
+ why: `The managed session must provide a spawnable Node ${BUZZ_DOCTOR_MIN_NODE_MAJOR}+ runtime.`,
546
+ });
547
+ }
548
+ function run402InstallRepair() {
549
+ return shellAction({
550
+ type: "install_run402_cli",
551
+ argv: ["npm", "install", "-g", "run402@latest"],
552
+ why: "Install Run402 in the user-global npm context used by the Buzz setup helper.",
553
+ safeToAutoExecute: true,
554
+ requiresApproval: false,
555
+ });
556
+ }
557
+ function run402RepairForInstall(install, cwd) {
558
+ if (install?.kind === "global_npm")
559
+ return run402InstallRepair();
560
+ if (install?.kind === "local_project") {
561
+ const manager = install.package_manager ?? "npm";
562
+ const argv = manager === "pnpm" ? ["pnpm", "add", "-D", "run402@latest"]
563
+ : manager === "yarn" ? ["yarn", "add", "-D", "run402@latest"]
564
+ : manager === "bun" ? ["bun", "add", "-d", "run402@latest"]
565
+ : ["npm", "install", "-D", "run402@latest"];
566
+ return shellAction({ type: "upgrade_run402_cli", argv, cwd, why: "Upgrade the project-local Run402 CLI that executed this diagnostic." });
567
+ }
568
+ return shellAction({
569
+ type: "inspect_run402_cli",
570
+ argv: ["run402", "doctor", "--refresh"],
571
+ cwd,
572
+ why: "The Run402 executable context is custom or ambiguous; inspect it without guessing a package-manager mutation.",
573
+ safeToAutoExecute: true,
574
+ requiresApproval: false,
575
+ });
576
+ }
577
+ function normalizeUpgradeAction(rawAction, install, cwd) {
578
+ const argv = Array.isArray(rawAction?.argv) && rawAction.argv.every((item) => typeof item === "string")
579
+ ? rawAction.argv
580
+ : run402RepairForInstall(install, cwd).argv;
581
+ return shellAction({
582
+ type: "upgrade_run402_cli",
583
+ argv,
584
+ cwd: rawAction?.cwd ?? cwd,
585
+ why: "Upgrade guidance is advisory because the executing Run402 CLI remains compatible.",
586
+ });
587
+ }
588
+ function buzzSidecarRepair() {
589
+ return nonShellAction({
590
+ type: "repair_buzz_cli_sidecar",
591
+ surface: BUZZ_CAPABILITIES.managed_sidecar.repair_surface,
592
+ command: BUZZ_CAPABILITIES.managed_sidecar.repair_instruction,
593
+ why: "The Buzz CLI is bundled with Buzz Desktop; a public package-manager command would be an unverified repair path.",
594
+ });
595
+ }
596
+ function targetRequiredRepair(expectedSubjectHex) {
597
+ return nonShellAction({
598
+ type: "rerun_buzz_setup_with_explicit_identity",
599
+ surface: "buzz_chat",
600
+ command: expectedSubjectHex
601
+ ? "@Fizz rerun Run402 setup using your current public Buzz npub and a unique dedicated Run402 profile."
602
+ : "@Fizz rerun Run402 setup using your current public Buzz npub and a unique dedicated Run402 profile such as buzz-fizz.",
603
+ why: "The agent must bind setup to one explicit public Buzz identity and one dedicated Run402 profile.",
604
+ });
605
+ }
606
+ function targetUnverifiedRepair() {
607
+ return nonShellAction({
608
+ type: "publish_buzz_public_profile",
609
+ surface: "buzz_chat",
610
+ command: "@Fizz publish or repair your public Buzz profile, then rerun Run402 setup.",
611
+ why: "The released read-only `buzz users get` boundary needs one public self profile to prove the active agent.",
612
+ });
613
+ }
614
+ function targetMismatchRepair() {
615
+ return nonShellAction({
616
+ type: "select_expected_buzz_agent",
617
+ surface: "buzz_chat",
618
+ command: "Send the Run402 setup request to the intended Buzz agent, then have that agent rerun setup from its own managed session.",
619
+ why: "Run402 will not relink or infer identity when the active Buzz signer differs from the requested public agent.",
620
+ });
621
+ }
622
+ function apiRepair(origin) {
623
+ return nonShellAction({
624
+ type: "restore_run402_api_access",
625
+ surface: "buzz_settings",
626
+ command: `Open Buzz Desktop > Settings > Agents, select this agent, allow HTTPS access to ${safeOrigin(origin)}, restart the agent, then rerun Run402 setup.`,
627
+ why: "The agent must reach the Run402 API before setup can safely initialize or verify state.",
628
+ });
629
+ }
630
+ function consoleRepair() {
631
+ return nonShellAction({
632
+ type: "restore_run402_console_access",
633
+ surface: "buzz_settings",
634
+ command: "Open Buzz Desktop > Settings > Agents, select this agent, allow HTTPS access to https://console.run402.com, restart the agent, then rerun Run402 setup.",
635
+ why: "The no-terminal human adoption offer requires the Run402 console origin.",
636
+ });
637
+ }
638
+ function relayUnsafeRepair() {
639
+ return nonShellAction({
640
+ type: "repair_buzz_relay_url",
641
+ surface: "buzz_settings",
642
+ command: "Open Buzz Desktop > Settings > Communities, select the current community, choose its public wss:// relay, restart this agent, then rerun Run402 setup.",
643
+ why: "Run402 refuses literal, private, reserved, mixed-address, credential-bearing, or non-TLS relay destinations.",
644
+ });
645
+ }
646
+ function relayAvailabilityRepair(origin, failure) {
647
+ if (failure === "tls_handshake_failed") {
648
+ return nonShellAction({
649
+ type: "repair_buzz_relay_tls",
650
+ surface: "buzz_settings",
651
+ command: `Ask the Buzz community operator to provision a valid TLS certificate and public hostname route for ${origin}; after the endpoint completes verified TLS, restart this agent and rerun Run402 setup.`,
652
+ why: "Reconnect cannot repair a relay hostname whose public TLS handshake fails; community installation and enrollment stay unavailable until the endpoint is fixed.",
653
+ });
654
+ }
655
+ if (failure === "dns") {
656
+ return nonShellAction({
657
+ type: "repair_buzz_relay_dns",
658
+ surface: "buzz_settings",
659
+ command: `Ask the Buzz community operator to publish working public DNS for ${origin}; after the hostname resolves publicly, restart this agent and rerun Run402 setup.`,
660
+ why: "Community installation and enrollment require a publicly resolvable relay, while founder-agent setup remains independent.",
661
+ });
662
+ }
663
+ if (failure.startsWith("http_") || failure === "nip11_invalid" || failure === "redirect_rejected" || failure === "response_too_large") {
664
+ return nonShellAction({
665
+ type: "repair_buzz_relay_service",
666
+ surface: "buzz_settings",
667
+ command: `Ask the Buzz community operator to restore the public NIP-11 endpoint at ${origin}; after it returns a bounded non-redirecting JSON response, restart this agent and rerun Run402 setup.`,
668
+ why: "Community installation and enrollment require the relay's public NIP-11 service, while founder-agent setup remains independent.",
669
+ });
670
+ }
671
+ return nonShellAction({
672
+ type: "restore_buzz_relay_access",
673
+ surface: "buzz_settings",
674
+ command: `Ask the Buzz community operator to restore public relay access at ${origin}; after the endpoint is reachable, restart this agent and rerun Run402 setup.`,
675
+ why: "Community installation and enrollment require a live public relay, while founder-agent setup remains independent.",
676
+ });
677
+ }
678
+ function walletCreateRepair(walletProfile) {
679
+ return shellAction({
680
+ type: "create_buzz_wallet_profile",
681
+ argv: ["run402", "wallets", "new", walletProfile],
682
+ why: "Create the explicitly named dedicated agent profile, then rerun the complete preflight before setup.",
683
+ safeToAutoExecute: true,
684
+ requiresApproval: false,
685
+ });
686
+ }
687
+ function rerunDoctorRepair(walletProfile, expectedSubjectHex) {
688
+ const argv = ["run402", "--wallet", walletProfile, "doctor", "--buzz"];
689
+ if (expectedSubjectHex)
690
+ argv.push("--buzz-agent", expectedSubjectHex);
691
+ return shellAction({
692
+ type: "rerun_buzz_doctor",
693
+ argv,
694
+ why: "Rerun the read-only diagnostic with the exact explicit profile and public Buzz identity.",
695
+ safeToAutoExecute: true,
696
+ requiresApproval: false,
697
+ });
698
+ }
699
+ function shellCommand(argv) {
700
+ return argv.map((part) => SAFE_TOKEN.test(part) ? part : `'${part.replaceAll("'", "'\\''")}'`).join(" ");
701
+ }
702
+ function commandPassed(result) {
703
+ return !result?.error && result?.status === 0;
704
+ }
705
+ function commandFailureClass(result) {
706
+ if (result?.error?.code === "ETIMEDOUT")
707
+ return "timeout";
708
+ if (result?.error?.code === "ENOENT")
709
+ return "not_found";
710
+ if (result?.error)
711
+ return "spawn_error";
712
+ return Number.isInteger(result?.status) ? `exit_${result.status}` : "unknown";
713
+ }
714
+ function compareMajorMinorPatch(left, right) {
715
+ const parse = (value) => String(value).match(/^v?(\d+)\.(\d+)\.(\d+)/)?.slice(1, 4).map(Number) ?? null;
716
+ const a = parse(left);
717
+ const b = parse(right);
718
+ if (!a || !b)
719
+ return -1;
720
+ for (let index = 0; index < 3; index += 1) {
721
+ if (a[index] !== b[index])
722
+ return a[index] < b[index] ? -1 : 1;
723
+ }
724
+ return 0;
725
+ }
726
+ function parseWalletContext(raw) {
727
+ try {
728
+ const parsed = JSON.parse(raw || "");
729
+ return parsed && typeof parsed === "object" ? parsed : null;
730
+ }
731
+ catch {
732
+ return null;
733
+ }
734
+ }
735
+ function safeRealpath(path) {
736
+ if (typeof path !== "string" || path.length === 0)
737
+ return null;
738
+ try {
739
+ return existsSync(path) ? realpathSync(path) : path;
740
+ }
741
+ catch {
742
+ return path;
743
+ }
744
+ }
745
+ function safePath(path) {
746
+ return typeof path === "string" ? path : null;
747
+ }
748
+ function safeOrigin(value) {
749
+ try {
750
+ return new URL(value).origin;
751
+ }
752
+ catch {
753
+ return null;
754
+ }
755
+ }
756
+ function safeFailure(error) {
757
+ if (error?.name === "AbortError")
758
+ return "timeout";
759
+ const e = error;
760
+ const message = error instanceof Error ? `${e.message} ${e.code ?? ""} ${e.cause?.message ?? ""} ${e.cause?.code ?? ""}` : String(error);
761
+ if (/certificate|tls|ssl|eproto|secure tls connection|handshake/i.test(message))
762
+ return "tls_handshake_failed";
763
+ if (/dns|dns_empty|getaddrinfo|enotfound|eai_again/i.test(message))
764
+ return "dns";
765
+ if (/timeout|timed out|abort/i.test(message))
766
+ return "timeout";
767
+ if (/redirect/i.test(message))
768
+ return "redirect_rejected";
769
+ if (/response_too_large/i.test(message))
770
+ return "response_too_large";
771
+ if (/nip11_invalid/i.test(message))
772
+ return "nip11_invalid";
773
+ if (/http_\d+/.test(message))
774
+ return message.match(/http_\d+/)[0];
775
+ return "network";
776
+ }
777
+ function assertRepairMatches(code, action) {
778
+ const expected = BUZZ_DOCTOR_REPAIR_MATRIX[code];
779
+ if (!expected)
780
+ throw new LocalError(`No frozen Buzz doctor repair mapping for ${code}`, "running the Buzz preflight");
781
+ const types = Array.isArray(expected.type) ? expected.type : [expected.type];
782
+ const surfaces = Array.isArray(expected.surface) ? expected.surface : [expected.surface];
783
+ if (!types.includes(action?.type) || !surfaces.includes(action?.surface)) {
784
+ throw new LocalError(`Buzz doctor repair drift for ${code}`, "running the Buzz preflight");
785
+ }
786
+ }
787
+ class UnsafeRelayError extends Error {
788
+ }
789
+ /** An internal probe failure carrying its classified reason as the message; caught within this module. */
790
+ class ProbeFailure extends Error {
791
+ }
792
+ /**
793
+ * `r.buzz` on the Node entry: the isomorphic Buzz workflows plus the
794
+ * zero-mutation setup preflight.
795
+ */
796
+ export class NodeBuzz extends Buzz {
797
+ #diagnostics;
798
+ constructor(client, diagnostics) {
799
+ super(client);
800
+ this.#diagnostics = diagnostics;
801
+ }
802
+ /** The `run402.buzz-doctor.v1` report. Read-only: nothing is created, signed, or published. */
803
+ async doctor(opts = {}) {
804
+ return buildBuzzDoctorReport({
805
+ originProbe: (url, { timeoutMs }) => this.#diagnostics.probeOrigin(url, { timeoutMs }),
806
+ ...opts,
807
+ });
808
+ }
809
+ }
810
+ //# sourceMappingURL=buzz-doctor.js.map