fullcourtdefense-cli 1.14.10 → 1.14.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.
@@ -43,6 +43,7 @@ const child_process_1 = require("child_process");
43
43
  const config_1 = require("../config");
44
44
  const discoverProxy_1 = require("./discoverProxy");
45
45
  const discoverSchedule_1 = require("./discoverSchedule");
46
+ const mcpGateway_1 = require("./mcpGateway");
46
47
  const discoverPaths_1 = require("./discoverPaths");
47
48
  const knownMcpServers_1 = require("./knownMcpServers");
48
49
  const discoverAgentFiles_1 = require("./discoverAgentFiles");
@@ -770,6 +771,17 @@ async function discoverCommand(args, config) {
770
771
  let clientCoverage = [];
771
772
  if (runMcp) {
772
773
  const candidates = candidateConfigPaths(cwd, args.extraPath);
774
+ // Self-heal BEFORE parsing: configs written by old CLI versions can carry a stale
775
+ // generic `--agent-name` that breaks dashboard policy matching. Fix them in place so
776
+ // every discovery run converges the fleet to the per-server identity contract.
777
+ const healedIdentities = (0, mcpGateway_1.healGatewayAgentIdentities)(candidates.map(c => c.path).filter(p => fs.existsSync(p)));
778
+ if (healedIdentities.length > 0 && !silent) {
779
+ console.log(`${COLOR.yellow}Healed ${healedIdentities.length} stale gateway identit${healedIdentities.length === 1 ? 'y' : 'ies'} (old CLI defaults):${COLOR.reset}`);
780
+ for (const heal of healedIdentities) {
781
+ console.log(` ${COLOR.dim}•${COLOR.reset} ${heal.server}: ${heal.from} → ${heal.to} (${heal.file})`);
782
+ }
783
+ console.log(`${COLOR.gray}Restart the affected AI clients to pick up the corrected identity.${COLOR.reset}\n`);
784
+ }
773
785
  for (const c of candidates) {
774
786
  scanned.push({ path: c.path, source: c.source });
775
787
  if (fs.existsSync(c.path)) {
@@ -63,6 +63,22 @@ export interface ProtectAllArgs extends McpGatewayArgs {
63
63
  config?: string;
64
64
  clients?: string;
65
65
  }
66
+ export interface GatewayIdentityHealResult {
67
+ file: string;
68
+ server: string;
69
+ from: string;
70
+ to: string;
71
+ }
72
+ /**
73
+ * SELF-HEAL for the policy-identity contract. Scans MCP client config files (JSON and Codex
74
+ * TOML) for FCD-gateway-wrapped servers whose baked-in `--agent-name` is a stale generic
75
+ * `<developer>-<client>` default, and rewrites it to `<developer>-<server>` so dashboard
76
+ * policies targeting the MCP server (`agentName contains <server>`) match at runtime.
77
+ *
78
+ * Runs automatically from `discover` and `protect-all`, so a customer machine with configs
79
+ * written by an old CLI version converges to the correct identity without manual edits.
80
+ */
81
+ export declare function healGatewayAgentIdentities(files: string[], dryRun?: boolean): GatewayIdentityHealResult[];
66
82
  export declare function protectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
67
83
  export declare function unprotectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
68
84
  export declare function installClaudeCodeMcpGatewayCommand(args: InstallClaudeCodeMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
@@ -37,6 +37,7 @@ exports.ALL_GATEWAY_CLIENTS = void 0;
37
37
  exports.mcpGatewayCommand = mcpGatewayCommand;
38
38
  exports.installCursorMcpGatewayCommand = installCursorMcpGatewayCommand;
39
39
  exports.installMcpGatewayCommand = installMcpGatewayCommand;
40
+ exports.healGatewayAgentIdentities = healGatewayAgentIdentities;
40
41
  exports.protectAllCommand = protectAllCommand;
41
42
  exports.unprotectAllCommand = unprotectAllCommand;
42
43
  exports.installClaudeCodeMcpGatewayCommand = installClaudeCodeMcpGatewayCommand;
@@ -967,7 +968,7 @@ function clientInstallArgs(args) {
967
968
  };
968
969
  }
969
970
  async function installCursorMcpGatewayCommand(args, config) {
970
- const gatewayConfig = resolveGatewayConfig(args, config, {
971
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
971
972
  agentClient: 'cursor',
972
973
  agentNamePrefix: 'cursor',
973
974
  userObjective: 'Developer requested this action from Cursor.',
@@ -976,6 +977,7 @@ async function installCursorMcpGatewayCommand(args, config) {
976
977
  const file = cursorMcpPath(projectScope);
977
978
  const nodeExe = process.execPath;
978
979
  const serverName = args.serverName || MANAGED_SERVER_NAME;
980
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
979
981
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
980
982
  if (!downstreamCommand)
981
983
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1141,6 +1143,147 @@ function backupFile(file) {
1141
1143
  function perServerAgentName(developerName, _agentClient, serverName) {
1142
1144
  return `${safeIdentityPart(developerName)}-${safeIdentityPart(serverName)}`;
1143
1145
  }
1146
+ /**
1147
+ * Generic `<developer>-<client>` identities that pre-1.14.11 per-server installs baked into
1148
+ * configs. These are the ONLY values self-heal is allowed to rewrite — a custom --agent-name
1149
+ * an admin chose on purpose is never touched.
1150
+ */
1151
+ const GENERIC_CLIENT_AGENT_SUFFIXES = ['cursor', 'claude-code', 'claude-desktop', 'codex', 'gemini', 'gemini-cli', 'windsurf', 'vscode'];
1152
+ /**
1153
+ * Rewrite a wrapped entry's `--agent-name` to the per-server contract (`<developer>-<server>`)
1154
+ * when it still carries a known-buggy generic client default. Mutates `args` in place.
1155
+ */
1156
+ function healAgentNameInArgs(args, serverName) {
1157
+ const nameIdx = args.indexOf('--agent-name');
1158
+ if (nameIdx === -1 || nameIdx + 1 >= args.length)
1159
+ return { changed: false };
1160
+ const current = String(args[nameIdx + 1]);
1161
+ const devIdx = args.indexOf('--developer-name');
1162
+ const developerName = devIdx !== -1 && args[devIdx + 1] ? String(args[devIdx + 1]) : machineScopedUser();
1163
+ const devPart = safeIdentityPart(developerName);
1164
+ const expected = `${devPart}-${safeIdentityPart(serverName)}`;
1165
+ if (current === expected)
1166
+ return { changed: false };
1167
+ const isGenericDefault = GENERIC_CLIENT_AGENT_SUFFIXES.some(suffix => current === `${devPart}-${suffix}`);
1168
+ if (!isGenericDefault)
1169
+ return { changed: false };
1170
+ args[nameIdx + 1] = expected;
1171
+ return { changed: true, from: current, to: expected };
1172
+ }
1173
+ /**
1174
+ * SELF-HEAL for the policy-identity contract. Scans MCP client config files (JSON and Codex
1175
+ * TOML) for FCD-gateway-wrapped servers whose baked-in `--agent-name` is a stale generic
1176
+ * `<developer>-<client>` default, and rewrites it to `<developer>-<server>` so dashboard
1177
+ * policies targeting the MCP server (`agentName contains <server>`) match at runtime.
1178
+ *
1179
+ * Runs automatically from `discover` and `protect-all`, so a customer machine with configs
1180
+ * written by an old CLI version converges to the correct identity without manual edits.
1181
+ */
1182
+ function healGatewayAgentIdentities(files, dryRun = false) {
1183
+ const healed = [];
1184
+ const seen = new Set();
1185
+ for (const file of files) {
1186
+ let resolvedKey;
1187
+ try {
1188
+ resolvedKey = path.resolve(file).toLowerCase();
1189
+ }
1190
+ catch {
1191
+ continue;
1192
+ }
1193
+ if (seen.has(resolvedKey))
1194
+ continue;
1195
+ seen.add(resolvedKey);
1196
+ if (/\.toml$/i.test(file)) {
1197
+ healed.push(...healCodexTomlAgentIdentities(file, dryRun));
1198
+ continue;
1199
+ }
1200
+ let raw;
1201
+ try {
1202
+ raw = fs.readFileSync(file, 'utf8');
1203
+ }
1204
+ catch {
1205
+ continue;
1206
+ }
1207
+ let json;
1208
+ try {
1209
+ json = JSON.parse(raw);
1210
+ }
1211
+ catch {
1212
+ continue;
1213
+ }
1214
+ const maps = collectJsonServerMaps(json);
1215
+ let changed = false;
1216
+ for (const map of maps) {
1217
+ for (const [name, entry] of Object.entries(map)) {
1218
+ if (name === MANAGED_SERVER_NAME || !isGatewayWrappedEntry(entry))
1219
+ continue;
1220
+ const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
1221
+ const result = healAgentNameInArgs(args, name);
1222
+ if (result.changed) {
1223
+ entry.args = args;
1224
+ healed.push({ file, server: name, from: result.from, to: result.to });
1225
+ changed = true;
1226
+ }
1227
+ }
1228
+ }
1229
+ if (changed && !dryRun) {
1230
+ backupFile(file);
1231
+ fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
1232
+ }
1233
+ }
1234
+ return healed;
1235
+ }
1236
+ /** Codex config.toml variant of the identity self-heal (args are written as a JSON-style array). */
1237
+ function healCodexTomlAgentIdentities(file, dryRun) {
1238
+ let raw;
1239
+ try {
1240
+ raw = fs.readFileSync(file, 'utf8');
1241
+ }
1242
+ catch {
1243
+ return [];
1244
+ }
1245
+ const healed = [];
1246
+ const updated = raw.replace(/\[mcp_servers\.("?)([^\]"]+)\1\]([\s\S]*?)(?=\n\[|$)/g, (section, _q, serverName, body) => {
1247
+ if (serverName === MANAGED_SERVER_NAME || !body.includes('mcp-gateway'))
1248
+ return section;
1249
+ const newBody = body.replace(/("--agent-name",\s*")([^"]+)(")/, (m, pre, current, post) => {
1250
+ const devMatch = body.match(/"--developer-name",\s*"([^"]+)"/);
1251
+ const devPart = safeIdentityPart(devMatch?.[1] || machineScopedUser());
1252
+ const expected = `${devPart}-${safeIdentityPart(serverName)}`;
1253
+ if (current === expected)
1254
+ return m;
1255
+ const isGenericDefault = GENERIC_CLIENT_AGENT_SUFFIXES.some(suffix => current === `${devPart}-${suffix}`);
1256
+ if (!isGenericDefault)
1257
+ return m;
1258
+ healed.push({ file, server: serverName, from: current, to: expected });
1259
+ return `${pre}${expected}${post}`;
1260
+ });
1261
+ return section.replace(body, newBody);
1262
+ });
1263
+ if (healed.length > 0 && !dryRun) {
1264
+ backupFile(file);
1265
+ fs.writeFileSync(file, updated, 'utf8');
1266
+ }
1267
+ return healed;
1268
+ }
1269
+ /**
1270
+ * Per-server installs (`install-*-mcp-gateway --server-name foo`) must report the SAME
1271
+ * runtime-agnostic agent identity that protect-all/auto-protect produce (`<developer>-<server>`).
1272
+ * Otherwise the gateway reports a generic `<developer>-<client>` name and a dashboard policy
1273
+ * targeting the MCP server (agentName contains <server>) silently never matches — the exact
1274
+ * bug where a block policy on `patria-bank-finance` did not fire because the Claude Desktop
1275
+ * entry reported `boazl-claude-desktop`. An explicit --agent-name / FCD_AGENT_NAME still wins.
1276
+ */
1277
+ function applyPerServerAgentName(gatewayConfig, args, serverName) {
1278
+ if (args.agentName || process.env.FCD_AGENT_NAME)
1279
+ return gatewayConfig;
1280
+ if (!serverName || serverName === MANAGED_SERVER_NAME)
1281
+ return gatewayConfig;
1282
+ return {
1283
+ ...gatewayConfig,
1284
+ agentName: perServerAgentName(gatewayConfig.developerName, gatewayConfig.agentClient, serverName),
1285
+ };
1286
+ }
1144
1287
  function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1145
1288
  const stats = newWrapStats();
1146
1289
  let json;
@@ -1411,6 +1554,15 @@ async function protectAllCommand(args, config) {
1411
1554
  return;
1412
1555
  }
1413
1556
  console.log(`\x1b[2mAuto-searching every known MCP client on this machine — ${files.length} config file(s) found.\x1b[0m\n`);
1557
+ // Converge stale generic gateway identities (old CLI defaults) to the per-server
1558
+ // contract before wrapping, so policy matching works on already-wrapped entries too.
1559
+ const healedIdentities = healGatewayAgentIdentities(files.map(f => f.path), dryRun);
1560
+ if (healedIdentities.length > 0) {
1561
+ console.log(`\x1b[32m✓ ${dryRun ? 'would fix' : 'fixed'} ${healedIdentities.length} stale gateway identit${healedIdentities.length === 1 ? 'y' : 'ies'}:\x1b[0m`);
1562
+ for (const heal of healedIdentities)
1563
+ console.log(` \x1b[2m•\x1b[0m ${heal.server}: ${heal.from} → ${heal.to}`);
1564
+ console.log('');
1565
+ }
1414
1566
  let totalWrapped = 0;
1415
1567
  let totalHealed = 0;
1416
1568
  let totalManaged = 0;
@@ -1495,7 +1647,7 @@ async function unprotectAllCommand(args, config) {
1495
1647
  console.log(`\n${dryRun ? 'Would restore' : 'Restored'} ${total} server(s) to their original commands. Restart each client.`);
1496
1648
  }
1497
1649
  async function installClaudeCodeMcpGatewayCommand(args, config) {
1498
- const gatewayConfig = resolveGatewayConfig(args, config, {
1650
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1499
1651
  agentClient: 'claude-code',
1500
1652
  agentNamePrefix: 'claude-code',
1501
1653
  userObjective: 'Developer requested this action from Claude Code.',
@@ -1503,6 +1655,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1503
1655
  const scope = normalizeClaudeCodeScope(args.scope);
1504
1656
  const nodeExe = process.execPath;
1505
1657
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1658
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1506
1659
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1507
1660
  if (!downstreamCommand)
1508
1661
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1513,7 +1666,13 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1513
1666
  includeShieldKey,
1514
1667
  });
1515
1668
  const claudeArgs = ['mcp', 'add', '--scope', scope, '--transport', 'stdio', serverName, '--', nodeExe, ...commandArgs];
1516
- let result = (0, child_process_1.spawnSync)('claude', claudeArgs, {
1669
+ // On Windows `claude` is a .cmd shim, so spawnSync needs shell:true — but with shell:true
1670
+ // Node does NOT quote args. Unquoted args with spaces/JSON (e.g. --mcp-args '["C:\\..."]',
1671
+ // --user-objective 'Developer requested...') get word-split by cmd.exe and written mangled
1672
+ // into the config. Quote them ourselves before handing the line to the shell.
1673
+ const shellQuote = (value) => (/[\s"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value);
1674
+ const spawnClaudeArgs = process.platform === 'win32' ? claudeArgs.map(shellQuote) : claudeArgs;
1675
+ let result = (0, child_process_1.spawnSync)('claude', spawnClaudeArgs, {
1517
1676
  stdio: 'inherit',
1518
1677
  shell: process.platform === 'win32',
1519
1678
  });
@@ -1523,7 +1682,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1523
1682
  shell: process.platform === 'win32',
1524
1683
  });
1525
1684
  if (removeResult.status === 0) {
1526
- result = (0, child_process_1.spawnSync)('claude', claudeArgs, {
1685
+ result = (0, child_process_1.spawnSync)('claude', spawnClaudeArgs, {
1527
1686
  stdio: 'inherit',
1528
1687
  shell: process.platform === 'win32',
1529
1688
  });
@@ -1555,7 +1714,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1555
1714
  throw new Error('Claude Code CLI was not available. Install Claude Code or rerun with --scope project to write .mcp.json directly.');
1556
1715
  }
1557
1716
  async function installClaudeDesktopMcpGatewayCommand(args, config) {
1558
- const gatewayConfig = resolveGatewayConfig(args, config, {
1717
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1559
1718
  agentClient: 'claude-desktop',
1560
1719
  agentNamePrefix: 'claude-desktop',
1561
1720
  userObjective: 'Developer requested this action from Claude Desktop.',
@@ -1563,6 +1722,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1563
1722
  const targets = (0, discoverPaths_1.claudeDesktopInstallTargets)(args.configPath);
1564
1723
  const nodeExe = process.execPath;
1565
1724
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1725
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1566
1726
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1567
1727
  if (!downstreamCommand)
1568
1728
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1580,7 +1740,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1580
1740
  console.log('Restart Claude Desktop to load the protected tools.');
1581
1741
  }
1582
1742
  async function installCodexMcpGatewayCommand(args, config) {
1583
- const gatewayConfig = resolveGatewayConfig(args, config, {
1743
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1584
1744
  agentClient: 'codex',
1585
1745
  agentNamePrefix: 'codex',
1586
1746
  userObjective: 'Developer requested this action from Codex.',
@@ -1589,6 +1749,7 @@ async function installCodexMcpGatewayCommand(args, config) {
1589
1749
  const file = codexConfigPath(projectScope);
1590
1750
  const nodeExe = process.execPath;
1591
1751
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1752
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1592
1753
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1593
1754
  if (!downstreamCommand)
1594
1755
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1601,7 +1762,7 @@ async function installCodexMcpGatewayCommand(args, config) {
1601
1762
  console.log('Restart Codex or reload MCP servers. Project config requires a trusted project.');
1602
1763
  }
1603
1764
  async function installGeminiMcpGatewayCommand(args, config) {
1604
- const gatewayConfig = resolveGatewayConfig(args, config, {
1765
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1605
1766
  agentClient: 'gemini-cli',
1606
1767
  agentNamePrefix: 'gemini',
1607
1768
  userObjective: 'Developer requested this action from Gemini CLI.',
@@ -1610,6 +1771,7 @@ async function installGeminiMcpGatewayCommand(args, config) {
1610
1771
  const file = geminiSettingsPath(projectScope);
1611
1772
  const nodeExe = process.execPath;
1612
1773
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1774
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1613
1775
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1614
1776
  if (!downstreamCommand)
1615
1777
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1622,7 +1784,7 @@ async function installGeminiMcpGatewayCommand(args, config) {
1622
1784
  console.log('Restart Gemini CLI to load the protected tools.');
1623
1785
  }
1624
1786
  async function installWindsurfMcpGatewayCommand(args, config) {
1625
- const gatewayConfig = resolveGatewayConfig(args, config, {
1787
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1626
1788
  agentClient: 'windsurf',
1627
1789
  agentNamePrefix: 'windsurf',
1628
1790
  userObjective: 'Developer requested this action from Windsurf.',
@@ -1630,6 +1792,7 @@ async function installWindsurfMcpGatewayCommand(args, config) {
1630
1792
  const file = windsurfConfigPath();
1631
1793
  const nodeExe = process.execPath;
1632
1794
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1795
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1633
1796
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1634
1797
  if (!downstreamCommand)
1635
1798
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
@@ -1642,7 +1805,7 @@ async function installWindsurfMcpGatewayCommand(args, config) {
1642
1805
  console.log('Restart Windsurf to load the protected tools.');
1643
1806
  }
1644
1807
  async function installVscodeMcpGatewayCommand(args, config) {
1645
- const gatewayConfig = resolveGatewayConfig(args, config, {
1808
+ const baseGatewayConfig = resolveGatewayConfig(args, config, {
1646
1809
  agentClient: 'vscode',
1647
1810
  agentNamePrefix: 'vscode',
1648
1811
  userObjective: 'Developer requested this action from VS Code.',
@@ -1650,6 +1813,7 @@ async function installVscodeMcpGatewayCommand(args, config) {
1650
1813
  const projectScope = args.project !== 'false';
1651
1814
  const nodeExe = process.execPath;
1652
1815
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1816
+ const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1653
1817
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1654
1818
  if (!downstreamCommand)
1655
1819
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.14.10"
2
+ "version": "1.14.12"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.14.10",
3
+ "version": "1.14.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": {
@@ -24,6 +24,7 @@
24
24
  "test:agent-file-posture": "npm run build && node scripts/test-agent-file-posture.js",
25
25
  "test:realworld-posture": "npm run build && node scripts/test-realworld-posture-fixtures.js",
26
26
  "test:discover-stdio-mcp": "npm run build && node scripts/test-discover-stdio-mcp-tools.js",
27
+ "test:per-server-agent-name": "npm run build && node scripts/test-per-server-agent-name.js",
27
28
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
28
29
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
29
30
  "prepublishOnly": "npm run build"