@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.
Files changed (3) hide show
  1. package/README.md +56 -1
  2. package/bin/ccm.js +142 -38
  3. 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, Cursor, etc.
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.21";
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
- configPaths.push(path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'));
27
- configPaths.push(path.join(home, '.gemini', 'antigravity', 'mcp_config.json'));
28
- configPaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
29
- configPaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'cline_mcp_settings.json'));
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
- configPaths.push(path.join(appData, 'Claude', 'claude_desktop_config.json'));
33
- configPaths.push(path.join(process.env.USERPROFILE || '', 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
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
- configPaths.push(path.join(home, '.config', 'Claude', 'claude_desktop_config.json'));
36
- configPaths.push(path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
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
- "command": "npx",
41
- "args": ["-y", "@senoldogann/context-manager", "mcp"],
42
- "env": {
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
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
70
- console.log(`[CCM] Successfully updated: ${configPath}`);
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] Please add this to your mcp_config.json manually:");
78
- console.log(JSON.stringify({ "context-manager": mcpConfig }, null, 2));
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
- return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
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
- return downloadText(response.headers.location).then(resolve).catch(reject);
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')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senoldogann/context-manager",
3
- "version": "0.1.21",
3
+ "version": "0.1.25",
4
4
  "description": "LLM Context Manager MCP Server & CLI wrapper using npx",
5
5
  "main": "bin/ccm.js",
6
6
  "bin": {