msgsearch 0.2.0__tar.gz → 0.2.2__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.0 → msgsearch-0.2.2}/ARCHITECTURE.md +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/CHANGELOG.md +34 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/PKG-INFO +101 -23
- {msgsearch-0.2.0 → msgsearch-0.2.2}/README.md +100 -22
- {msgsearch-0.2.0 → msgsearch-0.2.2}/pyproject.toml +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/__init__.py +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/cli.py +58 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/config.py +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/doctor.py +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/embedder.py +1 -1
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_cli.py +23 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/agents/corpus-explorer.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/agents/retrieval-critic.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/commands/bench.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/commands/go.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/commands/recon.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/hooks/verify-retrieval.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.claude/settings.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.github/workflows/ci.yml +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.github/workflows/release.yml +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/.gitignore +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/AGENTS.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/CLAUDE.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/CONTRIBUTING.md +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/LICENSE +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/__init__.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/bench.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/gold.jsonl +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/label.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/results/baseline.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/results/retriever.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/results/retriever_bm25.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/results/retriever_dense.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/results/retriever_norerank.json +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/retriever.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/retriever_bm25.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/retriever_dense.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/eval/retriever_norerank.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/attributed_body.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/chunk.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/contacts.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/explore.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/extract.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/index.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/py.typed +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/search.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/sync.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/src/msgsearch/tagging.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_chunk.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_contacts.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_index.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_search.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_sync.py +0 -0
- {msgsearch-0.2.0 → msgsearch-0.2.2}/tests/test_tagging.py +0 -0
|
@@ -125,7 +125,7 @@ Both run locally on Metal via MPS.
|
|
|
125
125
|
|
|
126
126
|
| role | model | notes |
|
|
127
127
|
|---|---|---|
|
|
128
|
-
| embedding | `google/embeddinggemma-300m` | 768-dim. **Gated** — needs Gemma licence acceptance and `
|
|
128
|
+
| embedding | `google/embeddinggemma-300m` | 768-dim. **Gated** — needs Gemma licence acceptance and `msgsearch login`. Matryoshka truncation to 512/256/128 available if the array grows. |
|
|
129
129
|
| rerank | `Qwen/Qwen3-Reranker-0.6B` | matches qmd's choice |
|
|
130
130
|
|
|
131
131
|
Rerankers measured over 50 candidates on the one query available:
|
|
@@ -3,6 +3,40 @@
|
|
|
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.2] — 2026-09-06
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `msgsearch login`, which authenticates with HuggingFace. The instructions
|
|
10
|
+
previously said to run `hf auth login`, but installing msgsearch does not put
|
|
11
|
+
`hf` on your PATH -- pipx exposes only the entry points a package declares, so
|
|
12
|
+
that step failed with "command not found" for everyone who installed normally.
|
|
13
|
+
It also verifies afterwards that the gated model is actually reachable, since
|
|
14
|
+
being logged in and having accepted the model licence are different things
|
|
15
|
+
whose failures look identical.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Full Disk Access instructions are now step by step, including that macOS grants
|
|
19
|
+
it to the application rather than the shell and that the application must be
|
|
20
|
+
restarted before it takes effect.
|
|
21
|
+
|
|
22
|
+
## [0.2.1] — 2026-09-06
|
|
23
|
+
|
|
24
|
+
Documentation only. PyPI freezes a project's description at publish time, so
|
|
25
|
+
correcting the README required a release.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- The install instructions recommended `pipx install --python <arm64 python>`,
|
|
29
|
+
which does not work on Apple silicon with an Intel Homebrew. Python from
|
|
30
|
+
python.org is a universal binary that runs as whichever architecture its
|
|
31
|
+
parent process is, so an Intel-built pipx launches it as x86_64 whichever
|
|
32
|
+
interpreter you name, and PyTorch has no x86_64 macOS wheels. The working
|
|
33
|
+
recipe creates the venv under `arch -arm64`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- A quickstart covering install through first search in one block, and an
|
|
37
|
+
explicit note that Full Disk Access and Contacts are separate permissions
|
|
38
|
+
granted to the application rather than the shell.
|
|
39
|
+
|
|
6
40
|
## [0.2.0] — 2026-09-06
|
|
7
41
|
|
|
8
42
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: msgsearch
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -63,46 +63,124 @@ up to make committing it difficult. Treat that directory the way you would treat
|
|
|
63
63
|
password manager's database. Both models run locally, so nothing is uploaded, but
|
|
64
64
|
what lands on your disk is unencrypted.
|
|
65
65
|
|
|
66
|
-
##
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. install (needs an arm64 Python — see Install if this errors)
|
|
70
|
+
pipx install msgsearch
|
|
71
|
+
|
|
72
|
+
# 2. get the embedding model (accept the licence in a browser first, then:)
|
|
73
|
+
msgsearch login
|
|
74
|
+
|
|
75
|
+
# 3. snapshot your messages [needs Full Disk Access]
|
|
76
|
+
msgsearch sync
|
|
77
|
+
|
|
78
|
+
# 4. check the setup, and fix whatever it names
|
|
79
|
+
msgsearch doctor
|
|
80
|
+
|
|
81
|
+
# 5. build the index [~30 min per 100k messages, once]
|
|
82
|
+
msgsearch index
|
|
83
|
+
|
|
84
|
+
# 6. search
|
|
85
|
+
msgsearch search "that restaurant we talked about"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Afterwards, one command keeps it current — only genuinely new text is embedded,
|
|
89
|
+
so it takes seconds:
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
on Apple silicon. Check with:
|
|
91
|
+
```bash
|
|
92
|
+
msgsearch sync --index
|
|
93
|
+
```
|
|
72
94
|
|
|
73
|
-
|
|
74
|
-
python3 -c "import platform; print(platform.machine())" # must say arm64
|
|
75
|
-
```
|
|
95
|
+
### Granting Full Disk Access
|
|
76
96
|
|
|
77
|
-
|
|
97
|
+
Step 3 fails without it. macOS grants this to the **application**, not to your
|
|
98
|
+
shell, so the grant goes to whatever program you type commands into.
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
1. Open **System Settings → Privacy & Security → Full Disk Access**
|
|
101
|
+
2. Click **+**
|
|
102
|
+
3. Press **⌘⇧G** and paste `/Applications/Utilities/Terminal.app`, then Open
|
|
103
|
+
(if you use iTerm, VS Code or another terminal, choose that instead)
|
|
104
|
+
4. Make sure its toggle is **on**
|
|
105
|
+
5. **Quit and reopen that application** — the permission is only read at launch
|
|
82
106
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
Resolving contact names needs a *separate* permission, **Contacts**, granted the
|
|
108
|
+
same way in the same place. It is optional; without it speakers appear as phone
|
|
109
|
+
numbers. Full Disk Access does not include it.
|
|
110
|
+
|
|
111
|
+
## Requirements
|
|
112
|
+
|
|
113
|
+
- **macOS on Apple silicon.** PyTorch no longer publishes x86_64 macOS wheels.
|
|
114
|
+
- **Python 3.10 or newer, running as arm64.** See the note below — this is the
|
|
115
|
+
one thing that reliably goes wrong.
|
|
86
116
|
- About 3 GB of disk for dependencies and model weights.
|
|
87
117
|
|
|
88
118
|
## Install
|
|
89
119
|
|
|
90
120
|
```bash
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
pipx install msgsearch
|
|
122
|
+
msgsearch doctor
|
|
93
123
|
```
|
|
94
124
|
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
This pulls PyTorch, around 1 GB, so the first install is slow. Everything after
|
|
126
|
+
that is local and fast.
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>If that fails with "No matching distribution found for torch"</b></summary>
|
|
97
130
|
|
|
98
|
-
|
|
131
|
+
You have an Intel-built Python, which is common on Apple silicon after migrating
|
|
132
|
+
from an Intel Mac. Check:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python3 -c "import platform; print(platform.machine())" # must say arm64
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The confusing part is that `pipx install --python /path/to/arm64/python` often
|
|
139
|
+
**does not fix it**. Python from python.org is a *universal* binary that runs as
|
|
140
|
+
whichever architecture its parent process is, and if pipx itself was installed by
|
|
141
|
+
an Intel Homebrew (`/usr/local/...`), it launches that Python as x86_64. pip then
|
|
142
|
+
looks for x86_64 wheels that PyTorch does not publish.
|
|
143
|
+
|
|
144
|
+
Install into a venv created explicitly under arm64 instead:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
arch -arm64 /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 \
|
|
148
|
+
-m venv ~/.msgsearch-venv
|
|
149
|
+
~/.msgsearch-venv/bin/pip install msgsearch
|
|
150
|
+
ln -sf ~/.msgsearch-venv/bin/msgsearch /usr/local/bin/msgsearch
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Adjust the interpreter path to any arm64 Python 3.10+. To undo:
|
|
154
|
+
`rm /usr/local/bin/msgsearch && rm -rf ~/.msgsearch-venv`.
|
|
155
|
+
|
|
156
|
+
Architecture cannot be expressed in package metadata, which is why this surfaces
|
|
157
|
+
as a dependency-resolution wall rather than a useful error. `msgsearch doctor`
|
|
158
|
+
reports it in one line.
|
|
159
|
+
</details>
|
|
160
|
+
|
|
161
|
+
To work on msgsearch rather than use it:
|
|
99
162
|
|
|
100
163
|
```bash
|
|
101
164
|
git clone https://github.com/dhruv1707/msgsearch && cd msgsearch
|
|
102
|
-
python3 -m venv .venv
|
|
165
|
+
arch -arm64 python3 -m venv .venv # must be an arm64 interpreter
|
|
103
166
|
./.venv/bin/python -m pip install -e ".[dev]"
|
|
104
167
|
```
|
|
105
168
|
|
|
169
|
+
## Setup
|
|
170
|
+
|
|
171
|
+
Get access to the embedding model. `google/embeddinggemma-300m` is gated, so:
|
|
172
|
+
|
|
173
|
+
1. Accept the licence at <https://huggingface.co/google/embeddinggemma-300m>
|
|
174
|
+
2. Create a read token at <https://huggingface.co/settings/tokens>
|
|
175
|
+
3. Run `msgsearch login` and paste it
|
|
176
|
+
|
|
177
|
+
`msgsearch login` checks afterwards that you can actually reach the model, since
|
|
178
|
+
being logged in and having accepted the licence are different things — and the
|
|
179
|
+
failure for the second looks identical to the first.
|
|
180
|
+
|
|
181
|
+
Any sentence-transformers model works instead, for example
|
|
182
|
+
`MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5`.
|
|
183
|
+
|
|
106
184
|
Take a snapshot of the Messages database. **Never point this tool at
|
|
107
185
|
`~/Library/Messages`**: that file is live, Messages.app holds locks on it, and it
|
|
108
186
|
is irreplaceable.
|
|
@@ -126,7 +204,7 @@ accept the Gemma licence at
|
|
|
126
204
|
<https://huggingface.co/google/embeddinggemma-300m> and authenticate:
|
|
127
205
|
|
|
128
206
|
```bash
|
|
129
|
-
./.venv/bin/
|
|
207
|
+
./.venv/bin/msgsearch login
|
|
130
208
|
```
|
|
131
209
|
|
|
132
210
|
Any sentence-transformers model works instead if you would rather not, for example
|
|
@@ -17,46 +17,124 @@ up to make committing it difficult. Treat that directory the way you would treat
|
|
|
17
17
|
password manager's database. Both models run locally, so nothing is uploaded, but
|
|
18
18
|
what lands on your disk is unencrypted.
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 1. install (needs an arm64 Python — see Install if this errors)
|
|
24
|
+
pipx install msgsearch
|
|
25
|
+
|
|
26
|
+
# 2. get the embedding model (accept the licence in a browser first, then:)
|
|
27
|
+
msgsearch login
|
|
28
|
+
|
|
29
|
+
# 3. snapshot your messages [needs Full Disk Access]
|
|
30
|
+
msgsearch sync
|
|
31
|
+
|
|
32
|
+
# 4. check the setup, and fix whatever it names
|
|
33
|
+
msgsearch doctor
|
|
34
|
+
|
|
35
|
+
# 5. build the index [~30 min per 100k messages, once]
|
|
36
|
+
msgsearch index
|
|
37
|
+
|
|
38
|
+
# 6. search
|
|
39
|
+
msgsearch search "that restaurant we talked about"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Afterwards, one command keeps it current — only genuinely new text is embedded,
|
|
43
|
+
so it takes seconds:
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
on Apple silicon. Check with:
|
|
45
|
+
```bash
|
|
46
|
+
msgsearch sync --index
|
|
47
|
+
```
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
python3 -c "import platform; print(platform.machine())" # must say arm64
|
|
29
|
-
```
|
|
49
|
+
### Granting Full Disk Access
|
|
30
50
|
|
|
31
|
-
|
|
51
|
+
Step 3 fails without it. macOS grants this to the **application**, not to your
|
|
52
|
+
shell, so the grant goes to whatever program you type commands into.
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
1. Open **System Settings → Privacy & Security → Full Disk Access**
|
|
55
|
+
2. Click **+**
|
|
56
|
+
3. Press **⌘⇧G** and paste `/Applications/Utilities/Terminal.app`, then Open
|
|
57
|
+
(if you use iTerm, VS Code or another terminal, choose that instead)
|
|
58
|
+
4. Make sure its toggle is **on**
|
|
59
|
+
5. **Quit and reopen that application** — the permission is only read at launch
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
Resolving contact names needs a *separate* permission, **Contacts**, granted the
|
|
62
|
+
same way in the same place. It is optional; without it speakers appear as phone
|
|
63
|
+
numbers. Full Disk Access does not include it.
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- **macOS on Apple silicon.** PyTorch no longer publishes x86_64 macOS wheels.
|
|
68
|
+
- **Python 3.10 or newer, running as arm64.** See the note below — this is the
|
|
69
|
+
one thing that reliably goes wrong.
|
|
40
70
|
- About 3 GB of disk for dependencies and model weights.
|
|
41
71
|
|
|
42
72
|
## Install
|
|
43
73
|
|
|
44
74
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
pipx install msgsearch
|
|
76
|
+
msgsearch doctor
|
|
47
77
|
```
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
This pulls PyTorch, around 1 GB, so the first install is slow. Everything after
|
|
80
|
+
that is local and fast.
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary><b>If that fails with "No matching distribution found for torch"</b></summary>
|
|
51
84
|
|
|
52
|
-
|
|
85
|
+
You have an Intel-built Python, which is common on Apple silicon after migrating
|
|
86
|
+
from an Intel Mac. Check:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python3 -c "import platform; print(platform.machine())" # must say arm64
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The confusing part is that `pipx install --python /path/to/arm64/python` often
|
|
93
|
+
**does not fix it**. Python from python.org is a *universal* binary that runs as
|
|
94
|
+
whichever architecture its parent process is, and if pipx itself was installed by
|
|
95
|
+
an Intel Homebrew (`/usr/local/...`), it launches that Python as x86_64. pip then
|
|
96
|
+
looks for x86_64 wheels that PyTorch does not publish.
|
|
97
|
+
|
|
98
|
+
Install into a venv created explicitly under arm64 instead:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
arch -arm64 /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 \
|
|
102
|
+
-m venv ~/.msgsearch-venv
|
|
103
|
+
~/.msgsearch-venv/bin/pip install msgsearch
|
|
104
|
+
ln -sf ~/.msgsearch-venv/bin/msgsearch /usr/local/bin/msgsearch
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Adjust the interpreter path to any arm64 Python 3.10+. To undo:
|
|
108
|
+
`rm /usr/local/bin/msgsearch && rm -rf ~/.msgsearch-venv`.
|
|
109
|
+
|
|
110
|
+
Architecture cannot be expressed in package metadata, which is why this surfaces
|
|
111
|
+
as a dependency-resolution wall rather than a useful error. `msgsearch doctor`
|
|
112
|
+
reports it in one line.
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
To work on msgsearch rather than use it:
|
|
53
116
|
|
|
54
117
|
```bash
|
|
55
118
|
git clone https://github.com/dhruv1707/msgsearch && cd msgsearch
|
|
56
|
-
python3 -m venv .venv
|
|
119
|
+
arch -arm64 python3 -m venv .venv # must be an arm64 interpreter
|
|
57
120
|
./.venv/bin/python -m pip install -e ".[dev]"
|
|
58
121
|
```
|
|
59
122
|
|
|
123
|
+
## Setup
|
|
124
|
+
|
|
125
|
+
Get access to the embedding model. `google/embeddinggemma-300m` is gated, so:
|
|
126
|
+
|
|
127
|
+
1. Accept the licence at <https://huggingface.co/google/embeddinggemma-300m>
|
|
128
|
+
2. Create a read token at <https://huggingface.co/settings/tokens>
|
|
129
|
+
3. Run `msgsearch login` and paste it
|
|
130
|
+
|
|
131
|
+
`msgsearch login` checks afterwards that you can actually reach the model, since
|
|
132
|
+
being logged in and having accepted the licence are different things — and the
|
|
133
|
+
failure for the second looks identical to the first.
|
|
134
|
+
|
|
135
|
+
Any sentence-transformers model works instead, for example
|
|
136
|
+
`MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5`.
|
|
137
|
+
|
|
60
138
|
Take a snapshot of the Messages database. **Never point this tool at
|
|
61
139
|
`~/Library/Messages`**: that file is live, Messages.app holds locks on it, and it
|
|
62
140
|
is irreplaceable.
|
|
@@ -80,7 +158,7 @@ accept the Gemma licence at
|
|
|
80
158
|
<https://huggingface.co/google/embeddinggemma-300m> and authenticate:
|
|
81
159
|
|
|
82
160
|
```bash
|
|
83
|
-
./.venv/bin/
|
|
161
|
+
./.venv/bin/msgsearch login
|
|
84
162
|
```
|
|
85
163
|
|
|
86
164
|
Any sentence-transformers model works instead if you would rather not, for example
|
|
@@ -156,6 +156,63 @@ def _run_search(args) -> int:
|
|
|
156
156
|
return 0
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
def _add_login_command(subparsers) -> None:
|
|
160
|
+
parser = subparsers.add_parser(
|
|
161
|
+
"login",
|
|
162
|
+
help="authenticate with HuggingFace to download the embedding model",
|
|
163
|
+
description="The default embedding model is gated, so it needs a "
|
|
164
|
+
"HuggingFace account that has accepted its licence. This wraps the same "
|
|
165
|
+
"login the huggingface_hub library provides -- msgsearch owns the "
|
|
166
|
+
"command because installing msgsearch does not put `hf` on your PATH.",
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--token",
|
|
170
|
+
metavar="TOKEN",
|
|
171
|
+
help="read token from https://huggingface.co/settings/tokens "
|
|
172
|
+
"(prompted for if omitted)",
|
|
173
|
+
)
|
|
174
|
+
parser.set_defaults(handler=_run_login)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _run_login(args) -> int:
|
|
178
|
+
from getpass import getpass
|
|
179
|
+
|
|
180
|
+
from huggingface_hub import login
|
|
181
|
+
|
|
182
|
+
from . import config
|
|
183
|
+
|
|
184
|
+
token = args.token
|
|
185
|
+
if not token:
|
|
186
|
+
print(f"A read token is needed to download {config.EMBED_MODEL}.")
|
|
187
|
+
print("Create one at https://huggingface.co/settings/tokens")
|
|
188
|
+
print(f"and accept the licence at https://huggingface.co/{config.EMBED_MODEL}")
|
|
189
|
+
try:
|
|
190
|
+
token = getpass("\nToken (input hidden): ").strip()
|
|
191
|
+
except (EOFError, KeyboardInterrupt):
|
|
192
|
+
print()
|
|
193
|
+
return 130
|
|
194
|
+
if not token:
|
|
195
|
+
print("No token given.", file=sys.stderr)
|
|
196
|
+
return 1
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
login(token=token)
|
|
200
|
+
except Exception as error:
|
|
201
|
+
print(f"Login failed: {error}", file=sys.stderr)
|
|
202
|
+
return 1
|
|
203
|
+
|
|
204
|
+
# Logging in successfully is not the same as having access to a gated model,
|
|
205
|
+
# and that difference is exactly what produces a baffling 401 later.
|
|
206
|
+
from .doctor import check_model
|
|
207
|
+
|
|
208
|
+
result = check_model()
|
|
209
|
+
print(f"\nlogged in. model: {result.detail}")
|
|
210
|
+
if result.fix:
|
|
211
|
+
print(result.fix)
|
|
212
|
+
return 1
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
|
|
159
216
|
def _add_contacts_command(subparsers) -> None:
|
|
160
217
|
parser = subparsers.add_parser(
|
|
161
218
|
"contacts",
|
|
@@ -325,6 +382,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
325
382
|
_add_doctor_command(subparsers)
|
|
326
383
|
_add_explore_command(subparsers)
|
|
327
384
|
_add_index_command(subparsers)
|
|
385
|
+
_add_login_command(subparsers)
|
|
328
386
|
_add_search_command(subparsers)
|
|
329
387
|
_add_sync_command(subparsers)
|
|
330
388
|
return parser
|
|
@@ -76,7 +76,7 @@ PASSAGE_STRIDE = 1
|
|
|
76
76
|
|
|
77
77
|
# --- models ------------------------------------------------------------------
|
|
78
78
|
|
|
79
|
-
# Gated on HuggingFace: accept the model terms and `
|
|
79
|
+
# Gated on HuggingFace: accept the model terms and run `msgsearch login` once,
|
|
80
80
|
# 401s. Must match the model the index was built with -- search.py checks.
|
|
81
81
|
EMBED_MODEL = os.environ.get("MSGSEARCH_EMBED_MODEL", "google/embeddinggemma-300m")
|
|
82
82
|
|
|
@@ -124,7 +124,7 @@ def check_model() -> Check:
|
|
|
124
124
|
FAIL,
|
|
125
125
|
f"{name} is gated and this machine is not authorised",
|
|
126
126
|
f"Accept the licence at https://huggingface.co/{name}, then run "
|
|
127
|
-
"'
|
|
127
|
+
"'msgsearch login'. An expired token gives this same error, so log in "
|
|
128
128
|
"again even if you have before. Or set "
|
|
129
129
|
"MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5 to use an ungated model.",
|
|
130
130
|
)
|
|
@@ -37,7 +37,7 @@ def _load_failure_hint(model_name: str, error: Exception) -> str:
|
|
|
37
37
|
f"is not authorised.\n\n"
|
|
38
38
|
f" 1. Accept the licence at https://huggingface.co/{model_name}\n"
|
|
39
39
|
f" 2. Create a read token at https://huggingface.co/settings/tokens\n"
|
|
40
|
-
f" 3. Run:
|
|
40
|
+
f" 3. Run: msgsearch login\n\n"
|
|
41
41
|
f"Note that an *expired* token produces this same error, so re-run step 3 "
|
|
42
42
|
f"even if you have logged in before. To use an ungated model instead:\n\n"
|
|
43
43
|
f" MSGSEARCH_EMBED_MODEL=BAAI/bge-small-en-v1.5\n\n"
|
|
@@ -75,3 +75,26 @@ class TestEntryPoint(unittest.TestCase):
|
|
|
75
75
|
|
|
76
76
|
if __name__ == "__main__":
|
|
77
77
|
unittest.main()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestLoginCommand(unittest.TestCase):
|
|
81
|
+
"""Authentication is a msgsearch command because `hf` is not on the PATH.
|
|
82
|
+
|
|
83
|
+
Installing msgsearch exposes only the entry points msgsearch declares, so
|
|
84
|
+
telling people to run `hf auth login` failed with "command not found" for
|
|
85
|
+
everyone who installed it normally rather than from a checkout.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def setUp(self):
|
|
89
|
+
self.parser = cli.build_parser()
|
|
90
|
+
|
|
91
|
+
def test_login_is_a_command(self):
|
|
92
|
+
args = self.parser.parse_args(["login"])
|
|
93
|
+
self.assertTrue(callable(args.handler))
|
|
94
|
+
|
|
95
|
+
def test_token_can_be_passed_non_interactively(self):
|
|
96
|
+
args = self.parser.parse_args(["login", "--token", "hf_example"])
|
|
97
|
+
self.assertEqual(args.token, "hf_example")
|
|
98
|
+
|
|
99
|
+
def test_token_is_optional_so_it_can_prompt(self):
|
|
100
|
+
self.assertIsNone(self.parser.parse_args(["login"]).token)
|
|
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
|
|
File without changes
|