@keeperhub/wallet 0.1.3 → 0.1.5

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/hmac.ts","../src/skill-install.ts","../src/storage.ts","../src/types.ts","../src/client.ts","../src/safety-config.ts","../src/hook.ts","../src/hook-entrypoint.ts","../src/mpp-detect.ts","../src/payment-signer.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 agent: \"claude-code\" | \"cursor\" | \"cline\" | \"windsurf\" | \"opencode\";\n skillsDir: string;\n settingsFile: string;\n hookSupport: \"claude-code\" | \"notice\";\n};\n\ntype AgentSpec = {\n agent: AgentTarget[\"agent\"];\n skillsRel: string[];\n settingsRel: string[];\n hookSupport: AgentTarget[\"hookSupport\"];\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 {\n agent: \"claude-code\",\n skillsRel: [\".claude\", \"skills\"],\n settingsRel: [\".claude\", \"settings.json\"],\n hookSupport: \"claude-code\",\n },\n {\n agent: \"cursor\",\n skillsRel: [\".cursor\", \"skills\"],\n settingsRel: [\".cursor\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"cline\",\n skillsRel: [\".cline\", \"skills\"],\n settingsRel: [\".cline\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"windsurf\",\n skillsRel: [\".windsurf\", \"skills\"],\n settingsRel: [\".windsurf\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"opencode\",\n skillsRel: [\".config\", \"opencode\", \"skills\"],\n settingsRel: [\".config\", \"opencode\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n];\n\nexport function detectAgents(homeOverride?: string): AgentTarget[] {\n const home = homeOverride ?? homedir();\n const results: AgentTarget[] = [];\n for (const spec of AGENT_SPECS) {\n const skillsDir = join(home, ...spec.skillsRel);\n const settingsFile = join(home, ...spec.settingsRel);\n // \"Detected\" iff the parent of skills/ exists (e.g. ~/.claude/).\n // skills/ itself may be absent; installer creates it.\n if (existsSync(dirname(skillsDir))) {\n results.push({\n agent: spec.agent,\n skillsDir,\n settingsFile,\n hookSupport: spec.hookSupport,\n });\n }\n }\n return 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 5 subcommands:\n// add (provision -- NO auth), link (HMAC + KH_SESSION_COOKIE dual-proof),\n// fund (pure string-build Coinbase Onramp + Tempo address), balance (Base\n// USDC + Tempo USDC.e), info (print subOrgId + walletAddress from\n// ~/.keeperhub/wallet.json).\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 { installSkill } from \"./skill-install.js\";\nimport {\n getWalletConfigPath,\n readWalletConfig,\n writeWalletConfig,\n} from \"./storage.js\";\nimport { WalletConfigMissingError } from \"./types.js\";\n\nconst TRAILING_SLASH = /\\/$/;\nconst WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;\n\nfunction resolveBaseUrl(override: string | undefined): string {\n const candidate =\n override ?? process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n return candidate.replace(TRAILING_SLASH, \"\");\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nfunction provisionInvalidError(\n message: string\n): Error & { code: \"PROVISION_RESPONSE_INVALID\" } {\n const err = new Error(message) as Error & {\n code: \"PROVISION_RESPONSE_INVALID\";\n };\n err.code = \"PROVISION_RESPONSE_INVALID\";\n return err;\n}\n\nfunction validateProvisionResponse(data: unknown): {\n subOrgId: string;\n walletAddress: `0x${string}`;\n hmacSecret: string;\n} {\n if (typeof data !== \"object\" || data === null) {\n throw provisionInvalidError(\"provision response is not an object\");\n }\n const { subOrgId, walletAddress, hmacSecret } = data as Record<\n string,\n unknown\n >;\n if (\n !(\n isNonEmptyString(subOrgId) &&\n isNonEmptyString(walletAddress) &&\n isNonEmptyString(hmacSecret)\n )\n ) {\n throw provisionInvalidError(\n \"provision response missing subOrgId, walletAddress, or hmacSecret\"\n );\n }\n if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {\n throw provisionInvalidError(\n `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`\n );\n }\n return {\n subOrgId,\n walletAddress: walletAddress as `0x${string}`,\n hmacSecret,\n };\n}\n\nasync function cmdAdd(opts: { baseUrl?: string } = {}): Promise<void> {\n const baseUrl = resolveBaseUrl(opts.baseUrl);\n const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: \"{}\",\n });\n if (!response.ok) {\n const text = await response.text();\n process.stderr.write(\n `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}\\n`\n );\n process.exit(1);\n }\n const raw = (await response.json()) as unknown;\n const data = validateProvisionResponse(raw);\n await writeWalletConfig({\n subOrgId: data.subOrgId,\n walletAddress: data.walletAddress,\n hmacSecret: data.hmacSecret,\n });\n // Intentionally print only public fields. The hmacSecret is written to\n // wallet.json (chmod 0o600) but never printed -- T-34-cli-02 mitigation.\n process.stdout.write(`subOrgId: ${data.subOrgId}\\n`);\n process.stdout.write(`walletAddress: ${data.walletAddress}\\n`);\n process.stdout.write(`config written to ${getWalletConfigPath()}\\n`);\n}\n\nasync function cmdLink(opts: { baseUrl?: string } = {}): Promise<void> {\n const wallet = await readWalletConfig();\n const baseUrl = resolveBaseUrl(opts.baseUrl);\n const sessionCookie = process.env.KH_SESSION_COOKIE;\n if (!sessionCookie) {\n process.stderr.write(\n \"[keeperhub-wallet] link requires KH_SESSION_COOKIE env var.\\n\" +\n \"Sign in at app.keeperhub.com, copy the session cookie, and re-run with:\\n\" +\n \" KH_SESSION_COOKIE='<cookie>' npx @keeperhub/wallet link\\n\"\n );\n process.exit(1);\n }\n const body = JSON.stringify({ subOrgId: wallet.subOrgId });\n const headers = buildHmacHeaders(\n wallet.hmacSecret,\n \"POST\",\n \"/api/agentic-wallet/link\",\n wallet.subOrgId,\n body\n );\n const response = await fetch(`${baseUrl}/api/agentic-wallet/link`, {\n method: \"POST\",\n headers: {\n ...headers,\n \"content-type\": \"application/json\",\n cookie: sessionCookie,\n },\n body,\n });\n const json = (await response.json().catch(() => ({}))) as {\n ok?: boolean;\n already?: boolean;\n error?: string;\n code?: string;\n };\n if (!response.ok) {\n process.stderr.write(\n `[keeperhub-wallet] link failed: ${json.code ?? response.status}: ${json.error ?? \"\"}\\n`\n );\n process.exit(1);\n }\n if (json.already) {\n process.stdout.write(\"already linked\\n\");\n return;\n }\n process.stdout.write(\"linked\\n\");\n}\n\nasync function cmdFund(): Promise<void> {\n const wallet = await readWalletConfig();\n const out = fund(wallet.walletAddress);\n process.stdout.write(`${out.coinbaseOnrampUrl}\\n`);\n process.stdout.write(`Tempo address: ${out.tempoAddress}\\n`);\n process.stdout.write(`${out.disclaimer}\\n`);\n}\n\nasync function cmdBalance(): Promise<void> {\n const wallet = await readWalletConfig();\n const snap = await checkBalance(wallet);\n process.stdout.write(`Base USDC: ${snap.base.amount}\\n`);\n process.stdout.write(`Tempo USDC.e: ${snap.tempo.amount}\\n`);\n}\n\nasync function cmdInfo(): Promise<void> {\n const wallet = await readWalletConfig();\n process.stdout.write(`subOrgId: ${wallet.subOrgId}\\n`);\n process.stdout.write(`walletAddress: ${wallet.walletAddress}\\n`);\n}\n\nexport async function runCli(argv: string[] = process.argv): Promise<void> {\n const program = new Command();\n program\n .name(\"keeperhub-wallet\")\n .description(\n \"KeeperHub agentic wallet CLI (auto-pay x402 + MPP 402 responses)\"\n )\n .version(\"0.1.3\");\n\n program\n .command(\"add\")\n .description(\"Provision a new agentic wallet (no account required)\")\n .option(\"--base-url <url>\", \"KeeperHub API base URL\")\n .action(async (opts: { baseUrl?: string }) => {\n await cmdAdd(opts);\n });\n\n program\n .command(\"link\")\n .description(\n \"Link the current wallet to your KeeperHub account (requires KH_SESSION_COOKIE env)\"\n )\n .option(\"--base-url <url>\", \"KeeperHub API base URL\")\n .action(async (opts: { baseUrl?: string }) => {\n await cmdLink(opts);\n });\n\n program\n .command(\"fund\")\n .description(\n \"Print Coinbase Onramp URL (Base USDC) and Tempo deposit address\"\n )\n .action(async () => {\n await cmdFund();\n });\n\n program\n .command(\"balance\")\n .description(\"Print on-chain balance: Base USDC + Tempo USDC.e\")\n .action(async () => {\n await cmdBalance();\n });\n\n program\n .command(\"info\")\n .description(\"Print subOrgId and walletAddress from local config\")\n .action(async () => {\n await cmdInfo();\n });\n\n program\n .command(\"skill\")\n .description(\n \"Install the KeeperHub skill file into detected agent directories\"\n )\n .addCommand(\n new Command(\"install\")\n .description(\n \"Write skill file + register PreToolUse hook in all detected agents\"\n )\n .action(async () => {\n const result = await installSkill();\n for (const write of result.skillWrites) {\n process.stdout.write(\n `skill: ${write.agent} -> ${write.path} (${write.status})\\n`\n );\n }\n for (const reg of result.hookRegistrations) {\n if (reg.status === \"registered\") {\n process.stdout.write(\n `hook: ${reg.agent} -> PreToolUse registered\\n`\n );\n } else if (reg.status === \"notice\") {\n process.stderr.write(\n `notice: ${reg.agent} -> ${reg.message ?? \"\"}\\n`\n );\n }\n }\n if (result.skillWrites.length === 0) {\n process.stderr.write(\n \"No supported agent skill directories detected under $HOME. Create ~/.claude/, ~/.cursor/, ~/.cline/, ~/.windsurf/, or ~/.config/opencode/ and re-run.\\n\"\n );\n }\n })\n );\n\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof WalletConfigMissingError) {\n process.stderr.write(`[keeperhub-wallet] ${err.message}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n `[keeperhub-wallet] ${(err as Error).message ?? String(err)}\\n`\n );\n process.exit(1);\n }\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","// 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) -- pure settings.json patcher\n// used internally; exported so tests can drive it directly.\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//\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 { chmod, copyFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type AgentTarget, detectAgents } from \"./agent-detect.js\";\n\nconst HOOK_COMMAND = \"keeperhub-wallet-hook\";\n// Match rule for de-dup: any existing PreToolUse entry whose JSON form\n// mentions this string is considered \"ours\" and is removed before append.\nconst KEEPERHUB_HOOK_MARKER = \"keeperhub-wallet-hook\";\n\nexport type InstallResult = {\n skillWrites: Array<{\n agent: string;\n path: string;\n status: \"written\" | \"skipped\";\n }>;\n hookRegistrations: Array<{\n agent: string;\n status: \"registered\" | \"notice\" | \"skipped\";\n message?: string;\n }>;\n};\n\nexport type InstallOptions = {\n homeOverride?: string;\n skillSourcePath?: string;\n onNotice?: (msg: string) => void;\n};\n\ntype ClaudeHookEntry = {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n};\n\ntype ClaudeSettings = {\n hooks?: {\n PreToolUse?: unknown[];\n [k: string]: unknown;\n };\n [k: string]: unknown;\n};\n\nfunction buildKeeperhubEntry(): ClaudeHookEntry {\n return {\n matcher: \"*\",\n hooks: [{ type: \"command\", command: HOOK_COMMAND }],\n };\n}\n\nfunction resolveDefaultSkillSource(): string {\n // Resolve the module's own directory in a way that works in both ESM\n // (import.meta.url) and CJS (__dirname shim emitted by tsup). At runtime\n // the module lives inside dist/, so `../skill/` points at the sibling\n // skill/ directory shipped via pkg.files. During vitest tests the module\n // executes from src/, and `../skill/` resolves to packages/wallet/skill/.\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, \"..\", \"skill\", \"keeperhub-wallet.skill.md\");\n}\n\nfunction defaultNotice(msg: string): void {\n process.stderr.write(`${msg}\\n`);\n}\n\nexport async function registerClaudeCodeHook(\n settingsPath: string\n): Promise<void> {\n let raw: string | null = null;\n try {\n raw = await readFile(settingsPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw err;\n }\n }\n\n let config: ClaudeSettings = {};\n if (raw !== null) {\n try {\n config = JSON.parse(raw) as ClaudeSettings;\n } catch {\n throw new Error(\n `settings.json at ${settingsPath} is not valid JSON; aborting hook registration`\n );\n }\n }\n\n const hooks: Record<string, unknown> =\n typeof config.hooks === \"object\" && config.hooks !== null\n ? (config.hooks as Record<string, unknown>)\n : {};\n\n const existingPreToolUse = Array.isArray(hooks.PreToolUse)\n ? (hooks.PreToolUse as unknown[])\n : [];\n\n // De-dup: drop any element that references keeperhub-wallet-hook in its\n // serialised form. Covers both exact-shape matches and any legacy\n // representations we may have written in earlier versions.\n const filtered: unknown[] = [];\n for (const entry of existingPreToolUse) {\n const serialised = JSON.stringify(entry);\n if (!serialised.includes(KEEPERHUB_HOOK_MARKER)) {\n filtered.push(entry);\n }\n }\n filtered.push(buildKeeperhubEntry());\n\n hooks.PreToolUse = filtered;\n config.hooks = hooks as ClaudeSettings[\"hooks\"];\n\n await mkdir(dirname(settingsPath), { recursive: true, mode: 0o700 });\n const payload = `${JSON.stringify(config, null, 2)}\\n`;\n await writeFile(settingsPath, payload, { mode: 0o600 });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(settingsPath, 0o600);\n}\n\nasync function writeSkillToAgent(\n agent: AgentTarget,\n skillSource: string\n): Promise<{ agent: string; path: string; status: \"written\" | \"skipped\" }> {\n await mkdir(agent.skillsDir, { recursive: true, mode: 0o755 });\n const target = join(agent.skillsDir, \"keeperhub-wallet.skill.md\");\n await copyFile(skillSource, target);\n await chmod(target, 0o644);\n return { agent: agent.agent, path: target, status: \"written\" };\n}\n\nfunction buildNoticeMessage(agent: AgentTarget): string {\n return `${agent.agent} does not support auto-registered PreToolUse hooks; run \\`${HOOK_COMMAND}\\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;\n}\n\nexport async function installSkill(\n options: InstallOptions = {}\n): Promise<InstallResult> {\n const agents = detectAgents(options.homeOverride);\n const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();\n const onNotice = options.onNotice ?? defaultNotice;\n\n const skillWrites: InstallResult[\"skillWrites\"] = [];\n const hookRegistrations: InstallResult[\"hookRegistrations\"] = [];\n\n for (const agent of agents) {\n const write = await writeSkillToAgent(agent, skillSource);\n skillWrites.push(write);\n\n if (agent.hookSupport === \"claude-code\") {\n await registerClaudeCodeHook(agent.settingsFile);\n hookRegistrations.push({\n agent: agent.agent,\n status: \"registered\",\n });\n } else {\n const message = buildNoticeMessage(agent);\n hookRegistrations.push({\n agent: agent.agent,\n status: \"notice\",\n message,\n });\n onNotice(message);\n }\n }\n\n return { skillWrites, hookRegistrations };\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { type WalletConfig, WalletConfigMissingError } 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 const walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n let raw: string;\n try {\n raw = await readFile(walletPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new WalletConfigMissingError();\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<WalletConfig>;\n if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {\n throw new Error(`Malformed wallet.json at ${walletPath}`);\n }\n return parsed as WalletConfig;\n}\n\nexport async function writeWalletConfig(config: WalletConfig): Promise<void> {\n const walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n await mkdir(dirname(walletPath), { recursive: true, mode: 0o700 });\n await writeFile(walletPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(walletPath, 0o600);\n}\n\nexport function getWalletConfigPath(): string {\n return join(homedir(), \".keeperhub\", \"wallet.json\");\n}\n","// Shared types across the package. Phase 34.\nexport type WalletConfig = {\n /** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */\n subOrgId: string;\n /** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */\n walletAddress: `0x${string}`;\n /** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */\n hmacSecret: string;\n};\n\nexport type HmacHeaders = {\n \"X-KH-Sub-Org\": string;\n \"X-KH-Timestamp\": string;\n \"X-KH-Signature\": string;\n};\n\nexport type HookDecision = {\n decision: \"allow\" | \"deny\" | \"ask\";\n reason?: string;\n};\n\nexport class KeeperHubError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string) {\n super(message);\n this.name = \"KeeperHubError\";\n this.code = code;\n }\n}\n\nexport class WalletConfigMissingError extends Error {\n constructor() {\n super(\n \"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision.\"\n );\n this.name = \"WalletConfigMissingError\";\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 { KeeperHubClient } from \"./client.js\";\nimport { loadSafetyConfig, type SafetyConfig } from \"./safety-config.js\";\nimport { readWalletConfig } from \"./storage.js\";\nimport type { HookDecision, WalletConfig } 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 walletLoader?: () => Promise<WalletConfig>;\n /** Injected for tests */\n configLoader?: () => Promise<SafetyConfig>;\n /** Injected for tests */\n clientFactory?: (w: WalletConfig) => KeeperHubClient;\n /**\n * Called when the ask tier opens an approval URL. Default: write to stderr\n * (stdout is reserved for the Claude Code hook JSON output).\n */\n onAskOpen?: (url: string) => void;\n /** Polling config for the ask tier */\n poll?: { intervalMs: number; maxAttempts: number };\n};\n\nconst DEFAULT_POLL = { intervalMs: 2000, maxAttempts: 150 } as const;\nconst APPROVAL_URL_BASE = \"https://app.keeperhub.com/approve/\";\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 * 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 the three\n * client-side safety tiers (auto / ask / block) sourced EXCLUSIVELY from\n * ~/.keeperhub/safety.json -- never from the tool payload (GUARD-05).\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 const walletLoader = options.walletLoader ?? readWalletConfig;\n const clientFactory =\n options.clientFactory ?? ((w: WalletConfig) => new KeeperHubClient(w));\n const onAskOpen =\n options.onAskOpen ??\n ((url: string): void => {\n process.stderr.write(\n `\\n[keeperhub-wallet] Approval required. Visit: ${url}\\n`\n );\n });\n const poll = options.poll ?? DEFAULT_POLL;\n\n const safety = await configLoader();\n\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 // 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 askMicro = usdToMicro(safety.ask_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 >= askMicro) {\n // Open approval flow (create approval-request + poll until non-pending).\n const wallet = await walletLoader();\n const client = clientFactory(wallet);\n // Server returns `{id}` on create; poll endpoint returns `{status}`.\n const created = await client.request<{\n id: string;\n }>(\"POST\", \"/api/agentic-wallet/approval-request\", {\n // Server contract (Phase 33): riskLevel MUST be 'ask' or 'block';\n // operationPayload MUST be a non-array object. The hook only creates\n // approval-requests at the ask tier (block tier short-circuits above).\n riskLevel: \"ask\",\n operationPayload: {\n amountMicroUsdc: amountMicro.toString(),\n contractAddress: contractAddr ?? \"\",\n toolName: hookInput.tool_name ?? \"\",\n reason: `Agent tool ${hookInput.tool_name}`,\n },\n });\n const approvalId =\n \"_status\" in created ? created.approvalRequestId : created.id;\n onAskOpen(`${APPROVAL_URL_BASE}${approvalId}`);\n for (let attempt = 0; attempt < poll.maxAttempts; attempt++) {\n await new Promise<void>((r) => setTimeout(r, poll.intervalMs));\n const status = await client.request<{\n status: \"pending\" | \"approved\" | \"rejected\";\n }>(\"GET\", `/api/agentic-wallet/approval-request/${approvalId}`);\n // WR-03: align with payment-signer.ts:84 -- positive-shape check on\n // the expected envelope. If a future server change or proxy returns\n // a 202/other wrapper without `status`, keep polling instead of\n // treating an unknown envelope as \"pending\" by implication.\n if (!(\"status\" in status)) {\n continue;\n }\n if (status.status === \"approved\") {\n return { decision: \"allow\" };\n }\n if (status.status === \"rejected\") {\n return { decision: \"deny\", reason: \"USER_REJECTED\" };\n }\n // status === \"pending\" -- continue polling.\n }\n return { decision: \"deny\", reason: \"APPROVAL_TIMEOUT\" };\n }\n\n if (amountMicro <= autoMicro) {\n return { decision: \"allow\" };\n }\n\n // Middle band: above auto but below ask.\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 WalletConfig } from \"./types.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\nexport type PaymentSigner = {\n pay: (response: Response) => Promise<Response>;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((resolve) => setTimeout(resolve, ms));\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 ): Promise<Response> {\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n // POST is correct for v0.1.0 target (KeeperHub paid workflows per\n // 34-CONTEXT scope). Custom HTTP methods can be added via retryOptions in\n // a later release; do NOT read X-Replay-Method here.\n return fetchImpl(response.url, {\n method: \"POST\",\n headers: { Authorization: `Payment ${signature}` },\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig\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 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 paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // Build the PAYMENT-SIGNATURE header: base64(JSON({payload.authorization:\n // {from,to,value,validAfter,validBefore,nonce},signature})) in the exact\n // shape lib/x402/payment-gate.ts::extractPayerAddress decodes.\n const paymentSigPayload = {\n payload: {\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n signature,\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n // POST is correct for v0.1.0 target (KeeperHub paid workflows). Custom\n // methods can be added via retryOptions in a later release; do NOT read\n // X-Replay-Method here.\n return fetchImpl(retryUrl, {\n method: \"POST\",\n headers: { \"PAYMENT-SIGNATURE\": paymentSigHeader },\n });\n }\n\n return {\n async pay(response: Response): 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 // PAY-03: prefer MPP when both present. Submit EXACTLY ONE credential.\n // Early return on the MPP branch guarantees payViaX402 is unreachable\n // when both challenges are offered (T-34-ps-02 mitigation).\n // Semantic rule: `if (mpp) return payViaMpp(...)` takes precedence\n // over `if (x402) return payViaX402(...)` -- no dual-protocol submission.\n if (mpp) {\n return payViaMpp(response, mpp, wallet);\n }\n if (x402) {\n return payViaX402(response, x402, wallet);\n }\n return response;\n },\n };\n}\n\n// Default instance backed by the real fetch + storage.\nexport const paymentSigner: PaymentSigner = createPaymentSigner();\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":["dirname","join","dirname","join","chmod","mkdir","readFile","writeFile","homedir","dirname","join","join","homedir","readFile","mkdir","dirname","writeFile","chmod","TRAILING_SLASH","chmod","mkdir","readFile","writeFile","homedir","dirname","join","USDC_DECIMALS"]}
1
+ {"version":3,"sources":["../src/agent-detect.ts","../src/balance.ts","../src/chains.ts","../src/cli.ts","../src/fund.ts","../src/skill-install.ts","../src/storage.ts","../src/types.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 agent: \"claude-code\" | \"cursor\" | \"cline\" | \"windsurf\" | \"opencode\";\n skillsDir: string;\n settingsFile: string;\n hookSupport: \"claude-code\" | \"notice\";\n};\n\ntype AgentSpec = {\n agent: AgentTarget[\"agent\"];\n skillsRel: string[];\n settingsRel: string[];\n hookSupport: AgentTarget[\"hookSupport\"];\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 {\n agent: \"claude-code\",\n skillsRel: [\".claude\", \"skills\"],\n settingsRel: [\".claude\", \"settings.json\"],\n hookSupport: \"claude-code\",\n },\n {\n agent: \"cursor\",\n skillsRel: [\".cursor\", \"skills\"],\n settingsRel: [\".cursor\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"cline\",\n skillsRel: [\".cline\", \"skills\"],\n settingsRel: [\".cline\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"windsurf\",\n skillsRel: [\".windsurf\", \"skills\"],\n settingsRel: [\".windsurf\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n {\n agent: \"opencode\",\n skillsRel: [\".config\", \"opencode\", \"skills\"],\n settingsRel: [\".config\", \"opencode\", \"settings.json\"],\n hookSupport: \"notice\",\n },\n];\n\nexport function detectAgents(homeOverride?: string): AgentTarget[] {\n const home = homeOverride ?? homedir();\n const results: AgentTarget[] = [];\n for (const spec of AGENT_SPECS) {\n const skillsDir = join(home, ...spec.skillsRel);\n const settingsFile = join(home, ...spec.settingsRel);\n // \"Detected\" iff the parent of skills/ exists (e.g. ~/.claude/).\n // skills/ itself may be absent; installer creates it.\n if (existsSync(dirname(skillsDir))) {\n results.push({\n agent: spec.agent,\n skillsDir,\n settingsFile,\n hookSupport: spec.hookSupport,\n });\n }\n }\n return 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 { installSkill } from \"./skill-install.js\";\nimport {\n getWalletConfigPath,\n readWalletConfig,\n writeWalletConfig,\n} from \"./storage.js\";\nimport { WalletConfigMissingError } from \"./types.js\";\n\nconst TRAILING_SLASH = /\\/$/;\nconst WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;\n\nfunction resolveBaseUrl(override: string | undefined): string {\n const candidate =\n override ?? process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n return candidate.replace(TRAILING_SLASH, \"\");\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nfunction provisionInvalidError(\n message: string\n): Error & { code: \"PROVISION_RESPONSE_INVALID\" } {\n const err = new Error(message) as Error & {\n code: \"PROVISION_RESPONSE_INVALID\";\n };\n err.code = \"PROVISION_RESPONSE_INVALID\";\n return err;\n}\n\nfunction validateProvisionResponse(data: unknown): {\n subOrgId: string;\n walletAddress: `0x${string}`;\n hmacSecret: string;\n} {\n if (typeof data !== \"object\" || data === null) {\n throw provisionInvalidError(\"provision response is not an object\");\n }\n const { subOrgId, walletAddress, hmacSecret } = data as Record<\n string,\n unknown\n >;\n if (\n !(\n isNonEmptyString(subOrgId) &&\n isNonEmptyString(walletAddress) &&\n isNonEmptyString(hmacSecret)\n )\n ) {\n throw provisionInvalidError(\n \"provision response missing subOrgId, walletAddress, or hmacSecret\"\n );\n }\n if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {\n throw provisionInvalidError(\n `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`\n );\n }\n return {\n subOrgId,\n walletAddress: walletAddress as `0x${string}`,\n hmacSecret,\n };\n}\n\nasync function cmdAdd(opts: { baseUrl?: string } = {}): Promise<void> {\n const baseUrl = resolveBaseUrl(opts.baseUrl);\n const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: \"{}\",\n });\n if (!response.ok) {\n const text = await response.text();\n process.stderr.write(\n `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}\\n`\n );\n process.exit(1);\n }\n const raw = (await response.json()) as unknown;\n const data = validateProvisionResponse(raw);\n await writeWalletConfig({\n subOrgId: data.subOrgId,\n walletAddress: data.walletAddress,\n hmacSecret: data.hmacSecret,\n });\n // Intentionally print only public fields. The hmacSecret is written to\n // wallet.json (chmod 0o600) but never printed -- T-34-cli-02 mitigation.\n process.stdout.write(`subOrgId: ${data.subOrgId}\\n`);\n process.stdout.write(`walletAddress: ${data.walletAddress}\\n`);\n process.stdout.write(`config written to ${getWalletConfigPath()}\\n`);\n}\n\nasync function cmdFund(): Promise<void> {\n const wallet = await readWalletConfig();\n const out = fund(wallet.walletAddress);\n process.stdout.write(`${out.coinbaseOnrampUrl}\\n`);\n process.stdout.write(`Tempo address: ${out.tempoAddress}\\n`);\n process.stdout.write(`${out.disclaimer}\\n`);\n}\n\nasync function cmdBalance(): Promise<void> {\n const wallet = await readWalletConfig();\n const snap = await checkBalance(wallet);\n process.stdout.write(`Base USDC: ${snap.base.amount}\\n`);\n process.stdout.write(`Tempo USDC.e: ${snap.tempo.amount}\\n`);\n}\n\nasync function cmdInfo(): Promise<void> {\n const wallet = await readWalletConfig();\n process.stdout.write(`subOrgId: ${wallet.subOrgId}\\n`);\n process.stdout.write(`walletAddress: ${wallet.walletAddress}\\n`);\n}\n\nexport async function runCli(argv: string[] = process.argv): Promise<void> {\n const program = new Command();\n program\n .name(\"keeperhub-wallet\")\n .description(\n \"KeeperHub agentic wallet CLI (auto-pay x402 + MPP 402 responses)\"\n )\n .version(\"0.1.3\");\n\n program\n .command(\"add\")\n .description(\"Provision a new agentic wallet (no account required)\")\n .option(\"--base-url <url>\", \"KeeperHub API base URL\")\n .action(async (opts: { baseUrl?: string }) => {\n await cmdAdd(opts);\n });\n\n program\n .command(\"fund\")\n .description(\n \"Print Coinbase Onramp URL (Base USDC) and Tempo deposit address\"\n )\n .action(async () => {\n await cmdFund();\n });\n\n program\n .command(\"balance\")\n .description(\"Print on-chain balance: Base USDC + Tempo USDC.e\")\n .action(async () => {\n await cmdBalance();\n });\n\n program\n .command(\"info\")\n .description(\"Print subOrgId and walletAddress from local config\")\n .action(async () => {\n await cmdInfo();\n });\n\n program\n .command(\"skill\")\n .description(\n \"Install the KeeperHub skill file into detected agent directories\"\n )\n .addCommand(\n new Command(\"install\")\n .description(\n \"Write skill file + register PreToolUse hook in all detected agents\"\n )\n .action(async () => {\n const result = await installSkill();\n for (const write of result.skillWrites) {\n process.stdout.write(\n `skill: ${write.agent} -> ${write.path} (${write.status})\\n`\n );\n }\n for (const reg of result.hookRegistrations) {\n if (reg.status === \"registered\") {\n process.stdout.write(\n `hook: ${reg.agent} -> PreToolUse registered\\n`\n );\n } else if (reg.status === \"notice\") {\n process.stderr.write(\n `notice: ${reg.agent} -> ${reg.message ?? \"\"}\\n`\n );\n }\n }\n if (result.skillWrites.length === 0) {\n process.stderr.write(\n \"No supported agent skill directories detected under $HOME. Create ~/.claude/, ~/.cursor/, ~/.cline/, ~/.windsurf/, or ~/.config/opencode/ and re-run.\\n\"\n );\n }\n })\n );\n\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof WalletConfigMissingError) {\n process.stderr.write(`[keeperhub-wallet] ${err.message}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n `[keeperhub-wallet] ${(err as Error).message ?? String(err)}\\n`\n );\n process.exit(1);\n }\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","// 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) -- pure settings.json patcher\n// used internally; exported so tests can drive it directly.\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//\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 { chmod, copyFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type AgentTarget, detectAgents } from \"./agent-detect.js\";\n\nconst HOOK_COMMAND = \"keeperhub-wallet-hook\";\n// Match rule for de-dup: any existing PreToolUse entry whose JSON form\n// mentions this string is considered \"ours\" and is removed before append.\nconst KEEPERHUB_HOOK_MARKER = \"keeperhub-wallet-hook\";\n\nexport type InstallResult = {\n skillWrites: Array<{\n agent: string;\n path: string;\n status: \"written\" | \"skipped\";\n }>;\n hookRegistrations: Array<{\n agent: string;\n status: \"registered\" | \"notice\" | \"skipped\";\n message?: string;\n }>;\n};\n\nexport type InstallOptions = {\n homeOverride?: string;\n skillSourcePath?: string;\n onNotice?: (msg: string) => void;\n};\n\ntype ClaudeHookEntry = {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n};\n\ntype ClaudeSettings = {\n hooks?: {\n PreToolUse?: unknown[];\n [k: string]: unknown;\n };\n [k: string]: unknown;\n};\n\nfunction buildKeeperhubEntry(): ClaudeHookEntry {\n return {\n matcher: \"*\",\n hooks: [{ type: \"command\", command: HOOK_COMMAND }],\n };\n}\n\nfunction resolveDefaultSkillSource(): string {\n // Resolve the module's own directory in a way that works in both ESM\n // (import.meta.url) and CJS (__dirname shim emitted by tsup). At runtime\n // the module lives inside dist/, so `../skill/` points at the sibling\n // skill/ directory shipped via pkg.files. During vitest tests the module\n // executes from src/, and `../skill/` resolves to packages/wallet/skill/.\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, \"..\", \"skill\", \"keeperhub-wallet.skill.md\");\n}\n\nfunction defaultNotice(msg: string): void {\n process.stderr.write(`${msg}\\n`);\n}\n\nexport async function registerClaudeCodeHook(\n settingsPath: string\n): Promise<void> {\n let raw: string | null = null;\n try {\n raw = await readFile(settingsPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw err;\n }\n }\n\n let config: ClaudeSettings = {};\n if (raw !== null) {\n try {\n config = JSON.parse(raw) as ClaudeSettings;\n } catch {\n throw new Error(\n `settings.json at ${settingsPath} is not valid JSON; aborting hook registration`\n );\n }\n }\n\n const hooks: Record<string, unknown> =\n typeof config.hooks === \"object\" && config.hooks !== null\n ? (config.hooks as Record<string, unknown>)\n : {};\n\n const existingPreToolUse = Array.isArray(hooks.PreToolUse)\n ? (hooks.PreToolUse as unknown[])\n : [];\n\n // De-dup: drop any element that references keeperhub-wallet-hook in its\n // serialised form. Covers both exact-shape matches and any legacy\n // representations we may have written in earlier versions.\n const filtered: unknown[] = [];\n for (const entry of existingPreToolUse) {\n const serialised = JSON.stringify(entry);\n if (!serialised.includes(KEEPERHUB_HOOK_MARKER)) {\n filtered.push(entry);\n }\n }\n filtered.push(buildKeeperhubEntry());\n\n hooks.PreToolUse = filtered;\n config.hooks = hooks as ClaudeSettings[\"hooks\"];\n\n await mkdir(dirname(settingsPath), { recursive: true, mode: 0o700 });\n const payload = `${JSON.stringify(config, null, 2)}\\n`;\n await writeFile(settingsPath, payload, { mode: 0o600 });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(settingsPath, 0o600);\n}\n\nasync function writeSkillToAgent(\n agent: AgentTarget,\n skillSource: string\n): Promise<{ agent: string; path: string; status: \"written\" | \"skipped\" }> {\n await mkdir(agent.skillsDir, { recursive: true, mode: 0o755 });\n const target = join(agent.skillsDir, \"keeperhub-wallet.skill.md\");\n await copyFile(skillSource, target);\n await chmod(target, 0o644);\n return { agent: agent.agent, path: target, status: \"written\" };\n}\n\nfunction buildNoticeMessage(agent: AgentTarget): string {\n return `${agent.agent} does not support auto-registered PreToolUse hooks; run \\`${HOOK_COMMAND}\\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;\n}\n\nexport async function installSkill(\n options: InstallOptions = {}\n): Promise<InstallResult> {\n const agents = detectAgents(options.homeOverride);\n const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();\n const onNotice = options.onNotice ?? defaultNotice;\n\n const skillWrites: InstallResult[\"skillWrites\"] = [];\n const hookRegistrations: InstallResult[\"hookRegistrations\"] = [];\n\n for (const agent of agents) {\n const write = await writeSkillToAgent(agent, skillSource);\n skillWrites.push(write);\n\n if (agent.hookSupport === \"claude-code\") {\n await registerClaudeCodeHook(agent.settingsFile);\n hookRegistrations.push({\n agent: agent.agent,\n status: \"registered\",\n });\n } else {\n const message = buildNoticeMessage(agent);\n hookRegistrations.push({\n agent: agent.agent,\n status: \"notice\",\n message,\n });\n onNotice(message);\n }\n }\n\n return { skillWrites, hookRegistrations };\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { type WalletConfig, WalletConfigMissingError } 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 const walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n let raw: string;\n try {\n raw = await readFile(walletPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new WalletConfigMissingError();\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<WalletConfig>;\n if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {\n throw new Error(`Malformed wallet.json at ${walletPath}`);\n }\n return parsed as WalletConfig;\n}\n\nexport async function writeWalletConfig(config: WalletConfig): Promise<void> {\n const walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n await mkdir(dirname(walletPath), { recursive: true, mode: 0o700 });\n await writeFile(walletPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(walletPath, 0o600);\n}\n\nexport function getWalletConfigPath(): string {\n return join(homedir(), \".keeperhub\", \"wallet.json\");\n}\n","// Shared types across the package. Phase 34.\nexport type WalletConfig = {\n /** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */\n subOrgId: string;\n /** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */\n walletAddress: `0x${string}`;\n /** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */\n hmacSecret: string;\n};\n\nexport type HmacHeaders = {\n \"X-KH-Sub-Org\": string;\n \"X-KH-Timestamp\": string;\n \"X-KH-Signature\": string;\n};\n\nexport type HookDecision = {\n decision: \"allow\" | \"deny\" | \"ask\";\n reason?: string;\n};\n\nexport class KeeperHubError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string) {\n super(message);\n this.name = \"KeeperHubError\";\n this.code = code;\n }\n}\n\nexport class WalletConfigMissingError extends Error {\n constructor() {\n super(\n \"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision.\"\n );\n this.name = \"WalletConfigMissingError\";\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 { 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 * 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 // 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 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\nexport type PaymentSigner = {\n pay: (response: Response) => Promise<Response>;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((resolve) => setTimeout(resolve, ms));\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 ): 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 // POST is correct for v0.1.0 target (KeeperHub paid workflows per\n // 34-CONTEXT scope). Custom HTTP methods can be added via retryOptions in\n // a later release; do NOT read X-Replay-Method here.\n return fetchImpl(response.url, {\n method: \"POST\",\n headers: { Authorization: `Payment ${signature}` },\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig\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 // Build the PAYMENT-SIGNATURE header: base64(JSON({payload.authorization:\n // {from,to,value,validAfter,validBefore,nonce},signature})) in the exact\n // shape lib/x402/payment-gate.ts::extractPayerAddress decodes.\n const paymentSigPayload = {\n payload: {\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n signature,\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n // POST is correct for v0.1.0 target (KeeperHub paid workflows). Custom\n // methods can be added via retryOptions in a later release; do NOT read\n // X-Replay-Method here.\n return fetchImpl(retryUrl, {\n method: \"POST\",\n headers: { \"PAYMENT-SIGNATURE\": paymentSigHeader },\n });\n }\n\n return {\n async pay(response: Response): 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 // PAY-03: prefer MPP when both present. Submit EXACTLY ONE credential.\n // Early return on the MPP branch guarantees payViaX402 is unreachable\n // when both challenges are offered (T-34-ps-02 mitigation).\n // Semantic rule: `if (mpp) return payViaMpp(...)` takes precedence\n // over `if (x402) return payViaX402(...)` -- no dual-protocol submission.\n if (mpp) {\n return payViaMpp(response, mpp, wallet);\n }\n if (x402) {\n return payViaX402(response, x402, wallet);\n }\n return response;\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":["dirname","join","dirname","join","chmod","mkdir","readFile","writeFile","homedir","dirname","join","join","homedir","readFile","mkdir","dirname","writeFile","chmod","TRAILING_SLASH","chmod","mkdir","readFile","writeFile","homedir","dirname","join","USDC_DECIMALS"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keeperhub/wallet",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "KeeperHub agentic wallet: auto-pay x402 and MPP 402 responses with a server-side Turnkey proxy.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: keeperhub-wallet
3
3
  description: Pay x402 and MPP 402 responses with a server-proxied Turnkey wallet. Auto-pays Base USDC + Tempo USDC.e. Includes check balance, fund wallet, and three-tier safety hook (auto/ask/block). Install with `npx @keeperhub/wallet skill install`.
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  license: Apache-2.0
6
6
  ---
7
7
 
@@ -46,7 +46,6 @@ Direct npm package invocation:
46
46
  - `npx @keeperhub/wallet add` — provision a new agentic wallet (no KeeperHub account required).
47
47
  - `npx @keeperhub/wallet info` — print `subOrgId` and `walletAddress` for the current wallet.
48
48
  - `npx @keeperhub/wallet fund` — print a Coinbase Onramp URL (Base USDC) and a Tempo deposit address.
49
- - `npx @keeperhub/wallet link` — link the current wallet to a KeeperHub account (requires `KH_SESSION_COOKIE`).
50
49
  - `npx @keeperhub/wallet balance` — print on-chain balance across Base USDC and Tempo USDC.e.
51
50
 
52
51
  Equivalent Go CLI wrappers (thin pass-through; delegate to the npm package):
@@ -54,19 +53,38 @@ Equivalent Go CLI wrappers (thin pass-through; delegate to the npm package):
54
53
  - `kh wallet add`
55
54
  - `kh wallet info`
56
55
  - `kh wallet fund`
57
- - `kh wallet link`
58
56
 
59
57
  ## Safety
60
58
 
61
59
  Three-tier PreToolUse hook enforced on every signing call:
62
60
 
63
- - **auto** — amount below `auto_approve_max_usd` signs without prompting.
64
- - **ask** — amount between `auto_approve_max_usd` and `ask_threshold_usd` surfaces an approval prompt in-chat.
65
- - **block** — amount above `block_threshold_usd` or contract not in `allowlisted_contracts` denies outright.
61
+ - **auto** — amount at or below `auto_approve_max_usd` signs without prompting.
62
+ - **ask** — amount above `auto_approve_max_usd` and at or below `block_threshold_usd` returns `{decision: "ask"}` so Claude Code surfaces an inline prompt in the agent chat.
63
+ - **block** — amount above `block_threshold_usd`, or a contract not in `allowlisted_contracts`, is denied without calling `/sign`.
66
64
 
67
65
  Thresholds live in `~/.keeperhub/safety.json` (chmod 0o644). The `npx @keeperhub/wallet skill install` path registers the `keeperhub-wallet-hook` PreToolUse entry in `~/.claude/settings.json` automatically. For agents without auto-registration support (Cursor, Cline, Windsurf, OpenCode), the installer prints a copy-paste notice with the hook invocation.
68
66
 
69
- The hook reads only `tool_input.amount`, `tool_input.unit`, and `tool_input.to` forged fields such as `trust-level hint`, `is-safe boolean`, or `admin-override bit` on the tool input are ignored by design (GUARD-05).
67
+ The hook reads only the payment-challenge fields `amount`, `unit`, and the asset contract address from the tool payload. Forged fields like `trust-level hint`, `is-safe boolean`, or `admin-override bit` are ignored by design (GUARD-05).
68
+
69
+ ### Default safety config
70
+
71
+ Used when `~/.keeperhub/safety.json` is absent:
72
+
73
+ ```json
74
+ {
75
+ "auto_approve_max_usd": 5,
76
+ "block_threshold_usd": 100,
77
+ "allowlisted_contracts": [
78
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
79
+ "0x20C000000000000000000000B9537D11c60E8b50"
80
+ ]
81
+ }
82
+ ```
83
+
84
+ - `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` — **Base USDC**. Canonical Circle USDC contract on Base mainnet (chain id 8453). Used by x402 challenges from KeeperHub and any other x402-compliant service.
85
+ - `0x20C000000000000000000000B9537D11c60E8b50` — **Tempo USDC.e**. USDC bridge token on Tempo mainnet (chain id 4217). Used by MPP challenges from KeeperHub paid workflows that settle on Tempo.
86
+
87
+ These two addresses are the only tokens the hook will authorise by default. Adding other ERC-20 contracts to `allowlisted_contracts` allows your agent to sign against them too — at your own risk. To check any address, paste it into [BaseScan](https://basescan.org) (Base) or the Tempo block explorer; the contract page shows the token name, issuer, and whether it is verified.
70
88
 
71
89
  ## Storage
72
90