context-gatekeeper 0.3.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.
- package/LICENSE +21 -0
- package/README.md +774 -0
- package/README.zh.md +765 -0
- package/bin/context-gatekeeper-cli.js +60 -0
- package/dist/api/gdpr.d.ts +104 -0
- package/dist/api/gdpr.d.ts.map +1 -0
- package/dist/api/gdpr.js +229 -0
- package/dist/api/gdpr.js.map +1 -0
- package/dist/api/health-check.d.ts +13 -0
- package/dist/api/health-check.d.ts.map +1 -0
- package/dist/api/health-check.js +2 -0
- package/dist/api/health-check.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/observability.d.ts +39 -0
- package/dist/api/observability.d.ts.map +1 -0
- package/dist/api/observability.js +132 -0
- package/dist/api/observability.js.map +1 -0
- package/dist/api/session-manager.d.ts +41 -0
- package/dist/api/session-manager.d.ts.map +1 -0
- package/dist/api/session-manager.js +129 -0
- package/dist/api/session-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +613 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/configure-llm.d.ts +26 -0
- package/dist/mcp/tools/configure-llm.d.ts.map +1 -0
- package/dist/mcp/tools/configure-llm.js +32 -0
- package/dist/mcp/tools/configure-llm.js.map +1 -0
- package/dist/mcp/tools/context-compress.d.ts +15 -0
- package/dist/mcp/tools/context-compress.d.ts.map +1 -0
- package/dist/mcp/tools/context-compress.js +15 -0
- package/dist/mcp/tools/context-compress.js.map +1 -0
- package/dist/mcp/tools/dual-mode-execute.d.ts +78 -0
- package/dist/mcp/tools/dual-mode-execute.d.ts.map +1 -0
- package/dist/mcp/tools/dual-mode-execute.js +299 -0
- package/dist/mcp/tools/dual-mode-execute.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +19 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +20 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/intelligent-recall.d.ts +67 -0
- package/dist/mcp/tools/intelligent-recall.d.ts.map +1 -0
- package/dist/mcp/tools/intelligent-recall.js +208 -0
- package/dist/mcp/tools/intelligent-recall.js.map +1 -0
- package/dist/mcp/tools/memory-anchor.d.ts +13 -0
- package/dist/mcp/tools/memory-anchor.d.ts.map +1 -0
- package/dist/mcp/tools/memory-anchor.js +16 -0
- package/dist/mcp/tools/memory-anchor.js.map +1 -0
- package/dist/mcp/tools/memory-delete-batch.d.ts +16 -0
- package/dist/mcp/tools/memory-delete-batch.d.ts.map +1 -0
- package/dist/mcp/tools/memory-delete-batch.js +26 -0
- package/dist/mcp/tools/memory-delete-batch.js.map +1 -0
- package/dist/mcp/tools/memory-extract.d.ts +68 -0
- package/dist/mcp/tools/memory-extract.d.ts.map +1 -0
- package/dist/mcp/tools/memory-extract.js +280 -0
- package/dist/mcp/tools/memory-extract.js.map +1 -0
- package/dist/mcp/tools/memory-recall.d.ts +42 -0
- package/dist/mcp/tools/memory-recall.d.ts.map +1 -0
- package/dist/mcp/tools/memory-recall.js +37 -0
- package/dist/mcp/tools/memory-recall.js.map +1 -0
- package/dist/mcp/tools/memory-report-usage.d.ts +17 -0
- package/dist/mcp/tools/memory-report-usage.d.ts.map +1 -0
- package/dist/mcp/tools/memory-report-usage.js +15 -0
- package/dist/mcp/tools/memory-report-usage.js.map +1 -0
- package/dist/mcp/tools/memory-search.d.ts +43 -0
- package/dist/mcp/tools/memory-search.d.ts.map +1 -0
- package/dist/mcp/tools/memory-search.js +38 -0
- package/dist/mcp/tools/memory-search.js.map +1 -0
- package/dist/mcp/tools/memory-session.d.ts +118 -0
- package/dist/mcp/tools/memory-session.d.ts.map +1 -0
- package/dist/mcp/tools/memory-session.js +113 -0
- package/dist/mcp/tools/memory-session.js.map +1 -0
- package/dist/mcp/tools/memory-stats.d.ts +10 -0
- package/dist/mcp/tools/memory-stats.d.ts.map +1 -0
- package/dist/mcp/tools/memory-stats.js +35 -0
- package/dist/mcp/tools/memory-stats.js.map +1 -0
- package/dist/mcp/tools/memory-store-batch.d.ts +49 -0
- package/dist/mcp/tools/memory-store-batch.d.ts.map +1 -0
- package/dist/mcp/tools/memory-store-batch.js +48 -0
- package/dist/mcp/tools/memory-store-batch.js.map +1 -0
- package/dist/mcp/tools/memory-store.d.ts +37 -0
- package/dist/mcp/tools/memory-store.d.ts.map +1 -0
- package/dist/mcp/tools/memory-store.js +34 -0
- package/dist/mcp/tools/memory-store.js.map +1 -0
- package/dist/mcp/tools/project-create.d.ts +16 -0
- package/dist/mcp/tools/project-create.d.ts.map +1 -0
- package/dist/mcp/tools/project-create.js +14 -0
- package/dist/mcp/tools/project-create.js.map +1 -0
- package/dist/models/types.d.ts +88 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +19 -0
- package/dist/models/types.js.map +1 -0
- package/dist/schema/compression.d.ts +7 -0
- package/dist/schema/compression.d.ts.map +1 -0
- package/dist/schema/compression.js +66 -0
- package/dist/schema/compression.js.map +1 -0
- package/dist/schema/fulltext-search.d.ts +10 -0
- package/dist/schema/fulltext-search.d.ts.map +1 -0
- package/dist/schema/fulltext-search.js +73 -0
- package/dist/schema/fulltext-search.js.map +1 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/knowledge-graph.d.ts +108 -0
- package/dist/schema/knowledge-graph.d.ts.map +1 -0
- package/dist/schema/knowledge-graph.js +372 -0
- package/dist/schema/knowledge-graph.js.map +1 -0
- package/dist/schema/memory-session.d.ts +62 -0
- package/dist/schema/memory-session.d.ts.map +1 -0
- package/dist/schema/memory-session.js +258 -0
- package/dist/schema/memory-session.js.map +1 -0
- package/dist/schema/memory.d.ts +84 -0
- package/dist/schema/memory.d.ts.map +1 -0
- package/dist/schema/memory.js +622 -0
- package/dist/schema/memory.js.map +1 -0
- package/dist/schema/project.d.ts +8 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/project.js +68 -0
- package/dist/schema/project.js.map +1 -0
- package/dist/schema/schema-init.d.ts +2 -0
- package/dist/schema/schema-init.d.ts.map +1 -0
- package/dist/schema/schema-init.js +199 -0
- package/dist/schema/schema-init.js.map +1 -0
- package/dist/schema/vector-index.d.ts +28 -0
- package/dist/schema/vector-index.d.ts.map +1 -0
- package/dist/schema/vector-index.js +179 -0
- package/dist/schema/vector-index.js.map +1 -0
- package/dist/scripts/agents/base.d.ts +89 -0
- package/dist/scripts/agents/base.d.ts.map +1 -0
- package/dist/scripts/agents/base.js +148 -0
- package/dist/scripts/agents/base.js.map +1 -0
- package/dist/scripts/agents/base.ts +193 -0
- package/dist/scripts/agents/claude-code.d.ts +21 -0
- package/dist/scripts/agents/claude-code.d.ts.map +1 -0
- package/dist/scripts/agents/claude-code.js +33 -0
- package/dist/scripts/agents/claude-code.js.map +1 -0
- package/dist/scripts/agents/claude-code.ts +36 -0
- package/dist/scripts/agents/claude-desktop.d.ts +25 -0
- package/dist/scripts/agents/claude-desktop.d.ts.map +1 -0
- package/dist/scripts/agents/claude-desktop.js +36 -0
- package/dist/scripts/agents/claude-desktop.js.map +1 -0
- package/dist/scripts/agents/claude-desktop.ts +39 -0
- package/dist/scripts/agents/cline.d.ts +22 -0
- package/dist/scripts/agents/cline.d.ts.map +1 -0
- package/dist/scripts/agents/cline.js +35 -0
- package/dist/scripts/agents/cline.js.map +1 -0
- package/dist/scripts/agents/cline.ts +38 -0
- package/dist/scripts/agents/continue.d.ts +20 -0
- package/dist/scripts/agents/continue.d.ts.map +1 -0
- package/dist/scripts/agents/continue.js +35 -0
- package/dist/scripts/agents/continue.js.map +1 -0
- package/dist/scripts/agents/continue.ts +38 -0
- package/dist/scripts/agents/cursor.d.ts +27 -0
- package/dist/scripts/agents/cursor.d.ts.map +1 -0
- package/dist/scripts/agents/cursor.js +38 -0
- package/dist/scripts/agents/cursor.js.map +1 -0
- package/dist/scripts/agents/cursor.ts +41 -0
- package/dist/scripts/cli/config-gen.d.ts +59 -0
- package/dist/scripts/cli/config-gen.d.ts.map +1 -0
- package/dist/scripts/cli/config-gen.js +156 -0
- package/dist/scripts/cli/config-gen.js.map +1 -0
- package/dist/scripts/cli/config-gen.ts +164 -0
- package/dist/scripts/cli/detect.d.ts +42 -0
- package/dist/scripts/cli/detect.d.ts.map +1 -0
- package/dist/scripts/cli/detect.js +131 -0
- package/dist/scripts/cli/detect.js.map +1 -0
- package/dist/scripts/cli/detect.ts +162 -0
- package/dist/scripts/cli/install.d.ts +31 -0
- package/dist/scripts/cli/install.d.ts.map +1 -0
- package/dist/scripts/cli/install.js +125 -0
- package/dist/scripts/cli/install.js.map +1 -0
- package/dist/scripts/cli/install.ts +157 -0
- package/dist/scripts/cli/status.d.ts +8 -0
- package/dist/scripts/cli/status.d.ts.map +1 -0
- package/dist/scripts/cli/status.js +39 -0
- package/dist/scripts/cli/status.js.map +1 -0
- package/dist/scripts/cli/status.ts +48 -0
- package/dist/scripts/cli/uninstall.d.ts +22 -0
- package/dist/scripts/cli/uninstall.d.ts.map +1 -0
- package/dist/scripts/cli/uninstall.js +141 -0
- package/dist/scripts/cli/uninstall.js.map +1 -0
- package/dist/scripts/cli/uninstall.ts +157 -0
- package/dist/scripts/cli.d.ts +23 -0
- package/dist/scripts/cli.d.ts.map +1 -0
- package/dist/scripts/cli.js +166 -0
- package/dist/scripts/cli.js.map +1 -0
- package/dist/scripts/cli.ts +173 -0
- package/dist/services/classifier/index.d.ts +36 -0
- package/dist/services/classifier/index.d.ts.map +1 -0
- package/dist/services/classifier/index.js +104 -0
- package/dist/services/classifier/index.js.map +1 -0
- package/dist/services/classifier/llm.d.ts +37 -0
- package/dist/services/classifier/llm.d.ts.map +1 -0
- package/dist/services/classifier/llm.js +119 -0
- package/dist/services/classifier/llm.js.map +1 -0
- package/dist/services/classifier/rules.d.ts +22 -0
- package/dist/services/classifier/rules.d.ts.map +1 -0
- package/dist/services/classifier/rules.js +98 -0
- package/dist/services/classifier/rules.js.map +1 -0
- package/dist/services/compressor/index.d.ts +3 -0
- package/dist/services/compressor/index.d.ts.map +1 -0
- package/dist/services/compressor/index.js +3 -0
- package/dist/services/compressor/index.js.map +1 -0
- package/dist/services/compressor/threshold.d.ts +35 -0
- package/dist/services/compressor/threshold.d.ts.map +1 -0
- package/dist/services/compressor/threshold.js +60 -0
- package/dist/services/compressor/threshold.js.map +1 -0
- package/dist/services/compressor/trigger.d.ts +24 -0
- package/dist/services/compressor/trigger.d.ts.map +1 -0
- package/dist/services/compressor/trigger.js +91 -0
- package/dist/services/compressor/trigger.js.map +1 -0
- package/dist/services/constraint-extractor.d.ts +25 -0
- package/dist/services/constraint-extractor.d.ts.map +1 -0
- package/dist/services/constraint-extractor.js +97 -0
- package/dist/services/constraint-extractor.js.map +1 -0
- package/dist/services/database-health.d.ts +22 -0
- package/dist/services/database-health.d.ts.map +1 -0
- package/dist/services/database-health.js +122 -0
- package/dist/services/database-health.js.map +1 -0
- package/dist/services/embedding-fixed.d.ts +9 -0
- package/dist/services/embedding-fixed.d.ts.map +1 -0
- package/dist/services/embedding-fixed.js +70 -0
- package/dist/services/embedding-fixed.js.map +1 -0
- package/dist/services/embedding-provider.d.ts +79 -0
- package/dist/services/embedding-provider.d.ts.map +1 -0
- package/dist/services/embedding-provider.js +229 -0
- package/dist/services/embedding-provider.js.map +1 -0
- package/dist/services/embedding.d.ts +17 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +99 -0
- package/dist/services/embedding.js.map +1 -0
- package/dist/services/hnsw-index.d.ts +76 -0
- package/dist/services/hnsw-index.d.ts.map +1 -0
- package/dist/services/hnsw-index.js +301 -0
- package/dist/services/hnsw-index.js.map +1 -0
- package/dist/services/llm.d.ts +39 -0
- package/dist/services/llm.d.ts.map +1 -0
- package/dist/services/llm.js +207 -0
- package/dist/services/llm.js.map +1 -0
- package/dist/services/memory-tiers.d.ts +75 -0
- package/dist/services/memory-tiers.d.ts.map +1 -0
- package/dist/services/memory-tiers.js +275 -0
- package/dist/services/memory-tiers.js.map +1 -0
- package/dist/services/memory.d.ts +33 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +209 -0
- package/dist/services/memory.js.map +1 -0
- package/dist/services/multi-agent-sharing.d.ts +83 -0
- package/dist/services/multi-agent-sharing.d.ts.map +1 -0
- package/dist/services/multi-agent-sharing.js +278 -0
- package/dist/services/multi-agent-sharing.js.map +1 -0
- package/dist/services/reranker.d.ts +88 -0
- package/dist/services/reranker.d.ts.map +1 -0
- package/dist/services/reranker.js +234 -0
- package/dist/services/reranker.js.map +1 -0
- package/dist/services/triple-extractor.d.ts +35 -0
- package/dist/services/triple-extractor.d.ts.map +1 -0
- package/dist/services/triple-extractor.js +293 -0
- package/dist/services/triple-extractor.js.map +1 -0
- package/dist/services/vector-provider.d.ts +40 -0
- package/dist/services/vector-provider.d.ts.map +1 -0
- package/dist/services/vector-provider.js +225 -0
- package/dist/services/vector-provider.js.map +1 -0
- package/dist/utils/after-chain-executor.d.ts +26 -0
- package/dist/utils/after-chain-executor.d.ts.map +1 -0
- package/dist/utils/after-chain-executor.js +135 -0
- package/dist/utils/after-chain-executor.js.map +1 -0
- package/dist/utils/after-chain.d.ts +94 -0
- package/dist/utils/after-chain.d.ts.map +1 -0
- package/dist/utils/after-chain.js +155 -0
- package/dist/utils/after-chain.js.map +1 -0
- package/dist/utils/db.d.ts +29 -0
- package/dist/utils/db.d.ts.map +1 -0
- package/dist/utils/db.js +201 -0
- package/dist/utils/db.js.map +1 -0
- package/dist/utils/encryption.d.ts +87 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +175 -0
- package/dist/utils/encryption.js.map +1 -0
- package/dist/utils/errors.d.ts +35 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +27 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +177 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/priority.d.ts +26 -0
- package/dist/utils/priority.d.ts.map +1 -0
- package/dist/utils/priority.js +43 -0
- package/dist/utils/priority.js.map +1 -0
- package/dist/utils/watchdog.d.ts +57 -0
- package/dist/utils/watchdog.d.ts.map +1 -0
- package/dist/utils/watchdog.js +164 -0
- package/dist/utils/watchdog.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent config-file path detection.
|
|
3
|
+
*
|
|
4
|
+
* Each supported MCP agent has its own global config path (and most also
|
|
5
|
+
* support a project-local override). This module maps agent names to the
|
|
6
|
+
* absolute path of the config file the agent will actually read, taking
|
|
7
|
+
* the current platform into account.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
function appDataWindows() {
|
|
13
|
+
return process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Path specs for each agent's *global* config file.
|
|
17
|
+
* Project-level paths (workspace-local) are resolved by the caller.
|
|
18
|
+
*/
|
|
19
|
+
const GLOBAL_PATHS = {
|
|
20
|
+
cursor: {
|
|
21
|
+
win: () => join(homedir(), '.cursor', 'mcp.json'),
|
|
22
|
+
posix: () => join(homedir(), '.cursor', 'mcp.json'),
|
|
23
|
+
},
|
|
24
|
+
'claude-desktop': {
|
|
25
|
+
win: () => join(appDataWindows(), 'Claude', 'claude_desktop_config.json'),
|
|
26
|
+
posix: () => join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
|
|
27
|
+
},
|
|
28
|
+
cline: {
|
|
29
|
+
win: () => join(homedir(), '.vscode', 'mcp.json'),
|
|
30
|
+
posix: () => join(homedir(), '.vscode', 'mcp.json'),
|
|
31
|
+
},
|
|
32
|
+
continue: {
|
|
33
|
+
win: () => join(appDataWindows(), 'continue', 'config.json'),
|
|
34
|
+
posix: () => join(homedir(), '.continue', 'config.json'),
|
|
35
|
+
},
|
|
36
|
+
'claude-code': {
|
|
37
|
+
win: () => join(homedir(), '.mcp.json'),
|
|
38
|
+
posix: () => join(homedir(), '.mcp.json'),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
function pickGlobalPath(agent) {
|
|
42
|
+
const spec = GLOBAL_PATHS[agent];
|
|
43
|
+
return process.platform === 'win32' ? spec.win() : spec.posix();
|
|
44
|
+
}
|
|
45
|
+
function pickProjectPath(agent, cwd) {
|
|
46
|
+
// Claude Desktop is global-only.
|
|
47
|
+
if (agent === 'claude-desktop')
|
|
48
|
+
return null;
|
|
49
|
+
switch (agent) {
|
|
50
|
+
case 'cursor':
|
|
51
|
+
return join(cwd, '.cursor', 'mcp.json');
|
|
52
|
+
case 'cline':
|
|
53
|
+
return join(cwd, '.vscode', 'mcp.json');
|
|
54
|
+
case 'continue':
|
|
55
|
+
return join(cwd, '.continue', 'config.json');
|
|
56
|
+
case 'claude-code':
|
|
57
|
+
return join(cwd, '.mcp.json');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export const SUPPORTED_AGENTS = [
|
|
61
|
+
'cursor',
|
|
62
|
+
'claude-desktop',
|
|
63
|
+
'cline',
|
|
64
|
+
'continue',
|
|
65
|
+
'claude-code',
|
|
66
|
+
];
|
|
67
|
+
export function isValidAgent(name) {
|
|
68
|
+
return SUPPORTED_AGENTS.includes(name);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Detect where the agent's config file lives. Prefers project-level when
|
|
72
|
+
* `scope === 'project'`, otherwise global. If `scope` is omitted, returns
|
|
73
|
+
* the path that exists (or would be written), preferring project-level.
|
|
74
|
+
*/
|
|
75
|
+
export function detectAgent(agent, options = {}) {
|
|
76
|
+
const cwd = options.cwd ?? process.cwd();
|
|
77
|
+
const scope = options.scope ?? 'global';
|
|
78
|
+
const configPath = scope === 'project'
|
|
79
|
+
? pickProjectPath(agent, cwd) ?? pickGlobalPath(agent)
|
|
80
|
+
: pickGlobalPath(agent);
|
|
81
|
+
const exists = existsSync(configPath);
|
|
82
|
+
const installed = exists && configContainsGatekeeper(configPath);
|
|
83
|
+
return { agent, scope, configPath, exists, installed };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Probe all supported agents for their detection state.
|
|
87
|
+
*/
|
|
88
|
+
export function detectAll(options = {}) {
|
|
89
|
+
return SUPPORTED_AGENTS.map((a) => detectAgent(a, options));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Filter a list of detections to those whose config file currently exists.
|
|
93
|
+
* Used by `--all` to skip agents that are not installed on this machine.
|
|
94
|
+
*/
|
|
95
|
+
export function filterDetected(detections) {
|
|
96
|
+
return detections.filter((d) => d.exists);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Cheap heuristic check: read the file and see if "context-gatekeeper"
|
|
100
|
+
* appears as a key in the `mcpServers` map or as an entry in
|
|
101
|
+
* `experimental.modelContextProtocolServers`.
|
|
102
|
+
*/
|
|
103
|
+
function configContainsGatekeeper(filePath) {
|
|
104
|
+
let raw;
|
|
105
|
+
try {
|
|
106
|
+
raw = readFileSync(filePath, 'utf8');
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(raw);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!parsed || typeof parsed !== 'object')
|
|
119
|
+
return false;
|
|
120
|
+
const obj = parsed;
|
|
121
|
+
const servers = obj.mcpServers;
|
|
122
|
+
if (servers && typeof servers === 'object' && 'context-gatekeeper' in servers) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const exp = obj.experimental;
|
|
126
|
+
if (exp?.modelContextProtocolServers?.some((s) => s.name === 'context-gatekeeper')) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["detect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent config-file path detection.
|
|
3
|
+
*
|
|
4
|
+
* Each supported MCP agent has its own global config path (and most also
|
|
5
|
+
* support a project-local override). This module maps agent names to the
|
|
6
|
+
* absolute path of the config file the agent will actually read, taking
|
|
7
|
+
* the current platform into account.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
|
|
14
|
+
export type AgentName = 'cursor' | 'claude-desktop' | 'cline' | 'continue' | 'claude-code';
|
|
15
|
+
|
|
16
|
+
export type Scope = 'global' | 'project';
|
|
17
|
+
|
|
18
|
+
export interface AgentDetection {
|
|
19
|
+
agent: AgentName;
|
|
20
|
+
scope: Scope;
|
|
21
|
+
configPath: string;
|
|
22
|
+
exists: boolean;
|
|
23
|
+
/** True if the config file already references context-gatekeeper. */
|
|
24
|
+
installed: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PathSpec {
|
|
28
|
+
win: () => string;
|
|
29
|
+
posix: () => string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function appDataWindows(): string {
|
|
33
|
+
return process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Path specs for each agent's *global* config file.
|
|
38
|
+
* Project-level paths (workspace-local) are resolved by the caller.
|
|
39
|
+
*/
|
|
40
|
+
const GLOBAL_PATHS: Record<AgentName, PathSpec> = {
|
|
41
|
+
cursor: {
|
|
42
|
+
win: () => join(homedir(), '.cursor', 'mcp.json'),
|
|
43
|
+
posix: () => join(homedir(), '.cursor', 'mcp.json'),
|
|
44
|
+
},
|
|
45
|
+
'claude-desktop': {
|
|
46
|
+
win: () => join(appDataWindows(), 'Claude', 'claude_desktop_config.json'),
|
|
47
|
+
posix: () => join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
|
|
48
|
+
},
|
|
49
|
+
cline: {
|
|
50
|
+
win: () => join(homedir(), '.vscode', 'mcp.json'),
|
|
51
|
+
posix: () => join(homedir(), '.vscode', 'mcp.json'),
|
|
52
|
+
},
|
|
53
|
+
continue: {
|
|
54
|
+
win: () => join(appDataWindows(), 'continue', 'config.json'),
|
|
55
|
+
posix: () => join(homedir(), '.continue', 'config.json'),
|
|
56
|
+
},
|
|
57
|
+
'claude-code': {
|
|
58
|
+
win: () => join(homedir(), '.mcp.json'),
|
|
59
|
+
posix: () => join(homedir(), '.mcp.json'),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function pickGlobalPath(agent: AgentName): string {
|
|
64
|
+
const spec = GLOBAL_PATHS[agent];
|
|
65
|
+
return process.platform === 'win32' ? spec.win() : spec.posix();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pickProjectPath(agent: AgentName, cwd: string): string | null {
|
|
69
|
+
// Claude Desktop is global-only.
|
|
70
|
+
if (agent === 'claude-desktop') return null;
|
|
71
|
+
switch (agent) {
|
|
72
|
+
case 'cursor':
|
|
73
|
+
return join(cwd, '.cursor', 'mcp.json');
|
|
74
|
+
case 'cline':
|
|
75
|
+
return join(cwd, '.vscode', 'mcp.json');
|
|
76
|
+
case 'continue':
|
|
77
|
+
return join(cwd, '.continue', 'config.json');
|
|
78
|
+
case 'claude-code':
|
|
79
|
+
return join(cwd, '.mcp.json');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const SUPPORTED_AGENTS: AgentName[] = [
|
|
84
|
+
'cursor',
|
|
85
|
+
'claude-desktop',
|
|
86
|
+
'cline',
|
|
87
|
+
'continue',
|
|
88
|
+
'claude-code',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export function isValidAgent(name: string): name is AgentName {
|
|
92
|
+
return (SUPPORTED_AGENTS as string[]).includes(name);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Detect where the agent's config file lives. Prefers project-level when
|
|
97
|
+
* `scope === 'project'`, otherwise global. If `scope` is omitted, returns
|
|
98
|
+
* the path that exists (or would be written), preferring project-level.
|
|
99
|
+
*/
|
|
100
|
+
export function detectAgent(
|
|
101
|
+
agent: AgentName,
|
|
102
|
+
options: { scope?: Scope; cwd?: string } = {},
|
|
103
|
+
): AgentDetection {
|
|
104
|
+
const cwd = options.cwd ?? process.cwd();
|
|
105
|
+
const scope: Scope = options.scope ?? 'global';
|
|
106
|
+
|
|
107
|
+
const configPath =
|
|
108
|
+
scope === 'project'
|
|
109
|
+
? pickProjectPath(agent, cwd) ?? pickGlobalPath(agent)
|
|
110
|
+
: pickGlobalPath(agent);
|
|
111
|
+
|
|
112
|
+
const exists = existsSync(configPath);
|
|
113
|
+
const installed = exists && configContainsGatekeeper(configPath);
|
|
114
|
+
|
|
115
|
+
return { agent, scope, configPath, exists, installed };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Probe all supported agents for their detection state.
|
|
120
|
+
*/
|
|
121
|
+
export function detectAll(options: { scope?: Scope; cwd?: string } = {}): AgentDetection[] {
|
|
122
|
+
return SUPPORTED_AGENTS.map((a) => detectAgent(a, options));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Filter a list of detections to those whose config file currently exists.
|
|
127
|
+
* Used by `--all` to skip agents that are not installed on this machine.
|
|
128
|
+
*/
|
|
129
|
+
export function filterDetected(detections: AgentDetection[]): AgentDetection[] {
|
|
130
|
+
return detections.filter((d) => d.exists);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cheap heuristic check: read the file and see if "context-gatekeeper"
|
|
135
|
+
* appears as a key in the `mcpServers` map or as an entry in
|
|
136
|
+
* `experimental.modelContextProtocolServers`.
|
|
137
|
+
*/
|
|
138
|
+
function configContainsGatekeeper(filePath: string): boolean {
|
|
139
|
+
let raw: string;
|
|
140
|
+
try {
|
|
141
|
+
raw = readFileSync(filePath, 'utf8');
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
let parsed: unknown;
|
|
146
|
+
try {
|
|
147
|
+
parsed = JSON.parse(raw);
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (!parsed || typeof parsed !== 'object') return false;
|
|
152
|
+
const obj = parsed as Record<string, unknown>;
|
|
153
|
+
const servers = obj.mcpServers as Record<string, unknown> | undefined;
|
|
154
|
+
if (servers && typeof servers === 'object' && 'context-gatekeeper' in servers) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
const exp = obj.experimental as { modelContextProtocolServers?: Array<{ name?: string }> } | undefined;
|
|
158
|
+
if (exp?.modelContextProtocolServers?.some((s) => s.name === 'context-gatekeeper')) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install (merge) the context-gatekeeper MCP entry into an agent's config
|
|
3
|
+
* file. Reads the existing JSON (if any), merges in the new entry under
|
|
4
|
+
* the right location, and atomically writes the result back.
|
|
5
|
+
*/
|
|
6
|
+
import { AgentName, Scope } from './detect.ts';
|
|
7
|
+
export interface InstallResult {
|
|
8
|
+
agent: AgentName;
|
|
9
|
+
configPath: string;
|
|
10
|
+
scope: Scope;
|
|
11
|
+
status: 'installed' | 'updated' | 'skipped' | 'error';
|
|
12
|
+
message?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Install context-gatekeeper into a single agent's config file.
|
|
16
|
+
* Returns a status record the caller can present to the user.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installAgent(agent: AgentName, options?: {
|
|
19
|
+
scope?: Scope;
|
|
20
|
+
cwd?: string;
|
|
21
|
+
force?: boolean;
|
|
22
|
+
}): InstallResult;
|
|
23
|
+
/**
|
|
24
|
+
* Install across multiple agents. If `agents` is empty, installs into all
|
|
25
|
+
* agents whose config file already exists on disk.
|
|
26
|
+
*/
|
|
27
|
+
export declare function installAgents(agents: AgentName[], options?: {
|
|
28
|
+
scope?: Scope;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
}): InstallResult[];
|
|
31
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["install.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,SAAS,EAAe,KAAK,EAAoC,MAAM,aAAa,CAAC;AAG9F,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmFD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7D,aAAa,CAiCf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,aAAa,EAAE,CAMjB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install (merge) the context-gatekeeper MCP entry into an agent's config
|
|
3
|
+
* file. Reads the existing JSON (if any), merges in the new entry under
|
|
4
|
+
* the right location, and atomically writes the result back.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
import { detectAgent, SUPPORTED_AGENTS } from './detect.ts';
|
|
9
|
+
import { buildConfigBlock, GATEKEEPER_KEY } from './config-gen.ts';
|
|
10
|
+
function readJsonOrEmpty(filePath) {
|
|
11
|
+
if (!existsSync(filePath))
|
|
12
|
+
return {};
|
|
13
|
+
try {
|
|
14
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
15
|
+
const trimmed = raw.trim();
|
|
16
|
+
if (trimmed.length === 0)
|
|
17
|
+
return {};
|
|
18
|
+
const parsed = JSON.parse(trimmed);
|
|
19
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
20
|
+
throw new Error(`Expected JSON object at ${filePath}, got ${typeof parsed}`);
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
throw new Error(`Failed to parse ${filePath}: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function atomicWriteJson(filePath, value) {
|
|
29
|
+
const dir = dirname(filePath);
|
|
30
|
+
if (!existsSync(dir))
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
33
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
34
|
+
renameSync(tmp, filePath);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* For the four agents that use the `mcpServers` shape, merge our entry
|
|
38
|
+
* under the `context-gatekeeper` key without touching other entries.
|
|
39
|
+
*/
|
|
40
|
+
function mergeMcpServersShape(existing, block) {
|
|
41
|
+
const existingServers = existing.mcpServers ?? {};
|
|
42
|
+
const newServers = block.mcpServers ?? {};
|
|
43
|
+
const hadExisting = GATEKEEPER_KEY in existingServers;
|
|
44
|
+
const mergedServers = { ...existingServers, ...newServers };
|
|
45
|
+
return {
|
|
46
|
+
merged: { ...existing, mcpServers: mergedServers },
|
|
47
|
+
hadExisting,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Continue uses an array under `experimental.modelContextProtocolServers`,
|
|
52
|
+
* identified by each entry's `name` field.
|
|
53
|
+
*/
|
|
54
|
+
function mergeContinueShape(existing, block) {
|
|
55
|
+
const exp = existing.experimental ?? {};
|
|
56
|
+
const arr = exp.modelContextProtocolServers ?? [];
|
|
57
|
+
const newArr = block.experimental?.modelContextProtocolServers ?? [];
|
|
58
|
+
const newEntry = newArr[0];
|
|
59
|
+
const hadExisting = arr.some((e) => e.name === newEntry?.name);
|
|
60
|
+
const filtered = arr.filter((e) => e.name !== newEntry?.name);
|
|
61
|
+
filtered.push(newEntry);
|
|
62
|
+
return {
|
|
63
|
+
merged: {
|
|
64
|
+
...existing,
|
|
65
|
+
experimental: { ...exp, modelContextProtocolServers: filtered },
|
|
66
|
+
},
|
|
67
|
+
hadExisting,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function mergeIntoExisting(agent, existing) {
|
|
71
|
+
const block = buildConfigBlock(agent);
|
|
72
|
+
if (agent === 'continue')
|
|
73
|
+
return mergeContinueShape(existing, block);
|
|
74
|
+
return mergeMcpServersShape(existing, block);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Install context-gatekeeper into a single agent's config file.
|
|
78
|
+
* Returns a status record the caller can present to the user.
|
|
79
|
+
*/
|
|
80
|
+
export function installAgent(agent, options = {}) {
|
|
81
|
+
// Claude Desktop has no project-level config; skip with a clear message
|
|
82
|
+
// instead of silently falling back to the global path (which would
|
|
83
|
+
// surprise the user when they thought they were writing to the project).
|
|
84
|
+
if (options.scope === 'project' && agent === 'claude-desktop') {
|
|
85
|
+
return {
|
|
86
|
+
agent,
|
|
87
|
+
configPath: '(project scope not supported)',
|
|
88
|
+
scope: 'global',
|
|
89
|
+
status: 'skipped',
|
|
90
|
+
message: 'Claude Desktop is global-only; omit --local to install',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const detection = detectAgent(agent, options);
|
|
94
|
+
try {
|
|
95
|
+
const existing = readJsonOrEmpty(detection.configPath);
|
|
96
|
+
const { merged, hadExisting } = mergeIntoExisting(agent, existing);
|
|
97
|
+
atomicWriteJson(detection.configPath, merged);
|
|
98
|
+
return {
|
|
99
|
+
agent,
|
|
100
|
+
configPath: detection.configPath,
|
|
101
|
+
scope: detection.scope,
|
|
102
|
+
status: hadExisting ? 'updated' : 'installed',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return {
|
|
107
|
+
agent,
|
|
108
|
+
configPath: detection.configPath,
|
|
109
|
+
scope: detection.scope,
|
|
110
|
+
status: 'error',
|
|
111
|
+
message: err.message,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Install across multiple agents. If `agents` is empty, installs into all
|
|
117
|
+
* agents whose config file already exists on disk.
|
|
118
|
+
*/
|
|
119
|
+
export function installAgents(agents, options = {}) {
|
|
120
|
+
const targets = agents.length > 0
|
|
121
|
+
? agents.map((a) => detectAgent(a, options))
|
|
122
|
+
: SUPPORTED_AGENTS.map((a) => detectAgent(a, options)).filter((d) => d.exists);
|
|
123
|
+
return targets.map((d) => installAgent(d.agent, options));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install (merge) the context-gatekeeper MCP entry into an agent's config
|
|
3
|
+
* file. Reads the existing JSON (if any), merges in the new entry under
|
|
4
|
+
* the right location, and atomically writes the result back.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import { AgentName, detectAgent, Scope, AgentDetection, SUPPORTED_AGENTS } from './detect.ts';
|
|
11
|
+
import { buildConfigBlock, GATEKEEPER_KEY } from './config-gen.ts';
|
|
12
|
+
|
|
13
|
+
export interface InstallResult {
|
|
14
|
+
agent: AgentName;
|
|
15
|
+
configPath: string;
|
|
16
|
+
scope: Scope;
|
|
17
|
+
status: 'installed' | 'updated' | 'skipped' | 'error';
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readJsonOrEmpty(filePath: string): Record<string, unknown> {
|
|
22
|
+
if (!existsSync(filePath)) return {};
|
|
23
|
+
try {
|
|
24
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
25
|
+
const trimmed = raw.trim();
|
|
26
|
+
if (trimmed.length === 0) return {};
|
|
27
|
+
const parsed = JSON.parse(trimmed);
|
|
28
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
29
|
+
throw new Error(`Expected JSON object at ${filePath}, got ${typeof parsed}`);
|
|
30
|
+
}
|
|
31
|
+
return parsed as Record<string, unknown>;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(`Failed to parse ${filePath}: ${(err as Error).message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function atomicWriteJson(filePath: string, value: unknown): void {
|
|
38
|
+
const dir = dirname(filePath);
|
|
39
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
40
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
41
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
42
|
+
renameSync(tmp, filePath);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* For the four agents that use the `mcpServers` shape, merge our entry
|
|
47
|
+
* under the `context-gatekeeper` key without touching other entries.
|
|
48
|
+
*/
|
|
49
|
+
function mergeMcpServersShape(
|
|
50
|
+
existing: Record<string, unknown>,
|
|
51
|
+
block: Record<string, unknown>,
|
|
52
|
+
): { merged: Record<string, unknown>; hadExisting: boolean } {
|
|
53
|
+
const existingServers =
|
|
54
|
+
(existing.mcpServers as Record<string, unknown> | undefined) ?? {};
|
|
55
|
+
const newServers =
|
|
56
|
+
(block.mcpServers as Record<string, unknown> | undefined) ?? {};
|
|
57
|
+
const hadExisting = GATEKEEPER_KEY in existingServers;
|
|
58
|
+
const mergedServers = { ...existingServers, ...newServers };
|
|
59
|
+
return {
|
|
60
|
+
merged: { ...existing, mcpServers: mergedServers },
|
|
61
|
+
hadExisting,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Continue uses an array under `experimental.modelContextProtocolServers`,
|
|
67
|
+
* identified by each entry's `name` field.
|
|
68
|
+
*/
|
|
69
|
+
function mergeContinueShape(
|
|
70
|
+
existing: Record<string, unknown>,
|
|
71
|
+
block: Record<string, unknown>,
|
|
72
|
+
): { merged: Record<string, unknown>; hadExisting: boolean } {
|
|
73
|
+
const exp = (existing.experimental as Record<string, unknown> | undefined) ?? {};
|
|
74
|
+
const arr =
|
|
75
|
+
(exp.modelContextProtocolServers as Array<Record<string, unknown>> | undefined) ?? [];
|
|
76
|
+
const newArr =
|
|
77
|
+
((block.experimental as Record<string, unknown> | undefined)?.modelContextProtocolServers as
|
|
78
|
+
| Array<Record<string, unknown>>
|
|
79
|
+
| undefined) ?? [];
|
|
80
|
+
const newEntry = newArr[0];
|
|
81
|
+
const hadExisting = arr.some((e) => e.name === newEntry?.name);
|
|
82
|
+
const filtered = arr.filter((e) => e.name !== newEntry?.name);
|
|
83
|
+
filtered.push(newEntry);
|
|
84
|
+
return {
|
|
85
|
+
merged: {
|
|
86
|
+
...existing,
|
|
87
|
+
experimental: { ...exp, modelContextProtocolServers: filtered },
|
|
88
|
+
},
|
|
89
|
+
hadExisting,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function mergeIntoExisting(
|
|
94
|
+
agent: AgentName,
|
|
95
|
+
existing: Record<string, unknown>,
|
|
96
|
+
): { merged: Record<string, unknown>; hadExisting: boolean } {
|
|
97
|
+
const block = buildConfigBlock(agent) as Record<string, unknown>;
|
|
98
|
+
if (agent === 'continue') return mergeContinueShape(existing, block);
|
|
99
|
+
return mergeMcpServersShape(existing, block);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Install context-gatekeeper into a single agent's config file.
|
|
104
|
+
* Returns a status record the caller can present to the user.
|
|
105
|
+
*/
|
|
106
|
+
export function installAgent(
|
|
107
|
+
agent: AgentName,
|
|
108
|
+
options: { scope?: Scope; cwd?: string; force?: boolean } = {},
|
|
109
|
+
): InstallResult {
|
|
110
|
+
// Claude Desktop has no project-level config; skip with a clear message
|
|
111
|
+
// instead of silently falling back to the global path (which would
|
|
112
|
+
// surprise the user when they thought they were writing to the project).
|
|
113
|
+
if (options.scope === 'project' && agent === 'claude-desktop') {
|
|
114
|
+
return {
|
|
115
|
+
agent,
|
|
116
|
+
configPath: '(project scope not supported)',
|
|
117
|
+
scope: 'global',
|
|
118
|
+
status: 'skipped',
|
|
119
|
+
message: 'Claude Desktop is global-only; omit --local to install',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const detection = detectAgent(agent, options);
|
|
123
|
+
try {
|
|
124
|
+
const existing = readJsonOrEmpty(detection.configPath);
|
|
125
|
+
const { merged, hadExisting } = mergeIntoExisting(agent, existing);
|
|
126
|
+
atomicWriteJson(detection.configPath, merged);
|
|
127
|
+
return {
|
|
128
|
+
agent,
|
|
129
|
+
configPath: detection.configPath,
|
|
130
|
+
scope: detection.scope,
|
|
131
|
+
status: hadExisting ? 'updated' : 'installed',
|
|
132
|
+
};
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return {
|
|
135
|
+
agent,
|
|
136
|
+
configPath: detection.configPath,
|
|
137
|
+
scope: detection.scope,
|
|
138
|
+
status: 'error',
|
|
139
|
+
message: (err as Error).message,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Install across multiple agents. If `agents` is empty, installs into all
|
|
146
|
+
* agents whose config file already exists on disk.
|
|
147
|
+
*/
|
|
148
|
+
export function installAgents(
|
|
149
|
+
agents: AgentName[],
|
|
150
|
+
options: { scope?: Scope; cwd?: string } = {},
|
|
151
|
+
): InstallResult[] {
|
|
152
|
+
const targets: AgentDetection[] =
|
|
153
|
+
agents.length > 0
|
|
154
|
+
? agents.map((a) => detectAgent(a, options))
|
|
155
|
+
: SUPPORTED_AGENTS.map((a) => detectAgent(a, options)).filter((d) => d.exists);
|
|
156
|
+
return targets.map((d) => installAgent(d.agent, options));
|
|
157
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print a status table showing which supported agents have a config
|
|
3
|
+
* file on disk and whether context-gatekeeper is already wired into it.
|
|
4
|
+
*/
|
|
5
|
+
import { AgentDetection } from './detect.ts';
|
|
6
|
+
export declare function renderStatusTable(detections: AgentDetection[]): string;
|
|
7
|
+
export declare function printStatus(detections?: AgentDetection[]): void;
|
|
8
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAa,cAAc,EAA+B,MAAM,aAAa,CAAC;AAIrF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,CAqBtE;AAOD,wBAAgB,WAAW,CAAC,UAAU,GAAE,cAAc,EAAgB,GAAG,IAAI,CAU5E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print a status table showing which supported agents have a config
|
|
3
|
+
* file on disk and whether context-gatekeeper is already wired into it.
|
|
4
|
+
*/
|
|
5
|
+
import { detectAll, SUPPORTED_AGENTS } from './detect.ts';
|
|
6
|
+
const PAD = (s, n) => String(s).padEnd(n);
|
|
7
|
+
export function renderStatusTable(detections) {
|
|
8
|
+
const headers = ['Agent', 'Scope', 'Config Path', 'Status'];
|
|
9
|
+
const rows = detections.map((d) => [
|
|
10
|
+
d.agent,
|
|
11
|
+
d.scope,
|
|
12
|
+
d.configPath,
|
|
13
|
+
describe(d),
|
|
14
|
+
]);
|
|
15
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => String(r[i]).length)));
|
|
16
|
+
const sep = widths.map((w) => '-'.repeat(w)).join(' ');
|
|
17
|
+
const out = [];
|
|
18
|
+
out.push(headers.map((h, i) => PAD(h, widths[i])).join(' '));
|
|
19
|
+
out.push(sep);
|
|
20
|
+
for (const r of rows) {
|
|
21
|
+
out.push(r.map((cell, i) => PAD(cell, widths[i])).join(' '));
|
|
22
|
+
}
|
|
23
|
+
return out.join('\n');
|
|
24
|
+
}
|
|
25
|
+
function describe(d) {
|
|
26
|
+
if (!d.exists)
|
|
27
|
+
return 'not detected';
|
|
28
|
+
return d.installed ? 'installed' : 'available';
|
|
29
|
+
}
|
|
30
|
+
export function printStatus(detections = detectAll()) {
|
|
31
|
+
// Sort for predictable output: known agent order.
|
|
32
|
+
const order = new Map(SUPPORTED_AGENTS.map((a, i) => [a, i]));
|
|
33
|
+
const sorted = [...detections].sort((a, b) => (order.get(a.agent) ?? 99) - (order.get(b.agent) ?? 99));
|
|
34
|
+
console.log(renderStatusTable(sorted));
|
|
35
|
+
const installed = sorted.filter((d) => d.installed).length;
|
|
36
|
+
const available = sorted.filter((d) => d.exists && !d.installed).length;
|
|
37
|
+
console.log(`\n${installed} installed, ${available} available, ${sorted.length - installed - available} not detected`);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=status.js.map
|