fullcourtdefense-cli 1.21.12 → 1.21.13
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/appDetection.d.ts +8 -0
- package/dist/appDetection.js +107 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/hook.js +9 -2
- package/dist/commands/onboard.js +25 -1
- package/dist/commands/uninstallAll.d.ts +18 -0
- package/dist/commands/uninstallAll.js +132 -0
- package/dist/index.js +29 -2
- package/dist/integrity.js +13 -4
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Whether the Cursor IDE appears installed (app binaries or app data, not our hook file). */
|
|
2
|
+
export declare function isCursorAppInstalled(): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Whether any client of the Claude-format hook family appears installed:
|
|
5
|
+
* Claude Code, VS Code (Copilot agent mode), or GitHub Copilot CLI — one
|
|
6
|
+
* `~/.claude/settings.json` hook file covers all three.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isClaudeFamilyAppInstalled(): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
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.isCursorAppInstalled = isCursorAppInstalled;
|
|
37
|
+
exports.isClaudeFamilyAppInstalled = isClaudeFamilyAppInstalled;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
/**
|
|
42
|
+
* Best-effort detection of whether the AI clients we hook are actually
|
|
43
|
+
* installed on this machine. Used to decide whether a missing hook is a
|
|
44
|
+
* protection FAILURE (app present, hook gone) or merely informational
|
|
45
|
+
* (nothing to hook — e.g. a VS Code-only machine without Claude).
|
|
46
|
+
*
|
|
47
|
+
* Deliberately does NOT use our own config artifacts (`~/.cursor/hooks.json`,
|
|
48
|
+
* `~/.claude/settings.json`) as evidence — the hook installers create those
|
|
49
|
+
* directories themselves, which would make every machine look like it has
|
|
50
|
+
* every app.
|
|
51
|
+
*/
|
|
52
|
+
function anyExists(candidates) {
|
|
53
|
+
for (const candidate of candidates) {
|
|
54
|
+
try {
|
|
55
|
+
if (fs.existsSync(candidate))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch { /* unreadable path — treat as absent */ }
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/** Whether the Cursor IDE appears installed (app binaries or app data, not our hook file). */
|
|
63
|
+
function isCursorAppInstalled() {
|
|
64
|
+
const home = os.homedir();
|
|
65
|
+
const candidates = [
|
|
66
|
+
// Populated by the app itself (extensions, chats, state) — not by our installer.
|
|
67
|
+
path.join(home, '.cursor', 'extensions'),
|
|
68
|
+
];
|
|
69
|
+
if (process.platform === 'win32') {
|
|
70
|
+
if (process.env.LOCALAPPDATA)
|
|
71
|
+
candidates.push(path.join(process.env.LOCALAPPDATA, 'Programs', 'cursor'));
|
|
72
|
+
if (process.env.APPDATA)
|
|
73
|
+
candidates.push(path.join(process.env.APPDATA, 'Cursor'));
|
|
74
|
+
}
|
|
75
|
+
else if (process.platform === 'darwin') {
|
|
76
|
+
candidates.push('/Applications/Cursor.app');
|
|
77
|
+
candidates.push(path.join(home, 'Library', 'Application Support', 'Cursor'));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
candidates.push(path.join(home, '.config', 'Cursor'));
|
|
81
|
+
}
|
|
82
|
+
return anyExists(candidates);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether any client of the Claude-format hook family appears installed:
|
|
86
|
+
* Claude Code, VS Code (Copilot agent mode), or GitHub Copilot CLI — one
|
|
87
|
+
* `~/.claude/settings.json` hook file covers all three.
|
|
88
|
+
*/
|
|
89
|
+
function isClaudeFamilyAppInstalled() {
|
|
90
|
+
const home = os.homedir();
|
|
91
|
+
const candidates = [
|
|
92
|
+
path.join(home, '.claude.json'), // Claude Code state file
|
|
93
|
+
path.join(home, '.claude', 'projects'), // Claude Code session data
|
|
94
|
+
path.join(home, '.copilot'), // GitHub Copilot CLI
|
|
95
|
+
];
|
|
96
|
+
if (process.platform === 'win32') {
|
|
97
|
+
if (process.env.APPDATA)
|
|
98
|
+
candidates.push(path.join(process.env.APPDATA, 'Code'));
|
|
99
|
+
}
|
|
100
|
+
else if (process.platform === 'darwin') {
|
|
101
|
+
candidates.push(path.join(home, 'Library', 'Application Support', 'Code'));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
candidates.push(path.join(home, '.config', 'Code'));
|
|
105
|
+
}
|
|
106
|
+
return anyExists(candidates);
|
|
107
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -13,7 +13,7 @@ async function doctorCommand(args, config) {
|
|
|
13
13
|
const pingUrl = `${apiUrl}/api/health/ping`;
|
|
14
14
|
const rootUrl = `${apiUrl}/`;
|
|
15
15
|
console.log('');
|
|
16
|
-
console.log('
|
|
16
|
+
console.log('FullCourtDefense outbound diagnostic');
|
|
17
17
|
console.log(`Target: ${apiUrl}`);
|
|
18
18
|
console.log('');
|
|
19
19
|
const started = Date.now();
|
package/dist/commands/hook.js
CHANGED
|
@@ -46,6 +46,13 @@ const localSafetySnapshot_1 = require("../localSafetySnapshot");
|
|
|
46
46
|
const taintLedger_1 = require("./taintLedger");
|
|
47
47
|
const notify_1 = require("../notify");
|
|
48
48
|
const DEBUG_LOG = path.join(os.homedir(), '.fullcourtdefense-hook.log');
|
|
49
|
+
/**
|
|
50
|
+
* Verbose diagnostics (FCD_HOOK_DEBUG=1) additionally log content previews
|
|
51
|
+
* (raw payload + prompt text snippets). Default logging keeps operational
|
|
52
|
+
* metadata only — user prompt content must never sit in a plaintext file on
|
|
53
|
+
* a customer laptop unless support explicitly opts in.
|
|
54
|
+
*/
|
|
55
|
+
const HOOK_DEBUG_VERBOSE = process.env.FCD_HOOK_DEBUG === '1';
|
|
49
56
|
/** Append a diagnostic line so we can see exactly what Cursor invoked + the verdict. */
|
|
50
57
|
function dbg(obj) {
|
|
51
58
|
try {
|
|
@@ -552,7 +559,7 @@ async function hookCommand(args, config) {
|
|
|
552
559
|
event = inferEvent(args.event, payload);
|
|
553
560
|
}
|
|
554
561
|
dbg({ phase: 'invoke', event, argEvent: args.event, format: hookFormat, client, pid: process.pid,
|
|
555
|
-
isTTY, rawLen: raw.length, rawPreview: raw.slice(0, 300), stdinErr, parseErr,
|
|
562
|
+
isTTY, rawLen: raw.length, ...(HOOK_DEBUG_VERBOSE ? { rawPreview: raw.slice(0, 300) } : {}), stdinErr, parseErr,
|
|
556
563
|
argv: process.argv.slice(2), payloadKeys: Object.keys(payload) });
|
|
557
564
|
// Emit the verdict in the shape THIS client expects, then exit.
|
|
558
565
|
// Cursor: beforeSubmitPrompt blocks via { continue: false }; execution hooks
|
|
@@ -644,7 +651,7 @@ async function hookCommand(args, config) {
|
|
|
644
651
|
// --- Shield text analysis for prompts (developer chat) ---
|
|
645
652
|
if (event === 'prompt') {
|
|
646
653
|
const text = extractPromptText(payload).trim();
|
|
647
|
-
dbg({ phase: 'prompt_text', textLen: text.length, textPreview: text.slice(0, 300) });
|
|
654
|
+
dbg({ phase: 'prompt_text', textLen: text.length, ...(HOOK_DEBUG_VERBOSE ? { textPreview: text.slice(0, 300) } : {}) });
|
|
648
655
|
if (!text)
|
|
649
656
|
respond(false);
|
|
650
657
|
// Record the developer's prompt so taint-tracking can later tell whether a
|
package/dist/commands/onboard.js
CHANGED
|
@@ -50,6 +50,7 @@ const posixShellGuard_1 = require("./posixShellGuard");
|
|
|
50
50
|
const notify_1 = require("../notify");
|
|
51
51
|
const daemon_1 = require("./daemon");
|
|
52
52
|
const machineIdentity_1 = require("../machineIdentity");
|
|
53
|
+
const appDetection_1 = require("../appDetection");
|
|
53
54
|
const integrity_1 = require("../integrity");
|
|
54
55
|
const telemetry_1 = require("../telemetry");
|
|
55
56
|
const onboardingJournal_1 = require("./onboardingJournal");
|
|
@@ -123,12 +124,24 @@ function checkCursorHooks() {
|
|
|
123
124
|
const hooksFile = path.join(os.homedir(), '.cursor', 'hooks.json');
|
|
124
125
|
const text = readTextSafe(hooksFile);
|
|
125
126
|
const ok = text.includes('fcd-managed') || /fullcourtdefense|botguard/i.test(text);
|
|
127
|
+
if (!ok && !(0, appDetection_1.isCursorAppInstalled)()) {
|
|
128
|
+
// Nothing to hook — a machine without Cursor must not fail onboarding.
|
|
129
|
+
return { label: 'Cursor runtime hooks', ok: true, optional: true, detail: 'Cursor is not installed on this machine — hooks install automatically when it is' };
|
|
130
|
+
}
|
|
126
131
|
return { label: 'Cursor runtime hooks', ok, detail: ok ? hooksFile : 'not installed (install-cursor-hook)' };
|
|
127
132
|
}
|
|
128
133
|
function checkClaudeHooks() {
|
|
129
134
|
const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
|
|
130
135
|
const text = readTextSafe(settingsFile);
|
|
131
136
|
const ok = /fullcourtdefense|fcd-managed|botguard/i.test(text);
|
|
137
|
+
if (!ok && !(0, appDetection_1.isClaudeFamilyAppInstalled)()) {
|
|
138
|
+
return {
|
|
139
|
+
label: 'Claude Code / VS Code / Copilot CLI hooks',
|
|
140
|
+
ok: true,
|
|
141
|
+
optional: true,
|
|
142
|
+
detail: 'no Claude Code / VS Code / Copilot CLI found on this machine — hooks install automatically when one is',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
132
145
|
return {
|
|
133
146
|
label: 'Claude Code / VS Code / Copilot CLI hooks',
|
|
134
147
|
ok,
|
|
@@ -213,6 +226,17 @@ async function onboardCommand(args, config) {
|
|
|
213
226
|
};
|
|
214
227
|
console.log(`\n${BOLD}\x1b[36mFullCourtDefense — machine onboarding${RESET}`);
|
|
215
228
|
console.log(`${DIM}Connectivity -> enrollment -> protection -> verification. One command.${RESET}\n`);
|
|
229
|
+
// Onboarding is a persisted transaction: completed steps are reused on any
|
|
230
|
+
// re-run, so `--resume` is an explicit alias that also surfaces the journal.
|
|
231
|
+
const resume = args.resume === 'true' || args.resume === '';
|
|
232
|
+
if (resume) {
|
|
233
|
+
const previouslyCompleted = Object.entries(journal.steps)
|
|
234
|
+
.filter(([, step]) => step.status === 'completed')
|
|
235
|
+
.map(([name]) => name);
|
|
236
|
+
console.log(previouslyCompleted.length > 0
|
|
237
|
+
? `${DIM}Resuming onboarding — reusing completed steps: ${previouslyCompleted.join(', ')} (journal: ${journalFile}).${RESET}\n`
|
|
238
|
+
: `${DIM}Resuming onboarding — no completed steps found in the journal; running from the start.${RESET}\n`);
|
|
239
|
+
}
|
|
216
240
|
const creds = (0, config_1.resolveCliCredentials)(config, { apiUrl: args.apiUrl });
|
|
217
241
|
const apiUrl = creds.apiUrl;
|
|
218
242
|
const token = (args.token || process.env.FCD_ENROLL_TOKEN || '').trim();
|
|
@@ -419,7 +443,7 @@ async function onboardCommand(args, config) {
|
|
|
419
443
|
const dashboard = (0, notify_1.consoleUrl)('/agent-security/users?view=desktop');
|
|
420
444
|
console.log('');
|
|
421
445
|
if (requiredFailures.length === 0) {
|
|
422
|
-
console.log(`${GREEN}${BOLD}This machine is protected.${RESET} It reports to your org's AI Fleet and enforces your policies
|
|
446
|
+
console.log(`${GREEN}${BOLD}This machine is protected.${RESET} It reports to your org's AI Fleet and enforces your policies in blocking mode — dangerous actions are stopped, not just logged.`);
|
|
423
447
|
console.log(`${DIM}Fleet view: ${dashboard}${RESET}`);
|
|
424
448
|
console.log(`${DIM}Restart your AI clients (Cursor, Claude, VS Code, …) so they pick up the wrapped configs.${RESET}`);
|
|
425
449
|
mark('verification', 'completed');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BotGuardConfig } from '../config';
|
|
2
|
+
import { ProtectAllArgs } from './mcpGateway';
|
|
3
|
+
export interface UninstallAllArgs extends ProtectAllArgs {
|
|
4
|
+
/** 'true' => also delete local state/config files (~/.fullcourtdefense*). */
|
|
5
|
+
purge?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `fullcourtdefense uninstall` — ONE command that reverses `onboard`/`install-all`:
|
|
9
|
+
* daemon autostart, self-heal schedule, MCP gateway wraps, IDE hooks, terminal
|
|
10
|
+
* guards, and the daily discovery schedule. Each step is best-effort so a
|
|
11
|
+
* single failure never leaves the rest of the machine half-uninstalled.
|
|
12
|
+
*
|
|
13
|
+
* Machine credentials (~/.fullcourtdefense.yml) and local state are KEPT by
|
|
14
|
+
* default so the machine can re-onboard without a new enrollment token; pass
|
|
15
|
+
* `--purge true` to delete them too. The fleet record in the dashboard is not
|
|
16
|
+
* touched — deprovision it from the console (Machines → machine → Deprovision).
|
|
17
|
+
*/
|
|
18
|
+
export declare function uninstallAllCommand(args: UninstallAllArgs, config: BotGuardConfig): Promise<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.uninstallAllCommand = uninstallAllCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const daemon_1 = require("./daemon");
|
|
41
|
+
const autoProtect_1 = require("./autoProtect");
|
|
42
|
+
const mcpGateway_1 = require("./mcpGateway");
|
|
43
|
+
const discoverSchedule_1 = require("./discoverSchedule");
|
|
44
|
+
const installCursorHook_1 = require("./installCursorHook");
|
|
45
|
+
const installClaudeHook_1 = require("./installClaudeHook");
|
|
46
|
+
const shellGuard_1 = require("./shellGuard");
|
|
47
|
+
const posixShellGuard_1 = require("./posixShellGuard");
|
|
48
|
+
const cmdGuard_1 = require("./cmdGuard");
|
|
49
|
+
const BOLD = '\x1b[1m';
|
|
50
|
+
const GREEN = '\x1b[32m';
|
|
51
|
+
const YELLOW = '\x1b[33m';
|
|
52
|
+
const DIM = '\x1b[2m';
|
|
53
|
+
const RESET = '\x1b[0m';
|
|
54
|
+
/**
|
|
55
|
+
* `fullcourtdefense uninstall` — ONE command that reverses `onboard`/`install-all`:
|
|
56
|
+
* daemon autostart, self-heal schedule, MCP gateway wraps, IDE hooks, terminal
|
|
57
|
+
* guards, and the daily discovery schedule. Each step is best-effort so a
|
|
58
|
+
* single failure never leaves the rest of the machine half-uninstalled.
|
|
59
|
+
*
|
|
60
|
+
* Machine credentials (~/.fullcourtdefense.yml) and local state are KEPT by
|
|
61
|
+
* default so the machine can re-onboard without a new enrollment token; pass
|
|
62
|
+
* `--purge true` to delete them too. The fleet record in the dashboard is not
|
|
63
|
+
* touched — deprovision it from the console (Machines → machine → Deprovision).
|
|
64
|
+
*/
|
|
65
|
+
async function uninstallAllCommand(args, config) {
|
|
66
|
+
console.log(`\n${BOLD}\x1b[36mFullCourtDefense — uninstall local protection${RESET}`);
|
|
67
|
+
console.log(`${DIM}Daemon -> self-heal schedule -> MCP gateways -> IDE hooks -> terminal guards -> discovery schedule.${RESET}\n`);
|
|
68
|
+
const steps = [
|
|
69
|
+
{ label: 'Resident daemon + autostart', run: () => (0, daemon_1.daemonCommand)({ ...args, uninstall: 'true', quiet: 'true' }, config) },
|
|
70
|
+
{ label: 'Self-healing protect schedule', run: () => (0, autoProtect_1.autoProtectCommand)({ ...args, uninstall: 'true' }, config) },
|
|
71
|
+
{ label: 'MCP gateway wraps (restore original server commands)', run: () => (0, mcpGateway_1.unprotectAllCommand)({ ...args, clients: args.clients || 'all' }, config) },
|
|
72
|
+
{ label: 'Cursor runtime hooks', run: () => (0, installCursorHook_1.uninstallCursorHookCommand)({}) },
|
|
73
|
+
{ label: 'Claude Code / VS Code / Copilot CLI hooks', run: () => (0, installClaudeHook_1.uninstallClaudeHookCommand)({}) },
|
|
74
|
+
...(process.platform === 'win32'
|
|
75
|
+
? [
|
|
76
|
+
{ label: 'PowerShell shell guard', run: () => (0, shellGuard_1.uninstallShellGuardCommand)() },
|
|
77
|
+
{ label: 'cmd.exe shell guard', run: () => (0, cmdGuard_1.uninstallCmdGuardCommand)() },
|
|
78
|
+
]
|
|
79
|
+
: [
|
|
80
|
+
{ label: 'bash/zsh shell guard', run: () => (0, posixShellGuard_1.uninstallPosixShellGuardCommand)() },
|
|
81
|
+
]),
|
|
82
|
+
{ label: 'Daily discovery schedule', run: () => (0, discoverSchedule_1.uninstallDailyDiscoverSchedule)() },
|
|
83
|
+
];
|
|
84
|
+
const failures = [];
|
|
85
|
+
for (const step of steps) {
|
|
86
|
+
console.log(`${BOLD}› ${step.label}${RESET}`);
|
|
87
|
+
try {
|
|
88
|
+
await step.run();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
failures.push(step.label);
|
|
92
|
+
console.log(`${YELLOW} ⚠ ${step.label} failed: ${error instanceof Error ? error.message : String(error)} — continuing.${RESET}`);
|
|
93
|
+
}
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
if (args.purge === 'true') {
|
|
97
|
+
console.log(`${BOLD}› Local state files${RESET}`);
|
|
98
|
+
const home = os.homedir();
|
|
99
|
+
const targets = [
|
|
100
|
+
path.join(home, '.fullcourtdefense.yml'),
|
|
101
|
+
path.join(home, '.fullcourtdefense-spool.jsonl'),
|
|
102
|
+
path.join(home, '.fullcourtdefense-runtime.json'),
|
|
103
|
+
path.join(home, '.fullcourtdefense-hook.log'),
|
|
104
|
+
path.join(home, '.fullcourtdefense'),
|
|
105
|
+
];
|
|
106
|
+
for (const target of targets) {
|
|
107
|
+
try {
|
|
108
|
+
if (!fs.existsSync(target))
|
|
109
|
+
continue;
|
|
110
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
111
|
+
console.log(`${DIM} removed ${target}${RESET}`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
failures.push(target);
|
|
115
|
+
console.log(`${YELLOW} ⚠ could not remove ${target}: ${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
}
|
|
120
|
+
if (failures.length === 0) {
|
|
121
|
+
console.log(`${GREEN}${BOLD}Local protection removed.${RESET}`);
|
|
122
|
+
if (args.purge !== 'true') {
|
|
123
|
+
console.log(`${DIM}Machine credentials were kept (re-run \`fullcourtdefense onboard\` to re-protect). Use --purge true to delete all local state.${RESET}`);
|
|
124
|
+
}
|
|
125
|
+
console.log(`${DIM}The machine still appears in the fleet console until an admin deprovisions it (Machines → machine → Deprovision).${RESET}`);
|
|
126
|
+
console.log(`${DIM}Restart your AI clients (Cursor, Claude, VS Code, …) so they reload the restored configs.${RESET}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(`${YELLOW}${BOLD}Uninstall finished with ${failures.length} step(s) needing attention:${RESET} ${failures.join(', ')}`);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,7 @@ const installClaudeHook_1 = require("./commands/installClaudeHook");
|
|
|
50
50
|
const mcpGateway_1 = require("./commands/mcpGateway");
|
|
51
51
|
const installAll_1 = require("./commands/installAll");
|
|
52
52
|
const onboard_1 = require("./commands/onboard");
|
|
53
|
+
const uninstallAll_1 = require("./commands/uninstallAll");
|
|
53
54
|
const autoProtect_1 = require("./commands/autoProtect");
|
|
54
55
|
const daemon_1 = require("./commands/daemon");
|
|
55
56
|
const desktopChatGuard_1 = require("./commands/desktopChatGuard");
|
|
@@ -129,6 +130,14 @@ function printHelp() {
|
|
|
129
130
|
connectivity check -> enroll (--token <fleet-token> or
|
|
130
131
|
FCD_ENROLL_TOKEN) -> install-all -> verification checklist.
|
|
131
132
|
Exits non-zero if a required protection surface failed.
|
|
133
|
+
Safe to re-run: completed steps are reused automatically
|
|
134
|
+
(--repair true re-runs them; --dry-run true previews;
|
|
135
|
+
--json true prints the transaction as JSON).
|
|
136
|
+
uninstall ONE command that reverses onboard/install-all on this machine:
|
|
137
|
+
daemon, self-heal schedule, MCP gateway wraps, IDE hooks,
|
|
138
|
+
terminal guards, and the daily discovery schedule. Keeps
|
|
139
|
+
machine credentials for easy re-onboarding; --purge true
|
|
140
|
+
deletes all local state too.
|
|
132
141
|
doctor First step. Checks outbound HTTPS access to FullCourtDefense.
|
|
133
142
|
login Enrolls this machine with a fleet enrollment token and saves
|
|
134
143
|
per-machine Shield credentials to ~/.fullcourtdefense.yml.
|
|
@@ -203,10 +212,15 @@ function printHelp() {
|
|
|
203
212
|
install-vscode-mcp-gateway
|
|
204
213
|
VS Code only — .vscode/mcp.json or user settings.
|
|
205
214
|
mcp-gateway
|
|
206
|
-
Runs a local MCP gateway that checks
|
|
215
|
+
Runs a local MCP gateway that checks FullCourtDefense runtime/action
|
|
207
216
|
policies before forwarding tool calls to a real MCP server.
|
|
208
217
|
hook Internal: invoked by Cursor per agent event (reads stdin JSON,
|
|
209
218
|
returns an allow/deny verdict). Not run by hand.
|
|
219
|
+
desktop-chat-guard
|
|
220
|
+
Windows-only advisory guard for Claude Desktop's native chat
|
|
221
|
+
(clipboard/secret warnings). Usually started by the daemon.
|
|
222
|
+
flush-spool Manually drain locally spooled telemetry events to the backend
|
|
223
|
+
(they flush automatically; useful after long offline periods).
|
|
210
224
|
credits Shows hosted scan credits for CI/CD API-key scans.
|
|
211
225
|
init Creates a starter .fullcourtdefense.yml config file.
|
|
212
226
|
|
|
@@ -300,7 +314,7 @@ function printHelp() {
|
|
|
300
314
|
--fail-on <severity> agent-ci fail threshold: critical, high, medium, low
|
|
301
315
|
--require-gateway agent-ci requires risky MCP servers to use FCD gateway
|
|
302
316
|
--require-approval-for agent-ci risky controls list, e.g. payment,delete,shell,secrets
|
|
303
|
-
--require-upload agent-ci fails if evidence cannot upload to
|
|
317
|
+
--require-upload agent-ci fails if evidence cannot upload to the FullCourtDefense UI
|
|
304
318
|
--changed-only agent-ci limits findings to changed files
|
|
305
319
|
--changed-files <path> file or comma/newline list of changed files for agent-ci
|
|
306
320
|
--method <GET|POST> Local endpoint method
|
|
@@ -362,6 +376,8 @@ function printHelp() {
|
|
|
362
376
|
$ fullcourtdefense install-all --auto-protect true --upload
|
|
363
377
|
$ fullcourtdefense onboard --token <fleet-enrollment-token> --resume
|
|
364
378
|
$ fullcourtdefense onboard --dry-run true --json true
|
|
379
|
+
$ fullcourtdefense uninstall
|
|
380
|
+
$ fullcourtdefense uninstall --purge true
|
|
365
381
|
$ fullcourtdefense discover --surface all --upload --schedule logon
|
|
366
382
|
$ fullcourtdefense install-mcp-gateway --clients cursor,codex --mcp-command npm --mcp-args "run mcp"
|
|
367
383
|
$ fullcourtdefense install-codex-mcp-gateway --mcp-command npm --mcp-args "run mcp"
|
|
@@ -645,6 +661,17 @@ async function main() {
|
|
|
645
661
|
await (0, installAll_1.installAllCommand)(args, config);
|
|
646
662
|
break;
|
|
647
663
|
}
|
|
664
|
+
case 'uninstall':
|
|
665
|
+
case 'uninstall-all': {
|
|
666
|
+
const args = {
|
|
667
|
+
...buildGatewayArgs(),
|
|
668
|
+
clients: flags.clients,
|
|
669
|
+
config: flags.config,
|
|
670
|
+
purge: flags.purge,
|
|
671
|
+
};
|
|
672
|
+
await (0, uninstallAll_1.uninstallAllCommand)(args, config);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
648
675
|
case 'onboard': {
|
|
649
676
|
const args = {
|
|
650
677
|
...buildInstallArgs(),
|
package/dist/integrity.js
CHANGED
|
@@ -41,6 +41,7 @@ const path = __importStar(require("path"));
|
|
|
41
41
|
const installClaudeHook_1 = require("./commands/installClaudeHook");
|
|
42
42
|
const installCursorHook_1 = require("./commands/installCursorHook");
|
|
43
43
|
const mcpGateway_1 = require("./commands/mcpGateway");
|
|
44
|
+
const appDetection_1 = require("./appDetection");
|
|
44
45
|
function readText(file) {
|
|
45
46
|
try {
|
|
46
47
|
return fs.readFileSync(file, 'utf8');
|
|
@@ -93,14 +94,22 @@ function getLocalIntegrityReport(options = {}) {
|
|
|
93
94
|
}
|
|
94
95
|
if (protectedMcpConfigs < discoveredMcpConfigs)
|
|
95
96
|
reasons.push('mcp_gateway_missing');
|
|
97
|
+
// A missing hook only counts as damage when the app it protects is actually
|
|
98
|
+
// installed — a VS Code-only machine without Cursor (or vice versa) must not
|
|
99
|
+
// report broken integrity forever. Stale-path entries remain failures either
|
|
100
|
+
// way: the hook file exists but points at a dead CLI path.
|
|
96
101
|
const cursorHooks = (0, installCursorHook_1.inspectCursorManagedHooks)();
|
|
97
|
-
if (!cursorHooks.existed || cursorHooks.managedEntries === 0)
|
|
98
|
-
|
|
102
|
+
if (!cursorHooks.existed || cursorHooks.managedEntries === 0) {
|
|
103
|
+
if ((0, appDetection_1.isCursorAppInstalled)())
|
|
104
|
+
reasons.push('cursor_hook_missing');
|
|
105
|
+
}
|
|
99
106
|
else if (cursorHooks.staleEntries > 0)
|
|
100
107
|
reasons.push('cursor_hook_stale_path');
|
|
101
108
|
const claudeHooks = (0, installClaudeHook_1.inspectClaudeManagedHooks)();
|
|
102
|
-
if (!claudeHooks.existed || claudeHooks.managedEntries === 0)
|
|
103
|
-
|
|
109
|
+
if (!claudeHooks.existed || claudeHooks.managedEntries === 0) {
|
|
110
|
+
if ((0, appDetection_1.isClaudeFamilyAppInstalled)())
|
|
111
|
+
reasons.push('claude_hook_missing');
|
|
112
|
+
}
|
|
104
113
|
else if (claudeHooks.staleEntries > 0)
|
|
105
114
|
reasons.push('claude_hook_stale_path');
|
|
106
115
|
const daemonRunning = isDaemonRunning();
|
package/dist/version.json
CHANGED