klypix-mcp 1.11.0 → 1.12.0

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,38 @@
1
+ #!/usr/bin/env node
2
+ // klypix-link — make THIS project's brain automatic for EVERY agent tool.
3
+ // `npx klypix-mcp install` gives Claude Code the brain via hooks; `link` extends the
4
+ // same automatic read+capture to Cursor, Cline, Windsurf, Copilot/VS Code, and any
5
+ // AGENTS.md-reading agent, by dropping each tool's native MCP config + rules file in
6
+ // the current project. Idempotent — re-run anytime to refresh. Project-scoped (cwd);
7
+ // touches only files inside the project. Reports what it did; exits non-zero on hard fail.
8
+ //
9
+ // cd your-project && npx klypix-mcp link
10
+ //
11
+ // Synchronous top-level by design: the dispatcher does `await import(this); process.exit(0)`,
12
+ // so all work (and its logs) must complete during module evaluation, before exit.
13
+ import path from 'path';
14
+ import { linkProject } from '../src/agent-rules.mjs';
15
+
16
+ try {
17
+ const dirArg = process.argv.slice(3).find(a => !a.startsWith('-'));
18
+ const projectDir = path.resolve(dirArg || process.cwd());
19
+ const { rules, mcp, hasBrain } = linkProject(projectDir);
20
+
21
+ const mark = (a) => a === 'unchanged' ? '·' : a === 'skipped' ? '⚠' : '✓';
22
+ console.log(`klypix — linking the brain to every agent tool in ${projectDir}\n`);
23
+ console.log(' MCP server (so each tool can reach the brain):');
24
+ for (const r of mcp) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
25
+ console.log('\n Rules (so each tool auto-reads + captures the brain):');
26
+ for (const r of rules) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
27
+
28
+ const changed = [...rules, ...mcp].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
29
+ console.log(`\n✓ ${changed} file(s) written/updated — every agent opened in this project now reads + captures ./brain.klypix.`);
30
+ console.log(' Cline & Windsurf MCP servers live in their global config; the rules file points them at the brain regardless.');
31
+ if (!hasBrain) {
32
+ console.log('\n⚠ No ./brain.klypix here yet — the rules reference it for when you create one.');
33
+ console.log(' Make one in the KLYPIX app (Canvas → Save as brain) or with the create_canvas MCP tool.');
34
+ }
35
+ } catch (e) {
36
+ console.error(`✗ link failed: ${e?.message || e}`);
37
+ process.exit(1);
38
+ }
@@ -41,6 +41,12 @@ const log = (...a) => console.error('[klypix-mcp]', ...a);
41
41
  // server setup; delegates to the dedicated bin so `npx klypix-install` also works.
42
42
  if (process.argv[2] === 'install') { await import('./klypix-install.mjs'); process.exit(0); }
43
43
 
44
+ // `npx klypix-mcp link` — make THIS project's brain automatic for EVERY agent tool,
45
+ // not just Claude Code: drop each tool's native MCP config + rules file (Cursor, Cline,
46
+ // Windsurf, Copilot/VS Code, AGENTS.md) so any agent opened here reads + captures the
47
+ // brain on its own. Project-scoped (cwd); idempotent. Runs before any server setup.
48
+ if (process.argv[2] === 'link') { await import('./klypix-link.mjs'); process.exit(0); }
49
+
44
50
  // `npx klypix-mcp init` — 60-second onboarding: seed a starter project brain in
45
51
  // the current folder so a new user's FIRST contact isn't an empty vault, then
46
52
  // print a paste-ready MCP config. Runs before any server setup.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "An open, local-first, agent-neutral canvas file your AI reads and writes over MCP — works with Claude, Cursor, Cline, any model.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "bin": {
28
28
  "klypix-mcp": "bin/klypix-mcp.mjs",
29
+ "klypix-link": "bin/klypix-link.mjs",
29
30
  "klypix-a2a": "bin/klypix-a2a.mjs",
30
31
  "klypix-read": "bin/klypix-read.mjs",
31
32
  "klypix-write": "bin/klypix-write.mjs",
@@ -0,0 +1,126 @@
1
+ // agent-rules — make a project's brain.klypix AUTOMATIC for EVERY agent tool, not
2
+ // just Claude Code. Claude Code gets the brain via hooks (settings.json, see
3
+ // klypix-install). Other agents (Cursor, Cline, Windsurf, Copilot/VS Code, and the
4
+ // AGENTS.md cross-tool standard) have no hook system — so we drop their NATIVE files:
5
+ //
6
+ // • an MCP server config → the agent CAN reach the brain's tools
7
+ // • a rules / instructions → the agent is TOLD to read the brain at task start and
8
+ // capture decisions, on every session, automatically
9
+ //
10
+ // Agent-neutral by construction: the brain DATA is one shared brain.klypix; this just
11
+ // teaches each tool to use it. Idempotent — owned files are rewritten wholesale; shared
12
+ // files (AGENTS.md, copilot-instructions.md) get a fenced block merged in place, never
13
+ // clobbering the user's own content. Pure fs/path; never throws for one bad target.
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+
17
+ const FENCE_START = '<!-- klypix-brain:start (managed by klypix-mcp — re-run `npx klypix-mcp link`) -->';
18
+ const FENCE_END = '<!-- klypix-brain:end -->';
19
+ const FENCE_RE = /<!--\s*klypix-brain:start[\s\S]*?klypix-brain:end\s*-->/;
20
+
21
+ // The one canonical instruction every agent gets. Tool-and-CLI dual so it works whether
22
+ // or not the agent has the klypix-canvas MCP wired.
23
+ const BRAIN_INSTRUCTIONS = `## KLYPIX project brain
24
+
25
+ This project has a **spatial brain** at \`./brain.klypix\` — the living memory of its
26
+ decisions, open questions, and findings (the shared human↔agent memory for this repo).
27
+ Treat it as authoritative project context.
28
+
29
+ **At the start of a task — read it** so you know the project's state and past decisions:
30
+ - with the \`klypix-canvas\` MCP server: call \`search_canvases\` / \`read_canvas\` (canvas: \`"brain"\`), or \`brain_insights\` for the load-bearing cards.
31
+ - or via CLI: \`npx klypix-read brain.klypix\`
32
+
33
+ **When you make a real decision, finding, or milestone — capture it** so it persists for the next session/agent:
34
+ - with MCP: call \`brain_note\` with a one-line decision, or
35
+ - emit a marker line in your output: \`🧠 BRAIN [Area]: <one-line decision>\`, or
36
+ - via CLI: \`echo "🧠 BRAIN [Area]: <decision>" | npx klypix-append brain.klypix\`
37
+
38
+ Capture **sparingly** — real decisions/milestones, not routine steps. Link related cards with \`[[other-card]]\`.
39
+
40
+ **Don't** hand-edit \`brain.klypix\` (it's a packaged canvas — use the tools) or dump file contents into it; capture the *decision*, not the file.`;
41
+
42
+ const fencedBlock = () => `${FENCE_START}\n${BRAIN_INSTRUCTIONS}\n${FENCE_END}`;
43
+
44
+ const exists = (p) => { try { fs.statSync(p); return true; } catch { return false; } };
45
+ const ensureDir = (p) => fs.mkdirSync(path.dirname(p), { recursive: true });
46
+
47
+ // Shared markdown (AGENTS.md, copilot-instructions.md): merge our fenced block in place.
48
+ function fenceMerge(file) {
49
+ let cur = '';
50
+ let had = false;
51
+ if (exists(file)) { cur = fs.readFileSync(file, 'utf8'); had = true; }
52
+ const block = fencedBlock();
53
+ let next;
54
+ let action;
55
+ if (FENCE_RE.test(cur)) { next = cur.replace(FENCE_RE, block); action = 'updated'; }
56
+ else if (cur.trim()) { next = cur.replace(/\s*$/, '') + '\n\n' + block + '\n'; action = 'merged'; }
57
+ else { next = block + '\n'; action = had ? 'merged' : 'created'; }
58
+ if (next === cur) return { action: 'unchanged' };
59
+ ensureDir(file); fs.writeFileSync(file, next, 'utf8');
60
+ return { action };
61
+ }
62
+
63
+ // Owned dedicated rules file: rewrite wholesale (optional frontmatter for always-apply).
64
+ function writeDedicated(file, frontmatter) {
65
+ const body = (frontmatter ? frontmatter + '\n' : '') + fencedBlock() + '\n';
66
+ if (exists(file) && fs.readFileSync(file, 'utf8') === body) return { action: 'unchanged' };
67
+ ensureDir(file); fs.writeFileSync(file, body, 'utf8');
68
+ return { action: exists(file) ? 'updated' : 'created' };
69
+ }
70
+
71
+ // Project-level MCP config: add the klypix-canvas server, preserving any sibling servers.
72
+ // wrapKey differs by tool: Cursor/Claude use "mcpServers"; VS Code uses "servers".
73
+ function mergeMcpJson(file, wrapKey, withType) {
74
+ const entry = withType
75
+ ? { type: 'stdio', command: 'npx', args: ['-y', 'klypix-mcp', '--vault', '.'] }
76
+ : { command: 'npx', args: ['-y', 'klypix-mcp', '--vault', '.'] };
77
+ let cfg = {};
78
+ if (exists(file)) {
79
+ const raw = fs.readFileSync(file, 'utf8');
80
+ if (raw.trim()) { try { cfg = JSON.parse(raw); } catch { return { action: 'skipped', why: 'invalid JSON — left untouched' }; } }
81
+ }
82
+ if (!cfg[wrapKey] || typeof cfg[wrapKey] !== 'object' || Array.isArray(cfg[wrapKey])) cfg[wrapKey] = {};
83
+ const before = JSON.stringify(cfg[wrapKey]['klypix-canvas']);
84
+ cfg[wrapKey]['klypix-canvas'] = entry;
85
+ if (JSON.stringify(cfg[wrapKey]['klypix-canvas']) === before) return { action: 'unchanged' };
86
+ ensureDir(file); fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
87
+ return { action: before === undefined ? 'created' : 'updated' };
88
+ }
89
+
90
+ /**
91
+ * Wire a project so EVERY agent tool reads + captures its brain automatically.
92
+ * @param {string} projectDir absolute project root (holds ./brain.klypix)
93
+ * @returns {{ rules: Array, mcp: Array, hasBrain: boolean }}
94
+ */
95
+ export function linkProject(projectDir) {
96
+ const j = (...p) => path.join(projectDir, ...p);
97
+ const hasBrain = exists(j('brain.klypix')) || exists(j('brain.any'));
98
+
99
+ // ── Rules / instructions (the "automatically use the brain" half) ──────────────
100
+ const rules = [
101
+ // AGENTS.md — the emerging cross-tool standard (Codex, Jules, Zed, Cursor-also-reads…)
102
+ { tool: 'AGENTS.md (cross-tool standard)', file: 'AGENTS.md', ...fenceMerge(j('AGENTS.md')) },
103
+ // Cursor — dedicated always-applied rule
104
+ { tool: 'Cursor', file: '.cursor/rules/klypix-brain.mdc', ...writeDedicated(j('.cursor', 'rules', 'klypix-brain.mdc'),
105
+ '---\ndescription: KLYPIX project brain — read at task start, capture decisions\nalwaysApply: true\n---') },
106
+ // Windsurf — dedicated always-on rule
107
+ { tool: 'Windsurf', file: '.windsurf/rules/klypix-brain.md', ...writeDedicated(j('.windsurf', 'rules', 'klypix-brain.md'),
108
+ '---\ntrigger: always_on\n---') },
109
+ // Cline — drops a file in .clinerules/ (Cline reads every file there)
110
+ { tool: 'Cline', file: '.clinerules/klypix-brain.md', ...writeDedicated(j('.clinerules', 'klypix-brain.md'), '') },
111
+ // GitHub Copilot — repo-wide custom instructions
112
+ { tool: 'GitHub Copilot', file: '.github/copilot-instructions.md', ...fenceMerge(j('.github', 'copilot-instructions.md')) },
113
+ ];
114
+
115
+ // ── MCP server config (the "can reach the brain's tools" half) ─────────────────
116
+ // Project-level files only; Claude Code is covered by `install` (hooks + bundled MCP),
117
+ // so we skip .mcp.json to avoid double-registering its server.
118
+ const mcp = [
119
+ { tool: 'Cursor', file: '.cursor/mcp.json', ...mergeMcpJson(j('.cursor', 'mcp.json'), 'mcpServers', false) },
120
+ { tool: 'VS Code (Copilot/Continue)', file: '.vscode/mcp.json', ...mergeMcpJson(j('.vscode', 'mcp.json'), 'servers', true) },
121
+ ];
122
+
123
+ return { rules, mcp, hasBrain };
124
+ }
125
+
126
+ export { BRAIN_INSTRUCTIONS };