elesync 1.2.0__tar.gz → 1.3.0__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.
- {elesync-1.2.0 → elesync-1.3.0}/PKG-INFO +31 -2
- {elesync-1.2.0 → elesync-1.3.0}/README.md +30 -1
- {elesync-1.2.0 → elesync-1.3.0}/elesync/__init__.py +1 -1
- {elesync-1.2.0 → elesync-1.3.0}/elesync/cli.py +17 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/usb.py +18 -11
- elesync-1.3.0/elesync/web.py +987 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/PKG-INFO +31 -2
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/SOURCES.txt +3 -1
- {elesync-1.2.0 → elesync-1.3.0}/pyproject.toml +1 -1
- elesync-1.3.0/tests/test_web.py +319 -0
- {elesync-1.2.0 → elesync-1.3.0}/LICENSE +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/conflicts.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/crypto.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/embeddings.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/mcp_server.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/models.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/normalize.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/onboarding.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/scopes.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/service.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/store.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/summary.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync/watch.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/dependency_links.txt +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/entry_points.txt +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/requires.txt +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/elesync.egg-info/top_level.txt +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/setup.cfg +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_conflicts.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_encryption.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_scopes.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_semantic.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_service.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_summary.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_usb.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_vault.py +0 -0
- {elesync-1.2.0 → elesync-1.3.0}/tests/test_watch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elesync
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Local-first, MCP-native unified memory vault — your AI memory as files you own, shared across every model.
|
|
5
5
|
Author: Alphanymous
|
|
6
6
|
License: MIT
|
|
@@ -56,6 +56,7 @@ Dynamic: license-file
|
|
|
56
56
|
[](https://github.com/darknodebros/EleSync/actions/workflows/ci.yml)
|
|
57
57
|
[](LICENSE)
|
|
58
58
|
[](https://modelcontextprotocol.io)
|
|
59
|
+
[](https://buymeacoffee.com/Darknodebros)
|
|
59
60
|
|
|
60
61
|
<!-- mcp-name: io.github.darknodebros/elesync -->
|
|
61
62
|
|
|
@@ -84,7 +85,7 @@ pip install "elesync[watch]"
|
|
|
84
85
|
ele watch --autostart # runs in the background from now on
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
**Stable at v1.
|
|
88
|
+
**Stable at v1.3** — a 179-test suite, CI across Python 3.10–3.14 (including a dev-extras job
|
|
88
89
|
covering encryption and real-model semantic recall) plus ruff lint/format and mypy type-checking,
|
|
89
90
|
and a public surface (CLI commands, MCP tools, vault layout, export format) you can build on
|
|
90
91
|
without breakage inside 1.x. See the [CHANGELOG](CHANGELOG.md) for the full history.
|
|
@@ -441,6 +442,34 @@ exposed nothing without it. The same XSalsa20-Poly1305 + argon2id encryption use
|
|
|
441
442
|
> automatically between the USB vault and your main vault — use `ele export` + `ele import` to
|
|
442
443
|
> move memories between them.
|
|
443
444
|
|
|
445
|
+
## Local web UI
|
|
446
|
+
|
|
447
|
+
No terminal needed once EleSync is installed — `ele web` opens a clean browser-based
|
|
448
|
+
vault explorer:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
ele web # opens http://127.0.0.1:7477 in your browser
|
|
452
|
+
ele web --port 8080 # custom port
|
|
453
|
+
ele web --no-browser # start server without auto-opening browser
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**What you get:**
|
|
457
|
+
|
|
458
|
+
| Tab | What it does |
|
|
459
|
+
|---|---|
|
|
460
|
+
| **Dashboard** | Total memories, AI source breakdown, FTS / embedding status |
|
|
461
|
+
| **Browse** | Paginated list with live filter, source & type dropdowns |
|
|
462
|
+
| **Search** | Instant full-text search across all memories |
|
|
463
|
+
| **Add Memory** | Manually add a memory (content, source, type, tags) |
|
|
464
|
+
| **Import** | Drag-and-drop a ChatGPT / Claude / Gemini / Grok export JSON |
|
|
465
|
+
|
|
466
|
+
Click any memory to open a slide-in editor — update content, type, tags, or delete it.
|
|
467
|
+
|
|
468
|
+
**Zero extra dependencies** — pure Python stdlib `http.server`. Works with encrypted
|
|
469
|
+
vaults (prompts for passphrase at startup). Binds to `127.0.0.1` only.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
444
473
|
## Sync across devices
|
|
445
474
|
|
|
446
475
|
Your vault is **just a folder** (`notes/*.md` + a small `vault.json`), so sync it however you
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
[](https://github.com/darknodebros/EleSync/actions/workflows/ci.yml)
|
|
17
17
|
[](LICENSE)
|
|
18
18
|
[](https://modelcontextprotocol.io)
|
|
19
|
+
[](https://buymeacoffee.com/Darknodebros)
|
|
19
20
|
|
|
20
21
|
<!-- mcp-name: io.github.darknodebros/elesync -->
|
|
21
22
|
|
|
@@ -44,7 +45,7 @@ pip install "elesync[watch]"
|
|
|
44
45
|
ele watch --autostart # runs in the background from now on
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
**Stable at v1.
|
|
48
|
+
**Stable at v1.3** — a 179-test suite, CI across Python 3.10–3.14 (including a dev-extras job
|
|
48
49
|
covering encryption and real-model semantic recall) plus ruff lint/format and mypy type-checking,
|
|
49
50
|
and a public surface (CLI commands, MCP tools, vault layout, export format) you can build on
|
|
50
51
|
without breakage inside 1.x. See the [CHANGELOG](CHANGELOG.md) for the full history.
|
|
@@ -401,6 +402,34 @@ exposed nothing without it. The same XSalsa20-Poly1305 + argon2id encryption use
|
|
|
401
402
|
> automatically between the USB vault and your main vault — use `ele export` + `ele import` to
|
|
402
403
|
> move memories between them.
|
|
403
404
|
|
|
405
|
+
## Local web UI
|
|
406
|
+
|
|
407
|
+
No terminal needed once EleSync is installed — `ele web` opens a clean browser-based
|
|
408
|
+
vault explorer:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
ele web # opens http://127.0.0.1:7477 in your browser
|
|
412
|
+
ele web --port 8080 # custom port
|
|
413
|
+
ele web --no-browser # start server without auto-opening browser
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**What you get:**
|
|
417
|
+
|
|
418
|
+
| Tab | What it does |
|
|
419
|
+
|---|---|
|
|
420
|
+
| **Dashboard** | Total memories, AI source breakdown, FTS / embedding status |
|
|
421
|
+
| **Browse** | Paginated list with live filter, source & type dropdowns |
|
|
422
|
+
| **Search** | Instant full-text search across all memories |
|
|
423
|
+
| **Add Memory** | Manually add a memory (content, source, type, tags) |
|
|
424
|
+
| **Import** | Drag-and-drop a ChatGPT / Claude / Gemini / Grok export JSON |
|
|
425
|
+
|
|
426
|
+
Click any memory to open a slide-in editor — update content, type, tags, or delete it.
|
|
427
|
+
|
|
428
|
+
**Zero extra dependencies** — pure Python stdlib `http.server`. Works with encrypted
|
|
429
|
+
vaults (prompts for passphrase at startup). Binds to `127.0.0.1` only.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
404
433
|
## Sync across devices
|
|
405
434
|
|
|
406
435
|
Your vault is **just a folder** (`notes/*.md` + a small `vault.json`), so sync it however you
|
|
@@ -392,6 +392,17 @@ def cmd_serve(args) -> int:
|
|
|
392
392
|
return 0
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
def cmd_web(args) -> int:
|
|
396
|
+
from .web import serve
|
|
397
|
+
|
|
398
|
+
serve(
|
|
399
|
+
vault_dir=getattr(args, "vault", None),
|
|
400
|
+
port=getattr(args, "port", 7477),
|
|
401
|
+
open_browser=not getattr(args, "no_browser", False),
|
|
402
|
+
)
|
|
403
|
+
return 0
|
|
404
|
+
|
|
405
|
+
|
|
395
406
|
def _vault_path(args) -> str:
|
|
396
407
|
return args.vault or os.environ.get("ELESYNC_DIR", "~/EleSyncVault")
|
|
397
408
|
|
|
@@ -770,6 +781,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
770
781
|
pusb_eject.add_argument("drive", help="Path to the USB drive")
|
|
771
782
|
pusb_eject.set_defaults(usb_func=cmd_usb_eject)
|
|
772
783
|
|
|
784
|
+
# ── web UI ───────────────────────────────────────────────────────────
|
|
785
|
+
pweb = sub.add_parser("web", help="Open the local web UI in your browser")
|
|
786
|
+
pweb.add_argument("--port", type=int, default=7477, help="Port to listen on (default: 7477)")
|
|
787
|
+
pweb.add_argument("--no-browser", action="store_true", help="Don't auto-open the browser")
|
|
788
|
+
pweb.set_defaults(func=cmd_web)
|
|
789
|
+
|
|
773
790
|
psum = sub.add_parser(
|
|
774
791
|
"summary",
|
|
775
792
|
help="Show a human-readable digest of everything in your vault",
|
|
@@ -49,6 +49,22 @@ import subprocess
|
|
|
49
49
|
import sys
|
|
50
50
|
from pathlib import Path
|
|
51
51
|
|
|
52
|
+
# Module-level references so tests can patch elesync.usb.crypto / MemoryStore / onboarding
|
|
53
|
+
try:
|
|
54
|
+
from . import crypto # type: ignore[attr-defined]
|
|
55
|
+
except Exception: # pragma: no cover
|
|
56
|
+
crypto = None # type: ignore[assignment]
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
from .store import MemoryStore # type: ignore[attr-defined]
|
|
60
|
+
except Exception: # pragma: no cover
|
|
61
|
+
MemoryStore = None # type: ignore[assignment]
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
from . import onboarding # type: ignore[attr-defined]
|
|
65
|
+
except Exception: # pragma: no cover
|
|
66
|
+
onboarding = None # type: ignore[assignment]
|
|
67
|
+
|
|
52
68
|
# ---------------------------------------------------------------------------
|
|
53
69
|
# Drive detection
|
|
54
70
|
# ---------------------------------------------------------------------------
|
|
@@ -199,10 +215,7 @@ def install(drive: str | Path, passphrase: str, label: str = "USB") -> dict:
|
|
|
199
215
|
|
|
200
216
|
Returns a result dict with the paths created.
|
|
201
217
|
"""
|
|
202
|
-
|
|
203
|
-
from .store import MemoryStore
|
|
204
|
-
|
|
205
|
-
if not crypto.available():
|
|
218
|
+
if crypto is None or not crypto.available():
|
|
206
219
|
raise RuntimeError(
|
|
207
220
|
'USB vaults are always encrypted. '
|
|
208
221
|
'Run: pip install "elesync[encryption]" first.'
|
|
@@ -252,8 +265,6 @@ def attach(drive: str | Path, passphrase: str, name: str = "elesync-usb") -> dic
|
|
|
252
265
|
|
|
253
266
|
Returns the result dict from `onboarding.wire_client_config`.
|
|
254
267
|
"""
|
|
255
|
-
from . import crypto, onboarding
|
|
256
|
-
|
|
257
268
|
drive = Path(drive)
|
|
258
269
|
vp = vault_path(drive)
|
|
259
270
|
|
|
@@ -303,8 +314,6 @@ def detach(drive: str | Path, name: str = "elesync-usb") -> dict:
|
|
|
303
314
|
Call this before physically unplugging the drive so Claude won't try to
|
|
304
315
|
reconnect to a missing vault on next launch.
|
|
305
316
|
"""
|
|
306
|
-
from . import onboarding
|
|
307
|
-
|
|
308
317
|
cfg_path = onboarding.default_client_config_path()
|
|
309
318
|
if not cfg_path.exists():
|
|
310
319
|
return {"removed": False, "reason": "Claude Desktop config not found"}
|
|
@@ -356,7 +365,7 @@ def status(drive: str | Path) -> dict:
|
|
|
356
365
|
if not result["vault_present"]:
|
|
357
366
|
return result
|
|
358
367
|
|
|
359
|
-
result["encrypted"] = crypto.is_encrypted(vp)
|
|
368
|
+
result["encrypted"] = (crypto is not None and crypto.is_encrypted(vp))
|
|
360
369
|
result["note_count"] = len(list((vp / "notes").glob("*.md*")))
|
|
361
370
|
return result
|
|
362
371
|
|
|
@@ -404,8 +413,6 @@ def eject(drive: str | Path) -> str:
|
|
|
404
413
|
def _validate_key(vault_dir: Path, key: bytes) -> None:
|
|
405
414
|
"""Raise ValueError if the key cannot decrypt an existing note.
|
|
406
415
|
If there are no notes yet (empty vault), the key is accepted as valid."""
|
|
407
|
-
from . import crypto
|
|
408
|
-
|
|
409
416
|
encs = sorted((vault_dir / "notes").glob("*.md.enc"))
|
|
410
417
|
if not encs:
|
|
411
418
|
return # empty vault — nothing to validate against, trust it
|