brainplug 0.4.0__tar.gz → 0.4.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.
- {brainplug-0.4.0 → brainplug-0.4.1}/PKG-INFO +2 -4
- {brainplug-0.4.0 → brainplug-0.4.1}/README.md +1 -3
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/__init__.py +1 -1
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/__main__.py +5 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/connect.py +22 -7
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/PKG-INFO +2 -4
- {brainplug-0.4.0 → brainplug-0.4.1}/pyproject.toml +1 -1
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_connect.py +16 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_memory.py +10 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/LICENSE +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/agent.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/analysis.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/app.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/brain.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/demo.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/export.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/memory.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/notion_api.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/pipeline.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/render.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/sources.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/stopwords.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/sync.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/templates/app.html +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/templates/viewer.html +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/vendor/INTER_LICENSE +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/vendor/THREE_LICENSE +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/vendor/inter-latin.woff2 +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug/vendor/three.module.min.js +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/SOURCES.txt +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/dependency_links.txt +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/entry_points.txt +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/requires.txt +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/brainplug.egg-info/top_level.txt +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/setup.cfg +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_brainplug.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_extension_package.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_new_sources.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_tools.py +0 -0
- {brainplug-0.4.0 → brainplug-0.4.1}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainplug
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A second brain for you and your AI assistants: a 3D map of everything you write and a memory they plug into. 100% local.
|
|
5
5
|
Author: Juan Rodríguez
|
|
6
6
|
License-Expression: MIT
|
|
@@ -65,9 +65,7 @@ brainplug doctor ~/Documents/MyVault # checks that it answers, end to e
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
`brainplug brain ~/Documents/MyVault --draft-about` writes a first draft of your "About me"
|
|
68
|
-
note from what you have written, while it is still empty.
|
|
69
|
-
install it from GitHub:
|
|
70
|
-
`uv tool install "brainplug @ git+https://github.com/juanmariarodriguez2-png/brainplug"`.
|
|
68
|
+
note from what you have written, while it is still empty.
|
|
71
69
|
Add `--sessions` to `connect` to include what you typed to Claude Code and Codex, and put
|
|
72
70
|
export files after the vault to include what you wrote before (`… MyVault chatgpt-export.zip`).
|
|
73
71
|
|
|
@@ -29,9 +29,7 @@ brainplug doctor ~/Documents/MyVault # checks that it answers, end to e
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
`brainplug brain ~/Documents/MyVault --draft-about` writes a first draft of your "About me"
|
|
32
|
-
note from what you have written, while it is still empty.
|
|
33
|
-
install it from GitHub:
|
|
34
|
-
`uv tool install "brainplug @ git+https://github.com/juanmariarodriguez2-png/brainplug"`.
|
|
32
|
+
note from what you have written, while it is still empty.
|
|
35
33
|
Add `--sessions` to `connect` to include what you typed to Claude Code and Codex, and put
|
|
36
34
|
export files after the vault to include what you wrote before (`… MyVault chatgpt-export.zip`).
|
|
37
35
|
|
|
@@ -174,6 +174,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
174
174
|
vault = getattr(args, "brain", None) or getattr(args, "vault", None)
|
|
175
175
|
if args.cmd == "mcp" and not args.inputs and not args.notion and not vault and not args.sessions:
|
|
176
176
|
ap.error("mcp needs your vault or your files, e.g.: brainplug mcp --brain ~/Vault export.zip")
|
|
177
|
+
if vault and not Path(vault).expanduser().is_dir():
|
|
178
|
+
if args.cmd == "mcp" or args.remember or args.review or args.context or args.draft_about:
|
|
179
|
+
ap.error(f'{vault} does not exist. Check the path; to start a new, empty brain there, '
|
|
180
|
+
f'run: brainplug brain "{vault}"')
|
|
181
|
+
print(f"Creating a new folder for your brain: {Path(vault).expanduser().resolve()}")
|
|
177
182
|
if args.cmd == "brain" and args.remember:
|
|
178
183
|
path = memory.remember(vault, args.remember, kind=args.kind, pin=args.pin, source="me")
|
|
179
184
|
print(f"Saved {path.resolve()}")
|
|
@@ -64,11 +64,24 @@ class Client:
|
|
|
64
64
|
return bool((self.cli and shutil.which(self.cli)) or (self.marker and self.marker.exists()))
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def _claude_desktop() -> list[Client]:
|
|
68
|
+
"""Claude Desktop reads %APPDATA%\\Claude (or the macOS/Linux equivalent). Installed as a Windows
|
|
69
|
+
app package (MSIX), it reads a virtualized copy instead, under
|
|
70
|
+
%LOCALAPPDATA%\\Packages\\Claude_<id>\\LocalCache\\Roaming\\Claude, and its own "Edit Config"
|
|
71
|
+
button may open the copy it does not read. Both get the entry when both exist."""
|
|
72
|
+
found = [Client("claude-desktop", "Claude Desktop", _app_config("Claude") / "claude_desktop_config.json",
|
|
73
|
+
marker=_app_config("Claude"))]
|
|
74
|
+
local = os.environ.get("LOCALAPPDATA")
|
|
75
|
+
for package in sorted(Path(local).glob("Packages/Claude_*")) if local else []:
|
|
76
|
+
folder = package / "LocalCache" / "Roaming" / "Claude"
|
|
77
|
+
found.append(Client("claude-desktop", "Claude Desktop", folder / "claude_desktop_config.json", marker=package))
|
|
78
|
+
return found
|
|
79
|
+
|
|
80
|
+
|
|
67
81
|
def clients() -> list[Client]:
|
|
68
82
|
home = _home()
|
|
69
83
|
return [
|
|
70
|
-
|
|
71
|
-
marker=_app_config("Claude")),
|
|
84
|
+
*_claude_desktop(),
|
|
72
85
|
Client("claude-code", "Claude Code", cli="claude", marker=home / ".claude"),
|
|
73
86
|
Client("cursor", "Cursor", home / ".cursor" / "mcp.json", marker=home / ".cursor"),
|
|
74
87
|
Client("codex", "Codex", _codex_home() / "config.toml", cli="codex", marker=_codex_home()),
|
|
@@ -247,19 +260,21 @@ def _handshake(command: str, args: list[str], timeout: float = 90.0) -> tuple[bo
|
|
|
247
260
|
async with ClientSession(read, write) as session:
|
|
248
261
|
await session.initialize()
|
|
249
262
|
tools = [t.name for t in (await session.list_tools()).tools]
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
await session.call_tool("context", {}) # answers at once, before the files are read
|
|
264
|
+
answered = time.monotonic() - began
|
|
265
|
+
summary = (await session.call_tool("overview", {})).content[0].text # waits for the index
|
|
266
|
+
return answered, tools, summary
|
|
252
267
|
|
|
253
268
|
try:
|
|
254
|
-
took, tools,
|
|
269
|
+
took, tools, summary = asyncio.run(asyncio.wait_for(talk(), timeout))
|
|
255
270
|
except Exception as err: # noqa: BLE001 (shown to the person, who can act on it)
|
|
256
271
|
log.seek(0)
|
|
257
272
|
said = " | ".join(line.strip() for line in log.read().splitlines()[-3:] if line.strip())
|
|
258
273
|
return False, f"the server did not answer: {type(err).__name__}: {err}" + (f" (it said: {said})" if said else "")
|
|
259
274
|
finally:
|
|
260
275
|
log.close()
|
|
261
|
-
|
|
262
|
-
return True, f"answered in {took:.1f} s with {len(tools)} tools;
|
|
276
|
+
first = next((line for line in summary.splitlines() if line.strip()), "(empty)")
|
|
277
|
+
return True, f"answered in {took:.1f} s with {len(tools)} tools; your brain: {first}"
|
|
263
278
|
|
|
264
279
|
|
|
265
280
|
def _registered(client: Client) -> tuple[str, list[str]] | None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainplug
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A second brain for you and your AI assistants: a 3D map of everything you write and a memory they plug into. 100% local.
|
|
5
5
|
Author: Juan Rodríguez
|
|
6
6
|
License-Expression: MIT
|
|
@@ -65,9 +65,7 @@ brainplug doctor ~/Documents/MyVault # checks that it answers, end to e
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
`brainplug brain ~/Documents/MyVault --draft-about` writes a first draft of your "About me"
|
|
68
|
-
note from what you have written, while it is still empty.
|
|
69
|
-
install it from GitHub:
|
|
70
|
-
`uv tool install "brainplug @ git+https://github.com/juanmariarodriguez2-png/brainplug"`.
|
|
68
|
+
note from what you have written, while it is still empty.
|
|
71
69
|
Add `--sessions` to `connect` to include what you typed to Claude Code and Codex, and put
|
|
72
70
|
export files after the vault to include what you wrote before (`… MyVault chatgpt-export.zip`).
|
|
73
71
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "brainplug"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "A second brain for you and your AI assistants: a 3D map of everything you write and a memory they plug into. 100% local."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -16,6 +16,7 @@ def home(tmp_path, monkeypatch):
|
|
|
16
16
|
h = tmp_path / "home"
|
|
17
17
|
h.mkdir()
|
|
18
18
|
for name, value in (("HOME", h), ("USERPROFILE", h), ("APPDATA", h / "AppData" / "Roaming"),
|
|
19
|
+
("LOCALAPPDATA", h / "AppData" / "Local"), # never a real Windows app package
|
|
19
20
|
("XDG_CONFIG_HOME", h / ".config"), ("BRAINPLUG_CACHE", tmp_path / "cache")):
|
|
20
21
|
monkeypatch.setenv(name, str(value))
|
|
21
22
|
monkeypatch.delenv("CODEX_HOME", raising=False)
|
|
@@ -105,6 +106,7 @@ def test_doctor_checks_everything_end_to_end(home, tmp_path, monkeypatch, capsys
|
|
|
105
106
|
assert main(["doctor", str(vault)]) == 0
|
|
106
107
|
out = capsys.readouterr().out
|
|
107
108
|
assert "the server starts and answers" in out and "All good." in out and "✗" not in out
|
|
109
|
+
assert "your brain: 21 items" in out # it waits for the files to be read, then says how much is there
|
|
108
110
|
(home / ".cursor" / "mcp.json").write_text('{"mcpServers": {"brainplug": {"command": "/no/such/brainplug", "args": []}}}')
|
|
109
111
|
assert main(["doctor", str(vault)]) == 1
|
|
110
112
|
assert "brainplug connect" in capsys.readouterr().out
|
|
@@ -122,3 +124,17 @@ def test_a_hanging_assistant_cli_is_reported_not_fatal(monkeypatch):
|
|
|
122
124
|
raise bc.subprocess.TimeoutExpired("claude", 60)
|
|
123
125
|
monkeypatch.setattr(bc.subprocess, "run", hang)
|
|
124
126
|
assert bc._run(["claude", "mcp", "add"]).returncode == 1
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_connect_finds_claude_desktop_installed_as_a_windows_package(home, tmp_path, monkeypatch, capsys):
|
|
130
|
+
# The packaged (MSIX) app reads a virtualized copy of %APPDATA%\\Claude under its package folder.
|
|
131
|
+
monkeypatch.setattr(bc.shutil, "which", fake_which({"brainplug": "/opt/tools/brainplug"}))
|
|
132
|
+
package = home / "AppData" / "Local" / "Packages" / "Claude_pzs8sxrjxfjjc"
|
|
133
|
+
package.mkdir(parents=True)
|
|
134
|
+
vault = tmp_path / "Vault"
|
|
135
|
+
vault.mkdir()
|
|
136
|
+
assert main(["connect", str(vault)]) == 0
|
|
137
|
+
config = package / "LocalCache" / "Roaming" / "Claude" / "claude_desktop_config.json"
|
|
138
|
+
assert json.loads(config.read_text())["mcpServers"]["brainplug"]["args"] == ["mcp", "--brain", str(vault.resolve())]
|
|
139
|
+
assert not bc._app_config("Claude").exists() # the copy this app does not read is not created
|
|
140
|
+
assert "Claude Desktop" in capsys.readouterr().out
|
|
@@ -315,3 +315,13 @@ def test_a_vault_inside_another_input_is_read_once(tmp_path):
|
|
|
315
315
|
(tmp_path / "Docs" / "elsewhere.md").write_text("a loose note outside the vault about tide pools", encoding="utf-8")
|
|
316
316
|
items = sources.load([vault, tmp_path / "Docs"])
|
|
317
317
|
assert len(items) == 11 and len({i.text for i in items}) == 11
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_brain_does_not_create_a_mistyped_folder(tmp_path, capsys):
|
|
321
|
+
import pytest
|
|
322
|
+
for flag in ("--draft-about", "--context", "--review"):
|
|
323
|
+
with pytest.raises(SystemExit):
|
|
324
|
+
main(["brain", str(tmp_path / "C" / "ruta" / "de" / "su" / "bóveda"), flag])
|
|
325
|
+
assert not (tmp_path / "C").exists() and "does not exist" in capsys.readouterr().err
|
|
326
|
+
assert main(["brain", str(tmp_path / "New brain")]) == 0 # setting up a new brain may create it, and says so
|
|
327
|
+
assert "Creating a new folder for your brain" in capsys.readouterr().out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|