memor-cli 0.6.1__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {memor_cli-0.6.1/memor_cli.egg-info → memor_cli-0.7.0}/PKG-INFO +33 -16
  2. {memor_cli-0.6.1 → memor_cli-0.7.0}/README.md +32 -15
  3. memor_cli-0.7.0/memor/__init__.py +1 -0
  4. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/cli.py +101 -10
  5. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/dashboard/server.py +5 -0
  6. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/dashboard/static/index.html +50 -2
  7. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/hook_cli.py +3 -7
  8. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/hook_server.py +29 -20
  9. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/store/sqlite_store.py +28 -4
  10. {memor_cli-0.6.1 → memor_cli-0.7.0/memor_cli.egg-info}/PKG-INFO +33 -16
  11. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor_cli.egg-info/SOURCES.txt +1 -0
  12. {memor_cli-0.6.1 → memor_cli-0.7.0}/pyproject.toml +1 -1
  13. memor_cli-0.7.0/tests/test_multi_agent_hook.py +199 -0
  14. memor_cli-0.6.1/memor/__init__.py +0 -1
  15. {memor_cli-0.6.1 → memor_cli-0.7.0}/LICENSE +0 -0
  16. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/daemon.py +0 -0
  17. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/dashboard/__init__.py +0 -0
  18. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/distill/__init__.py +0 -0
  19. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/distill/distiller.py +0 -0
  20. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/distill/extractive.py +0 -0
  21. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/embed/__init__.py +0 -0
  22. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/embed/api.py +0 -0
  23. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/embed/fake.py +0 -0
  24. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/embed/local.py +0 -0
  25. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/__init__.py +0 -0
  26. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/baselines/__init__.py +0 -0
  27. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/baselines/base.py +0 -0
  28. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/baselines/claude_mem.py +0 -0
  29. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/baselines/graphiti.py +0 -0
  30. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/counterfactual.py +0 -0
  31. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/dataset.py +0 -0
  32. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/embed_benchmark.py +0 -0
  33. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/judge.py +0 -0
  34. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/metrics.py +0 -0
  35. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/eval/runner.py +0 -0
  36. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/feedback.py +0 -0
  37. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/global_memories.py +0 -0
  38. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/ingest/__init__.py +0 -0
  39. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/ingest/claude_code.py +0 -0
  40. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/ingest/documents.py +0 -0
  41. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/interfaces.py +0 -0
  42. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/llm/__init__.py +0 -0
  43. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/llm/anthropic.py +0 -0
  44. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/llm/base.py +0 -0
  45. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/llm/openai_compat.py +0 -0
  46. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/project.py +0 -0
  47. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/query_complexity.py +0 -0
  48. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/recall.py +0 -0
  49. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/redact.py +0 -0
  50. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/retrieve/__init__.py +0 -0
  51. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/retrieve/retriever.py +0 -0
  52. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/service.py +0 -0
  53. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/session_context.py +0 -0
  54. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/store/__init__.py +0 -0
  55. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/tokencount.py +0 -0
  56. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/turn_metrics.py +0 -0
  57. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor/types.py +0 -0
  58. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor_cli.egg-info/dependency_links.txt +0 -0
  59. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor_cli.egg-info/entry_points.txt +0 -0
  60. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor_cli.egg-info/requires.txt +0 -0
  61. {memor_cli-0.6.1 → memor_cli-0.7.0}/memor_cli.egg-info/top_level.txt +0 -0
  62. {memor_cli-0.6.1 → memor_cli-0.7.0}/setup.cfg +0 -0
  63. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_cli_smoke.py +0 -0
  64. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_counterfactual.py +0 -0
  65. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_daemon.py +0 -0
  66. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_dashboard.py +0 -0
  67. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_dashboard_quality.py +0 -0
  68. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_dataset_builder.py +0 -0
  69. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_dimension_safety.py +0 -0
  70. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_distiller.py +0 -0
  71. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_embed.py +0 -0
  72. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_embed_benchmark.py +0 -0
  73. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_eval_ablation.py +0 -0
  74. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_eval_runner.py +0 -0
  75. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_external_baselines.py +0 -0
  76. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_extractive.py +0 -0
  77. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_feedback.py +0 -0
  78. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_global_memories.py +0 -0
  79. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_hook.py +0 -0
  80. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_hook_server.py +0 -0
  81. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_hybrid_retrieval.py +0 -0
  82. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_ingest_claude_code.py +0 -0
  83. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_ingest_documents.py +0 -0
  84. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_install_hook.py +0 -0
  85. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_interfaces.py +0 -0
  86. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_judge.py +0 -0
  87. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_metrics.py +0 -0
  88. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_negative_feedback.py +0 -0
  89. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_noise_filter.py +0 -0
  90. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_project_resolver.py +0 -0
  91. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_quality_gate.py +0 -0
  92. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_query_complexity.py +0 -0
  93. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_recall_core.py +0 -0
  94. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_redact.py +0 -0
  95. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_retriever.py +0 -0
  96. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_roi_trend.py +0 -0
  97. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_semantic_feedback.py +0 -0
  98. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_service.py +0 -0
  99. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_session_context.py +0 -0
  100. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_skill_recall.py +0 -0
  101. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_store.py +0 -0
  102. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_supersession.py +0 -0
  103. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_tokencount.py +0 -0
  104. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_turn_metrics.py +0 -0
  105. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_types.py +0 -0
  106. {memor_cli-0.6.1 → memor_cli-0.7.0}/tests/test_vec_compaction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memor-cli
3
- Version: 0.6.1
3
+ Version: 0.7.0
4
4
  Summary: Measured memory for coding agents. Fire and forget — no API keys needed.
5
5
  Author-email: Nimit Bhandari <nimitbhandari17@gmail.com>
6
6
  License-Expression: MIT
@@ -46,13 +46,13 @@ Dynamic: license-file
46
46
  ```
47
47
 
48
48
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
49
- [![Tests](https://img.shields.io/badge/tests-256%20passing-brightgreen.svg)]()
49
+ [![Tests](https://img.shields.io/badge/tests-285%20passing-brightgreen.svg)]()
50
50
  [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)]()
51
51
  [![PyPI](https://img.shields.io/pypi/v/memor-cli.svg)](https://pypi.org/project/memor-cli/)
52
52
 
53
- **Automatic background memory for Claude Code.** Fire and forget — no API keys needed.
53
+ **Automatic background memory for Claude Code, Codex, and Copilot.** Fire and forget — no API keys needed.
54
54
 
55
- Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Zero configuration. One install. Your agent remembers everything.
55
+ Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Works with Claude Code, OpenAI Codex CLI, and GitHub Copilot CLI. Zero configuration. One install. Your agent remembers everything.
56
56
 
57
57
  ---
58
58
 
@@ -62,8 +62,9 @@ Memor watches your coding sessions, extracts decisions and patterns, and recalls
62
62
  # Install globally (recommended)
63
63
  pipx install memor-cli
64
64
 
65
- # Install the Claude Code hook + download embedding model (~60MB)
66
- memor install-hook
65
+ # Install the hook + download embedding model (~60MB)
66
+ memor install-hook # interactive — pick Claude Code, Codex, or Copilot
67
+ memor install-hook --agent claude # or pass directly
67
68
 
68
69
  # Start as a background service (macOS/Linux)
69
70
  memor service install
@@ -72,7 +73,7 @@ memor service install
72
73
  memor daemon
73
74
  ```
74
75
 
75
- That's it. Every Claude Code prompt now gets automatic context recall. Open the dashboard to see it working:
76
+ That's it. Every prompt now gets automatic context recall. Open the dashboard to see it working:
76
77
 
77
78
  ```bash
78
79
  memor dashboard
@@ -86,10 +87,10 @@ memor dashboard
86
87
  ## How It Works
87
88
 
88
89
  ```
89
- You type a prompt in Claude Code
90
+ You type a prompt (Claude Code / Codex / Copilot)
90
91
  |
91
92
  v
92
- Hook fires (UserPromptSubmit)
93
+ Hook fires — auto-detects which agent
93
94
  |
94
95
  v
95
96
  Embed query locally (model2vec, ~2ms)
@@ -107,14 +108,26 @@ memor dashboard
107
108
  Inject relevant context into prompt
108
109
  |
109
110
  v
110
- Claude sees your past decisions, bugfixes,
111
+ Your agent sees past decisions, bugfixes,
111
112
  architecture choices — without you re-explaining
112
113
  ```
113
114
 
115
+ ### Supported Agents
116
+
117
+ | Agent | Hook protocol | Config location | Install |
118
+ |---|---|---|---|
119
+ | **Claude Code** | `UserPromptSubmit` + `additionalContext` | `~/.claude/settings.json` | `memor install-hook --agent claude` |
120
+ | **Codex CLI** | `UserPromptSubmit` + `additionalContext` | `~/.codex/hooks/hooks.json` | `memor install-hook --agent codex` |
121
+ | **Copilot CLI** | `userPromptSubmitted` + `additionalContext` | `~/.copilot/hooks/memor.json` | `memor install-hook --agent copilot` |
122
+
123
+ A single `memor-hook` binary auto-detects which agent is calling it — no separate entry points needed. The dashboard tracks recalls per agent so you can see usage across all your environments.
124
+
125
+ > **Note:** Cloud-hosted agents (Codex cloud, Copilot cloud agent) run in remote sandboxes and cannot reach local hooks. MCP server support for sandboxed agents is planned ([#26](https://github.com/bnimit/memor-ai/issues/26)).
126
+
114
127
  **Two background processes:**
115
128
 
116
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, tracks session-level token usage. All local.
117
- 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
129
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, auto-compacts the vector index when bloated, tracks session-level token usage. All local.
130
+ 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms. Works across Claude Code, Codex, and Copilot.
118
131
 
119
132
  **No API keys required.** Embeddings run locally via [model2vec](https://github.com/MinishLab/model2vec) (potion-base-8M, 256-dim). Vectors stored in [sqlite-vec](https://github.com/asg017/sqlite-vec). Everything runs on your machine.
120
133
 
@@ -188,10 +201,11 @@ memor dashboard
188
201
 
189
202
  Dark fintech-inspired UI showing:
190
203
  - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
204
+ - **Agent breakdown** — per-agent recall stats (Claude, Codex, Copilot) with hit rates
191
205
  - **Daily recall activity** — stacked bar chart of hits vs misses over time
192
206
  - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
193
207
  - **Per-project breakdown** — artifact counts, token totals, last activity
194
- - **Recent recalls** — every hook event with scores, latency, and status
208
+ - **Recent recalls** — every hook event with agent badge, scores, latency, and status
195
209
 
196
210
  ---
197
211
 
@@ -199,7 +213,8 @@ Dark fintech-inspired UI showing:
199
213
 
200
214
  ```
201
215
  memor help Print the full manual
202
- memor install-hook Install Claude Code hook + download model
216
+ memor install-hook Install hook + download model (interactive agent picker)
217
+ --agent claude|codex|copilot Choose agent directly
203
218
  memor daemon Auto-ingest + distill (background watcher)
204
219
  memor dashboard Web dashboard on localhost:8420
205
220
  memor version Print installed version
@@ -211,6 +226,7 @@ memor query <text> Search memories from the CLI
211
226
  memor reingest Wipe DB and re-ingest everything
212
227
  memor reingest --project <name> Re-ingest only one project
213
228
  memor forget-stale Deactivate memories unused for 30+ days
229
+ memor compact Rebuild vector index, reclaim wasted space
214
230
  memor scan Audit DB for leaked secrets
215
231
  memor scan --purge Redact secrets in place
216
232
  memor setup-model Download/retry the embedding model
@@ -264,7 +280,8 @@ memor/
264
280
  +-- judge.py LLM-as-judge evaluation
265
281
  +-- embed_benchmark.py Embedding model comparison
266
282
 
267
- memor/hook_cli.py Claude Code hook entry point (thin client)
283
+ memor/hook_cli.py Hook entry point — auto-detects Claude/Codex/Copilot
284
+ memor/hook_server.py Hook server with agent detection + response formatting
268
285
  skill/recall.py Standalone recall script
269
286
  ```
270
287
 
@@ -311,7 +328,7 @@ cd memor-ai
311
328
  python3 -m venv .venv && source .venv/bin/activate
312
329
  pip install -e ".[dev]"
313
330
 
314
- pytest # 251 tests
331
+ pytest # 285 tests
315
332
  ```
316
333
 
317
334
  ---
@@ -9,13 +9,13 @@
9
9
  ```
10
10
 
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
12
- [![Tests](https://img.shields.io/badge/tests-256%20passing-brightgreen.svg)]()
12
+ [![Tests](https://img.shields.io/badge/tests-285%20passing-brightgreen.svg)]()
13
13
  [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)]()
14
14
  [![PyPI](https://img.shields.io/pypi/v/memor-cli.svg)](https://pypi.org/project/memor-cli/)
15
15
 
16
- **Automatic background memory for Claude Code.** Fire and forget — no API keys needed.
16
+ **Automatic background memory for Claude Code, Codex, and Copilot.** Fire and forget — no API keys needed.
17
17
 
18
- Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Zero configuration. One install. Your agent remembers everything.
18
+ Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Works with Claude Code, OpenAI Codex CLI, and GitHub Copilot CLI. Zero configuration. One install. Your agent remembers everything.
19
19
 
20
20
  ---
21
21
 
@@ -25,8 +25,9 @@ Memor watches your coding sessions, extracts decisions and patterns, and recalls
25
25
  # Install globally (recommended)
26
26
  pipx install memor-cli
27
27
 
28
- # Install the Claude Code hook + download embedding model (~60MB)
29
- memor install-hook
28
+ # Install the hook + download embedding model (~60MB)
29
+ memor install-hook # interactive — pick Claude Code, Codex, or Copilot
30
+ memor install-hook --agent claude # or pass directly
30
31
 
31
32
  # Start as a background service (macOS/Linux)
32
33
  memor service install
@@ -35,7 +36,7 @@ memor service install
35
36
  memor daemon
36
37
  ```
37
38
 
38
- That's it. Every Claude Code prompt now gets automatic context recall. Open the dashboard to see it working:
39
+ That's it. Every prompt now gets automatic context recall. Open the dashboard to see it working:
39
40
 
40
41
  ```bash
41
42
  memor dashboard
@@ -49,10 +50,10 @@ memor dashboard
49
50
  ## How It Works
50
51
 
51
52
  ```
52
- You type a prompt in Claude Code
53
+ You type a prompt (Claude Code / Codex / Copilot)
53
54
  |
54
55
  v
55
- Hook fires (UserPromptSubmit)
56
+ Hook fires — auto-detects which agent
56
57
  |
57
58
  v
58
59
  Embed query locally (model2vec, ~2ms)
@@ -70,14 +71,26 @@ memor dashboard
70
71
  Inject relevant context into prompt
71
72
  |
72
73
  v
73
- Claude sees your past decisions, bugfixes,
74
+ Your agent sees past decisions, bugfixes,
74
75
  architecture choices — without you re-explaining
75
76
  ```
76
77
 
78
+ ### Supported Agents
79
+
80
+ | Agent | Hook protocol | Config location | Install |
81
+ |---|---|---|---|
82
+ | **Claude Code** | `UserPromptSubmit` + `additionalContext` | `~/.claude/settings.json` | `memor install-hook --agent claude` |
83
+ | **Codex CLI** | `UserPromptSubmit` + `additionalContext` | `~/.codex/hooks/hooks.json` | `memor install-hook --agent codex` |
84
+ | **Copilot CLI** | `userPromptSubmitted` + `additionalContext` | `~/.copilot/hooks/memor.json` | `memor install-hook --agent copilot` |
85
+
86
+ A single `memor-hook` binary auto-detects which agent is calling it — no separate entry points needed. The dashboard tracks recalls per agent so you can see usage across all your environments.
87
+
88
+ > **Note:** Cloud-hosted agents (Codex cloud, Copilot cloud agent) run in remote sandboxes and cannot reach local hooks. MCP server support for sandboxed agents is planned ([#26](https://github.com/bnimit/memor-ai/issues/26)).
89
+
77
90
  **Two background processes:**
78
91
 
79
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, tracks session-level token usage. All local.
80
- 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
92
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, auto-compacts the vector index when bloated, tracks session-level token usage. All local.
93
+ 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms. Works across Claude Code, Codex, and Copilot.
81
94
 
82
95
  **No API keys required.** Embeddings run locally via [model2vec](https://github.com/MinishLab/model2vec) (potion-base-8M, 256-dim). Vectors stored in [sqlite-vec](https://github.com/asg017/sqlite-vec). Everything runs on your machine.
83
96
 
@@ -151,10 +164,11 @@ memor dashboard
151
164
 
152
165
  Dark fintech-inspired UI showing:
153
166
  - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
167
+ - **Agent breakdown** — per-agent recall stats (Claude, Codex, Copilot) with hit rates
154
168
  - **Daily recall activity** — stacked bar chart of hits vs misses over time
155
169
  - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
156
170
  - **Per-project breakdown** — artifact counts, token totals, last activity
157
- - **Recent recalls** — every hook event with scores, latency, and status
171
+ - **Recent recalls** — every hook event with agent badge, scores, latency, and status
158
172
 
159
173
  ---
160
174
 
@@ -162,7 +176,8 @@ Dark fintech-inspired UI showing:
162
176
 
163
177
  ```
164
178
  memor help Print the full manual
165
- memor install-hook Install Claude Code hook + download model
179
+ memor install-hook Install hook + download model (interactive agent picker)
180
+ --agent claude|codex|copilot Choose agent directly
166
181
  memor daemon Auto-ingest + distill (background watcher)
167
182
  memor dashboard Web dashboard on localhost:8420
168
183
  memor version Print installed version
@@ -174,6 +189,7 @@ memor query <text> Search memories from the CLI
174
189
  memor reingest Wipe DB and re-ingest everything
175
190
  memor reingest --project <name> Re-ingest only one project
176
191
  memor forget-stale Deactivate memories unused for 30+ days
192
+ memor compact Rebuild vector index, reclaim wasted space
177
193
  memor scan Audit DB for leaked secrets
178
194
  memor scan --purge Redact secrets in place
179
195
  memor setup-model Download/retry the embedding model
@@ -227,7 +243,8 @@ memor/
227
243
  +-- judge.py LLM-as-judge evaluation
228
244
  +-- embed_benchmark.py Embedding model comparison
229
245
 
230
- memor/hook_cli.py Claude Code hook entry point (thin client)
246
+ memor/hook_cli.py Hook entry point — auto-detects Claude/Codex/Copilot
247
+ memor/hook_server.py Hook server with agent detection + response formatting
231
248
  skill/recall.py Standalone recall script
232
249
  ```
233
250
 
@@ -274,7 +291,7 @@ cd memor-ai
274
291
  python3 -m venv .venv && source .venv/bin/activate
275
292
  pip install -e ".[dev]"
276
293
 
277
- pytest # 251 tests
294
+ pytest # 285 tests
278
295
  ```
279
296
 
280
297
  ---
@@ -0,0 +1 @@
1
+ __version__ = "0.7.0"
@@ -44,9 +44,10 @@ HELP_TEXT = """\
44
44
  memor — measured memory for coding agents
45
45
 
46
46
  GETTING STARTED
47
- memor install-hook Install the Claude Code recall hook + download model
48
- memor service install Start the daemon as a background service
49
- memor dashboard Open the web dashboard at localhost:8420
47
+ memor install-hook Install the recall hook + download model
48
+ --agent claude|codex|copilot Choose your agent (default: claude)
49
+ memor service install Start the daemon as a background service
50
+ memor dashboard Open the web dashboard at localhost:8420
50
51
 
51
52
  SERVICE MANAGEMENT
52
53
  memor service install Install and start as a background service (survives reboots)
@@ -89,7 +90,9 @@ CONFIGURATION
89
90
  No configuration needed — just install, hook, and run the daemon.
90
91
 
91
92
  EXAMPLES
92
- memor install-hook && memor service install # one-time setup
93
+ memor install-hook && memor service install # Claude Code (default)
94
+ memor install-hook --agent codex && memor service install # Codex CLI
95
+ memor install-hook --agent copilot && memor service install # Copilot CLI
93
96
  memor query "auth flow" --project my-app
94
97
  memor reingest --project my-app -y # refresh one project
95
98
  memor dashboard # see stats at localhost:8420
@@ -487,20 +490,108 @@ def _install_hook_logic(settings_path: Path, hook_command: str) -> None:
487
490
  settings_path.write_text(json.dumps(data, indent=2))
488
491
 
489
492
 
493
+ def _install_hook_logic_codex(hooks_path: Path, hook_command: str) -> None:
494
+ if hooks_path.exists():
495
+ data = json.loads(hooks_path.read_text())
496
+ else:
497
+ data = {}
498
+ hooks = data.setdefault("hooks", {})
499
+ prompt_hooks = hooks.setdefault("UserPromptSubmit", [])
500
+ entry = {"command": hook_command, "timeout": 5}
501
+ existing_idx = None
502
+ for i, h in enumerate(prompt_hooks):
503
+ if "memor-hook" in h.get("command", ""):
504
+ existing_idx = i
505
+ break
506
+ if existing_idx is not None:
507
+ prompt_hooks[existing_idx] = entry
508
+ else:
509
+ prompt_hooks.append(entry)
510
+ hooks_path.parent.mkdir(parents=True, exist_ok=True)
511
+ hooks_path.write_text(json.dumps(data, indent=2))
512
+
513
+
514
+ def _install_hook_logic_copilot(hooks_path: Path, hook_command: str) -> None:
515
+ if hooks_path.exists():
516
+ data = json.loads(hooks_path.read_text())
517
+ else:
518
+ data = {"version": 1, "hooks": {}}
519
+ data.setdefault("version", 1)
520
+ hooks = data.setdefault("hooks", {})
521
+ prompt_hooks = hooks.setdefault("userPromptSubmitted", [])
522
+ entry = {"type": "command", "bash": hook_command, "timeoutSec": 5}
523
+ existing_idx = None
524
+ for i, h in enumerate(prompt_hooks):
525
+ if "memor-hook" in h.get("bash", ""):
526
+ existing_idx = i
527
+ break
528
+ if existing_idx is not None:
529
+ prompt_hooks[existing_idx] = entry
530
+ else:
531
+ prompt_hooks.append(entry)
532
+ hooks_path.parent.mkdir(parents=True, exist_ok=True)
533
+ hooks_path.write_text(json.dumps(data, indent=2))
534
+
535
+
536
+ AGENT_CHOICES = [
537
+ ("claude", "~/.claude/settings.json", "Claude Code"),
538
+ ("codex", "~/.codex/hooks/hooks.json", "Codex CLI"),
539
+ ("copilot", "~/.copilot/hooks/memor.json", "Copilot CLI"),
540
+ ]
541
+
542
+
543
+ def _prompt_agent() -> str:
544
+ typer.echo("Which agent do you want to install the recall hook for?\n")
545
+ for i, (key, _, label) in enumerate(AGENT_CHOICES, 1):
546
+ typer.echo(f" {i}. {label}")
547
+ typer.echo()
548
+ while True:
549
+ choice = typer.prompt("Choose (1-3)", default="1")
550
+ try:
551
+ idx = int(choice) - 1
552
+ if 0 <= idx < len(AGENT_CHOICES):
553
+ return AGENT_CHOICES[idx][0]
554
+ except ValueError:
555
+ pass
556
+ typer.echo("Invalid choice. Enter 1, 2, or 3.")
557
+
558
+
490
559
  @app.command("install-hook")
491
- def install_hook():
492
- """Install the Claude Code recall hook into ~/.claude/settings.json."""
560
+ def install_hook(agent: str | None = typer.Option(None, help="Agent: claude, codex, copilot (interactive if omitted)")):
561
+ """Install the recall hook for Claude Code, Codex, or Copilot."""
493
562
  import shutil
563
+ agent_keys = {key for key, _, _ in AGENT_CHOICES}
564
+
565
+ if agent is None:
566
+ agent = _prompt_agent()
567
+ else:
568
+ agent = agent.lower()
569
+
570
+ if agent not in agent_keys:
571
+ typer.echo(f"Unknown agent '{agent}'. Choose: claude, codex, copilot", err=True)
572
+ raise typer.Exit(1)
573
+
494
574
  hook_bin = shutil.which("memor-hook")
495
575
  if not hook_bin:
496
576
  typer.echo("Error: 'memor-hook' not found on PATH.", err=True)
497
577
  typer.echo(" If you installed with pipx: pipx install memor-cli", err=True)
498
578
  typer.echo(" If developing locally: pip install -e .", err=True)
499
579
  raise typer.Exit(1)
500
- settings_path = Path.home() / ".claude" / "settings.json"
501
- _install_hook_logic(settings_path, hook_bin)
502
- typer.echo(f"Hook installed: {hook_bin}")
503
- typer.echo(f"Settings updated: {settings_path}")
580
+
581
+ config_path_str, agent_name = next(
582
+ (p, n) for k, p, n in AGENT_CHOICES if k == agent
583
+ )
584
+ config_path = Path(config_path_str).expanduser()
585
+
586
+ if agent == "claude":
587
+ _install_hook_logic(config_path, hook_bin)
588
+ elif agent == "codex":
589
+ _install_hook_logic_codex(config_path, hook_bin)
590
+ elif agent == "copilot":
591
+ _install_hook_logic_copilot(config_path, hook_bin)
592
+
593
+ typer.echo(f"\nHook installed for {agent_name}: {hook_bin}")
594
+ typer.echo(f"Config updated: {config_path}")
504
595
  typer.echo()
505
596
  typer.echo("Pre-downloading embedding model...")
506
597
  try:
@@ -177,6 +177,11 @@ def create_app(db_path: str | None = None) -> FastAPI:
177
177
  return {"status": "no_runs"}
178
178
  return result
179
179
 
180
+ @app.get("/api/agent-breakdown")
181
+ def agent_breakdown():
182
+ store = _store()
183
+ return store.get_agent_breakdown()
184
+
180
185
  @app.get("/api/version")
181
186
  def version():
182
187
  from memor import __version__
@@ -304,6 +304,12 @@
304
304
  .badge-global::before { background: #a090ff; }
305
305
  .badge-negative { background: rgba(232,84,84,0.12); color: var(--error); }
306
306
  .badge-negative::before { background: var(--error); }
307
+ .badge-claude { background: var(--accent-dim); color: var(--accent); }
308
+ .badge-claude::before { background: var(--accent); }
309
+ .badge-codex { background: rgba(61,214,140,0.12); color: #3dd68c; }
310
+ .badge-codex::before { background: #3dd68c; }
311
+ .badge-copilot { background: rgba(100,160,255,0.12); color: #64a0ff; }
312
+ .badge-copilot::before { background: #64a0ff; }
307
313
 
308
314
  .quality-bar { display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--surface3); width: 100%; min-width: 60px; }
309
315
  .quality-fill { height: 100%; border-radius: 3px; transition: width 0.3s; }
@@ -398,6 +404,12 @@
398
404
  </div>
399
405
  </section>
400
406
 
407
+ <!-- ── Agent breakdown ─────────────────────────────────── -->
408
+ <section id="agent-section" style="display:none;">
409
+ <div class="section-label"><span class="dot"></span> Agent Breakdown</div>
410
+ <div class="metric-grid" id="agent-grid" style="grid-template-columns:repeat(auto-fit,minmax(180px,1fr));"></div>
411
+ </section>
412
+
401
413
  <!-- ── Token ROI hero ─────────────────────────────────── -->
402
414
  <section>
403
415
  <div class="chart-card" id="roi-card" style="display:none;">
@@ -538,6 +550,7 @@
538
550
  <thead>
539
551
  <tr>
540
552
  <th>Time</th>
553
+ <th>Agent</th>
541
554
  <th>Project</th>
542
555
  <th>Query</th>
543
556
  <th>Hits</th>
@@ -595,6 +608,16 @@
595
608
  return '<span class="badge ' + m[0] + '">' + m[1] + '</span>';
596
609
  }
597
610
 
611
+ function agentBadge(agent) {
612
+ var map = {
613
+ claude: ['badge-claude', 'Claude'],
614
+ codex: ['badge-codex', 'Codex'],
615
+ copilot: ['badge-copilot', 'Copilot'],
616
+ };
617
+ var m = map[agent || 'claude'] || ['badge-claude', agent || 'claude'];
618
+ return '<span class="badge ' + m[0] + '">' + m[1] + '</span>';
619
+ }
620
+
598
621
  function el(tag, attrs, inner) {
599
622
  var e = document.createElement(tag);
600
623
  for (var k in (attrs || {})) e.setAttribute(k, attrs[k]);
@@ -860,13 +883,14 @@
860
883
  var tbody = document.getElementById('recalls-body');
861
884
  tbody.innerHTML = '';
862
885
  if (!data.length) {
863
- tbody.innerHTML = '<tr><td colspan="8" class="empty-state">No recall events yet.</td></tr>';
886
+ tbody.innerHTML = '<tr><td colspan="9" class="empty-state">No recall events yet.</td></tr>';
864
887
  return;
865
888
  }
866
889
  data.forEach(function(row) {
867
890
  var tr = el('tr', {});
868
891
  tr.innerHTML =
869
892
  '<td class="mono" style="white-space:nowrap;color:var(--text-muted)">' + relativeTime(row.timestamp) + '</td>' +
893
+ '<td>' + agentBadge(row.agent) + '</td>' +
870
894
  '<td class="truncate mono" style="max-width:120px" title="' + esc(row.project) + '">' + esc(row.project) + '</td>' +
871
895
  '<td class="truncate" style="max-width:220px" title="' + esc(row.query_preview) + '">' + esc(row.query_preview) + '</td>' +
872
896
  '<td>' + (row.hits_count||0) + '</td>' +
@@ -1002,6 +1026,29 @@
1002
1026
  });
1003
1027
  }
1004
1028
 
1029
+ /* ── Agent breakdown ────────────────────────────────────── */
1030
+ function renderAgentBreakdown(data) {
1031
+ var section = document.getElementById('agent-section');
1032
+ var grid = document.getElementById('agent-grid');
1033
+ if (!data || data.length <= 1) { section.style.display = 'none'; return; }
1034
+ section.style.display = '';
1035
+ grid.innerHTML = '';
1036
+ var colors = { claude: 'var(--accent)', codex: '#3dd68c', copilot: '#64a0ff' };
1037
+ data.forEach(function(row) {
1038
+ var agent = row.agent || 'claude';
1039
+ var color = colors[agent] || 'var(--text-muted)';
1040
+ var hitRate = row.recalls > 0 ? ((row.hits / row.recalls) * 100) : 0;
1041
+ var card = el('div', {class: 'metric-card'});
1042
+ card.innerHTML =
1043
+ '<div class="metric-info">' +
1044
+ '<div class="metric-label" style="color:' + color + ';">' + esc(agent.charAt(0).toUpperCase() + agent.slice(1)) + '</div>' +
1045
+ '<div class="metric-value">' + fmt(row.recalls) + '</div>' +
1046
+ '<div class="metric-sub">' + fmtPct(hitRate) + ' hit rate · ' + fmtMs(row.avg_latency) + '</div>' +
1047
+ '</div>';
1048
+ grid.appendChild(card);
1049
+ });
1050
+ }
1051
+
1005
1052
  /* ── Data loaders ──────────────────────────────────────── */
1006
1053
  async function loadSummary() { try { renderSummary(await api('/api/summary')); } catch(e) { console.warn('summary',e); } }
1007
1054
  async function loadProjects() { try { renderProjects(await api('/api/projects')); } catch(e) { console.warn('projects',e); } }
@@ -1012,6 +1059,7 @@
1012
1059
  async function loadROI() { try { renderROI(await api('/api/roi')); } catch(e) { console.warn('roi',e); } }
1013
1060
  async function loadROITrend() { try { renderROITrend(await api('/api/roi-trend')); } catch(e) { console.warn('roi-trend',e); } }
1014
1061
  async function loadQuality() { try { renderQuality(await api('/api/quality')); } catch(e) { console.warn('quality',e); } }
1062
+ async function loadAgentBreakdown() { try { renderAgentBreakdown(await api('/api/agent-breakdown')); } catch(e) { console.warn('agent-breakdown',e); } }
1015
1063
  async function loadRecalls() {
1016
1064
  try {
1017
1065
  var url = '/api/recalls?limit=50' + (projectFilter ? '&project=' + encodeURIComponent(projectFilter) : '');
@@ -1020,7 +1068,7 @@
1020
1068
  }
1021
1069
 
1022
1070
  async function refresh() {
1023
- await Promise.allSettled([loadSummary(), loadProjects(), loadEfficiency(), loadSessionEfficiency(), loadHealth(), loadTrend(), loadRecalls(), loadROI(), loadROITrend(), loadQuality()]);
1071
+ await Promise.allSettled([loadSummary(), loadProjects(), loadEfficiency(), loadSessionEfficiency(), loadHealth(), loadTrend(), loadRecalls(), loadROI(), loadROITrend(), loadQuality(), loadAgentBreakdown()]);
1024
1072
  document.getElementById('last-updated').textContent = new Date().toLocaleTimeString();
1025
1073
 
1026
1074
  renderMiniBars('mb-chunks', null);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Claude Code UserPromptSubmit hook — thin client for Memor recall.
2
+ """Memor recall hook — works with Claude Code, Codex, and Copilot.
3
3
 
4
4
  Tries to connect to the warm sidecar at ~/.memor/hook.sock.
5
5
  Falls back to inline execution if sidecar is unavailable.
@@ -57,12 +57,8 @@ def _inline_fallback(request: dict) -> dict:
57
57
  return handle_request(request)
58
58
  except Exception as e:
59
59
  print(json.dumps({"error": str(e)}), file=sys.stderr)
60
- return {
61
- "hookSpecificOutput": {
62
- "hookEventName": "UserPromptSubmit",
63
- "additionalContext": "",
64
- }
65
- }
60
+ from memor.hook_server import detect_agent, format_hook_response
61
+ return format_hook_response(detect_agent(request), "")
66
62
 
67
63
 
68
64
  def main():
@@ -12,6 +12,26 @@ PID_PATH = Path.home() / ".memor" / "hook.pid"
12
12
  DEFAULT_DB = str(Path.home() / ".memor" / "memor.db")
13
13
  IDLE_TIMEOUT_S = 600
14
14
 
15
+
16
+ def detect_agent(req: dict) -> str:
17
+ event = req.get("hook_event_name", "")
18
+ if event == "userPromptSubmitted":
19
+ return "copilot"
20
+ if "model" in req or "permission_mode" in req:
21
+ return "codex"
22
+ return "claude"
23
+
24
+
25
+ def format_hook_response(agent: str, additional_context: str) -> dict:
26
+ if agent == "copilot":
27
+ return {"additionalContext": additional_context}
28
+ return {
29
+ "hookSpecificOutput": {
30
+ "hookEventName": "UserPromptSubmit",
31
+ "additionalContext": additional_context,
32
+ }
33
+ }
34
+
15
35
  _embedder = None
16
36
  _last_activity = 0.0
17
37
  _session_injected: dict[str, set[str]] = {}
@@ -49,14 +69,11 @@ def handle_request(req: dict, *, db_path: str = DEFAULT_DB,
49
69
 
50
70
  if embedder is _UNSET:
51
71
  embedder = _get_embedder()
72
+ agent = detect_agent(req)
73
+
52
74
  if embedder is None:
53
75
  msg = _status_message("no_embedder", project, 0, 0, 0.0)
54
- return {
55
- "hookSpecificOutput": {
56
- "hookEventName": "UserPromptSubmit",
57
- "additionalContext": f"---\n{msg}",
58
- }
59
- }
76
+ return format_hook_response(agent, f"---\n{msg}")
60
77
 
61
78
  from memor.query_complexity import route_query, Tier
62
79
 
@@ -71,15 +88,11 @@ def handle_request(req: dict, *, db_path: str = DEFAULT_DB,
71
88
  project=project, query_preview=query[:100],
72
89
  hits_count=0, top_score=0.0,
73
90
  tokens_injected=0, latency_ms=0.0,
74
- status="skipped_trivial", session_id=session_id)
91
+ status="skipped_trivial", session_id=session_id,
92
+ agent=agent)
75
93
  except Exception:
76
94
  pass
77
- return {
78
- "hookSpecificOutput": {
79
- "hookEventName": "UserPromptSubmit",
80
- "additionalContext": f"---\n{msg}",
81
- }
82
- }
95
+ return format_hook_response(agent, f"---\n{msg}")
83
96
 
84
97
  try:
85
98
  env_max = int(os.environ.get("MEMOR_MAX_TOKENS", "0"))
@@ -113,19 +126,15 @@ def handle_request(req: dict, *, db_path: str = DEFAULT_DB,
113
126
  project=project, query_preview=query[:100],
114
127
  hits_count=result.hits_count, top_score=result.top_score,
115
128
  tokens_injected=result.tokens_injected, latency_ms=result.latency_ms,
116
- status=result.status, session_id=session_id)
129
+ status=result.status, session_id=session_id,
130
+ agent=agent)
117
131
  if result.hit_ids:
118
132
  store.record_recall(result.hit_ids)
119
133
  except Exception:
120
134
  pass
121
135
 
122
136
  additional_context = result.formatted_context or f"---\n{result.status_message}"
123
- return {
124
- "hookSpecificOutput": {
125
- "hookEventName": "UserPromptSubmit",
126
- "additionalContext": additional_context,
127
- }
128
- }
137
+ return format_hook_response(agent, additional_context)
129
138
 
130
139
 
131
140
  async def _handle_client(reader: asyncio.StreamReader,