fullcourtdefense-cli 1.5.3 → 1.6.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/dist/commands/autoProtect.d.ts +10 -0
- package/dist/commands/autoProtect.js +150 -0
- package/dist/commands/mcpGateway.js +22 -7
- package/dist/index.js +20 -0
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BotGuardConfig } from '../config';
|
|
2
|
+
import { ProtectAllArgs } from './mcpGateway';
|
|
3
|
+
export interface AutoProtectArgs extends ProtectAllArgs {
|
|
4
|
+
install?: string;
|
|
5
|
+
uninstall?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
intervalMinutes?: string;
|
|
8
|
+
onLogon?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function autoProtectCommand(args: AutoProtectArgs, config: BotGuardConfig): Promise<void>;
|
|
@@ -0,0 +1,150 @@
|
|
|
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.autoProtectCommand = autoProtectCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const mcpGateway_1 = require("./mcpGateway");
|
|
42
|
+
const COLOR = {
|
|
43
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
44
|
+
red: '\x1b[31m', yellow: '\x1b[33m', green: '\x1b[32m', cyan: '\x1b[36m', gray: '\x1b[90m',
|
|
45
|
+
};
|
|
46
|
+
const TASK_NAME = 'FullCourtDefense Auto-Protect';
|
|
47
|
+
const CRON_MARKER = '# FCD_AUTO_PROTECT';
|
|
48
|
+
function cliEntry() {
|
|
49
|
+
return path.resolve(process.argv[1] || path.join(__dirname, '..', 'index.js'));
|
|
50
|
+
}
|
|
51
|
+
function launcherDir() {
|
|
52
|
+
const base = process.platform === 'win32'
|
|
53
|
+
? (process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'))
|
|
54
|
+
: path.join(os.homedir(), '.fullcourtdefense');
|
|
55
|
+
return path.join(base, 'FullCourtDefense');
|
|
56
|
+
}
|
|
57
|
+
/** On Windows, write a hidden VBS launcher so the scheduled run doesn't flash a console window. */
|
|
58
|
+
function ensureWindowsLauncher() {
|
|
59
|
+
const dir = launcherDir();
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
const vbs = path.join(dir, 'auto-protect.vbs');
|
|
62
|
+
const node = process.execPath;
|
|
63
|
+
const cli = cliEntry();
|
|
64
|
+
// WScript.Shell.Run with window style 0 = hidden, wait = false.
|
|
65
|
+
const inner = `"${node}" "${cli}" protect-all`;
|
|
66
|
+
const content = `CreateObject("WScript.Shell").Run "cmd /c ${inner.replace(/"/g, '""')}", 0, False\n`;
|
|
67
|
+
fs.writeFileSync(vbs, content, 'utf8');
|
|
68
|
+
return vbs;
|
|
69
|
+
}
|
|
70
|
+
function installWindows(intervalMinutes, onLogon) {
|
|
71
|
+
const vbs = ensureWindowsLauncher();
|
|
72
|
+
const tr = `wscript.exe "${vbs}"`;
|
|
73
|
+
const baseArgs = ['/Create', '/TN', TASK_NAME, '/TR', tr, '/F', '/RL', 'LIMITED'];
|
|
74
|
+
const schedArgs = onLogon
|
|
75
|
+
? ['/SC', 'ONLOGON']
|
|
76
|
+
: ['/SC', 'MINUTE', '/MO', String(intervalMinutes)];
|
|
77
|
+
const result = (0, child_process_1.spawnSync)('schtasks', [...baseArgs, ...schedArgs], { stdio: 'inherit' });
|
|
78
|
+
return result.status === 0;
|
|
79
|
+
}
|
|
80
|
+
function uninstallWindows() {
|
|
81
|
+
const result = (0, child_process_1.spawnSync)('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'inherit' });
|
|
82
|
+
const vbs = path.join(launcherDir(), 'auto-protect.vbs');
|
|
83
|
+
try {
|
|
84
|
+
if (fs.existsSync(vbs))
|
|
85
|
+
fs.unlinkSync(vbs);
|
|
86
|
+
}
|
|
87
|
+
catch { /* ignore */ }
|
|
88
|
+
return result.status === 0;
|
|
89
|
+
}
|
|
90
|
+
function statusWindows() {
|
|
91
|
+
(0, child_process_1.spawnSync)('schtasks', ['/Query', '/TN', TASK_NAME, '/V', '/FO', 'LIST'], { stdio: 'inherit' });
|
|
92
|
+
}
|
|
93
|
+
function readCrontab() {
|
|
94
|
+
const result = (0, child_process_1.spawnSync)('crontab', ['-l'], { encoding: 'utf8' });
|
|
95
|
+
return result.status === 0 ? (result.stdout || '') : '';
|
|
96
|
+
}
|
|
97
|
+
function writeCrontab(content) {
|
|
98
|
+
const result = (0, child_process_1.spawnSync)('crontab', ['-'], { input: content.endsWith('\n') ? content : `${content}\n`, encoding: 'utf8' });
|
|
99
|
+
return result.status === 0;
|
|
100
|
+
}
|
|
101
|
+
function installUnix(intervalMinutes, onLogon) {
|
|
102
|
+
const node = process.execPath;
|
|
103
|
+
const cli = cliEntry();
|
|
104
|
+
const command = `"${node}" "${cli}" protect-all >/dev/null 2>&1`;
|
|
105
|
+
const schedule = onLogon ? '@reboot' : `*/${Math.max(1, intervalMinutes)} * * * *`;
|
|
106
|
+
const line = `${schedule} ${command} ${CRON_MARKER}`;
|
|
107
|
+
const existing = readCrontab().split('\n').filter(l => l && !l.includes(CRON_MARKER));
|
|
108
|
+
return writeCrontab([...existing, line].join('\n'));
|
|
109
|
+
}
|
|
110
|
+
function uninstallUnix() {
|
|
111
|
+
const existing = readCrontab().split('\n').filter(l => l && !l.includes(CRON_MARKER));
|
|
112
|
+
return writeCrontab(existing.join('\n'));
|
|
113
|
+
}
|
|
114
|
+
async function autoProtectCommand(args, config) {
|
|
115
|
+
const intervalMinutes = Number(args.intervalMinutes) > 0 ? Math.floor(Number(args.intervalMinutes)) : 60;
|
|
116
|
+
const onLogon = args.onLogon === 'true';
|
|
117
|
+
if (args.uninstall === 'true') {
|
|
118
|
+
const ok = process.platform === 'win32' ? uninstallWindows() : uninstallUnix();
|
|
119
|
+
console.log(ok
|
|
120
|
+
? `${COLOR.green}Removed the FullCourtDefense auto-protect schedule.${COLOR.reset}`
|
|
121
|
+
: `${COLOR.yellow}No auto-protect schedule found (or removal failed).${COLOR.reset}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (args.status === 'true') {
|
|
125
|
+
if (process.platform === 'win32')
|
|
126
|
+
statusWindows();
|
|
127
|
+
else
|
|
128
|
+
console.log(readCrontab().split('\n').filter(l => l.includes(CRON_MARKER)).join('\n') || 'No auto-protect cron entry found.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Default action is install.
|
|
132
|
+
console.log(`\n${COLOR.bold}${COLOR.cyan}FullCourtDefense — schedule self-healing auto-protect${COLOR.reset}`);
|
|
133
|
+
console.log(`${COLOR.gray}Re-runs \`protect-all\` ${onLogon ? 'at every logon' : `every ${intervalMinutes} min`} so any server that loses the proxy is automatically re-wrapped.${COLOR.reset}\n`);
|
|
134
|
+
// Protect everything once right now, then register the recurring task.
|
|
135
|
+
await (0, mcpGateway_1.protectAllCommand)({ ...args, install: undefined, uninstall: undefined, status: undefined }, config);
|
|
136
|
+
const ok = process.platform === 'win32'
|
|
137
|
+
? installWindows(intervalMinutes, onLogon)
|
|
138
|
+
: installUnix(intervalMinutes, onLogon);
|
|
139
|
+
console.log('');
|
|
140
|
+
if (ok) {
|
|
141
|
+
console.log(`${COLOR.green}${COLOR.bold}Auto-protect scheduled.${COLOR.reset}`);
|
|
142
|
+
console.log(`${COLOR.gray}Trigger:${COLOR.reset} ${onLogon ? 'at logon' : `every ${intervalMinutes} minutes`}`);
|
|
143
|
+
console.log(`${COLOR.gray}Task:${COLOR.reset} ${process.platform === 'win32' ? `Scheduled Task "${TASK_NAME}"` : 'crontab entry'}`);
|
|
144
|
+
console.log(`${COLOR.gray}Status:${COLOR.reset} fullcourtdefense auto-protect --status true`);
|
|
145
|
+
console.log(`${COLOR.gray}Remove:${COLOR.reset} fullcourtdefense auto-protect --uninstall true`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(`${COLOR.red}Could not register the schedule.${COLOR.reset} On Windows you may need to run the terminal as Administrator, or use --on-logon true.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -1279,33 +1279,48 @@ async function protectAllCommand(args, config) {
|
|
|
1279
1279
|
: 'No MCP client config files found on this machine. Configure an MCP server in any client, then re-run.');
|
|
1280
1280
|
return;
|
|
1281
1281
|
}
|
|
1282
|
+
console.log(`\x1b[2mAuto-searching every known MCP client on this machine — ${files.length} config file(s) found.\x1b[0m\n`);
|
|
1282
1283
|
let totalWrapped = 0;
|
|
1283
1284
|
let totalManaged = 0;
|
|
1284
1285
|
let totalRemote = 0;
|
|
1286
|
+
let totalEmpty = 0;
|
|
1287
|
+
const presentClientKeys = new Set();
|
|
1285
1288
|
for (const file of files) {
|
|
1289
|
+
presentClientKeys.add(file.clientKey);
|
|
1286
1290
|
const agentClient = CLIENT_KEY_TO_AGENT[file.clientKey] || 'cursor';
|
|
1287
1291
|
const isToml = /\.toml$/i.test(file.path);
|
|
1288
1292
|
const stats = isToml
|
|
1289
1293
|
? transformCodexToml(file.path, 'wrap', gatewayConfig, dryRun)
|
|
1290
1294
|
: wrapJsonConfigFile(file.path, gatewayConfig, agentClient, dryRun);
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1295
|
+
console.log(`\x1b[1m${file.source}\x1b[0m \x1b[2m${file.path}\x1b[0m`);
|
|
1296
|
+
if (!stats) {
|
|
1297
|
+
console.log(' \x1b[33m• could not parse — left unchanged\x1b[0m\n');
|
|
1294
1298
|
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const found = stats.wrapped.length + stats.skippedManaged.length + stats.skippedRemote.length;
|
|
1295
1301
|
totalWrapped += stats.wrapped.length;
|
|
1296
1302
|
totalManaged += stats.skippedManaged.length;
|
|
1297
1303
|
totalRemote += stats.skippedRemote.length;
|
|
1298
|
-
console.log(`\x1b[1m${file.source}\x1b[0m \x1b[2m${file.path}\x1b[0m`);
|
|
1299
1304
|
if (stats.wrapped.length)
|
|
1300
|
-
console.log(` \x1b[32m✓
|
|
1305
|
+
console.log(` \x1b[32m✓ ${dryRun ? 'would update' : 'updated'} → now protected:\x1b[0m ${stats.wrapped.join(', ')}`);
|
|
1301
1306
|
if (stats.skippedManaged.length)
|
|
1302
|
-
console.log(` \x1b[2m• already protected:\x1b[0m ${stats.skippedManaged.join(', ')}`);
|
|
1307
|
+
console.log(` \x1b[2m• already protected (unchanged):\x1b[0m ${stats.skippedManaged.join(', ')}`);
|
|
1303
1308
|
if (stats.skippedRemote.length)
|
|
1304
1309
|
console.log(` \x1b[33m• skipped (remote/no command):\x1b[0m ${stats.skippedRemote.join(', ')}`);
|
|
1310
|
+
if (found === 0) {
|
|
1311
|
+
totalEmpty++;
|
|
1312
|
+
console.log(' \x1b[2m• no MCP servers configured — nothing to protect\x1b[0m');
|
|
1313
|
+
}
|
|
1305
1314
|
console.log('');
|
|
1306
1315
|
}
|
|
1316
|
+
// Show leading tools that have NO config file at all, so it's clear they were searched.
|
|
1317
|
+
const consideredClients = exports.ALL_GATEWAY_CLIENTS.filter(c => !allowedClientKeys || (AGENT_TO_CLIENT_KEYS[c] || []).some(k => allowedClientKeys.has(k)));
|
|
1318
|
+
const notConfigured = consideredClients.filter(c => !(AGENT_TO_CLIENT_KEYS[c] || []).some(k => presentClientKeys.has(k)));
|
|
1319
|
+
if (notConfigured.length) {
|
|
1320
|
+
console.log(`\x1b[2mNot configured (no config file found): ${notConfigured.map(c => CLIENT_LABELS[c]).join(', ')}\x1b[0m\n`);
|
|
1321
|
+
}
|
|
1307
1322
|
console.log('\x1b[1mSummary\x1b[0m');
|
|
1308
|
-
console.log(` ${dryRun ? 'would
|
|
1323
|
+
console.log(` ${dryRun ? 'would update' : 'updated'}: ${totalWrapped} already protected: ${totalManaged} no servers: ${totalEmpty} skipped remote: ${totalRemote}`);
|
|
1309
1324
|
console.log(` Each protected server enforces your org Action Policies (allow / block / wait-for-human-approval) on its tool calls.`);
|
|
1310
1325
|
if (!dryRun && totalWrapped > 0)
|
|
1311
1326
|
console.log(' Backups saved next to each edited file (*.fcd-backup-*). Restart each client to load the protected servers.');
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ const hook_1 = require("./commands/hook");
|
|
|
45
45
|
const installCursorHook_1 = require("./commands/installCursorHook");
|
|
46
46
|
const mcpGateway_1 = require("./commands/mcpGateway");
|
|
47
47
|
const installAll_1 = require("./commands/installAll");
|
|
48
|
+
const autoProtect_1 = require("./commands/autoProtect");
|
|
48
49
|
const fs = __importStar(require("fs"));
|
|
49
50
|
const path = __importStar(require("path"));
|
|
50
51
|
function readCliVersion() {
|
|
@@ -133,6 +134,10 @@ function printHelp() {
|
|
|
133
134
|
unprotect-all
|
|
134
135
|
Reverse protect-all: restore every server to its original command.
|
|
135
136
|
Also supports --clients to target one tool, and --dry-run.
|
|
137
|
+
auto-protect
|
|
138
|
+
Self-healing: schedule protect-all to re-run automatically so any
|
|
139
|
+
server that loses the proxy is re-wrapped. --interval-minutes 60
|
|
140
|
+
(default) or --on-logon true. --uninstall true / --status true.
|
|
136
141
|
install-mcp-gateway
|
|
137
142
|
Install MCP gateway into selected clients (--clients all for every tool).
|
|
138
143
|
install-cursor-mcp-gateway
|
|
@@ -530,6 +535,21 @@ async function main() {
|
|
|
530
535
|
await (0, mcpGateway_1.unprotectAllCommand)(args, config);
|
|
531
536
|
break;
|
|
532
537
|
}
|
|
538
|
+
case 'auto-protect': {
|
|
539
|
+
const args = {
|
|
540
|
+
...buildGatewayArgs(),
|
|
541
|
+
clients: flags.clients,
|
|
542
|
+
config: flags.config,
|
|
543
|
+
dryRun: flags['dry-run'],
|
|
544
|
+
install: flags.install,
|
|
545
|
+
uninstall: flags.uninstall,
|
|
546
|
+
status: flags.status,
|
|
547
|
+
intervalMinutes: flags['interval-minutes'],
|
|
548
|
+
onLogon: flags['on-logon'],
|
|
549
|
+
};
|
|
550
|
+
await (0, autoProtect_1.autoProtectCommand)(args, config);
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
533
553
|
case 'install-cursor-mcp-gateway': {
|
|
534
554
|
const args = {
|
|
535
555
|
...buildGatewayArgs(),
|
package/dist/version.json
CHANGED