fullcourtdefense-cli 1.36.4 → 1.37.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.
Files changed (90) hide show
  1. package/dist/askDialog.d.ts +1 -0
  2. package/dist/askDialog.js +20 -3
  3. package/dist/callerEnv.d.ts +29 -2
  4. package/dist/callerEnv.js +48 -2
  5. package/dist/commands/daemon.d.ts +10 -0
  6. package/dist/commands/daemon.js +348 -170
  7. package/dist/commands/desktopChatGuard.js +2 -0
  8. package/dist/commands/discoverPaths.d.ts +12 -9
  9. package/dist/commands/discoverPaths.js +30 -0
  10. package/dist/commands/ephemeralStack.js +7 -0
  11. package/dist/commands/flushSpool.d.ts +2 -0
  12. package/dist/commands/flushSpool.js +9 -2
  13. package/dist/commands/hook.d.ts +14 -0
  14. package/dist/commands/hook.js +302 -101
  15. package/dist/commands/httpGatewayCommand.d.ts +1 -0
  16. package/dist/commands/httpGatewayCommand.js +7 -1
  17. package/dist/commands/installClaudeHook.js +32 -10
  18. package/dist/commands/installCursorHook.js +20 -9
  19. package/dist/commands/login.js +8 -1
  20. package/dist/commands/mcpGateway.d.ts +21 -0
  21. package/dist/commands/mcpGateway.js +90 -32
  22. package/dist/commands/refreshPolicyCache.d.ts +28 -0
  23. package/dist/commands/refreshPolicyCache.js +59 -0
  24. package/dist/commands/watchdog.d.ts +2 -0
  25. package/dist/commands/watchdog.js +30 -5
  26. package/dist/config.d.ts +36 -0
  27. package/dist/config.js +78 -12
  28. package/dist/daemonDetectorBoot.d.ts +1 -0
  29. package/dist/daemonDetectorBoot.js +12 -0
  30. package/dist/daemonForensics.d.ts +14 -1
  31. package/dist/daemonForensics.js +32 -0
  32. package/dist/detectorInProcess.d.ts +18 -0
  33. package/dist/detectorInProcess.js +234 -0
  34. package/dist/detectorInThread.d.ts +15 -0
  35. package/dist/detectorInThread.js +129 -0
  36. package/dist/distress.d.ts +2 -0
  37. package/dist/distress.js +2 -0
  38. package/dist/fileWriteCanon.d.ts +17 -0
  39. package/dist/fileWriteCanon.js +141 -0
  40. package/dist/gateReports.d.ts +144 -0
  41. package/dist/gateReports.js +488 -0
  42. package/dist/hookIo.d.ts +59 -0
  43. package/dist/hookIo.js +319 -9
  44. package/dist/hookSlim.d.ts +15 -8
  45. package/dist/hookSlim.js +187 -25
  46. package/dist/housekeeping.d.ts +64 -0
  47. package/dist/housekeeping.js +384 -0
  48. package/dist/housekeepingOps.d.ts +150 -0
  49. package/dist/housekeepingOps.js +218 -0
  50. package/dist/housekeepingWorker.d.ts +1 -0
  51. package/dist/housekeepingWorker.js +113 -0
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +19 -0
  54. package/dist/localDetectionData.d.ts +10 -0
  55. package/dist/localDetectionData.js +48 -0
  56. package/dist/localDetectionUpdates.d.ts +2 -2
  57. package/dist/localDetectionUpdates.js +4 -4
  58. package/dist/localSafetySnapshot.d.ts +48 -2
  59. package/dist/localSafetySnapshot.js +241 -25
  60. package/dist/machineIdentity.d.ts +7 -0
  61. package/dist/machineIdentity.js +20 -2
  62. package/dist/monitorFastPath.d.ts +191 -0
  63. package/dist/monitorFastPath.js +1198 -0
  64. package/dist/nativeHook.js +9 -2
  65. package/dist/pendingDrain.d.ts +82 -0
  66. package/dist/pendingDrain.js +215 -0
  67. package/dist/pendingDrainer.d.ts +30 -0
  68. package/dist/pendingDrainer.js +217 -0
  69. package/dist/perfSnapshot.d.ts +1 -1
  70. package/dist/perfSnapshot.js +28 -4
  71. package/dist/policyCacheFiles.d.ts +73 -0
  72. package/dist/policyCacheFiles.js +320 -0
  73. package/dist/policyGateHealth.d.ts +8 -0
  74. package/dist/policyGateHealth.js +17 -0
  75. package/dist/policyRefresh.d.ts +28 -0
  76. package/dist/policyRefresh.js +69 -0
  77. package/dist/protectionMaintenance.d.ts +15 -0
  78. package/dist/protectionMaintenance.js +43 -1
  79. package/dist/runtimeConfig.d.ts +77 -20
  80. package/dist/runtimeConfig.js +278 -57
  81. package/dist/selfTest.d.ts +6 -1
  82. package/dist/selfTest.js +11 -4
  83. package/dist/telemetry.d.ts +136 -4
  84. package/dist/telemetry.js +493 -96
  85. package/dist/verdictIpc.d.ts +60 -0
  86. package/dist/verdictIpc.js +504 -19
  87. package/dist/verdictIpcClient.d.ts +175 -18
  88. package/dist/verdictIpcClient.js +277 -59
  89. package/dist/version.json +1 -1
  90. package/package.json +2 -2
@@ -460,6 +460,8 @@ function startDesktopChatGuard(runtime) {
460
460
  shieldKey: runtime.shieldKey,
461
461
  developerName: snapshotIdentity.developerName,
462
462
  machineName: snapshotIdentity.machineName,
463
+ // Read what the daemon poll keeps current — no re-download per tick.
464
+ hotPath: true,
463
465
  });
464
466
  scanOptions = (0, localSafetySnapshot_1.snapshotToScanOptions)(snapshot);
465
467
  }
@@ -38,6 +38,16 @@ export declare function claudeDesktopInstallTargets(configPath?: string): Claude
38
38
  export declare function candidateConfigPaths(cwd: string, extra?: string): ConfigPathCandidate[];
39
39
  /** Common places developers keep code, used by `--scan-root auto`. */
40
40
  export declare function defaultScanRoots(): string[];
41
+ type ProjectConfigScan = {
42
+ candidates: ConfigPathCandidate[];
43
+ scannedDirs: number;
44
+ excludedDirs: number;
45
+ truncated: boolean;
46
+ truncatedReason?: ScanTruncationReason;
47
+ maxDepth: number;
48
+ maxDirs: number;
49
+ };
50
+ export declare function setProjectConfigScanPass(key: string | undefined): void;
41
51
  /**
42
52
  * Bounded walk of developer folders looking for project-scoped MCP configs
43
53
  * (`--scan-root`). Depth- and volume-capped so it stays fast and predictable —
@@ -48,15 +58,7 @@ export declare function scanRootsForProjectConfigs(roots: string[], options?: {
48
58
  maxDepth?: number;
49
59
  maxDirs?: number;
50
60
  excludedRoots?: string[];
51
- }): {
52
- candidates: ConfigPathCandidate[];
53
- scannedDirs: number;
54
- excludedDirs: number;
55
- truncated: boolean;
56
- truncatedReason?: ScanTruncationReason;
57
- maxDepth: number;
58
- maxDirs: number;
59
- };
61
+ }): ProjectConfigScan;
60
62
  export interface FolderCatalogEntry {
61
63
  path: string;
62
64
  /** Depth relative to its walk root (home or a dev root) — lets the UI indent. */
@@ -85,3 +87,4 @@ export declare function discoverScanTargets(cwd: string, extra?: string): Config
85
87
  * `~/.codex` / `~/.cursor` without Cursor.exe must not count as installed.
86
88
  */
87
89
  export declare function detectIdeAppInstalled(clientKey: string, env?: NodeJS.ProcessEnv, home?: string, platform?: NodeJS.Platform): boolean;
90
+ export {};
@@ -46,6 +46,7 @@ exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
46
46
  exports.claudeDesktopInstallTargets = claudeDesktopInstallTargets;
47
47
  exports.candidateConfigPaths = candidateConfigPaths;
48
48
  exports.defaultScanRoots = defaultScanRoots;
49
+ exports.setProjectConfigScanPass = setProjectConfigScanPass;
49
50
  exports.scanRootsForProjectConfigs = scanRootsForProjectConfigs;
50
51
  exports.buildFolderCatalog = buildFolderCatalog;
51
52
  exports.resolveScanRoots = resolveScanRoots;
@@ -297,6 +298,23 @@ function defaultScanRoots() {
297
298
  }
298
299
  });
299
300
  }
301
+ /**
302
+ * Reuse of one walk across a single daemon protection pass. A pass inspects,
303
+ * repairs, rescans and verifies, and each step walked every scan root again
304
+ * (4-7 walks of up to 25,000 folders). Only the daemon's housekeeping sets a
305
+ * pass key; every other caller (and work between a pass's steps) walks fresh.
306
+ */
307
+ let scanPassKey;
308
+ const scanPassResults = new Map();
309
+ const SCAN_PASSES_KEPT = 4;
310
+ function setProjectConfigScanPass(key) {
311
+ scanPassKey = key;
312
+ if (key === undefined || scanPassResults.has(key))
313
+ return;
314
+ scanPassResults.set(key, new Map());
315
+ while (scanPassResults.size > SCAN_PASSES_KEPT)
316
+ scanPassResults.delete(scanPassResults.keys().next().value);
317
+ }
300
318
  /**
301
319
  * Bounded walk of developer folders looking for project-scoped MCP configs
302
320
  * (`--scan-root`). Depth- and volume-capped so it stays fast and predictable —
@@ -304,6 +322,18 @@ function defaultScanRoots() {
304
322
  * are pruned; only the known project config filenames are checked.
305
323
  */
306
324
  function scanRootsForProjectConfigs(roots, options = {}) {
325
+ const pass = scanPassKey === undefined ? undefined : scanPassResults.get(scanPassKey);
326
+ if (!pass)
327
+ return walkProjectConfigs(roots, options);
328
+ const memoKey = JSON.stringify([roots, options.maxDepth, options.maxDirs, options.excludedRoots]);
329
+ const cached = pass.get(memoKey);
330
+ if (cached)
331
+ return { ...cached, candidates: cached.candidates.map(candidate => ({ ...candidate })) };
332
+ const result = walkProjectConfigs(roots, options);
333
+ pass.set(memoKey, result);
334
+ return { ...result, candidates: result.candidates.map(candidate => ({ ...candidate })) };
335
+ }
336
+ function walkProjectConfigs(roots, options) {
307
337
  const maxDepth = options.maxDepth ?? 4;
308
338
  const maxDirs = options.maxDirs ?? 25000;
309
339
  const candidates = [];
@@ -51,6 +51,7 @@ const config_1 = require("../config");
51
51
  const child_process_1 = require("child_process");
52
52
  const installClaudeHook_1 = require("./installClaudeHook");
53
53
  const mcpGateway_1 = require("./mcpGateway");
54
+ const refreshPolicyCache_1 = require("./refreshPolicyCache");
54
55
  /**
55
56
  * Written by `ci-protect` / `workload-protect` next to the credentials they
56
57
  * save. Its presence says "the enrollment on this machine is EPHEMERAL", so a
@@ -214,6 +215,12 @@ async function startDetachedHttpGateway(input) {
214
215
  */
215
216
  async function installEphemeralProtectionStack(options, config) {
216
217
  const sendLog = options.sendLog || exports.silentLogSink;
218
+ // Seed both policy caches before the agent's first action, as login does:
219
+ // hooks and gateways never fetch policy while a cache exists, and a job or
220
+ // container has no daemon to poll. Callers export the ephemeral identity
221
+ // (FCD_MACHINE_ID) first, so the seed carries it. Best-effort and silent —
222
+ // on failure the hot path's single-flight no-cache attempt still applies.
223
+ await (0, refreshPolicyCache_1.refreshPolicyCachesInBackground)({ apiUrl: options.apiUrl, shieldId: options.shieldId, shieldKey: options.shieldKey });
217
224
  // The Claude-format hook covers Claude Code, VS Code agent mode, and
218
225
  // Copilot CLI — the common agents on runners and in containers.
219
226
  if (options.hooks !== 'false') {
@@ -4,6 +4,8 @@ export interface FlushSpoolArgs {
4
4
  shieldId?: string;
5
5
  shieldKey?: string;
6
6
  heartbeat?: string;
7
+ /** `false`: leave the terminal guard rule files alone (the daemon keeps their 30 s cadence). */
8
+ guardRules?: string;
7
9
  }
8
10
  /**
9
11
  * `fullcourtdefense flush-spool` — drain the local telemetry spool to the backend
@@ -16,12 +16,19 @@ async function flushSpoolCommand(args, config) {
16
16
  });
17
17
  if (!creds.shieldId)
18
18
  return; // nothing to attribute to
19
- const result = await (0, telemetry_1.flushSpool)({
19
+ // Drains in batches and records completion for the hooks that coalesced
20
+ // onto this flusher. `--heartbeat liveness` (hook-requested flushes):
21
+ // liveness only, no diagnostic payload, and a short linger for the rest of
22
+ // the burst that requested it.
23
+ const hookRequested = args.heartbeat === 'liveness';
24
+ const result = await (0, telemetry_1.drainSpool)({
20
25
  apiUrl: creds.apiUrl,
21
26
  shieldId: creds.shieldId,
22
27
  shieldKey: creds.shieldKey,
23
28
  heartbeat: args.heartbeat !== 'false',
24
- });
29
+ livenessOnly: hookRequested,
30
+ refreshGuardRules: args.guardRules !== 'false',
31
+ }, { linger: hookRequested });
25
32
  if (result) {
26
33
  console.log(`Flushed ${result.accepted} event(s)${result.deduped ? `, ${result.deduped} deduped` : ''}.`);
27
34
  }
@@ -34,6 +34,7 @@ export interface HookArgs {
34
34
  approvalMode?: string;
35
35
  approvalTimeoutMs?: string;
36
36
  approvalPollMs?: string;
37
+ ideTimeout?: string;
37
38
  }
38
39
  /**
39
40
  * `post` = a post-execution hook (Cursor afterShellExecution / afterMCPExecution, Claude
@@ -209,6 +210,14 @@ export interface HookIo {
209
210
  * mode, where `exit` only unwinds the evaluation and the process lives on.
210
211
  */
211
212
  exitsProcess: boolean;
213
+ /**
214
+ * Native hooks (Codex, Gemini CLI, Windsurf, Kiro): a block-mode local-first
215
+ * allow queues its check-tool-call report (gateReports.ts) instead of
216
+ * spooling it. These processes used to send that report themselves and
217
+ * wait for it; its answer still ends a gate-failure streak and feeds the
218
+ * server's behavior baseline, only after the verdict and off this process.
219
+ */
220
+ deferGateReports?: boolean;
212
221
  /** Evaluated inside the resident daemon for a thin client (see DaemonDeclined). */
213
222
  viaDaemon?: boolean;
214
223
  /** Set when this evaluation's verdict is a stop still being settled (approval wait). */
@@ -245,9 +254,14 @@ export declare function crashVerdictForProcess(event: string | undefined, messag
245
254
  * snapshot, policy engine) is warm here — verdicts resolve in milliseconds.
246
255
  * Mirrors hookCommand's fail-open contract: any unexpected exception yields
247
256
  * an allow verdict, never an error the thin client would surface.
257
+ * `exitsProcess`: the caller is a one-shot hook process that ends with this
258
+ * verdict (the slim entry's fallback, native hooks) — see HookIo.exitsProcess.
259
+ * `deferGateReports`: native hooks — see HookIo.deferGateReports.
248
260
  */
249
261
  export declare function evaluateHookRequest(args: HookArgs, stdinRaw: string, config: BotGuardConfig, opts?: {
250
262
  viaDaemon?: boolean;
263
+ exitsProcess?: boolean;
264
+ deferGateReports?: boolean;
251
265
  }): Promise<{
252
266
  stdout: string;
253
267
  stderr: string;