fullcourtdefense-cli 1.0.2 → 1.1.1

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.
@@ -68,20 +68,20 @@ async function initCommand() {
68
68
  const categories = categoriesRaw === 'all'
69
69
  ? '[jailbreak, prompt_injection, data_extraction, role_manipulation, encoding_attack, social_engineering]'
70
70
  : `[${categoriesRaw}]`;
71
- const content = `# FullCourtDefense CLI Configuration
72
- # Docs: https://fullcourtdefense.ai/docs/cli
73
-
74
- apiKey: \${BOTGUARD_API_KEY}
75
- apiUrl: https://api.fullcourtdefense.ai
76
- shieldId: ${shieldId}
77
- ${shieldKey ? `shieldKey: ${shieldKey}\n` : ''}
78
-
79
- scan:
80
- endpoint: ${endpoint}
81
- description: "${description}"
82
- categories: ${categories}
83
- failThreshold: ${threshold}
84
- format: table
71
+ const content = `# FullCourtDefense CLI Configuration
72
+ # Docs: https://fullcourtdefense.ai/docs/cli
73
+
74
+ apiKey: \${FULLCOURTDEFENSE_API_KEY}
75
+ apiUrl: https://api.fullcourtdefense.ai
76
+ shieldId: ${shieldId}
77
+ ${shieldKey ? `shieldKey: ${shieldKey}\n` : ''}
78
+
79
+ scan:
80
+ endpoint: ${endpoint}
81
+ description: "${description}"
82
+ categories: ${categories}
83
+ failThreshold: ${threshold}
84
+ format: table
85
85
  `;
86
86
  fs.writeFileSync(target, content, 'utf-8');
87
87
  console.log('');
@@ -0,0 +1,12 @@
1
+ import { BotGuardConfig } from '../config';
2
+ export interface InstallHookArgs {
3
+ project?: string;
4
+ shieldId?: string;
5
+ shieldKey?: string;
6
+ apiUrl?: string;
7
+ shadow?: string;
8
+ failClosed?: string;
9
+ events?: string;
10
+ }
11
+ export declare function installCursorHookCommand(args: InstallHookArgs, config: BotGuardConfig): Promise<void>;
12
+ export declare function uninstallCursorHookCommand(args: InstallHookArgs): Promise<void>;
@@ -0,0 +1,186 @@
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.installCursorHookCommand = installCursorHookCommand;
37
+ exports.uninstallCursorHookCommand = uninstallCursorHookCommand;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const COLOR = {
42
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
43
+ red: '\x1b[31m', yellow: '\x1b[33m', green: '\x1b[32m', cyan: '\x1b[36m', gray: '\x1b[90m',
44
+ };
45
+ // Stable marker embedded in every command we write, so uninstall/re-install can
46
+ // find our entries regardless of the install path (npm global vs local dev).
47
+ const MANAGED_MARKER = '--fcd-managed true';
48
+ const MANAGED_TAG = 'fullcourtdefense';
49
+ const EVENT_MAP = {
50
+ prompt: { hookKey: 'beforeSubmitPrompt', flag: 'prompt', failClosedDefault: false },
51
+ shell: { hookKey: 'beforeShellExecution', flag: 'shell', failClosedDefault: true },
52
+ mcp: { hookKey: 'beforeMCPExecution', flag: 'mcp', failClosedDefault: true },
53
+ file: { hookKey: 'afterFileEdit', flag: 'file', failClosedDefault: false },
54
+ read: { hookKey: 'beforeReadFile', flag: 'read', failClosedDefault: false },
55
+ };
56
+ /** Build the absolute, shell-agnostic command that invokes this CLI's `hook`. */
57
+ function buildHookCommand(flag, opts) {
58
+ const nodeExe = process.execPath; // real node binary (handles Windows/Linux/mac)
59
+ const scriptPath = process.argv[1] || path.join(__dirname, '..', 'index.js');
60
+ const q = (s) => (/\s/.test(s) ? `"${s}"` : s);
61
+ let cmd = `${q(nodeExe)} ${q(scriptPath)} hook --event ${flag}`;
62
+ if (opts.shadow)
63
+ cmd += ' --shadow true';
64
+ if (opts.failClosed)
65
+ cmd += ' --fail-closed true';
66
+ cmd += ` ${MANAGED_MARKER}`;
67
+ return cmd;
68
+ }
69
+ function hooksJsonPath(projectScope) {
70
+ return projectScope
71
+ ? path.join(process.cwd(), '.cursor', 'hooks.json')
72
+ : path.join(os.homedir(), '.cursor', 'hooks.json');
73
+ }
74
+ function readHooksJson(file) {
75
+ if (!fs.existsSync(file))
76
+ return { version: 1, hooks: {} };
77
+ try {
78
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
79
+ if (!parsed || typeof parsed !== 'object')
80
+ return { version: 1, hooks: {} };
81
+ parsed.version = parsed.version || 1;
82
+ parsed.hooks = parsed.hooks && typeof parsed.hooks === 'object' ? parsed.hooks : {};
83
+ return parsed;
84
+ }
85
+ catch {
86
+ return { version: 1, hooks: {} };
87
+ }
88
+ }
89
+ function isManaged(entry) {
90
+ return typeof entry?.command === 'string'
91
+ && (entry.command.includes(MANAGED_MARKER) || entry.command.includes(MANAGED_TAG));
92
+ }
93
+ /** Save the home-level Shield credentials so the hook can find them machine-wide. */
94
+ function writeHomeShield(input) {
95
+ if (!input.shieldId && !input.shieldKey && !input.apiUrl)
96
+ return undefined;
97
+ const target = path.join(os.homedir(), '.fullcourtdefense.yml');
98
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '# FullCourtDefense CLI Configuration\n';
99
+ const lines = existing.split(/\r?\n/);
100
+ const setKey = (key, value) => {
101
+ if (!value)
102
+ return;
103
+ const idx = lines.findIndex((l) => new RegExp(`^\\s*${key}\\s*:`).test(l));
104
+ const next = `${key}: ${value}`;
105
+ if (idx >= 0)
106
+ lines[idx] = next;
107
+ else
108
+ lines.push(next);
109
+ };
110
+ setKey('shieldId', input.shieldId);
111
+ setKey('shieldKey', input.shieldKey);
112
+ setKey('apiUrl', input.apiUrl);
113
+ fs.writeFileSync(target, lines.filter((l, i, a) => !(l === '' && a[i - 1] === '')).join('\n').trim() + '\n', 'utf8');
114
+ return target;
115
+ }
116
+ async function installCursorHookCommand(args, config) {
117
+ const projectScope = args.project === 'true';
118
+ const shadow = args.shadow === 'true';
119
+ const file = hooksJsonPath(projectScope);
120
+ const requested = (args.events || 'prompt,shell,mcp')
121
+ .split(',').map((e) => e.trim().toLowerCase()).filter(Boolean);
122
+ const events = requested.filter((e) => EVENT_MAP[e]);
123
+ if (events.length === 0) {
124
+ console.error(`${COLOR.red}No valid events. Choose from: ${Object.keys(EVENT_MAP).join(', ')}${COLOR.reset}`);
125
+ process.exit(1);
126
+ }
127
+ // Persist creds machine-wide (home) so the runtime hook resolves them.
128
+ const shieldId = args.shieldId || config.shieldId;
129
+ const shieldKey = args.shieldKey || config.shieldKey;
130
+ const apiUrl = args.apiUrl || config.apiUrl;
131
+ const credFile = writeHomeShield({ shieldId, shieldKey, apiUrl });
132
+ const json = readHooksJson(file);
133
+ for (const e of events) {
134
+ const { hookKey, flag, failClosedDefault } = EVENT_MAP[e];
135
+ const failClosed = args.failClosed !== undefined ? args.failClosed === 'true' : failClosedDefault;
136
+ const entry = {
137
+ command: buildHookCommand(flag, { shadow, failClosed }),
138
+ timeout: 10,
139
+ };
140
+ if (failClosed)
141
+ entry.failClosed = true;
142
+ const list = Array.isArray(json.hooks[hookKey]) ? json.hooks[hookKey] : [];
143
+ // Replace any prior FullCourtDefense-managed entry; preserve the user's other hooks.
144
+ const preserved = list.filter((x) => !isManaged(x));
145
+ json.hooks[hookKey] = [...preserved, entry];
146
+ }
147
+ fs.mkdirSync(path.dirname(file), { recursive: true });
148
+ fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf8');
149
+ console.log('');
150
+ console.log(`${COLOR.green}${COLOR.bold}FullCourtDefense Cursor hook installed.${COLOR.reset}`);
151
+ console.log(`${COLOR.gray}Scope:${COLOR.reset} ${projectScope ? 'project (.cursor/hooks.json)' : 'machine-wide (~/.cursor/hooks.json)'}`);
152
+ console.log(`${COLOR.gray}File:${COLOR.reset} ${file}`);
153
+ console.log(`${COLOR.gray}Events:${COLOR.reset} ${events.map((e) => EVENT_MAP[e].hookKey).join(', ')}`);
154
+ if (shadow)
155
+ console.log(`${COLOR.yellow}Mode: SHADOW (monitor only — nothing is blocked).${COLOR.reset}`);
156
+ if (credFile)
157
+ console.log(`${COLOR.gray}Creds:${COLOR.reset} ${credFile}`);
158
+ if (!shieldId) {
159
+ console.log('');
160
+ console.log(`${COLOR.yellow}No Shield ID set.${COLOR.reset} Run ${COLOR.bold}fullcourtdefense configure${COLOR.reset} or pass ${COLOR.bold}--shield-id${COLOR.reset}, then re-run install.`);
161
+ }
162
+ console.log('');
163
+ console.log(`${COLOR.gray}Restart Cursor (or it will hot-reload hooks.json). Verify in Cursor → Settings → Hooks.${COLOR.reset}`);
164
+ console.log(`${COLOR.gray}Remove with:${COLOR.reset} fullcourtdefense uninstall-cursor-hook${projectScope ? ' --project true' : ''}`);
165
+ }
166
+ async function uninstallCursorHookCommand(args) {
167
+ const projectScope = args.project === 'true';
168
+ const file = hooksJsonPath(projectScope);
169
+ if (!fs.existsSync(file)) {
170
+ console.log(`${COLOR.yellow}No hooks.json found at ${file}. Nothing to remove.${COLOR.reset}`);
171
+ return;
172
+ }
173
+ const json = readHooksJson(file);
174
+ let removed = 0;
175
+ for (const key of Object.keys(json.hooks)) {
176
+ const list = Array.isArray(json.hooks[key]) ? json.hooks[key] : [];
177
+ const kept = list.filter((x) => !isManaged(x));
178
+ removed += list.length - kept.length;
179
+ if (kept.length)
180
+ json.hooks[key] = kept;
181
+ else
182
+ delete json.hooks[key];
183
+ }
184
+ fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf8');
185
+ console.log(`${COLOR.green}Removed ${removed} FullCourtDefense hook entr${removed === 1 ? 'y' : 'ies'} from ${file}.${COLOR.reset}`);
186
+ }
@@ -37,5 +37,8 @@ export interface LocalScanArgs {
37
37
  configShieldKey?: string;
38
38
  configApiUrl?: string;
39
39
  apiUrl?: string;
40
+ open?: string;
41
+ noOpen?: string;
42
+ openUi?: string;
40
43
  }
41
44
  export declare function localScanCommand(args: LocalScanArgs): Promise<void>;