@posthog/wizard 2.0.2 → 2.2.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/bin.js +27 -4
- package/dist/bin.js.map +1 -1
- package/dist/src/__tests__/cli.test.js +50 -3
- package/dist/src/__tests__/cli.test.js.map +1 -1
- package/dist/src/__tests__/package-json.test.d.ts +1 -0
- package/dist/src/__tests__/package-json.test.js +173 -0
- package/dist/src/__tests__/package-json.test.js.map +1 -0
- package/dist/src/__tests__/run.test.js +4 -2
- package/dist/src/__tests__/run.test.js.map +1 -1
- package/dist/src/lib/__tests__/agent-interface.test.js +3 -1
- package/dist/src/lib/__tests__/agent-interface.test.js.map +1 -1
- package/dist/src/lib/__tests__/yara-hooks.test.d.ts +1 -0
- package/dist/src/lib/__tests__/yara-hooks.test.js +432 -0
- package/dist/src/lib/__tests__/yara-hooks.test.js.map +1 -0
- package/dist/src/lib/__tests__/yara-scanner.test.d.ts +1 -0
- package/dist/src/lib/__tests__/yara-scanner.test.js +613 -0
- package/dist/src/lib/__tests__/yara-scanner.test.js.map +1 -0
- package/dist/src/lib/agent-interface.d.ts +6 -3
- package/dist/src/lib/agent-interface.js +70 -34
- package/dist/src/lib/agent-interface.js.map +1 -1
- package/dist/src/lib/agent-runner.js +60 -23
- package/dist/src/lib/agent-runner.js.map +1 -1
- package/dist/src/lib/commandments.js +3 -1
- package/dist/src/lib/commandments.js.map +1 -1
- package/dist/src/lib/constants.d.ts +4 -3
- package/dist/src/lib/constants.js +3 -2
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/health-checks/readiness.d.ts +5 -0
- package/dist/src/lib/health-checks/readiness.js +79 -19
- package/dist/src/lib/health-checks/readiness.js.map +1 -1
- package/dist/src/lib/skill-install.d.ts +10 -0
- package/dist/src/lib/skill-install.js +23 -0
- package/dist/src/lib/skill-install.js.map +1 -0
- package/dist/src/lib/version.d.ts +1 -1
- package/dist/src/lib/version.js +1 -1
- package/dist/src/lib/version.js.map +1 -1
- package/dist/src/lib/wizard-session.d.ts +5 -4
- package/dist/src/lib/wizard-session.js +3 -1
- package/dist/src/lib/wizard-session.js.map +1 -1
- package/dist/src/lib/wizard-tools.d.ts +2 -0
- package/dist/src/lib/wizard-tools.js +119 -2
- package/dist/src/lib/wizard-tools.js.map +1 -1
- package/dist/src/lib/yara-hooks.d.ts +44 -0
- package/dist/src/lib/yara-hooks.js +377 -0
- package/dist/src/lib/yara-hooks.js.map +1 -0
- package/dist/src/lib/yara-scanner.d.ts +61 -0
- package/dist/src/lib/yara-scanner.js +328 -0
- package/dist/src/lib/yara-scanner.js.map +1 -0
- package/dist/src/run.d.ts +3 -0
- package/dist/src/run.js +10 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/steps/add-mcp-server-to-clients/index.d.ts +2 -1
- package/dist/src/steps/add-mcp-server-to-clients/index.js +1 -1
- package/dist/src/steps/add-mcp-server-to-clients/index.js.map +1 -1
- package/dist/src/ui/logging-ui.d.ts +2 -4
- package/dist/src/ui/logging-ui.js +12 -4
- package/dist/src/ui/logging-ui.js.map +1 -1
- package/dist/src/ui/tui/__tests__/store.test.js +81 -29
- package/dist/src/ui/tui/__tests__/store.test.js.map +1 -1
- package/dist/src/ui/tui/components/ServiceHealthList.d.ts +15 -0
- package/dist/src/ui/tui/components/ServiceHealthList.js +57 -0
- package/dist/src/ui/tui/components/ServiceHealthList.js.map +1 -0
- package/dist/src/ui/tui/flows.d.ts +1 -0
- package/dist/src/ui/tui/flows.js +12 -0
- package/dist/src/ui/tui/flows.js.map +1 -1
- package/dist/src/ui/tui/ink-ui.d.ts +2 -4
- package/dist/src/ui/tui/ink-ui.js +8 -4
- package/dist/src/ui/tui/ink-ui.js.map +1 -1
- package/dist/src/ui/tui/playground/PlaygroundApp.js +12 -0
- package/dist/src/ui/tui/playground/PlaygroundApp.js.map +1 -1
- package/dist/src/ui/tui/playground/demos/HealthCheckDemo.d.ts +11 -0
- package/dist/src/ui/tui/playground/demos/HealthCheckDemo.js +56 -0
- package/dist/src/ui/tui/playground/demos/HealthCheckDemo.js.map +1 -0
- package/dist/src/ui/tui/playground/demos/ModalDemo.d.ts +6 -0
- package/dist/src/ui/tui/playground/demos/ModalDemo.js +13 -0
- package/dist/src/ui/tui/playground/demos/ModalDemo.js.map +1 -0
- package/dist/src/ui/tui/primitives/ModalOverlay.d.ts +25 -0
- package/dist/src/ui/tui/primitives/ModalOverlay.js +7 -0
- package/dist/src/ui/tui/primitives/ModalOverlay.js.map +1 -0
- package/dist/src/ui/tui/primitives/ProgressList.js +3 -1
- package/dist/src/ui/tui/primitives/ProgressList.js.map +1 -1
- package/dist/src/ui/tui/primitives/index.d.ts +1 -0
- package/dist/src/ui/tui/primitives/index.js +1 -0
- package/dist/src/ui/tui/primitives/index.js.map +1 -1
- package/dist/src/ui/tui/router.d.ts +0 -1
- package/dist/src/ui/tui/router.js +0 -1
- package/dist/src/ui/tui/router.js.map +1 -1
- package/dist/src/ui/tui/screen-registry.js +2 -2
- package/dist/src/ui/tui/screen-registry.js.map +1 -1
- package/dist/src/ui/tui/screens/PortConflictScreen.js +13 -13
- package/dist/src/ui/tui/screens/PortConflictScreen.js.map +1 -1
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.js +8 -8
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.js.map +1 -1
- package/dist/src/ui/tui/screens/health/HealthCheckScreen.d.ts +14 -0
- package/dist/src/ui/tui/screens/health/HealthCheckScreen.js +32 -0
- package/dist/src/ui/tui/screens/health/HealthCheckScreen.js.map +1 -0
- package/dist/src/ui/tui/store.d.ts +15 -5
- package/dist/src/ui/tui/store.js +47 -3
- package/dist/src/ui/tui/store.js.map +1 -1
- package/dist/src/ui/wizard-ui.d.ts +4 -5
- package/dist/src/ui/wizard-ui.js.map +1 -1
- package/dist/src/utils/__tests__/setup-utils.test.js +2 -1
- package/dist/src/utils/__tests__/setup-utils.test.js.map +1 -1
- package/dist/src/utils/rules/universal.md +12 -0
- package/dist/src/utils/types.d.ts +9 -0
- package/dist/src/utils/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/src/ui/tui/screens/OutageScreen.d.ts +0 -10
- package/dist/src/ui/tui/screens/OutageScreen.js +0 -17
- package/dist/src/ui/tui/screens/OutageScreen.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard-tools.js","sourceRoot":"","sources":["../../../src/lib/wizard-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unified in-process MCP server for the PostHog wizard.\n *\n * Provides tools that run locally (secret values never leave the machine):\n * - check_env_keys: Check which env var keys exist in a .env file\n * - set_env_values: Create/update env vars in a .env file\n * - detect_package_manager: Detect the project's package manager(s)\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { z } from 'zod';\nimport { logToFile } from '../utils/debug';\nimport type { PackageManagerDetector } from './package-manager-detection';\n\n// ---------------------------------------------------------------------------\n// SDK dynamic import (ESM module loaded once, cached)\n// ---------------------------------------------------------------------------\n\nlet _sdkModule: any = null;\nasync function getSDKModule(): Promise<any> {\n if (!_sdkModule) {\n _sdkModule = await import('@anthropic-ai/claude-agent-sdk');\n }\n return _sdkModule;\n}\n\n// ---------------------------------------------------------------------------\n// Options for creating the wizard tools server\n// ---------------------------------------------------------------------------\n\nexport interface WizardToolsOptions {\n /** Root directory of the project being analyzed */\n workingDirectory: string;\n\n /** Framework-specific package manager detector */\n detectPackageManager: PackageManagerDetector;\n}\n\n// ---------------------------------------------------------------------------\n// Env file helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve filePath relative to workingDirectory, rejecting path traversal.\n */\nexport function resolveEnvPath(\n workingDirectory: string,\n filePath: string,\n): string {\n const resolved = path.resolve(workingDirectory, filePath);\n if (\n !resolved.startsWith(workingDirectory + path.sep) &&\n resolved !== workingDirectory\n ) {\n throw new Error(\n `Path traversal rejected: \"${filePath}\" resolves outside working directory`,\n );\n }\n return resolved;\n}\n\n/**\n * Ensure the given env file basename is covered by .gitignore in the working directory.\n * Creates .gitignore if it doesn't exist; appends the entry if missing.\n */\nexport function ensureGitignoreCoverage(\n workingDirectory: string,\n envFileName: string,\n): void {\n const gitignorePath = path.join(workingDirectory, '.gitignore');\n\n if (fs.existsSync(gitignorePath)) {\n const content = fs.readFileSync(gitignorePath, 'utf8');\n // Check if the file (or a glob covering it) is already listed\n if (content.split('\\n').some((line) => line.trim() === envFileName)) {\n return;\n }\n const newContent = content.endsWith('\\n')\n ? `${content}${envFileName}\\n`\n : `${content}\\n${envFileName}\\n`;\n fs.writeFileSync(gitignorePath, newContent, 'utf8');\n } else {\n fs.writeFileSync(gitignorePath, `${envFileName}\\n`, 'utf8');\n }\n}\n\n/**\n * Parse a .env file's content and return the set of defined key names.\n */\nexport function parseEnvKeys(content: string): Set<string> {\n const keys = new Set<string>();\n for (const line of content.split('\\n')) {\n const match = line.match(/^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (match) {\n keys.add(match[1]);\n }\n }\n return keys;\n}\n\n/**\n * Merge key-value pairs into existing .env content.\n * Updates existing keys in-place, appends new keys at the end.\n */\nexport function mergeEnvValues(\n content: string,\n values: Record<string, string>,\n): string {\n let result = content;\n const updatedKeys = new Set<string>();\n\n for (const [key, value] of Object.entries(values)) {\n const regex = new RegExp(`^(\\\\s*${key}\\\\s*=).*$`, 'm');\n if (regex.test(result)) {\n result = result.replace(regex, `$1${value}`);\n updatedKeys.add(key);\n }\n }\n\n const newKeys = Object.entries(values).filter(\n ([key]) => !updatedKeys.has(key),\n );\n if (newKeys.length > 0) {\n if (result.length > 0 && !result.endsWith('\\n')) {\n result += '\\n';\n }\n for (const [key, value] of newKeys) {\n result += `${key}=${value}\\n`;\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Server factory\n// ---------------------------------------------------------------------------\n\nconst SERVER_NAME = 'wizard-tools';\n\n/**\n * Create the unified in-process MCP server with all wizard tools.\n * Must be called asynchronously because the SDK is an ESM module loaded via dynamic import.\n */\nexport async function createWizardToolsServer(options: WizardToolsOptions) {\n const { workingDirectory, detectPackageManager } = options;\n const sdk = await getSDKModule();\n const { tool, createSdkMcpServer } = sdk;\n\n // -- check_env_keys -------------------------------------------------------\n\n const checkEnvKeys = tool(\n 'check_env_keys',\n 'Check which environment variable keys are present or missing in a .env file. Never reveals values.',\n {\n filePath: z\n .string()\n .describe('Path to the .env file, relative to the project root'),\n keys: z\n .array(z.string())\n .describe('Environment variable key names to check'),\n },\n (args: { filePath: string; keys: string[] }) => {\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n logToFile(`check_env_keys: ${resolved}, keys: ${args.keys.join(', ')}`);\n\n const existingKeys: Set<string> = fs.existsSync(resolved)\n ? parseEnvKeys(fs.readFileSync(resolved, 'utf8'))\n : new Set<string>();\n\n const results: Record<string, 'present' | 'missing'> = {};\n for (const key of args.keys) {\n results[key] = existingKeys.has(key) ? 'present' : 'missing';\n }\n\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(results, null, 2) },\n ],\n };\n },\n );\n\n // -- set_env_values -------------------------------------------------------\n\n const setEnvValues = tool(\n 'set_env_values',\n 'Create or update environment variable keys in a .env file. Creates the file if it does not exist. Ensures .gitignore coverage.',\n {\n filePath: z\n .string()\n .describe('Path to the .env file, relative to the project root'),\n values: z\n .record(z.string(), z.string())\n .describe('Key-value pairs to set'),\n },\n (args: { filePath: string; values: Record<string, string> }) => {\n // Block the wrong key name — the correct key is NEXT_PUBLIC_POSTHOG_KEY or similar\n const forbidden = Object.keys(args.values).find(\n (k) => k.toUpperCase() === 'POSTHOG_API_KEY',\n );\n if (forbidden) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Error: \"${forbidden}\" is not a valid PostHog env var name. Use the project-specific key name from your framework's integration guide (e.g. NEXT_PUBLIC_POSTHOG_KEY).`,\n },\n ],\n isError: true,\n };\n }\n\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n logToFile(\n `set_env_values: ${resolved}, keys: ${Object.keys(args.values).join(\n ', ',\n )}`,\n );\n\n const existing = fs.existsSync(resolved)\n ? fs.readFileSync(resolved, 'utf8')\n : '';\n const content = mergeEnvValues(existing, args.values);\n\n // Ensure parent directory exists\n const dir = path.dirname(resolved);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n fs.writeFileSync(resolved, content, 'utf8');\n\n // Ensure .gitignore coverage for this env file\n const envFileName = path.basename(resolved);\n ensureGitignoreCoverage(workingDirectory, envFileName);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Updated ${Object.keys(args.values).length} key(s) in ${\n args.filePath\n }`,\n },\n ],\n };\n },\n );\n\n // -- detect_package_manager -----------------------------------------------\n\n const detectPM = tool(\n 'detect_package_manager',\n 'Detect which package manager(s) the project uses. Returns the name, install command, and run command for each detected package manager. Call this before running any install commands.',\n {},\n async () => {\n logToFile(`detect_package_manager: scanning ${workingDirectory}`);\n\n const result = await detectPackageManager(workingDirectory);\n\n logToFile(\n `detect_package_manager: detected ${result.detected.length} package manager(s)`,\n );\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n },\n );\n\n // -- Assemble server ------------------------------------------------------\n\n return createSdkMcpServer({\n name: SERVER_NAME,\n version: '1.0.0',\n tools: [checkEnvKeys, setEnvValues, detectPM],\n });\n}\n\n/** Tool names exposed by the wizard-tools server, for use in allowedTools */\nexport const WIZARD_TOOL_NAMES = [\n `${SERVER_NAME}:check_env_keys`,\n `${SERVER_NAME}:set_env_values`,\n `${SERVER_NAME}:detect_package_manager`,\n];\n"]}
|
|
1
|
+
{"version":3,"file":"wizard-tools.js","sourceRoot":"","sources":["../../../src/lib/wizard-tools.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;AA2CH,wCAcC;AAMD,0DAmBC;AAKD,oCASC;AAMD,wCA4BC;AAYD,0DAgSC;AA5aD,gDAAwB;AACxB,4CAAoB;AACpB,iDAA6C;AAC7C,6BAAwB;AACxB,0CAA2C;AAG3C,8EAA8E;AAC9E,sDAAsD;AACtD,8EAA8E;AAE9E,IAAI,UAAU,GAAQ,IAAI,CAAC;AAC3B,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,UAAU,GAAG,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAiBD,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;GAEG;AACH,SAAgB,cAAc,CAC5B,gBAAwB,EACxB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAC1D,IACE,CAAC,QAAQ,CAAC,UAAU,CAAC,gBAAgB,GAAG,cAAI,CAAC,GAAG,CAAC;QACjD,QAAQ,KAAK,gBAAgB,EAC7B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,6BAA6B,QAAQ,sCAAsC,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CACrC,gBAAwB,EACxB,WAAmB;IAEnB,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;IAEhE,IAAI,YAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACvD,8DAA8D;QAC9D,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,WAAW,CAAC,EAAE,CAAC;YACpE,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YACvC,CAAC,CAAC,GAAG,OAAO,GAAG,WAAW,IAAI;YAC9B,CAAC,CAAC,GAAG,OAAO,KAAK,WAAW,IAAI,CAAC;QACnC,YAAE,CAAC,aAAa,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,YAAE,CAAC,aAAa,CAAC,aAAa,EAAE,GAAG,WAAW,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,OAAe;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC7D,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAC5B,OAAe,EACf,MAA8B;IAE9B,IAAI,MAAM,GAAG,OAAO,CAAC;IACrB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,SAAS,GAAG,WAAW,EAAE,GAAG,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,KAAK,EAAE,CAAC,CAAC;YAC7C,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAC3C,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CACjC,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,IAAI,CAAC;QACjB,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,WAAW,GAAG,cAAc,CAAC;AAEnC;;;GAGG;AACI,KAAK,UAAU,uBAAuB,CAAC,OAA2B;IACvE,MAAM,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAC1E,MAAM,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,GAAG,GAAG,CAAC;IAIzC,IAAI,eAAe,GAAiC,EAAE,CAAC;IACvD,IAAI,aAAa,GAA0B,CAAC,aAAa,CAAC,CAAC;IAE3D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,GAAG,aAAa,kBAAkB,CAAC;QACnD,IAAA,iBAAS,EAAC,8CAA8C,OAAO,EAAE,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAE9B,CAAC;YACF,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC;YAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC1C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,aAAa,GAAG,IAA6B,CAAC;YAChD,CAAC;YACD,IAAA,iBAAS,EAAC,oCAAoC,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,IAAA,iBAAS,EAAC,0CAA0C,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAA,iBAAS,EAAC,yCAAyC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,4EAA4E;IAE5E,MAAM,YAAY,GAAG,IAAI,CACvB,gBAAgB,EAChB,oGAAoG,EACpG;QACE,QAAQ,EAAE,OAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,qDAAqD,CAAC;QAClE,IAAI,EAAE,OAAC;aACJ,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,CAAC,yCAAyC,CAAC;KACvD,EACD,CAAC,IAA0C,EAAE,EAAE;QAC7C,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAA,iBAAS,EAAC,mBAAmB,QAAQ,WAAW,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAExE,MAAM,YAAY,GAAgB,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YACvD,CAAC,CAAC,YAAY,CAAC,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACjD,CAAC,CAAC,IAAI,GAAG,EAAU,CAAC;QAEtB,MAAM,OAAO,GAA0C,EAAE,CAAC;QAC1D,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/D,CAAC;QAED,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aAClE;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,4EAA4E;IAE5E,MAAM,YAAY,GAAG,IAAI,CACvB,gBAAgB,EAChB,gIAAgI,EAChI;QACE,QAAQ,EAAE,OAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,qDAAqD,CAAC;QAClE,MAAM,EAAE,OAAC;aACN,MAAM,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC;aAC9B,QAAQ,CAAC,wBAAwB,CAAC;KACtC,EACD,CAAC,IAA0D,EAAE,EAAE;QAC7D,mFAAmF;QACnF,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,iBAAiB,CAC7C,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,WAAW,SAAS,kJAAkJ;qBAC7K;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAA,iBAAS,EACP,mBAAmB,QAAQ,WAAW,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACjE,IAAI,CACL,EAAE,CACJ,CAAC;QAEF,MAAM,QAAQ,GAAG,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YACtC,CAAC,CAAC,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC;YACnC,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEtD,iCAAiC;QACjC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,YAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QAED,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAE5C,+CAA+C;QAC/C,MAAM,WAAW,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,uBAAuB,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;QAEvD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,WAAW,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,cAC9C,IAAI,CAAC,QACP,EAAE;iBACH;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,4EAA4E;IAE5E,MAAM,QAAQ,GAAG,IAAI,CACnB,wBAAwB,EACxB,wLAAwL,EACxL,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAA,iBAAS,EAAC,oCAAoC,gBAAgB,EAAE,CAAC,CAAC;QAElE,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;QAE5D,IAAA,iBAAS,EACP,oCAAoC,MAAM,CAAC,QAAQ,CAAC,MAAM,qBAAqB,CAChF,CAAC;QAEF,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBACtC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,4EAA4E;IAE5E,MAAM,aAAa,GAAG,IAAI,CACxB,iBAAiB,EACjB,wIAAwI,EACxI;QACE,QAAQ,EAAE,OAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC;KAC3D,EACD,CAAC,IAA0B,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,iCAAiC,IAAI,CAAC,QAAQ,IAAI;qBACzD;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEtE,IAAA,iBAAS,EACP,8BAA8B,MAAM,CAAC,MAAM,gBAAgB,IAAI,CAAC,QAAQ,GAAG,CAC5E,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;SACrD,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,4EAA4E;IAE5E,MAAM,YAAY,GAAG,IAAI,CACvB,eAAe,EACf,kJAAkJ,EAClJ;QACE,OAAO,EAAE,OAAC;aACP,MAAM,EAAE;aACR,QAAQ,CACP,sEAAsE,CACvE;KACJ,EACD,CAAC,IAAyB,EAAE,EAAE;QAC5B,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/C,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,6DAA6D;qBACpE;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,wCAAwC;QACxC,MAAM,SAAS,GAAiB,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,iBAAiB,IAAI,CAAC,OAAO,2DAA2D;qBAC/F;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CACxB,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,IAAI,CAAC,OAAO,CACb,CAAC;QACF,MAAM,OAAO,GAAG,sBAAsB,IAAI,CAAC,OAAO,MAAM,CAAC;QAEzD,IAAI,CAAC;YACH,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAA,4BAAY,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;gBAC9D,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YACH,IAAA,4BAAY,EAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;gBACrD,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YACH,IAAI,CAAC;gBACH,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;YAED,IAAA,iBAAS,EACP,4BAA4B,IAAI,CAAC,OAAO,SAAS,KAAK,CAAC,WAAW,EAAE,CACrE,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,qCAAqC,IAAI,CAAC,OAAO,GAAG;qBAC3D;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAA,iBAAS,EAAC,wBAAwB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACjD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE;qBAC/C;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,4EAA4E;IAE5E,OAAO,kBAAkB,CAAC;QACxB,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC;KAC3E,CAAC,CAAC;AACL,CAAC;AAED,6EAA6E;AAChE,QAAA,iBAAiB,GAAG;IAC/B,GAAG,WAAW,iBAAiB;IAC/B,GAAG,WAAW,iBAAiB;IAC/B,GAAG,WAAW,yBAAyB;IACvC,GAAG,WAAW,kBAAkB;IAChC,GAAG,WAAW,gBAAgB;CAC/B,CAAC","sourcesContent":["/**\n * Unified in-process MCP server for the PostHog wizard.\n *\n * Provides tools that run locally (secret values never leave the machine):\n * - check_env_keys: Check which env var keys exist in a .env file\n * - set_env_values: Create/update env vars in a .env file\n * - detect_package_manager: Detect the project's package manager(s)\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { execFileSync } from 'child_process';\nimport { z } from 'zod';\nimport { logToFile } from '../utils/debug';\nimport type { PackageManagerDetector } from './package-manager-detection';\n\n// ---------------------------------------------------------------------------\n// SDK dynamic import (ESM module loaded once, cached)\n// ---------------------------------------------------------------------------\n\nlet _sdkModule: any = null;\nasync function getSDKModule(): Promise<any> {\n if (!_sdkModule) {\n _sdkModule = await import('@anthropic-ai/claude-agent-sdk');\n }\n return _sdkModule;\n}\n\n// ---------------------------------------------------------------------------\n// Options for creating the wizard tools server\n// ---------------------------------------------------------------------------\n\nexport interface WizardToolsOptions {\n /** Root directory of the project being analyzed */\n workingDirectory: string;\n\n /** Framework-specific package manager detector */\n detectPackageManager: PackageManagerDetector;\n\n /** Base URL for the skills server (e.g. http://localhost:8765 or GitHub releases URL) */\n skillsBaseUrl: string;\n}\n\n// ---------------------------------------------------------------------------\n// Env file helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve filePath relative to workingDirectory, rejecting path traversal.\n */\nexport function resolveEnvPath(\n workingDirectory: string,\n filePath: string,\n): string {\n const resolved = path.resolve(workingDirectory, filePath);\n if (\n !resolved.startsWith(workingDirectory + path.sep) &&\n resolved !== workingDirectory\n ) {\n throw new Error(\n `Path traversal rejected: \"${filePath}\" resolves outside working directory`,\n );\n }\n return resolved;\n}\n\n/**\n * Ensure the given env file basename is covered by .gitignore in the working directory.\n * Creates .gitignore if it doesn't exist; appends the entry if missing.\n */\nexport function ensureGitignoreCoverage(\n workingDirectory: string,\n envFileName: string,\n): void {\n const gitignorePath = path.join(workingDirectory, '.gitignore');\n\n if (fs.existsSync(gitignorePath)) {\n const content = fs.readFileSync(gitignorePath, 'utf8');\n // Check if the file (or a glob covering it) is already listed\n if (content.split('\\n').some((line) => line.trim() === envFileName)) {\n return;\n }\n const newContent = content.endsWith('\\n')\n ? `${content}${envFileName}\\n`\n : `${content}\\n${envFileName}\\n`;\n fs.writeFileSync(gitignorePath, newContent, 'utf8');\n } else {\n fs.writeFileSync(gitignorePath, `${envFileName}\\n`, 'utf8');\n }\n}\n\n/**\n * Parse a .env file's content and return the set of defined key names.\n */\nexport function parseEnvKeys(content: string): Set<string> {\n const keys = new Set<string>();\n for (const line of content.split('\\n')) {\n const match = line.match(/^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (match) {\n keys.add(match[1]);\n }\n }\n return keys;\n}\n\n/**\n * Merge key-value pairs into existing .env content.\n * Updates existing keys in-place, appends new keys at the end.\n */\nexport function mergeEnvValues(\n content: string,\n values: Record<string, string>,\n): string {\n let result = content;\n const updatedKeys = new Set<string>();\n\n for (const [key, value] of Object.entries(values)) {\n const regex = new RegExp(`^(\\\\s*${key}\\\\s*=).*$`, 'm');\n if (regex.test(result)) {\n result = result.replace(regex, `$1${value}`);\n updatedKeys.add(key);\n }\n }\n\n const newKeys = Object.entries(values).filter(\n ([key]) => !updatedKeys.has(key),\n );\n if (newKeys.length > 0) {\n if (result.length > 0 && !result.endsWith('\\n')) {\n result += '\\n';\n }\n for (const [key, value] of newKeys) {\n result += `${key}=${value}\\n`;\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Server factory\n// ---------------------------------------------------------------------------\n\nconst SERVER_NAME = 'wizard-tools';\n\n/**\n * Create the unified in-process MCP server with all wizard tools.\n * Must be called asynchronously because the SDK is an ESM module loaded via dynamic import.\n */\nexport async function createWizardToolsServer(options: WizardToolsOptions) {\n const { workingDirectory, detectPackageManager, skillsBaseUrl } = options;\n const sdk = await getSDKModule();\n const { tool, createSdkMcpServer } = sdk;\n\n // Pre-fetch skill menu so category names are available in the tool schema\n type SkillEntry = { id: string; name: string; downloadUrl: string };\n let cachedSkillMenu: Record<string, SkillEntry[]> = {};\n let categoryNames: [string, ...string[]] = ['integration'];\n\n try {\n const menuUrl = `${skillsBaseUrl}/skill-menu.json`;\n logToFile(`wizard-tools: pre-fetching skill menu from ${menuUrl}`);\n const resp = await fetch(menuUrl);\n if (resp.ok) {\n const data = (await resp.json()) as {\n categories: Record<string, SkillEntry[]>;\n };\n cachedSkillMenu = data.categories;\n const keys = Object.keys(cachedSkillMenu);\n if (keys.length > 0) {\n categoryNames = keys as [string, ...string[]];\n }\n logToFile(`wizard-tools: loaded skill menu (${keys.length} categories)`);\n } else {\n logToFile(`wizard-tools: skill menu fetch failed: ${resp.status}`);\n }\n } catch (err: any) {\n logToFile(`wizard-tools: skill menu fetch error: ${err.message}`);\n }\n\n // -- check_env_keys -------------------------------------------------------\n\n const checkEnvKeys = tool(\n 'check_env_keys',\n 'Check which environment variable keys are present or missing in a .env file. Never reveals values.',\n {\n filePath: z\n .string()\n .describe('Path to the .env file, relative to the project root'),\n keys: z\n .array(z.string())\n .describe('Environment variable key names to check'),\n },\n (args: { filePath: string; keys: string[] }) => {\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n logToFile(`check_env_keys: ${resolved}, keys: ${args.keys.join(', ')}`);\n\n const existingKeys: Set<string> = fs.existsSync(resolved)\n ? parseEnvKeys(fs.readFileSync(resolved, 'utf8'))\n : new Set<string>();\n\n const results: Record<string, 'present' | 'missing'> = {};\n for (const key of args.keys) {\n results[key] = existingKeys.has(key) ? 'present' : 'missing';\n }\n\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(results, null, 2) },\n ],\n };\n },\n );\n\n // -- set_env_values -------------------------------------------------------\n\n const setEnvValues = tool(\n 'set_env_values',\n 'Create or update environment variable keys in a .env file. Creates the file if it does not exist. Ensures .gitignore coverage.',\n {\n filePath: z\n .string()\n .describe('Path to the .env file, relative to the project root'),\n values: z\n .record(z.string(), z.string())\n .describe('Key-value pairs to set'),\n },\n (args: { filePath: string; values: Record<string, string> }) => {\n // Block the wrong key name — the correct key is NEXT_PUBLIC_POSTHOG_KEY or similar\n const forbidden = Object.keys(args.values).find(\n (k) => k.toUpperCase() === 'POSTHOG_API_KEY',\n );\n if (forbidden) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Error: \"${forbidden}\" is not a valid PostHog env var name. Use the project-specific key name from your framework's integration guide (e.g. NEXT_PUBLIC_POSTHOG_KEY).`,\n },\n ],\n isError: true,\n };\n }\n\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n logToFile(\n `set_env_values: ${resolved}, keys: ${Object.keys(args.values).join(\n ', ',\n )}`,\n );\n\n const existing = fs.existsSync(resolved)\n ? fs.readFileSync(resolved, 'utf8')\n : '';\n const content = mergeEnvValues(existing, args.values);\n\n // Ensure parent directory exists\n const dir = path.dirname(resolved);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n fs.writeFileSync(resolved, content, 'utf8');\n\n // Ensure .gitignore coverage for this env file\n const envFileName = path.basename(resolved);\n ensureGitignoreCoverage(workingDirectory, envFileName);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Updated ${Object.keys(args.values).length} key(s) in ${\n args.filePath\n }`,\n },\n ],\n };\n },\n );\n\n // -- detect_package_manager -----------------------------------------------\n\n const detectPM = tool(\n 'detect_package_manager',\n 'Detect which package manager(s) the project uses. Returns the name, install command, and run command for each detected package manager. Call this before running any install commands.',\n {},\n async () => {\n logToFile(`detect_package_manager: scanning ${workingDirectory}`);\n\n const result = await detectPackageManager(workingDirectory);\n\n logToFile(\n `detect_package_manager: detected ${result.detected.length} package manager(s)`,\n );\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n },\n );\n\n // -- load_skill_menu ------------------------------------------------------\n\n const loadSkillMenu = tool(\n 'load_skill_menu',\n 'Load available PostHog skills for a category. Returns skill IDs and names. Call this first, then use install_skill with the chosen ID.',\n {\n category: z.enum(categoryNames).describe('Skill category'),\n },\n (args: { category: string }) => {\n const skills = cachedSkillMenu[args.category];\n if (!skills || skills.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `No skills found for category \"${args.category}\".`,\n },\n ],\n isError: true,\n };\n }\n\n const menuText = skills.map((s) => `- ${s.id}: ${s.name}`).join('\\n');\n\n logToFile(\n `load_skill_menu: returning ${skills.length} skills for \"${args.category}\"`,\n );\n\n return {\n content: [{ type: 'text' as const, text: menuText }],\n };\n },\n );\n\n // -- install_skill --------------------------------------------------------\n\n const installSkill = tool(\n 'install_skill',\n 'Download and install a PostHog skill by ID. Call load_skill_menu first to see available skills. Extracts the skill to .claude/skills/<skillId>/.',\n {\n skillId: z\n .string()\n .describe(\n 'Skill ID from the skill menu (e.g., \"integration-nextjs-app-router\")',\n ),\n },\n (args: { skillId: string }) => {\n if (!/^[a-z0-9][a-z0-9-]*$/.test(args.skillId)) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Error: skillId must be lowercase alphanumeric with hyphens.',\n },\n ],\n isError: true,\n };\n }\n\n // Look up download URL from cached menu\n const allSkills: SkillEntry[] = Object.values(cachedSkillMenu).flat();\n const skill = allSkills.find((s) => s.id === args.skillId);\n if (!skill) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Error: skill \"${args.skillId}\" not found. Use load_skill_menu to see available skills.`,\n },\n ],\n isError: true,\n };\n }\n\n const skillDir = path.join(\n workingDirectory,\n '.claude',\n 'skills',\n args.skillId,\n );\n const tmpFile = `/tmp/posthog-skill-${args.skillId}.zip`;\n\n try {\n fs.mkdirSync(skillDir, { recursive: true });\n execFileSync('curl', ['-sL', skill.downloadUrl, '-o', tmpFile], {\n timeout: 30000,\n });\n execFileSync('unzip', ['-o', tmpFile, '-d', skillDir], {\n timeout: 30000,\n });\n try {\n fs.unlinkSync(tmpFile);\n } catch {\n /* ignore cleanup errors */\n }\n\n logToFile(\n `install_skill: installed ${args.skillId} from ${skill.downloadUrl}`,\n );\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Skill installed to .claude/skills/${args.skillId}/`,\n },\n ],\n };\n } catch (err: any) {\n logToFile(`install_skill error: ${err.message}`);\n return {\n content: [\n {\n type: 'text' as const,\n text: `Error installing skill: ${err.message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n\n // -- Assemble server ------------------------------------------------------\n\n return createSdkMcpServer({\n name: SERVER_NAME,\n version: '1.0.0',\n tools: [checkEnvKeys, setEnvValues, detectPM, loadSkillMenu, installSkill],\n });\n}\n\n/** Tool names exposed by the wizard-tools server, for use in allowedTools */\nexport const WIZARD_TOOL_NAMES = [\n `${SERVER_NAME}:check_env_keys`,\n `${SERVER_NAME}:set_env_values`,\n `${SERVER_NAME}:detect_package_manager`,\n `${SERVER_NAME}:load_skill_menu`,\n `${SERVER_NAME}:install_skill`,\n];\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YARA hook wiring for the Claude Agent SDK.
|
|
3
|
+
*
|
|
4
|
+
* Creates PreToolUse and PostToolUse hook callback arrays that
|
|
5
|
+
* integrate the YARA scanner into the wizard's agent loop. These
|
|
6
|
+
* hooks are registered in the SDK's query() options alongside the
|
|
7
|
+
* existing Stop hook.
|
|
8
|
+
*
|
|
9
|
+
* PreToolUse hooks block dangerous commands before execution.
|
|
10
|
+
* PostToolUse hooks detect violations in written code and prompt
|
|
11
|
+
* injection in read content, and scan context-mill skill downloads.
|
|
12
|
+
*/
|
|
13
|
+
type HookInput = Record<string, unknown>;
|
|
14
|
+
type HookOutput = Record<string, unknown>;
|
|
15
|
+
type HookCallback = (input: HookInput, toolUseID: string | undefined, options: {
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
}) => Promise<HookOutput>;
|
|
18
|
+
export interface HookCallbackMatcher {
|
|
19
|
+
matcher?: string;
|
|
20
|
+
hooks: HookCallback[];
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Reset counters (for testing) */
|
|
24
|
+
export declare function resetScanReport(): void;
|
|
25
|
+
/** Format the scan report summary. Returns null if no scans occurred */
|
|
26
|
+
export declare function formatScanReport(): string | null;
|
|
27
|
+
/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */
|
|
28
|
+
export declare function writeScanReport(): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Create PreToolUse hook matchers for YARA scanning.
|
|
31
|
+
* Scans Bash commands before execution for exfiltration,
|
|
32
|
+
* destructive operations, and supply chain violations.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createPreToolUseYaraHooks(): HookCallbackMatcher[];
|
|
35
|
+
/**
|
|
36
|
+
* Create PostToolUse hook matchers for YARA scanning.
|
|
37
|
+
*
|
|
38
|
+
* Three matchers:
|
|
39
|
+
* 1. Write/Edit — scan written content for PII, secrets, config violations
|
|
40
|
+
* 2. Read/Grep — scan read content for prompt injection
|
|
41
|
+
* 3. Bash (skill install) — scan downloaded skill files for poisoned content
|
|
42
|
+
*/
|
|
43
|
+
export declare function createPostToolUseYaraHooks(): HookCallbackMatcher[];
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* YARA hook wiring for the Claude Agent SDK.
|
|
4
|
+
*
|
|
5
|
+
* Creates PreToolUse and PostToolUse hook callback arrays that
|
|
6
|
+
* integrate the YARA scanner into the wizard's agent loop. These
|
|
7
|
+
* hooks are registered in the SDK's query() options alongside the
|
|
8
|
+
* existing Stop hook.
|
|
9
|
+
*
|
|
10
|
+
* PreToolUse hooks block dangerous commands before execution.
|
|
11
|
+
* PostToolUse hooks detect violations in written code and prompt
|
|
12
|
+
* injection in read content, and scan context-mill skill downloads.
|
|
13
|
+
*/
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.resetScanReport = resetScanReport;
|
|
19
|
+
exports.formatScanReport = formatScanReport;
|
|
20
|
+
exports.writeScanReport = writeScanReport;
|
|
21
|
+
exports.createPreToolUseYaraHooks = createPreToolUseYaraHooks;
|
|
22
|
+
exports.createPostToolUseYaraHooks = createPostToolUseYaraHooks;
|
|
23
|
+
const fs_1 = __importDefault(require("fs"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
26
|
+
const yara_scanner_1 = require("./yara-scanner");
|
|
27
|
+
const debug_1 = require("../utils/debug");
|
|
28
|
+
const analytics_1 = require("../utils/analytics");
|
|
29
|
+
const skill_install_1 = require("./skill-install");
|
|
30
|
+
let scanCount = 0;
|
|
31
|
+
const scanViolations = [];
|
|
32
|
+
function recordScan() {
|
|
33
|
+
scanCount++;
|
|
34
|
+
}
|
|
35
|
+
function recordViolation(entry) {
|
|
36
|
+
scanViolations.push(entry);
|
|
37
|
+
}
|
|
38
|
+
/** Reset counters (for testing) */
|
|
39
|
+
function resetScanReport() {
|
|
40
|
+
scanCount = 0;
|
|
41
|
+
scanViolations.length = 0;
|
|
42
|
+
}
|
|
43
|
+
/** Format the scan report summary. Returns null if no scans occurred */
|
|
44
|
+
function formatScanReport() {
|
|
45
|
+
if (scanCount === 0)
|
|
46
|
+
return null;
|
|
47
|
+
const lines = ['', '— YARA Scanner Summary —'];
|
|
48
|
+
const violationCount = scanViolations.length;
|
|
49
|
+
const cleanCount = scanCount - violationCount;
|
|
50
|
+
lines.push(`✓ ${scanCount} tool calls scanned, ${violationCount} violation${violationCount !== 1 ? 's' : ''} detected`);
|
|
51
|
+
if (violationCount > 0) {
|
|
52
|
+
lines.push('');
|
|
53
|
+
for (const v of scanViolations) {
|
|
54
|
+
const tag = v.action.toUpperCase();
|
|
55
|
+
lines.push(` [${tag}] ${v.rule} (${v.severity.toUpperCase()}) — ${v.phase}:${v.tool}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (cleanCount > 0) {
|
|
59
|
+
lines.push('');
|
|
60
|
+
lines.push(`No violations: ✓ ${cleanCount} clean scan${cleanCount !== 1 ? 's' : ''}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
const YARA_REPORT_PATH = '/tmp/posthog-wizard-yara-report.json';
|
|
66
|
+
/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */
|
|
67
|
+
function writeScanReport() {
|
|
68
|
+
if (scanCount === 0)
|
|
69
|
+
return null;
|
|
70
|
+
const report = {
|
|
71
|
+
summary: {
|
|
72
|
+
totalScans: scanCount,
|
|
73
|
+
violations: scanViolations.length,
|
|
74
|
+
clean: scanCount - scanViolations.length,
|
|
75
|
+
},
|
|
76
|
+
violations: scanViolations,
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
fs_1.default.writeFileSync(YARA_REPORT_PATH, JSON.stringify(report, null, 2));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
(0, debug_1.logToFile)('[YARA] Failed to write scan report:', err);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return YARA_REPORT_PATH;
|
|
86
|
+
}
|
|
87
|
+
// ─── Hook Timeouts (ms) ─────────────────────────────────────────
|
|
88
|
+
/** Timeout for synchronous scan hooks (PreToolUse, PostToolUse Write/Edit/Read) */
|
|
89
|
+
const HOOK_TIMEOUT_MS = 60;
|
|
90
|
+
/** Timeout for skill install hook (involves filesystem I/O) */
|
|
91
|
+
const SKILL_SCAN_HOOK_TIMEOUT_MS = 120;
|
|
92
|
+
// ─── Logging ─────────────────────────────────────────────────────
|
|
93
|
+
function logYaraMatch(phase, tool, match, action) {
|
|
94
|
+
(0, debug_1.logToFile)(`[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule "${match.rule.name}" ` +
|
|
95
|
+
`(severity: ${match.rule.severity}, category: ${match.rule.category})\n` +
|
|
96
|
+
` Description: ${match.rule.description}\n` +
|
|
97
|
+
` Matched text: "${match.matchedText.substring(0, 200)}"`);
|
|
98
|
+
analytics_1.analytics.wizardCapture('yara rule matched', {
|
|
99
|
+
rule: match.rule.name,
|
|
100
|
+
severity: match.rule.severity,
|
|
101
|
+
category: match.rule.category,
|
|
102
|
+
action,
|
|
103
|
+
phase,
|
|
104
|
+
tool,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// ─── Severity helpers ────────────────────────────────────────────
|
|
108
|
+
const SEVERITY_RANK = {
|
|
109
|
+
critical: 4,
|
|
110
|
+
high: 3,
|
|
111
|
+
medium: 2,
|
|
112
|
+
low: 1,
|
|
113
|
+
};
|
|
114
|
+
/** Return the highest-severity match from a list of matches. */
|
|
115
|
+
function highestSeverityMatch(matches) {
|
|
116
|
+
return matches.reduce((worst, m) => (SEVERITY_RANK[m.rule.severity] ?? 0) >
|
|
117
|
+
(SEVERITY_RANK[worst.rule.severity] ?? 0)
|
|
118
|
+
? m
|
|
119
|
+
: worst);
|
|
120
|
+
}
|
|
121
|
+
// ─── PreToolUse Hooks ────────────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* Create PreToolUse hook matchers for YARA scanning.
|
|
124
|
+
* Scans Bash commands before execution for exfiltration,
|
|
125
|
+
* destructive operations, and supply chain violations.
|
|
126
|
+
*/
|
|
127
|
+
function createPreToolUseYaraHooks() {
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
hooks: [
|
|
131
|
+
(input) => {
|
|
132
|
+
try {
|
|
133
|
+
const toolName = input.tool_name;
|
|
134
|
+
if (toolName !== 'Bash')
|
|
135
|
+
return Promise.resolve({});
|
|
136
|
+
const toolInput = input.tool_input;
|
|
137
|
+
const command = typeof toolInput?.command === 'string' ? toolInput.command : '';
|
|
138
|
+
if (!command)
|
|
139
|
+
return Promise.resolve({});
|
|
140
|
+
recordScan();
|
|
141
|
+
const result = (0, yara_scanner_1.scan)(command, 'PreToolUse', 'Bash');
|
|
142
|
+
if (!result.matched)
|
|
143
|
+
return Promise.resolve({});
|
|
144
|
+
const match = highestSeverityMatch(result.matches);
|
|
145
|
+
logYaraMatch('PreToolUse', 'Bash', match, 'blocked');
|
|
146
|
+
recordViolation({
|
|
147
|
+
rule: match.rule.name,
|
|
148
|
+
severity: match.rule.severity,
|
|
149
|
+
action: 'blocked',
|
|
150
|
+
phase: 'PreToolUse',
|
|
151
|
+
tool: 'Bash',
|
|
152
|
+
});
|
|
153
|
+
return Promise.resolve({
|
|
154
|
+
decision: 'block',
|
|
155
|
+
reason: `[YARA] ${match.rule.name}: ${match.rule.description}. Command blocked for security.`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
(0, debug_1.logToFile)('[YARA] PreToolUse hook error:', error);
|
|
160
|
+
// Fail closed: block the command if scanning fails
|
|
161
|
+
return Promise.resolve({
|
|
162
|
+
decision: 'block',
|
|
163
|
+
reason: '[YARA] Scanner error — command blocked as a precaution.',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
// ─── PostToolUse Hooks ───────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Create PostToolUse hook matchers for YARA scanning.
|
|
175
|
+
*
|
|
176
|
+
* Three matchers:
|
|
177
|
+
* 1. Write/Edit — scan written content for PII, secrets, config violations
|
|
178
|
+
* 2. Read/Grep — scan read content for prompt injection
|
|
179
|
+
* 3. Bash (skill install) — scan downloaded skill files for poisoned content
|
|
180
|
+
*/
|
|
181
|
+
function createPostToolUseYaraHooks() {
|
|
182
|
+
return [
|
|
183
|
+
// ── Write/Edit content scanning ──
|
|
184
|
+
{
|
|
185
|
+
hooks: [
|
|
186
|
+
(input) => {
|
|
187
|
+
try {
|
|
188
|
+
const toolName = input.tool_name;
|
|
189
|
+
if (toolName !== 'Write' && toolName !== 'Edit')
|
|
190
|
+
return Promise.resolve({});
|
|
191
|
+
const toolInput = input.tool_input;
|
|
192
|
+
// For Write, scan the content being written
|
|
193
|
+
// For Edit, scan the new_str (replacement text)
|
|
194
|
+
const content = toolName === 'Write'
|
|
195
|
+
? toolInput?.content ?? ''
|
|
196
|
+
: toolInput?.new_str ?? '';
|
|
197
|
+
if (!content)
|
|
198
|
+
return Promise.resolve({});
|
|
199
|
+
recordScan();
|
|
200
|
+
const tool = toolName;
|
|
201
|
+
const result = (0, yara_scanner_1.scan)(content, 'PostToolUse', tool);
|
|
202
|
+
if (!result.matched)
|
|
203
|
+
return Promise.resolve({});
|
|
204
|
+
const match = highestSeverityMatch(result.matches);
|
|
205
|
+
logYaraMatch('PostToolUse', tool, match, 'reverted');
|
|
206
|
+
recordViolation({
|
|
207
|
+
rule: match.rule.name,
|
|
208
|
+
severity: match.rule.severity,
|
|
209
|
+
action: 'reverted',
|
|
210
|
+
phase: 'PostToolUse',
|
|
211
|
+
tool,
|
|
212
|
+
});
|
|
213
|
+
return Promise.resolve({
|
|
214
|
+
hookSpecificOutput: {
|
|
215
|
+
hookEventName: 'PostToolUse',
|
|
216
|
+
additionalContext: `[YARA VIOLATION] ${match.rule.name}: ${match.rule.description}. ` +
|
|
217
|
+
`You MUST revert this change immediately. The content you just wrote violates security policy.`,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
(0, debug_1.logToFile)('[YARA] PostToolUse Write/Edit hook error:', error);
|
|
223
|
+
// Fail closed: instruct the agent to revert if scanning fails
|
|
224
|
+
return Promise.resolve({
|
|
225
|
+
hookSpecificOutput: {
|
|
226
|
+
hookEventName: 'PostToolUse',
|
|
227
|
+
additionalContext: '[YARA] Scanner error — you MUST revert this change as a precaution.',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
234
|
+
},
|
|
235
|
+
// ── Read/Grep prompt injection scanning ──
|
|
236
|
+
{
|
|
237
|
+
hooks: [
|
|
238
|
+
(input) => {
|
|
239
|
+
try {
|
|
240
|
+
const toolName = input.tool_name;
|
|
241
|
+
if (toolName !== 'Read' && toolName !== 'Grep')
|
|
242
|
+
return Promise.resolve({});
|
|
243
|
+
const toolResponse = input.tool_response;
|
|
244
|
+
const content = typeof toolResponse === 'string'
|
|
245
|
+
? toolResponse
|
|
246
|
+
: JSON.stringify(toolResponse ?? '');
|
|
247
|
+
if (!content)
|
|
248
|
+
return Promise.resolve({});
|
|
249
|
+
recordScan();
|
|
250
|
+
const tool = toolName;
|
|
251
|
+
const result = (0, yara_scanner_1.scan)(content, 'PostToolUse', tool);
|
|
252
|
+
if (!result.matched)
|
|
253
|
+
return Promise.resolve({});
|
|
254
|
+
const match = highestSeverityMatch(result.matches);
|
|
255
|
+
if (match.rule.severity === 'critical') {
|
|
256
|
+
logYaraMatch('PostToolUse', tool, match, 'aborted');
|
|
257
|
+
recordViolation({
|
|
258
|
+
rule: match.rule.name,
|
|
259
|
+
severity: match.rule.severity,
|
|
260
|
+
action: 'aborted',
|
|
261
|
+
phase: 'PostToolUse',
|
|
262
|
+
tool,
|
|
263
|
+
});
|
|
264
|
+
// Prompt injection: abort the session — context is poisoned
|
|
265
|
+
return Promise.resolve({
|
|
266
|
+
stopReason: `[YARA CRITICAL] ${match.rule.name}: Prompt injection detected in file content. ` +
|
|
267
|
+
`Agent context is potentially poisoned. Session terminated for safety.`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
logYaraMatch('PostToolUse', tool, match, 'warned');
|
|
271
|
+
recordViolation({
|
|
272
|
+
rule: match.rule.name,
|
|
273
|
+
severity: match.rule.severity,
|
|
274
|
+
action: 'warned',
|
|
275
|
+
phase: 'PostToolUse',
|
|
276
|
+
tool,
|
|
277
|
+
});
|
|
278
|
+
return Promise.resolve({
|
|
279
|
+
hookSpecificOutput: {
|
|
280
|
+
hookEventName: 'PostToolUse',
|
|
281
|
+
additionalContext: `[YARA WARNING] ${match.rule.name}: ${match.rule.description}`,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
(0, debug_1.logToFile)('[YARA] PostToolUse Read/Grep hook error:', error);
|
|
287
|
+
// Fail closed: terminate session if scanning fails on read content
|
|
288
|
+
return Promise.resolve({
|
|
289
|
+
stopReason: '[YARA] Scanner error while scanning read content — session terminated as a precaution.',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
295
|
+
},
|
|
296
|
+
// ── Context-mill skill install scanning ──
|
|
297
|
+
{
|
|
298
|
+
hooks: [
|
|
299
|
+
async (input) => {
|
|
300
|
+
try {
|
|
301
|
+
const toolName = input.tool_name;
|
|
302
|
+
if (toolName !== 'Bash')
|
|
303
|
+
return {};
|
|
304
|
+
const toolInput = input.tool_input;
|
|
305
|
+
const command = typeof toolInput?.command === 'string' ? toolInput.command : '';
|
|
306
|
+
// Only scan after skill install commands
|
|
307
|
+
if (!(0, skill_install_1.isSkillInstallCommand)(command))
|
|
308
|
+
return {};
|
|
309
|
+
// Extract skill directory from command
|
|
310
|
+
const dirMatch = command.match(/mkdir -p (.claude\/skills\/[^\s&]+)/);
|
|
311
|
+
if (!dirMatch)
|
|
312
|
+
return {};
|
|
313
|
+
const skillDir = dirMatch[1];
|
|
314
|
+
const cwd = input.cwd ?? process.cwd();
|
|
315
|
+
recordScan();
|
|
316
|
+
const result = await scanSkillFiles(cwd, skillDir);
|
|
317
|
+
if (!result.matched)
|
|
318
|
+
return {};
|
|
319
|
+
const match = highestSeverityMatch(result.matches);
|
|
320
|
+
logYaraMatch('PostToolUse', 'Bash (skill install)', match, 'aborted');
|
|
321
|
+
recordViolation({
|
|
322
|
+
rule: match.rule.name,
|
|
323
|
+
severity: match.rule.severity,
|
|
324
|
+
action: 'aborted',
|
|
325
|
+
phase: 'PostToolUse',
|
|
326
|
+
tool: 'Bash (skill)',
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
stopReason: `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${match.rule.name}. ` +
|
|
330
|
+
`The downloaded skill contains potential prompt injection. Session terminated for safety.`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
(0, debug_1.logToFile)('[YARA] PostToolUse skill install hook error:', error);
|
|
335
|
+
// Fail closed: terminate if skill scanning fails
|
|
336
|
+
return {
|
|
337
|
+
stopReason: '[YARA] Scanner error while scanning skill files — session terminated as a precaution.',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
timeout: SKILL_SCAN_HOOK_TIMEOUT_MS,
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
// ─── Skill File Scanner ──────────────────────────────────────────
|
|
347
|
+
/**
|
|
348
|
+
* Read and scan all text files in a skill directory for prompt injection.
|
|
349
|
+
*/
|
|
350
|
+
async function scanSkillFiles(cwd, skillDir) {
|
|
351
|
+
const absoluteDir = path_1.default.resolve(cwd, skillDir);
|
|
352
|
+
if (!fs_1.default.existsSync(absoluteDir)) {
|
|
353
|
+
(0, debug_1.logToFile)(`[YARA] Skill directory does not exist: ${absoluteDir}`);
|
|
354
|
+
return { matched: false };
|
|
355
|
+
}
|
|
356
|
+
const files = await (0, fast_glob_1.default)('**/*.{md,txt,yaml,yml,json,js,ts,py,rb,sh}', {
|
|
357
|
+
cwd: absoluteDir,
|
|
358
|
+
absolute: true,
|
|
359
|
+
});
|
|
360
|
+
const fileContents = [];
|
|
361
|
+
for (const filePath of files) {
|
|
362
|
+
try {
|
|
363
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
364
|
+
fileContents.push({ path: filePath, content });
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
(0, debug_1.logToFile)(`[YARA] Could not read skill file ${filePath}:`, err);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (fileContents.length === 0) {
|
|
371
|
+
(0, debug_1.logToFile)(`[YARA] No text files found in skill directory: ${absoluteDir}`);
|
|
372
|
+
return { matched: false };
|
|
373
|
+
}
|
|
374
|
+
(0, debug_1.logToFile)(`[YARA] Scanning ${fileContents.length} files in skill directory: ${skillDir}`);
|
|
375
|
+
return (0, yara_scanner_1.scanSkillDirectory)(fileContents);
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=yara-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yara-hooks.js","sourceRoot":"","sources":["../../../src/lib/yara-hooks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * YARA hook wiring for the Claude Agent SDK.\n *\n * Creates PreToolUse and PostToolUse hook callback arrays that\n * integrate the YARA scanner into the wizard's agent loop. These\n * hooks are registered in the SDK's query() options alongside the\n * existing Stop hook.\n *\n * PreToolUse hooks block dangerous commands before execution.\n * PostToolUse hooks detect violations in written code and prompt\n * injection in read content, and scan context-mill skill downloads.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport fg from 'fast-glob';\nimport { scan, scanSkillDirectory } from './yara-scanner';\nimport type { YaraMatch, ScanResult } from './yara-scanner';\nimport { logToFile } from '../utils/debug';\nimport { analytics } from '../utils/analytics';\nimport { isSkillInstallCommand } from './skill-install';\n\n// ─── Types ───────────────────────────────────────────────────────\n// Using loose types to avoid tight coupling to 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\n// ─── Scan Report Accumulator ─────────────────────────────────────\n\ntype ScanAction = 'blocked' | 'reverted' | 'warned' | 'aborted';\n\ninterface ScanReportEntry {\n rule: string;\n severity: string;\n action: ScanAction;\n phase: string;\n tool: string;\n}\n\nlet scanCount = 0;\nconst scanViolations: ScanReportEntry[] = [];\n\nfunction recordScan(): void {\n scanCount++;\n}\n\nfunction recordViolation(entry: ScanReportEntry): void {\n scanViolations.push(entry);\n}\n\n/** Reset counters (for testing) */\nexport function resetScanReport(): void {\n scanCount = 0;\n scanViolations.length = 0;\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\nconst YARA_REPORT_PATH = '/tmp/posthog-wizard-yara-report.json';\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(YARA_REPORT_PATH, JSON.stringify(report, null, 2));\n } catch (err) {\n logToFile('[YARA] Failed to write scan report:', err);\n return null;\n }\n return YARA_REPORT_PATH;\n}\n\n// ─── Hook Timeouts (ms) ─────────────────────────────────────────\n\n/** Timeout for synchronous scan hooks (PreToolUse, PostToolUse Write/Edit/Read) */\nconst HOOK_TIMEOUT_MS = 60;\n/** Timeout for skill install hook (involves filesystem I/O) */\nconst SKILL_SCAN_HOOK_TIMEOUT_MS = 120;\n\n// ─── Logging ─────────────────────────────────────────────────────\n\nfunction logYaraMatch(\n phase: string,\n tool: string,\n match: YaraMatch,\n action: ScanAction,\n): void {\n logToFile(\n `[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule \"${\n match.rule.name\n }\" ` +\n `(severity: ${match.rule.severity}, category: ${match.rule.category})\\n` +\n ` Description: ${match.rule.description}\\n` +\n ` Matched text: \"${match.matchedText.substring(0, 200)}\"`,\n );\n analytics.wizardCapture('yara rule matched', {\n rule: match.rule.name,\n severity: match.rule.severity,\n category: match.rule.category,\n action,\n phase,\n tool,\n });\n}\n\n// ─── Severity helpers ────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<string, number> = {\n critical: 4,\n high: 3,\n medium: 2,\n low: 1,\n};\n\n/** Return the highest-severity match from a list of matches. */\nfunction highestSeverityMatch(matches: YaraMatch[]): YaraMatch {\n return matches.reduce((worst, m) =>\n (SEVERITY_RANK[m.rule.severity] ?? 0) >\n (SEVERITY_RANK[worst.rule.severity] ?? 0)\n ? m\n : worst,\n );\n}\n\n// ─── PreToolUse Hooks ────────────────────────────────────────────\n\n/**\n * Create PreToolUse hook matchers for YARA scanning.\n * Scans Bash commands before execution for exfiltration,\n * destructive operations, and supply chain violations.\n */\nexport function createPreToolUseYaraHooks(): HookCallbackMatcher[] {\n return [\n {\n hooks: [\n (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return Promise.resolve({});\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 Promise.resolve({});\n\n recordScan();\n const result = scan(command, 'PreToolUse', 'Bash');\n if (!result.matched) return Promise.resolve({});\n\n const match = highestSeverityMatch(result.matches);\n logYaraMatch('PreToolUse', 'Bash', match, 'blocked');\n recordViolation({\n rule: match.rule.name,\n severity: match.rule.severity,\n action: 'blocked',\n phase: 'PreToolUse',\n tool: 'Bash',\n });\n\n return Promise.resolve({\n decision: 'block',\n reason: `[YARA] ${match.rule.name}: ${match.rule.description}. Command blocked for security.`,\n });\n } catch (error) {\n logToFile('[YARA] PreToolUse hook error:', error);\n // Fail closed: block the command if scanning fails\n return Promise.resolve({\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}\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 for PII, secrets, config violations\n * 2. Read/Grep — scan read content for prompt injection\n * 3. Bash (skill install) — scan downloaded skill files for poisoned content\n */\nexport function createPostToolUseYaraHooks(): HookCallbackMatcher[] {\n return [\n // ── Write/Edit content scanning ──\n {\n hooks: [\n (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Write' && toolName !== 'Edit')\n return Promise.resolve({});\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_str (replacement text)\n const content =\n toolName === 'Write'\n ? (toolInput?.content as string) ?? ''\n : (toolInput?.new_str as string) ?? '';\n\n if (!content) return Promise.resolve({});\n\n recordScan();\n const tool = toolName;\n const result = scan(content, 'PostToolUse', tool);\n if (!result.matched) return Promise.resolve({});\n\n const match = highestSeverityMatch(result.matches);\n logYaraMatch('PostToolUse', tool, match, 'reverted');\n recordViolation({\n rule: match.rule.name,\n severity: match.rule.severity,\n action: 'reverted',\n phase: 'PostToolUse',\n tool,\n });\n\n return Promise.resolve({\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext:\n `[YARA VIOLATION] ${match.rule.name}: ${match.rule.description}. ` +\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: instruct the agent to revert if scanning fails\n return Promise.resolve({\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext:\n '[YARA] Scanner error — you MUST revert this change as a precaution.',\n },\n });\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Read/Grep prompt injection scanning ──\n {\n hooks: [\n (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Read' && toolName !== 'Grep')\n return Promise.resolve({});\n\n const toolResponse = input.tool_response;\n const content =\n typeof toolResponse === 'string'\n ? toolResponse\n : JSON.stringify(toolResponse ?? '');\n\n if (!content) return Promise.resolve({});\n\n recordScan();\n const tool = toolName;\n const result = scan(content, 'PostToolUse', tool);\n if (!result.matched) return Promise.resolve({});\n\n const match = highestSeverityMatch(result.matches);\n\n if (match.rule.severity === 'critical') {\n logYaraMatch('PostToolUse', tool, match, 'aborted');\n recordViolation({\n rule: match.rule.name,\n severity: match.rule.severity,\n action: 'aborted',\n phase: 'PostToolUse',\n tool,\n });\n // Prompt injection: abort the session — context is poisoned\n return Promise.resolve({\n stopReason:\n `[YARA CRITICAL] ${match.rule.name}: Prompt injection detected in file content. ` +\n `Agent context is potentially poisoned. Session terminated for safety.`,\n });\n }\n\n logYaraMatch('PostToolUse', tool, match, 'warned');\n recordViolation({\n rule: match.rule.name,\n severity: match.rule.severity,\n action: 'warned',\n phase: 'PostToolUse',\n tool,\n });\n return Promise.resolve({\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext: `[YARA WARNING] ${match.rule.name}: ${match.rule.description}`,\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 return Promise.resolve({\n stopReason:\n '[YARA] Scanner error while scanning read content — session terminated as a precaution.',\n });\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 result = await scanSkillFiles(cwd, skillDir);\n\n if (!result.matched) return {};\n\n const match = highestSeverityMatch(result.matches);\n logYaraMatch(\n 'PostToolUse',\n 'Bash (skill install)',\n match,\n 'aborted',\n );\n recordViolation({\n rule: match.rule.name,\n severity: match.rule.severity,\n action: 'aborted',\n phase: 'PostToolUse',\n tool: 'Bash (skill)',\n });\n\n return {\n stopReason:\n `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${match.rule.name}. ` +\n `The downloaded skill contains potential prompt injection. Session terminated for safety.`,\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse skill install hook error:', error);\n // Fail closed: terminate if skill scanning fails\n return {\n stopReason:\n '[YARA] Scanner error while scanning skill files — session terminated as a precaution.',\n };\n }\n },\n ],\n timeout: SKILL_SCAN_HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── Skill File Scanner ──────────────────────────────────────────\n\n/**\n * Read and scan all text files in a skill directory for prompt injection.\n */\nasync function scanSkillFiles(\n cwd: string,\n skillDir: string,\n): Promise<ScanResult> {\n const absoluteDir = path.resolve(cwd, skillDir);\n\n if (!fs.existsSync(absoluteDir)) {\n logToFile(`[YARA] Skill directory does not exist: ${absoluteDir}`);\n return { matched: false };\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 const fileContents: Array<{ path: string; content: string }> = [];\n for (const filePath of files) {\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n fileContents.push({ path: filePath, content });\n } catch (err) {\n logToFile(`[YARA] Could not read skill file ${filePath}:`, err);\n }\n }\n\n if (fileContents.length === 0) {\n logToFile(`[YARA] No text files found in skill directory: ${absoluteDir}`);\n return { matched: false };\n }\n\n logToFile(\n `[YARA] Scanning ${fileContents.length} files in skill directory: ${skillDir}`,\n );\n return scanSkillDirectory(fileContents);\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YARA content scanner for the PostHog wizard.
|
|
3
|
+
*
|
|
4
|
+
* This file is the single source of truth for all wizard YARA rules.
|
|
5
|
+
*
|
|
6
|
+
* Scans tool inputs (pre-execution) and outputs (post-execution) for
|
|
7
|
+
* security violations including PII leakage, hardcoded secrets,
|
|
8
|
+
* prompt injection, and secret exfiltration.
|
|
9
|
+
*
|
|
10
|
+
* We use YARA-style regex rules rather than the real YARA C library to
|
|
11
|
+
* avoid native binary dependencies in an npx-distributed npm package.
|
|
12
|
+
*
|
|
13
|
+
* This is Layer 2 (L2) in the wizard's defense-in-depth model,
|
|
14
|
+
* complementing the prompt-based commandments (L0) and the
|
|
15
|
+
* canUseTool() allowlist (L1).
|
|
16
|
+
*/
|
|
17
|
+
export type YaraSeverity = 'critical' | 'high' | 'medium' | 'low';
|
|
18
|
+
export type YaraCategory = 'posthog_pii' | 'posthog_hardcoded_key' | 'posthog_autocapture' | 'posthog_config' | 'prompt_injection' | 'exfiltration' | 'filesystem_safety' | 'supply_chain';
|
|
19
|
+
export type HookPhase = 'PreToolUse' | 'PostToolUse';
|
|
20
|
+
export type ToolTarget = 'Bash' | 'Write' | 'Edit' | 'Read' | 'Grep';
|
|
21
|
+
export interface YaraRule {
|
|
22
|
+
/** Rule name matching the .yar file (e.g. 'pii_in_capture_call') */
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
severity: YaraSeverity;
|
|
26
|
+
category: YaraCategory;
|
|
27
|
+
/** Which hook+tool combinations this rule applies to */
|
|
28
|
+
appliesTo: Array<{
|
|
29
|
+
phase: HookPhase;
|
|
30
|
+
tool: ToolTarget;
|
|
31
|
+
}>;
|
|
32
|
+
/** Compiled regex patterns — any match triggers the rule */
|
|
33
|
+
patterns: RegExp[];
|
|
34
|
+
}
|
|
35
|
+
export interface YaraMatch {
|
|
36
|
+
rule: YaraRule;
|
|
37
|
+
/** The matched substring */
|
|
38
|
+
matchedText: string;
|
|
39
|
+
/** Byte offset in the scanned content */
|
|
40
|
+
offset: number;
|
|
41
|
+
}
|
|
42
|
+
export type ScanResult = {
|
|
43
|
+
matched: false;
|
|
44
|
+
} | {
|
|
45
|
+
matched: true;
|
|
46
|
+
matches: YaraMatch[];
|
|
47
|
+
};
|
|
48
|
+
export declare const RULES: YaraRule[];
|
|
49
|
+
/**
|
|
50
|
+
* Scan content against rules applicable to a given hook phase and tool.
|
|
51
|
+
* Returns all matching rules (one match per rule, first pattern wins).
|
|
52
|
+
*/
|
|
53
|
+
export declare function scan(content: string, phase: HookPhase, tool: ToolTarget): ScanResult;
|
|
54
|
+
/**
|
|
55
|
+
* Scan all files in a skill directory for prompt injection.
|
|
56
|
+
* Used for context-mill scanning after skill installation.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scanSkillDirectory(files: Array<{
|
|
59
|
+
path: string;
|
|
60
|
+
content: string;
|
|
61
|
+
}>): ScanResult;
|