leviathan-hivemind-mcp 0.2.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chaz Leland Hamm (HaChazal) — Metanoia Unlimited LLC
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.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: leviathan-hivemind-mcp
3
+ Version: 0.2.0
4
+ Summary: HiveMind MCP — persistent swarm memory through Obsidian, powered by your preferred LLM providers, gated by GitHub Sponsors subscription.
5
+ Author-email: "Chaz Leland Hamm (HaChazal)" <HaChazal@metanoiaunlimited.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Chaz Leland Hamm (HaChazal) — Metanoia Unlimited LLC
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/chazam41892-gif/hivemind-mcp
29
+ Project-URL: Documentation, https://github.com/chazam41892-gif/hivemind-mcp#readme
30
+ Project-URL: Issue Tracker, https://github.com/chazam41892-gif/hivemind-mcp/issues
31
+ Requires-Python: >=3.9
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+ Requires-Dist: mcp>=1.0.0
35
+ Requires-Dist: httpx>=0.27.0
36
+ Requires-Dist: pydantic>=2.0.0
37
+ Dynamic: license-file
38
+
39
+ # HiveMind MCP
40
+
41
+ **Persistent swarm memory through Obsidian — powered by your preferred LLM providers, gated by a GitHub Sponsors subscription.**
42
+
43
+ > **Created by HaChazal (Chaz Leland Hamm) — Metanoia Unlimited LLC.**
44
+ > This is the memory layer of the LeviathanTalon swarm. Every agent, every
45
+ > conversation, every decision lives in your Obsidian vault and is recalled
46
+ > semantically — across any provider you already pay for.
47
+
48
+ ---
49
+
50
+ ## Why this exists
51
+
52
+ AI agents forget. HiveMind MCP makes them *remember* — permanently, locally,
53
+ in your own Obsidian vault, with semantic recall across all your preferred
54
+ LLM providers (OpenAI, Anthropic, Gemini, Grok, DeepSeek, Mistral, Cohere,
55
+ Cerebras, Together, OpenRouter, Braintrust) routed through the Leviathan
56
+ Key Rotator so you never burn a single key dry.
57
+
58
+ ## What it does
59
+
60
+ - **Persistent memory** — reads/writes `HiveMind.md` and the full vault
61
+ (journals, entities, decisions) through Obsidian.
62
+ - **Semantic recall** — embeddings + cosine similarity, not just keyword grep.
63
+ Ask "what did we decide about the video pipeline?" and get the actual
64
+ decision, even if the words don't match.
65
+ - **Provider-agnostic** — one interface, your preferred providers, key-rotated
66
+ and budget-capped.
67
+ - **Subscription-gated** — a GitHub Sponsors subscription unlocks the paid
68
+ provider tier. Free tier works fully local (Ollama) with no API costs.
69
+
70
+ ## Credit
71
+
72
+ This project is the work of **HaChazal (Chaz Leland Hamm)**, Principal
73
+ Operator of the LeviathanTalon swarm, founder of **Metanoia Unlimited LLC**.
74
+ The HiveMind vault architecture, the key rotator, the swarm memory protocol,
75
+ and the Obsidian integration are all his design. If you use this, star the
76
+ repo and follow HaChazal — the swarm runs on community.
77
+
78
+ ## Install
79
+
80
+ ```bash
81
+ pip install leviathan-hivemind-mcp
82
+ ```
83
+
84
+ The distribution is **`leviathan-hivemind-mcp`** on PyPI; the command stays **`hivemind-mcp`**. Zero-config launch from anywhere (auto-downloads on first use):
85
+
86
+ ```bash
87
+ uvx --from leviathan-hivemind-mcp hivemind-mcp
88
+ ```
89
+
90
+ Or clone and run from source:
91
+
92
+ ```bash
93
+ git clone https://github.com/chazam41892-gif/leviathan-hivemind-mcp
94
+ cd leviathan-hivemind-mcp
95
+ pip install -e .
96
+ ```
97
+
98
+ ## Configure
99
+
100
+ Set your vault and provider keys — or drop them into the unified
101
+ `~/.leviathan/HiveMind/.obsidian/HiveMind.env`, which every HiveMind MCP
102
+ server loads automatically (front-end ↔ back-end):
103
+
104
+ ```bash
105
+ export HIVEMIND_VAULT=~/.leviathan/HiveMind # your Obsidian vault
106
+ export HIVEMIND_OWNER=1 # owner mode: skip subscription gate
107
+ export LEVIATHAN_KEY_OPENAI_1=sk-... # key rotator format
108
+ export LEVIATHAN_KEY_ANTHROPIC_1=sk-ant-...
109
+ export LEVIATHAN_BUDGET_OPENAI=10.0 # optional budget caps
110
+ ```
111
+
112
+ ## Run
113
+
114
+ ```bash
115
+ hivemind-mcp
116
+ ```
117
+
118
+ Register it in your MCP client (Claude Desktop, Cursor, OpenCode, etc.):
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "hivemind": {
124
+ "command": "hivemind-mcp",
125
+ "args": []
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Tools
132
+
133
+ | Tool | Description |
134
+ |------|-------------|
135
+ | `read_shared_memory` | Read `HiveMind.md` shared swarm state |
136
+ | `read_soul` | Read `SOUL.md` — swarm identity + doctrine |
137
+ | `append_to_journal` | Append a memory to an agent's journal (append-only) |
138
+ | `recall` | Keyword or semantic recall across the vault |
139
+ | `semantic_recall` | Embedding-based recall (needs a provider or local model) |
140
+ | `read_entity` / `upsert_entity` | Read / layer canonical entities |
141
+ | `read_agent_identity` | Read an agent's identity.md |
142
+ | `read_openai_hivemind` | Read the OpenAI/Codex shared operating memory |
143
+ | `record_openai_highlight` | Write a resumable project capsule → 4 replicas |
144
+ | `chat` | Ask any preferred provider (gated by subscription) |
145
+ | `list_providers` | Show configured providers + key health |
146
+
147
+ ## License
148
+
149
+ MIT — but the *credit* is HaChazal's. Star the repo. Follow the swarm.
150
+
151
+ **Built by HaChazal — Metanoia Unlimited. The swarm remembers.**
@@ -0,0 +1,113 @@
1
+ # HiveMind MCP
2
+
3
+ **Persistent swarm memory through Obsidian — powered by your preferred LLM providers, gated by a GitHub Sponsors subscription.**
4
+
5
+ > **Created by HaChazal (Chaz Leland Hamm) — Metanoia Unlimited LLC.**
6
+ > This is the memory layer of the LeviathanTalon swarm. Every agent, every
7
+ > conversation, every decision lives in your Obsidian vault and is recalled
8
+ > semantically — across any provider you already pay for.
9
+
10
+ ---
11
+
12
+ ## Why this exists
13
+
14
+ AI agents forget. HiveMind MCP makes them *remember* — permanently, locally,
15
+ in your own Obsidian vault, with semantic recall across all your preferred
16
+ LLM providers (OpenAI, Anthropic, Gemini, Grok, DeepSeek, Mistral, Cohere,
17
+ Cerebras, Together, OpenRouter, Braintrust) routed through the Leviathan
18
+ Key Rotator so you never burn a single key dry.
19
+
20
+ ## What it does
21
+
22
+ - **Persistent memory** — reads/writes `HiveMind.md` and the full vault
23
+ (journals, entities, decisions) through Obsidian.
24
+ - **Semantic recall** — embeddings + cosine similarity, not just keyword grep.
25
+ Ask "what did we decide about the video pipeline?" and get the actual
26
+ decision, even if the words don't match.
27
+ - **Provider-agnostic** — one interface, your preferred providers, key-rotated
28
+ and budget-capped.
29
+ - **Subscription-gated** — a GitHub Sponsors subscription unlocks the paid
30
+ provider tier. Free tier works fully local (Ollama) with no API costs.
31
+
32
+ ## Credit
33
+
34
+ This project is the work of **HaChazal (Chaz Leland Hamm)**, Principal
35
+ Operator of the LeviathanTalon swarm, founder of **Metanoia Unlimited LLC**.
36
+ The HiveMind vault architecture, the key rotator, the swarm memory protocol,
37
+ and the Obsidian integration are all his design. If you use this, star the
38
+ repo and follow HaChazal — the swarm runs on community.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install leviathan-hivemind-mcp
44
+ ```
45
+
46
+ The distribution is **`leviathan-hivemind-mcp`** on PyPI; the command stays **`hivemind-mcp`**. Zero-config launch from anywhere (auto-downloads on first use):
47
+
48
+ ```bash
49
+ uvx --from leviathan-hivemind-mcp hivemind-mcp
50
+ ```
51
+
52
+ Or clone and run from source:
53
+
54
+ ```bash
55
+ git clone https://github.com/chazam41892-gif/leviathan-hivemind-mcp
56
+ cd leviathan-hivemind-mcp
57
+ pip install -e .
58
+ ```
59
+
60
+ ## Configure
61
+
62
+ Set your vault and provider keys — or drop them into the unified
63
+ `~/.leviathan/HiveMind/.obsidian/HiveMind.env`, which every HiveMind MCP
64
+ server loads automatically (front-end ↔ back-end):
65
+
66
+ ```bash
67
+ export HIVEMIND_VAULT=~/.leviathan/HiveMind # your Obsidian vault
68
+ export HIVEMIND_OWNER=1 # owner mode: skip subscription gate
69
+ export LEVIATHAN_KEY_OPENAI_1=sk-... # key rotator format
70
+ export LEVIATHAN_KEY_ANTHROPIC_1=sk-ant-...
71
+ export LEVIATHAN_BUDGET_OPENAI=10.0 # optional budget caps
72
+ ```
73
+
74
+ ## Run
75
+
76
+ ```bash
77
+ hivemind-mcp
78
+ ```
79
+
80
+ Register it in your MCP client (Claude Desktop, Cursor, OpenCode, etc.):
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "hivemind": {
86
+ "command": "hivemind-mcp",
87
+ "args": []
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Tools
94
+
95
+ | Tool | Description |
96
+ |------|-------------|
97
+ | `read_shared_memory` | Read `HiveMind.md` shared swarm state |
98
+ | `read_soul` | Read `SOUL.md` — swarm identity + doctrine |
99
+ | `append_to_journal` | Append a memory to an agent's journal (append-only) |
100
+ | `recall` | Keyword or semantic recall across the vault |
101
+ | `semantic_recall` | Embedding-based recall (needs a provider or local model) |
102
+ | `read_entity` / `upsert_entity` | Read / layer canonical entities |
103
+ | `read_agent_identity` | Read an agent's identity.md |
104
+ | `read_openai_hivemind` | Read the OpenAI/Codex shared operating memory |
105
+ | `record_openai_highlight` | Write a resumable project capsule → 4 replicas |
106
+ | `chat` | Ask any preferred provider (gated by subscription) |
107
+ | `list_providers` | Show configured providers + key health |
108
+
109
+ ## License
110
+
111
+ MIT — but the *credit* is HaChazal's. Star the repo. Follow the swarm.
112
+
113
+ **Built by HaChazal — Metanoia Unlimited. The swarm remembers.**
@@ -0,0 +1,8 @@
1
+ """HiveMind MCP — persistent swarm memory through Obsidian.
2
+
3
+ Created by HaChazal (Chaz Leland Hamm) — Metanoia Unlimited LLC.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+ __author__ = "Chaz Leland Hamm (HaChazal)"
8
+ __license__ = "MIT"
@@ -0,0 +1,243 @@
1
+ """Vault — the markdown read/write engine for the HiveMind.
2
+
3
+ Operates directly on the vault's markdown files per the contract in
4
+ shared/conventions.md. Stdlib-only. Preservation Doctrine is enforced
5
+ structurally: journals are opened in append mode, entities are layered
6
+ (never rewritten), and nothing is ever truncated.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import random
12
+ import re
13
+ import time
14
+ from datetime import datetime, timezone
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ # Allowed journal entry kinds (from .brain/config.yaml entry_kinds).
19
+ ENTRY_KINDS = (
20
+ "observation", "decision", "action", "result",
21
+ "question", "learning", "handoff",
22
+ )
23
+
24
+ # type -> entities subdir (the canonical entity folders that exist in the vault).
25
+ ENTITY_DIRS = {
26
+ "person": "people", "people": "people",
27
+ "project": "projects", "projects": "projects",
28
+ "token": "tokens", "tokens": "tokens",
29
+ "account": "accounts", "accounts": "accounts",
30
+ "client": "clients", "clients": "clients",
31
+ "concept": "concepts", "concepts": "concepts",
32
+ "media": "media", "video": "media", "image": "media",
33
+ }
34
+
35
+ _CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
36
+
37
+
38
+ def _now() -> str:
39
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
40
+
41
+
42
+ def _ulid() -> str:
43
+ """Sortable ULID-ish id: 10-char time + 16-char randomness, Crockford b32.
44
+ Good enough for vault ids (monotonic-ish, collision-resistant); no deps."""
45
+ t = int(time.time() * 1000)
46
+ ts = ""
47
+ for _ in range(10):
48
+ ts = _CROCKFORD[t & 31] + ts
49
+ t >>= 5
50
+ rnd = "".join(random.choice(_CROCKFORD) for _ in range(16))
51
+ return ts + rnd
52
+
53
+
54
+ def _vault_root(root: Optional[str] = None) -> Path:
55
+ if root:
56
+ return Path(root).expanduser()
57
+ env = os.environ.get("HIVEMIND_VAULT")
58
+ if env:
59
+ return Path(env).expanduser()
60
+ return Path.home() / ".leviathan" / "HiveMind"
61
+
62
+
63
+ def _as_list(x) -> list:
64
+ if x is None:
65
+ return []
66
+ if isinstance(x, (list, tuple)):
67
+ return [str(i) for i in x]
68
+ return [str(x)]
69
+
70
+
71
+ class Vault:
72
+ def __init__(self, root: Optional[str] = None):
73
+ self.root = _vault_root(root)
74
+ if not self.root.exists():
75
+ raise FileNotFoundError(f"HiveMind vault not found at {self.root}")
76
+
77
+ # ── paths ──────────────────────────────────────────────────────────
78
+ def _journal(self, agent: str) -> Path:
79
+ return self.root / "agents" / agent / "journal.md"
80
+
81
+ # ── 1. append_to_journal (APPEND-ONLY) ─────────────────────────────
82
+ def append_to_journal(self, agent: str, kind: str, body: str,
83
+ tags=None, links=None, session: Optional[str] = None) -> dict:
84
+ if kind not in ENTRY_KINDS:
85
+ raise ValueError(f"kind {kind!r} not in {ENTRY_KINDS}")
86
+ agent = str(agent).strip()
87
+ if not agent:
88
+ raise ValueError("agent is required")
89
+ tags = _as_list(tags)
90
+ links = _as_list(links)
91
+ ts = _now()
92
+
93
+ jp = self._journal(agent)
94
+ jp.parent.mkdir(parents=True, exist_ok=True)
95
+ if not jp.exists(): # seed with frontmatter on first write
96
+ jp.write_text(
97
+ f"---\nid: {_ulid()}\ntype: journal\nagent: {agent}\n"
98
+ f"created: {ts}\nupdated: {ts}\ntags: [journal]\n---\n\n"
99
+ f"# {agent} — journal (append-only)\n",
100
+ encoding="utf-8",
101
+ )
102
+
103
+ links_md = ", ".join(f"[[{l}]]" for l in links)
104
+ block = (
105
+ f"\n## {ts} — {kind}\n"
106
+ f"- session: {session or ''}\n"
107
+ f"- tags: [{', '.join(tags)}]\n"
108
+ f"- links: {links_md}\n\n"
109
+ f"{body.rstrip()}\n"
110
+ )
111
+ with jp.open("a", encoding="utf-8") as f: # append mode = Preservation
112
+ f.write(block)
113
+ return {"agent": agent, "kind": kind, "ts": ts, "tags": tags,
114
+ "links": links, "session": session, "body": body, "file": str(jp)}
115
+
116
+ # ── 2. recall ───────────────────────────────────────────────────────
117
+ def recall(self, query: Optional[str] = None, agent: Optional[str] = None,
118
+ kind: Optional[str] = None, tags=None, links=None,
119
+ since: Optional[str] = None, mode: str = "keyword",
120
+ limit: int = 50, fast: bool = False) -> list:
121
+ # Optional FTS accelerator (as-of-last-reindex). Falls back to the
122
+ # always-correct scan below if the index is missing/unavailable.
123
+ if fast and query and not tags and not links and not since:
124
+ try:
125
+ from .index import Index
126
+ rows = Index(self.root).search(query, agent=agent, kind=kind, limit=limit)
127
+ if rows is not None:
128
+ return rows
129
+ except Exception:
130
+ pass
131
+ want_tags = set(_as_list(tags))
132
+ want_links = set(_as_list(links))
133
+ q = (query or "").lower().strip()
134
+ results = []
135
+
136
+ agents_dir = self.root / "agents"
137
+ agent_dirs = ([agents_dir / agent] if agent else
138
+ [d for d in agents_dir.glob("*") if d.is_dir()])
139
+ for ad in agent_dirs:
140
+ jp = ad / "journal.md"
141
+ if not jp.exists():
142
+ continue
143
+ who = ad.name
144
+ for e in self._parse_journal(jp.read_text(encoding="utf-8", errors="replace")):
145
+ e["agent"] = who
146
+ e["file"] = str(jp)
147
+ if kind and e["kind"] != kind:
148
+ continue
149
+ if since and e["ts"] < since:
150
+ continue
151
+ if want_tags and not want_tags & set(e["tags"]):
152
+ continue
153
+ if want_links and not want_links & set(e["links"]):
154
+ continue
155
+ if q and q not in e["body"].lower() and not any(q in t.lower() for t in e["tags"]):
156
+ continue
157
+ results.append(e)
158
+
159
+ results.sort(key=lambda e: e["ts"], reverse=True)
160
+ return results[:limit]
161
+
162
+ @staticmethod
163
+ def _parse_journal(text: str) -> list:
164
+ """Split a journal.md into entry dicts on the `## <ts> — <kind>` headers."""
165
+ out = []
166
+ header = re.compile(r"^##\s+(\S+)\s+—\s+(\w+)\s*$", re.M)
167
+ marks = list(header.finditer(text))
168
+ for i, m in enumerate(marks):
169
+ ts, kind = m.group(1), m.group(2)
170
+ seg = text[m.end(): marks[i + 1].start() if i + 1 < len(marks) else len(text)]
171
+ tags, links, session = [], [], ""
172
+ body_lines = []
173
+ for line in seg.splitlines():
174
+ ls = line.strip()
175
+ if ls.startswith("- session:"):
176
+ session = ls[len("- session:"):].strip()
177
+ elif ls.startswith("- tags:"):
178
+ inner = ls[ls.find("[") + 1: ls.rfind("]")] if "[" in ls else ""
179
+ tags = [t.strip() for t in inner.split(",") if t.strip()]
180
+ elif ls.startswith("- links:"):
181
+ links = re.findall(r"\[\[([^\]|]+)", ls)
182
+ else:
183
+ body_lines.append(line)
184
+ out.append({"ts": ts, "kind": kind, "tags": tags, "links": links,
185
+ "session": session, "body": "\n".join(body_lines).strip()})
186
+ return out
187
+
188
+ # ── 3. read_entity ──────────────────────────────────────────────────
189
+ def read_entity(self, name: str) -> Optional[dict]:
190
+ ent = self.root / "entities"
191
+ for f in ent.rglob(f"{name}.md"): # first match wins
192
+ return {"name": name, "file": str(f), "text": f.read_text(encoding="utf-8", errors="replace")}
193
+ return None
194
+
195
+ # ── 4. upsert_entity (CREATE or LAYER — never overwrite) ────────────
196
+ def upsert_entity(self, name: str, body: str, type: str = "concept",
197
+ merge: bool = True) -> dict:
198
+ existing = self.read_entity(name)
199
+ ts = _now()
200
+ if existing:
201
+ if not merge:
202
+ raise FileExistsError(f"entity {name!r} exists and merge=False")
203
+ with Path(existing["file"]).open("a", encoding="utf-8") as f: # LAYER
204
+ f.write(f"\n\n## update {ts}\n\n{body.rstrip()}\n")
205
+ return {**existing, "layered": True, "ts": ts}
206
+ sub = ENTITY_DIRS.get(type.lower(), "concepts")
207
+ d = self.root / "entities" / sub
208
+ d.mkdir(parents=True, exist_ok=True)
209
+ fp = d / f"{name}.md"
210
+ fp.write_text(
211
+ f"---\nid: {_ulid()}\ntype: entity\ncreated: {ts}\nupdated: {ts}\n"
212
+ f"tags: [{type}]\n---\n\n# {name}\n\n{body.rstrip()}\n",
213
+ encoding="utf-8",
214
+ )
215
+ return {"name": name, "file": str(fp), "created": True, "ts": ts}
216
+
217
+
218
+ # ── module-level convenience wrappers (the tool surface) ────────────────
219
+ _default: Optional[Vault] = None
220
+
221
+
222
+ def _v() -> Vault:
223
+ global _default
224
+ if _default is None:
225
+ _default = Vault()
226
+ return _default
227
+
228
+
229
+ def append_to_journal(agent, kind, body, tags=None, links=None, session=None):
230
+ return _v().append_to_journal(agent, kind, body, tags, links, session)
231
+
232
+
233
+ def recall(query=None, agent=None, kind=None, tags=None, links=None,
234
+ since=None, mode="keyword", limit=50):
235
+ return _v().recall(query, agent, kind, tags, links, since, mode, limit)
236
+
237
+
238
+ def read_entity(name):
239
+ return _v().read_entity(name)
240
+
241
+
242
+ def upsert_entity(name, body, type="concept", merge=True):
243
+ return _v().upsert_entity(name, body, type, merge)
@@ -0,0 +1,84 @@
1
+ """HiveMind.env bridge — the front-end ↔ back-end credential layer.
2
+
3
+ Loads the operator's consolidated HiveMind.env (default:
4
+ ``~/.leviathan/HiveMind/.obsidian/HiveMind.env``, override with
5
+ ``HIVEMIND_ENV_FILE``) into ``os.environ`` for any AI client that spawns
6
+ this MCP server. Existing process environment ALWAYS wins — this only
7
+ fills gaps, so an app that already exports a key is never overridden.
8
+
9
+ Mapping rules:
10
+ * ``KEY="value"`` / ``KEY=value`` / tab-indented lines are parsed; quotes
11
+ and trailing ``\\r`` are stripped; ``#`` comments and blanks skipped.
12
+ * Plural pool → singular export: ``FOO_API_KEYS="k1, k2"`` also exports
13
+ ``FOO_API_KEY`` = first key (matching the provider adapters' env
14
+ fallback names) when the singular is not already set.
15
+ * Alias: ``XAI_API_KEYS``/``XAI_API_KEY`` also fill ``GROK_API_KEY``.
16
+
17
+ Stdlib-only; never raises; returns the number of new names exported.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ from pathlib import Path
24
+ from typing import Optional
25
+
26
+ DEFAULT_ENV_FILE = Path.home() / ".leviathan" / "HiveMind" / ".obsidian" / "HiveMind.env"
27
+
28
+
29
+ def _parse_env_file(path: Path) -> dict[str, str]:
30
+ pairs: dict[str, str] = {}
31
+ try:
32
+ text = path.read_text(encoding="utf-8", errors="replace")
33
+ except Exception:
34
+ return pairs
35
+ for raw in text.splitlines():
36
+ line = raw.strip().lstrip("\ufeff")
37
+ if not line or line.startswith("#"):
38
+ continue
39
+ if "=" not in line:
40
+ continue
41
+ key, _, value = line.partition("=")
42
+ key = key.strip()
43
+ value = value.strip().strip("\r")
44
+ if not key or not value:
45
+ continue
46
+ if value[:1] in ('"', "'") and value[-1:] == value[:1]:
47
+ value = value[1:-1]
48
+ if not value:
49
+ continue
50
+ pairs.setdefault(key, value)
51
+ return pairs
52
+
53
+
54
+ def load_env_file(path: Optional[Path] = None) -> int:
55
+ """Export HiveMind.env into os.environ (fill-gaps only). Returns count."""
56
+ env_file = Path(path) if path else Path(os.environ.get("HIVEMIND_ENV_FILE", DEFAULT_ENV_FILE))
57
+ pairs = _parse_env_file(env_file)
58
+ if not pairs:
59
+ return 0
60
+
61
+ exported = 0
62
+ for key, value in pairs.items():
63
+ if key not in os.environ:
64
+ os.environ[key] = value
65
+ exported += 1
66
+
67
+ # Plural pool → singular provider-key export.
68
+ for key, value in pairs.items():
69
+ if key.endswith("_API_KEYS"):
70
+ singular = key[:-1] # FOO_API_KEYS -> FOO_API_KEY
71
+ if not os.environ.get(singular):
72
+ first = value.split(",")[0].strip()
73
+ if first:
74
+ os.environ[singular] = first
75
+ exported += 1
76
+
77
+ # xAI → Grok alias (the provider registry calls it "grok").
78
+ for src, dst in (("XAI_API_KEY", "GROK_API_KEY"),
79
+ ("XAI_API_KEYS", "GROK_API_KEYS")):
80
+ if os.environ.get(src) and not os.environ.get(dst):
81
+ os.environ[dst] = os.environ[src]
82
+ exported += 1
83
+
84
+ return exported