fullcourtdefense-cli 1.5.0 → 1.5.2

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.
@@ -61,6 +61,7 @@ export declare function installMcpGatewayCommand(args: InstallMcpGatewayArgs, co
61
61
  export interface ProtectAllArgs extends McpGatewayArgs {
62
62
  dryRun?: string;
63
63
  config?: string;
64
+ clients?: string;
64
65
  }
65
66
  export declare function protectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
66
67
  export declare function unprotectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
@@ -942,6 +942,24 @@ async function installMcpGatewayCommand(args, config) {
942
942
  console.log('Add --upload to refresh AI Fleet after install.');
943
943
  }
944
944
  }
945
+ /** Map a high-level client to the discovery clientKey(s) its config files use. */
946
+ const AGENT_TO_CLIENT_KEYS = {
947
+ cursor: ['cursor'],
948
+ 'claude-code': ['claude_code'],
949
+ 'claude-desktop': ['claude_desktop'],
950
+ codex: ['codex'],
951
+ 'gemini-cli': ['gemini_cli'],
952
+ windsurf: ['windsurf'],
953
+ vscode: ['vscode'],
954
+ };
955
+ /** Resolve the optional --clients filter into a set of clientKeys, or undefined for "all". */
956
+ function resolveProtectClientKeys(clients) {
957
+ const raw = (clients || '').trim().toLowerCase();
958
+ if (!raw || raw === 'auto' || raw === 'all')
959
+ return undefined; // no filter — process every client
960
+ const selection = parseClientList(clients);
961
+ return new Set(selection.flatMap(client => AGENT_TO_CLIENT_KEYS[client] || []));
962
+ }
945
963
  const CLIENT_KEY_TO_AGENT = {
946
964
  cursor: 'cursor',
947
965
  claude_code: 'claude-code',
@@ -1035,7 +1053,7 @@ function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1035
1053
  }
1036
1054
  const downstreamCommand = entry.command;
1037
1055
  const downstreamArgs = Array.isArray(entry.args) ? entry.args.map(String) : [];
1038
- const perServer = { ...gatewayConfig, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1056
+ const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1039
1057
  const wrappedArgs = buildGatewayCommandArgs(perServer, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1040
1058
  entry.command = nodeExe;
1041
1059
  entry.args = wrappedArgs;
@@ -1087,30 +1105,47 @@ function unwrapJsonConfigFile(file, dryRun) {
1087
1105
  }
1088
1106
  return { restored };
1089
1107
  }
1090
- /** Parse a single-line TOML scalar string value: command = "x". */
1108
+ /** Parse a single-line TOML scalar string value: command = "x" or command = 'x'. */
1091
1109
  function parseTomlInlineString(line) {
1092
- const m = line.match(/=\s*"((?:[^"\\]|\\.)*)"/);
1093
- if (!m)
1094
- return undefined;
1095
- try {
1096
- return JSON.parse(`"${m[1]}"`);
1097
- }
1098
- catch {
1099
- return m[1];
1110
+ const rhs = line.slice(line.indexOf('=') + 1).trim();
1111
+ const basic = rhs.match(/^"((?:[^"\\]|\\.)*)"/);
1112
+ if (basic) {
1113
+ try {
1114
+ return JSON.parse(`"${basic[1]}"`);
1115
+ }
1116
+ catch {
1117
+ return basic[1];
1118
+ }
1100
1119
  }
1120
+ const literal = rhs.match(/^'([^']*)'/); // TOML literal string — no escapes
1121
+ if (literal)
1122
+ return literal[1];
1123
+ return undefined;
1101
1124
  }
1102
- /** Parse a single-line TOML string array: args = ["a", "b"]. */
1125
+ /** Parse a single-line TOML string array: args = ["a", 'b'] (mixed quotes ok). */
1103
1126
  function parseTomlInlineStringArray(line) {
1104
- const m = line.match(/=\s*(\[.*\])\s*$/);
1105
- if (!m)
1106
- return undefined;
1107
- try {
1108
- const parsed = JSON.parse(m[1]);
1109
- if (Array.isArray(parsed))
1110
- return parsed.map(String);
1127
+ const rhs = line.slice(line.indexOf('=') + 1).trim();
1128
+ if (!rhs.startsWith('[') || !rhs.endsWith(']'))
1129
+ return undefined; // multi-line / not an inline array
1130
+ const inner = rhs.slice(1, -1).trim();
1131
+ if (inner === '')
1132
+ return [];
1133
+ const out = [];
1134
+ const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'/g;
1135
+ let match;
1136
+ while ((match = re.exec(inner)) !== null) {
1137
+ if (match[1] !== undefined) {
1138
+ try {
1139
+ out.push(JSON.parse(`"${match[1]}"`));
1140
+ }
1141
+ catch {
1142
+ out.push(match[1]);
1143
+ }
1144
+ }
1145
+ else
1146
+ out.push(match[2] ?? '');
1111
1147
  }
1112
- catch { /* not single-line */ }
1113
- return undefined;
1148
+ return out;
1114
1149
  }
1115
1150
  function tomlStringArrayLiteral(values) {
1116
1151
  return `[${values.map(v => JSON.stringify(v)).join(', ')}]`;
@@ -1169,7 +1204,7 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1169
1204
  stats.skippedManaged.push(section.name);
1170
1205
  continue;
1171
1206
  }
1172
- const perServer = { ...gatewayConfig, agentName: perServerAgentName(gatewayConfig.developerName, 'codex', section.name) };
1207
+ const perServer = { ...gatewayConfig, agentClient: 'codex', agentName: perServerAgentName(gatewayConfig.developerName, 'codex', section.name) };
1173
1208
  const wrappedArgs = buildGatewayCommandArgs(perServer, section.command, section.args || [], INSTALL_GATEWAY_ARGS);
1174
1209
  const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
1175
1210
  lines[section.cmdLine] = `${indent}command = ${JSON.stringify(nodeExe)}`;
@@ -1201,24 +1236,29 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1201
1236
  }
1202
1237
  return stats;
1203
1238
  }
1204
- function protectAllTargetFiles(extra) {
1239
+ function protectAllTargetFiles(extra, allowedClientKeys) {
1205
1240
  const cwd = process.cwd();
1206
1241
  const seen = new Set();
1242
+ const allow = (clientKey) => !allowedClientKeys || allowedClientKeys.has(clientKey);
1207
1243
  const files = [];
1208
1244
  for (const candidate of (0, discoverPaths_1.candidateConfigPaths)(cwd, extra)) {
1209
1245
  const key = path.resolve(candidate.path).toLowerCase();
1210
1246
  if (seen.has(key))
1211
1247
  continue;
1248
+ if (!allow(candidate.clientKey))
1249
+ continue;
1212
1250
  if (!fs.existsSync(candidate.path))
1213
1251
  continue;
1214
1252
  seen.add(key);
1215
1253
  files.push(candidate);
1216
1254
  }
1217
- for (const target of (0, discoverPaths_1.claudeDesktopInstallTargets)()) {
1218
- const key = path.resolve(target.file).toLowerCase();
1219
- if (target.exists && !seen.has(key)) {
1220
- seen.add(key);
1221
- files.push({ path: target.file, source: target.source, clientKey: 'claude_desktop' });
1255
+ if (allow('claude_desktop')) {
1256
+ for (const target of (0, discoverPaths_1.claudeDesktopInstallTargets)()) {
1257
+ const key = path.resolve(target.file).toLowerCase();
1258
+ if (target.exists && !seen.has(key)) {
1259
+ seen.add(key);
1260
+ files.push({ path: target.file, source: target.source, clientKey: 'claude_desktop' });
1261
+ }
1222
1262
  }
1223
1263
  }
1224
1264
  return files;
@@ -1227,11 +1267,14 @@ async function protectAllCommand(args, config) {
1227
1267
  (0, config_1.requireCliSetup)(config, { shieldId: args.shieldId, shieldKey: args.shieldKey, apiUrl: args.apiUrl }, { requireOrganizationId: false });
1228
1268
  const gatewayConfig = resolveGatewayConfig(args, config);
1229
1269
  const dryRun = args.dryRun === 'true';
1230
- const files = protectAllTargetFiles(args.config);
1270
+ const allowedClientKeys = resolveProtectClientKeys(args.clients);
1271
+ const files = protectAllTargetFiles(args.config, allowedClientKeys);
1231
1272
  console.log('\n\x1b[1m\x1b[36mFullCourtDefense — protect every MCP server in every client\x1b[0m');
1232
1273
  console.log(`\x1b[2mRuntime identity: ${gatewayConfig.developerName} | Shield: ${gatewayConfig.shieldId}${dryRun ? ' | DRY RUN' : ''}\x1b[0m\n`);
1233
1274
  if (files.length === 0) {
1234
- console.log('No MCP client config files found on this machine. Configure an MCP server in any client, then re-run.');
1275
+ console.log(allowedClientKeys
1276
+ ? 'No matching client config files found for the requested --clients. Try without --clients, or pick a client that is installed.'
1277
+ : 'No MCP client config files found on this machine. Configure an MCP server in any client, then re-run.');
1235
1278
  return;
1236
1279
  }
1237
1280
  let totalWrapped = 0;
@@ -1268,7 +1311,8 @@ async function protectAllCommand(args, config) {
1268
1311
  }
1269
1312
  async function unprotectAllCommand(args, config) {
1270
1313
  const dryRun = args.dryRun === 'true';
1271
- const files = protectAllTargetFiles(args.config);
1314
+ const allowedClientKeys = resolveProtectClientKeys(args.clients);
1315
+ const files = protectAllTargetFiles(args.config, allowedClientKeys);
1272
1316
  let gatewayConfig;
1273
1317
  try {
1274
1318
  gatewayConfig = resolveGatewayConfig(args, config);
package/dist/index.js CHANGED
@@ -128,8 +128,11 @@ function printHelp() {
128
128
  (Cursor, Claude Code/Desktop, Codex, Gemini, Windsurf, VS Code)
129
129
  and wrap each through the Shield proxy — no --mcp-command needed.
130
130
  Enforces your org Action Policies on every tool. --dry-run to preview.
131
+ Per-tool: --clients cursor (or claude-code, claude-desktop, codex,
132
+ gemini-cli, windsurf, vscode; comma-separated). Default: all tools.
131
133
  unprotect-all
132
134
  Reverse protect-all: restore every server to its original command.
135
+ Also supports --clients to target one tool, and --dry-run.
133
136
  install-mcp-gateway
134
137
  Install MCP gateway into selected clients (--clients all for every tool).
135
138
  install-cursor-mcp-gateway
@@ -511,6 +514,7 @@ async function main() {
511
514
  ...buildGatewayArgs(),
512
515
  dryRun: flags['dry-run'],
513
516
  config: flags.config,
517
+ clients: flags.clients,
514
518
  };
515
519
  await (0, mcpGateway_1.protectAllCommand)(args, config);
516
520
  break;
@@ -521,6 +525,7 @@ async function main() {
521
525
  ...buildGatewayArgs(),
522
526
  dryRun: flags['dry-run'],
523
527
  config: flags.config,
528
+ clients: flags.clients,
524
529
  };
525
530
  await (0, mcpGateway_1.unprotectAllCommand)(args, config);
526
531
  break;
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.5.0"
2
+ "version": "1.5.2"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {