cloxy 5.1__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.
cloxy-5.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cloxy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cloxy-5.1/PKG-INFO ADDED
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloxy
3
+ Version: 5.1
4
+ Summary: Give your local AI eyes and memory — web proxy, self-maintaining conversation memory, MCP server, and an Apple Silicon LLM in one process.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/roygurner-gif/cloxy
7
+ Project-URL: Issues, https://github.com/roygurner-gif/cloxy/issues
8
+ Keywords: llm,rag,memory,mcp,mlx,apple-silicon,web-proxy,claude-code
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: fastapi>=0.136.0
24
+ Requires-Dist: uvicorn>=0.49.0
25
+ Requires-Dist: httpx>=0.28.0
26
+ Requires-Dist: trafilatura>=2.1.0
27
+ Requires-Dist: markdownify>=1.2.0
28
+ Requires-Dist: beautifulsoup4>=4.15.0
29
+ Requires-Dist: fastembed>=0.8.0
30
+ Requires-Dist: numpy>=2.4.0
31
+ Requires-Dist: aiosqlite>=0.22.0
32
+ Requires-Dist: cachetools>=7.1.0
33
+ Requires-Dist: mcp>=1.10
34
+ Provides-Extra: mlx
35
+ Requires-Dist: mlx-lm>=0.31.0; extra == "mlx"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=8; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # CLOXY
41
+
42
+ **Give your local AI eyes and memory — native to your Mac.**
43
+
44
+ Cloxy is one process that gives any AI tool three things it doesn't have on its own:
45
+
46
+ - **Memory that keeps itself.** Cloxy watches your Claude Code sessions and ingests them as they happen. Ask "what did we decide about the auth flow last week" and get the actual conversation back — dated, tagged with the project, ranked by a hybrid semantic + keyword search.
47
+ - **Eyes.** A web proxy that turns any URL into clean text, markdown, or a CSS-selected extract, and a `/verify` endpoint that ranks a page's passages against a claim.
48
+ - **A local LLM** (optional, Apple Silicon). `cloxy init` picks an MLX model that fits your unified memory; `/v1/chat/completions` serves it OpenAI-style — with your memory injected if you want.
49
+
50
+ All of it is exposed as an **MCP server**, so Claude Code, Cursor, Continue, and Zed pick it up with one line. Nothing leaves your machine.
51
+
52
+ ## Install
53
+
54
+ Requires Python 3.11+. macOS (Apple Silicon) for the local LLM; the proxy, memory, and MCP server run anywhere.
55
+
56
+ ```bash
57
+ pipx install cloxy # proxy + memory + MCP
58
+ pipx install "cloxy[mlx]" # + Apple Silicon LLM
59
+
60
+ # or from a clone
61
+ pip install ".[mlx]"
62
+
63
+ # or the bleeding edge straight from GitHub
64
+ pipx install "git+https://github.com/roygurner-gif/cloxy"
65
+ ```
66
+
67
+ ## Quick start
68
+
69
+ ```bash
70
+ cloxy start # server on http://127.0.0.1:9055 — the watcher starts ingesting ~/.claude/projects
71
+ cloxy status # memories, watcher, index sizes
72
+ cloxy recall "what port does the staging cluster use"
73
+ ```
74
+
75
+ Keep it running across logins (macOS):
76
+
77
+ ```bash
78
+ cloxy install-service # launchd agent; logs in ~/.cloxy/logs/cloxy.log
79
+ ```
80
+
81
+ ### Give Claude Code the tools
82
+
83
+ ```bash
84
+ claude mcp add cloxy -- cloxy mcp
85
+ ```
86
+
87
+ or in `.mcp.json` (Claude Code, Cursor, Continue, Zed all read this shape):
88
+
89
+ ```json
90
+ { "mcpServers": { "cloxy": { "command": "cloxy", "args": ["mcp"] } } }
91
+ ```
92
+
93
+ Tools exposed: `recall`, `remember`, `forget`, `fetch`, `search_page`, `verify`, `projects`, `memory_status`. The MCP server is a thin client of the running Cloxy server (`CLOXY_URL`), so every editor shares one index and one embedder.
94
+
95
+ **Server on another machine?** Put the address and key in `~/.cloxy/client.env` on the client and the CLI / MCP server pick them up:
96
+
97
+ ```
98
+ CLOXY_URL=http://192.168.1.20:9055
99
+ CLOXY_API_KEY=…
100
+ ```
101
+
102
+ To feed that server this machine's Claude Code sessions, mirror them into one of its `CLOXY_WATCH_DIRS` (e.g. a `rsync -a ~/.claude/projects/ host:claude-sessions/$(hostname)/` on a timer).
103
+
104
+ ## How memory works
105
+
106
+ ```
107
+ ~/.claude/projects/**/*.jsonl ──watcher (5s)──▶ parse new lines from last byte offset
108
+ │
109
+ pack whole messages into ~1500-char chunks
110
+ header: [2026-09-12 14:40 · rmbr · Board colors]
111
+ │
112
+ embed (bge-small) ─┼─ SQLite: content + project + session
113
+ │ + ts_start/ts_end + metadata
114
+ numpy vector index + FTS5 keyword index
115
+ │
116
+ /recall = dense ⊕ BM25 (reciprocal rank fusion) × recency
117
+ ```
118
+
119
+ - **Incremental.** Each session file is tracked by byte offset. Only new lines are read. The last, still-growing chunk is stored so it's searchable immediately and replaced on the next pass.
120
+ - **Dated and scoped.** Every memory carries the session's working directory (project), timestamps, git branch, and title. Filter with `project`, `since`, `until`.
121
+ - **Hybrid.** Dense vectors catch meaning; FTS5 catches the exact port number, hostname, or flag that embeddings blur. Results are fused and gently tilted toward recent memories (30-day half-life; `recency_weight` 0–1). The `score` on each hit is the fused rank value (typically 0.01–0.04), so compare hits within one query by order, not by magnitude — it is not a cosine similarity like `/verify` reports.
122
+ - **Asymmetric embeddings.** A short question is embedded differently from the long text it searches. Models trained that way (the E5 family) get their `query: ` / `passage: ` prefixes automatically; fastembed does not add them, and without them E5 recall drops sharply. Override with `CLOXY_EMBED_QUERY_PREFIX` / `CLOXY_EMBED_PASSAGE_PREFIX`.
123
+ - **Optional reranker.** `CLOXY_RERANK=1` runs a small cross-encoder over the top 20.
124
+ - **Self-cleaning.** Delete one memory, a whole source, or force a re-ingest; the vector and keyword indexes stay in sync.
125
+
126
+ Existing v3/v4 databases migrate in place on first start.
127
+
128
+ ## Endpoints
129
+
130
+ | Method | Path | Description |
131
+ |---|---|---|
132
+ | `POST` | `/recall` | Hybrid search. `{query, top_k, mode: hybrid\|dense\|keyword, project, since, until, recency_weight, rerank}` |
133
+ | `POST` | `/ingest_text` | Store any text. `{text, source, project?, metadata?}` |
134
+ | `POST` | `/ingest_convos` | Run an ingest pass now. `{convo_dir?, force?}` |
135
+ | `GET` | `/projects` | Projects present in memory with counts and date ranges |
136
+ | `GET` | `/ingest_status` | Watcher state |
137
+ | `GET` | `/memory_stats` | Counts, sources, index sizes |
138
+ | `DELETE` | `/memory/{id}` | Delete one memory |
139
+ | `POST` | `/forget` | Delete memories by source prefix (`convo:`, a session id, `manual`…) |
140
+ | `POST` | `/reindex` | Rebuild the vector + keyword indexes from the DB |
141
+ | `POST` | `/fetch` | Fetch a URL. `{url, mode: clean\|raw\|markdown\|extract, selector?, headers?}` |
142
+ | `POST` | `/search` | Fetch a URL, return lines containing a pattern |
143
+ | `POST` | `/verify` | Fetch a URL, rank passages by semantic match to a claim |
144
+ | `POST` | `/v1/chat/completions` | OpenAI-compatible chat (streaming or not). Extra: `memory`, `memory_top_k`, `memory_project` |
145
+ | `GET` | `/v1/models` | The currently loaded model |
146
+ | `GET` | `/health` | Health + watcher summary |
147
+
148
+ ### Examples
149
+
150
+ ```bash
151
+ # recall, scoped to one project since a date
152
+ curl -s localhost:9055/recall -H 'content-type: application/json' \
153
+ -d '{"query":"why did we switch to WAL mode","project":"cloxy","since":"2026-09-01","top_k":3}'
154
+
155
+ # remember something
156
+ curl -s localhost:9055/ingest_text -H 'content-type: application/json' \
157
+ -d '{"text":"Staging DB is read-only on Fridays.","source":"decision","project":"/w/infra"}'
158
+
159
+ # read a page as clean text
160
+ curl -s localhost:9055/fetch -H 'content-type: application/json' \
161
+ -d '{"url":"https://example.com","mode":"clean"}'
162
+
163
+ # check a claim against a page
164
+ curl -s localhost:9055/verify -H 'content-type: application/json' \
165
+ -d '{"url":"https://example.com/press","claim":"Revenue grew 12% year over year","top_k":3}'
166
+ ```
167
+
168
+ `/verify` returns the top-K passages with cosine scores. The caller decides support/contradiction — Cloxy stays a tool, not a judge.
169
+
170
+ ## Local LLM (Apple Silicon)
171
+
172
+ ```bash
173
+ pip install ".[mlx]"
174
+ cloxy init # detects chip + memory, recommends MLX models that fit, downloads your pick
175
+ cloxy start
176
+ ```
177
+
178
+ The model loads on first request (or at startup with `CLOXY_EAGER_LLM=1`). Any OpenAI-compatible client works:
179
+
180
+ ```python
181
+ from openai import OpenAI
182
+ client = OpenAI(base_url="http://localhost:9055/v1", api_key="not-required")
183
+ resp = client.chat.completions.create(
184
+ model="cloxy",
185
+ messages=[{"role": "user", "content": "What did we decide about the auth flow?"}],
186
+ extra_body={"memory": True}, # prepend relevant recall to the prompt
187
+ )
188
+ print(resp.choices[0].message.content)
189
+ ```
190
+
191
+ Continue.dev / Cursor: add an OpenAI-compatible model with base URL `http://localhost:9055/v1` and any API key (or your `CLOXY_API_KEY`).
192
+
193
+ > Claude Code speaks the Anthropic Messages API, not the OpenAI one, so it can't use Cloxy as its *model* — but it uses Cloxy's memory and eyes through MCP (above).
194
+
195
+ Why MLX: it's Apple's framework for the unified-memory architecture, it runs in-process (no daemon, no HTTP hop between proxy and model), and it's fast on M-series parts. Cross-platform inference via `llama-cpp-python` is planned as a separate extra.
196
+
197
+ ## CLI
198
+
199
+ ```
200
+ cloxy start [--host H] [--port P] run the server
201
+ cloxy mcp MCP stdio server (for editors)
202
+ cloxy recall QUERY [-k N] [--project P] [--since D] [--until D] [--mode M] [--full] [--json]
203
+ cloxy ingest [DIR] [--force] run an ingest pass now
204
+ cloxy status health, memory, watcher
205
+ cloxy reembed re-embed every memory (after changing model/prefix; server stopped)
206
+ cloxy install-service | uninstall-service launchd (macOS)
207
+ cloxy init | show | list local LLM setup
208
+ ```
209
+
210
+ ## Configuration
211
+
212
+ Everything is an environment variable.
213
+
214
+ | Variable | Default | Description |
215
+ |---|---|---|
216
+ | `CLOXY_PORT` | `9055` | Server port |
217
+ | `CLOXY_HOST` | `127.0.0.1` | Bind address. `0.0.0.0` exposes it on the network — set an API key |
218
+ | `CLOXY_URL` | `http://127.0.0.1:9055` | Where the CLI and MCP server find the server |
219
+ | `CLOXY_API_KEY` | *(none)* | API key (`X-API-Key` header). Empty = open |
220
+ | `CLOXY_DATA_DIR` | `~/.cloxy` | Database, LLM config, logs, downloaded embedding models (`models/`) |
221
+ | `FASTEMBED_CACHE_PATH` | `$CLOXY_DATA_DIR/models` | Where embedding/reranker models are cached (set explicitly to share an existing cache) |
222
+ | `CLOXY_WATCH` | `1` | Run the conversation watcher |
223
+ | `CLOXY_WATCH_DIRS` | `~/.claude/projects` | Directories to watch (`:`-separated) |
224
+ | `CLOXY_WATCH_INTERVAL` | `5` | Seconds between scans |
225
+ | `CLOXY_EMBED_MODEL` | `BAAI/bge-small-en-v1.5` | Embedding model (recorded in the DB; change it, then `cloxy reembed`) |
226
+ | `CLOXY_EMBED_DIM` | `384` | Must match the model |
227
+ | `CLOXY_EMBED_QUERY_PREFIX` | by model | Prepended to every query before embedding (`query: ` for E5, empty otherwise) |
228
+ | `CLOXY_EMBED_PASSAGE_PREFIX` | by model | Prepended to stored text before embedding (`passage: ` for E5). Recorded in the DB; change it, then `cloxy reembed` |
229
+ | `CLOXY_RERANK` | *(unset)* | `1` to rerank the top 20 with a cross-encoder |
230
+ | `CLOXY_RERANK_MODEL` | `Xenova/ms-marco-MiniLM-L-6-v2` | Reranker |
231
+ | `CLOXY_CHAT_MEMORY` | *(unset)* | `1` to inject memory into every chat completion by default |
232
+ | `CLOXY_CHAT_MEMORY_TOP_K` | `5` | How many memories to inject |
233
+ | `CLOXY_ALLOW_PRIVATE_URLS` | *(unset)* | `1` lets the proxy fetch private/loopback addresses (SSRF risk) |
234
+ | `CLOXY_USER_AGENT` | Chrome UA | User agent for web requests |
235
+ | `CLOXY_FETCH_TIMEOUT` | `30` | Web fetch timeout, seconds |
236
+ | `CLOXY_CONFIG` | `~/.cloxy/config.json` | LLM config written by `cloxy init` |
237
+ | `CLOXY_EAGER_LLM` | *(unset)* | `1` loads the LLM at startup |
238
+
239
+ ## Security
240
+
241
+ - **Loopback by default.** Nothing is reachable off your machine unless you set `CLOXY_HOST=0.0.0.0`.
242
+ - **If you expose it, set an API key.** Otherwise anyone on the network can read and write your memory. The key is compared in constant time.
243
+ - **SSRF guard.** `/fetch`, `/search`, `/verify` resolve the target and refuse private, loopback, link-local, and cloud-metadata addresses — and re-check every redirect hop (max 5). Bodies are streamed and cut at 500 KB; binary content types are refused.
244
+ - **Your transcripts stay local.** The watcher reads `~/.claude/projects` on this machine and writes to `~/.cloxy/memory.db`. No telemetry. The only outbound traffic is `/fetch` requests you make and one-time model downloads.
245
+
246
+ ## Docker (proxy + memory + MCP; no LLM, no watcher)
247
+
248
+ ```bash
249
+ docker build -t cloxy .
250
+ docker run -p 9055:9055 -v cloxy-data:/data -e CLOXY_API_KEY=change-me cloxy
251
+ # or: docker compose up -d
252
+ ```
253
+
254
+ The image binds `0.0.0.0` (containers need that) — set `CLOXY_API_KEY`. Feed it with `/ingest_text` or `/ingest_convos` against a mounted directory.
255
+
256
+ ## Development
257
+
258
+ ```bash
259
+ pip install -e ".[dev]"
260
+ pytest -q
261
+ ```
262
+
263
+ The suite (no network, no models) covers the SSRF guard and redirect walking, cache keys, hybrid recall and filters, incremental ingest, migration, the MCP tools, and the OpenAI request shapes.
264
+
265
+ ## FAQ
266
+
267
+ **Does it work offline?** Yes. Memory, recall, and the local LLM are offline once models are downloaded. `/fetch` needs the network.
268
+
269
+ **What if I change the embedding model?** Cloxy refuses to start against a database built with a different model, dimension, or passage prefix, and tells you why. Stop the server, run `cloxy reembed` with the new settings (every memory is re-embedded in place; a few minutes per few thousand chunks on Apple Silicon), then start it again.
270
+
271
+ **I was already on an E5 model before v5.1.** Your memories were embedded without the `passage: ` prefix, so the first start after upgrading refuses with a prefix mismatch. `cloxy reembed` fixes it once; to keep the old behaviour instead, set `CLOXY_EMBED_PASSAGE_PREFIX=` and `CLOXY_EMBED_QUERY_PREFIX=` (both empty).
272
+
273
+ **Can I use a model not in the catalog?** `cloxy init` → Custom → any Hugging Face MLX id (usually `mlx-community/...`).
274
+
275
+ **How is this different from Ollama / LM Studio?** They serve models. Cloxy is the memory and eyes around a model — with a small model of its own on Apple Silicon. Point Cloxy at Ollama's OpenAI endpoint if you prefer it as the brain; the memory works the same.
276
+
277
+ **Is my data sent anywhere?** No.
278
+
279
+ ## License
280
+
281
+ MIT
cloxy-5.1/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # CLOXY
2
+
3
+ **Give your local AI eyes and memory — native to your Mac.**
4
+
5
+ Cloxy is one process that gives any AI tool three things it doesn't have on its own:
6
+
7
+ - **Memory that keeps itself.** Cloxy watches your Claude Code sessions and ingests them as they happen. Ask "what did we decide about the auth flow last week" and get the actual conversation back — dated, tagged with the project, ranked by a hybrid semantic + keyword search.
8
+ - **Eyes.** A web proxy that turns any URL into clean text, markdown, or a CSS-selected extract, and a `/verify` endpoint that ranks a page's passages against a claim.
9
+ - **A local LLM** (optional, Apple Silicon). `cloxy init` picks an MLX model that fits your unified memory; `/v1/chat/completions` serves it OpenAI-style — with your memory injected if you want.
10
+
11
+ All of it is exposed as an **MCP server**, so Claude Code, Cursor, Continue, and Zed pick it up with one line. Nothing leaves your machine.
12
+
13
+ ## Install
14
+
15
+ Requires Python 3.11+. macOS (Apple Silicon) for the local LLM; the proxy, memory, and MCP server run anywhere.
16
+
17
+ ```bash
18
+ pipx install cloxy # proxy + memory + MCP
19
+ pipx install "cloxy[mlx]" # + Apple Silicon LLM
20
+
21
+ # or from a clone
22
+ pip install ".[mlx]"
23
+
24
+ # or the bleeding edge straight from GitHub
25
+ pipx install "git+https://github.com/roygurner-gif/cloxy"
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```bash
31
+ cloxy start # server on http://127.0.0.1:9055 — the watcher starts ingesting ~/.claude/projects
32
+ cloxy status # memories, watcher, index sizes
33
+ cloxy recall "what port does the staging cluster use"
34
+ ```
35
+
36
+ Keep it running across logins (macOS):
37
+
38
+ ```bash
39
+ cloxy install-service # launchd agent; logs in ~/.cloxy/logs/cloxy.log
40
+ ```
41
+
42
+ ### Give Claude Code the tools
43
+
44
+ ```bash
45
+ claude mcp add cloxy -- cloxy mcp
46
+ ```
47
+
48
+ or in `.mcp.json` (Claude Code, Cursor, Continue, Zed all read this shape):
49
+
50
+ ```json
51
+ { "mcpServers": { "cloxy": { "command": "cloxy", "args": ["mcp"] } } }
52
+ ```
53
+
54
+ Tools exposed: `recall`, `remember`, `forget`, `fetch`, `search_page`, `verify`, `projects`, `memory_status`. The MCP server is a thin client of the running Cloxy server (`CLOXY_URL`), so every editor shares one index and one embedder.
55
+
56
+ **Server on another machine?** Put the address and key in `~/.cloxy/client.env` on the client and the CLI / MCP server pick them up:
57
+
58
+ ```
59
+ CLOXY_URL=http://192.168.1.20:9055
60
+ CLOXY_API_KEY=…
61
+ ```
62
+
63
+ To feed that server this machine's Claude Code sessions, mirror them into one of its `CLOXY_WATCH_DIRS` (e.g. a `rsync -a ~/.claude/projects/ host:claude-sessions/$(hostname)/` on a timer).
64
+
65
+ ## How memory works
66
+
67
+ ```
68
+ ~/.claude/projects/**/*.jsonl ──watcher (5s)──▶ parse new lines from last byte offset
69
+ │
70
+ pack whole messages into ~1500-char chunks
71
+ header: [2026-09-12 14:40 · rmbr · Board colors]
72
+ │
73
+ embed (bge-small) ─┼─ SQLite: content + project + session
74
+ │ + ts_start/ts_end + metadata
75
+ numpy vector index + FTS5 keyword index
76
+ │
77
+ /recall = dense ⊕ BM25 (reciprocal rank fusion) × recency
78
+ ```
79
+
80
+ - **Incremental.** Each session file is tracked by byte offset. Only new lines are read. The last, still-growing chunk is stored so it's searchable immediately and replaced on the next pass.
81
+ - **Dated and scoped.** Every memory carries the session's working directory (project), timestamps, git branch, and title. Filter with `project`, `since`, `until`.
82
+ - **Hybrid.** Dense vectors catch meaning; FTS5 catches the exact port number, hostname, or flag that embeddings blur. Results are fused and gently tilted toward recent memories (30-day half-life; `recency_weight` 0–1). The `score` on each hit is the fused rank value (typically 0.01–0.04), so compare hits within one query by order, not by magnitude — it is not a cosine similarity like `/verify` reports.
83
+ - **Asymmetric embeddings.** A short question is embedded differently from the long text it searches. Models trained that way (the E5 family) get their `query: ` / `passage: ` prefixes automatically; fastembed does not add them, and without them E5 recall drops sharply. Override with `CLOXY_EMBED_QUERY_PREFIX` / `CLOXY_EMBED_PASSAGE_PREFIX`.
84
+ - **Optional reranker.** `CLOXY_RERANK=1` runs a small cross-encoder over the top 20.
85
+ - **Self-cleaning.** Delete one memory, a whole source, or force a re-ingest; the vector and keyword indexes stay in sync.
86
+
87
+ Existing v3/v4 databases migrate in place on first start.
88
+
89
+ ## Endpoints
90
+
91
+ | Method | Path | Description |
92
+ |---|---|---|
93
+ | `POST` | `/recall` | Hybrid search. `{query, top_k, mode: hybrid\|dense\|keyword, project, since, until, recency_weight, rerank}` |
94
+ | `POST` | `/ingest_text` | Store any text. `{text, source, project?, metadata?}` |
95
+ | `POST` | `/ingest_convos` | Run an ingest pass now. `{convo_dir?, force?}` |
96
+ | `GET` | `/projects` | Projects present in memory with counts and date ranges |
97
+ | `GET` | `/ingest_status` | Watcher state |
98
+ | `GET` | `/memory_stats` | Counts, sources, index sizes |
99
+ | `DELETE` | `/memory/{id}` | Delete one memory |
100
+ | `POST` | `/forget` | Delete memories by source prefix (`convo:`, a session id, `manual`…) |
101
+ | `POST` | `/reindex` | Rebuild the vector + keyword indexes from the DB |
102
+ | `POST` | `/fetch` | Fetch a URL. `{url, mode: clean\|raw\|markdown\|extract, selector?, headers?}` |
103
+ | `POST` | `/search` | Fetch a URL, return lines containing a pattern |
104
+ | `POST` | `/verify` | Fetch a URL, rank passages by semantic match to a claim |
105
+ | `POST` | `/v1/chat/completions` | OpenAI-compatible chat (streaming or not). Extra: `memory`, `memory_top_k`, `memory_project` |
106
+ | `GET` | `/v1/models` | The currently loaded model |
107
+ | `GET` | `/health` | Health + watcher summary |
108
+
109
+ ### Examples
110
+
111
+ ```bash
112
+ # recall, scoped to one project since a date
113
+ curl -s localhost:9055/recall -H 'content-type: application/json' \
114
+ -d '{"query":"why did we switch to WAL mode","project":"cloxy","since":"2026-09-01","top_k":3}'
115
+
116
+ # remember something
117
+ curl -s localhost:9055/ingest_text -H 'content-type: application/json' \
118
+ -d '{"text":"Staging DB is read-only on Fridays.","source":"decision","project":"/w/infra"}'
119
+
120
+ # read a page as clean text
121
+ curl -s localhost:9055/fetch -H 'content-type: application/json' \
122
+ -d '{"url":"https://example.com","mode":"clean"}'
123
+
124
+ # check a claim against a page
125
+ curl -s localhost:9055/verify -H 'content-type: application/json' \
126
+ -d '{"url":"https://example.com/press","claim":"Revenue grew 12% year over year","top_k":3}'
127
+ ```
128
+
129
+ `/verify` returns the top-K passages with cosine scores. The caller decides support/contradiction — Cloxy stays a tool, not a judge.
130
+
131
+ ## Local LLM (Apple Silicon)
132
+
133
+ ```bash
134
+ pip install ".[mlx]"
135
+ cloxy init # detects chip + memory, recommends MLX models that fit, downloads your pick
136
+ cloxy start
137
+ ```
138
+
139
+ The model loads on first request (or at startup with `CLOXY_EAGER_LLM=1`). Any OpenAI-compatible client works:
140
+
141
+ ```python
142
+ from openai import OpenAI
143
+ client = OpenAI(base_url="http://localhost:9055/v1", api_key="not-required")
144
+ resp = client.chat.completions.create(
145
+ model="cloxy",
146
+ messages=[{"role": "user", "content": "What did we decide about the auth flow?"}],
147
+ extra_body={"memory": True}, # prepend relevant recall to the prompt
148
+ )
149
+ print(resp.choices[0].message.content)
150
+ ```
151
+
152
+ Continue.dev / Cursor: add an OpenAI-compatible model with base URL `http://localhost:9055/v1` and any API key (or your `CLOXY_API_KEY`).
153
+
154
+ > Claude Code speaks the Anthropic Messages API, not the OpenAI one, so it can't use Cloxy as its *model* — but it uses Cloxy's memory and eyes through MCP (above).
155
+
156
+ Why MLX: it's Apple's framework for the unified-memory architecture, it runs in-process (no daemon, no HTTP hop between proxy and model), and it's fast on M-series parts. Cross-platform inference via `llama-cpp-python` is planned as a separate extra.
157
+
158
+ ## CLI
159
+
160
+ ```
161
+ cloxy start [--host H] [--port P] run the server
162
+ cloxy mcp MCP stdio server (for editors)
163
+ cloxy recall QUERY [-k N] [--project P] [--since D] [--until D] [--mode M] [--full] [--json]
164
+ cloxy ingest [DIR] [--force] run an ingest pass now
165
+ cloxy status health, memory, watcher
166
+ cloxy reembed re-embed every memory (after changing model/prefix; server stopped)
167
+ cloxy install-service | uninstall-service launchd (macOS)
168
+ cloxy init | show | list local LLM setup
169
+ ```
170
+
171
+ ## Configuration
172
+
173
+ Everything is an environment variable.
174
+
175
+ | Variable | Default | Description |
176
+ |---|---|---|
177
+ | `CLOXY_PORT` | `9055` | Server port |
178
+ | `CLOXY_HOST` | `127.0.0.1` | Bind address. `0.0.0.0` exposes it on the network — set an API key |
179
+ | `CLOXY_URL` | `http://127.0.0.1:9055` | Where the CLI and MCP server find the server |
180
+ | `CLOXY_API_KEY` | *(none)* | API key (`X-API-Key` header). Empty = open |
181
+ | `CLOXY_DATA_DIR` | `~/.cloxy` | Database, LLM config, logs, downloaded embedding models (`models/`) |
182
+ | `FASTEMBED_CACHE_PATH` | `$CLOXY_DATA_DIR/models` | Where embedding/reranker models are cached (set explicitly to share an existing cache) |
183
+ | `CLOXY_WATCH` | `1` | Run the conversation watcher |
184
+ | `CLOXY_WATCH_DIRS` | `~/.claude/projects` | Directories to watch (`:`-separated) |
185
+ | `CLOXY_WATCH_INTERVAL` | `5` | Seconds between scans |
186
+ | `CLOXY_EMBED_MODEL` | `BAAI/bge-small-en-v1.5` | Embedding model (recorded in the DB; change it, then `cloxy reembed`) |
187
+ | `CLOXY_EMBED_DIM` | `384` | Must match the model |
188
+ | `CLOXY_EMBED_QUERY_PREFIX` | by model | Prepended to every query before embedding (`query: ` for E5, empty otherwise) |
189
+ | `CLOXY_EMBED_PASSAGE_PREFIX` | by model | Prepended to stored text before embedding (`passage: ` for E5). Recorded in the DB; change it, then `cloxy reembed` |
190
+ | `CLOXY_RERANK` | *(unset)* | `1` to rerank the top 20 with a cross-encoder |
191
+ | `CLOXY_RERANK_MODEL` | `Xenova/ms-marco-MiniLM-L-6-v2` | Reranker |
192
+ | `CLOXY_CHAT_MEMORY` | *(unset)* | `1` to inject memory into every chat completion by default |
193
+ | `CLOXY_CHAT_MEMORY_TOP_K` | `5` | How many memories to inject |
194
+ | `CLOXY_ALLOW_PRIVATE_URLS` | *(unset)* | `1` lets the proxy fetch private/loopback addresses (SSRF risk) |
195
+ | `CLOXY_USER_AGENT` | Chrome UA | User agent for web requests |
196
+ | `CLOXY_FETCH_TIMEOUT` | `30` | Web fetch timeout, seconds |
197
+ | `CLOXY_CONFIG` | `~/.cloxy/config.json` | LLM config written by `cloxy init` |
198
+ | `CLOXY_EAGER_LLM` | *(unset)* | `1` loads the LLM at startup |
199
+
200
+ ## Security
201
+
202
+ - **Loopback by default.** Nothing is reachable off your machine unless you set `CLOXY_HOST=0.0.0.0`.
203
+ - **If you expose it, set an API key.** Otherwise anyone on the network can read and write your memory. The key is compared in constant time.
204
+ - **SSRF guard.** `/fetch`, `/search`, `/verify` resolve the target and refuse private, loopback, link-local, and cloud-metadata addresses — and re-check every redirect hop (max 5). Bodies are streamed and cut at 500 KB; binary content types are refused.
205
+ - **Your transcripts stay local.** The watcher reads `~/.claude/projects` on this machine and writes to `~/.cloxy/memory.db`. No telemetry. The only outbound traffic is `/fetch` requests you make and one-time model downloads.
206
+
207
+ ## Docker (proxy + memory + MCP; no LLM, no watcher)
208
+
209
+ ```bash
210
+ docker build -t cloxy .
211
+ docker run -p 9055:9055 -v cloxy-data:/data -e CLOXY_API_KEY=change-me cloxy
212
+ # or: docker compose up -d
213
+ ```
214
+
215
+ The image binds `0.0.0.0` (containers need that) — set `CLOXY_API_KEY`. Feed it with `/ingest_text` or `/ingest_convos` against a mounted directory.
216
+
217
+ ## Development
218
+
219
+ ```bash
220
+ pip install -e ".[dev]"
221
+ pytest -q
222
+ ```
223
+
224
+ The suite (no network, no models) covers the SSRF guard and redirect walking, cache keys, hybrid recall and filters, incremental ingest, migration, the MCP tools, and the OpenAI request shapes.
225
+
226
+ ## FAQ
227
+
228
+ **Does it work offline?** Yes. Memory, recall, and the local LLM are offline once models are downloaded. `/fetch` needs the network.
229
+
230
+ **What if I change the embedding model?** Cloxy refuses to start against a database built with a different model, dimension, or passage prefix, and tells you why. Stop the server, run `cloxy reembed` with the new settings (every memory is re-embedded in place; a few minutes per few thousand chunks on Apple Silicon), then start it again.
231
+
232
+ **I was already on an E5 model before v5.1.** Your memories were embedded without the `passage: ` prefix, so the first start after upgrading refuses with a prefix mismatch. `cloxy reembed` fixes it once; to keep the old behaviour instead, set `CLOXY_EMBED_PASSAGE_PREFIX=` and `CLOXY_EMBED_QUERY_PREFIX=` (both empty).
233
+
234
+ **Can I use a model not in the catalog?** `cloxy init` → Custom → any Hugging Face MLX id (usually `mlx-community/...`).
235
+
236
+ **How is this different from Ollama / LM Studio?** They serve models. Cloxy is the memory and eyes around a model — with a small model of its own on Apple Silicon. Point Cloxy at Ollama's OpenAI endpoint if you prefer it as the brain; the memory works the same.
237
+
238
+ **Is my data sent anywhere?** No.
239
+
240
+ ## License
241
+
242
+ MIT
@@ -0,0 +1,3 @@
1
+ """CLOXY — give your local AI eyes and memory."""
2
+
3
+ __version__ = "5.1"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
File without changes