import { BotGuardConfig } from '../config'; /** * `fullcourtdefense hook` — Cursor hook bridge. * * Cursor invokes this command for agent lifecycle events (beforeSubmitPrompt, * beforeShellExecution, beforeMCPExecution, afterFileEdit, beforeReadFile). * Cursor pipes a JSON payload on stdin and reads a JSON verdict on stdout. * * Two enforcement engines, by event: * - prompt -> Shield text analysis (POST /api/shield/proxy/{shieldId}). * Action Policies don't apply to free-form chat, so prompts are * judged by the developer-chat Shield (monitor or block per mode). * - shell / mcp / file / read -> Action Policies, the SAME engine the MCP * gateway uses (POST /api/agent-security/runtime/check-tool-call). * The org's policies attached to the runtime identity decide: * allow / block / require_approval — enforced in EVERY repo on the * machine, regardless of which folder Cursor is open in. * * require_approval pauses the action and polls the approval endpoint until a * human approves/rejects in the FullCourtDefense console (same as the gateway). */ export interface HookArgs { event?: string; apiUrl?: string; shieldId?: string; shieldKey?: string; shadow?: string; enforce?: string; fcdManaged?: string; failClosed?: string; localOnly?: string; timeout?: string; approvalMode?: string; approvalTimeoutMs?: string; approvalPollMs?: string; } type HookEvent = 'prompt' | 'shell' | 'mcp' | 'file' | 'read' | 'unknown'; /** * Claude-format hooks (Claude Code, VS Code Copilot agent mode, GitHub Copilot * CLI — all share the same schema) send `hook_event_name` + `tool_name` + * `tool_input` on stdin. Normalize that payload into the field shapes the rest * of this file already understands (command/file_path/tool_name/tool_input), * and map the tool onto our event taxonomy. * * Returns null when the payload is not Claude-format. */ export declare function normalizeClaudePayload(payload: Record): { event: HookEvent | 'ignore'; payload: Record; } | null; /** * IO seam: the hook logic emits its verdict through this interface instead of * touching process.stdout / process.exit directly, so the SAME evaluation can * run (a) in the per-event hook process — real stdio + exit — and (b) inside * the resident daemon's verdict IPC server, where output is captured and * returned over the pipe (see verdictIpc.ts). */ export interface HookIo { /** Raw stdin payload (already read; daemon mode receives it over IPC). */ stdin: string; stdinErr?: string; isTTY: boolean; write(text: string): void; writeErr(text: string): void; exit(code: number): never; } /** * Daemon-side entry: evaluate one hook request in-process and return the * verdict instead of writing/exiting. Everything the per-event hook process * would have loaded cold (config caches, runtime bundle, local-safety * snapshot, policy engine) is warm here — verdicts resolve in milliseconds. * Mirrors hookCommand's fail-open contract: any unexpected exception yields * an allow verdict, never an error the thin client would surface. */ export declare function evaluateHookRequest(args: HookArgs, stdinRaw: string, config: BotGuardConfig): Promise<{ stdout: string; stderr: string; exitCode: number; }>; /** * TOP-LEVEL FAIL-OPEN WRAPPER. An unexpected exception anywhere in the hook * must never surface as a stack trace, a nonzero exit, or a blocked command — * to the developer that is indistinguishable from a broken IDE. On any * unhandled error: emit an allow verdict (a merged shape both Cursor and * Claude formats read as "no opinion / allow"), one silent log line, one * spooled event for the org console, exit 0. Deliberate blocks always go * through respond() inside and never reach this catch. */ export declare function hookCommand(args: HookArgs, config: BotGuardConfig): Promise; export {};