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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ recall = recall.cli:cli
@@ -0,0 +1 @@
1
+ recall
recall/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Recall — Local-first developer memory layer."""
2
+
3
+ __version__ = "0.1.0"
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
+ '''