@keeperhub/wallet 0.1.12 → 0.1.13

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/agent-detect.ts","../src/balance.ts","../src/chains.ts","../src/cli.ts","../src/fund.ts","../src/storage.ts","../src/types.ts","../src/provision.ts","../src/skill-install.ts","../src/mcp-register.ts","../src/runtime-detect.ts","../src/hmac.ts","../src/client.ts","../src/safety-config.ts","../src/hook.ts","../src/hook-entrypoint.ts","../src/mpp-detect.ts","../src/payment-signer.ts","../src/workflow-slug.ts","../src/x402-detect.ts"],"sourcesContent":["// Cross-agent skill/settings directory discovery.\n//\n// Probes canonical paths under $HOME and returns one AgentTarget record per\n// agent whose parent directory exists. The `skills/` leaf may be absent --\n// installSkill() creates it.\n//\n// NOTE: `homedir()` is called per-invocation (via `homeOverride ?? homedir()`)\n// and NEVER hoisted to a module-level constant. Tests override\n// `process.env.HOME` in `beforeEach`; hoisting would freeze the harness's\n// original HOME at import time and detection would run against the real $HOME.\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport type AgentTarget = {\n\tagent: \"claude-code\" | \"cursor\" | \"cline\" | \"windsurf\" | \"opencode\";\n\tskillsDir: string;\n\tsettingsFile: string;\n\thookSupport: \"claude-code\" | \"notice\";\n\t/**\n\t * Path-segments under $HOME where the agent stores its MCP server registry.\n\t * `undefined` for agents without a known MCP config location (cline today\n\t * stores per-VS-Code-variant globalStorage state that is too fragile to\n\t * auto-detect, so we ship \"notice\" instead).\n\t */\n\tmcpConfigRel?: string[];\n\t/**\n\t * Which MCP config shape to write. `claude-code`, `cursor`, and `windsurf`\n\t * all use the standard `{ mcpServers: { name: { command, args, env } } }`\n\t * shape. `opencode` uses a divergent `{ mcp: { name: { type:\"local\",\n\t * command:[...], enabled, environment } } }` shape. `notice` agents get\n\t * a copy-paste hint instead of an auto-registered entry.\n\t */\n\tmcpSupport: \"claude-code\" | \"cursor\" | \"windsurf\" | \"opencode\" | \"notice\";\n};\n\ntype AgentSpec = {\n\tagent: AgentTarget[\"agent\"];\n\tskillsRel: string[];\n\tsettingsRel: string[];\n\thookSupport: AgentTarget[\"hookSupport\"];\n\tmcpConfigRel?: string[];\n\tmcpSupport: AgentTarget[\"mcpSupport\"];\n\t/**\n\t * Optional extra path segments — when present, the agent is detected only\n\t * if AT LEAST ONE of `[skillsRel.dirname, ...extraDetect]` exists under\n\t * $HOME. Used by windsurf, which keeps state under both `.windsurf/` and\n\t * the legacy `.codeium/windsurf/`.\n\t */\n\textraDetect?: Array<string[]>;\n};\n\n// Deterministic order: claude-code first (only agent with hook support),\n// then cursor, cline, windsurf, opencode.\nconst AGENT_SPECS: readonly AgentSpec[] = [\n\t{\n\t\tagent: \"claude-code\",\n\t\tskillsRel: [\".claude\", \"skills\"],\n\t\tsettingsRel: [\".claude\", \"settings.json\"],\n\t\thookSupport: \"claude-code\",\n\t\t// ~/.claude.json is at HOME root (not under .claude/) and is large\n\t\t// (100+KB on real installs). registerMcpServer reads/parses/rewrites it\n\t\t// while preserving every other top-level key byte-for-byte.\n\t\tmcpConfigRel: [\".claude.json\"],\n\t\tmcpSupport: \"claude-code\",\n\t},\n\t{\n\t\tagent: \"cursor\",\n\t\tskillsRel: [\".cursor\", \"skills\"],\n\t\tsettingsRel: [\".cursor\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".cursor\", \"mcp.json\"],\n\t\tmcpSupport: \"cursor\",\n\t},\n\t{\n\t\tagent: \"cline\",\n\t\tskillsRel: [\".cline\", \"skills\"],\n\t\tsettingsRel: [\".cline\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\t// Cline keeps MCP state in a per-VS-Code-variant globalStorage path\n\t\t// (e.g. ~/Library/Application Support/Code/User/globalStorage/\n\t\t// saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too\n\t\t// fragile to auto-detect. Ship \"notice\" with a copy-paste entry shape\n\t\t// instead of guessing the variant.\n\t\tmcpSupport: \"notice\",\n\t},\n\t{\n\t\tagent: \"windsurf\",\n\t\tskillsRel: [\".windsurf\", \"skills\"],\n\t\tsettingsRel: [\".windsurf\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".codeium\", \"windsurf\", \"mcp_config.json\"],\n\t\tmcpSupport: \"windsurf\",\n\t\t// Windsurf historically ships under both `.windsurf/` and the legacy\n\t\t// `.codeium/windsurf/`; detect either.\n\t\textraDetect: [[\".codeium\", \"windsurf\"]],\n\t},\n\t{\n\t\tagent: \"opencode\",\n\t\tskillsRel: [\".config\", \"opencode\", \"skills\"],\n\t\tsettingsRel: [\".config\", \"opencode\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".config\", \"opencode\", \"opencode.json\"],\n\t\tmcpSupport: \"opencode\",\n\t},\n];\n\nexport function detectAgents(homeOverride?: string): AgentTarget[] {\n\tconst home = homeOverride ?? homedir();\n\tconst results: AgentTarget[] = [];\n\tfor (const spec of AGENT_SPECS) {\n\t\tconst skillsDir = join(home, ...spec.skillsRel);\n\t\tconst settingsFile = join(home, ...spec.settingsRel);\n\t\t// \"Detected\" iff the parent of skills/ exists (e.g. ~/.claude/) OR any\n\t\t// of the spec's extra-detect paths exist (windsurf's legacy location).\n\t\tlet detected = existsSync(dirname(skillsDir));\n\t\tif (!detected && spec.extraDetect) {\n\t\t\tfor (const seg of spec.extraDetect) {\n\t\t\t\tif (existsSync(join(home, ...seg))) {\n\t\t\t\t\tdetected = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!detected) {\n\t\t\tcontinue;\n\t\t}\n\t\tresults.push({\n\t\t\tagent: spec.agent,\n\t\t\tskillsDir,\n\t\t\tsettingsFile,\n\t\t\thookSupport: spec.hookSupport,\n\t\t\tmcpConfigRel: spec.mcpConfigRel,\n\t\t\tmcpSupport: spec.mcpSupport,\n\t\t});\n\t}\n\treturn results;\n}\n","// checkBalance() unified view (PAY-05):\n// - Base USDC balanceOf (viem publicClient on Base)\n// - Tempo USDC.e balanceOf (viem publicClient on Tempo)\n//\n// Both legs are fetched in parallel via Promise.all. The on-chain reads\n// touch only the canonical USDC contract on their respective chains\n// (read-only ERC-20 balanceOf with no state mutation).\n//\n// The /api/agentic-wallet/credit ledger is intentionally NOT read here:\n// the server endpoint exists but no debit path is wired, so surfacing the\n// balance to users implied a capability that has not shipped. Restore the\n// leg here when KEEP-305/306 lands.\n//\n// @security balance.ts does not emit balance data to stdout/stderr via the\n// global console object or util.inspect (T-34-bal-02 mitigation). Any\n// stdout emitter added here is a privacy regression; grep-enforced in\n// acceptance criteria.\nimport {\n createPublicClient,\n erc20Abi,\n formatUnits,\n http,\n type PublicClient,\n} from \"viem\";\nimport { BASE_USDC, base, TEMPO_USDC_E, tempo } from \"./chains.js\";\nimport type { WalletConfig } from \"./types.js\";\n\n// USDC and USDC.e both use 6 decimals on Base + Tempo respectively.\nconst USDC_DECIMALS = 6;\n\nexport type BalanceSnapshot = {\n base: {\n chain: \"base\";\n token: \"USDC\";\n amount: string;\n address: `0x${string}`;\n };\n tempo: {\n chain: \"tempo\";\n token: \"USDC.e\";\n amount: string;\n address: `0x${string}`;\n };\n};\n\nexport type CheckBalanceOptions = {\n /** Injectable viem client for Base (tests mock readContract). */\n baseClient?: PublicClient;\n /** Injectable viem client for Tempo (tests mock readContract). */\n tempoClient?: PublicClient;\n};\n\n/**\n * Read the wallet's on-chain balance across Base + Tempo in parallel. Both\n * legs must resolve; any single failure rejects the Promise.\n *\n * Amounts are formatted as decimal strings (6-decimal USDC precision) so the\n * caller can render them without BigInt math.\n */\nexport async function checkBalance(\n wallet: WalletConfig,\n opts: CheckBalanceOptions = {}\n): Promise<BalanceSnapshot> {\n const baseClient =\n opts.baseClient ??\n (createPublicClient({\n chain: base,\n transport: http(),\n }) as unknown as PublicClient);\n const tempoClient =\n opts.tempoClient ??\n (createPublicClient({\n chain: tempo,\n transport: http(),\n }) as unknown as PublicClient);\n\n // Promise.all fires both reads concurrently. Total elapsed ~= max(leg)\n // rather than sum(leg); SC-3 (<2s) test asserts this.\n const [baseRaw, tempoRaw] = await Promise.all([\n baseClient.readContract({\n address: BASE_USDC,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n tempoClient.readContract({\n address: TEMPO_USDC_E,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n ]);\n\n return {\n base: {\n chain: \"base\",\n token: \"USDC\",\n amount: formatUnits(baseRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n tempo: {\n chain: \"tempo\",\n token: \"USDC.e\",\n amount: formatUnits(tempoRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n };\n}\n","// Sources (truth):\n// - lib/agentic-wallet/sign.ts:56 -- Base USDC at\n// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (chainId 8453).\n// - lib/mpp/server.ts:3 -- Tempo USDC.e at\n// 0x20c000000000000000000000b9537d11c60e8b50 (chainId 4217).\n//\n// Tempo is not in viem/chains core as of viem 2.48.1 (the version pinned in\n// this package). Define it inline via defineChain so the only dependency is\n// viem itself. TEMPO_RPC_URL overrides the default RPC for heavy readers who\n// want to point at their own node (T-34-bal-01 mitigation).\nimport { defineChain } from \"viem\";\n\nexport { base } from \"viem/chains\";\n\nexport const tempo = defineChain({\n id: 4217,\n name: \"Tempo\",\n nativeCurrency: { decimals: 18, name: \"Ether\", symbol: \"ETH\" },\n rpcUrls: {\n default: {\n http: [process.env.TEMPO_RPC_URL ?? \"https://rpc.tempo.xyz\"],\n },\n },\n blockExplorers: {\n default: { name: \"Tempo Explorer\", url: \"https://explorer.tempo.xyz\" },\n },\n});\n\n/** Circle-issued USDC on Base mainnet. */\nexport const BASE_USDC = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\n/** Bridged USDC (USDC.e) on Tempo mainnet. NOT the same contract as BASE_USDC. */\nexport const TEMPO_USDC_E =\n \"0x20c000000000000000000000b9537d11c60e8b50\" as const;\n","// CLI dispatcher for `npx @keeperhub/wallet <cmd>`. Ships 4 subcommands:\n// add (provision -- NO auth), fund (pure string-build Coinbase Onramp +\n// Tempo address), balance (Base USDC + Tempo USDC.e), info (print subOrgId\n// + walletAddress from ~/.keeperhub/wallet.json).\n//\n// v0.1.4 removed the `link` subcommand. /api/agentic-wallet/link still\n// exists server-side but the UX (copy-paste session cookie) was not fit\n// for real users; the server-approval ask tier that required linking\n// also collapsed into an inline ask in this release. See KEEP-307 and\n// KEEP-308 for the long-term design decisions.\n//\n// @security The HMAC secret written to wallet.json is NEVER printed to stdout\n// or stderr. `add` prints only subOrgId + walletAddress + the config path so\n// users can inspect perms. `info` never references the secret at all. Grep\n// rule: no process.stdout/process.stderr line in this file should include\n// wallet.hmacSecret or data.hmacSecret.\n//\n// Exit codes: 0 on success, 1 on any error (WalletConfigMissingError,\n// HTTP failure, validation error). Uncaught errors are written to stderr.\n\nimport { Command } from \"commander\";\nimport { checkBalance } from \"./balance.js\";\nimport { fund } from \"./fund.js\";\nimport { ProvisionHttpError, provisionWallet } from \"./provision.js\";\nimport { installSkill } from \"./skill-install.js\";\nimport { getWalletConfigPath, readWalletConfig } from \"./storage.js\";\nimport { WalletConfigMissingError } from \"./types.js\";\n\nasync function cmdAdd(opts: { baseUrl?: string } = {}): Promise<void> {\n\ttry {\n\t\tconst data = await provisionWallet({ baseUrl: opts.baseUrl });\n\t\t// Intentionally print only public fields. The hmacSecret is written to\n\t\t// wallet.json (chmod 0o600) but never printed -- T-34-cli-02 mitigation.\n\t\tprocess.stdout.write(`subOrgId: ${data.subOrgId}\\n`);\n\t\tprocess.stdout.write(`walletAddress: ${data.walletAddress}\\n`);\n\t\tprocess.stdout.write(`config written to ${getWalletConfigPath()}\\n`);\n\t} catch (err) {\n\t\tif (err instanceof ProvisionHttpError) {\n\t\t\tprocess.stderr.write(\n\t\t\t\t`[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}\\n`,\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function cmdFund(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tconst out = fund(wallet.walletAddress);\n\tprocess.stdout.write(`${out.coinbaseOnrampUrl}\\n`);\n\tprocess.stdout.write(`Tempo address: ${out.tempoAddress}\\n`);\n\tprocess.stdout.write(`${out.disclaimer}\\n`);\n}\n\nasync function cmdBalance(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tconst snap = await checkBalance(wallet);\n\tprocess.stdout.write(`Base USDC: ${snap.base.amount}\\n`);\n\tprocess.stdout.write(`Tempo USDC.e: ${snap.tempo.amount}\\n`);\n}\n\nasync function cmdInfo(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tprocess.stdout.write(`subOrgId: ${wallet.subOrgId}\\n`);\n\tprocess.stdout.write(`walletAddress: ${wallet.walletAddress}\\n`);\n}\n\nexport async function runCli(argv: string[] = process.argv): Promise<void> {\n\tconst program = new Command();\n\tprogram\n\t\t.name(\"keeperhub-wallet\")\n\t\t.description(\n\t\t\t\"KeeperHub agentic wallet CLI (auto-pay x402 + MPP 402 responses)\",\n\t\t)\n\t\t.version(\"0.1.3\");\n\n\tprogram\n\t\t.command(\"add\")\n\t\t.description(\"Provision a new agentic wallet (no account required)\")\n\t\t.option(\"--base-url <url>\", \"KeeperHub API base URL\")\n\t\t.action(async (opts: { baseUrl?: string }) => {\n\t\t\tawait cmdAdd(opts);\n\t\t});\n\n\tprogram\n\t\t.command(\"fund\")\n\t\t.description(\n\t\t\t\"Print Coinbase Onramp URL (Base USDC) and Tempo deposit address\",\n\t\t)\n\t\t.action(async () => {\n\t\t\tawait cmdFund();\n\t\t});\n\n\tprogram\n\t\t.command(\"balance\")\n\t\t.description(\"Print on-chain balance: Base USDC + Tempo USDC.e\")\n\t\t.action(async () => {\n\t\t\tawait cmdBalance();\n\t\t});\n\n\tprogram\n\t\t.command(\"info\")\n\t\t.description(\"Print subOrgId and walletAddress from local config\")\n\t\t.action(async () => {\n\t\t\tawait cmdInfo();\n\t\t});\n\n\tprogram\n\t\t.command(\"skill\")\n\t\t.description(\n\t\t\t\"Install the KeeperHub skill file into detected agent directories\",\n\t\t)\n\t\t.addCommand(\n\t\t\tnew Command(\"install\")\n\t\t\t\t.description(\n\t\t\t\t\t\"Write skill file + register PreToolUse hook in all detected agents\",\n\t\t\t\t)\n\t\t\t\t.action(async () => {\n\t\t\t\t\tconst result = await installSkill();\n\t\t\t\t\tfor (const write of result.skillWrites) {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`skill: ${write.agent} -> ${write.path} (${write.status})\\n`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tfor (const reg of result.hookRegistrations) {\n\t\t\t\t\t\tif (reg.status === \"registered\") {\n\t\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t\t`hook: ${reg.agent} -> PreToolUse registered\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"notice\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`notice: ${reg.agent} -> ${reg.message ?? \"\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"failed\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`hook: ${reg.agent} -> FAILED (${reg.message ?? \"unknown error\"})\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const reg of result.mcpRegistrations) {\n\t\t\t\t\t\tif (reg.status === \"registered\") {\n\t\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t\t`mcp: ${reg.agent} -> registered at ${reg.path ?? \"(unknown path)\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"notice\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`notice: ${reg.agent} mcp -> ${reg.message ?? \"\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"failed\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`mcp: ${reg.agent} -> FAILED (${reg.message ?? \"unknown error\"})\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (result.skillWrites.length === 0) {\n\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\"No supported agent skill directories detected under $HOME. Create ~/.claude/, ~/.cursor/, ~/.cline/, ~/.windsurf/, or ~/.config/opencode/ and re-run.\\n\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t);\n\n\ttry {\n\t\tawait program.parseAsync(argv);\n\t} catch (err) {\n\t\tif (err instanceof WalletConfigMissingError) {\n\t\t\tprocess.stderr.write(`[keeperhub-wallet] ${err.message}\\n`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tprocess.stderr.write(\n\t\t\t`[keeperhub-wallet] ${(err as Error).message ?? String(err)}\\n`,\n\t\t);\n\t\tprocess.exit(1);\n\t}\n}\n","// Source: 34-RESEARCH Pattern 5 + Pitfall 5.\n// Coinbase deprecated the query-param pay.coinbase.com flow in favour of\n// sessionToken URLs on 2025-07-31, but the legacy endpoint still returns a\n// working Onramp page (it just may not pre-fill the asset/network/address\n// fields). We print the legacy URL for zero-dependency ergonomics and a\n// follow-up disclaimer so users know to paste manually if prefill is dropped.\n//\n// fund() is a pure string-build: no HTTP, no process spawn, no browser\n// invocation. Callers (the CLI `keeperhub-wallet fund` subcommand, the\n// `check_balance` skill in Phase 35) decide how to display the result.\n//\n// T-34-fund-01 mitigation: the host is hard-coded (pay.coinbase.com) and the\n// only user-supplied input is the wallet address, which is regex-validated\n// against the canonical 0x-prefixed 40-hex-char EVM format before any string\n// interpolation.\n\nexport type FundInstructions = {\n /** Coinbase Onramp deeplink (legacy query-param form). */\n coinbaseOnrampUrl: string;\n /** Tempo deposit address — same as the input wallet (EVM address shared). */\n tempoAddress: `0x${string}`;\n /** Plain-ASCII guidance string; no emojis (CLAUDE.md rule). */\n disclaimer: string;\n};\n\n// 0x followed by exactly 40 hex chars, case-insensitive. Kept at module scope\n// so the regex literal is compiled once (biome/ultracite useTopLevelRegex).\nconst EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\n\n// Coinbase Onramp legacy deeplink. The host + path pair is the documented\n// entry point for query-param-style Onramp sessions.\nconst COINBASE_HOST = \"pay.coinbase.com\";\nconst COINBASE_PATH = \"/buy/select-asset\";\n\n/**\n * Build Coinbase Onramp URL + Tempo deposit address for the given wallet.\n *\n * No HTTP calls are performed. The caller is expected to either print the\n * resulting URL (CLI) or render it in a chat bubble (skill). The returned\n * `disclaimer` explains the Onramp deprecation + the Tempo external-transfer\n * fallback in plain ASCII so terminal clients with ASCII-only fonts render\n * identically to emoji-capable clients.\n *\n * @throws if `walletAddress` does not match /^0x[0-9a-fA-F]{40}$/.\n */\nexport function fund(walletAddress: string): FundInstructions {\n if (!EVM_ADDRESS_RE.test(walletAddress)) {\n throw new Error(`Invalid EVM wallet address: ${walletAddress}`);\n }\n\n // addresses is a JSON-encoded map {walletAddress: [\"base\"]} per Coinbase\n // Onramp docs. Encoding into URLSearchParams guarantees the colon,\n // brackets, and quotes are percent-escaped correctly.\n const params = new URLSearchParams({\n defaultNetwork: \"base\",\n defaultAsset: \"USDC\",\n addresses: JSON.stringify({ [walletAddress]: [\"base\"] }),\n presetCryptoAmount: \"5\",\n });\n\n const coinbaseOnrampUrl = `https://${COINBASE_HOST}${COINBASE_PATH}?${params.toString()}`;\n\n const disclaimer =\n \"If the Coinbase page does not pre-fill, paste your address manually. \" +\n \"For Tempo USDC.e, transfer from an exchange or another wallet to the \" +\n \"address above -- Onramp does not support Tempo directly. Coinbase \" +\n \"sessionToken URLs are the 2025+ canonical form; legacy query-param \" +\n \"URLs may drop prefill on some accounts.\";\n\n return {\n coinbaseOnrampUrl,\n tempoAddress: walletAddress as `0x${string}`,\n disclaimer,\n };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { chmod, mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport {\n\ttype WalletConfig,\n\tWalletConfigCorruptError,\n\tWalletConfigMissingError,\n} from \"./types.js\";\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"wallet.json\")`\n// itself. Do NOT hoist to a module-level `const WALLET_PATH` -- tests\n// override `process.env.HOME` in `beforeEach` and `homedir()` must re-read\n// that on each call. A hoisted constant would freeze the harness's original\n// HOME at import time and every test would write into the real\n// ~/.keeperhub/ directory.\n\nexport async function readWalletConfig(): Promise<WalletConfig> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(walletPath, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tthrow new WalletConfigMissingError();\n\t\t}\n\t\tthrow err;\n\t}\n\t// The file's PRESENCE means it was created on purpose. Distinguish parse\n\t// failures (malformed JSON, truncated write) and field-shape failures\n\t// (missing required keys) from \"missing entirely\" so callers — including\n\t// the MCP server's auto-provision path — can refuse to silently mint a\n\t// replacement wallet over an existing-but-broken one.\n\tlet parsed: Partial<WalletConfig>;\n\ttry {\n\t\tparsed = JSON.parse(raw) as Partial<WalletConfig>;\n\t} catch (err) {\n\t\tconst reason = err instanceof Error ? err.message : String(err);\n\t\tthrow new WalletConfigCorruptError(walletPath, reason);\n\t}\n\tif (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {\n\t\tthrow new WalletConfigCorruptError(walletPath, \"missing required fields\");\n\t}\n\treturn parsed as WalletConfig;\n}\n\n/**\n * Atomic write: serialise to a sibling tmp file, fsync via the close, then\n * rename(2) into place. Concurrent provisioning races (two MCP sessions\n * spawning at once on a fresh install) collapse to last-rename-wins on the\n * final path rather than torn-write-wins. Combined with the in-process\n * promise cache in mcp-server.ts:ensureWallet this gates duplicate-mint\n * within a single process AND keeps disk consistent across multiple.\n *\n * The tmp filename includes 16 bytes of randomness so two writers don't\n * stomp each other's tmp files mid-write either.\n */\nexport async function writeWalletConfig(config: WalletConfig): Promise<void> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tawait mkdir(dirname(walletPath), { recursive: true, mode: 0o700 });\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;\n\tawait writeFile(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n\t// chmod again on the tmp before rename: writeFile honours the mode on\n\t// creation but a pre-existing tmp (extremely unlikely thanks to the\n\t// randomness) might keep looser perms.\n\tawait chmod(tmpPath, 0o600);\n\tawait rename(tmpPath, walletPath);\n}\n\nexport function getWalletConfigPath(): string {\n\treturn join(homedir(), \".keeperhub\", \"wallet.json\");\n}\n","// Shared types across the package. Phase 34.\nexport type WalletConfig = {\n\t/** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */\n\tsubOrgId: string;\n\t/** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */\n\twalletAddress: `0x${string}`;\n\t/** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */\n\thmacSecret: string;\n};\n\nexport type HmacHeaders = {\n\t\"X-KH-Sub-Org\": string;\n\t\"X-KH-Timestamp\": string;\n\t\"X-KH-Signature\": string;\n};\n\nexport type HookDecision = {\n\tdecision: \"allow\" | \"deny\" | \"ask\";\n\treason?: string;\n};\n\nexport class KeeperHubError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"KeeperHubError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Protocol preference for a single pay() or fetch() call. \"auto\" preserves\n * the x402-first default when both challenges are offered. */\nexport type PaymentHint = \"x402\" | \"mpp\" | \"auto\";\n\nexport class WalletConfigMissingError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision.\",\n\t\t);\n\t\tthis.name = \"WalletConfigMissingError\";\n\t}\n}\n\n/**\n * Thrown when ~/.keeperhub/wallet.json exists but is unreadable as a wallet\n * config (malformed JSON, truncated write, missing required fields, hand-edit\n * gone wrong). Distinct from WalletConfigMissingError — the file's PRESENCE\n * means the user (or a prior install) intentionally created it, so the MCP\n * server MUST NOT silently auto-provision a replacement and abandon any funds\n * held in the existing wallet. Surface a structured error with the path so\n * the user can repair or delete the file deliberately.\n */\nexport class WalletConfigCorruptError extends Error {\n\treadonly path: string;\n\n\tconstructor(path: string, reason: string) {\n\t\tsuper(\n\t\t\t`Wallet config at ${path} is unreadable: ${reason}. Repair the file by hand or delete it to re-provision a new wallet (this will abandon any funds held in the current wallet).`,\n\t\t);\n\t\tthis.name = \"WalletConfigCorruptError\";\n\t\tthis.path = path;\n\t}\n}\n","// Wallet provisioning: shared between `keeperhub-wallet add` (CLI) and the\n// MCP server's auto-provision-on-first-call path.\n//\n// Why a shared module: the user-facing UX target is \"install the wallet\n// package and start calling paid workflows\" — manual `keeperhub-wallet add`\n// ceremony is an unnecessary speed bump. The MCP server therefore reuses\n// this exact provision flow on the first tool call when ~/.keeperhub/wallet.json\n// is missing. Keeping the call site in one module avoids the obvious\n// drift hazard of two divergent provision implementations.\n//\n// @security The HMAC secret minted by /api/agentic-wallet/provision is\n// returned to the caller AND written to ~/.keeperhub/wallet.json (chmod 0o600\n// via writeWalletConfig). Callers MUST NOT log the returned secret to stdout\n// or stderr. The CLI's `cmdAdd` and the MCP server's auto-provision branch\n// both honour this — every printed/serialised path uses only `subOrgId` and\n// `walletAddress`.\n\nimport { writeWalletConfig } from \"./storage.js\";\nimport type { WalletConfig } from \"./types.js\";\n\nconst TRAILING_SLASH = /\\/$/;\nconst WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;\n\nexport type ProvisionOptions = {\n\t/** Override KEEPERHUB_API_URL. Defaults to env var or app.keeperhub.com. */\n\tbaseUrl?: string;\n\t/** Injectable fetch for tests. */\n\tfetchImpl?: typeof fetch;\n};\n\nexport class ProvisionResponseInvalidError extends Error {\n\treadonly code = \"PROVISION_RESPONSE_INVALID\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ProvisionResponseInvalidError\";\n\t}\n}\n\nexport class ProvisionHttpError extends Error {\n\treadonly code = \"PROVISION_HTTP_ERROR\" as const;\n\treadonly status: number;\n\treadonly body: string;\n\n\tconstructor(status: number, body: string) {\n\t\tsuper(`provision failed: HTTP ${status}: ${body}`);\n\t\tthis.name = \"ProvisionHttpError\";\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nfunction resolveBaseUrl(override: string | undefined): string {\n\tconst candidate =\n\t\toverride ?? process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n\treturn candidate.replace(TRAILING_SLASH, \"\");\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction validateProvisionResponse(data: unknown): WalletConfig {\n\tif (typeof data !== \"object\" || data === null) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response is not an object\",\n\t\t);\n\t}\n\tconst { subOrgId, walletAddress, hmacSecret } = data as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tif (\n\t\t!(\n\t\t\tisNonEmptyString(subOrgId) &&\n\t\t\tisNonEmptyString(walletAddress) &&\n\t\t\tisNonEmptyString(hmacSecret)\n\t\t)\n\t) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response missing subOrgId, walletAddress, or hmacSecret\",\n\t\t);\n\t}\n\tif (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`,\n\t\t);\n\t}\n\treturn {\n\t\tsubOrgId,\n\t\twalletAddress: walletAddress as `0x${string}`,\n\t\thmacSecret,\n\t};\n}\n\n/**\n * Mint a new agentic wallet via POST /api/agentic-wallet/provision and\n * persist the result to ~/.keeperhub/wallet.json (chmod 0o600). Returns the\n * provisioned WalletConfig. Throws ProvisionHttpError on non-2xx and\n * ProvisionResponseInvalidError on malformed payloads.\n *\n * Used by:\n * - `keeperhub-wallet add` (manual provisioning).\n * - The MCP server's auto-provision-on-first-call path (no manual ceremony).\n */\nexport async function provisionWallet(\n\toptions: ProvisionOptions = {},\n): Promise<WalletConfig> {\n\tconst baseUrl = resolveBaseUrl(options.baseUrl);\n\tconst fetchImpl = options.fetchImpl ?? globalThis.fetch;\n\t// Bound the wedged-upstream failure mode so a hung provision endpoint\n\t// can't freeze the MCP tool call indefinitely. AbortError surfaces as\n\t// UPSTREAM_TIMEOUT in the MCP envelope; CLI users see the raw cause.\n\tconst response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: \"{}\",\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new ProvisionHttpError(response.status, text);\n\t}\n\tconst raw = (await response.json()) as unknown;\n\tconst data = validateProvisionResponse(raw);\n\tawait writeWalletConfig(data);\n\treturn data;\n}\n","// Idempotent skill installer for @keeperhub/wallet.\n//\n// Two public entry points:\n// - installSkill(options?) -- writes keeperhub-wallet.skill.md into every\n// detected agent's skills directory and, for Claude Code, registers a\n// PreToolUse hook pointing at `keeperhub-wallet-hook` in\n// ~/.claude/settings.json. For non-claude agents, emits a stderr notice.\n// - registerClaudeCodeHook(settingsPath, options?) -- pure settings.json\n// patcher used internally; exported so tests can drive it directly.\n//\n// Hook command resolution: the README's recommended install path is\n// `npx @keeperhub/wallet skill install`, which does not put the bin on the\n// system PATH. If we wrote a bare `keeperhub-wallet-hook` command in that\n// case, the hook would fire `command not found` on every tool call. So at\n// install time we probe PATH; if the bin resolves we keep the bare command\n// (lowest startup latency), otherwise we fall back to an `npx` invocation\n// that resolves regardless of where future shells run.\n//\n// Idempotency rule: re-running the installer MUST NOT create a duplicate\n// hook entry. We filter any existing array element whose serialised form\n// contains `keeperhub-wallet-hook` before appending a single fresh record.\n// The marker substring is present in BOTH the bare and npx forms, so the\n// de-dup survives a global-install → npx-install transition (and back).\n//\n// Preservation rule: all top-level keys in settings.json other than\n// hooks.PreToolUse MUST be byte-preserved. We only ever touch\n// hooks.PreToolUse; any foreign hooks.PostToolUse entries survive verbatim.\n\nimport { randomBytes } from \"node:crypto\";\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trename,\n\tunlink,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type AgentTarget, detectAgents } from \"./agent-detect.js\";\nimport {\n\ttype RegisterMcpServerOptions,\n\tregisterMcpServer,\n\tresolveMcpCommand,\n} from \"./mcp-register.js\";\nimport { resolveBinCommand } from \"./runtime-detect.js\";\n\nconst HOOK_BIN = \"keeperhub-wallet-hook\";\n\n// Match rule for de-dup: any existing PreToolUse entry whose `command`\n// string contains this substring is considered \"ours\" and is removed\n// before append. The marker is present in BOTH the bare and pinned-npx\n// forms, so the de-dup survives a global-install <-> npx-install\n// transition (and across version bumps).\n//\n// Why match on the `command` field rather than JSON.stringify(entry):\n// the wider marker would silently delete an unrelated hook whose args\n// or matcher happen to mention the bin name (e.g. a logger). Scoping\n// to `command` is narrower and equally idempotent for our writes since\n// we always write the marker into `command`.\nconst KEEPERHUB_HOOK_MARKER = HOOK_BIN;\n\ntype PreToolUseLikeEntry = {\n\thooks?: Array<{ command?: unknown }>;\n};\n\n/**\n * Drop only the `hooks[]` items that reference the keeperhub bin, leaving\n * sibling commands inside the same `PreToolUse` element intact. Returns\n * the (possibly modified) entry, or null when every `hooks[]` item was\n * keeperhub-related and the whole element should be removed.\n *\n * Why per-item: a user may merge our hook into a single `PreToolUse`\n * element alongside their own commands, e.g.:\n *\n * { matcher: \"*\", hooks: [\n * { type: \"command\", command: \"/usr/local/bin/audit-logger\" },\n * { type: \"command\", command: \"keeperhub-wallet-hook\" } ] }\n *\n * Dropping the whole element on re-install would silently delete the\n * audit-logger sibling. Dropping only matching items preserves it.\n *\n * Non-object entries and entries without a `hooks[]` array are returned\n * unchanged — we never inspect or mutate shapes we don't recognise.\n */\nfunction filterKeeperhubHooksFromEntry(entry: unknown): unknown {\n\tif (typeof entry !== \"object\" || entry === null) {\n\t\treturn entry;\n\t}\n\tconst candidate = entry as PreToolUseLikeEntry;\n\tif (!Array.isArray(candidate.hooks)) {\n\t\treturn entry;\n\t}\n\tconst survivors = candidate.hooks.filter((h) => {\n\t\tconst cmd = h?.command;\n\t\treturn !(typeof cmd === \"string\" && cmd.includes(KEEPERHUB_HOOK_MARKER));\n\t});\n\tif (survivors.length === candidate.hooks.length) {\n\t\t// No keeperhub hooks present in this entry — return original byte-for-byte.\n\t\treturn entry;\n\t}\n\tif (survivors.length === 0) {\n\t\t// Every hook in this element was ours; drop the whole element so we\n\t\t// don't leave a `{matcher, hooks: []}` shell behind.\n\t\treturn null;\n\t}\n\treturn { ...candidate, hooks: survivors };\n}\n\n/**\n * Pick the hook command to write into settings.json.\n *\n * Returns the bare bin name if it resolves to a STABLE install on PATH\n * (global install, brew, distro pkg, dev-time `npm link`), otherwise a\n * version-pinned `npx` invocation that pulls the installer's own version\n * of `@keeperhub/wallet` on demand. Implementation lives in\n * runtime-detect.ts so the hook + MCP installers share a single decision.\n *\n * Override-able via the env var `KEEPERHUB_WALLET_HOOK_COMMAND` for test\n * fixtures and unusual deployments (env input is trusted — it is written\n * verbatim into settings.json and executed by the user's shell).\n */\nexport function resolveHookCommand(): string {\n\tconst envOverride = process.env.KEEPERHUB_WALLET_HOOK_COMMAND;\n\tif (envOverride && envOverride.length > 0) {\n\t\treturn envOverride;\n\t}\n\treturn resolveBinCommand(HOOK_BIN).commandString;\n}\n\nexport type InstallResult = {\n\tskillWrites: Array<{\n\t\tagent: string;\n\t\tpath: string;\n\t\tstatus: \"written\" | \"skipped\";\n\t}>;\n\thookRegistrations: Array<{\n\t\tagent: string;\n\t\tstatus: \"registered\" | \"notice\" | \"skipped\" | \"failed\";\n\t\tmessage?: string;\n\t}>;\n\tmcpRegistrations: Array<{\n\t\tagent: string;\n\t\tstatus: \"registered\" | \"notice\" | \"skipped\" | \"failed\";\n\t\tpath?: string;\n\t\tmessage?: string;\n\t}>;\n};\n\nexport type InstallOptions = {\n\thomeOverride?: string;\n\tskillSourcePath?: string;\n\tonNotice?: (msg: string) => void;\n\t/**\n\t * Hook command to write into settings.json (and reference in stderr\n\t * notices for non-Claude agents). Defaults to {@link resolveHookCommand}.\n\t * Override for tests, monorepo setups, or unusual deployments.\n\t */\n\thookCommand?: string;\n\t/**\n\t * MCP command + args to register with each detected agent. Defaults to\n\t * {@link resolveMcpCommand}. Tests pass an explicit value to pin\n\t * assertions regardless of host PATH.\n\t */\n\tmcpCommand?: RegisterMcpServerOptions[\"command\"];\n};\n\nexport type RegisterClaudeCodeHookOptions = {\n\t/**\n\t * Hook command to write. Defaults to {@link resolveHookCommand}. Tests\n\t * pass a deterministic value to keep assertions stable across host\n\t * environments (CI may or may not have the bin on PATH).\n\t */\n\thookCommand?: string;\n};\n\ntype ClaudeHookEntry = {\n\tmatcher: string;\n\thooks: Array<{ type: string; command: string }>;\n};\n\ntype ClaudeSettings = {\n\thooks?: {\n\t\tPreToolUse?: unknown[];\n\t\t[k: string]: unknown;\n\t};\n\t[k: string]: unknown;\n};\n\nfunction buildKeeperhubEntry(command: string): ClaudeHookEntry {\n\treturn {\n\t\tmatcher: \"*\",\n\t\thooks: [{ type: \"command\", command }],\n\t};\n}\n\nfunction resolveDefaultSkillSource(): string {\n\t// Resolve the module's own directory in a way that works in both ESM\n\t// (import.meta.url) and CJS (__dirname shim emitted by tsup). At runtime\n\t// the module lives inside dist/, so `../skill/` points at the sibling\n\t// skill/ directory shipped via pkg.files. During vitest tests the module\n\t// executes from src/, and `../skill/` resolves to packages/wallet/skill/.\n\tconst here = dirname(fileURLToPath(import.meta.url));\n\treturn join(here, \"..\", \"skill\", \"keeperhub-wallet.skill.md\");\n}\n\nfunction defaultNotice(msg: string): void {\n\tprocess.stderr.write(`${msg}\\n`);\n}\n\nexport async function registerClaudeCodeHook(\n\tsettingsPath: string,\n\toptions: RegisterClaudeCodeHookOptions = {},\n): Promise<void> {\n\tconst command = options.hookCommand ?? resolveHookCommand();\n\n\tlet raw: string | null = null;\n\ttry {\n\t\traw = await readFile(settingsPath, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tlet config: ClaudeSettings = {};\n\tif (raw !== null) {\n\t\ttry {\n\t\t\tconfig = JSON.parse(raw) as ClaudeSettings;\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`settings.json at ${settingsPath} is not valid JSON; aborting hook registration`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst hooks: Record<string, unknown> =\n\t\ttypeof config.hooks === \"object\" && config.hooks !== null\n\t\t\t? (config.hooks as Record<string, unknown>)\n\t\t\t: {};\n\n\tconst existingPreToolUse = Array.isArray(hooks.PreToolUse)\n\t\t? (hooks.PreToolUse as unknown[])\n\t\t: [];\n\n\t// De-dup: drop only the hooks[] items whose `command` field references\n\t// the keeperhub-wallet-hook bin, leaving sibling commands within the\n\t// same PreToolUse element untouched. Scoped to the `command` field (not\n\t// the full serialised entry) so an unrelated hook that mentions the bin\n\t// name in its matcher or args isn't silently deleted. Covers both the\n\t// bare-bin and version-pinned npx forms, and older versions of this\n\t// installer.\n\tconst filtered: unknown[] = [];\n\tfor (const entry of existingPreToolUse) {\n\t\tconst survivor = filterKeeperhubHooksFromEntry(entry);\n\t\tif (survivor !== null) {\n\t\t\tfiltered.push(survivor);\n\t\t}\n\t}\n\tfiltered.push(buildKeeperhubEntry(command));\n\n\thooks.PreToolUse = filtered;\n\tconfig.hooks = hooks as ClaudeSettings[\"hooks\"];\n\n\tawait mkdir(dirname(settingsPath), { recursive: true, mode: 0o700 });\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\t// Atomic write: tmp + rename so a crash mid-write cannot truncate the\n\t// user's settings.json (which on Claude Code typically holds many\n\t// unrelated PreToolUse/PostToolUse entries plus mcpServers, theme, etc.).\n\t// rename(2) is the atomic step on every POSIX filesystem and on Windows\n\t// NTFS — the live file is either the old contents or the full new\n\t// contents, never partial. chmod the tmp before rename so the live\n\t// permissions are correct from the first readable byte.\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;\n\ttry {\n\t\tawait writeFile(tmpPath, payload, { mode: 0o600 });\n\t\tawait chmod(tmpPath, 0o600);\n\t\tawait rename(tmpPath, settingsPath);\n\t} catch (err) {\n\t\tawait unlink(tmpPath).catch(() => {\n\t\t\t// best-effort cleanup; ignore ENOENT and other failures\n\t\t});\n\t\tthrow err;\n\t}\n}\n\nasync function writeSkillToAgent(\n\tagent: AgentTarget,\n\tskillSource: string,\n): Promise<{ agent: string; path: string; status: \"written\" | \"skipped\" }> {\n\tawait mkdir(agent.skillsDir, { recursive: true, mode: 0o755 });\n\tconst target = join(agent.skillsDir, \"keeperhub-wallet.skill.md\");\n\tawait copyFile(skillSource, target);\n\tawait chmod(target, 0o644);\n\treturn { agent: agent.agent, path: target, status: \"written\" };\n}\n\nfunction buildHookNoticeMessage(agent: AgentTarget, command: string): string {\n\treturn `${agent.agent} does not support auto-registered PreToolUse hooks; run \\`${command}\\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;\n}\n\nfunction buildMcpNoticeMessage(\n\tagent: AgentTarget,\n\tcommand: { command: string; args: string[] },\n): string {\n\tconst cmd = [command.command, ...command.args].join(\" \");\n\treturn `${agent.agent} does not support auto-registered MCP servers; add an entry named \\`keeperhub-wallet\\` running \\`${cmd}\\` to your MCP config manually`;\n}\n\n/**\n * Install the keeperhub-wallet skill plus the PreToolUse safety hook plus\n * the keeperhub-wallet MCP server into every detected agent.\n *\n * Per-agent flow:\n * 1. Copy `keeperhub-wallet.skill.md` into the agent's `skills/` dir.\n * 2. If the agent supports PreToolUse hooks (claude-code), register the\n * safety hook in `settings.json`. Otherwise print a stderr notice.\n * 3. If the agent supports MCP server registration (claude-code, cursor,\n * windsurf, opencode), register the keeperhub-wallet MCP server in the\n * agent's MCP config file (claude.json / mcp.json / opencode.json).\n * Otherwise print a stderr notice.\n *\n * MCP idempotency is automatic: each agent's MCP config keys servers by name,\n * so a re-run overwrites the existing `keeperhub-wallet` entry rather than\n * appending a duplicate. All other keys are byte-preserved.\n */\nexport async function installSkill(\n\toptions: InstallOptions = {},\n): Promise<InstallResult> {\n\tconst agents = detectAgents(options.homeOverride);\n\tconst skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();\n\tconst onNotice = options.onNotice ?? defaultNotice;\n\t// Resolve once per install run so the bare-vs-npx decision stays\n\t// consistent across every detected agent. Tests pass an explicit value to\n\t// pin the assertion shape regardless of host PATH.\n\tconst hookCommand = options.hookCommand ?? resolveHookCommand();\n\tconst mcpCommand = options.mcpCommand ?? resolveMcpCommand();\n\n\tconst skillWrites: InstallResult[\"skillWrites\"] = [];\n\tconst hookRegistrations: InstallResult[\"hookRegistrations\"] = [];\n\tconst mcpRegistrations: InstallResult[\"mcpRegistrations\"] = [];\n\n\t// Per-agent error isolation. A single agent's settings file being\n\t// permission-locked or otherwise unwritable used to abort the whole\n\t// install loop with a raw stack trace, leaving the user in a partial\n\t// state (skill copied, hook maybe registered, MCP maybe not) and\n\t// without a returned `InstallResult` to inspect. Catch at the per-step\n\t// boundary so the loop completes for every agent and the caller gets\n\t// a structured `failed` entry with the error message.\n\tfor (const agent of agents) {\n\t\ttry {\n\t\t\tconst write = await writeSkillToAgent(agent, skillSource);\n\t\t\tskillWrites.push(write);\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tskillWrites.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tpath: \"\",\n\t\t\t\tstatus: \"skipped\",\n\t\t\t});\n\t\t\tonNotice(`${agent.agent}: skill copy failed (${message})`);\n\t\t\t// If we couldn't even write the skill, the rest of the agent's\n\t\t\t// install is meaningless. Skip to the next agent.\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (agent.hookSupport === \"claude-code\") {\n\t\t\ttry {\n\t\t\t\tawait registerClaudeCodeHook(agent.settingsFile, { hookCommand });\n\t\t\t\thookRegistrations.push({\n\t\t\t\t\tagent: agent.agent,\n\t\t\t\t\tstatus: \"registered\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\thookRegistrations.push({\n\t\t\t\t\tagent: agent.agent,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\tmessage,\n\t\t\t\t});\n\t\t\t\tonNotice(`${agent.agent}: hook registration failed (${message})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconst noticeMessage = buildHookNoticeMessage(agent, hookCommand);\n\t\t\thookRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"notice\",\n\t\t\t\tmessage: noticeMessage,\n\t\t\t});\n\t\t\tonNotice(noticeMessage);\n\t\t}\n\n\t\tif (agent.mcpSupport === \"notice\") {\n\t\t\tconst noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"notice\",\n\t\t\t\tmessage: noticeMessage,\n\t\t\t});\n\t\t\tonNotice(noticeMessage);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst mcpResult = await registerMcpServer(agent, {\n\t\t\t\thomeOverride: options.homeOverride,\n\t\t\t\tcommand: mcpCommand,\n\t\t\t});\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"registered\",\n\t\t\t\tpath: mcpResult.path,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage,\n\t\t\t});\n\t\t\tonNotice(`${agent.agent}: MCP registration failed (${message})`);\n\t\t}\n\t}\n\n\treturn { skillWrites, hookRegistrations, mcpRegistrations };\n}\n","// MCP server registration: writes a `keeperhub-wallet` entry into each\n// detected agent's MCP config file. Mirrors the patterns in skill-install.ts\n// (registerClaudeCodeHook), with two important divergences:\n//\n// 1. Idempotency is automatic. MCP entries are NAME-keyed objects\n// (`{ mcpServers: { \"keeperhub-wallet\": {...} } }`), not positional\n// arrays like PreToolUse. Re-running overwrites the existing entry; we\n// never need a de-dup matcher.\n//\n// 2. The opencode shape is divergent: instead of\n// `{command, args, env}` it stores\n// `{type:\"local\", command:[command,...args], enabled:true, environment}`\n// under a top-level `mcp` key. We branch on `target.mcpSupport` and\n// write the appropriate shape.\n//\n// Preservation rule: every other top-level key in the config file MUST be\n// byte-preserved. We read the file, parse, mutate only our slot, and write\n// back. Same chmod 0o600 semantics as registerClaudeCodeHook.\n\nimport { randomBytes } from \"node:crypto\";\nimport {\n\tchmod,\n\tmkdir,\n\treadFile,\n\trename,\n\tunlink,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { AgentTarget } from \"./agent-detect.js\";\nimport { resolveBinCommand } from \"./runtime-detect.js\";\n\nconst MCP_BIN = \"keeperhub-wallet-mcp\";\n\n/** The literal name our MCP server registers under. Stable across all agents. */\nexport const MCP_SERVER_NAME = \"keeperhub-wallet\";\n\nexport type McpCommand = {\n\tcommand: string;\n\targs: string[];\n\t/** Optional environment overrides. Currently unused but reserved for future flags. */\n\tenv?: Record<string, string>;\n};\n\n/**\n * Resolve the MCP launch command. Returns the bare bin if it lives at a\n * stable PATH location, otherwise a version-pinned npx fallback. Identical\n * decision logic to {@link resolveHookCommand} — both flows go through\n * runtime-detect.ts.\n *\n * Override-able via `KEEPERHUB_WALLET_MCP_COMMAND` (env-var input is trusted\n * — it is written verbatim into the MCP config and executed by the agent).\n */\nexport function resolveMcpCommand(): McpCommand {\n\tconst envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;\n\tif (envOverride && envOverride.length > 0) {\n\t\t// Env override is a single string; split on whitespace into command + args.\n\t\tconst parts = envOverride.trim().split(/\\s+/);\n\t\tconst head = parts[0] ?? envOverride;\n\t\treturn { command: head, args: parts.slice(1) };\n\t}\n\tconst resolved = resolveBinCommand(MCP_BIN);\n\treturn { command: resolved.command, args: resolved.args };\n}\n\nexport type RegisterMcpServerOptions = {\n\t/** Override $HOME (tests). */\n\thomeOverride?: string;\n\t/** Override the launch command (tests, monorepo setups). */\n\tcommand?: McpCommand;\n};\n\nexport type RegisterMcpServerResult = {\n\t/** Absolute path of the MCP config file that was written. */\n\tpath: string;\n\t/** Server name written into the config. */\n\tname: typeof MCP_SERVER_NAME;\n};\n\ntype StandardMcpEntry = {\n\tcommand: string;\n\targs: string[];\n\tenv?: Record<string, string>;\n};\n\ntype StandardMcpConfig = {\n\tmcpServers?: Record<string, unknown>;\n\t[k: string]: unknown;\n};\n\ntype OpencodeMcpEntry = {\n\ttype: \"local\";\n\tcommand: string[];\n\tenabled: true;\n\tenvironment: Record<string, string>;\n};\n\ntype OpencodeMcpConfig = {\n\tmcp?: Record<string, unknown>;\n\t[k: string]: unknown;\n};\n\nfunction buildStandardEntry(cmd: McpCommand): StandardMcpEntry {\n\tconst entry: StandardMcpEntry = {\n\t\tcommand: cmd.command,\n\t\targs: cmd.args,\n\t};\n\tif (cmd.env && Object.keys(cmd.env).length > 0) {\n\t\tentry.env = cmd.env;\n\t}\n\treturn entry;\n}\n\nfunction buildOpencodeEntry(cmd: McpCommand): OpencodeMcpEntry {\n\treturn {\n\t\ttype: \"local\",\n\t\tcommand: [cmd.command, ...cmd.args],\n\t\tenabled: true,\n\t\tenvironment: cmd.env ?? {},\n\t};\n}\n\nasync function readJsonOrEmpty<T>(path: string): Promise<T> {\n\tlet raw: string | null = null;\n\ttry {\n\t\traw = await readFile(path, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n\t\t\tthrow err;\n\t\t}\n\t}\n\tif (raw === null) {\n\t\treturn {} as T;\n\t}\n\ttry {\n\t\treturn JSON.parse(raw) as T;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`MCP config at ${path} is not valid JSON; aborting MCP registration`,\n\t\t);\n\t}\n}\n\n/**\n * Truly-atomic write to a config file. Writes to a randomly-suffixed sibling\n * tmp file then `rename(2)`s into place — the rename is the atomic step on\n * every POSIX filesystem and on Windows NTFS, so a crash, signal, ENOSPC,\n * or EIO mid-write CANNOT leave the live config truncated. Critical for\n * `~/.claude.json` (100KB+, holds the user's MCP allowlist + project state)\n * and for any settings.json where a torn write would brick Claude Code.\n *\n * Best-effort cleanup of the tmp file on rename failure so we don't\n * accumulate `*.tmp` siblings. The chmod happens on the tmp before rename\n * so the live file's permissions are correct from the first byte readable.\n */\nasync function writeJsonAtomic(path: string, payload: string): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true, mode: 0o700 });\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${path}.${process.pid}.${suffix}.tmp`;\n\ttry {\n\t\tawait writeFile(tmpPath, payload, { mode: 0o600 });\n\t\tawait chmod(tmpPath, 0o600);\n\t\tawait rename(tmpPath, path);\n\t} catch (err) {\n\t\tawait unlink(tmpPath).catch(() => {\n\t\t\t// best-effort cleanup; ignore ENOENT and other failures\n\t\t});\n\t\tthrow err;\n\t}\n}\n\nasync function writeStandardMcp(\n\tpath: string,\n\tentry: StandardMcpEntry,\n): Promise<void> {\n\tconst config = await readJsonOrEmpty<StandardMcpConfig>(path);\n\tconst servers: Record<string, unknown> =\n\t\ttypeof config.mcpServers === \"object\" && config.mcpServers !== null\n\t\t\t? (config.mcpServers as Record<string, unknown>)\n\t\t\t: {};\n\tservers[MCP_SERVER_NAME] = entry;\n\tconfig.mcpServers = servers;\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\tawait writeJsonAtomic(path, payload);\n}\n\nasync function writeOpencodeMcp(\n\tpath: string,\n\tentry: OpencodeMcpEntry,\n): Promise<void> {\n\tconst config = await readJsonOrEmpty<OpencodeMcpConfig>(path);\n\tconst servers: Record<string, unknown> =\n\t\ttypeof config.mcp === \"object\" && config.mcp !== null\n\t\t\t? (config.mcp as Record<string, unknown>)\n\t\t\t: {};\n\tservers[MCP_SERVER_NAME] = entry;\n\tconfig.mcp = servers;\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\tawait writeJsonAtomic(path, payload);\n}\n\n/**\n * Register the keeperhub-wallet MCP server with one detected agent.\n *\n * Behavior by `target.mcpSupport`:\n * - `claude-code` / `cursor` / `windsurf` — write the standard\n * `{ mcpServers: { \"keeperhub-wallet\": {command, args, env?} } }` shape\n * into the agent's MCP config file. All other top-level keys preserved.\n * - `opencode` — write the divergent\n * `{ mcp: { \"keeperhub-wallet\": {type:\"local\", command:[...], enabled,\n * environment} } }` shape.\n * - `notice` — throws. installSkill handles the notice path before calling\n * this function.\n *\n * Idempotent by construction: the `keeperhub-wallet` slot is name-keyed, so\n * re-running overwrites rather than duplicates.\n */\nexport async function registerMcpServer(\n\ttarget: AgentTarget,\n\toptions: RegisterMcpServerOptions = {},\n): Promise<RegisterMcpServerResult> {\n\tif (target.mcpSupport === \"notice\") {\n\t\tthrow new Error(\n\t\t\t`agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`,\n\t\t);\n\t}\n\tif (!target.mcpConfigRel) {\n\t\tthrow new Error(\n\t\t\t`agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`,\n\t\t);\n\t}\n\tconst home = options.homeOverride ?? homedir();\n\tconst path = join(home, ...target.mcpConfigRel);\n\tconst cmd = options.command ?? resolveMcpCommand();\n\n\tif (target.mcpSupport === \"opencode\") {\n\t\tawait writeOpencodeMcp(path, buildOpencodeEntry(cmd));\n\t} else {\n\t\tawait writeStandardMcp(path, buildStandardEntry(cmd));\n\t}\n\n\treturn { path, name: MCP_SERVER_NAME };\n}\n","// Shared runtime / environment detection used by skill-install (hook\n// command resolution) and mcp-register (MCP command resolution).\n//\n// Why split out: both flows need to decide whether the package's bin lives\n// at a stable PATH location (global install, brew, distro pkg, npm link) or\n// inside a transient package-runner cache (npx _npx, pnpm dlx, yarn dlx,\n// bun x). The bare-bin-vs-npx decision is identical between hook and MCP\n// registrations — keep one source of truth so hook and MCP cannot drift.\n\nimport { execFileSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const PACKAGE_NAME = \"@keeperhub/wallet\";\n\n/**\n * Read this package's own version from package.json. Both the hook and\n * MCP installers pin npx invocations to the installer's version so that\n * upgrades are explicit (re-run skill install) and the supply-chain blast\n * radius is bounded to \"code that was already trusted enough to install\".\n *\n * Falls back to \"latest\" only when package.json cannot be located, which\n * should never happen in published builds (dist/ sits next to package.json\n * via pkg.files).\n */\nexport function readPackageVersion(): string {\n\ttry {\n\t\tconst here = dirname(fileURLToPath(import.meta.url));\n\t\tconst pkgPath = join(here, \"..\", \"package.json\");\n\t\tconst raw = readFileSync(pkgPath, \"utf-8\");\n\t\tconst parsed = JSON.parse(raw) as { version?: string };\n\t\tif (typeof parsed.version === \"string\" && parsed.version.length > 0) {\n\t\t\treturn parsed.version;\n\t\t}\n\t} catch {\n\t\t// Fall through.\n\t}\n\treturn \"latest\";\n}\n\n/**\n * Detect whether the current process is being driven by `npx`. npm/npx set\n * `npm_execpath` to the path of the CLI binary that spawned the process; for\n * `npx` invocations that path ends in `npx-cli.js` (or, on Windows, an `npx`\n * shim in node_modules/.bin).\n *\n * Why this matters: when the user runs `npx @keeperhub/wallet skill install`,\n * npx prepends its transient cache dir to PATH for the lifetime of the\n * installer. `command -v <bin>` therefore succeeds inside the installer, but\n * the cache dir disappears from PATH the moment npx exits — and any later\n * spawn (hook fire / MCP launch) would crash with `command not found`.\n */\nexport function isNpxExecution(): boolean {\n\tconst execPath = process.env.npm_execpath;\n\tif (typeof execPath !== \"string\" || execPath.length === 0) {\n\t\treturn false;\n\t}\n\tif (/(?:^|[\\\\/])npx-cli\\.(?:js|cjs|mjs)$/i.test(execPath)) {\n\t\treturn true;\n\t}\n\tif (/(?:^|[\\\\/])npx(?:\\.cmd|\\.exe|\\.ps1)?$/i.test(execPath)) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n// Recognise a path that lives inside any transient package-runner cache.\n// Patterns:\n// _npx — npm/npx cache\n// dlx-<hash> — pnpm dlx staging\n// xfs-<hash> — yarn dlx (Berry) temp project\n// .bun/install/cache — bun x / bunx package cache\nconst TRANSIENT_CACHE_PATTERNS: ReadonlyArray<RegExp> = [\n\t/[\\\\/]_npx[\\\\/]/,\n\t/[\\\\/]dlx-[A-Za-z0-9]+[\\\\/]/,\n\t/[\\\\/]xfs-[A-Za-z0-9]+[\\\\/]/,\n\t/[\\\\/]\\.bun[\\\\/]install[\\\\/]cache[\\\\/]/,\n];\n\nexport function isPathUnderTransientCache(resolvedPath: string): boolean {\n\tfor (const re of TRANSIENT_CACHE_PATTERNS) {\n\t\tif (re.test(resolvedPath)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport type ResolvedCommand = {\n\t/** The literal string that will be written into settings.json's `command` field. */\n\tcommandString: string;\n\t/** Split form (command + args[]) for MCP entry shapes that require it. */\n\tcommand: string;\n\targs: string[];\n};\n\n/**\n * Resolve a bin name to a stable command form. Returns the bare bin if it\n * lives at a durable PATH location, otherwise a version-pinned npx fallback.\n *\n * The single decision logic is shared between the PreToolUse hook installer\n * and the MCP registration installer.\n */\nexport function resolveBinCommand(binName: string): ResolvedCommand {\n\tconst version = readPackageVersion();\n\tconst npxArgs = [\"-y\", \"-p\", `${PACKAGE_NAME}@${version}`, binName];\n\tconst npxCommandString = `npx ${npxArgs.join(\" \")}`;\n\n\tif (isNpxExecution()) {\n\t\treturn {\n\t\t\tcommandString: npxCommandString,\n\t\t\tcommand: \"npx\",\n\t\t\targs: npxArgs,\n\t\t};\n\t}\n\n\ttry {\n\t\tconst resolved = execFileSync(\"/bin/sh\", [\"-c\", `command -v ${binName}`], {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t})\n\t\t\t.toString()\n\t\t\t.trim();\n\t\tif (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {\n\t\t\treturn {\n\t\t\t\tcommandString: binName,\n\t\t\t\tcommand: binName,\n\t\t\t\targs: [],\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// command -v failed: bin not on PATH at all. Fall through.\n\t}\n\n\treturn {\n\t\tcommandString: npxCommandString,\n\t\tcommand: \"npx\",\n\t\targs: npxArgs,\n\t};\n}\n","import { createHash, createHmac } from \"node:crypto\";\nimport type { HmacHeaders } from \"./types.js\";\n\n/**\n * Mirror of lib/agentic-wallet/hmac.ts::computeSignature.\n * Format (byte-for-byte identical to server):\n * `${method}\\n${path}\\n${subOrgId}\\n${sha256_hex(body)}\\n${timestamp}`\n * Post-HI-05: subOrgId is a signed field.\n *\n * @security Do NOT log the secret or the returned signature. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced).\n */\nexport function computeSignature(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string,\n timestamp: string\n): string {\n const bodyDigest = createHash(\"sha256\").update(body).digest(\"hex\");\n const signingString = `${method}\\n${path}\\n${subOrgId}\\n${bodyDigest}\\n${timestamp}`;\n return createHmac(\"sha256\", secret).update(signingString).digest(\"hex\");\n}\n\n/**\n * Build the three X-KH-* headers that authenticate every request to\n * /api/agentic-wallet/* (except /provision, which uses the session cookie).\n *\n * Timestamp is unix seconds (Math.floor(Date.now() / 1000)); the server\n * enforces a symmetric 300-second replay window.\n */\nexport function buildHmacHeaders(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string\n): HmacHeaders {\n const timestamp = String(Math.floor(Date.now() / 1000));\n const signature = computeSignature(\n secret,\n method,\n path,\n subOrgId,\n body,\n timestamp\n );\n return {\n \"X-KH-Sub-Org\": subOrgId,\n \"X-KH-Timestamp\": timestamp,\n \"X-KH-Signature\": signature,\n };\n}\n","import { buildHmacHeaders } from \"./hmac.js\";\nimport { KeeperHubError, type WalletConfig } from \"./types.js\";\n\nexport type ClientOptions = {\n /** Defaults to process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\" */\n baseUrl?: string;\n /** Injected for tests; defaults to global fetch */\n fetch?: typeof fetch;\n};\n\n/**\n * 202 ask-tier envelope returned by /sign and /approval-request when the\n * risk classifier routes a request to the ask queue. Callers poll\n * `/api/agentic-wallet/approval-request/:id` until status !== \"pending\".\n */\nexport type AskTierResponse = {\n _status: 202;\n approvalRequestId: string;\n};\n\nconst TRAILING_SLASH = /\\/$/;\n\nfunction defaultCodeForStatus(status: number): string {\n if (status === 401) {\n return \"HMAC_INVALID\";\n }\n if (status === 403) {\n return \"POLICY_BLOCKED\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 502) {\n return \"TURNKEY_UPSTREAM\";\n }\n return `HTTP_${status}`;\n}\n\n/**\n * HMAC-signed HTTP client for the KeeperHub agentic-wallet API surface.\n * Every request to /api/agentic-wallet/* (except /provision, which uses\n * the session cookie) flows through this class.\n *\n * @security No logging of headers, body, or response bodies. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced in CI).\n */\nexport class KeeperHubClient {\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly wallet: WalletConfig;\n\n constructor(wallet: WalletConfig, opts: ClientOptions = {}) {\n this.wallet = wallet;\n const envBase = process.env.KEEPERHUB_API_URL;\n this.baseUrl = (\n opts.baseUrl ??\n envBase ??\n \"https://app.keeperhub.com\"\n ).replace(TRAILING_SLASH, \"\");\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n }\n\n /**\n * HMAC-signed POST/GET to any /api/agentic-wallet/* route except\n * /provision. Path MUST start with a leading slash. Body is\n * JSON.stringify'd (or the empty string for GET).\n *\n * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,\n * message)` where `code` is the server-supplied field or the default\n * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,\n * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an\n * AskTierResponse envelope.\n */\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown\n ): Promise<T | AskTierResponse> {\n const bodyStr = body === undefined ? \"\" : JSON.stringify(body);\n const hmacHeaders = buildHmacHeaders(\n this.wallet.hmacSecret,\n method,\n path,\n this.wallet.subOrgId,\n bodyStr\n );\n const headers: Record<string, string> =\n method === \"POST\"\n ? { ...hmacHeaders, \"content-type\": \"application/json\" }\n : { ...hmacHeaders };\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: method === \"POST\" ? bodyStr : undefined,\n });\n\n if (response.status === 202) {\n const data = (await response.json()) as {\n approvalRequestId: string;\n status: string;\n };\n return { _status: 202, approvalRequestId: data.approvalRequestId };\n }\n\n if (!response.ok) {\n let code = \"UNKNOWN\";\n let message = `HTTP ${response.status}`;\n try {\n const data = (await response.json()) as {\n code?: string;\n error?: string;\n };\n code = data.code ?? defaultCodeForStatus(response.status);\n message = data.error ?? message;\n } catch {\n // body is not JSON -- keep the default code + message\n }\n throw new KeeperHubError(code, message);\n }\n\n return (await response.json()) as T;\n }\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * User-owned safety config at ~/.keeperhub/safety.json. File mode 0o644 so the\n * user can freely edit thresholds and the allowlist; server-side Turnkey policy\n * remains the authoritative hard cap (GUARD-06).\n */\nexport type SafetyConfig = {\n auto_approve_max_usd: number;\n ask_threshold_usd: number;\n block_threshold_usd: number;\n allowlisted_contracts: string[];\n};\n\n/**\n * Defaults per 34-CONTEXT lines 61-68. Thresholds bracket the Turnkey policy\n * hard cap (100 USDC). Allowlisted contracts mirror the server Turnkey policy\n * allowlist (lib/agentic-wallet/policy.ts FACILITATOR_ALLOWLIST) -- lowercased\n * for case-insensitive match against tool_input.to / paymentChallenge.payTo.\n */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n auto_approve_max_usd: 5,\n ask_threshold_usd: 50,\n block_threshold_usd: 100,\n allowlisted_contracts: [\n \"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\", // Base USDC\n \"0x20c000000000000000000000b9537d11c60e8b50\", // Tempo USDC.e\n ],\n};\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"safety.json\")`\n// itself -- matches storage.ts. Hoisting to a module-level constant would\n// freeze $HOME at import time and break tests that override process.env.HOME\n// in beforeEach.\n\nfunction getSafetyPath(): string {\n return join(homedir(), \".keeperhub\", \"safety.json\");\n}\n\nexport async function loadSafetyConfig(): Promise<SafetyConfig> {\n const path = getSafetyPath();\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeFile(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {\n mode: 0o644,\n });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(path, 0o644);\n return DEFAULT_SAFETY_CONFIG;\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<SafetyConfig>;\n return validateAndMerge(parsed);\n}\n\nconst THRESHOLD_KEYS = [\n \"auto_approve_max_usd\",\n \"ask_threshold_usd\",\n \"block_threshold_usd\",\n] as const;\n\nexport function validateAndMerge(partial: Partial<SafetyConfig>): SafetyConfig {\n const merged: SafetyConfig = {\n auto_approve_max_usd:\n partial.auto_approve_max_usd ??\n DEFAULT_SAFETY_CONFIG.auto_approve_max_usd,\n ask_threshold_usd:\n partial.ask_threshold_usd ?? DEFAULT_SAFETY_CONFIG.ask_threshold_usd,\n block_threshold_usd:\n partial.block_threshold_usd ?? DEFAULT_SAFETY_CONFIG.block_threshold_usd,\n allowlisted_contracts:\n partial.allowlisted_contracts ??\n DEFAULT_SAFETY_CONFIG.allowlisted_contracts,\n };\n\n for (const key of THRESHOLD_KEYS) {\n const v = merged[key];\n if (!(Number.isFinite(v) && v >= 0)) {\n throw new Error(\n `safety.json: ${key} must be a non-negative finite number; got ${String(v)}`\n );\n }\n }\n if (merged.ask_threshold_usd < merged.auto_approve_max_usd) {\n throw new Error(\n \"safety.json: ask_threshold_usd must be >= auto_approve_max_usd\"\n );\n }\n if (merged.block_threshold_usd < merged.ask_threshold_usd) {\n throw new Error(\n \"safety.json: block_threshold_usd must be >= ask_threshold_usd\"\n );\n }\n if (!Array.isArray(merged.allowlisted_contracts)) {\n throw new Error(\"safety.json: allowlisted_contracts must be an array\");\n }\n merged.allowlisted_contracts = merged.allowlisted_contracts.map((a) =>\n a.toLowerCase()\n );\n return merged;\n}\n\nexport function getSafetyConfigPath(): string {\n return getSafetyPath();\n}\n","import { loadSafetyConfig, type SafetyConfig } from \"./safety-config.js\";\nimport type { HookDecision } from \"./types.js\";\n\ntype HookInput = {\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n};\n\nexport type CreateHookOptions = {\n /** Match against tool_name. Default: /keeperhub|wallet|sign/i */\n toolNameMatcher?: (name: string) => boolean;\n /** Injected for tests */\n configLoader?: () => Promise<SafetyConfig>;\n};\n\nconst USDC_DECIMALS = 1_000_000;\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst MICRO_USDC_RE = /^\\d+$/;\nconst DEFAULT_TOOL_RE = /keeperhub|wallet|sign/i;\n\nfunction defaultToolMatcher(name: string): boolean {\n return DEFAULT_TOOL_RE.test(name);\n}\n\n/**\n * Detect whether a tool input carries any payment shape — the only kind\n * of call this hook is meant to gate. Free admin reads in the keeperhub\n * MCP namespace (e.g. mcp__plugin_keeperhub_keeperhub__get_execution_logs,\n * unlist_workflow, list_workflows) match the broad name regex but have\n * no amount, no contract, no challenge — there is nothing for the safety\n * gate to evaluate, so the hook should pass them through rather than\n * deny with AMOUNT_UNDETERMINED.\n *\n * A call is \"payment-shaped\" when at least one of these is present:\n * - tool_input.paymentChallenge (object)\n * - tool_input.amount (any non-null value)\n * - tool_input.unit (any non-null value)\n * - tool_input.to / contract / assetAddress (only when the value\n * looks like an EVM address; non-address strings such as Discord\n * channel ids are not payment indicators)\n *\n * This complements the existing tool-name regex: the regex still gates\n * what the hook will *consider*, but presence of any payment field is\n * required before the safety thresholds actually run. Callers that pass\n * the regex but provide no payment context (the bug surfaced live with\n * mcp__plugin_keeperhub_keeperhub__unlist_workflow during marketplace\n * testing on 2026-05-01) are now allowed through.\n */\nfunction hasPaymentShape(input: HookInput): boolean {\n const ti = input.tool_input ?? {};\n const challenge = ti.paymentChallenge;\n if (challenge !== undefined && challenge !== null) {\n return true;\n }\n if (ti.amount !== undefined && ti.amount !== null) {\n return true;\n }\n if (ti.unit !== undefined && ti.unit !== null) {\n return true;\n }\n for (const field of [\"to\", \"contract\", \"assetAddress\"] as const) {\n const v = ti[field];\n if (typeof v === \"string\" && ADDRESS_RE.test(v)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Coerce an amount field to micro-USDC. Inputs MUST be explicitly tagged with\n * `unit`:\n * - `{amount: string, unit: \"microUsdc\"}` -> parsed as integer micro-USDC\n * (x402 wire format)\n * - `{amount: number, unit: \"usd\"}` -> multiplied by 1_000_000\n *\n * Untagged amounts are REJECTED with a thrown TypeError. This is GUARD-05:\n * we refuse to guess whether a \"5\" is 5 USD or 5 micro-USDC (a six-order-of-\n * magnitude reading error). The caller must commit.\n *\n * Fields read: ONLY tool_input.paymentChallenge.{amount,unit} and\n * tool_input.{amount,unit}. Forged safety-bypass fields (any \"trust-level\"\n * hint, \"is-safe\" boolean, \"admin-override\" bit, or similar) are NEVER read;\n * thresholds come exclusively from ~/.keeperhub/safety.json.\n */\nfunction extractAmountMicroUsdc(input: HookInput): bigint | null {\n const ti = input.tool_input ?? {};\n const challenge = (ti.paymentChallenge ?? {}) as Record<string, unknown>;\n // WR-01: prefer the signed wire field (paymentChallenge.amount/unit) over\n // caller-supplied sibling tool_input fields. The nested challenge is the\n // field the downstream /sign call actually binds into the signed bytes, so\n // a misbehaving tool cannot slip a larger nested amount past the auto cap\n // by shadowing it with a small top-level sibling. Fall back to top-level\n // only when no challenge is present (e.g. direct /sign tool calls with no\n // 402 round).\n const directAmount = challenge.amount ?? ti.amount;\n const directUnit = challenge.unit ?? ti.unit;\n\n if (directAmount === undefined || directAmount === null) {\n return null;\n }\n if (directUnit !== \"usd\" && directUnit !== \"microUsdc\") {\n throw new TypeError(\n `Amount input must be tagged with unit:\"usd\" or unit:\"microUsdc\"; got unit=${JSON.stringify(directUnit)}. GUARD-05 refuses to guess - specify explicitly.`\n );\n }\n if (directUnit === \"microUsdc\") {\n if (\n !(typeof directAmount === \"string\" && MICRO_USDC_RE.test(directAmount))\n ) {\n throw new TypeError(\n `unit:\"microUsdc\" requires amount as a non-negative integer string; got ${typeof directAmount}`\n );\n }\n return BigInt(directAmount);\n }\n // unit === \"usd\"\n if (\n !(\n typeof directAmount === \"number\" &&\n Number.isFinite(directAmount) &&\n directAmount >= 0\n )\n ) {\n throw new TypeError(\n `unit:\"usd\" requires amount as a finite non-negative number; got ${typeof directAmount}`\n );\n }\n return BigInt(Math.round(directAmount * USDC_DECIMALS));\n}\n\nfunction extractContractAddress(input: HookInput): string | null {\n const ti = input.tool_input ?? {};\n const challenge = (ti.paymentChallenge ?? {}) as Record<string, unknown>;\n // Precedence order:\n // 1. challenge.asset -- x402 TransferWithAuthorization: the ERC-20 contract\n // the authorization is bound to (the EVM `eth.tx.to` at execution time).\n // This mirrors the server-side Turnkey policy (policy.ts) which denies\n // `eth.tx.to not in [USDC_BASE, USDC_TEMPO]`.\n // 2. ti.contract / ti.assetAddress -- agent-runtime-supplied hints.\n // 3. ti.to / challenge.to -- legacy tool_inputs that labeled the asset as\n // \"to\" (some older MCP implementations). Kept for backwards compat.\n // NEVER reads challenge.payTo: that is the transfer recipient (the\n // facilitator or service operator), not the ERC-20 contract being invoked.\n const contract =\n challenge.asset ??\n ti.contract ??\n ti.assetAddress ??\n ti.to ??\n challenge.to;\n if (typeof contract === \"string\" && ADDRESS_RE.test(contract)) {\n return contract.toLowerCase();\n }\n return null;\n}\n\nfunction usdToMicro(usd: number): bigint {\n return BigInt(Math.round(usd * USDC_DECIMALS));\n}\n\n/**\n * Factory returning the PreToolUse hook function. The hook enforces three\n * client-side safety tiers (auto / ask / block) sourced EXCLUSIVELY from\n * ~/.keeperhub/safety.json -- never from the tool payload (GUARD-05).\n *\n * v0.1.4 collapsed the previous four-band behaviour into three:\n *\n * amount <= auto_approve_max_usd -> {decision: \"allow\"}\n * auto_approve_max_usd < amount <= block_threshold -> {decision: \"ask\"} (Claude Code prompts user inline)\n * amount > block_threshold -> {decision: \"deny\"}\n *\n * The previous server-approval branch (amount >= ask_threshold -> create a\n * /api/agentic-wallet/approval-request row, print an approval URL, poll for\n * browser approval) was removed. It required the wallet to be linked to a\n * KeeperHub user via /link, and the link command was rough enough that we\n * never wired it into the documented flow. Returning {decision: \"ask\"}\n * inline lets Claude Code surface the prompt in the agent chat directly.\n *\n * `ask_threshold_usd` is still read from safety.json for backward-compat\n * with existing configs but is not consulted for decision-making. Tracked\n * as KEEP-307 for the permanent architectural decision.\n */\nexport async function createPreToolUseHook(\n options: CreateHookOptions = {}\n): Promise<(input: unknown) => Promise<HookDecision>> {\n const toolMatcher = options.toolNameMatcher ?? defaultToolMatcher;\n const configLoader = options.configLoader ?? loadSafetyConfig;\n\n const safety = await configLoader();\n\n // The hook function is declared async so that synchronous throws in\n // extractAmountMicroUsdc (GUARD-05 unit-tag enforcement) become rejected\n // promises, matching the original pre-0.1.4 behaviour the tests rely on.\n return async (raw: unknown): Promise<HookDecision> => {\n const hookInput = (raw ?? {}) as HookInput;\n\n // Pass-through for non-wallet tool calls.\n if (\n !(\n typeof hookInput.tool_name === \"string\" &&\n toolMatcher(hookInput.tool_name)\n )\n ) {\n return { decision: \"allow\" };\n }\n\n // Pass-through for tool calls that match the wallet name regex but\n // carry no payment context whatsoever. The default matcher\n // /keeperhub|wallet|sign/i catches every keeperhub MCP tool by name\n // (call_workflow, list_workflows, unlist_workflow,\n // get_execution_logs, ...), but the bulk of those are free reads\n // and admin operations with no amount, no contract, no challenge.\n // Without this gate they would all fall through to the\n // AMOUNT_UNDETERMINED branch below and DENY, breaking ordinary\n // marketplace use of any agent that has the safety hook installed.\n if (!hasPaymentShape(hookInput)) {\n return { decision: \"allow\" };\n }\n\n // GUARD-05: ONLY these fields. No trust/override/admin_* reads.\n const contractAddr = extractContractAddress(hookInput);\n const amountMicro = extractAmountMicroUsdc(hookInput);\n\n if (contractAddr && !safety.allowlisted_contracts.includes(contractAddr)) {\n return { decision: \"deny\", reason: \"CONTRACT_NOT_ALLOWLISTED\" };\n }\n\n if (amountMicro === null) {\n return { decision: \"deny\", reason: \"AMOUNT_UNDETERMINED\" };\n }\n\n const blockMicro = usdToMicro(safety.block_threshold_usd);\n const autoMicro = usdToMicro(safety.auto_approve_max_usd);\n\n if (amountMicro > blockMicro) {\n return { decision: \"deny\", reason: \"BLOCKED_BY_SAFETY_RULE\" };\n }\n\n if (amountMicro <= autoMicro) {\n return { decision: \"allow\" };\n }\n\n // Everything between auto and block is an inline ask -- Claude Code\n // surfaces the prompt in-chat.\n return { decision: \"ask\" };\n };\n}\n","import { createPreToolUseHook } from \"./hook.js\";\n\n/**\n * Binary entrypoint for `npx @keeperhub/wallet hook` or direct invocation via\n * Claude Code settings.json:\n *\n * { \"type\": \"command\", \"command\": \"npx @keeperhub/wallet hook\", \"timeout\": 30 }\n *\n * Reads JSON from stdin (Claude Code PreToolUse payload), writes the JSON\n * decision envelope to stdout, and exits 2 on deny (the universal \"block\"\n * signal across agent-hook runtimes per the Claude Code docs). A non-JSON\n * stdin is treated as a deny.\n *\n * @security Stdout is RESERVED for the envelope JSON; any diagnostic output\n * (approval URL, errors) goes to stderr via onAskOpen or direct writes.\n */\nexport async function runHookCli(): Promise<void> {\n const hook = await createPreToolUseHook();\n\n let raw = \"\";\n for await (const chunk of process.stdin as unknown as AsyncIterable<Buffer>) {\n raw += chunk.toString(\"utf-8\");\n }\n\n let parsed: unknown;\n try {\n parsed = raw.trim().length > 0 ? JSON.parse(raw) : {};\n } catch (err) {\n process.stderr.write(\n `[keeperhub-wallet] hook input is not valid JSON: ${(err as Error).message}\\n`\n );\n process.exit(2);\n }\n\n const decision = await hook(parsed);\n\n const output = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\" as const,\n permissionDecision: decision.decision,\n ...(decision.reason ? { permissionDecisionReason: decision.reason } : {}),\n },\n };\n process.stdout.write(JSON.stringify(output));\n process.exit(decision.decision === \"deny\" ? 2 : 0);\n}\n","// Source: lib/payments/router.ts:152-175 -- MPP WWW-Authenticate emission.\n// We forward the raw serialized challenge to /api/agentic-wallet/sign; the server\n// has mppx in its deps. Keeps client runtime dep list minimal (supply-chain T-34-02).\n\nexport type MppChallenge = { serialized: string };\n\nconst MPP_PREFIX = \"Payment \";\n\nexport function parseMppChallenge(response: Response): MppChallenge | null {\n const header = response.headers.get(\"WWW-Authenticate\");\n if (!header) {\n return null;\n }\n if (!header.startsWith(MPP_PREFIX)) {\n return null;\n }\n const serialized = header.slice(MPP_PREFIX.length).trim();\n if (serialized.length === 0) {\n return null;\n }\n return { serialized };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { KeeperHubClient } from \"./client.js\";\nimport { type MppChallenge, parseMppChallenge } from \"./mpp-detect.js\";\nimport { readWalletConfig } from \"./storage.js\";\nimport { KeeperHubError, type PaymentHint, type WalletConfig } from \"./types.js\";\nimport { extractKeeperHubWorkflowSlug } from \"./workflow-slug.js\";\nimport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n\n// Tempo mainnet chain id. Forwarded to /sign so the server routes MPP\n// challenges to the correct signer. Kept in sync with\n// app/api/agentic-wallet/sign/route.ts::TEMPO_CHAIN_ID.\nconst TEMPO_CHAIN_ID = 4217;\n\n// Approval polling: 2s * 150 = 5 minute ceiling on a human response.\n// T-34-ps-04 mitigation (DoS via infinite loop).\nconst DEFAULT_APPROVAL_POLL = { intervalMs: 2000, maxAttempts: 150 };\n\n// Small clock-drift buffer on validAfter. Mirrors the server's\n// VALID_AFTER_FUTURE_SLACK_SECONDS in app/api/agentic-wallet/sign/route.ts.\nconst VALID_AFTER_PAST_SLACK_SECONDS = 60;\n\n// x402 protocol nonce: 32-byte hex (bytes32).\nconst NONCE_BYTES = 32;\n\n/**\n * Polymorphic /sign response. For `chain:\"base\"` the signature is a 132-char\n * 0x-prefixed EIP-712 hex string embedded inside the PAYMENT-SIGNATURE\n * base64-JSON payload. For `chain:\"tempo\"` it is a base64url-encoded MPP\n * credential produced by the server's mppx instance; the client forwards it\n * verbatim as the `Authorization: Payment <signature>` value. The client\n * never parses, decodes, or mutates the MPP credential -- opaque pass-through.\n */\ntype SignResponseOk = { signature: string };\n\ntype ApprovalStatus = \"pending\" | \"approved\" | \"rejected\";\n\ntype PaySignerOptions = {\n /** Override wallet loader (primarily for tests). */\n walletLoader?: () => Promise<WalletConfig>;\n /** Override KeeperHubClient factory (tests inject a mocked fetch). */\n clientFactory?: (wallet: WalletConfig) => KeeperHubClient;\n /** Replayed fetch (tests intercept the retry). */\n fetchImpl?: typeof fetch;\n /** Approval polling override: interval + max attempts. */\n approval?: { intervalMs: number; maxAttempts: number };\n};\n\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n /**\n * Per-call protocol preference. \"x402\" forces Base USDC; \"mpp\" forces Tempo\n * USDC.e; \"auto\" (default, also the behaviour when omitted) uses x402 when\n * offered, MPP otherwise. Throws KeeperHubError(\"X402_NOT_OFFERED\") or\n * KeeperHubError(\"MPP_NOT_OFFERED\") when the requested protocol is absent\n * from the challenge (KEEP-361).\n */\n paymentHint?: PaymentHint;\n};\n\n/** RequestInit extended with paymentHint for per-call protocol selection. */\nexport type FetchInit = RequestInit & { paymentHint?: PaymentHint };\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n *\n * Pass `init.paymentHint` to force a specific payment protocol for this\n * call. Omitting it is equivalent to `paymentHint: \"auto\"` (x402-first).\n */\n fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Pure function that decides which payment protocol to use given challenge\n * availability and caller's hint. Exported for unit testing.\n *\n * Returns \"x402\" or \"mpp\" to direct the caller to the appropriate path,\n * or null when hint is \"auto\" and no challenge is present (pay() then\n * returns the original 402 response unchanged). Throws KeeperHubError with\n * a specific code when the requested protocol is unavailable (KEEP-361).\n */\nexport function selectProtocol(\n x402: X402Challenge | null,\n mpp: MppChallenge | null,\n hint: PaymentHint | undefined\n): \"x402\" | \"mpp\" | null {\n const h = hint ?? \"auto\";\n if (h === \"x402\") {\n if (!x402) {\n throw new KeeperHubError(\n \"X402_NOT_OFFERED\",\n \"x402 is not offered by this endpoint\"\n );\n }\n return \"x402\";\n }\n if (h === \"mpp\") {\n if (!mpp) {\n throw new KeeperHubError(\n \"MPP_NOT_OFFERED\",\n \"mpp is not offered by this endpoint\"\n );\n }\n return \"mpp\";\n }\n // h === \"auto\": x402-first, then MPP, then null\n if (x402) return \"x402\";\n if (mpp) return \"mpp\";\n return null;\n}\n\nexport function createPaymentSigner(\n opts: PaySignerOptions = {}\n): PaymentSigner {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const walletLoader = opts.walletLoader ?? readWalletConfig;\n const clientFactory =\n opts.clientFactory ??\n ((wallet: WalletConfig): KeeperHubClient =>\n new KeeperHubClient(wallet, { fetch: fetchImpl }));\n const pollCfg = opts.approval ?? DEFAULT_APPROVAL_POLL;\n\n async function signOrPoll(\n client: KeeperHubClient,\n body: Record<string, unknown>\n ): Promise<string> {\n const result = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in result && result._status === 202) {\n const approvalRequestId = result.approvalRequestId;\n // Poll approval-request until status !== \"pending\" or timeout.\n for (let attempt = 0; attempt < pollCfg.maxAttempts; attempt++) {\n await sleep(pollCfg.intervalMs);\n const status = await client.request<{ status: ApprovalStatus }>(\n \"GET\",\n `/api/agentic-wallet/approval-request/${approvalRequestId}`\n );\n if (\"status\" in status && status.status !== \"pending\") {\n if (status.status === \"rejected\") {\n throw new KeeperHubError(\n \"APPROVAL_REJECTED\",\n \"User rejected the operation\"\n );\n }\n // approved -- retry the sign call (which should now return 200).\n const retry = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in retry) {\n throw new KeeperHubError(\n \"APPROVAL_LOOP\",\n \"Sign returned 202 again after approval\"\n );\n }\n return retry.signature;\n }\n }\n throw new KeeperHubError(\n \"APPROVAL_TIMEOUT\",\n `No human response within ${pollCfg.intervalMs * pollCfg.maxAttempts}ms`\n );\n }\n return (result as SignResponseOk).signature;\n }\n\n async function payViaMpp(\n response: Response,\n mpp: MppChallenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // x402 v2 PaymentPayload per @x402/core mechanisms-* d.ts:\n // { x402Version: 2, accepted: PaymentRequirements, payload: {...} }\n // The server's findMatchingRequirements does a deepEqual between\n // `paymentPayload.accepted` and each challenge `accepts[]` entry, so we\n // mirror the exact accept object we signed against.\n //\n // EIP-3009 inner payload: authorization.value/validAfter/validBefore/nonce\n // must be STRINGS at the wire format (per @x402/evm ExactEIP3009Payload).\n // /sign takes them as numbers; we stringify on the way out.\n const paymentSigPayload = {\n x402Version: 2,\n accepted: accept,\n payload: {\n signature,\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n },\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // Protocol selection is delegated to selectProtocol(). Default \"auto\"\n // preserves x402-first behavior. Per-call override via paymentHint\n // option (KEEP-361). Submit EXACTLY ONE credential (T-34-ps-02).\n const protocol = selectProtocol(x402, mpp, options?.paymentHint);\n if (protocol === \"x402\") {\n return payViaX402(response, x402 as X402Challenge, wallet, options);\n }\n if (protocol === \"mpp\") {\n return payViaMpp(response, mpp as MppChallenge, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: FetchInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method + paymentHint to the\n // post-sign retry so the paid workflow receives the same payload on the\n // retry as on the 402 attempt. Fixes the dropped-body bug that made any\n // workflow with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n paymentHint: init?.paymentHint,\n });\n },\n };\n}\n\n// Default instance backed by the real fetch + storage.\nexport const paymentSigner: PaymentSigner = createPaymentSigner();\n","// Server-derived payTo binding (Phase 37 fix #2 in keeperhub repo).\n//\n// The wallet only signs payments for KeeperHub-registered workflows. The\n// resource URL of the 402 challenge is matched against the canonical\n// /api/mcp/workflows/<slug>/call pattern; the slug is forwarded to /sign so\n// the server can verify payTo + amount against the workflows registry.\n//\n// URLs that don't match this pattern (e.g. arbitrary x402 services discovered\n// in the wild) are unsupported in v0.1.5 — the signer throws\n// UNSUPPORTED_RECIPIENT and refuses to round-trip. KEEP-311's generic 402\n// fetch CLI is a separate codepath with its own threat model.\n\nconst KEEPERHUB_WORKFLOW_RE =\n /\\/api\\/mcp\\/workflows\\/([a-zA-Z0-9_-]+)\\/call(?:\\/?)(?:\\?|$|#)/;\n\nexport type SlugExtractionResult =\n | { ok: true; slug: string }\n | { ok: false; reason: \"EMPTY_URL\" | \"URL_PATTERN_MISMATCH\" };\n\nexport function extractKeeperHubWorkflowSlug(\n url: string | null | undefined\n): SlugExtractionResult {\n if (!url || url.length === 0) {\n return { ok: false, reason: \"EMPTY_URL\" };\n }\n const match = KEEPERHUB_WORKFLOW_RE.exec(url);\n if (!match || !match[1]) {\n return { ok: false, reason: \"URL_PATTERN_MISMATCH\" };\n }\n return { ok: true, slug: match[1] };\n}\n","// Source: lib/payments/router.ts:48-62 (PaymentRequiredV2 server-side shape).\n// Strict parsing per 34-RESEARCH Pitfall 4 -- false-positive 402 detection is a\n// wasted /sign HMAC roundtrip and a potential agent-loop trigger.\n\nexport type X402Challenge = {\n x402Version: 2;\n accepts: Array<{\n scheme: \"exact\";\n network: string;\n asset: string;\n amount: string;\n payTo: string;\n maxTimeoutSeconds: number;\n extra: Record<string, unknown>;\n }>;\n resource: { url: string; description: string; mimeType: string };\n};\n\nfunction isX402Shape(value: unknown): value is X402Challenge {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (v.x402Version !== 2) {\n return false;\n }\n if (!Array.isArray(v.accepts) || v.accepts.length === 0) {\n return false;\n }\n const first = v.accepts[0] as Record<string, unknown>;\n if (first.scheme !== \"exact\") {\n return false;\n }\n return true;\n}\n\nexport async function parseX402Challenge(\n response: Response\n): Promise<X402Challenge | null> {\n // Header path (preferred -- matches lib/payments/router.ts's PAYMENT-REQUIRED emit).\n const headerB64 = response.headers.get(\"PAYMENT-REQUIRED\");\n if (headerB64) {\n try {\n const decoded: unknown = JSON.parse(\n Buffer.from(headerB64, \"base64\").toString(\"utf-8\")\n );\n if (isX402Shape(decoded)) {\n return decoded;\n }\n } catch {\n // fall through to body\n }\n }\n\n // Body path (lib/payments/router.ts also emits the PaymentRequired as the 402 body).\n try {\n const clone = response.clone();\n const body: unknown = await clone.json();\n if (isX402Shape(body)) {\n return body;\n }\n } catch {\n // not JSON\n }\n return null;\n}\n"],"mappings":"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","names":["homedir","dirname","join","join","homedir","dirname","randomBytes","chmod","mkdir","readFile","rename","unlink","writeFile","dirname","join","fileURLToPath","randomBytes","chmod","mkdir","readFile","rename","writeFile","homedir","dirname","join","dirname","join","readFile","mkdir","dirname","randomBytes","writeFile","chmod","rename","homedir","join","dirname","fileURLToPath","join","readFile","mkdir","randomBytes","writeFile","chmod","rename","unlink","TRAILING_SLASH","chmod","mkdir","readFile","writeFile","homedir","dirname","join","USDC_DECIMALS","randomBytes","randomBytes"]}
1
+ {"version":3,"sources":["../src/agent-detect.ts","../src/balance.ts","../src/chains.ts","../src/cli.ts","../src/fund.ts","../src/hmac.ts","../src/storage.ts","../src/types.ts","../src/provision.ts","../src/skill-install.ts","../src/mcp-register.ts","../src/runtime-detect.ts","../src/client.ts","../src/safety-config.ts","../src/hook.ts","../src/hook-entrypoint.ts","../src/mpp-detect.ts","../src/payment-signer.ts","../src/workflow-slug.ts","../src/x402-detect.ts"],"sourcesContent":["// Cross-agent skill/settings directory discovery.\n//\n// Probes canonical paths under $HOME and returns one AgentTarget record per\n// agent whose parent directory exists. The `skills/` leaf may be absent --\n// installSkill() creates it.\n//\n// NOTE: `homedir()` is called per-invocation (via `homeOverride ?? homedir()`)\n// and NEVER hoisted to a module-level constant. Tests override\n// `process.env.HOME` in `beforeEach`; hoisting would freeze the harness's\n// original HOME at import time and detection would run against the real $HOME.\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport type AgentTarget = {\n\tagent: \"claude-code\" | \"cursor\" | \"cline\" | \"windsurf\" | \"opencode\";\n\tskillsDir: string;\n\tsettingsFile: string;\n\thookSupport: \"claude-code\" | \"notice\";\n\t/**\n\t * Path-segments under $HOME where the agent stores its MCP server registry.\n\t * `undefined` for agents without a known MCP config location (cline today\n\t * stores per-VS-Code-variant globalStorage state that is too fragile to\n\t * auto-detect, so we ship \"notice\" instead).\n\t */\n\tmcpConfigRel?: string[];\n\t/**\n\t * Which MCP config shape to write. `claude-code`, `cursor`, and `windsurf`\n\t * all use the standard `{ mcpServers: { name: { command, args, env } } }`\n\t * shape. `opencode` uses a divergent `{ mcp: { name: { type:\"local\",\n\t * command:[...], enabled, environment } } }` shape. `notice` agents get\n\t * a copy-paste hint instead of an auto-registered entry.\n\t */\n\tmcpSupport: \"claude-code\" | \"cursor\" | \"windsurf\" | \"opencode\" | \"notice\";\n};\n\ntype AgentSpec = {\n\tagent: AgentTarget[\"agent\"];\n\tskillsRel: string[];\n\tsettingsRel: string[];\n\thookSupport: AgentTarget[\"hookSupport\"];\n\tmcpConfigRel?: string[];\n\tmcpSupport: AgentTarget[\"mcpSupport\"];\n\t/**\n\t * Optional extra path segments — when present, the agent is detected only\n\t * if AT LEAST ONE of `[skillsRel.dirname, ...extraDetect]` exists under\n\t * $HOME. Used by windsurf, which keeps state under both `.windsurf/` and\n\t * the legacy `.codeium/windsurf/`.\n\t */\n\textraDetect?: Array<string[]>;\n};\n\n// Deterministic order: claude-code first (only agent with hook support),\n// then cursor, cline, windsurf, opencode.\nconst AGENT_SPECS: readonly AgentSpec[] = [\n\t{\n\t\tagent: \"claude-code\",\n\t\tskillsRel: [\".claude\", \"skills\"],\n\t\tsettingsRel: [\".claude\", \"settings.json\"],\n\t\thookSupport: \"claude-code\",\n\t\t// ~/.claude.json is at HOME root (not under .claude/) and is large\n\t\t// (100+KB on real installs). registerMcpServer reads/parses/rewrites it\n\t\t// while preserving every other top-level key byte-for-byte.\n\t\tmcpConfigRel: [\".claude.json\"],\n\t\tmcpSupport: \"claude-code\",\n\t},\n\t{\n\t\tagent: \"cursor\",\n\t\tskillsRel: [\".cursor\", \"skills\"],\n\t\tsettingsRel: [\".cursor\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".cursor\", \"mcp.json\"],\n\t\tmcpSupport: \"cursor\",\n\t},\n\t{\n\t\tagent: \"cline\",\n\t\tskillsRel: [\".cline\", \"skills\"],\n\t\tsettingsRel: [\".cline\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\t// Cline keeps MCP state in a per-VS-Code-variant globalStorage path\n\t\t// (e.g. ~/Library/Application Support/Code/User/globalStorage/\n\t\t// saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too\n\t\t// fragile to auto-detect. Ship \"notice\" with a copy-paste entry shape\n\t\t// instead of guessing the variant.\n\t\tmcpSupport: \"notice\",\n\t},\n\t{\n\t\tagent: \"windsurf\",\n\t\tskillsRel: [\".windsurf\", \"skills\"],\n\t\tsettingsRel: [\".windsurf\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".codeium\", \"windsurf\", \"mcp_config.json\"],\n\t\tmcpSupport: \"windsurf\",\n\t\t// Windsurf historically ships under both `.windsurf/` and the legacy\n\t\t// `.codeium/windsurf/`; detect either.\n\t\textraDetect: [[\".codeium\", \"windsurf\"]],\n\t},\n\t{\n\t\tagent: \"opencode\",\n\t\tskillsRel: [\".config\", \"opencode\", \"skills\"],\n\t\tsettingsRel: [\".config\", \"opencode\", \"settings.json\"],\n\t\thookSupport: \"notice\",\n\t\tmcpConfigRel: [\".config\", \"opencode\", \"opencode.json\"],\n\t\tmcpSupport: \"opencode\",\n\t},\n];\n\nexport function detectAgents(homeOverride?: string): AgentTarget[] {\n\tconst home = homeOverride ?? homedir();\n\tconst results: AgentTarget[] = [];\n\tfor (const spec of AGENT_SPECS) {\n\t\tconst skillsDir = join(home, ...spec.skillsRel);\n\t\tconst settingsFile = join(home, ...spec.settingsRel);\n\t\t// \"Detected\" iff the parent of skills/ exists (e.g. ~/.claude/) OR any\n\t\t// of the spec's extra-detect paths exist (windsurf's legacy location).\n\t\tlet detected = existsSync(dirname(skillsDir));\n\t\tif (!detected && spec.extraDetect) {\n\t\t\tfor (const seg of spec.extraDetect) {\n\t\t\t\tif (existsSync(join(home, ...seg))) {\n\t\t\t\t\tdetected = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!detected) {\n\t\t\tcontinue;\n\t\t}\n\t\tresults.push({\n\t\t\tagent: spec.agent,\n\t\t\tskillsDir,\n\t\t\tsettingsFile,\n\t\t\thookSupport: spec.hookSupport,\n\t\t\tmcpConfigRel: spec.mcpConfigRel,\n\t\t\tmcpSupport: spec.mcpSupport,\n\t\t});\n\t}\n\treturn results;\n}\n","// checkBalance() unified view (PAY-05):\n// - Base USDC balanceOf (viem publicClient on Base)\n// - Tempo USDC.e balanceOf (viem publicClient on Tempo)\n//\n// Both legs are fetched in parallel via Promise.all. The on-chain reads\n// touch only the canonical USDC contract on their respective chains\n// (read-only ERC-20 balanceOf with no state mutation).\n//\n// The /api/agentic-wallet/credit ledger is intentionally NOT read here:\n// the server endpoint exists but no debit path is wired, so surfacing the\n// balance to users implied a capability that has not shipped. Restore the\n// leg here when KEEP-305/306 lands.\n//\n// @security balance.ts does not emit balance data to stdout/stderr via the\n// global console object or util.inspect (T-34-bal-02 mitigation). Any\n// stdout emitter added here is a privacy regression; grep-enforced in\n// acceptance criteria.\nimport {\n createPublicClient,\n erc20Abi,\n formatUnits,\n http,\n type PublicClient,\n} from \"viem\";\nimport { BASE_USDC, base, TEMPO_USDC_E, tempo } from \"./chains.js\";\nimport type { WalletConfig } from \"./types.js\";\n\n// USDC and USDC.e both use 6 decimals on Base + Tempo respectively.\nconst USDC_DECIMALS = 6;\n\nexport type BalanceSnapshot = {\n base: {\n chain: \"base\";\n token: \"USDC\";\n amount: string;\n address: `0x${string}`;\n };\n tempo: {\n chain: \"tempo\";\n token: \"USDC.e\";\n amount: string;\n address: `0x${string}`;\n };\n};\n\nexport type CheckBalanceOptions = {\n /** Injectable viem client for Base (tests mock readContract). */\n baseClient?: PublicClient;\n /** Injectable viem client for Tempo (tests mock readContract). */\n tempoClient?: PublicClient;\n};\n\n/**\n * Read the wallet's on-chain balance across Base + Tempo in parallel. Both\n * legs must resolve; any single failure rejects the Promise.\n *\n * Amounts are formatted as decimal strings (6-decimal USDC precision) so the\n * caller can render them without BigInt math.\n */\nexport async function checkBalance(\n wallet: WalletConfig,\n opts: CheckBalanceOptions = {}\n): Promise<BalanceSnapshot> {\n const baseClient =\n opts.baseClient ??\n (createPublicClient({\n chain: base,\n transport: http(),\n }) as unknown as PublicClient);\n const tempoClient =\n opts.tempoClient ??\n (createPublicClient({\n chain: tempo,\n transport: http(),\n }) as unknown as PublicClient);\n\n // Promise.all fires both reads concurrently. Total elapsed ~= max(leg)\n // rather than sum(leg); SC-3 (<2s) test asserts this.\n const [baseRaw, tempoRaw] = await Promise.all([\n baseClient.readContract({\n address: BASE_USDC,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n tempoClient.readContract({\n address: TEMPO_USDC_E,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n ]);\n\n return {\n base: {\n chain: \"base\",\n token: \"USDC\",\n amount: formatUnits(baseRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n tempo: {\n chain: \"tempo\",\n token: \"USDC.e\",\n amount: formatUnits(tempoRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n };\n}\n","// Sources (truth):\n// - lib/agentic-wallet/sign.ts:56 -- Base USDC at\n// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (chainId 8453).\n// - lib/mpp/server.ts:3 -- Tempo USDC.e at\n// 0x20c000000000000000000000b9537d11c60e8b50 (chainId 4217).\n//\n// Tempo is not in viem/chains core as of viem 2.48.1 (the version pinned in\n// this package). Define it inline via defineChain so the only dependency is\n// viem itself. TEMPO_RPC_URL overrides the default RPC for heavy readers who\n// want to point at their own node (T-34-bal-01 mitigation).\nimport { defineChain } from \"viem\";\n\nexport { base } from \"viem/chains\";\n\nexport const tempo = defineChain({\n id: 4217,\n name: \"Tempo\",\n nativeCurrency: { decimals: 18, name: \"Ether\", symbol: \"ETH\" },\n rpcUrls: {\n default: {\n http: [process.env.TEMPO_RPC_URL ?? \"https://rpc.tempo.xyz\"],\n },\n },\n blockExplorers: {\n default: { name: \"Tempo Explorer\", url: \"https://explorer.tempo.xyz\" },\n },\n});\n\n/** Circle-issued USDC on Base mainnet. */\nexport const BASE_USDC = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\n/** Bridged USDC (USDC.e) on Tempo mainnet. NOT the same contract as BASE_USDC. */\nexport const TEMPO_USDC_E =\n \"0x20c000000000000000000000b9537d11c60e8b50\" as const;\n","// CLI dispatcher for `npx @keeperhub/wallet <cmd>`. Ships 4 subcommands:\n// add (provision -- NO auth), fund (pure string-build Coinbase Onramp +\n// Tempo address), balance (Base USDC + Tempo USDC.e), info (print subOrgId\n// + walletAddress from ~/.keeperhub/wallet.json).\n//\n// v0.1.4 removed the `link` subcommand. /api/agentic-wallet/link still\n// exists server-side but the UX (copy-paste session cookie) was not fit\n// for real users; the server-approval ask tier that required linking\n// also collapsed into an inline ask in this release. See KEEP-307 and\n// KEEP-308 for the long-term design decisions.\n//\n// @security The HMAC secret written to wallet.json is NEVER printed to stdout\n// or stderr. `add` prints only subOrgId + walletAddress + the config path so\n// users can inspect perms. `info` never references the secret at all. Grep\n// rule: no process.stdout/process.stderr line in this file should include\n// wallet.hmacSecret or data.hmacSecret.\n//\n// Exit codes: 0 on success, 1 on any error (WalletConfigMissingError,\n// HTTP failure, validation error). Uncaught errors are written to stderr.\n\nimport { Command } from \"commander\";\nimport { checkBalance } from \"./balance.js\";\nimport { fund } from \"./fund.js\";\nimport { buildHmacHeaders } from \"./hmac.js\";\nimport { ProvisionHttpError, provisionWallet } from \"./provision.js\";\nimport { installSkill } from \"./skill-install.js\";\nimport { getWalletConfigPath, readWalletConfig } from \"./storage.js\";\nimport { WalletConfigMissingError } from \"./types.js\";\n\nasync function cmdAdd(opts: { baseUrl?: string } = {}): Promise<void> {\n\ttry {\n\t\tconst data = await provisionWallet({ baseUrl: opts.baseUrl });\n\t\t// Intentionally print only public fields. The hmacSecret is written to\n\t\t// wallet.json (chmod 0o600) but never printed -- T-34-cli-02 mitigation.\n\t\tprocess.stdout.write(`subOrgId: ${data.subOrgId}\\n`);\n\t\tprocess.stdout.write(`walletAddress: ${data.walletAddress}\\n`);\n\t\tprocess.stdout.write(`config written to ${getWalletConfigPath()}\\n`);\n\t} catch (err) {\n\t\tif (err instanceof ProvisionHttpError) {\n\t\t\tprocess.stderr.write(\n\t\t\t\t`[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}\\n`,\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function cmdFund(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tconst out = fund(wallet.walletAddress);\n\tprocess.stdout.write(`${out.coinbaseOnrampUrl}\\n`);\n\tprocess.stdout.write(`Tempo address: ${out.tempoAddress}\\n`);\n\tprocess.stdout.write(`${out.disclaimer}\\n`);\n}\n\nasync function cmdBalance(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tconst snap = await checkBalance(wallet);\n\tprocess.stdout.write(`Base USDC: ${snap.base.amount}\\n`);\n\tprocess.stdout.write(`Tempo USDC.e: ${snap.tempo.amount}\\n`);\n}\n\nasync function cmdInfo(): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tprocess.stdout.write(`subOrgId: ${wallet.subOrgId}\\n`);\n\tprocess.stdout.write(`walletAddress: ${wallet.walletAddress}\\n`);\n}\n\ntype FeedbackOpts = {\n\texecutionId: string;\n\tvalue: string;\n\tdecimals?: string;\n\tcomment?: string;\n\tagentId?: string;\n\tchainId?: string;\n\tbaseUrl?: string;\n};\n\nconst FEEDBACK_DEFAULT_BASE_URL = \"https://app.keeperhub.com\";\n\nasync function cmdFeedback(opts: FeedbackOpts): Promise<void> {\n\tconst wallet = await readWalletConfig();\n\tconst baseUrl = (opts.baseUrl ?? FEEDBACK_DEFAULT_BASE_URL).replace(\n\t\t/\\/$/,\n\t\t\"\",\n\t);\n\tconst path = \"/api/agentic-wallet/feedback\";\n\n\t// Build the request body. Server validates int128 range + 0..18 decimals\n\t// + executionId payment ownership; we forward verbatim and let it speak.\n\tconst body: Record<string, unknown> = {\n\t\texecutionId: opts.executionId,\n\t\tvalue: Number.parseInt(opts.value, 10),\n\t\tvalueDecimals: Number.parseInt(opts.decimals ?? \"0\", 10),\n\t};\n\tif (opts.comment !== undefined) {\n\t\tbody.comment = opts.comment;\n\t}\n\tif (opts.agentId !== undefined) {\n\t\tbody.agentId = opts.agentId;\n\t}\n\tif (opts.chainId !== undefined) {\n\t\tbody.agentChainId = Number.parseInt(opts.chainId, 10);\n\t}\n\tconst bodyJson = JSON.stringify(body);\n\n\tconst headers = buildHmacHeaders(\n\t\twallet.hmacSecret,\n\t\t\"POST\",\n\t\tpath,\n\t\twallet.subOrgId,\n\t\tbodyJson,\n\t);\n\n\tconst response = await fetch(`${baseUrl}${path}`, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\t...headers,\n\t\t},\n\t\tbody: bodyJson,\n\t});\n\tconst text = await response.text();\n\tif (!response.ok) {\n\t\tprocess.stderr.write(`HTTP ${response.status}: ${text}\\n`);\n\t\tprocess.exit(1);\n\t}\n\tconst parsed = JSON.parse(text) as {\n\t\tfeedbackId?: string;\n\t\ttxHash?: string;\n\t\tpublicUrl?: string;\n\t};\n\tprocess.stdout.write(`feedbackId: ${parsed.feedbackId ?? \"\"}\\n`);\n\tprocess.stdout.write(`txHash: ${parsed.txHash ?? \"\"}\\n`);\n\tprocess.stdout.write(`publicUrl: ${parsed.publicUrl ?? \"\"}\\n`);\n}\n\nexport async function runCli(argv: string[] = process.argv): Promise<void> {\n\tconst program = new Command();\n\tprogram\n\t\t.name(\"keeperhub-wallet\")\n\t\t.description(\n\t\t\t\"KeeperHub agentic wallet CLI (auto-pay x402 + MPP 402 responses)\",\n\t\t)\n\t\t.version(\"0.1.3\");\n\n\tprogram\n\t\t.command(\"add\")\n\t\t.description(\"Provision a new agentic wallet (no account required)\")\n\t\t.option(\"--base-url <url>\", \"KeeperHub API base URL\")\n\t\t.action(async (opts: { baseUrl?: string }) => {\n\t\t\tawait cmdAdd(opts);\n\t\t});\n\n\tprogram\n\t\t.command(\"fund\")\n\t\t.description(\n\t\t\t\"Print Coinbase Onramp URL (Base USDC) and Tempo deposit address\",\n\t\t)\n\t\t.action(async () => {\n\t\t\tawait cmdFund();\n\t\t});\n\n\tprogram\n\t\t.command(\"balance\")\n\t\t.description(\"Print on-chain balance: Base USDC + Tempo USDC.e\")\n\t\t.action(async () => {\n\t\t\tawait cmdBalance();\n\t\t});\n\n\tprogram\n\t\t.command(\"info\")\n\t\t.description(\"Print subOrgId and walletAddress from local config\")\n\t\t.action(async () => {\n\t\t\tawait cmdInfo();\n\t\t});\n\n\tprogram\n\t\t.command(\"feedback\")\n\t\t.description(\n\t\t\t\"Submit ERC-8004 ReputationRegistry feedback for a workflow execution this wallet paid for. Signs giveFeedback() via Turnkey and broadcasts on Ethereum mainnet. Caller wallet pays gas natively.\",\n\t\t)\n\t\t.requiredOption(\n\t\t\t\"--execution-id <id>\",\n\t\t\t\"workflow execution id to leave feedback for\",\n\t\t)\n\t\t.requiredOption(\n\t\t\t\"--value <int>\",\n\t\t\t\"raw int128 rating value (e.g. 5 with --decimals 0 for a 5-star rating)\",\n\t\t)\n\t\t.option(\n\t\t\t\"--decimals <int>\",\n\t\t\t\"decimals for value (0..18); 0 for integer scores, 1 for 0.1-step\",\n\t\t\t\"0\",\n\t\t)\n\t\t.option(\"--comment <text>\", \"optional plaintext comment\")\n\t\t.option(\n\t\t\t\"--agent-id <id>\",\n\t\t\t\"rated agent NFT id (uint256 decimal); defaults to KeeperHub agent 31875\",\n\t\t)\n\t\t.option(\n\t\t\t\"--chain-id <int>\",\n\t\t\t\"agent chain id; defaults to 1 (Ethereum mainnet, only chain supported today)\",\n\t\t)\n\t\t.option(\"--base-url <url>\", \"KeeperHub API base URL\")\n\t\t.action(async (opts: FeedbackOpts) => {\n\t\t\tawait cmdFeedback(opts);\n\t\t});\n\n\tprogram\n\t\t.command(\"skill\")\n\t\t.description(\n\t\t\t\"Install the KeeperHub skill file into detected agent directories\",\n\t\t)\n\t\t.addCommand(\n\t\t\tnew Command(\"install\")\n\t\t\t\t.description(\n\t\t\t\t\t\"Write skill file + register PreToolUse hook in all detected agents\",\n\t\t\t\t)\n\t\t\t\t.action(async () => {\n\t\t\t\t\tconst result = await installSkill();\n\t\t\t\t\tfor (const write of result.skillWrites) {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`skill: ${write.agent} -> ${write.path} (${write.status})\\n`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tfor (const reg of result.hookRegistrations) {\n\t\t\t\t\t\tif (reg.status === \"registered\") {\n\t\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t\t`hook: ${reg.agent} -> PreToolUse registered\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"notice\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`notice: ${reg.agent} -> ${reg.message ?? \"\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"failed\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`hook: ${reg.agent} -> FAILED (${reg.message ?? \"unknown error\"})\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const reg of result.mcpRegistrations) {\n\t\t\t\t\t\tif (reg.status === \"registered\") {\n\t\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t\t`mcp: ${reg.agent} -> registered at ${reg.path ?? \"(unknown path)\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"notice\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`notice: ${reg.agent} mcp -> ${reg.message ?? \"\"}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (reg.status === \"failed\") {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`mcp: ${reg.agent} -> FAILED (${reg.message ?? \"unknown error\"})\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (result.skillWrites.length === 0) {\n\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\"No supported agent skill directories detected under $HOME. Create ~/.claude/, ~/.cursor/, ~/.cline/, ~/.windsurf/, or ~/.config/opencode/ and re-run.\\n\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t);\n\n\ttry {\n\t\tawait program.parseAsync(argv);\n\t} catch (err) {\n\t\tif (err instanceof WalletConfigMissingError) {\n\t\t\tprocess.stderr.write(`[keeperhub-wallet] ${err.message}\\n`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tprocess.stderr.write(\n\t\t\t`[keeperhub-wallet] ${(err as Error).message ?? String(err)}\\n`,\n\t\t);\n\t\tprocess.exit(1);\n\t}\n}\n","// Source: 34-RESEARCH Pattern 5 + Pitfall 5.\n// Coinbase deprecated the query-param pay.coinbase.com flow in favour of\n// sessionToken URLs on 2025-07-31, but the legacy endpoint still returns a\n// working Onramp page (it just may not pre-fill the asset/network/address\n// fields). We print the legacy URL for zero-dependency ergonomics and a\n// follow-up disclaimer so users know to paste manually if prefill is dropped.\n//\n// fund() is a pure string-build: no HTTP, no process spawn, no browser\n// invocation. Callers (the CLI `keeperhub-wallet fund` subcommand, the\n// `check_balance` skill in Phase 35) decide how to display the result.\n//\n// T-34-fund-01 mitigation: the host is hard-coded (pay.coinbase.com) and the\n// only user-supplied input is the wallet address, which is regex-validated\n// against the canonical 0x-prefixed 40-hex-char EVM format before any string\n// interpolation.\n\nexport type FundInstructions = {\n /** Coinbase Onramp deeplink (legacy query-param form). */\n coinbaseOnrampUrl: string;\n /** Tempo deposit address — same as the input wallet (EVM address shared). */\n tempoAddress: `0x${string}`;\n /** Plain-ASCII guidance string; no emojis (CLAUDE.md rule). */\n disclaimer: string;\n};\n\n// 0x followed by exactly 40 hex chars, case-insensitive. Kept at module scope\n// so the regex literal is compiled once (biome/ultracite useTopLevelRegex).\nconst EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\n\n// Coinbase Onramp legacy deeplink. The host + path pair is the documented\n// entry point for query-param-style Onramp sessions.\nconst COINBASE_HOST = \"pay.coinbase.com\";\nconst COINBASE_PATH = \"/buy/select-asset\";\n\n/**\n * Build Coinbase Onramp URL + Tempo deposit address for the given wallet.\n *\n * No HTTP calls are performed. The caller is expected to either print the\n * resulting URL (CLI) or render it in a chat bubble (skill). The returned\n * `disclaimer` explains the Onramp deprecation + the Tempo external-transfer\n * fallback in plain ASCII so terminal clients with ASCII-only fonts render\n * identically to emoji-capable clients.\n *\n * @throws if `walletAddress` does not match /^0x[0-9a-fA-F]{40}$/.\n */\nexport function fund(walletAddress: string): FundInstructions {\n if (!EVM_ADDRESS_RE.test(walletAddress)) {\n throw new Error(`Invalid EVM wallet address: ${walletAddress}`);\n }\n\n // addresses is a JSON-encoded map {walletAddress: [\"base\"]} per Coinbase\n // Onramp docs. Encoding into URLSearchParams guarantees the colon,\n // brackets, and quotes are percent-escaped correctly.\n const params = new URLSearchParams({\n defaultNetwork: \"base\",\n defaultAsset: \"USDC\",\n addresses: JSON.stringify({ [walletAddress]: [\"base\"] }),\n presetCryptoAmount: \"5\",\n });\n\n const coinbaseOnrampUrl = `https://${COINBASE_HOST}${COINBASE_PATH}?${params.toString()}`;\n\n const disclaimer =\n \"If the Coinbase page does not pre-fill, paste your address manually. \" +\n \"For Tempo USDC.e, transfer from an exchange or another wallet to the \" +\n \"address above -- Onramp does not support Tempo directly. Coinbase \" +\n \"sessionToken URLs are the 2025+ canonical form; legacy query-param \" +\n \"URLs may drop prefill on some accounts.\";\n\n return {\n coinbaseOnrampUrl,\n tempoAddress: walletAddress as `0x${string}`,\n disclaimer,\n };\n}\n","import { createHash, createHmac } from \"node:crypto\";\nimport type { HmacHeaders } from \"./types.js\";\n\n/**\n * Mirror of lib/agentic-wallet/hmac.ts::computeSignature.\n * Format (byte-for-byte identical to server):\n * `${method}\\n${path}\\n${subOrgId}\\n${sha256_hex(body)}\\n${timestamp}`\n * Post-HI-05: subOrgId is a signed field.\n *\n * @security Do NOT log the secret or the returned signature. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced).\n */\nexport function computeSignature(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string,\n timestamp: string\n): string {\n const bodyDigest = createHash(\"sha256\").update(body).digest(\"hex\");\n const signingString = `${method}\\n${path}\\n${subOrgId}\\n${bodyDigest}\\n${timestamp}`;\n return createHmac(\"sha256\", secret).update(signingString).digest(\"hex\");\n}\n\n/**\n * Build the three X-KH-* headers that authenticate every request to\n * /api/agentic-wallet/* (except /provision, which uses the session cookie).\n *\n * Timestamp is unix seconds (Math.floor(Date.now() / 1000)); the server\n * enforces a symmetric 300-second replay window.\n */\nexport function buildHmacHeaders(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string\n): HmacHeaders {\n const timestamp = String(Math.floor(Date.now() / 1000));\n const signature = computeSignature(\n secret,\n method,\n path,\n subOrgId,\n body,\n timestamp\n );\n return {\n \"X-KH-Sub-Org\": subOrgId,\n \"X-KH-Timestamp\": timestamp,\n \"X-KH-Signature\": signature,\n };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { chmod, mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport {\n\ttype WalletConfig,\n\tWalletConfigCorruptError,\n\tWalletConfigMissingError,\n} from \"./types.js\";\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"wallet.json\")`\n// itself. Do NOT hoist to a module-level `const WALLET_PATH` -- tests\n// override `process.env.HOME` in `beforeEach` and `homedir()` must re-read\n// that on each call. A hoisted constant would freeze the harness's original\n// HOME at import time and every test would write into the real\n// ~/.keeperhub/ directory.\n\nexport async function readWalletConfig(): Promise<WalletConfig> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(walletPath, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tthrow new WalletConfigMissingError();\n\t\t}\n\t\tthrow err;\n\t}\n\t// The file's PRESENCE means it was created on purpose. Distinguish parse\n\t// failures (malformed JSON, truncated write) and field-shape failures\n\t// (missing required keys) from \"missing entirely\" so callers — including\n\t// the MCP server's auto-provision path — can refuse to silently mint a\n\t// replacement wallet over an existing-but-broken one.\n\tlet parsed: Partial<WalletConfig>;\n\ttry {\n\t\tparsed = JSON.parse(raw) as Partial<WalletConfig>;\n\t} catch (err) {\n\t\tconst reason = err instanceof Error ? err.message : String(err);\n\t\tthrow new WalletConfigCorruptError(walletPath, reason);\n\t}\n\tif (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {\n\t\tthrow new WalletConfigCorruptError(walletPath, \"missing required fields\");\n\t}\n\treturn parsed as WalletConfig;\n}\n\n/**\n * Atomic write: serialise to a sibling tmp file, fsync via the close, then\n * rename(2) into place. Concurrent provisioning races (two MCP sessions\n * spawning at once on a fresh install) collapse to last-rename-wins on the\n * final path rather than torn-write-wins. Combined with the in-process\n * promise cache in mcp-server.ts:ensureWallet this gates duplicate-mint\n * within a single process AND keeps disk consistent across multiple.\n *\n * The tmp filename includes 16 bytes of randomness so two writers don't\n * stomp each other's tmp files mid-write either.\n */\nexport async function writeWalletConfig(config: WalletConfig): Promise<void> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tawait mkdir(dirname(walletPath), { recursive: true, mode: 0o700 });\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;\n\tawait writeFile(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n\t// chmod again on the tmp before rename: writeFile honours the mode on\n\t// creation but a pre-existing tmp (extremely unlikely thanks to the\n\t// randomness) might keep looser perms.\n\tawait chmod(tmpPath, 0o600);\n\tawait rename(tmpPath, walletPath);\n}\n\nexport function getWalletConfigPath(): string {\n\treturn join(homedir(), \".keeperhub\", \"wallet.json\");\n}\n","// Shared types across the package. Phase 34.\nexport type WalletConfig = {\n\t/** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */\n\tsubOrgId: string;\n\t/** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */\n\twalletAddress: `0x${string}`;\n\t/** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */\n\thmacSecret: string;\n};\n\nexport type HmacHeaders = {\n\t\"X-KH-Sub-Org\": string;\n\t\"X-KH-Timestamp\": string;\n\t\"X-KH-Signature\": string;\n};\n\nexport type HookDecision = {\n\tdecision: \"allow\" | \"deny\" | \"ask\";\n\treason?: string;\n};\n\nexport class KeeperHubError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"KeeperHubError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Protocol preference for a single pay() or fetch() call. \"auto\" preserves\n * the x402-first default when both challenges are offered. */\nexport type PaymentHint = \"x402\" | \"mpp\" | \"auto\";\n\nexport class WalletConfigMissingError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision.\",\n\t\t);\n\t\tthis.name = \"WalletConfigMissingError\";\n\t}\n}\n\n/**\n * Thrown when ~/.keeperhub/wallet.json exists but is unreadable as a wallet\n * config (malformed JSON, truncated write, missing required fields, hand-edit\n * gone wrong). Distinct from WalletConfigMissingError — the file's PRESENCE\n * means the user (or a prior install) intentionally created it, so the MCP\n * server MUST NOT silently auto-provision a replacement and abandon any funds\n * held in the existing wallet. Surface a structured error with the path so\n * the user can repair or delete the file deliberately.\n */\nexport class WalletConfigCorruptError extends Error {\n\treadonly path: string;\n\n\tconstructor(path: string, reason: string) {\n\t\tsuper(\n\t\t\t`Wallet config at ${path} is unreadable: ${reason}. Repair the file by hand or delete it to re-provision a new wallet (this will abandon any funds held in the current wallet).`,\n\t\t);\n\t\tthis.name = \"WalletConfigCorruptError\";\n\t\tthis.path = path;\n\t}\n}\n","// Wallet provisioning: shared between `keeperhub-wallet add` (CLI) and the\n// MCP server's auto-provision-on-first-call path.\n//\n// Why a shared module: the user-facing UX target is \"install the wallet\n// package and start calling paid workflows\" — manual `keeperhub-wallet add`\n// ceremony is an unnecessary speed bump. The MCP server therefore reuses\n// this exact provision flow on the first tool call when ~/.keeperhub/wallet.json\n// is missing. Keeping the call site in one module avoids the obvious\n// drift hazard of two divergent provision implementations.\n//\n// @security The HMAC secret minted by /api/agentic-wallet/provision is\n// returned to the caller AND written to ~/.keeperhub/wallet.json (chmod 0o600\n// via writeWalletConfig). Callers MUST NOT log the returned secret to stdout\n// or stderr. The CLI's `cmdAdd` and the MCP server's auto-provision branch\n// both honour this — every printed/serialised path uses only `subOrgId` and\n// `walletAddress`.\n\nimport { writeWalletConfig } from \"./storage.js\";\nimport type { WalletConfig } from \"./types.js\";\n\nconst TRAILING_SLASH = /\\/$/;\nconst WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;\n\nexport type ProvisionOptions = {\n\t/** Override KEEPERHUB_API_URL. Defaults to env var or app.keeperhub.com. */\n\tbaseUrl?: string;\n\t/** Injectable fetch for tests. */\n\tfetchImpl?: typeof fetch;\n};\n\nexport class ProvisionResponseInvalidError extends Error {\n\treadonly code = \"PROVISION_RESPONSE_INVALID\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ProvisionResponseInvalidError\";\n\t}\n}\n\nexport class ProvisionHttpError extends Error {\n\treadonly code = \"PROVISION_HTTP_ERROR\" as const;\n\treadonly status: number;\n\treadonly body: string;\n\n\tconstructor(status: number, body: string) {\n\t\tsuper(`provision failed: HTTP ${status}: ${body}`);\n\t\tthis.name = \"ProvisionHttpError\";\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nfunction resolveBaseUrl(override: string | undefined): string {\n\tconst candidate =\n\t\toverride ?? process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n\treturn candidate.replace(TRAILING_SLASH, \"\");\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction validateProvisionResponse(data: unknown): WalletConfig {\n\tif (typeof data !== \"object\" || data === null) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response is not an object\",\n\t\t);\n\t}\n\tconst { subOrgId, walletAddress, hmacSecret } = data as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tif (\n\t\t!(\n\t\t\tisNonEmptyString(subOrgId) &&\n\t\t\tisNonEmptyString(walletAddress) &&\n\t\t\tisNonEmptyString(hmacSecret)\n\t\t)\n\t) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response missing subOrgId, walletAddress, or hmacSecret\",\n\t\t);\n\t}\n\tif (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`,\n\t\t);\n\t}\n\treturn {\n\t\tsubOrgId,\n\t\twalletAddress: walletAddress as `0x${string}`,\n\t\thmacSecret,\n\t};\n}\n\n/**\n * Mint a new agentic wallet via POST /api/agentic-wallet/provision and\n * persist the result to ~/.keeperhub/wallet.json (chmod 0o600). Returns the\n * provisioned WalletConfig. Throws ProvisionHttpError on non-2xx and\n * ProvisionResponseInvalidError on malformed payloads.\n *\n * Used by:\n * - `keeperhub-wallet add` (manual provisioning).\n * - The MCP server's auto-provision-on-first-call path (no manual ceremony).\n */\nexport async function provisionWallet(\n\toptions: ProvisionOptions = {},\n): Promise<WalletConfig> {\n\tconst baseUrl = resolveBaseUrl(options.baseUrl);\n\tconst fetchImpl = options.fetchImpl ?? globalThis.fetch;\n\t// Bound the wedged-upstream failure mode so a hung provision endpoint\n\t// can't freeze the MCP tool call indefinitely. AbortError surfaces as\n\t// UPSTREAM_TIMEOUT in the MCP envelope; CLI users see the raw cause.\n\tconst response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: \"{}\",\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new ProvisionHttpError(response.status, text);\n\t}\n\tconst raw = (await response.json()) as unknown;\n\tconst data = validateProvisionResponse(raw);\n\tawait writeWalletConfig(data);\n\treturn data;\n}\n","// Idempotent skill installer for @keeperhub/wallet.\n//\n// Two public entry points:\n// - installSkill(options?) -- writes keeperhub-wallet.skill.md into every\n// detected agent's skills directory and, for Claude Code, registers a\n// PreToolUse hook pointing at `keeperhub-wallet-hook` in\n// ~/.claude/settings.json. For non-claude agents, emits a stderr notice.\n// - registerClaudeCodeHook(settingsPath, options?) -- pure settings.json\n// patcher used internally; exported so tests can drive it directly.\n//\n// Hook command resolution: the README's recommended install path is\n// `npx -p @keeperhub/wallet keeperhub-wallet skill install`, which does\n// not put the bin on the system PATH. If we wrote a bare\n// `keeperhub-wallet-hook` command in that case, the hook would fire\n// `command not found` on every tool call. So at install time we probe\n// PATH; if the bin resolves we keep the bare command (lowest startup\n// latency), otherwise we fall back to an `npx` invocation that resolves\n// regardless of where future shells run.\n//\n// Idempotency rule: re-running the installer MUST NOT create a duplicate\n// hook entry. We filter any existing array element whose serialised form\n// contains `keeperhub-wallet-hook` before appending a single fresh record.\n// The marker substring is present in BOTH the bare and npx forms, so the\n// de-dup survives a global-install → npx-install transition (and back).\n//\n// Preservation rule: all top-level keys in settings.json other than\n// hooks.PreToolUse MUST be byte-preserved. We only ever touch\n// hooks.PreToolUse; any foreign hooks.PostToolUse entries survive verbatim.\n\nimport { randomBytes } from \"node:crypto\";\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trename,\n\tunlink,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type AgentTarget, detectAgents } from \"./agent-detect.js\";\nimport {\n\ttype RegisterMcpServerOptions,\n\tregisterMcpServer,\n\tresolveMcpCommand,\n} from \"./mcp-register.js\";\nimport { resolveBinCommand } from \"./runtime-detect.js\";\n\nconst HOOK_BIN = \"keeperhub-wallet-hook\";\n\n// Match rule for de-dup: any existing PreToolUse entry whose `command`\n// string contains this substring is considered \"ours\" and is removed\n// before append. The marker is present in BOTH the bare and pinned-npx\n// forms, so the de-dup survives a global-install <-> npx-install\n// transition (and across version bumps).\n//\n// Why match on the `command` field rather than JSON.stringify(entry):\n// the wider marker would silently delete an unrelated hook whose args\n// or matcher happen to mention the bin name (e.g. a logger). Scoping\n// to `command` is narrower and equally idempotent for our writes since\n// we always write the marker into `command`.\nconst KEEPERHUB_HOOK_MARKER = HOOK_BIN;\n\ntype PreToolUseLikeEntry = {\n\thooks?: Array<{ command?: unknown }>;\n};\n\n/**\n * Drop only the `hooks[]` items that reference the keeperhub bin, leaving\n * sibling commands inside the same `PreToolUse` element intact. Returns\n * the (possibly modified) entry, or null when every `hooks[]` item was\n * keeperhub-related and the whole element should be removed.\n *\n * Why per-item: a user may merge our hook into a single `PreToolUse`\n * element alongside their own commands, e.g.:\n *\n * { matcher: \"*\", hooks: [\n * { type: \"command\", command: \"/usr/local/bin/audit-logger\" },\n * { type: \"command\", command: \"keeperhub-wallet-hook\" } ] }\n *\n * Dropping the whole element on re-install would silently delete the\n * audit-logger sibling. Dropping only matching items preserves it.\n *\n * Non-object entries and entries without a `hooks[]` array are returned\n * unchanged — we never inspect or mutate shapes we don't recognise.\n */\nfunction filterKeeperhubHooksFromEntry(entry: unknown): unknown {\n\tif (typeof entry !== \"object\" || entry === null) {\n\t\treturn entry;\n\t}\n\tconst candidate = entry as PreToolUseLikeEntry;\n\tif (!Array.isArray(candidate.hooks)) {\n\t\treturn entry;\n\t}\n\tconst survivors = candidate.hooks.filter((h) => {\n\t\tconst cmd = h?.command;\n\t\treturn !(typeof cmd === \"string\" && cmd.includes(KEEPERHUB_HOOK_MARKER));\n\t});\n\tif (survivors.length === candidate.hooks.length) {\n\t\t// No keeperhub hooks present in this entry — return original byte-for-byte.\n\t\treturn entry;\n\t}\n\tif (survivors.length === 0) {\n\t\t// Every hook in this element was ours; drop the whole element so we\n\t\t// don't leave a `{matcher, hooks: []}` shell behind.\n\t\treturn null;\n\t}\n\treturn { ...candidate, hooks: survivors };\n}\n\n/**\n * Pick the hook command to write into settings.json.\n *\n * Returns the bare bin name if it resolves to a STABLE install on PATH\n * (global install, brew, distro pkg, dev-time `npm link`), otherwise a\n * version-pinned `npx` invocation that pulls the installer's own version\n * of `@keeperhub/wallet` on demand. Implementation lives in\n * runtime-detect.ts so the hook + MCP installers share a single decision.\n *\n * Override-able via the env var `KEEPERHUB_WALLET_HOOK_COMMAND` for test\n * fixtures and unusual deployments (env input is trusted — it is written\n * verbatim into settings.json and executed by the user's shell).\n */\nexport function resolveHookCommand(): string {\n\tconst envOverride = process.env.KEEPERHUB_WALLET_HOOK_COMMAND;\n\tif (envOverride && envOverride.length > 0) {\n\t\treturn envOverride;\n\t}\n\treturn resolveBinCommand(HOOK_BIN).commandString;\n}\n\nexport type InstallResult = {\n\tskillWrites: Array<{\n\t\tagent: string;\n\t\tpath: string;\n\t\tstatus: \"written\" | \"skipped\";\n\t}>;\n\thookRegistrations: Array<{\n\t\tagent: string;\n\t\tstatus: \"registered\" | \"notice\" | \"skipped\" | \"failed\";\n\t\tmessage?: string;\n\t}>;\n\tmcpRegistrations: Array<{\n\t\tagent: string;\n\t\tstatus: \"registered\" | \"notice\" | \"skipped\" | \"failed\";\n\t\tpath?: string;\n\t\tmessage?: string;\n\t}>;\n};\n\nexport type InstallOptions = {\n\thomeOverride?: string;\n\tskillSourcePath?: string;\n\tonNotice?: (msg: string) => void;\n\t/**\n\t * Hook command to write into settings.json (and reference in stderr\n\t * notices for non-Claude agents). Defaults to {@link resolveHookCommand}.\n\t * Override for tests, monorepo setups, or unusual deployments.\n\t */\n\thookCommand?: string;\n\t/**\n\t * MCP command + args to register with each detected agent. Defaults to\n\t * {@link resolveMcpCommand}. Tests pass an explicit value to pin\n\t * assertions regardless of host PATH.\n\t */\n\tmcpCommand?: RegisterMcpServerOptions[\"command\"];\n};\n\nexport type RegisterClaudeCodeHookOptions = {\n\t/**\n\t * Hook command to write. Defaults to {@link resolveHookCommand}. Tests\n\t * pass a deterministic value to keep assertions stable across host\n\t * environments (CI may or may not have the bin on PATH).\n\t */\n\thookCommand?: string;\n};\n\ntype ClaudeHookEntry = {\n\tmatcher: string;\n\thooks: Array<{ type: string; command: string }>;\n};\n\ntype ClaudeSettings = {\n\thooks?: {\n\t\tPreToolUse?: unknown[];\n\t\t[k: string]: unknown;\n\t};\n\t[k: string]: unknown;\n};\n\nfunction buildKeeperhubEntry(command: string): ClaudeHookEntry {\n\treturn {\n\t\tmatcher: \"*\",\n\t\thooks: [{ type: \"command\", command }],\n\t};\n}\n\nfunction resolveDefaultSkillSource(): string {\n\t// Resolve the module's own directory in a way that works in both ESM\n\t// (import.meta.url) and CJS (__dirname shim emitted by tsup). At runtime\n\t// the module lives inside dist/, so `../skill/` points at the sibling\n\t// skill/ directory shipped via pkg.files. During vitest tests the module\n\t// executes from src/, and `../skill/` resolves to packages/wallet/skill/.\n\tconst here = dirname(fileURLToPath(import.meta.url));\n\treturn join(here, \"..\", \"skill\", \"keeperhub-wallet.skill.md\");\n}\n\nfunction defaultNotice(msg: string): void {\n\tprocess.stderr.write(`${msg}\\n`);\n}\n\nexport async function registerClaudeCodeHook(\n\tsettingsPath: string,\n\toptions: RegisterClaudeCodeHookOptions = {},\n): Promise<void> {\n\tconst command = options.hookCommand ?? resolveHookCommand();\n\n\tlet raw: string | null = null;\n\ttry {\n\t\traw = await readFile(settingsPath, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tlet config: ClaudeSettings = {};\n\tif (raw !== null) {\n\t\ttry {\n\t\t\tconfig = JSON.parse(raw) as ClaudeSettings;\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`settings.json at ${settingsPath} is not valid JSON; aborting hook registration`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst hooks: Record<string, unknown> =\n\t\ttypeof config.hooks === \"object\" && config.hooks !== null\n\t\t\t? (config.hooks as Record<string, unknown>)\n\t\t\t: {};\n\n\tconst existingPreToolUse = Array.isArray(hooks.PreToolUse)\n\t\t? (hooks.PreToolUse as unknown[])\n\t\t: [];\n\n\t// De-dup: drop only the hooks[] items whose `command` field references\n\t// the keeperhub-wallet-hook bin, leaving sibling commands within the\n\t// same PreToolUse element untouched. Scoped to the `command` field (not\n\t// the full serialised entry) so an unrelated hook that mentions the bin\n\t// name in its matcher or args isn't silently deleted. Covers both the\n\t// bare-bin and version-pinned npx forms, and older versions of this\n\t// installer.\n\tconst filtered: unknown[] = [];\n\tfor (const entry of existingPreToolUse) {\n\t\tconst survivor = filterKeeperhubHooksFromEntry(entry);\n\t\tif (survivor !== null) {\n\t\t\tfiltered.push(survivor);\n\t\t}\n\t}\n\tfiltered.push(buildKeeperhubEntry(command));\n\n\thooks.PreToolUse = filtered;\n\tconfig.hooks = hooks as ClaudeSettings[\"hooks\"];\n\n\tawait mkdir(dirname(settingsPath), { recursive: true, mode: 0o700 });\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\t// Atomic write: tmp + rename so a crash mid-write cannot truncate the\n\t// user's settings.json (which on Claude Code typically holds many\n\t// unrelated PreToolUse/PostToolUse entries plus mcpServers, theme, etc.).\n\t// rename(2) is the atomic step on every POSIX filesystem and on Windows\n\t// NTFS — the live file is either the old contents or the full new\n\t// contents, never partial. chmod the tmp before rename so the live\n\t// permissions are correct from the first readable byte.\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;\n\ttry {\n\t\tawait writeFile(tmpPath, payload, { mode: 0o600 });\n\t\tawait chmod(tmpPath, 0o600);\n\t\tawait rename(tmpPath, settingsPath);\n\t} catch (err) {\n\t\tawait unlink(tmpPath).catch(() => {\n\t\t\t// best-effort cleanup; ignore ENOENT and other failures\n\t\t});\n\t\tthrow err;\n\t}\n}\n\nasync function writeSkillToAgent(\n\tagent: AgentTarget,\n\tskillSource: string,\n): Promise<{ agent: string; path: string; status: \"written\" | \"skipped\" }> {\n\tawait mkdir(agent.skillsDir, { recursive: true, mode: 0o755 });\n\tconst target = join(agent.skillsDir, \"keeperhub-wallet.skill.md\");\n\tawait copyFile(skillSource, target);\n\tawait chmod(target, 0o644);\n\treturn { agent: agent.agent, path: target, status: \"written\" };\n}\n\nfunction buildHookNoticeMessage(agent: AgentTarget, command: string): string {\n\treturn `${agent.agent} does not support auto-registered PreToolUse hooks; run \\`${command}\\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;\n}\n\nfunction buildMcpNoticeMessage(\n\tagent: AgentTarget,\n\tcommand: { command: string; args: string[] },\n): string {\n\tconst cmd = [command.command, ...command.args].join(\" \");\n\treturn `${agent.agent} does not support auto-registered MCP servers; add an entry named \\`keeperhub-wallet\\` running \\`${cmd}\\` to your MCP config manually`;\n}\n\n/**\n * Install the keeperhub-wallet skill plus the PreToolUse safety hook plus\n * the keeperhub-wallet MCP server into every detected agent.\n *\n * Per-agent flow:\n * 1. Copy `keeperhub-wallet.skill.md` into the agent's `skills/` dir.\n * 2. If the agent supports PreToolUse hooks (claude-code), register the\n * safety hook in `settings.json`. Otherwise print a stderr notice.\n * 3. If the agent supports MCP server registration (claude-code, cursor,\n * windsurf, opencode), register the keeperhub-wallet MCP server in the\n * agent's MCP config file (claude.json / mcp.json / opencode.json).\n * Otherwise print a stderr notice.\n *\n * MCP idempotency is automatic: each agent's MCP config keys servers by name,\n * so a re-run overwrites the existing `keeperhub-wallet` entry rather than\n * appending a duplicate. All other keys are byte-preserved.\n */\nexport async function installSkill(\n\toptions: InstallOptions = {},\n): Promise<InstallResult> {\n\tconst agents = detectAgents(options.homeOverride);\n\tconst skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();\n\tconst onNotice = options.onNotice ?? defaultNotice;\n\t// Resolve once per install run so the bare-vs-npx decision stays\n\t// consistent across every detected agent. Tests pass an explicit value to\n\t// pin the assertion shape regardless of host PATH.\n\tconst hookCommand = options.hookCommand ?? resolveHookCommand();\n\tconst mcpCommand = options.mcpCommand ?? resolveMcpCommand();\n\n\tconst skillWrites: InstallResult[\"skillWrites\"] = [];\n\tconst hookRegistrations: InstallResult[\"hookRegistrations\"] = [];\n\tconst mcpRegistrations: InstallResult[\"mcpRegistrations\"] = [];\n\n\t// Per-agent error isolation. A single agent's settings file being\n\t// permission-locked or otherwise unwritable used to abort the whole\n\t// install loop with a raw stack trace, leaving the user in a partial\n\t// state (skill copied, hook maybe registered, MCP maybe not) and\n\t// without a returned `InstallResult` to inspect. Catch at the per-step\n\t// boundary so the loop completes for every agent and the caller gets\n\t// a structured `failed` entry with the error message.\n\tfor (const agent of agents) {\n\t\ttry {\n\t\t\tconst write = await writeSkillToAgent(agent, skillSource);\n\t\t\tskillWrites.push(write);\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tskillWrites.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tpath: \"\",\n\t\t\t\tstatus: \"skipped\",\n\t\t\t});\n\t\t\tonNotice(`${agent.agent}: skill copy failed (${message})`);\n\t\t\t// If we couldn't even write the skill, the rest of the agent's\n\t\t\t// install is meaningless. Skip to the next agent.\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (agent.hookSupport === \"claude-code\") {\n\t\t\ttry {\n\t\t\t\tawait registerClaudeCodeHook(agent.settingsFile, { hookCommand });\n\t\t\t\thookRegistrations.push({\n\t\t\t\t\tagent: agent.agent,\n\t\t\t\t\tstatus: \"registered\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\thookRegistrations.push({\n\t\t\t\t\tagent: agent.agent,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\tmessage,\n\t\t\t\t});\n\t\t\t\tonNotice(`${agent.agent}: hook registration failed (${message})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconst noticeMessage = buildHookNoticeMessage(agent, hookCommand);\n\t\t\thookRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"notice\",\n\t\t\t\tmessage: noticeMessage,\n\t\t\t});\n\t\t\tonNotice(noticeMessage);\n\t\t}\n\n\t\tif (agent.mcpSupport === \"notice\") {\n\t\t\tconst noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"notice\",\n\t\t\t\tmessage: noticeMessage,\n\t\t\t});\n\t\t\tonNotice(noticeMessage);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst mcpResult = await registerMcpServer(agent, {\n\t\t\t\thomeOverride: options.homeOverride,\n\t\t\t\tcommand: mcpCommand,\n\t\t\t});\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"registered\",\n\t\t\t\tpath: mcpResult.path,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tmcpRegistrations.push({\n\t\t\t\tagent: agent.agent,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage,\n\t\t\t});\n\t\t\tonNotice(`${agent.agent}: MCP registration failed (${message})`);\n\t\t}\n\t}\n\n\treturn { skillWrites, hookRegistrations, mcpRegistrations };\n}\n","// MCP server registration: writes a `keeperhub-wallet` entry into each\n// detected agent's MCP config file. Mirrors the patterns in skill-install.ts\n// (registerClaudeCodeHook), with two important divergences:\n//\n// 1. Idempotency is automatic. MCP entries are NAME-keyed objects\n// (`{ mcpServers: { \"keeperhub-wallet\": {...} } }`), not positional\n// arrays like PreToolUse. Re-running overwrites the existing entry; we\n// never need a de-dup matcher.\n//\n// 2. The opencode shape is divergent: instead of\n// `{command, args, env}` it stores\n// `{type:\"local\", command:[command,...args], enabled:true, environment}`\n// under a top-level `mcp` key. We branch on `target.mcpSupport` and\n// write the appropriate shape.\n//\n// Preservation rule: every other top-level key in the config file MUST be\n// byte-preserved. We read the file, parse, mutate only our slot, and write\n// back. Same chmod 0o600 semantics as registerClaudeCodeHook.\n\nimport { randomBytes } from \"node:crypto\";\nimport {\n\tchmod,\n\tmkdir,\n\treadFile,\n\trename,\n\tunlink,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { AgentTarget } from \"./agent-detect.js\";\nimport { resolveBinCommand } from \"./runtime-detect.js\";\n\nconst MCP_BIN = \"keeperhub-wallet-mcp\";\n\n/** The literal name our MCP server registers under. Stable across all agents. */\nexport const MCP_SERVER_NAME = \"keeperhub-wallet\";\n\nexport type McpCommand = {\n\tcommand: string;\n\targs: string[];\n\t/** Optional environment overrides. Currently unused but reserved for future flags. */\n\tenv?: Record<string, string>;\n};\n\n/**\n * Resolve the MCP launch command. Returns the bare bin if it lives at a\n * stable PATH location, otherwise a version-pinned npx fallback. Identical\n * decision logic to {@link resolveHookCommand} — both flows go through\n * runtime-detect.ts.\n *\n * Override-able via `KEEPERHUB_WALLET_MCP_COMMAND` (env-var input is trusted\n * — it is written verbatim into the MCP config and executed by the agent).\n */\nexport function resolveMcpCommand(): McpCommand {\n\tconst envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;\n\tif (envOverride && envOverride.length > 0) {\n\t\t// Env override is a single string; split on whitespace into command + args.\n\t\tconst parts = envOverride.trim().split(/\\s+/);\n\t\tconst head = parts[0] ?? envOverride;\n\t\treturn { command: head, args: parts.slice(1) };\n\t}\n\tconst resolved = resolveBinCommand(MCP_BIN);\n\treturn { command: resolved.command, args: resolved.args };\n}\n\nexport type RegisterMcpServerOptions = {\n\t/** Override $HOME (tests). */\n\thomeOverride?: string;\n\t/** Override the launch command (tests, monorepo setups). */\n\tcommand?: McpCommand;\n};\n\nexport type RegisterMcpServerResult = {\n\t/** Absolute path of the MCP config file that was written. */\n\tpath: string;\n\t/** Server name written into the config. */\n\tname: typeof MCP_SERVER_NAME;\n};\n\ntype StandardMcpEntry = {\n\tcommand: string;\n\targs: string[];\n\tenv?: Record<string, string>;\n};\n\ntype StandardMcpConfig = {\n\tmcpServers?: Record<string, unknown>;\n\t[k: string]: unknown;\n};\n\ntype OpencodeMcpEntry = {\n\ttype: \"local\";\n\tcommand: string[];\n\tenabled: true;\n\tenvironment: Record<string, string>;\n};\n\ntype OpencodeMcpConfig = {\n\tmcp?: Record<string, unknown>;\n\t[k: string]: unknown;\n};\n\nfunction buildStandardEntry(cmd: McpCommand): StandardMcpEntry {\n\tconst entry: StandardMcpEntry = {\n\t\tcommand: cmd.command,\n\t\targs: cmd.args,\n\t};\n\tif (cmd.env && Object.keys(cmd.env).length > 0) {\n\t\tentry.env = cmd.env;\n\t}\n\treturn entry;\n}\n\nfunction buildOpencodeEntry(cmd: McpCommand): OpencodeMcpEntry {\n\treturn {\n\t\ttype: \"local\",\n\t\tcommand: [cmd.command, ...cmd.args],\n\t\tenabled: true,\n\t\tenvironment: cmd.env ?? {},\n\t};\n}\n\nasync function readJsonOrEmpty<T>(path: string): Promise<T> {\n\tlet raw: string | null = null;\n\ttry {\n\t\traw = await readFile(path, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n\t\t\tthrow err;\n\t\t}\n\t}\n\tif (raw === null) {\n\t\treturn {} as T;\n\t}\n\ttry {\n\t\treturn JSON.parse(raw) as T;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`MCP config at ${path} is not valid JSON; aborting MCP registration`,\n\t\t);\n\t}\n}\n\n/**\n * Truly-atomic write to a config file. Writes to a randomly-suffixed sibling\n * tmp file then `rename(2)`s into place — the rename is the atomic step on\n * every POSIX filesystem and on Windows NTFS, so a crash, signal, ENOSPC,\n * or EIO mid-write CANNOT leave the live config truncated. Critical for\n * `~/.claude.json` (100KB+, holds the user's MCP allowlist + project state)\n * and for any settings.json where a torn write would brick Claude Code.\n *\n * Best-effort cleanup of the tmp file on rename failure so we don't\n * accumulate `*.tmp` siblings. The chmod happens on the tmp before rename\n * so the live file's permissions are correct from the first byte readable.\n */\nasync function writeJsonAtomic(path: string, payload: string): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true, mode: 0o700 });\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${path}.${process.pid}.${suffix}.tmp`;\n\ttry {\n\t\tawait writeFile(tmpPath, payload, { mode: 0o600 });\n\t\tawait chmod(tmpPath, 0o600);\n\t\tawait rename(tmpPath, path);\n\t} catch (err) {\n\t\tawait unlink(tmpPath).catch(() => {\n\t\t\t// best-effort cleanup; ignore ENOENT and other failures\n\t\t});\n\t\tthrow err;\n\t}\n}\n\nasync function writeStandardMcp(\n\tpath: string,\n\tentry: StandardMcpEntry,\n): Promise<void> {\n\tconst config = await readJsonOrEmpty<StandardMcpConfig>(path);\n\tconst servers: Record<string, unknown> =\n\t\ttypeof config.mcpServers === \"object\" && config.mcpServers !== null\n\t\t\t? (config.mcpServers as Record<string, unknown>)\n\t\t\t: {};\n\tservers[MCP_SERVER_NAME] = entry;\n\tconfig.mcpServers = servers;\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\tawait writeJsonAtomic(path, payload);\n}\n\nasync function writeOpencodeMcp(\n\tpath: string,\n\tentry: OpencodeMcpEntry,\n): Promise<void> {\n\tconst config = await readJsonOrEmpty<OpencodeMcpConfig>(path);\n\tconst servers: Record<string, unknown> =\n\t\ttypeof config.mcp === \"object\" && config.mcp !== null\n\t\t\t? (config.mcp as Record<string, unknown>)\n\t\t\t: {};\n\tservers[MCP_SERVER_NAME] = entry;\n\tconfig.mcp = servers;\n\tconst payload = `${JSON.stringify(config, null, 2)}\\n`;\n\tawait writeJsonAtomic(path, payload);\n}\n\n/**\n * Register the keeperhub-wallet MCP server with one detected agent.\n *\n * Behavior by `target.mcpSupport`:\n * - `claude-code` / `cursor` / `windsurf` — write the standard\n * `{ mcpServers: { \"keeperhub-wallet\": {command, args, env?} } }` shape\n * into the agent's MCP config file. All other top-level keys preserved.\n * - `opencode` — write the divergent\n * `{ mcp: { \"keeperhub-wallet\": {type:\"local\", command:[...], enabled,\n * environment} } }` shape.\n * - `notice` — throws. installSkill handles the notice path before calling\n * this function.\n *\n * Idempotent by construction: the `keeperhub-wallet` slot is name-keyed, so\n * re-running overwrites rather than duplicates.\n */\nexport async function registerMcpServer(\n\ttarget: AgentTarget,\n\toptions: RegisterMcpServerOptions = {},\n): Promise<RegisterMcpServerResult> {\n\tif (target.mcpSupport === \"notice\") {\n\t\tthrow new Error(\n\t\t\t`agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`,\n\t\t);\n\t}\n\tif (!target.mcpConfigRel) {\n\t\tthrow new Error(\n\t\t\t`agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`,\n\t\t);\n\t}\n\tconst home = options.homeOverride ?? homedir();\n\tconst path = join(home, ...target.mcpConfigRel);\n\tconst cmd = options.command ?? resolveMcpCommand();\n\n\tif (target.mcpSupport === \"opencode\") {\n\t\tawait writeOpencodeMcp(path, buildOpencodeEntry(cmd));\n\t} else {\n\t\tawait writeStandardMcp(path, buildStandardEntry(cmd));\n\t}\n\n\treturn { path, name: MCP_SERVER_NAME };\n}\n","// Shared runtime / environment detection used by skill-install (hook\n// command resolution) and mcp-register (MCP command resolution).\n//\n// Why split out: both flows need to decide whether the package's bin lives\n// at a stable PATH location (global install, brew, distro pkg, npm link) or\n// inside a transient package-runner cache (npx _npx, pnpm dlx, yarn dlx,\n// bun x). The bare-bin-vs-npx decision is identical between hook and MCP\n// registrations — keep one source of truth so hook and MCP cannot drift.\n\nimport { execFileSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const PACKAGE_NAME = \"@keeperhub/wallet\";\n\n/**\n * Read this package's own version from package.json. Both the hook and\n * MCP installers pin npx invocations to the installer's version so that\n * upgrades are explicit (re-run skill install) and the supply-chain blast\n * radius is bounded to \"code that was already trusted enough to install\".\n *\n * Falls back to \"latest\" only when package.json cannot be located, which\n * should never happen in published builds (dist/ sits next to package.json\n * via pkg.files).\n */\nexport function readPackageVersion(): string {\n\ttry {\n\t\tconst here = dirname(fileURLToPath(import.meta.url));\n\t\tconst pkgPath = join(here, \"..\", \"package.json\");\n\t\tconst raw = readFileSync(pkgPath, \"utf-8\");\n\t\tconst parsed = JSON.parse(raw) as { version?: string };\n\t\tif (typeof parsed.version === \"string\" && parsed.version.length > 0) {\n\t\t\treturn parsed.version;\n\t\t}\n\t} catch {\n\t\t// Fall through.\n\t}\n\treturn \"latest\";\n}\n\n/**\n * Detect whether the current process is being driven by `npx`. npm/npx set\n * `npm_execpath` to the path of the CLI binary that spawned the process; for\n * `npx` invocations that path ends in `npx-cli.js` (or, on Windows, an `npx`\n * shim in node_modules/.bin).\n *\n * Why this matters: when the user runs the install path\n * (`npx -p @keeperhub/wallet keeperhub-wallet skill install`), npx prepends\n * its transient cache dir to PATH for the lifetime of the installer.\n * `command -v <bin>` therefore succeeds inside the installer, but the cache\n * dir disappears from PATH the moment npx exits — and any later spawn (hook\n * fire / MCP launch) would crash with `command not found`.\n */\nexport function isNpxExecution(): boolean {\n\tconst execPath = process.env.npm_execpath;\n\tif (typeof execPath !== \"string\" || execPath.length === 0) {\n\t\treturn false;\n\t}\n\tif (/(?:^|[\\\\/])npx-cli\\.(?:js|cjs|mjs)$/i.test(execPath)) {\n\t\treturn true;\n\t}\n\tif (/(?:^|[\\\\/])npx(?:\\.cmd|\\.exe|\\.ps1)?$/i.test(execPath)) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n// Recognise a path that lives inside any transient package-runner cache.\n// Patterns:\n// _npx — npm/npx cache\n// dlx-<hash> — pnpm dlx staging\n// xfs-<hash> — yarn dlx (Berry) temp project\n// .bun/install/cache — bun x / bunx package cache\nconst TRANSIENT_CACHE_PATTERNS: ReadonlyArray<RegExp> = [\n\t/[\\\\/]_npx[\\\\/]/,\n\t/[\\\\/]dlx-[A-Za-z0-9]+[\\\\/]/,\n\t/[\\\\/]xfs-[A-Za-z0-9]+[\\\\/]/,\n\t/[\\\\/]\\.bun[\\\\/]install[\\\\/]cache[\\\\/]/,\n];\n\nexport function isPathUnderTransientCache(resolvedPath: string): boolean {\n\tfor (const re of TRANSIENT_CACHE_PATTERNS) {\n\t\tif (re.test(resolvedPath)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport type ResolvedCommand = {\n\t/** The literal string that will be written into settings.json's `command` field. */\n\tcommandString: string;\n\t/** Split form (command + args[]) for MCP entry shapes that require it. */\n\tcommand: string;\n\targs: string[];\n};\n\n/**\n * Resolve a bin name to a stable command form. Returns the bare bin if it\n * lives at a durable PATH location, otherwise a version-pinned npx fallback.\n *\n * The single decision logic is shared between the PreToolUse hook installer\n * and the MCP registration installer.\n */\nexport function resolveBinCommand(binName: string): ResolvedCommand {\n\tconst version = readPackageVersion();\n\tconst npxArgs = [\"-y\", \"-p\", `${PACKAGE_NAME}@${version}`, binName];\n\tconst npxCommandString = `npx ${npxArgs.join(\" \")}`;\n\n\tif (isNpxExecution()) {\n\t\treturn {\n\t\t\tcommandString: npxCommandString,\n\t\t\tcommand: \"npx\",\n\t\t\targs: npxArgs,\n\t\t};\n\t}\n\n\ttry {\n\t\tconst resolved = execFileSync(\"/bin/sh\", [\"-c\", `command -v ${binName}`], {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t})\n\t\t\t.toString()\n\t\t\t.trim();\n\t\tif (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {\n\t\t\treturn {\n\t\t\t\tcommandString: binName,\n\t\t\t\tcommand: binName,\n\t\t\t\targs: [],\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// command -v failed: bin not on PATH at all. Fall through.\n\t}\n\n\treturn {\n\t\tcommandString: npxCommandString,\n\t\tcommand: \"npx\",\n\t\targs: npxArgs,\n\t};\n}\n","import { buildHmacHeaders } from \"./hmac.js\";\nimport { KeeperHubError, type WalletConfig } from \"./types.js\";\n\nexport type ClientOptions = {\n /** Defaults to process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\" */\n baseUrl?: string;\n /** Injected for tests; defaults to global fetch */\n fetch?: typeof fetch;\n};\n\n/**\n * 202 ask-tier envelope returned by /sign and /approval-request when the\n * risk classifier routes a request to the ask queue. Callers poll\n * `/api/agentic-wallet/approval-request/:id` until status !== \"pending\".\n */\nexport type AskTierResponse = {\n _status: 202;\n approvalRequestId: string;\n};\n\nconst TRAILING_SLASH = /\\/$/;\n\nfunction defaultCodeForStatus(status: number): string {\n if (status === 401) {\n return \"HMAC_INVALID\";\n }\n if (status === 403) {\n return \"POLICY_BLOCKED\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 502) {\n return \"TURNKEY_UPSTREAM\";\n }\n return `HTTP_${status}`;\n}\n\n/**\n * HMAC-signed HTTP client for the KeeperHub agentic-wallet API surface.\n * Every request to /api/agentic-wallet/* (except /provision, which uses\n * the session cookie) flows through this class.\n *\n * @security No logging of headers, body, or response bodies. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced in CI).\n */\nexport class KeeperHubClient {\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly wallet: WalletConfig;\n\n constructor(wallet: WalletConfig, opts: ClientOptions = {}) {\n this.wallet = wallet;\n const envBase = process.env.KEEPERHUB_API_URL;\n this.baseUrl = (\n opts.baseUrl ??\n envBase ??\n \"https://app.keeperhub.com\"\n ).replace(TRAILING_SLASH, \"\");\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n }\n\n /**\n * HMAC-signed POST/GET to any /api/agentic-wallet/* route except\n * /provision. Path MUST start with a leading slash. Body is\n * JSON.stringify'd (or the empty string for GET).\n *\n * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,\n * message)` where `code` is the server-supplied field or the default\n * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,\n * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an\n * AskTierResponse envelope.\n */\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown\n ): Promise<T | AskTierResponse> {\n const bodyStr = body === undefined ? \"\" : JSON.stringify(body);\n const hmacHeaders = buildHmacHeaders(\n this.wallet.hmacSecret,\n method,\n path,\n this.wallet.subOrgId,\n bodyStr\n );\n const headers: Record<string, string> =\n method === \"POST\"\n ? { ...hmacHeaders, \"content-type\": \"application/json\" }\n : { ...hmacHeaders };\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: method === \"POST\" ? bodyStr : undefined,\n });\n\n if (response.status === 202) {\n const data = (await response.json()) as {\n approvalRequestId: string;\n status: string;\n };\n return { _status: 202, approvalRequestId: data.approvalRequestId };\n }\n\n if (!response.ok) {\n let code = \"UNKNOWN\";\n let message = `HTTP ${response.status}`;\n try {\n const data = (await response.json()) as {\n code?: string;\n error?: string;\n };\n code = data.code ?? defaultCodeForStatus(response.status);\n message = data.error ?? message;\n } catch {\n // body is not JSON -- keep the default code + message\n }\n throw new KeeperHubError(code, message);\n }\n\n return (await response.json()) as T;\n }\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * User-owned safety config at ~/.keeperhub/safety.json. File mode 0o644 so the\n * user can freely edit thresholds and the allowlist; server-side Turnkey policy\n * remains the authoritative hard cap (GUARD-06).\n */\nexport type SafetyConfig = {\n auto_approve_max_usd: number;\n ask_threshold_usd: number;\n block_threshold_usd: number;\n allowlisted_contracts: string[];\n};\n\n/**\n * Defaults per 34-CONTEXT lines 61-68. Thresholds bracket the Turnkey policy\n * hard cap (100 USDC). Allowlisted contracts mirror the server Turnkey policy\n * allowlist (lib/agentic-wallet/policy.ts FACILITATOR_ALLOWLIST) -- lowercased\n * for case-insensitive match against tool_input.to / paymentChallenge.payTo.\n */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n auto_approve_max_usd: 5,\n ask_threshold_usd: 50,\n block_threshold_usd: 100,\n allowlisted_contracts: [\n \"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\", // Base USDC\n \"0x20c000000000000000000000b9537d11c60e8b50\", // Tempo USDC.e\n ],\n};\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"safety.json\")`\n// itself -- matches storage.ts. Hoisting to a module-level constant would\n// freeze $HOME at import time and break tests that override process.env.HOME\n// in beforeEach.\n\nfunction getSafetyPath(): string {\n return join(homedir(), \".keeperhub\", \"safety.json\");\n}\n\nexport async function loadSafetyConfig(): Promise<SafetyConfig> {\n const path = getSafetyPath();\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeFile(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {\n mode: 0o644,\n });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(path, 0o644);\n return DEFAULT_SAFETY_CONFIG;\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<SafetyConfig>;\n return validateAndMerge(parsed);\n}\n\nconst THRESHOLD_KEYS = [\n \"auto_approve_max_usd\",\n \"ask_threshold_usd\",\n \"block_threshold_usd\",\n] as const;\n\nexport function validateAndMerge(partial: Partial<SafetyConfig>): SafetyConfig {\n const merged: SafetyConfig = {\n auto_approve_max_usd:\n partial.auto_approve_max_usd ??\n DEFAULT_SAFETY_CONFIG.auto_approve_max_usd,\n ask_threshold_usd:\n partial.ask_threshold_usd ?? DEFAULT_SAFETY_CONFIG.ask_threshold_usd,\n block_threshold_usd:\n partial.block_threshold_usd ?? DEFAULT_SAFETY_CONFIG.block_threshold_usd,\n allowlisted_contracts:\n partial.allowlisted_contracts ??\n DEFAULT_SAFETY_CONFIG.allowlisted_contracts,\n };\n\n for (const key of THRESHOLD_KEYS) {\n const v = merged[key];\n if (!(Number.isFinite(v) && v >= 0)) {\n throw new Error(\n `safety.json: ${key} must be a non-negative finite number; got ${String(v)}`\n );\n }\n }\n if (merged.ask_threshold_usd < merged.auto_approve_max_usd) {\n throw new Error(\n \"safety.json: ask_threshold_usd must be >= auto_approve_max_usd\"\n );\n }\n if (merged.block_threshold_usd < merged.ask_threshold_usd) {\n throw new Error(\n \"safety.json: block_threshold_usd must be >= ask_threshold_usd\"\n );\n }\n if (!Array.isArray(merged.allowlisted_contracts)) {\n throw new Error(\"safety.json: allowlisted_contracts must be an array\");\n }\n merged.allowlisted_contracts = merged.allowlisted_contracts.map((a) =>\n a.toLowerCase()\n );\n return merged;\n}\n\nexport function getSafetyConfigPath(): string {\n return getSafetyPath();\n}\n","import { loadSafetyConfig, type SafetyConfig } from \"./safety-config.js\";\nimport type { HookDecision } from \"./types.js\";\n\ntype HookInput = {\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n};\n\nexport type CreateHookOptions = {\n /** Match against tool_name. Default: /keeperhub|wallet|sign/i */\n toolNameMatcher?: (name: string) => boolean;\n /** Injected for tests */\n configLoader?: () => Promise<SafetyConfig>;\n};\n\nconst USDC_DECIMALS = 1_000_000;\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst MICRO_USDC_RE = /^\\d+$/;\nconst DEFAULT_TOOL_RE = /keeperhub|wallet|sign/i;\n\nfunction defaultToolMatcher(name: string): boolean {\n return DEFAULT_TOOL_RE.test(name);\n}\n\n/**\n * Detect whether a tool input carries any payment shape — the only kind\n * of call this hook is meant to gate. Free admin reads in the keeperhub\n * MCP namespace (e.g. mcp__plugin_keeperhub_keeperhub__get_execution_logs,\n * unlist_workflow, list_workflows) match the broad name regex but have\n * no amount, no contract, no challenge — there is nothing for the safety\n * gate to evaluate, so the hook should pass them through rather than\n * deny with AMOUNT_UNDETERMINED.\n *\n * A call is \"payment-shaped\" when at least one of these is present:\n * - tool_input.paymentChallenge (object)\n * - tool_input.amount (any non-null value)\n * - tool_input.unit (any non-null value)\n * - tool_input.to / contract / assetAddress (only when the value\n * looks like an EVM address; non-address strings such as Discord\n * channel ids are not payment indicators)\n *\n * This complements the existing tool-name regex: the regex still gates\n * what the hook will *consider*, but presence of any payment field is\n * required before the safety thresholds actually run. Callers that pass\n * the regex but provide no payment context (the bug surfaced live with\n * mcp__plugin_keeperhub_keeperhub__unlist_workflow during marketplace\n * testing on 2026-05-01) are now allowed through.\n */\nfunction hasPaymentShape(input: HookInput): boolean {\n const ti = input.tool_input ?? {};\n const challenge = ti.paymentChallenge;\n if (challenge !== undefined && challenge !== null) {\n return true;\n }\n if (ti.amount !== undefined && ti.amount !== null) {\n return true;\n }\n if (ti.unit !== undefined && ti.unit !== null) {\n return true;\n }\n for (const field of [\"to\", \"contract\", \"assetAddress\"] as const) {\n const v = ti[field];\n if (typeof v === \"string\" && ADDRESS_RE.test(v)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Coerce an amount field to micro-USDC. Inputs MUST be explicitly tagged with\n * `unit`:\n * - `{amount: string, unit: \"microUsdc\"}` -> parsed as integer micro-USDC\n * (x402 wire format)\n * - `{amount: number, unit: \"usd\"}` -> multiplied by 1_000_000\n *\n * Untagged amounts are REJECTED with a thrown TypeError. This is GUARD-05:\n * we refuse to guess whether a \"5\" is 5 USD or 5 micro-USDC (a six-order-of-\n * magnitude reading error). The caller must commit.\n *\n * Fields read: ONLY tool_input.paymentChallenge.{amount,unit} and\n * tool_input.{amount,unit}. Forged safety-bypass fields (any \"trust-level\"\n * hint, \"is-safe\" boolean, \"admin-override\" bit, or similar) are NEVER read;\n * thresholds come exclusively from ~/.keeperhub/safety.json.\n */\nfunction extractAmountMicroUsdc(input: HookInput): bigint | null {\n const ti = input.tool_input ?? {};\n const challenge = (ti.paymentChallenge ?? {}) as Record<string, unknown>;\n // WR-01: prefer the signed wire field (paymentChallenge.amount/unit) over\n // caller-supplied sibling tool_input fields. The nested challenge is the\n // field the downstream /sign call actually binds into the signed bytes, so\n // a misbehaving tool cannot slip a larger nested amount past the auto cap\n // by shadowing it with a small top-level sibling. Fall back to top-level\n // only when no challenge is present (e.g. direct /sign tool calls with no\n // 402 round).\n const directAmount = challenge.amount ?? ti.amount;\n const directUnit = challenge.unit ?? ti.unit;\n\n if (directAmount === undefined || directAmount === null) {\n return null;\n }\n if (directUnit !== \"usd\" && directUnit !== \"microUsdc\") {\n throw new TypeError(\n `Amount input must be tagged with unit:\"usd\" or unit:\"microUsdc\"; got unit=${JSON.stringify(directUnit)}. GUARD-05 refuses to guess - specify explicitly.`\n );\n }\n if (directUnit === \"microUsdc\") {\n if (\n !(typeof directAmount === \"string\" && MICRO_USDC_RE.test(directAmount))\n ) {\n throw new TypeError(\n `unit:\"microUsdc\" requires amount as a non-negative integer string; got ${typeof directAmount}`\n );\n }\n return BigInt(directAmount);\n }\n // unit === \"usd\"\n if (\n !(\n typeof directAmount === \"number\" &&\n Number.isFinite(directAmount) &&\n directAmount >= 0\n )\n ) {\n throw new TypeError(\n `unit:\"usd\" requires amount as a finite non-negative number; got ${typeof directAmount}`\n );\n }\n return BigInt(Math.round(directAmount * USDC_DECIMALS));\n}\n\nfunction extractContractAddress(input: HookInput): string | null {\n const ti = input.tool_input ?? {};\n const challenge = (ti.paymentChallenge ?? {}) as Record<string, unknown>;\n // Precedence order:\n // 1. challenge.asset -- x402 TransferWithAuthorization: the ERC-20 contract\n // the authorization is bound to (the EVM `eth.tx.to` at execution time).\n // This mirrors the server-side Turnkey policy (policy.ts) which denies\n // `eth.tx.to not in [USDC_BASE, USDC_TEMPO]`.\n // 2. ti.contract / ti.assetAddress -- agent-runtime-supplied hints.\n // 3. ti.to / challenge.to -- legacy tool_inputs that labeled the asset as\n // \"to\" (some older MCP implementations). Kept for backwards compat.\n // NEVER reads challenge.payTo: that is the transfer recipient (the\n // facilitator or service operator), not the ERC-20 contract being invoked.\n const contract =\n challenge.asset ??\n ti.contract ??\n ti.assetAddress ??\n ti.to ??\n challenge.to;\n if (typeof contract === \"string\" && ADDRESS_RE.test(contract)) {\n return contract.toLowerCase();\n }\n return null;\n}\n\nfunction usdToMicro(usd: number): bigint {\n return BigInt(Math.round(usd * USDC_DECIMALS));\n}\n\n/**\n * Factory returning the PreToolUse hook function. The hook enforces three\n * client-side safety tiers (auto / ask / block) sourced EXCLUSIVELY from\n * ~/.keeperhub/safety.json -- never from the tool payload (GUARD-05).\n *\n * v0.1.4 collapsed the previous four-band behaviour into three:\n *\n * amount <= auto_approve_max_usd -> {decision: \"allow\"}\n * auto_approve_max_usd < amount <= block_threshold -> {decision: \"ask\"} (Claude Code prompts user inline)\n * amount > block_threshold -> {decision: \"deny\"}\n *\n * The previous server-approval branch (amount >= ask_threshold -> create a\n * /api/agentic-wallet/approval-request row, print an approval URL, poll for\n * browser approval) was removed. It required the wallet to be linked to a\n * KeeperHub user via /link, and the link command was rough enough that we\n * never wired it into the documented flow. Returning {decision: \"ask\"}\n * inline lets Claude Code surface the prompt in the agent chat directly.\n *\n * `ask_threshold_usd` is still read from safety.json for backward-compat\n * with existing configs but is not consulted for decision-making. Tracked\n * as KEEP-307 for the permanent architectural decision.\n */\nexport async function createPreToolUseHook(\n options: CreateHookOptions = {}\n): Promise<(input: unknown) => Promise<HookDecision>> {\n const toolMatcher = options.toolNameMatcher ?? defaultToolMatcher;\n const configLoader = options.configLoader ?? loadSafetyConfig;\n\n const safety = await configLoader();\n\n // The hook function is declared async so that synchronous throws in\n // extractAmountMicroUsdc (GUARD-05 unit-tag enforcement) become rejected\n // promises, matching the original pre-0.1.4 behaviour the tests rely on.\n return async (raw: unknown): Promise<HookDecision> => {\n const hookInput = (raw ?? {}) as HookInput;\n\n // Pass-through for non-wallet tool calls.\n if (\n !(\n typeof hookInput.tool_name === \"string\" &&\n toolMatcher(hookInput.tool_name)\n )\n ) {\n return { decision: \"allow\" };\n }\n\n // Pass-through for tool calls that match the wallet name regex but\n // carry no payment context whatsoever. The default matcher\n // /keeperhub|wallet|sign/i catches every keeperhub MCP tool by name\n // (call_workflow, list_workflows, unlist_workflow,\n // get_execution_logs, ...), but the bulk of those are free reads\n // and admin operations with no amount, no contract, no challenge.\n // Without this gate they would all fall through to the\n // AMOUNT_UNDETERMINED branch below and DENY, breaking ordinary\n // marketplace use of any agent that has the safety hook installed.\n if (!hasPaymentShape(hookInput)) {\n return { decision: \"allow\" };\n }\n\n // GUARD-05: ONLY these fields. No trust/override/admin_* reads.\n const contractAddr = extractContractAddress(hookInput);\n const amountMicro = extractAmountMicroUsdc(hookInput);\n\n if (contractAddr && !safety.allowlisted_contracts.includes(contractAddr)) {\n return { decision: \"deny\", reason: \"CONTRACT_NOT_ALLOWLISTED\" };\n }\n\n if (amountMicro === null) {\n return { decision: \"deny\", reason: \"AMOUNT_UNDETERMINED\" };\n }\n\n const blockMicro = usdToMicro(safety.block_threshold_usd);\n const autoMicro = usdToMicro(safety.auto_approve_max_usd);\n\n if (amountMicro > blockMicro) {\n return { decision: \"deny\", reason: \"BLOCKED_BY_SAFETY_RULE\" };\n }\n\n if (amountMicro <= autoMicro) {\n return { decision: \"allow\" };\n }\n\n // Everything between auto and block is an inline ask -- Claude Code\n // surfaces the prompt in-chat.\n return { decision: \"ask\" };\n };\n}\n","import { createPreToolUseHook } from \"./hook.js\";\n\n/**\n * Binary entrypoint for `npx @keeperhub/wallet hook` or direct invocation via\n * Claude Code settings.json:\n *\n * { \"type\": \"command\", \"command\": \"npx @keeperhub/wallet hook\", \"timeout\": 30 }\n *\n * Reads JSON from stdin (Claude Code PreToolUse payload), writes the JSON\n * decision envelope to stdout, and exits 2 on deny (the universal \"block\"\n * signal across agent-hook runtimes per the Claude Code docs). A non-JSON\n * stdin is treated as a deny.\n *\n * @security Stdout is RESERVED for the envelope JSON; any diagnostic output\n * (approval URL, errors) goes to stderr via onAskOpen or direct writes.\n */\nexport async function runHookCli(): Promise<void> {\n const hook = await createPreToolUseHook();\n\n let raw = \"\";\n for await (const chunk of process.stdin as unknown as AsyncIterable<Buffer>) {\n raw += chunk.toString(\"utf-8\");\n }\n\n let parsed: unknown;\n try {\n parsed = raw.trim().length > 0 ? JSON.parse(raw) : {};\n } catch (err) {\n process.stderr.write(\n `[keeperhub-wallet] hook input is not valid JSON: ${(err as Error).message}\\n`\n );\n process.exit(2);\n }\n\n const decision = await hook(parsed);\n\n const output = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\" as const,\n permissionDecision: decision.decision,\n ...(decision.reason ? { permissionDecisionReason: decision.reason } : {}),\n },\n };\n process.stdout.write(JSON.stringify(output));\n process.exit(decision.decision === \"deny\" ? 2 : 0);\n}\n","// Source: lib/payments/router.ts:152-175 -- MPP WWW-Authenticate emission.\n// We forward the raw serialized challenge to /api/agentic-wallet/sign; the server\n// has mppx in its deps. Keeps client runtime dep list minimal (supply-chain T-34-02).\n\nexport type MppChallenge = { serialized: string };\n\nconst MPP_PREFIX = \"Payment \";\n\nexport function parseMppChallenge(response: Response): MppChallenge | null {\n const header = response.headers.get(\"WWW-Authenticate\");\n if (!header) {\n return null;\n }\n if (!header.startsWith(MPP_PREFIX)) {\n return null;\n }\n const serialized = header.slice(MPP_PREFIX.length).trim();\n if (serialized.length === 0) {\n return null;\n }\n return { serialized };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { KeeperHubClient } from \"./client.js\";\nimport { type MppChallenge, parseMppChallenge } from \"./mpp-detect.js\";\nimport { readWalletConfig } from \"./storage.js\";\nimport { KeeperHubError, type PaymentHint, type WalletConfig } from \"./types.js\";\nimport { extractKeeperHubWorkflowSlug } from \"./workflow-slug.js\";\nimport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n\n// Tempo mainnet chain id. Forwarded to /sign so the server routes MPP\n// challenges to the correct signer. Kept in sync with\n// app/api/agentic-wallet/sign/route.ts::TEMPO_CHAIN_ID.\nconst TEMPO_CHAIN_ID = 4217;\n\n// Approval polling: 2s * 150 = 5 minute ceiling on a human response.\n// T-34-ps-04 mitigation (DoS via infinite loop).\nconst DEFAULT_APPROVAL_POLL = { intervalMs: 2000, maxAttempts: 150 };\n\n// Small clock-drift buffer on validAfter. Mirrors the server's\n// VALID_AFTER_FUTURE_SLACK_SECONDS in app/api/agentic-wallet/sign/route.ts.\nconst VALID_AFTER_PAST_SLACK_SECONDS = 60;\n\n// x402 protocol nonce: 32-byte hex (bytes32).\nconst NONCE_BYTES = 32;\n\n/**\n * Polymorphic /sign response. For `chain:\"base\"` the signature is a 132-char\n * 0x-prefixed EIP-712 hex string embedded inside the PAYMENT-SIGNATURE\n * base64-JSON payload. For `chain:\"tempo\"` it is a base64url-encoded MPP\n * credential produced by the server's mppx instance; the client forwards it\n * verbatim as the `Authorization: Payment <signature>` value. The client\n * never parses, decodes, or mutates the MPP credential -- opaque pass-through.\n */\ntype SignResponseOk = { signature: string };\n\ntype ApprovalStatus = \"pending\" | \"approved\" | \"rejected\";\n\ntype PaySignerOptions = {\n /** Override wallet loader (primarily for tests). */\n walletLoader?: () => Promise<WalletConfig>;\n /** Override KeeperHubClient factory (tests inject a mocked fetch). */\n clientFactory?: (wallet: WalletConfig) => KeeperHubClient;\n /** Replayed fetch (tests intercept the retry). */\n fetchImpl?: typeof fetch;\n /** Approval polling override: interval + max attempts. */\n approval?: { intervalMs: number; maxAttempts: number };\n};\n\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n /**\n * Per-call protocol preference. \"x402\" forces Base USDC; \"mpp\" forces Tempo\n * USDC.e; \"auto\" (default, also the behaviour when omitted) uses x402 when\n * offered, MPP otherwise. Throws KeeperHubError(\"X402_NOT_OFFERED\") or\n * KeeperHubError(\"MPP_NOT_OFFERED\") when the requested protocol is absent\n * from the challenge (KEEP-361).\n */\n paymentHint?: PaymentHint;\n};\n\n/** RequestInit extended with paymentHint for per-call protocol selection. */\nexport type FetchInit = RequestInit & { paymentHint?: PaymentHint };\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n *\n * Pass `init.paymentHint` to force a specific payment protocol for this\n * call. Omitting it is equivalent to `paymentHint: \"auto\"` (x402-first).\n */\n fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Pure function that decides which payment protocol to use given challenge\n * availability and caller's hint. Exported for unit testing.\n *\n * Returns \"x402\" or \"mpp\" to direct the caller to the appropriate path,\n * or null when hint is \"auto\" and no challenge is present (pay() then\n * returns the original 402 response unchanged). Throws KeeperHubError with\n * a specific code when the requested protocol is unavailable (KEEP-361).\n */\nexport function selectProtocol(\n x402: X402Challenge | null,\n mpp: MppChallenge | null,\n hint: PaymentHint | undefined\n): \"x402\" | \"mpp\" | null {\n const h = hint ?? \"auto\";\n if (h === \"x402\") {\n if (!x402) {\n throw new KeeperHubError(\n \"X402_NOT_OFFERED\",\n \"x402 is not offered by this endpoint\"\n );\n }\n return \"x402\";\n }\n if (h === \"mpp\") {\n if (!mpp) {\n throw new KeeperHubError(\n \"MPP_NOT_OFFERED\",\n \"mpp is not offered by this endpoint\"\n );\n }\n return \"mpp\";\n }\n // h === \"auto\": x402-first, then MPP, then null\n if (x402) return \"x402\";\n if (mpp) return \"mpp\";\n return null;\n}\n\nexport function createPaymentSigner(\n opts: PaySignerOptions = {}\n): PaymentSigner {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const walletLoader = opts.walletLoader ?? readWalletConfig;\n const clientFactory =\n opts.clientFactory ??\n ((wallet: WalletConfig): KeeperHubClient =>\n new KeeperHubClient(wallet, { fetch: fetchImpl }));\n const pollCfg = opts.approval ?? DEFAULT_APPROVAL_POLL;\n\n async function signOrPoll(\n client: KeeperHubClient,\n body: Record<string, unknown>\n ): Promise<string> {\n const result = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in result && result._status === 202) {\n const approvalRequestId = result.approvalRequestId;\n // Poll approval-request until status !== \"pending\" or timeout.\n for (let attempt = 0; attempt < pollCfg.maxAttempts; attempt++) {\n await sleep(pollCfg.intervalMs);\n const status = await client.request<{ status: ApprovalStatus }>(\n \"GET\",\n `/api/agentic-wallet/approval-request/${approvalRequestId}`\n );\n if (\"status\" in status && status.status !== \"pending\") {\n if (status.status === \"rejected\") {\n throw new KeeperHubError(\n \"APPROVAL_REJECTED\",\n \"User rejected the operation\"\n );\n }\n // approved -- retry the sign call (which should now return 200).\n const retry = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in retry) {\n throw new KeeperHubError(\n \"APPROVAL_LOOP\",\n \"Sign returned 202 again after approval\"\n );\n }\n return retry.signature;\n }\n }\n throw new KeeperHubError(\n \"APPROVAL_TIMEOUT\",\n `No human response within ${pollCfg.intervalMs * pollCfg.maxAttempts}ms`\n );\n }\n return (result as SignResponseOk).signature;\n }\n\n async function payViaMpp(\n response: Response,\n mpp: MppChallenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // x402 v2 PaymentPayload per @x402/core mechanisms-* d.ts:\n // { x402Version: 2, accepted: PaymentRequirements, payload: {...} }\n // The server's findMatchingRequirements does a deepEqual between\n // `paymentPayload.accepted` and each challenge `accepts[]` entry, so we\n // mirror the exact accept object we signed against.\n //\n // EIP-3009 inner payload: authorization.value/validAfter/validBefore/nonce\n // must be STRINGS at the wire format (per @x402/evm ExactEIP3009Payload).\n // /sign takes them as numbers; we stringify on the way out.\n const paymentSigPayload = {\n x402Version: 2,\n accepted: accept,\n payload: {\n signature,\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n },\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // Protocol selection is delegated to selectProtocol(). Default \"auto\"\n // preserves x402-first behavior. Per-call override via paymentHint\n // option (KEEP-361). Submit EXACTLY ONE credential (T-34-ps-02).\n const protocol = selectProtocol(x402, mpp, options?.paymentHint);\n if (protocol === \"x402\") {\n return payViaX402(response, x402 as X402Challenge, wallet, options);\n }\n if (protocol === \"mpp\") {\n return payViaMpp(response, mpp as MppChallenge, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: FetchInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method + paymentHint to the\n // post-sign retry so the paid workflow receives the same payload on the\n // retry as on the 402 attempt. Fixes the dropped-body bug that made any\n // workflow with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n paymentHint: init?.paymentHint,\n });\n },\n };\n}\n\n// Default instance backed by the real fetch + storage.\nexport const paymentSigner: PaymentSigner = createPaymentSigner();\n","// Server-derived payTo binding (Phase 37 fix #2 in keeperhub repo).\n//\n// The wallet only signs payments for KeeperHub-registered workflows. The\n// resource URL of the 402 challenge is matched against the canonical\n// /api/mcp/workflows/<slug>/call pattern; the slug is forwarded to /sign so\n// the server can verify payTo + amount against the workflows registry.\n//\n// URLs that don't match this pattern (e.g. arbitrary x402 services discovered\n// in the wild) are unsupported in v0.1.5 — the signer throws\n// UNSUPPORTED_RECIPIENT and refuses to round-trip. KEEP-311's generic 402\n// fetch CLI is a separate codepath with its own threat model.\n\nconst KEEPERHUB_WORKFLOW_RE =\n /\\/api\\/mcp\\/workflows\\/([a-zA-Z0-9_-]+)\\/call(?:\\/?)(?:\\?|$|#)/;\n\nexport type SlugExtractionResult =\n | { ok: true; slug: string }\n | { ok: false; reason: \"EMPTY_URL\" | \"URL_PATTERN_MISMATCH\" };\n\nexport function extractKeeperHubWorkflowSlug(\n url: string | null | undefined\n): SlugExtractionResult {\n if (!url || url.length === 0) {\n return { ok: false, reason: \"EMPTY_URL\" };\n }\n const match = KEEPERHUB_WORKFLOW_RE.exec(url);\n if (!match || !match[1]) {\n return { ok: false, reason: \"URL_PATTERN_MISMATCH\" };\n }\n return { ok: true, slug: match[1] };\n}\n","// Source: lib/payments/router.ts:48-62 (PaymentRequiredV2 server-side shape).\n// Strict parsing per 34-RESEARCH Pitfall 4 -- false-positive 402 detection is a\n// wasted /sign HMAC roundtrip and a potential agent-loop trigger.\n\nexport type X402Challenge = {\n x402Version: 2;\n accepts: Array<{\n scheme: \"exact\";\n network: string;\n asset: string;\n amount: string;\n payTo: string;\n maxTimeoutSeconds: number;\n extra: Record<string, unknown>;\n }>;\n resource: { url: string; description: string; mimeType: string };\n};\n\nfunction isX402Shape(value: unknown): value is X402Challenge {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (v.x402Version !== 2) {\n return false;\n }\n if (!Array.isArray(v.accepts) || v.accepts.length === 0) {\n return false;\n }\n const first = v.accepts[0] as Record<string, unknown>;\n if (first.scheme !== \"exact\") {\n return false;\n }\n return true;\n}\n\nexport async function parseX402Challenge(\n response: Response\n): Promise<X402Challenge | null> {\n // Header path (preferred -- matches lib/payments/router.ts's PAYMENT-REQUIRED emit).\n const headerB64 = response.headers.get(\"PAYMENT-REQUIRED\");\n if (headerB64) {\n try {\n const decoded: unknown = JSON.parse(\n Buffer.from(headerB64, \"base64\").toString(\"utf-8\")\n );\n if (isX402Shape(decoded)) {\n return decoded;\n }\n } catch {\n // fall through to body\n }\n }\n\n // Body path (lib/payments/router.ts also emits the PaymentRequired as the 402 body).\n try {\n const clone = response.clone();\n const body: unknown = await clone.json();\n if (isX402Shape(body)) {\n return body;\n }\n } catch {\n // not JSON\n }\n return null;\n}\n"],"mappings":"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","names":["homedir","dirname","join","join","homedir","dirname","randomBytes","chmod","mkdir","readFile","rename","unlink","writeFile","dirname","join","fileURLToPath","randomBytes","chmod","mkdir","readFile","rename","writeFile","homedir","dirname","join","dirname","join","readFile","mkdir","dirname","randomBytes","writeFile","chmod","rename","homedir","join","dirname","fileURLToPath","join","readFile","mkdir","randomBytes","writeFile","chmod","rename","unlink","TRAILING_SLASH","chmod","mkdir","readFile","writeFile","homedir","dirname","join","USDC_DECIMALS","randomBytes","randomBytes"]}