fullcourtdefense-cli 1.1.17 → 1.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.
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.claudeDesktopMsixCandidates = claudeDesktopMsixCandidates;
37
+ exports.claudeDesktopConfigCandidates = claudeDesktopConfigCandidates;
38
+ exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
39
+ exports.claudeDesktopInstallTargets = claudeDesktopInstallTargets;
40
+ exports.candidateConfigPaths = candidateConfigPaths;
41
+ exports.discoverScanTargets = discoverScanTargets;
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ function claudeDesktopDocumentedPath() {
46
+ const home = os.homedir();
47
+ if (process.platform === 'win32') {
48
+ const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
49
+ return path.join(appData, 'Claude', 'claude_desktop_config.json');
50
+ }
51
+ if (process.platform === 'darwin') {
52
+ return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
53
+ }
54
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
55
+ return path.join(xdg, 'Claude', 'claude_desktop_config.json');
56
+ }
57
+ function claudeDesktopMsixCandidates() {
58
+ if (process.platform !== 'win32')
59
+ return [];
60
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
61
+ const packagesDir = path.join(localAppData, 'Packages');
62
+ try {
63
+ return fs.readdirSync(packagesDir)
64
+ .filter(name => /^Claude_/i.test(name))
65
+ .map(name => ({
66
+ file: path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json'),
67
+ source: 'Claude Desktop (win MSIX)',
68
+ exists: fs.existsSync(path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json')),
69
+ }));
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ }
75
+ function claudeDesktopConfigCandidates() {
76
+ const documented = claudeDesktopDocumentedPath();
77
+ const normal = {
78
+ file: documented,
79
+ source: process.platform === 'win32'
80
+ ? 'Claude Desktop (win)'
81
+ : process.platform === 'darwin'
82
+ ? 'Claude Desktop (mac)'
83
+ : 'Claude Desktop (linux)',
84
+ exists: fs.existsSync(documented),
85
+ };
86
+ const seen = new Set();
87
+ return [...claudeDesktopMsixCandidates(), normal].filter(candidate => {
88
+ const key = path.resolve(candidate.file).toLowerCase();
89
+ if (seen.has(key))
90
+ return false;
91
+ seen.add(key);
92
+ return true;
93
+ });
94
+ }
95
+ function claudeDesktopLikelyInstalled() {
96
+ if (claudeDesktopConfigCandidates().some(candidate => candidate.exists))
97
+ return true;
98
+ if (claudeDesktopMsixCandidates().length > 0)
99
+ return true;
100
+ if (process.platform === 'darwin' && fs.existsSync('/Applications/Claude.app'))
101
+ return true;
102
+ if (process.platform === 'win32') {
103
+ const pf = process.env.ProgramFiles || 'C:\\Program Files';
104
+ if (fs.existsSync(path.join(pf, 'Claude', 'Claude.exe')))
105
+ return true;
106
+ }
107
+ return fs.existsSync(path.dirname(claudeDesktopDocumentedPath()));
108
+ }
109
+ function claudeDesktopInstallTargets(configPath) {
110
+ if (configPath) {
111
+ const file = path.resolve(configPath);
112
+ return [{ file, source: 'Claude Desktop (custom)', exists: fs.existsSync(file) }];
113
+ }
114
+ const candidates = claudeDesktopConfigCandidates();
115
+ const existing = candidates.filter(candidate => candidate.exists);
116
+ if (existing.length > 0)
117
+ return existing;
118
+ const msix = candidates.find(candidate => candidate.source.includes('MSIX'));
119
+ if (msix)
120
+ return [msix];
121
+ return [candidates[candidates.length - 1] || {
122
+ file: claudeDesktopDocumentedPath(),
123
+ source: 'Claude Desktop',
124
+ exists: false,
125
+ }];
126
+ }
127
+ /** All MCP client config locations discover / install should check. */
128
+ function candidateConfigPaths(cwd, extra) {
129
+ const home = os.homedir();
130
+ const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
131
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
132
+ const list = [
133
+ { path: path.join(home, '.cursor', 'mcp.json'), source: 'Cursor (global)', clientKey: 'cursor' },
134
+ { path: path.join(cwd, '.cursor', 'mcp.json'), source: 'Cursor (project)', clientKey: 'cursor' },
135
+ { path: path.join(appData, 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, win)', clientKey: 'cursor' },
136
+ { path: path.join(home, 'Library', 'Application Support', 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, mac)', clientKey: 'cursor' },
137
+ { path: '/etc/cursor/managed-mcp.json', source: 'Cursor (managed, linux)', clientKey: 'cursor' },
138
+ { path: path.join(home, '.claude.json'), source: 'Claude Code', clientKey: 'claude_code' },
139
+ { path: path.join(home, '.claude', 'settings.json'), source: 'Claude Code (global)', clientKey: 'claude_code' },
140
+ { path: path.join(cwd, '.claude', 'settings.json'), source: 'Claude Code (project)', clientKey: 'claude_code' },
141
+ { path: path.join(cwd, '.mcp.json'), source: 'Claude Code (.mcp.json project)', clientKey: 'claude_code' },
142
+ { path: path.join(home, '.gemini', 'settings.json'), source: 'Gemini CLI', clientKey: 'gemini_cli' },
143
+ { path: path.join(xdg, 'gemini', 'settings.json'), source: 'Gemini CLI (xdg)', clientKey: 'gemini_cli' },
144
+ { path: path.join(home, '.codex', 'mcp.json'), source: 'Codex', clientKey: 'codex' },
145
+ { path: path.join(xdg, 'codex', 'mcp.json'), source: 'Codex (xdg)', clientKey: 'codex' },
146
+ { path: path.join(cwd, '.vscode', 'mcp.json'), source: 'VS Code (project)', clientKey: 'vscode' },
147
+ { path: path.join(appData, 'Code', 'User', 'settings.json'), source: 'VS Code (user, win)', clientKey: 'vscode' },
148
+ { path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json'), source: 'VS Code (user, mac)', clientKey: 'vscode' },
149
+ { path: path.join(xdg, 'Code', 'User', 'settings.json'), source: 'VS Code (user, linux)', clientKey: 'vscode' },
150
+ { path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'), source: 'Windsurf', clientKey: 'windsurf' },
151
+ { path: path.join(home, '.kiro', 'mcp.json'), source: 'Kiro', clientKey: 'kiro' },
152
+ { path: path.join(cwd, '.mcp.json'), source: 'Repo (.mcp.json)', clientKey: 'other' },
153
+ { path: path.join(cwd, 'mcp.json'), source: 'Repo (mcp.json)', clientKey: 'other' },
154
+ ];
155
+ for (const candidate of claudeDesktopConfigCandidates()) {
156
+ list.push({ path: candidate.file, source: candidate.source, clientKey: 'claude_desktop' });
157
+ }
158
+ if (process.platform === 'win32') {
159
+ list.push({
160
+ path: path.join(process.env.ProgramData || 'C:\\ProgramData', 'Cursor', 'managed-mcp.json'),
161
+ source: 'Cursor (managed, program data)',
162
+ clientKey: 'cursor',
163
+ });
164
+ }
165
+ if (extra) {
166
+ list.push({ path: path.resolve(extra), source: 'Custom path', clientKey: 'other' });
167
+ }
168
+ const seen = new Set();
169
+ return list.filter(item => {
170
+ const key = path.resolve(item.path).toLowerCase();
171
+ if (seen.has(key))
172
+ return false;
173
+ seen.add(key);
174
+ return true;
175
+ });
176
+ }
177
+ /** Include config paths for apps that exist on disk even when the MCP file is not created yet. */
178
+ function discoverScanTargets(cwd, extra) {
179
+ const candidates = candidateConfigPaths(cwd, extra);
180
+ const existing = candidates.filter(item => fs.existsSync(item.path));
181
+ const implicit = [];
182
+ if (claudeDesktopLikelyInstalled()) {
183
+ for (const target of claudeDesktopInstallTargets()) {
184
+ if (!existing.some(item => path.resolve(item.path).toLowerCase() === path.resolve(target.file).toLowerCase())) {
185
+ implicit.push({ path: target.file, source: target.source, clientKey: 'claude_desktop' });
186
+ }
187
+ }
188
+ }
189
+ const cursorGlobal = path.join(os.homedir(), '.cursor', 'mcp.json');
190
+ if (fs.existsSync(path.join(os.homedir(), '.cursor')) && !existing.some(item => path.resolve(item.path).toLowerCase() === cursorGlobal.toLowerCase())) {
191
+ implicit.push({ path: cursorGlobal, source: 'Cursor (global)', clientKey: 'cursor' });
192
+ }
193
+ return [...existing, ...implicit];
194
+ }
@@ -0,0 +1,42 @@
1
+ export type DesktopProxyStatus = 'fcd_gateway' | 'proxied' | 'direct' | 'unknown';
2
+ export declare const FCD_GATEWAY_SERVER_NAMES: Set<string>;
3
+ export declare const FCD_HOOK_MARKER = "--fcd-managed true";
4
+ export declare const FCD_HOOK_TAG = "fullcourtdefense";
5
+ export interface ClientCoverageRow {
6
+ client: string;
7
+ clientKey: string;
8
+ configPath: string;
9
+ configPresent: boolean;
10
+ mcpServerCount: number;
11
+ mcpGatewayInstalled: boolean;
12
+ cursorHooksInstalled: boolean;
13
+ cursorHookEvents: string[];
14
+ cursorHookShadow: boolean;
15
+ }
16
+ export interface ProxyClassifiableServer {
17
+ serverName: string;
18
+ command?: string;
19
+ args?: string[];
20
+ url?: string;
21
+ source: string;
22
+ configPath: string;
23
+ }
24
+ export declare function isFcdGatewayServer(server: ProxyClassifiableServer): boolean;
25
+ export declare function extractGatewayDownstream(server: ProxyClassifiableServer): {
26
+ command?: string;
27
+ args?: string[];
28
+ label?: string;
29
+ } | null;
30
+ /** Per-client downstream targets wrapped by FCD gateway entries in the same config file. */
31
+ export declare function buildGatewayWrapIndex(servers: ProxyClassifiableServer[]): Map<string, Set<string>>;
32
+ export declare function classifyProxyStatus(server: ProxyClassifiableServer, wrapsByConfig: Map<string, Set<string>>): DesktopProxyStatus;
33
+ export declare function scanCursorHooks(projectPath?: string): {
34
+ installed: boolean;
35
+ events: string[];
36
+ shadow: boolean;
37
+ };
38
+ export declare function clientKeyFromSource(source: string): string;
39
+ export declare function buildClientCoverage(scanned: Array<{
40
+ path: string;
41
+ source: string;
42
+ }>, servers: ProxyClassifiableServer[], cwd: string): ClientCoverageRow[];
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FCD_HOOK_TAG = exports.FCD_HOOK_MARKER = exports.FCD_GATEWAY_SERVER_NAMES = void 0;
37
+ exports.isFcdGatewayServer = isFcdGatewayServer;
38
+ exports.extractGatewayDownstream = extractGatewayDownstream;
39
+ exports.buildGatewayWrapIndex = buildGatewayWrapIndex;
40
+ exports.classifyProxyStatus = classifyProxyStatus;
41
+ exports.scanCursorHooks = scanCursorHooks;
42
+ exports.clientKeyFromSource = clientKeyFromSource;
43
+ exports.buildClientCoverage = buildClientCoverage;
44
+ const fs = __importStar(require("fs"));
45
+ const os = __importStar(require("os"));
46
+ const path = __importStar(require("path"));
47
+ exports.FCD_GATEWAY_SERVER_NAMES = new Set([
48
+ 'agentguard-gateway',
49
+ 'fullcourtdefense-gateway',
50
+ 'fcd-gateway',
51
+ ]);
52
+ exports.FCD_HOOK_MARKER = '--fcd-managed true';
53
+ exports.FCD_HOOK_TAG = 'fullcourtdefense';
54
+ function commandBlob(server) {
55
+ return `${server.serverName} ${server.command || ''} ${(server.args || []).join(' ')} ${server.url || ''}`.toLowerCase();
56
+ }
57
+ function isFcdGatewayServer(server) {
58
+ const name = server.serverName.toLowerCase();
59
+ if (exports.FCD_GATEWAY_SERVER_NAMES.has(name))
60
+ return true;
61
+ const blob = commandBlob(server);
62
+ return blob.includes('mcp-gateway')
63
+ && (blob.includes('fullcourtdefense') || blob.includes('agentguard') || blob.includes('botguard') || blob.includes('fcd-managed'));
64
+ }
65
+ function extractGatewayDownstream(server) {
66
+ const args = server.args || [];
67
+ const cmdIdx = args.indexOf('--mcp-command');
68
+ if (cmdIdx < 0 || !args[cmdIdx + 1])
69
+ return null;
70
+ let downstreamArgs = [];
71
+ const argsIdx = args.indexOf('--mcp-args');
72
+ if (argsIdx >= 0 && args[argsIdx + 1]) {
73
+ try {
74
+ downstreamArgs = JSON.parse(args[argsIdx + 1]);
75
+ }
76
+ catch {
77
+ downstreamArgs = [args[argsIdx + 1]];
78
+ }
79
+ }
80
+ const command = args[cmdIdx + 1];
81
+ const label = inferDownstreamLabel(command, downstreamArgs);
82
+ return { command, args: downstreamArgs, label };
83
+ }
84
+ function inferDownstreamLabel(command, args) {
85
+ const hay = `${command || ''} ${(args || []).join(' ')}`.toLowerCase();
86
+ const pkgMatch = hay.match(/@([a-z0-9._-]+\/mcp[a-z0-9._-]*|[a-z0-9._-]+-mcp[a-z0-9._-]*)/i);
87
+ if (pkgMatch)
88
+ return pkgMatch[1].replace(/^@/, '').split('@')[0];
89
+ const fileMatch = hay.match(/([^/\\]+\.(js|ts|py|mjs))(?:\s|$)/i);
90
+ if (fileMatch)
91
+ return fileMatch[1].replace(/\.(js|ts|py|mjs)$/i, '');
92
+ if (command)
93
+ return path.basename(command).replace(/\.(cmd|exe|js)$/i, '');
94
+ return undefined;
95
+ }
96
+ function downstreamFingerprint(command, args) {
97
+ return `${command || ''}|${(args || []).join(' ')}`.trim().toLowerCase();
98
+ }
99
+ function serverFingerprints(server) {
100
+ const out = new Set();
101
+ out.add(server.serverName.toLowerCase());
102
+ const blob = commandBlob(server);
103
+ out.add(blob);
104
+ if (server.command)
105
+ out.add(downstreamFingerprint(server.command, server.args));
106
+ const label = inferDownstreamLabel(server.command, server.args);
107
+ if (label)
108
+ out.add(label.toLowerCase());
109
+ return [...out];
110
+ }
111
+ /** Per-client downstream targets wrapped by FCD gateway entries in the same config file. */
112
+ function buildGatewayWrapIndex(servers) {
113
+ const byConfig = new Map();
114
+ for (const server of servers) {
115
+ const key = server.configPath.toLowerCase();
116
+ const list = byConfig.get(key) || [];
117
+ list.push(server);
118
+ byConfig.set(key, list);
119
+ }
120
+ const wrapsByConfig = new Map();
121
+ for (const [configPath, list] of byConfig.entries()) {
122
+ const wrapped = new Set();
123
+ for (const server of list) {
124
+ if (!isFcdGatewayServer(server))
125
+ continue;
126
+ const downstream = extractGatewayDownstream(server);
127
+ if (downstream?.label)
128
+ wrapped.add(downstream.label.toLowerCase());
129
+ if (downstream?.command)
130
+ wrapped.add(downstreamFingerprint(downstream.command, downstream.args));
131
+ wrapped.add(server.serverName.toLowerCase());
132
+ }
133
+ wrapsByConfig.set(configPath, wrapped);
134
+ }
135
+ return wrapsByConfig;
136
+ }
137
+ function classifyProxyStatus(server, wrapsByConfig) {
138
+ if (isFcdGatewayServer(server))
139
+ return 'fcd_gateway';
140
+ const wrapped = wrapsByConfig.get(server.configPath.toLowerCase());
141
+ if (!wrapped || wrapped.size === 0)
142
+ return 'direct';
143
+ for (const fp of serverFingerprints(server)) {
144
+ if (wrapped.has(fp))
145
+ return 'proxied';
146
+ for (const w of wrapped) {
147
+ if (fp.includes(w) || w.includes(fp))
148
+ return 'proxied';
149
+ }
150
+ }
151
+ return 'direct';
152
+ }
153
+ const CURSOR_HOOK_EVENTS = {
154
+ beforeSubmitPrompt: 'prompt',
155
+ beforeShellExecution: 'shell',
156
+ beforeMCPExecution: 'mcp',
157
+ afterFileEdit: 'file',
158
+ beforeReadFile: 'read',
159
+ };
160
+ function readHooksJson(file) {
161
+ if (!fs.existsSync(file))
162
+ return { hooks: {} };
163
+ try {
164
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
165
+ return { hooks: parsed?.hooks && typeof parsed.hooks === 'object' ? parsed.hooks : {} };
166
+ }
167
+ catch {
168
+ return { hooks: {} };
169
+ }
170
+ }
171
+ function scanCursorHooks(projectPath) {
172
+ const globalFile = path.join(os.homedir(), '.cursor', 'hooks.json');
173
+ const projectFile = projectPath ? path.join(projectPath, '.cursor', 'hooks.json') : '';
174
+ const files = [globalFile, projectFile].filter(Boolean);
175
+ const events = new Set();
176
+ let shadow = false;
177
+ let installed = false;
178
+ for (const file of files) {
179
+ const { hooks } = readHooksJson(file);
180
+ for (const [hookKey, entries] of Object.entries(hooks)) {
181
+ for (const entry of entries || []) {
182
+ const cmd = entry?.command || '';
183
+ if (!cmd.includes(exports.FCD_HOOK_MARKER) && !cmd.includes(exports.FCD_HOOK_TAG))
184
+ continue;
185
+ installed = true;
186
+ const mapped = CURSOR_HOOK_EVENTS[hookKey] || hookKey;
187
+ events.add(mapped);
188
+ if (cmd.includes('--shadow true'))
189
+ shadow = true;
190
+ }
191
+ }
192
+ }
193
+ return { installed, events: [...events].sort(), shadow };
194
+ }
195
+ function clientKeyFromSource(source) {
196
+ const s = source.toLowerCase();
197
+ if (s.includes('cursor'))
198
+ return 'cursor';
199
+ if (s.includes('claude desktop'))
200
+ return 'claude_desktop';
201
+ if (s.includes('claude code'))
202
+ return 'claude_code';
203
+ if (s.includes('vs code'))
204
+ return 'vscode';
205
+ if (s.includes('windsurf'))
206
+ return 'windsurf';
207
+ if (s.includes('gemini'))
208
+ return 'gemini_cli';
209
+ if (s.includes('codex'))
210
+ return 'codex';
211
+ if (s.includes('kiro'))
212
+ return 'kiro';
213
+ return 'other';
214
+ }
215
+ function buildClientCoverage(scanned, servers, cwd) {
216
+ const hooks = scanCursorHooks(cwd);
217
+ const serversByConfig = new Map();
218
+ for (const server of servers) {
219
+ const key = server.configPath.toLowerCase();
220
+ const list = serversByConfig.get(key) || [];
221
+ list.push(server);
222
+ serversByConfig.set(key, list);
223
+ }
224
+ const rows = [];
225
+ for (const item of scanned) {
226
+ const list = serversByConfig.get(item.path.toLowerCase()) || [];
227
+ const clientKey = clientKeyFromSource(item.source);
228
+ const mcpGatewayInstalled = list.some(isFcdGatewayServer);
229
+ rows.push({
230
+ client: item.source,
231
+ clientKey,
232
+ configPath: item.path,
233
+ configPresent: true,
234
+ mcpServerCount: list.filter(s => !isFcdGatewayServer(s)).length,
235
+ mcpGatewayInstalled,
236
+ cursorHooksInstalled: clientKey === 'cursor' ? hooks.installed : false,
237
+ cursorHookEvents: clientKey === 'cursor' ? hooks.events : [],
238
+ cursorHookShadow: clientKey === 'cursor' ? hooks.shadow : false,
239
+ });
240
+ }
241
+ // Cursor hooks apply globally even if only project mcp.json was scanned
242
+ if (!rows.some(r => r.clientKey === 'cursor') && hooks.installed) {
243
+ rows.push({
244
+ client: 'Cursor (hooks)',
245
+ clientKey: 'cursor',
246
+ configPath: path.join(os.homedir(), '.cursor', 'hooks.json'),
247
+ configPresent: fs.existsSync(path.join(os.homedir(), '.cursor', 'hooks.json')),
248
+ mcpServerCount: 0,
249
+ mcpGatewayInstalled: false,
250
+ cursorHooksInstalled: true,
251
+ cursorHookEvents: hooks.events,
252
+ cursorHookShadow: hooks.shadow,
253
+ });
254
+ }
255
+ return rows.sort((a, b) => a.client.localeCompare(b.client));
256
+ }
@@ -0,0 +1,6 @@
1
+ export interface ScheduleArgs {
2
+ userEmail?: string;
3
+ hour?: number;
4
+ }
5
+ export declare function installDailyDiscoverSchedule(args?: ScheduleArgs): void;
6
+ export declare function uninstallDailyDiscoverSchedule(): void;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.installDailyDiscoverSchedule = installDailyDiscoverSchedule;
37
+ exports.uninstallDailyDiscoverSchedule = uninstallDailyDiscoverSchedule;
38
+ const child_process_1 = require("child_process");
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const TASK_NAME = 'FullCourtDefenseDesktopDiscover';
43
+ function discoverCommandLine(userEmail) {
44
+ const node = process.execPath;
45
+ const script = path.resolve(process.argv[1] || path.join(__dirname, '..', 'index.js'));
46
+ const q = (value) => (/\s/.test(value) ? `"${value}"` : value);
47
+ const parts = [q(node), q(script), 'discover', '--upload', '--silent'];
48
+ if (userEmail)
49
+ parts.push('--user-email', q(userEmail));
50
+ return parts.join(' ');
51
+ }
52
+ function installWindowsSchedule(hour, userEmail) {
53
+ const tr = discoverCommandLine(userEmail);
54
+ const st = `${String(hour).padStart(2, '0')}:00`;
55
+ try {
56
+ (0, child_process_1.execSync)(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: 'ignore' });
57
+ }
58
+ catch {
59
+ // task may not exist
60
+ }
61
+ (0, child_process_1.execSync)(`schtasks /Create /F /TN "${TASK_NAME}" /SC DAILY /ST ${st} /TR "${tr.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
62
+ }
63
+ function installMacSchedule(hour, userEmail) {
64
+ const label = 'ai.fullcourtdefense.discover';
65
+ const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
66
+ const plistPath = path.join(plistDir, `${label}.plist`);
67
+ fs.mkdirSync(plistDir, { recursive: true });
68
+ const cmd = discoverCommandLine(userEmail).split(/\s+/);
69
+ const program = cmd.shift() || process.execPath;
70
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
71
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
72
+ <plist version="1.0">
73
+ <dict>
74
+ <key>Label</key><string>${label}</string>
75
+ <key>ProgramArguments</key>
76
+ <array>${[program, ...cmd].map(part => `<string>${part.replace(/&/g, '&amp;').replace(/</g, '&lt;')}</string>`).join('')}</array>
77
+ <key>StartCalendarInterval</key>
78
+ <dict><key>Hour</key><integer>${hour}</integer><key>Minute</key><integer>0</integer></dict>
79
+ <key>StandardOutPath</key><string>${path.join(os.homedir(), '.fullcourtdefense-discover.log')}</string>
80
+ <key>StandardErrorPath</key><string>${path.join(os.homedir(), '.fullcourtdefense-discover.err.log')}</string>
81
+ </dict>
82
+ </plist>`;
83
+ fs.writeFileSync(plistPath, plist, 'utf8');
84
+ (0, child_process_1.spawnSync)('launchctl', ['unload', plistPath], { stdio: 'ignore' });
85
+ (0, child_process_1.spawnSync)('launchctl', ['load', plistPath], { stdio: 'inherit' });
86
+ }
87
+ function installLinuxCron(hour, userEmail) {
88
+ const line = `0 ${hour} * * * ${discoverCommandLine(userEmail)} >> ${path.join(os.homedir(), '.fullcourtdefense-discover.log')} 2>&1`;
89
+ const marker = '# fullcourtdefense-discover';
90
+ let crontab = '';
91
+ try {
92
+ crontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf8' });
93
+ }
94
+ catch {
95
+ crontab = '';
96
+ }
97
+ const filtered = crontab.split('\n').filter(l => !l.includes(marker) && !l.includes('discover --upload --silent'));
98
+ filtered.push(`${marker}`);
99
+ filtered.push(line);
100
+ (0, child_process_1.execSync)(`echo "${filtered.join('\\n')}" | crontab -`, { stdio: 'inherit', shell: '/bin/bash' });
101
+ }
102
+ function installDailyDiscoverSchedule(args = {}) {
103
+ const hour = Number.isFinite(args.hour) ? Math.min(23, Math.max(0, args.hour)) : 9;
104
+ if (process.platform === 'win32')
105
+ installWindowsSchedule(hour, args.userEmail);
106
+ else if (process.platform === 'darwin')
107
+ installMacSchedule(hour, args.userEmail);
108
+ else
109
+ installLinuxCron(hour, args.userEmail);
110
+ }
111
+ function uninstallDailyDiscoverSchedule() {
112
+ if (process.platform === 'win32') {
113
+ (0, child_process_1.execSync)(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: 'inherit' });
114
+ return;
115
+ }
116
+ if (process.platform === 'darwin') {
117
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'ai.fullcourtdefense.discover.plist');
118
+ (0, child_process_1.spawnSync)('launchctl', ['unload', plistPath], { stdio: 'ignore' });
119
+ if (fs.existsSync(plistPath))
120
+ fs.unlinkSync(plistPath);
121
+ return;
122
+ }
123
+ let crontab = '';
124
+ try {
125
+ crontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf8' });
126
+ }
127
+ catch {
128
+ return;
129
+ }
130
+ const filtered = crontab.split('\n').filter(l => !l.includes('fullcourtdefense-discover') && !l.includes('discover --upload --silent'));
131
+ (0, child_process_1.execSync)(`echo "${filtered.join('\\n')}" | crontab -`, { stdio: 'inherit', shell: '/bin/bash' });
132
+ }
@@ -0,0 +1,8 @@
1
+ export interface KnownMcpServerInfo {
2
+ title: string;
3
+ description: string;
4
+ category: 'browser' | 'filesystem' | 'database' | 'gateway' | 'other';
5
+ recommendGateway: boolean;
6
+ }
7
+ export declare function knownMcpServerInfo(serverName: string, command?: string): KnownMcpServerInfo | undefined;
8
+ export declare function directServerFixHint(serverName: string): string | undefined;