fullcourtdefense-cli 1.6.10 → 1.6.12

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.
@@ -6,6 +6,7 @@ const discover_1 = require("./discover");
6
6
  const installCursorHook_1 = require("./installCursorHook");
7
7
  const mcpGateway_1 = require("./mcpGateway");
8
8
  const autoProtect_1 = require("./autoProtect");
9
+ const restartNotice_1 = require("./restartNotice");
9
10
  /**
10
11
  * One-click install: discover/wrap every existing MCP server, install Cursor
11
12
  * hooks for built-in Cursor actions, optional self-heal and fleet upload.
@@ -32,7 +33,7 @@ async function installAllCommand(args, config) {
32
33
  shieldKey: creds.shieldKey,
33
34
  apiUrl: creds.apiUrl,
34
35
  project: args.cursorProject,
35
- events: 'shell,mcp',
36
+ events: 'prompt,shell,mcp',
36
37
  failClosed: 'true',
37
38
  };
38
39
  try {
@@ -61,5 +62,6 @@ async function installAllCommand(args, config) {
61
62
  };
62
63
  await (0, discover_1.discoverCommand)(discoverArgs, config);
63
64
  }
64
- console.log('\n\x1b[32mDone.\x1b[0m Restart each AI client you use, then run \x1b[1mfullcourtdefense protect-all --dry-run true\x1b[0m to verify gateways.');
65
+ console.log('\n\x1b[32mDone.\x1b[0m Run \x1b[1mfullcourtdefense protect-all --dry-run true\x1b[0m to verify gateways.');
66
+ await (0, restartNotice_1.promptRestartNotice)();
65
67
  }
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const os = __importStar(require("os"));
40
40
  const path = __importStar(require("path"));
41
41
  const config_1 = require("../config");
42
+ const restartNotice_1 = require("./restartNotice");
42
43
  const COLOR = {
43
44
  reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
44
45
  red: '\x1b[31m', yellow: '\x1b[33m', green: '\x1b[32m', cyan: '\x1b[36m', gray: '\x1b[90m',
@@ -168,7 +169,8 @@ async function installCursorHookCommand(args, config) {
168
169
  console.log(`${COLOR.yellow}No Shield ID set.${COLOR.reset} Run ${COLOR.bold}fullcourtdefense setup${COLOR.reset} first, then re-run install.`);
169
170
  }
170
171
  console.log('');
171
- console.log(`${COLOR.gray}Restart Cursor (or it will hot-reload hooks.json). Verify in Cursor → Settings → Hooks.${COLOR.reset}`);
172
+ (0, restartNotice_1.printRestartNotice)('Cursor');
173
+ console.log(`${COLOR.gray}Verify in Cursor → Settings → Hooks after restart.${COLOR.reset}`);
172
174
  console.log(`${COLOR.gray}Remove with:${COLOR.reset} fullcourtdefense uninstall-cursor-hook${projectScope ? ' --project true' : ''}`);
173
175
  }
174
176
  async function uninstallCursorHookCommand(args) {
@@ -53,6 +53,7 @@ const config_1 = require("../config");
53
53
  const discoverPaths_1 = require("./discoverPaths");
54
54
  const discover_1 = require("./discover");
55
55
  const deterministicGuard_1 = require("./deterministicGuard");
56
+ const restartNotice_1 = require("./restartNotice");
56
57
  const DEFAULT_API_URL = 'https://api.fullcourtdefense.ai';
57
58
  const MANAGED_SERVER_NAME = 'agentguard-gateway';
58
59
  const INSTALL_GATEWAY_ARGS = { includeShieldKey: false };
@@ -936,7 +937,7 @@ async function installMcpGatewayCommand(args, config) {
936
937
  console.log(` - ${failure}`);
937
938
  }
938
939
  console.log(` Runtime identity: ${detectedDeveloperName(args)}`);
939
- console.log('Restart/reload each client to use protected MCP tools.');
940
+ (0, restartNotice_1.printRestartNotice)(clients.map(c => CLIENT_LABELS[c]).join(', '));
940
941
  if (args.upload === 'true') {
941
942
  console.log('\nUploading desktop inventory to AI Fleet…');
942
943
  const creds = (0, config_1.resolveCliCredentials)(config, { apiKey: args.apiKey, apiUrl: args.apiUrl });
@@ -1334,8 +1335,10 @@ async function protectAllCommand(args, config) {
1334
1335
  console.log('\x1b[1mSummary\x1b[0m');
1335
1336
  console.log(` ${dryRun ? 'would update' : 'updated'}: ${totalWrapped} already protected: ${totalManaged} no servers: ${totalEmpty} skipped remote: ${totalRemote}`);
1336
1337
  console.log(` Each protected server enforces your org Action Policies (allow / block / wait-for-human-approval) on its tool calls.`);
1337
- if (!dryRun && totalWrapped > 0)
1338
- console.log(' Backups saved next to each edited file (*.fcd-backup-*). Restart each client to load the protected servers.');
1338
+ if (!dryRun && totalWrapped > 0) {
1339
+ console.log(' Backups saved next to each edited file (*.fcd-backup-*).');
1340
+ (0, restartNotice_1.printRestartNotice)(allowedClientKeys ? [...allowedClientKeys].join(', ') : undefined);
1341
+ }
1339
1342
  console.log(' Reverse anytime with: \x1b[1mfullcourtdefense unprotect-all\x1b[0m');
1340
1343
  }
1341
1344
  async function unprotectAllCommand(args, config) {
@@ -0,0 +1,2 @@
1
+ export declare function printRestartNotice(clients?: string): void;
2
+ export declare function promptRestartNotice(clients?: string): Promise<void>;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printRestartNotice = printRestartNotice;
4
+ exports.promptRestartNotice = promptRestartNotice;
5
+ const child_process_1 = require("child_process");
6
+ function printRestartNotice(clients = 'Cursor, Claude, Codex, VS Code, Windsurf, Gemini') {
7
+ console.log('');
8
+ console.log('\x1b[33m\x1b[1mACTION REQUIRED: restart your AI clients\x1b[0m');
9
+ console.log(`\x1b[33mRestart/reload ${clients} so they load the protected MCP gateway and hooks.\x1b[0m`);
10
+ console.log('\x1b[2mAlready-running MCP servers may keep using old unprotected processes until the client restarts.\x1b[0m');
11
+ }
12
+ function shouldPrompt() {
13
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
14
+ return false;
15
+ if (process.env.CI === 'true' || process.env.FCD_NO_RESTART_PROMPT === 'true')
16
+ return false;
17
+ return true;
18
+ }
19
+ function parseJsonArray(raw) {
20
+ const trimmed = raw.trim();
21
+ if (!trimmed)
22
+ return [];
23
+ const parsed = JSON.parse(trimmed);
24
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
25
+ return rows
26
+ .map((row) => row)
27
+ .map((row) => ({
28
+ pid: Number(row.ProcessId || row.pid),
29
+ name: String(row.Name || row.name || ''),
30
+ executablePath: typeof row.ExecutablePath === 'string' ? row.ExecutablePath : undefined,
31
+ }))
32
+ .filter((row) => Number.isFinite(row.pid) && row.pid > 0 && row.name);
33
+ }
34
+ function detectWindowsClients() {
35
+ const script = [
36
+ "$names=@('Cursor.exe','Claude.exe','Code.exe','Windsurf.exe');",
37
+ 'Get-CimInstance Win32_Process |',
38
+ 'Where-Object { $names -contains $_.Name } |',
39
+ 'Select-Object ProcessId,Name,ExecutablePath |',
40
+ 'ConvertTo-Json -Compress',
41
+ ].join(' ');
42
+ try {
43
+ return parseJsonArray((0, child_process_1.execFileSync)('powershell.exe', ['-NoProfile', '-Command', script], { encoding: 'utf8' }));
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ function detectRestartableClients() {
50
+ if (process.platform === 'win32')
51
+ return detectWindowsClients();
52
+ return [];
53
+ }
54
+ async function askRestart(clients) {
55
+ const names = [...new Set(clients.map(client => client.name.replace(/\.exe$/i, '')))].join(', ');
56
+ return new Promise((resolve) => {
57
+ process.stdout.write(`\n\x1b[1mRestart detected AI clients now (${names})? Press r then Enter to restart, or Enter to skip.\x1b[0m `);
58
+ process.stdin.resume();
59
+ process.stdin.once('data', (chunk) => {
60
+ process.stdin.pause();
61
+ resolve(String(chunk).trim().toLowerCase() === 'r');
62
+ });
63
+ });
64
+ }
65
+ async function restartWindowsClients(clients) {
66
+ const launchPaths = [...new Set(clients.map(client => client.executablePath).filter((value) => !!value))];
67
+ for (const client of clients) {
68
+ try {
69
+ (0, child_process_1.execFileSync)('taskkill.exe', ['/PID', String(client.pid), '/T', '/F'], { stdio: 'ignore' });
70
+ }
71
+ catch {
72
+ // The process may already be gone.
73
+ }
74
+ }
75
+ await new Promise(resolve => setTimeout(resolve, 1200));
76
+ for (const exePath of launchPaths) {
77
+ try {
78
+ const child = (0, child_process_1.spawn)(exePath, [], { detached: true, stdio: 'ignore' });
79
+ child.unref();
80
+ console.log(`Restarted ${exePath}`);
81
+ }
82
+ catch (error) {
83
+ console.log(`Could not restart ${exePath}: ${error instanceof Error ? error.message : String(error)}`);
84
+ }
85
+ }
86
+ }
87
+ async function restartClients(clients) {
88
+ if (process.platform === 'win32') {
89
+ await restartWindowsClients(clients);
90
+ return;
91
+ }
92
+ console.log('Automatic restart is not supported on this OS yet. Please restart your AI clients manually.');
93
+ }
94
+ async function promptRestartNotice(clients = 'Cursor, Claude, Codex, VS Code, Windsurf, Gemini') {
95
+ printRestartNotice(clients);
96
+ if (!shouldPrompt())
97
+ return;
98
+ const runningClients = detectRestartableClients();
99
+ if (runningClients.length === 0) {
100
+ console.log('\n\x1b[2mNo running GUI AI clients detected. Start them again to load protection.\x1b[0m');
101
+ return;
102
+ }
103
+ if (await askRestart(runningClients)) {
104
+ await restartClients(runningClients);
105
+ }
106
+ else {
107
+ console.log('Skipped automatic restart. Protection applies after you restart/reload the clients.');
108
+ }
109
+ }
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.6.10"
2
+ "version": "1.6.12"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.6.10",
3
+ "version": "1.6.12",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {