msgsearch 0.2.2__tar.gz → 0.2.4__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.
- {msgsearch-0.2.2 → msgsearch-0.2.4}/CHANGELOG.md +22 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/PKG-INFO +41 -30
- {msgsearch-0.2.2 → msgsearch-0.2.4}/README.md +40 -29
- {msgsearch-0.2.2 → msgsearch-0.2.4}/pyproject.toml +1 -1
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/__init__.py +1 -1
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/cli.py +6 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/agents/corpus-explorer.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/agents/retrieval-critic.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/commands/bench.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/commands/go.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/commands/recon.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/hooks/verify-retrieval.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.claude/settings.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.github/workflows/ci.yml +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.github/workflows/release.yml +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/.gitignore +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/AGENTS.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/ARCHITECTURE.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/CLAUDE.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/CONTRIBUTING.md +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/LICENSE +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/__init__.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/bench.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/gold.jsonl +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/label.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/results/baseline.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/results/retriever.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/results/retriever_bm25.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/results/retriever_dense.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/results/retriever_norerank.json +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/retriever.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/retriever_bm25.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/retriever_dense.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/eval/retriever_norerank.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/attributed_body.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/chunk.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/config.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/contacts.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/doctor.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/embedder.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/explore.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/extract.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/index.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/py.typed +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/search.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/sync.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/src/msgsearch/tagging.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_chunk.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_cli.py +4 -4
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_contacts.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_index.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_search.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_sync.py +0 -0
- {msgsearch-0.2.2 → msgsearch-0.2.4}/tests/test_tagging.py +0 -0
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
Notable changes to msgsearch. Retrieval changes carry the measurement that
|
|
4
4
|
justified them; see `ARCHITECTURE.md` for the full evaluation.
|
|
5
5
|
|
|
6
|
+
## [0.2.4] — 2026-09-06
|
|
7
|
+
|
|
8
|
+
Documentation only; released because PyPI freezes a project's description at
|
|
9
|
+
publish time.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- The Setup section explained the gated model twice, and the second copy used a
|
|
13
|
+
development path (`./.venv/bin/msgsearch`) that an installed user does not have.
|
|
14
|
+
- Model access is now spelled out step by step: create an account, accept the
|
|
15
|
+
Gemma licence, create a **Read** token, run `msgsearch login`. It previously
|
|
16
|
+
assumed a HuggingFace account already existed and did not say which kind of
|
|
17
|
+
token to make.
|
|
18
|
+
- States explicitly that the model downloads automatically on first index, about
|
|
19
|
+
1.2 GB, so nobody goes looking for a download step that does not exist.
|
|
20
|
+
|
|
21
|
+
## [0.2.3] — 2026-09-06
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Model-loading failures printed a Python traceback around their instructions,
|
|
25
|
+
which reads as a crash rather than "you need to log in" and buries the steps
|
|
26
|
+
under a stack. They now print the guidance alone and exit non-zero.
|
|
27
|
+
|
|
6
28
|
## [0.2.2] — 2026-09-06
|
|
7
29
|
|
|
8
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: msgsearch
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Search your iMessage history by meaning, entirely on your own machine.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dhruv1707/msgsearch
|
|
6
6
|
Project-URL: Documentation, https://github.com/dhruv1707/msgsearch/blob/main/README.md
|
|
@@ -168,49 +168,60 @@ arch -arm64 python3 -m venv .venv # must be an arm64 interpreter
|
|
|
168
168
|
|
|
169
169
|
## Setup
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
### 1. Model access
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
3. Run `msgsearch login` and paste it
|
|
173
|
+
The default embedding model, `google/embeddinggemma-300m`, is gated: Google
|
|
174
|
+
requires you to accept its licence before downloading. This is a one-time thing.
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
1. **Create a HuggingFace account** if you do not have one — <https://huggingface.co/join>. Free.
|
|
177
|
+
2. **Accept the licence.** Open <https://huggingface.co/google/embeddinggemma-300m>,
|
|
178
|
+
sign in, and click the button acknowledging the Gemma terms at the top of the
|
|
179
|
+
page. Approval is normally immediate.
|
|
180
|
+
3. **Create a token.** Go to <https://huggingface.co/settings/tokens>, click
|
|
181
|
+
*Create new token*, give it the **Read** role, and copy it. It looks like
|
|
182
|
+
`hf_...`.
|
|
183
|
+
4. **Log in:**
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
```bash
|
|
186
|
+
msgsearch login
|
|
187
|
+
```
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
Paste the token when prompted. It is stored in `~/.cache/huggingface`, not by
|
|
190
|
+
msgsearch. You can also pass it directly with `msgsearch login --token hf_...`.
|
|
191
|
+
|
|
192
|
+
`msgsearch login` checks afterwards that the model is genuinely reachable, rather
|
|
193
|
+
than only that the token is valid. Those are different conditions — you can be
|
|
194
|
+
perfectly logged in and still be refused because step 2 was skipped — and
|
|
195
|
+
HuggingFace reports both as the same 401 saying "please log in", which sends
|
|
196
|
+
people back to re-do the step they already did.
|
|
197
|
+
|
|
198
|
+
The model itself downloads automatically the first time you index, about 1.2 GB,
|
|
199
|
+
cached in `~/.cache/huggingface`. There is no separate download step.
|
|
200
|
+
|
|
201
|
+
**Don't want a HuggingFace account?** Use an ungated model instead. Retrieval is
|
|
202
|
+
somewhat weaker, but nothing else changes:
|
|
187
203
|
|
|
188
204
|
```bash
|
|
189
|
-
|
|
205
|
+
export MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5
|
|
190
206
|
```
|
|
191
207
|
|
|
192
|
-
|
|
193
|
-
command (System Settings → Privacy & Security → Full Disk Access). A terminal
|
|
194
|
-
usually has it; an editor's integrated terminal often does not.
|
|
195
|
-
|
|
196
|
-
It uses SQLite's backup API rather than `cp`, which matters more than it sounds.
|
|
197
|
-
Messages runs in WAL mode, so your most recent messages live in a `chat.db-wal`
|
|
198
|
-
sidecar until they are checkpointed — `cp chat.db` alone loses exactly the
|
|
199
|
-
messages you are most likely to search for, silently. The snapshot folds the
|
|
200
|
-
write-ahead log in and leaves a single self-contained file.
|
|
208
|
+
### 2. Snapshot your messages
|
|
201
209
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<https://huggingface.co/google/embeddinggemma-300m> and authenticate:
|
|
210
|
+
**Never point this tool at `~/Library/Messages`** — that file is live, Messages
|
|
211
|
+
holds locks on it, and it is irreplaceable.
|
|
205
212
|
|
|
206
213
|
```bash
|
|
207
|
-
|
|
214
|
+
msgsearch sync
|
|
208
215
|
```
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
`
|
|
217
|
+
This needs Full Disk Access (see above for how to grant it). It uses SQLite's
|
|
218
|
+
backup API rather than `cp`, which matters more than it sounds: Messages runs in
|
|
219
|
+
WAL mode, so your most recent messages live in a `chat.db-wal` sidecar until they
|
|
220
|
+
are checkpointed. `cp chat.db` alone drops exactly the messages you are most
|
|
221
|
+
likely to search for, silently. The snapshot folds the write-ahead log in and
|
|
222
|
+
leaves a single self-contained file.
|
|
212
223
|
|
|
213
|
-
### Names
|
|
224
|
+
### 3. Names (optional)
|
|
214
225
|
|
|
215
226
|
Without names, speakers appear as phone numbers. Resolving them makes results
|
|
216
227
|
readable *and* improves retrieval, because the speaker label is part of the text
|
|
@@ -122,49 +122,60 @@ arch -arm64 python3 -m venv .venv # must be an arm64 interpreter
|
|
|
122
122
|
|
|
123
123
|
## Setup
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
### 1. Model access
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
3. Run `msgsearch login` and paste it
|
|
127
|
+
The default embedding model, `google/embeddinggemma-300m`, is gated: Google
|
|
128
|
+
requires you to accept its licence before downloading. This is a one-time thing.
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
1. **Create a HuggingFace account** if you do not have one — <https://huggingface.co/join>. Free.
|
|
131
|
+
2. **Accept the licence.** Open <https://huggingface.co/google/embeddinggemma-300m>,
|
|
132
|
+
sign in, and click the button acknowledging the Gemma terms at the top of the
|
|
133
|
+
page. Approval is normally immediate.
|
|
134
|
+
3. **Create a token.** Go to <https://huggingface.co/settings/tokens>, click
|
|
135
|
+
*Create new token*, give it the **Read** role, and copy it. It looks like
|
|
136
|
+
`hf_...`.
|
|
137
|
+
4. **Log in:**
|
|
134
138
|
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
```bash
|
|
140
|
+
msgsearch login
|
|
141
|
+
```
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
Paste the token when prompted. It is stored in `~/.cache/huggingface`, not by
|
|
144
|
+
msgsearch. You can also pass it directly with `msgsearch login --token hf_...`.
|
|
145
|
+
|
|
146
|
+
`msgsearch login` checks afterwards that the model is genuinely reachable, rather
|
|
147
|
+
than only that the token is valid. Those are different conditions — you can be
|
|
148
|
+
perfectly logged in and still be refused because step 2 was skipped — and
|
|
149
|
+
HuggingFace reports both as the same 401 saying "please log in", which sends
|
|
150
|
+
people back to re-do the step they already did.
|
|
151
|
+
|
|
152
|
+
The model itself downloads automatically the first time you index, about 1.2 GB,
|
|
153
|
+
cached in `~/.cache/huggingface`. There is no separate download step.
|
|
154
|
+
|
|
155
|
+
**Don't want a HuggingFace account?** Use an ungated model instead. Retrieval is
|
|
156
|
+
somewhat weaker, but nothing else changes:
|
|
141
157
|
|
|
142
158
|
```bash
|
|
143
|
-
|
|
159
|
+
export MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5
|
|
144
160
|
```
|
|
145
161
|
|
|
146
|
-
|
|
147
|
-
command (System Settings → Privacy & Security → Full Disk Access). A terminal
|
|
148
|
-
usually has it; an editor's integrated terminal often does not.
|
|
149
|
-
|
|
150
|
-
It uses SQLite's backup API rather than `cp`, which matters more than it sounds.
|
|
151
|
-
Messages runs in WAL mode, so your most recent messages live in a `chat.db-wal`
|
|
152
|
-
sidecar until they are checkpointed — `cp chat.db` alone loses exactly the
|
|
153
|
-
messages you are most likely to search for, silently. The snapshot folds the
|
|
154
|
-
write-ahead log in and leaves a single self-contained file.
|
|
162
|
+
### 2. Snapshot your messages
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<https://huggingface.co/google/embeddinggemma-300m> and authenticate:
|
|
164
|
+
**Never point this tool at `~/Library/Messages`** — that file is live, Messages
|
|
165
|
+
holds locks on it, and it is irreplaceable.
|
|
159
166
|
|
|
160
167
|
```bash
|
|
161
|
-
|
|
168
|
+
msgsearch sync
|
|
162
169
|
```
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
`
|
|
171
|
+
This needs Full Disk Access (see above for how to grant it). It uses SQLite's
|
|
172
|
+
backup API rather than `cp`, which matters more than it sounds: Messages runs in
|
|
173
|
+
WAL mode, so your most recent messages live in a `chat.db-wal` sidecar until they
|
|
174
|
+
are checkpointed. `cp chat.db` alone drops exactly the messages you are most
|
|
175
|
+
likely to search for, silently. The snapshot folds the write-ahead log in and
|
|
176
|
+
leaves a single self-contained file.
|
|
166
177
|
|
|
167
|
-
### Names
|
|
178
|
+
### 3. Names (optional)
|
|
168
179
|
|
|
169
180
|
Without names, speakers appear as phone numbers. Resolving them makes results
|
|
170
181
|
readable *and* improves retrieval, because the speaker label is part of the text
|
|
@@ -398,6 +398,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
398
398
|
return args.handler(args)
|
|
399
399
|
except KeyboardInterrupt:
|
|
400
400
|
return 130
|
|
401
|
+
except RuntimeError as error:
|
|
402
|
+
# Model loading raises RuntimeError carrying instructions the user can
|
|
403
|
+
# act on. A traceback around them reads as "this crashed" rather than
|
|
404
|
+
# "you need to log in", and buries the steps under a stack.
|
|
405
|
+
print(error, file=sys.stderr)
|
|
406
|
+
return 1
|
|
401
407
|
|
|
402
408
|
|
|
403
409
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -73,10 +73,6 @@ class TestEntryPoint(unittest.TestCase):
|
|
|
73
73
|
cli.main(["nonsense"])
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
if __name__ == "__main__":
|
|
77
|
-
unittest.main()
|
|
78
|
-
|
|
79
|
-
|
|
80
76
|
class TestLoginCommand(unittest.TestCase):
|
|
81
77
|
"""Authentication is a msgsearch command because `hf` is not on the PATH.
|
|
82
78
|
|
|
@@ -98,3 +94,7 @@ class TestLoginCommand(unittest.TestCase):
|
|
|
98
94
|
|
|
99
95
|
def test_token_is_optional_so_it_can_prompt(self):
|
|
100
96
|
self.assertIsNone(self.parser.parse_args(["login"]).token)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|