UNPKG

run402-mcp

Version:

MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.

824 lines • 39.9 kB
/** * `r.buzz.doctor()` — the zero-mutation Buzz setup preflight * (`run402.buzz-doctor.v1`), owned by the Node SDK: the managed session's * shell and Node runtime, the Run402 CLI, the managed Buzz CLI sidecar and * its capabilities, the public agent identity, the Run402 API and console * origins (measured with `r.diagnostics.probeOrigin`), the Buzz relay (a * DNS-pinned NIP-11 read), and the explicit wallet profile. Every check's * status is `ok | warning | blocked`; a blocked or warning check carries * exactly one repair action from the frozen matrix below. * * `run402 doctor --buzz` is a shim over this. The Run402 CLI facts (its * version, how it was installed, whether it is current) are the calling * program's own, supplied as hooks; without them the `run402_cli` check * reports the CLI unavailable. */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { spawnSync } from "node:child_process"; import { accessSync, constants as fsConstants, existsSync, realpathSync } from "node:fs"; import { lookup as dnsLookup } from "node:dns/promises"; import { request as httpsRequest } from "node:https"; import { delimiter, extname, join } from "node:path"; import ipaddr from "ipaddr.js"; import { bech32 } from "@scure/base"; import { getActiveProfile as coreGetActiveProfile, getApiBase } from "../../core-dist/config.js"; import { profileExists, readMeta } from "../../core-dist/profiles.js"; import { LocalError } from "../errors.js"; import { Buzz } from "../namespaces/buzz.js"; import { BUZZ_CLI_CAPABILITIES, BUZZ_DOCTOR_CHECK_ORDER, BUZZ_DOCTOR_CONTRACT, BUZZ_DOCTOR_CONTRACT_ID, } from "./buzz-doctor-contract.js"; import { Diagnostics } from "./diagnostics.js"; const BUZZ_CAPABILITIES = BUZZ_CLI_CAPABILITIES; export const BUZZ_DOCTOR_TIMEOUT_MS = 3_000; export const BUZZ_DOCTOR_MAX_RESPONSE_BYTES = 64 * 1024; export const BUZZ_DOCTOR_MIN_NODE_MAJOR = 22; // v4.17.2 is the first client whose Buzz doctor keeps a safely contained relay // availability failure warning-only for founder-agent setup. Older clients can // expose the same command surface while enforcing the wrong setup semantics. export const BUZZ_DOCTOR_MIN_RUN402_VERSION = "4.17.2"; const SPEND_NONE = Object.freeze({ currency: "USD", max_amount: "0" }); const SAFE_TOKEN = /^[A-Za-z0-9_./:=@+,-]+$/; export const BUZZ_DOCTOR_REPAIR_MATRIX = Object.freeze({ BUZZ_PREFLIGHT_SHELL_UNAVAILABLE: { type: "repair_buzz_agent_runtime", surface: "buzz_settings" }, BUZZ_PREFLIGHT_NODE_UNAVAILABLE: { type: "repair_buzz_node_runtime", surface: "buzz_settings" }, BUZZ_PREFLIGHT_NODE_INCOMPATIBLE: { type: "repair_buzz_node_runtime", surface: "buzz_settings" }, BUZZ_PREFLIGHT_RUN402_UNAVAILABLE: { type: "install_run402_cli", surface: "shell" }, BUZZ_PREFLIGHT_RUN402_INCOMPATIBLE: { type: ["upgrade_run402_cli", "inspect_run402_cli", "install_run402_cli"], surface: "shell" }, BUZZ_PREFLIGHT_RUN402_UPDATE_AVAILABLE: { type: "upgrade_run402_cli", surface: "shell" }, BUZZ_PREFLIGHT_BUZZ_CLI_UNAVAILABLE: { type: "repair_buzz_cli_sidecar", surface: "buzz_settings" }, BUZZ_PREFLIGHT_BUZZ_CLI_INCOMPATIBLE: { type: "repair_buzz_cli_sidecar", surface: "buzz_settings" }, BUZZ_AGENT_TARGET_REQUIRED: { type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat" }, BUZZ_AGENT_TARGET_UNVERIFIED: { type: ["publish_buzz_public_profile", "repair_buzz_cli_sidecar"], surface: ["buzz_chat", "buzz_settings"] }, BUZZ_AGENT_TARGET_MISMATCH: { type: "select_expected_buzz_agent", surface: "buzz_chat" }, BUZZ_PREFLIGHT_API_UNREACHABLE: { type: "restore_run402_api_access", surface: "buzz_settings" }, BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE: { type: "restore_run402_console_access", surface: "buzz_settings" }, BUZZ_PREFLIGHT_RELAY_UNSAFE: { type: "repair_buzz_relay_url", surface: "buzz_settings" }, BUZZ_PREFLIGHT_RELAY_UNREACHABLE: { type: ["repair_buzz_relay_tls", "repair_buzz_relay_dns", "repair_buzz_relay_service", "restore_buzz_relay_access"], surface: "buzz_settings", }, BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED: { type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat" }, BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND: { type: "create_buzz_wallet_profile", surface: "shell" }, BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH: { type: "rerun_buzz_doctor", surface: "shell" }, }); export function normalizeNostrSubject(value) { if (typeof value !== "string") return null; const normalized = value.trim().toLowerCase(); if (/^[0-9a-f]{64}$/.test(normalized)) return normalized; if (!normalized.startsWith("npub1")) return null; try { const decoded = bech32.decode(normalized); if (decoded.prefix !== "npub") return null; const bytes = Uint8Array.from(bech32.fromWords(decoded.words)); return bytes.length === 32 ? Buffer.from(bytes).toString("hex") : null; } catch { return null; } } const defaultDiagnostics = new Diagnostics(); 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", } = {}) { const shellPath = configuredShell(env, platform); const nodeExecutable = safeRealpath(execPath) ?? execPath ?? null; const run402Executable = safeRealpath(cliExecutable) ?? cliExecutable ?? null; const walletProfile = getActiveProfile(); const relay = parseRelayOrigin(env.BUZZ_RELAY_URL); const binding = { contract_id: BUZZ_DOCTOR_CONTRACT_ID, expected_subject_hex: expectedSubjectHex, wallet_profile: walletProfile, node_executable: nodeExecutable, run402_executable: run402Executable, relay_origin: relay?.origin ?? null, }; const sessionShell = checkSessionShell({ shellPath, platform, runCommand }); const nodeRuntime = checkNodeRuntime({ nodeExecutable, runCommand }); const run402CliCheck = await checkRun402Cli({ cwd, env, argv, run402Executable: run402Cli ? run402Executable : null, hooks: run402Cli }); const buzzExecutable = findExecutable("buzz", env, platform); const buzzCli = checkBuzzCli({ buzzExecutable, runCommand }); const [buzzAgentTarget, run402Api, run402Console, buzzRelay, walletProfileCheck] = await Promise.all([ checkBuzzAgentTarget({ expectedSubjectHex, buzzExecutable, buzzCli, runCommand }), checkOrigin("run402_api", apiOrigin, "BUZZ_PREFLIGHT_API_UNREACHABLE", originProbe, apiRepair(apiOrigin)), checkOrigin("run402_console", consoleOrigin, "BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE", originProbe, consoleRepair()), checkBuzzRelay({ relay, lookup, pinnedRelayRead }), Promise.resolve(checkWalletProfile({ walletProfile, env, profileExistsImpl, readMetaImpl, expectedSubjectHex })), ]); const checks = [ sessionShell, nodeRuntime, run402CliCheck, buzzCli, buzzAgentTarget, run402Api, run402Console, buzzRelay, walletProfileCheck, ]; if (checks.some((check, index) => check.name !== BUZZ_DOCTOR_CHECK_ORDER[index])) { throw new LocalError("Buzz doctor check order drifted from the frozen contract", "running the Buzz preflight"); } return { ok: checks.every((check) => check.status !== "blocked"), mode: "buzz", contract_id: BUZZ_DOCTOR_CONTRACT_ID, generated_at: new Date(now).toISOString(), mutation_state: BUZZ_DOCTOR_CONTRACT.zero_mutation.mutation_state, binding, checks, telemetry: { status: env.RUN402_TELEMETRY === "0" ? "disabled" : "not_configured", queued: false, }, }; } function checkSessionShell({ shellPath, platform, runCommand }) { if (!shellPath) return blocked("session_shell", "BUZZ_PREFLIGHT_SHELL_UNAVAILABLE", "The managed session has no configured shell executable.", shellRepair()); const args = platform === "win32" ? ["/d", "/s", "/c", "exit", "0"] : ["-c", ":"]; const result = runCommand(shellPath, args, { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false }); if (!commandPassed(result)) { return blocked("session_shell", "BUZZ_PREFLIGHT_SHELL_UNAVAILABLE", "The configured shell cannot execute a bounded no-op.", shellRepair(), { shell: safePath(shellPath), failure: commandFailureClass(result), }); } return ok("session_shell", { shell: safePath(shellPath) }); } function checkNodeRuntime({ nodeExecutable, runCommand }) { if (!nodeExecutable) return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_UNAVAILABLE", "The managed session has no Node executable.", nodeRepair()); const result = runCommand(nodeExecutable, ["--version"], { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false }); if (!commandPassed(result)) { return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_UNAVAILABLE", "The exact Node executable cannot be spawned.", nodeRepair(), { executable: safePath(nodeExecutable), failure: commandFailureClass(result), }); } const version = String(result.stdout ?? "").trim(); const major = Number(version.match(/^v?(\d+)/)?.[1]); if (!Number.isSafeInteger(major) || major < BUZZ_DOCTOR_MIN_NODE_MAJOR) { return blocked("node_runtime", "BUZZ_PREFLIGHT_NODE_INCOMPATIBLE", `Node ${BUZZ_DOCTOR_MIN_NODE_MAJOR}+ is required by this Run402 package.`, nodeRepair(), { executable: safePath(nodeExecutable), version: version || null, required_major: BUZZ_DOCTOR_MIN_NODE_MAJOR, }); } return ok("node_runtime", { executable: safePath(nodeExecutable), version, major, compatibility_source: "version" }); } async function checkRun402Cli({ cwd, env, argv, run402Executable, hooks }) { if (!run402Executable || !hooks) { return blocked("run402_cli", "BUZZ_PREFLIGHT_RUN402_UNAVAILABLE", "The Run402 CLI executable is unavailable.", run402InstallRepair()); } const updateCheck = hooks.updateCheck ?? (async () => null); const version = hooks.currentVersion(); const install = hooks.detectInstall({ cwd, env, argv, execPath: run402Executable }); if (compareMajorMinorPatch(version, BUZZ_DOCTOR_MIN_RUN402_VERSION) < 0) { return blocked("run402_cli", "BUZZ_PREFLIGHT_RUN402_INCOMPATIBLE", "The executing Run402 CLI predates the Buzz doctor contract.", run402RepairForInstall(install, cwd), { version, minimum_version: BUZZ_DOCTOR_MIN_RUN402_VERSION, executable: safePath(run402Executable), install_context: install.kind, }); } let update = null; try { update = await updateCheck({ refresh: false, cwd, env, argv, execPath: run402Executable, current: version }); } catch { update = null; } const value = { version, executable: safePath(run402Executable), install_context: install.kind, install_confidence: install.confidence, package_manager: install.package_manager, compatibility_source: "version_and_capability_probe", required_capabilities: ["doctor --buzz", "doctor --buzz-agent", "wallets current", "whoami", "identity link nostr"], }; if (update?.status === "warning") { const rawAction = update?.value?.next_actions?.[0]; return warning("run402_cli", "BUZZ_PREFLIGHT_RUN402_UPDATE_AVAILABLE", "A newer compatible Run402 CLI is available; setup may continue.", normalizeUpgradeAction(rawAction, install, cwd), value); } return ok("run402_cli", value); } function checkBuzzCli({ buzzExecutable, runCommand }) { if (!buzzExecutable) { return blocked("buzz_cli", "BUZZ_PREFLIGHT_BUZZ_CLI_UNAVAILABLE", "The managed Buzz CLI sidecar is unavailable.", buzzSidecarRepair()); } const observations = []; for (const probe of BUZZ_CAPABILITIES.help_probes) { const args = probe.argv.slice(1); const result = runCommand(buzzExecutable, args, { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false }); const output = String(result?.stdout ?? ""); const compatible = commandPassed(result) && probe.stdout_contains.every((fragment) => output.includes(fragment)); observations.push({ capability: args.join(" "), compatible }); if (!compatible) { return blocked("buzz_cli", "BUZZ_PREFLIGHT_BUZZ_CLI_INCOMPATIBLE", "The managed Buzz CLI lacks a released capability required by setup.", buzzSidecarRepair(), { executable: safePath(buzzExecutable), fixture_id: BUZZ_CAPABILITIES.fixture_id, compatibility_source: "capability_probe", observations, }); } } return ok("buzz_cli", { executable: safePath(buzzExecutable), fixture_id: BUZZ_CAPABILITIES.fixture_id, install_context: "managed_buzz_sidecar", compatibility_source: "capability_probe", version_status: "version_unknown_capabilities_ok", observations, }); } async function checkBuzzAgentTarget({ expectedSubjectHex, buzzExecutable, buzzCli, runCommand }) { if (!expectedSubjectHex) { return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_REQUIRED", "Buzz setup requires the intended agent's public npub or hex subject.", targetRequiredRepair()); } if (buzzCli.status === "blocked" || !buzzExecutable) { return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The active Buzz agent cannot be proven until the managed Buzz CLI is repaired.", buzzSidecarRepair(), { expected_subject_hex: expectedSubjectHex, observed_subject_hex: null, }); } const result = runCommand(buzzExecutable, ["users", "get"], { timeout: BUZZ_DOCTOR_TIMEOUT_MS, shell: false }); if (!commandPassed(result)) { return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The released Buzz public self query did not return a verifiable profile.", targetUnverifiedRepair(), { expected_subject_hex: expectedSubjectHex, observed_subject_hex: null, failure: commandFailureClass(result), }); } let parsed; try { parsed = JSON.parse(String(result.stdout ?? "")); } catch { parsed = null; } const observed = Array.isArray(parsed) && parsed.length === 1 ? normalizeNostrSubject(parsed[0]?.pubkey) : null; if (!observed) { return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_UNVERIFIED", "The released Buzz public self query returned no single public signer profile.", targetUnverifiedRepair(), { expected_subject_hex: expectedSubjectHex, observed_subject_hex: null, }); } if (observed !== expectedSubjectHex) { return blocked("buzz_agent_target", "BUZZ_AGENT_TARGET_MISMATCH", "The active Buzz CLI identity is not the explicitly requested managed agent.", targetMismatchRepair(), { expected_subject_hex: expectedSubjectHex, observed_subject_hex: observed, }); } return ok("buzz_agent_target", { expected_subject_hex: expectedSubjectHex, observed_subject_hex: observed, observation: "buzz users get" }); } async function checkOrigin(name, origin, code, originProbe, repair) { try { const url = new URL("/status", origin); if (name === "run402_console") url.pathname = "/"; const result = await originProbe(url, { timeoutMs: BUZZ_DOCTOR_TIMEOUT_MS, maxBytes: BUZZ_DOCTOR_MAX_RESPONSE_BYTES }); if (!result?.reachable) throw new ProbeFailure(result?.error ?? `http_${result?.status ?? "unknown"}`); return ok(name, { origin: new URL(origin).origin, elapsed_ms: result.elapsed_ms ?? null }); } catch (error) { return blocked(name, code, `${name === "run402_api" ? "Run402 API" : "Run402 console"} reachability failed.`, repair, { origin: safeOrigin(origin), failure: safeFailure(error), }); } } async function checkBuzzRelay({ relay, lookup, pinnedRelayRead }) { if (!relay) { return blocked("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNSAFE", "BUZZ_RELAY_URL must be a credential-free public wss:// origin.", relayUnsafeRepair()); } try { const records = await lookup(relay.hostname, { all: true, verbatim: true }); if (!Array.isArray(records) || records.length === 0) throw new ProbeFailure("dns_empty"); for (const record of records) { if (!isPublicAddress(record.address)) throw new UnsafeRelayError("dns_non_public"); } const result = await pinnedRelayRead(relay, records[0], { timeoutMs: BUZZ_DOCTOR_TIMEOUT_MS, maxBytes: BUZZ_DOCTOR_MAX_RESPONSE_BYTES, }); if (!result?.ok) throw new ProbeFailure(result?.failure ?? `http_${result?.status ?? "unknown"}`); return ok("buzz_relay", { origin: relay.origin, elapsed_ms: result.elapsed_ms ?? null, compatibility_source: "nip11_public_read", address_count: records.length, }); } catch (error) { if (error instanceof UnsafeRelayError) { return blocked("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNSAFE", "The configured Buzz relay is not a safe public destination.", relayUnsafeRepair(), { origin: relay.origin, failure: error.message, }); } const failure = safeFailure(error); 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), { origin: relay.origin, failure, community_operations: "unavailable", }); } } function checkWalletProfile({ walletProfile, env, profileExistsImpl, readMetaImpl, expectedSubjectHex }) { const context = parseWalletContext(env.RUN402_ACTIVE_WALLET_JSON); if (!walletProfile || walletProfile === "default") { return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED", "Buzz setup requires a unique named Run402 wallet profile.", targetRequiredRepair(expectedSubjectHex)); } if (context?.source !== "flag" || context?.sourceDetail !== "--wallet") { return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH", "The Run402 profile was not selected by the explicit --wallet argument.", rerunDoctorRepair(walletProfile, expectedSubjectHex), { profile_label: walletProfile, selection_source: context?.source ?? "unknown", selection_source_detail: context?.sourceDetail ?? null, }); } if (!profileExistsImpl(walletProfile)) { return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND", `The dedicated Run402 profile '${walletProfile}' does not exist yet.`, walletCreateRepair(walletProfile), { profile_label: walletProfile, profile_type: "eoa_wallet", initialized: false, selection_source: "explicit_argument", }); } const meta = readMetaImpl(walletProfile); if (meta?.name && meta.name !== walletProfile) { return blocked("wallet_profile", "BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH", "The selected profile metadata does not match the explicit profile label.", rerunDoctorRepair(walletProfile, expectedSubjectHex), { profile_label: walletProfile, observed_label: meta.name, initialized: true, selection_source: "explicit_argument", }); } return ok("wallet_profile", { profile_label: walletProfile, profile_type: "eoa_wallet", rail: meta?.rail ?? null, initialized: true, selection_source: "explicit_argument", }); } export function defaultPinnedRelayRead(relay, record, { timeoutMs = BUZZ_DOCTOR_TIMEOUT_MS, maxBytes = BUZZ_DOCTOR_MAX_RESPONSE_BYTES } = {}) { return new Promise((resolve, reject) => { const started = Date.now(); const request = httpsRequest({ protocol: "https:", hostname: relay.hostname, port: relay.port || 443, path: "/", method: "GET", servername: relay.hostname, headers: { accept: "application/nostr+json,application/json" }, lookup: pinnedLookup(record, relay.hostname), timeout: timeoutMs, }, (response) => { const statusCode = response.statusCode ?? 0; if (statusCode >= 300 && statusCode < 400) { response.destroy(); reject(new Error("redirect_rejected")); return; } const chunks = []; let bytes = 0; response.on("data", (chunk) => { bytes += chunk.length; if (bytes > maxBytes) { response.destroy(new Error("response_too_large")); return; } chunks.push(chunk); }); response.on("end", () => { if (statusCode < 200 || statusCode >= 300) { reject(new Error(`http_${statusCode}`)); return; } try { const body = JSON.parse(Buffer.concat(chunks).toString("utf8")); if (!body || typeof body !== "object" || Array.isArray(body)) throw new ProbeFailure("nip11_invalid"); resolve({ ok: true, status: statusCode, elapsed_ms: Date.now() - started }); } catch (error) { reject(error); } }); }); request.on("timeout", () => request.destroy(new Error("timeout"))); request.on("error", reject); request.end(); }); } export function isPublicAddress(address) { try { return ipaddr.parse(address).range() === "unicast"; } catch { return false; } } export function pinnedLookup(record, expectedHostname) { return (hostname, options, callback) => { if (hostname !== expectedHostname) { callback(new Error("relay_hostname_changed")); return; } // Node 20+ sockets (autoSelectFamily) ask with `{ all: true }` and expect // an array of records; answering in the single-address form there makes // the connect fail with "Invalid IP address: undefined" — i.e. every relay // looked unreachable from a current Node. if (options && typeof options === "object" && options.all) { callback(null, [{ address: record.address, family: record.family }]); return; } callback(null, record.address, record.family); }; } function defaultRunCommand(command, args, options = {}) { return spawnSync(command, args, { encoding: "utf8", shell: false, maxBuffer: BUZZ_DOCTOR_MAX_RESPONSE_BYTES, timeout: BUZZ_DOCTOR_TIMEOUT_MS, ...options, }); } function findExecutable(name, env, platform) { const pathValue = typeof env.PATH === "string" ? env.PATH : ""; const extensions = platform === "win32" ? String(env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""]; for (const directory of pathValue.split(delimiter).filter(Boolean)) { for (const extension of extensions) { const candidate = join(directory, platform === "win32" && !extname(name) ? `${name}${extension}` : name); try { accessSync(candidate, platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK); return safeRealpath(candidate) ?? candidate; } catch { /* keep searching */ } } } return null; } function configuredShell(env, platform) { if (platform === "win32") return env.ComSpec || env.COMSPEC || null; return typeof env.SHELL === "string" && env.SHELL.trim() ? env.SHELL.trim() : null; } function parseRelayOrigin(raw) { if (typeof raw !== "string" || raw.trim() === "") return null; try { const url = new URL(raw); if (url.protocol !== "wss:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null; if (url.hostname === "localhost" || ipaddr.isValid(url.hostname)) return null; return { origin: url.origin, hostname: url.hostname, port: url.port ? Number(url.port) : 443 }; } catch { return null; } } function blocked(name, code, message, action, value) { assertRepairMatches(code, action); return { name, status: "blocked", code, ...(value ? { value } : {}), message, next_actions: [action] }; } function warning(name, code, message, action, value) { assertRepairMatches(code, action); return { name, status: "warning", code, ...(value ? { value } : {}), message, next_actions: [action] }; } function ok(name, value) { return { name, status: "ok", ...(value ? { value } : {}) }; } function shellAction({ type, argv, why, cwd, safeToAutoExecute = false, requiresApproval = true }) { return { type, surface: "shell", command: shellCommand(argv), argv, ...(cwd ? { cwd } : {}), why, safe_to_auto_execute: safeToAutoExecute, requires_approval: requiresApproval, destructive: false, idempotent: true, spend_impact: SPEND_NONE, }; } function nonShellAction({ type, surface, command, why }) { return { type, surface, command, why, safe_to_auto_execute: false, requires_approval: true, destructive: false, idempotent: true, spend_impact: SPEND_NONE, }; } function shellRepair() { return nonShellAction({ type: "repair_buzz_agent_runtime", surface: "buzz_settings", command: "Open Buzz Desktop > Settings > Agents, select this agent, choose a runtime with command execution enabled, restart the agent, then rerun Run402 setup.", why: "Run402 setup needs the managed agent's configured shell before it can diagnose or mutate anything.", }); } function nodeRepair() { return nonShellAction({ type: "repair_buzz_node_runtime", surface: "buzz_settings", command: "Open Buzz Desktop > Settings > Updates, install the available Buzz update, restart this agent, then rerun Run402 setup.", why: `The managed session must provide a spawnable Node ${BUZZ_DOCTOR_MIN_NODE_MAJOR}+ runtime.`, }); } function run402InstallRepair() { return shellAction({ type: "install_run402_cli", argv: [...GLOBAL_UPGRADE_ARGV], why: "Install Run402 in the user-global npm context used by the Buzz setup helper.", safeToAutoExecute: true, requiresApproval: false, }); } function run402RepairForInstall(install, cwd) { if (install?.kind === "global_npm") return run402InstallRepair(); if (install?.kind === "local_project") { const manager = install.package_manager ?? "npm"; const argv = manager === "pnpm" ? ["pnpm", "add", "-D", "run402@latest"] : manager === "yarn" ? ["yarn", "add", "-D", "run402@latest"] : manager === "bun" ? ["bun", "add", "-d", "run402@latest"] : ["npm", "install", "-D", "run402@latest"]; return shellAction({ type: "upgrade_run402_cli", argv, cwd, why: "Upgrade the project-local Run402 CLI that executed this diagnostic." }); } return shellAction({ type: "inspect_run402_cli", argv: ["run402", "doctor", "--refresh"], cwd, why: "The Run402 executable context is custom or ambiguous; inspect it without guessing a package-manager mutation.", safeToAutoExecute: true, requiresApproval: false, }); } const GLOBAL_UPGRADE_ARGV = ["npm", "install", "-g", "run402@latest"]; /** * A newer release for a user-global npm install is the agent's own toolchain: * the managed Buzz agent applies it unasked (at most once a day, bounded by * the CLI's 24-hour update cache). A project-local or ambiguous install stays * advisory, because upgrading it changes files the human owns. */ function normalizeUpgradeAction(rawAction, install, cwd) { const argv = Array.isArray(rawAction?.argv) && rawAction.argv.every((item) => typeof item === "string") ? rawAction.argv : run402RepairForInstall(install, cwd).argv; const globalUpgrade = install?.kind === "global_npm" && argv.length === GLOBAL_UPGRADE_ARGV.length && argv.every((part, index) => part === GLOBAL_UPGRADE_ARGV[index]); return shellAction({ type: "upgrade_run402_cli", argv, cwd: rawAction?.cwd ?? cwd, why: globalUpgrade ? "A newer Run402 CLI is available for the user-global npm install this agent runs; apply it and continue." : "Upgrade guidance is advisory because the executing Run402 CLI remains compatible.", safeToAutoExecute: globalUpgrade, requiresApproval: !globalUpgrade, }); } function buzzSidecarRepair() { return nonShellAction({ type: "repair_buzz_cli_sidecar", surface: BUZZ_CAPABILITIES.managed_sidecar.repair_surface, command: BUZZ_CAPABILITIES.managed_sidecar.repair_instruction, why: "The Buzz CLI is bundled with Buzz Desktop; a public package-manager command would be an unverified repair path.", }); } function targetRequiredRepair(expectedSubjectHex) { return nonShellAction({ type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat", command: expectedSubjectHex ? "@Fizz rerun Run402 setup using your current public Buzz npub and a unique dedicated Run402 profile." : "@Fizz rerun Run402 setup using your current public Buzz npub and a unique dedicated Run402 profile such as buzz-fizz.", why: "The agent must bind setup to one explicit public Buzz identity and one dedicated Run402 profile.", }); } function targetUnverifiedRepair() { return nonShellAction({ type: "publish_buzz_public_profile", surface: "buzz_chat", command: "@Fizz publish or repair your public Buzz profile, then rerun Run402 setup.", why: "The released read-only `buzz users get` boundary needs one public self profile to prove the active agent.", }); } function targetMismatchRepair() { return nonShellAction({ type: "select_expected_buzz_agent", surface: "buzz_chat", command: "Send the Run402 setup request to the intended Buzz agent, then have that agent rerun setup from its own managed session.", why: "Run402 will not relink or infer identity when the active Buzz signer differs from the requested public agent.", }); } function apiRepair(origin) { return nonShellAction({ type: "restore_run402_api_access", surface: "buzz_settings", command: `Open Buzz Desktop > Settings > Agents, select this agent, allow HTTPS access to ${safeOrigin(origin)}, restart the agent, then rerun Run402 setup.`, why: "The agent must reach the Run402 API before setup can safely initialize or verify state.", }); } function consoleRepair() { return nonShellAction({ type: "restore_run402_console_access", surface: "buzz_settings", command: "Open Buzz Desktop > Settings > Agents, select this agent, allow HTTPS access to https://console.run402.com, restart the agent, then rerun Run402 setup.", why: "The no-terminal human adoption offer requires the Run402 console origin.", }); } function relayUnsafeRepair() { return nonShellAction({ type: "repair_buzz_relay_url", surface: "buzz_settings", command: "Open Buzz Desktop > Settings > Communities, select the current community, choose its public wss:// relay, restart this agent, then rerun Run402 setup.", why: "Run402 refuses literal, private, reserved, mixed-address, credential-bearing, or non-TLS relay destinations.", }); } function relayAvailabilityRepair(origin, failure) { if (failure === "tls_handshake_failed") { return nonShellAction({ type: "repair_buzz_relay_tls", surface: "buzz_settings", 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.`, why: "Reconnect cannot repair a relay hostname whose public TLS handshake fails; community installation and enrollment stay unavailable until the endpoint is fixed.", }); } if (failure === "dns") { return nonShellAction({ type: "repair_buzz_relay_dns", surface: "buzz_settings", 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.`, why: "Community installation and enrollment require a publicly resolvable relay, while founder-agent setup remains independent.", }); } if (failure.startsWith("http_") || failure === "nip11_invalid" || failure === "redirect_rejected" || failure === "response_too_large") { return nonShellAction({ type: "repair_buzz_relay_service", surface: "buzz_settings", 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.`, why: "Community installation and enrollment require the relay's public NIP-11 service, while founder-agent setup remains independent.", }); } return nonShellAction({ type: "restore_buzz_relay_access", surface: "buzz_settings", 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.`, why: "Community installation and enrollment require a live public relay, while founder-agent setup remains independent.", }); } function walletCreateRepair(walletProfile) { return shellAction({ type: "create_buzz_wallet_profile", argv: ["run402", "wallets", "new", walletProfile], why: "Create the explicitly named dedicated agent profile, then rerun the complete preflight before setup.", safeToAutoExecute: true, requiresApproval: false, }); } function rerunDoctorRepair(walletProfile, expectedSubjectHex) { const argv = ["run402", "--wallet", walletProfile, "doctor", "--buzz"]; if (expectedSubjectHex) argv.push("--buzz-agent", expectedSubjectHex); return shellAction({ type: "rerun_buzz_doctor", argv, why: "Rerun the read-only diagnostic with the exact explicit profile and public Buzz identity.", safeToAutoExecute: true, requiresApproval: false, }); } function shellCommand(argv) { return argv.map((part) => SAFE_TOKEN.test(part) ? part : `'${part.replaceAll("'", "'\\''")}'`).join(" "); } function commandPassed(result) { return !result?.error && result?.status === 0; } function commandFailureClass(result) { if (result?.error?.code === "ETIMEDOUT") return "timeout"; if (result?.error?.code === "ENOENT") return "not_found"; if (result?.error) return "spawn_error"; return Number.isInteger(result?.status) ? `exit_${result.status}` : "unknown"; } function compareMajorMinorPatch(left, right) { const parse = (value) => String(value).match(/^v?(\d+)\.(\d+)\.(\d+)/)?.slice(1, 4).map(Number) ?? null; const a = parse(left); const b = parse(right); if (!a || !b) return -1; for (let index = 0; index < 3; index += 1) { if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1; } return 0; } function parseWalletContext(raw) { try { const parsed = JSON.parse(raw || ""); return parsed && typeof parsed === "object" ? parsed : null; } catch { return null; } } function safeRealpath(path) { if (typeof path !== "string" || path.length === 0) return null; try { return existsSync(path) ? realpathSync(path) : path; } catch { return path; } } function safePath(path) { return typeof path === "string" ? path : null; } function safeOrigin(value) { try { return new URL(value).origin; } catch { return null; } } function safeFailure(error) { if (error?.name === "AbortError") return "timeout"; const e = error; const message = error instanceof Error ? `${e.message} ${e.code ?? ""} ${e.cause?.message ?? ""} ${e.cause?.code ?? ""}` : String(error); if (/certificate|tls|ssl|eproto|secure tls connection|handshake/i.test(message)) return "tls_handshake_failed"; if (/dns|dns_empty|getaddrinfo|enotfound|eai_again/i.test(message)) return "dns"; if (/timeout|timed out|abort/i.test(message)) return "timeout"; if (/redirect/i.test(message)) return "redirect_rejected"; if (/response_too_large/i.test(message)) return "response_too_large"; if (/nip11_invalid/i.test(message)) return "nip11_invalid"; if (/http_\d+/.test(message)) return message.match(/http_\d+/)[0]; return "network"; } function assertRepairMatches(code, action) { const expected = BUZZ_DOCTOR_REPAIR_MATRIX[code]; if (!expected) throw new LocalError(`No frozen Buzz doctor repair mapping for ${code}`, "running the Buzz preflight"); const types = Array.isArray(expected.type) ? expected.type : [expected.type]; const surfaces = Array.isArray(expected.surface) ? expected.surface : [expected.surface]; if (!types.includes(action?.type) || !surfaces.includes(action?.surface)) { throw new LocalError(`Buzz doctor repair drift for ${code}`, "running the Buzz preflight"); } } class UnsafeRelayError extends Error { } /** An internal probe failure carrying its classified reason as the message; caught within this module. */ class ProbeFailure extends Error { } /** * `r.buzz` on the Node entry: the isomorphic Buzz workflows plus the * zero-mutation setup preflight. */ export class NodeBuzz extends Buzz { #diagnostics; constructor(client, diagnostics) { super(client); this.#diagnostics = diagnostics; } /** The `run402.buzz-doctor.v1` report. Read-only: nothing is created, signed, or published. */ async doctor(opts = {}) { return buildBuzzDoctorReport({ originProbe: (url, { timeoutMs }) => this.#diagnostics.probeOrigin(url, { timeoutMs }), ...opts, }); } } //# sourceMappingURL=buzz-doctor.js.map