@senoldogann/context-manager 0.1.21 → 0.1.25
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 +56 -1
- package/bin/ccm.js +142 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
### Installation
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
# 1. Install and configure for Claude Desktop, Antigravity,
|
|
25
|
+
# 1. Install and configure for Codex, Cursor, Claude Desktop, Antigravity, etc.
|
|
26
26
|
npx @senoldogann/context-manager install
|
|
27
27
|
|
|
28
28
|
# 2. Index your project
|
|
@@ -31,6 +31,20 @@ npx @senoldogann/context-manager index --path .
|
|
|
31
31
|
|
|
32
32
|
**That's it!** Restart your AI editor and start asking questions about your code.
|
|
33
33
|
|
|
34
|
+
### First-Run Verification
|
|
35
|
+
|
|
36
|
+
Use this quick smoke test after install:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Verify the CLI responds
|
|
40
|
+
npx @senoldogann/context-manager query --text "src/main.rs:1"
|
|
41
|
+
|
|
42
|
+
# Verify the MCP binary starts
|
|
43
|
+
npx @senoldogann/context-manager mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If the wrapper is healthy, the query command should return code context and the MCP process should stay open waiting for stdio messages.
|
|
47
|
+
|
|
34
48
|
---
|
|
35
49
|
|
|
36
50
|
## 📖 What is CCM?
|
|
@@ -55,6 +69,15 @@ The npm wrapper downloads pre-built binaries and passes commands through:
|
|
|
55
69
|
| `npx @senoldogann/context-manager mcp` | Run MCP server directly |
|
|
56
70
|
| `npx @senoldogann/context-manager eval --tasks <file>` | Run evaluation tasks |
|
|
57
71
|
|
|
72
|
+
### Supported Hosts
|
|
73
|
+
|
|
74
|
+
| Host | Status |
|
|
75
|
+
|------|--------|
|
|
76
|
+
| Codex | Supported |
|
|
77
|
+
| Cursor | Supported |
|
|
78
|
+
| Claude Desktop | Supported |
|
|
79
|
+
| Antigravity | Supported |
|
|
80
|
+
|
|
58
81
|
### Options
|
|
59
82
|
|
|
60
83
|
```bash
|
|
@@ -140,6 +163,15 @@ This package handles:
|
|
|
140
163
|
2. Binary download from GitHub Releases
|
|
141
164
|
3. Global persistence in `~/.ccm`
|
|
142
165
|
|
|
166
|
+
### ✅ Release Reliability
|
|
167
|
+
|
|
168
|
+
- GitHub Releases publish platform binaries plus `checksums.txt`
|
|
169
|
+
- The npm wrapper verifies checksums before using downloaded binaries
|
|
170
|
+
- Redirects are restricted to approved GitHub release hosts
|
|
171
|
+
- Release builds run for Linux, macOS, and Windows before assets are attached
|
|
172
|
+
- npm publishing uses GitHub Actions trusted publishing with OIDC
|
|
173
|
+
- The same smoke path is documented here and in the main repository README
|
|
174
|
+
|
|
143
175
|
### ✅ Binary Integrity
|
|
144
176
|
|
|
145
177
|
Downloads are verified against `checksums.txt` from the GitHub Release.
|
|
@@ -152,10 +184,33 @@ Enable `CCM_EMBED_DATA_FILES=1` to include them in semantic search.
|
|
|
152
184
|
|
|
153
185
|
**Source:** https://github.com/senoldogann/LLM-Context-Manager
|
|
154
186
|
|
|
187
|
+
**Docs:** [English README](https://github.com/senoldogann/LLM-Context-Manager/blob/main/README.md) | [Turkish README](https://github.com/senoldogann/LLM-Context-Manager/blob/main/README.tr.md)
|
|
188
|
+
|
|
155
189
|
---
|
|
156
190
|
|
|
157
191
|
## 📝 Changelog
|
|
158
192
|
|
|
193
|
+
### v0.1.25
|
|
194
|
+
- ✅ npm publish now uses GitHub Actions trusted publishing (OIDC)
|
|
195
|
+
- ✅ Clearer first-run verification and release reliability docs
|
|
196
|
+
|
|
197
|
+
### v0.1.24
|
|
198
|
+
- ✅ Native `protoc` installation in GitHub Actions release builds
|
|
199
|
+
- ✅ Updated release workflow actions for newer runner compatibility
|
|
200
|
+
|
|
201
|
+
### v0.1.23
|
|
202
|
+
- ✅ JSON-RPC request size limit for safer MCP stdio transport
|
|
203
|
+
- ✅ Sensitive value redaction in MCP debug logs
|
|
204
|
+
- ✅ Hardened path normalization for MCP graph and context tools
|
|
205
|
+
- ✅ GitHub release redirect allowlist for binary downloads
|
|
206
|
+
- ✅ Unused core dependency cleanup
|
|
207
|
+
|
|
208
|
+
### v0.1.22
|
|
209
|
+
- ✅ Codex installer support via `codex mcp add`
|
|
210
|
+
- ✅ Cursor config support via `~/.cursor/mcp.json`
|
|
211
|
+
- ✅ Evaluation bootstraps missing indexes before scoring
|
|
212
|
+
- ✅ Golden tasks refreshed for the current repo layout
|
|
213
|
+
|
|
159
214
|
### v0.1.21
|
|
160
215
|
- ✅ Release checksums (`checksums.txt`) for binary integrity
|
|
161
216
|
- ✅ MCP allowlist with optional strict enforcement
|
package/bin/ccm.js
CHANGED
|
@@ -1,47 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process');
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const https = require('https');
|
|
8
8
|
const crypto = require('crypto');
|
|
9
9
|
|
|
10
|
-
const VERSION = "0.1.
|
|
10
|
+
const VERSION = "0.1.25";
|
|
11
11
|
const REPO = 'senoldogann/LLM-Context-Manager';
|
|
12
12
|
const BIN_DIR = path.join(os.homedir(), '.ccm', 'bin');
|
|
13
13
|
const CHECKSUMS_FILE = 'checksums.txt';
|
|
14
14
|
let checksumCache = null;
|
|
15
15
|
|
|
16
|
+
const MCP_SERVER_NAME = 'context-manager';
|
|
17
|
+
const MCP_COMMAND = 'npx';
|
|
18
|
+
const MCP_ARGS = ['-y', '@senoldogann/context-manager', 'mcp'];
|
|
19
|
+
const MCP_ENV = {
|
|
20
|
+
RUST_LOG: 'info'
|
|
21
|
+
};
|
|
22
|
+
const ALLOWED_REDIRECT_HOSTS = new Set([
|
|
23
|
+
'github.com',
|
|
24
|
+
'objects.githubusercontent.com',
|
|
25
|
+
'release-assets.githubusercontent.com'
|
|
26
|
+
]);
|
|
27
|
+
|
|
16
28
|
function allowUnverifiedBinaries() {
|
|
17
29
|
const raw = process.env.CCM_ALLOW_UNVERIFIED_BINARIES || process.env.CCM_SKIP_CHECKSUM || '';
|
|
18
30
|
return ['1', 'true', 'yes'].includes(raw.toLowerCase());
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
async function installMcp() {
|
|
22
|
-
const configPaths = [];
|
|
23
34
|
const home = os.homedir();
|
|
35
|
+
const jsonTargets = [];
|
|
24
36
|
|
|
25
37
|
if (os.platform() === 'darwin') {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
jsonTargets.push(path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'));
|
|
39
|
+
jsonTargets.push(path.join(home, '.gemini', 'antigravity', 'mcp_config.json'));
|
|
40
|
+
jsonTargets.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
41
|
+
jsonTargets.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'cline_mcp_settings.json'));
|
|
30
42
|
} else if (os.platform() === 'win32') {
|
|
31
43
|
const appData = process.env.APPDATA || '';
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
jsonTargets.push(path.join(appData, 'Claude', 'claude_desktop_config.json'));
|
|
45
|
+
jsonTargets.push(path.join(process.env.USERPROFILE || '', 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
34
46
|
} else if (os.platform() === 'linux') {
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
jsonTargets.push(path.join(home, '.config', 'Claude', 'claude_desktop_config.json'));
|
|
48
|
+
jsonTargets.push(path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
|
|
37
49
|
}
|
|
38
50
|
|
|
51
|
+
jsonTargets.push(path.join(home, '.cursor', 'mcp.json'));
|
|
52
|
+
|
|
39
53
|
const mcpConfig = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"RUST_LOG": "info"
|
|
44
|
-
}
|
|
54
|
+
command: MCP_COMMAND,
|
|
55
|
+
args: MCP_ARGS,
|
|
56
|
+
env: MCP_ENV
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
console.log("[CCM] Pre-downloading binaries for all tools...");
|
|
@@ -49,38 +61,104 @@ async function installMcp() {
|
|
|
49
61
|
await getBinaryFor('ccm-mcp');
|
|
50
62
|
|
|
51
63
|
let installedCount = 0;
|
|
52
|
-
for (const configPath of configPaths) {
|
|
53
|
-
const dir = path.dirname(configPath);
|
|
54
|
-
if (fs.existsSync(dir)) {
|
|
55
|
-
let config = { mcpServers: {} };
|
|
56
|
-
if (fs.existsSync(configPath)) {
|
|
57
|
-
try {
|
|
58
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
59
|
-
config = JSON.parse(content);
|
|
60
|
-
fs.copyFileSync(configPath, `${configPath}.bak`);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.warn(`[CCM] Could not parse ${configPath}, creating backup and starting fresh section.`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
67
|
-
config.mcpServers["context-manager"] = mcpConfig;
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
for (const configPath of jsonTargets) {
|
|
66
|
+
if (installJsonConfig(configPath, mcpConfig)) {
|
|
71
67
|
installedCount++;
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
70
|
|
|
71
|
+
if (installCodexConfig()) {
|
|
72
|
+
installedCount++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
75
|
if (installedCount === 0) {
|
|
76
76
|
console.log("[CCM] No supported MCP config directories found.");
|
|
77
|
-
console.log("[CCM]
|
|
78
|
-
console.log(JSON.stringify({
|
|
77
|
+
console.log("[CCM] Add this server manually:");
|
|
78
|
+
console.log(JSON.stringify({ [MCP_SERVER_NAME]: mcpConfig }, null, 2));
|
|
79
|
+
console.log("[CCM] Codex example:");
|
|
80
|
+
console.log(`codex mcp add ${MCP_SERVER_NAME} --env RUST_LOG=info -- ${MCP_COMMAND} ${MCP_ARGS.join(' ')}`);
|
|
79
81
|
} else {
|
|
80
82
|
console.log("[CCM] Installation complete! Restart your AI editor to see the changes.");
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
function installJsonConfig(configPath, mcpConfig) {
|
|
87
|
+
const dir = path.dirname(configPath);
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(dir)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let config = { mcpServers: {} };
|
|
94
|
+
if (fs.existsSync(configPath)) {
|
|
95
|
+
try {
|
|
96
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
97
|
+
config = JSON.parse(content);
|
|
98
|
+
fs.copyFileSync(configPath, `${configPath}.bak`);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.warn(`[CCM] Could not parse ${configPath}, creating backup and starting fresh section.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
105
|
+
config.mcpServers = {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
config.mcpServers[MCP_SERVER_NAME] = mcpConfig;
|
|
109
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
110
|
+
console.log(`[CCM] ✓ Successfully updated: ${configPath}`);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function installCodexConfig() {
|
|
115
|
+
const listResult = spawnSync('codex', ['mcp', 'list', '--json'], {
|
|
116
|
+
encoding: 'utf8'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (listResult.error) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (listResult.status !== 0) {
|
|
124
|
+
console.warn(`[CCM] Codex MCP inspection failed: ${listResult.stderr.trim()}`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let existingServers = [];
|
|
129
|
+
try {
|
|
130
|
+
existingServers = JSON.parse(listResult.stdout);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn('[CCM] Could not parse Codex MCP list output.');
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const existing = existingServers.find((server) => server.name === MCP_SERVER_NAME);
|
|
137
|
+
if (existing) {
|
|
138
|
+
const removeResult = spawnSync('codex', ['mcp', 'remove', MCP_SERVER_NAME], {
|
|
139
|
+
encoding: 'utf8'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (removeResult.status !== 0) {
|
|
143
|
+
console.warn(`[CCM] Codex MCP removal failed: ${removeResult.stderr.trim()}`);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const addArgs = ['mcp', 'add', MCP_SERVER_NAME, '--env', 'RUST_LOG=info', '--', MCP_COMMAND, ...MCP_ARGS];
|
|
149
|
+
const addResult = spawnSync('codex', addArgs, {
|
|
150
|
+
encoding: 'utf8'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (addResult.status !== 0) {
|
|
154
|
+
console.warn(`[CCM] Codex MCP install failed: ${addResult.stderr.trim()}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('[CCM] ✓ Successfully updated: ~/.codex/config.toml');
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
84
162
|
async function getBinaryFor(commandName) {
|
|
85
163
|
const platform = os.platform();
|
|
86
164
|
const arch = os.arch();
|
|
@@ -152,7 +230,12 @@ function downloadFile(url, dest) {
|
|
|
152
230
|
return new Promise((resolve, reject) => {
|
|
153
231
|
https.get(url, (response) => {
|
|
154
232
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
155
|
-
|
|
233
|
+
try {
|
|
234
|
+
const redirectUrl = resolveRedirectUrl(url, response.headers.location);
|
|
235
|
+
return downloadFile(redirectUrl, dest).then(resolve).catch(reject);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return reject(error);
|
|
238
|
+
}
|
|
156
239
|
}
|
|
157
240
|
if (response.statusCode !== 200) {
|
|
158
241
|
return reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
@@ -177,7 +260,12 @@ function downloadText(url) {
|
|
|
177
260
|
return new Promise((resolve, reject) => {
|
|
178
261
|
https.get(url, (response) => {
|
|
179
262
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
180
|
-
|
|
263
|
+
try {
|
|
264
|
+
const redirectUrl = resolveRedirectUrl(url, response.headers.location);
|
|
265
|
+
return downloadText(redirectUrl).then(resolve).catch(reject);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return reject(error);
|
|
268
|
+
}
|
|
181
269
|
}
|
|
182
270
|
if (response.statusCode !== 200) {
|
|
183
271
|
return reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
@@ -205,6 +293,22 @@ async function getChecksums() {
|
|
|
205
293
|
}
|
|
206
294
|
}
|
|
207
295
|
|
|
296
|
+
function resolveRedirectUrl(sourceUrl, location) {
|
|
297
|
+
if (!location) {
|
|
298
|
+
throw new Error('Redirect response did not include a Location header');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const resolved = new URL(location, sourceUrl);
|
|
302
|
+
if (resolved.protocol !== 'https:') {
|
|
303
|
+
throw new Error(`Blocked redirect to non-HTTPS URL: ${resolved.href}`);
|
|
304
|
+
}
|
|
305
|
+
if (!ALLOWED_REDIRECT_HOSTS.has(resolved.hostname)) {
|
|
306
|
+
throw new Error(`Blocked redirect to unexpected host: ${resolved.hostname}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return resolved.toString();
|
|
310
|
+
}
|
|
311
|
+
|
|
208
312
|
function parseChecksums(text) {
|
|
209
313
|
const map = new Map();
|
|
210
314
|
for (const line of text.split('\n')) {
|