@arkheia/mcp-server 0.1.4 → 0.1.6

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.
package/README.md CHANGED
@@ -1,89 +1,177 @@
1
- # Arkheia MCP Server — Fabrication Detection for AI Agents
2
-
3
- Know when your AI is making things up.
4
-
5
- Arkheia screens model responses for fabrication using behavioural fingerprinting. Works with Claude, GPT, Gemini, Grok, Llama, Mistral, and 30+ other models. One tool call. Real-time risk scoring.
6
-
7
- Free tier: 1,500 detections/month. No credit card.
8
-
9
- ## Quick Start
10
-
11
- ```bash
12
- npx @arkheia/mcp-server
13
- ```
14
-
15
- Get a free API key:
16
-
17
- ```bash
18
- curl -X POST https://arkheia-proxy-production.up.railway.app/v1/provision \
19
- -H "Content-Type: application/json" \
20
- -d '{"email": "you@example.com"}'
21
- ```
22
-
23
- Add to your agent config (Claude Code, Claude Desktop, Cursor, or any MCP-compatible tool):
24
-
25
- ```json
26
- {
27
- "mcpServers": {
28
- "arkheia": {
29
- "command": "python",
30
- "args": ["-m", "mcp_server.server"],
31
- "cwd": "~/.arkheia/mcp",
32
- "env": {
33
- "PYTHONPATH": "~/.arkheia/mcp",
34
- "ARKHEIA_API_KEY": "ak_live_your_key_here"
35
- }
36
- }
37
- }
38
- }
39
- ```
40
-
41
- Restart your agent. Then ask it:
42
-
43
- > "Use arkheia_verify to check this response: The Kafka 4.1 ConsumerLease API introduces a lease-based partition ownership model."
44
-
45
- It should flag this as **HIGH** risk — because the Kafka 4.1 ConsumerLease API doesn't exist.
46
-
47
- ## What You Get
48
-
49
- | Tool | Description |
50
- |------|-------------|
51
- | `arkheia_verify` | Score any model response for fabrication risk (LOW/MEDIUM/HIGH) |
52
- | `arkheia_audit_log` | Review your detection history |
53
- | `run_grok` | Call Grok + screen for fabrication |
54
- | `run_gemini` | Call Gemini + screen for fabrication |
55
- | `run_ollama` | Call local Ollama model + screen |
56
- | `run_together` | Call Together AI (Kimi, DeepSeek) + screen |
57
-
58
- ## 35+ Model Profiles
59
-
60
- GPT-4o, GPT-5.4, Claude Opus/Sonnet/Haiku, Gemini 2.5/3.0, Grok 4, Llama, Mixtral, CodeLlama, Falcon, Phi4, Kimi K2.5, and more. If your model isn't listed, [let us know](mailto:dmurfet@arkheia.ai).
61
-
62
- ## Pricing
63
-
64
- | Plan | Price | Detections |
65
- |------|-------|------------|
66
- | Free | $0 | 1,500/month |
67
- | Single Contributor | $99/month | Unlimited |
68
- | Professional | $499/month | Unlimited |
69
- | Team | $1,999/month | Unlimited |
70
-
71
- ## Full Documentation
72
-
73
- See the [GitHub repo](https://github.com/arkheiaai/arkheia-mcp) for:
74
- - Complete setup guide for all agents
75
- - CLAUDE.md template for automatic detection across projects
76
- - Multi-agent quorum pattern
77
- - Test prompts and examples
78
-
79
- ## Feedback
80
-
81
- - **GitHub Issues:** https://github.com/arkheiaai/arkheia-mcp/issues
82
- - **Email:** dmurfet@arkheia.ai
83
-
84
- Every message is read by the founder.
85
-
86
- ## Links
87
-
88
- - Website: https://arkheia.ai
89
- - GitHub: https://github.com/arkheiaai/arkheia-mcp
1
+ # Arkheia MCP Server — Fabrication Detection for AI Agents
2
+
3
+ Know when your AI is making things up.
4
+
5
+ Arkheia screens model responses for fabrication using behavioural fingerprinting. Works with Claude, GPT, Gemini, Grok, Llama, Mistral, and 30+ other models. One tool call. Real-time risk scoring.
6
+
7
+ Free tier: 1,500 detections/month. No credit card.
8
+
9
+ ## Prerequisites
10
+
11
+ ```
12
+ Requires:
13
+ - Node 18+
14
+ - Python 3.10–3.13 with working pyexpat
15
+
16
+ macOS note: Homebrew's current `brew install python` installs 3.14,
17
+ which has a broken pyexpat link. Use `brew install python@3.12` until
18
+ Homebrew ships a fix. Verify with:
19
+ python3.12 -c "import pyexpat, ensurepip"
20
+ ```
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install -g @arkheia/mcp-server
26
+ ```
27
+
28
+ Get a free API key at [arkheia.ai/mcp/account](https://arkheia.ai/mcp/account), or via the CLI:
29
+
30
+ ```bash
31
+ curl -X POST https://arkheia-proxy-production.up.railway.app/v1/provision \
32
+ -H "Content-Type: application/json" \
33
+ -d '{"email": "you@example.com"}'
34
+ ```
35
+
36
+ Set your key:
37
+
38
+ ```bash
39
+ export ARKHEIA_API_KEY="ak_live_..."
40
+ ```
41
+
42
+ ## Register with your CLI
43
+
44
+ Each AI CLI has a slightly different `mcp add` command. Use the one that matches your tool. All assume you've installed globally with `npm install -g`.
45
+
46
+ ### Claude Code
47
+
48
+ ```bash
49
+ claude mcp add arkheia -s user \
50
+ -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
51
+ -- mcp-server
52
+ ```
53
+
54
+ Config lands in: `~/.claude.json` under `mcpServers.arkheia`
55
+
56
+ ### Codex
57
+
58
+ ```bash
59
+ codex mcp add arkheia \
60
+ --env ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
61
+ -- mcp-server
62
+ ```
63
+
64
+ Config lands in: `~/.codex/config.toml` under `[mcp_servers.arkheia.env]`
65
+
66
+ Note: `codex login --api-key` is deprecated. Use `printenv OPENAI_API_KEY | codex login --with-api-key` instead.
67
+
68
+ ### Gemini
69
+
70
+ ```bash
71
+ gemini mcp add -s user \
72
+ -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
73
+ arkheia mcp-server
74
+ ```
75
+
76
+ Config lands in: `~/.gemini/settings.json` under `mcpServers.arkheia`
77
+
78
+ **Gotcha:** `gemini mcp list` only shows project-scope servers. If you registered with `-s user`, verify by reading `~/.gemini/settings.json` directly.
79
+
80
+ **Gotcha:** Don't use `npx -y @arkheia/mcp-server` with Gemini — the `-y` flag gets eaten by Gemini's yargs parser as `--yolo`. Use the globally-installed `mcp-server` binary directly.
81
+
82
+ ### Grok
83
+
84
+ ```bash
85
+ grok mcp add arkheia \
86
+ -t stdio \
87
+ -c mcp-server \
88
+ -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY"
89
+ ```
90
+
91
+ Config lands in: `~/.grok/settings.json` under `mcpServers.arkheia` (note: env is nested under `transport`, unlike other CLIs)
92
+
93
+ ## Verify it works
94
+
95
+ ```bash
96
+ # Claude Code — live connection test
97
+ claude mcp list
98
+
99
+ # Codex — shows 'enabled' (not a live check)
100
+ codex mcp list
101
+
102
+ # Grok — best: spawns the server and lists all 9 tools
103
+ grok mcp test arkheia
104
+
105
+ # Gemini — no built-in test; start a session and try the tool
106
+ ```
107
+
108
+ **Important:** MCP registrations are not hot-reloaded. Restart your CLI session after running `mcp add`.
109
+
110
+ ## What You Get
111
+
112
+ | Tool | Description |
113
+ |------|-------------|
114
+ | `arkheia_verify` | Score any model response for fabrication risk (LOW/MEDIUM/HIGH) |
115
+ | `arkheia_audit_log` | Review your detection history |
116
+ | `run_grok` | Call Grok + screen for fabrication |
117
+ | `run_gemini` | Call Gemini + screen for fabrication |
118
+ | `run_ollama` | Call local Ollama model + screen |
119
+ | `run_together` | Call Together AI (Kimi, DeepSeek) + screen |
120
+ | `memory_store` | Persistent knowledge graph — upsert entity |
121
+ | `memory_retrieve` | Knowledge graph lookup |
122
+ | `memory_relate` | Create relationship between entities |
123
+
124
+ ## 35+ Model Profiles
125
+
126
+ GPT-4o, GPT-5.4, Claude Opus/Sonnet/Haiku, Gemini 2.5/3.0, Grok 4, Llama, Mixtral, CodeLlama, Falcon, Phi4, Kimi K2.5, and more. If your model isn't listed, [let us know](mailto:dmurfet@arkheia.ai).
127
+
128
+ ## Pricing
129
+
130
+ | Plan | Price | Detections |
131
+ |------|-------|------------|
132
+ | Free | $0 | 1,500/month |
133
+ | Single Contributor | $99/month | Unlimited |
134
+ | Professional | $499/month | Unlimited |
135
+ | Team | $1,999/month | Unlimited |
136
+
137
+ Manage your account at [arkheia.ai/mcp/account](https://arkheia.ai/mcp/account).
138
+
139
+ ## Where API keys are stored
140
+
141
+ | CLI | Config file | Key location |
142
+ |-----|-------------|-------------|
143
+ | Claude Code | `~/.claude.json` | `mcpServers.arkheia.env.ARKHEIA_API_KEY` |
144
+ | Codex | `~/.codex/config.toml` | `[mcp_servers.arkheia.env]` section |
145
+ | Gemini | `~/.gemini/settings.json` | `mcpServers.arkheia.env.ARKHEIA_API_KEY` |
146
+ | Grok | `~/.grok/settings.json` | `mcpServers.arkheia.transport.env.ARKHEIA_API_KEY` |
147
+
148
+ ## Troubleshooting
149
+
150
+ **"Python 3.10+ is required but not found"** — Install Python 3.12: `brew install python@3.12` (macOS) or download from [python.org](https://python.org).
151
+
152
+ **"No module named pip"** — Your Python installation has broken pip (common with Python 3.14 on macOS). Delete `~/.arkheia/venv` and switch to Python 3.12: `brew install python@3.12`.
153
+
154
+ **Server registered but tools not showing** — Restart your CLI session. MCP registrations are not hot-reloaded.
155
+
156
+ **API key rejected** — Check for trailing whitespace or `\r` characters. If your env file was created on Windows, run `dos2unix` on it. The server will warn about this on startup.
157
+
158
+ ## Full Documentation
159
+
160
+ See the [GitHub repo](https://github.com/arkheiaai/arkheia-mcp) for:
161
+ - Complete setup guide for all agents
162
+ - CLAUDE.md template for automatic detection across projects
163
+ - Multi-agent quorum pattern
164
+ - Test prompts and examples
165
+
166
+ ## Feedback
167
+
168
+ - **GitHub Issues:** https://github.com/arkheiaai/arkheia-mcp/issues
169
+ - **Email:** dmurfet@arkheia.ai
170
+
171
+ Every message is read by the founder.
172
+
173
+ ## Links
174
+
175
+ - Website: https://arkheia.ai
176
+ - MCP Account: https://arkheia.ai/mcp/account
177
+ - GitHub: https://github.com/arkheiaai/arkheia-mcp
package/dist/index.js ADDED
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+ const zod_1 = require("zod");
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ const fs = __importStar(require("fs"));
43
+ const proxy_client_js_1 = require("./proxy-client.js");
44
+ const tool_registry_js_1 = require("./tool-registry.js");
45
+ const providers_js_1 = require("./providers.js");
46
+ const memory_js_1 = require("./memory.js");
47
+ // ---------------------------------------------------------------------------
48
+ // Config Loading
49
+ // ---------------------------------------------------------------------------
50
+ function loadConfig() {
51
+ const configPath = path.join(os.homedir(), '.arkheia', 'config.json');
52
+ if (!fs.existsSync(configPath))
53
+ return;
54
+ try {
55
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
56
+ if (config.api_key && !process.env.ARKHEIA_API_KEY) {
57
+ process.env.ARKHEIA_API_KEY = config.api_key;
58
+ process.stderr.write(`[arkheia] API key loaded from ${configPath}\n`);
59
+ }
60
+ if (config.proxy_url && !process.env.ARKHEIA_HOSTED_URL) {
61
+ process.env.ARKHEIA_HOSTED_URL = config.proxy_url;
62
+ process.stderr.write(`[arkheia] Hosted URL: ${config.proxy_url}\n`);
63
+ }
64
+ }
65
+ catch (e) {
66
+ process.stderr.write(`[arkheia] Warning: Could not read ${configPath}\n`);
67
+ }
68
+ }
69
+ function checkCRLF() {
70
+ for (const k of ['ARKHEIA_API_KEY', 'ARKHEIA_PROXY_URL', 'ARKHEIA_HOSTED_URL']) {
71
+ const v = process.env[k];
72
+ if (v && /[\r\n]/.test(v)) {
73
+ process.stderr.write(`[arkheia] WARNING: ${k} contains whitespace/newline characters. Run 'dos2unix' on your env file.\n`);
74
+ process.env[k] = v.trim();
75
+ }
76
+ }
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Helpers
80
+ // ---------------------------------------------------------------------------
81
+ function wrapResult(result) {
82
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
83
+ }
84
+ function handleError(toolName, e) {
85
+ if (e instanceof tool_registry_js_1.PolicyViolation) {
86
+ return wrapResult({ error: e.reason, risk_level: "UNKNOWN" });
87
+ }
88
+ process.stderr.write(`[arkheia] ${toolName} error: ${e.message}\n`);
89
+ return wrapResult({ error: e.message, risk_level: "UNKNOWN" });
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Main
93
+ // ---------------------------------------------------------------------------
94
+ async function main() {
95
+ loadConfig();
96
+ checkCRLF();
97
+ const ARKHEIA_PROXY_URL = process.env.ARKHEIA_PROXY_URL || "http://localhost:8098";
98
+ const ARKHEIA_HOSTED_URL = process.env.ARKHEIA_HOSTED_URL || "https://arkheia-proxy-production.up.railway.app";
99
+ const ARKHEIA_API_KEY = process.env.ARKHEIA_API_KEY;
100
+ if (!ARKHEIA_API_KEY) {
101
+ process.stderr.write("[arkheia] Warning: ARKHEIA_API_KEY not set.\n" +
102
+ "Get a free API key at https://arkheia.ai/mcp\n" +
103
+ "Then set: export ARKHEIA_API_KEY=ak_live_...\n\n");
104
+ }
105
+ const proxy = new proxy_client_js_1.ProxyClient(ARKHEIA_PROXY_URL, 10.0, ARKHEIA_HOSTED_URL, ARKHEIA_API_KEY);
106
+ const server = new mcp_js_1.McpServer({ name: "arkheia-trust", version: "0.1.6" });
107
+ // ── Detection & audit ──────────────────────────────────────────
108
+ server.tool("arkheia_verify", "Verify whether an AI response shows signs of fabrication. Call this on EVERY model response before acting on it.", { prompt: zod_1.z.string(), response: zod_1.z.string(), model: zod_1.z.string() }, async ({ prompt, response, model }) => {
109
+ try {
110
+ (0, tool_registry_js_1.check)("arkheia_verify");
111
+ const result = await proxy.verify(prompt, response, model);
112
+ return wrapResult(result);
113
+ }
114
+ catch (e) {
115
+ return handleError("arkheia_verify", e);
116
+ }
117
+ });
118
+ server.tool("arkheia_audit_log", "Retrieve structured audit evidence for compliance review.", { session_id: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(500).default(50) }, async ({ session_id, limit }) => {
119
+ try {
120
+ (0, tool_registry_js_1.check)("arkheia_audit_log");
121
+ const result = await proxy.get_audit_log(session_id, Math.min(limit, 500));
122
+ return wrapResult(result);
123
+ }
124
+ catch (e) {
125
+ return handleError("arkheia_audit_log", e);
126
+ }
127
+ });
128
+ // ── Provider wrappers ──────────────────────────────────────────
129
+ server.tool("run_grok", "Call xAI Grok and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("grok-4-fast-non-reasoning") }, async ({ prompt, model }) => {
130
+ try {
131
+ (0, tool_registry_js_1.check)("run_grok");
132
+ const pr = await (0, providers_js_1.call_grok)(prompt, model);
133
+ if (pr.error)
134
+ return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
135
+ const risk = await proxy.verify(prompt, pr.response, model);
136
+ return wrapResult({ ...pr, arkheia: risk });
137
+ }
138
+ catch (e) {
139
+ return handleError("run_grok", e);
140
+ }
141
+ });
142
+ server.tool("run_gemini", "Call Google Gemini and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("gemini-2.5-flash") }, async ({ prompt, model }) => {
143
+ try {
144
+ (0, tool_registry_js_1.check)("run_gemini");
145
+ const pr = await (0, providers_js_1.call_gemini)(prompt, model);
146
+ if (pr.error)
147
+ return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
148
+ const risk = await proxy.verify(prompt, pr.response, model);
149
+ return wrapResult({ ...pr, arkheia: risk });
150
+ }
151
+ catch (e) {
152
+ return handleError("run_gemini", e);
153
+ }
154
+ });
155
+ server.tool("run_ollama", "Call a local Ollama model and screen the response through Arkheia. No network egress.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("phi4:14b") }, async ({ prompt, model }) => {
156
+ try {
157
+ (0, tool_registry_js_1.check)("run_ollama");
158
+ const pr = await (0, providers_js_1.call_ollama)(prompt, model);
159
+ if (pr.error)
160
+ return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
161
+ const risk = await proxy.verify(prompt, pr.response, model);
162
+ return wrapResult({ ...pr, arkheia: risk });
163
+ }
164
+ catch (e) {
165
+ return handleError("run_ollama", e);
166
+ }
167
+ });
168
+ server.tool("run_together", "Call Together AI (Kimi K2.5, DeepSeek, etc.) and screen the response through Arkheia.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("moonshotai/Kimi-K2.5") }, async ({ prompt, model }) => {
169
+ try {
170
+ (0, tool_registry_js_1.check)("run_together");
171
+ const pr = await (0, providers_js_1.call_together)(prompt, model);
172
+ if (pr.error)
173
+ return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
174
+ const risk = await proxy.verify(prompt, pr.response, model);
175
+ return wrapResult({ ...pr, arkheia: risk });
176
+ }
177
+ catch (e) {
178
+ return handleError("run_together", e);
179
+ }
180
+ });
181
+ // ── Memory / Knowledge Graph ───────────────────────────────────
182
+ server.tool("memory_store", "Store an entity and its observations in the persistent knowledge graph. Entities are upserted by name+type.", { name: zod_1.z.string(), entity_type: zod_1.z.string(), observations: zod_1.z.array(zod_1.z.string()) }, async ({ name, entity_type, observations }) => {
183
+ try {
184
+ (0, tool_registry_js_1.check)("memory_store");
185
+ const result = await (0, memory_js_1.store_entity)(name, entity_type, observations);
186
+ return wrapResult(result);
187
+ }
188
+ catch (e) {
189
+ return handleError("memory_store", e);
190
+ }
191
+ });
192
+ server.tool("memory_retrieve", "Search entities in the persistent knowledge graph by name (case-insensitive).", { query: zod_1.z.string(), entity_type: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(50).default(10) }, async ({ query, entity_type, limit }) => {
193
+ try {
194
+ (0, tool_registry_js_1.check)("memory_retrieve");
195
+ const result = await (0, memory_js_1.retrieve_entities)(query, entity_type, Math.min(limit, 50));
196
+ return wrapResult(result);
197
+ }
198
+ catch (e) {
199
+ return handleError("memory_retrieve", e);
200
+ }
201
+ });
202
+ server.tool("memory_relate", "Store a named relationship between two entities in the knowledge graph.", { from_entity: zod_1.z.string(), relation_type: zod_1.z.string(), to_entity: zod_1.z.string() }, async ({ from_entity, relation_type, to_entity }) => {
203
+ try {
204
+ (0, tool_registry_js_1.check)("memory_relate");
205
+ const result = await (0, memory_js_1.store_relation)(from_entity, relation_type, to_entity);
206
+ return wrapResult(result);
207
+ }
208
+ catch (e) {
209
+ return handleError("memory_relate", e);
210
+ }
211
+ });
212
+ // ── Start ──────────────────────────────────────────────────────
213
+ const transport = new stdio_js_1.StdioServerTransport();
214
+ await server.connect(transport);
215
+ }
216
+ main().catch((err) => {
217
+ process.stderr.write(`[arkheia] Fatal error: ${err}\n`);
218
+ process.exit(1);
219
+ });
package/dist/memory.js ADDED
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.store_entity = store_entity;
40
+ exports.retrieve_entities = retrieve_entities;
41
+ exports.store_relation = store_relation;
42
+ const sql_js_1 = __importDefault(require("sql.js"));
43
+ const path = __importStar(require("path"));
44
+ const fs = __importStar(require("fs"));
45
+ const crypto = __importStar(require("crypto"));
46
+ const os = __importStar(require("os"));
47
+ const logger = console;
48
+ // ---------------------------------------------------------------------------
49
+ // DB setup — sql.js (pure JS, no native module)
50
+ // ---------------------------------------------------------------------------
51
+ function _db_path() {
52
+ return process.env.MEMORY_DB_PATH || path.join(os.homedir(), '.arkheia', 'memory.db');
53
+ }
54
+ let _db = null;
55
+ let _dbReady = null;
56
+ function _getDb() {
57
+ if (_dbReady)
58
+ return _dbReady;
59
+ _dbReady = (async () => {
60
+ const SQL = await (0, sql_js_1.default)();
61
+ const dbPath = _db_path();
62
+ const dir = path.dirname(dbPath);
63
+ if (!fs.existsSync(dir)) {
64
+ fs.mkdirSync(dir, { recursive: true });
65
+ }
66
+ // Load existing DB or create new
67
+ if (fs.existsSync(dbPath)) {
68
+ const buffer = fs.readFileSync(dbPath);
69
+ _db = new SQL.Database(buffer);
70
+ }
71
+ else {
72
+ _db = new SQL.Database();
73
+ }
74
+ // Init schema
75
+ _db.run(`
76
+ CREATE TABLE IF NOT EXISTS entities (
77
+ entity_id TEXT PRIMARY KEY,
78
+ name TEXT NOT NULL,
79
+ entity_type TEXT NOT NULL,
80
+ created_at TEXT NOT NULL
81
+ );
82
+ CREATE TABLE IF NOT EXISTS observations (
83
+ obs_id TEXT PRIMARY KEY,
84
+ entity_id TEXT NOT NULL REFERENCES entities(entity_id),
85
+ content TEXT NOT NULL,
86
+ created_at TEXT NOT NULL
87
+ );
88
+ CREATE TABLE IF NOT EXISTS relations (
89
+ rel_id TEXT PRIMARY KEY,
90
+ from_entity TEXT NOT NULL,
91
+ relation_type TEXT NOT NULL,
92
+ to_entity TEXT NOT NULL,
93
+ created_at TEXT NOT NULL
94
+ );
95
+ `);
96
+ _save(_db);
97
+ return _db;
98
+ })();
99
+ return _dbReady;
100
+ }
101
+ function _save(db) {
102
+ const data = db.export();
103
+ const buffer = Buffer.from(data);
104
+ fs.writeFileSync(_db_path(), buffer);
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Public functions
108
+ // ---------------------------------------------------------------------------
109
+ async function store_entity(name, entity_type, observations) {
110
+ const db = await _getDb();
111
+ const now = new Date().toISOString();
112
+ // Upsert entity — look up by name+type
113
+ let entity_id;
114
+ const existing = db.exec("SELECT entity_id FROM entities WHERE name = ? AND entity_type = ?", [name, entity_type]);
115
+ if (existing.length > 0 && existing[0].values.length > 0) {
116
+ entity_id = existing[0].values[0][0];
117
+ }
118
+ else {
119
+ entity_id = crypto.randomUUID();
120
+ db.run("INSERT INTO entities (entity_id, name, entity_type, created_at) VALUES (?, ?, ?, ?)", [entity_id, name, entity_type, now]);
121
+ }
122
+ // Fetch existing observation contents to deduplicate
123
+ const existingObs = db.exec("SELECT content FROM observations WHERE entity_id = ?", [entity_id]);
124
+ const existingContentSet = new Set();
125
+ if (existingObs.length > 0) {
126
+ for (const row of existingObs[0].values) {
127
+ existingContentSet.add(row[0]);
128
+ }
129
+ }
130
+ let added = 0;
131
+ for (const content of observations) {
132
+ if (!existingContentSet.has(content)) {
133
+ db.run("INSERT INTO observations (obs_id, entity_id, content, created_at) VALUES (?, ?, ?, ?)", [crypto.randomUUID(), entity_id, content, now]);
134
+ existingContentSet.add(content);
135
+ added++;
136
+ }
137
+ }
138
+ const countResult = db.exec("SELECT COUNT(*) AS n FROM observations WHERE entity_id = ?", [entity_id]);
139
+ const totalObservations = countResult.length > 0 ? countResult[0].values[0][0] : 0;
140
+ _save(db);
141
+ return {
142
+ entity_id,
143
+ name,
144
+ entity_type,
145
+ observations_added: added,
146
+ total_observations: totalObservations,
147
+ };
148
+ }
149
+ async function retrieve_entities(query, entity_type = undefined, limit = 10) {
150
+ const db = await _getDb();
151
+ const pattern = `%${query}%`;
152
+ let rows;
153
+ if (entity_type) {
154
+ const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ? AND entity_type = ?", [pattern, entity_type]);
155
+ rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
156
+ }
157
+ else {
158
+ const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ?", [pattern]);
159
+ rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
160
+ }
161
+ const total = rows.length;
162
+ rows = rows.slice(0, Math.min(limit, 50));
163
+ const entities = [];
164
+ for (const row of rows) {
165
+ const obsResult = db.exec("SELECT content, created_at FROM observations WHERE entity_id = ? ORDER BY created_at", [row.entity_id]);
166
+ const obs = obsResult.length > 0 ? obsResult[0].values.map(o => ({ content: o[0], created_at: o[1] })) : [];
167
+ const relResult = db.exec("SELECT relation_type, to_entity FROM relations WHERE from_entity = ? ORDER BY created_at", [row.name]);
168
+ const rels = relResult.length > 0 ? relResult[0].values.map(r => ({ relation_type: r[0], to_entity: r[1] })) : [];
169
+ entities.push({
170
+ entity_id: row.entity_id,
171
+ name: row.name,
172
+ entity_type: row.entity_type,
173
+ created_at: row.created_at,
174
+ observations: obs,
175
+ relations: rels,
176
+ });
177
+ }
178
+ return { entities, total };
179
+ }
180
+ async function store_relation(from_entity, relation_type, to_entity) {
181
+ const db = await _getDb();
182
+ const rel_id = crypto.randomUUID();
183
+ const now = new Date().toISOString();
184
+ db.run("INSERT INTO relations (rel_id, from_entity, relation_type, to_entity, created_at) VALUES (?, ?, ?, ?, ?)", [rel_id, from_entity, relation_type, to_entity, now]);
185
+ _save(db);
186
+ return { rel_id, from_entity, relation_type, to_entity };
187
+ }