@arkheia/mcp-server 0.1.8 → 0.1.10

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,33 @@
1
+ # Fabrication Detection — Arkheia
2
+
3
+ When Arkheia MCP tools are available in this session, follow this protocol.
4
+
5
+ ## Detection
6
+
7
+ Call `arkheia_verify` on EVERY model response before surfacing it to the user. Do not skip responses that appear obviously correct.
8
+
9
+ ## Risk Escalation
10
+
11
+ - **HIGH risk**: Warn the user that the response may be fabricated. Include the `detection_id`. Still show the response — never hide or suppress it. Ask the user if they want to verify with a different model.
12
+ - **MEDIUM risk**: Show the response with a brief confidence note.
13
+ - **LOW risk**: Show normally.
14
+ - **UNKNOWN risk**: Flag for human review. Include the `detection_id`.
15
+
16
+ ## Cross-Model Verification
17
+
18
+ When HIGH or UNKNOWN risk is detected, offer to verify using: `run_grok`, `run_gemini`, `run_ollama`, `run_together`.
19
+
20
+ ## Audit Trail
21
+
22
+ Use `arkheia_audit_log` when the user asks about detection history or for compliance evidence.
23
+
24
+ ## Persistent Knowledge Graph
25
+
26
+ Use `memory_store` to persist facts, `memory_retrieve` to recall them, `memory_relate` to track relationships between entities.
27
+
28
+ ## Key Rules
29
+
30
+ 1. Never hide a response from the user regardless of risk level
31
+ 2. Always include the `detection_id` when reporting HIGH or UNKNOWN risk
32
+ 3. Call `arkheia_verify` proactively — do not wait for the user to ask
33
+ 4. Audit logging happens automatically through `arkheia_verify`
package/dist/index.js CHANGED
@@ -125,6 +125,13 @@ async function checkForUpdate() {
125
125
  }
126
126
  }
127
127
  async function main() {
128
+ // --setup flag: install protocol + register MCP with all detected CLIs, then exit
129
+ if (process.argv.includes('--setup')) {
130
+ const { execSync } = await import('child_process');
131
+ const setupScript = path.join(__dirname, '..', 'scripts', 'setup-claude.js');
132
+ execSync(`node "${setupScript}"`, { stdio: 'inherit', env: process.env });
133
+ process.exit(0);
134
+ }
128
135
  loadConfig();
129
136
  checkCRLF();
130
137
  checkForUpdate(); // fire-and-forget, don't await — never blocks startup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkheia/mcp-server",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Arkheia MCP Server — Fabrication detection for AI agents. Know when your AI is making things up.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "files": [
16
16
  "dist/",
17
17
  "scripts/",
18
- "README.md"
18
+ "README.md",
19
+ "CLAUDE_MD_TEMPLATE.md"
19
20
  ],
20
21
  "keywords": [
21
22
  "mcp",
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Installs the Arkheia detection protocol into AI CLI instruction files
4
+ * and registers the MCP server with each detected CLI.
5
+ *
6
+ * Usage:
7
+ * npx @arkheia/mcp-server --setup
8
+ * node scripts/setup-claude.js
9
+ *
10
+ * What it does:
11
+ * 1. Detects which AI CLIs are installed (claude, codex, gemini, grok)
12
+ * 2. Appends detection protocol to instruction files that need it:
13
+ * - Claude Code: ~/.claude/CLAUDE.md
14
+ * - Codex: ~/.codex/CODEX.md
15
+ * - Gemini: not needed (reads protocol from tool descriptions)
16
+ * - Grok: not needed (no instruction file mechanism)
17
+ * 3. Registers the MCP server with each detected CLI
18
+ */
19
+
20
+ const fs = require("fs");
21
+ const path = require("path");
22
+ const os = require("os");
23
+ const { execSync } = require("child_process");
24
+
25
+ const MARKER = "# Arkheia Integration — Detection Protocol";
26
+
27
+ const PROTOCOL = `
28
+ # Arkheia Integration — Detection Protocol
29
+
30
+ When Arkheia MCP tools are available in this session, follow this protocol.
31
+
32
+ ## Fabrication Detection
33
+
34
+ Call \`arkheia_verify\` on EVERY model response before surfacing it to the user. Do not skip responses that appear obviously correct.
35
+
36
+ ## Risk Escalation
37
+
38
+ - **HIGH risk**: Warn the user that the response may be fabricated. Include the \`detection_id\`. Still show the response — never hide or suppress it. Ask the user if they want to verify with a different model.
39
+ - **MEDIUM risk**: Show the response with a brief confidence note.
40
+ - **LOW risk**: Show normally.
41
+ - **UNKNOWN risk**: Flag for human review. Include the \`detection_id\`.
42
+
43
+ ## Cross-Model Verification
44
+
45
+ When HIGH or UNKNOWN risk is detected, offer to verify using: \`run_grok\`, \`run_gemini\`, \`run_ollama\`, \`run_together\`.
46
+
47
+ ## Audit Trail
48
+
49
+ Use \`arkheia_audit_log\` when the user asks about detection history or for compliance evidence.
50
+
51
+ ## Persistent Knowledge Graph
52
+
53
+ Use \`memory_store\` to persist facts, \`memory_retrieve\` to recall them, \`memory_relate\` to track relationships between entities.
54
+
55
+ ## Key Rules
56
+
57
+ 1. Never hide a response from the user regardless of risk level
58
+ 2. Always include the \`detection_id\` when reporting HIGH or UNKNOWN risk
59
+ 3. Call \`arkheia_verify\` proactively — do not wait for the user to ask
60
+ 4. Audit logging happens automatically through \`arkheia_verify\`
61
+ `;
62
+
63
+ function cmdExists(cmd) {
64
+ try {
65
+ execSync(process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`, { stdio: "pipe" });
66
+ return true;
67
+ } catch { return false; }
68
+ }
69
+
70
+ function installProtocol(filePath, cliName) {
71
+ const dir = path.dirname(filePath);
72
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
73
+
74
+ if (fs.existsSync(filePath)) {
75
+ const existing = fs.readFileSync(filePath, "utf8");
76
+ if (existing.includes(MARKER)) {
77
+ console.log(` [${cliName}] Detection protocol already installed in ${filePath}`);
78
+ return;
79
+ }
80
+ fs.appendFileSync(filePath, "\n" + PROTOCOL);
81
+ console.log(` [${cliName}] Detection protocol appended to ${filePath}`);
82
+ } else {
83
+ fs.writeFileSync(filePath, PROTOCOL.trim() + "\n");
84
+ console.log(` [${cliName}] Detection protocol written to ${filePath}`);
85
+ }
86
+ }
87
+
88
+ function registerMcp(cli, args) {
89
+ try {
90
+ execSync(args, { stdio: "inherit", timeout: 15000 });
91
+ console.log(` [${cli}] MCP server registered`);
92
+ } catch {
93
+ console.log(` [${cli}] Auto-registration failed — run manually:`);
94
+ console.log(` ${args}`);
95
+ }
96
+ }
97
+
98
+ function main() {
99
+ const apiKey = process.env.ARKHEIA_API_KEY || "";
100
+ const home = os.homedir();
101
+ const detected = [];
102
+
103
+ console.log("\n[arkheia] Setting up detection protocol for installed AI CLIs...\n");
104
+
105
+ // ── Claude Code ──────────────────────────────────────────────
106
+ if (cmdExists("claude")) {
107
+ detected.push("claude");
108
+ console.log("[claude] Detected");
109
+ installProtocol(path.join(home, ".claude", "CLAUDE.md"), "claude");
110
+ if (apiKey) {
111
+ registerMcp("claude", `claude mcp add arkheia -s user -e ARKHEIA_API_KEY="${apiKey}" -- mcp-server`);
112
+ } else {
113
+ console.log(' [claude] Set ARKHEIA_API_KEY then run: claude mcp add arkheia -s user -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" -- mcp-server');
114
+ }
115
+ }
116
+
117
+ // ── Codex ────────────────────────────────────────────────────
118
+ if (cmdExists("codex")) {
119
+ detected.push("codex");
120
+ console.log("[codex] Detected");
121
+ installProtocol(path.join(home, ".codex", "CODEX.md"), "codex");
122
+ if (apiKey) {
123
+ registerMcp("codex", `codex mcp add arkheia --env ARKHEIA_API_KEY="${apiKey}" -- mcp-server`);
124
+ } else {
125
+ console.log(' [codex] Set ARKHEIA_API_KEY then run: codex mcp add arkheia --env ARKHEIA_API_KEY="$ARKHEIA_API_KEY" -- mcp-server');
126
+ }
127
+ }
128
+
129
+ // ── Gemini ───────────────────────────────────────────────────
130
+ // Gemini reads the detection protocol directly from tool descriptions.
131
+ // No instruction file needed — just register the MCP server.
132
+ if (cmdExists("gemini")) {
133
+ detected.push("gemini");
134
+ console.log("[gemini] Detected (no instruction file needed — reads protocol from tool descriptions)");
135
+ if (apiKey) {
136
+ registerMcp("gemini", `gemini mcp add -s user -e ARKHEIA_API_KEY="${apiKey}" arkheia mcp-server`);
137
+ } else {
138
+ console.log(' [gemini] Set ARKHEIA_API_KEY then run: gemini mcp add -s user -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" arkheia mcp-server');
139
+ }
140
+ }
141
+
142
+ // ── Grok ─────────────────────────────────────────────────────
143
+ // No instruction file mechanism. Register MCP server only.
144
+ if (cmdExists("grok")) {
145
+ detected.push("grok");
146
+ console.log("[grok] Detected (no instruction file mechanism)");
147
+ if (apiKey) {
148
+ registerMcp("grok", `grok mcp add arkheia -t stdio -c mcp-server -e ARKHEIA_API_KEY="${apiKey}"`);
149
+ } else {
150
+ console.log(' [grok] Set ARKHEIA_API_KEY then run: grok mcp add arkheia -t stdio -c mcp-server -e ARKHEIA_API_KEY="$ARKHEIA_API_KEY"');
151
+ }
152
+ }
153
+
154
+ if (detected.length === 0) {
155
+ console.log("No AI CLIs detected on PATH (claude, codex, gemini, grok).");
156
+ console.log("Install one and re-run: npx @arkheia/mcp-server --setup");
157
+ } else {
158
+ console.log(`\n[arkheia] Setup complete for: ${detected.join(", ")}`);
159
+ console.log("[arkheia] Restart each CLI to activate Arkheia detection.\n");
160
+ }
161
+ }
162
+
163
+ main();
package/scripts/setup.js CHANGED
@@ -1,151 +1,241 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Post-install script verifies Python is available and prints setup instructions.
4
- * Does NOT auto-install Python dependencies (that happens on first run).
5
- */
6
-
7
- const { execSync } = require("child_process");
8
- const fs = require("fs");
9
- const path = require("path");
10
-
11
- const ARKHEIA_DIR = path.join(
12
- process.env.HOME || process.env.USERPROFILE || "/tmp",
13
- ".arkheia"
14
- );
15
- const CONFIG_FILE = path.join(ARKHEIA_DIR, "config.json");
16
-
17
- function checkApiKey() {
18
- // Check if config.json exists and has api_key
19
- try {
20
- if (fs.existsSync(CONFIG_FILE)) {
21
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
22
- if (config.api_key && config.api_key.length > 0) {
23
- return config.api_key;
24
- }
25
- }
26
- } catch {
27
- // Corrupt config treat as missing
28
- }
29
-
30
- // Check environment variable
31
- if (process.env.ARKHEIA_API_KEY) {
32
- // Save env-provided key to config for future runs
33
- saveConfig(process.env.ARKHEIA_API_KEY);
34
- return process.env.ARKHEIA_API_KEY;
35
- }
36
-
37
- return null;
38
- }
39
-
40
- function saveConfig(apiKey) {
41
- try {
42
- if (!fs.existsSync(ARKHEIA_DIR)) {
43
- fs.mkdirSync(ARKHEIA_DIR, { recursive: true });
44
- }
45
- const config = {
46
- api_key: apiKey,
47
- proxy_url: "https://arkheia-proxy-production.up.railway.app",
48
- provisioned_at: new Date().toISOString(),
49
- };
50
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
51
- } catch (err) {
52
- console.error(` [arkheia] Warning: Could not save config: ${err.message}`);
53
- }
54
- }
55
-
56
- function checkPython() {
57
- // Try versioned interpreters first — on Homebrew, keg-only formulae like
58
- // python@3.12 only expose python3.12, not python3.
59
- const candidates = ["python3.13", "python3.12", "python3.11", "python3", "python"];
60
- for (const cmd of candidates) {
61
- try {
62
- // Verify version AND that pyexpat + ensurepip work.
63
- // Python 3.14 on macOS has broken pyexpat (missing libexpat symbol).
64
- const output = execSync(
65
- `${cmd} -c "import sys,pyexpat,ensurepip; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`,
66
- { encoding: "utf-8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] }
67
- ).trim();
68
- const match = output.match(/^(\d+)\.(\d+)$/);
69
- if (match) {
70
- const major = parseInt(match[1]);
71
- const minor = parseInt(match[2]);
72
- if (major === 3 && minor >= 10 && minor <= 13) {
73
- const version = execSync(`${cmd} --version 2>&1`, {
74
- encoding: "utf-8", timeout: 5000,
75
- }).trim();
76
- return { cmd, version };
77
- }
78
- }
79
- } catch {
80
- // Try next
81
- }
82
- }
83
- return null;
84
- }
85
-
86
- const python = checkPython();
87
-
88
- if (!python) {
89
- console.error(`
90
- ============================================================
91
- ERROR: Arkheia MCP Server requires Python 3.10–3.13
92
- with working pyexpat and ensurepip.
93
-
94
- macOS (Homebrew):
95
- brew install python@3.12
96
-
97
- NOTE: Homebrew's current default 'brew install python'
98
- installs 3.14, which has a broken pyexpat link on macOS
99
- as of April 2026. Use python@3.12 until Homebrew ships a fix.
100
-
101
- After installing, verify with:
102
- python3.12 -c "import pyexpat, ensurepip"
103
-
104
- Other platforms: https://python.org
105
- ============================================================
106
- `);
107
- process.exit(1);
108
- } else {
109
- console.log(`
110
- ============================================================
111
- Arkheia MCP Server installed successfully.
112
- Python: ${python.version} (${python.cmd})
113
- ============================================================
114
- `);
115
- }
116
-
117
- // ── API key provisioning check ──────────────────────────────────
118
- const existingKey = checkApiKey();
119
-
120
- if (existingKey) {
121
- const maskedKey =
122
- existingKey.substring(0, 8) + "..." + existingKey.substring(existingKey.length - 4);
123
- console.log(`
124
- ============================================================
125
- API key found: ${maskedKey}
126
- Config: ${CONFIG_FILE}
127
- ============================================================
128
- `);
129
- } else {
130
- console.log(`
131
- ============================================================
132
- No Arkheia API key configured.
133
-
134
- To enable hosted detection and encrypted profiles:
135
-
136
- 1. Get a free API key at: https://arkheia.ai/mcp
137
- 2. Set it in your environment:
138
- export ARKHEIA_API_KEY=ak_live_...
139
-
140
- Or save it directly to ${CONFIG_FILE}:
141
- {
142
- "api_key": "ak_live_...",
143
- "proxy_url": "https://arkheia-proxy-production.up.railway.app",
144
- "provisioned_at": "..."
145
- }
146
-
147
- The server will work without a key, but encrypted profiles
148
- and hosted detection will be unavailable.
149
- ============================================================
150
- `);
151
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Post-install script for @arkheia/mcp-server.
4
+ *
5
+ * 1. Checks for API key (saves env → config if found)
6
+ * 2. Installs/updates Arkheia detection protocol in ~/.claude/CLAUDE.md
7
+ * - Versioned managed block with BEGIN/END markers
8
+ * - Idempotent, non-destructive, backs up before write
9
+ * - Opt-out: ARKHEIA_SKIP_CLAUDE_MD=1
10
+ */
11
+
12
+ const { execSync } = require("child_process");
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const os = require("os");
16
+
17
+ // Resolve home dir — handle sudo
18
+ const HOME = (process.env.SUDO_USER
19
+ ? path.join("/home", process.env.SUDO_USER)
20
+ : process.env.HOME || process.env.USERPROFILE || "/tmp");
21
+
22
+ const ARKHEIA_DIR = path.join(HOME, ".arkheia");
23
+ const CONFIG_FILE = path.join(ARKHEIA_DIR, "config.json");
24
+ const CLAUDE_DIR = path.join(HOME, ".claude");
25
+ const CLAUDE_MD = path.join(CLAUDE_DIR, "CLAUDE.md");
26
+
27
+ // Read version from package.json
28
+ const PKG_VERSION = (() => {
29
+ try {
30
+ return JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
31
+ } catch { return "unknown"; }
32
+ })();
33
+
34
+ // Read template from shipped file
35
+ const TEMPLATE = (() => {
36
+ try {
37
+ return fs.readFileSync(path.join(__dirname, "..", "CLAUDE_MD_TEMPLATE.md"), "utf8").trim();
38
+ } catch { return ""; }
39
+ })();
40
+
41
+ const BEGIN_MARKER = `<!-- BEGIN ARKHEIA PROTOCOL v${PKG_VERSION} -->`;
42
+ const BEGIN_REGEX = /<!-- BEGIN ARKHEIA PROTOCOL v(.+?) -->/;
43
+ const BLOCK_REGEX = /<!-- BEGIN ARKHEIA PROTOCOL v.+? -->[\s\S]*?<!-- END ARKHEIA PROTOCOL -->/;
44
+ const END_MARKER = "<!-- END ARKHEIA PROTOCOL -->";
45
+
46
+ // ── API key provisioning ───────────────────────────────────────
47
+
48
+ function checkApiKey() {
49
+ try {
50
+ if (fs.existsSync(CONFIG_FILE)) {
51
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
52
+ if (config.api_key && config.api_key.length > 0) return config.api_key;
53
+ }
54
+ } catch {}
55
+ if (process.env.ARKHEIA_API_KEY) {
56
+ saveConfig(process.env.ARKHEIA_API_KEY);
57
+ return process.env.ARKHEIA_API_KEY;
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function saveConfig(apiKey) {
63
+ try {
64
+ if (!fs.existsSync(ARKHEIA_DIR)) fs.mkdirSync(ARKHEIA_DIR, { recursive: true, mode: 0o700 });
65
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify({
66
+ api_key: apiKey,
67
+ proxy_url: "https://arkheia-proxy-production.up.railway.app",
68
+ provisioned_at: new Date().toISOString(),
69
+ }, null, 2), "utf-8");
70
+ } catch (err) {
71
+ console.error(` [arkheia] Warning: Could not save config: ${err.message}`);
72
+ }
73
+ }
74
+
75
+ // ── CLAUDE.md managed block install ────────────────────────────
76
+
77
+ function installClaudeMd() {
78
+ // Opt-out
79
+ if (process.env.ARKHEIA_SKIP_CLAUDE_MD === "1") {
80
+ console.log(" [arkheia] CLAUDE.md install skipped (ARKHEIA_SKIP_CLAUDE_MD=1)");
81
+ return;
82
+ }
83
+
84
+ if (!TEMPLATE) {
85
+ console.log(" [arkheia] Warning: CLAUDE_MD_TEMPLATE.md not found in package");
86
+ return;
87
+ }
88
+
89
+ const newBlock = `${BEGIN_MARKER}\n${TEMPLATE}\n${END_MARKER}`;
90
+
91
+ // Ensure ~/.claude exists
92
+ try {
93
+ if (!fs.existsSync(CLAUDE_DIR)) {
94
+ fs.mkdirSync(CLAUDE_DIR, { recursive: true, mode: 0o700 });
95
+ }
96
+ } catch (err) {
97
+ console.error(` [arkheia] Could not create ${CLAUDE_DIR}: ${err.message}`);
98
+ return;
99
+ }
100
+
101
+ // Symlink check — don't follow symlinks (chezmoi, yadm)
102
+ try {
103
+ if (fs.existsSync(CLAUDE_MD) && fs.lstatSync(CLAUDE_MD).isSymbolicLink()) {
104
+ console.log(` [arkheia] ${CLAUDE_MD} is a symlink — skipping to avoid corrupting dotfile manager.`);
105
+ console.log(` [arkheia] Install the Arkheia block manually. Template at: ${path.join(__dirname, "..", "CLAUDE_MD_TEMPLATE.md")}`);
106
+ return;
107
+ }
108
+ } catch {}
109
+
110
+ // Case 1: No CLAUDE.md exists — create fresh
111
+ if (!fs.existsSync(CLAUDE_MD)) {
112
+ try {
113
+ fs.writeFileSync(CLAUDE_MD, newBlock + "\n", "utf-8");
114
+ console.log(` [arkheia] Installed detection protocol to ${CLAUDE_MD}`);
115
+ } catch (err) {
116
+ console.error(` [arkheia] Could not write ${CLAUDE_MD}: ${err.message}`);
117
+ }
118
+ return;
119
+ }
120
+
121
+ // Case 2+: CLAUDE.md exists — read it
122
+ let content;
123
+ try {
124
+ content = fs.readFileSync(CLAUDE_MD, "utf-8");
125
+ } catch (err) {
126
+ console.error(` [arkheia] Could not read ${CLAUDE_MD}: ${err.message}`);
127
+ return;
128
+ }
129
+
130
+ const match = content.match(BEGIN_REGEX);
131
+
132
+ // Case 2: Exists but no Arkheia block append
133
+ if (!match) {
134
+ // Check for multiple BEGIN markers (shouldn't happen)
135
+ const allMatches = content.match(/<!-- BEGIN ARKHEIA PROTOCOL/g);
136
+ if (allMatches && allMatches.length > 1) {
137
+ console.log(" [arkheia] Multiple Arkheia blocks found — manual intervention needed. Skipping.");
138
+ return;
139
+ }
140
+
141
+ backup(content);
142
+ // Preserve line endings
143
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
144
+ const separator = content.endsWith(eol) ? eol : eol + eol;
145
+ try {
146
+ fs.appendFileSync(CLAUDE_MD, separator + newBlock + eol, "utf-8");
147
+ console.log(` [arkheia] Appended detection protocol to existing ${CLAUDE_MD} (backup at ${CLAUDE_MD}.arkheia.bak)`);
148
+ } catch (err) {
149
+ console.error(` [arkheia] Could not append to ${CLAUDE_MD}: ${err.message}`);
150
+ }
151
+ return;
152
+ }
153
+
154
+ // Case 3: Block exists — check version
155
+ const existingVersion = match[1];
156
+ const existingBlock = content.match(BLOCK_REGEX);
157
+
158
+ if (existingVersion === PKG_VERSION && existingBlock && existingBlock[0] === newBlock) {
159
+ console.log(` [arkheia] Detection protocol already up to date (v${PKG_VERSION})`);
160
+ return;
161
+ }
162
+
163
+ // Case 4: Version mismatch or body drifted — replace in place
164
+ backup(content);
165
+ try {
166
+ const updated = content.replace(BLOCK_REGEX, newBlock);
167
+ fs.writeFileSync(CLAUDE_MD, updated, "utf-8");
168
+ console.log(` [arkheia] Updated detection protocol ${existingVersion} → ${PKG_VERSION} (backup at ${CLAUDE_MD}.arkheia.bak)`);
169
+ } catch (err) {
170
+ console.error(` [arkheia] Could not update ${CLAUDE_MD}: ${err.message}`);
171
+ }
172
+ }
173
+
174
+ function backup(content) {
175
+ try {
176
+ fs.writeFileSync(CLAUDE_MD + ".arkheia.bak", content, "utf-8");
177
+ } catch {}
178
+ }
179
+
180
+ // ── Also install to Codex if present ───────────────────────────
181
+
182
+ function installCodexMd() {
183
+ if (process.env.ARKHEIA_SKIP_CLAUDE_MD === "1") return;
184
+ if (!TEMPLATE) return;
185
+
186
+ const codexDir = path.join(HOME, ".codex");
187
+ const codexMd = path.join(codexDir, "CODEX.md");
188
+ const newBlock = `${BEGIN_MARKER}\n${TEMPLATE}\n${END_MARKER}`;
189
+
190
+ // Only install if codex CLI exists
191
+ try {
192
+ execSync(process.platform === "win32" ? "where codex" : "which codex", { stdio: "pipe" });
193
+ } catch { return; }
194
+
195
+ try {
196
+ if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true, mode: 0o700 });
197
+
198
+ if (!fs.existsSync(codexMd)) {
199
+ fs.writeFileSync(codexMd, newBlock + "\n", "utf-8");
200
+ console.log(` [arkheia] Installed detection protocol to ${codexMd}`);
201
+ return;
202
+ }
203
+
204
+ const content = fs.readFileSync(codexMd, "utf-8");
205
+ if (content.includes(BEGIN_MARKER)) {
206
+ console.log(` [arkheia] Codex protocol already up to date (v${PKG_VERSION})`);
207
+ return;
208
+ }
209
+
210
+ if (content.match(BEGIN_REGEX)) {
211
+ // Upgrade
212
+ fs.writeFileSync(codexMd + ".arkheia.bak", content, "utf-8");
213
+ const updated = content.replace(BLOCK_REGEX, newBlock);
214
+ fs.writeFileSync(codexMd, updated, "utf-8");
215
+ console.log(` [arkheia] Updated Codex detection protocol → ${PKG_VERSION}`);
216
+ } else {
217
+ // Append
218
+ fs.writeFileSync(codexMd + ".arkheia.bak", content, "utf-8");
219
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
220
+ fs.appendFileSync(codexMd, eol + eol + newBlock + eol, "utf-8");
221
+ console.log(` [arkheia] Appended detection protocol to ${codexMd}`);
222
+ }
223
+ } catch {}
224
+ }
225
+
226
+ // ── Main ───────────────────────────────────────────────────────
227
+
228
+ // API key check
229
+ const existingKey = checkApiKey();
230
+ if (existingKey) {
231
+ const masked = existingKey.substring(0, 8) + "..." + existingKey.substring(existingKey.length - 4);
232
+ console.log(`\n [arkheia] API key: ${masked}`);
233
+ } else {
234
+ console.log(`\n [arkheia] No API key. Get one free at https://arkheia.ai/mcp/account`);
235
+ }
236
+
237
+ // Install detection protocol
238
+ installClaudeMd();
239
+ installCodexMd();
240
+
241
+ console.log(` [arkheia] @arkheia/mcp-server v${PKG_VERSION} ready\n`);