dev-recall 0.2.0__py3-none-any.whl
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.
- dev_recall-0.2.0.dist-info/METADATA +281 -0
- dev_recall-0.2.0.dist-info/RECORD +34 -0
- dev_recall-0.2.0.dist-info/WHEEL +5 -0
- dev_recall-0.2.0.dist-info/entry_points.txt +2 -0
- dev_recall-0.2.0.dist-info/top_level.txt +1 -0
- recall/__init__.py +3 -0
- recall/_hooks.py +211 -0
- recall/cli.py +1032 -0
- recall/collectors/__init__.py +1 -0
- recall/collectors/ai_chat.py +644 -0
- recall/collectors/containers.py +164 -0
- recall/collectors/git.py +540 -0
- recall/collectors/linux_process.py +230 -0
- recall/collectors/linux_session.py +229 -0
- recall/collectors/linux_window.py +199 -0
- recall/collectors/shell.py +300 -0
- recall/collectors/vscode.py +175 -0
- recall/config.py +257 -0
- recall/daemon.py +466 -0
- recall/daemon_main.py +25 -0
- recall/mcp_server.py +290 -0
- recall/models.py +225 -0
- recall/processor/__init__.py +1 -0
- recall/processor/embedder.py +213 -0
- recall/processor/enricher.py +213 -0
- recall/processor/session.py +142 -0
- recall/query/__init__.py +1 -0
- recall/query/context.py +130 -0
- recall/query/llm.py +85 -0
- recall/query/retriever.py +147 -0
- recall/query/timeparser.py +188 -0
- recall/storage/__init__.py +1 -0
- recall/storage/db.py +528 -0
- recall/storage/vectors.py +166 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dev-recall
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local-first developer memory layer
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: developer-tools,memory,productivity,cli
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: click>=8.1
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
11
|
+
Requires-Dist: sentence-transformers>=3.0
|
|
12
|
+
Requires-Dist: faiss-cpu>=1.8
|
|
13
|
+
Requires-Dist: watchdog>=4.0
|
|
14
|
+
Requires-Dist: flask>=3.0
|
|
15
|
+
Requires-Dist: requests>=2.31
|
|
16
|
+
Requires-Dist: platformdirs>=4.0
|
|
17
|
+
Requires-Dist: python-dateutil>=2.9
|
|
18
|
+
Requires-Dist: humanize>=4.0
|
|
19
|
+
Requires-Dist: mcp>=1.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff; extra == "dev"
|
|
25
|
+
Provides-Extra: linux
|
|
26
|
+
Requires-Dist: psutil>=5.9; extra == "linux"
|
|
27
|
+
Requires-Dist: dbus-next>=0.2; extra == "linux"
|
|
28
|
+
Requires-Dist: docker>=7.0; extra == "linux"
|
|
29
|
+
|
|
30
|
+
# Recall
|
|
31
|
+
|
|
32
|
+
**Local-first developer memory layer.** Captures every developer activity — terminal commands, git commits, file edits, repo opens, AI chat sessions — into a structured SQLite database with a FAISS vector index. Enables natural language recall:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
recall ask "what did I work on last Tuesday?"
|
|
36
|
+
recall ask "how did I fix the auth bug?"
|
|
37
|
+
recall today
|
|
38
|
+
recall timeline
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install dev-recall
|
|
47
|
+
recall init
|
|
48
|
+
source .zshrc # or .bashrc
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`recall init` will:
|
|
52
|
+
1. Create `~/.local/share/recall/` and `~/.config/recall/`
|
|
53
|
+
2. Initialize the SQLite database + FAISS vector index
|
|
54
|
+
3. Install the zsh/bash shell hook (appends `source` line to your rc file)
|
|
55
|
+
4. Set `git config --global core.hooksPath` to capture all commits
|
|
56
|
+
5. Start the background daemon (via systemd user service or subprocess)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
| Command | Description |
|
|
63
|
+
|---------|-------------|
|
|
64
|
+
| `recall init` | First-time setup |
|
|
65
|
+
| `recall ask "<query>"` | Natural language search with LLM answer |
|
|
66
|
+
| `recall today` | Summary of today's activity |
|
|
67
|
+
| `recall week` | Summary of this week's activity |
|
|
68
|
+
| `recall timeline` | Chronological event list for a day |
|
|
69
|
+
| `recall search "<query>"` | Raw hybrid search (no LLM) |
|
|
70
|
+
| `recall repos` | List all tracked repos |
|
|
71
|
+
| `recall stats` | Capture statistics |
|
|
72
|
+
| `recall export` | Export events as JSON or CSV |
|
|
73
|
+
| `recall config` | View/edit configuration |
|
|
74
|
+
| `recall privacy list` | Show what's captured |
|
|
75
|
+
| `recall privacy delete` | Delete captured events |
|
|
76
|
+
| `recall privacy ignore --cmd "pattern"` | Add a privacy filter |
|
|
77
|
+
| `recall daemon start/stop/status/logs` | Manage the background daemon |
|
|
78
|
+
| `recall mcp-serve` | Start MCP server (for Claude Code / Copilot) |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Collectors (shell hook, git hooks, VS Code ext, AI log watcher)
|
|
86
|
+
↓ events (TSV files + HTTP POST)
|
|
87
|
+
Daemon (FileWatcher → Enricher → SessionDetector → DB insert → Embedder)
|
|
88
|
+
↓
|
|
89
|
+
Storage (SQLite events.db + FTS5, FAISS vectors.faiss)
|
|
90
|
+
↓
|
|
91
|
+
Query (Hybrid FAISS+FTS5 → RRF → LLM via OpenRouter)
|
|
92
|
+
↓
|
|
93
|
+
CLI + MCP Server
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Data Sources
|
|
99
|
+
|
|
100
|
+
### Shell commands (zsh / bash)
|
|
101
|
+
Add to `~/.zshrc` (done automatically by `recall init`):
|
|
102
|
+
```bash
|
|
103
|
+
source ~/.config/dev-recall/hook.zsh
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Git commits
|
|
107
|
+
`recall init` sets `core.hooksPath` globally — all future commits in any repo are captured.
|
|
108
|
+
|
|
109
|
+
### VS Code activity
|
|
110
|
+
Install the extension:
|
|
111
|
+
```bash
|
|
112
|
+
code --install-extension recall.recall-vscode
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### AI chat sessions
|
|
116
|
+
Automatically scanned from:
|
|
117
|
+
- **GitHub Copilot Chat**: `~/.config/Code/User/workspaceStorage/*/GitHub.copilot-chat/debug-logs/`
|
|
118
|
+
- **Claude Code**: `~/.claude/projects/*/sessions/`
|
|
119
|
+
- **Aider**: `.aider.chat.history.md` in git repos
|
|
120
|
+
- **Cursor**: `~/.config/Cursor/User/workspaceStorage/`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## LLM Integration
|
|
125
|
+
|
|
126
|
+
Recall uses [OpenRouter](https://openrouter.ai) for the `ask` command and daily summaries.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
130
|
+
recall ask "what was I debugging yesterday?"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Without an API key, `recall ask` falls back to `--no-llm` mode (shows retrieved events directly). All other commands work fully offline.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## MCP Server
|
|
138
|
+
|
|
139
|
+
Use Recall as a context source in Claude Code or VS Code Copilot:
|
|
140
|
+
|
|
141
|
+
**Claude Code** (`~/.config/claude/mcp.json`):
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"dev-recall": {
|
|
146
|
+
"command": "dev-recall",
|
|
147
|
+
"args": ["mcp-serve"]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**VS Code / Copilot** (`.vscode/mcp.json`):
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"servers": {
|
|
157
|
+
"dev-recall": {
|
|
158
|
+
"type": "stdio",
|
|
159
|
+
"command": "dev-recall",
|
|
160
|
+
"args": ["mcp-serve"]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Available MCP tools: `recall`, `today_summary`, `recent_repos`, `find_command`, `timeline`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Privacy
|
|
171
|
+
|
|
172
|
+
- Commands matching `*password*`, `*secret*`, `*token*` etc. are **dropped before storage**
|
|
173
|
+
- AI chat messages are truncated to 200 characters (intent only, not full content)
|
|
174
|
+
- File saves store only **path + language**, never file content
|
|
175
|
+
- All data is **local only** — LLM calls send only small event snippets, only when you run `ask`
|
|
176
|
+
- Default retention: **90 days** (configurable)
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
recall privacy list # see what's captured
|
|
180
|
+
recall privacy delete --before 2026-01-01
|
|
181
|
+
recall privacy ignore --cmd "*mycompany*"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
Config file: `~/.config/dev-recall/config.json`
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
recall config # show all settings
|
|
192
|
+
recall config daemon_port 8080 # change port
|
|
193
|
+
recall config retention_days 30 # shorter retention
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Key settings:
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"daemon_port": 27182,
|
|
200
|
+
"embedding_model": "all-MiniLM-L6-v2",
|
|
201
|
+
"llm_model": "anthropic/claude-sonnet-4",
|
|
202
|
+
"retention_days": 90,
|
|
203
|
+
"capture": { "terminal": true, "git": true, "vscode": true, "ai_chat": true }
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Data Storage
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
~/.local/share/dev-recall/
|
|
213
|
+
├── events.db # SQLite (events + FTS5 + sessions + daily_summaries)
|
|
214
|
+
├── vectors.faiss # FAISS vector index
|
|
215
|
+
├── shell.tsv # shell hook ring buffer
|
|
216
|
+
├── git.tsv # git hook ring buffer
|
|
217
|
+
└── daemon.pid # running daemon PID
|
|
218
|
+
|
|
219
|
+
~/.config/dev-recall/
|
|
220
|
+
├── config.json
|
|
221
|
+
├── hook.zsh / hook.bash
|
|
222
|
+
└── git-hooks/post-commit + post-checkout
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
git clone <repo>
|
|
231
|
+
cd dev-recall
|
|
232
|
+
pip install -e ".[dev]"
|
|
233
|
+
pytest
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Sandbox testing
|
|
237
|
+
|
|
238
|
+
`recall init` makes system-wide changes (modifies `~/.zshrc`, sets a global `git config core.hooksPath`, starts a background daemon). Use the provided Docker sandbox to test safely without touching your host environment.
|
|
239
|
+
|
|
240
|
+
**Prerequisites:** Docker
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Interactive shell — explore freely
|
|
244
|
+
./sandbox.sh
|
|
245
|
+
|
|
246
|
+
# Run the full test suite
|
|
247
|
+
./sandbox.sh test
|
|
248
|
+
|
|
249
|
+
# Run `recall init` and inspect every file it creates
|
|
250
|
+
./sandbox.sh init
|
|
251
|
+
|
|
252
|
+
# Run any arbitrary command
|
|
253
|
+
./sandbox.sh "recall --help"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
What the sandbox isolates:
|
|
257
|
+
|
|
258
|
+
| Risk | Mitigation |
|
|
259
|
+
|------|------------|
|
|
260
|
+
| Modifies `~/.zshrc` | Only affects the container's home directory |
|
|
261
|
+
| Sets global `git config core.hooksPath` | Sandboxed git config, discarded on exit |
|
|
262
|
+
| Starts a background daemon | Killed automatically when the container exits |
|
|
263
|
+
| Network calls to OpenRouter | Blocked via `--network none` |
|
|
264
|
+
| Privilege escalation | `--cap-drop ALL --security-opt no-new-privileges` |
|
|
265
|
+
|
|
266
|
+
Alternatively, use a throwaway VM: `multipass launch --name dev-recall-test`
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Roadmap
|
|
271
|
+
|
|
272
|
+
- **v0.1** (current): Shell + git + daemon + CLI ask/today/timeline/stats
|
|
273
|
+
- **v0.2**: VS Code extension + AI chat parsers + week/repos/search/export + auto-summary
|
|
274
|
+
- **v0.3**: MCP server + privacy management + Aider/Cursor parsers
|
|
275
|
+
- **v1.0**: Cross-machine sync + web dashboard + Wakatime-compatible API
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
recall/__init__.py,sha256=90hv-rD82T7MQuhcJ8vOlQ7PFUVnT-CCOW37dnlv0Dw,76
|
|
2
|
+
recall/_hooks.py,sha256=x_svAsg0LGi2kcIBSjZ9a6CpyT7RrQwdRZF7N8YBqbE,6879
|
|
3
|
+
recall/cli.py,sha256=5jCBQDwrlITA1UhlIp81QFHWg6Lcl8SZyhhhF1YL71Q,35424
|
|
4
|
+
recall/config.py,sha256=67a26FE7pPV0wFYQHQDIvydBnOU3IVYZbl4pTdrgXsA,7580
|
|
5
|
+
recall/daemon.py,sha256=EG5iN-Y2GX52nFZ2-5BUj2BWLdNE8ZUfMccnsgSIcXA,16269
|
|
6
|
+
recall/daemon_main.py,sha256=GmKvZb9KnJuJ2qj5wgBh7zBn0XIVZiS0AKdlx9_CSg0,853
|
|
7
|
+
recall/mcp_server.py,sha256=2aMplCG8KgM87CFAexWN2YAN3DU3wuPl3nmEFj2OUS4,9418
|
|
8
|
+
recall/models.py,sha256=2qfZOtCL_P23VUAAbGzULgbpzxGxd308YeupnXBvpfw,8477
|
|
9
|
+
recall/collectors/__init__.py,sha256=rJSiPLE8nou5iFNyYnXtNN5QOBcjxWRXl7Qdk9BHTUs,26
|
|
10
|
+
recall/collectors/ai_chat.py,sha256=1DC6gXKe_M06YNayDXLOOXFMcjo9k30tUIF3Mim4rRo,24345
|
|
11
|
+
recall/collectors/containers.py,sha256=Pl06XJSbtMxpeqhsgq1l-74RCXzslTDfurUzMlswaMU,4973
|
|
12
|
+
recall/collectors/git.py,sha256=t14M6vX826IVgkRd6gZnNhITvD58sIXgUgOZflUstYk,17720
|
|
13
|
+
recall/collectors/linux_process.py,sha256=zaw7qVp1GOAxmOozp7o5mKkhxXnDXFIH9gf_b0SwjvU,6386
|
|
14
|
+
recall/collectors/linux_session.py,sha256=qC1m24pVmpG5YVjfSmloz7_P6ogjVrrdgM92JAFhqpA,7864
|
|
15
|
+
recall/collectors/linux_window.py,sha256=X67XA2i9hcAwYri7VNvBBnBNiz7j5swY-vfXTLWr_gc,6900
|
|
16
|
+
recall/collectors/shell.py,sha256=P2qZn0Do4LG-C3q4iyZFZkgv8eax8IM_gqx4d1238oo,9183
|
|
17
|
+
recall/collectors/vscode.py,sha256=9lzKdM9sq7gTNeMBSp--n3Po4cOuvDwa0tZy2gZQ8Hs,6296
|
|
18
|
+
recall/processor/__init__.py,sha256=8wXgE0m7gtHtI798r0QIjCNalk5jmgbFhW-2dJRdV4U,25
|
|
19
|
+
recall/processor/embedder.py,sha256=e7cdM8v3v5mWTYZJYGnPYh-ZC6g_k_gZYah0VGRjN2E,7089
|
|
20
|
+
recall/processor/enricher.py,sha256=1yIudA2xKcV0YnO9tjAPLMTw5rgbnMF63vP0bLh4VeI,7954
|
|
21
|
+
recall/processor/session.py,sha256=mPW9YY9vNQGCnDv5SgznoEturzP7WqlfiQ_oZJDoTDs,4761
|
|
22
|
+
recall/query/__init__.py,sha256=BgQ-2XnXxYbiAJM6BbOJQurykZlGMgXPpIUjU5NPUGg,21
|
|
23
|
+
recall/query/context.py,sha256=vXmlBMOPHxxtgBtfVvhjbPTv3xo8iDNqPy7uKeu5_dg,4057
|
|
24
|
+
recall/query/llm.py,sha256=hcXvJWl3SwOohC57_zJIcxi1k_sL0HW4AXqj1if7G9o,2499
|
|
25
|
+
recall/query/retriever.py,sha256=h1xL7o-v1Pw0rqNyrNC6PZ4-QU_UMrAR74AUopLcroA,5345
|
|
26
|
+
recall/query/timeparser.py,sha256=PamqwdhMh37-TRep2bSXwUsqXZ0yXyRqiidl1HjWTfU,6327
|
|
27
|
+
recall/storage/__init__.py,sha256=ZEUYaCfEWtVDcx9rqxHDgRbbBitY1hLMAQJJ1anSAN0,23
|
|
28
|
+
recall/storage/db.py,sha256=jrO49bRyYD55i6ihlu0iFGagP9GWZtr7nJyl1zvucW4,20088
|
|
29
|
+
recall/storage/vectors.py,sha256=-QE7VMfkg9KRkp7MNYCEa8fOXEIaSAWqmxGscOlg7pQ,5797
|
|
30
|
+
dev_recall-0.2.0.dist-info/METADATA,sha256=Aqe_GyfwQOMLc3T7r9DFl5btH0-5b5SWcT2GNKcFD0U,7544
|
|
31
|
+
dev_recall-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
32
|
+
dev_recall-0.2.0.dist-info/entry_points.txt,sha256=0ZHnL2YA9GCYwigaztgNOMShOzGF0ecJ2sFbLzfKTpo,42
|
|
33
|
+
dev_recall-0.2.0.dist-info/top_level.txt,sha256=REbPxLkxL1quicUx08ayPQvqFNwInZCLD9hGV6EFxdQ,7
|
|
34
|
+
dev_recall-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recall
|
recall/__init__.py
ADDED
recall/_hooks.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Bundled shell hook scripts for pip-installed environments.
|
|
2
|
+
|
|
3
|
+
When dev-recall is installed via pip, the shell/ directory isn't available.
|
|
4
|
+
This module embeds the hook scripts as strings so `recall init` can
|
|
5
|
+
write them to ~/.config/dev-recall/.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
ZSH_HOOK = r'''# Recall shell hook — source this in ~/.zshrc
|
|
9
|
+
# Installed by: recall init
|
|
10
|
+
|
|
11
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/dev-recall}" {
|
|
12
|
+
__devrecall_cmd="$1"
|
|
13
|
+
__devrecall_start_ms=$(( $(date +%s) * 1000 ))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
__devrecall_precmd() {
|
|
17
|
+
local exit_code=$?
|
|
18
|
+
[[ -z "$__devrecall_cmd" ]] && return
|
|
19
|
+
|
|
20
|
+
local end_ms=$(( $(date +%s) * 1000 ))
|
|
21
|
+
local dur=$(( end_ms - __devrecall_start_ms ))
|
|
22
|
+
local ts
|
|
23
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
24
|
+
|
|
25
|
+
# Tab-separated: timestamp TAB cwd TAB command TAB exit_code TAB duration_ms
|
|
26
|
+
# Tabs inside cmd/cwd are replaced with space to avoid ambiguity
|
|
27
|
+
local safe_cmd="${__devrecall_cmd//$'\t'/ }"
|
|
28
|
+
local safe_cwd="${PWD//$'\t'/ }"
|
|
29
|
+
|
|
30
|
+
printf '%s\t%s\t%s\t%d\t%d\n' \
|
|
31
|
+
"$ts" "$safe_cwd" "$safe_cmd" "$exit_code" "$dur" \
|
|
32
|
+
>> "$__devrecall_shell_log" 2>/dev/null
|
|
33
|
+
|
|
34
|
+
unset __devrecall_cmd
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
autoload -Uz add-zsh-hook
|
|
38
|
+
add-zsh-hook preexec __devrecall_preexec
|
|
39
|
+
add-zsh-hook precmd __devrecall_precmd
|
|
40
|
+
'''
|
|
41
|
+
|
|
42
|
+
BASH_HOOK = r'''# Recall shell hook — source this in ~/.bashrc
|
|
43
|
+
# Installed by: recall init
|
|
44
|
+
|
|
45
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/dev-recall}"
|
|
46
|
+
__devrecall_start_ms=0
|
|
47
|
+
|
|
48
|
+
# trap DEBUG fires before each command executes
|
|
49
|
+
__devrecall_debug_trap() {
|
|
50
|
+
# BASH_COMMAND is set to the command string before it runs.
|
|
51
|
+
# Skip the trap when it fires inside PROMPT_COMMAND itself.
|
|
52
|
+
if [[ "$BASH_COMMAND" != "__devrecall_precmd"* ]]; then
|
|
53
|
+
__devrecall_cmd="$BASH_COMMAND"
|
|
54
|
+
__devrecall_start_ms=$(( $(date +%s) * 1000 ))
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
trap '__devrecall_debug_trap' DEBUG
|
|
58
|
+
|
|
59
|
+
__devrecall_precmd() {
|
|
60
|
+
local exit_code=$?
|
|
61
|
+
[[ -z "$__devrecall_cmd" ]] && return
|
|
62
|
+
|
|
63
|
+
local end_ms
|
|
64
|
+
end_ms=$(( $(date +%s) * 1000 ))
|
|
65
|
+
local dur=$(( end_ms - __devrecall_start_ms ))
|
|
66
|
+
local ts
|
|
67
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
68
|
+
|
|
69
|
+
# Replace tabs with spaces to keep TSV well-formed
|
|
70
|
+
local safe_cmd="${__devrecall_cmd//$'\t'/ }"
|
|
71
|
+
local safe_cwd="${PWD//$'\t'/ }"
|
|
72
|
+
|
|
73
|
+
printf '%s\t%s\t%s\t%d\t%d\n' \
|
|
74
|
+
"$ts" "$safe_cwd" "$safe_cmd" "$exit_code" "$dur" \
|
|
75
|
+
>> "$__devrecall_shell_log" 2>/dev/null
|
|
76
|
+
|
|
77
|
+
__devrecall_cmd=""
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Append to PROMPT_COMMAND — preserve existing hooks
|
|
81
|
+
if [[ -z "$PROMPT_COMMAND" ]]; then
|
|
82
|
+
PROMPT_COMMAND="__devrecall_precmd"
|
|
83
|
+
elif [[ "$PROMPT_COMMAND" != *"__devrecall_precmd"* ]]; then
|
|
84
|
+
PROMPT_COMMAND="${PROMPT_COMMAND};__devrecall_precmd"
|
|
85
|
+
fi
|
|
86
|
+
'''
|
|
87
|
+
|
|
88
|
+
GIT_POST_COMMIT = r'''#!/bin/sh
|
|
89
|
+
# Recall git post-commit hook
|
|
90
|
+
# Installed globally via: git config --global core.hooksPath ~/.config/dev-recall/git-hooks/
|
|
91
|
+
|
|
92
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/devmem}"
|
|
93
|
+
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
94
|
+
HASH=$(git rev-parse HEAD 2>/dev/null) || exit 0
|
|
95
|
+
MSG=$(git log -1 --format="%s" 2>/dev/null)
|
|
96
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
97
|
+
FILES=$(git diff-tree --no-commit-id -r --name-only HEAD 2>/dev/null | tr '\n' '|' | sed 's/|$//')
|
|
98
|
+
AUTHOR=$(git log -1 --format="%an" 2>/dev/null)
|
|
99
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
100
|
+
|
|
101
|
+
printf '%s\tcommit\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
102
|
+
"$TS" "$REPO_PATH" "$HASH" "$BRANCH" "$MSG" "$FILES" "$AUTHOR" \
|
|
103
|
+
>> "$__devrecall_dir/git.tsv" 2>/dev/null
|
|
104
|
+
|
|
105
|
+
exit 0
|
|
106
|
+
'''
|
|
107
|
+
|
|
108
|
+
GIT_POST_CHECKOUT = r'''#!/bin/sh
|
|
109
|
+
# Recall git post-checkout hook
|
|
110
|
+
# $1=prev ref, $2=new ref, $3=flag (1=branch, 0=file checkout)
|
|
111
|
+
|
|
112
|
+
# Only record branch switches, not file checkouts
|
|
113
|
+
[ "$3" = "1" ] || exit 0
|
|
114
|
+
|
|
115
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/devmem}"
|
|
116
|
+
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
117
|
+
NEW_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
118
|
+
OLD_BRANCH=$(git name-rev --name-only "$1" 2>/dev/null || echo "unknown")
|
|
119
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
120
|
+
|
|
121
|
+
printf '%s\tbranch\t%s\t%s\t%s\n' \
|
|
122
|
+
"$TS" "$REPO_PATH" "$OLD_BRANCH" "$NEW_BRANCH" \
|
|
123
|
+
>> "$__devrecall_dir/git.tsv" 2>/dev/null
|
|
124
|
+
|
|
125
|
+
exit 0
|
|
126
|
+
'''
|
|
127
|
+
|
|
128
|
+
GIT_PRE_PUSH = r'''#!/bin/sh
|
|
129
|
+
# Recall git pre-push hook
|
|
130
|
+
# Installed globally via: git config --global core.hooksPath ~/.config/dev-recall/git-hooks/
|
|
131
|
+
|
|
132
|
+
REMOTE="$1"
|
|
133
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/devmem}"
|
|
134
|
+
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
135
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
136
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
137
|
+
|
|
138
|
+
COMMIT_COUNT=0
|
|
139
|
+
while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
|
|
140
|
+
[ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue
|
|
141
|
+
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
|
|
142
|
+
COUNT=$(git rev-list "$local_sha" --count 2>/dev/null || echo 0)
|
|
143
|
+
else
|
|
144
|
+
COUNT=$(git rev-list "${remote_sha}..${local_sha}" --count 2>/dev/null || echo 0)
|
|
145
|
+
fi
|
|
146
|
+
COMMIT_COUNT=$(( COMMIT_COUNT + COUNT ))
|
|
147
|
+
done
|
|
148
|
+
|
|
149
|
+
printf '%s\tpush\t%s\t%s\t%s\t%d\n' \
|
|
150
|
+
"$TS" "$REPO_PATH" "$REMOTE" "$BRANCH" "$COMMIT_COUNT" \
|
|
151
|
+
>> "$__devrecall_dir/git.tsv" 2>/dev/null
|
|
152
|
+
|
|
153
|
+
exit 0
|
|
154
|
+
'''
|
|
155
|
+
|
|
156
|
+
GIT_POST_MERGE = r'''#!/bin/sh
|
|
157
|
+
# Recall git post-merge hook
|
|
158
|
+
# $1=1 if squash merge, 0 otherwise
|
|
159
|
+
|
|
160
|
+
IS_SQUASH="${1:-0}"
|
|
161
|
+
__devrecall_dir="${DEV_RECALL_DATA_DIR:-$HOME/.local/share/devmem}"
|
|
162
|
+
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
163
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
164
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
165
|
+
|
|
166
|
+
MERGED_BRANCH=$(git reflog show --format="%gs" -1 HEAD 2>/dev/null \
|
|
167
|
+
| sed -n "s/^merge //p" | head -1)
|
|
168
|
+
|
|
169
|
+
printf '%s\tmerge\t%s\t%s\t%s\t%s\n' \
|
|
170
|
+
"$TS" "$REPO_PATH" "$BRANCH" "$MERGED_BRANCH" "$IS_SQUASH" \
|
|
171
|
+
>> "$__devrecall_dir/git.tsv" 2>/dev/null
|
|
172
|
+
|
|
173
|
+
exit 0
|
|
174
|
+
'''
|
|
175
|
+
|
|
176
|
+
FISH_HOOK = r'''# Recall shell hook — source this in ~/.config/fish/config.fish
|
|
177
|
+
# Installed by: recall init
|
|
178
|
+
|
|
179
|
+
set -g __devrecall_dir (set -q DEV_RECALL_DATA_DIR; and echo $DEV_RECALL_DATA_DIR; or echo "$HOME/.local/share/devmem")
|
|
180
|
+
set -g __devrecall_shell_log "$__devrecall_dir/shell.tsv"
|
|
181
|
+
set -g __devrecall_cmd ""
|
|
182
|
+
set -g __devrecall_start_ms 0
|
|
183
|
+
|
|
184
|
+
function __devrecall_preexec --on-event fish_preexec
|
|
185
|
+
set -g __devrecall_cmd $argv[1]
|
|
186
|
+
set -g __devrecall_start_ms (math (date +%s) \* 1000)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
function __devrecall_postexec --on-event fish_postexec
|
|
190
|
+
set cmd $argv[1]
|
|
191
|
+
set exit_code 0
|
|
192
|
+
if test (count $argv) -ge 2
|
|
193
|
+
set exit_code $argv[2]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
test -z "$__devrecall_cmd"; and return
|
|
197
|
+
|
|
198
|
+
set end_ms (math (date +%s) \* 1000)
|
|
199
|
+
set dur (math $end_ms - $__devrecall_start_ms)
|
|
200
|
+
set ts (date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
201
|
+
|
|
202
|
+
set safe_cmd (string replace --all \t ' ' "$__devrecall_cmd")
|
|
203
|
+
set safe_cwd (string replace --all \t ' ' "$PWD")
|
|
204
|
+
|
|
205
|
+
printf '%s\t%s\t%s\t%d\t%d\n' \
|
|
206
|
+
"$ts" "$safe_cwd" "$safe_cmd" "$exit_code" "$dur" \
|
|
207
|
+
>> "$__devrecall_shell_log" 2>/dev/null
|
|
208
|
+
|
|
209
|
+
set -g __devrecall_cmd ""
|
|
210
|
+
end
|
|
211
|
+
'''
|