nodedex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/claude-code-watcher.mjs +336 -0
- package/adapters/hermes-statedb-watcher.mjs +234 -0
- package/adapters/nodedex-capture-core.mjs +129 -0
- package/adapters/nodedex-capture.mjs +169 -0
- package/dist/agent-protocol.d.ts +7 -0
- package/dist/agent-protocol.d.ts.map +1 -0
- package/dist/agent-protocol.js +38 -0
- package/dist/agent-protocol.js.map +1 -0
- package/dist/api-server.d.ts +5 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +351 -0
- package/dist/api-server.js.map +1 -0
- package/dist/boot-env.d.ts +2 -0
- package/dist/boot-env.d.ts.map +1 -0
- package/dist/boot-env.js +12 -0
- package/dist/boot-env.js.map +1 -0
- package/dist/engine/__tests__/search-core.test.d.ts +2 -0
- package/dist/engine/__tests__/search-core.test.d.ts.map +1 -0
- package/dist/engine/__tests__/search-core.test.js +139 -0
- package/dist/engine/__tests__/search-core.test.js.map +1 -0
- package/dist/engine/ai-provider.d.ts +45 -0
- package/dist/engine/ai-provider.d.ts.map +1 -0
- package/dist/engine/ai-provider.js +5 -0
- package/dist/engine/ai-provider.js.map +1 -0
- package/dist/engine/embeddings.d.ts +51 -0
- package/dist/engine/embeddings.d.ts.map +1 -0
- package/dist/engine/embeddings.js +89 -0
- package/dist/engine/embeddings.js.map +1 -0
- package/dist/engine/providers/__tests__/failure-policy.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/failure-policy.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/failure-policy.test.js +134 -0
- package/dist/engine/providers/__tests__/failure-policy.test.js.map +1 -0
- package/dist/engine/providers/__tests__/model-caps.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/model-caps.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/model-caps.test.js +38 -0
- package/dist/engine/providers/__tests__/model-caps.test.js.map +1 -0
- package/dist/engine/providers/__tests__/openai-structured.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/openai-structured.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/openai-structured.test.js +73 -0
- package/dist/engine/providers/__tests__/openai-structured.test.js.map +1 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.js +108 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.js.map +1 -0
- package/dist/engine/providers/anthropic.d.ts +17 -0
- package/dist/engine/providers/anthropic.d.ts.map +1 -0
- package/dist/engine/providers/anthropic.js +125 -0
- package/dist/engine/providers/anthropic.js.map +1 -0
- package/dist/engine/providers/failure-policy.d.ts +56 -0
- package/dist/engine/providers/failure-policy.d.ts.map +1 -0
- package/dist/engine/providers/failure-policy.js +120 -0
- package/dist/engine/providers/failure-policy.js.map +1 -0
- package/dist/engine/providers/gemini.d.ts +22 -0
- package/dist/engine/providers/gemini.d.ts.map +1 -0
- package/dist/engine/providers/gemini.js +180 -0
- package/dist/engine/providers/gemini.js.map +1 -0
- package/dist/engine/providers/index.d.ts +8 -0
- package/dist/engine/providers/index.d.ts.map +1 -0
- package/dist/engine/providers/index.js +67 -0
- package/dist/engine/providers/index.js.map +1 -0
- package/dist/engine/providers/local.d.ts +12 -0
- package/dist/engine/providers/local.d.ts.map +1 -0
- package/dist/engine/providers/local.js +46 -0
- package/dist/engine/providers/local.js.map +1 -0
- package/dist/engine/providers/model-caps.d.ts +6 -0
- package/dist/engine/providers/model-caps.d.ts.map +1 -0
- package/dist/engine/providers/model-caps.js +49 -0
- package/dist/engine/providers/model-caps.js.map +1 -0
- package/dist/engine/providers/openai.d.ts +30 -0
- package/dist/engine/providers/openai.d.ts.map +1 -0
- package/dist/engine/providers/openai.js +309 -0
- package/dist/engine/providers/openai.js.map +1 -0
- package/dist/engine/providers/usage-ledger.d.ts +69 -0
- package/dist/engine/providers/usage-ledger.d.ts.map +1 -0
- package/dist/engine/providers/usage-ledger.js +209 -0
- package/dist/engine/providers/usage-ledger.js.map +1 -0
- package/dist/engine/search-core.d.ts +40 -0
- package/dist/engine/search-core.d.ts.map +1 -0
- package/dist/engine/search-core.js +109 -0
- package/dist/engine/search-core.js.map +1 -0
- package/dist/engine/vector-math.d.ts +5 -0
- package/dist/engine/vector-math.d.ts.map +1 -0
- package/dist/engine/vector-math.js +25 -0
- package/dist/engine/vector-math.js.map +1 -0
- package/dist/home-env.d.ts +26 -0
- package/dist/home-env.d.ts.map +1 -0
- package/dist/home-env.js +87 -0
- package/dist/home-env.js.map +1 -0
- package/dist/mcp-server.d.ts +13 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +79 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/middleware/auth.d.ts +23 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +104 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/auto-recall.d.ts +7 -0
- package/dist/middleware/auto-recall.d.ts.map +1 -0
- package/dist/middleware/auto-recall.js +257 -0
- package/dist/middleware/auto-recall.js.map +1 -0
- package/dist/middleware/auto-reflect.d.ts +4 -0
- package/dist/middleware/auto-reflect.d.ts.map +1 -0
- package/dist/middleware/auto-reflect.js +5 -0
- package/dist/middleware/auto-reflect.js.map +1 -0
- package/dist/middleware/reflect/apply-flag-verdict.d.ts +27 -0
- package/dist/middleware/reflect/apply-flag-verdict.d.ts.map +1 -0
- package/dist/middleware/reflect/apply-flag-verdict.js +57 -0
- package/dist/middleware/reflect/apply-flag-verdict.js.map +1 -0
- package/dist/middleware/reflect/arc-entity-resolve.d.ts +29 -0
- package/dist/middleware/reflect/arc-entity-resolve.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-entity-resolve.js +356 -0
- package/dist/middleware/reflect/arc-entity-resolve.js.map +1 -0
- package/dist/middleware/reflect/arc-inactivity-timer.d.ts +47 -0
- package/dist/middleware/reflect/arc-inactivity-timer.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-inactivity-timer.js +175 -0
- package/dist/middleware/reflect/arc-inactivity-timer.js.map +1 -0
- package/dist/middleware/reflect/arc-pipeline.d.ts +33 -0
- package/dist/middleware/reflect/arc-pipeline.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-pipeline.js +498 -0
- package/dist/middleware/reflect/arc-pipeline.js.map +1 -0
- package/dist/middleware/reflect/comprehend-pergroup.d.ts +100 -0
- package/dist/middleware/reflect/comprehend-pergroup.d.ts.map +1 -0
- package/dist/middleware/reflect/comprehend-pergroup.js +610 -0
- package/dist/middleware/reflect/comprehend-pergroup.js.map +1 -0
- package/dist/middleware/reflect/comprehend.d.ts +237 -0
- package/dist/middleware/reflect/comprehend.d.ts.map +1 -0
- package/dist/middleware/reflect/comprehend.js +706 -0
- package/dist/middleware/reflect/comprehend.js.map +1 -0
- package/dist/middleware/reflect/config.d.ts +34 -0
- package/dist/middleware/reflect/config.d.ts.map +1 -0
- package/dist/middleware/reflect/config.js +131 -0
- package/dist/middleware/reflect/config.js.map +1 -0
- package/dist/middleware/reflect/context.d.ts +138 -0
- package/dist/middleware/reflect/context.d.ts.map +1 -0
- package/dist/middleware/reflect/context.js +619 -0
- package/dist/middleware/reflect/context.js.map +1 -0
- package/dist/middleware/reflect/cost-breakdown.d.ts +69 -0
- package/dist/middleware/reflect/cost-breakdown.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-breakdown.js +63 -0
- package/dist/middleware/reflect/cost-breakdown.js.map +1 -0
- package/dist/middleware/reflect/cost-guard.d.ts +102 -0
- package/dist/middleware/reflect/cost-guard.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-guard.js +243 -0
- package/dist/middleware/reflect/cost-guard.js.map +1 -0
- package/dist/middleware/reflect/cost-pricing.d.ts +54 -0
- package/dist/middleware/reflect/cost-pricing.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-pricing.js +148 -0
- package/dist/middleware/reflect/cost-pricing.js.map +1 -0
- package/dist/middleware/reflect/cross-group-link.d.ts +61 -0
- package/dist/middleware/reflect/cross-group-link.d.ts.map +1 -0
- package/dist/middleware/reflect/cross-group-link.js +212 -0
- package/dist/middleware/reflect/cross-group-link.js.map +1 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.d.ts +70 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.d.ts.map +1 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.js +0 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.js.map +1 -0
- package/dist/middleware/reflect/describe-roots.d.ts +58 -0
- package/dist/middleware/reflect/describe-roots.d.ts.map +1 -0
- package/dist/middleware/reflect/describe-roots.js +266 -0
- package/dist/middleware/reflect/describe-roots.js.map +1 -0
- package/dist/middleware/reflect/flag-reviewer-startup.d.ts +16 -0
- package/dist/middleware/reflect/flag-reviewer-startup.d.ts.map +1 -0
- package/dist/middleware/reflect/flag-reviewer-startup.js +107 -0
- package/dist/middleware/reflect/flag-reviewer-startup.js.map +1 -0
- package/dist/middleware/reflect/flag-reviewer.d.ts +69 -0
- package/dist/middleware/reflect/flag-reviewer.d.ts.map +1 -0
- package/dist/middleware/reflect/flag-reviewer.js +520 -0
- package/dist/middleware/reflect/flag-reviewer.js.map +1 -0
- package/dist/middleware/reflect/inline-dedup.d.ts +26 -0
- package/dist/middleware/reflect/inline-dedup.d.ts.map +1 -0
- package/dist/middleware/reflect/inline-dedup.js +131 -0
- package/dist/middleware/reflect/inline-dedup.js.map +1 -0
- package/dist/middleware/reflect/justify-decisions.d.ts +37 -0
- package/dist/middleware/reflect/justify-decisions.d.ts.map +1 -0
- package/dist/middleware/reflect/justify-decisions.js +159 -0
- package/dist/middleware/reflect/justify-decisions.js.map +1 -0
- package/dist/middleware/reflect/nl-accept.d.ts +35 -0
- package/dist/middleware/reflect/nl-accept.d.ts.map +1 -0
- package/dist/middleware/reflect/nl-accept.js +167 -0
- package/dist/middleware/reflect/nl-accept.js.map +1 -0
- package/dist/middleware/reflect/pass0.d.ts +20 -0
- package/dist/middleware/reflect/pass0.d.ts.map +1 -0
- package/dist/middleware/reflect/pass0.js +423 -0
- package/dist/middleware/reflect/pass0.js.map +1 -0
- package/dist/middleware/reflect/pass1.d.ts +17 -0
- package/dist/middleware/reflect/pass1.d.ts.map +1 -0
- package/dist/middleware/reflect/pass1.js +241 -0
- package/dist/middleware/reflect/pass1.js.map +1 -0
- package/dist/middleware/reflect/pass2-quarantine.d.ts +129 -0
- package/dist/middleware/reflect/pass2-quarantine.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-quarantine.js +272 -0
- package/dist/middleware/reflect/pass2-quarantine.js.map +1 -0
- package/dist/middleware/reflect/pass2-seams.d.ts +205 -0
- package/dist/middleware/reflect/pass2-seams.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-seams.js +279 -0
- package/dist/middleware/reflect/pass2-seams.js.map +1 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.d.ts +37 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.js +531 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.js.map +1 -0
- package/dist/middleware/reflect/pass2.d.ts +17 -0
- package/dist/middleware/reflect/pass2.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2.js +324 -0
- package/dist/middleware/reflect/pass2.js.map +1 -0
- package/dist/middleware/reflect/pass2a.d.ts +141 -0
- package/dist/middleware/reflect/pass2a.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2a.js +404 -0
- package/dist/middleware/reflect/pass2a.js.map +1 -0
- package/dist/middleware/reflect/pass2b.d.ts +108 -0
- package/dist/middleware/reflect/pass2b.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2b.js +480 -0
- package/dist/middleware/reflect/pass2b.js.map +1 -0
- package/dist/middleware/reflect/pass2c.d.ts +113 -0
- package/dist/middleware/reflect/pass2c.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2c.js +360 -0
- package/dist/middleware/reflect/pass2c.js.map +1 -0
- package/dist/middleware/reflect/pass3-batch.d.ts +62 -0
- package/dist/middleware/reflect/pass3-batch.d.ts.map +1 -0
- package/dist/middleware/reflect/pass3-batch.js +139 -0
- package/dist/middleware/reflect/pass3-batch.js.map +1 -0
- package/dist/middleware/reflect/pass3.d.ts +23 -0
- package/dist/middleware/reflect/pass3.d.ts.map +1 -0
- package/dist/middleware/reflect/pass3.js +371 -0
- package/dist/middleware/reflect/pass3.js.map +1 -0
- package/dist/middleware/reflect/pass4-slice.d.ts +25 -0
- package/dist/middleware/reflect/pass4-slice.d.ts.map +1 -0
- package/dist/middleware/reflect/pass4-slice.js +315 -0
- package/dist/middleware/reflect/pass4-slice.js.map +1 -0
- package/dist/middleware/reflect/pass4.d.ts +30 -0
- package/dist/middleware/reflect/pass4.d.ts.map +1 -0
- package/dist/middleware/reflect/pass4.js +193 -0
- package/dist/middleware/reflect/pass4.js.map +1 -0
- package/dist/middleware/reflect/pass5.d.ts +22 -0
- package/dist/middleware/reflect/pass5.d.ts.map +1 -0
- package/dist/middleware/reflect/pass5.js +178 -0
- package/dist/middleware/reflect/pass5.js.map +1 -0
- package/dist/middleware/reflect/pass_judge.d.ts +44 -0
- package/dist/middleware/reflect/pass_judge.d.ts.map +1 -0
- package/dist/middleware/reflect/pass_judge.js +263 -0
- package/dist/middleware/reflect/pass_judge.js.map +1 -0
- package/dist/middleware/reflect/pipeline-flags.d.ts +140 -0
- package/dist/middleware/reflect/pipeline-flags.d.ts.map +1 -0
- package/dist/middleware/reflect/pipeline-flags.js +314 -0
- package/dist/middleware/reflect/pipeline-flags.js.map +1 -0
- package/dist/middleware/reflect/pipeline.d.ts +237 -0
- package/dist/middleware/reflect/pipeline.d.ts.map +1 -0
- package/dist/middleware/reflect/pipeline.js +3114 -0
- package/dist/middleware/reflect/pipeline.js.map +1 -0
- package/dist/middleware/reflect/promptOverride.d.ts +14 -0
- package/dist/middleware/reflect/promptOverride.d.ts.map +1 -0
- package/dist/middleware/reflect/promptOverride.js +28 -0
- package/dist/middleware/reflect/promptOverride.js.map +1 -0
- package/dist/middleware/reflect/provenance-check.d.ts +48 -0
- package/dist/middleware/reflect/provenance-check.d.ts.map +1 -0
- package/dist/middleware/reflect/provenance-check.js +180 -0
- package/dist/middleware/reflect/provenance-check.js.map +1 -0
- package/dist/middleware/reflect/provenance-reviewer.d.ts +52 -0
- package/dist/middleware/reflect/provenance-reviewer.d.ts.map +1 -0
- package/dist/middleware/reflect/provenance-reviewer.js +253 -0
- package/dist/middleware/reflect/provenance-reviewer.js.map +1 -0
- package/dist/middleware/reflect/prune-collapsed-types.d.ts +11 -0
- package/dist/middleware/reflect/prune-collapsed-types.d.ts.map +1 -0
- package/dist/middleware/reflect/prune-collapsed-types.js +32 -0
- package/dist/middleware/reflect/prune-collapsed-types.js.map +1 -0
- package/dist/middleware/reflect/recognize-root.d.ts +75 -0
- package/dist/middleware/reflect/recognize-root.d.ts.map +1 -0
- package/dist/middleware/reflect/recognize-root.js +204 -0
- package/dist/middleware/reflect/recognize-root.js.map +1 -0
- package/dist/middleware/reflect/render-agent-flag.d.ts +25 -0
- package/dist/middleware/reflect/render-agent-flag.d.ts.map +1 -0
- package/dist/middleware/reflect/render-agent-flag.js +39 -0
- package/dist/middleware/reflect/render-agent-flag.js.map +1 -0
- package/dist/middleware/reflect/retrieve-graph-slice.d.ts +54 -0
- package/dist/middleware/reflect/retrieve-graph-slice.d.ts.map +1 -0
- package/dist/middleware/reflect/retrieve-graph-slice.js +173 -0
- package/dist/middleware/reflect/retrieve-graph-slice.js.map +1 -0
- package/dist/middleware/reflect/root-relatedness.d.ts +31 -0
- package/dist/middleware/reflect/root-relatedness.d.ts.map +1 -0
- package/dist/middleware/reflect/root-relatedness.js +92 -0
- package/dist/middleware/reflect/root-relatedness.js.map +1 -0
- package/dist/middleware/reflect/schema-heal.d.ts +22 -0
- package/dist/middleware/reflect/schema-heal.d.ts.map +1 -0
- package/dist/middleware/reflect/schema-heal.js +119 -0
- package/dist/middleware/reflect/schema-heal.js.map +1 -0
- package/dist/middleware/reflect/schema-validator.d.ts +85 -0
- package/dist/middleware/reflect/schema-validator.d.ts.map +1 -0
- package/dist/middleware/reflect/schema-validator.js +196 -0
- package/dist/middleware/reflect/schema-validator.js.map +1 -0
- package/dist/middleware/reflect/stage-audit-graph.d.ts +115 -0
- package/dist/middleware/reflect/stage-audit-graph.d.ts.map +1 -0
- package/dist/middleware/reflect/stage-audit-graph.js +563 -0
- package/dist/middleware/reflect/stage-audit-graph.js.map +1 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.d.ts +87 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.d.ts.map +1 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.js +256 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.js.map +1 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.d.ts +15 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.d.ts.map +1 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.js +91 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.js.map +1 -0
- package/dist/middleware/reflect/types.d.ts +261 -0
- package/dist/middleware/reflect/types.d.ts.map +1 -0
- package/dist/middleware/reflect/types.js +3 -0
- package/dist/middleware/reflect/types.js.map +1 -0
- package/dist/middleware/reflect/v2-integrate.d.ts +120 -0
- package/dist/middleware/reflect/v2-integrate.d.ts.map +1 -0
- package/dist/middleware/reflect/v2-integrate.js +388 -0
- package/dist/middleware/reflect/v2-integrate.js.map +1 -0
- package/dist/middleware/reflect/v2-judge.d.ts +44 -0
- package/dist/middleware/reflect/v2-judge.d.ts.map +1 -0
- package/dist/middleware/reflect/v2-judge.js +191 -0
- package/dist/middleware/reflect/v2-judge.js.map +1 -0
- package/dist/relation-sets.d.ts +2 -0
- package/dist/relation-sets.d.ts.map +1 -0
- package/dist/relation-sets.js +35 -0
- package/dist/relation-sets.js.map +1 -0
- package/dist/routes/__tests__/flags.test.d.ts +2 -0
- package/dist/routes/__tests__/flags.test.d.ts.map +1 -0
- package/dist/routes/__tests__/flags.test.js +257 -0
- package/dist/routes/__tests__/flags.test.js.map +1 -0
- package/dist/routes/__tests__/models-catalog.test.d.ts +2 -0
- package/dist/routes/__tests__/models-catalog.test.d.ts.map +1 -0
- package/dist/routes/__tests__/models-catalog.test.js +130 -0
- package/dist/routes/__tests__/models-catalog.test.js.map +1 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.d.ts +2 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.d.ts.map +1 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.js +38 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.js.map +1 -0
- package/dist/routes/__tests__/spend-pause-drain.test.d.ts +2 -0
- package/dist/routes/__tests__/spend-pause-drain.test.d.ts.map +1 -0
- package/dist/routes/__tests__/spend-pause-drain.test.js +38 -0
- package/dist/routes/__tests__/spend-pause-drain.test.js.map +1 -0
- package/dist/routes/admin.d.ts +49 -0
- package/dist/routes/admin.d.ts.map +1 -0
- package/dist/routes/admin.js +471 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/blocks.d.ts +4 -0
- package/dist/routes/blocks.d.ts.map +1 -0
- package/dist/routes/blocks.js +893 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/chat-proxy.d.ts +5 -0
- package/dist/routes/chat-proxy.d.ts.map +1 -0
- package/dist/routes/chat-proxy.js +225 -0
- package/dist/routes/chat-proxy.js.map +1 -0
- package/dist/routes/conversations.d.ts +4 -0
- package/dist/routes/conversations.d.ts.map +1 -0
- package/dist/routes/conversations.js +139 -0
- package/dist/routes/conversations.js.map +1 -0
- package/dist/routes/flags.d.ts +4 -0
- package/dist/routes/flags.d.ts.map +1 -0
- package/dist/routes/flags.js +151 -0
- package/dist/routes/flags.js.map +1 -0
- package/dist/routes/inject.d.ts +4 -0
- package/dist/routes/inject.d.ts.map +1 -0
- package/dist/routes/inject.js +183 -0
- package/dist/routes/inject.js.map +1 -0
- package/dist/routes/mcp-http.d.ts +5 -0
- package/dist/routes/mcp-http.d.ts.map +1 -0
- package/dist/routes/mcp-http.js +94 -0
- package/dist/routes/mcp-http.js.map +1 -0
- package/dist/routes/quarantine.d.ts +4 -0
- package/dist/routes/quarantine.d.ts.map +1 -0
- package/dist/routes/quarantine.js +66 -0
- package/dist/routes/quarantine.js.map +1 -0
- package/dist/routes/recall.d.ts +5 -0
- package/dist/routes/recall.d.ts.map +1 -0
- package/dist/routes/recall.js +573 -0
- package/dist/routes/recall.js.map +1 -0
- package/dist/routes/reflect.d.ts +5 -0
- package/dist/routes/reflect.d.ts.map +1 -0
- package/dist/routes/reflect.js +231 -0
- package/dist/routes/reflect.js.map +1 -0
- package/dist/routes/session.d.ts +4 -0
- package/dist/routes/session.d.ts.map +1 -0
- package/dist/routes/session.js +418 -0
- package/dist/routes/session.js.map +1 -0
- package/dist/routes/state.d.ts +116 -0
- package/dist/routes/state.d.ts.map +1 -0
- package/dist/routes/state.js +621 -0
- package/dist/routes/state.js.map +1 -0
- package/dist/routes/usage.d.ts +3 -0
- package/dist/routes/usage.d.ts.map +1 -0
- package/dist/routes/usage.js +141 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/workspace.d.ts +5 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +435 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +298 -0
- package/dist/server.js.map +1 -0
- package/dist/store/__tests__/backup.test.d.ts +2 -0
- package/dist/store/__tests__/backup.test.d.ts.map +1 -0
- package/dist/store/__tests__/backup.test.js +53 -0
- package/dist/store/__tests__/backup.test.js.map +1 -0
- package/dist/store/__tests__/quality.test.d.ts +2 -0
- package/dist/store/__tests__/quality.test.d.ts.map +1 -0
- package/dist/store/__tests__/quality.test.js +75 -0
- package/dist/store/__tests__/quality.test.js.map +1 -0
- package/dist/store/backup.d.ts +14 -0
- package/dist/store/backup.d.ts.map +1 -0
- package/dist/store/backup.js +95 -0
- package/dist/store/backup.js.map +1 -0
- package/dist/store/database.d.ts +407 -0
- package/dist/store/database.d.ts.map +1 -0
- package/dist/store/database.js +2004 -0
- package/dist/store/database.js.map +1 -0
- package/dist/store/quality.d.ts +25 -0
- package/dist/store/quality.d.ts.map +1 -0
- package/dist/store/quality.js +48 -0
- package/dist/store/quality.js.map +1 -0
- package/dist/tools/__tests__/assemble-block-chains.test.d.ts +2 -0
- package/dist/tools/__tests__/assemble-block-chains.test.d.ts.map +1 -0
- package/dist/tools/__tests__/assemble-block-chains.test.js +118 -0
- package/dist/tools/__tests__/assemble-block-chains.test.js.map +1 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.d.ts +2 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.d.ts.map +1 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.js +68 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.js.map +1 -0
- package/dist/tools/__tests__/flag-surface.test.d.ts +2 -0
- package/dist/tools/__tests__/flag-surface.test.d.ts.map +1 -0
- package/dist/tools/__tests__/flag-surface.test.js +130 -0
- package/dist/tools/__tests__/flag-surface.test.js.map +1 -0
- package/dist/tools/core.d.ts +5 -0
- package/dist/tools/core.d.ts.map +1 -0
- package/dist/tools/core.js +962 -0
- package/dist/tools/core.js.map +1 -0
- package/dist/tools/derive.d.ts +5 -0
- package/dist/tools/derive.d.ts.map +1 -0
- package/dist/tools/derive.js +182 -0
- package/dist/tools/derive.js.map +1 -0
- package/dist/tools/flag-surface.d.ts +26 -0
- package/dist/tools/flag-surface.d.ts.map +1 -0
- package/dist/tools/flag-surface.js +59 -0
- package/dist/tools/flag-surface.js.map +1 -0
- package/dist/tools/helpers.d.ts +99 -0
- package/dist/tools/helpers.d.ts.map +1 -0
- package/dist/tools/helpers.js +243 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/projects.d.ts +5 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +175 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/system.d.ts +5 -0
- package/dist/tools/system.d.ts.map +1 -0
- package/dist/tools/system.js +1361 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/tasks.d.ts +5 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +289 -0
- package/dist/tools/tasks.js.map +1 -0
- package/package.json +69 -0
- package/scripts/nodedex-entry.mjs +396 -0
- package/tui-dist/App.js +185 -0
- package/tui-dist/api.js +197 -0
- package/tui-dist/cli.js +53 -0
- package/tui-dist/components.js +63 -0
- package/tui-dist/config.js +242 -0
- package/tui-dist/connect-snippets.js +98 -0
- package/tui-dist/feed.js +51 -0
- package/tui-dist/health.js +465 -0
- package/tui-dist/hooks.js +23 -0
- package/tui-dist/memory.js +220 -0
- package/tui-dist/onboarding.js +498 -0
- package/tui-dist/review.js +193 -0
- package/tui-dist/servers.js +556 -0
- package/tui-dist/smoke.js +15 -0
- package/tui-dist/theme.js +106 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// claude-code-watcher.mjs — capture Claude Code turns into NodeDex by reading Claude Code's
|
|
3
|
+
// own session transcripts (~/.claude/projects/<project>/<session>.jsonl). Zero cooperation
|
|
4
|
+
// needed from the host: no hooks to wire, no config in Claude Code itself — every session is
|
|
5
|
+
// already persisted line-by-line as it happens; we tail it.
|
|
6
|
+
//
|
|
7
|
+
// WHAT IT IS
|
|
8
|
+
// A thin, host-specific shim over the shared capture core (nodedex-capture-core.mjs), the
|
|
9
|
+
// same architecture as hermes-statedb-watcher.mjs. All it adds is "how Claude Code hands me
|
|
10
|
+
// a turn": tail the JSONL files, assemble whole turns, call captureTurn().
|
|
11
|
+
//
|
|
12
|
+
// THE HARD PART — turn ASSEMBLY (what one logical turn looks like in the JSONL)
|
|
13
|
+
// One turn spans many lines:
|
|
14
|
+
// user(text) → assistant(thinking|text|tool_use)* → user(tool_result)* → … → assistant(text)
|
|
15
|
+
// There is NO explicit end-of-turn marker. A turn CLOSES when:
|
|
16
|
+
// • the NEXT real user prompt arrives in that session (the reliable boundary), or
|
|
17
|
+
// • the file has been idle past idleFlushMs with a complete buffered turn (the last turn
|
|
18
|
+
// of a session would otherwise never emit).
|
|
19
|
+
// Mapping (mirrors the Hermes watcher: narration → response, raw traces → thinking):
|
|
20
|
+
// agent_response = the assistant's TEXT blocks across the turn (preambles + final answer)
|
|
21
|
+
// user_message = the user prompt(s) that opened the turn (system tags stripped)
|
|
22
|
+
// agent_thinking = thinking blocks + tool_use/tool_result previews (recorded, not extracted)
|
|
23
|
+
// Skipped lines: isSidechain (subagent chatter), isMeta, and non-user/assistant types
|
|
24
|
+
// (queue-operation, attachment, file-history-snapshot, ai-title, system, mode, …).
|
|
25
|
+
//
|
|
26
|
+
// WATERMARK (why turnNumber = a byte offset)
|
|
27
|
+
// Same anchoring trick as Hermes (turn_number = state.db stop-id): the turn's identity lives
|
|
28
|
+
// in the HOST's own log, not in a drift-prone private counter. Here that identity is the byte
|
|
29
|
+
// offset where the turn's opening user line starts — stable across restarts, monotonic per
|
|
30
|
+
// file, so (agent_id, turn_number) is idempotent and the extraction range IS a watermark.
|
|
31
|
+
//
|
|
32
|
+
// SAFETY
|
|
33
|
+
// • READ-ONLY — opens transcripts with plain reads; never writes to ~/.claude.
|
|
34
|
+
// • Privacy filter: only project dirs in the allow-list are read (default "*" = all — the
|
|
35
|
+
// TUI consent step is where the user scopes this). Sub-agent sidechains are never captured.
|
|
36
|
+
// • Fresh start captures FORWARD only (cursor = current EOF) — no history replay.
|
|
37
|
+
//
|
|
38
|
+
// CONFIG (read live each poll from ~/.nodedex/config.json → claudeCapture):
|
|
39
|
+
// { enabled, projects: string[], pollMs, idleFlushMs, projectsDir } (all optional)
|
|
40
|
+
//
|
|
41
|
+
// RUN
|
|
42
|
+
// node server/adapters/claude-code-watcher.mjs # poll forever
|
|
43
|
+
// node server/adapters/claude-code-watcher.mjs --dry-run # print assembled turns, POST nothing
|
|
44
|
+
// node server/adapters/claude-code-watcher.mjs --once # one pass then exit (testing)
|
|
45
|
+
// node server/adapters/claude-code-watcher.mjs --backfill # also capture ALL pre-existing turns
|
|
46
|
+
// node server/adapters/claude-code-watcher.mjs --last 50 # bounded backfill: only the last N
|
|
47
|
+
// turns per file (the "see your memory
|
|
48
|
+
// in 2 minutes" onboarding path)
|
|
49
|
+
// node server/adapters/claude-code-watcher.mjs --newest # only the newest session per project
|
|
50
|
+
|
|
51
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, openSync, readSync, closeSync, createReadStream } from "node:fs";
|
|
52
|
+
import { createInterface } from "node:readline";
|
|
53
|
+
import { homedir } from "node:os";
|
|
54
|
+
import { join, basename } from "node:path";
|
|
55
|
+
import { captureTurn } from "./nodedex-capture-core.mjs";
|
|
56
|
+
|
|
57
|
+
const HOME = join(homedir(), ".nodedex");
|
|
58
|
+
const CONFIG_FILE = join(HOME, "config.json");
|
|
59
|
+
const CURSOR_FILE = join(HOME, "claude-code-capture-cursor.json");
|
|
60
|
+
|
|
61
|
+
const args = new Set(process.argv.slice(2));
|
|
62
|
+
const DRY_RUN = args.has("--dry-run");
|
|
63
|
+
const ONCE = args.has("--once");
|
|
64
|
+
const BACKFILL = args.has("--backfill");
|
|
65
|
+
const NEWEST = args.has("--newest");
|
|
66
|
+
const LAST = (() => {
|
|
67
|
+
const i = process.argv.indexOf("--last");
|
|
68
|
+
return i >= 0 ? Math.max(1, Number(process.argv[i + 1]) || 0) : 0;
|
|
69
|
+
})();
|
|
70
|
+
|
|
71
|
+
const TOOL_PREVIEW = 240; // truncate each tool result/args inside the thinking trace
|
|
72
|
+
|
|
73
|
+
// ─── config (re-read each poll so TUI changes apply without a restart) ────────────────────────
|
|
74
|
+
function defaultProjectsDir() {
|
|
75
|
+
return join(homedir(), ".claude", "projects");
|
|
76
|
+
}
|
|
77
|
+
function loadCaptureConfig() {
|
|
78
|
+
let raw = {};
|
|
79
|
+
try { raw = JSON.parse(readFileSync(CONFIG_FILE, "utf8"))?.claudeCapture ?? {}; } catch { /* none */ }
|
|
80
|
+
const projects = Array.isArray(raw.projects) && raw.projects.length ? raw.projects.map(String) : ["*"];
|
|
81
|
+
return {
|
|
82
|
+
enabled: raw.enabled !== false,
|
|
83
|
+
projects,
|
|
84
|
+
allowAll: projects.includes("*"),
|
|
85
|
+
pollMs: Number.isFinite(raw.pollMs) && raw.pollMs >= 1000 ? raw.pollMs : 5000,
|
|
86
|
+
idleFlushMs: Number.isFinite(raw.idleFlushMs) && raw.idleFlushMs >= 15000 ? raw.idleFlushMs : 120000,
|
|
87
|
+
projectsDir: raw.projectsDir || defaultProjectsDir(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const projectAllowed = (cfg, dirName) => cfg.allowAll || cfg.projects.includes(dirName);
|
|
91
|
+
|
|
92
|
+
// ─── cursor: file → byte offset of the CURRENT (still-buffering) turn's opening line ──────────
|
|
93
|
+
// We persist the offset where the pending turn STARTS, not EOF — so a restart re-reads and
|
|
94
|
+
// re-buffers the in-flight turn instead of losing it. Emitted turns are idempotent via
|
|
95
|
+
// turnNumber (= that same offset), so a re-emit after restart dedups server-side.
|
|
96
|
+
function loadCursors() {
|
|
97
|
+
try { return JSON.parse(readFileSync(CURSOR_FILE, "utf8"))?.files ?? {}; } catch { return {}; }
|
|
98
|
+
}
|
|
99
|
+
function saveCursors(files) {
|
|
100
|
+
try { mkdirSync(HOME, { recursive: true }); writeFileSync(CURSOR_FILE, JSON.stringify({ files }, null, 2)); }
|
|
101
|
+
catch { /* read-only home → memory-only cursors */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── line classification ───────────────────────────────────────────────────────────────────────
|
|
105
|
+
const clean = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
106
|
+
const truncate = (s, n) => { const t = String(s ?? ""); return t.length > n ? t.slice(0, n) + "…" : t; };
|
|
107
|
+
|
|
108
|
+
// System-injected user texts that are NOT a human prompt. Stripped from prompts; a user line
|
|
109
|
+
// that is ONLY these never opens a turn.
|
|
110
|
+
const SYSTEM_TEXT = /^\s*(<command-name>|<local-command-stdout>|<local-command-caveat>|<ide_|<system-reminder>|Caveat: The messages below)/;
|
|
111
|
+
|
|
112
|
+
/** Real prompt text from a user line ("" when it's tool results / meta / system tags only). */
|
|
113
|
+
function userPromptText(msg) {
|
|
114
|
+
const c = msg?.content;
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (typeof c === "string") { if (!SYSTEM_TEXT.test(c)) parts.push(c); }
|
|
117
|
+
else if (Array.isArray(c)) {
|
|
118
|
+
for (const item of c) {
|
|
119
|
+
if (item?.type === "text" && typeof item.text === "string" && !SYSTEM_TEXT.test(item.text)) parts.push(item.text);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return clean(parts.join("\n"));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Tool-result previews from a user line (the feedback half of tool use). */
|
|
126
|
+
function toolResultPreviews(msg) {
|
|
127
|
+
const out = [];
|
|
128
|
+
const c = msg?.content;
|
|
129
|
+
if (!Array.isArray(c)) return out;
|
|
130
|
+
for (const item of c) {
|
|
131
|
+
if (item?.type !== "tool_result") continue;
|
|
132
|
+
const inner = typeof item.content === "string"
|
|
133
|
+
? item.content
|
|
134
|
+
: Array.isArray(item.content) ? item.content.map((x) => (x?.type === "text" ? x.text : "")).join(" ") : "";
|
|
135
|
+
const t = clean(inner);
|
|
136
|
+
if (t) out.push(`[result] ${truncate(t, TOOL_PREVIEW)}`);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── bounded backfill: find where the Nth-from-last turn STARTS ────────────────────────────────
|
|
142
|
+
// Streams the file once (memory-light even on multi-hundred-MB transcripts), recording the byte
|
|
143
|
+
// offset of every line that opens a turn (a real user prompt), and returns the offset to start
|
|
144
|
+
// from so exactly the last N turns are captured. The cheap `includes` pre-filter skips JSON.parse
|
|
145
|
+
// on the ~90% of lines that can't be user prompts.
|
|
146
|
+
async function offsetOfNthLastPrompt(filePath, n) {
|
|
147
|
+
const offsets = [];
|
|
148
|
+
let offset = 0;
|
|
149
|
+
const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
|
|
150
|
+
for await (const line of rl) {
|
|
151
|
+
const lineBytes = Buffer.byteLength(line, "utf8") + 1;
|
|
152
|
+
if (line.includes('"type":"user"')) {
|
|
153
|
+
try {
|
|
154
|
+
const j = JSON.parse(line);
|
|
155
|
+
if (j?.type === "user" && !j.isMeta && !j.isSidechain && userPromptText(j.message)) offsets.push(offset);
|
|
156
|
+
} catch { /* garbled line → skip */ }
|
|
157
|
+
}
|
|
158
|
+
offset += lineBytes;
|
|
159
|
+
}
|
|
160
|
+
return offsets[Math.max(0, offsets.length - n)] ?? 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── per-file turn assembly state ──────────────────────────────────────────────────────────────
|
|
164
|
+
/** files: path → { offset, pendingStart, buf, partial, lastGrowth, sessionId, project } */
|
|
165
|
+
const state = new Map();
|
|
166
|
+
|
|
167
|
+
function newBuffer() {
|
|
168
|
+
return { user: [], response: [], thinking: [], openedAt: 0 };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function emitReady(fileState) {
|
|
172
|
+
const b = fileState.buf;
|
|
173
|
+
return b && b.user.length > 0 && b.response.length > 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function emitTurn(fileState, filePath) {
|
|
177
|
+
const b = fileState.buf;
|
|
178
|
+
const sid = fileState.sessionId || basename(filePath, ".jsonl");
|
|
179
|
+
const turn = {
|
|
180
|
+
agentResponse: b.response.join("\n"),
|
|
181
|
+
userMessage: b.user.join("\n"),
|
|
182
|
+
reasoning: b.thinking.join("\n"),
|
|
183
|
+
// #agent-id convention: host-prefixed + per-session → one arc per Claude Code session,
|
|
184
|
+
// and provenance in the graph reads as "this came from Claude Code".
|
|
185
|
+
agentId: `claude-code-${String(sid).slice(0, 8)}`,
|
|
186
|
+
turnNumber: b.openedAt, // byte offset of the turn's opening line — the file-anchored watermark
|
|
187
|
+
turnName: `cc-${fileState.project.slice(0, 20)}-${b.openedAt}`,
|
|
188
|
+
hint: "discovery",
|
|
189
|
+
};
|
|
190
|
+
if (DRY_RUN) {
|
|
191
|
+
console.log("─".repeat(72));
|
|
192
|
+
console.log(`TURN project=${fileState.project} session=${String(sid).slice(0, 8)} at=${b.openedAt}`);
|
|
193
|
+
console.log(` user: ${truncate(turn.userMessage.replace(/\n/g, " ⏎ "), 160)}`);
|
|
194
|
+
console.log(` response: ${truncate(clean(turn.agentResponse), 200)} (${turn.agentResponse.length} ch)`);
|
|
195
|
+
console.log(` thinking: ${truncate(turn.reasoning.replace(/\n/g, " · "), 200)} (${turn.reasoning.length} ch)`);
|
|
196
|
+
} else {
|
|
197
|
+
const status = await captureTurn(turn);
|
|
198
|
+
console.log(`[claude-code-watcher] captured ${fileState.project}/${String(sid).slice(0, 8)}@${b.openedAt} → ${status}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Feed one parsed JSONL line into the file's buffer. Returns "opened" when a new turn began. */
|
|
203
|
+
async function feedLine(fileState, filePath, lineStartOffset, j) {
|
|
204
|
+
if (!j || j.isSidechain === true || j.isMeta === true) return;
|
|
205
|
+
if (j.sessionId && !fileState.sessionId) fileState.sessionId = j.sessionId;
|
|
206
|
+
|
|
207
|
+
if (j.type === "user") {
|
|
208
|
+
const prompt = userPromptText(j.message);
|
|
209
|
+
if (prompt) {
|
|
210
|
+
// A real user prompt = the boundary. Emit the buffered turn, then open a new one here.
|
|
211
|
+
if (emitReady(fileState)) await emitTurn(fileState, filePath);
|
|
212
|
+
if (!fileState.buf || fileState.buf.response.length > 0 || fileState.buf.user.length === 0) {
|
|
213
|
+
fileState.buf = newBuffer();
|
|
214
|
+
fileState.buf.openedAt = lineStartOffset;
|
|
215
|
+
fileState.pendingStart = lineStartOffset;
|
|
216
|
+
}
|
|
217
|
+
fileState.buf.user.push(prompt);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// tool results ride the CURRENT turn's thinking trace
|
|
221
|
+
if (fileState.buf) fileState.buf.thinking.push(...toolResultPreviews(j.message));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (j.type === "assistant" && fileState.buf) {
|
|
226
|
+
const c = j.message?.content;
|
|
227
|
+
if (!Array.isArray(c)) return;
|
|
228
|
+
for (const item of c) {
|
|
229
|
+
if (item?.type === "text" && item.text) fileState.buf.response.push(clean(item.text));
|
|
230
|
+
else if (item?.type === "thinking" && item.thinking) fileState.buf.thinking.push(truncate(clean(item.thinking), 2000));
|
|
231
|
+
else if (item?.type === "tool_use") fileState.buf.thinking.push(`[tool] ${item.name || "?"}(${truncate(JSON.stringify(item.input ?? {}), TOOL_PREVIEW)})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── one pass over one file: read appended bytes, feed complete lines ──────────────────────────
|
|
237
|
+
async function passFile(fileState, filePath, cfg) {
|
|
238
|
+
const size = statSync(filePath).size;
|
|
239
|
+
if (size < fileState.offset) fileState.offset = size; // truncated? resync forward-only
|
|
240
|
+
if (size > fileState.offset) {
|
|
241
|
+
const fd = openSync(filePath, "r");
|
|
242
|
+
try {
|
|
243
|
+
const len = size - fileState.offset;
|
|
244
|
+
const buf = Buffer.alloc(Math.min(len, 8 * 1024 * 1024)); // cap one pass at 8MB
|
|
245
|
+
const read = readSync(fd, buf, 0, buf.length, fileState.offset);
|
|
246
|
+
const chunk = buf.toString("utf8", 0, read);
|
|
247
|
+
// Only COMPLETE lines advance the offset (summed in exact bytes per line, so a
|
|
248
|
+
// trailing partial line — or a mid-multibyte-char cut — is simply re-read next
|
|
249
|
+
// pass once its newline lands; no in-memory carry, no byte drift).
|
|
250
|
+
let text = chunk;
|
|
251
|
+
let lineStart = fileState.offset;
|
|
252
|
+
let consumed = 0;
|
|
253
|
+
let idx;
|
|
254
|
+
while ((idx = text.indexOf("\n")) >= 0) {
|
|
255
|
+
const line = text.slice(0, idx);
|
|
256
|
+
const lineBytes = Buffer.byteLength(line, "utf8") + 1;
|
|
257
|
+
if (line.trim()) {
|
|
258
|
+
let j = null; try { j = JSON.parse(line); } catch { /* garbled line → skip */ }
|
|
259
|
+
if (j) await feedLine(fileState, filePath, lineStart, j);
|
|
260
|
+
}
|
|
261
|
+
lineStart += lineBytes;
|
|
262
|
+
consumed += lineBytes;
|
|
263
|
+
text = text.slice(idx + 1);
|
|
264
|
+
}
|
|
265
|
+
fileState.offset += consumed;
|
|
266
|
+
fileState.lastGrowth = Date.now();
|
|
267
|
+
} finally { closeSync(fd); }
|
|
268
|
+
} else if (emitReady(fileState) && Date.now() - fileState.lastGrowth > cfg.idleFlushMs) {
|
|
269
|
+
// idle flush: the session went quiet with a complete turn buffered (the session's last turn)
|
|
270
|
+
await emitTurn(fileState, filePath);
|
|
271
|
+
fileState.buf = newBuffer();
|
|
272
|
+
fileState.pendingStart = fileState.offset;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── one pass: discover files, tail each, persist cursors ─────────────────────────────────────
|
|
277
|
+
async function pass(cfg) {
|
|
278
|
+
let dirs = [];
|
|
279
|
+
try { dirs = readdirSync(cfg.projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()); } catch { return; }
|
|
280
|
+
for (const dir of dirs) {
|
|
281
|
+
if (!projectAllowed(cfg, dir.name)) continue;
|
|
282
|
+
let files = [];
|
|
283
|
+
try { files = readdirSync(join(cfg.projectsDir, dir.name)).filter((f) => f.endsWith(".jsonl")); } catch { continue; }
|
|
284
|
+
if (NEWEST && files.length > 1) {
|
|
285
|
+
files = [files
|
|
286
|
+
.map((f) => ({ f, m: (() => { try { return statSync(join(cfg.projectsDir, dir.name, f)).mtimeMs; } catch { return 0; } })() }))
|
|
287
|
+
.sort((a, b) => b.m - a.m)[0].f];
|
|
288
|
+
}
|
|
289
|
+
for (const f of files) {
|
|
290
|
+
const filePath = join(cfg.projectsDir, dir.name, f);
|
|
291
|
+
let fs_ = state.get(filePath);
|
|
292
|
+
if (!fs_) {
|
|
293
|
+
// New file: forward-only unless --backfill / --last N (an explicit bound wins
|
|
294
|
+
// over a persisted cursor so a bounded backfill run is deterministic).
|
|
295
|
+
const persisted = loadCursors()[filePath];
|
|
296
|
+
let startAt;
|
|
297
|
+
if (LAST > 0) {
|
|
298
|
+
startAt = await offsetOfNthLastPrompt(filePath, LAST);
|
|
299
|
+
console.log(`[claude-code-watcher] ${basename(filePath)}: --last ${LAST} → starting at byte ${startAt}`);
|
|
300
|
+
} else if (BACKFILL) startAt = 0;
|
|
301
|
+
else startAt = persisted?.offset ?? statSync(filePath).size;
|
|
302
|
+
fs_ = { offset: startAt, pendingStart: startAt, buf: newBuffer(), lastGrowth: Date.now(), sessionId: null, project: dir.name };
|
|
303
|
+
state.set(filePath, fs_);
|
|
304
|
+
}
|
|
305
|
+
try { await passFile(fs_, filePath, cfg); } catch (e) { console.error(`[claude-code-watcher] ${basename(filePath)}: ${e?.message ?? e}`); }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (!DRY_RUN) {
|
|
309
|
+
const files = {};
|
|
310
|
+
for (const [p, s] of state) { if (existsSync(p)) files[p] = { offset: s.pendingStart }; }
|
|
311
|
+
saveCursors(files);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─── main loop ─────────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
async function main() {
|
|
317
|
+
const boot = loadCaptureConfig();
|
|
318
|
+
console.log(`[claude-code-watcher] ${DRY_RUN ? "DRY-RUN " : ""}watching ${boot.projectsDir}`);
|
|
319
|
+
console.log(`[claude-code-watcher] projects=${boot.allowAll ? "* (all)" : boot.projects.join(",")} poll=${boot.pollMs}ms${BACKFILL ? " (backfill)" : ""}`);
|
|
320
|
+
let warnedMissing = false;
|
|
321
|
+
do {
|
|
322
|
+
const cfg = loadCaptureConfig();
|
|
323
|
+
if (cfg.enabled) {
|
|
324
|
+
if (!existsSync(cfg.projectsDir)) {
|
|
325
|
+
if (!warnedMissing) { console.log(`[claude-code-watcher] waiting — no Claude Code projects dir at ${cfg.projectsDir} yet`); warnedMissing = true; }
|
|
326
|
+
} else {
|
|
327
|
+
if (warnedMissing) { console.log(`[claude-code-watcher] projects dir appeared — starting capture`); warnedMissing = false; }
|
|
328
|
+
try { await pass(cfg); } catch (e) { console.error(`[claude-code-watcher] pass error: ${e?.message ?? e}`); }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (ONCE) break;
|
|
332
|
+
await new Promise((r) => setTimeout(r, cfg.pollMs));
|
|
333
|
+
} while (true);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
main().catch((e) => { console.error(`[claude-code-watcher] fatal: ${e?.message ?? e}`); process.exit(1); });
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hermes-statedb-watcher.mjs — capture Hermes/Owl turns into NodeDex by reading Hermes's own
|
|
3
|
+
// state.db. This is the ONLY working capture path for Hermes: the model proxy is ignored
|
|
4
|
+
// (Hermes hardcodes its OpenRouter endpoint) and shell hooks are registered but never invoked.
|
|
5
|
+
// Hermes persists every turn to state.db itself, so we read it — zero Hermes cooperation needed.
|
|
6
|
+
//
|
|
7
|
+
// WHAT IT IS
|
|
8
|
+
// A thin, host-specific shim over the shared capture core (nodedex-capture-core.mjs). All it
|
|
9
|
+
// adds is "how Hermes hands me a turn": it polls the messages table read-only, assembles whole
|
|
10
|
+
// turns, and calls captureTurn(). Finding the live server + POSTing is the core's job.
|
|
11
|
+
//
|
|
12
|
+
// THE HARD PART — turn ASSEMBLY (why a naive row-pair is wrong)
|
|
13
|
+
// One logical agent turn spans many rows:
|
|
14
|
+
// user → assistant(tool_calls) → tool → assistant(tool_calls) → tool → … → assistant(stop)
|
|
15
|
+
// Hermes marks the END of a turn with finish_reason='stop'; every intermediate assistant row is
|
|
16
|
+
// finish_reason='tool_calls' (a "let me check…" preamble). So:
|
|
17
|
+
// • agent_response = the agent's whole NARRATION across the turn — the tool-call preambles
|
|
18
|
+
// AND the final answer, joined — so extraction (v2 COMPREHEND reads
|
|
19
|
+
// user+response) sees the tool-driven investigation (dead-ends,
|
|
20
|
+
// decisions), not just the closing summary.
|
|
21
|
+
// • user_message = the user row(s) that opened the turn
|
|
22
|
+
// • agent_thinking = the raw tool RESULTS + any reasoning_content — recorded to the turn
|
|
23
|
+
// but NOT fed to extraction (raw tool JSON there adds noise / over-seg).
|
|
24
|
+
// Pairing "each assistant with the preceding user" would instead emit garbage fragments.
|
|
25
|
+
//
|
|
26
|
+
// SAFETY
|
|
27
|
+
// • READ-ONLY (mode=ro&immutable=1) — never opens the live DB for write; cannot corrupt it.
|
|
28
|
+
// • Privacy filter: only captures sessions whose `source` is in the allow-list (default ["tui"]),
|
|
29
|
+
// so telegram/discord/other sources are never read into the graph. Configurable in the TUI.
|
|
30
|
+
// • In-flight turns (no closing stop yet) are deferred until their stop row appears.
|
|
31
|
+
// • Fresh start captures FORWARD only (cursor = current max id) — it does not replay all history.
|
|
32
|
+
//
|
|
33
|
+
// CONFIG (read live each poll from ~/.nodedex/config.json → hermesCapture):
|
|
34
|
+
// { enabled, sources: string[], pollMs, stateDbPath } (all optional; sane defaults below)
|
|
35
|
+
//
|
|
36
|
+
// RUN
|
|
37
|
+
// node server/adapters/hermes-statedb-watcher.mjs # poll forever
|
|
38
|
+
// node server/adapters/hermes-statedb-watcher.mjs --dry-run # print assembled turns, POST nothing
|
|
39
|
+
// node server/adapters/hermes-statedb-watcher.mjs --once # one pass then exit (testing)
|
|
40
|
+
// node server/adapters/hermes-statedb-watcher.mjs --backfill # also capture pre-existing turns
|
|
41
|
+
|
|
42
|
+
import { createRequire } from "node:module";
|
|
43
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
44
|
+
import { homedir, platform } from "node:os";
|
|
45
|
+
import { join, resolve, dirname } from "node:path";
|
|
46
|
+
import { fileURLToPath } from "node:url";
|
|
47
|
+
import { captureTurn } from "./nodedex-capture-core.mjs";
|
|
48
|
+
|
|
49
|
+
// better-sqlite3 lives in server/node_modules; resolve it relative to THIS file so the watcher
|
|
50
|
+
// runs from any cwd (TUI spawns it with cwd=server, but a manual run shouldn't have to).
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const Database = require("better-sqlite3");
|
|
53
|
+
|
|
54
|
+
// Open Hermes's live DB strictly READ-ONLY. better-sqlite3 (unlike Python/node-sqlite3) does NOT
|
|
55
|
+
// support `file:…?mode=ro` URIs — pass the OS path + { readonly:true }. We deliberately do NOT use
|
|
56
|
+
// immutable mode: the file IS changing (Hermes writes every turn), and immutable would risk stale
|
|
57
|
+
// reads. readonly gives a consistent SQLite snapshot without ever writing the main db.
|
|
58
|
+
const openRO = (path) => new Database(path, { readonly: true });
|
|
59
|
+
|
|
60
|
+
const HOME = join(homedir(), ".nodedex");
|
|
61
|
+
const CONFIG_FILE = join(HOME, "config.json");
|
|
62
|
+
const CURSOR_FILE = join(HOME, "hermes-capture-cursor.json");
|
|
63
|
+
|
|
64
|
+
const args = new Set(process.argv.slice(2));
|
|
65
|
+
const DRY_RUN = args.has("--dry-run");
|
|
66
|
+
const ONCE = args.has("--once");
|
|
67
|
+
const BACKFILL = args.has("--backfill");
|
|
68
|
+
|
|
69
|
+
const TOOL_PREVIEW = 240; // truncate each tool result inside the reasoning trace
|
|
70
|
+
|
|
71
|
+
// ─── config (re-read each poll so TUI changes apply without a restart) ────────────────────────
|
|
72
|
+
function defaultStateDbPath() {
|
|
73
|
+
if (platform() === "win32" && process.env.LOCALAPPDATA) {
|
|
74
|
+
return join(process.env.LOCALAPPDATA, "hermes", "state.db");
|
|
75
|
+
}
|
|
76
|
+
// mac/linux Hermes (best-effort default; override via config.stateDbPath)
|
|
77
|
+
return join(homedir(), ".local", "share", "hermes", "state.db");
|
|
78
|
+
}
|
|
79
|
+
function loadCaptureConfig() {
|
|
80
|
+
let raw = {};
|
|
81
|
+
try { raw = JSON.parse(readFileSync(CONFIG_FILE, "utf8"))?.hermesCapture ?? {}; } catch { /* none */ }
|
|
82
|
+
const sources = Array.isArray(raw.sources) && raw.sources.length ? raw.sources.map(String) : ["tui"];
|
|
83
|
+
return {
|
|
84
|
+
enabled: raw.enabled !== false, // present-but-not-false → on (the watcher only runs when started)
|
|
85
|
+
sources, // [] or ["*"] handled by sourceAllowed()
|
|
86
|
+
allowAll: sources.includes("*"),
|
|
87
|
+
pollMs: Number.isFinite(raw.pollMs) && raw.pollMs >= 500 ? raw.pollMs : 4000,
|
|
88
|
+
stateDbPath: raw.stateDbPath || defaultStateDbPath(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const sourceAllowed = (cfg, source) => cfg.allowAll || cfg.sources.includes(String(source ?? ""));
|
|
92
|
+
|
|
93
|
+
// ─── cursor (last stop-row id we've emitted a turn for) ───────────────────────────────────────
|
|
94
|
+
function loadCursor() {
|
|
95
|
+
try { return Number(JSON.parse(readFileSync(CURSOR_FILE, "utf8"))?.lastStopId) || 0; } catch { return 0; }
|
|
96
|
+
}
|
|
97
|
+
function saveCursor(lastStopId) {
|
|
98
|
+
try { mkdirSync(HOME, { recursive: true }); writeFileSync(CURSOR_FILE, JSON.stringify({ lastStopId }, null, 2)); }
|
|
99
|
+
catch { /* read-only home → no persistence; we just re-process from memory cursor */ }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── turn assembly ────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
const clean = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
104
|
+
const truncate = (s, n) => { const t = String(s ?? ""); return t.length > n ? t.slice(0, n) + "…" : t; };
|
|
105
|
+
|
|
106
|
+
/** Build one turn from the rows in [prevStopId+1 .. stop.id] of a session. null if no user prompt. */
|
|
107
|
+
function assembleTurn(db, sid, prevStopId, stop) {
|
|
108
|
+
const rows = db.prepare(
|
|
109
|
+
`SELECT id, role, content, tool_name, finish_reason, reasoning_content
|
|
110
|
+
FROM messages WHERE session_id = ? AND id > ? AND id <= ? ORDER BY id`
|
|
111
|
+
).all(sid, prevStopId, stop.id);
|
|
112
|
+
|
|
113
|
+
const userParts = [];
|
|
114
|
+
const agentParts = []; // agent narration across the turn (preambles + final answer) → response
|
|
115
|
+
const toolTrace = []; // raw tool outputs → thinking (recorded, not extracted)
|
|
116
|
+
for (const r of rows) {
|
|
117
|
+
if (r.role === "user") { const c = clean(r.content); if (c) userParts.push(c); }
|
|
118
|
+
else if (r.role === "assistant") { const c = clean(r.content); if (c) agentParts.push(c); } // incl. the final 'stop' row
|
|
119
|
+
else if (r.role === "tool") { const c = clean(r.content); if (c) toolTrace.push(`[${r.tool_name || "tool"}] ${truncate(c, TOOL_PREVIEW)}`); }
|
|
120
|
+
}
|
|
121
|
+
if (!userParts.length || !agentParts.length) return null; // no prompt or no agent text → skip (orphan)
|
|
122
|
+
|
|
123
|
+
const stopThinking = clean(stop.reasoning_content); // thinking-model reasoning on the final row
|
|
124
|
+
if (stopThinking) toolTrace.unshift(stopThinking);
|
|
125
|
+
|
|
126
|
+
// turn_number = the state.db message id of this turn's closing 'stop' row — the turn's identity
|
|
127
|
+
// in HERMES'S OWN log, NOT a private ordinal. Anchoring to it means NodeDex never keeps a
|
|
128
|
+
// drift-prone copy of "what's extracted": the extraction RANGE bounds ARE state.db ids
|
|
129
|
+
// (conversation_turn_ranges.end_turn_number = the last extracted stop-id = a watermark), so
|
|
130
|
+
// "extracted vs not" is just `state.db stop.id <= watermark`. Globally unique + monotonic.
|
|
131
|
+
// agent_id is PER-SESSION ("owl-<sid>") so each conversation is its own arc (turns don't bleed
|
|
132
|
+
// across sessions into one range).
|
|
133
|
+
const turnNumber = stop.id;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
agentResponse: agentParts.join("\n"),
|
|
137
|
+
userMessage: userParts.join("\n"),
|
|
138
|
+
reasoning: toolTrace.join("\n"),
|
|
139
|
+
agentId: `owl-${String(sid)}`, // per-session → one arc per Hermes conversation
|
|
140
|
+
turnNumber, // = state.db stop-row id (the state-anchored watermark unit)
|
|
141
|
+
turnName: `hermes-${String(sid).slice(0, 15)}-${stop.id}`,
|
|
142
|
+
hint: "discovery",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── one pass: find new completed turns, assemble, capture ────────────────────────────────────
|
|
147
|
+
async function pass(cfg, cursor) {
|
|
148
|
+
const db = openRO(cfg.stateDbPath);
|
|
149
|
+
try {
|
|
150
|
+
// Completed turns since the cursor = assistant rows with finish_reason='stop' and id > cursor.
|
|
151
|
+
const stops = db.prepare(
|
|
152
|
+
`SELECT m.id, m.session_id, m.content, m.reasoning_content, s.source
|
|
153
|
+
FROM messages m JOIN sessions s ON s.id = m.session_id
|
|
154
|
+
WHERE m.role = 'assistant' AND m.finish_reason = 'stop' AND m.id > ?
|
|
155
|
+
ORDER BY m.id`
|
|
156
|
+
).all(cursor);
|
|
157
|
+
|
|
158
|
+
let lastStopId = cursor;
|
|
159
|
+
for (const stop of stops) {
|
|
160
|
+
const sid = stop.session_id;
|
|
161
|
+
// walk back to the previous stop in THIS session (the window start), else session start (0)
|
|
162
|
+
const prevStopId = db.prepare(
|
|
163
|
+
`SELECT MAX(id) AS p FROM messages
|
|
164
|
+
WHERE session_id = ? AND role = 'assistant' AND finish_reason = 'stop' AND id < ?`
|
|
165
|
+
).get(sid, stop.id)?.p ?? 0;
|
|
166
|
+
|
|
167
|
+
if (!sourceAllowed(cfg, stop.source)) { lastStopId = stop.id; continue; } // privacy skip, still advance
|
|
168
|
+
|
|
169
|
+
const turn = assembleTurn(db, sid, prevStopId, stop);
|
|
170
|
+
if (turn) {
|
|
171
|
+
if (DRY_RUN) {
|
|
172
|
+
console.log("─".repeat(72));
|
|
173
|
+
console.log(`TURN session=${sid} stop_id=${stop.id} source=${stop.source}`);
|
|
174
|
+
console.log(` user: ${truncate(turn.userMessage.replace(/\n/g, " ⏎ "), 160)}`);
|
|
175
|
+
console.log(` response: ${truncate(clean(turn.agentResponse), 200)} (${turn.agentResponse.length} ch)`);
|
|
176
|
+
console.log(` thinking: ${truncate(turn.reasoning.replace(/\n/g, " · "), 220)} (${turn.reasoning.length} ch)`);
|
|
177
|
+
} else {
|
|
178
|
+
const status = await captureTurn(turn);
|
|
179
|
+
console.log(`[hermes-watcher] captured stop_id=${stop.id} session=${String(sid).slice(0, 15)} → ${status}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
lastStopId = stop.id;
|
|
183
|
+
}
|
|
184
|
+
return lastStopId;
|
|
185
|
+
} finally {
|
|
186
|
+
db.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── main loop ────────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
async function main() {
|
|
192
|
+
const boot = loadCaptureConfig();
|
|
193
|
+
console.log(`[hermes-watcher] ${DRY_RUN ? "DRY-RUN " : ""}watching ${boot.stateDbPath}`);
|
|
194
|
+
console.log(`[hermes-watcher] sources=${boot.allowAll ? "* (all)" : boot.sources.join(",")} poll=${boot.pollMs}ms${BACKFILL ? " (backfill)" : ""}`);
|
|
195
|
+
|
|
196
|
+
// --backfill starts at 0; else the persisted cursor wins. If neither, DEFER picking the start
|
|
197
|
+
// point until state.db first appears, then start forward-only (current max stop-id). This lets a
|
|
198
|
+
// default-on watcher start BEFORE Hermes exists without crashing or later replaying all history.
|
|
199
|
+
let cursor = BACKFILL ? 0 : loadCursor();
|
|
200
|
+
let cursorReady = BACKFILL || cursor > 0;
|
|
201
|
+
let warnedMissing = false;
|
|
202
|
+
|
|
203
|
+
do {
|
|
204
|
+
const cfg = loadCaptureConfig(); // re-read each pass → TUI changes apply live
|
|
205
|
+
if (cfg.enabled) {
|
|
206
|
+
if (!existsSync(cfg.stateDbPath)) {
|
|
207
|
+
// Default-on, but no Hermes turn-log yet (or Hermes not installed). Wait — never crash;
|
|
208
|
+
// a harmless idle poll until it appears (or the user toggles capture off in the TUI).
|
|
209
|
+
if (!warnedMissing) { console.log(`[hermes-watcher] waiting — no Hermes state.db at ${cfg.stateDbPath} yet`); warnedMissing = true; }
|
|
210
|
+
} else {
|
|
211
|
+
if (warnedMissing) { console.log(`[hermes-watcher] state.db appeared — starting capture`); warnedMissing = false; }
|
|
212
|
+
if (!cursorReady) {
|
|
213
|
+
const db = openRO(cfg.stateDbPath);
|
|
214
|
+
try { cursor = db.prepare(`SELECT MAX(id) AS m FROM messages WHERE role='assistant' AND finish_reason='stop'`).get()?.m ?? 0; }
|
|
215
|
+
finally { db.close(); }
|
|
216
|
+
cursorReady = true;
|
|
217
|
+
console.log(`[hermes-watcher] capturing forward from cursor=${cursor}`);
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const next = await pass(cfg, cursor);
|
|
221
|
+
if (next > cursor) { cursor = next; if (!DRY_RUN) saveCursor(cursor); }
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error(`[hermes-watcher] pass error: ${e?.message ?? e}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (ONCE) break;
|
|
228
|
+
await new Promise((r) => setTimeout(r, loadCaptureConfig().pollMs));
|
|
229
|
+
} while (!ONCE);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
main().catch((e) => { console.error(`[hermes-watcher] fatal: ${e?.message ?? e}`); process.exit(1); });
|
|
233
|
+
|
|
234
|
+
export { assembleTurn, sourceAllowed, loadCaptureConfig }; // for testing
|