@agentmemory/agentmemory 0.9.30 → 0.9.31
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/.env.example +39 -33
- package/AGENTS.md +8 -8
- package/README.md +173 -558
- package/dist/.env.example +39 -33
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +1741 -966
- package/dist/cli.mjs.map +1 -1
- package/dist/compression-recovery-CnhLQC6N.mjs +5912 -0
- package/dist/compression-recovery-CnhLQC6N.mjs.map +1 -0
- package/dist/{connect-B4_ca6BD.mjs → connect-BSLIAMpw.mjs} +874 -267
- package/dist/connect-BSLIAMpw.mjs.map +1 -0
- package/dist/hooks/_capture.d.mts +5 -2
- package/dist/hooks/_capture.d.mts.map +1 -1
- package/dist/hooks/_capture.mjs +55 -19
- package/dist/hooks/_capture.mjs.map +1 -1
- package/dist/hooks/notification.mjs +20 -6
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +25 -5
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +12 -4
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +12 -4
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +37 -10
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +25 -5
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +12 -4
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +47 -16
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +37 -10
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +74 -72
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +13 -5
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +18 -6
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +13 -5
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/{image-refs-T_urbwHM.mjs → image-refs-Cyta4Lf9.mjs} +2 -2
- package/dist/{image-refs-T_urbwHM.mjs.map → image-refs-Cyta4Lf9.mjs.map} +1 -1
- package/dist/index.mjs +8539 -4203
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/dsh.d.mts +55 -0
- package/dist/integrations/dsh.d.mts.map +1 -0
- package/dist/integrations/dsh.mjs +1145 -0
- package/dist/integrations/dsh.mjs.map +1 -0
- package/dist/{keyed-mutex-DchXsyfP.mjs → keyed-mutex-DCUh7PG-.mjs} +58 -9
- package/dist/keyed-mutex-DCUh7PG-.mjs.map +1 -0
- package/dist/{secret-store-BnY3A50C.mjs → secret-store-D9GwO0xp.mjs} +535 -434
- package/dist/secret-store-D9GwO0xp.mjs.map +1 -0
- package/dist/session-arguments-zxsbbd8y.mjs +113 -0
- package/dist/session-arguments-zxsbbd8y.mjs.map +1 -0
- package/dist/{src-Jsq9LCeH.mjs → src-DaMtDzt_.mjs} +4087 -3788
- package/dist/src-DaMtDzt_.mjs.map +1 -0
- package/dist/{standalone-DKCdrf1d.mjs → standalone-BlqshyZ5.mjs} +213 -56
- package/dist/standalone-BlqshyZ5.mjs.map +1 -0
- package/dist/standalone.d.mts +1 -1
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +626 -73
- package/dist/standalone.mjs.map +1 -1
- package/dist/viewer/index.html +288 -140
- package/integrations/hermes/README.md +84 -0
- package/integrations/hermes/__init__.py +669 -0
- package/integrations/hermes/plugin.yaml +27 -0
- package/integrations/omp/LICENSE +190 -0
- package/integrations/omp/README.md +76 -0
- package/integrations/omp/capture.ts +1 -0
- package/integrations/omp/dist/index.mjs +1450 -0
- package/integrations/omp/index.ts +488 -0
- package/integrations/omp/package.json +13 -0
- package/integrations/omp/security.ts +67 -0
- package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/omp/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/omp/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/omp/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/omp/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/omp/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/omp/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/omp/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/omp/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/omp/skills/commit-context/SKILL.md +64 -0
- package/integrations/omp/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/omp/skills/commit-history/SKILL.md +62 -0
- package/integrations/omp/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/forget/EXAMPLES.md +40 -0
- package/integrations/omp/skills/forget/SKILL.md +73 -0
- package/integrations/omp/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/omp/skills/handoff/SKILL.md +75 -0
- package/integrations/omp/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/lesson/SKILL.md +64 -0
- package/integrations/omp/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/omp/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/recall/EXAMPLES.md +74 -0
- package/integrations/omp/skills/recall/SKILL.md +74 -0
- package/integrations/omp/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/recap/EXAMPLES.md +62 -0
- package/integrations/omp/skills/recap/SKILL.md +68 -0
- package/integrations/omp/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/remember/EXAMPLES.md +67 -0
- package/integrations/omp/skills/remember/SKILL.md +66 -0
- package/integrations/omp/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/omp/skills/session-history/SKILL.md +67 -0
- package/integrations/omp/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/omp/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/LICENSE +190 -0
- package/integrations/openclaw/README.md +88 -0
- package/integrations/openclaw/openclaw.plugin.json +71 -0
- package/integrations/openclaw/package.json +38 -0
- package/integrations/openclaw/plugin.mjs +425 -0
- package/integrations/openclaw/plugin.yaml +26 -0
- package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/openclaw/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/openclaw/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/openclaw/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/openclaw/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/openclaw/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/openclaw/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/openclaw/skills/commit-context/SKILL.md +64 -0
- package/integrations/openclaw/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/openclaw/skills/commit-history/SKILL.md +62 -0
- package/integrations/openclaw/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/forget/EXAMPLES.md +40 -0
- package/integrations/openclaw/skills/forget/SKILL.md +73 -0
- package/integrations/openclaw/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/openclaw/skills/handoff/SKILL.md +75 -0
- package/integrations/openclaw/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/lesson/SKILL.md +64 -0
- package/integrations/openclaw/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/openclaw/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/recall/EXAMPLES.md +74 -0
- package/integrations/openclaw/skills/recall/SKILL.md +74 -0
- package/integrations/openclaw/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/recap/EXAMPLES.md +62 -0
- package/integrations/openclaw/skills/recap/SKILL.md +68 -0
- package/integrations/openclaw/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/remember/EXAMPLES.md +67 -0
- package/integrations/openclaw/skills/remember/SKILL.md +66 -0
- package/integrations/openclaw/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/openclaw/skills/session-history/SKILL.md +67 -0
- package/integrations/openclaw/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/openclaw/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/LICENSE +190 -0
- package/integrations/pi/README.md +45 -51
- package/integrations/pi/dist/index.mjs +1379 -0
- package/integrations/pi/index.ts +138 -171
- package/integrations/pi/package.json +16 -6
- package/integrations/pi/security.ts +6 -26
- package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/pi/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/pi/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/pi/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/pi/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/pi/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/pi/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/pi/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/pi/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/pi/skills/commit-context/SKILL.md +64 -0
- package/integrations/pi/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/pi/skills/commit-history/SKILL.md +62 -0
- package/integrations/pi/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/forget/EXAMPLES.md +40 -0
- package/integrations/pi/skills/forget/SKILL.md +73 -0
- package/integrations/pi/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/pi/skills/handoff/SKILL.md +75 -0
- package/integrations/pi/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/lesson/SKILL.md +64 -0
- package/integrations/pi/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/pi/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/recall/EXAMPLES.md +74 -0
- package/integrations/pi/skills/recall/SKILL.md +74 -0
- package/integrations/pi/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/recap/EXAMPLES.md +62 -0
- package/integrations/pi/skills/recap/SKILL.md +68 -0
- package/integrations/pi/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/remember/EXAMPLES.md +67 -0
- package/integrations/pi/skills/remember/SKILL.md +66 -0
- package/integrations/pi/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/pi/skills/session-history/SKILL.md +67 -0
- package/integrations/pi/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/pi/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/package.json +32 -11
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +4 -3
- package/plugin/.devin-plugin/plugin.json +5 -3
- package/plugin/cursor/hooks.json +23 -8
- package/plugin/hooks.json +67 -0
- package/plugin/opencode/README.md +1 -1
- package/plugin/opencode/agentmemory-capture.ts +25 -11
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
- package/plugin/scripts/_capture.d.mts +5 -2
- package/plugin/scripts/_capture.d.mts.map +1 -1
- package/plugin/scripts/_capture.mjs +55 -19
- package/plugin/scripts/_capture.mjs.map +1 -1
- package/plugin/scripts/cursor/run-detached.d.mts +1 -0
- package/plugin/scripts/cursor/run-detached.mjs +786 -0
- package/plugin/scripts/cursor/run-detached.mjs.map +1 -0
- package/plugin/scripts/cursor/run-hook.d.mts +1 -0
- package/plugin/scripts/cursor/run-hook.mjs +704 -0
- package/plugin/scripts/cursor/run-hook.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +20 -6
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/plugin-bridge.mjs +37 -6
- package/plugin/scripts/post-commit.mjs +25 -5
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +12 -4
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +12 -4
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +37 -10
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +25 -5
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +12 -4
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +47 -16
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +37 -10
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +74 -72
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +13 -5
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +18 -6
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +13 -5
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +21 -7
- package/plugin/skills/agentmemory-agents/REFERENCE.md +7 -6
- package/plugin/skills/agentmemory-agents/SKILL.md +19 -5
- package/plugin/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-architecture/SKILL.md +4 -4
- package/plugin/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-config/REFERENCE.md +12 -15
- package/plugin/skills/agentmemory-config/SKILL.md +7 -3
- package/plugin/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-hooks/SKILL.md +15 -5
- package/plugin/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +4 -4
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +6 -3
- package/plugin/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +9 -1
- package/plugin/skills/agentmemory-rest-api/SKILL.md +12 -6
- package/plugin/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/commit-context/SKILL.md +1 -1
- package/plugin/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/commit-history/SKILL.md +1 -1
- package/plugin/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/forget/EXAMPLES.md +19 -54
- package/plugin/skills/forget/SKILL.md +16 -12
- package/plugin/skills/forget/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/handoff/EXAMPLES.md +5 -4
- package/plugin/skills/handoff/SKILL.md +10 -5
- package/plugin/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/lesson/SKILL.md +1 -1
- package/plugin/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/memory-discipline/SKILL.md +1 -1
- package/plugin/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/recall/SKILL.md +18 -7
- package/plugin/skills/recall/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/recap/EXAMPLES.md +5 -3
- package/plugin/skills/recap/SKILL.md +11 -6
- package/plugin/skills/recap/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/remember/EXAMPLES.md +3 -3
- package/plugin/skills/remember/SKILL.md +1 -1
- package/plugin/skills/remember/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/session-history/EXAMPLES.md +2 -2
- package/plugin/skills/session-history/SKILL.md +10 -4
- package/plugin/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/write-agentmemory-skill/SKILL.md +2 -1
- package/plugin/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/dist/connect-B4_ca6BD.mjs.map +0 -1
- package/dist/docker-compose.yml +0 -73
- package/dist/iii-config.docker.yaml +0 -53
- package/dist/iii-config.yaml +0 -63
- package/dist/keyed-mutex-DchXsyfP.mjs.map +0 -1
- package/dist/secret-store-BnY3A50C.mjs.map +0 -1
- package/dist/src-Jsq9LCeH.mjs.map +0 -1
- package/dist/standalone-DKCdrf1d.mjs.map +0 -1
- package/dist/status-IRz7ta8-.mjs +0 -2344
- package/dist/status-IRz7ta8-.mjs.map +0 -1
- package/docker-compose.yml +0 -73
- package/iii-config.docker.yaml +0 -53
- package/iii-config.yaml +0 -63
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-hook.mjs","names":["canonicalProject"],"sources":["../../../src/hooks/_project.ts","../../../src/hooks/cursor/cursor-db.ts","../../../src/hooks/cursor/workspace.ts","../../../src/hooks/cursor/delegate.ts","../../../src/hooks/cursor/run-hook.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git repository basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const [top, gitDir, commonDir] = execSync(\n \"git rev-parse --show-toplevel --git-dir --git-common-dir\",\n {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n },\n )\n .toString()\n .trim()\n .split(/\\r?\\n/);\n if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));\n } catch {}\n return basename(dir);\n}\n\nfunction repositoryRoot(dir: string, top: string, gitDir?: string, commonDir?: string): string {\n if (!gitDir || !commonDir) return top;\n const physical = realpathSync(dir);\n const common = resolve(physical, commonDir);\n if (resolve(physical, gitDir) === common || basename(common) !== \".git\") return top;\n return dirname(common);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","/**\n * Exact session -> workspace lookup from Cursor's own SQLite storage.\n *\n * Everything else in the resolver infers the workspace: from payload fields\n * Cursor may or may not send, from paths that happen to appear in tool\n * arguments, from a transcript directory name whose path separators have been\n * flattened into hyphens. Those work, but they are inference, and inference\n * that lands on the wrong project writes a user's memories into the wrong\n * place without ever reporting an error.\n *\n * Cursor knows the answer exactly, and stores it. The layout (reverse\n * engineered; see integrations/cursor/README.md for the full picture):\n *\n * <userdir>/globalStorage/state.vscdb\n * ItemTable[\"composer.composerHeaders\"]\n * -> { allComposers: [ { composerId, workspaceIdentifier: {\n * id, uri: { fsPath, ... } } } ] }\n *\n * A Cursor hook's `session_id` is the `composerId`, so one indexed lookup\n * gives the workspace path with no guessing.\n *\n * Two things stop this from being the only strategy:\n *\n * - Cursor 3.0 (April 2026) moved this index from per-workspace databases\n * into the global one, and the migration is lazy: a workspace migrates\n * when it is next opened. Machines still on <=2.6, or with workspaces not\n * opened since the upgrade, keep the old per-workspace `allComposers`\n * array instead. Both shapes are handled below.\n * - Reading SQLite needs a driver. `node:sqlite` only exists from Node 22.5,\n * and this package supports Node >=20. better-sqlite3 is an optional\n * dependency of the daemon, not something a hook can count on.\n *\n * So every failure path here returns null and the caller falls back to\n * inference. This module is an accuracy upgrade, never a requirement.\n */\nimport { createRequire } from \"node:module\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst require = createRequire(import.meta.url);\n\ninterface Statement {\n get(...params: unknown[]): unknown;\n}\ninterface DbHandle {\n prepare(sql: string): Statement;\n close(): void;\n}\ninterface Driver {\n open(path: string): DbHandle;\n}\n\n// `undefined` = not probed yet, `null` = probed and unavailable.\nlet driverCache: Driver | null | undefined;\n\n// node:sqlite is still flagged experimental and emits a process warning on\n// load. Hook stderr is surfaced in Cursor's hook log, so that would put a\n// paragraph about an implementation detail in front of the user on every\n// session that reaches this module.\n//\n// The filter is installed once and left in place, rather than wrapped around\n// the require, because process.emitWarning defers the actual emission to the\n// next tick: restoring the original listeners in a `finally` happens before\n// the warning is ever emitted, so the wrapping approach silences nothing. It\n// is scoped as tightly as it can be -- only this one warning is dropped, and\n// everything else is re-emitted untouched.\nlet warningFilterInstalled = false;\n\nfunction suppressSqliteExperimentalWarning(): void {\n if (warningFilterInstalled) return;\n warningFilterInstalled = true;\n const previous = process.listeners(\"warning\");\n process.removeAllListeners(\"warning\");\n process.on(\"warning\", (warning: Error) => {\n if (warning.name === \"ExperimentalWarning\" && /sqlite/i.test(warning.message)) return;\n for (const listener of previous) listener(warning);\n });\n}\n\nfunction requireQuietly<T>(id: string): T | null {\n suppressSqliteExperimentalWarning();\n try {\n return require(id) as T;\n } catch {\n return null;\n }\n}\n\nfunction loadDriver(): Driver | null {\n if (driverCache !== undefined) return driverCache;\n\n const nodeSqlite = requireQuietly<{ DatabaseSync: new (p: string, o?: object) => DbHandle }>(\n \"node:sqlite\",\n );\n if (nodeSqlite?.DatabaseSync) {\n driverCache = {\n open: (path) => new nodeSqlite.DatabaseSync(path, { readOnly: true }),\n };\n return driverCache;\n }\n\n // Same lazy-optional pattern the daemon uses for better-sqlite3 in\n // src/functions/migrate.ts -- used when installed, never required.\n const better = requireQuietly<new (p: string, o?: object) => DbHandle>(\"better-sqlite3\");\n if (better) {\n driverCache = {\n open: (path) => new better(path, { readonly: true, fileMustExist: true }),\n };\n return driverCache;\n }\n\n driverCache = null;\n return driverCache;\n}\n\n/** Read one ItemTable value. Returns null for any failure, including a locked DB. */\nfunction readItemTableValue(dbPath: string, key: string): string | null {\n if (!existsSync(dbPath)) return null;\n const driver = loadDriver();\n if (!driver) return null;\n\n let db: DbHandle | null = null;\n try {\n db = driver.open(dbPath);\n const row = db.prepare(\"SELECT value FROM ItemTable WHERE key = ?\").get(key) as\n | { value?: unknown }\n | undefined;\n const value = row?.value;\n if (typeof value === \"string\") return value;\n if (value instanceof Uint8Array) return Buffer.from(value).toString(\"utf-8\");\n return null;\n } catch {\n // Cursor holds the database open while running. Readers normally coexist\n // fine, but a locked or half-migrated file must not break the hook.\n return null;\n } finally {\n try {\n db?.close();\n } catch {}\n }\n}\n\nexport interface CursorStorageRoots {\n globalStorage: string;\n workspaceStorage: string;\n}\n\nexport function cursorStorageRoots(): CursorStorageRoots | null {\n const home = homedir();\n const bases: string[] = [];\n if (process.platform === \"win32\") {\n const appData = process.env[\"APPDATA\"];\n if (appData) bases.push(join(appData, \"Cursor\", \"User\"));\n bases.push(join(home, \"AppData\", \"Roaming\", \"Cursor\", \"User\"));\n } else if (process.platform === \"darwin\") {\n bases.push(join(home, \"Library\", \"Application Support\", \"Cursor\", \"User\"));\n } else {\n const configHome = process.env[\"XDG_CONFIG_HOME\"];\n if (configHome) bases.push(join(configHome, \"Cursor\", \"User\"));\n bases.push(join(home, \".config\", \"Cursor\", \"User\"));\n }\n\n for (const base of bases) {\n const globalStorage = join(base, \"globalStorage\");\n if (existsSync(globalStorage)) {\n return { globalStorage, workspaceStorage: join(base, \"workspaceStorage\") };\n }\n }\n return null;\n}\n\ninterface ComposerHeader {\n composerId?: unknown;\n workspaceIdentifier?: { uri?: { fsPath?: unknown; path?: unknown } };\n}\n\nfunction fsPathFromHeader(header: ComposerHeader | undefined): string | null {\n const uri = header?.workspaceIdentifier?.uri;\n const value = uri?.fsPath ?? uri?.path;\n return typeof value === \"string\" && value.trim() ? value : null;\n}\n\n/** Cursor 3.0+: one central index in the global database. */\nfunction fromGlobalIndex(sessionId: string, roots: CursorStorageRoots): string | null {\n const raw = readItemTableValue(\n join(roots.globalStorage, \"state.vscdb\"),\n \"composer.composerHeaders\",\n );\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as { allComposers?: ComposerHeader[] };\n const hit = parsed.allComposers?.find((c) => c?.composerId === sessionId);\n // A hit with no uri is a workspace-less \"empty window\" chat -- Cursor\n // genuinely has no path for it, so neither do we.\n return fsPathFromHeader(hit);\n } catch {\n return null;\n }\n}\n\n/** `{\"folder\":\"file:///d%3A/repo\"}`, or a vscode-remote:// URI we cannot map. */\nfunction folderFromWorkspaceJson(workspaceDir: string): string | null {\n const file = join(workspaceDir, \"workspace.json\");\n if (!existsSync(file)) return null;\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf-8\")) as { folder?: unknown };\n const folder = parsed.folder;\n if (typeof folder !== \"string\" || !folder.startsWith(\"file://\")) return null;\n return fileURLToPath(folder);\n } catch {\n return null;\n }\n}\n\n// Pre-3.0 lookup means opening one database per workspace, so it is ordered\n// by recency and capped: the workspace a live session belongs to was touched\n// moments ago and sits at the top. A full scan of ~120 workspaces measures\n// around 200ms; this ordering turns the common case into one or two opens.\nconst LEGACY_SCAN_LIMIT = 40;\n\nfunction fromLegacyWorkspaceDbs(sessionId: string, roots: CursorStorageRoots): string | null {\n if (!existsSync(roots.workspaceStorage)) return null;\n\n let dirs: Array<{ dir: string; mtime: number }>;\n try {\n dirs = readdirSync(roots.workspaceStorage)\n .map((dir) => {\n try {\n return { dir, mtime: statSync(join(roots.workspaceStorage, dir, \"state.vscdb\")).mtimeMs };\n } catch {\n return null;\n }\n })\n .filter((x): x is { dir: string; mtime: number } => x !== null)\n .sort((a, b) => b.mtime - a.mtime)\n .slice(0, LEGACY_SCAN_LIMIT);\n } catch {\n return null;\n }\n\n for (const { dir } of dirs) {\n const raw = readItemTableValue(\n join(roots.workspaceStorage, dir, \"state.vscdb\"),\n \"composer.composerData\",\n );\n if (!raw) continue;\n try {\n const parsed = JSON.parse(raw) as { allComposers?: Array<{ composerId?: unknown }> };\n // Migrated workspaces no longer carry allComposers; their sessions are\n // in the global index handled above.\n if (!Array.isArray(parsed.allComposers)) continue;\n if (!parsed.allComposers.some((c) => c?.composerId === sessionId)) continue;\n return folderFromWorkspaceJson(join(roots.workspaceStorage, dir));\n } catch {\n continue;\n }\n }\n return null;\n}\n\n/**\n * The workspace directory Cursor recorded for this session, or null when it\n * cannot be determined (no driver, no storage, workspace-less window, or a\n * remote URI that does not map to a local path).\n */\nexport function workspaceFromCursorDb(sessionId: string): string | null {\n if (!sessionId) return null;\n const roots = cursorStorageRoots();\n if (!roots) return null;\n return fromGlobalIndex(sessionId, roots) ?? fromLegacyWorkspaceDbs(sessionId, roots);\n}\n","import {\n chmodSync,\n closeSync,\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n readSync,\n renameSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { execSync } from \"node:child_process\";\nimport { dirname, join, basename } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { resolveProject as canonicalProject } from \"../_project.js\";\nimport { workspaceFromCursorDb } from \"./cursor-db.js\";\n\n// Cursor does not hand hooks a trustworthy working directory: `cwd` can be\n// `.cursor`, the IDE install path (via VSCODE_CWD), or absent entirely. This\n// module is the Cursor-specific piece of the adapter — everything it exists\n// to do is turn whatever Cursor sends into a real project directory, so the\n// canonical hooks in src/hooks/*.ts can stay unchanged.\nconst HOME = homedir();\nconst CURSOR_PROJECTS_DIR = join(HOME, \".cursor\", \"projects\");\nconst SESSION_CACHE_PATH = join(HOME, \".cursor\", \"hooks\", \".agentmemory-session-cache.json\");\nconst HOOK_PAYLOAD_DIR = join(HOME, \".cursor\", \"hooks\", \".am-hook-payloads\");\n\nexport interface Workspace {\n project: string;\n cwd: string;\n}\n\nexport type HookData = Record<string, unknown> | null | undefined;\n\ninterface SessionCacheEntry {\n project: string;\n cwd: string;\n updatedAt: string;\n}\n\ntype SessionCache = Record<string, SessionCacheEntry>;\n\nconst SESSION_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\nconst SESSION_CACHE_MAX_ENTRIES = 200;\n\nexport function normalizePathSlashes(value: unknown): string {\n return String(value).replace(/\\\\/g, \"/\");\n}\n\nexport function isCursorMetadataPath(value: unknown): boolean {\n if (!value || typeof value !== \"string\") return false;\n const trimmed = normalizePathSlashes(value.trim());\n if (trimmed === \".cursor\") return true;\n // ~/.cursor/worktrees/<name> is the exception: Cursor puts real git\n // checkouts there for its background agents. Treating those as metadata\n // sends the session to whatever the transcript scan guesses instead of to\n // the repository the agent is actually editing.\n if (/(^|\\/)\\.cursor\\/worktrees\\/[^/]/.test(trimmed)) return false;\n return /(^|\\/)\\.cursor(\\/|$)/.test(trimmed);\n}\n\nexport function pathUnderHome(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const homeNorm = normalizePathSlashes(HOME);\n const valueNorm = normalizePathSlashes(value);\n return valueNorm === homeNorm || valueNorm.startsWith(`${homeNorm}/`);\n}\n\n// VSCODE_CWD points at the Cursor install directory, which resolves to a\n// project literally named \"cursor\" if it is allowed through.\nfunction isIdeInstallPath(value: unknown): boolean {\n if (!value || typeof value !== \"string\") return false;\n const norm = normalizePathSlashes(value).toLowerCase();\n return (\n /(^|[\\\\/])(programs|program files|program files \\(x86\\))[\\\\/]cursor([\\\\/]|$)/i.test(norm) ||\n /cursor\\.app[\\\\/]contents/i.test(norm) ||\n /(^|[\\\\/])microsoft vs code[\\\\/]resources[\\\\/]app([\\\\/]|$)/i.test(norm)\n );\n}\n\nfunction isBadPath(value: unknown): boolean {\n if (!value || typeof value !== \"string\") return true;\n const trimmed = normalizePathSlashes(value.trim());\n if (!trimmed || trimmed === \"/\" || trimmed === \".\") return true;\n // A bare drive root is never a project. Cursor emits a single-letter\n // transcript slug for some legacy windows (\"c\"), which decodes to \"C:\".\n if (/^[a-zA-Z]:\\/?$/.test(trimmed)) return true;\n if (isCursorMetadataPath(trimmed)) return true;\n if (isIdeInstallPath(trimmed)) return true;\n return false;\n}\n\nfunction sleepMs(ms: number): void {\n const end = Date.now() + ms;\n while (Date.now() < end) {\n // Busy wait: this runs inside a lock retry loop in a short-lived hook\n // process, where blocking is cheaper than an async scheduler hop.\n }\n}\n\nfunction withSessionCacheLock<T>(fn: () => T): T | undefined {\n const lockPath = `${SESSION_CACHE_PATH}.lock`;\n mkdirSync(dirname(SESSION_CACHE_PATH), { recursive: true });\n let fd: number | undefined;\n for (let i = 0; i < 50; i++) {\n try {\n fd = openSync(lockPath, \"wx\");\n break;\n } catch {\n sleepMs(10);\n }\n }\n if (fd === undefined) return undefined;\n try {\n return fn();\n } finally {\n closeSync(fd);\n try {\n unlinkSync(lockPath);\n } catch {}\n }\n}\n\nfunction loadSessionCache(): SessionCache {\n try {\n const value: unknown = JSON.parse(readFileSync(SESSION_CACHE_PATH, \"utf-8\"));\n return value && typeof value === \"object\" && !Array.isArray(value) ? value as SessionCache : {};\n } catch {\n return {};\n }\n}\n\n// Every hook is a fresh process that parses this file, so it is kept small:\n// stale sessions are dropped and only the most recent entries survive.\nfunction pruneSessionCache(cache: SessionCache, now: number): SessionCache {\n const fresh = Object.entries(cache).filter(([, entry]) => {\n const updated = Date.parse(entry?.updatedAt ?? \"\");\n return typeof entry?.project === \"string\" && typeof entry?.cwd === \"string\" && Number.isFinite(updated) && now - updated < SESSION_CACHE_MAX_AGE_MS;\n });\n fresh.sort((a, b) => Date.parse(b[1].updatedAt) - Date.parse(a[1].updatedAt));\n return Object.fromEntries(fresh.slice(0, SESSION_CACHE_MAX_ENTRIES));\n}\n\nfunction rememberSession(sessionId: string | undefined, project: string, cwd: string): void {\n if (!sessionId || !project || project === \".cursor\") return;\n withSessionCacheLock(() => {\n try {\n const now = Date.now();\n const previous = loadSessionCache();\n delete previous[sessionId];\n const cache = pruneSessionCache({ [sessionId]: { project, cwd, updatedAt: new Date(now).toISOString() }, ...previous }, now);\n const tmp = `${SESSION_CACHE_PATH}.${process.pid}.${Date.now()}.tmp`;\n writeFileSync(tmp, JSON.stringify(cache, null, 2));\n renameSync(tmp, SESSION_CACHE_PATH);\n } catch {}\n });\n}\n\nfunction recallSession(sessionId: string | undefined): SessionCacheEntry | null {\n if (!sessionId) return null;\n const cache = pruneSessionCache(loadSessionCache(), Date.now());\n return Object.hasOwn(cache, sessionId) ? cache[sessionId]! : null;\n}\n\n// Absolute paths that belong to the OS rather than to any user project. A\n// stray /usr/lib/... or /etc/... reference inside tool_input must not be\n// allowed to resolve to a project named \"lib\" or \"etc\".\nconst SYSTEM_PATH_PREFIXES = [\n \"/usr/\",\n \"/etc/\",\n \"/bin/\",\n \"/sbin/\",\n \"/lib/\",\n \"/lib64/\",\n \"/opt/\",\n \"/var/\",\n \"/proc/\",\n \"/sys/\",\n \"/dev/\",\n \"/run/\",\n \"/boot/\",\n \"/snap/\",\n \"/nix/\",\n];\n\nfunction isSystemPath(value: string): boolean {\n const norm = normalizePathSlashes(value);\n const temp = normalizePathSlashes(tmpdir()).replace(/\\/$/, \"\");\n if (norm.startsWith(`${temp}/`)) return false;\n return SYSTEM_PATH_PREFIXES.some((prefix) => norm.startsWith(prefix));\n}\n\n// tool_input carries whatever shape the tool used, so paths are harvested by\n// walking the object. The filter has to be permissive enough to find the\n// project and strict enough not to invent one.\nfunction isCollectablePath(value: unknown): value is string {\n if (typeof value !== \"string\" || isCursorMetadataPath(value)) return false;\n if (pathUnderHome(value)) return true;\n // Windows drive-absolute: HOME is regularly on C: while the checkout sits\n // on D:, so a HOME-only rule loses every cross-drive project.\n if (/^[a-zA-Z]:[\\\\/]/.test(value)) return true;\n // POSIX-absolute outside $HOME: containers (Codespaces, devcontainers)\n // check repos out at /workspaces/..., which a HOME-only rule silently\n // rejects. existingDirectory() and the git lookup downstream validate it.\n if (value.startsWith(\"/\")) return !isSystemPath(value);\n return false;\n}\n\nfunction collectPathStrings(value: unknown, out: string[] = []): string[] {\n if (typeof value === \"string\") {\n if (isCollectablePath(value)) out.push(value);\n return out;\n }\n if (Array.isArray(value)) {\n for (const item of value) collectPathStrings(item, out);\n return out;\n }\n if (value && typeof value === \"object\") {\n for (const v of Object.values(value)) collectPathStrings(v, out);\n }\n return out;\n}\n\nfunction pathExists(pathValue: string): boolean {\n if (existsSync(pathValue)) return true;\n if (process.platform === \"win32\") {\n const native = pathValue.replace(/\\//g, \"\\\\\");\n if (native !== pathValue && existsSync(native)) return true;\n }\n return false;\n}\n\nconst MAX_ANCESTOR_STEPS = 64;\n\n// Payloads often carry a file path (tool_input.path) rather than a\n// directory, and sometimes a path that no longer exists. Walk up until\n// something real is found, then normalise a file down to its directory.\nfunction existingAncestor(pathValue: string): string | null {\n let current = pathValue;\n for (let step = 0; step < MAX_ANCESTOR_STEPS; step++) {\n if (!current || current === HOME || current === \"/\") return null;\n if (pathExists(current)) {\n const resolved = process.platform === \"win32\" ? current.replace(/\\//g, \"\\\\\") : current;\n try {\n if (statSync(resolved).isFile()) {\n return dirname(resolved);\n }\n } catch {}\n return resolved;\n }\n // dirname() is a fixed point at every filesystem root -- dirname(\"//\") is\n // \"//\", dirname(\"C:\") is \"C:\", dirname(\"D:/\") is \"D:/\" -- so climbing\n // without a progress check spins forever. Nothing produced such an input\n // while this only ever saw $HOME-prefixed paths; a general path scan\n // produces them constantly, because every \"https://host/x\" in a\n // transcript contains a \"//host/x\".\n const parent = dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n return null;\n}\n\nfunction gitRootFromPath(targetPath: string): string {\n return execSync(\"git rev-parse --show-toplevel\", {\n cwd: targetPath,\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n }).trim();\n}\n\n// Filesystem-only equivalent of `git rev-parse --show-toplevel`, for hot\n// paths that would otherwise spawn a process per candidate. `.git` is a\n// directory in a normal clone and a file in a linked worktree, so a plain\n// existence check covers both.\nfunction gitRootNearby(startPath: string): string | null {\n let current = startPath;\n for (let step = 0; step < MAX_ANCESTOR_STEPS; step++) {\n if (!current || current === \"/\") return null;\n if (pathExists(join(current, \".git\"))) return current;\n const parent = dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n return null;\n}\n\n\ninterface ResolvedProject {\n name: string;\n /** Whether the name came from a git toplevel rather than a bare directory. */\n fromGitRoot: boolean;\n}\n\nfunction projectFromPath(targetPath: string): ResolvedProject {\n try {\n return { name: canonicalProject(gitRootFromPath(targetPath)), fromGitRoot: true };\n } catch {\n return { name: canonicalProject(targetPath), fromGitRoot: false };\n }\n}\n\n// Cursor names each directory under ~/.cursor/projects after the workspace\n// path with every separator flattened to \"-\", so the transcript directory for\n// a session already encodes where that session was running. Decoding it is\n// lossy in one direction only: a directory name may itself contain hyphens,\n// which makes \"d-Andrew-Code-cc-router\" mean D:/Andrew/Code/cc-router and,\n// just as validly on paper, D:/Andrew/Code/cc/router.\n//\n// The disambiguator is the filesystem. Try every way of grouping consecutive\n// segments into one directory name, but only descend into groupings that\n// actually exist -- pruning collapses what looks like a 2^segments search\n// into the handful of real directories on the machine.\nfunction decodeSlugCandidates(slug: string): string[] {\n if (!slug || slug === \"empty-window\") return [];\n // Cursor 3.x names workspace-less windows (started from the welcome screen)\n // after a timestamp. Those never correspond to a path.\n if (/^\\d{10,}$/.test(slug)) return [];\n\n const parts = slug.split(\"-\");\n if (!parts.length) return [];\n\n const results = new Set<string>();\n\n function walk(index: number, currentPath: string): void {\n if (index >= parts.length) {\n results.add(currentPath);\n return;\n }\n for (let take = 1; index + take <= parts.length; take++) {\n const next = `${currentPath}/${parts.slice(index, index + take).join(\"-\")}`;\n if (!pathExists(next)) continue;\n walk(index + take, next);\n }\n }\n\n // A single leading letter is a Windows drive: \"d-Andrew-Code\" -> D:/Andrew/Code.\n // Without this branch the whole function returns nothing on Windows, which\n // is where HOME and the checkout most often sit on different drives.\n const first = parts[0];\n if (first && /^[a-zA-Z]$/.test(first)) walk(1, `${first.toUpperCase()}:`);\n // Otherwise the slug starts at the filesystem root: \"Users-alice-src\",\n // \"home-andrew-src\", \"workspaces-repo\".\n walk(0, \"\");\n\n return [...results];\n}\n\nfunction pickBestCandidate(candidates: string[], preferredLabel: string): string | null {\n if (!candidates.length) return null;\n if (preferredLabel) {\n const labelMatch = candidates.find((p) => basename(p) === preferredLabel);\n if (labelMatch) return labelMatch;\n }\n\n const gitRoots: string[] = [];\n for (const candidate of candidates) {\n try {\n gitRoots.push(gitRootFromPath(candidate));\n } catch {}\n }\n const uniqueGitRoots = [...new Set(gitRoots)];\n if (uniqueGitRoots.length === 1) return uniqueGitRoots[0] ?? null;\n\n return candidates.sort((a, b) => b.length - a.length)[0] ?? null;\n}\n\nfunction findSessionTranscript(\n sessionId: string,\n): { slug: string; transcriptFile: string | null } | null {\n if (!sessionId || !existsSync(CURSOR_PROJECTS_DIR)) return null;\n\n for (const slug of readdirSync(CURSOR_PROJECTS_DIR)) {\n const transcriptsRoot = join(CURSOR_PROJECTS_DIR, slug, \"agent-transcripts\");\n if (!existsSync(transcriptsRoot)) continue;\n\n for (const entry of readdirSync(transcriptsRoot)) {\n if (entry === sessionId || entry.startsWith(`${sessionId}-`)) {\n const transcriptFile = join(transcriptsRoot, entry, `${entry}.jsonl`);\n return {\n slug,\n transcriptFile: existsSync(transcriptFile) ? transcriptFile : null,\n };\n }\n }\n }\n return null;\n}\n\nconst TRANSCRIPT_SCAN_BYTES = 250000;\nconst TRANSCRIPT_CANDIDATE_LIMIT = 120;\nconst TRANSCRIPT_MATCH_LIMIT = 4000;\nconst TRANSCRIPT_MIN_VOTES = 3;\n\n// Paths are normalised to forward slashes before matching, so a Windows path\n// looks like \"D:/repo/src/a.ts\" by the time these run.\n//\n// Both patterns are a single character class with one bounded quantifier, on\n// purpose. The obvious formulation -- /\\/(?:[\\w.-]+\\/)+[\\w.-]*/ -- nests a\n// quantifier inside a quantifier, and on a 250KB transcript full of\n// slash-bearing strings that backtracks badly enough to hang the hook for\n// minutes. There is no clever matching to do here anyway: grab anything\n// path-shaped and let the existence check below decide.\nconst TRANSCRIPT_PATH_PATTERNS = [\n /[a-zA-Z]:[A-Za-z0-9._@+\\-/]{3,240}/g, // Windows drive-absolute\n /\\/[A-Za-z0-9._@+\\-/]{3,240}/g, // POSIX absolute\n];\n\n// Last resort before environment variables: mine the session transcript for\n// paths and take the directory that shows up across the most of them.\n//\n// This used to anchor its regex on $HOME, which quietly made the whole layer\n// dead on the two most common non-trivial setups -- Windows with HOME on C:\n// and the checkout on D:, and containers that check out under /workspaces.\n// Match any absolute path shape instead and let existence plus the git lookup\n// downstream throw out the noise (URLs, log fragments, OS paths).\nfunction workspaceFromTranscriptFile(transcriptFile: string | null): string | null {\n if (!transcriptFile || !existsSync(transcriptFile)) return null;\n\n let chunk: string;\n const fd = openSync(transcriptFile, \"r\");\n try {\n const buffer = Buffer.alloc(TRANSCRIPT_SCAN_BYTES);\n const bytes = readSync(fd, buffer, 0, buffer.length, 0);\n chunk = normalizePathSlashes(buffer.subarray(0, bytes).toString(\"utf8\"));\n } finally { closeSync(fd); }\n const counts = new Map<string, number>();\n const seen = new Set<string>();\n\n // Bounded on both axes: every candidate costs filesystem walks, and a busy\n // transcript can contain thousands of path-shaped strings.\n for (const pattern of TRANSCRIPT_PATH_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n let scanned = 0;\n while ((match = pattern.exec(chunk)) !== null) {\n if (++scanned > TRANSCRIPT_MATCH_LIMIT) break;\n if (seen.size >= TRANSCRIPT_CANDIDATE_LIMIT) break;\n const value = match[0];\n if (seen.has(value)) continue;\n seen.add(value);\n // \"//host/path\" is the tail of a URL, not a filesystem path.\n if (value.startsWith(\"//\")) continue;\n if (isCursorMetadataPath(value) || isIdeInstallPath(value) || isSystemPath(value)) continue;\n // Only paths that still exist vote, and they vote for their git root.\n //\n // Both halves matter. Letting a path climb to whatever ancestor still\n // exists (existingAncestor) means a deleted D:/repo/pkg/src/a.ts votes\n // for D:/repo, and a transcript is full of such paths, so the\n // shallowest common directory always wins -- a session in\n // D:/Andrew/Code/pkg resolves to the project \"Code\". Counting existing\n // directories as themselves instead just moves the problem: the winner\n // becomes whatever generic directory the conversation mentioned most,\n // which in practice is \"/bin\" or \"C:/Users\". Requiring a repository\n // root is what makes a vote mean \"this is a project\".\n const existing = existingDirectory(value);\n if (!existing || existing === HOME || isBadPath(existing)) continue;\n const root = gitRootNearby(existing);\n if (!root || root === HOME || isBadPath(root)) continue;\n counts.set(root, (counts.get(root) || 0) + 1);\n }\n }\n\n let best: string | null = null;\n let bestCount = 0;\n for (const [pathValue, count] of counts) {\n if (count > bestCount) {\n best = pathValue;\n bestCount = count;\n }\n }\n\n // One passing mention of a repository is not evidence that the session was\n // running in it. This layer is a guess of last resort, and a wrong guess\n // files a user's memories under someone else's project -- worse than\n // admitting the workspace is unknown.\n return bestCount >= TRANSCRIPT_MIN_VOTES ? best : null;\n}\n\nfunction workspaceFromSessionId(sessionId: string): string | null {\n const hit = findSessionTranscript(sessionId);\n if (!hit) return null;\n\n // Slug first. It is a lossy encoding of the workspace path, but every\n // candidate it produces is verified against the filesystem, so a result is\n // a directory that really exists and really matches the name Cursor gave\n // this session's transcript directory.\n const preferredLabel = process.env[\"CURSOR_WORKSPACE_LABEL\"] || \"\";\n const fromSlug = pickBestCandidate(decodeSlugCandidates(hit.slug), preferredLabel);\n if (fromSlug) return fromSlug;\n\n // Transcript scan last: it answers \"which directory is mentioned most in\n // this conversation\", which is a guess, not a fact. Asked first it will\n // happily answer \"agentmemory\" for a session about agentmemory that was\n // actually running somewhere else entirely. It stays because it is the only\n // thing that can place a workspace-less window that was still working on\n // real files.\n return workspaceFromTranscriptFile(hit.transcriptFile);\n}\n\nexport function readHookStdinComplete(maxWaitMs = 30000): Promise<string> {\n return new Promise((resolve) => {\n let input = \"\";\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n try {\n process.stdin.destroy();\n } catch {}\n resolve(input);\n };\n const t = setTimeout(finish, maxWaitMs);\n if (t.unref) t.unref();\n process.stdin.on(\"data\", (c) => {\n input += c;\n });\n process.stdin.on(\"end\", () => {\n clearTimeout(t);\n finish();\n });\n process.stdin.on(\"error\", () => {\n clearTimeout(t);\n finish();\n });\n });\n}\n\nexport function writeHookPayloadTemp(input: string): string {\n mkdirSync(HOOK_PAYLOAD_DIR, { recursive: true });\n try {\n chmodSync(HOOK_PAYLOAD_DIR, 0o700);\n } catch {}\n const path = join(HOOK_PAYLOAD_DIR, `am-hook-${process.pid}-${Date.now()}.json`);\n writeFileSync(path, input, { encoding: \"utf-8\", mode: 0o600 });\n return path;\n}\n\nexport function readWorkerHookPayload(): Record<string, unknown> | null {\n const file = process.env[\"AM_HOOK_INPUT_FILE\"];\n if (!file) {\n console.error(\"[agentmemory] missing AM_HOOK_INPUT_FILE in worker\");\n return null;\n }\n try {\n const raw = readFileSync(file, \"utf-8\");\n const value: unknown = JSON.parse(raw);\n return value && typeof value === \"object\" && !Array.isArray(value) ? value as Record<string, unknown> : null;\n } catch (err) {\n console.error(\"[agentmemory] failed to parse hook payload:\", (err as Error).message);\n return null;\n } finally {\n try {\n unlinkSync(file);\n } catch {}\n }\n}\n\n// Sessions were landing under \".codex\": a path pointing into an agent's state\n// directory walked up to ~/.codex and that became the project name.\n//\n// Rejecting every dot-named directory would be wrong, though. Plenty of real\n// projects are dot-named -- ~/.dotfiles, ~/.emacs.d, ~/.config kept under\n// chezmoi, and GitHub's own convention of a repository literally named\n// \".github\". What separates those from ~/.codex or ~/.vscode is not the name,\n// it is that a human deliberately version controls them. So the rule is \"a\n// dot-named directory that is not a repository\", which needs no list of tool\n// names to keep up to date as new agents ship.\nfunction isMetadataProject(project: ResolvedProject): boolean {\n return project.name.startsWith(\".\") && !project.fromGitRoot;\n}\n\nfunction isHomeDirectory(pathValue: string): boolean {\n return normalizePathSlashes(pathValue) === normalizePathSlashes(HOME);\n}\n\n// The path exactly, or its parent when it points at a file. Unlike\n// existingAncestor() this does not climb: for a workspace path that some\n// source claims is authoritative, \"the directory is gone\" means the record is\n// stale, not \"use whatever ancestor still exists\". Climbing there silently\n// turns D:/Andrew/Code/cc-router (moved away) into the project \"Code\" and\n// files the session's memories under it.\nfunction existingDirectory(pathValue: string): string | null {\n if (!pathExists(pathValue)) return null;\n const resolved = process.platform === \"win32\" ? pathValue.replace(/\\//g, \"\\\\\") : pathValue;\n try {\n return statSync(resolved).isFile() ? dirname(resolved) : resolved;\n } catch {\n return null;\n }\n}\n\nfunction resolveFromPathCandidates(\n candidates: unknown[],\n sessionId: string | undefined,\n options: { exact?: boolean; remember?: boolean } = {},\n): Workspace | null {\n for (const candidate of candidates) {\n if (typeof candidate !== \"string\" || isBadPath(candidate)) continue;\n const existing = options.exact ? existingDirectory(candidate) : existingAncestor(candidate);\n if (!existing || isBadPath(existing)) continue;\n const project = projectFromPath(existing);\n // $HOME is what a hook sees when the agent was launched with no workspace\n // at all. Accepting it invents a project named after the account -- real\n // sessions were being filed under \"Andrew\" with cwd C:\\Users\\Andrew --\n // when the honest answer is that there is no workspace. Same carve-out as\n // the dot-directory rule: allowed when HOME is itself a repository, which\n // is the dotfiles-checked-out-in-$HOME layout.\n if (isHomeDirectory(existing) && !project.fromGitRoot) continue;\n if (!isMetadataProject(project)) {\n if (options.remember !== false) rememberSession(sessionId, project.name, existing);\n return { project: project.name, cwd: existing };\n }\n }\n return null;\n}\n\n// Which layer answered is the single most useful thing to know when a session\n// lands under the wrong project, and it is invisible from the outside: every\n// layer returns the same shape. Set AM_CURSOR_DEBUG=1 to have the resolver say\n// so on stderr, which Cursor surfaces in its hook log.\nfunction debugLayer(layer: string, result: Workspace | null): Workspace | null {\n if (result && process.env[\"AM_CURSOR_DEBUG\"] === \"1\") {\n console.error(`[agentmemory] workspace resolved by ${layer}: ${result.project} (${result.cwd})`);\n }\n return result;\n}\n\nexport function resolveWorkspace(data: HookData): Workspace {\n const sessionId = [data?.[\"session_id\"], data?.[\"sessionId\"], data?.[\"conversation_id\"]].find((value): value is string => typeof value === \"string\" && Boolean(value.trim()));\n // A cached workspace that has since moved or been deleted is stale; fall\n // through so the current payload can replace it.\n const cached = recallSession(sessionId);\n if (cached?.cwd && !isBadPath(cached.cwd) && existingDirectory(cached.cwd)) {\n return { project: canonicalProject(cached.cwd), cwd: cached.cwd };\n }\n\n const payloadCandidates: unknown[] = [\n ...(Array.isArray(data?.[\"workspace_roots\"]) ? (data[\"workspace_roots\"] as unknown[]) : []),\n ...(Array.isArray(data?.[\"workspace_folders\"]) ? (data[\"workspace_folders\"] as unknown[]) : []),\n data?.[\"workspace_folder\"],\n data?.[\"workspaceFolder\"],\n data?.[\"workspace\"],\n data?.[\"cwd\"],\n data?.[\"root_path\"],\n data?.[\"project_path\"],\n ];\n\n const fromPayload = debugLayer(\"payload\", resolveFromPathCandidates(payloadCandidates, sessionId));\n if (fromPayload) return fromPayload;\n\n const toolPaths = collectPathStrings(data?.[\"tool_input\"] ?? data?.[\"toolArgs\"])\n .map(existingDirectory)\n .filter((p): p is string => Boolean(p) && !isIdeInstallPath(p));\n const fromTools = debugLayer(\"tool_input\", resolveFromPathCandidates(toolPaths, sessionId));\n if (fromTools) return fromTools;\n\n if (sessionId) {\n // Cursor's own record of where this session was running. Exact, so it is\n // tried before the inference layers below -- it costs one indexed SQLite\n // read (~5ms) and, because the result is cached per session, happens at\n // most once per session rather than once per hook.\n const fromDb = debugLayer(\n \"cursor-db\",\n resolveFromPathCandidates([workspaceFromCursorDb(sessionId)], sessionId, { exact: true }),\n );\n if (fromDb) return fromDb;\n\n // Same validation as every other layer: both sources here already\n // verified the directory exists, so `exact` keeps a stale one from\n // silently climbing to a parent.\n const fromSession = debugLayer(\n \"transcript-dir\",\n resolveFromPathCandidates([workspaceFromSessionId(sessionId)], sessionId, { exact: true }),\n );\n if (fromSession) return fromSession;\n }\n\n // Env comes last. CURSOR_PROJECT_DIR is Cursor's documented workspace root\n // for hooks; the other two are explicit user overrides. Those are cached.\n const authoritativeEnv: unknown[] = [\n process.env[\"CURSOR_PROJECT_DIR\"],\n process.env[\"CURSOR_WORKSPACE_ROOT\"],\n process.env[\"CURSOR_WORKSPACE_FOLDER\"],\n ];\n const fromEnv = debugLayer(\"env\", resolveFromPathCandidates(authoritativeEnv, sessionId));\n if (fromEnv) return fromEnv;\n\n // PWD and VSCODE_CWD only describe where the hook process was launched\n // (VSCODE_CWD is often the IDE install directory), so they are used for\n // this hook but never cached: later hooks with real evidence must win.\n const launchEnv: unknown[] = [process.env[\"PWD\"], process.env[\"VSCODE_CWD\"]];\n const fromLaunch = debugLayer(\n \"launch-env\",\n resolveFromPathCandidates(launchEnv, sessionId, { remember: false }),\n );\n if (fromLaunch) return fromLaunch;\n\n const label = process.env[\"CURSOR_WORKSPACE_LABEL\"];\n if (label) return { project: label, cwd: label };\n\n return { project: \"unknown-project\", cwd: \"unknown-project\" };\n}\n\nexport function resolveProject(data: HookData): string {\n return resolveWorkspace(data).project;\n}\n","import { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveWorkspace } from \"./workspace.js\";\n\n// Cursor fires camelCase lifecycle events; each one maps to the canonical\n// hook compiled from src/hooks/*.ts. Keep this table the only place that\n// knows Cursor's naming — everything downstream is the shared hook.\nexport const HOOK_MAP = {\n sessionStart: \"session-start.mjs\",\n beforeSubmitPrompt: \"prompt-submit.mjs\",\n preToolUse: \"pre-tool-use.mjs\",\n postToolUse: \"post-tool-use.mjs\",\n postToolUseFailure: \"post-tool-failure.mjs\",\n preCompact: \"pre-compact.mjs\",\n subagentStart: \"subagent-start.mjs\",\n subagentStop: \"subagent-stop.mjs\",\n stop: \"stop.mjs\",\n sessionEnd: \"session-end.mjs\",\n} as const;\n\nexport type CursorHookKey = keyof typeof HOOK_MAP;\n\nexport function isCursorHookKey(value: unknown): value is CursorHookKey {\n return typeof value === \"string\" && Object.hasOwn(HOOK_MAP, value);\n}\n\n// stop/sessionEnd fan out to summarize + consolidate on the daemon side, so\n// they get a longer leash than the interactive hooks.\nconst SLOW_HOOKS = new Set<CursorHookKey>([\"stop\", \"sessionEnd\"]);\n\nexport type HookPayload = Record<string, unknown>;\n\nexport interface DelegateOptions {\n // Tests run against src/, where the canonical .mjs files do not sit one\n // level up. Bundled output resolves it correctly on its own.\n officialDir?: string;\n}\n\n// Bundled to plugin/scripts/cursor/<entry>.mjs, so the canonical hooks are\n// exactly one directory up.\nfunction defaultOfficialDir(): string {\n return join(dirname(fileURLToPath(import.meta.url)), \"..\");\n}\n\nexport function enrichPayload(data: HookPayload): {\n project: string;\n payload: HookPayload;\n} {\n const { project: resolvedProject, cwd } = resolveWorkspace(data);\n const project = process.env[\"AGENTMEMORY_PROJECT_NAME\"]?.trim() || resolvedProject;\n return {\n project,\n payload: {\n ...data,\n session_id: data[\"session_id\"] ?? data[\"sessionId\"] ?? data[\"conversation_id\"],\n cwd,\n project,\n },\n };\n}\n\nexport function delegateHook(\n hookKey: CursorHookKey,\n data: HookPayload,\n options: DelegateOptions = {},\n): number {\n const script = HOOK_MAP[hookKey];\n const scriptPath = join(options.officialDir ?? defaultOfficialDir(), script);\n\n // Checked explicitly rather than left to the spawn: node starts fine and\n // then exits 1 with a module-not-found stack, which the block below would\n // forward as a deliberate non-zero decision from the hook, and which lands\n // a Node stack trace in Cursor's hook log. This is a real installation\n // state -- the plugin ships built .mjs, so a source checkout that has not\n // run `npm run build` hits it -- and it deserves an answer the user can act\n // on rather than a stack trace.\n if (!existsSync(scriptPath)) {\n console.error(\n `[agentmemory] cursor hook \"${hookKey}\": canonical hook not found at ${scriptPath}.` +\n ` Run \\`npm run build\\` in the agentmemory checkout.`,\n );\n return 0;\n }\n\n const { project, payload } = enrichPayload(data);\n\n const child = spawnSync(process.execPath, [scriptPath], {\n input: JSON.stringify(payload),\n // resolveProject() in the canonical hooks reads this before falling back\n // to git/cwd, which is how the Cursor-specific resolution wins.\n env: { ...process.env, AGENTMEMORY_PROJECT_NAME: project },\n encoding: \"utf-8\",\n maxBuffer: 10 * 1024 * 1024,\n timeout: SLOW_HOOKS.has(hookKey) ? 180000 : 30000,\n });\n\n if (child.stdout) process.stdout.write(child.stdout);\n if (child.stderr) process.stderr.write(child.stderr);\n\n // spawnSync reports a failure to launch (missing script, timeout kill) as\n // status === null, so `status ?? 0` would announce success and lose the\n // observation silently -- the exact failure mode that makes a memory hook\n // untrustworthy. Say so on stderr, but still exit 0: agentmemory is a\n // passive recorder and must never block the editor. A genuine non-zero\n // exit from the canonical hook is a real decision and is passed through.\n if (child.error) {\n console.error(\n `[agentmemory] cursor hook \"${hookKey}\" could not run ${script}: ${child.error.message}`,\n );\n return 0;\n }\n if (child.signal) {\n console.error(\n `[agentmemory] cursor hook \"${hookKey}\" (${script}) was killed by ${child.signal}` +\n ` -- treating as no-op`,\n );\n return 0;\n }\n return child.status ?? 0;\n}\n","#!/usr/bin/env node\nimport { delegateHook, isCursorHookKey, type HookPayload } from \"./delegate.js\";\n\n// CLI entrypoint for Cursor's synchronous hooks. Kept separate from\n// delegate.ts so importing the dispatcher never starts a second stdin\n// reader in the same process (run-detached.ts depends on that).\nasync function main(): Promise<void> {\n const hookKey = process.argv[2];\n if (!isCursorHookKey(hookKey)) process.exit(0);\n\n let input = \"\";\n for await (const chunk of process.stdin) input += chunk;\n if (!input.trim()) process.exit(0);\n\n let data: HookPayload;\n try {\n data = JSON.parse(input) as HookPayload;\n } catch {\n process.exit(0);\n }\n if (!data || typeof data !== \"object\" || Array.isArray(data)) process.exit(0);\n\n process.exit(delegateHook(hookKey, data));\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;;;;;;;AAKA,SAAgB,eAAe,KAAsB;CACnD,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO,SAAS,MAAM;CACvD,MAAM,MAAM,OAAO,IAAI,MAAM,GAAG,MAAM,QAAQ,KAAK;AACnD,KAAI;EACF,MAAM,CAAC,KAAK,QAAQ,aAAa,SAC/B,4DACA;GACE,KAAK;GACL,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CACF,CACE,UAAU,CACV,MAAM,CACN,MAAM,QAAQ;AACjB,MAAI,IAAK,QAAO,SAAS,eAAe,KAAK,KAAK,QAAQ,UAAU,CAAC;SAC/D;AACR,QAAO,SAAS,IAAI;;AAGtB,SAAS,eAAe,KAAa,KAAa,QAAiB,WAA4B;AAC7F,KAAI,CAAC,UAAU,CAAC,UAAW,QAAO;CAClC,MAAM,WAAW,aAAa,IAAI;CAClC,MAAM,SAAS,QAAQ,UAAU,UAAU;AAC3C,KAAI,QAAQ,UAAU,OAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAQ,QAAO;AAChF,QAAO,QAAQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACUxB,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAc9C,IAAI;AAaJ,IAAI,yBAAyB;AAE7B,SAAS,oCAA0C;AACjD,KAAI,uBAAwB;AAC5B,0BAAyB;CACzB,MAAM,WAAW,QAAQ,UAAU,UAAU;AAC7C,SAAQ,mBAAmB,UAAU;AACrC,SAAQ,GAAG,YAAY,YAAmB;AACxC,MAAI,QAAQ,SAAS,yBAAyB,UAAU,KAAK,QAAQ,QAAQ,CAAE;AAC/E,OAAK,MAAM,YAAY,SAAU,UAAS,QAAQ;GAClD;;AAGJ,SAAS,eAAkB,IAAsB;AAC/C,oCAAmC;AACnC,KAAI;AACF,SAAO,QAAQ,GAAG;SACZ;AACN,SAAO;;;AAIX,SAAS,aAA4B;AACnC,KAAI,gBAAgB,KAAA,EAAW,QAAO;CAEtC,MAAM,aAAa,eACjB,cACD;AACD,KAAI,YAAY,cAAc;AAC5B,gBAAc,EACZ,OAAO,SAAS,IAAI,WAAW,aAAa,MAAM,EAAE,UAAU,MAAM,CAAC,EACtE;AACD,SAAO;;CAKT,MAAM,SAAS,eAAwD,iBAAiB;AACxF,KAAI,QAAQ;AACV,gBAAc,EACZ,OAAO,SAAS,IAAI,OAAO,MAAM;GAAE,UAAU;GAAM,eAAe;GAAM,CAAC,EAC1E;AACD,SAAO;;AAGT,eAAc;AACd,QAAO;;;AAIT,SAAS,mBAAmB,QAAgB,KAA4B;AACtE,KAAI,CAAC,WAAW,OAAO,CAAE,QAAO;CAChC,MAAM,SAAS,YAAY;AAC3B,KAAI,CAAC,OAAQ,QAAO;CAEpB,IAAI,KAAsB;AAC1B,KAAI;AACF,OAAK,OAAO,KAAK,OAAO;EAIxB,MAAM,QAHM,GAAG,QAAQ,4CAA4C,CAAC,IAAI,IAGvD,EAAE;AACnB,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,WAAY,QAAO,OAAO,KAAK,MAAM,CAAC,SAAS,QAAQ;AAC5E,SAAO;SACD;AAGN,SAAO;WACC;AACR,MAAI;AACF,OAAI,OAAO;UACL;;;AASZ,SAAgB,qBAAgD;CAC9D,MAAM,OAAO,SAAS;CACtB,MAAM,QAAkB,EAAE;AAC1B,KAAI,QAAQ,aAAa,SAAS;EAChC,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QAAS,OAAM,KAAK,KAAK,SAAS,UAAU,OAAO,CAAC;AACxD,QAAM,KAAK,KAAK,MAAM,WAAW,WAAW,UAAU,OAAO,CAAC;YACrD,QAAQ,aAAa,SAC9B,OAAM,KAAK,KAAK,MAAM,WAAW,uBAAuB,UAAU,OAAO,CAAC;MACrE;EACL,MAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,WAAY,OAAM,KAAK,KAAK,YAAY,UAAU,OAAO,CAAC;AAC9D,QAAM,KAAK,KAAK,MAAM,WAAW,UAAU,OAAO,CAAC;;AAGrD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,gBAAgB,KAAK,MAAM,gBAAgB;AACjD,MAAI,WAAW,cAAc,CAC3B,QAAO;GAAE;GAAe,kBAAkB,KAAK,MAAM,mBAAmB;GAAE;;AAG9E,QAAO;;AAQT,SAAS,iBAAiB,QAAmD;CAC3E,MAAM,MAAM,QAAQ,qBAAqB;CACzC,MAAM,QAAQ,KAAK,UAAU,KAAK;AAClC,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,QAAQ;;;AAI7D,SAAS,gBAAgB,WAAmB,OAA0C;CACpF,MAAM,MAAM,mBACV,KAAK,MAAM,eAAe,cAAc,EACxC,2BACD;AACD,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI;EAEF,MAAM,MADS,KAAK,MAAM,IACR,CAAC,cAAc,MAAM,MAAM,GAAG,eAAe,UAAU;AAGzE,SAAO,iBAAiB,IAAI;SACtB;AACN,SAAO;;;;AAKX,SAAS,wBAAwB,cAAqC;CACpE,MAAM,OAAO,KAAK,cAAc,iBAAiB;AACjD,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;EAEF,MAAM,SADS,KAAK,MAAM,aAAa,MAAM,QAAQ,CAChC,CAAC;AACtB,MAAI,OAAO,WAAW,YAAY,CAAC,OAAO,WAAW,UAAU,CAAE,QAAO;AACxE,SAAO,cAAc,OAAO;SACtB;AACN,SAAO;;;AAQX,MAAM,oBAAoB;AAE1B,SAAS,uBAAuB,WAAmB,OAA0C;AAC3F,KAAI,CAAC,WAAW,MAAM,iBAAiB,CAAE,QAAO;CAEhD,IAAI;AACJ,KAAI;AACF,SAAO,YAAY,MAAM,iBAAiB,CACvC,KAAK,QAAQ;AACZ,OAAI;AACF,WAAO;KAAE;KAAK,OAAO,SAAS,KAAK,MAAM,kBAAkB,KAAK,cAAc,CAAC,CAAC;KAAS;WACnF;AACN,WAAO;;IAET,CACD,QAAQ,MAA2C,MAAM,KAAK,CAC9D,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CACjC,MAAM,GAAG,kBAAkB;SACxB;AACN,SAAO;;AAGT,MAAK,MAAM,EAAE,SAAS,MAAM;EAC1B,MAAM,MAAM,mBACV,KAAK,MAAM,kBAAkB,KAAK,cAAc,EAChD,wBACD;AACD,MAAI,CAAC,IAAK;AACV,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAG9B,OAAI,CAAC,MAAM,QAAQ,OAAO,aAAa,CAAE;AACzC,OAAI,CAAC,OAAO,aAAa,MAAM,MAAM,GAAG,eAAe,UAAU,CAAE;AACnE,UAAO,wBAAwB,KAAK,MAAM,kBAAkB,IAAI,CAAC;UAC3D;AACN;;;AAGJ,QAAO;;;;;;;AAQT,SAAgB,sBAAsB,WAAkC;AACtE,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,QAAQ,oBAAoB;AAClC,KAAI,CAAC,MAAO,QAAO;AACnB,QAAO,gBAAgB,WAAW,MAAM,IAAI,uBAAuB,WAAW,MAAM;;;;ACtPtF,MAAM,OAAO,SAAS;AACtB,MAAM,sBAAsB,KAAK,MAAM,WAAW,WAAW;AAC7D,MAAM,qBAAqB,KAAK,MAAM,WAAW,SAAS,kCAAkC;AACnE,KAAK,MAAM,WAAW,SAAS,oBAAoB;AAiB5E,MAAM,2BAA2B,QAAc,KAAK;AACpD,MAAM,4BAA4B;AAElC,SAAgB,qBAAqB,OAAwB;AAC3D,QAAO,OAAO,MAAM,CAAC,QAAQ,OAAO,IAAI;;AAG1C,SAAgB,qBAAqB,OAAyB;AAC5D,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,UAAU,qBAAqB,MAAM,MAAM,CAAC;AAClD,KAAI,YAAY,UAAW,QAAO;AAKlC,KAAI,kCAAkC,KAAK,QAAQ,CAAE,QAAO;AAC5D,QAAO,uBAAuB,KAAK,QAAQ;;AAG7C,SAAgB,cAAc,OAAyB;AACrD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,WAAW,qBAAqB,KAAK;CAC3C,MAAM,YAAY,qBAAqB,MAAM;AAC7C,QAAO,cAAc,YAAY,UAAU,WAAW,GAAG,SAAS,GAAG;;AAKvE,SAAS,iBAAiB,OAAyB;AACjD,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,OAAO,qBAAqB,MAAM,CAAC,aAAa;AACtD,QACE,+EAA+E,KAAK,KAAK,IACzF,4BAA4B,KAAK,KAAK,IACtC,6DAA6D,KAAK,KAAK;;AAI3E,SAAS,UAAU,OAAyB;AAC1C,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,UAAU,qBAAqB,MAAM,MAAM,CAAC;AAClD,KAAI,CAAC,WAAW,YAAY,OAAO,YAAY,IAAK,QAAO;AAG3D,KAAI,iBAAiB,KAAK,QAAQ,CAAE,QAAO;AAC3C,KAAI,qBAAqB,QAAQ,CAAE,QAAO;AAC1C,KAAI,iBAAiB,QAAQ,CAAE,QAAO;AACtC,QAAO;;AAGT,SAAS,QAAQ,IAAkB;CACjC,MAAM,MAAM,KAAK,KAAK,GAAG;AACzB,QAAO,KAAK,KAAK,GAAG;;AAMtB,SAAS,qBAAwB,IAA4B;CAC3D,MAAM,WAAW,GAAG,mBAAmB;AACvC,WAAU,QAAQ,mBAAmB,EAAE,EAAE,WAAW,MAAM,CAAC;CAC3D,IAAI;AACJ,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,IACtB,KAAI;AACF,OAAK,SAAS,UAAU,KAAK;AAC7B;SACM;AACN,UAAQ,GAAG;;AAGf,KAAI,OAAO,KAAA,EAAW,QAAO,KAAA;AAC7B,KAAI;AACF,SAAO,IAAI;WACH;AACR,YAAU,GAAG;AACb,MAAI;AACF,cAAW,SAAS;UACd;;;AAIZ,SAAS,mBAAiC;AACxC,KAAI;EACF,MAAM,QAAiB,KAAK,MAAM,aAAa,oBAAoB,QAAQ,CAAC;AAC5E,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG,QAAwB,EAAE;SACzF;AACN,SAAO,EAAE;;;AAMb,SAAS,kBAAkB,OAAqB,KAA2B;CACzE,MAAM,QAAQ,OAAO,QAAQ,MAAM,CAAC,QAAQ,GAAG,WAAW;EACxD,MAAM,UAAU,KAAK,MAAM,OAAO,aAAa,GAAG;AAClD,SAAO,OAAO,OAAO,YAAY,YAAY,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,QAAQ,IAAI,MAAM,UAAU;GAC3H;AACF,OAAM,MAAM,GAAG,MAAM,KAAK,MAAM,EAAE,GAAG,UAAU,GAAG,KAAK,MAAM,EAAE,GAAG,UAAU,CAAC;AAC7E,QAAO,OAAO,YAAY,MAAM,MAAM,GAAG,0BAA0B,CAAC;;AAGtE,SAAS,gBAAgB,WAA+B,SAAiB,KAAmB;AAC1F,KAAI,CAAC,aAAa,CAAC,WAAW,YAAY,UAAW;AACrD,4BAA2B;AACzB,MAAI;GACF,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,WAAW,kBAAkB;AACnC,UAAO,SAAS;GAChB,MAAM,QAAQ,kBAAkB;KAAG,YAAY;KAAE;KAAS;KAAK,WAAW,IAAI,KAAK,IAAI,CAAC,aAAa;KAAE;IAAE,GAAG;IAAU,EAAE,IAAI;GAC5H,MAAM,MAAM,GAAG,mBAAmB,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC;AAC/D,iBAAc,KAAK,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;AAClD,cAAW,KAAK,mBAAmB;UAC7B;GACR;;AAGJ,SAAS,cAAc,WAAyD;AAC9E,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,QAAQ,kBAAkB,kBAAkB,EAAE,KAAK,KAAK,CAAC;AAC/D,QAAO,OAAO,OAAO,OAAO,UAAU,GAAG,MAAM,aAAc;;AAM/D,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,aAAa,OAAwB;CAC5C,MAAM,OAAO,qBAAqB,MAAM;CACxC,MAAM,OAAO,qBAAqB,QAAQ,CAAC,CAAC,QAAQ,OAAO,GAAG;AAC9D,KAAI,KAAK,WAAW,GAAG,KAAK,GAAG,CAAE,QAAO;AACxC,QAAO,qBAAqB,MAAM,WAAW,KAAK,WAAW,OAAO,CAAC;;AAMvE,SAAS,kBAAkB,OAAiC;AAC1D,KAAI,OAAO,UAAU,YAAY,qBAAqB,MAAM,CAAE,QAAO;AACrE,KAAI,cAAc,MAAM,CAAE,QAAO;AAGjC,KAAI,kBAAkB,KAAK,MAAM,CAAE,QAAO;AAI1C,KAAI,MAAM,WAAW,IAAI,CAAE,QAAO,CAAC,aAAa,MAAM;AACtD,QAAO;;AAGT,SAAS,mBAAmB,OAAgB,MAAgB,EAAE,EAAY;AACxE,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,kBAAkB,MAAM,CAAE,KAAI,KAAK,MAAM;AAC7C,SAAO;;AAET,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAK,MAAM,QAAQ,MAAO,oBAAmB,MAAM,IAAI;AACvD,SAAO;;AAET,KAAI,SAAS,OAAO,UAAU,SAC5B,MAAK,MAAM,KAAK,OAAO,OAAO,MAAM,CAAE,oBAAmB,GAAG,IAAI;AAElE,QAAO;;AAGT,SAAS,WAAW,WAA4B;AAC9C,KAAI,WAAW,UAAU,CAAE,QAAO;AAClC,KAAI,QAAQ,aAAa,SAAS;EAChC,MAAM,SAAS,UAAU,QAAQ,OAAO,KAAK;AAC7C,MAAI,WAAW,aAAa,WAAW,OAAO,CAAE,QAAO;;AAEzD,QAAO;;AAGT,MAAM,qBAAqB;AAK3B,SAAS,iBAAiB,WAAkC;CAC1D,IAAI,UAAU;AACd,MAAK,IAAI,OAAO,GAAG,OAAO,oBAAoB,QAAQ;AACpD,MAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,IAAK,QAAO;AAC5D,MAAI,WAAW,QAAQ,EAAE;GACvB,MAAM,WAAW,QAAQ,aAAa,UAAU,QAAQ,QAAQ,OAAO,KAAK,GAAG;AAC/E,OAAI;AACF,QAAI,SAAS,SAAS,CAAC,QAAQ,CAC7B,QAAO,QAAQ,SAAS;WAEpB;AACR,UAAO;;EAQT,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;AAEZ,QAAO;;AAGT,SAAS,gBAAgB,YAA4B;AACnD,QAAO,SAAS,iCAAiC;EAC/C,KAAK;EACL,UAAU;EACV,OAAO;GAAC;GAAU;GAAQ;GAAS;EACnC,SAAS;EACV,CAAC,CAAC,MAAM;;AAOX,SAAS,cAAc,WAAkC;CACvD,IAAI,UAAU;AACd,MAAK,IAAI,OAAO,GAAG,OAAO,oBAAoB,QAAQ;AACpD,MAAI,CAAC,WAAW,YAAY,IAAK,QAAO;AACxC,MAAI,WAAW,KAAK,SAAS,OAAO,CAAC,CAAE,QAAO;EAC9C,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;AAEZ,QAAO;;AAUT,SAAS,gBAAgB,YAAqC;AAC5D,KAAI;AACF,SAAO;GAAE,MAAMA,eAAiB,gBAAgB,WAAW,CAAC;GAAE,aAAa;GAAM;SAC3E;AACN,SAAO;GAAE,MAAMA,eAAiB,WAAW;GAAE,aAAa;GAAO;;;AAerE,SAAS,qBAAqB,MAAwB;AACpD,KAAI,CAAC,QAAQ,SAAS,eAAgB,QAAO,EAAE;AAG/C,KAAI,YAAY,KAAK,KAAK,CAAE,QAAO,EAAE;CAErC,MAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,KAAI,CAAC,MAAM,OAAQ,QAAO,EAAE;CAE5B,MAAM,0BAAU,IAAI,KAAa;CAEjC,SAAS,KAAK,OAAe,aAA2B;AACtD,MAAI,SAAS,MAAM,QAAQ;AACzB,WAAQ,IAAI,YAAY;AACxB;;AAEF,OAAK,IAAI,OAAO,GAAG,QAAQ,QAAQ,MAAM,QAAQ,QAAQ;GACvD,MAAM,OAAO,GAAG,YAAY,GAAG,MAAM,MAAM,OAAO,QAAQ,KAAK,CAAC,KAAK,IAAI;AACzE,OAAI,CAAC,WAAW,KAAK,CAAE;AACvB,QAAK,QAAQ,MAAM,KAAK;;;CAO5B,MAAM,QAAQ,MAAM;AACpB,KAAI,SAAS,aAAa,KAAK,MAAM,CAAE,MAAK,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG;AAGzE,MAAK,GAAG,GAAG;AAEX,QAAO,CAAC,GAAG,QAAQ;;AAGrB,SAAS,kBAAkB,YAAsB,gBAAuC;AACtF,KAAI,CAAC,WAAW,OAAQ,QAAO;AAC/B,KAAI,gBAAgB;EAClB,MAAM,aAAa,WAAW,MAAM,MAAM,SAAS,EAAE,KAAK,eAAe;AACzE,MAAI,WAAY,QAAO;;CAGzB,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,aAAa,WACtB,KAAI;AACF,WAAS,KAAK,gBAAgB,UAAU,CAAC;SACnC;CAEV,MAAM,iBAAiB,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAC7C,KAAI,eAAe,WAAW,EAAG,QAAO,eAAe,MAAM;AAE7D,QAAO,WAAW,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM;;AAG9D,SAAS,sBACP,WACwD;AACxD,KAAI,CAAC,aAAa,CAAC,WAAW,oBAAoB,CAAE,QAAO;AAE3D,MAAK,MAAM,QAAQ,YAAY,oBAAoB,EAAE;EACnD,MAAM,kBAAkB,KAAK,qBAAqB,MAAM,oBAAoB;AAC5E,MAAI,CAAC,WAAW,gBAAgB,CAAE;AAElC,OAAK,MAAM,SAAS,YAAY,gBAAgB,CAC9C,KAAI,UAAU,aAAa,MAAM,WAAW,GAAG,UAAU,GAAG,EAAE;GAC5D,MAAM,iBAAiB,KAAK,iBAAiB,OAAO,GAAG,MAAM,QAAQ;AACrE,UAAO;IACL;IACA,gBAAgB,WAAW,eAAe,GAAG,iBAAiB;IAC/D;;;AAIP,QAAO;;AAGT,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B;AACnC,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAW7B,MAAM,2BAA2B,CAC/B,uCACA,+BACD;AAUD,SAAS,4BAA4B,gBAA8C;AACjF,KAAI,CAAC,kBAAkB,CAAC,WAAW,eAAe,CAAE,QAAO;CAE3D,IAAI;CACJ,MAAM,KAAK,SAAS,gBAAgB,IAAI;AACxC,KAAI;EACF,MAAM,SAAS,OAAO,MAAM,sBAAsB;EAClD,MAAM,QAAQ,SAAS,IAAI,QAAQ,GAAG,OAAO,QAAQ,EAAE;AACvD,UAAQ,qBAAqB,OAAO,SAAS,GAAG,MAAM,CAAC,SAAS,OAAO,CAAC;WAChE;AAAE,YAAU,GAAG;;CACzB,MAAM,yBAAS,IAAI,KAAqB;CACxC,MAAM,uBAAO,IAAI,KAAa;AAI9B,MAAK,MAAM,WAAW,0BAA0B;AAC9C,UAAQ,YAAY;EACpB,IAAI;EACJ,IAAI,UAAU;AACd,UAAQ,QAAQ,QAAQ,KAAK,MAAM,MAAM,MAAM;AAC7C,OAAI,EAAE,UAAU,uBAAwB;AACxC,OAAI,KAAK,QAAQ,2BAA4B;GAC7C,MAAM,QAAQ,MAAM;AACpB,OAAI,KAAK,IAAI,MAAM,CAAE;AACrB,QAAK,IAAI,MAAM;AAEf,OAAI,MAAM,WAAW,KAAK,CAAE;AAC5B,OAAI,qBAAqB,MAAM,IAAI,iBAAiB,MAAM,IAAI,aAAa,MAAM,CAAE;GAYnF,MAAM,WAAW,kBAAkB,MAAM;AACzC,OAAI,CAAC,YAAY,aAAa,QAAQ,UAAU,SAAS,CAAE;GAC3D,MAAM,OAAO,cAAc,SAAS;AACpC,OAAI,CAAC,QAAQ,SAAS,QAAQ,UAAU,KAAK,CAAE;AAC/C,UAAO,IAAI,OAAO,OAAO,IAAI,KAAK,IAAI,KAAK,EAAE;;;CAIjD,IAAI,OAAsB;CAC1B,IAAI,YAAY;AAChB,MAAK,MAAM,CAAC,WAAW,UAAU,OAC/B,KAAI,QAAQ,WAAW;AACrB,SAAO;AACP,cAAY;;AAQhB,QAAO,aAAa,uBAAuB,OAAO;;AAGpD,SAAS,uBAAuB,WAAkC;CAChE,MAAM,MAAM,sBAAsB,UAAU;AAC5C,KAAI,CAAC,IAAK,QAAO;CAMjB,MAAM,iBAAiB,QAAQ,IAAI,6BAA6B;CAChE,MAAM,WAAW,kBAAkB,qBAAqB,IAAI,KAAK,EAAE,eAAe;AAClF,KAAI,SAAU,QAAO;AAQrB,QAAO,4BAA4B,IAAI,eAAe;;AAuExD,SAAS,kBAAkB,SAAmC;AAC5D,QAAO,QAAQ,KAAK,WAAW,IAAI,IAAI,CAAC,QAAQ;;AAGlD,SAAS,gBAAgB,WAA4B;AACnD,QAAO,qBAAqB,UAAU,KAAK,qBAAqB,KAAK;;AASvE,SAAS,kBAAkB,WAAkC;AAC3D,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO;CACnC,MAAM,WAAW,QAAQ,aAAa,UAAU,UAAU,QAAQ,OAAO,KAAK,GAAG;AACjF,KAAI;AACF,SAAO,SAAS,SAAS,CAAC,QAAQ,GAAG,QAAQ,SAAS,GAAG;SACnD;AACN,SAAO;;;AAIX,SAAS,0BACP,YACA,WACA,UAAmD,EAAE,EACnC;AAClB,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,OAAO,cAAc,YAAY,UAAU,UAAU,CAAE;EAC3D,MAAM,WAAW,QAAQ,QAAQ,kBAAkB,UAAU,GAAG,iBAAiB,UAAU;AAC3F,MAAI,CAAC,YAAY,UAAU,SAAS,CAAE;EACtC,MAAM,UAAU,gBAAgB,SAAS;AAOzC,MAAI,gBAAgB,SAAS,IAAI,CAAC,QAAQ,YAAa;AACvD,MAAI,CAAC,kBAAkB,QAAQ,EAAE;AAC/B,OAAI,QAAQ,aAAa,MAAO,iBAAgB,WAAW,QAAQ,MAAM,SAAS;AAClF,UAAO;IAAE,SAAS,QAAQ;IAAM,KAAK;IAAU;;;AAGnD,QAAO;;AAOT,SAAS,WAAW,OAAe,QAA4C;AAC7E,KAAI,UAAU,QAAQ,IAAI,uBAAuB,IAC/C,SAAQ,MAAM,uCAAuC,MAAM,IAAI,OAAO,QAAQ,IAAI,OAAO,IAAI,GAAG;AAElG,QAAO;;AAGT,SAAgB,iBAAiB,MAA2B;CAC1D,MAAM,YAAY;EAAC,OAAO;EAAe,OAAO;EAAc,OAAO;EAAmB,CAAC,MAAM,UAA2B,OAAO,UAAU,YAAY,QAAQ,MAAM,MAAM,CAAC,CAAC;CAG7K,MAAM,SAAS,cAAc,UAAU;AACvC,KAAI,QAAQ,OAAO,CAAC,UAAU,OAAO,IAAI,IAAI,kBAAkB,OAAO,IAAI,CACxE,QAAO;EAAE,SAASA,eAAiB,OAAO,IAAI;EAAE,KAAK,OAAO;EAAK;CAcnE,MAAM,cAAc,WAAW,WAAW,0BAA0B;EAVlE,GAAI,MAAM,QAAQ,OAAO,mBAAmB,GAAI,KAAK,qBAAmC,EAAE;EAC1F,GAAI,MAAM,QAAQ,OAAO,qBAAqB,GAAI,KAAK,uBAAqC,EAAE;EAC9F,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EAG4E,EAAE,UAAU,CAAC;AAClG,KAAI,YAAa,QAAO;CAKxB,MAAM,YAAY,WAAW,cAAc,0BAHzB,mBAAmB,OAAO,iBAAiB,OAAO,YAAY,CAC7E,IAAI,kBAAkB,CACtB,QAAQ,MAAmB,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,CACc,EAAE,UAAU,CAAC;AAC3F,KAAI,UAAW,QAAO;AAEtB,KAAI,WAAW;EAKb,MAAM,SAAS,WACb,aACA,0BAA0B,CAAC,sBAAsB,UAAU,CAAC,EAAE,WAAW,EAAE,OAAO,MAAM,CAAC,CAC1F;AACD,MAAI,OAAQ,QAAO;EAKnB,MAAM,cAAc,WAClB,kBACA,0BAA0B,CAAC,uBAAuB,UAAU,CAAC,EAAE,WAAW,EAAE,OAAO,MAAM,CAAC,CAC3F;AACD,MAAI,YAAa,QAAO;;CAU1B,MAAM,UAAU,WAAW,OAAO,0BAA0B;EAJ1D,QAAQ,IAAI;EACZ,QAAQ,IAAI;EACZ,QAAQ,IAAI;EAE8D,EAAE,UAAU,CAAC;AACzF,KAAI,QAAS,QAAO;CAMpB,MAAM,aAAa,WACjB,cACA,0BAA0B,CAHE,QAAQ,IAAI,QAAQ,QAAQ,IAAI,cAGzB,EAAE,WAAW,EAAE,UAAU,OAAO,CAAC,CACrE;AACD,KAAI,WAAY,QAAO;CAEvB,MAAM,QAAQ,QAAQ,IAAI;AAC1B,KAAI,MAAO,QAAO;EAAE,SAAS;EAAO,KAAK;EAAO;AAEhD,QAAO;EAAE,SAAS;EAAmB,KAAK;EAAmB;;;;ACxrB/D,MAAa,WAAW;CACtB,cAAc;CACd,oBAAoB;CACpB,YAAY;CACZ,aAAa;CACb,oBAAoB;CACpB,YAAY;CACZ,eAAe;CACf,cAAc;CACd,MAAM;CACN,YAAY;CACb;AAID,SAAgB,gBAAgB,OAAwC;AACtE,QAAO,OAAO,UAAU,YAAY,OAAO,OAAO,UAAU,MAAM;;AAKpE,MAAM,aAAa,IAAI,IAAmB,CAAC,QAAQ,aAAa,CAAC;AAYjE,SAAS,qBAA6B;AACpC,QAAO,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,KAAK;;AAG5D,SAAgB,cAAc,MAG5B;CACA,MAAM,EAAE,SAAS,iBAAiB,QAAQ,iBAAiB,KAAK;CAChE,MAAM,UAAU,QAAQ,IAAI,6BAA6B,MAAM,IAAI;AACnE,QAAO;EACL;EACA,SAAS;GACP,GAAG;GACH,YAAY,KAAK,iBAAiB,KAAK,gBAAgB,KAAK;GAC5D;GACA;GACD;EACF;;AAGH,SAAgB,aACd,SACA,MACA,UAA2B,EAAE,EACrB;CACR,MAAM,SAAS,SAAS;CACxB,MAAM,aAAa,KAAK,QAAQ,eAAe,oBAAoB,EAAE,OAAO;AAS5E,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,UAAQ,MACN,8BAA8B,QAAQ,iCAAiC,WAAW,sDAEnF;AACD,SAAO;;CAGT,MAAM,EAAE,SAAS,YAAY,cAAc,KAAK;CAEhD,MAAM,QAAQ,UAAU,QAAQ,UAAU,CAAC,WAAW,EAAE;EACtD,OAAO,KAAK,UAAU,QAAQ;EAG9B,KAAK;GAAE,GAAG,QAAQ;GAAK,0BAA0B;GAAS;EAC1D,UAAU;EACV,WAAW,KAAK,OAAO;EACvB,SAAS,WAAW,IAAI,QAAQ,GAAG,OAAS;EAC7C,CAAC;AAEF,KAAI,MAAM,OAAQ,SAAQ,OAAO,MAAM,MAAM,OAAO;AACpD,KAAI,MAAM,OAAQ,SAAQ,OAAO,MAAM,MAAM,OAAO;AAQpD,KAAI,MAAM,OAAO;AACf,UAAQ,MACN,8BAA8B,QAAQ,kBAAkB,OAAO,IAAI,MAAM,MAAM,UAChF;AACD,SAAO;;AAET,KAAI,MAAM,QAAQ;AAChB,UAAQ,MACN,8BAA8B,QAAQ,KAAK,OAAO,kBAAkB,MAAM,OAAA,uBAE3E;AACD,SAAO;;AAET,QAAO,MAAM,UAAU;;;;AClHzB,eAAe,OAAsB;CACnC,MAAM,UAAU,QAAQ,KAAK;AAC7B,KAAI,CAAC,gBAAgB,QAAQ,CAAE,SAAQ,KAAK,EAAE;CAE9C,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAAO,UAAS;AAClD,KAAI,CAAC,MAAM,MAAM,CAAE,SAAQ,KAAK,EAAE;CAElC,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN,UAAQ,KAAK,EAAE;;AAEjB,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,SAAQ,KAAK,EAAE;AAE7E,SAAQ,KAAK,aAAa,SAAS,KAAK,CAAC;;AAG3C,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
5
|
import { captureObservation, isDrainChild, runDrainChild, withEventId } from "./_capture.mjs";
|
|
5
6
|
//#region src/hooks/_project.ts
|
|
6
7
|
function resolveProject(cwd) {
|
|
@@ -8,7 +9,7 @@ function resolveProject(cwd) {
|
|
|
8
9
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
9
10
|
const dir = cwd && cwd.trim() ? cwd : process.cwd();
|
|
10
11
|
try {
|
|
11
|
-
const top = execSync("git rev-parse --show-toplevel", {
|
|
12
|
+
const [top, gitDir, commonDir] = execSync("git rev-parse --show-toplevel --git-dir --git-common-dir", {
|
|
12
13
|
cwd: dir,
|
|
13
14
|
stdio: [
|
|
14
15
|
"ignore",
|
|
@@ -16,11 +17,18 @@ function resolveProject(cwd) {
|
|
|
16
17
|
"ignore"
|
|
17
18
|
],
|
|
18
19
|
timeout: 500
|
|
19
|
-
}).toString().trim();
|
|
20
|
-
if (top) return basename(top);
|
|
20
|
+
}).toString().trim().split(/\r?\n/);
|
|
21
|
+
if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));
|
|
21
22
|
} catch {}
|
|
22
23
|
return basename(dir);
|
|
23
24
|
}
|
|
25
|
+
function repositoryRoot(dir, top, gitDir, commonDir) {
|
|
26
|
+
if (!gitDir || !commonDir) return top;
|
|
27
|
+
const physical = realpathSync(dir);
|
|
28
|
+
const common = resolve(physical, commonDir);
|
|
29
|
+
if (resolve(physical, gitDir) === common || basename(common) !== ".git") return top;
|
|
30
|
+
return dirname(common);
|
|
31
|
+
}
|
|
24
32
|
function hookCwd(data) {
|
|
25
33
|
if (!data || typeof data !== "object") return void 0;
|
|
26
34
|
if (typeof data.cwd === "string" && data.cwd.trim()) return data.cwd;
|
|
@@ -61,7 +69,8 @@ async function main() {
|
|
|
61
69
|
].find((v) => typeof v === "string" && v.length > 0);
|
|
62
70
|
const sessionId = typeof rawSessionId === "string" ? rawSessionId : "unknown";
|
|
63
71
|
const cwd = hookCwd(data) || process.cwd();
|
|
64
|
-
|
|
72
|
+
const promptId = [data.prompt_id, data.promptId].find((v) => typeof v === "string" && v.length > 0);
|
|
73
|
+
const body = {
|
|
65
74
|
hookType: "notification",
|
|
66
75
|
sessionId,
|
|
67
76
|
project: resolveProject(cwd),
|
|
@@ -72,7 +81,12 @@ async function main() {
|
|
|
72
81
|
title: data.title,
|
|
73
82
|
message: data.message
|
|
74
83
|
}
|
|
75
|
-
}
|
|
84
|
+
};
|
|
85
|
+
captureObservation(withEventId(body, {}, {
|
|
86
|
+
...body.data,
|
|
87
|
+
prompt_id: promptId,
|
|
88
|
+
agent_id: data.agent_id
|
|
89
|
+
}, { stable: promptId !== void 0 }), OBSERVE_TIMEOUT_MS);
|
|
76
90
|
setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();
|
|
77
91
|
}
|
|
78
92
|
main().catch(() => process.exit(0));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/notification.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git
|
|
1
|
+
{"version":3,"file":"notification.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/notification.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git repository basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const [top, gitDir, commonDir] = execSync(\n \"git rev-parse --show-toplevel --git-dir --git-common-dir\",\n {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n },\n )\n .toString()\n .trim()\n .split(/\\r?\\n/);\n if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));\n } catch {}\n return basename(dir);\n}\n\nfunction repositoryRoot(dir: string, top: string, gitDir?: string, commonDir?: string): string {\n if (!gitDir || !commonDir) return top;\n const physical = realpathSync(dir);\n const common = resolve(physical, commonDir);\n if (resolve(physical, gitDir) === common || basename(common) !== \".git\") return top;\n return dirname(common);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveProject, hookCwd } from \"./_project.js\";\nimport { captureObservation, isDrainChild, runDrainChild, withEventId } from \"./_capture.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst OBSERVE_TIMEOUT_MS = 2000;\nconst EXIT_CAP_MS = 2500;\n\nasync function main() {\n if (isDrainChild()) return runDrainChild();\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n const notificationType = data.notification_type ?? data.notificationType;\n if (notificationType !== \"permission_prompt\") return;\n\n const rawSessionId = [data.session_id, data.sessionId, data.conversation_id].find(\n (v) => typeof v === \"string\" && v.length > 0,\n );\n const sessionId = typeof rawSessionId === \"string\" ? rawSessionId : \"unknown\";\n\n const cwd = hookCwd(data) || process.cwd();\n\n const promptId = [data.prompt_id, data.promptId].find((v) => typeof v === \"string\" && v.length > 0);\n const body = {\n hookType: \"notification\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n notification_type: notificationType,\n title: data.title,\n message: data.message,\n },\n };\n void captureObservation(\n withEventId(\n body,\n {},\n { ...body.data, prompt_id: promptId, agent_id: data.agent_id },\n { stable: promptId !== undefined },\n ),\n OBSERVE_TIMEOUT_MS,\n );\n setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;;;;;AAKA,SAAgB,eAAe,KAAsB;CACnD,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO,SAAS,MAAM;CACvD,MAAM,MAAM,OAAO,IAAI,MAAM,GAAG,MAAM,QAAQ,KAAK;AACnD,KAAI;EACF,MAAM,CAAC,KAAK,QAAQ,aAAa,SAC/B,4DACA;GACE,KAAK;GACL,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CACF,CACE,UAAU,CACV,MAAM,CACN,MAAM,QAAQ;AACjB,MAAI,IAAK,QAAO,SAAS,eAAe,KAAK,KAAK,QAAQ,UAAU,CAAC;SAC/D;AACR,QAAO,SAAS,IAAI;;AAGtB,SAAS,eAAe,KAAa,KAAa,QAAiB,WAA4B;AAC7F,KAAI,CAAC,UAAU,CAAC,UAAW,QAAO;CAClC,MAAM,WAAW,aAAa,IAAI;CAClC,MAAM,SAAS,QAAQ,UAAU,UAAU;AAC3C,KAAI,QAAQ,UAAU,OAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAQ,QAAO;AAChF,QAAO,QAAQ,OAAO;;AAGxB,SAAgB,QAAQ,MAAsE;AAC5F,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,KAAA;AAC9C,KAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,MAAM,CAAE,QAAO,KAAK;CACjE,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAM,QAAQ,MAAM;OACjB,MAAM,QAAQ,MACjB,KAAI,OAAO,SAAS,YAAY,KAAK,MAAM,CAAE,QAAO;;CAGxD,MAAM,aACJ,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AAClD,KAAI,cAAc,WAAW,MAAM,CAAE,QAAO;;;;ACzC9C,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AAEpB,eAAe,OAAO;AACpB,KAAI,cAAc,CAAE,QAAO,eAAe;CAC1C,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,KAAI,kBAAkB,KAAK,CAAE;CAC7B,MAAM,mBAAmB,KAAK,qBAAqB,KAAK;AACxD,KAAI,qBAAqB,oBAAqB;CAE9C,MAAM,eAAe;EAAC,KAAK;EAAY,KAAK;EAAW,KAAK;EAAgB,CAAC,MAC1E,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,EAC5C;CACD,MAAM,YAAY,OAAO,iBAAiB,WAAW,eAAe;CAEpE,MAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,KAAK;CAE1C,MAAM,WAAW,CAAC,KAAK,WAAW,KAAK,SAAS,CAAC,MAAM,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,EAAE;CACnG,MAAM,OAAO;EACX,UAAU;EACV;EACA,SAAS,eAAe,IAAI;EAC5B;EACA,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,MAAM;GACJ,mBAAmB;GACnB,OAAO,KAAK;GACZ,SAAS,KAAK;GACf;EACF;AACI,oBACH,YACE,MACA,EAAE,EACF;EAAE,GAAG,KAAK;EAAM,WAAW;EAAU,UAAU,KAAK;EAAU,EAC9D,EAAE,QAAQ,aAAa,KAAA,GAAW,CACnC,EACD,mBACD;AACD,kBAAiB,QAAQ,KAAK,EAAE,EAAE,YAAY,CAAC,OAAO;;AAGxD,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
|
|
@@ -3,12 +3,12 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
//#region src/version.ts
|
|
6
|
-
const VERSION = "0.9.
|
|
6
|
+
const VERSION = "0.9.31";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/secret-store.ts
|
|
9
9
|
const SECRET_KEY = "AGENTMEMORY_SECRET";
|
|
10
10
|
function agentmemoryHomeDir() {
|
|
11
|
-
return join(homedir(), ".agentmemory");
|
|
11
|
+
return process.env["AGENTMEMORY_DATA_DIR"] || join(homedir(), ".agentmemory");
|
|
12
12
|
}
|
|
13
13
|
function secretFilePath() {
|
|
14
14
|
return join(agentmemoryHomeDir(), "secret");
|
|
@@ -32,7 +32,7 @@ function unquote(value) {
|
|
|
32
32
|
function readEnvFileSecret() {
|
|
33
33
|
let content;
|
|
34
34
|
try {
|
|
35
|
-
content = readFileSync(join(
|
|
35
|
+
content = readFileSync(process.env["AGENTMEMORY_ENV_FILE"] || join(homedir(), ".agentmemory", ".env"), "utf-8");
|
|
36
36
|
} catch {
|
|
37
37
|
return "";
|
|
38
38
|
}
|
|
@@ -48,13 +48,29 @@ function readEnvFileSecret() {
|
|
|
48
48
|
}
|
|
49
49
|
return found;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
51
|
+
function readSecretFile(path) {
|
|
52
52
|
try {
|
|
53
|
-
return usable(readFileSync(
|
|
53
|
+
return usable(readFileSync(path, "utf-8"));
|
|
54
54
|
} catch {
|
|
55
55
|
return "";
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
function legacyDataDirSecretPaths(env = process.env) {
|
|
59
|
+
const dataDir = env["AGENTMEMORY_DATA_DIR"];
|
|
60
|
+
if (!dataDir) return [];
|
|
61
|
+
const paths = [
|
|
62
|
+
join(dataDir, "secret"),
|
|
63
|
+
join(dataDir, ".hmac"),
|
|
64
|
+
join(dataDir, "..", ".hmac")
|
|
65
|
+
];
|
|
66
|
+
return [...new Set(paths.filter((path) => path !== secretFilePath()))];
|
|
67
|
+
}
|
|
68
|
+
function readStoredSecret(env = process.env) {
|
|
69
|
+
const canonicalPath = canonicalSecretPath();
|
|
70
|
+
const localPath = secretFilePath();
|
|
71
|
+
const legacyPaths = legacyDataDirSecretPaths(env);
|
|
72
|
+
return readSecretFile(canonicalPath) || (localPath === canonicalPath ? "" : readSecretFile(localPath)) || legacyPaths.map(readSecretFile).find(Boolean) || "";
|
|
73
|
+
}
|
|
58
74
|
function isLoopbackUrl(url) {
|
|
59
75
|
let hostname;
|
|
60
76
|
try {
|
|
@@ -68,8 +84,12 @@ function isLoopbackUrl(url) {
|
|
|
68
84
|
function resolveClientSecret(baseUrl, env = process.env) {
|
|
69
85
|
const fromEnv = usable(env[SECRET_KEY]);
|
|
70
86
|
if (fromEnv) return fromEnv;
|
|
87
|
+
if (env.AGENTMEMORY_CREDENTIAL_MODE === "explicit") return "";
|
|
71
88
|
if (!isLoopbackUrl(baseUrl)) return "";
|
|
72
|
-
return readEnvFileSecret() || readStoredSecret();
|
|
89
|
+
return readEnvFileSecret() || readStoredSecret(env);
|
|
90
|
+
}
|
|
91
|
+
function canonicalSecretPath() {
|
|
92
|
+
return join(homedir(), ".agentmemory", "secret");
|
|
73
93
|
}
|
|
74
94
|
//#endregion
|
|
75
95
|
//#region src/mcp/rest-proxy.ts
|
|
@@ -81,6 +101,7 @@ function resolveEnvOrEmpty(name) {
|
|
|
81
101
|
}
|
|
82
102
|
//#endregion
|
|
83
103
|
//#region src/mcp/transport.ts
|
|
104
|
+
let activeStdioTransports = 0;
|
|
84
105
|
var JsonRpcError = class extends Error {
|
|
85
106
|
constructor(code, message) {
|
|
86
107
|
super(message);
|
|
@@ -233,6 +254,7 @@ function createMessageParser(onMessage, writeErr = (msg) => process.stderr.write
|
|
|
233
254
|
function createStdioTransport(handler) {
|
|
234
255
|
let parser = null;
|
|
235
256
|
let queue = Promise.resolve();
|
|
257
|
+
let active = false;
|
|
236
258
|
const writeResponse = (response) => {
|
|
237
259
|
const formatted = formatResponse(response, parser?.isFramed() ?? false);
|
|
238
260
|
if (typeof formatted === "string") {
|
|
@@ -244,6 +266,10 @@ function createStdioTransport(handler) {
|
|
|
244
266
|
const onData = (chunk) => parser?.push(chunk);
|
|
245
267
|
return {
|
|
246
268
|
start() {
|
|
269
|
+
if (active) return;
|
|
270
|
+
active = true;
|
|
271
|
+
activeStdioTransports += 1;
|
|
272
|
+
process.env["AGENTMEMORY_MCP_STDIO__"] = "1";
|
|
247
273
|
parser = createMessageParser((message) => {
|
|
248
274
|
queue = queue.then(() => processLine(message, handler, writeResponse));
|
|
249
275
|
queue.catch((err) => {
|
|
@@ -254,6 +280,11 @@ function createStdioTransport(handler) {
|
|
|
254
280
|
},
|
|
255
281
|
stop() {
|
|
256
282
|
process.stdin.off("data", onData);
|
|
283
|
+
if (active) {
|
|
284
|
+
active = false;
|
|
285
|
+
activeStdioTransports -= 1;
|
|
286
|
+
if (activeStdioTransports === 0) delete process.env["AGENTMEMORY_MCP_STDIO__"];
|
|
287
|
+
}
|
|
257
288
|
parser = null;
|
|
258
289
|
}
|
|
259
290
|
};
|
|
@@ -7,7 +7,7 @@ import { promisify } from "node:util";
|
|
|
7
7
|
//#region src/secret-store.ts
|
|
8
8
|
const SECRET_KEY = "AGENTMEMORY_SECRET";
|
|
9
9
|
function agentmemoryHomeDir() {
|
|
10
|
-
return join(homedir(), ".agentmemory");
|
|
10
|
+
return process.env["AGENTMEMORY_DATA_DIR"] || join(homedir(), ".agentmemory");
|
|
11
11
|
}
|
|
12
12
|
function secretFilePath() {
|
|
13
13
|
return join(agentmemoryHomeDir(), "secret");
|
|
@@ -31,7 +31,7 @@ function unquote(value) {
|
|
|
31
31
|
function readEnvFileSecret() {
|
|
32
32
|
let content;
|
|
33
33
|
try {
|
|
34
|
-
content = readFileSync(join(
|
|
34
|
+
content = readFileSync(process.env["AGENTMEMORY_ENV_FILE"] || join(homedir(), ".agentmemory", ".env"), "utf-8");
|
|
35
35
|
} catch {
|
|
36
36
|
return "";
|
|
37
37
|
}
|
|
@@ -47,13 +47,29 @@ function readEnvFileSecret() {
|
|
|
47
47
|
}
|
|
48
48
|
return found;
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function readSecretFile(path) {
|
|
51
51
|
try {
|
|
52
|
-
return usable(readFileSync(
|
|
52
|
+
return usable(readFileSync(path, "utf-8"));
|
|
53
53
|
} catch {
|
|
54
54
|
return "";
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function legacyDataDirSecretPaths(env = process.env) {
|
|
58
|
+
const dataDir = env["AGENTMEMORY_DATA_DIR"];
|
|
59
|
+
if (!dataDir) return [];
|
|
60
|
+
const paths = [
|
|
61
|
+
join(dataDir, "secret"),
|
|
62
|
+
join(dataDir, ".hmac"),
|
|
63
|
+
join(dataDir, "..", ".hmac")
|
|
64
|
+
];
|
|
65
|
+
return [...new Set(paths.filter((path) => path !== secretFilePath()))];
|
|
66
|
+
}
|
|
67
|
+
function readStoredSecret(env = process.env) {
|
|
68
|
+
const canonicalPath = canonicalSecretPath();
|
|
69
|
+
const localPath = secretFilePath();
|
|
70
|
+
const legacyPaths = legacyDataDirSecretPaths(env);
|
|
71
|
+
return readSecretFile(canonicalPath) || (localPath === canonicalPath ? "" : readSecretFile(localPath)) || legacyPaths.map(readSecretFile).find(Boolean) || "";
|
|
72
|
+
}
|
|
57
73
|
function isLoopbackUrl(url) {
|
|
58
74
|
let hostname;
|
|
59
75
|
try {
|
|
@@ -67,8 +83,12 @@ function isLoopbackUrl(url) {
|
|
|
67
83
|
function resolveClientSecret(baseUrl, env = process.env) {
|
|
68
84
|
const fromEnv = usable(env[SECRET_KEY]);
|
|
69
85
|
if (fromEnv) return fromEnv;
|
|
86
|
+
if (env.AGENTMEMORY_CREDENTIAL_MODE === "explicit") return "";
|
|
70
87
|
if (!isLoopbackUrl(baseUrl)) return "";
|
|
71
|
-
return readEnvFileSecret() || readStoredSecret();
|
|
88
|
+
return readEnvFileSecret() || readStoredSecret(env);
|
|
89
|
+
}
|
|
90
|
+
function canonicalSecretPath() {
|
|
91
|
+
return join(homedir(), ".agentmemory", "secret");
|
|
72
92
|
}
|
|
73
93
|
//#endregion
|
|
74
94
|
//#region src/hooks/_project.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/hooks/_project.ts","../../src/hooks/post-commit.ts"],"sourcesContent":["import {\n chmodSync,\n closeSync,\n linkSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst SECRET_KEY = \"AGENTMEMORY_SECRET\";\n\nexport function agentmemoryHomeDir(): string {\n return join(homedir(), \".agentmemory\");\n}\n\nexport function secretFilePath(): string {\n return join(agentmemoryHomeDir(), \"secret\");\n}\n\nfunction usable(value: string | undefined): string {\n if (typeof value !== \"string\") return \"\";\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed.startsWith(\"${\") && trimmed.endsWith(\"}\")) return \"\";\n return trimmed;\n}\n\nfunction unquote(value: string): string {\n const quote = value[0];\n if ((quote === '\"' || quote === \"'\") && value.length > 1) {\n const close = value.indexOf(quote, 1);\n if (close !== -1) return value.slice(1, close);\n }\n const hash = value.indexOf(\" #\");\n return hash === -1 ? value : value.slice(0, hash).trim();\n}\n\nexport function readEnvFileSecret(): string {\n let content: unknown;\n try {\n content = readFileSync(join(agentmemoryHomeDir(), \".env\"), \"utf-8\");\n } catch {\n return \"\";\n }\n if (typeof content !== \"string\") return \"\";\n let found = \"\";\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).replace(/^export\\s+/, \"\").trim();\n if (key !== SECRET_KEY) continue;\n found = usable(unquote(trimmed.slice(eq + 1).trim()));\n }\n return found;\n}\n\nexport function readStoredSecret(): string {\n try {\n return usable(readFileSync(secretFilePath(), \"utf-8\"));\n } catch {\n return \"\";\n }\n}\n\nexport function explicitSecret(env: NodeJS.ProcessEnv = process.env): string {\n return usable(env[SECRET_KEY]) || readEnvFileSecret();\n}\n\nexport function isLoopbackUrl(url: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return false;\n }\n const bare = hostname.replace(/^\\[|\\]$/g, \"\");\n return bare === \"localhost\" || bare === \"::1\" || /^127(?:\\.\\d{1,3}){3}$/.test(bare);\n}\n\nexport function resolveClientSecret(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const fromEnv = usable(env[SECRET_KEY]);\n if (fromEnv) return fromEnv;\n if (!isLoopbackUrl(baseUrl)) return \"\";\n return readEnvFileSecret() || readStoredSecret();\n}\n\nexport function ensureServerSecret(env: NodeJS.ProcessEnv = process.env): {\n secret: string;\n source: \"env\" | \"file\" | \"generated\";\n} {\n const configured = explicitSecret(env);\n if (configured) return { secret: configured, source: \"env\" };\n const stored = readStoredSecret();\n if (stored) return { secret: stored, source: \"file\" };\n const generated = randomBytes(32).toString(\"hex\");\n const path = secretFilePath();\n mkdirSync(agentmemoryHomeDir(), { recursive: true, mode: 0o700 });\n const staging = `${path}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n const fd = openSync(staging, \"wx\", 0o600);\n try {\n writeSync(fd, `${generated}\\n`);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(staging, 0o600);\n linkSync(staging, path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const raced = readStoredSecret();\n if (!raced) throw err;\n return { secret: raced, source: \"file\" };\n } finally {\n try {\n unlinkSync(staging);\n } catch {}\n }\n return { secret: generated, source: \"generated\" };\n}\n\nexport function bearerHeaders(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const secret = resolveClientSecret(baseUrl, env);\n return secret ? { Authorization: `Bearer ${secret}` } : {};\n}\n","import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveClientSecret } from \"../secret-store.js\";\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { hookCwd } from \"./_project.js\";\n\nconst exec = promisify(execFile);\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = resolveClientSecret(REST_URL);\nconst TIMEOUT_MS = 1500;\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown> = {};\n if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (!data || typeof data !== \"object\") data = {};\n if (isSdkChildContext(data)) return;\n\n const cwd =\n hookCwd(data) || process.env[\"AGENTMEMORY_CWD\"] || process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/hooks/_project.ts","../../src/hooks/post-commit.ts"],"sourcesContent":["import {\n chmodSync,\n closeSync,\n linkSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst SECRET_KEY = \"AGENTMEMORY_SECRET\";\n\nexport function agentmemoryHomeDir(): string {\n return process.env[\"AGENTMEMORY_DATA_DIR\"] || join(homedir(), \".agentmemory\");\n}\n\nexport function secretFilePath(): string {\n return join(agentmemoryHomeDir(), \"secret\");\n}\n\nfunction usable(value: string | undefined): string {\n if (typeof value !== \"string\") return \"\";\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed.startsWith(\"${\") && trimmed.endsWith(\"}\")) return \"\";\n return trimmed;\n}\n\nfunction unquote(value: string): string {\n const quote = value[0];\n if ((quote === '\"' || quote === \"'\") && value.length > 1) {\n const close = value.indexOf(quote, 1);\n if (close !== -1) return value.slice(1, close);\n }\n const hash = value.indexOf(\" #\");\n return hash === -1 ? value : value.slice(0, hash).trim();\n}\n\nexport function readEnvFileSecret(): string {\n let content: unknown;\n try {\n content = readFileSync(process.env[\"AGENTMEMORY_ENV_FILE\"] || join(homedir(), \".agentmemory\", \".env\"), \"utf-8\");\n } catch {\n return \"\";\n }\n if (typeof content !== \"string\") return \"\";\n let found = \"\";\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).replace(/^export\\s+/, \"\").trim();\n if (key !== SECRET_KEY) continue;\n found = usable(unquote(trimmed.slice(eq + 1).trim()));\n }\n return found;\n}\n\nfunction readSecretFile(path: string): string {\n try {\n return usable(readFileSync(path, \"utf-8\"));\n } catch {\n return \"\";\n }\n}\n\nfunction legacyDataDirSecretPaths(env: NodeJS.ProcessEnv = process.env): string[] {\n const dataDir = env[\"AGENTMEMORY_DATA_DIR\"];\n if (!dataDir) return [];\n const paths = [join(dataDir, \"secret\"), join(dataDir, \".hmac\"), join(dataDir, \"..\", \".hmac\")];\n return [...new Set(paths.filter((path) => path !== secretFilePath()))];\n}\n\nexport function readStoredSecret(env: NodeJS.ProcessEnv = process.env): string {\n const canonicalPath = canonicalSecretPath();\n const localPath = secretFilePath();\n const legacyPaths = legacyDataDirSecretPaths(env);\n return readSecretFile(canonicalPath) ||\n (localPath === canonicalPath ? \"\" : readSecretFile(localPath)) ||\n legacyPaths.map(readSecretFile).find(Boolean) || \"\";\n}\n\nexport function explicitSecret(env: NodeJS.ProcessEnv = process.env): string {\n return usable(env[SECRET_KEY]) || readEnvFileSecret();\n}\n\nexport function isLoopbackUrl(url: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return false;\n }\n const bare = hostname.replace(/^\\[|\\]$/g, \"\");\n return bare === \"localhost\" || bare === \"::1\" || /^127(?:\\.\\d{1,3}){3}$/.test(bare);\n}\n\nexport function resolveClientSecret(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const fromEnv = usable(env[SECRET_KEY]);\n if (fromEnv) return fromEnv;\n if (env.AGENTMEMORY_CREDENTIAL_MODE === \"explicit\") return \"\";\n if (!isLoopbackUrl(baseUrl)) return \"\";\n return readEnvFileSecret() || readStoredSecret(env);\n}\n\nfunction canonicalSecretPath(): string {\n return join(homedir(), \".agentmemory\", \"secret\");\n}\n\nfunction writeSecretFile(path: string, secret: string): void {\n mkdirSync(join(path, \"..\"), { recursive: true, mode: 0o700 });\n const staging = `${path}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n const fd = openSync(staging, \"wx\", 0o600);\n try {\n writeSync(fd, `${secret}\\n`);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(staging, 0o600);\n renameSync(staging, path);\n chmodSync(path, 0o600);\n } finally {\n try { unlinkSync(staging); } catch {}\n }\n}\n\nfunction writeSecretIfAbsent(path: string, secret: string): string {\n mkdirSync(join(path, \"..\"), { recursive: true, mode: 0o700 });\n const staging = `${path}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n const fd = openSync(staging, \"wx\", 0o600);\n try {\n writeSync(fd, `${secret}\\n`);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(staging, 0o600);\n linkSync(staging, path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n } finally {\n try { unlinkSync(staging); } catch {}\n }\n const stored = readSecretFile(path);\n if (!stored) throw new Error(`Could not read stored API secret at ${path}.`);\n return stored;\n}\n\nexport function ensureServerSecret(env: NodeJS.ProcessEnv = process.env): {\n secret: string;\n source: \"env\" | \"file\" | \"generated\";\n} {\n const configured = explicitSecret(env);\n if (configured) return { secret: configured, source: \"env\" };\n\n const localPath = secretFilePath();\n const canonicalPath = canonicalSecretPath();\n const canonical = localPath === canonicalPath ? \"\" : readSecretFile(canonicalPath);\n if (canonical) {\n if (localPath !== canonicalPath && readSecretFile(localPath) !== canonical) {\n writeSecretFile(localPath, canonical);\n }\n return { secret: canonical, source: \"file\" };\n }\n\n const legacy = readSecretFile(localPath) || legacyDataDirSecretPaths(env).map(readSecretFile).find(Boolean) || \"\";\n const candidate = legacy || randomBytes(32).toString(\"hex\");\n const localSecret = writeSecretIfAbsent(localPath, candidate);\n const secret = localPath === canonicalPath\n ? localSecret\n : writeSecretIfAbsent(canonicalPath, localSecret);\n if (localPath !== canonicalPath && localSecret !== secret) writeSecretFile(localPath, secret);\n return { secret, source: legacy ? \"file\" : \"generated\" };\n}\n\nexport function bearerHeaders(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const secret = resolveClientSecret(baseUrl, env);\n return secret ? { Authorization: `Bearer ${secret}` } : {};\n}\n","import { execSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git repository basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const [top, gitDir, commonDir] = execSync(\n \"git rev-parse --show-toplevel --git-dir --git-common-dir\",\n {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n },\n )\n .toString()\n .trim()\n .split(/\\r?\\n/);\n if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));\n } catch {}\n return basename(dir);\n}\n\nfunction repositoryRoot(dir: string, top: string, gitDir?: string, commonDir?: string): string {\n if (!gitDir || !commonDir) return top;\n const physical = realpathSync(dir);\n const common = resolve(physical, commonDir);\n if (resolve(physical, gitDir) === common || basename(common) !== \".git\") return top;\n return dirname(common);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveClientSecret } from \"../secret-store.js\";\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { hookCwd } from \"./_project.js\";\n\nconst exec = promisify(execFile);\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = resolveClientSecret(REST_URL);\nconst TIMEOUT_MS = 1500;\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown> = {};\n if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (!data || typeof data !== \"object\") data = {};\n if (isSdkChildContext(data)) return;\n\n const cwd =\n hookCwd(data) || process.env[\"AGENTMEMORY_CWD\"] || process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
5
|
import { captureObservation, isDrainChild, runDrainChild, withEventId } from "./_capture.mjs";
|
|
5
6
|
//#region src/hooks/_capture-filter.ts
|
|
6
7
|
const DEFAULT_DENY_PATTERNS = [
|
|
@@ -49,7 +50,7 @@ function resolveProject(cwd) {
|
|
|
49
50
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
50
51
|
const dir = cwd && cwd.trim() ? cwd : process.cwd();
|
|
51
52
|
try {
|
|
52
|
-
const top = execSync("git rev-parse --show-toplevel", {
|
|
53
|
+
const [top, gitDir, commonDir] = execSync("git rev-parse --show-toplevel --git-dir --git-common-dir", {
|
|
53
54
|
cwd: dir,
|
|
54
55
|
stdio: [
|
|
55
56
|
"ignore",
|
|
@@ -57,11 +58,18 @@ function resolveProject(cwd) {
|
|
|
57
58
|
"ignore"
|
|
58
59
|
],
|
|
59
60
|
timeout: 500
|
|
60
|
-
}).toString().trim();
|
|
61
|
-
if (top) return basename(top);
|
|
61
|
+
}).toString().trim().split(/\r?\n/);
|
|
62
|
+
if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));
|
|
62
63
|
} catch {}
|
|
63
64
|
return basename(dir);
|
|
64
65
|
}
|
|
66
|
+
function repositoryRoot(dir, top, gitDir, commonDir) {
|
|
67
|
+
if (!gitDir || !commonDir) return top;
|
|
68
|
+
const physical = realpathSync(dir);
|
|
69
|
+
const common = resolve(physical, commonDir);
|
|
70
|
+
if (resolve(physical, gitDir) === common || basename(common) !== ".git") return top;
|
|
71
|
+
return dirname(common);
|
|
72
|
+
}
|
|
65
73
|
function hookCwd(data) {
|
|
66
74
|
if (!data || typeof data !== "object") return void 0;
|
|
67
75
|
if (typeof data.cwd === "string" && data.cwd.trim()) return data.cwd;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/_capture-filter.ts","../../src/hooks/_project.ts","../../src/hooks/post-tool-failure.ts"],"sourcesContent":["const DEFAULT_DENY_PATTERNS = [\n \"memory_*\",\n \"toolsearch\",\n \"listmcpresources\",\n \"fetchmcpresource\",\n];\n\nfunction parseEnvList(raw: string | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n return raw\n .split(/[,\\s]+/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nexport function bareToolName(toolName: string): string {\n const trimmed = toolName.trim();\n if (/^mcp__/i.test(trimmed)) {\n const parts = trimmed.split(\"__\");\n if (parts.length >= 3) return parts[parts.length - 1]!;\n }\n return trimmed;\n}\n\nfunction normalizePattern(pattern: string): string {\n return pattern.trim().toLowerCase();\n}\n\nfunction matchesPattern(toolName: string, pattern: string): boolean {\n const bare = bareToolName(toolName).toLowerCase();\n const full = toolName.trim().toLowerCase();\n const pat = normalizePattern(pattern);\n if (!pat.includes(\"*\")) return bare === pat || full === pat;\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`^${escaped.replace(/\\*/g, \".*\")}$`);\n return re.test(bare) || re.test(full);\n}\n\nfunction matchesAny(toolName: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(toolName, pattern));\n}\n\nexport function shouldCaptureTool(toolName: unknown): boolean {\n if (typeof toolName !== \"string\" || !toolName.trim()) return true;\n\n const allow = parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_ALLOW\"]);\n if (allow) return matchesAny(toolName, allow);\n\n const deny = [\n ...DEFAULT_DENY_PATTERNS,\n ...(parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_DENY\"]) ?? []),\n ];\n return !matchesAny(toolName, deny);\n}\n\nexport function captureOutputMax(): number {\n const raw = process.env[\"AGENTMEMORY_CAPTURE_OUTPUT_MAX\"];\n if (!raw?.trim()) return 8000;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;\n}\n\nexport function truncateCaptureOutput(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n const suffix = \"\\n[...truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : value.slice(0, max - suffix.length) + suffix;\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) {\n const suffix = \"...[truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : str.slice(0, max - suffix.length) + suffix;\n }\n return value;\n }\n return value;\n}\n\nexport function preCompactBudget(): number {\n const raw = process.env[\"AGENTMEMORY_PRE_COMPACT_BUDGET\"];\n if (raw?.trim() === \"0\") return 0;\n if (!raw?.trim()) return 1500;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;\n}\n","import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { shouldCaptureTool } from \"./_capture-filter.js\";\nimport { resolveProject, hookCwd } from \"./_project.js\";\nimport { captureObservation, isDrainChild, runDrainChild, withEventId } from \"./_capture.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst OBSERVE_TIMEOUT_MS = 3000;\nconst EXIT_CAP_MS = 3500;\n\nasync function main() {\n if (isDrainChild()) return runDrainChild();\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n if (data.is_interrupt || data.isInterrupt) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n if (!shouldCaptureTool(toolName)) return;\n\n const toolInput = data.tool_input ?? data.toolArgs;\n const error = data.error ?? data.errorMessage;\n\n const cwd = hookCwd(data) || process.cwd();\n\n void captureObservation(\n withEventId(\n {\n hookType: \"post_tool_failure\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input:\n typeof toolInput === \"string\"\n ? toolInput.slice(0, 4000)\n : JSON.stringify(toolInput ?? \"\").slice(0, 4000),\n error:\n typeof error === \"string\"\n ? error.slice(0, 4000)\n : JSON.stringify(error ?? \"\").slice(0, 4000),\n },\n },\n data,\n ),\n OBSERVE_TIMEOUT_MS,\n );\n setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/_capture-filter.ts","../../src/hooks/_project.ts","../../src/hooks/post-tool-failure.ts"],"sourcesContent":["const DEFAULT_DENY_PATTERNS = [\n \"memory_*\",\n \"toolsearch\",\n \"listmcpresources\",\n \"fetchmcpresource\",\n];\n\nfunction parseEnvList(raw: string | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n return raw\n .split(/[,\\s]+/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nexport function bareToolName(toolName: string): string {\n const trimmed = toolName.trim();\n if (/^mcp__/i.test(trimmed)) {\n const parts = trimmed.split(\"__\");\n if (parts.length >= 3) return parts[parts.length - 1]!;\n }\n return trimmed;\n}\n\nfunction normalizePattern(pattern: string): string {\n return pattern.trim().toLowerCase();\n}\n\nfunction matchesPattern(toolName: string, pattern: string): boolean {\n const bare = bareToolName(toolName).toLowerCase();\n const full = toolName.trim().toLowerCase();\n const pat = normalizePattern(pattern);\n if (!pat.includes(\"*\")) return bare === pat || full === pat;\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`^${escaped.replace(/\\*/g, \".*\")}$`);\n return re.test(bare) || re.test(full);\n}\n\nfunction matchesAny(toolName: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(toolName, pattern));\n}\n\nexport function shouldCaptureTool(toolName: unknown): boolean {\n if (typeof toolName !== \"string\" || !toolName.trim()) return true;\n\n const allow = parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_ALLOW\"]);\n if (allow) return matchesAny(toolName, allow);\n\n const deny = [\n ...DEFAULT_DENY_PATTERNS,\n ...(parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_DENY\"]) ?? []),\n ];\n return !matchesAny(toolName, deny);\n}\n\nexport function captureOutputMax(): number {\n const raw = process.env[\"AGENTMEMORY_CAPTURE_OUTPUT_MAX\"];\n if (!raw?.trim()) return 8000;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;\n}\n\nexport function truncateCaptureOutput(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n const suffix = \"\\n[...truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : value.slice(0, max - suffix.length) + suffix;\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) {\n const suffix = \"...[truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : str.slice(0, max - suffix.length) + suffix;\n }\n return value;\n }\n return value;\n}\n\nexport function preCompactBudget(): number {\n const raw = process.env[\"AGENTMEMORY_PRE_COMPACT_BUDGET\"];\n if (raw?.trim() === \"0\") return 0;\n if (!raw?.trim()) return 1500;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;\n}\n","import { execSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git repository basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const [top, gitDir, commonDir] = execSync(\n \"git rev-parse --show-toplevel --git-dir --git-common-dir\",\n {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n },\n )\n .toString()\n .trim()\n .split(/\\r?\\n/);\n if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));\n } catch {}\n return basename(dir);\n}\n\nfunction repositoryRoot(dir: string, top: string, gitDir?: string, commonDir?: string): string {\n if (!gitDir || !commonDir) return top;\n const physical = realpathSync(dir);\n const common = resolve(physical, commonDir);\n if (resolve(physical, gitDir) === common || basename(common) !== \".git\") return top;\n return dirname(common);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { shouldCaptureTool } from \"./_capture-filter.js\";\nimport { resolveProject, hookCwd } from \"./_project.js\";\nimport { captureObservation, isDrainChild, runDrainChild, withEventId } from \"./_capture.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst OBSERVE_TIMEOUT_MS = 3000;\nconst EXIT_CAP_MS = 3500;\n\nasync function main() {\n if (isDrainChild()) return runDrainChild();\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n if (data.is_interrupt || data.isInterrupt) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n if (!shouldCaptureTool(toolName)) return;\n\n const toolInput = data.tool_input ?? data.toolArgs;\n const error = data.error ?? data.errorMessage;\n\n const cwd = hookCwd(data) || process.cwd();\n\n void captureObservation(\n withEventId(\n {\n hookType: \"post_tool_failure\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input:\n typeof toolInput === \"string\"\n ? toolInput.slice(0, 4000)\n : JSON.stringify(toolInput ?? \"\").slice(0, 4000),\n error:\n typeof error === \"string\"\n ? error.slice(0, 4000)\n : JSON.stringify(error ?? \"\").slice(0, 4000),\n },\n },\n data,\n ),\n OBSERVE_TIMEOUT_MS,\n );\n setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
5
|
import { captureObservation, isDrainChild, runDrainChild, withEventId } from "./_capture.mjs";
|
|
5
6
|
//#region src/hooks/_capture-filter.ts
|
|
6
7
|
const DEFAULT_DENY_PATTERNS = [
|
|
@@ -70,7 +71,7 @@ function resolveProject(cwd) {
|
|
|
70
71
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
71
72
|
const dir = cwd && cwd.trim() ? cwd : process.cwd();
|
|
72
73
|
try {
|
|
73
|
-
const top = execSync("git rev-parse --show-toplevel", {
|
|
74
|
+
const [top, gitDir, commonDir] = execSync("git rev-parse --show-toplevel --git-dir --git-common-dir", {
|
|
74
75
|
cwd: dir,
|
|
75
76
|
stdio: [
|
|
76
77
|
"ignore",
|
|
@@ -78,11 +79,18 @@ function resolveProject(cwd) {
|
|
|
78
79
|
"ignore"
|
|
79
80
|
],
|
|
80
81
|
timeout: 500
|
|
81
|
-
}).toString().trim();
|
|
82
|
-
if (top) return basename(top);
|
|
82
|
+
}).toString().trim().split(/\r?\n/);
|
|
83
|
+
if (top) return basename(repositoryRoot(dir, top, gitDir, commonDir));
|
|
83
84
|
} catch {}
|
|
84
85
|
return basename(dir);
|
|
85
86
|
}
|
|
87
|
+
function repositoryRoot(dir, top, gitDir, commonDir) {
|
|
88
|
+
if (!gitDir || !commonDir) return top;
|
|
89
|
+
const physical = realpathSync(dir);
|
|
90
|
+
const common = resolve(physical, commonDir);
|
|
91
|
+
if (resolve(physical, gitDir) === common || basename(common) !== ".git") return top;
|
|
92
|
+
return dirname(common);
|
|
93
|
+
}
|
|
86
94
|
function hookCwd(data) {
|
|
87
95
|
if (!data || typeof data !== "object") return void 0;
|
|
88
96
|
if (typeof data.cwd === "string" && data.cwd.trim()) return data.cwd;
|