fullcourtdefense-cli 1.22.7 → 1.22.8

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.
@@ -0,0 +1,7 @@
1
+ export declare function backupManifestPath(): string;
2
+ /** Every recorded backup path (absent/corrupt manifest = empty list). */
3
+ export declare function readBackupManifest(): string[];
4
+ /** Record one backup file path. Deduped, capped, never throws. */
5
+ export declare function recordConfigBackup(backupPath: string): void;
6
+ /** Drop entries whose backup file no longer exists (post-cleanup compaction). */
7
+ export declare function pruneBackupManifest(): void;
@@ -0,0 +1,108 @@
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.backupManifestPath = backupManifestPath;
37
+ exports.readBackupManifest = readBackupManifest;
38
+ exports.recordConfigBackup = recordConfigBackup;
39
+ exports.pruneBackupManifest = pruneBackupManifest;
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ /**
44
+ * Ledger of every `*.fcd-backup-*` config safety copy the CLI creates.
45
+ *
46
+ * MCP-config edits back the original file up next to it — including
47
+ * PROJECT-LOCAL configs (a repo's .cursor/mcp.json), which live in places no
48
+ * directory scan can enumerate. Those backups can embed live credentials, so
49
+ * `uninstall` / `verify-removed` must be able to find every one of them: each
50
+ * backup path is recorded here at creation time and the manifest is the
51
+ * authoritative list at cleanup time (union-ed with the known-dir scan for
52
+ * backups created by older CLI versions).
53
+ *
54
+ * Best-effort by design: a manifest write failure must never break a protect
55
+ * operation, and cleanup falls back to the directory scan.
56
+ */
57
+ const MANIFEST_NAME = 'backup-manifest.json';
58
+ const MAX_ENTRIES = 2000;
59
+ function backupManifestPath() {
60
+ return path.join(os.homedir(), '.fullcourtdefense', MANIFEST_NAME);
61
+ }
62
+ /** Every recorded backup path (absent/corrupt manifest = empty list). */
63
+ function readBackupManifest() {
64
+ try {
65
+ const raw = JSON.parse(fs.readFileSync(backupManifestPath(), 'utf8'));
66
+ const entries = Array.isArray(raw?.backups) ? raw.backups : [];
67
+ return entries.filter((entry) => typeof entry === 'string' && entry.length > 0);
68
+ }
69
+ catch {
70
+ return [];
71
+ }
72
+ }
73
+ /** Record one backup file path. Deduped, capped, never throws. */
74
+ function recordConfigBackup(backupPath) {
75
+ try {
76
+ const existing = readBackupManifest();
77
+ if (existing.includes(backupPath))
78
+ return;
79
+ const entries = [...existing, backupPath].slice(-MAX_ENTRIES);
80
+ const file = backupManifestPath();
81
+ fs.mkdirSync(path.dirname(file), { recursive: true });
82
+ fs.writeFileSync(file, `${JSON.stringify({ backups: entries }, null, 2)}\n`, 'utf8');
83
+ }
84
+ catch { /* best effort — cleanup still has the directory scan */ }
85
+ }
86
+ /** Drop entries whose backup file no longer exists (post-cleanup compaction). */
87
+ function pruneBackupManifest() {
88
+ try {
89
+ const existing = readBackupManifest();
90
+ const remaining = existing.filter(entry => {
91
+ try {
92
+ return fs.existsSync(entry);
93
+ }
94
+ catch {
95
+ return true;
96
+ }
97
+ });
98
+ if (remaining.length === existing.length)
99
+ return;
100
+ const file = backupManifestPath();
101
+ if (remaining.length === 0) {
102
+ fs.rmSync(file, { force: true });
103
+ return;
104
+ }
105
+ fs.writeFileSync(file, `${JSON.stringify({ backups: remaining }, null, 2)}\n`, 'utf8');
106
+ }
107
+ catch { /* best effort */ }
108
+ }
@@ -1187,6 +1187,8 @@ async function runDaemon(args, config) {
1187
1187
  const result = (0, windowsAudit_1.pruneTranscripts)();
1188
1188
  if (result.pruned > 0)
1189
1189
  log(`Transcript retention: pruned ${result.pruned} transcript file(s) older than ${(0, windowsAudit_1.transcriptRetentionDays)()} day(s).`);
1190
+ if ((result.prunedBySize || 0) > 0)
1191
+ log(`Transcript retention: pruned ${result.prunedBySize} more transcript file(s) (oldest first) to stay under the ${(0, windowsAudit_1.transcriptMaxMb)()}MB size budget.`);
1190
1192
  }
1191
1193
  catch { /* never let housekeeping hurt the daemon */ }
1192
1194
  };
@@ -258,9 +258,18 @@ function disabled(options) {
258
258
  function isEnabled(itemId, options) {
259
259
  return !disabled(options).has(itemId);
260
260
  }
261
+ /**
262
+ * Items that WARN by default (catalog-level default action). Applies only when
263
+ * the snapshot carries no explicit action for the item — an org override to
264
+ * 'block' ships explicitly in the snapshot and wins. Keeps offline machines
265
+ * and stale snapshots on the warn-first rollout instead of hard-blocking.
266
+ */
267
+ const DEFAULT_WARN_BUILT_INS = new Set(['env_workspace']);
261
268
  function actionFor(itemId, options) {
262
269
  const action = options?.itemActions?.[itemId];
263
- return action === 'warn' || action === 'mask' ? action : 'block';
270
+ if (action === 'warn' || action === 'mask' || action === 'block')
271
+ return action;
272
+ return DEFAULT_WARN_BUILT_INS.has(itemId) ? 'warn' : 'block';
264
273
  }
265
274
  function builtIn(itemId, categoryId, category, ruleId, reason, findingEvidence, explanation, options) {
266
275
  if (!isEnabled(itemId, options))
@@ -396,8 +405,14 @@ function containsSensitiveCredentialPath(value) {
396
405
  if (check.re.test(text))
397
406
  return { itemId: check.itemId, label: check.label };
398
407
  }
399
- if (traversal && /(?:^|\/)\.env(?:\.[a-z0-9_-]+)?(?:$|[/?#\s'"])/.test(text)) {
400
- return { itemId: 'env_traversal', label: 'environment file via path traversal' };
408
+ if (/(?:^|\/)\.env(?:\.[a-z0-9_-]+)?(?:$|[/?#\s'"])/.test(text)) {
409
+ // Traversal to a .env outside the workspace stays a hard block
410
+ // (env_traversal); a plain workspace .env is a separate warn-by-default
411
+ // item (env_workspace) — agents legitimately read it during development,
412
+ // but the fleet should still see every touch.
413
+ return traversal
414
+ ? { itemId: 'env_traversal', label: 'environment file via path traversal' }
415
+ : { itemId: 'env_workspace', label: 'workspace environment file' };
401
416
  }
402
417
  return undefined;
403
418
  }
@@ -1097,6 +1097,11 @@ async function discoverCommand(args, config) {
1097
1097
  printAgentFilesReport(agentFiles, silent);
1098
1098
  if (posture)
1099
1099
  printPostureReport(posture, silent);
1100
+ // Explicit scope boundary: a broad security/credential report during an
1101
+ // active incident can reasonably be over-read as endpoint protection.
1102
+ if (!silent) {
1103
+ console.log(`${COLOR.gray}Scope: this discovery inventories AI agents, MCP servers, credentials, and machine posture. It is NOT antivirus/EDR — binaries, running processes, and quarantined files are not scanned. Keep your endpoint protection (e.g. Microsoft Defender) active alongside it.${COLOR.reset}`);
1104
+ }
1100
1105
  if (uploadRequested) {
1101
1106
  console.log('');
1102
1107
  await maybeUpload();
@@ -47,6 +47,7 @@ const taintLedger_1 = require("./taintLedger");
47
47
  const notify_1 = require("../notify");
48
48
  const distress_1 = require("../distress");
49
49
  const policyGateHealth_1 = require("../policyGateHealth");
50
+ const machineIdentity_1 = require("../machineIdentity");
50
51
  const actionPolicyEngine_1 = require("../actionPolicyEngine");
51
52
  const DEBUG_LOG = path.join(os.homedir(), '.fullcourtdefense-hook.log');
52
53
  /**
@@ -533,7 +534,16 @@ function machineMetadata(client = 'cursor') {
533
534
  username = os.userInfo().username;
534
535
  }
535
536
  catch { /* ignore */ }
537
+ // Stable fleet machine identity: lets the backend attribute this decision to
538
+ // THIS machine exactly (timeline + behavior baseline), instead of matching
539
+ // by hostname. Best-effort — identity must never break the hook.
540
+ let machineId;
541
+ try {
542
+ machineId = (0, machineIdentity_1.getMachineIdentity)().machineId;
543
+ }
544
+ catch { /* ignore */ }
536
545
  return {
546
+ ...(machineId ? { machineId } : {}),
537
547
  developerName: developerId(),
538
548
  machineName: os.hostname(),
539
549
  os: `${os.platform()} ${os.release()}`,
@@ -44,7 +44,8 @@ async function installAllCommand(args, config) {
44
44
  // Prompt secrets are checked locally; prompt text never leaves the machine.
45
45
  // No failClosed flag: the offline stance is server-authoritative (from
46
46
  // the runtime bundle), unified with the Claude/VS Code hook below.
47
- events: 'prompt,shell,mcp',
47
+ // file + read close the "agent reads ~/.aws/credentials silently" hole.
48
+ events: 'prompt,shell,mcp,file,read',
48
49
  };
49
50
  try {
50
51
  await (0, installCursorHook_1.installCursorHookCommand)(hookArgs, config);
@@ -72,9 +73,11 @@ async function installAllCommand(args, config) {
72
73
  }
73
74
  // Windows-wide command auditing — PowerShell ScriptBlock Logging + Transcription.
74
75
  // Covers commands typed in ANY PowerShell (inside or outside the IDE), not just
75
- // agent-driven actions. Requires ONE admin (UAC) approval; failure is a warning,
76
- // never an install blocker missing coverage shows on the fleet dashboard.
77
- if (process.platform === 'win32' && args.windowsAudit !== 'false') {
76
+ // agent-driven actions. OFF BY DEFAULT: it flips machine-wide HKLM policy and
77
+ // transcription writes sizeable plaintext logs to ProgramData that footprint
78
+ // is an explicit opt-in (--windows-audit true, or org policy), not a side
79
+ // effect of installing protection. Requires ONE admin (UAC) approval.
80
+ if (process.platform === 'win32' && args.windowsAudit === 'true') {
78
81
  console.log('\n\x1b[1mEnabling Windows PowerShell audit logging (ScriptBlock Logging + Transcription)…\x1b[0m');
79
82
  try {
80
83
  const current = (0, windowsAudit_1.getWindowsAuditStatus)();
@@ -97,6 +100,9 @@ async function installAllCommand(args, config) {
97
100
  console.log(`\x1b[33m⚠ Warning: PowerShell audit logging step failed (${error instanceof Error ? error.message : String(error)}). Installation continues.\x1b[0m`);
98
101
  }
99
102
  }
103
+ else if (process.platform === 'win32' && args.windowsAudit !== 'false') {
104
+ console.log('\n\x1b[2mPowerShell audit logging (ScriptBlock Logging + Transcription) is OFF by default — it changes machine-wide policy and writes command transcripts to disk. Opt in with --windows-audit true, or later via: fullcourtdefense windows-audit --enable true\x1b[0m');
105
+ }
100
106
  // Interactive PowerShell guard — blocks dangerous commands TYPED in a
101
107
  // terminal (inside or outside the IDE) using the same rule families the IDE
102
108
  // hooks enforce, from the local cache (no admin needed, no network hot path).
@@ -147,7 +147,7 @@ function repairCursorManagedHooks() {
147
147
  const json = readHooksJson(before.file);
148
148
  let changed = false;
149
149
  if (before.managedEntries === 0) {
150
- for (const event of ['prompt', 'shell', 'mcp']) {
150
+ for (const event of ['prompt', 'shell', 'mcp', 'file', 'read']) {
151
151
  const { hookKey, flag, timeoutSec } = EVENT_MAP[event];
152
152
  const list = Array.isArray(json.hooks[hookKey]) ? json.hooks[hookKey] : [];
153
153
  list.push({
@@ -218,7 +218,11 @@ async function installCursorHookCommand(args, config) {
218
218
  const projectScope = args.project === 'true';
219
219
  const shadow = args.shadow === 'true';
220
220
  const file = hooksJsonPath(projectScope);
221
- const requested = (args.events || 'prompt,shell,mcp')
221
+ // file + read are in the default set: without them a Cursor agent can read
222
+ // ~/.aws/credentials or write a poisoned config with no hook ever firing —
223
+ // the exact coverage hole customers assume is closed. Opt out with
224
+ // --events prompt,shell,mcp.
225
+ const requested = (args.events || 'prompt,shell,mcp,file,read')
222
226
  .split(/[\s,]+/).map((e) => e.trim().toLowerCase()).filter(Boolean);
223
227
  const events = requested.filter((e) => EVENT_MAP[e]);
224
228
  if (events.length === 0) {
@@ -63,6 +63,8 @@ const telemetry_1 = require("../telemetry");
63
63
  const notify_1 = require("../notify");
64
64
  const distress_1 = require("../distress");
65
65
  const actionPolicyEngine_1 = require("../actionPolicyEngine");
66
+ const machineIdentity_1 = require("../machineIdentity");
67
+ const backupManifest_1 = require("../backupManifest");
66
68
  const DEFAULT_API_URL = 'https://api.fullcourtdefense.ai';
67
69
  const MANAGED_SERVER_NAME = 'agentguard-gateway';
68
70
  const INSTALL_GATEWAY_ARGS = { includeShieldKey: false };
@@ -230,7 +232,15 @@ function resolveGatewayConfig(args, config, defaults = {}) {
230
232
  }
231
233
  function machineMetadata(developerName, agentClient = 'cursor') {
232
234
  const sessionId = runtimeSessionId();
235
+ // Exact fleet machine attribution for the backend's runtime decision mirror
236
+ // (timeline + behavior baseline). Best-effort — never break the gateway.
237
+ let machineId;
238
+ try {
239
+ machineId = (0, machineIdentity_1.getMachineIdentity)().machineId;
240
+ }
241
+ catch { /* ignore */ }
233
242
  return {
243
+ ...(machineId ? { machineId } : {}),
234
244
  developerName: developerName || machineScopedUser(),
235
245
  machineName: os.hostname(),
236
246
  os: `${os.platform()} ${os.release()}`,
@@ -1621,6 +1631,9 @@ function backupFile(file) {
1621
1631
  try {
1622
1632
  const bak = `${file}.fcd-backup-${Date.now()}`;
1623
1633
  fs.copyFileSync(file, bak);
1634
+ // Backups can embed live credentials and land in project-local dirs no
1635
+ // scan can enumerate — record each one so uninstall can delete them all.
1636
+ (0, backupManifest_1.recordConfigBackup)(bak);
1624
1637
  }
1625
1638
  catch { /* best effort */ }
1626
1639
  }
@@ -15,6 +15,8 @@ export interface OnboardArgs extends InstallAllArgs {
15
15
  json?: string;
16
16
  /** Do not enable optional self-healing protection. */
17
17
  noDaemon?: string;
18
+ /** 'true' => skip the interactive trust disclosure (automation/MDM; the MSI consents via its own dialog). */
19
+ acceptDisclosure?: string;
18
20
  }
19
21
  /**
20
22
  * `fullcourtdefense onboard` — the one-command path from a fresh machine to a
@@ -63,6 +63,50 @@ const YELLOW = '\x1b[33m';
63
63
  const BOLD = '\x1b[1m';
64
64
  const DIM = '\x1b[2m';
65
65
  const RESET = '\x1b[0m';
66
+ /**
67
+ * The trust disclosure — the npm/CLI twin of the MSI EnrollmentDlg text
68
+ * (FullCourtDefense.wxs). A user installing from npm must see the same trust
69
+ * boundary the Windows installer shows BEFORE anything touches the machine:
70
+ * what gets installed, what runs in the background, and what data leaves the
71
+ * device. Keep both texts in sync when either changes.
72
+ *
73
+ * Consent model: interactive terminals must confirm; `--accept-disclosure
74
+ * true` / FCD_ACCEPT_DISCLOSURE=1 skip the prompt for automation and MDM;
75
+ * non-interactive runs (MSI custom action — consent was the Install click)
76
+ * print the text and continue, because a hidden prompt would hang the install.
77
+ * Returns false when the user explicitly declined.
78
+ */
79
+ async function confirmTrustDisclosure(args) {
80
+ console.log(`${BOLD}Before continuing, understand what FullCourtDefense sets up on this machine:${RESET}`);
81
+ console.log(`${DIM} - background protection tasks (resident daemon + watchdog) that start with your session`);
82
+ console.log(' - IDE/terminal hooks, and MCP tool traffic routed through a local security gateway');
83
+ console.log(" - agent/tool activity (commands, tool-call argument summaries) sent to your organization's");
84
+ console.log(' FullCourtDefense console for policy decisions and audit');
85
+ console.log(' - intended for organization-managed devices');
86
+ console.log(` - removed with \`fullcourtdefense uninstall\`${process.platform === 'win32' ? ' (MSI installs: Windows Settings > Apps)' : ''}${RESET}`);
87
+ console.log('');
88
+ const accepted = args.acceptDisclosure === 'true'
89
+ || process.env.FCD_ACCEPT_DISCLOSURE === '1'
90
+ || process.env.FCD_ACCEPT_DISCLOSURE === 'true';
91
+ if (accepted) {
92
+ console.log(`${DIM}Disclosure accepted via --accept-disclosure / FCD_ACCEPT_DISCLOSURE.${RESET}\n`);
93
+ return true;
94
+ }
95
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
96
+ // Non-interactive (MSI custom action, CI, MDM) — consent happened upstream
97
+ // (installer dialog / management policy); prompting here would hang.
98
+ console.log(`${DIM}Non-interactive run — continuing (pass --accept-disclosure true to make consent explicit in automation).${RESET}\n`);
99
+ return true;
100
+ }
101
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
102
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
103
+ const answer = await new Promise(resolve => {
104
+ rl.question(`${BOLD}Continue? [y/N] ${RESET}`, resolve);
105
+ });
106
+ rl.close();
107
+ console.log('');
108
+ return /^y(es)?$/i.test(answer.trim());
109
+ }
66
110
  function printCheck(check) {
67
111
  const mark = check.ok ? `${GREEN}✓${RESET}` : check.optional ? `${YELLOW}○${RESET}` : `${RED}✗${RESET}`;
68
112
  const detail = check.detail ? ` ${DIM}${check.detail}${RESET}` : '';
@@ -193,10 +237,10 @@ function checkTerminalGuards() {
193
237
  const audit = (0, windowsAudit_1.getWindowsAuditStatus)();
194
238
  const auditOk = audit.scriptBlockLogging && audit.transcription;
195
239
  checks.push({
196
- label: 'PowerShell audit logging',
240
+ label: 'PowerShell audit logging (opt-in)',
197
241
  ok: auditOk,
198
242
  optional: true,
199
- detail: auditOk ? undefined : 'run windows-audit --enable true (needs one UAC approval)',
243
+ detail: auditOk ? undefined : 'off by default — enable with windows-audit --enable true (one UAC approval; writes transcripts to ProgramData)',
200
244
  });
201
245
  }
202
246
  else {
@@ -234,6 +278,13 @@ async function onboardCommand(args, config) {
234
278
  };
235
279
  console.log(`\n${BOLD}\x1b[36mFullCourtDefense — machine onboarding${RESET}`);
236
280
  console.log(`${DIM}Connectivity -> enrollment -> protection -> verification. One command.${RESET}\n`);
281
+ // Trust disclosure first — nothing touches the machine before consent.
282
+ // Dry runs report the plan without changing anything, so no prompt there.
283
+ if (!dryRun && !(await confirmTrustDisclosure(args))) {
284
+ console.log(`${YELLOW}Onboarding cancelled — nothing was installed or changed on this machine.${RESET}`);
285
+ process.exitCode = 1;
286
+ return;
287
+ }
237
288
  // Onboarding is a persisted transaction: completed steps are reused on any
238
289
  // re-run, so `--resume` is an explicit alias that also surfaces the journal.
239
290
  const resume = args.resume === 'true' || args.resume === '';
@@ -1,18 +1,67 @@
1
1
  import { BotGuardConfig } from '../config';
2
2
  import { ProtectAllArgs } from './mcpGateway';
3
3
  export interface UninstallAllArgs extends ProtectAllArgs {
4
- /** 'true' => also delete local state/config files (~/.fullcourtdefense*). */
4
+ /** Kept for backward compatibility `uninstall` now always purges. */
5
5
  purge?: string;
6
+ /** 'true' => never hand off to the Windows (MSI) uninstaller. Advanced/CI. */
7
+ localOnly?: string;
8
+ /** Optional one-line reason recorded with the fleet uninstall report. */
9
+ reason?: string;
6
10
  }
7
11
  /**
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
+ * Every `*.fcd-backup-*` config safety copy: the known client dirs (scan)
13
+ * union-ed with the backup manifest, which records PROJECT-LOCAL backups
14
+ * (a repo's .cursor/mcp.json.fcd-backup-*) that no directory scan can find.
15
+ * The scan still covers backups created by pre-manifest CLI versions.
16
+ */
17
+ export declare function listFcdConfigBackups(): string[];
18
+ /** Delete every known `*.fcd-backup-*` config safety copy (scan + manifest). */
19
+ export declare function removeFcdConfigBackups(): {
20
+ removed: string[];
21
+ failures: string[];
22
+ };
23
+ /**
24
+ * Delete all local FullCourtDefense state: config + spool + logs, the
25
+ * ~/.fullcourtdefense dir (machine key file lives inside), the Credential
26
+ * Manager entry, %LOCALAPPDATA%\FullCourtDefense, and config backups.
27
+ */
28
+ export declare function purgeLocalState(config: BotGuardConfig): {
29
+ failures: string[];
30
+ };
31
+ /**
32
+ * Product code of the installed FullCourtDefense MSI, from the registry
33
+ * uninstall hive (both 64-bit and WOW6432Node views). Undefined on npm
34
+ * installs or when the MSI entry is missing.
35
+ */
36
+ export declare function parseMsiProductCodeFromRegOutput(output: string): string | undefined;
37
+ export declare function findMsiProductCode(): string | undefined;
38
+ /**
39
+ * `fullcourtdefense detach` — reverse `onboard`/`install-all` protection but
40
+ * KEEP machine credentials and the installed application, so the machine can
41
+ * re-onboard without a new enrollment token. This is the old `uninstall`
42
+ * behavior, renamed to say what it actually does.
43
+ */
44
+ export declare function detachCommand(args: UninstallAllArgs, config: BotGuardConfig): Promise<void>;
45
+ /**
46
+ * Tell the fleet console this machine is being uninstalled, BEFORE the local
47
+ * credential purge erases the shield key we need to authenticate the call.
48
+ * Best-effort with a short timeout: an offline uninstall must never hang or
49
+ * fail because the control plane is unreachable. On success the console shows
50
+ * the machine as "Uninstalled" instead of silently ghosting until an admin
51
+ * notices the missing heartbeats.
52
+ */
53
+ export declare function reportUninstallToFleet(config: BotGuardConfig, reason?: string): Promise<boolean>;
54
+ /**
55
+ * `fullcourtdefense uninstall` — COMPLETE removal.
56
+ *
57
+ * On MSI installs (Windows): runs the local teardown + credential purge, then
58
+ * hands off to the Windows uninstaller (`msiexec /x`) so the SYSTEM updater
59
+ * task, HKLM audit policies, and the installed application are all removed in
60
+ * the same flow — one command, one UAC prompt, machine back to its pre-FCD
61
+ * state. On npm installs: local teardown + purge, then tells the user the one
62
+ * remaining step (removing the npm package itself).
12
63
  *
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).
64
+ * To detach protection but keep credentials/the app, use `fullcourtdefense
65
+ * detach` instead.
17
66
  */
18
67
  export declare function uninstallAllCommand(args: UninstallAllArgs, config: BotGuardConfig): Promise<void>;