@misterhuydo/cairn-mcp 1.1.2 → 1.2.1
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 +1 -1
- package/bin/cairn-cli.js +111 -0
- package/bin/cairn.js +21 -0
- package/how-to-use.md +134 -98
- package/package.json +3 -2
package/README.md
CHANGED
package/bin/cairn-cli.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { LANGUAGE_MAP } from '../src/indexer/fileWalker.js';
|
|
6
|
+
import { minifyContent } from '../src/bundler/minifier.js';
|
|
7
|
+
import { minifyVue } from '../src/bundler/vueMinifier.js';
|
|
8
|
+
import { checkpoint } from '../src/tools/checkpoint.js';
|
|
9
|
+
|
|
10
|
+
const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
|
|
11
|
+
|
|
12
|
+
const subcommand = process.argv[2];
|
|
13
|
+
|
|
14
|
+
if (subcommand === 'minify') {
|
|
15
|
+
let stdinData = '';
|
|
16
|
+
process.stdin.setEncoding('utf8');
|
|
17
|
+
process.stdin.on('data', chunk => { stdinData += chunk; });
|
|
18
|
+
process.stdin.on('end', () => {
|
|
19
|
+
let filePath;
|
|
20
|
+
try {
|
|
21
|
+
const input = JSON.parse(stdinData);
|
|
22
|
+
filePath = input?.tool_input?.file_path;
|
|
23
|
+
} catch {
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!filePath) process.exit(0);
|
|
28
|
+
|
|
29
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
30
|
+
const lang = LANGUAGE_MAP[ext];
|
|
31
|
+
|
|
32
|
+
if (!lang || !MINIFY_LANGS.has(lang)) process.exit(0);
|
|
33
|
+
|
|
34
|
+
let content;
|
|
35
|
+
try {
|
|
36
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
37
|
+
} catch {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const originalLines = content.split('\n').length;
|
|
42
|
+
|
|
43
|
+
const minified = lang === 'vue'
|
|
44
|
+
? minifyVue(content)
|
|
45
|
+
: minifyContent(content, lang);
|
|
46
|
+
|
|
47
|
+
const minifiedLines = minified.split('\n').length;
|
|
48
|
+
const pct = originalLines > 0 ? Math.round((1 - minifiedLines / originalLines) * 100) : 0;
|
|
49
|
+
|
|
50
|
+
process.stdout.write(`[cairn: ${originalLines} → ${minifiedLines} lines (-${pct}%)]\n${minified}\n`);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
} else if (subcommand === 'checkpoint' && process.argv[3] === '--auto') {
|
|
55
|
+
const message = `Auto-checkpoint at ${new Date().toISOString()}`;
|
|
56
|
+
checkpoint(null, { message, active_files: [], notes: [] });
|
|
57
|
+
process.exit(0);
|
|
58
|
+
|
|
59
|
+
} else if (subcommand === 'install-hooks') {
|
|
60
|
+
const isGlobal = process.argv.includes('--global');
|
|
61
|
+
const settingsDir = isGlobal
|
|
62
|
+
? path.join(os.homedir(), '.claude')
|
|
63
|
+
: path.join(process.cwd(), '.claude');
|
|
64
|
+
const settingsPath = path.join(settingsDir, 'settings.json');
|
|
65
|
+
|
|
66
|
+
let settings = {};
|
|
67
|
+
if (fs.existsSync(settingsPath)) {
|
|
68
|
+
try {
|
|
69
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
70
|
+
} catch {
|
|
71
|
+
settings = {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
settings.hooks = settings.hooks || {};
|
|
76
|
+
|
|
77
|
+
// Add PreToolUse hook for Read
|
|
78
|
+
const preHooks = settings.hooks.PreToolUse || [];
|
|
79
|
+
const hasMinify = preHooks.some(h =>
|
|
80
|
+
h.matcher === 'Read' && h.hooks?.some(hh => hh.command === 'cairn minify')
|
|
81
|
+
);
|
|
82
|
+
if (!hasMinify) {
|
|
83
|
+
preHooks.push({ matcher: 'Read', hooks: [{ type: 'command', command: 'cairn minify' }] });
|
|
84
|
+
settings.hooks.PreToolUse = preHooks;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Add Stop hook for auto-checkpoint
|
|
88
|
+
const stopHooks = settings.hooks.Stop || [];
|
|
89
|
+
const hasCheckpoint = stopHooks.some(h =>
|
|
90
|
+
h.hooks?.some(hh => hh.command === 'cairn checkpoint --auto')
|
|
91
|
+
);
|
|
92
|
+
if (!hasCheckpoint) {
|
|
93
|
+
stopHooks.push({ hooks: [{ type: 'command', command: 'cairn checkpoint --auto' }] });
|
|
94
|
+
settings.hooks.Stop = stopHooks;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
98
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
99
|
+
|
|
100
|
+
const scope = isGlobal ? 'global (~/.claude/settings.json)' : 'project (.claude/settings.json)';
|
|
101
|
+
console.log(`Cairn hooks installed in ${scope}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Active hooks:');
|
|
104
|
+
console.log(' PreToolUse[Read] → cairn minify (compress source files)');
|
|
105
|
+
console.log(' Stop → cairn checkpoint --auto (auto-save session)');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
|
|
108
|
+
} else {
|
|
109
|
+
console.error('Usage: cairn <minify|checkpoint --auto|install-hooks>');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
package/bin/cairn.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const cliPath = join(__dirname, 'cairn-cli.js');
|
|
8
|
+
|
|
9
|
+
const child = spawn(
|
|
10
|
+
process.execPath,
|
|
11
|
+
['--experimental-sqlite', '--no-warnings=ExperimentalWarning', cliPath, ...process.argv.slice(2)],
|
|
12
|
+
{ stdio: 'inherit', env: process.env }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
child.on('exit', (code, signal) => {
|
|
16
|
+
if (signal) process.kill(process.pid, signal);
|
|
17
|
+
else process.exit(code ?? 0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
21
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
package/how-to-use.md
CHANGED
|
@@ -1,98 +1,134 @@
|
|
|
1
|
-
# Cairn — Quick Start
|
|
2
|
-
|
|
3
|
-
## 1. Install globally
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install -g @misterhuydo/cairn-mcp
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## 2.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Claude
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
# Cairn — Quick Start
|
|
2
|
+
|
|
3
|
+
## 1. Install globally
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @misterhuydo/cairn-mcp
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Two commands are now available: `cairn-mcp` (MCP server) and `cairn` (CLI for hooks).
|
|
10
|
+
|
|
11
|
+
## 2. Install passive hooks (recommended)
|
|
12
|
+
|
|
13
|
+
Cairn hooks into Claude Code so that compression and checkpointing happen automatically — no need to call `cairn_bundle` or `cairn_checkpoint` manually.
|
|
14
|
+
|
|
15
|
+
**Global — applies to every project on this machine:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cairn install-hooks --global
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Project-level — applies to this project only:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cairn install-hooks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both write to `~/.claude/settings.json` (global) or `.claude/settings.json` (project). Safe to run multiple times — existing settings are preserved.
|
|
28
|
+
|
|
29
|
+
What this sets up:
|
|
30
|
+
|
|
31
|
+
| Hook | Effect |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `PreToolUse[Read]` | Every source file read is silently compressed before Claude sees it (~68% fewer tokens for `.java/.ts/.js/.vue/.py/.sql`) |
|
|
34
|
+
| `Stop` | Session auto-saved to `.cairn/session.json` every time Claude finishes responding |
|
|
35
|
+
|
|
36
|
+
After this you never need to call `cairn_bundle` or `cairn_checkpoint` — Claude reads files normally and compression + checkpointing happen transparently in the background.
|
|
37
|
+
|
|
38
|
+
## 3. Register the MCP server with Claude Code
|
|
39
|
+
|
|
40
|
+
Add to `~/.claude.json` (create it if it doesn't exist):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"cairn": {
|
|
46
|
+
"command": "cairn-mcp"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> If you already have other MCP servers, just add `"cairn": { ... }` inside the existing `"mcpServers"` block.
|
|
53
|
+
|
|
54
|
+
Restart Claude Code. You should see 8 cairn tools available.
|
|
55
|
+
|
|
56
|
+
## 4. Index your project
|
|
57
|
+
|
|
58
|
+
Run Claude Code from your project root, then:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
cairn_maintain
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
No paths needed — Cairn works like git and finds your project from the current directory. The index is stored in `.cairn/index.db` inside your project.
|
|
65
|
+
|
|
66
|
+
Run this once at the start of each session (or use `cairn_resume` to pick up where you left off).
|
|
67
|
+
|
|
68
|
+
## 5. Tools at a glance
|
|
69
|
+
|
|
70
|
+
| Tool | What to use it for |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `cairn_maintain` | Index the current project (run at session start) |
|
|
73
|
+
| `cairn_search` | Find classes, functions, components by name or concept |
|
|
74
|
+
| `cairn_describe` | Summarize what a folder/module does |
|
|
75
|
+
| `cairn_code_graph` | Check instability, health, or cycles before refactoring |
|
|
76
|
+
| `cairn_security` | Scan for vulnerabilities (XSS, SQLi, hardcoded secrets, etc.) |
|
|
77
|
+
| `cairn_bundle` | Package source files into a minified snapshot for Claude to read |
|
|
78
|
+
| `cairn_checkpoint` | Save what you're working on so the next session can resume |
|
|
79
|
+
| `cairn_resume` | Restore the last checkpoint + incremental re-index of changed files |
|
|
80
|
+
|
|
81
|
+
> `cairn_bundle` and `cairn_checkpoint` are called automatically when passive hooks are installed. They remain available for explicit use when needed.
|
|
82
|
+
|
|
83
|
+
## 6. Typical session
|
|
84
|
+
|
|
85
|
+
**With passive hooks (recommended):**
|
|
86
|
+
```
|
|
87
|
+
1. cairn_maintain → index the project
|
|
88
|
+
2. cairn_search → find what you need
|
|
89
|
+
3. cairn_describe → understand a module before modifying it
|
|
90
|
+
4. cairn_security → check for issues before a PR
|
|
91
|
+
(file reads auto-compressed, session auto-saved — nothing else needed)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Without hooks:**
|
|
95
|
+
```
|
|
96
|
+
1. cairn_maintain → index the project
|
|
97
|
+
2. cairn_search → find what you need
|
|
98
|
+
3. cairn_bundle → get a readable snapshot of relevant files
|
|
99
|
+
4. cairn_describe → understand a module before modifying it
|
|
100
|
+
5. cairn_security → check for issues before a PR
|
|
101
|
+
6. cairn_checkpoint → save your progress before ending the session
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Resuming work:**
|
|
105
|
+
```
|
|
106
|
+
1. cairn_resume → restore last checkpoint + re-index only changed files
|
|
107
|
+
2. cairn_search → continue where you left off
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 7. Complete session lifecycle
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
─── START OF SESSION ──────────────────────────────────────────
|
|
114
|
+
You: "Hey Claude, please resume and continue"
|
|
115
|
+
Claude: cairn_resume
|
|
116
|
+
→ "Last session: adding multi-currency to checkout.
|
|
117
|
+
PaymentStep.vue was modified since checkpoint (2 files changed, re-indexed).
|
|
118
|
+
Notes: CurrencyService expects ISO 4217 codes. PaymentStep EUR bug not fixed yet.
|
|
119
|
+
Ready to continue."
|
|
120
|
+
|
|
121
|
+
─── DURING SESSION ────────────────────────────────────────────
|
|
122
|
+
Claude uses: cairn_search, cairn_code_graph, cairn_security
|
|
123
|
+
as needed — all reading from .cairn/index.db in cwd
|
|
124
|
+
(file reads auto-compressed by PreToolUse hook)
|
|
125
|
+
|
|
126
|
+
─── END OF SESSION ────────────────────────────────────────────
|
|
127
|
+
You: "Ok let's stop here for today"
|
|
128
|
+
(Stop hook fires → .cairn/session.json auto-saved)
|
|
129
|
+
→ Next session: cairn_resume picks up automatically
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
**Requirements:** Node.js >= 22.15.0
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misterhuydo/cairn-mcp",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "MCP server that gives Claude Code persistent memory across sessions. Index your codebase once, search symbols, bundle source, scan for vulnerabilities, and checkpoint/resume work — across Java, TypeScript, Vue, Python, SQL and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"cairn-mcp": "bin/cairn-mcp.js"
|
|
8
|
+
"cairn-mcp": "bin/cairn-mcp.js",
|
|
9
|
+
"cairn": "bin/cairn.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"start": "node --experimental-sqlite index.js"
|