chain-insights 0.2.18 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +54 -12
  2. package/bin/cli.js +2 -3
  3. package/bin/install.cjs +0 -1
  4. package/dist/{app-DxlQE_P5.cjs → app-BxojXjtB.cjs} +1 -1
  5. package/dist/{app-DdWQF_zb.mjs → app-CRd39JJ8.mjs} +2 -2
  6. package/dist/{app-DdWQF_zb.mjs.map → app-CRd39JJ8.mjs.map} +1 -1
  7. package/dist/{artifact-server-4DiMvwhC.mjs → artifact-server-CP6LXQ9d.mjs} +2 -2
  8. package/dist/{artifact-server-4DiMvwhC.mjs.map → artifact-server-CP6LXQ9d.mjs.map} +1 -1
  9. package/dist/{artifact-server-B-3ho4bk.cjs → artifact-server-XbN16DwU.cjs} +1 -1
  10. package/dist/cli.cjs +66 -25
  11. package/dist/cli.mjs +66 -25
  12. package/dist/cli.mjs.map +1 -1
  13. package/dist/{config-BhYbhLDI.cjs → config-BwVx19Og.cjs} +48 -15
  14. package/dist/config-Drgc2HuF.mjs +77 -0
  15. package/dist/config-Drgc2HuF.mjs.map +1 -0
  16. package/dist/frontmatter-D0ccQnUM.mjs.map +1 -1
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.cts.map +1 -1
  20. package/dist/index.d.mts +3 -3
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +4 -4
  23. package/dist/{init-CZbZegIW.mjs → init-4tn7jfhN.mjs} +3 -2
  24. package/dist/init-4tn7jfhN.mjs.map +1 -0
  25. package/dist/{init-BvpZtFiT.cjs → init-TCQY5RDJ.cjs} +2 -1
  26. package/dist/mcp-endpoint-BaV8h_lq.cjs +60 -0
  27. package/dist/mcp-endpoint-DHs1cRFH.mjs +39 -0
  28. package/dist/mcp-endpoint-DHs1cRFH.mjs.map +1 -0
  29. package/dist/mcp-proxy.cjs +108 -9
  30. package/dist/mcp-proxy.d.cts.map +1 -1
  31. package/dist/mcp-proxy.d.mts.map +1 -1
  32. package/dist/mcp-proxy.mjs +108 -9
  33. package/dist/mcp-proxy.mjs.map +1 -1
  34. package/dist/{public-tools-D6Q5MTcO.mjs → public-tools-B13J0MJZ.mjs} +465 -70
  35. package/dist/public-tools-B13J0MJZ.mjs.map +1 -0
  36. package/dist/{public-tools-V7ON7goq.cjs → public-tools-BC1fi0DV.cjs} +464 -68
  37. package/dist/resolver-D7VBb0uB.mjs.map +1 -1
  38. package/dist/{runner-BatyCxv7.mjs → runner-DIs04IhN.mjs} +2 -2
  39. package/dist/{runner-BatyCxv7.mjs.map → runner-DIs04IhN.mjs.map} +1 -1
  40. package/dist/{runner-CCA7SJ7X.cjs → runner-ZYowxCVl.cjs} +1 -1
  41. package/dist/schema-BFEWhzg7.mjs +60 -0
  42. package/dist/schema-BFEWhzg7.mjs.map +1 -0
  43. package/dist/{schema-DN-KLkYN.cjs → schema-Vl9yuOFO.cjs} +31 -8
  44. package/dist/{server-BDlbmGbL.mjs → server-BXLX2j_A.mjs} +2 -2
  45. package/dist/{server-BDlbmGbL.mjs.map → server-BXLX2j_A.mjs.map} +1 -1
  46. package/dist/{server-C3y1gQmZ.cjs → server-BqVdWath.cjs} +1 -1
  47. package/dist/{topup-server-6MH7q73X.mjs → topup-server-BJgVw6Jt.mjs} +100 -42
  48. package/dist/topup-server-BJgVw6Jt.mjs.map +1 -0
  49. package/dist/{topup-server-DjUjhNjv.cjs → topup-server-yAaXYkJP.cjs} +98 -40
  50. package/docs/architecture.md +4 -0
  51. package/docs/contributing.md +1 -0
  52. package/docs/debugging.md +10 -14
  53. package/docs/graph-tools.md +60 -2
  54. package/docs/mcp-proxy.md +44 -0
  55. package/package.json +2 -2
  56. package/skills/chain-insights-developer-experience/SKILL.md +4 -2
  57. package/skills/chain-insights-investigation/SKILL.md +1 -1
  58. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +4 -5
  59. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +5 -24
  60. package/dist/config-9KYXaAv-.mjs +0 -44
  61. package/dist/config-9KYXaAv-.mjs.map +0 -1
  62. package/dist/init-CZbZegIW.mjs.map +0 -1
  63. package/dist/public-tools-D6Q5MTcO.mjs.map +0 -1
  64. package/dist/schema-BbQVXp36.mjs +0 -37
  65. package/dist/schema-BbQVXp36.mjs.map +0 -1
  66. package/dist/topup-server-6MH7q73X.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"resolver-D7VBb0uB.mjs","names":[],"sources":["../src/playbooks/builtins.ts","../src/playbooks/resolver.ts"],"sourcesContent":["// Built-in playbook definitions tied to the current GraphRAG public MCP tools.\n// Source of truth inspected in rbmk/repos/ml/graphrag/src/mcp_server/tools.\n\nexport const KNOWN_GRAPHRAG_PUBLIC_TOOLS = [\n 'address_risk',\n 'scam_topology',\n 'track_funds',\n 'graph_query',\n 'graph_query_batch',\n] as const\n\nexport const TRACE_FUNDS_PLAYBOOK = `---\nname: trace-funds\ndescription: Trace stolen funds from a victim address to exchange deposits using Chain Insights GraphRAG\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Screen Victim Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Trace Funds To Exchanges\n\n\\`\\`\\`tool\ntrack_funds\n\\`\\`\\`\n\n\\`\\`\\`params\ntrusted_addresses: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const RISK_CHECK_PLAYBOOK = `---\nname: risk-check\ndescription: Screen an address for Chain Insights risk, behavior, counterparties, exchange connections, and AML patterns\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Address Risk\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const ENTITY_PROFILE_PLAYBOOK = `---\nname: entity-profile\ndescription: Build an entity profile from Chain Insights address identity, metrics, risk, counterparties, and labels\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Entity Profile\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const SCAM_TOPOLOGY_PLAYBOOK = `---\nname: scam-topology\ndescription: Build laundering topology and scam labels from a victim incident\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: incident_timestamp_ms\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n - name: activity_policy\n type: string\n required: false\n default: node_relative_only\n---\n\n## Step 1: Screen Known Scam Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Build Scam Topology\n\nThe victim-only traversal is outward from victim/source funds. The\nprimary traversal is a node-relative novelty wave: each new node expands only\nonce, repeated targets remain as non-expanding convergence context, and\ndownstream edges must be active at or after the current node's wave-arrival\ntimestamp. Set activity_policy to global_incident_only to filter every wave\nagainst incident_timestamp_ms instead. Exchange terminal safety stops exchange\nendpoints, and label candidates are reviewable, not automatic writes.\nContract summary: victim-only traversal is outward from victim/source funds;\nnode-relative novelty wave by default; global_incident_only is available;\nexchange terminal safety; scam_labels are ML-ready flags.\n\n\\`\\`\\`tool\nscam_topology\n\\`\\`\\`\n\n\\`\\`\\`params\nvictim_address: {{address}}\nincident_timestamp_ms: {{incident_timestamp_ms}}\nnetwork: {{network}}\nactivity_policy: {{activity_policy}}\n\\`\\`\\`\n`\n\nexport const BUILTIN_PLAYBOOKS: Record<string, string> = {\n 'trace-funds': TRACE_FUNDS_PLAYBOOK,\n 'scam-topology': SCAM_TOPOLOGY_PLAYBOOK,\n 'risk-check': RISK_CHECK_PLAYBOOK,\n 'entity-profile': ENTITY_PROFILE_PLAYBOOK,\n}\n","import path from 'node:path'\nimport { access, readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport { BUILTIN_PLAYBOOKS } from './builtins.js'\n\nfunction userDir(): string {\n return path.join(os.homedir(), '.chain-insights', 'playbooks')\n}\n\n/**\n * Resolve a playbook name to its absolute file path.\n * Checks user directory (~/.chain-insights/playbooks/) first, then built-in directory.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n *\n * @deprecated Prefer resolvePlaybookContent() which returns markdown directly.\n */\nexport async function resolvePlaybook(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n const userPath = path.join(userDir(), `${safeName}.md`)\n\n try {\n await access(userPath)\n return userPath\n } catch {\n // Fall through to built-in check\n }\n\n // Check built-in playbooks map (T-05-01: no filesystem path exposure for built-ins)\n if (safeName in BUILTIN_PLAYBOOKS) {\n // Return a sentinel path for legacy callers — content comes from BUILTIN_PLAYBOOKS\n return `builtin:${safeName}`\n }\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * Resolve a playbook name to its markdown content.\n * Checks user directory (~/.chain-insights/playbooks/<name>.md) first,\n * then falls back to the built-in BUILTIN_PLAYBOOKS map.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n */\nexport async function resolvePlaybookContent(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n // 1. Check user directory first (user playbooks override built-ins)\n const userPath = path.join(userDir(), `${safeName}.md`)\n try {\n return await readFile(userPath, 'utf8')\n } catch {\n // Not in user dir — fall through\n }\n\n // 2. Fall back to built-in map\n const builtin = BUILTIN_PLAYBOOKS[safeName]\n if (builtin !== undefined) return builtin\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * List all available playbooks — user dir first (overrides), then built-ins.\n * Returns array of { name, source } objects.\n */\nexport async function listPlaybooks(): Promise<Array<{ name: string; source: 'builtin' | 'user' }>> {\n const result: Array<{ name: string; source: 'builtin' | 'user' }> = []\n const seen = new Set<string>()\n\n // User playbooks first\n try {\n const userFiles = await readdir(userDir())\n for (const file of userFiles) {\n if (!file.endsWith('.md')) continue\n const name = file.slice(0, -3) // remove .md\n seen.add(name)\n result.push({ name, source: 'user' })\n }\n } catch {\n // User dir doesn't exist — that's fine\n }\n\n // Built-in playbooks from the embedded map (not filesystem)\n for (const name of Object.keys(BUILTIN_PLAYBOOKS)) {\n if (seen.has(name)) continue // user override takes precedence\n result.push({ name, source: 'builtin' })\n }\n\n return result\n}\n"],"mappings":";;;AA6JA,MAAa,oBAA4C;CACvD,eAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,iBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,cAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;AACpB;;;AC7JA,SAAS,UAAkB;CACzB,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,mBAAmB,WAAW;AAC/D;;;;;;;AAwCA,eAAsB,uBAAuB,MAA+B;CAE1E,MAAM,WAAW,KAAK,QAAQ,iBAAiB,EAAE;CACjD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,0BAA0B,MAAM;CAG/D,MAAM,WAAW,KAAK,KAAK,QAAQ,GAAG,GAAG,SAAS,IAAI;CACtD,IAAI;EACF,OAAO,MAAM,SAAS,UAAU,MAAM;CACxC,QAAQ,CAER;CAGA,MAAM,UAAU,kBAAkB;CAClC,IAAI,YAAY,KAAA,GAAW,OAAO;CAElC,MAAM,IAAI,MACR,wBAAwB,SAAS,oEACnC;AACF;;;;;AAMA,eAAsB,gBAA8E;CAClG,MAAM,SAA8D,CAAC;CACrE,MAAM,uBAAO,IAAI,IAAY;CAG7B,IAAI;EACF,MAAM,YAAY,MAAM,QAAQ,QAAQ,CAAC;EACzC,KAAK,MAAM,QAAQ,WAAW;GAC5B,IAAI,CAAC,KAAK,SAAS,KAAK,GAAG;GAC3B,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE;GAC7B,KAAK,IAAI,IAAI;GACb,OAAO,KAAK;IAAE;IAAM,QAAQ;GAAO,CAAC;EACtC;CACF,QAAQ,CAER;CAGA,KAAK,MAAM,QAAQ,OAAO,KAAK,iBAAiB,GAAG;EACjD,IAAI,KAAK,IAAI,IAAI,GAAG;EACpB,OAAO,KAAK;GAAE;GAAM,QAAQ;EAAU,CAAC;CACzC;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"resolver-D7VBb0uB.mjs","names":[],"sources":["../src/playbooks/builtins.ts","../src/playbooks/resolver.ts"],"sourcesContent":["// Built-in playbook definitions tied to the current GraphRAG public MCP tools.\n// Source of truth inspected in rbmk/repos/ml/graphrag/src/mcp_server/tools.\n\nexport const KNOWN_GRAPHRAG_PUBLIC_TOOLS = [\n 'address_risk',\n 'scam_topology',\n 'stake_insights',\n 'track_funds',\n 'graph_query',\n 'graph_query_batch',\n] as const\n\nexport const TRACE_FUNDS_PLAYBOOK = `---\nname: trace-funds\ndescription: Trace stolen funds from a victim address to exchange deposits using Chain Insights GraphRAG\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Screen Victim Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Trace Funds To Exchanges\n\n\\`\\`\\`tool\ntrack_funds\n\\`\\`\\`\n\n\\`\\`\\`params\ntrusted_addresses: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const RISK_CHECK_PLAYBOOK = `---\nname: risk-check\ndescription: Screen an address for Chain Insights risk, behavior, counterparties, exchange connections, and AML patterns\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Address Risk\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const ENTITY_PROFILE_PLAYBOOK = `---\nname: entity-profile\ndescription: Build an entity profile from Chain Insights address identity, metrics, risk, counterparties, and labels\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Entity Profile\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const SCAM_TOPOLOGY_PLAYBOOK = `---\nname: scam-topology\ndescription: Build laundering topology and scam labels from a victim incident\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: incident_timestamp_ms\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n - name: activity_policy\n type: string\n required: false\n default: node_relative_only\n---\n\n## Step 1: Screen Known Scam Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Build Scam Topology\n\nThe victim-only traversal is outward from victim/source funds. The\nprimary traversal is a node-relative novelty wave: each new node expands only\nonce, repeated targets remain as non-expanding convergence context, and\ndownstream edges must be active at or after the current node's wave-arrival\ntimestamp. Set activity_policy to global_incident_only to filter every wave\nagainst incident_timestamp_ms instead. Exchange terminal safety stops exchange\nendpoints, and label candidates are reviewable, not automatic writes.\nContract summary: victim-only traversal is outward from victim/source funds;\nnode-relative novelty wave by default; global_incident_only is available;\nexchange terminal safety; scam_labels are ML-ready flags.\n\n\\`\\`\\`tool\nscam_topology\n\\`\\`\\`\n\n\\`\\`\\`params\nvictim_address: {{address}}\nincident_timestamp_ms: {{incident_timestamp_ms}}\nnetwork: {{network}}\nactivity_policy: {{activity_policy}}\n\\`\\`\\`\n`\n\nexport const BUILTIN_PLAYBOOKS: Record<string, string> = {\n 'trace-funds': TRACE_FUNDS_PLAYBOOK,\n 'scam-topology': SCAM_TOPOLOGY_PLAYBOOK,\n 'risk-check': RISK_CHECK_PLAYBOOK,\n 'entity-profile': ENTITY_PROFILE_PLAYBOOK,\n}\n","import path from 'node:path'\nimport { access, readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport { BUILTIN_PLAYBOOKS } from './builtins.js'\n\nfunction userDir(): string {\n return path.join(os.homedir(), '.chain-insights', 'playbooks')\n}\n\n/**\n * Resolve a playbook name to its absolute file path.\n * Checks user directory (~/.chain-insights/playbooks/) first, then built-in directory.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n *\n * @deprecated Prefer resolvePlaybookContent() which returns markdown directly.\n */\nexport async function resolvePlaybook(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n const userPath = path.join(userDir(), `${safeName}.md`)\n\n try {\n await access(userPath)\n return userPath\n } catch {\n // Fall through to built-in check\n }\n\n // Check built-in playbooks map (T-05-01: no filesystem path exposure for built-ins)\n if (safeName in BUILTIN_PLAYBOOKS) {\n // Return a sentinel path for legacy callers — content comes from BUILTIN_PLAYBOOKS\n return `builtin:${safeName}`\n }\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * Resolve a playbook name to its markdown content.\n * Checks user directory (~/.chain-insights/playbooks/<name>.md) first,\n * then falls back to the built-in BUILTIN_PLAYBOOKS map.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n */\nexport async function resolvePlaybookContent(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n // 1. Check user directory first (user playbooks override built-ins)\n const userPath = path.join(userDir(), `${safeName}.md`)\n try {\n return await readFile(userPath, 'utf8')\n } catch {\n // Not in user dir — fall through\n }\n\n // 2. Fall back to built-in map\n const builtin = BUILTIN_PLAYBOOKS[safeName]\n if (builtin !== undefined) return builtin\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * List all available playbooks — user dir first (overrides), then built-ins.\n * Returns array of { name, source } objects.\n */\nexport async function listPlaybooks(): Promise<Array<{ name: string; source: 'builtin' | 'user' }>> {\n const result: Array<{ name: string; source: 'builtin' | 'user' }> = []\n const seen = new Set<string>()\n\n // User playbooks first\n try {\n const userFiles = await readdir(userDir())\n for (const file of userFiles) {\n if (!file.endsWith('.md')) continue\n const name = file.slice(0, -3) // remove .md\n seen.add(name)\n result.push({ name, source: 'user' })\n }\n } catch {\n // User dir doesn't exist — that's fine\n }\n\n // Built-in playbooks from the embedded map (not filesystem)\n for (const name of Object.keys(BUILTIN_PLAYBOOKS)) {\n if (seen.has(name)) continue // user override takes precedence\n result.push({ name, source: 'builtin' })\n }\n\n return result\n}\n"],"mappings":";;;AA8JA,MAAa,oBAA4C;CACvD,eAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,iBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,cAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;AACpB;;;AC9JA,SAAS,UAAkB;CACzB,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,mBAAmB,WAAW;AAC/D;;;;;;;AAwCA,eAAsB,uBAAuB,MAA+B;CAE1E,MAAM,WAAW,KAAK,QAAQ,iBAAiB,EAAE;CACjD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,0BAA0B,MAAM;CAG/D,MAAM,WAAW,KAAK,KAAK,QAAQ,GAAG,GAAG,SAAS,IAAI;CACtD,IAAI;EACF,OAAO,MAAM,SAAS,UAAU,MAAM;CACxC,QAAQ,CAER;CAGA,MAAM,UAAU,kBAAkB;CAClC,IAAI,YAAY,KAAA,GAAW,OAAO;CAElC,MAAM,IAAI,MACR,wBAAwB,SAAS,oEACnC;AACF;;;;;AAMA,eAAsB,gBAA8E;CAClG,MAAM,SAA8D,CAAC;CACrE,MAAM,uBAAO,IAAI,IAAY;CAG7B,IAAI;EACF,MAAM,YAAY,MAAM,QAAQ,QAAQ,CAAC;EACzC,KAAK,MAAM,QAAQ,WAAW;GAC5B,IAAI,CAAC,KAAK,SAAS,KAAK,GAAG;GAC3B,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE;GAC7B,KAAK,IAAI,IAAI;GACb,OAAO,KAAK;IAAE;IAAM,QAAQ;GAAO,CAAC;EACtC;CACF,QAAQ,CAER;CAGA,KAAK,MAAM,QAAQ,OAAO,KAAK,iBAAiB,GAAG;EACjD,IAAI,KAAK,IAAI,IAAI,GAAG;EACpB,OAAO,KAAK;GAAE;GAAM,QAAQ;EAAU,CAAC;CACzC;CAEA,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
2
- import { n as loadConfig } from "./config-9KYXaAv-.mjs";
2
+ import { n as loadConfig } from "./config-Drgc2HuF.mjs";
3
3
  import { r as createConfiguredMcpFetch } from "./client-D4_hd4AP.mjs";
4
4
  import { t as generateVisualization } from "./viz-DkJyqlUu.mjs";
5
5
  import { CaseStore } from "./store-BT2SCcQr.mjs";
@@ -146,4 +146,4 @@ async run(playbook, opts) {
146
146
  //#endregion
147
147
  export { PlaybookRunner };
148
148
 
149
- //# sourceMappingURL=runner-BatyCxv7.mjs.map
149
+ //# sourceMappingURL=runner-DIs04IhN.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner-BatyCxv7.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights config set walletPrivateKey <key>\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runner-DIs04IhN.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights config set walletPrivateKey <key>\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_version = require("./version-CO9Or_YV.cjs");
2
- const require_config = require("./config-BhYbhLDI.cjs");
2
+ const require_config = require("./config-BwVx19Og.cjs");
3
3
  const require_client = require("./client-DPc2eyVN.cjs");
4
4
  const require_viz = require("./viz-Da9YWN_I.cjs");
5
5
  const require_store = require("./store-DogLawSj.cjs");
@@ -0,0 +1,60 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { i as validateMcpEndpoint, n as LOCAL_LEGACY_MCP_ENDPOINT, t as LOCAL_GRAPH_MCP_ENDPOINT } from "./mcp-endpoint-DHs1cRFH.mjs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import * as z from "zod";
6
+ //#region src/config/schema.ts
7
+ var schema_exports = /* @__PURE__ */ __exportAll({
8
+ CONFIG_KEYS: () => CONFIG_KEYS,
9
+ ConfigSchema: () => ConfigSchema,
10
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
11
+ parseInvestigatorConfig: () => parseInvestigatorConfig
12
+ });
13
+ function endpointSchema(key) {
14
+ return z.string().transform((value, ctx) => {
15
+ try {
16
+ return validateMcpEndpoint(value, key);
17
+ } catch (err) {
18
+ ctx.addIssue({
19
+ code: z.ZodIssueCode.custom,
20
+ message: err.message
21
+ });
22
+ return z.NEVER;
23
+ }
24
+ });
25
+ }
26
+ const ConfigSchema = z.object({
27
+ mcpEndpoint: endpointSchema("mcpEndpoint").default(LOCAL_LEGACY_MCP_ENDPOINT),
28
+ mcpAuthToken: z.string().optional(),
29
+ graphMcpEndpoint: endpointSchema("graphMcpEndpoint").default(LOCAL_GRAPH_MCP_ENDPOINT),
30
+ graphMcpAuthToken: z.string().optional(),
31
+ graphMcpMode: z.enum(["paid", "debug"]).default("paid"),
32
+ walletAddress: z.string().optional(),
33
+ serverPort: z.number().int().min(1024).max(65535).default(4321),
34
+ dataDir: z.string().default(path.join(os.homedir(), ".chain-insights")),
35
+ version: z.string().default("1")
36
+ });
37
+ function formatConfigValidationError(error) {
38
+ return error.issues.map((issue) => issue.message).join("\n");
39
+ }
40
+ function parseInvestigatorConfig(input) {
41
+ const parsed = ConfigSchema.safeParse(input);
42
+ if (parsed.success) return parsed.data;
43
+ throw new Error(formatConfigValidationError(parsed.error));
44
+ }
45
+ const DEFAULT_CONFIG = parseInvestigatorConfig({});
46
+ const CONFIG_KEYS = [
47
+ "mcpEndpoint",
48
+ "mcpAuthToken",
49
+ "graphMcpEndpoint",
50
+ "graphMcpAuthToken",
51
+ "graphMcpMode",
52
+ "walletAddress",
53
+ "serverPort",
54
+ "dataDir",
55
+ "version"
56
+ ];
57
+ //#endregion
58
+ export { parseInvestigatorConfig as n, schema_exports as r, DEFAULT_CONFIG as t };
59
+
60
+ //# sourceMappingURL=schema-BFEWhzg7.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-BFEWhzg7.mjs","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":["import * as z from 'zod'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT, LOCAL_LEGACY_MCP_ENDPOINT, validateMcpEndpoint } from './mcp-endpoint.js'\n\nfunction endpointSchema(key: 'mcpEndpoint' | 'graphMcpEndpoint') {\n return z.string().transform((value, ctx) => {\n try {\n return validateMcpEndpoint(value, key)\n } catch (err) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: (err as Error).message,\n })\n return z.NEVER\n }\n })\n}\n\nexport const ConfigSchema = z.object({\n mcpEndpoint: endpointSchema('mcpEndpoint').default(LOCAL_LEGACY_MCP_ENDPOINT),\n mcpAuthToken: z.string().optional(),\n graphMcpEndpoint: endpointSchema('graphMcpEndpoint').default(LOCAL_GRAPH_MCP_ENDPOINT),\n graphMcpAuthToken: z.string().optional(),\n graphMcpMode: z.enum(['paid', 'debug']).default('paid'),\n walletAddress: z.string().optional(),\n serverPort: z.number().int().min(1024).max(65535).default(4321),\n dataDir: z.string().default(path.join(os.homedir(), '.chain-insights')),\n version: z.string().default('1'),\n})\n\nexport type InvestigatorConfig = z.infer<typeof ConfigSchema>\n\nfunction formatConfigValidationError(error: z.ZodError): string {\n return error.issues.map((issue) => issue.message).join('\\n')\n}\n\nexport function parseInvestigatorConfig(input: unknown): InvestigatorConfig {\n const parsed = ConfigSchema.safeParse(input)\n if (parsed.success) return parsed.data\n throw new Error(formatConfigValidationError(parsed.error))\n}\n\nexport const DEFAULT_CONFIG: InvestigatorConfig = parseInvestigatorConfig({})\n\nexport const CONFIG_KEYS = [\n 'mcpEndpoint',\n 'mcpAuthToken',\n 'graphMcpEndpoint',\n 'graphMcpAuthToken',\n 'graphMcpMode',\n 'walletAddress',\n 'serverPort',\n 'dataDir',\n 'version',\n] as const\n\nexport type ConfigKey = typeof CONFIG_KEYS[number]\n"],"mappings":";;;;;;;;;;;;AAKA,SAAS,eAAe,KAAyC;CAC/D,OAAO,EAAE,OAAO,EAAE,WAAW,OAAO,QAAQ;EAC1C,IAAI;GACF,OAAO,oBAAoB,OAAO,GAAG;EACvC,SAAS,KAAK;GACZ,IAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB,SAAU,IAAc;GAC1B,CAAC;GACD,OAAO,EAAE;EACX;CACF,CAAC;AACH;AAEA,MAAa,eAAe,EAAE,OAAO;CACnC,aAAmB,eAAe,aAAa,EAAE,QAAQ,yBAAyB;CAClF,cAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,kBAAmB,eAAe,kBAAkB,EAAE,QAAQ,wBAAwB;CACtF,mBAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,cAAmB,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;CAC3D,eAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,YAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI;CACrE,SAAmB,EAAE,OAAO,EAAE,QAAQ,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB,CAAC;CAChF,SAAmB,EAAE,OAAO,EAAE,QAAQ,GAAG;AAC3C,CAAC;AAID,SAAS,4BAA4B,OAA2B;CAC9D,OAAO,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,KAAK,IAAI;AAC7D;AAEA,SAAgB,wBAAwB,OAAoC;CAC1E,MAAM,SAAS,aAAa,UAAU,KAAK;CAC3C,IAAI,OAAO,SAAS,OAAO,OAAO;CAClC,MAAM,IAAI,MAAM,4BAA4B,OAAO,KAAK,CAAC;AAC3D;AAEA,MAAa,iBAAqC,wBAAwB,CAAC,CAAC;AAE5E,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -1,4 +1,5 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
+ const require_mcp_endpoint = require("./mcp-endpoint-BaV8h_lq.cjs");
2
3
  let node_path = require("node:path");
3
4
  node_path = require_chunk.__toESM(node_path, 1);
4
5
  let node_os = require("node:os");
@@ -9,12 +10,26 @@ zod = require_chunk.__toESM(zod, 1);
9
10
  var schema_exports = /* @__PURE__ */ require_chunk.__exportAll({
10
11
  CONFIG_KEYS: () => CONFIG_KEYS,
11
12
  ConfigSchema: () => ConfigSchema,
12
- DEFAULT_CONFIG: () => DEFAULT_CONFIG
13
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
14
+ parseInvestigatorConfig: () => parseInvestigatorConfig
13
15
  });
16
+ function endpointSchema(key) {
17
+ return zod.string().transform((value, ctx) => {
18
+ try {
19
+ return require_mcp_endpoint.validateMcpEndpoint(value, key);
20
+ } catch (err) {
21
+ ctx.addIssue({
22
+ code: zod.ZodIssueCode.custom,
23
+ message: err.message
24
+ });
25
+ return zod.NEVER;
26
+ }
27
+ });
28
+ }
14
29
  const ConfigSchema = zod.object({
15
- mcpEndpoint: zod.string().url().default("http://localhost:4000"),
30
+ mcpEndpoint: endpointSchema("mcpEndpoint").default(require_mcp_endpoint.LOCAL_LEGACY_MCP_ENDPOINT),
16
31
  mcpAuthToken: zod.string().optional(),
17
- graphMcpEndpoint: zod.string().default("https://staging-mcp.chain-insights.ai/mcp"),
32
+ graphMcpEndpoint: endpointSchema("graphMcpEndpoint").default(require_mcp_endpoint.LOCAL_GRAPH_MCP_ENDPOINT),
18
33
  graphMcpAuthToken: zod.string().optional(),
19
34
  graphMcpMode: zod.enum(["paid", "debug"]).default("paid"),
20
35
  walletAddress: zod.string().optional(),
@@ -22,7 +37,15 @@ const ConfigSchema = zod.object({
22
37
  dataDir: zod.string().default(node_path.default.join(node_os.default.homedir(), ".chain-insights")),
23
38
  version: zod.string().default("1")
24
39
  });
25
- const DEFAULT_CONFIG = ConfigSchema.parse({});
40
+ function formatConfigValidationError(error) {
41
+ return error.issues.map((issue) => issue.message).join("\n");
42
+ }
43
+ function parseInvestigatorConfig(input) {
44
+ const parsed = ConfigSchema.safeParse(input);
45
+ if (parsed.success) return parsed.data;
46
+ throw new Error(formatConfigValidationError(parsed.error));
47
+ }
48
+ const DEFAULT_CONFIG = parseInvestigatorConfig({});
26
49
  const CONFIG_KEYS = [
27
50
  "mcpEndpoint",
28
51
  "mcpAuthToken",
@@ -35,16 +58,16 @@ const CONFIG_KEYS = [
35
58
  "version"
36
59
  ];
37
60
  //#endregion
38
- Object.defineProperty(exports, "ConfigSchema", {
61
+ Object.defineProperty(exports, "DEFAULT_CONFIG", {
39
62
  enumerable: true,
40
63
  get: function() {
41
- return ConfigSchema;
64
+ return DEFAULT_CONFIG;
42
65
  }
43
66
  });
44
- Object.defineProperty(exports, "DEFAULT_CONFIG", {
67
+ Object.defineProperty(exports, "parseInvestigatorConfig", {
45
68
  enumerable: true,
46
69
  get: function() {
47
- return DEFAULT_CONFIG;
70
+ return parseInvestigatorConfig;
48
71
  }
49
72
  });
50
73
  Object.defineProperty(exports, "schema_exports", {
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { t as createApp } from "./app-DdWQF_zb.mjs";
2
+ import { t as createApp } from "./app-CRd39JJ8.mjs";
3
3
  import { serve } from "@hono/node-server";
4
4
  //#region src/server/index.ts
5
5
  var server_exports = /* @__PURE__ */ __exportAll({ startServer: () => startServer });
@@ -42,4 +42,4 @@ function startServer(port = 4321) {
42
42
  //#endregion
43
43
  export { startServer as n, server_exports as t };
44
44
 
45
- //# sourceMappingURL=server-BDlbmGbL.mjs.map
45
+ //# sourceMappingURL=server-BXLX2j_A.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-BDlbmGbL.mjs","names":[],"sources":["../src/server/index.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { createApp } from './app.js'\n\nexport function startServer(port = 4321): () => void {\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1', // localhost-only — REQUIRED (default 0.0.0.0 is insecure)\n port,\n })\n\n server.on('listening', () => {\n console.log(`Chain Insights server running on http://127.0.0.1:${port}`)\n })\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(`Port already in use: 127.0.0.1:${port}\\n`)\n } else {\n process.stderr.write(`Chain Insights server failed: ${err.message}\\n`)\n }\n process.exitCode = 1\n })\n\n let stopped = false\n const stop = (callback?: () => void): void => {\n if (stopped) {\n callback?.()\n return\n }\n stopped = true\n process.off('SIGINT', onSigint)\n process.off('SIGTERM', onSigterm)\n server.close(callback)\n }\n const onSigint = () => { stop(); process.exit(0) }\n const onSigterm = () => { stop(() => process.exit(0)) }\n\n process.on('SIGINT', onSigint)\n process.on('SIGTERM', onSigterm)\n\n return stop\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,OAAO,MAAkB;CAEnD,MAAM,SAAS,MAAM;EACnB,OAFa,UAED,EAAE;EACd,UAAU;EACV;CACF,CAAC;CAED,OAAO,GAAG,mBAAmB;EAC3B,QAAQ,IAAI,qDAAqD,MAAM;CACzE,CAAC;CACD,OAAO,GAAG,UAAU,QAA+B;EACjD,IAAI,IAAI,SAAS,cACf,QAAQ,OAAO,MAAM,kCAAkC,KAAK,GAAG;OAE/D,QAAQ,OAAO,MAAM,iCAAiC,IAAI,QAAQ,GAAG;EAEvE,QAAQ,WAAW;CACrB,CAAC;CAED,IAAI,UAAU;CACd,MAAM,QAAQ,aAAgC;EAC5C,IAAI,SAAS;GACX,WAAW;GACX;EACF;EACA,UAAU;EACV,QAAQ,IAAI,UAAU,QAAQ;EAC9B,QAAQ,IAAI,WAAW,SAAS;EAChC,OAAO,MAAM,QAAQ;CACvB;CACA,MAAM,iBAAiB;EAAE,KAAK;EAAG,QAAQ,KAAK,CAAC;CAAE;CACjD,MAAM,kBAAkB;EAAE,WAAW,QAAQ,KAAK,CAAC,CAAC;CAAE;CAEtD,QAAQ,GAAG,UAAU,QAAQ;CAC7B,QAAQ,GAAG,WAAW,SAAS;CAE/B,OAAO;AACT"}
1
+ {"version":3,"file":"server-BXLX2j_A.mjs","names":[],"sources":["../src/server/index.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { createApp } from './app.js'\n\nexport function startServer(port = 4321): () => void {\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1', // localhost-only — REQUIRED (default 0.0.0.0 is insecure)\n port,\n })\n\n server.on('listening', () => {\n console.log(`Chain Insights server running on http://127.0.0.1:${port}`)\n })\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(`Port already in use: 127.0.0.1:${port}\\n`)\n } else {\n process.stderr.write(`Chain Insights server failed: ${err.message}\\n`)\n }\n process.exitCode = 1\n })\n\n let stopped = false\n const stop = (callback?: () => void): void => {\n if (stopped) {\n callback?.()\n return\n }\n stopped = true\n process.off('SIGINT', onSigint)\n process.off('SIGTERM', onSigterm)\n server.close(callback)\n }\n const onSigint = () => { stop(); process.exit(0) }\n const onSigterm = () => { stop(() => process.exit(0)) }\n\n process.on('SIGINT', onSigint)\n process.on('SIGTERM', onSigterm)\n\n return stop\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,OAAO,MAAkB;CAEnD,MAAM,SAAS,MAAM;EACnB,OAFa,UAED,EAAE;EACd,UAAU;EACV;CACF,CAAC;CAED,OAAO,GAAG,mBAAmB;EAC3B,QAAQ,IAAI,qDAAqD,MAAM;CACzE,CAAC;CACD,OAAO,GAAG,UAAU,QAA+B;EACjD,IAAI,IAAI,SAAS,cACf,QAAQ,OAAO,MAAM,kCAAkC,KAAK,GAAG;OAE/D,QAAQ,OAAO,MAAM,iCAAiC,IAAI,QAAQ,GAAG;EAEvE,QAAQ,WAAW;CACrB,CAAC;CAED,IAAI,UAAU;CACd,MAAM,QAAQ,aAAgC;EAC5C,IAAI,SAAS;GACX,WAAW;GACX;EACF;EACA,UAAU;EACV,QAAQ,IAAI,UAAU,QAAQ;EAC9B,QAAQ,IAAI,WAAW,SAAS;EAChC,OAAO,MAAM,QAAQ;CACvB;CACA,MAAM,iBAAiB;EAAE,KAAK;EAAG,QAAQ,KAAK,CAAC;CAAE;CACjD,MAAM,kBAAkB;EAAE,WAAW,QAAQ,KAAK,CAAC,CAAC;CAAE;CAEtD,QAAQ,GAAG,UAAU,QAAQ;CAC7B,QAAQ,GAAG,WAAW,SAAS;CAE/B,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_app = require("./app-DxlQE_P5.cjs");
2
+ const require_app = require("./app-BxojXjtB.cjs");
3
3
  let _hono_node_server = require("@hono/node-server");
4
4
  //#region src/server/index.ts
5
5
  var server_exports = /* @__PURE__ */ require_chunk.__exportAll({ startServer: () => startServer });
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { dirname, join } from "node:path";
4
4
  import { readFileSync } from "node:fs";
5
- import { createPublicClient, formatEther, formatUnits, http } from "viem";
5
+ import { createPublicClient, formatEther, formatUnits, http, isAddress } from "viem";
6
6
  import { base } from "viem/chains";
7
7
  import { createServer } from "node:http";
8
8
  //#region src/wallet/mcp-proxy/qr.ts
@@ -197,6 +197,9 @@ const USDC_ABI = [{
197
197
  type: "uint256"
198
198
  }]
199
199
  }];
200
+ function walletAddress(wallet) {
201
+ return typeof wallet === "string" ? wallet : wallet.address;
202
+ }
200
203
  async function getBalanceUsdc(wallet) {
201
204
  const envRpcUrl = process.env.BASE_RPC_URL;
202
205
  const rpcUrls = [...envRpcUrl ? [envRpcUrl] : [], ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl)];
@@ -208,7 +211,7 @@ async function getBalanceUsdc(wallet) {
208
211
  address: USDC_ADDRESS$1,
209
212
  abi: USDC_ABI,
210
213
  functionName: "balanceOf",
211
- args: [wallet.address]
214
+ args: [walletAddress(wallet)]
212
215
  }), 6);
213
216
  } catch {}
214
217
  return "unknown";
@@ -220,7 +223,7 @@ async function getBalanceEth(wallet) {
220
223
  return formatEther(await createPublicClient({
221
224
  chain: base,
222
225
  transport: http(rpcUrl)
223
- }).getBalance({ address: wallet.address }));
226
+ }).getBalance({ address: walletAddress(wallet) }));
224
227
  } catch {}
225
228
  return "unknown";
226
229
  }
@@ -241,10 +244,22 @@ const logoPng = loadAsset("logo.png");
241
244
  const bgPatternPng = loadAsset("bg-pattern.png");
242
245
  let server = null;
243
246
  let serverPort = null;
247
+ function assertWalletAddress(wallet) {
248
+ const walletAddress = typeof wallet === "string" ? wallet : wallet.address;
249
+ if (!isAddress(walletAddress)) throw new Error("Wallet address must be a valid 0x-prefixed 20-byte EVM address");
250
+ return walletAddress;
251
+ }
252
+ function escapeHtml(value) {
253
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
254
+ }
255
+ function jsonForScript(value) {
256
+ return JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("&", "\\u0026");
257
+ }
244
258
  function getTopupUrl$1() {
245
259
  return serverPort ? `http://localhost:${serverPort}` : null;
246
260
  }
247
261
  async function startTopupServer$1(wallet) {
262
+ const walletAddress = assertWalletAddress(wallet);
248
263
  if (server && serverPort) return `http://localhost:${serverPort}`;
249
264
  return new Promise((resolve, reject) => {
250
265
  server = createServer((req, res) => {
@@ -253,11 +268,11 @@ async function startTopupServer$1(wallet) {
253
268
  "Content-Type": "application/json",
254
269
  "Access-Control-Allow-Origin": "*"
255
270
  });
256
- res.end(JSON.stringify({ address: wallet.address }));
271
+ res.end(JSON.stringify({ address: walletAddress }));
257
272
  return;
258
273
  }
259
274
  if (req.url === "/api/balance") {
260
- Promise.all([getBalanceUsdc(wallet), getBalanceEth(wallet)]).then(([balanceUsdc, balanceEth]) => {
275
+ Promise.all([getBalanceUsdc(walletAddress), getBalanceEth(walletAddress)]).then(([balanceUsdc, balanceEth]) => {
261
276
  res.writeHead(200, {
262
277
  "Content-Type": "application/json",
263
278
  "Access-Control-Allow-Origin": "*"
@@ -297,7 +312,7 @@ async function startTopupServer$1(wallet) {
297
312
  return;
298
313
  }
299
314
  res.writeHead(200, { "Content-Type": "text/html" });
300
- res.end(generatePage(wallet.address));
315
+ res.end(generatePage(walletAddress));
301
316
  });
302
317
  server.listen(0, "127.0.0.1", () => {
303
318
  const addr = server.address();
@@ -311,7 +326,11 @@ async function startTopupServer$1(wallet) {
311
326
  server.on("error", reject);
312
327
  });
313
328
  }
314
- function generateArtifactHtml(walletAddress, topupUrl) {
329
+ function generateArtifactHtml(walletAddressInput, topupUrl) {
330
+ const walletAddress = assertWalletAddress(walletAddressInput);
331
+ const safeWalletAddress = escapeHtml(walletAddress);
332
+ const safeTopupUrl = escapeHtml(topupUrl);
333
+ const topupUrlJson = jsonForScript(topupUrl);
315
334
  return `<!DOCTYPE html>
316
335
  <html lang="en">
317
336
  <head>
@@ -321,7 +340,7 @@ function generateArtifactHtml(walletAddress, topupUrl) {
321
340
  * { margin: 0; padding: 0; box-sizing: border-box; }
322
341
  body {
323
342
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
324
- background: rgba(10, 12, 17, 1) url('${topupUrl}/assets/bg-pattern.png') top left / contain no-repeat;
343
+ background: rgba(10, 12, 17, 1) url('${safeTopupUrl}/assets/bg-pattern.png') top left / contain no-repeat;
325
344
  color: rgba(255, 255, 255, 0.9);
326
345
  display: flex;
327
346
  justify-content: center;
@@ -397,11 +416,11 @@ function generateArtifactHtml(walletAddress, topupUrl) {
397
416
  </head>
398
417
  <body>
399
418
  <div class="card">
400
- <div class="logo"><img src="${topupUrl}/assets/logo.png" alt="Chain Insights"></div>
419
+ <div class="logo"><img src="${safeTopupUrl}/assets/logo.png" alt="Chain Insights"></div>
401
420
  <p class="subtitle">Fund your wallet with USDC on Base</p>
402
421
  <div class="qr">${generateQrSvg(walletAddress, { cellSize: 8 })}</div>
403
422
  <div class="address-wrap">
404
- <div class="address" id="addr" onclick="selectAndCopy()">${walletAddress}</div>
423
+ <div class="address" id="addr" onclick="selectAndCopy()">${safeWalletAddress}</div>
405
424
  <div class="address-hint" id="addrHint">Click to copy address</div>
406
425
  </div>
407
426
  <div class="badge"><span class="dot"></span>Base Network &middot; USDC</div>
@@ -460,8 +479,9 @@ function generateArtifactHtml(walletAddress, topupUrl) {
460
479
 
461
480
  // Live balance polling
462
481
  var lastBal = null;
482
+ var TOPUP_URL = ${topupUrlJson};
463
483
  function fetchBal() {
464
- fetch('${topupUrl}/api/balance')
484
+ fetch(TOPUP_URL + '/api/balance')
465
485
  .then(function(r) { return r.json(); })
466
486
  .then(function(d) {
467
487
  var el = document.getElementById('bal');
@@ -514,7 +534,8 @@ function selectAndCopy() {
514
534
  </body>
515
535
  </html>`;
516
536
  }
517
- function generatePage(walletAddress) {
537
+ function generatePage(walletAddressInput) {
538
+ const walletAddress = assertWalletAddress(walletAddressInput);
518
539
  return `<!DOCTYPE html>
519
540
  <html lang="en">
520
541
  <head>
@@ -741,7 +762,7 @@ function generatePage(walletAddress) {
741
762
  <div class="qr-container" id="qr"></div>
742
763
 
743
764
  <div class="address-box">
744
- <code id="address">${walletAddress}</code>
765
+ <code id="address">${escapeHtml(walletAddress)}</code>
745
766
  <button class="copy-btn" onclick="copyAddress()" id="copyBtn" title="Copy address">&#x2398;</button>
746
767
  </div>
747
768
 
@@ -767,7 +788,7 @@ function generatePage(walletAddress) {
767
788
  </div>
768
789
 
769
790
  <script>
770
- const WALLET = '${walletAddress}';
791
+ const WALLET = ${jsonForScript(walletAddress)};
771
792
  const USDC = '${USDC_ADDRESS}';
772
793
  const CHAIN_ID = '${BASE_CHAIN_ID}';
773
794
 
@@ -833,21 +854,23 @@ var topup_server_exports = /* @__PURE__ */ __exportAll({
833
854
  generateArtifactHtml: () => generateArtifactHtml,
834
855
  getTopupArtifactUrl: () => getTopupArtifactUrl,
835
856
  getTopupUrl: () => getTopupUrl,
836
- startTopupServer: () => startTopupServer
857
+ startTopupServer: () => startTopupServer,
858
+ stopTopupServer: () => stopTopupServer
837
859
  });
838
- const FALLBACK_PRIVATE_KEY = `0x${"0".repeat(63)}1`;
839
860
  let artifactServerState = null;
840
- function toWalletData(account) {
841
- if (typeof account === "string") return {
842
- address: account,
843
- privateKey: FALLBACK_PRIVATE_KEY,
844
- createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
845
- };
846
- return {
847
- address: account.address,
848
- privateKey: account.privateKey,
849
- createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
850
- };
861
+ const REQUEST_TARGET_BASE = "http://localhost";
862
+ var ProxyRequestError = class extends Error {
863
+ status;
864
+ constructor(status, message) {
865
+ super(message);
866
+ this.name = "ProxyRequestError";
867
+ this.status = status;
868
+ }
869
+ };
870
+ function toWalletAddress(account) {
871
+ const address = typeof account === "string" ? account : account.address;
872
+ if (!isAddress(address)) throw new Error("Wallet address must be a valid 0x-prefixed 20-byte EVM address");
873
+ return address;
851
874
  }
852
875
  function send(res, status, body, contentType) {
853
876
  res.writeHead(status, {
@@ -857,8 +880,26 @@ function send(res, status, body, contentType) {
857
880
  });
858
881
  res.end(body);
859
882
  }
860
- async function proxyToCopiedServer(reqUrl, res, assetServerUrl) {
861
- const upstreamUrl = new URL(reqUrl, assetServerUrl);
883
+ function normalizeProxyTarget(reqUrl) {
884
+ if (!reqUrl.startsWith("/") || reqUrl.startsWith("//")) throw new ProxyRequestError(400, "Proxy request target must be an origin-form path");
885
+ const parsed = new URL(reqUrl, REQUEST_TARGET_BASE);
886
+ if (parsed.origin !== REQUEST_TARGET_BASE) throw new ProxyRequestError(400, "Absolute and protocol-relative proxy targets are not allowed");
887
+ let decodedPathname;
888
+ try {
889
+ decodedPathname = decodeURIComponent(parsed.pathname);
890
+ } catch {
891
+ throw new ProxyRequestError(400, "Proxy request target contains invalid encoding");
892
+ }
893
+ if (decodedPathname.startsWith("//") || /^\/[a-z][a-z0-9+.-]*:\/\//i.test(decodedPathname)) throw new ProxyRequestError(400, "Encoded host override targets are not allowed");
894
+ return {
895
+ pathAndSearch: `${parsed.pathname}${parsed.search}`,
896
+ pathname: parsed.pathname
897
+ };
898
+ }
899
+ async function proxyToCopiedServer(proxyTarget, res, assetServerUrl) {
900
+ const allowedOrigin = new URL(assetServerUrl).origin;
901
+ const upstreamUrl = new URL(proxyTarget.pathAndSearch, assetServerUrl);
902
+ if (upstreamUrl.origin !== allowedOrigin) throw new ProxyRequestError(403, "Upstream origin is not allowed");
862
903
  const upstream = await fetch(upstreamUrl);
863
904
  const contentType = upstream.headers.get("content-type") ?? "application/octet-stream";
864
905
  const body = Buffer.from(await upstream.arrayBuffer());
@@ -870,24 +911,41 @@ function getTopupArtifactUrl() {
870
911
  function getTopupUrl() {
871
912
  return getTopupArtifactUrl() ?? getTopupUrl$1();
872
913
  }
914
+ async function stopTopupServer() {
915
+ if (!artifactServerState) return;
916
+ const { server } = artifactServerState;
917
+ artifactServerState = null;
918
+ await new Promise((resolve) => server.close(() => resolve()));
919
+ }
873
920
  async function startTopupServer(account) {
874
- const wallet = toWalletData(account);
875
- if (artifactServerState && artifactServerState.address.toLowerCase() === wallet.address.toLowerCase()) return artifactServerState.url;
876
- const assetServerUrl = await startTopupServer$1(wallet);
877
- if (artifactServerState) {
878
- await new Promise((resolve) => artifactServerState?.server.close(() => resolve()));
879
- artifactServerState = null;
880
- }
921
+ const walletAddress = toWalletAddress(account);
922
+ if (artifactServerState && artifactServerState.address.toLowerCase() === walletAddress.toLowerCase()) return artifactServerState.url;
923
+ const assetServerUrl = await startTopupServer$1(walletAddress);
924
+ if (artifactServerState) await stopTopupServer();
881
925
  const server = createServer((req, res) => {
882
926
  const reqUrl = req.url ?? "/";
883
- const pathname = new URL(reqUrl, "http://localhost").pathname;
927
+ let proxyTarget;
928
+ try {
929
+ proxyTarget = normalizeProxyTarget(reqUrl);
930
+ } catch (err) {
931
+ if (err instanceof ProxyRequestError) {
932
+ send(res, err.status, JSON.stringify({ error: err.message }) + "\n", "application/json; charset=utf-8");
933
+ return;
934
+ }
935
+ send(res, 400, JSON.stringify({ error: "Invalid request target" }) + "\n", "application/json; charset=utf-8");
936
+ return;
937
+ }
938
+ const { pathname } = proxyTarget;
884
939
  if (pathname === "/" || pathname === "/index.html") {
885
- const artifactUrl = artifactServerState?.url ?? assetServerUrl;
886
- send(res, 200, generateArtifactHtml(wallet.address, artifactUrl), "text/html; charset=utf-8");
940
+ send(res, 200, generateArtifactHtml(walletAddress, artifactServerState?.url ?? assetServerUrl), "text/html; charset=utf-8");
887
941
  return;
888
942
  }
889
943
  if (pathname.startsWith("/assets/") || pathname.startsWith("/api/")) {
890
- proxyToCopiedServer(reqUrl, res, assetServerUrl).catch((err) => {
944
+ proxyToCopiedServer(proxyTarget, res, assetServerUrl).catch((err) => {
945
+ if (err instanceof ProxyRequestError) {
946
+ send(res, err.status, JSON.stringify({ error: err.message }) + "\n", "application/json; charset=utf-8");
947
+ return;
948
+ }
891
949
  send(res, 502, JSON.stringify({ error: err.message }) + "\n", "application/json; charset=utf-8");
892
950
  });
893
951
  return;
@@ -906,7 +964,7 @@ async function startTopupServer(account) {
906
964
  });
907
965
  });
908
966
  artifactServerState = {
909
- address: wallet.address,
967
+ address: walletAddress,
910
968
  assetServerUrl,
911
969
  server,
912
970
  url
@@ -916,4 +974,4 @@ async function startTopupServer(account) {
916
974
  //#endregion
917
975
  export { generateArtifactHtml as i, startTopupServer as n, topup_server_exports as r, getTopupUrl as t };
918
976
 
919
- //# sourceMappingURL=topup-server-6MH7q73X.mjs.map
977
+ //# sourceMappingURL=topup-server-BJgVw6Jt.mjs.map