@senoldogann/context-manager 0.1.20 → 0.1.24

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 +159 -51
  2. package/bin/ccm.js +236 -37
  3. package/package.json +3 -5
package/README.md CHANGED
@@ -2,81 +2,189 @@
2
2
 
3
3
  > 🧠 The Neural Backbone for Autonomous AI Agents
4
4
 
5
- This is the Node.js wrapper for the **Cognitive Codebase Matrix (CCM)**. It allows you to run the CCM CLI and MCP server without manually installing Rust or building from source.
5
+ **Node.js wrapper for Cognitive Codebase Matrix (CCM)** - Enables AI agents to understand and navigate your codebase with surgical precision.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@senoldogann/context-manager?color=orange)](https://www.npmjs.com/package/@senoldogann/context-manager)
8
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io/)
9
+ [![Rust](https://img.shields.io/badge/Built%20With-Rust-orange.svg)](https://www.rust-lang.org/)
10
+
11
+ ---
6
12
 
7
13
  ## 🚀 Quick Start
8
14
 
9
- > [!IMPORTANT]
10
- > **Prerequisite:** This tool relies on local AI models. You must have **[Ollama](https://ollama.com)** installed and running.
11
- > CCM will **automatically pull** the required embedding model (`nomic-embed-text`) if missing, but the Ollama engine itself is required.
15
+ ### Prerequisites
16
+
17
+ 1. **Node.js** 16+ installed
18
+ 2. **Ollama** installed and running (for local embeddings)
19
+ - Download: https://ollama.com
20
+ - Pull required model: `ollama pull mxbai-embed-large`
12
21
 
13
- ### 🔒 Privacy by Default
14
- CCM uses a **Local-First** architecture by default. This means:
15
- * Your code is **never** sent to 3rd party servers (OpenAI, Anthropic, etc.).
16
- * All vector operations (Embedding) happen on your local machine.
17
- * You can safely use it for internal or confidential projects.
22
+ ### Installation
18
23
 
19
- The easiest way to get started. This will automatically add CCM to your Claude or Antigravity configuration:
20
24
  ```bash
25
+ # 1. Install and configure for Codex, Cursor, Claude Desktop, Antigravity, etc.
21
26
  npx @senoldogann/context-manager install
27
+
28
+ # 2. Index your project
29
+ npx @senoldogann/context-manager index --path .
22
30
  ```
23
31
 
24
- ### 2. Index your Project (Automatic)
25
- CCM now implements **Lazy Indexing**. You don't actually need to run this command; the MCP server will automatically index your project the first time you or your AI agent run a query.
32
+ **That's it!** Restart your AI editor and start asking questions about your code.
33
+
34
+ ---
35
+
36
+ ## 📖 What is CCM?
37
+
38
+ CCM transforms static source code into a dynamic, queryable Knowledge Graph:
39
+
40
+ - **🔍 Semantic Search** - Find code by meaning ("where is auth logic?")
41
+ - **🧠 Graph Navigation** - Understand relationships ("who calls this function?")
42
+ - **📍 Cursor Context** - Get relevant code based on your position
43
+
44
+ ---
45
+
46
+ ## 🔧 Commands
47
+
48
+ The npm wrapper downloads pre-built binaries and passes commands through:
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `npx @senoldogann/context-manager install` | Auto-configure MCP for editors |
53
+ | `npx @senoldogann/context-manager index --path <dir>` | Index a project |
54
+ | `npx @senoldogann/context-manager query --text "..."` | Search codebase |
55
+ | `npx @senoldogann/context-manager mcp` | Run MCP server directly |
56
+ | `npx @senoldogann/context-manager eval --tasks <file>` | Run evaluation tasks |
57
+
58
+ ### Options
26
59
 
27
- If you still want to index manually:
28
60
  ```bash
29
- npx @senoldogann/context-manager index --path .
30
- ```
61
+ # Watch mode - auto-reindex on file changes
62
+ npx @senoldogann/context-manager index --path . --watch
31
63
 
32
- ## ⚒️ Manual Configuration (Alternative)
33
-
34
- If you prefer to configure your AI editor manually without the `install` command, add this to your `mcp_config.json`:
35
-
36
- ```json
37
- {
38
- "mcpServers": {
39
- "context-manager": {
40
- "command": "npx",
41
- "args": ["-y", "@senoldogann/context-manager", "mcp"],
42
- "env": {
43
- "RUST_LOG": "info"
44
- }
45
- }
46
- }
47
- }
64
+ # Custom database path
65
+ npx @senoldogann/context-manager index --path . --db-path /custom/path
48
66
  ```
49
67
 
50
- ## 💡 Pro-Tip: Enforcing CCM Usage
51
- To ensure your AI agent (Claude, Cursor, etc.) always uses CCM for deep analysis, add this to your **Custom Instructions** or **System Prompt**:
68
+ ---
52
69
 
53
- > "You are an expert architect. For any question about the codebase, DO NOT guess. Use the `context-manager` tools to explore the Graph and Vector store. Always prioritize `search_code` to find entry points and `read_graph` to navigate dependencies before proposing any code changes."
70
+ ## 🔒 Privacy by Default
71
+
72
+ CCM uses a **Local-First** architecture:
73
+
74
+ - ✅ Your code **never** leaves your machine
75
+ - ✅ All embeddings run locally via Ollama
76
+ - ✅ No external API calls (unless you configure OpenAI)
54
77
 
55
78
  ---
56
79
 
57
- ## 🇹🇷 Türkçe Özet
80
+ ## ⚙️ Configuration
58
81
 
59
- Bu paket, **Cognitive Codebase Matrix (CCM)** için Node.js wrapper'ıdır. Rust kurulumuna gerek kalmadan CCM araçlarını kullanmanızı sağlar.
82
+ ### Environment Variables
60
83
 
61
- **Hızlı Kurulum:**
62
- ```bash
63
- npx @senoldogann/context-manager install
64
- npx @senoldogann/context-manager index --path .
84
+ Create `~/.ccm/.env`:
85
+
86
+ ```ini
87
+ # Local (Recommended)
88
+ EMBEDDING_PROVIDER=ollama
89
+ EMBEDDING_HOST=http://127.0.0.1:11434
90
+ EMBEDDING_MODEL=mxbai-embed-large
91
+
92
+ # Cloud (Optional)
93
+ EMBEDDING_PROVIDER=openai
94
+ EMBEDDING_API_KEY=sk-your-key
95
+ EMBEDDING_MODEL=text-embedding-3-small
96
+
97
+ # Networking & Limits
98
+ EMBEDDING_TIMEOUT_SECS=30
99
+ CCM_MAX_FILE_BYTES=2097152
100
+
101
+ # MCP Security
102
+ CCM_ALLOWED_ROOTS=/Users/you/projects:/Users/you/sandbox
103
+ CCM_REQUIRE_ALLOWED_ROOTS=0
104
+
105
+ # MCP Runtime
106
+ CCM_MCP_ENGINE_CACHE_SIZE=8
107
+ CCM_MCP_DEBUG=0
108
+
109
+ # Optional: disable embeddings entirely (semantic search disabled)
110
+ CCM_DISABLE_EMBEDDER=0
111
+
112
+ # Optional: embed data files (md/json/yaml) into vector search
113
+ CCM_EMBED_DATA_FILES=0
114
+
115
+ # Binary checksum verification (0 = enforce, 1 = bypass)
116
+ CCM_ALLOW_UNVERIFIED_BINARIES=0
65
117
  ```
66
118
 
119
+ **Production Tip:** Set `CCM_ALLOWED_ROOTS` and enable `CCM_REQUIRE_ALLOWED_ROOTS=1` to restrict MCP access.
120
+
67
121
  ---
68
122
 
69
- ## 📦 What this package does
70
- This package is a lightweight wrapper that:
71
- 1. Detects your OS and CPU architecture.
72
- 2. Downloads the pre-built Rust binaries from GitHub Releases if not already present.
73
- 3. Automatically manages your global index and persistence in `~/.ccm`.
123
+ ## 🤖 Usage in AI
124
+
125
+ Once configured, ask your AI agent:
126
+
127
+ > "Search for the authentication flow in this codebase."
74
128
 
75
- For more details, visit the [Main Repository](https://github.com/senoldogann/LLM-Context-Manager).
129
+ > "Read the graph for `UserService` and show me its callers."
130
+
131
+ > "What functions call `parse_config`?"
132
+
133
+ ---
76
134
 
77
- ### 🆕 v0.1.15 Updates
78
- * **Lazy Indexing:** Automatic project indexing on first query. No background hogging.
79
- * **Universal Support:** Falls back to generic text indexing for ALL file types.
80
- * **Zero-Config:** Improved project root detection and cross-platform binary handling.
135
+ ## 📦 For Developers
136
+
137
+ This package handles:
138
+
139
+ 1. OS/architecture detection
140
+ 2. Binary download from GitHub Releases
141
+ 3. Global persistence in `~/.ccm`
142
+
143
+ ### ✅ Binary Integrity
144
+
145
+ Downloads are verified against `checksums.txt` from the GitHub Release.
146
+ If the manifest is missing or a mismatch occurs, you can set `CCM_ALLOW_UNVERIFIED_BINARIES=1` to bypass verification (not recommended).
147
+
148
+ ### 📄 Data Files in Search
149
+
150
+ By default, data files (`.md`, `.json`, `.yaml`) are indexed but not embedded.
151
+ Enable `CCM_EMBED_DATA_FILES=1` to include them in semantic search.
152
+
153
+ **Source:** https://github.com/senoldogann/LLM-Context-Manager
154
+
155
+ ---
156
+
157
+ ## 📝 Changelog
158
+
159
+ ### v0.1.24
160
+ - ✅ Native `protoc` installation in GitHub Actions release builds
161
+ - ✅ Updated release workflow actions for newer runner compatibility
162
+
163
+ ### v0.1.23
164
+ - ✅ JSON-RPC request size limit for safer MCP stdio transport
165
+ - ✅ Sensitive value redaction in MCP debug logs
166
+ - ✅ Hardened path normalization for MCP graph and context tools
167
+ - ✅ GitHub release redirect allowlist for binary downloads
168
+ - ✅ Unused core dependency cleanup
169
+
170
+ ### v0.1.22
171
+ - ✅ Codex installer support via `codex mcp add`
172
+ - ✅ Cursor config support via `~/.cursor/mcp.json`
173
+ - ✅ Evaluation bootstraps missing indexes before scoring
174
+ - ✅ Golden tasks refreshed for the current repo layout
175
+
176
+ ### v0.1.21
177
+ - ✅ Release checksums (`checksums.txt`) for binary integrity
178
+ - ✅ MCP allowlist with optional strict enforcement
179
+ - ✅ Data file embedding is optional (`CCM_EMBED_DATA_FILES`)
180
+ - ✅ CLI/MCP integration tests
181
+
182
+ ### v0.1.20
183
+ - ✅ 100% evaluation pass rate
184
+ - ✅ Hybrid scoring (graph + semantic)
185
+ - ✅ Lazy indexing support
186
+ - ✅ Watch mode for auto-reindexing
187
+
188
+ ---
81
189
 
82
- Built with ❤️ in **Rust**.
190
+ Built with ❤️ in **Rust**
package/bin/ccm.js CHANGED
@@ -1,39 +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
+ const crypto = require('crypto');
8
9
 
9
- const VERSION = "0.1.20";
10
+ const VERSION = "0.1.24";
10
11
  const REPO = 'senoldogann/LLM-Context-Manager';
11
12
  const BIN_DIR = path.join(os.homedir(), '.ccm', 'bin');
13
+ const CHECKSUMS_FILE = 'checksums.txt';
14
+ let checksumCache = null;
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
+
28
+ function allowUnverifiedBinaries() {
29
+ const raw = process.env.CCM_ALLOW_UNVERIFIED_BINARIES || process.env.CCM_SKIP_CHECKSUM || '';
30
+ return ['1', 'true', 'yes'].includes(raw.toLowerCase());
31
+ }
12
32
 
13
33
  async function installMcp() {
14
- const configPaths = [];
15
34
  const home = os.homedir();
35
+ const jsonTargets = [];
16
36
 
17
37
  if (os.platform() === 'darwin') {
18
- configPaths.push(path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'));
19
- configPaths.push(path.join(home, '.gemini', 'antigravity', 'mcp_config.json'));
20
- configPaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
21
- 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'));
22
42
  } else if (os.platform() === 'win32') {
23
43
  const appData = process.env.APPDATA || '';
24
- configPaths.push(path.join(appData, 'Claude', 'claude_desktop_config.json'));
25
- 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'));
26
46
  } else if (os.platform() === 'linux') {
27
- configPaths.push(path.join(home, '.config', 'Claude', 'claude_desktop_config.json'));
28
- 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'));
29
49
  }
30
50
 
51
+ jsonTargets.push(path.join(home, '.cursor', 'mcp.json'));
52
+
31
53
  const mcpConfig = {
32
- "command": "npx",
33
- "args": ["-y", "@senoldogann/context-manager", "mcp"],
34
- "env": {
35
- "RUST_LOG": "info"
36
- }
54
+ command: MCP_COMMAND,
55
+ args: MCP_ARGS,
56
+ env: MCP_ENV
37
57
  };
38
58
 
39
59
  console.log("[CCM] Pre-downloading binaries for all tools...");
@@ -41,38 +61,104 @@ async function installMcp() {
41
61
  await getBinaryFor('ccm-mcp');
42
62
 
43
63
  let installedCount = 0;
44
- for (const configPath of configPaths) {
45
- const dir = path.dirname(configPath);
46
- if (fs.existsSync(dir)) {
47
- let config = { mcpServers: {} };
48
- if (fs.existsSync(configPath)) {
49
- try {
50
- const content = fs.readFileSync(configPath, 'utf8');
51
- config = JSON.parse(content);
52
- fs.copyFileSync(configPath, `${configPath}.bak`);
53
- } catch (e) {
54
- console.warn(`[CCM] Could not parse ${configPath}, creating backup and starting fresh section.`);
55
- }
56
- }
57
-
58
- if (!config.mcpServers) config.mcpServers = {};
59
- config.mcpServers["context-manager"] = mcpConfig;
60
64
 
61
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
62
- console.log(`[CCM] Successfully updated: ${configPath}`);
65
+ for (const configPath of jsonTargets) {
66
+ if (installJsonConfig(configPath, mcpConfig)) {
63
67
  installedCount++;
64
68
  }
65
69
  }
66
70
 
71
+ if (installCodexConfig()) {
72
+ installedCount++;
73
+ }
74
+
67
75
  if (installedCount === 0) {
68
76
  console.log("[CCM] No supported MCP config directories found.");
69
- console.log("[CCM] Please add this to your mcp_config.json manually:");
70
- 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(' ')}`);
71
81
  } else {
72
82
  console.log("[CCM] Installation complete! Restart your AI editor to see the changes.");
73
83
  }
74
84
  }
75
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
+
76
162
  async function getBinaryFor(commandName) {
77
163
  const platform = os.platform();
78
164
  const arch = os.arch();
@@ -88,6 +174,7 @@ async function getBinaryFor(commandName) {
88
174
 
89
175
  const binFilename = `${commandName}-v${VERSION}-${target}`;
90
176
  const binPath = path.join(BIN_DIR, binFilename);
177
+ const remoteFilename = `${commandName}-${target}`;
91
178
 
92
179
  // If file exists, ensure it is executable
93
180
  if (fs.existsSync(binPath)) {
@@ -110,6 +197,7 @@ async function getBinaryFor(commandName) {
110
197
 
111
198
  try {
112
199
  await downloadFile(url, tmpPath);
200
+ await verifyChecksum(tmpPath, [remoteFilename, binFilename]);
113
201
  fs.chmodSync(tmpPath, '755');
114
202
  fs.renameSync(tmpPath, binPath);
115
203
  } catch (err) {
@@ -142,7 +230,12 @@ function downloadFile(url, dest) {
142
230
  return new Promise((resolve, reject) => {
143
231
  https.get(url, (response) => {
144
232
  if (response.statusCode === 302 || response.statusCode === 301) {
145
- 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
+ }
146
239
  }
147
240
  if (response.statusCode !== 200) {
148
241
  return reject(new Error(`Failed to download: ${response.statusCode}`));
@@ -163,6 +256,112 @@ function downloadFile(url, dest) {
163
256
  });
164
257
  }
165
258
 
259
+ function downloadText(url) {
260
+ return new Promise((resolve, reject) => {
261
+ https.get(url, (response) => {
262
+ if (response.statusCode === 302 || response.statusCode === 301) {
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
+ }
269
+ }
270
+ if (response.statusCode !== 200) {
271
+ return reject(new Error(`Failed to download: ${response.statusCode}`));
272
+ }
273
+ let data = '';
274
+ response.setEncoding('utf8');
275
+ response.on('data', (chunk) => {
276
+ data += chunk;
277
+ });
278
+ response.on('end', () => resolve(data));
279
+ }).on('error', reject);
280
+ });
281
+ }
282
+
283
+ async function getChecksums() {
284
+ if (checksumCache) return checksumCache;
285
+
286
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${CHECKSUMS_FILE}`;
287
+ try {
288
+ const text = await downloadText(url);
289
+ checksumCache = parseChecksums(text);
290
+ return checksumCache;
291
+ } catch (err) {
292
+ return null;
293
+ }
294
+ }
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
+
312
+ function parseChecksums(text) {
313
+ const map = new Map();
314
+ for (const line of text.split('\n')) {
315
+ const trimmed = line.trim();
316
+ if (!trimmed) continue;
317
+ const parts = trimmed.split(/\s+/);
318
+ if (parts.length < 2) continue;
319
+ const hash = parts[0].toLowerCase();
320
+ const filename = parts[parts.length - 1].replace(/^\*/, '');
321
+ map.set(filename, hash);
322
+ }
323
+ return map;
324
+ }
325
+
326
+ function sha256File(filePath) {
327
+ return new Promise((resolve, reject) => {
328
+ const hash = crypto.createHash('sha256');
329
+ const stream = fs.createReadStream(filePath);
330
+ stream.on('error', reject);
331
+ stream.on('data', (data) => hash.update(data));
332
+ stream.on('end', () => resolve(hash.digest('hex')));
333
+ });
334
+ }
335
+
336
+ async function verifyChecksum(filePath, candidates) {
337
+ const checksums = await getChecksums();
338
+ if (!checksums) {
339
+ if (allowUnverifiedBinaries()) {
340
+ console.warn('[CCM] Checksum manifest not found. Proceeding without verification.');
341
+ return;
342
+ }
343
+ throw new Error('Checksum manifest not found. Set CCM_ALLOW_UNVERIFIED_BINARIES=1 to bypass.');
344
+ }
345
+
346
+ const expected = candidates
347
+ .map((name) => [name, `${name}.exe`])
348
+ .flat()
349
+ .map((name) => checksums.get(name))
350
+ .find(Boolean);
351
+ if (!expected) {
352
+ if (allowUnverifiedBinaries()) {
353
+ console.warn('[CCM] Checksum not found for binary. Proceeding without verification.');
354
+ return;
355
+ }
356
+ throw new Error('Checksum not found for binary. Set CCM_ALLOW_UNVERIFIED_BINARIES=1 to bypass.');
357
+ }
358
+
359
+ const actual = await sha256File(filePath);
360
+ if (actual !== expected) {
361
+ throw new Error(`Checksum mismatch. Expected ${expected}, got ${actual}`);
362
+ }
363
+ }
364
+
166
365
  async function main() {
167
366
  try {
168
367
  const binPath = await getBinary();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senoldogann/context-manager",
3
- "version": "0.1.20",
3
+ "version": "0.1.24",
4
4
  "description": "LLM Context Manager MCP Server & CLI wrapper using npx",
5
5
  "main": "bin/ccm.js",
6
6
  "bin": {
@@ -20,10 +20,8 @@
20
20
  ],
21
21
  "author": "dogan",
22
22
  "license": "MIT",
23
- "dependencies": {
24
- "node-fetch": "^3.3.1"
25
- },
23
+ "dependencies": {},
26
24
  "engines": {
27
25
  "node": ">=16.0.0"
28
26
  }
29
- }
27
+ }