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 +21 -0
- cloxy-5.1/PKG-INFO +281 -0
- cloxy-5.1/README.md +242 -0
- cloxy-5.1/cloxy/__init__.py +3 -0
- cloxy-5.1/cloxy/__main__.py +3 -0
- cloxy-5.1/cloxy/backends/__init__.py +0 -0
- cloxy-5.1/cloxy/backends/mlx_backend.py +228 -0
- cloxy-5.1/cloxy/catalog.py +93 -0
- cloxy-5.1/cloxy/cli.py +429 -0
- cloxy-5.1/cloxy/config.py +106 -0
- cloxy-5.1/cloxy/convos.py +421 -0
- cloxy-5.1/cloxy/hardware.py +79 -0
- cloxy-5.1/cloxy/mcp_server.py +199 -0
- cloxy-5.1/cloxy/memory.py +752 -0
- cloxy-5.1/cloxy/proxy.py +190 -0
- cloxy-5.1/cloxy/server.py +557 -0
- cloxy-5.1/cloxy.egg-info/PKG-INFO +281 -0
- cloxy-5.1/cloxy.egg-info/SOURCES.txt +29 -0
- cloxy-5.1/cloxy.egg-info/dependency_links.txt +1 -0
- cloxy-5.1/cloxy.egg-info/entry_points.txt +2 -0
- cloxy-5.1/cloxy.egg-info/requires.txt +17 -0
- cloxy-5.1/cloxy.egg-info/top_level.txt +1 -0
- cloxy-5.1/pyproject.toml +59 -0
- cloxy-5.1/setup.cfg +4 -0
- cloxy-5.1/tests/test_cli.py +33 -0
- cloxy-5.1/tests/test_config.py +37 -0
- cloxy-5.1/tests/test_convos.py +151 -0
- cloxy-5.1/tests/test_endpoints.py +217 -0
- cloxy-5.1/tests/test_mcp.py +83 -0
- cloxy-5.1/tests/test_memory.py +208 -0
- cloxy-5.1/tests/test_proxy.py +56 -0
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
|
|
File without changes
|