chain-insights 0.37.3 → 0.42.1

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 (89) hide show
  1. package/README.md +31 -1
  2. package/bin/install.cjs +59 -2
  3. package/dist/{action-log-diTHyMbV.mjs → action-log-D49Par9P.mjs} +2 -2
  4. package/dist/{action-log-diTHyMbV.mjs.map → action-log-D49Par9P.mjs.map} +1 -1
  5. package/dist/{active-BQopLul8.mjs → active-Dkf8AB1R.mjs} +2 -2
  6. package/dist/{active-BQopLul8.mjs.map → active-Dkf8AB1R.mjs.map} +1 -1
  7. package/dist/apps/claude-view.html +117 -0
  8. package/dist/{capabilities-BbAQ4Tfa.cjs → capabilities-A8fsfP24.cjs} +4 -1
  9. package/dist/{capabilities-C8DQSfyW.mjs → capabilities-DfWL6n_g.mjs} +7 -4
  10. package/dist/capabilities-DfWL6n_g.mjs.map +1 -0
  11. package/dist/cli.cjs +84 -133
  12. package/dist/cli.mjs +102 -151
  13. package/dist/cli.mjs.map +1 -1
  14. package/dist/{client-CHYskPnG.mjs → client-gsLi528K.mjs} +4 -4
  15. package/dist/{client-CHYskPnG.mjs.map → client-gsLi528K.mjs.map} +1 -1
  16. package/dist/{config-DbbPiGk0.mjs → config-9MabJh5R.mjs} +36 -4
  17. package/dist/config-9MabJh5R.mjs.map +1 -0
  18. package/dist/{config-B542AM4w.cjs → config-BxtMoo9c.cjs} +33 -1
  19. package/dist/{create-C0buxEfU.mjs → create-CpfZq2fx.mjs} +2 -2
  20. package/dist/{create-C0buxEfU.mjs.map → create-CpfZq2fx.mjs.map} +1 -1
  21. package/dist/flows-BfUlyWvJ.mjs +344 -0
  22. package/dist/flows-BfUlyWvJ.mjs.map +1 -0
  23. package/dist/flows-CzsxUf35.cjs +394 -0
  24. package/dist/index.cjs +5 -4
  25. package/dist/index.d.cts.map +1 -1
  26. package/dist/index.d.mts.map +1 -1
  27. package/dist/index.mjs +7 -6
  28. package/dist/mcp-proxy.cjs +215 -45
  29. package/dist/mcp-proxy.d.cts +7 -0
  30. package/dist/mcp-proxy.d.cts.map +1 -1
  31. package/dist/mcp-proxy.d.mts +7 -0
  32. package/dist/mcp-proxy.d.mts.map +1 -1
  33. package/dist/mcp-proxy.mjs +219 -52
  34. package/dist/mcp-proxy.mjs.map +1 -1
  35. package/dist/print-result-BEA_PyNz.cjs +178 -0
  36. package/dist/print-result-k9kXVR2c.mjs +151 -0
  37. package/dist/print-result-k9kXVR2c.mjs.map +1 -0
  38. package/dist/{public-tools-DWONw7kZ.cjs → public-tools-3yTC17cA.cjs} +1 -6
  39. package/dist/{public-tools-CDpFHbZ8.mjs → public-tools-CZyeptJQ.mjs} +3 -8
  40. package/dist/public-tools-CZyeptJQ.mjs.map +1 -0
  41. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
  42. package/dist/{schema-B0OuHW_l.mjs → schema-KjZqCHbu.mjs} +3 -3
  43. package/dist/schema-KjZqCHbu.mjs.map +1 -0
  44. package/dist/schema-cache-DJdU4MIP.mjs.map +1 -1
  45. package/dist/{schema-CtFWYoJz.cjs → schema-tX0U0-VI.cjs} +1 -1
  46. package/dist/{subscription-status-MyeqOmbA.mjs → subscription-status-CJ-DaMW9.mjs} +2 -2
  47. package/dist/{subscription-status-MyeqOmbA.mjs.map → subscription-status-CJ-DaMW9.mjs.map} +1 -1
  48. package/dist/{tool-visibility-CyZqYnQA.cjs → tool-visibility-BdsE6vQB.cjs} +27 -4
  49. package/dist/{tool-visibility-CJV9HezW.mjs → tool-visibility-wW2vAaj9.mjs} +22 -6
  50. package/dist/tool-visibility-wW2vAaj9.mjs.map +1 -0
  51. package/dist/{tools-BRQAPF2R.mjs → tools-BnThuvE2.mjs} +3 -3
  52. package/dist/{tools-BRQAPF2R.mjs.map → tools-BnThuvE2.mjs.map} +1 -1
  53. package/dist/topup-server-BIbSfvX5.cjs +146 -0
  54. package/dist/{topup-server-YN3iHrHW.cjs → topup-server-Bh7MlzRt.cjs} +485 -239
  55. package/dist/{topup-server-BLdF1SFQ.mjs → topup-server-BrkPhqJG.mjs} +488 -242
  56. package/dist/topup-server-BrkPhqJG.mjs.map +1 -0
  57. package/dist/topup-server-DzeM_YAt.mjs +131 -0
  58. package/dist/topup-server-DzeM_YAt.mjs.map +1 -0
  59. package/dist/{transport-error-DmC9WLTW.cjs → transport-error-CenkuT7T.cjs} +5 -2
  60. package/dist/{transport-error-CSEb6sPr.mjs → transport-error-S0za4TCe.mjs} +6 -3
  61. package/dist/transport-error-S0za4TCe.mjs.map +1 -0
  62. package/dist/{update-MsoEFbhn.mjs → update-7TewxG_2.mjs} +2 -2
  63. package/dist/{update-MsoEFbhn.mjs.map → update-7TewxG_2.mjs.map} +1 -1
  64. package/dist/{update-RKd0UW90.cjs → update-Rn1RSqrb.cjs} +7 -7
  65. package/dist/{usage-status-CHiOPA-2.mjs → usage-status-CXOnrq-s.mjs} +2 -2
  66. package/dist/{usage-status-CHiOPA-2.mjs.map → usage-status-CXOnrq-s.mjs.map} +1 -1
  67. package/dist/{wallet-COcm4oIb.mjs → wallet-GNDl3FkR.mjs} +2 -2
  68. package/dist/{wallet-COcm4oIb.mjs.map → wallet-GNDl3FkR.mjs.map} +1 -1
  69. package/docs/graph-query-compatibility.md +33 -27
  70. package/docs/graph-tools.md +138 -39
  71. package/docs/mcp-proxy.md +39 -8
  72. package/docs/stability.md +2 -1
  73. package/package.json +5 -6
  74. package/skills/chain-insights-cypher/SKILL.md +249 -52
  75. package/skills/chain-insights-cypher/agents/openai.yaml +1 -1
  76. package/skills/chain-insights-schema-evm/SKILL.md +75 -47
  77. package/dist/capabilities-C8DQSfyW.mjs.map +0 -1
  78. package/dist/config-DbbPiGk0.mjs.map +0 -1
  79. package/dist/html-generator-BCDtYcGs.mjs +0 -16
  80. package/dist/html-generator-BCDtYcGs.mjs.map +0 -1
  81. package/dist/html-generator-DFwMRWN3.cjs +0 -16
  82. package/dist/public-tools-CDpFHbZ8.mjs.map +0 -1
  83. package/dist/schema-B0OuHW_l.mjs.map +0 -1
  84. package/dist/tool-visibility-CJV9HezW.mjs.map +0 -1
  85. package/dist/topup-server-BLdF1SFQ.mjs.map +0 -1
  86. package/dist/transport-error-CSEb6sPr.mjs.map +0 -1
  87. package/dist/version-BA3J8hu4.mjs +0 -8
  88. package/dist/version-BA3J8hu4.mjs.map +0 -1
  89. package/dist/version-CO9Or_YV.cjs +0 -17
package/README.md CHANGED
@@ -39,7 +39,7 @@ cia mcp tools # list remote graph tools
39
39
  Sixty seconds gets you the CLI, a network overview, a CIA workflow, and the
40
40
  available remote graph tools for Robinhood.
41
41
  To call the same tools from an agent, register the MCP proxy:
42
- `cia setup claude-code` (or `codex` / `hermes`).
42
+ `cia setup claude-code` (or `claude-desktop` / `codex` / `hermes`).
43
43
 
44
44
  CLI results are human-readable by default. Add `--json` when another tool or
45
45
  script needs indented JSON:
@@ -55,6 +55,29 @@ cia workflow aml-address-risk --json \
55
55
  The AML address-risk contract uses the latest version when `version` is
56
56
  omitted. Pin the current contract with `--version v1` on the workflow command.
57
57
 
58
+ ### Claude plugin
59
+
60
+ The `plugin/` folder is the Chain Insights plugin for Claude. It starts the
61
+ local Chain Insights MCP proxy on your computer over stdio
62
+ (`chain-insights-mcp-proxy` from the npm package, through `npx`), and brings the
63
+ `chain-insights-cypher` and `chain-insights-schema-evm` skills. The proxy
64
+ serves the money-flow, query-table and balance views itself, and reaches the
65
+ Chain Insights Graph for the data.
66
+
67
+ - **Runs in:** Claude Desktop, Cowork sessions on your computer, and Claude Code.
68
+ - **Cannot start it:** claude.ai on the web, the Claude mobile apps and Cowork
69
+ cloud sessions. They run no program on your computer.
70
+
71
+ The plugin needs Node.js 22 or newer. Add this repository as a plugin
72
+ marketplace, then install the `chain-insights` plugin from it.
73
+
74
+ Without the plugin, `npm install -g chain-insights` then
75
+ `cia setup claude-desktop` registers the installed proxy in Claude Desktop
76
+ directly. Restart Claude Desktop afterwards.
77
+
78
+ The plugin skills are copies of `skills/`. After editing a skill, run
79
+ `node scripts/sync-plugin-skills.mjs`; a test fails while the copies differ.
80
+
58
81
  ## Purpose And Ownership
59
82
 
60
83
  One public npm package (`chain-insights`) providing the `cia` CLI and a
@@ -95,6 +118,7 @@ Never touches:
95
118
  | `meta_usage_status` | Check the caller's daily free-tier graph query allowance |
96
119
  | `meta_help` | Show Chain Insights tool and workflow guidance |
97
120
  | `wallet_balance` | Show the local payment wallet amount |
121
+ | `wallet_topup` | Show the local payment wallet address and its QR code for a USDC top-up on Base |
98
122
 
99
123
  ## Dependencies
100
124
 
@@ -287,6 +311,12 @@ add `/mcp`.
287
311
  cia config set graphMcpEndpoint https://mcp.chain-insights.ai/
288
312
  ```
289
313
 
314
+ To undo every local override at once, after an upgrade or a test setup:
315
+
316
+ ```bash
317
+ cia config reset # production endpoint, paid mode, no test key; wallet kept; old file in config.json.bak
318
+ ```
319
+
290
320
  Optional one-shot override from the environment:
291
321
 
292
322
  ```bash
package/bin/install.cjs CHANGED
@@ -22,12 +22,16 @@ const args = process.argv.slice(2)
22
22
  const hasClaude = args.includes('--claude')
23
23
  const hasCodex = args.includes('--codex')
24
24
  const hasHermes = args.includes('--hermes')
25
+ const hasClaudeDesktop = args.includes('--claude-desktop')
25
26
  const hasLocal = args.includes('--local')
26
27
 
27
- if (!hasClaude && !hasCodex && !hasHermes && !hasLocal) {
28
+ if (!hasClaude && !hasCodex && !hasHermes && !hasClaudeDesktop && !hasLocal) {
28
29
  console.log(`\n${bold}chain-insights installer${reset}`)
29
- console.log(`\nUsage: node bin/install.cjs --claude | --codex | --hermes`)
30
+ console.log(`\nUsage: node bin/install.cjs --claude | --claude-desktop | --codex | --hermes`)
30
31
  console.log(` ${cyan}--claude${reset} Install Claude Code skills globally to ~/.claude/skills/`)
32
+ console.log(
33
+ ` ${cyan}--claude-desktop${reset} Register MCP in Claude Desktop (draws the money-flow views)`
34
+ )
31
35
  console.log(
32
36
  ` ${cyan}--codex${reset} Install Codex skills globally to ~/.codex/skills/ and register MCP`
33
37
  )
@@ -273,6 +277,59 @@ if (hasHermes) {
273
277
  console.log(` ${cyan}Hermes MCP:${reset} registered in ${hermesConfig}`)
274
278
  }
275
279
 
280
+ // Claude Desktop reads one JSON file per user. It is not a terminal program, so
281
+ // it does not see the PATH of a shell; the proxy is started with this Node's
282
+ // absolute path. Skills reach Claude Desktop through the plugin, not this file.
283
+ function claudeDesktopConfigPath() {
284
+ if (process.platform === 'darwin') {
285
+ return path.join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
286
+ }
287
+ if (process.platform === 'win32') {
288
+ const appData = process.env['APPDATA'] || path.join(homeDir, 'AppData', 'Roaming')
289
+ return path.join(appData, 'Claude', 'claude_desktop_config.json')
290
+ }
291
+ const configHome = process.env['XDG_CONFIG_HOME'] || path.join(homeDir, '.config')
292
+ return path.join(configHome, 'Claude', 'claude_desktop_config.json')
293
+ }
294
+
295
+ function installClaudeDesktopMcp(configFile, proxyPath) {
296
+ fs.mkdirSync(path.dirname(configFile), { recursive: true })
297
+ let config = {}
298
+ if (fs.existsSync(configFile)) {
299
+ const text = fs.readFileSync(configFile, 'utf8')
300
+ if (text.trim() !== '') {
301
+ try {
302
+ config = JSON.parse(text)
303
+ } catch {
304
+ throw new Error(`${configFile} is not valid JSON; fix it, then run the setup again`)
305
+ }
306
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
307
+ throw new Error(`${configFile} does not hold a JSON object; fix it, then run the setup again`)
308
+ }
309
+ }
310
+ fs.copyFileSync(configFile, `${configFile}.bak`)
311
+ }
312
+ const servers =
313
+ config.mcpServers && typeof config.mcpServers === 'object' && !Array.isArray(config.mcpServers)
314
+ ? config.mcpServers
315
+ : {}
316
+ servers['chain-insights'] = { command: process.execPath, args: [proxyPath] }
317
+ config.mcpServers = servers
318
+ fs.writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
319
+ }
320
+
321
+ if (hasClaudeDesktop) {
322
+ const desktopConfig = claudeDesktopConfigPath()
323
+ try {
324
+ installClaudeDesktopMcp(desktopConfig, proxyBinPath)
325
+ console.log(` ${cyan}Claude Desktop MCP:${reset} registered in ${desktopConfig}`)
326
+ console.log(` ${dim}Restart Claude Desktop to load it.${reset}`)
327
+ } catch (err) {
328
+ console.error(` Claude Desktop MCP: ${err.message}`)
329
+ process.exit(1)
330
+ }
331
+ }
332
+
276
333
  // ─── 5. Print installation summary ────────────────────────────────────────
277
334
 
278
335
  console.log(`\n${bold}${green}Chain Insights installed${reset}`)
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import path from "node:path";
3
3
  import { appendFile, mkdir } from "node:fs/promises";
4
4
  //#region src/mcp/action-log.ts
@@ -90,4 +90,4 @@ function installActionLogging(client) {
90
90
  //#endregion
91
91
  export { action_log_exports as n, appendActionLog as r, actionLogSignalsFromResult as t };
92
92
 
93
- //# sourceMappingURL=action-log-diTHyMbV.mjs.map
93
+ //# sourceMappingURL=action-log-D49Par9P.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-log-diTHyMbV.mjs","names":[],"sources":["../src/mcp/action-log.ts"],"sourcesContent":["// src/mcp/action-log.ts\n// Optional append-only record of every MCP tool invocation, for operators\n// running cia unattended who need to audit what ran and why a result looked\n// the way it did. Off unless CIA_ACTION_LOG names a path.\n//\n// `warnings` and `search_limits` are captured deliberately: they are how a\n// reader tells \"found nothing\" from \"hit a cap\", which is invisible in the\n// result alone.\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport interface ActionLogEntry {\n timestamp: number\n tool: string\n args: Record<string, unknown>\n outcome: 'ok' | 'error'\n duration_ms: number\n result_counts?: Record<string, number>\n warnings?: string[]\n search_limits?: Record<string, unknown>\n error?: string\n}\n\nexport function actionLogPath(): string | undefined {\n const configured = process.env.CIA_ACTION_LOG?.trim()\n return configured ? configured : undefined\n}\n\n// Some special filesystems (notably /proc) answer mkdir on a child with\n// ENOENT even though the parent visibly exists. Node's fs.mkdir(dir,\n// {recursive: true}) treats that as a transient race and retries with no\n// cap, which spins forever rather than rejecting — confirmed via strace\n// (an unbounded, rapid mkdir/statx loop; the event loop stays live and the\n// process never blocks, it just never stops). Walking the path ourselves,\n// one plain mkdir per component with no retry, makes every case settle in\n// a bounded number of syscalls: EEXIST is expected and skipped, anything\n// else (including that ENOENT-on-existing-parent case) fails once instead\n// of retrying.\nasync function ensureDir(dir: string): Promise<void> {\n const segments = dir.split(path.sep).filter(Boolean)\n let current = path.isAbsolute(dir) ? path.sep : ''\n for (const segment of segments) {\n current = current ? path.join(current, segment) : segment\n try {\n await mkdir(current)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') continue\n throw err\n }\n }\n}\n\n// A residual, undemonstrated risk remains distinct from the mkdir retry loop\n// above: a single mkdir/appendFile syscall genuinely wedged on a stalled\n// filesystem (e.g. a hung network mount) blocks like any normal blocking I/O\n// would. Node's fs promises do not support cancelling an in-flight mkdir, so\n// a bounded wait here only frees the *caller* — the wedged operation itself\n// keeps running in the background until the OS/filesystem gives up on its\n// own. That is an accepted, documented limitation, not a bug this module can\n// fix from userland; it is unrelated to (and much rarer than) the retry-loop\n// case ensureDir() eliminates above.\nconst WRITE_TIMEOUT_MS = 2000\n\nasync function writeEntry(file: string, entry: ActionLogEntry): Promise<void> {\n await ensureDir(path.dirname(file))\n await appendFile(file, JSON.stringify(entry) + '\\n', 'utf8')\n}\n\n// Never throws, and never hangs beyond WRITE_TIMEOUT_MS. A failure (or a\n// stall) recording an action must not fail (or stall) the action:\n// observability that can break the thing it observes is worse than none.\nexport async function appendActionLog(entry: ActionLogEntry): Promise<void> {\n const file = actionLogPath()\n if (!file) return\n try {\n await Promise.race([\n writeEntry(file, entry),\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, WRITE_TIMEOUT_MS)\n timer.unref?.()\n }),\n ])\n } catch {\n // Intentionally swallowed.\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Exported so BOTH client paths log identically: the MCP proxy's remote client\n// and the CLI's own client. They are separate clients, and wiring only one is how\n// an unattended instance ends up with an empty log while the mechanism 'works'.\nexport function actionLogSignalsFromResult(result: unknown): {\n warnings?: string[]\n search_limits?: Record<string, unknown>\n} {\n const structuredContent = isRecord(result) ? result['structuredContent'] : undefined\n if (!isRecord(structuredContent)) return {}\n const warnings = Array.isArray(structuredContent['warnings'])\n ? (structuredContent['warnings'] as string[])\n : undefined\n const input = structuredContent['input']\n const search_limits =\n isRecord(input) && isRecord(input['search_limits'])\n ? (input['search_limits'] as Record<string, unknown>)\n : undefined\n return { warnings, search_limits }\n}\n\ntype CallToolFn = (...args: never[]) => Promise<unknown>\n\n// Wrap a client's callTool so every invocation is recorded. Returns the client.\n// Idempotent-unsafe: call once per client.\nexport function installActionLogging<T extends { callTool: CallToolFn }>(client: T): T {\n const original = client.callTool.bind(client) as CallToolFn\n const wrapped = (async (...args: never[]) => {\n const startedAt = Date.now()\n const input = (args[0] ?? {}) as { name?: unknown; arguments?: Record<string, unknown> }\n const tool = String(input.name ?? 'unknown')\n const toolArgs = input.arguments ?? {}\n try {\n const result = await original(...args)\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n await appendActionLog({\n timestamp: startedAt,\n tool,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as CallToolFn\n client.callTool = wrapped as T['callTool']\n return client\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,gBAAoC;CAClD,MAAM,aAAa,QAAQ,IAAI,gBAAgB,KAAK;CACpD,OAAO,aAAa,aAAa,KAAA;AACnC;AAYA,eAAe,UAAU,KAA4B;CACnD,MAAM,WAAW,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,OAAO;CACnD,IAAI,UAAU,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM;CAChD,KAAK,MAAM,WAAW,UAAU;EAC9B,UAAU,UAAU,KAAK,KAAK,SAAS,OAAO,IAAI;EAClD,IAAI;GACF,MAAM,MAAM,OAAO;EACrB,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAAU;GACtD,MAAM;EACR;CACF;AACF;AAWA,MAAM,mBAAmB;AAEzB,eAAe,WAAW,MAAc,OAAsC;CAC5E,MAAM,UAAU,KAAK,QAAQ,IAAI,CAAC;CAClC,MAAM,WAAW,MAAM,KAAK,UAAU,KAAK,IAAI,MAAM,MAAM;AAC7D;AAKA,eAAsB,gBAAgB,OAAsC;CAC1E,MAAM,OAAO,cAAc;CAC3B,IAAI,CAAC,MAAM;CACX,IAAI;EACF,MAAM,QAAQ,KAAK,CACjB,WAAW,MAAM,KAAK,GACtB,IAAI,SAAe,YAAY;GAE7B,WADyB,SAAS,gBAC9B,CAAC,CAAC,QAAQ;EAChB,CAAC,CACH,CAAC;CACH,QAAQ,CAER;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAKA,SAAgB,2BAA2B,QAGzC;CACA,MAAM,oBAAoB,SAAS,MAAM,IAAI,OAAO,uBAAuB,KAAA;CAC3E,IAAI,CAAC,SAAS,iBAAiB,GAAG,OAAO,CAAC;CAC1C,MAAM,WAAW,MAAM,QAAQ,kBAAkB,WAAW,IACvD,kBAAkB,cACnB,KAAA;CACJ,MAAM,QAAQ,kBAAkB;CAKhC,OAAO;EAAE;EAAU,eAHjB,SAAS,KAAK,KAAK,SAAS,MAAM,gBAAgB,IAC7C,MAAM,mBACP,KAAA;CAC2B;AACnC;AAMA,SAAgB,qBAAyD,QAAc;CACrF,MAAM,WAAW,OAAO,SAAS,KAAK,MAAM;CAC5C,MAAM,WAAW,OAAO,GAAG,SAAkB;EAC3C,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,QAAS,KAAK,MAAM,CAAC;EAC3B,MAAM,OAAO,OAAO,MAAM,QAAQ,SAAS;EAC3C,MAAM,WAAW,MAAM,aAAa,CAAC;EACrC,IAAI;GACF,MAAM,SAAS,MAAM,SAAS,GAAG,IAAI;GACrC,MAAM,EAAE,UAAU,kBAAkB,2BAA2B,MAAM;GACrE,MAAM,gBAAgB;IACpB,WAAW;IACX;IACA,MAAM;IACN,SAAS;IACT,aAAa,KAAK,IAAI,IAAI;IAC1B;IACA;GACF,CAAC;GACD,OAAO;EACT,SAAS,KAAK;GACZ,MAAM,gBAAgB;IACpB,WAAW;IACX;IACA,MAAM;IACN,SAAS;IACT,aAAa,KAAK,IAAI,IAAI;IAC1B,OAAQ,IAAc;GACxB,CAAC;GACD,MAAM;EACR;CACF;CACA,OAAO,WAAW;CAClB,OAAO;AACT"}
1
+ {"version":3,"file":"action-log-D49Par9P.mjs","names":[],"sources":["../src/mcp/action-log.ts"],"sourcesContent":["// src/mcp/action-log.ts\n// Optional append-only record of every MCP tool invocation, for operators\n// running cia unattended who need to audit what ran and why a result looked\n// the way it did. Off unless CIA_ACTION_LOG names a path.\n//\n// `warnings` and `search_limits` are captured deliberately: they are how a\n// reader tells \"found nothing\" from \"hit a cap\", which is invisible in the\n// result alone.\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport interface ActionLogEntry {\n timestamp: number\n tool: string\n args: Record<string, unknown>\n outcome: 'ok' | 'error'\n duration_ms: number\n result_counts?: Record<string, number>\n warnings?: string[]\n search_limits?: Record<string, unknown>\n error?: string\n}\n\nexport function actionLogPath(): string | undefined {\n const configured = process.env.CIA_ACTION_LOG?.trim()\n return configured ? configured : undefined\n}\n\n// Some special filesystems (notably /proc) answer mkdir on a child with\n// ENOENT even though the parent visibly exists. Node's fs.mkdir(dir,\n// {recursive: true}) treats that as a transient race and retries with no\n// cap, which spins forever rather than rejecting — confirmed via strace\n// (an unbounded, rapid mkdir/statx loop; the event loop stays live and the\n// process never blocks, it just never stops). Walking the path ourselves,\n// one plain mkdir per component with no retry, makes every case settle in\n// a bounded number of syscalls: EEXIST is expected and skipped, anything\n// else (including that ENOENT-on-existing-parent case) fails once instead\n// of retrying.\nasync function ensureDir(dir: string): Promise<void> {\n const segments = dir.split(path.sep).filter(Boolean)\n let current = path.isAbsolute(dir) ? path.sep : ''\n for (const segment of segments) {\n current = current ? path.join(current, segment) : segment\n try {\n await mkdir(current)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') continue\n throw err\n }\n }\n}\n\n// A residual, undemonstrated risk remains distinct from the mkdir retry loop\n// above: a single mkdir/appendFile syscall genuinely wedged on a stalled\n// filesystem (e.g. a hung network mount) blocks like any normal blocking I/O\n// would. Node's fs promises do not support cancelling an in-flight mkdir, so\n// a bounded wait here only frees the *caller* — the wedged operation itself\n// keeps running in the background until the OS/filesystem gives up on its\n// own. That is an accepted, documented limitation, not a bug this module can\n// fix from userland; it is unrelated to (and much rarer than) the retry-loop\n// case ensureDir() eliminates above.\nconst WRITE_TIMEOUT_MS = 2000\n\nasync function writeEntry(file: string, entry: ActionLogEntry): Promise<void> {\n await ensureDir(path.dirname(file))\n await appendFile(file, JSON.stringify(entry) + '\\n', 'utf8')\n}\n\n// Never throws, and never hangs beyond WRITE_TIMEOUT_MS. A failure (or a\n// stall) recording an action must not fail (or stall) the action:\n// observability that can break the thing it observes is worse than none.\nexport async function appendActionLog(entry: ActionLogEntry): Promise<void> {\n const file = actionLogPath()\n if (!file) return\n try {\n await Promise.race([\n writeEntry(file, entry),\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, WRITE_TIMEOUT_MS)\n timer.unref?.()\n }),\n ])\n } catch {\n // Intentionally swallowed.\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Exported so BOTH client paths log identically: the MCP proxy's remote client\n// and the CLI's own client. They are separate clients, and wiring only one is how\n// an unattended instance ends up with an empty log while the mechanism 'works'.\nexport function actionLogSignalsFromResult(result: unknown): {\n warnings?: string[]\n search_limits?: Record<string, unknown>\n} {\n const structuredContent = isRecord(result) ? result['structuredContent'] : undefined\n if (!isRecord(structuredContent)) return {}\n const warnings = Array.isArray(structuredContent['warnings'])\n ? (structuredContent['warnings'] as string[])\n : undefined\n const input = structuredContent['input']\n const search_limits =\n isRecord(input) && isRecord(input['search_limits'])\n ? (input['search_limits'] as Record<string, unknown>)\n : undefined\n return { warnings, search_limits }\n}\n\ntype CallToolFn = (...args: never[]) => Promise<unknown>\n\n// Wrap a client's callTool so every invocation is recorded. Returns the client.\n// Idempotent-unsafe: call once per client.\nexport function installActionLogging<T extends { callTool: CallToolFn }>(client: T): T {\n const original = client.callTool.bind(client) as CallToolFn\n const wrapped = (async (...args: never[]) => {\n const startedAt = Date.now()\n const input = (args[0] ?? {}) as { name?: unknown; arguments?: Record<string, unknown> }\n const tool = String(input.name ?? 'unknown')\n const toolArgs = input.arguments ?? {}\n try {\n const result = await original(...args)\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n await appendActionLog({\n timestamp: startedAt,\n tool,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as CallToolFn\n client.callTool = wrapped as T['callTool']\n return client\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,gBAAoC;CAClD,MAAM,aAAa,QAAQ,IAAI,gBAAgB,KAAK;CACpD,OAAO,aAAa,aAAa,KAAA;AACnC;AAYA,eAAe,UAAU,KAA4B;CACnD,MAAM,WAAW,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,OAAO;CACnD,IAAI,UAAU,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM;CAChD,KAAK,MAAM,WAAW,UAAU;EAC9B,UAAU,UAAU,KAAK,KAAK,SAAS,OAAO,IAAI;EAClD,IAAI;GACF,MAAM,MAAM,OAAO;EACrB,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAAU;GACtD,MAAM;EACR;CACF;AACF;AAWA,MAAM,mBAAmB;AAEzB,eAAe,WAAW,MAAc,OAAsC;CAC5E,MAAM,UAAU,KAAK,QAAQ,IAAI,CAAC;CAClC,MAAM,WAAW,MAAM,KAAK,UAAU,KAAK,IAAI,MAAM,MAAM;AAC7D;AAKA,eAAsB,gBAAgB,OAAsC;CAC1E,MAAM,OAAO,cAAc;CAC3B,IAAI,CAAC,MAAM;CACX,IAAI;EACF,MAAM,QAAQ,KAAK,CACjB,WAAW,MAAM,KAAK,GACtB,IAAI,SAAe,YAAY;GAE7B,WADyB,SAAS,gBAC9B,CAAC,CAAC,QAAQ;EAChB,CAAC,CACH,CAAC;CACH,QAAQ,CAER;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAKA,SAAgB,2BAA2B,QAGzC;CACA,MAAM,oBAAoB,SAAS,MAAM,IAAI,OAAO,uBAAuB,KAAA;CAC3E,IAAI,CAAC,SAAS,iBAAiB,GAAG,OAAO,CAAC;CAC1C,MAAM,WAAW,MAAM,QAAQ,kBAAkB,WAAW,IACvD,kBAAkB,cACnB,KAAA;CACJ,MAAM,QAAQ,kBAAkB;CAKhC,OAAO;EAAE;EAAU,eAHjB,SAAS,KAAK,KAAK,SAAS,MAAM,gBAAgB,IAC7C,MAAM,mBACP,KAAA;CAC2B;AACnC;AAMA,SAAgB,qBAAyD,QAAc;CACrF,MAAM,WAAW,OAAO,SAAS,KAAK,MAAM;CAC5C,MAAM,WAAW,OAAO,GAAG,SAAkB;EAC3C,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,QAAS,KAAK,MAAM,CAAC;EAC3B,MAAM,OAAO,OAAO,MAAM,QAAQ,SAAS;EAC3C,MAAM,WAAW,MAAM,aAAa,CAAC;EACrC,IAAI;GACF,MAAM,SAAS,MAAM,SAAS,GAAG,IAAI;GACrC,MAAM,EAAE,UAAU,kBAAkB,2BAA2B,MAAM;GACrE,MAAM,gBAAgB;IACpB,WAAW;IACX;IACA,MAAM;IACN,SAAS;IACT,aAAa,KAAK,IAAI,IAAI;IAC1B;IACA;GACF,CAAC;GACD,OAAO;EACT,SAAS,KAAK;GACZ,MAAM,gBAAgB;IACpB,WAAW;IACX;IACA,MAAM;IACN,SAAS;IACT,aAAa,KAAK,IAAI,IAAI;IAC1B,OAAQ,IAAc;GACxB,CAAC;GACD,MAAM;EACR;CACF;CACA,OAAO,WAAW;CAClB,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
@@ -45,4 +45,4 @@ function activeDataDir(fallbackDataDir) {
45
45
  //#endregion
46
46
  export { findActiveWorkspace as n, active_exports as t };
47
47
 
48
- //# sourceMappingURL=active-BQopLul8.mjs.map
48
+ //# sourceMappingURL=active-Dkf8AB1R.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"active-BQopLul8.mjs","names":[],"sources":["../src/workspace/active.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\ninterface WorkspaceConfig {\n schema?: string\n workspace_root?: string\n artifacts_dir?: string\n domain_hints?: unknown\n}\n\nexport interface ActiveWorkspace {\n root: string\n metadataDir: string\n artifactsRoot: string\n domainHints: string[]\n}\n\nfunction workspaceFromRoot(rootCandidate: string): ActiveWorkspace | null {\n const root = path.resolve(rootCandidate)\n const metadataDir = path.join(root, '.chain-insights')\n const markerPath = path.join(metadataDir, 'workspace.json')\n if (!fs.existsSync(markerPath)) return null\n\n const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as WorkspaceConfig\n if (parsed.schema !== 'chain-insights.workspace.v1') return null\n\n const workspaceRoot = path.resolve(parsed.workspace_root ?? root)\n const artifactsDir = parsed.artifacts_dir ?? 'artifacts'\n const domainHints = Array.isArray(parsed.domain_hints)\n ? parsed.domain_hints.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0\n )\n : []\n return {\n root: workspaceRoot,\n metadataDir: path.join(workspaceRoot, '.chain-insights'),\n artifactsRoot: path.resolve(workspaceRoot, artifactsDir),\n domainHints,\n }\n}\n\nexport function findActiveWorkspace(startDir = process.cwd()): ActiveWorkspace | null {\n const envWorkspace = process.env['CHAIN_INSIGHTS_WORKSPACE']?.trim()\n if (envWorkspace) {\n const active = workspaceFromRoot(envWorkspace)\n if (active) return active\n }\n\n let current = path.resolve(startDir)\n while (true) {\n const active = workspaceFromRoot(current)\n if (active) return active\n\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport function activeMetadataDir(): string {\n return findActiveWorkspace()?.metadataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n\nexport function activeArtifactsRoot(): string {\n return (\n findActiveWorkspace()?.artifactsRoot ?? path.join(os.homedir(), '.chain-insights', 'artifacts')\n )\n}\n\nexport function activeDataDir(fallbackDataDir?: string): string {\n return (\n findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), '.chain-insights')\n )\n}\n"],"mappings":";;;;;;;;;AAkBA,SAAS,kBAAkB,eAA+C;CACxE,MAAM,OAAO,KAAK,QAAQ,aAAa;CACvC,MAAM,cAAc,KAAK,KAAK,MAAM,iBAAiB;CACrD,MAAM,aAAa,KAAK,KAAK,aAAa,gBAAgB;CAC1D,IAAI,CAAC,GAAG,WAAW,UAAU,GAAG,OAAO;CAEvC,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,YAAY,MAAM,CAAC;CAC7D,IAAI,OAAO,WAAW,+BAA+B,OAAO;CAE5D,MAAM,gBAAgB,KAAK,QAAQ,OAAO,kBAAkB,IAAI;CAChE,MAAM,eAAe,OAAO,iBAAiB;CAC7C,MAAM,cAAc,MAAM,QAAQ,OAAO,YAAY,IACjD,OAAO,aAAa,QACjB,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,CACjF,IACA,CAAC;CACL,OAAO;EACL,MAAM;EACN,aAAa,KAAK,KAAK,eAAe,iBAAiB;EACvD,eAAe,KAAK,QAAQ,eAAe,YAAY;EACvD;CACF;AACF;AAEA,SAAgB,oBAAoB,WAAW,QAAQ,IAAI,GAA2B;CACpF,MAAM,eAAe,QAAQ,IAAI,2BAA2B,EAAE,KAAK;CACnE,IAAI,cAAc;EAChB,MAAM,SAAS,kBAAkB,YAAY;EAC7C,IAAI,QAAQ,OAAO;CACrB;CAEA,IAAI,UAAU,KAAK,QAAQ,QAAQ;CACnC,OAAO,MAAM;EACX,MAAM,SAAS,kBAAkB,OAAO;EACxC,IAAI,QAAQ,OAAO;EAEnB,MAAM,SAAS,KAAK,QAAQ,OAAO;EACnC,IAAI,WAAW,SAAS,OAAO;EAC/B,UAAU;CACZ;AACF;AAYA,SAAgB,cAAc,iBAAkC;CAC9D,OACE,oBAAoB,CAAC,EAAE,QAAQ,mBAAmB,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB;AAE/F"}
1
+ {"version":3,"file":"active-Dkf8AB1R.mjs","names":[],"sources":["../src/workspace/active.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\ninterface WorkspaceConfig {\n schema?: string\n workspace_root?: string\n artifacts_dir?: string\n domain_hints?: unknown\n}\n\nexport interface ActiveWorkspace {\n root: string\n metadataDir: string\n artifactsRoot: string\n domainHints: string[]\n}\n\nfunction workspaceFromRoot(rootCandidate: string): ActiveWorkspace | null {\n const root = path.resolve(rootCandidate)\n const metadataDir = path.join(root, '.chain-insights')\n const markerPath = path.join(metadataDir, 'workspace.json')\n if (!fs.existsSync(markerPath)) return null\n\n const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as WorkspaceConfig\n if (parsed.schema !== 'chain-insights.workspace.v1') return null\n\n const workspaceRoot = path.resolve(parsed.workspace_root ?? root)\n const artifactsDir = parsed.artifacts_dir ?? 'artifacts'\n const domainHints = Array.isArray(parsed.domain_hints)\n ? parsed.domain_hints.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0\n )\n : []\n return {\n root: workspaceRoot,\n metadataDir: path.join(workspaceRoot, '.chain-insights'),\n artifactsRoot: path.resolve(workspaceRoot, artifactsDir),\n domainHints,\n }\n}\n\nexport function findActiveWorkspace(startDir = process.cwd()): ActiveWorkspace | null {\n const envWorkspace = process.env['CHAIN_INSIGHTS_WORKSPACE']?.trim()\n if (envWorkspace) {\n const active = workspaceFromRoot(envWorkspace)\n if (active) return active\n }\n\n let current = path.resolve(startDir)\n while (true) {\n const active = workspaceFromRoot(current)\n if (active) return active\n\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport function activeMetadataDir(): string {\n return findActiveWorkspace()?.metadataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n\nexport function activeArtifactsRoot(): string {\n return (\n findActiveWorkspace()?.artifactsRoot ?? path.join(os.homedir(), '.chain-insights', 'artifacts')\n )\n}\n\nexport function activeDataDir(fallbackDataDir?: string): string {\n return (\n findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), '.chain-insights')\n )\n}\n"],"mappings":";;;;;;;;;AAkBA,SAAS,kBAAkB,eAA+C;CACxE,MAAM,OAAO,KAAK,QAAQ,aAAa;CACvC,MAAM,cAAc,KAAK,KAAK,MAAM,iBAAiB;CACrD,MAAM,aAAa,KAAK,KAAK,aAAa,gBAAgB;CAC1D,IAAI,CAAC,GAAG,WAAW,UAAU,GAAG,OAAO;CAEvC,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,YAAY,MAAM,CAAC;CAC7D,IAAI,OAAO,WAAW,+BAA+B,OAAO;CAE5D,MAAM,gBAAgB,KAAK,QAAQ,OAAO,kBAAkB,IAAI;CAChE,MAAM,eAAe,OAAO,iBAAiB;CAC7C,MAAM,cAAc,MAAM,QAAQ,OAAO,YAAY,IACjD,OAAO,aAAa,QACjB,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,CACjF,IACA,CAAC;CACL,OAAO;EACL,MAAM;EACN,aAAa,KAAK,KAAK,eAAe,iBAAiB;EACvD,eAAe,KAAK,QAAQ,eAAe,YAAY;EACvD;CACF;AACF;AAEA,SAAgB,oBAAoB,WAAW,QAAQ,IAAI,GAA2B;CACpF,MAAM,eAAe,QAAQ,IAAI,2BAA2B,EAAE,KAAK;CACnE,IAAI,cAAc;EAChB,MAAM,SAAS,kBAAkB,YAAY;EAC7C,IAAI,QAAQ,OAAO;CACrB;CAEA,IAAI,UAAU,KAAK,QAAQ,QAAQ;CACnC,OAAO,MAAM;EACX,MAAM,SAAS,kBAAkB,OAAO;EACxC,IAAI,QAAQ,OAAO;EAEnB,MAAM,SAAS,KAAK,QAAQ,OAAO;EACnC,IAAI,WAAW,SAAS,OAAO;EAC/B,UAAU;CACZ;AACF;AAYA,SAAgB,cAAc,iBAAkC;CAC9D,OACE,oBAAoB,CAAC,EAAE,QAAQ,mBAAmB,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB;AAE/F"}