aether-cache 0.1.4__tar.gz → 0.1.6__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.
- aether_cache-0.1.6/LICENSE +21 -0
- aether_cache-0.1.4/README.md → aether_cache-0.1.6/PKG-INFO +32 -3
- aether_cache-0.1.4/PKG-INFO → aether_cache-0.1.6/README.md +17 -16
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/__init__.py +1 -1
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/cli.py +7 -2
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/engine.py +26 -1
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/portability.py +70 -13
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/server.py +9 -5
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/index.html +4 -1
- {aether_cache-0.1.4 → aether_cache-0.1.6}/pyproject.toml +5 -3
- {aether_cache-0.1.4 → aether_cache-0.1.6}/.gitignore +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/__main__.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/config.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/connect.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/engine/aether-engine.jar +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/javart.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/repos.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/OFL.txt +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui.py +0 -0
- {aether_cache-0.1.4 → aether_cache-0.1.6}/docs/README.md +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anish Chugh
|
|
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.
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aether-cache
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Shared memory for AI coding tools that stays true to your code.
|
|
5
|
+
Project-URL: Homepage, https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/
|
|
6
|
+
Project-URL: Repository, https://github.com/anishchugh05/Aether-Cache-Redis-Compatible-Vector-Store
|
|
7
|
+
Author: Anish Chugh
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: fastembed>=0.3
|
|
12
|
+
Requires-Dist: mcp>=1.2
|
|
13
|
+
Requires-Dist: redis>=5.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
[](https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/)
|
|
2
17
|
|
|
3
18
|
# Aether Cache
|
|
4
19
|
|
|
5
20
|
**Shared memory for AI coding tools that stays true to your code.**
|
|
6
21
|
|
|
22
|
+
<!-- mcp-name: io.github.anishchugh05/aether-cache -->
|
|
23
|
+
|
|
7
24
|
**[Website](https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/)** · **[PyPI](https://pypi.org/project/aether-cache/)**
|
|
8
25
|
|
|
9
26
|
Claude Code, Cursor, and other AI tools each forget your project between sessions, and when they do remember things,
|
|
@@ -48,7 +65,14 @@ other tools ─┘ (Python: embeddings,│ memories, HNSW vector inde
|
|
|
48
65
|
|
|
49
66
|
## What it looks like
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
The dashboard (`aether-cache ui`): memories whose files changed wait for a decision, with the file versions side by side.
|
|
69
|
+
|
|
70
|
+
<picture>
|
|
71
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/assets/dashboard-dark.png">
|
|
72
|
+
<img alt="The Aether Cache dashboard showing stale memories and a possible conflict" src="https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/assets/dashboard-light.png">
|
|
73
|
+
</picture>
|
|
74
|
+
|
|
75
|
+
And from your AI tool's side, a memory linked to a file, the file changes, and the next lookup catches it:
|
|
52
76
|
|
|
53
77
|
```
|
|
54
78
|
> remember("The database runs Postgres via docker compose", files=["docker-compose.yml"])
|
|
@@ -188,6 +212,8 @@ A Redis-compatible server written from scratch in Java. It speaks RESP, so `redi
|
|
|
188
212
|
[CodeCrafters "Build Your Own Redis"](https://codecrafters.io/challenges/redis) challenge; the vector index,
|
|
189
213
|
persistence hardening, and the memory layer are my own additions.
|
|
190
214
|
|
|
215
|
+
[](https://app.codecrafters.io/users/anishchugh05?r=2qF)
|
|
216
|
+
|
|
191
217
|
### Throughput vs. official Redis
|
|
192
218
|
|
|
193
219
|
`redis-benchmark -t set,get`, 100K requests, 50 parallel clients, 3-byte values, same machine:
|
|
@@ -353,4 +379,7 @@ aether_cache/ the Python package behind the `aether-cache` comman
|
|
|
353
379
|
memory_mcp/calibrate.py similarity calibration for duplicate/contradiction thresholds
|
|
354
380
|
vector_bench.py HNSW benchmark (writes results.json)
|
|
355
381
|
crash_test.py kill -9 crash-safety test
|
|
356
|
-
```
|
|
382
|
+
```
|
|
383
|
+
## License
|
|
384
|
+
|
|
385
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
Name: aether-cache
|
|
3
|
-
Version: 0.1.4
|
|
4
|
-
Summary: Shared memory for AI coding tools that stays true to your code.
|
|
5
|
-
Project-URL: Homepage, https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/
|
|
6
|
-
Project-URL: Repository, https://github.com/anishchugh05/Aether-Cache-Redis-Compatible-Vector-Store
|
|
7
|
-
Author: Anish Chugh
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Requires-Dist: fastembed>=0.3
|
|
10
|
-
Requires-Dist: mcp>=1.2
|
|
11
|
-
Requires-Dist: redis>=5.0
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
|
|
14
|
-
[](https://app.codecrafters.io/users/anishchugh05?r=2qF)
|
|
1
|
+
[](https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/)
|
|
15
2
|
|
|
16
3
|
# Aether Cache
|
|
17
4
|
|
|
18
5
|
**Shared memory for AI coding tools that stays true to your code.**
|
|
19
6
|
|
|
7
|
+
<!-- mcp-name: io.github.anishchugh05/aether-cache -->
|
|
8
|
+
|
|
20
9
|
**[Website](https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/)** · **[PyPI](https://pypi.org/project/aether-cache/)**
|
|
21
10
|
|
|
22
11
|
Claude Code, Cursor, and other AI tools each forget your project between sessions, and when they do remember things,
|
|
@@ -61,7 +50,14 @@ other tools ─┘ (Python: embeddings,│ memories, HNSW vector inde
|
|
|
61
50
|
|
|
62
51
|
## What it looks like
|
|
63
52
|
|
|
64
|
-
|
|
53
|
+
The dashboard (`aether-cache ui`): memories whose files changed wait for a decision, with the file versions side by side.
|
|
54
|
+
|
|
55
|
+
<picture>
|
|
56
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/assets/dashboard-dark.png">
|
|
57
|
+
<img alt="The Aether Cache dashboard showing stale memories and a possible conflict" src="https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/assets/dashboard-light.png">
|
|
58
|
+
</picture>
|
|
59
|
+
|
|
60
|
+
And from your AI tool's side, a memory linked to a file, the file changes, and the next lookup catches it:
|
|
65
61
|
|
|
66
62
|
```
|
|
67
63
|
> remember("The database runs Postgres via docker compose", files=["docker-compose.yml"])
|
|
@@ -201,6 +197,8 @@ A Redis-compatible server written from scratch in Java. It speaks RESP, so `redi
|
|
|
201
197
|
[CodeCrafters "Build Your Own Redis"](https://codecrafters.io/challenges/redis) challenge; the vector index,
|
|
202
198
|
persistence hardening, and the memory layer are my own additions.
|
|
203
199
|
|
|
200
|
+
[](https://app.codecrafters.io/users/anishchugh05?r=2qF)
|
|
201
|
+
|
|
204
202
|
### Throughput vs. official Redis
|
|
205
203
|
|
|
206
204
|
`redis-benchmark -t set,get`, 100K requests, 50 parallel clients, 3-byte values, same machine:
|
|
@@ -366,4 +364,7 @@ aether_cache/ the Python package behind the `aether-cache` comman
|
|
|
366
364
|
memory_mcp/calibrate.py similarity calibration for duplicate/contradiction thresholds
|
|
367
365
|
vector_bench.py HNSW benchmark (writes results.json)
|
|
368
366
|
crash_test.py kill -9 crash-safety test
|
|
369
|
-
```
|
|
367
|
+
```
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -263,8 +263,13 @@ def cmd_import(args) -> int:
|
|
|
263
263
|
if left_out:
|
|
264
264
|
print(f"\nLeft out ({len(left_out)}):")
|
|
265
265
|
for n in left_out:
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
if n["similar"]:
|
|
267
|
+
what = n["similar"]["id"] or "another note here"
|
|
268
|
+
why = f"very similar to {what}: \"{n['similar']['text']}\""
|
|
269
|
+
elif n["missing"]:
|
|
270
|
+
why = f"mentions {', '.join(n['missing'])}, which doesn't exist (probably out of date)"
|
|
271
|
+
else:
|
|
272
|
+
why = "doesn't look like a fact about the project"
|
|
268
273
|
print(f" - {n['text']}\n {why}")
|
|
269
274
|
if known:
|
|
270
275
|
print(f"\nAlready remembered: {len(known)}")
|
|
@@ -224,7 +224,7 @@ def start(wait: float = 60) -> str:
|
|
|
224
224
|
upgraded = True
|
|
225
225
|
if state == "other":
|
|
226
226
|
raise EngineError(port_taken_message())
|
|
227
|
-
java
|
|
227
|
+
java = _java_or_download()
|
|
228
228
|
note = _migrate_old_data()
|
|
229
229
|
config.DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
230
230
|
command = [java, "-Xmx1g", "-jar", str(JAR),
|
|
@@ -264,6 +264,31 @@ def start(wait: float = 60) -> str:
|
|
|
264
264
|
raise EngineError(f"The engine didn't answer within {int(wait)} seconds. See {config.LOG_FILE}.")
|
|
265
265
|
|
|
266
266
|
|
|
267
|
+
def _java_or_download() -> str:
|
|
268
|
+
"""Java for starting the engine. If there isn't any, downloads Aether Cache's private copy, so the engine
|
|
269
|
+
also starts for people whose AI tool runs the server without `aether-cache setup` having run first.
|
|
270
|
+
Called while holding the start lock, so several sessions starting at once download it only once."""
|
|
271
|
+
try:
|
|
272
|
+
return find_java()[0]
|
|
273
|
+
except EngineError as missing:
|
|
274
|
+
if os.environ.get("AETHER_NO_JAVA_DOWNLOAD"):
|
|
275
|
+
raise
|
|
276
|
+
from . import javart
|
|
277
|
+
notice = (f"{str(missing).splitlines()[0]} Downloading a private copy of Java {javart.FEATURE_VERSION} "
|
|
278
|
+
f"for Aether Cache into {javart.JAVA_DIR} (about 50 MB, only the first time).")
|
|
279
|
+
print(notice, file=sys.stderr, flush=True) # stderr: stdout carries the MCP protocol
|
|
280
|
+
try:
|
|
281
|
+
with open(config.LOG_FILE, "a") as log:
|
|
282
|
+
log.write(f"\n{time.strftime('%Y-%m-%d %H:%M:%S')} {notice}\n")
|
|
283
|
+
except OSError:
|
|
284
|
+
pass
|
|
285
|
+
try:
|
|
286
|
+
javart.download(progress=lambda done, total: None)
|
|
287
|
+
except javart.JavaDownloadError as e:
|
|
288
|
+
raise EngineError(f"{str(missing).splitlines()[0]}\n{e}")
|
|
289
|
+
return find_java()[0]
|
|
290
|
+
|
|
291
|
+
|
|
267
292
|
def ensure_running() -> None:
|
|
268
293
|
if probe() != "aether" or needs_upgrade():
|
|
269
294
|
start()
|
|
@@ -253,10 +253,60 @@ def _key(file: str, text: str) -> str:
|
|
|
253
253
|
return hashlib.sha1(f"{file}\n{text}".encode()).hexdigest()[:12]
|
|
254
254
|
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
# Same thresholds as the engine (Main.java): at or above DUPLICATE it treats a new memory as the same one,
|
|
257
|
+
# at or above CONFLICT as a possible contradiction. A note scoring that close to a memory you already have
|
|
258
|
+
# would only be skipped or flagged after importing, so it's flagged before instead.
|
|
259
|
+
DUPLICATE_SIMILARITY = 0.97
|
|
260
|
+
CONFLICT_SIMILARITY = 0.80
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _cosine(a: List[float], b: List[float]) -> float:
|
|
264
|
+
dot = sum(x * y for x, y in zip(a, b))
|
|
265
|
+
na = sum(x * x for x in a) ** 0.5
|
|
266
|
+
nb = sum(y * y for y in b) ** 0.5
|
|
267
|
+
return dot / (na * nb) if na and nb else 0.0
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _flag_similar(project: str, notes: List[dict]) -> Dict[str, List[str]]:
|
|
271
|
+
"""Compares each new note by meaning with the project's current memories, and with the notes before it
|
|
272
|
+
in this import. Marks near-copies as known and close matches as similar (unselected). Returns the
|
|
273
|
+
notes' embeddings by key, so importing them doesn't compute them twice."""
|
|
274
|
+
fresh = [n for n in notes if not n["known"]]
|
|
275
|
+
if not fresh:
|
|
276
|
+
return {}
|
|
277
|
+
vectors = server.embed_many([n["text"] for n in fresh])
|
|
278
|
+
pipe = server.db().pipeline(transaction=False)
|
|
279
|
+
for v in vectors:
|
|
280
|
+
pipe.execute_command("MEM.SEARCH", project, 1, "STATUS", "active", "VECTOR", *v)
|
|
281
|
+
try:
|
|
282
|
+
hits = pipe.execute()
|
|
283
|
+
except Exception:
|
|
284
|
+
hits = [server.run("MEM.SEARCH", project, 1, "STATUS", "active", "VECTOR", *v) for v in vectors]
|
|
285
|
+
floats = [[float(x) for x in v] for v in vectors]
|
|
286
|
+
for i, (note, hit) in enumerate(zip(fresh, hits)):
|
|
287
|
+
if hit:
|
|
288
|
+
mid, score, text = hit[0][0], float(hit[0][1]), hit[0][2]
|
|
289
|
+
if score >= DUPLICATE_SIMILARITY:
|
|
290
|
+
note["known"] = True
|
|
291
|
+
note["selected"] = False
|
|
292
|
+
continue
|
|
293
|
+
if score >= CONFLICT_SIMILARITY:
|
|
294
|
+
note["similar"] = {"id": mid, "text": text, "score": round(score, 2)}
|
|
295
|
+
note["selected"] = False
|
|
296
|
+
continue
|
|
297
|
+
for j in range(i): # two notes in this import that say the same thing, e.g. in CLAUDE.md and a rule
|
|
298
|
+
other = fresh[j]
|
|
299
|
+
if other["known"] or other.get("similar"):
|
|
300
|
+
continue
|
|
301
|
+
score = _cosine(floats[i], floats[j])
|
|
302
|
+
if score >= CONFLICT_SIMILARITY:
|
|
303
|
+
note["similar"] = {"id": None, "text": other["text"], "score": round(score, 2)}
|
|
304
|
+
note["selected"] = False
|
|
305
|
+
break
|
|
306
|
+
return {n["key"]: v for n, v in zip(fresh, vectors)}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _scan(project: str, root: Path) -> tuple:
|
|
260
310
|
if not root.is_dir():
|
|
261
311
|
raise NotesError(f"{root} isn't a folder.")
|
|
262
312
|
existing = set()
|
|
@@ -280,27 +330,34 @@ def scan(project: str, root: Path) -> dict:
|
|
|
280
330
|
linked, missing = mentioned_paths(note, root, skip=rels)
|
|
281
331
|
known = n in existing or body in existing
|
|
282
332
|
notes.append({"key": _key(rel, note), "text": note, "file": rel, "files": linked,
|
|
283
|
-
"missing": missing, "known": known, "weak": found["weak"],
|
|
333
|
+
"missing": missing, "known": known, "weak": found["weak"], "similar": None,
|
|
284
334
|
"selected": not known and not missing and not found["weak"]})
|
|
285
335
|
count += 1
|
|
286
336
|
per_file.append({"path": rel, "notes": count})
|
|
287
|
-
|
|
337
|
+
vectors = _flag_similar(project, notes)
|
|
338
|
+
return {"project": project, "root": str(root), "files": per_file, "notes": notes}, vectors
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def scan(project: str, root: Path) -> dict:
|
|
342
|
+
"""Everything import would bring in from this repo's notes files, tagged for review: known (already
|
|
343
|
+
remembered, word for word or nearly), similar (close in meaning to a memory you have, or to another note
|
|
344
|
+
here), missing (mentions a file that doesn't exist, so probably already stale), weak (doesn't read like
|
|
345
|
+
a fact about the project). Only notes with none of these are selected by default."""
|
|
346
|
+
return _scan(project, root)[0]
|
|
288
347
|
|
|
289
348
|
|
|
290
349
|
def apply(project: str, root: Path, keys: Optional[List[str]] = None) -> dict:
|
|
291
350
|
"""Saves the chosen notes as memories (by key from scan; None means the ones selected by default).
|
|
292
351
|
Notes that turn out to be already known are skipped, never duplicated."""
|
|
293
|
-
found =
|
|
294
|
-
|
|
352
|
+
found, vectors = _scan(project, root)
|
|
353
|
+
wanted = set(keys or ())
|
|
354
|
+
chosen = [n for n in found["notes"] if not n["known"] and (n["selected"] if keys is None else n["key"] in wanted)]
|
|
295
355
|
result = {"saved": [], "duplicates": 0, "conflicts": 0}
|
|
296
|
-
|
|
297
|
-
return result
|
|
298
|
-
vectors = server.embed_many([n["text"] for n in chosen])
|
|
299
|
-
for note, vector in zip(chosen, vectors):
|
|
356
|
+
for note in chosen:
|
|
300
357
|
args = ["MEM.ADD", project, note["text"], "SOURCE", f"{note['file']} (imported)", "SESSION", "import"]
|
|
301
358
|
if note["files"]:
|
|
302
359
|
args += ["FILES", server.link_files(note["files"], root)]
|
|
303
|
-
mem_id, outcome, _related = server.run(*args, "VECTOR", *
|
|
360
|
+
mem_id, outcome, _related = server.run(*args, "VECTOR", *vectors[note["key"]])
|
|
304
361
|
if outcome == "duplicate":
|
|
305
362
|
result["duplicates"] += 1
|
|
306
363
|
continue
|
|
@@ -16,7 +16,7 @@ from typing import List, Optional
|
|
|
16
16
|
|
|
17
17
|
import redis
|
|
18
18
|
|
|
19
|
-
from . import config, engine
|
|
19
|
+
from . import __version__, config, engine
|
|
20
20
|
|
|
21
21
|
try:
|
|
22
22
|
from mcp.server.mcpserver import MCPServer # mcp 2.x
|
|
@@ -39,10 +39,14 @@ INSTRUCTIONS = (
|
|
|
39
39
|
"replace_memory."
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
"aether-cache",
|
|
44
|
-
|
|
45
|
-
)
|
|
42
|
+
try:
|
|
43
|
+
mcp = MCPServer("aether-cache", instructions=INSTRUCTIONS, version=__version__)
|
|
44
|
+
except Exception: # mcp 1.x has no version argument; set it on the underlying server instead
|
|
45
|
+
mcp = MCPServer("aether-cache", instructions=INSTRUCTIONS)
|
|
46
|
+
try:
|
|
47
|
+
mcp._mcp_server.version = __version__
|
|
48
|
+
except AttributeError:
|
|
49
|
+
pass
|
|
46
50
|
|
|
47
51
|
def quiet_libraries() -> None:
|
|
48
52
|
"""Hides the download libraries' request logs, warnings and progress bars, which otherwise flood the
|
|
@@ -903,7 +903,10 @@ function renderImport() {
|
|
|
903
903
|
n.files.length ? h("span", { class: "linked" }, "Linked to ", n.files.map((p, i) => [i ? ", " : "", h("span", { class: "mono" }, pathNodes(p))])) : null,
|
|
904
904
|
n.missing.length ? h("span", { class: "warn" }, "Mentions ", n.missing.map((p, i) => [i ? ", " : "", h("span", { class: "mono" }, pathNodes(p))]),
|
|
905
905
|
n.missing.length === 1 ? ", which doesn’t exist. Probably out of date." : ", which don’t exist. Probably out of date.") : null,
|
|
906
|
-
n.
|
|
906
|
+
n.similar ? h("span", { class: "warn" }, n.similar.id
|
|
907
|
+
? ["Very similar to ", h("span", { class: "mono" }, n.similar.id), ", which you already have: “", richText(n.similar.text), "”"]
|
|
908
|
+
: ["Very similar to another note here: “", richText(n.similar.text), "”"]) : null,
|
|
909
|
+
n.weak && !n.missing.length && !n.similar ? h("span", {}, "Doesn’t look like a fact about the project, so it’s unchecked.") : null,
|
|
907
910
|
].filter(Boolean);
|
|
908
911
|
return h("label", { class: "pick" }, box,
|
|
909
912
|
h("span", {}, h("span", { class: "text", id: `t-${n.key}` }, richText(n.text)), tags.length ? h("span", { class: "tags" }, tags) : null));
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling>=1.
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aether-cache"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.6"
|
|
8
8
|
description = "Shared memory for AI coding tools that stays true to your code."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
authors = [{ name = "Anish Chugh" }]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
12
14
|
dependencies = [
|
|
13
15
|
"mcp>=1.2",
|
|
14
16
|
"fastembed>=0.3",
|
|
@@ -27,7 +29,7 @@ packages = ["aether_cache"]
|
|
|
27
29
|
artifacts = ["aether_cache/engine/aether-engine.jar"] # *.jar is gitignored; ship this one anyway
|
|
28
30
|
|
|
29
31
|
[tool.hatch.build.targets.sdist]
|
|
30
|
-
include = ["aether_cache", "README.md", "pyproject.toml"]
|
|
32
|
+
include = ["aether_cache", "README.md", "LICENSE", "pyproject.toml"]
|
|
31
33
|
artifacts = ["aether_cache/engine/aether-engine.jar"]
|
|
32
34
|
|
|
33
35
|
[tool.uv]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-mono-latin-400-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-mono-latin-500-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-400-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-500-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.4 → aether_cache-0.1.6}/aether_cache/ui/fonts/ibm-plex-sans-latin-600-normal.woff2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|