@posthog/wizard 2.65.0 → 2.67.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.
- package/dist/{add-mcp-server-to-clients-ArtlMX9-.js → add-mcp-server-to-clients-DHTRbyJX.js} +228 -64
- package/dist/add-mcp-server-to-clients-DHTRbyJX.js.map +1 -0
- package/dist/{agent-interface-ORzGEFNM.js → agent-interface-tM_b0l__.js} +458 -76
- package/dist/agent-interface-tM_b0l__.js.map +1 -0
- package/dist/{agent-prompt-loader-D3w6sc34.js → agent-prompt-loader-BSMh5bfD.js} +5 -5
- package/dist/{agent-prompt-loader-D3w6sc34.js.map → agent-prompt-loader-BSMh5bfD.js.map} +1 -1
- package/dist/{agent-runner-Ca-e2TOQ.js → agent-runner-DD65OMkB.js} +153 -71
- package/dist/agent-runner-DD65OMkB.js.map +1 -0
- package/dist/analytics-B4Igh5nv.js +2 -0
- package/dist/{analytics-zBrIVjMV.js → analytics-CtIYzkK4.js} +2 -2
- package/dist/{analytics-zBrIVjMV.js.map → analytics-CtIYzkK4.js.map} +1 -1
- package/dist/{api-Wgb7AhrP.js → api-BJft9v6K.js} +3 -3
- package/dist/{api-Wgb7AhrP.js.map → api-BJft9v6K.js.map} +1 -1
- package/dist/bin.js +245 -102
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-DqsVmh4r.js → bounded-fs-BWqoFQvQ.js} +3 -3
- package/dist/{bounded-fs-DqsVmh4r.js.map → bounded-fs-BWqoFQvQ.js.map} +1 -1
- package/dist/{ci-install-D1QqmJIc.js → ci-install-BXGnVI7T.js} +18 -9
- package/dist/ci-install-BXGnVI7T.js.map +1 -0
- package/dist/{debug-Cm1hvkVy.js → debug-BsHD83Vy.js} +4 -4
- package/dist/debug-BsHD83Vy.js.map +1 -0
- package/dist/{debug-CtQ13sCW.js → debug-mO3yg4Z7.js} +1 -1
- package/dist/{defaults-BHhXrX9F.js → defaults-DI642M3n.js} +1 -1
- package/dist/{defaults-BHhXrX9F.js.map → defaults-DI642M3n.js.map} +1 -1
- package/dist/{env-api-key-CdI2XZIz.js → env-api-key-DS0ghlcO.js} +2 -2
- package/dist/{env-api-key-CdI2XZIz.js.map → env-api-key-DS0ghlcO.js.map} +1 -1
- package/dist/{environment-BhFvfkQg.js → environment-C7CKGIOE.js} +4 -4
- package/dist/{environment-BhFvfkQg.js.map → environment-C7CKGIOE.js.map} +1 -1
- package/dist/errors-BHervYGs.js +156 -0
- package/dist/errors-BHervYGs.js.map +1 -0
- package/dist/{file-watcher-Dciz09Rr.js → file-watcher-D1bdo9mq.js} +2 -2
- package/dist/{file-watcher-Dciz09Rr.js.map → file-watcher-D1bdo9mq.js.map} +1 -1
- package/dist/{headless-ui-B4FnKm11.js → headless-ui-6QuByWZ_.js} +2 -2
- package/dist/{headless-ui-B4FnKm11.js.map → headless-ui-6QuByWZ_.js.map} +1 -1
- package/dist/{interactive-DxHFVYr2.js → interactive-DUpW84WU.js} +2 -2
- package/dist/{interactive-DxHFVYr2.js.map → interactive-DUpW84WU.js.map} +1 -1
- package/dist/{local-dev-BN6NKQ0B.js → local-dev-DPZiIPCK.js} +1 -1
- package/dist/{local-dev-BN6NKQ0B.js.map → local-dev-DPZiIPCK.js.map} +1 -1
- package/dist/{mcp-t0UxAPur.js → mcp-CHo9h-WV.js} +2 -2
- package/dist/{mcp-t0UxAPur.js.map → mcp-CHo9h-WV.js.map} +1 -1
- package/dist/mcp-install-smoke-test.ts +477 -0
- package/dist/{mcp-prompt-streaming-ro98iUDc.js → mcp-prompt-streaming-CHRPxxd0.js} +4 -4
- package/dist/{mcp-prompt-streaming-ro98iUDc.js.map → mcp-prompt-streaming-CHRPxxd0.js.map} +1 -1
- package/dist/{non-interactive-BA2L_jDT.js → non-interactive-Cr-chffe.js} +7 -2
- package/dist/non-interactive-Cr-chffe.js.map +1 -0
- package/dist/{orchestrator-tools-vZmNsHy9.js → orchestrator-tools-Tx18_TMb.js} +3 -3
- package/dist/{orchestrator-tools-vZmNsHy9.js.map → orchestrator-tools-Tx18_TMb.js.map} +1 -1
- package/dist/{package-json-D0DCWpe5.js → package-json-GEU2VZlS.js} +1 -1
- package/dist/{package-json-D0DCWpe5.js.map → package-json-GEU2VZlS.js.map} +1 -1
- package/dist/{package-manager-DgqJdAQk.js → package-manager-CLfc31yf.js} +4 -4
- package/dist/package-manager-CLfc31yf.js.map +1 -0
- package/dist/{pi-nwrCARRp.js → pi-RlnYUHdb.js} +12 -11
- package/dist/{pi-nwrCARRp.js.map → pi-RlnYUHdb.js.map} +1 -1
- package/dist/{playground-xkEke_h0.js → playground-DQ633rje.js} +7 -7
- package/dist/{playground-xkEke_h0.js.map → playground-DQ633rje.js.map} +1 -1
- package/dist/{posthog-9To56cSd.js → posthog-DSn9bIZc.js} +2 -2
- package/dist/{posthog-9To56cSd.js.map → posthog-DSn9bIZc.js.map} +1 -1
- package/dist/{posthog-integration-CoPvN6YZ.js → posthog-integration-DFfBRGOC.js} +667 -37
- package/dist/posthog-integration-DFfBRGOC.js.map +1 -0
- package/dist/{provisioning-BYFSHSHY.js → provisioning-Cvy_vQHV.js} +3 -3
- package/dist/{provisioning-BYFSHSHY.js.map → provisioning-Cvy_vQHV.js.map} +1 -1
- package/dist/{queue-tools-BREbgGSC.js → queue-tools-BC9HxOV6.js} +33 -7
- package/dist/queue-tools-BC9HxOV6.js.map +1 -0
- package/dist/{registry-DGJQ_8QI.js → registry-DCAElOyj.js} +81 -81
- package/dist/{registry-DGJQ_8QI.js.map → registry-DCAElOyj.js.map} +1 -1
- package/dist/{security-wc-naGq5.js → security-CuSX3XVo.js} +21 -9
- package/dist/security-CuSX3XVo.js.map +1 -0
- package/dist/{setup-utils-Cz0wPLsQ.js → setup-utils-D1tv0v_R.js} +13 -13
- package/dist/{setup-utils-Cz0wPLsQ.js.map → setup-utils-D1tv0v_R.js.map} +1 -1
- package/dist/{start-tui-DDW9uVnD.js → start-tui-B5YfmqNr.js} +57 -25
- package/dist/start-tui-B5YfmqNr.js.map +1 -0
- package/dist/{steps-CZI0B24_.js → steps-CLaSMwfR.js} +7 -7
- package/dist/{steps-CZI0B24_.js.map → steps-CLaSMwfR.js.map} +1 -1
- package/dist/{store-xMkU8xAa.js → store-DzEgsAPN.js} +7 -6
- package/dist/{store-xMkU8xAa.js.map → store-DzEgsAPN.js.map} +1 -1
- package/dist/{subagent-BztacyoV.js → subagent-BUV6aD7p.js} +2 -2
- package/dist/{subagent-BztacyoV.js.map → subagent-BUV6aD7p.js.map} +1 -1
- package/dist/{task-CJ7x3McC.js → task-GnWId75q.js} +13 -12
- package/dist/{task-CJ7x3McC.js.map → task-GnWId75q.js.map} +1 -1
- package/dist/{task-stream-BK7JQ27G.js → task-stream-DSzvwJrD.js} +16 -9
- package/dist/task-stream-DSzvwJrD.js.map +1 -0
- package/dist/{tasks-FGzp_zBd.js → tasks-BO0W8ocx.js} +2 -2
- package/dist/{tasks-FGzp_zBd.js.map → tasks-BO0W8ocx.js.map} +1 -1
- package/dist/{telemetry-By56gAtF.js → telemetry-Vi9dWddJ.js} +3 -3
- package/dist/{telemetry-By56gAtF.js.map → telemetry-Vi9dWddJ.js.map} +1 -1
- package/dist/{terminal-C_rmkaku.js → terminal-B5wZK7aS.js} +73 -80
- package/dist/terminal-B5wZK7aS.js.map +1 -0
- package/dist/{tools-BpCJgMez.js → tools-C9cNluqc.js} +54 -47
- package/dist/tools-C9cNluqc.js.map +1 -0
- package/dist/{wizard-abort-BlDla_Jd.js → wizard-abort-BsNbguJt.js} +1 -1
- package/dist/wizard-abort-CGv3lVxw.js +71 -0
- package/dist/wizard-abort-CGv3lVxw.js.map +1 -0
- package/dist/{wizard-session-Bn6dO1gE.js → wizard-session-Cex1BiXs.js} +3 -2
- package/dist/wizard-session-Cex1BiXs.js.map +1 -0
- package/dist/{wizard-session-UxA_A0cA.js → wizard-session-DHbUcjI7.js} +1 -1
- package/dist/yara-hooks-C3Vm2Gow.js +2 -0
- package/dist/{yara-hooks-BrisBebM.js → yara-hooks-DZIVzhA6.js} +57 -8
- package/dist/yara-hooks-DZIVzhA6.js.map +1 -0
- package/package.json +2 -1
- package/dist/add-mcp-server-to-clients-ArtlMX9-.js.map +0 -1
- package/dist/agent-interface-ORzGEFNM.js.map +0 -1
- package/dist/agent-runner-Ca-e2TOQ.js.map +0 -1
- package/dist/analytics-C4j6grao.js +0 -2
- package/dist/ci-install-D1QqmJIc.js.map +0 -1
- package/dist/debug-Cm1hvkVy.js.map +0 -1
- package/dist/non-interactive-BA2L_jDT.js.map +0 -1
- package/dist/package-manager-DgqJdAQk.js.map +0 -1
- package/dist/posthog-integration-CoPvN6YZ.js.map +0 -1
- package/dist/queue-tools-BREbgGSC.js.map +0 -1
- package/dist/security-wc-naGq5.js.map +0 -1
- package/dist/start-tui-DDW9uVnD.js.map +0 -1
- package/dist/task-stream-BK7JQ27G.js.map +0 -1
- package/dist/terminal-C_rmkaku.js.map +0 -1
- package/dist/tools-BpCJgMez.js.map +0 -1
- package/dist/wizard-abort-CzwID162.js +0 -49
- package/dist/wizard-abort-CzwID162.js.map +0 -1
- package/dist/wizard-session-Bn6dO1gE.js.map +0 -1
- package/dist/yara-hooks-BrisBebM.js.map +0 -1
- package/dist/yara-hooks-D2B7Fd9G.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yara-hooks-DZIVzhA6.js","names":["EVENTS_AUDIT_REPORT_FILE"],"sources":["../src/lib/skill-install.ts","../src/lib/yara-policy.ts","../src/lib/programs/events-audit/constants.ts","../src/lib/programs/audit/types.ts","../src/lib/programs/posthog-integration/constants.ts","../src/lib/yara-hooks.ts"],"sourcesContent":["/**\n * Check if command is a PostHog skill installation from MCP.\n * We control the MCP server, so we only need to verify:\n * 1. It installs to .claude/skills/\n * 2. It downloads from our GitHub releases or localhost (dev)\n *\n * Extracted to its own module to avoid a circular dependency\n * between agent-interface.ts and yara-hooks.ts.\n */\nexport function isSkillInstallCommand(command: string): boolean {\n if (!command.startsWith('mkdir -p .claude/skills/')) return false;\n\n const urlMatch = command.match(/curl -sL ['\"]([^'\"]+)['\"]/);\n if (!urlMatch) return false;\n\n const url = urlMatch[1];\n return (\n url.startsWith('https://github.com/PostHog/context-mill/releases/') ||\n /^http:\\/\\/localhost:\\d+\\//.test(url)\n );\n}\n","/**\n * Shared YARA scan policy: the vocabulary every scan surface speaks, and the\n * one decision they all make — does a set of matches terminate the session, or\n * only warn. Both fences (the anthropic PostToolUse hooks in `yara-hooks.ts`\n * and the pi tool_result extension in `harness/pi/security.ts`) resolve a\n * verdict here rather than each re-deriving it from severity and action.\n */\n\nimport type { ScanMatch } from '@posthog/warlock';\n\n/** Which content surface a warlock rule applies to (from rule `scan_context`). */\nexport type ScanContext = 'command' | 'input' | 'output';\n\n/** What a fence did about a match, as recorded in the scan report. */\nexport type ScanAction = 'blocked' | 'reverted' | 'warned' | 'aborted';\n\n/** Severity order, highest first, for picking the match worth reporting. */\nconst SEVERITY_RANK: Record<string, number> = {\n critical: 4,\n high: 3,\n medium: 2,\n low: 1,\n};\n\n/** Severities that end the session. Critical only — nothing below it is worth killing a run over. */\nconst TERMINAL_SEVERITIES = new Set(['critical']);\n\n/**\n * The match that ends the session rather than just warning. Severity alone\n * decides: a rule's `action: 'block'` is a recommendation about the *operation*\n * (PreToolUse still refuses the command), not a licence to end the run. Medium\n * and high `block` rules fire on first-party content often enough that\n * terminating on them costs more than it prevents.\n */\nexport function isTerminalMatch(match: ScanMatch): boolean {\n return TERMINAL_SEVERITIES.has(match.metadata.severity ?? '');\n}\n\n/** Return the highest-severity match from a list of matches. */\nexport function highestSeverityMatch(matches: ScanMatch[]): ScanMatch {\n return matches.reduce((worst, m) =>\n (SEVERITY_RANK[m.metadata.severity ?? ''] ?? 0) >\n (SEVERITY_RANK[worst.metadata.severity ?? ''] ?? 0)\n ? m\n : worst,\n );\n}\n\n/**\n * The match that refuses a publish, or null to allow. Critical only — every\n * 'output' rule asks for `remediate`, and the handoff has no second channel.\n */\nexport function publishBlockingMatch(matches: ScanMatch[]): ScanMatch | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n return isTerminalMatch(match) ? match : null;\n}\n\nexport interface ScanVerdict {\n /** The match worth reporting: the highest-severity one. */\n match: ScanMatch;\n /** Session ends now; anything else is advisory. */\n terminal: boolean;\n /** How the scan report records it. */\n action: ScanAction;\n}\n\n/** Resolve what a fence should do about a set of matches. Null when clean. */\nexport function scanVerdict(matches: ScanMatch[]): ScanVerdict | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n const terminal = isTerminalMatch(match);\n return { match, terminal, action: terminal ? 'aborted' : 'warned' };\n}\n","/**\n * Leaf-level constants for the events-audit program.\n *\n * Kept separate from `index.ts` so files like `yara-hooks.ts` can import\n * the filename constants without dragging in `index.ts`'s heavier imports\n * (agent-runner, audit/seed, etc.) — which can create import cycles.\n */\n\nexport const SETUP_REPORT_FILE = 'posthog-events-audit-report.md';\nexport const EVENT_INVENTORY_FILE = '.posthog-events-inventory.json';\n/** Per-part filename pattern emitted by events-audit subagents (e.g. `.posthog-events-inventory.part-3.json`). */\nexport const EVENT_INVENTORY_PART_PATTERN =\n /^\\.posthog-events-inventory\\.part-\\d+\\.json$/;\n","import type { WizardSession } from '@lib/wizard-session';\n\nexport type AuditStatus =\n | 'pending'\n | 'pass'\n | 'error'\n | 'warning'\n | 'suggestion';\n\nexport interface AuditCheck {\n id: string;\n area: string;\n label: string;\n status: AuditStatus;\n file?: string;\n details?: string;\n}\n\nexport interface AuditSeverityStyle {\n glyph: string;\n color: string;\n}\n\n/** Single source of truth for status glyph + color across audit views. */\nexport const AUDIT_SEVERITY_STYLE: Record<AuditStatus, AuditSeverityStyle> = {\n pending: { glyph: '◌', color: 'gray' },\n pass: { glyph: '✔', color: 'green' },\n error: { glyph: '✘', color: 'red' },\n warning: { glyph: '⚠', color: 'yellow' },\n suggestion: { glyph: '•', color: 'cyan' },\n};\n\nexport const AUDIT_CHECKS_FILE = '.posthog-audit-checks.json';\nexport const AUDIT_REPORT_FILE = 'posthog-audit-report.md';\nexport const AUDIT_CHECKS_KEY = 'auditChecks';\n\nexport function getAuditChecks(session: WizardSession): AuditCheck[] {\n const raw = session.frameworkContext[AUDIT_CHECKS_KEY];\n return Array.isArray(raw) ? (raw as AuditCheck[]) : [];\n}\n\n/**\n * Read the audit checks ledger off disk. Validation lives at write time —\n * every writer (`audit_seed_checks` / `audit_add_checks` / `audit_resolve_checks`\n * MCP tools, `seedAuditLedger`) zod-parses entries before the atomic write,\n * so by the time the file watcher fires we trust the shape and only guard\n * against the file not being a JSON array (corrupted / hand-edited / not yet\n * seeded).\n */\nexport function coerceAuditChecks(parsed: unknown): AuditCheck[] {\n return Array.isArray(parsed) ? (parsed as AuditCheck[]) : [];\n}\n","/**\n * Leaf-level constants for the posthog-integration program.\n *\n * Kept separate from `index.ts` so files like `yara-hooks.ts` can import\n * the filename constants without dragging in `index.ts`'s heavier imports\n * (agent-interface, framework-config, etc.) — which would create an import\n * cycle through agent-interface → yara-hooks.\n */\n\nexport const EVENT_PLAN_FILE = '.posthog-events.json';\n","/**\n * YARA hook wiring for the Claude Agent SDK.\n *\n * Creates PreToolUse and PostToolUse hook callback arrays that integrate the\n * real @posthog/warlock security scanner (YARA-X via WASM) into the wizard's\n * agent loop. These hooks are registered in the SDK's query() options alongside\n * the existing Stop hook.\n *\n * PreToolUse hooks block dangerous commands before execution. PostToolUse hooks\n * detect violations in written code and prompt injection in read content, and\n * scan context-mill skill downloads.\n *\n * Warlock owns the rules; this file owns the *policy* — how a match maps to a\n * block / revert / terminate response, plus the optional LLM triage pass that\n * filters false positives before we act.\n *\n * Naming: \"yara\" is the technique — scanning content against YARA rules — so the\n * wizard-side wiring keeps that name. \"warlock\" is the engine package that runs\n * the rules. Both names showing up here is intentional.\n *\n * This is Layer 2 (L2) in the wizard's defense-in-depth model, complementing the\n * prompt-based commandments (L0) and the canUseTool() allowlist (L1).\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createHash } from 'crypto';\nimport fg from 'fast-glob';\nimport type {\n ScanMatch,\n TriageMatch,\n LLMProvider,\n Category,\n} from '@posthog/warlock';\nimport { logToFile } from '@utils/debug';\nimport { readFileHead } from '@utils/bounded-fs';\nimport { analytics } from '@utils/analytics';\nimport { getUI } from '@ui';\nimport type { WizardSession } from '@lib/wizard-session';\nimport { isSkillInstallCommand } from './skill-install';\nimport {\n highestSeverityMatch,\n publishBlockingMatch,\n scanVerdict,\n} from './yara-policy';\nimport type { ScanAction, ScanContext } from './yara-policy';\nimport { WIZARD_YARA_REPORT_FILE } from '@utils/paths';\n// TODO(wizard#594): invert this dependency.\n// L2 infra (yara-hooks) imports product-specific filename constants from\n// individual programs. The leaf `constants.ts` modules break the *import*\n// cycle but don't fix the *layering* concern: this file knowing about\n// `events-audit`, `posthog-integration`, and `audit` violates \"product\n// knowledge never enters infrastructure code.\" Proper fix is inverted —\n// programs declare their own doc paths, the hooks read from a generic\n// registry. For now: every new program emitting a PII-shaped report has\n// to be added here. Land that cleanup before adding a fourth entry.\nimport {\n SETUP_REPORT_FILE as EVENTS_AUDIT_REPORT_FILE,\n EVENT_INVENTORY_FILE,\n EVENT_INVENTORY_PART_PATTERN,\n} from '@lib/programs/events-audit/constants';\nimport { AUDIT_REPORT_FILE } from '@lib/programs/audit/types';\nimport { EVENT_PLAN_FILE } from '@lib/programs/posthog-integration/constants';\n\n// ─── Warlock module accessor ─────────────────────────────────────\n// Warlock is ESM-only and lazily inits its WASM engine + compiles rules on the\n// first scan() call. We import it once and cache the module promise so the\n// warmed engine (a warlock-internal singleton) is shared across every hook.\n// Mirrors the lazy `await import(...)` pattern used for the agent SDK.\n\ntype WarlockModule = typeof import('@posthog/warlock');\n\nlet warlockModulePromise: Promise<WarlockModule> | null = null;\n\nfunction getWarlock(): Promise<WarlockModule> {\n if (!warlockModulePromise) {\n // Clear the cache on rejection so a transient failure (flaky disk, ESM\n // resolver hiccup) doesn't poison the cache for the rest of the process\n // lifetime. Without this, one rejected import permanently locks every\n // hook into fail-closed-terminate until restart.\n warlockModulePromise = import('@posthog/warlock').catch((err) => {\n warlockModulePromise = null;\n throw err;\n });\n }\n return warlockModulePromise;\n}\n\n/**\n * Pay the WASM-init + rule-compile cost up front, off the hook path, so the\n * first real tool-call scan doesn't eat cold-start under a hook timeout.\n * Best-effort: a failure here is non-fatal — hooks still fail closed per scan.\n */\nexport async function prewarmYaraScanner(): Promise<void> {\n try {\n const warlock = await getWarlock();\n await warlock.scan('');\n logToFile('[YARA] warlock pre-warmed');\n } catch (err) {\n logToFile('[YARA] warlock pre-warm failed:', err);\n }\n}\n\n// ─── Types ───────────────────────────────────────────────────────\n// Loose hook types to avoid tight coupling to the SDK version.\n// The SDK hook types are: HookCallbackMatcher[], where each matcher\n// has { matcher?: string, hooks: HookCallback[], timeout?: number }\n\ntype HookInput = Record<string, unknown>;\ntype HookOutput = Record<string, unknown>;\ntype HookCallback = (\n input: HookInput,\n toolUseID: string | undefined,\n options: { signal: AbortSignal },\n) => Promise<HookOutput>;\n\nexport interface HookCallbackMatcher {\n matcher?: string;\n hooks: HookCallback[];\n timeout?: number;\n}\n\nexport type { ScanAction, ScanContext } from './yara-policy';\n\n// ─── Scan Report Accumulator ─────────────────────────────────────\n\ninterface ScanReportEntry {\n rule: string;\n severity: string;\n category: string;\n action: ScanAction;\n phase: string;\n tool: string;\n /** Rule description — the human-readable \"why\". Rule-static, safe to report. */\n description: string;\n /** Triage outcome, when triage ran. */\n triageVerdict?: string;\n /**\n * Free-text triage rationale. May quote scanned content, so it is written to\n * the local report file only — NEVER sent to PostHog (see captureScanReport).\n */\n triageReason?: string;\n}\n\nlet scanCount = 0;\nconst scanViolations: ScanReportEntry[] = [];\n\nfunction recordScan(): void {\n scanCount++;\n}\n\n// recordMatch is the single entry point for a violation (log + telemetry +\n// report). scanViolations is appended only there.\n\n/**\n * Log a match to the run log + PostHog, and record it in the run's scan report.\n * Single entry point so every violation is reported consistently.\n */\nfunction recordMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const triage = (match as TriageMatch).triage;\n logYaraMatch(phase, tool, match, action);\n scanViolations.push({\n rule: match.rule,\n severity: match.metadata.severity ?? 'unknown',\n category: match.metadata.category ?? 'unknown',\n action,\n phase,\n tool,\n description: match.metadata.description ?? '',\n triageVerdict: triage?.verdict,\n triageReason: triage?.reason,\n });\n}\n\n/** Reset counters (for testing) */\nexport function resetScanReport(): void {\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * Scan bookkeeping for harnesses that run a scanner outside these hooks (pi):\n * counts the scan and routes violations through `recordMatch`, so they reach\n * the run log, per-match telemetry, and the end-of-run scan report.\n */\nexport function recordExternalScan(\n phase: string,\n tool: string,\n violations: Array<{\n rule: string;\n severity: string;\n category: string;\n description: string;\n }>,\n action: ScanAction,\n): void {\n recordScan();\n for (const v of violations) {\n recordMatch(\n phase,\n tool,\n {\n rule: v.rule,\n metadata: {\n severity: v.severity as ScanMatch['metadata']['severity'],\n category: v.category as ScanMatch['metadata']['category'],\n description: v.description,\n },\n matchedStrings: [],\n },\n action,\n );\n }\n}\n\n/** Format the scan report summary. Returns null if no scans occurred */\nexport function formatScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const lines: string[] = ['', '— YARA Scanner Summary —'];\n const violationCount = scanViolations.length;\n const cleanCount = scanCount - violationCount;\n\n lines.push(\n `✓ ${scanCount} tool calls scanned, ${violationCount} violation${\n violationCount !== 1 ? 's' : ''\n } detected`,\n );\n\n if (violationCount > 0) {\n lines.push('');\n for (const v of scanViolations) {\n const tag = v.action.toUpperCase();\n lines.push(\n ` [${tag}] ${v.rule} (${v.severity.toUpperCase()}) — ${v.phase}:${\n v.tool\n }`,\n );\n }\n }\n\n if (cleanCount > 0) {\n lines.push('');\n lines.push(\n `No violations: ✓ ${cleanCount} clean scan${cleanCount !== 1 ? 's' : ''}`,\n );\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */\nexport function writeScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const report = {\n summary: {\n totalScans: scanCount,\n violations: scanViolations.length,\n clean: scanCount - scanViolations.length,\n },\n violations: scanViolations,\n };\n\n try {\n fs.writeFileSync(WIZARD_YARA_REPORT_FILE, JSON.stringify(report, null, 2));\n } catch (err) {\n logToFile('[YARA] Failed to write scan report:', err);\n return null;\n }\n return WIZARD_YARA_REPORT_FILE;\n}\n\n// ─── User-facing abort copy ──────────────────────────────────────\n//\n// The abort screen is the one security surface a user actually sees, so it must\n// say what was caught and that we protected them, in plain language, instead of\n// a bare \"Security violation detected.\" Warlock owns the rules and categories;\n// this map is the wizard's *presentation* of a category it already detected. We\n// deliberately do not surface warlock's own rule `description` here: it is\n// written for maintainers (it ships to telemetry) and reads as technical. The\n// `Category` type is a stable, append-only warlock API, so a new category just\n// falls through to the generic line below rather than breaking the build.\n\nconst CATEGORY_DESCRIPTIONS: Record<Category, string> = {\n prompt_injection:\n 'hit hidden instructions in a file it read, a possible attempt to hijack the setup',\n exfiltration: 'tried to send data to an outside location',\n destructive_operations: 'tried to run a destructive command',\n supply_chain: 'tried to pull in untrusted code',\n posthog_pii: 'tried to write personal data (PII) into your project',\n posthog_hardcoded_key: 'tried to hardcode a PostHog key into your project',\n hardcoded_secret: 'tried to hardcode a secret into your project',\n};\n\n/** Most recent violation that terminated the run, or null. */\nfunction lastTerminatingViolation(): ScanReportEntry | null {\n for (let i = scanViolations.length - 1; i >= 0; i--) {\n if (scanViolations[i].action === 'aborted') return scanViolations[i];\n }\n return null;\n}\n\n/**\n * Build the user-facing copy for the security abort screen: what the scanner\n * caught and what we did about it. This screen renders only because the run was\n * stopped, so the \"what we did\" half is constant. Falls back to a generic line\n * when no specific violation was recorded (e.g. the scanner itself errored and\n * failed closed before a match was logged).\n */\nexport function formatYaraAbortMessage(): string {\n const v = lastTerminatingViolation();\n const what =\n (v && CATEGORY_DESCRIPTIONS[v.category as Category]) ??\n 'did something our security scanner flagged';\n\n return (\n 'Security check stopped the setup.\\n\\n' +\n `The agent ${what}. We stopped the run to keep your project safe.\\n` +\n 'No unsafe changes were left in your project.\\n\\n' +\n 'If this looks wrong, let us know: wizard@posthog.com'\n );\n}\n\n// ─── Hook Timeouts (ms) ─────────────────────────────────────────\n// Generous because triage adds an LLM round-trip, and because a *fired* hook\n// timeout means the protective action is dropped (fail-open). The triage HTTP\n// call has its own shorter internal timeout (see agent-interface), so a hung\n// triage fails inside our catch (fail-closed) before these fire.\n\n/** Timeout for scan hooks (PreToolUse, PostToolUse Write/Edit/Read/Grep) */\nconst HOOK_TIMEOUT_MS = 30_000;\n/** Timeout for the skill install hook (filesystem I/O + multiple scans) */\nconst SKILL_SCAN_HOOK_TIMEOUT_MS = 30_000;\n\n/**\n * Chunk size for scanning (100 KB). Oversized content is scanned in\n * overlapping chunks so coverage is complete — nothing is silently truncated.\n * The size also bounds what a single triage call pastes into the LLM prompt\n * (~25K tokens), so triage always sees the chunk its matches came from.\n */\nconst SCAN_CHUNK_SIZE = 100_000;\n\n// A skill file is read at most this far; the rest is head-scanned and logged.\nconst SKILL_FILE_SCAN_BYTES = 10 * 1024 * 1024;\n/**\n * Overlap between adjacent chunks so a pattern straddling a chunk boundary\n * still lands whole inside at least one chunk. YARA rule strings are at most\n * a few hundred bytes; 4 KB is generous.\n */\nconst SCAN_CHUNK_OVERLAP = 4_096;\n\n// ─── Logging ─────────────────────────────────────────────────────\n\nfunction logYaraMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const severity = match.metadata.severity ?? 'unknown';\n const category = match.metadata.category ?? 'unknown';\n const ruleAction = match.metadata.action ?? 'unknown';\n const description = match.metadata.description ?? '';\n logToFile(\n `[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule \"${match.rule}\" ` +\n `(severity: ${severity}, category: ${category}, action: ${ruleAction})\\n` +\n ` Description: ${description}`,\n );\n analytics.wizardCapture('yara rule matched', {\n rule: match.rule,\n severity: match.metadata.severity,\n category: match.metadata.category,\n rule_action: match.metadata.action,\n action,\n phase,\n tool,\n // Rule-static description — the \"why\". The free-text triage reason is\n // deliberately omitted (it can quote scanned content; stays local-only).\n description: match.metadata.description,\n triage_verdict: (match as TriageMatch).triage?.verdict,\n });\n}\n\n/**\n * Send the run's full scan report to PostHog so maintainers can see why a run\n * was flagged, warned, or aborted. This is the maintainer-facing report — it is\n * NEVER shown to the user. No-op if nothing was scanned. The free-text triage\n * reason is omitted to avoid sending scanned content off the user's machine.\n */\nexport function captureScanReport(): void {\n if (scanCount === 0) return;\n analytics.wizardCapture('yara scan report', {\n total_scans: scanCount,\n violation_count: scanViolations.length,\n clean_count: scanCount - scanViolations.length,\n violations: scanViolations.map((v) => ({\n rule: v.rule,\n severity: v.severity,\n category: v.category,\n action: v.action,\n phase: v.phase,\n tool: v.tool,\n description: v.description,\n triage_verdict: v.triageVerdict,\n })),\n });\n // Reset after sending so a second captureScanReport on this same run\n // (e.g. success-path call followed by an abort during shutdown) can't\n // ship duplicate telemetry. Also protects future chained-program runs.\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * End-of-run flush for the scan report. Wired once at the runner seam so every\n * harness (linear, orchestrator, and any future shape) reports without having\n * to know warlock exists — scanning is already harness-agnostic (it runs from\n * SDK Pre/PostToolUse hooks), and this keeps reporting at a single shared seam.\n *\n * Order matters: write the local --yara-report file FIRST (it reads scanCount /\n * scanViolations), THEN send telemetry. captureScanReport() zeroes scan state,\n * which is what makes this whole function idempotent — a second call from\n * another termination path (e.g. finally after an abort already flushed) finds\n * scanCount === 0 and every step no-ops.\n */\nexport function flushScanReport(\n session: Pick<WizardSession, 'yaraReport'>,\n): void {\n if (session.yaraReport) {\n const reportPath = writeScanReport();\n if (reportPath) {\n const summary = formatScanReport();\n getUI().log.info(`YARA scan report: ${reportPath}${summary ?? ''}`);\n }\n }\n captureScanReport();\n}\n\n// ─── Wizard-documentation allowlist ───────────────────────────────\n//\n// Files the wizard's own programs write to describe events the user's\n// codebase already captures, or events the integration program is\n// proposing to add. When the agent copies a literal\n// `posthog.capture('event', { email: ... })` snippet (or a property\n// list including PII-shaped keys) into one of these files, the\n// `pii_in_capture_call` rule (category: posthog_pii) fires even though\n// the wizard is documenting / planning, not introducing, the pattern.\n// Suppress posthog_pii matches on these paths only; every other rule\n// (secrets, prompt injection, supply chain, destructive ops) still\n// fires normally so the file cannot be used as a smuggling vector for\n// actual violations.\n\nconst WIZARD_DOC_BASENAMES = new Set([\n EVENT_INVENTORY_FILE,\n EVENTS_AUDIT_REPORT_FILE,\n AUDIT_REPORT_FILE,\n EVENT_PLAN_FILE,\n]);\n\nconst WIZARD_DOC_PATTERNS: RegExp[] = [EVENT_INVENTORY_PART_PATTERN];\n\nexport function isWizardDocumentationPath(\n filePath: string | undefined,\n): boolean {\n if (!filePath) return false;\n const basename = path.basename(filePath);\n if (WIZARD_DOC_BASENAMES.has(basename)) return true;\n return WIZARD_DOC_PATTERNS.some((re) => re.test(basename));\n}\n\n// ─── Repeat-block tracker ─────────────────────────────────────────\n//\n// Field data (switchboard YARA tile): an agent whose edit gets blocked often\n// retries the identical payload — the scan is deterministic, so every retry\n// blocks again and the run burns turns (~4.2 blocks per affected run). The\n// runtime notes already say \"don't retry\"; the data shows the prompt alone\n// doesn't hold. This is the code-side guard: remember the exact payloads we\n// blocked, and when the same content comes back, escalate the block reason —\n// first to \"change the code, not the retry\", then to \"report it and move on\".\n// Used by both fences: the anthropic PreToolUse Bash hook below, and the pi\n// security extension's pre-execution scan (bash/write/edit).\n\n/** Identical-payload attempt at which the agent is told to move on. */\nconst REPEAT_BLOCK_MOVE_ON_ATTEMPT = 3;\n\nexport interface RepeatBlockTracker {\n /**\n * Record a YARA block of `content` on `tool`; returns the attempt count\n * for that exact payload (1 = first time blocked).\n */\n attempt(tool: string, content: string): number;\n}\n\n/** Per-run tracker of YARA-blocked payloads (keyed by tool + content hash). */\nexport function createRepeatBlockTracker(): RepeatBlockTracker {\n const counts = new Map<string, number>();\n return {\n attempt(tool: string, content: string): number {\n const key = `${tool} ${createHash('sha256')\n .update(content)\n .digest('hex')}`;\n const next = (counts.get(key) ?? 0) + 1;\n counts.set(key, next);\n return next;\n },\n };\n}\n\n/**\n * The block reason for the given attempt: the plain reason the first time,\n * an explicit \"this exact retry can never work\" on the second, and a\n * \"report it and move on\" instruction from the third on.\n */\nexport function repeatBlockReason(\n attempt: number,\n tool: string,\n baseReason: string,\n): string {\n if (attempt <= 1) return baseReason;\n if (attempt < REPEAT_BLOCK_MOVE_ON_ATTEMPT) {\n return (\n `${baseReason} This exact ${tool} content was ALREADY blocked — the scan is ` +\n 'deterministic, so retrying identical content will always block again. ' +\n 'Change the code to remove the flagged pattern instead of retrying.'\n );\n }\n return (\n `${baseReason} This exact ${tool} content has now been blocked ${attempt} times. ` +\n 'STOP retrying it. Note the blocked change in the setup report and move on to the next task.'\n );\n}\n\n// ─── Severity helpers ────────────────────────────────────────────\n// Severity vocabulary and the terminate-or-warn decision live in yara-policy.\n\n// ─── Scan + triage core ──────────────────────────────────────────\n\n/**\n * Keep only matches whose rule targets this content surface. Rules carry a\n * `scan_context` of 'command' | 'input' | 'output'. An undefined context is\n * treated as \"applies everywhere\" (fail-safe — a rule that forgets the tag\n * still gets enforced rather than silently skipped).\n */\nfunction matchesForContext(\n matches: ScanMatch[],\n ctx: ScanContext,\n): ScanMatch[] {\n return matches.filter((m) => {\n const c = m.metadata.scan_context as string | undefined;\n return c === ctx || c === undefined;\n });\n}\n\n/**\n * Drop false positives via warlock's LLM triage. Fail-closed: if no provider is\n * available, or the triage call throws, every match is treated as real — we\n * never silently suppress a flagged match.\n *\n * Every overruled match is reported to PostHog (rule metadata only, never the\n * free-text reason) so maintainers can alert on triage-overrule patterns — the\n * signal that someone is either tripping a noisy rule or trying to talk the\n * triage model out of a real finding.\n */\nasync function triageFilter(\n content: string,\n matches: ScanMatch[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n if (matches.length === 0) return [];\n if (!llmProvider) {\n logToFile(\n `[YARA] triage skipped (no provider) — treating ${matches.length} match(es) as real`,\n );\n return matches;\n }\n try {\n const warlock = await getWarlock();\n const triaged = await warlock.triageMatches(content, matches, llmProvider);\n const kept = triaged.filter((m) => m.triage.verdict === 'true_positive');\n for (const m of triaged) {\n if (m.triage.verdict === 'true_positive') continue;\n logToFile(\n `[YARA] triage overruled rule \"${m.rule}\" (${\n m.metadata.severity ?? 'unknown'\n }) — not acting on it`,\n );\n analytics.wizardCapture('yara triage overruled', {\n rule: m.rule,\n severity: m.metadata.severity,\n category: m.metadata.category,\n scan_context: ctx,\n // The free-text triage reason is deliberately omitted — it can quote\n // scanned content, which must never leave the user's machine.\n });\n }\n logToFile(\n `[YARA] triage: ${matches.length} flagged → ${kept.length} kept as real`,\n );\n return kept;\n } catch (err) {\n logToFile('[YARA] triage failed — treating all matches as real:', err);\n return matches;\n }\n}\n\n/** A chunk of scanned content together with the matches found inside it. */\ninterface FlaggedChunk {\n chunk: string;\n matches: ScanMatch[];\n}\n\n/**\n * Split oversized content into overlapping chunks so the scanner covers all\n * of it. Content that fits in one chunk is returned as-is.\n */\nfunction chunkContent(content: string): string[] {\n if (content.length <= SCAN_CHUNK_SIZE) return [content];\n const chunks: string[] = [];\n const step = SCAN_CHUNK_SIZE - SCAN_CHUNK_OVERLAP;\n for (let start = 0; start < content.length; start += step) {\n chunks.push(content.slice(start, start + SCAN_CHUNK_SIZE));\n if (start + SCAN_CHUNK_SIZE >= content.length) break;\n }\n return chunks;\n}\n\n/**\n * Scan content against warlock rules — chunked when oversized, so nothing is\n * skipped — and keep only matches for this content surface. Chunks scan\n * sequentially (the WASM engine is single-threaded; parallelism buys nothing).\n * Returns each flagged chunk with its matches so triage can later judge every\n * match against the exact content it came from.\n */\nasync function scanForContext(\n content: string,\n ctx: ScanContext,\n): Promise<FlaggedChunk[]> {\n const warlock = await getWarlock();\n const chunks = chunkContent(content);\n if (chunks.length > 1) {\n logToFile(\n `[YARA] content is ${content.length} chars — scanning ${chunks.length} overlapping chunks`,\n );\n // Alertable signal: oversized content should be rare; a spike could mean\n // someone is padding content to probe the scanner.\n analytics.wizardCapture('yara scan chunked', {\n content_length: content.length,\n chunk_count: chunks.length,\n scan_context: ctx,\n });\n }\n const flagged: FlaggedChunk[] = [];\n for (const chunk of chunks) {\n const result = await warlock.scan(chunk);\n if (!result.matched) continue;\n const matches = matchesForContext(result.matches, ctx);\n if (matches.length === 0) continue;\n flagged.push({ chunk, matches });\n }\n return flagged;\n}\n\n/** The overlap between chunks can surface the same rule twice; count it once. */\nfunction dedupeByRule(matches: ScanMatch[]): ScanMatch[] {\n const seen = new Set<string>();\n return matches.filter((m) => {\n if (seen.has(m.rule)) return false;\n seen.add(m.rule);\n return true;\n });\n}\n\n/**\n * Triage each flagged chunk against its own content — the evidence is always\n * inside the window the LLM sees. Triage calls run in parallel (each is just\n * an HTTP round trip) so N flagged chunks cost ~1× triage latency, not N×.\n */\nasync function triageFlagged(\n flagged: FlaggedChunk[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const triaged = await Promise.all(\n flagged.map(({ chunk, matches }) =>\n triageFilter(chunk, matches, ctx, llmProvider),\n ),\n );\n return dedupeByRule(triaged.flat());\n}\n\n/**\n * Scan content, filter to the relevant context, triage. Returns real matches.\n * Exported for harnesses that run their own hook loop (pi) so every harness\n * scans through the same warlock engine, chunking, and triage policy.\n */\nexport async function scanAndTriage(\n content: string,\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const flagged = await scanForContext(content, ctx);\n return triageFlagged(flagged, ctx, llmProvider);\n}\n\n// ─── PreToolUse Hooks ────────────────────────────────────────────\n\n/**\n * Create PreToolUse hook matchers for YARA scanning.\n * 1. Bash — scans commands before execution for exfiltration, destructive\n * operations, and supply chain violations ('command'-context rules).\n * 2. publish_handoff — scans the report content with the same 'output'-context\n * rules + triage as the PostToolUse Write/Edit hook, but BEFORE the MCP\n * tool runs: the report is agent-authored and is persisted to a\n * host-designated file (POSTHOG_HANDOFF_OUTPUT_PATH) as well as pushed to\n * the task stream. Only a CRITICAL match refuses the publish; below that\n * the report goes out with the match recorded.\n */\nexport function createPreToolUseYaraHooks(\n // Used only by the publish_handoff matcher below (output-context triage,\n // same fail-closed semantics as Write/Edit). The Bash matcher still passes\n // undefined — any flagged command blocks regardless of triage verdict, so\n // the LLM call would be wasted.\n llmProvider?: LLMProvider,\n // Ends the run when a handoff scan errors — without it that path just blocks,\n // which is the old behavior, so it stays optional.\n onTerminate?: (reason: string) => void,\n): HookCallbackMatcher[] {\n const repeatTracker = createRepeatBlockTracker();\n return [\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n if (!command) return {};\n\n recordScan();\n // Skip triage on PreToolUse Bash: any flagged command is blocked\n // regardless of triage verdict, so the LLM call would be wasted.\n const matches = await scanAndTriage(command, 'command', undefined);\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'Bash', match, 'blocked');\n\n const attempt = repeatTracker.attempt('Bash', command);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'Bash',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Command blocked for security.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse hook error:', error);\n // Fail closed: block the command if scanning fails\n return {\n decision: 'block',\n reason: '[YARA] Scanner error — command blocked as a precaution.',\n };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n {\n // ── publish_handoff content scanning ──\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n // The wizard's publish_handoff MCP tool, fully qualified by the\n // SDK (e.g. mcp__wizard-tools__publish_handoff). Matched by\n // suffix so a server rename can't silently reopen the gap — the\n // exact FQN lives in WIZARD_TOOL_NAMES (wizard-tools/tools.ts),\n // which this module can't import without a cycle (tools.ts\n // imports scanInstalledSkill from here).\n if (\n typeof toolName !== 'string' ||\n !toolName.endsWith('__publish_handoff')\n ) {\n return {};\n }\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const content =\n typeof toolInput?.content === 'string' ? toolInput.content : '';\n\n if (!content) return {};\n\n recordScan();\n // Same 'output'-context scan + triage as the Write/Edit hook:\n // the report is agent-authored, quotes the user's code, and now\n // reaches a second persisted consumer (the host file + the cloud\n // worker's PR body) — so it must clear the bar of any other\n // agent-driven write BEFORE the tool executes.\n const matches = await scanAndTriage(content, 'output', llmProvider);\n if (matches.length === 0) return {};\n\n // Below critical: record it, don't cost the run its only report.\n const match = publishBlockingMatch(matches);\n if (!match) {\n const worst = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'publish_handoff', worst, 'warned');\n logToFile(\n `[YARA] publish_handoff allowed with a ${\n worst.metadata.severity ?? 'unknown'\n } match (${worst.rule}) — below the critical publish bar`,\n );\n return {};\n }\n\n recordMatch('PreToolUse', 'publish_handoff', match, 'blocked');\n\n const attempt = repeatTracker.attempt('publish_handoff', content);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'publish_handoff',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Handoff blocked for security — remove the flagged content from the report.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse publish_handoff hook error:', error);\n // Fail closed, but end the run: a silent block would just leave\n // the agent rewording a report that can never publish.\n const reason =\n '[YARA] Scanner error while scanning the handoff report — session terminated as a precaution.';\n onTerminate?.(reason);\n return { decision: 'block', reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── PostToolUse Hooks ───────────────────────────────────────────\n\n/**\n * Create PostToolUse hook matchers for YARA scanning.\n *\n * Three matchers:\n * 1. Write/Edit — scan written content ('output'-context rules: PII, secrets)\n * 2. Read/Grep — scan read content ('input'-context rules: prompt injection)\n * 3. Bash (skill install) — scan downloaded skill files for poisoned content\n *\n * `onTerminate` is invoked on the terminal paths (critical prompt injection in\n * read content, a poisoned skill, or a scanner error under fail-closed). It is\n * what ACTUALLY stops the run — returning `stopReason` from a PostToolUse hook\n * is not honored by the SDK, so the caller wires this to the run's\n * AbortController (see agent-interface).\n */\nexport function createPostToolUseYaraHooks(\n llmProvider: LLMProvider | undefined,\n // REQUIRED, not optional: the SDK ignores `stopReason` from PostToolUse\n // hooks, so terminal paths (critical prompt injection, poisoned skill,\n // fail-closed scanner error) depend entirely on this callback firing.\n // Making it required at compile time prevents a future caller from\n // silently regressing every terminal path to a no-op.\n onTerminate: (reason: string) => void,\n): HookCallbackMatcher[] {\n return [\n // ── Write/Edit content scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Write' && toolName !== 'Edit') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n // For Write, scan the content being written.\n // For Edit, scan the new_string (replacement text).\n const content =\n toolName === 'Write'\n ? (toolInput?.content as string) ?? ''\n : (toolInput?.new_string as string) ?? '';\n\n if (!content) return {};\n\n recordScan();\n const flagged = await scanForContext(content, 'output');\n if (flagged.length === 0) return {};\n\n // Wizard-documentation paths: suppress posthog_pii matches that\n // come from the agent verbatim-copying the user's existing\n // capture calls into an inventory / report, or planning new\n // events with PII-shaped property keys. Every other category\n // still triggers the revert. Suppression runs BEFORE triage so\n // we never pay an LLM round trip for a match we're about to\n // discard anyway.\n // TODO(warlock#33): once warlock PR #33 lands with its\n // more-precise PII rules, the wizard-side suppression below\n // should become unnecessary — remove `WIZARD_DOC_BASENAMES`,\n // `WIZARD_DOC_PATTERNS`, and `isWizardDocumentationPath` and\n // verify the noisy-PII issue stays fixed.\n const filePath = toolInput?.file_path as string | undefined;\n const activeFlagged = isWizardDocumentationPath(filePath)\n ? flagged\n .map(({ chunk, matches }) => ({\n chunk,\n matches: matches.filter(\n (m) => m.metadata.category !== 'posthog_pii',\n ),\n }))\n .filter(({ matches }) => matches.length > 0)\n : flagged;\n if (activeFlagged.length === 0) {\n logToFile(\n `[YARA] posthog_pii match suppressed on wizard doc ${path.basename(\n filePath ?? '',\n )} (rule: ${flagged[0]?.matches[0]?.rule})`,\n );\n return {};\n }\n\n const matches = await triageFlagged(\n activeFlagged,\n 'output',\n llmProvider,\n );\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PostToolUse', toolName, match, 'reverted');\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext:\n `[YARA VIOLATION] ${match.rule}: ${\n match.metadata.description ?? ''\n }. ` +\n `You MUST revert this change immediately. The content you just wrote violates security policy.`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Write/Edit hook error:', error);\n // Fail closed: if scanning is broken on a Write/Edit, the next\n // Read/skill-install scan is also going to be broken — terminate\n // cleanly instead of looping the agent through \"please revert\"\n // until a Read finally aborts. Matches Read/Grep + skill-install\n // catch behavior; consistent fail-closed across all PostToolUse.\n const reason =\n '[YARA] Scanner error while scanning written content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Read/Grep prompt injection scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Read' && toolName !== 'Grep') return {};\n\n const toolResponse = input.tool_response;\n // Guard before stringify: `JSON.stringify(undefined ?? '')`\n // returns the 2-char string '\"\"', which is truthy and slips\n // past the empty-content check below. Exit early when the SDK\n // gave us no response payload.\n if (toolResponse == null) return {};\n const content =\n typeof toolResponse === 'string'\n ? toolResponse\n : JSON.stringify(toolResponse);\n\n if (!content) return {};\n\n recordScan();\n const matches = await scanAndTriage(content, 'input', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n const { match } = verdict;\n recordMatch('PostToolUse', toolName, match, verdict.action);\n\n if (verdict.terminal) {\n const reason =\n `[YARA CRITICAL] ${match.rule}: Prompt injection detected in file content. ` +\n `Agent context is potentially poisoned. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext: `[YARA WARNING] ${match.rule}: ${\n match.metadata.description ?? ''\n }`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Read/Grep hook error:', error);\n // Fail closed: terminate session if scanning fails on read content\n const reason =\n '[YARA] Scanner error while scanning read content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Context-mill skill install scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n // Only scan after skill install commands\n if (!isSkillInstallCommand(command)) return {};\n\n // Extract skill directory from command\n const dirMatch = command.match(\n /mkdir -p (.claude\\/skills\\/[^\\s&]+)/,\n );\n if (!dirMatch) return {};\n\n const skillDir = dirMatch[1];\n const cwd = (input.cwd as string) ?? process.cwd();\n recordScan();\n const matches = await scanSkillFiles(cwd, skillDir, llmProvider);\n\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n recordMatch(\n 'PostToolUse',\n 'Bash (skill install)',\n verdict.match,\n verdict.action,\n );\n // Same verdict as every other surface: critical terminates, the\n // rest warn. These rules fire on first-party skill prose often\n // enough that terminating costs more than it prevents.\n if (!verdict.terminal) return {};\n\n const reason =\n `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${verdict.match.rule}. ` +\n `The downloaded skill contains potential prompt injection. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n } catch (error) {\n logToFile('[YARA] PostToolUse skill install hook error:', error);\n // Fail closed: terminate if skill scanning fails\n const reason =\n '[YARA] Scanner error while scanning skill files — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: SKILL_SCAN_HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── Skill File Scanner ──────────────────────────────────────────\n\n/**\n * Scan a freshly installed skill directory (any root — .claude/skills or the\n * orchestrator's run cache) and return a terminate reason when it is poisoned,\n * else null. The choke point for TS-path installs (downloadSkill); agent Bash\n * installs are covered by the PostToolUse matcher above. Runs the same LLM\n * triage as the tool-use scans; fail-closed to treating every match as real when\n * no provider is configured, so a missing key never weakens the check.\n * `llmProvider` is explicit — pi and the orchestrator never set the env fallback.\n *\n * Terminates on the same verdict as every other surface (critical only). A\n * non-terminal match is recorded and the skill is kept: these rules fire on\n * first-party skill prose, and deleting the skill leaves the agent working blind\n * on the very content it needed.\n */\nexport async function scanInstalledSkill(\n absoluteSkillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<string | null> {\n recordScan();\n const matches = await scanSkillFiles(absoluteSkillDir, '.', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return null;\n recordMatch(\n 'skill-install',\n 'installSkillById',\n verdict.match,\n verdict.action,\n );\n if (!verdict.terminal) {\n logToFile(\n `[YARA] ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir} — non-terminal, keeping the skill`,\n );\n return null;\n }\n return `Poisoned skill detected: ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir}`;\n}\n\n/**\n * Read and scan all text files in a skill directory for prompt injection.\n * Scans each file sequentially (single-threaded WASM — parallelism buys\n * nothing), unions the 'input'-context matches, then triages once across the\n * combined content. Returns the real (post-triage) matches.\n */\nasync function scanSkillFiles(\n cwd: string,\n skillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const absoluteDir = path.resolve(cwd, skillDir);\n\n if (!fs.existsSync(absoluteDir)) {\n logToFile(`[YARA] Skill directory does not exist: ${absoluteDir}`);\n return [];\n }\n\n const files = await fg('**/*.{md,txt,yaml,yml,json,js,ts,py,rb,sh}', {\n cwd: absoluteDir,\n absolute: true,\n });\n\n if (files.length === 0) {\n logToFile(`[YARA] No text files found in skill directory: ${absoluteDir}`);\n return [];\n }\n\n logToFile(\n `[YARA] Scanning ${files.length} files in skill directory: ${skillDir}`,\n );\n\n // Pass 1 (sequential): read and scan ONE file at a time through the\n // chunk-aware path. Oversized files are scanned up to the cap and logged —\n // never silently skipped. Flagged chunks keep a reference to the exact\n // content their matches came from.\n const flagged: FlaggedChunk[] = [];\n for (const filePath of files) {\n let content: string | null;\n try {\n if (fs.statSync(filePath).size > SKILL_FILE_SCAN_BYTES) {\n logToFile(\n `[YARA] Skill file over ${SKILL_FILE_SCAN_BYTES} bytes; scanning its head only: ${filePath}`,\n );\n content = readFileHead(filePath, SKILL_FILE_SCAN_BYTES);\n } else {\n content = fs.readFileSync(filePath, 'utf-8');\n }\n } catch (err) {\n logToFile(`[YARA] Could not read skill file ${filePath}:`, err);\n continue;\n }\n if (content) {\n flagged.push(...(await scanForContext(content, 'input')));\n }\n }\n\n // Pass 2 (parallel, inside triageFlagged): triage each flagged chunk\n // against its own content concurrently. Triage is just an axios call —\n // running them in parallel cuts total wall-clock from N × triage-timeout\n // to ~1×. Important because the hook's HOOK_TIMEOUT_MS is 30s; serial\n // triage on 2+ matched files could blow the hook timeout under a slow\n // Haiku.\n return triageFlagged(flagged, 'input', llmProvider);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AASA,SAAgB,sBAAsB,SAA0B;AAC9D,KAAI,CAAC,QAAQ,WAAW,2BAA2B,CAAE,QAAO;CAE5D,MAAM,WAAW,QAAQ,MAAM,4BAA4B;AAC3D,KAAI,CAAC,SAAU,QAAO;CAEtB,MAAM,MAAM,SAAS;AACrB,QACE,IAAI,WAAW,oDAAoD,IACnE,4BAA4B,KAAK,IAAI;;;;;ACDzC,MAAM,gBAAwC;CAC5C,UAAU;CACV,MAAM;CACN,QAAQ;CACR,KAAK;CACN;;AAGD,MAAM,sBAAsB,IAAI,IAAI,CAAC,WAAW,CAAC;;;;;;;;AASjD,SAAgB,gBAAgB,OAA2B;AACzD,QAAO,oBAAoB,IAAI,MAAM,SAAS,YAAY,GAAG;;;AAI/D,SAAgB,qBAAqB,SAAiC;AACpE,QAAO,QAAQ,QAAQ,OAAO,OAC3B,cAAc,EAAE,SAAS,YAAY,OAAO,MAC5C,cAAc,MAAM,SAAS,YAAY,OAAO,KAC7C,IACA,MACL;;;;;;AAOH,SAAgB,qBAAqB,SAAwC;AAC3E,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,MAAM,QAAQ,qBAAqB,QAAQ;AAC3C,QAAO,gBAAgB,MAAM,GAAG,QAAQ;;;AAa1C,SAAgB,YAAY,SAA0C;AACpE,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,MAAM,QAAQ,qBAAqB,QAAQ;CAC3C,MAAM,WAAW,gBAAgB,MAAM;AACvC,QAAO;EAAE;EAAO;EAAU,QAAQ,WAAW,YAAY;EAAU;;;;;;;;;;;AChErE,MAAa,oBAAoB;AACjC,MAAa,uBAAuB;;AAEpC,MAAa,+BACX;;;;ACYF,MAAa,uBAAgE;CAC3E,SAAS;EAAE,OAAO;EAAK,OAAO;EAAQ;CACtC,MAAM;EAAE,OAAO;EAAK,OAAO;EAAS;CACpC,OAAO;EAAE,OAAO;EAAK,OAAO;EAAO;CACnC,SAAS;EAAE,OAAO;EAAK,OAAO;EAAU;CACxC,YAAY;EAAE,OAAO;EAAK,OAAO;EAAQ;CAC1C;AAED,MAAa,oBAAoB;AACjC,MAAa,oBAAoB;AACjC,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,SAAsC;CACnE,MAAM,MAAM,QAAQ,iBAAiB;AACrC,QAAO,MAAM,QAAQ,IAAI,GAAI,MAAuB,EAAE;;;;;;;;;;AAWxD,SAAgB,kBAAkB,QAA+B;AAC/D,QAAO,MAAM,QAAQ,OAAO,GAAI,SAA0B,EAAE;;;;;;;;;;;;ACzC9D,MAAa,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;AC+D/B,IAAI,uBAAsD;AAE1D,SAAS,aAAqC;AAC5C,KAAI,CAAC,qBAKH,wBAAuB,OAAO,oBAAoB,OAAO,QAAQ;AAC/D,yBAAuB;AACvB,QAAM;GACN;AAEJ,QAAO;;;;;;;AAQT,eAAsB,qBAAoC;AACxD,KAAI;AAEF,SADgB,MAAM,YAAY,EACpB,KAAK,GAAG;AACtB,YAAU,4BAA4B;UAC/B,KAAK;AACZ,YAAU,mCAAmC,IAAI;;;AA6CrD,IAAI,YAAY;AAChB,MAAM,iBAAoC,EAAE;AAE5C,SAAS,aAAmB;AAC1B;;;;;;AAUF,SAAS,YACP,OACA,MACA,OACA,QACM;CACN,MAAM,SAAU,MAAsB;AACtC,cAAa,OAAO,MAAM,OAAO,OAAO;AACxC,gBAAe,KAAK;EAClB,MAAM,MAAM;EACZ,UAAU,MAAM,SAAS,YAAY;EACrC,UAAU,MAAM,SAAS,YAAY;EACrC;EACA;EACA;EACA,aAAa,MAAM,SAAS,eAAe;EAC3C,eAAe,QAAQ;EACvB,cAAc,QAAQ;EACvB,CAAC;;;;;;;AAcJ,SAAgB,mBACd,OACA,MACA,YAMA,QACM;AACN,aAAY;AACZ,MAAK,MAAM,KAAK,WACd,aACE,OACA,MACA;EACE,MAAM,EAAE;EACR,UAAU;GACR,UAAU,EAAE;GACZ,UAAU,EAAE;GACZ,aAAa,EAAE;GAChB;EACD,gBAAgB,EAAE;EACnB,EACD,OACD;;;AAKL,SAAgB,mBAAkC;AAChD,KAAI,cAAc,EAAG,QAAO;CAE5B,MAAM,QAAkB,CAAC,IAAI,2BAA2B;CACxD,MAAM,iBAAiB,eAAe;CACtC,MAAM,aAAa,YAAY;AAE/B,OAAM,KACJ,KAAK,UAAU,uBAAuB,eAAe,YACnD,mBAAmB,IAAI,MAAM,GAC9B,WACF;AAED,KAAI,iBAAiB,GAAG;AACtB,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,KAAK,gBAAgB;GAC9B,MAAM,MAAM,EAAE,OAAO,aAAa;AAClC,SAAM,KACJ,MAAM,IAAI,IAAI,EAAE,KAAK,IAAI,EAAE,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,GAC9D,EAAE,OAEL;;;AAIL,KAAI,aAAa,GAAG;AAClB,QAAM,KAAK,GAAG;AACd,QAAM,KACJ,oBAAoB,WAAW,aAAa,eAAe,IAAI,MAAM,KACtE;;AAGH,OAAM,KAAK,GAAG;AACd,QAAO,MAAM,KAAK,KAAK;;;AAIzB,SAAgB,kBAAiC;AAC/C,KAAI,cAAc,EAAG,QAAO;CAE5B,MAAM,SAAS;EACb,SAAS;GACP,YAAY;GACZ,YAAY,eAAe;GAC3B,OAAO,YAAY,eAAe;GACnC;EACD,YAAY;EACb;AAED,KAAI;AACF,KAAG,cAAc,yBAAyB,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;UACnE,KAAK;AACZ,YAAU,uCAAuC,IAAI;AACrD,SAAO;;AAET,QAAO;;AAcT,MAAM,wBAAkD;CACtD,kBACE;CACF,cAAc;CACd,wBAAwB;CACxB,cAAc;CACd,aAAa;CACb,uBAAuB;CACvB,kBAAkB;CACnB;;AAGD,SAAS,2BAAmD;AAC1D,MAAK,IAAI,IAAI,eAAe,SAAS,GAAG,KAAK,GAAG,IAC9C,KAAI,eAAe,GAAG,WAAW,UAAW,QAAO,eAAe;AAEpE,QAAO;;;;;;;;;AAUT,SAAgB,yBAAiC;CAC/C,MAAM,IAAI,0BAA0B;AAKpC,QACE;;aAJC,KAAK,sBAAsB,EAAE,cAC9B,6CAIkB;;;;;AAatB,MAAM,kBAAkB;;AAExB,MAAM,6BAA6B;;;;;;;AAQnC,MAAM,kBAAkB;AAGxB,MAAM,wBAAwB,KAAK,OAAO;;;;;;AAM1C,MAAM,qBAAqB;AAI3B,SAAS,aACP,OACA,MACA,OACA,QACM;CACN,MAAM,WAAW,MAAM,SAAS,YAAY;CAC5C,MAAM,WAAW,MAAM,SAAS,YAAY;CAC5C,MAAM,aAAa,MAAM,SAAS,UAAU;CAC5C,MAAM,cAAc,MAAM,SAAS,eAAe;AAClD,WACE,UAAU,MAAM,GAAG,KAAK,IAAI,OAAO,aAAa,CAAC,UAAU,MAAM,KAAK,eACtD,SAAS,cAAc,SAAS,YAAY,WAAW,oBACnD,cACrB;AACD,WAAU,cAAc,qBAAqB;EAC3C,MAAM,MAAM;EACZ,UAAU,MAAM,SAAS;EACzB,UAAU,MAAM,SAAS;EACzB,aAAa,MAAM,SAAS;EAC5B;EACA;EACA;EAGA,aAAa,MAAM,SAAS;EAC5B,gBAAiB,MAAsB,QAAQ;EAChD,CAAC;;;;;;;;AASJ,SAAgB,oBAA0B;AACxC,KAAI,cAAc,EAAG;AACrB,WAAU,cAAc,oBAAoB;EAC1C,aAAa;EACb,iBAAiB,eAAe;EAChC,aAAa,YAAY,eAAe;EACxC,YAAY,eAAe,KAAK,OAAO;GACrC,MAAM,EAAE;GACR,UAAU,EAAE;GACZ,UAAU,EAAE;GACZ,QAAQ,EAAE;GACV,OAAO,EAAE;GACT,MAAM,EAAE;GACR,aAAa,EAAE;GACf,gBAAgB,EAAE;GACnB,EAAE;EACJ,CAAC;AAIF,aAAY;AACZ,gBAAe,SAAS;;;;;;;;;;;;;;AAe1B,SAAgB,gBACd,SACM;AACN,KAAI,QAAQ,YAAY;EACtB,MAAM,aAAa,iBAAiB;AACpC,MAAI,YAAY;GACd,MAAM,UAAU,kBAAkB;AAClC,UAAO,CAAC,IAAI,KAAK,qBAAqB,aAAa,WAAW,KAAK;;;AAGvE,oBAAmB;;AAiBrB,MAAM,uBAAuB,IAAI,IAAI;CACnC;CACAA;CACA;CACA;CACD,CAAC;AAEF,MAAM,sBAAgC,CAAC,6BAA6B;AAEpE,SAAgB,0BACd,UACS;AACT,KAAI,CAAC,SAAU,QAAO;CACtB,MAAM,WAAW,KAAK,SAAS,SAAS;AACxC,KAAI,qBAAqB,IAAI,SAAS,CAAE,QAAO;AAC/C,QAAO,oBAAoB,MAAM,OAAO,GAAG,KAAK,SAAS,CAAC;;;AAgB5D,MAAM,+BAA+B;;AAWrC,SAAgB,2BAA+C;CAC7D,MAAM,yBAAS,IAAI,KAAqB;AACxC,QAAO,EACL,QAAQ,MAAc,SAAyB;EAC7C,MAAM,MAAM,GAAG,KAAK,GAAG,WAAW,SAAS,CACxC,OAAO,QAAQ,CACf,OAAO,MAAM;EAChB,MAAM,QAAQ,OAAO,IAAI,IAAI,IAAI,KAAK;AACtC,SAAO,IAAI,KAAK,KAAK;AACrB,SAAO;IAEV;;;;;;;AAQH,SAAgB,kBACd,SACA,MACA,YACQ;AACR,KAAI,WAAW,EAAG,QAAO;AACzB,KAAI,UAAU,6BACZ,QACE,GAAG,WAAW,cAAc,KAAK;AAKrC,QACE,GAAG,WAAW,cAAc,KAAK,gCAAgC,QAAQ;;;;;;;;AAgB7E,SAAS,kBACP,SACA,KACa;AACb,QAAO,QAAQ,QAAQ,MAAM;EAC3B,MAAM,IAAI,EAAE,SAAS;AACrB,SAAO,MAAM,OAAO,MAAM,KAAA;GAC1B;;;;;;;;;;;;AAaJ,eAAe,aACb,SACA,SACA,KACA,aACsB;AACtB,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;AACnC,KAAI,CAAC,aAAa;AAChB,YACE,kDAAkD,QAAQ,OAAO,oBAClE;AACD,SAAO;;AAET,KAAI;EAEF,MAAM,UAAU,OADA,MAAM,YAAY,EACJ,cAAc,SAAS,SAAS,YAAY;EAC1E,MAAM,OAAO,QAAQ,QAAQ,MAAM,EAAE,OAAO,YAAY,gBAAgB;AACxE,OAAK,MAAM,KAAK,SAAS;AACvB,OAAI,EAAE,OAAO,YAAY,gBAAiB;AAC1C,aACE,iCAAiC,EAAE,KAAK,KACtC,EAAE,SAAS,YAAY,UACxB,sBACF;AACD,aAAU,cAAc,yBAAyB;IAC/C,MAAM,EAAE;IACR,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,cAAc;IAGf,CAAC;;AAEJ,YACE,kBAAkB,QAAQ,OAAO,aAAa,KAAK,OAAO,eAC3D;AACD,SAAO;UACA,KAAK;AACZ,YAAU,wDAAwD,IAAI;AACtE,SAAO;;;;;;;AAcX,SAAS,aAAa,SAA2B;AAC/C,KAAI,QAAQ,UAAU,gBAAiB,QAAO,CAAC,QAAQ;CACvD,MAAM,SAAmB,EAAE;CAC3B,MAAM,OAAO,kBAAkB;AAC/B,MAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,MAAM;AACzD,SAAO,KAAK,QAAQ,MAAM,OAAO,QAAQ,gBAAgB,CAAC;AAC1D,MAAI,QAAQ,mBAAmB,QAAQ,OAAQ;;AAEjD,QAAO;;;;;;;;;AAUT,eAAe,eACb,SACA,KACyB;CACzB,MAAM,UAAU,MAAM,YAAY;CAClC,MAAM,SAAS,aAAa,QAAQ;AACpC,KAAI,OAAO,SAAS,GAAG;AACrB,YACE,qBAAqB,QAAQ,OAAO,oBAAoB,OAAO,OAAO,qBACvE;AAGD,YAAU,cAAc,qBAAqB;GAC3C,gBAAgB,QAAQ;GACxB,aAAa,OAAO;GACpB,cAAc;GACf,CAAC;;CAEJ,MAAM,UAA0B,EAAE;AAClC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM;AACxC,MAAI,CAAC,OAAO,QAAS;EACrB,MAAM,UAAU,kBAAkB,OAAO,SAAS,IAAI;AACtD,MAAI,QAAQ,WAAW,EAAG;AAC1B,UAAQ,KAAK;GAAE;GAAO;GAAS,CAAC;;AAElC,QAAO;;;AAIT,SAAS,aAAa,SAAmC;CACvD,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAO,QAAQ,QAAQ,MAAM;AAC3B,MAAI,KAAK,IAAI,EAAE,KAAK,CAAE,QAAO;AAC7B,OAAK,IAAI,EAAE,KAAK;AAChB,SAAO;GACP;;;;;;;AAQJ,eAAe,cACb,SACA,KACA,aACsB;AAMtB,QAAO,cALS,MAAM,QAAQ,IAC5B,QAAQ,KAAK,EAAE,OAAO,cACpB,aAAa,OAAO,SAAS,KAAK,YAAY,CAC/C,CACF,EAC2B,MAAM,CAAC;;;;;;;AAQrC,eAAsB,cACpB,SACA,KACA,aACsB;AAEtB,QAAO,cADS,MAAM,eAAe,SAAS,IAAI,EACpB,KAAK,YAAY;;;;;;;;;;;;;AAgBjD,SAAgB,0BAKd,aAGA,aACuB;CACvB,MAAM,gBAAgB,0BAA0B;AAChD,QAAO,CACL;EACE,OAAO,CACL,OAAO,UAA0C;AAC/C,OAAI;AAEF,QADiB,MAAM,cACN,OAAQ,QAAO,EAAE;IAElC,MAAM,YAAY,MAAM;IACxB,MAAM,UACJ,OAAO,WAAW,YAAY,WAAW,UAAU,UAAU;AAE/D,QAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,gBAAY;IAGZ,MAAM,UAAU,MAAM,cAAc,SAAS,WAAW,KAAA,EAAU;AAClE,QAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;IAEnC,MAAM,QAAQ,qBAAqB,QAAQ;AAC3C,gBAAY,cAAc,QAAQ,OAAO,UAAU;AAGnD,WAAO;KACL,UAAU;KACV,QAAQ,kBAHM,cAAc,QAAQ,QAAQ,QAAQ,EAKlD,QACA,UAAU,MAAM,KAAK,IACnB,MAAM,SAAS,eAAe,4BAC/B,iCACF;KACF;YACM,OAAO;AACd,cAAU,iCAAiC,MAAM;AAEjD,WAAO;KACL,UAAU;KACV,QAAQ;KACT;;IAGN;EACD,SAAS;EACV,EACD;EAEE,OAAO,CACL,OAAO,UAA0C;AAC/C,OAAI;IACF,MAAM,WAAW,MAAM;AAOvB,QACE,OAAO,aAAa,YACpB,CAAC,SAAS,SAAS,oBAAoB,CAEvC,QAAO,EAAE;IAGX,MAAM,YAAY,MAAM;IACxB,MAAM,UACJ,OAAO,WAAW,YAAY,WAAW,UAAU,UAAU;AAE/D,QAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,gBAAY;IAMZ,MAAM,UAAU,MAAM,cAAc,SAAS,UAAU,YAAY;AACnE,QAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;IAGnC,MAAM,QAAQ,qBAAqB,QAAQ;AAC3C,QAAI,CAAC,OAAO;KACV,MAAM,QAAQ,qBAAqB,QAAQ;AAC3C,iBAAY,cAAc,mBAAmB,OAAO,SAAS;AAC7D,eACE,yCACE,MAAM,SAAS,YAAY,UAC5B,UAAU,MAAM,KAAK,oCACvB;AACD,YAAO,EAAE;;AAGX,gBAAY,cAAc,mBAAmB,OAAO,UAAU;AAG9D,WAAO;KACL,UAAU;KACV,QAAQ,kBAHM,cAAc,QAAQ,mBAAmB,QAAQ,EAK7D,mBACA,UAAU,MAAM,KAAK,IACnB,MAAM,SAAS,eAAe,4BAC/B,8EACF;KACF;YACM,OAAO;AACd,cAAU,iDAAiD,MAAM;IAGjE,MAAM,SACJ;AACF,kBAAc,OAAO;AACrB,WAAO;KAAE,UAAU;KAAS;KAAQ;;IAGzC;EACD,SAAS;EACV,CACF;;;;;;;;;;;;;;;;AAmBH,SAAgB,2BACd,aAMA,aACuB;AACvB,QAAO;EAEL;GACE,OAAO,CACL,OAAO,UAA0C;AAC/C,QAAI;KACF,MAAM,WAAW,MAAM;AACvB,SAAI,aAAa,WAAW,aAAa,OAAQ,QAAO,EAAE;KAE1D,MAAM,YAAY,MAAM;KAGxB,MAAM,UACJ,aAAa,UACR,WAAW,WAAsB,KACjC,WAAW,cAAyB;AAE3C,SAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,iBAAY;KACZ,MAAM,UAAU,MAAM,eAAe,SAAS,SAAS;AACvD,SAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;KAcnC,MAAM,WAAW,WAAW;KAC5B,MAAM,gBAAgB,0BAA0B,SAAS,GACrD,QACG,KAAK,EAAE,OAAO,eAAe;MAC5B;MACA,SAAS,QAAQ,QACd,MAAM,EAAE,SAAS,aAAa,cAChC;MACF,EAAE,CACF,QAAQ,EAAE,cAAc,QAAQ,SAAS,EAAE,GAC9C;AACJ,SAAI,cAAc,WAAW,GAAG;AAC9B,gBACE,qDAAqD,KAAK,SACxD,YAAY,GACb,CAAC,UAAU,QAAQ,IAAI,QAAQ,IAAI,KAAK,GAC1C;AACD,aAAO,EAAE;;KAGX,MAAM,UAAU,MAAM,cACpB,eACA,UACA,YACD;AACD,SAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;KAEnC,MAAM,QAAQ,qBAAqB,QAAQ;AAC3C,iBAAY,eAAe,UAAU,OAAO,WAAW;AAEvD,YAAO,EACL,oBAAoB;MAClB,eAAe;MACf,mBACE,oBAAoB,MAAM,KAAK,IAC7B,MAAM,SAAS,eAAe,GAC/B;MAEJ,EACF;aACM,OAAO;AACd,eAAU,6CAA6C,MAAM;KAM7D,MAAM,SACJ;AACF,iBAAY,OAAO;AACnB,YAAO,EAAE,YAAY,QAAQ;;KAGlC;GACD,SAAS;GACV;EAGD;GACE,OAAO,CACL,OAAO,UAA0C;AAC/C,QAAI;KACF,MAAM,WAAW,MAAM;AACvB,SAAI,aAAa,UAAU,aAAa,OAAQ,QAAO,EAAE;KAEzD,MAAM,eAAe,MAAM;AAK3B,SAAI,gBAAgB,KAAM,QAAO,EAAE;KACnC,MAAM,UACJ,OAAO,iBAAiB,WACpB,eACA,KAAK,UAAU,aAAa;AAElC,SAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,iBAAY;KAEZ,MAAM,UAAU,YADA,MAAM,cAAc,SAAS,SAAS,YAAY,CAC9B;AACpC,SAAI,CAAC,QAAS,QAAO,EAAE;KAEvB,MAAM,EAAE,UAAU;AAClB,iBAAY,eAAe,UAAU,OAAO,QAAQ,OAAO;AAE3D,SAAI,QAAQ,UAAU;MACpB,MAAM,SACJ,mBAAmB,MAAM,KAAK;AAEhC,kBAAY,OAAO;AACnB,aAAO,EAAE,YAAY,QAAQ;;AAG/B,YAAO,EACL,oBAAoB;MAClB,eAAe;MACf,mBAAmB,kBAAkB,MAAM,KAAK,IAC9C,MAAM,SAAS,eAAe;MAEjC,EACF;aACM,OAAO;AACd,eAAU,4CAA4C,MAAM;KAE5D,MAAM,SACJ;AACF,iBAAY,OAAO;AACnB,YAAO,EAAE,YAAY,QAAQ;;KAGlC;GACD,SAAS;GACV;EAGD;GACE,OAAO,CACL,OAAO,UAA0C;AAC/C,QAAI;AAEF,SADiB,MAAM,cACN,OAAQ,QAAO,EAAE;KAElC,MAAM,YAAY,MAAM;KACxB,MAAM,UACJ,OAAO,WAAW,YAAY,WAAW,UAAU,UAAU;AAG/D,SAAI,CAAC,sBAAsB,QAAQ,CAAE,QAAO,EAAE;KAG9C,MAAM,WAAW,QAAQ,MACvB,sCACD;AACD,SAAI,CAAC,SAAU,QAAO,EAAE;KAExB,MAAM,WAAW,SAAS;KAC1B,MAAM,MAAO,MAAM,OAAkB,QAAQ,KAAK;AAClD,iBAAY;KAGZ,MAAM,UAAU,YAFA,MAAM,eAAe,KAAK,UAAU,YAAY,CAE5B;AACpC,SAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,iBACE,eACA,wBACA,QAAQ,OACR,QAAQ,OACT;AAID,SAAI,CAAC,QAAQ,SAAU,QAAO,EAAE;KAEhC,MAAM,SACJ,8CAA8C,SAAS,IAAI,QAAQ,MAAM,KAAK;AAEhF,iBAAY,OAAO;AACnB,YAAO,EAAE,YAAY,QAAQ;aACtB,OAAO;AACd,eAAU,gDAAgD,MAAM;KAEhE,MAAM,SACJ;AACF,iBAAY,OAAO;AACnB,YAAO,EAAE,YAAY,QAAQ;;KAGlC;GACD,SAAS;GACV;EACF;;;;;;;;;;;;;;;;AAmBH,eAAsB,mBACpB,kBACA,aACwB;AACxB,aAAY;CAEZ,MAAM,UAAU,YADA,MAAM,eAAe,kBAAkB,KAAK,YAAY,CACpC;AACpC,KAAI,CAAC,QAAS,QAAO;AACrB,aACE,iBACA,oBACA,QAAQ,OACR,QAAQ,OACT;AACD,KAAI,CAAC,QAAQ,UAAU;AACrB,YACE,UAAU,QAAQ,MAAM,KAAK,IAC3B,QAAQ,MAAM,SAAS,YAAY,UACpC,OAAO,iBAAiB,oCAC1B;AACD,SAAO;;AAET,QAAO,4BAA4B,QAAQ,MAAM,KAAK,IACpD,QAAQ,MAAM,SAAS,YAAY,UACpC,OAAO;;;;;;;;AASV,eAAe,eACb,KACA,UACA,aACsB;CACtB,MAAM,cAAc,KAAK,QAAQ,KAAK,SAAS;AAE/C,KAAI,CAAC,GAAG,WAAW,YAAY,EAAE;AAC/B,YAAU,0CAA0C,cAAc;AAClE,SAAO,EAAE;;CAGX,MAAM,QAAQ,MAAM,GAAG,8CAA8C;EACnE,KAAK;EACL,UAAU;EACX,CAAC;AAEF,KAAI,MAAM,WAAW,GAAG;AACtB,YAAU,kDAAkD,cAAc;AAC1E,SAAO,EAAE;;AAGX,WACE,mBAAmB,MAAM,OAAO,6BAA6B,WAC9D;CAMD,MAAM,UAA0B,EAAE;AAClC,MAAK,MAAM,YAAY,OAAO;EAC5B,IAAI;AACJ,MAAI;AACF,OAAI,GAAG,SAAS,SAAS,CAAC,OAAO,uBAAuB;AACtD,cACE,0BAA0B,sBAAsB,kCAAkC,WACnF;AACD,cAAU,aAAa,UAAU,sBAAsB;SAEvD,WAAU,GAAG,aAAa,UAAU,QAAQ;WAEvC,KAAK;AACZ,aAAU,oCAAoC,SAAS,IAAI,IAAI;AAC/D;;AAEF,MAAI,QACF,SAAQ,KAAK,GAAI,MAAM,eAAe,SAAS,QAAQ,CAAE;;AAU7D,QAAO,cAAc,SAAS,SAAS,YAAY"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/wizard",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.67.0",
|
|
4
4
|
"homepage": "https://github.com/PostHog/wizard",
|
|
5
5
|
"repository": "https://github.com/PostHog/wizard",
|
|
6
6
|
"description": "The PostHog wizard helps you to configure your project",
|
|
@@ -135,6 +135,7 @@
|
|
|
135
135
|
"postbuild": "chmod +x ./dist/bin.js && cp -r scripts/** dist && rm -f dist/*.no-jest.* && pnpm test:smoke && pnpm test:warlock",
|
|
136
136
|
"test:smoke": "bash ./scripts/smoke-test.sh",
|
|
137
137
|
"test:warlock": "tsx scripts/warlock-smoke-test.ts",
|
|
138
|
+
"test:mcp-install": "tsx scripts/mcp-install-smoke-test.ts",
|
|
138
139
|
"lint": "pnpm lint:prettier && pnpm lint:eslint",
|
|
139
140
|
"lint:prettier": "prettier --check \"{lib,src,test}/**/*.ts\"",
|
|
140
141
|
"lint:eslint": "eslint . --cache --format stylish",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"add-mcp-server-to-clients-ArtlMX9-.js","names":["fs","path","path","os","path","os","fs","execSync","z","os","path","z","os","path","path","fs","z","path","fs","os"],"sources":["../src/steps/add-mcp-server-to-clients/MCPClient.ts","../src/steps/add-mcp-server-to-clients/clients/cursor.ts","../src/steps/add-mcp-server-to-clients/clients/claude-code.ts","../src/steps/add-mcp-server-to-clients/clients/claude-web.ts","../src/steps/add-mcp-server-to-clients/clients/visual-studio-code.ts","../src/steps/add-mcp-server-to-clients/clients/zed.ts","../src/steps/add-mcp-server-to-clients/clients/codex.ts","../src/steps/add-mcp-server-to-clients/clients/opencode.ts","../src/steps/add-mcp-server-to-clients/plugin-client.ts","../src/steps/add-mcp-server-to-clients/index.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as jsonc from 'jsonc-parser';\nimport { getDefaultServerConfig } from './defaults';\nimport type { InstallResult } from './results';\n\nexport type MCPServerConfig = Record<string, unknown>;\n\nexport abstract class MCPClient {\n name: string;\n abstract getConfigPath(): Promise<string>;\n abstract getServerPropertyName(): string;\n abstract isServerInstalled(local?: boolean): Promise<boolean>;\n abstract addServer(\n apiKey?: string,\n selectedFeatures?: string[],\n local?: boolean,\n ): Promise<InstallResult>;\n abstract removeServer(local?: boolean): Promise<InstallResult>;\n abstract isClientSupported(): Promise<boolean>;\n}\n\nexport abstract class DefaultMCPClient extends MCPClient {\n name = 'Default';\n\n constructor() {\n super();\n }\n\n getServerPropertyName(): string {\n return 'mcpServers';\n }\n\n getServerConfig(\n apiKey: string | undefined,\n selectedFeatures?: string[],\n local?: boolean,\n ): MCPServerConfig {\n return getDefaultServerConfig(apiKey, selectedFeatures, local);\n }\n\n async isServerInstalled(local?: boolean): Promise<boolean> {\n try {\n const configPath = await this.getConfigPath();\n\n if (!fs.existsSync(configPath)) {\n return false;\n }\n\n const configContent = await fs.promises.readFile(configPath, 'utf8');\n const config = jsonc.parse(configContent) as Record<string, any>;\n const serverPropertyName = this.getServerPropertyName();\n const serverName = local ? 'posthog-local' : 'posthog';\n\n return (\n serverPropertyName in config && serverName in config[serverPropertyName]\n );\n } catch {\n return false;\n }\n }\n\n async addServer(\n apiKey?: string,\n selectedFeatures?: string[],\n local?: boolean,\n ): Promise<InstallResult> {\n try {\n const configPath = await this.getConfigPath();\n const configDir = path.dirname(configPath);\n\n await fs.promises.mkdir(configDir, { recursive: true });\n\n const serverPropertyName = this.getServerPropertyName();\n let configContent = '';\n let existingConfig = {};\n\n if (fs.existsSync(configPath)) {\n configContent = await fs.promises.readFile(configPath, 'utf8');\n existingConfig = jsonc.parse(configContent) || {};\n }\n\n const newServerConfig = this.getServerConfig(\n apiKey,\n selectedFeatures,\n local,\n );\n const typedConfig = existingConfig as Record<string, any>;\n if (!typedConfig[serverPropertyName]) {\n typedConfig[serverPropertyName] = {};\n }\n const serverName = local ? 'posthog-local' : 'posthog';\n\n // An identical entry means this config is already set up — leave the file\n // untouched and report it, so a re-run says \"already installed\" instead of\n // claiming a write that changed nothing. A differing entry (new features,\n // new key) still gets overwritten and reported as installed.\n const existingServerConfig = typedConfig[serverPropertyName][serverName];\n if (\n existingServerConfig !== undefined &&\n JSON.stringify(existingServerConfig) === JSON.stringify(newServerConfig)\n ) {\n return { success: true, alreadyInstalled: true };\n }\n\n typedConfig[serverPropertyName][serverName] = newServerConfig;\n\n const edits = jsonc.modify(\n configContent,\n [serverPropertyName, serverName],\n newServerConfig,\n {\n formattingOptions: {\n tabSize: 2,\n insertSpaces: true,\n },\n },\n );\n\n const modifiedContent = jsonc.applyEdits(configContent, edits);\n\n await fs.promises.writeFile(configPath, modifiedContent, 'utf8');\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n reason: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n async removeServer(local?: boolean): Promise<InstallResult> {\n let configPath = '';\n try {\n configPath = await this.getConfigPath();\n\n if (!fs.existsSync(configPath)) {\n return { success: true, alreadyInstalled: true };\n }\n\n const configContent = await fs.promises.readFile(configPath, 'utf8');\n const config = jsonc.parse(configContent) as Record<string, any>;\n const serverPropertyName = this.getServerPropertyName();\n\n const serverName = local ? 'posthog-local' : 'posthog';\n\n if (\n serverPropertyName in config &&\n serverName in config[serverPropertyName]\n ) {\n const edits = jsonc.modify(\n configContent,\n [serverPropertyName, serverName],\n undefined,\n {\n formattingOptions: {\n tabSize: 2,\n insertSpaces: true,\n },\n },\n );\n\n const modifiedContent = jsonc.applyEdits(configContent, edits);\n\n await fs.promises.writeFile(configPath, modifiedContent, 'utf8');\n\n return { success: true };\n }\n // No PostHog entry to delete — the config changed under us between the\n // detection pass and now. Nothing failed, so don't report a failure.\n return { success: true, alreadyInstalled: true };\n } catch (error) {\n return {\n success: false,\n reason: `${configPath}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n };\n }\n }\n}\n","import {\n DefaultMCPClient,\n MCPServerConfig,\n} from '@steps/add-mcp-server-to-clients/MCPClient';\nimport * as path from 'path';\nimport * as os from 'os';\nimport {\n DefaultMCPClientConfig,\n getNativeHTTPServerConfig,\n} from '@steps/add-mcp-server-to-clients/defaults';\nimport { z } from 'zod';\n\nexport const CursorMCPConfig = DefaultMCPClientConfig;\n\nexport type CursorMCPConfig = z.infer<typeof DefaultMCPClientConfig>;\n\nexport class CursorMCPClient extends DefaultMCPClient {\n name = 'Cursor';\n\n constructor() {\n super();\n }\n\n async isClientSupported(): Promise<boolean> {\n return Promise.resolve(\n process.platform === 'darwin' || process.platform === 'win32',\n );\n }\n\n async getConfigPath(): Promise<string> {\n return Promise.resolve(path.join(os.homedir(), '.cursor', 'mcp.json'));\n }\n\n getServerConfig(\n apiKey: string | undefined,\n selectedFeatures?: string[],\n local?: boolean,\n ): MCPServerConfig {\n return getNativeHTTPServerConfig(apiKey, selectedFeatures, local);\n }\n}\n","import { DefaultMCPClient } from '@steps/add-mcp-server-to-clients/MCPClient';\nimport {\n DefaultMCPClientConfig,\n buildMCPUrl,\n} from '@steps/add-mcp-server-to-clients/defaults';\nimport {\n PluginCapable,\n PluginInstallResult,\n} from '@steps/add-mcp-server-to-clients/plugin-client';\nimport {\n redactSecrets,\n type InstallResult,\n} from '@steps/add-mcp-server-to-clients/results';\nimport { z } from 'zod';\nimport { execSync } from 'child_process';\nimport { analytics } from '@utils/analytics';\nimport { debug } from '@utils/debug';\nimport * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs';\n\nexport const ClaudeCodeMCPConfig = DefaultMCPClientConfig;\n\nexport type ClaudeCodeMCPConfig = z.infer<typeof DefaultMCPClientConfig>;\n\nexport class ClaudeCodeMCPClient\n extends DefaultMCPClient\n implements PluginCapable\n{\n name = 'Claude Code';\n private claudeBinaryPath: string | null = null;\n\n constructor() {\n super();\n }\n\n private findClaudeBinary(): string | null {\n if (this.claudeBinaryPath) {\n return this.claudeBinaryPath;\n }\n\n // Common installation paths for Claude Code CLI\n const possiblePaths = [\n path.join(os.homedir(), '.claude', 'local', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n for (const claudePath of possiblePaths) {\n if (fs.existsSync(claudePath)) {\n debug(` Found claude binary at: ${claudePath}`);\n this.claudeBinaryPath = claudePath;\n return claudePath;\n }\n }\n\n // Try PATH as fallback\n try {\n execSync('command -v claude', { stdio: 'pipe' });\n debug(' Found claude in PATH');\n this.claudeBinaryPath = 'claude';\n return 'claude';\n } catch {\n // Not in PATH\n }\n\n return null;\n }\n\n isClientSupported(): Promise<boolean> {\n try {\n debug(' Checking for Claude Code...');\n const claudeBinary = this.findClaudeBinary();\n\n if (!claudeBinary) {\n debug(' Claude Code not found. Installation paths checked:');\n debug(` - ${path.join(os.homedir(), '.claude', 'local', 'claude')}`);\n debug(` - /usr/local/bin/claude`);\n debug(` - /opt/homebrew/bin/claude`);\n debug(` - PATH`);\n return Promise.resolve(false);\n }\n\n const output = execSync(`${claudeBinary} --version`, { stdio: 'pipe' });\n const version = output.toString().trim();\n debug(` Claude Code detected: ${version}`);\n return Promise.resolve(true);\n } catch (error) {\n debug(\n ` Claude Code check failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return Promise.resolve(false);\n }\n }\n\n isServerInstalled(local?: boolean): Promise<boolean> {\n const binary = this.findClaudeBinary();\n if (!binary) return Promise.resolve(false);\n const serverName = local ? 'posthog-local' : 'posthog';\n try {\n const output = execSync(`${binary} mcp list`, { stdio: 'pipe' })\n .toString()\n .toLowerCase();\n return Promise.resolve(output.includes(serverName));\n } catch {\n return Promise.resolve(false);\n }\n }\n\n getConfigPath(): Promise<string> {\n throw new Error('Not implemented');\n }\n\n addServer(\n apiKey?: string,\n selectedFeatures?: string[],\n local?: boolean,\n ): Promise<InstallResult> {\n const binary = this.findClaudeBinary();\n if (!binary)\n return Promise.resolve({\n success: false,\n reason: 'The claude CLI is no longer on your PATH.',\n });\n\n const serverName = local ? 'posthog-local' : 'posthog';\n const url = buildMCPUrl(selectedFeatures, local);\n const args = [\n 'mcp',\n 'add',\n '--transport',\n 'http',\n '--scope',\n 'user',\n serverName,\n url,\n ];\n if (apiKey) {\n args.push('--header', `Authorization: Bearer ${apiKey}`);\n }\n\n try {\n execSync(`${binary} ${args.map((a) => JSON.stringify(a)).join(' ')}`, {\n stdio: 'pipe',\n });\n return Promise.resolve({ success: true });\n } catch (error) {\n // The failing command echoes back the Authorization header we passed, so\n // redact before this reaches a log, the screen, or an exception report.\n const msg = redactSecrets(\n error instanceof Error ? error.message : String(error),\n );\n if (msg.includes('already exists')) {\n return Promise.resolve({ success: true, alreadyInstalled: true });\n }\n analytics.captureException(\n new Error(`Claude Code MCP add failed: ${msg}`),\n );\n return Promise.resolve({ success: false, reason: msg });\n }\n }\n\n removeServer(local?: boolean): Promise<InstallResult> {\n const claudeBinary = this.findClaudeBinary();\n if (!claudeBinary) {\n return Promise.resolve({\n success: false,\n reason: 'The claude CLI is no longer on your PATH.',\n });\n }\n\n const serverName = local ? 'posthog-local' : 'posthog';\n const command = `${claudeBinary} mcp remove --scope user ${serverName}`;\n\n try {\n execSync(command, { stdio: 'pipe' });\n } catch (error) {\n const reason = redactSecrets(\n error instanceof Error ? error.message : String(error),\n );\n // Removing something that isn't there is the requested end state, not a\n // failure to report.\n if (/no( such)? mcp server|not found/i.test(reason)) {\n return Promise.resolve({ success: true, alreadyInstalled: true });\n }\n analytics.captureException(\n new Error(`Failed to remove server from Claude Code: ${reason}`),\n );\n return Promise.resolve({ success: false, reason });\n }\n\n return Promise.resolve({ success: true });\n }\n\n supportsPlugin(): boolean {\n return this.findClaudeBinary() !== null;\n }\n\n isPluginInstalled(): Promise<boolean> {\n const binary = this.findClaudeBinary();\n if (!binary) return Promise.resolve(false);\n try {\n const output = execSync(`${binary} plugin list`, {\n stdio: 'pipe',\n }).toString();\n return Promise.resolve(output.toLowerCase().includes('posthog'));\n } catch {\n return Promise.resolve(false);\n }\n }\n\n async installPlugin(): Promise<PluginInstallResult> {\n const binary = this.findClaudeBinary();\n if (!binary)\n return {\n success: false,\n reason: 'The claude CLI is no longer on your PATH.',\n };\n\n // Ask before installing so a re-run reports \"already installed\" rather than\n // relying on the CLI's error text to spot the no-op.\n if (await this.isPluginInstalled()) {\n return { success: true, alreadyInstalled: true };\n }\n\n try {\n execSync(`${binary} plugin install posthog`, { stdio: 'pipe' });\n return { success: true };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes('already installed') || msg.includes('already exists')) {\n return { success: true, alreadyInstalled: true };\n }\n analytics.captureException(\n new Error(`Claude Code plugin install failed: ${msg}`),\n );\n return { success: false, reason: msg };\n }\n }\n}\n","import { MCPClient } from '@steps/add-mcp-server-to-clients/MCPClient';\nimport type { InstallResult } from '@steps/add-mcp-server-to-clients/results';\nimport { BrowserFinishable } from '@steps/add-mcp-server-to-clients/browser-client';\nimport { openTrackedLink } from '@utils/links';\n\n/**\n * Claude Desktop / Claude.ai (web). PostHog ships here as a hosted connector,\n * not a local config — so instead of writing files we open the connector\n * directory page and let the user sign in and click \"Connect\".\n */\nexport class ClaudeWebMCPClient extends MCPClient implements BrowserFinishable {\n name = 'Claude Desktop/Web';\n connectorUrl = 'https://claude.ai/directory/connectors/posthog';\n finishInstruction = 'Sign in and click \"Connect\" to finish.';\n\n isClientSupported(): Promise<boolean> {\n // Browser-based — available on every platform.\n return Promise.resolve(true);\n }\n\n isServerInstalled(): Promise<boolean> {\n // The connector lives in the user's Claude account; nothing local to\n // inspect. Returning false also keeps it out of `mcp remove`.\n return Promise.resolve(false);\n }\n\n addServer(): Promise<InstallResult> {\n // Not a PostHog property, so no UTMs — just the tracked open.\n openTrackedLink(this.connectorUrl, 'claude-web-connector', {\n auto: true,\n skipUtm: true,\n });\n return Promise.resolve({ success: true });\n }\n\n removeServer(): Promise<InstallResult> {\n // The connector lives in the user's Claude account, so `mcp remove` can't\n // touch it. It never reaches here today (isServerInstalled is always\n // false), but say where to go if it ever does.\n return Promise.resolve({\n success: false,\n reason:\n 'This connector is managed in your Claude account — remove it at claude.ai/settings/connectors.',\n });\n }\n\n getConfigPath(): Promise<string> {\n throw new Error('Not implemented');\n }\n\n getServerPropertyName(): string {\n throw new Error('Not implemented');\n }\n}\n\nexport default ClaudeWebMCPClient;\n","import z from 'zod';\nimport * as path from 'path';\nimport * as os from 'os';\nimport {\n DefaultMCPClient,\n MCPServerConfig,\n} from '@steps/add-mcp-server-to-clients/MCPClient';\nimport { getNativeHTTPServerConfig } from '@steps/add-mcp-server-to-clients/defaults';\nimport { runtimeEnv } from '@env';\n\nexport const VisualStudioCodeMCPConfig = z\n .object({\n servers: z.record(\n z.string(),\n z.union([\n z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n }),\n z.object({\n type: z.enum(['http', 'sse']),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n }),\n ]),\n ),\n })\n .passthrough();\n\nexport type VisualStudioCodeMCPConfig = z.infer<\n typeof VisualStudioCodeMCPConfig\n>;\n\nexport class VisualStudioCodeClient extends DefaultMCPClient {\n name = 'Visual Studio Code';\n\n getServerPropertyName(): string {\n return 'servers';\n }\n\n async isClientSupported(): Promise<boolean> {\n return Promise.resolve(\n process.platform === 'darwin' ||\n process.platform === 'win32' ||\n process.platform === 'linux',\n );\n }\n\n async getConfigPath(): Promise<string> {\n const homeDir = os.homedir();\n const isWindows = process.platform === 'win32';\n const isMac = process.platform === 'darwin';\n const isLinux = process.platform === 'linux';\n\n if (isMac) {\n return Promise.resolve(\n path.join(\n homeDir,\n 'Library',\n 'Application Support',\n 'Code',\n 'User',\n 'mcp.json',\n ),\n );\n }\n\n if (isWindows) {\n return Promise.resolve(\n path.join(runtimeEnv('APPDATA') || '', 'Code', 'User', 'mcp.json'),\n );\n }\n\n if (isLinux) {\n return Promise.resolve(\n path.join(homeDir, '.config', 'Code', 'User', 'mcp.json'),\n );\n }\n\n throw new Error(`Unsupported platform: ${process.platform}`);\n }\n\n getServerConfig(\n apiKey: string | undefined,\n selectedFeatures?: string[],\n local?: boolean,\n ): MCPServerConfig {\n return {\n type: 'http',\n ...getNativeHTTPServerConfig(apiKey, selectedFeatures, local),\n };\n }\n}\n","import z from 'zod';\nimport * as path from 'path';\nimport * as os from 'os';\nimport {\n DefaultMCPClient,\n MCPServerConfig,\n} from '@steps/add-mcp-server-to-clients/MCPClient';\nimport { getNativeHTTPServerConfig } from '@steps/add-mcp-server-to-clients/defaults';\nimport { runtimeEnv } from '@env';\n\nexport const ZedMCPConfig = z\n .object({\n context_servers: z.record(\n z.string(),\n z.union([\n z.object({\n enabled: z.boolean().optional(),\n source: z.string().optional(),\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n }),\n z.object({\n enabled: z.boolean().optional(),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n }),\n ]),\n ),\n })\n .passthrough();\n\nexport type ZedMCPConfig = z.infer<typeof ZedMCPConfig>;\n\nexport class ZedClient extends DefaultMCPClient {\n name = 'Zed';\n\n getServerPropertyName(): string {\n return 'context_servers';\n }\n\n async isClientSupported(): Promise<boolean> {\n return Promise.resolve(\n process.platform === 'darwin' || process.platform === 'linux',\n );\n }\n\n async getConfigPath(): Promise<string> {\n const homeDir = os.homedir();\n const isMac = process.platform === 'darwin';\n const isLinux = process.platform === 'linux';\n\n if (isMac) {\n return Promise.resolve(\n path.join(homeDir, '.config', 'zed', 'settings.json'),\n );\n }\n\n if (isLinux) {\n // https://zed.dev/docs/configuring-zed#settings-files\n const xdgConfigHome = runtimeEnv('XDG_CONFIG_HOME');\n if (xdgConfigHome) {\n return Promise.resolve(\n path.join(xdgConfigHome, 'zed', 'settings.json'),\n );\n }\n return Promise.resolve(\n path.join(homeDir, '.config', 'zed', 'settings.json'),\n );\n }\n\n throw new Error(`Unsupported platform: ${process.platform}`);\n }\n\n getServerConfig(\n apiKey: string | undefined,\n selectedFeatures?: string[],\n local?: boolean,\n ): MCPServerConfig {\n return {\n enabled: true,\n ...getNativeHTTPServerConfig(apiKey, selectedFeatures, local),\n };\n }\n}\n","import { z } from 'zod';\nimport { execSync, spawnSync } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport { DefaultMCPClient } from '@steps/add-mcp-server-to-clients/MCPClient';\nimport {\n DefaultMCPClientConfig,\n buildMCPUrl,\n} from '@steps/add-mcp-server-to-clients/defaults';\nimport {\n PluginCapable,\n PluginInstallResult,\n} from '@steps/add-mcp-server-to-clients/plugin-client';\nimport {\n redactSecrets,\n type InstallResult,\n} from '@steps/add-mcp-server-to-clients/results';\n\nimport { analytics } from '@utils/analytics';\n\n/** Wording codex uses when the thing we're adding is already registered. */\nconst ALREADY_INSTALLED_PATTERN =\n /already (installed|exists|added|registered)/i;\n/** Wording that means the opposite: a cache entry exists but the plugin doesn't. */\nconst STALE_MARKETPLACE_CACHE = /already added from a different source/i;\n\nexport const CodexMCPConfig = DefaultMCPClientConfig;\n\nexport type CodexMCPConfig = z.infer<typeof DefaultMCPClientConfig>;\n\nexport class CodexMCPClient extends DefaultMCPClient implements PluginCapable {\n name = 'Codex';\n private codexBinaryPath: string | null = null;\n\n constructor() {\n super();\n }\n\n private findCodexBinary(): string | null {\n if (this.codexBinaryPath) return this.codexBinaryPath;\n try {\n const resolved = execSync('command -v codex', { stdio: 'pipe' })\n .toString()\n .trim();\n if (resolved) {\n this.codexBinaryPath = resolved;\n return resolved;\n }\n } catch {\n // not in PATH\n }\n return null;\n }\n\n isClientSupported(): Promise<boolean> {\n return Promise.resolve(this.findCodexBinary() !== null);\n }\n\n getConfigPath(): Promise<string> {\n throw new Error('Not implemented');\n }\n\n isServerInstalled(local?: boolean): Promise<boolean> {\n const binary = this.findCodexBinary();\n if (!binary) return Promise.resolve(false);\n const serverName = local ? 'posthog-local' : 'posthog';\n const result = spawnSync(binary, ['mcp', 'list'], { encoding: 'utf-8' });\n if (result.status !== 0) return Promise.resolve(false);\n return Promise.resolve(\n (result.stdout ?? '').toLowerCase().includes(serverName),\n );\n }\n\n addServer(\n apiKey?: string,\n selectedFeatures?: string[],\n local?: boolean,\n ): Promise<InstallResult> {\n const binary = this.findCodexBinary();\n if (!binary)\n return Promise.resolve({\n success: false,\n reason: 'The codex CLI is no longer on your PATH.',\n });\n\n const serverName = local ? 'posthog-local' : 'posthog';\n const url = buildMCPUrl(selectedFeatures, local);\n const args = ['mcp', 'add', serverName, '--url', url];\n const env = { ...process.env };\n if (apiKey) {\n const tokenVar = 'POSTHOG_AUTH_HEADER';\n env[tokenVar] = `Bearer ${apiKey}`;\n args.push('--bearer-token-env-var', tokenVar);\n }\n\n const result = spawnSync(binary, args, { encoding: 'utf-8', env });\n if (result.status !== 0) {\n const stderr = result.stderr ?? '';\n if (ALREADY_INSTALLED_PATTERN.test(stderr)) {\n return Promise.resolve({ success: true, alreadyInstalled: true });\n }\n const reason = redactSecrets(stderr);\n analytics.captureException(new Error(`Codex MCP add failed: ${reason}`));\n return Promise.resolve({ success: false, reason });\n }\n return Promise.resolve({ success: true });\n }\n\n removeServer(local?: boolean): Promise<InstallResult> {\n const binary = this.findCodexBinary();\n if (!binary)\n return Promise.resolve({\n success: false,\n reason: 'The codex CLI is no longer on your PATH.',\n });\n\n // `local` was ignored here, so `mcp remove --local` reported success while\n // leaving the posthog-local server in place.\n const serverName = local ? 'posthog-local' : 'posthog';\n const result = spawnSync(binary, ['mcp', 'remove', serverName], {\n encoding: 'utf-8',\n });\n\n if (result.error || result.status !== 0) {\n const reason = redactSecrets(\n result.error?.message ?? result.stderr ?? 'codex mcp remove failed',\n );\n analytics.captureException(\n new Error(`Failed to remove server from Codex CLI: ${reason}`),\n );\n return Promise.resolve({ success: false, reason });\n }\n\n return Promise.resolve({ success: true });\n }\n\n supportsPlugin(): boolean {\n return this.findCodexBinary() !== null;\n }\n\n isPluginInstalled(): Promise<boolean> {\n const configPath = path.join(os.homedir(), '.codex', 'config.toml');\n try {\n const contents = fs.readFileSync(configPath, 'utf-8');\n // Marketplace installs appear as [marketplaces.posthog] in config.toml\n return Promise.resolve(\n contents.toLowerCase().includes('[marketplaces.posthog]'),\n );\n } catch {\n return Promise.resolve(false);\n }\n }\n\n async installPlugin(): Promise<PluginInstallResult> {\n const binary = this.findCodexBinary();\n if (!binary)\n return {\n success: false,\n reason: 'The codex CLI is no longer on your PATH.',\n };\n\n // `codex plugin marketplace add` exits non-zero once the marketplace is\n // registered, so ask config.toml first. Without this the second run of\n // `mcp add` looked like a failure and reported nothing at all.\n if (await this.isPluginInstalled()) {\n return { success: true, alreadyInstalled: true };\n }\n\n const run = () =>\n spawnSync(binary, ['plugin', 'marketplace', 'add', 'PostHog/ai-plugin'], {\n encoding: 'utf-8',\n });\n\n let result = run();\n\n // Stale cache directory with no config.toml entry — clear it and retry\n if (\n result.status !== 0 &&\n STALE_MARKETPLACE_CACHE.test(result.stderr ?? '')\n ) {\n const staleDir = path.join(\n os.homedir(),\n '.codex',\n '.tmp',\n 'marketplaces',\n 'posthog',\n );\n try {\n fs.rmSync(staleDir, { recursive: true, force: true });\n } catch {\n // ignore — retry anyway\n }\n result = run();\n }\n\n if (result.status !== 0) {\n const stderr = result.stderr ?? '';\n // The marketplace was registered by something other than us (a manual\n // `codex plugin marketplace add`, or a version that writes config.toml\n // differently) — that's still \"already installed\", not a failure. The\n // stale-cache wording above is deliberately excluded: that one means the\n // plugin is NOT registered, and it only reaches here if the retry failed.\n if (\n ALREADY_INSTALLED_PATTERN.test(stderr) &&\n !STALE_MARKETPLACE_CACHE.test(stderr)\n ) {\n return { success: true, alreadyInstalled: true };\n }\n const reason = redactSecrets(stderr);\n analytics.captureException(\n new Error(`Codex plugin install failed: ${reason}`),\n );\n return { success: false, reason };\n }\n\n return { success: true };\n }\n}\n\nexport default CodexMCPClient;\n","import z from 'zod';\nimport * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs';\nimport { execSync } from 'node:child_process';\nimport {\n DefaultMCPClient,\n MCPServerConfig,\n} from '@steps/add-mcp-server-to-clients/MCPClient';\nimport { getNativeHTTPServerConfig } from '@steps/add-mcp-server-to-clients/defaults';\nimport { runtimeEnv } from '@env';\n\nexport const OpenCodeMCPConfig = z\n .object({\n mcp: z.record(\n z.string(),\n z.object({\n type: z.literal('remote'),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n }),\n ),\n })\n .passthrough();\n\nexport type OpenCodeMCPConfig = z.infer<typeof OpenCodeMCPConfig>;\n\nexport class OpenCodeMCPClient extends DefaultMCPClient {\n name = 'OpenCode';\n private opencodeBinaryPath: string | null = null;\n\n override getServerPropertyName(): string {\n return 'mcp';\n }\n\n private findOpenCodeBinary(): string | null {\n if (this.opencodeBinaryPath) return this.opencodeBinaryPath;\n try {\n const resolved = execSync('command -v opencode', { stdio: 'pipe' })\n .toString()\n .trim();\n if (resolved) {\n this.opencodeBinaryPath = resolved;\n return resolved;\n }\n } catch {\n // not in PATH\n }\n return null;\n }\n\n isClientSupported(): Promise<boolean> {\n return Promise.resolve(this.findOpenCodeBinary() !== null);\n }\n\n getConfigPath(): Promise<string> {\n const configDir = this.resolveConfigDir();\n const jsoncPath = path.join(configDir, 'opencode.jsonc');\n const jsonPath = path.join(configDir, 'opencode.json');\n if (fs.existsSync(jsoncPath)) return Promise.resolve(jsoncPath);\n return Promise.resolve(jsonPath);\n }\n\n private resolveConfigDir(): string {\n const envDir = runtimeEnv('OPENCODE_CONFIG_DIR');\n if (envDir) return envDir;\n\n const xdgHome = runtimeEnv('XDG_CONFIG_HOME');\n if (xdgHome) return path.join(xdgHome, 'opencode');\n\n return path.join(os.homedir(), '.config', 'opencode');\n }\n\n override getServerConfig(\n apiKey: string | undefined,\n selectedFeatures?: string[],\n local?: boolean,\n ): MCPServerConfig {\n return {\n type: 'remote',\n ...getNativeHTTPServerConfig(apiKey, selectedFeatures, local),\n };\n }\n}\n","import type { InstallResult } from './results';\n\nexport type PluginInstallResult = InstallResult;\n\nexport interface PluginCapable {\n supportsPlugin(): boolean;\n isPluginInstalled(): Promise<boolean>;\n installPlugin(): Promise<PluginInstallResult>;\n}\n\nexport function isPluginCapable<T>(client: T): client is T & PluginCapable {\n return (\n typeof client === 'object' &&\n client !== null &&\n 'supportsPlugin' in client &&\n 'installPlugin' in client\n );\n}\n","import type { Integration } from '@lib/constants';\nimport type { CloudRegion } from '@utils/types';\nimport { withProgress } from '../../telemetry';\nimport { analytics } from '@utils/analytics';\nimport { getUI } from '@ui';\nimport { MCPClient } from './MCPClient';\nimport { CursorMCPClient } from './clients/cursor';\nimport { ClaudeCodeMCPClient } from './clients/claude-code';\nimport { ClaudeWebMCPClient } from './clients/claude-web';\nimport { VisualStudioCodeClient } from './clients/visual-studio-code';\nimport { ZedClient } from './clients/zed';\nimport { CodexMCPClient } from './clients/codex';\nimport { OpenCodeMCPClient } from './clients/opencode';\nimport { ALL_FEATURE_VALUES } from './defaults';\nimport { debug } from '@utils/debug';\nimport { isPluginCapable, PluginCapable } from './plugin-client';\nimport {\n McpClientStatus,\n namesWithStatus,\n toClientResult,\n type McpClientResult,\n} from './results';\n\nexport const getSupportedClients = async (): Promise<MCPClient[]> => {\n const allClients = [\n new ClaudeCodeMCPClient(),\n new ClaudeWebMCPClient(),\n new CodexMCPClient(),\n new CursorMCPClient(),\n new VisualStudioCodeClient(),\n new ZedClient(),\n new OpenCodeMCPClient(),\n ];\n const supportedClients: MCPClient[] = [];\n\n debug('Checking for supported MCP clients...');\n for (const client of allClients) {\n const isSupported = await client.isClientSupported();\n debug(`${client.name}: ${isSupported ? '✓ supported' : '✗ not supported'}`);\n if (isSupported) {\n supportedClients.push(client);\n }\n }\n debug(\n `Found ${supportedClients.length} supported client(s): ${supportedClients\n .map((c) => c.name)\n .join(', ')}`,\n );\n\n return supportedClients;\n};\n\n/**\n * Add MCP server to clients. No prompts — pure orchestration.\n * Prompts are handled by McpScreen (TUI) or auto-accepted (CI).\n */\nexport const addMCPServerToClientsStep = async ({\n integration,\n local = false,\n ci = false,\n cloudRegion: _cloudRegion,\n features,\n apiKey,\n}: {\n integration?: Integration;\n local?: boolean;\n ci?: boolean;\n cloudRegion?: CloudRegion;\n features?: string[];\n apiKey?: string;\n}): Promise<string[]> => {\n const ui = getUI();\n\n // CI mode: skip MCP installation entirely\n if (ci) {\n ui.log.info('Skipping MCP installation (CI mode)');\n return [];\n }\n\n const supportedClients = await getSupportedClients();\n\n if (supportedClients.length === 0) {\n ui.log.info(\n 'No supported MCP clients detected. Skipping MCP installation.',\n );\n return [];\n }\n\n // Auto-install to all supported clients\n const results = await withProgress('adding mcp servers', () =>\n addMCPServer(\n supportedClients,\n apiKey,\n features ?? [...ALL_FEATURE_VALUES],\n local,\n ),\n );\n\n const installed = namesWithStatus(results, McpClientStatus.Changed);\n const already = namesWithStatus(results, McpClientStatus.Unchanged);\n const failed = results.filter((r) => r.status === McpClientStatus.Failed);\n\n // Report each outcome on its own — a blanket \"Added the MCP server to: ...\"\n // hid both the no-op re-runs and the outright failures.\n if (installed.length > 0) {\n ui.log.success(`Added the MCP server to:\\n${bulletList(installed)}`);\n }\n if (already.length > 0) {\n ui.log.info(\n `The PostHog MCP server was already installed, so nothing changed for:\\n${bulletList(\n already,\n )}`,\n );\n }\n if (failed.length > 0) {\n ui.log.warn(\n `Couldn't add the MCP server to:\\n${bulletList(\n failed.map((r) => (r.detail ? `${r.name} — ${r.detail}` : r.name)),\n )}`,\n );\n }\n\n const withServer = [...installed, ...already];\n\n analytics.wizardCapture('mcp servers added', {\n // `clients` stays \"every client that ended up with the MCP server\", which is\n // what it meant before — the new properties break that down.\n clients: withServer,\n already_installed_clients: already,\n failed_clients: failed.map((r) => r.name),\n attempted_clients: supportedClients.map((c) => c.name),\n integration,\n });\n\n return withServer;\n};\n\nconst bulletList = (items: string[]): string =>\n items.map((item) => ` - ${item}`).join('\\n');\n\nexport const removeMCPServerFromClientsStep = async ({\n integration,\n local = false,\n}: {\n integration?: Integration;\n local?: boolean;\n}): Promise<string[]> => {\n const ui = getUI();\n const installedClients = await getInstalledClients(local);\n if (installedClients.length === 0) {\n ui.log.info(\n 'The PostHog MCP server is not installed for any supported client. Nothing to remove.',\n );\n analytics.wizardCapture('mcp no servers to remove', {\n integration,\n });\n return [];\n }\n\n // Auto-remove from all installed clients\n const results = await withProgress('removing mcp servers', () =>\n removeMCPServer(installedClients, local),\n );\n\n const removed = namesWithStatus(results, McpClientStatus.Changed);\n const nothingToDo = namesWithStatus(results, McpClientStatus.Unchanged);\n const failed = results.filter((r) => r.status === McpClientStatus.Failed);\n\n // This step used to print nothing at all, so a non-TTY `mcp remove` gave no\n // hint whether anything happened — let alone whether a client failed.\n if (removed.length > 0) {\n ui.log.success(`Removed the MCP server from:\\n${bulletList(removed)}`);\n }\n if (nothingToDo.length > 0) {\n ui.log.info(\n `No PostHog MCP entry left to remove for:\\n${bulletList(nothingToDo)}`,\n );\n }\n if (failed.length > 0) {\n ui.log.warn(\n `Couldn't remove the MCP server from:\\n${bulletList(\n failed.map((r) => (r.detail ? `${r.name} — ${r.detail}` : r.name)),\n )}`,\n );\n }\n\n analytics.wizardCapture('mcp servers removed', {\n clients: removed,\n nothing_to_remove_clients: nothingToDo,\n failed_clients: failed.map((r) => r.name),\n attempted_clients: installedClients.map((c) => c.name),\n integration,\n });\n\n return removed;\n};\n\nexport const getInstalledClients = async (\n local?: boolean,\n): Promise<MCPClient[]> => {\n const clients = await getSupportedClients();\n const installedClients: MCPClient[] = [];\n\n for (const client of clients) {\n if (await client.isServerInstalled(local)) {\n installedClients.push(client);\n }\n }\n\n return installedClients;\n};\n\nexport const addMCPServer = async (\n clients: MCPClient[],\n personalApiKey?: string,\n selectedFeatures?: string[],\n local?: boolean,\n): Promise<McpClientResult[]> => {\n const results: McpClientResult[] = [];\n for (const client of clients) {\n try {\n const result = await client.addServer(\n personalApiKey,\n selectedFeatures,\n local,\n );\n results.push(toClientResult(client.name, result));\n } catch (err) {\n debug(`[addMCPServer] addServer threw for ${client.name}: ${err}`);\n results.push(\n toClientResult(client.name, {\n success: false,\n reason: err instanceof Error ? err.message : String(err),\n }),\n );\n }\n }\n return results;\n};\n\nexport const getSupportedPluginClients = (\n clients: MCPClient[],\n): Array<MCPClient & PluginCapable> => {\n return clients.filter(isPluginCapable).filter((c) => c.supportsPlugin());\n};\n\nexport const installPlugins = async (\n clients: Array<MCPClient & PluginCapable>,\n): Promise<McpClientResult[]> => {\n const results: McpClientResult[] = [];\n for (const client of clients) {\n try {\n results.push(toClientResult(client.name, await client.installPlugin()));\n } catch (err) {\n debug(`[installPlugins] installPlugin threw for ${client.name}: ${err}`);\n results.push(\n toClientResult(client.name, {\n success: false,\n reason: err instanceof Error ? err.message : String(err),\n }),\n );\n }\n }\n return results;\n};\n\nexport const removeMCPServer = async (\n clients: MCPClient[],\n local?: boolean,\n): Promise<McpClientResult[]> => {\n const results: McpClientResult[] = [];\n for (const client of clients) {\n try {\n results.push(\n toClientResult(client.name, await client.removeServer(local)),\n );\n } catch (err) {\n debug(`[removeMCPServer] removeServer threw for ${client.name}: ${err}`);\n results.push(\n toClientResult(client.name, {\n success: false,\n reason: err instanceof Error ? err.message : String(err),\n }),\n );\n }\n }\n return results;\n};\n"],"mappings":"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"}
|