@keeperhub/wallet 0.1.6 → 0.1.7
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.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skill/keeperhub-wallet.skill.md +1 -1
package/dist/index.cjs
CHANGED
|
@@ -972,16 +972,18 @@ function createPaymentSigner(opts = {}) {
|
|
|
972
972
|
}
|
|
973
973
|
});
|
|
974
974
|
const paymentSigPayload = {
|
|
975
|
+
x402Version: 2,
|
|
976
|
+
accepted: accept,
|
|
975
977
|
payload: {
|
|
978
|
+
signature,
|
|
976
979
|
authorization: {
|
|
977
980
|
from: wallet.walletAddress,
|
|
978
981
|
to: accept.payTo,
|
|
979
982
|
value: accept.amount,
|
|
980
|
-
validAfter,
|
|
981
|
-
validBefore,
|
|
983
|
+
validAfter: String(validAfter),
|
|
984
|
+
validBefore: String(validBefore),
|
|
982
985
|
nonce
|
|
983
|
-
}
|
|
984
|
-
signature
|
|
986
|
+
}
|
|
985
987
|
}
|
|
986
988
|
};
|
|
987
989
|
const paymentSigHeader = Buffer.from(
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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":["export { type AgentTarget, detectAgents } from \"./agent-detect.js\";\nexport {\n type BalanceSnapshot,\n type CheckBalanceOptions,\n checkBalance,\n} from \"./balance.js\";\nexport { BASE_USDC, base, TEMPO_USDC_E, tempo } from \"./chains.js\";\nexport { runCli } from \"./cli.js\";\nexport {\n type AskTierResponse,\n type ClientOptions,\n KeeperHubClient,\n} from \"./client.js\";\nexport { type FundInstructions, fund } from \"./fund.js\";\nexport { buildHmacHeaders, computeSignature } from \"./hmac.js\";\nexport { type CreateHookOptions, createPreToolUseHook } from \"./hook.js\";\nexport { runHookCli } from \"./hook-entrypoint.js\";\nexport { type MppChallenge, parseMppChallenge } from \"./mpp-detect.js\";\nexport {\n createPaymentSigner,\n type PaymentSigner,\n paymentSigner,\n} from \"./payment-signer.js\";\nexport {\n DEFAULT_SAFETY_CONFIG,\n getSafetyConfigPath,\n loadSafetyConfig,\n type SafetyConfig,\n validateAndMerge,\n} from \"./safety-config.js\";\nexport {\n type InstallOptions,\n type InstallResult,\n installSkill,\n registerClaudeCodeHook,\n} from \"./skill-install.js\";\nexport {\n getWalletConfigPath,\n readWalletConfig,\n writeWalletConfig,\n} from \"./storage.js\";\nexport {\n type HmacHeaders,\n type HookDecision,\n KeeperHubError,\n type WalletConfig,\n WalletConfigMissingError,\n} from \"./types.js\";\nexport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n","// 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\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n};\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n */\n fetch: (input: string | URL, init?: RequestInit) => 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 retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // 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 const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // 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, options);\n }\n if (x402) {\n return payViaX402(response, x402, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: RequestInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method to the post-sign retry\n // so the paid workflow receives the same payload on the retry as on\n // the 402 attempt. Fixes the dropped-body bug that made any workflow\n // with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n });\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":["import_viem","import_node_path","import_promises","import_node_os","import_node_path","TRAILING_SLASH","import_promises","import_node_os","import_node_path","USDC_DECIMALS","import_node_crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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":["export { type AgentTarget, detectAgents } from \"./agent-detect.js\";\nexport {\n type BalanceSnapshot,\n type CheckBalanceOptions,\n checkBalance,\n} from \"./balance.js\";\nexport { BASE_USDC, base, TEMPO_USDC_E, tempo } from \"./chains.js\";\nexport { runCli } from \"./cli.js\";\nexport {\n type AskTierResponse,\n type ClientOptions,\n KeeperHubClient,\n} from \"./client.js\";\nexport { type FundInstructions, fund } from \"./fund.js\";\nexport { buildHmacHeaders, computeSignature } from \"./hmac.js\";\nexport { type CreateHookOptions, createPreToolUseHook } from \"./hook.js\";\nexport { runHookCli } from \"./hook-entrypoint.js\";\nexport { type MppChallenge, parseMppChallenge } from \"./mpp-detect.js\";\nexport {\n createPaymentSigner,\n type PaymentSigner,\n paymentSigner,\n} from \"./payment-signer.js\";\nexport {\n DEFAULT_SAFETY_CONFIG,\n getSafetyConfigPath,\n loadSafetyConfig,\n type SafetyConfig,\n validateAndMerge,\n} from \"./safety-config.js\";\nexport {\n type InstallOptions,\n type InstallResult,\n installSkill,\n registerClaudeCodeHook,\n} from \"./skill-install.js\";\nexport {\n getWalletConfigPath,\n readWalletConfig,\n writeWalletConfig,\n} from \"./storage.js\";\nexport {\n type HmacHeaders,\n type HookDecision,\n KeeperHubError,\n type WalletConfig,\n WalletConfigMissingError,\n} from \"./types.js\";\nexport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n","// 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\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n};\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n */\n fetch: (input: string | URL, init?: RequestInit) => 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 retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // x402 v2 PaymentPayload per @x402/core mechanisms-* d.ts:\n // { x402Version: 2, accepted: PaymentRequirements, payload: {...} }\n // The server's findMatchingRequirements does a deepEqual between\n // `paymentPayload.accepted` and each challenge `accepts[]` entry, so we\n // mirror the exact accept object we signed against.\n //\n // EIP-3009 inner payload: authorization.value/validAfter/validBefore/nonce\n // must be STRINGS at the wire format (per @x402/evm ExactEIP3009Payload).\n // /sign takes them as numbers; we stringify on the way out.\n const paymentSigPayload = {\n x402Version: 2,\n accepted: accept,\n payload: {\n signature,\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n },\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // 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, options);\n }\n if (x402) {\n return payViaX402(response, x402, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: RequestInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method to the post-sign retry\n // so the paid workflow receives the same payload on the retry as on\n // the 402 attempt. Fixes the dropped-body bug that made any workflow\n // with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n });\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":["import_viem","import_node_path","import_promises","import_node_os","import_node_path","TRAILING_SLASH","import_promises","import_node_os","import_node_path","USDC_DECIMALS","import_node_crypto"]}
|
package/dist/index.js
CHANGED
|
@@ -924,16 +924,18 @@ function createPaymentSigner(opts = {}) {
|
|
|
924
924
|
}
|
|
925
925
|
});
|
|
926
926
|
const paymentSigPayload = {
|
|
927
|
+
x402Version: 2,
|
|
928
|
+
accepted: accept,
|
|
927
929
|
payload: {
|
|
930
|
+
signature,
|
|
928
931
|
authorization: {
|
|
929
932
|
from: wallet.walletAddress,
|
|
930
933
|
to: accept.payTo,
|
|
931
934
|
value: accept.amount,
|
|
932
|
-
validAfter,
|
|
933
|
-
validBefore,
|
|
935
|
+
validAfter: String(validAfter),
|
|
936
|
+
validBefore: String(validBefore),
|
|
934
937
|
nonce
|
|
935
|
-
}
|
|
936
|
-
signature
|
|
938
|
+
}
|
|
937
939
|
}
|
|
938
940
|
};
|
|
939
941
|
const paymentSigHeader = Buffer.from(
|
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/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\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n};\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n */\n fetch: (input: string | URL, init?: RequestInit) => 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 retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // 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 const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // 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, options);\n }\n if (x402) {\n return payViaX402(response, x402, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: RequestInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method to the post-sign retry\n // so the paid workflow receives the same payload on the retry as on\n // the 402 attempt. Fixes the dropped-body bug that made any workflow\n // with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n });\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"]}
|
|
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\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n};\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n */\n fetch: (input: string | URL, init?: RequestInit) => 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 retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // x402 v2 PaymentPayload per @x402/core mechanisms-* d.ts:\n // { x402Version: 2, accepted: PaymentRequirements, payload: {...} }\n // The server's findMatchingRequirements does a deepEqual between\n // `paymentPayload.accepted` and each challenge `accepts[]` entry, so we\n // mirror the exact accept object we signed against.\n //\n // EIP-3009 inner payload: authorization.value/validAfter/validBefore/nonce\n // must be STRINGS at the wire format (per @x402/evm ExactEIP3009Payload).\n // /sign takes them as numbers; we stringify on the way out.\n const paymentSigPayload = {\n x402Version: 2,\n accepted: accept,\n payload: {\n signature,\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n },\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // 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, options);\n }\n if (x402) {\n return payViaX402(response, x402, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: RequestInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method to the post-sign retry\n // so the paid workflow receives the same payload on the retry as on\n // the 402 attempt. Fixes the dropped-body bug that made any workflow\n // with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n });\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,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.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
license: Apache-2.0
|
|
6
6
|
---
|
|
7
7
|
|