@posthog/wizard 2.49.0 → 2.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/{add-mcp-server-to-clients-DnyxFVhW.js → add-mcp-server-to-clients-C1f2al7N.js} +5 -5
  2. package/dist/{add-mcp-server-to-clients-DnyxFVhW.js.map → add-mcp-server-to-clients-C1f2al7N.js.map} +1 -1
  3. package/dist/{agent-interface-Doo3apDE.js → agent-interface-BL2wCV3b.js} +33 -71
  4. package/dist/agent-interface-BL2wCV3b.js.map +1 -0
  5. package/dist/agent-prompt-loader-CEWWA98d.js +275 -0
  6. package/dist/agent-prompt-loader-CEWWA98d.js.map +1 -0
  7. package/dist/{agent-runner-D7OZPgIV.js → agent-runner-DSKXFDXa.js} +101 -319
  8. package/dist/agent-runner-DSKXFDXa.js.map +1 -0
  9. package/dist/{analytics-D6gRGiqt.js → analytics-Ddf3zJgF.js} +20 -2
  10. package/dist/analytics-Ddf3zJgF.js.map +1 -0
  11. package/dist/analytics-GD_xM1XO.js +2 -0
  12. package/dist/{api-ZT2Zd5yp.js → api-BdanR3Zp.js} +3 -3
  13. package/dist/{api-ZT2Zd5yp.js.map → api-BdanR3Zp.js.map} +1 -1
  14. package/dist/bin.js +89 -90
  15. package/dist/bin.js.map +1 -1
  16. package/dist/bounded-fs-d0dw1Qv5.js +242 -0
  17. package/dist/bounded-fs-d0dw1Qv5.js.map +1 -0
  18. package/dist/{ci-install-CQWSPGdM.js → ci-install-DnVNVs0f.js} +4 -4
  19. package/dist/{ci-install-CQWSPGdM.js.map → ci-install-DnVNVs0f.js.map} +1 -1
  20. package/dist/commandments-BagyrYRW.js +2 -0
  21. package/dist/{commandments-DSNrOeOC.js → commandments-DiMtIz1o.js} +1 -1
  22. package/dist/{commandments-DSNrOeOC.js.map → commandments-DiMtIz1o.js.map} +1 -1
  23. package/dist/{debug-D8AJ_YCW.js → debug-By1_VtgZ.js} +1 -1
  24. package/dist/{debug-DF51kDvZ.js → debug-DaXKSXEy.js} +8 -27
  25. package/dist/debug-DaXKSXEy.js.map +1 -0
  26. package/dist/{defaults-CtWMmC8s.js → defaults-DAE9_QMn.js} +1 -1
  27. package/dist/{defaults-CtWMmC8s.js.map → defaults-DAE9_QMn.js.map} +1 -1
  28. package/dist/{env-api-key-C3LzH8Lr.js → env-api-key-DDVkPQmo.js} +1 -1
  29. package/dist/{env-api-key-C3LzH8Lr.js.map → env-api-key-DDVkPQmo.js.map} +1 -1
  30. package/dist/{environment-Csj7vBHM.js → environment-DJQOs7MR.js} +4 -4
  31. package/dist/environment-DJQOs7MR.js.map +1 -0
  32. package/dist/{file-watcher-CQB00KDS.js → file-watcher-LZe_W3hM.js} +2 -2
  33. package/dist/{file-watcher-CQB00KDS.js.map → file-watcher-LZe_W3hM.js.map} +1 -1
  34. package/dist/{headless-ui-DfSOfXEo.js → headless-ui-wIeUgvJ8.js} +2 -2
  35. package/dist/{headless-ui-DfSOfXEo.js.map → headless-ui-wIeUgvJ8.js.map} +1 -1
  36. package/dist/{interactive-DmRqhAUD.js → interactive-j4DoJDnk.js} +2 -2
  37. package/dist/{interactive-DmRqhAUD.js.map → interactive-j4DoJDnk.js.map} +1 -1
  38. package/dist/{mcp-B_ycbOus.js → mcp-Butt7ktW.js} +2 -2
  39. package/dist/{mcp-B_ycbOus.js.map → mcp-Butt7ktW.js.map} +1 -1
  40. package/dist/{mcp-prompt-streaming-ChEBHPQ-.js → mcp-prompt-streaming-D25BQfRa.js} +3 -3
  41. package/dist/{mcp-prompt-streaming-ChEBHPQ-.js.map → mcp-prompt-streaming-D25BQfRa.js.map} +1 -1
  42. package/dist/{non-interactive-ViR2FrQC.js → non-interactive-Cpof2JsA.js} +2 -2
  43. package/dist/{non-interactive-ViR2FrQC.js.map → non-interactive-Cpof2JsA.js.map} +1 -1
  44. package/dist/{orchestrator-tools-B0vrJlpS.js → orchestrator-tools-CsJizPpV.js} +3 -3
  45. package/dist/{orchestrator-tools-B0vrJlpS.js.map → orchestrator-tools-CsJizPpV.js.map} +1 -1
  46. package/dist/{package-json-DCuoye-H.js → package-json-CDs1ZVKu.js} +1 -1
  47. package/dist/{package-json-DCuoye-H.js.map → package-json-CDs1ZVKu.js.map} +1 -1
  48. package/dist/{package-manager-B6QAvLyt.js → package-manager-DnzNEjci.js} +2 -2
  49. package/dist/{package-manager-B6QAvLyt.js.map → package-manager-DnzNEjci.js.map} +1 -1
  50. package/dist/{pi-CXhGyUbi.js → pi-DE2cAVfO.js} +89 -138
  51. package/dist/pi-DE2cAVfO.js.map +1 -0
  52. package/dist/{playground-D_cP2oXt.js → playground-C3sLBq6A.js} +7 -7
  53. package/dist/{playground-D_cP2oXt.js.map → playground-C3sLBq6A.js.map} +1 -1
  54. package/dist/{posthog-integration-Bt8jQC7c.js → posthog-integration-_GSKRotZ.js} +45 -38
  55. package/dist/posthog-integration-_GSKRotZ.js.map +1 -0
  56. package/dist/{posthog-DZZbziJ1.js → posthog-mgz-FQlS.js} +1 -1
  57. package/dist/{posthog-DZZbziJ1.js.map → posthog-mgz-FQlS.js.map} +1 -1
  58. package/dist/{provisioning-By5DVLJr.js → provisioning-BndgugFk.js} +3 -3
  59. package/dist/{provisioning-By5DVLJr.js.map → provisioning-BndgugFk.js.map} +1 -1
  60. package/dist/{queue-tools-3iQHCpWF.js → queue-tools-DZjAjiqk.js} +97 -10
  61. package/dist/queue-tools-DZjAjiqk.js.map +1 -0
  62. package/dist/{registry-DG8r_Wks.js → registry-BMjfECF2.js} +272 -429
  63. package/dist/registry-BMjfECF2.js.map +1 -0
  64. package/dist/{security-BpbwefgM.js → security-D5hjYVFi.js} +4 -4
  65. package/dist/{security-BpbwefgM.js.map → security-D5hjYVFi.js.map} +1 -1
  66. package/dist/{setup-utils-CFejYe1M.js → setup-utils-ZKEk71yV.js} +14 -17
  67. package/dist/setup-utils-ZKEk71yV.js.map +1 -0
  68. package/dist/smoke-test.sh +1 -1
  69. package/dist/{start-tui-BINHq3Yp.js → start-tui-B2Tb427o.js} +19 -19
  70. package/dist/{start-tui-BINHq3Yp.js.map → start-tui-B2Tb427o.js.map} +1 -1
  71. package/dist/{steps-DhmMIChh.js → steps-ClMZ6MZd.js} +7 -7
  72. package/dist/{steps-DhmMIChh.js.map → steps-ClMZ6MZd.js.map} +1 -1
  73. package/dist/{store-Bj1m0ws-.js → store-Db77bfRv.js} +5 -5
  74. package/dist/{store-Bj1m0ws-.js.map → store-Db77bfRv.js.map} +1 -1
  75. package/dist/{subagent-DND7lZg6.js → subagent--VXEEc8S.js} +2 -2
  76. package/dist/{subagent-DND7lZg6.js.map → subagent--VXEEc8S.js.map} +1 -1
  77. package/dist/{task-CsAczwW6.js → task-CBQfx_f5.js} +30 -45
  78. package/dist/task-CBQfx_f5.js.map +1 -0
  79. package/dist/{task-stream-BaiI6TNY.js → task-stream-DCrqCmFa.js} +5 -5
  80. package/dist/{task-stream-BaiI6TNY.js.map → task-stream-DCrqCmFa.js.map} +1 -1
  81. package/dist/{tasks-DqDHx5pW.js → tasks-BJuz1XQ1.js} +2 -2
  82. package/dist/{tasks-DqDHx5pW.js.map → tasks-BJuz1XQ1.js.map} +1 -1
  83. package/dist/{telemetry-DXzPfSXl.js → telemetry-tdU6F90U.js} +3 -3
  84. package/dist/{telemetry-DXzPfSXl.js.map → telemetry-tdU6F90U.js.map} +1 -1
  85. package/dist/{terminal-DKsibL3T.js → terminal-ClWui_UN.js} +13 -13
  86. package/dist/{terminal-DKsibL3T.js.map → terminal-ClWui_UN.js.map} +1 -1
  87. package/dist/{tools-D5pL_r3K.js → tools-DwpaEyEv.js} +10 -8
  88. package/dist/tools-DwpaEyEv.js.map +1 -0
  89. package/dist/{wizard-abort-Br1Dps20.js → wizard-abort-B1iPXM3K.js} +4 -4
  90. package/dist/{wizard-abort-Br1Dps20.js.map → wizard-abort-B1iPXM3K.js.map} +1 -1
  91. package/dist/{wizard-abort-DG6Atd5I.js → wizard-abort-cX4Kytai.js} +1 -1
  92. package/dist/{wizard-session-tp4Zdbwl.js → wizard-session-BAZql11N.js} +1 -1
  93. package/dist/{wizard-session-tp4Zdbwl.js.map → wizard-session-BAZql11N.js.map} +1 -1
  94. package/dist/{wizard-session-DWHe83uk.js → wizard-session-C5lZPPEc.js} +1 -1
  95. package/dist/{yara-hooks-DEHfsDSF.js → yara-hooks-CEs3jx7j.js} +139 -56
  96. package/dist/yara-hooks-CEs3jx7j.js.map +1 -0
  97. package/dist/yara-hooks-gz4AceFG.js +2 -0
  98. package/package.json +2 -2
  99. package/dist/agent-interface-Doo3apDE.js.map +0 -1
  100. package/dist/agent-runner-D7OZPgIV.js.map +0 -1
  101. package/dist/analytics-AOHLZy1k.js +0 -2
  102. package/dist/analytics-D6gRGiqt.js.map +0 -1
  103. package/dist/commandments-qTW7pwaT.js +0 -2
  104. package/dist/debug-DF51kDvZ.js.map +0 -1
  105. package/dist/environment-Csj7vBHM.js.map +0 -1
  106. package/dist/file-utils-Dj3UqDJz.js +0 -116
  107. package/dist/file-utils-Dj3UqDJz.js.map +0 -1
  108. package/dist/pi-CXhGyUbi.js.map +0 -1
  109. package/dist/posthog-integration-Bt8jQC7c.js.map +0 -1
  110. package/dist/queue-tools-3iQHCpWF.js.map +0 -1
  111. package/dist/registry-DG8r_Wks.js.map +0 -1
  112. package/dist/setup-utils-CFejYe1M.js.map +0 -1
  113. package/dist/task-CsAczwW6.js.map +0 -1
  114. package/dist/tools-D5pL_r3K.js.map +0 -1
  115. package/dist/yara-hooks-BExxs6sG.js +0 -2
  116. package/dist/yara-hooks-DEHfsDSF.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup-utils-CFejYe1M.js","names":["fs","path","join","fs"],"sources":["../src/utils/package-manager.ts","../src/lib/oauth/program-scopes.ts","../src/lib/host-resolution.ts","../src/utils/oauth-errors.ts","../src/utils/oauth.ts","../src/utils/project-resolution.ts","../src/utils/semver.ts","../src/utils/setup-utils.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { withProgress } from '../telemetry';\nimport { getPackageDotJson, updatePackageDotJson } from './setup-utils';\nimport type { PackageJson } from './package-json';\nimport { analytics } from './analytics';\nimport type { WizardRunOptions } from './types';\n\ntype InstallDirOpt = Pick<WizardRunOptions, 'installDir'>;\n\nexport interface PackageManager {\n name: string;\n label: string;\n installCommand: string;\n buildCommand: string;\n /** Command the manager uses to execute a `package.json` script. */\n runScriptCommand: string;\n flags: string;\n detect: (opts: InstallDirOpt) => boolean;\n addOverride: (\n pkgName: string,\n pkgVersion: string,\n opts: InstallDirOpt,\n ) => Promise<void>;\n}\n\nfunction hasLockfile(installDir: string, file: string): boolean {\n return fs.existsSync(path.join(installDir, file));\n}\n\nfunction lockfileHeaderContains(\n installDir: string,\n file: string,\n needle: string,\n): boolean {\n try {\n const head = fs\n .readFileSync(path.join(installDir, file), 'utf-8')\n .slice(0, 500);\n return head.includes(needle);\n } catch {\n return false;\n }\n}\n\ntype OverrideSlot = 'npm' | 'yarn' | 'pnpm';\n\nasync function writeOverride(\n slot: OverrideSlot,\n pkgName: string,\n pkgVersion: string,\n { installDir }: InstallDirOpt,\n): Promise<void> {\n const pkg = await getPackageDotJson({ installDir });\n let next: PackageJson;\n if (slot === 'yarn') {\n next = {\n ...pkg,\n resolutions: { ...(pkg.resolutions ?? {}), [pkgName]: pkgVersion },\n };\n } else if (slot === 'pnpm') {\n next = {\n ...pkg,\n pnpm: {\n ...(pkg.pnpm ?? {}),\n overrides: { ...(pkg.pnpm?.overrides ?? {}), [pkgName]: pkgVersion },\n },\n };\n } else {\n next = {\n ...pkg,\n overrides: { ...(pkg.overrides ?? {}), [pkgName]: pkgVersion },\n };\n }\n await updatePackageDotJson(next, { installDir });\n}\n\nexport const BUN: PackageManager = {\n name: 'bun',\n label: 'Bun',\n installCommand: 'bun add',\n buildCommand: 'bun run build',\n runScriptCommand: 'bun run',\n flags: '',\n detect: ({ installDir }) =>\n hasLockfile(installDir, 'bun.lockb') || hasLockfile(installDir, 'bun.lock'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\nexport const YARN_V1: PackageManager = {\n name: 'yarn',\n label: 'Yarn V1',\n installCommand: 'yarn add',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '--ignore-workspace-root-check',\n detect: ({ installDir }) =>\n lockfileHeaderContains(installDir, 'yarn.lock', 'yarn lockfile v1'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('yarn', pkgName, pkgVersion, opts),\n};\n\n/** YARN V2/3/4 */\nexport const YARN_V2: PackageManager = {\n name: 'yarn',\n label: 'Yarn V2/3/4',\n installCommand: 'yarn add',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '',\n detect: ({ installDir }) =>\n lockfileHeaderContains(installDir, 'yarn.lock', '__metadata'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('yarn', pkgName, pkgVersion, opts),\n};\n\nexport const PNPM: PackageManager = {\n name: 'pnpm',\n label: 'pnpm',\n installCommand: 'pnpm add',\n buildCommand: 'pnpm build',\n runScriptCommand: 'pnpm',\n flags: '--ignore-workspace-root-check',\n detect: ({ installDir }) => hasLockfile(installDir, 'pnpm-lock.yaml'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('pnpm', pkgName, pkgVersion, opts),\n};\n\nexport const NPM: PackageManager = {\n name: 'npm',\n label: 'npm',\n installCommand: 'npm add',\n buildCommand: 'npm run build',\n runScriptCommand: 'npm run',\n flags: '',\n detect: ({ installDir }) => hasLockfile(installDir, 'package-lock.json'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\n// Expo is selected by upstream config (app.json / app.config.*) rather than\n// a lockfile, so its detect always returns false here.\nexport const EXPO: PackageManager = {\n name: 'expo',\n label: 'Expo',\n installCommand: 'npx expo install',\n buildCommand: 'npx expo build',\n runScriptCommand: 'npx expo run',\n flags: '',\n detect: () => false,\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\nexport const packageManagers: PackageManager[] = [\n BUN,\n YARN_V1,\n YARN_V2,\n PNPM,\n NPM,\n EXPO,\n];\n\nexport function detectAllPackageManagers({\n installDir,\n}: InstallDirOpt): PackageManager[] {\n return withProgress('detect-package-manager', () => {\n const matches = packageManagers.filter((pm) => pm.detect({ installDir }));\n if (matches.length === 0) {\n analytics.setTag('package-manager', 'not-detected');\n }\n return matches;\n });\n}\n","/**\n * OAuth scope resolver — every program starts from the shared\n * `WIZARD_OAUTH_SCOPES` base set and a program can layer additional\n * scopes on top via `PROGRAM_SCOPE_ADDITIONS`.\n *\n * final scope set = WIZARD_OAUTH_SCOPES ∪ programAdditions\n *\n * Additions are merged in declaration order and deduped, so a program\n * never accidentally weakens the base set — only widens it. Programs\n * not listed in `PROGRAM_SCOPE_ADDITIONS` request the unchanged\n * base set, exactly like before.\n *\n * Current additions: `McpTutorial` layers read-only on every product\n * surface (feature flags, experiments, surveys, replays, errors, web\n * analytics, LLM analytics, cohorts, persons) plus read/write on\n * annotations; `AgentSkill` adds feature-flag read/write; the default\n * `PostHogIntegration` run and the standalone `slack` flow add\n * `integration:read` for the Connect-Slack step. Persistence writes (dashboard:write,\n * insight:write, notebook:write, query:read) come for free from the\n * base set, so the tutorial's \"save as insight / pin to dashboard /\n * add to notebook\" follow-ups keep working.\n *\n * Add a new program override by extending `PROGRAM_SCOPE_ADDITIONS`\n * below — no other call-site changes required as long as the program's\n * `programId` is threaded into `getOrAskForProjectData`.\n */\n\n// IMPORTANT: type-only import. A value import would create a circular\n// dependency (setup-utils → program-scopes → program-registry →\n// posthog-integration → ... → setup-utils), and `Program` would be\n// read as `undefined` at module init. Keep this type-only and reference\n// program IDs by their string-literal value below — TypeScript still\n// catches renames via the `Partial<Record<ProgramId, ...>>` keying.\nimport type { ProgramId } from '@lib/programs/program-registry';\nimport { WIZARD_OAUTH_SCOPES } from '@lib/constants';\n\n/**\n * Extra scopes the MCP tutorial needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Every scope requested here must stay within the wizard OAuth app's\n * ceiling on the PostHog side (`OAuthApplication.scopes`) — the full\n * list lives in the README under \"OAuth app scope ceiling\". The\n * tutorial's prompts and follow-ups touch most of the read surface,\n * plus annotation write for the \"PostHog wizard install\" verify-prompt.\n *\n * Already in the base `WIZARD_OAUTH_SCOPES` (and therefore not\n * repeated here):\n * • user:read, project:read, llm_gateway:read — auth + gateway\n * • query:read — HogQL\n * • dashboard:write, insight:write, notebook:write — Phase-5 persist\n *\n * Deliberately omitted (writes on read-only product surfaces):\n * • feature_flag:write, experiment:write, survey:write,\n * cohort:write, session_recording:write, error_tracking:write,\n * alert:write, subscription:write\n */\nexport const MCP_TUTORIAL_SCOPE_ADDITIONS = [\n // Explicit reads on the persistence surfaces. `*:write` usually\n // implies read on PostHog, but the consent flow grants exactly the\n // strings requested — explicit reads avoid a 403 when the agent\n // lists existing dashboards/insights/notebooks before saving.\n 'dashboard:read',\n 'insight:read',\n 'notebook:read',\n\n // Read on every product surface the tutorial demos.\n 'feature_flag:read',\n 'experiment:read',\n 'experiment_saved_metric:read',\n 'survey:read',\n 'session_recording:read',\n 'error_tracking:read',\n 'web_analytics:read',\n 'llm_analytics:read',\n 'cohort:read',\n 'person:read',\n\n // Annotation read + write — the verify prompt's \"annotate today\"\n // is the only mutation the tutorial performs outside the\n // dashboard/insight/notebook persistence triplet.\n 'annotation:read',\n 'annotation:write',\n\n // Metadata / exploration reads — for \"break down by user property\",\n // \"did that change land alongside a deploy\", autocapture actions,\n // etc. Otherwise the agent 403s on the supporting catalog calls\n // even though the parent query has `query:read`.\n 'activity_log:read',\n 'property_definition:read',\n 'event_definition:read',\n 'action:read',\n\n // Data warehouse reads — for the data-role cross-sells that join\n // event data with Stripe / Salesforce / S3.\n 'warehouse_table:read',\n 'warehouse_view:read',\n\n // Inspection-only — we don't write alerts or subscriptions, but the\n // model might want to read existing ones (e.g. \"is there already an\n // alert on this metric?\").\n 'alert:read',\n 'subscription:read',\n 'integration:read',\n] as const;\n\n/**\n * Extra scopes the agent-skill program needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Skills under this program (e.g. `creating-product-tours`) create feature\n * flags during the install flow. PostHog's consent grants exactly the scope\n * strings requested — `:write` does not imply `:read` — so listing existing\n * flags to avoid key collisions needs `feature_flag:read` explicitly.\n * `property_definition:read` lets the agent discover person properties when\n * building flag rollout filters instead of having to ask the user verbatim.\n */\nexport const AGENT_SKILL_SCOPE_ADDITIONS = [\n 'feature_flag:read',\n 'feature_flag:write',\n 'property_definition:read',\n] as const;\n\n/**\n * Extra scopes the self-driving program needs on top of\n * `WIZARD_OAUTH_SCOPES`. All consumed by the PostHog MCP tools the\n * agent drives during the run:\n * • task:read / task:write — the signal source config API\n * (`inbox-source-configs-*`) is permissioned under the generic\n * `task` scope object, NOT a signals-specific one. Unrelated to\n * the Tasks product.\n * • integration:read — `integrations-list`, to check whether the\n * team already has a GitHub integration and to verify the connect\n * flow completed.\n * • signal_scout:read / signal_scout:write — list, sync, and tune\n * the Signals scout troop (`signals-scout-config-*`).\n * • session_recording:read / survey:read / error_tracking:read —\n * server-side product-usage probes (`query-session-recordings-list`,\n * `survey-list`, `error-issue-list`). Product usage is a\n * project-level fact (often instrumented in another repo or via\n * the snippet), so the agent asks the server instead of inferring\n * only from the local setup report. All three are read-only and\n * already in the wizard OAuth app's production scope ceiling (the\n * mcp-tutorial program requests them).\n * • external_data_source:read / external_data_source:write — the\n * connected-tools step creates the GitHub Issues / Linear warehouse\n * sources directly (`external-data-sources-create`) and verifies\n * what's actually connected (`external-data-sources-list`) instead\n * of taking the user's word for it.\n * • llm_skill:read / llm_skill:write — the custom-scouts step\n * (skill step 6b): read the seeded `authoring-signals-scouts`\n * guide and canonical scout bodies (`llma-skill-get` /\n * `llma-skill-file-get`) and author the user-approved custom\n * `signals-scout-*` skills (`llma-skill-create`). Canonical scout\n * bodies are never edited.\n * • product_enablement:write — the \"Enable products\" step turns on\n * Session Replay / Error Tracking / Support so their sources have\n * data to read (`products-enable`). A purpose-built scope: the\n * server owns each enable recipe, so this can flip the product\n * toggles without the far broader `project:write`. NOTE: net-new —\n * must be added to the wizard OAuth app's scope ceiling on the\n * PostHog side before it can be granted (see README / ARCHITECTURE §9).\n */\nexport const SELF_DRIVING_SCOPE_ADDITIONS = [\n 'task:read',\n 'task:write',\n 'integration:read',\n 'signal_scout:read',\n 'signal_scout:write',\n 'session_recording:read',\n 'survey:read',\n 'error_tracking:read',\n 'external_data_source:read',\n 'external_data_source:write',\n 'llm_skill:read',\n 'llm_skill:write',\n 'product_enablement:write',\n] as const;\n\n/**\n * Extra scopes the warehouse-source program needs on top of\n * `WIZARD_OAUTH_SCOPES`. The agent creates data warehouse sources directly\n * (`external-data-sources-create`) and lists what's connected\n * (`external-data-sources-list`) to verify the result. Both are already within\n * the wizard OAuth app's scope ceiling — the self-driving program requests the\n * same pair.\n */\nexport const WAREHOUSE_SOURCE_SCOPE_ADDITIONS = [\n 'external_data_source:read',\n 'external_data_source:write',\n] as const;\n\n/**\n * Extra scope the Connect-Slack step needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * The step polls `/api/projects/:id/integrations/` (`fetchSlackConnected`)\n * to render the already-connected variant and to flip live once the user\n * completes the Slack OAuth step in the browser. Without `integration:read`\n * the first poll 403s, the screen stops polling, and an already-connected\n * project is nagged with the connect nudge. Used by the default integration\n * run (the step ends the run) and by the standalone `wizard slack` flow\n * (the step is the whole program).\n */\nexport const CONNECT_SLACK_SCOPE_ADDITIONS = ['integration:read'] as const;\n\n/**\n * Per-program scope additions, layered on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Programs not listed here request the unchanged base set. Use this\n * map only for programs that need *more* than the base — never for\n * narrowing, since narrowing risks breaking shared infrastructure\n * (e.g. dropping `llm_gateway:read` would 401 every agent call).\n *\n * Keyed by `ProgramId` so TypeScript catches stale entries when a\n * program is renamed or removed.\n */\nconst PROGRAM_SCOPE_ADDITIONS: Partial<Record<ProgramId, readonly string[]>> = {\n // String literal (not `Program.McpTutorial`) to avoid a runtime cycle\n // with `program-registry.ts`. The `Partial<Record<ProgramId, ...>>`\n // key constraint catches renames at compile time — if `mcpTutorialConfig.id`\n // ever changes, this line will fail to type-check.\n 'mcp-tutorial': MCP_TUTORIAL_SCOPE_ADDITIONS,\n 'agent-skill': AGENT_SKILL_SCOPE_ADDITIONS,\n 'self-driving': SELF_DRIVING_SCOPE_ADDITIONS,\n 'warehouse-source': WAREHOUSE_SOURCE_SCOPE_ADDITIONS,\n 'posthog-integration': CONNECT_SLACK_SCOPE_ADDITIONS,\n slack: CONNECT_SLACK_SCOPE_ADDITIONS,\n};\n\n/**\n * Resolve the OAuth scope list to request for a given program. Returns\n * `WIZARD_OAUTH_SCOPES` for programs without an addition entry; for\n * programs that do have one, returns the union of base + additions\n * with duplicates dropped (declaration order preserved, base first).\n *\n * `null` / `undefined` programId falls through to the default — same\n * behavior as the historical hardcoded `WIZARD_OAUTH_SCOPES` reference\n * in `askForWizardLogin`, so call sites that haven't been updated to\n * pass a programId continue to work unchanged.\n */\nexport function getOAuthScopesForProgram(\n programId: ProgramId | null | undefined,\n): readonly string[] {\n const additions = (programId && PROGRAM_SCOPE_ADDITIONS[programId]) || [];\n if (additions.length === 0) {\n return WIZARD_OAUTH_SCOPES;\n }\n // Dedupe while preserving order; base scopes appear first so the\n // consent screen shows them in their familiar slot.\n const seen = new Set<string>();\n const merged: string[] = [];\n for (const s of [...WIZARD_OAUTH_SCOPES, ...additions]) {\n if (seen.has(s)) continue;\n seen.add(s);\n merged.push(s);\n }\n return merged;\n}\n","/**\n * HostResolution — the single, immutable snapshot of where the wizard talks to\n * PostHog for one run.\n *\n * One cloud region (or a pinned `--base-url`) implies a whole family of hosts:\n * the event-ingestion/API host, the user-facing web app host, the CDN asset\n * host, the LLM gateway, and the MCP server. Rather than re-deriving each of\n * these at every call site and threading `region` + `baseUrl` around as loose\n * params, resolve once at auth time and pass this frozen object around — read\n * the property you need.\n *\n * The actual region→URL math (and the `--base-url` override) lives in\n * `@utils/urls`; this class is a thin, immutable façade over those resolvers so\n * the override flows through every field without callers having to know about\n * it. The OAuth-server URL stays in `@utils/urls` (`getOAuthUrl`) because it is\n * needed *before* a region is known, so it can't come from this post-auth object.\n */\n\nimport {\n getHost,\n getCloudUrl,\n getLlmGatewayUrl,\n getUiHostFromHost,\n detectRegion,\n resolveBaseUrl,\n} from '@utils/urls';\nimport { runtimeEnv } from '@env';\nimport type { CloudRegion } from '@utils/types';\n\n// The wizard's client gets CLI mode (a single `exec` tool) by server default,\n// so no mode is pinned; an `MCP_URL` override is taken verbatim.\nconst LOCAL_MCP_URL = 'http://localhost:8787/mcp';\nconst PROD_MCP_URL = 'https://mcp.posthog.com/mcp';\n\n/** Construction-time inputs that aren't implied by the region. */\nexport interface HostResolutionOptions {\n /** `--local-mcp`: point the agent's MCP url at the local dev server. */\n localMcp?: boolean;\n /** `--base-url`: pin every PostHog origin to one URL, bypassing region resolution. */\n baseUrl?: string;\n /** `--region`: trust the explicitly-provided region. */\n region?: CloudRegion;\n}\n\nfunction assetHostFor(region: CloudRegion, baseUrl?: string): string {\n const override = resolveBaseUrl(baseUrl);\n if (override) return override;\n return region === 'eu'\n ? 'https://eu-assets.i.posthog.com'\n : 'https://us-assets.i.posthog.com';\n}\n\nfunction assetHostFromApiHost(apiHost: string): string {\n if (apiHost.includes('us.i.posthog.com')) {\n return 'https://us-assets.i.posthog.com';\n }\n if (apiHost.includes('eu.i.posthog.com')) {\n return 'https://eu-assets.i.posthog.com';\n }\n return apiHost;\n}\n\nexport function mcpUrlFor(localMcp: boolean): string {\n if (localMcp) return LOCAL_MCP_URL;\n return runtimeEnv('MCP_URL') || PROD_MCP_URL;\n}\n\nexport class HostResolution {\n /** The resolved cloud region. `'us'` when a base URL is pinned. */\n readonly region: CloudRegion;\n /**\n * Event-ingestion / REST API host (e.g. `https://us.i.posthog.com`, or the\n * pinned `--base-url`). The SDK `host` written into the user's `.env`, the\n * base for the wizard-session REST calls, and the host shown to the agent.\n */\n readonly apiHost: string;\n /**\n * User-facing web app host (e.g. `https://us.posthog.com`). Use for any link\n * we hand to the user or open in their browser (dashboards, settings, inbox,\n * deep-link base).\n */\n readonly appHost: string;\n /** CDN asset host (e.g. `https://us-assets.i.posthog.com`). */\n readonly assetHost: string;\n /** PostHog LLM gateway URL the agent SDK authenticates its model calls against. */\n readonly gatewayUrl: string;\n /**\n * PostHog MCP server URL the agent connects to. Region-independent — the\n * server resolves the user's region from the bearer token — so this is driven\n * only by `--local-mcp` and the `MCP_URL` override, not by region/base-url.\n */\n readonly mcpUrl: string;\n\n private constructor(fields: {\n region: CloudRegion;\n apiHost: string;\n appHost: string;\n assetHost: string;\n gatewayUrl: string;\n mcpUrl: string;\n }) {\n this.region = fields.region;\n this.apiHost = fields.apiHost;\n this.appHost = fields.appHost;\n this.assetHost = fields.assetHost;\n this.gatewayUrl = fields.gatewayUrl;\n this.mcpUrl = fields.mcpUrl;\n Object.freeze(this);\n }\n\n /**\n * Canonical path: build the host family from a resolved region. A pinned\n * `--base-url` (via `opts.baseUrl`) wins for every origin; otherwise the\n * region's standard hosts are used. Honors IS_DEV through `resolveBaseUrl`.\n */\n static fromRegion(\n region: CloudRegion,\n opts: HostResolutionOptions = {},\n ): HostResolution {\n const apiHost = getHost(region, opts.baseUrl);\n return new HostResolution({\n region,\n apiHost,\n appHost: getCloudUrl(region, opts.baseUrl),\n assetHost: assetHostFor(region, opts.baseUrl),\n gatewayUrl: getLlmGatewayUrl(apiHost),\n mcpUrl: mcpUrlFor(opts.localMcp ?? false),\n });\n }\n\n /**\n * Build from an ingestion host string (the provisioning API returns one). The\n * given host is preserved verbatim as `apiHost`; the region and the derived\n * app/asset/gateway hosts are inferred from it.\n */\n static fromApiHost(\n apiHost: string,\n opts: Pick<HostResolutionOptions, 'localMcp'> = {},\n ): HostResolution {\n const region: CloudRegion = apiHost.includes('eu.') ? 'eu' : 'us';\n return new HostResolution({\n region,\n apiHost,\n appHost: getUiHostFromHost(apiHost),\n assetHost: assetHostFromApiHost(apiHost),\n gatewayUrl: getLlmGatewayUrl(apiHost),\n mcpUrl: mcpUrlFor(opts.localMcp ?? false),\n });\n }\n\n /**\n * Resolve the region from an access token (the us/eu probe, skipped when a\n * base URL is pinned), then build the host family. Used after OAuth and after\n * CI-mode API-key auth.\n */\n static async fromAccessToken(\n accessToken: string,\n opts: HostResolutionOptions = {},\n ): Promise<HostResolution> {\n const region =\n opts.region ?? (await detectRegion(accessToken, opts.baseUrl));\n return HostResolution.fromRegion(region, opts);\n }\n}\n","/**\n * OAuth failure presentation — the structured error the flow throws and the\n * user-facing messaging built from it.\n *\n * `oauth.ts` owns the flow machinery (callback server, token exchange, port\n * retries); this module owns what a failure *means*: the RFC 6749 error\n * fields (`error`, `error_description`, `error_uri`), per-code remediation\n * copy, and safe rendering for the browser callback page. Everything here is\n * pure so the copy can be unit-tested without the HTTP machinery.\n */\n\nimport { z } from 'zod';\nimport {\n ISSUES_URL,\n POSTHOG_STATUS_PAGE_URL,\n WIZARD_CONTACT_EMAIL,\n} from '@lib/constants';\n\n/**\n * A structured OAuth failure (RFC 6749 §4.1.2.1 authorize errors, §5.2 token\n * errors). Thrown by both the authorize callback and the token exchange so\n * the flow's error handler keys remediation copy and analytics fingerprints\n * off `code` instead of re-parsing `message`.\n *\n * The message stays exactly `OAuth error: <code>` — the analytics fingerprint\n * scheme (`wizard_oauth_<code>`) and the legacy message-prefix parsing both\n * depend on that shape. The description/uri ride along as properties.\n *\n * NAMING: never bind an instance to a variable or property whose name\n * contains \"oauth\" (use `flowError`, `callbackError`, ...). CodeQL's\n * clear-text-logging heuristic classifies any `*oauth*`-named variable read\n * as password data, and these errors are deliberately shown to the user, so\n * such a binding flags every log statement downstream of it (CWE-312).\n */\nexport class OAuthError extends Error {\n readonly code: string;\n /** The server's human-readable explanation (`error_description`), if sent. */\n readonly description?: string;\n /** The server's \"more info\" link (`error_uri`), if sent. */\n readonly uri?: string;\n\n constructor(code: string, description?: string, uri?: string) {\n super(`OAuth error: ${code}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n this.uri = uri;\n }\n}\n\n/**\n * Sanitize an OAuth error param before logging or rendering it: these arrive\n * on a query string (or a token-endpoint body) that anything able to hit the\n * local callback port can populate. Strips non-printable/non-ASCII characters\n * and caps the length. Returns undefined when nothing printable remains.\n */\nexport function sanitizeOAuthParam(\n value: string | null | undefined,\n maxLength = 200,\n): string | undefined {\n if (!value) return undefined;\n const cleaned = value\n .replace(/[^\\x20-\\x7e]/g, '')\n .slice(0, maxLength)\n .trim();\n return cleaned || undefined;\n}\n\n/** Description sentences get more room than bare codes/URIs. */\nconst MAX_DESCRIPTION_LENGTH = 500;\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nconst OAuthErrorBodySchema = z.object({\n error: z.string().min(1),\n error_description: z.string().optional(),\n error_uri: z.string().optional(),\n});\n\n/**\n * Build an `OAuthError` from an authorize-callback's query params.\n * Sanitizes every field — the callback URL is attacker-reachable input.\n */\nexport function oauthErrorFromCallbackParams(\n params: URLSearchParams,\n): OAuthError {\n return new OAuthError(\n sanitizeOAuthParam(params.get('error')) ?? 'unknown',\n sanitizeOAuthParam(params.get('error_description'), MAX_DESCRIPTION_LENGTH),\n sanitizeOAuthParam(params.get('error_uri')),\n );\n}\n\n/**\n * Build an `OAuthError` from a token-endpoint JSON error body (RFC 6749\n * §5.2). Returns null when the body isn't an OAuth error shape (HTML error\n * pages, proxy bodies, network failures) — the caller rethrows the raw\n * error in that case.\n */\nexport function oauthErrorFromTokenBody(data: unknown): OAuthError | null {\n const parsed = OAuthErrorBodySchema.safeParse(data);\n if (!parsed.success) return null;\n return new OAuthError(\n sanitizeOAuthParam(parsed.data.error) ?? 'unknown',\n sanitizeOAuthParam(parsed.data.error_description, MAX_DESCRIPTION_LENGTH),\n sanitizeOAuthParam(parsed.data.error_uri),\n );\n}\n\n/**\n * The error paragraphs for the browser callback page. The constant framing\n * (title, styles, \"return to your terminal\") stays in `oauth.ts`; this owns\n * the part that varies by error, with everything HTML-escaped since the\n * values come straight off the callback query string.\n */\nexport function buildCallbackErrorHtml(error: OAuthError): string {\n if (error.code === 'access_denied') {\n return '<p>Authorization cancelled.</p>';\n }\n const paragraphs = [\n `<p>Authorization failed (${escapeHtml(error.code)}).</p>`,\n ];\n if (error.description) {\n paragraphs.push(`<p>${escapeHtml(error.description)}</p>`);\n }\n if (error.uri) {\n paragraphs.push(`<p>More info: ${escapeHtml(error.uri)}</p>`);\n }\n return paragraphs.join('\\n');\n}\n\ninterface FailureMessageParams {\n error: Error;\n /** Scopes the flow requested (base set + program additions). */\n requestedScopes: readonly string[];\n clientId: string;\n /** OAuth server origin the flow targeted. */\n oauthUrl: string;\n /**\n * True when the flow targets a dev-seeded stack (IS_DEV build or a pinned\n * `--base-url`) — selects the \"fix your local OAuth app\" remediation over\n * the production-runbook one. Same condition that selects the dev client ID.\n */\n isDevStack: boolean;\n}\n\n/**\n * Per-code headline + \"what to do\" remediation for the terminal failure\n * message. Codes not listed fall through to a generic rendering of the\n * error. `access_denied` and the flow timeout never reach this — they keep\n * their dedicated handling in `performOAuthFlow`.\n */\nfunction headlineAndRemediation(params: FailureMessageParams): {\n headline: string;\n whatToDo?: string;\n} {\n const { error, oauthUrl, isDevStack } = params;\n if (!(error instanceof OAuthError)) {\n return { headline: error.message };\n }\n\n switch (error.code) {\n case 'invalid_scope':\n return {\n headline:\n 'PostHog rejected one or more of the requested scopes (invalid_scope).',\n whatToDo: isDevStack\n ? 'The wizard OAuth app on your local stack allows fewer scopes than the wizard requests (its OAuthApplication.scopes ceiling). Re-run your local wizard OAuth app setup so it allows every scope listed below, or add the missing ones in Django admin, then try again.'\n : `The wizard OAuth app's server-side scope ceiling (OAuthApplication.scopes) is missing scopes the wizard requests. This needs a fix on the PostHog side, so email ${WIZARD_CONTACT_EMAIL} with the details below (PostHog staff: the \"scope-ceiling-invalid-scope\" runbook in PostHog/runbooks has the fix). Re-running the wizard will not help until the ceiling is updated.`,\n };\n case 'invalid_client':\n return {\n headline: `PostHog at ${oauthUrl} does not recognize the wizard's OAuth client (invalid_client).`,\n whatToDo:\n 'The client ID below is not registered on the target instance. If you pointed the wizard at a local or self-hosted stack (--base-url), seed its wizard OAuth app first; otherwise re-run without --base-url to use PostHog Cloud.',\n };\n case 'invalid_grant':\n return {\n headline:\n 'The login code was rejected (invalid_grant): it expired or was already used.',\n whatToDo:\n 'Re-run the wizard and complete the browser login promptly. Login codes expire after 5 minutes and can only be used once.',\n };\n case 'server_error':\n return {\n headline:\n \"PostHog's OAuth server hit an internal error (server_error).\",\n whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`,\n };\n case 'temporarily_unavailable':\n return {\n headline:\n \"PostHog's OAuth server is temporarily unavailable (temporarily_unavailable).\",\n whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`,\n };\n default:\n return {\n headline: `PostHog returned an OAuth error: ${error.code}.`,\n whatToDo:\n 'Re-run the wizard. If the error persists, include the details below when asking for help.',\n };\n }\n}\n\n/**\n * Terminal message for a failed OAuth flow. Always ends with the request\n * context (target host, client ID, requested scopes) so a pasted bug report\n * is actionable without follow-up questions, plus how to get help: the\n * support email (Zendesk-backed) and the issues link.\n */\nexport function buildOAuthFailureMessage(params: FailureMessageParams): string {\n const { error, requestedScopes, clientId, oauthUrl } = params;\n const flowError = error instanceof OAuthError ? error : null;\n const { headline, whatToDo } = headlineAndRemediation(params);\n\n const sections = [`Authorization failed: ${headline}`];\n if (flowError?.description) {\n sections.push(`The server said: ${flowError.description}`);\n }\n if (flowError?.uri) {\n sections.push(`More info: ${flowError.uri}`);\n }\n if (whatToDo) {\n sections.push(`What to do: ${whatToDo}`);\n }\n sections.push(\n [\n 'Details for bug reports:',\n ` target host: ${oauthUrl}`,\n ` client ID: ${clientId}`,\n ` requested scopes: ${requestedScopes.join(' ')}`,\n ].join('\\n'),\n );\n sections.push(\n `Need help? Email ${WIZARD_CONTACT_EMAIL} with the details above, or create an issue:\\n${ISSUES_URL}`,\n );\n return sections.join('\\n\\n');\n}\n","import * as crypto from 'node:crypto';\nimport * as http from 'node:http';\nimport { execSync } from 'node:child_process';\nimport axios from 'axios';\nimport { logToFile } from './debug';\nimport { z } from 'zod';\nimport { getUI } from '@ui';\nimport {\n OAUTH_PORTS,\n OAUTH_TIMEOUT_MS,\n POSTHOG_DEV_CLIENT_ID,\n POSTHOG_PROXY_CLIENT_ID,\n WIZARD_USER_AGENT,\n} from '@lib/constants';\nimport { getOAuthUrl, resolveBaseUrl } from './urls';\nimport { abort } from './setup-utils';\nimport { openTrackedLink, withUtm } from './links';\nimport { analytics } from './analytics';\nimport {\n OAuthError,\n buildCallbackErrorHtml,\n buildOAuthFailureMessage,\n oauthErrorFromCallbackParams,\n oauthErrorFromTokenBody,\n} from './oauth-errors';\n\nconst OAUTH_CALLBACK_STYLES = `\n <style>\n * {\n font-family: monospace;\n background-color: #1b0a00;\n color: #F7A502;\n font-weight: medium;\n font-size: 24px;\n margin: .25rem;\n }\n\n .blink {\n animation: blink-animation 1s steps(2, start) infinite;\n }\n\n @keyframes blink-animation {\n to {\n opacity: 0;\n }\n }\n </style>\n`;\n\nexport const OAuthTokenResponseSchema = z.object({\n access_token: z.string(),\n expires_in: z.number(),\n token_type: z.string(),\n scope: z.string(),\n refresh_token: z.string().optional(),\n scoped_teams: z.array(z.number()).optional(),\n scoped_organizations: z.array(z.string()).optional(),\n // Sent by PostHog Cloud (and passed through the oauth.posthog.com proxy); absent on\n // self-hosted. `.catch(undefined)` so an unrecognized value degrades to the probe\n // fallback instead of failing the whole login.\n posthog_region: z.enum(['us', 'eu']).optional().catch(undefined),\n posthog_base_url: z.string().optional().catch(undefined),\n});\n\nexport type OAuthTokenResponse = z.infer<typeof OAuthTokenResponseSchema>;\n\nexport const WIZARD_COMPLETION_SCOPE = 'event_definition:write';\n\nexport function parseOAuthScopes(scope: string): string[] {\n return scope.split(/\\s+/).filter(Boolean);\n}\n\nexport function assertWizardCompletionScope(scope: string): void {\n if (parseOAuthScopes(scope).includes(WIZARD_COMPLETION_SCOPE)) return;\n\n throw new Error(\n `Your existing PostHog Wizard authorization is missing the ${WIZARD_COMPLETION_SCOPE} permission required to finish setup. Reconnect the wizard and approve the updated permissions. If PostHog reuses the old approval, revoke the existing Wizard authorization first, then rerun the wizard.`,\n );\n}\n\n// Stable marker for the authorization-flow timeout. Detection keys off the exact\n// message rather than a loose substring — `.includes('timeout')` never matched\n// `'timed out'`, which silently routed timeouts to the generic failure message.\nconst AUTHORIZATION_TIMEOUT_MESSAGE = 'Authorization timed out';\n\nexport function isAuthorizationTimeout(error: Error): boolean {\n return error.message === AUTHORIZATION_TIMEOUT_MESSAGE;\n}\n\ninterface OAuthConfig {\n scopes: string[];\n signup?: boolean;\n /** Project to pre-select on the consent screen (the `--project-id` flag). */\n projectId?: number;\n /**\n * Explicit base URL override (`--base-url`, from `session.baseUrl`). Pins the\n * OAuth server and selects the matching client ID.\n */\n baseUrl?: string;\n}\n\n/**\n * OAuth client ID for the current target. A pinned base URL (`--base-url`, or\n * IS_DEV's implicit localhost) means we're talking to a dev-seeded stack, which\n * registers the dev client; prod uses the proxy client.\n *\n * TODO: this assumes any pinned base URL is a dev-seeded instance that\n * registers POSTHOG_DEV_CLIENT_ID. If we ever point `--base-url` at a non-dev\n * instance with its own OAuth app, make the client ID configurable (e.g. a\n * `--oauth-client-id` flag) instead of always falling back to the dev client.\n */\nfunction getOAuthClientId(baseUrl?: string): string {\n return resolveBaseUrl(baseUrl)\n ? POSTHOG_DEV_CLIENT_ID\n : POSTHOG_PROXY_CLIENT_ID;\n}\n\nfunction getLocalOAuthOrigin(port: number): string {\n return `http://localhost:${port}`;\n}\n\nfunction getCallbackUrl(port: number): string {\n return `${getLocalOAuthOrigin(port)}/callback`;\n}\n\nfunction getLocalLoginUrl(port: number): string {\n return `${getLocalOAuthOrigin(port)}/authorize`;\n}\n\nfunction getLocalSignupUrl(port: number): string {\n return `${getLocalLoginUrl(port)}?signup=true`;\n}\n\n/**\n * Extract an OAuth authorization code from raw user input. Accepts either the\n * bare code, the full callback URL the browser was redirected to\n * (`http://localhost:8239/callback?code=abc123&...`), or just the query\n * string. Returns null when no code can be found.\n *\n * This backs the manual-entry fallback: in headless/remote environments the\n * browser can't reach the wizard's local callback server, so the user copies\n * the failed callback URL (or the code from it) back into the terminal.\n */\nexport function extractOAuthCode(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n\n // Full URL — pull the `code` query param.\n let looksLikeUrl = false;\n try {\n const url = new URL(trimmed);\n looksLikeUrl = true;\n const code = url.searchParams.get('code');\n if (code) return code;\n } catch {\n // Not a parseable URL — fall through to the looser checks below.\n }\n\n // A pasted query string or `code=...` fragment.\n const match = trimmed.match(/[?&]?code=([^&\\s]+)/);\n if (match) return decodeURIComponent(match[1]);\n\n // A URL with no code is invalid — don't mistake the whole URL for a code.\n if (looksLikeUrl) return null;\n\n // Otherwise treat the whole input as the bare code (no embedded whitespace).\n if (!/\\s/.test(trimmed)) return trimmed;\n\n return null;\n}\n\nfunction generateCodeVerifier(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return crypto.createHash('sha256').update(verifier).digest('base64url');\n}\n\nasync function startCallbackServer(\n authUrl: string,\n signupUrl: string,\n port: number,\n): Promise<{\n port: number;\n server: http.Server;\n waitForCallback: () => Promise<string>;\n}> {\n return new Promise((resolve, reject) => {\n let callbackResolve: (code: string) => void;\n let callbackReject: (error: Error) => void;\n\n const waitForCallback = () =>\n new Promise<string>((res, rej) => {\n callbackResolve = res;\n callbackReject = rej;\n });\n\n const server = http.createServer((req, res) => {\n if (!req.url) {\n res.writeHead(400);\n res.end();\n return;\n }\n const url = new URL(req.url, getLocalOAuthOrigin(port));\n\n if (url.pathname === '/authorize') {\n const isSignup = url.searchParams.get('signup') === 'true';\n const redirectUrl = isSignup ? signupUrl : authUrl;\n res.writeHead(302, { Location: redirectUrl });\n res.end();\n return;\n }\n\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n\n if (error) {\n // Carries error_description / error_uri (RFC 6749 §4.1.2.1) along\n // with the code, so the terminal message can show the server's own\n // explanation instead of just the bare code.\n const callbackError = oauthErrorFromCallbackParams(url.searchParams);\n const isAccessDenied = callbackError.code === 'access_denied';\n logToFile(\n `[oauth] callback received with error: ${callbackError.code}` +\n (callbackError.description\n ? ` (${callbackError.description})`\n : ''),\n );\n res.writeHead(isAccessDenied ? 200 : 400, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard - Authorization ${\n isAccessDenied ? 'cancelled' : 'failed'\n }</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n ${buildCallbackErrorHtml(callbackError)}\n <p>Return to your terminal. This window will close automatically.</p>\n <script>window.close();</script>\n </body>\n </html>\n `);\n callbackReject(callbackError);\n return;\n }\n\n if (code) {\n logToFile('[oauth] callback received with authorization code');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard is ready</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n <p>PostHog login complete!</p>\n <p>Return to your terminal: the wizard is hard at work on your project<span class=\"blink\">█</span></p>\n <script>window.close();</script>\n </body>\n </html>\n `);\n callbackResolve(code);\n } else {\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard - Invalid request</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n <p>Invalid request - no authorization code received.</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `);\n }\n });\n\n server.listen(port, () => {\n resolve({ port, server, waitForCallback });\n });\n\n server.on('error', reject);\n });\n}\n\nfunction getPortProcessInfo(port: number): {\n command: string;\n pid: string;\n port: number;\n user: string;\n} {\n try {\n const output = execSync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const lines = output.split('\\n');\n // First line is header, second is the process\n if (lines.length < 2)\n return { command: 'unknown', pid: 'unknown', port, user: 'unknown' };\n const fields = lines[1].split(/\\s+/);\n // lsof columns: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n const command = fields[0] ?? 'unknown';\n const pid = fields[1] ?? 'unknown';\n const user = fields[2] ?? 'unknown';\n return { command, pid, port, user };\n } catch {\n return { command: 'unknown', pid: 'unknown', port, user: 'unknown' };\n }\n}\n\nfunction isPortInUseError(error: unknown): boolean {\n return (\n error instanceof Error &&\n 'code' in error &&\n (error as NodeJS.ErrnoException).code === 'EADDRINUSE'\n );\n}\n\nasync function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n callbackUrl: string,\n baseUrl?: string,\n): Promise<OAuthTokenResponse> {\n const clientId = getOAuthClientId(baseUrl);\n const oauthUrl = getOAuthUrl(baseUrl);\n\n logToFile(`[oauth] exchanging code for token at ${oauthUrl}/oauth/token`);\n let response;\n try {\n response = await axios.post(\n `${oauthUrl}/oauth/token`,\n {\n grant_type: 'authorization_code',\n code,\n redirect_uri: callbackUrl,\n client_id: clientId,\n code_verifier: codeVerifier,\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': WIZARD_USER_AGENT,\n },\n },\n );\n } catch (e) {\n const status = axios.isAxiosError(e) ? e.response?.status : undefined;\n logToFile(\n `[oauth] token exchange failed${status ? ` (HTTP ${status})` : ''}:`,\n e instanceof Error ? e.message : e,\n );\n // Surface the OAuth error body (RFC 6749 §5.2) when the token endpoint\n // sent one — otherwise `invalid_grant`, PKCE mismatches, etc. reach the\n // user as a bare axios \"Request failed with status code 400\".\n const exchangeError = axios.isAxiosError(e)\n ? oauthErrorFromTokenBody(e.response?.data)\n : null;\n if (exchangeError) {\n logToFile(\n `[oauth] token endpoint error: ${exchangeError.code}` +\n (exchangeError.description ? ` (${exchangeError.description})` : ''),\n );\n throw exchangeError;\n }\n throw e;\n }\n\n const token = OAuthTokenResponseSchema.parse(response.data);\n logToFile(\n `[oauth] token exchange succeeded, granted scopes: ${token.scope}` +\n `${token.posthog_region ? `, region: ${token.posthog_region}` : ''}` +\n `${\n token.scoped_teams\n ? `, scoped_teams: [${token.scoped_teams.join(', ')}]`\n : ''\n }` +\n `${\n token.scoped_organizations\n ? `, scoped_organizations: ${token.scoped_organizations.length}`\n : ''\n }`,\n );\n return token;\n}\n\nexport async function performOAuthFlow(\n config: OAuthConfig,\n): Promise<OAuthTokenResponse> {\n const clientId = getOAuthClientId(config.baseUrl);\n const oauthUrl = getOAuthUrl(config.baseUrl);\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n let shouldRetry = false;\n\n logToFile(\n `[oauth] starting flow against ${oauthUrl}, ` +\n `requested scopes: ${config.scopes.join(' ')}`,\n );\n\n do {\n shouldRetry = false;\n let lastProcessInfo: {\n command: string;\n pid: string;\n port: number;\n user: string;\n } | null = null;\n\n for (const port of OAUTH_PORTS) {\n const callbackUrl = getCallbackUrl(port);\n const authUrl = new URL(`${oauthUrl}/oauth/authorize`);\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', callbackUrl);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('scope', config.scopes.join(' '));\n authUrl.searchParams.set('required_access_level', 'project');\n if (config.projectId !== undefined) {\n // Pre-select this project on the consent screen so the user just clicks Authorize.\n authUrl.searchParams.set('team_id', String(config.projectId));\n }\n\n // UTM-tag both kickoff URLs so the journey into the app is\n // attributable to the wizard command that started it.\n const taggedAuthUrl = withUtm(authUrl.toString(), 'oauth-authorize');\n const signupUrl = new URL(\n withUtm(\n `${oauthUrl}/signup?next=${encodeURIComponent(taggedAuthUrl)}`,\n 'oauth-signup',\n ),\n );\n const localSignupUrl = getLocalSignupUrl(port);\n const localLoginUrl = getLocalLoginUrl(port);\n const urlToOpen = config.signup ? localSignupUrl : localLoginUrl;\n\n logToFile(`[oauth] attempting callback server on port ${port}`);\n\n let server: http.Server;\n let waitForCallback: () => Promise<string>;\n try {\n ({ server, waitForCallback } = await startCallbackServer(\n taggedAuthUrl,\n signupUrl.toString(),\n port,\n ));\n } catch (e) {\n if (!isPortInUseError(e)) throw e;\n lastProcessInfo = getPortProcessInfo(port);\n continue;\n }\n\n logToFile('[oauth] callback server ready, showing login URL');\n\n getUI().setLoginUrl(urlToOpen);\n // The localhost proxy above only works on this machine. Surface the\n // direct PostHog authorize URL too, for the manual-paste modal — on a\n // remote/headless box the user opens it from another machine, where\n // localhost:<port> is unreachable.\n getUI().setAuthorizeUrl(\n config.signup ? signupUrl.toString() : taggedAuthUrl,\n );\n\n // The localhost proxy URL stays untagged — the PostHog destination\n // it redirects to carries the UTMs.\n openTrackedLink(urlToOpen, 'oauth', { auto: true, skipUtm: true });\n\n const loginSpinner = getUI().spinner();\n loginSpinner.start('Waiting for authorization...');\n\n try {\n // Race the local callback server against a manually-pasted code. The\n // manual path is the fallback for headless/remote shells where the\n // browser can't reach localhost — the user opens the auth screen's\n // paste modal and submits the callback URL or code by hand.\n const code = await Promise.race([\n waitForCallback(),\n getUI().waitForManualAuthCode(),\n new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new Error(AUTHORIZATION_TIMEOUT_MESSAGE)),\n OAUTH_TIMEOUT_MS,\n ),\n ),\n ]);\n\n const token = await exchangeCodeForToken(\n code,\n codeVerifier,\n callbackUrl,\n config.baseUrl,\n );\n\n server.close();\n getUI().setLoginUrl(null);\n getUI().setAuthorizeUrl(null);\n loginSpinner.stop('Authorization complete!');\n\n return token;\n } catch (e) {\n const error = e instanceof Error ? e : new Error('Unknown error');\n const timedOut = isAuthorizationTimeout(error);\n const flowError = error instanceof OAuthError ? error : null;\n\n loginSpinner.stop(\n timedOut ? 'Session timed out.' : 'Authorization failed.',\n );\n server.close();\n\n logToFile('[oauth] flow failed:', error);\n if (flowError?.description) {\n logToFile(\n `[oauth] server error_description: ${flowError.description}`,\n );\n }\n\n const accessDenied = flowError\n ? flowError.code === 'access_denied'\n : error.message.includes('access_denied');\n\n if (timedOut) {\n // Overlay bypasses the auth-step gating (which never completes\n // without credentials), so the user sees the failure instead of a\n // spinner that never stops; any key exits.\n getUI().showSessionTimeout();\n } else if (accessDenied) {\n getUI().log.info(\n `Authorization was cancelled.\\n\\nYou denied access to PostHog. To use the wizard, you need to authorize access to your PostHog account.\\n\\nYou can try again by re-running the wizard.`,\n );\n } else {\n getUI().log.error(\n buildOAuthFailureMessage({\n error,\n requestedScopes: config.scopes,\n clientId,\n oauthUrl,\n // Same condition that selects the dev client ID: a resolvable\n // base URL means a dev-seeded stack, where \"fix your local\n // OAuth app\" beats pointing at the production runbook.\n isDevStack: resolveBaseUrl(config.baseUrl) !== undefined,\n }),\n );\n }\n\n const oauthErrorCode = flowError\n ? flowError.code\n : error.message.startsWith('OAuth error: ')\n ? error.message.slice('OAuth error: '.length)\n : timedOut\n ? 'timeout'\n : 'unknown';\n\n analytics.captureException(error, {\n step: 'oauth_flow',\n oauth_error_code: oauthErrorCode,\n oauth_error_description: flowError?.description,\n client_id: clientId,\n requested_scopes: config.scopes.join(' '),\n // Collapse OAuth callback failures of the same kind into one issue\n // instead of fragmenting by each user's install path in the stack trace.\n $exception_fingerprint: `wizard_oauth_${oauthErrorCode}`,\n });\n\n await abort();\n throw error;\n }\n }\n\n if (!lastProcessInfo) {\n throw new Error('No OAuth callback ports configured');\n }\n\n await getUI().showPortConflict(lastProcessInfo);\n shouldRetry = true;\n } while (shouldRetry);\n\n throw new Error('OAuth port retry loop exited unexpectedly');\n}\n","export type GrantedProjectResolution =\n | { ok: true; projectId: number | undefined }\n | { ok: false; requested: number; granted: number };\n\n/**\n * Decide which project to use after OAuth. `--project-id` (when passed) is authoritative,\n * but only when the user granted access to it on the consent screen; a different grant is a\n * mismatch the caller must surface. With no `--project-id` this returns the granted project\n * (`scopedTeams[0]`), preserving the pre-flag behavior for every wizard program (the main\n * integration flow included).\n */\nexport function resolveGrantedProject(\n requestedProjectId: number | undefined,\n scopedTeams: number[] | undefined,\n): GrantedProjectResolution {\n // No --project-id: use whatever the user granted, exactly as before the flag existed.\n if (requestedProjectId === undefined) {\n return { ok: true, projectId: scopedTeams?.[0] };\n }\n // --project-id was among the granted teams: honor it.\n if (scopedTeams?.includes(requestedProjectId)) {\n return { ok: true, projectId: requestedProjectId };\n }\n // --project-id requested but a different project was authorized: mismatch.\n if (scopedTeams && scopedTeams.length > 0) {\n return {\n ok: false,\n requested: requestedProjectId,\n granted: scopedTeams[0],\n };\n }\n // Nothing granted at all — let the caller's \"no project access\" guard handle it.\n return { ok: true, projectId: undefined };\n}\n","import {\n major,\n minVersion,\n satisfies,\n subset,\n valid,\n validRange,\n} from 'semver';\n\n/**\n * Version strings from package.json that are not semver ranges.\n * URLs, git refs, dist-tags, local paths, workspace protocol, npm aliases, etc.\n * These should be rejected early — we can't determine a clear version from them.\n */\nfunction isNonSemverVersion(version: string): boolean {\n const v = version.trim();\n return (\n v === '' ||\n v.startsWith('http://') ||\n v.startsWith('https://') ||\n v.startsWith('git+') ||\n v.startsWith('git://') ||\n v.startsWith('file:') ||\n v.startsWith('npm:') ||\n v.startsWith('workspace:') ||\n v.startsWith('/') ||\n v.includes('/') // user/repo shorthand\n );\n}\n\nexport function versionSatisfiesRange({\n version,\n acceptableVersions,\n canBeLatest,\n}: {\n version: string;\n acceptableVersions: string;\n canBeLatest: boolean;\n}): boolean {\n if (version === 'latest') return canBeLatest;\n if (isNonSemverVersion(version)) return false;\n\n const concrete = valid(version);\n if (concrete !== null) {\n return satisfies(concrete, acceptableVersions);\n }\n\n const userRange = validRange(version);\n if (userRange === null) return false;\n return subset(userRange, acceptableVersions);\n}\n\n/**\n * Creates a version bucket function for analytics.\n * Converts versions like \"1.2.3\" to \"1.x\" for grouping in analytics.\n *\n * @param minMajorVersion - Optional minimum major version threshold.\n * Versions below this will be bucketed as \"<{min}.0.0\"\n *\n * @example\n * const getVersionBucket = createVersionBucket(); // no minimum\n * getVersionBucket(\"1.2.3\") // \"1.x\"\n *\n * const getVersionBucket = createVersionBucket(11);\n * getVersionBucket(\"15.3.0\") // \"15.x\"\n * getVersionBucket(\"10.0.0\") // \"<11.0.0\"\n */\nexport function createVersionBucket(minMajorVersion?: number) {\n return (version: string | undefined): string => {\n if (!version) {\n return 'none';\n }\n\n if (isNonSemverVersion(version)) {\n return 'unknown';\n }\n\n try {\n const minVer = minVersion(version);\n if (!minVer) {\n return 'invalid';\n }\n const majorVersion = major(minVer);\n if (minMajorVersion !== undefined && majorVersion < minMajorVersion) {\n return `<${minMajorVersion}.0.0`;\n }\n return `${majorVersion}.x`;\n } catch {\n return 'unknown';\n }\n };\n}\n","import * as childProcess from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport { basename, isAbsolute, join, relative } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { withProgress } from '../telemetry';\nimport { debug, logToFile } from './debug';\nimport type { PackageJson } from './package-json';\nimport {\n type PackageManager,\n detectAllPackageManagers,\n NPM as npm,\n} from './package-manager';\nimport type { CloudRegion, WizardRunOptions } from './types';\nimport { getDeclaredVersion } from './package-json';\nimport { DUMMY_PROJECT_API_KEY, ISSUES_URL } from '@lib/constants';\nimport { getOAuthScopesForProgram } from '@lib/oauth/program-scopes';\nimport type { ProgramId } from '@lib/programs/program-registry';\nimport { analytics } from './analytics';\nimport { getUI } from '@ui';\nimport { HostResolution } from '@lib/host-resolution';\nimport { assertWizardCompletionScope, performOAuthFlow } from './oauth';\nimport { resolveGrantedProject } from './project-resolution';\nimport { provisionNewAccount } from './provisioning';\nimport {\n fetchUserData,\n fetchProjectData,\n type ApiUser,\n type ApiProject,\n} from '@lib/api';\nimport { versionSatisfiesRange } from './semver';\nimport { wizardAbort } from './wizard-abort';\n\ninterface ProjectData {\n projectApiKey: string;\n accessToken: string;\n host: HostResolution;\n distinctId: string;\n projectId: number;\n /**\n * Optional `role_at_organization` from `/api/users/@me/`. Drives the\n * role-tailored prompt suggestions on the McpSuggestedPromptsScreen. Null\n * for signup flows (no role picked yet) and older accounts.\n */\n roleAtOrganization?: string | null;\n /**\n * Full user payload from `/api/users/@me/`. Carried through so\n * `getOrAskForProjectData` can forward it to the session as\n * `session.apiUser`. Null when the request failed or the CI key\n * lacked permissions.\n */\n user?: ApiUser | null;\n /**\n * Full project payload from `/api/projects/:id/`. Carries the team's\n * product opt-ins (replay, exception autocapture, surveys) so prompts\n * can state project-level product enablement instead of agents\n * inferring it from repo evidence. Null on signup flows.\n */\n project?: ApiProject | null;\n}\n\nexport interface CliSetupConfig {\n filename: string;\n name: string;\n gitignore: boolean;\n\n likelyAlreadyHasAuthToken(contents: string): boolean;\n tokenContent(authToken: string): string;\n\n likelyAlreadyHasOrgAndProject(contents: string): boolean;\n orgAndProjContent(org: string, project: string): string;\n\n likelyAlreadyHasUrl?(contents: string): boolean;\n urlContent?(url: string): string;\n}\n\nexport interface CliSetupConfigContent {\n authToken: string;\n org?: string;\n project?: string;\n url?: string;\n}\n\n/** @deprecated Use wizardAbort() directly for new code. */\nexport async function abort(message?: string, status?: number): Promise<never> {\n return wizardAbort({ message, exitCode: status });\n}\n\nexport function isInGitRepo(): boolean {\n try {\n childProcess.execSync('git rev-parse --show-toplevel', {\n stdio: 'ignore',\n });\n } catch {\n return false;\n }\n return true;\n}\n\nconst FREEMAIL_DOMAINS = new Set([\n 'gmail.com',\n 'googlemail.com',\n 'hotmail.com',\n 'outlook.com',\n 'yahoo.com',\n 'icloud.com',\n 'me.com',\n 'mail.com',\n 'protonmail.com',\n 'proton.me',\n 'live.com',\n 'aol.com',\n 'yandex.com',\n 'zoho.com',\n 'gmx.com',\n 'fastmail.com',\n]);\n\nfunction parseGitRemote(): { org: string; repo: string } | null {\n try {\n const url = childProcess\n .execSync('git remote get-url origin', {\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim();\n // git@github.com:acme-corp/my-app.git or https://github.com/acme-corp/my-app.git\n const match = url.match(/[/:]([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/);\n if (match) return { org: match[1], repo: match[2] };\n } catch {\n // not in a git repo or no remote\n }\n return null;\n}\n\nexport function detectOrgAndProject(email: string): {\n orgName: string | undefined;\n projectName: string | undefined;\n} {\n const remote = parseGitRemote();\n\n // Project name: git repo name > directory name\n const projectName = remote?.repo || basename(process.cwd()) || undefined;\n\n // Org name: git remote org > email domain (skip freemail)\n let orgName: string | undefined;\n if (remote?.org) {\n orgName = remote.org;\n } else {\n const domain = email.split('@')[1]?.toLowerCase();\n if (domain && !FREEMAIL_DOMAINS.has(domain)) {\n orgName = domain.split('.')[0];\n }\n }\n\n return { orgName, projectName };\n}\n\nexport function getUncommittedOrUntrackedFiles(): string[] {\n let gitStatus: string;\n try {\n gitStatus = childProcess\n .execSync('git status --porcelain=v1', {\n // we only care about stdout\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString();\n } catch {\n return [];\n }\n\n const result: string[] = [];\n for (const rawLine of gitStatus.split(os.EOL)) {\n const line = rawLine.trim();\n if (!line) continue;\n const match = /^\\S+\\s+(\\S+)/.exec(line);\n result.push(`- ${match?.[1]}`);\n }\n return result;\n}\n\nexport async function isReact19Installed({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<boolean> {\n try {\n const packageJson = await tryGetPackageJson({ installDir });\n if (!packageJson) return false;\n const reactVersion = getDeclaredVersion('react', packageJson);\n\n if (!reactVersion) {\n return false;\n }\n\n return versionSatisfiesRange({\n version: reactVersion,\n acceptableVersions: '>=19.0.0',\n canBeLatest: true,\n });\n } catch {\n return false;\n }\n}\n\n/**\n * Installs or updates a package with the user's package manager.\n *\n * IMPORTANT: This function modifies the `package.json`! Be sure to re-read\n * it if you make additional modifications to it after calling this function!\n */\nexport async function installPackage({\n packageName,\n alreadyInstalled,\n packageNameDisplayLabel,\n packageManager,\n integration,\n installDir,\n}: {\n packageName: string;\n alreadyInstalled: boolean;\n packageNameDisplayLabel?: string;\n packageManager?: PackageManager;\n integration?: string;\n installDir: string;\n}): Promise<{ packageManager?: PackageManager }> {\n return withProgress('install-package', async () => {\n const sdkInstallSpinner = getUI().spinner();\n\n const pkgManager =\n packageManager || (await getPackageManager({ installDir }));\n\n const isReact19 = await isReact19Installed({ installDir });\n const legacyPeerDepsFlag =\n isReact19 && pkgManager.name === 'npm' ? '--legacy-peer-deps' : '';\n\n sdkInstallSpinner.start(\n `${alreadyInstalled ? 'Updating' : 'Installing'} ${\n packageNameDisplayLabel ?? packageName\n } with ${pkgManager.label}.`,\n );\n\n const execAsync = promisify(childProcess.exec);\n const installCommand =\n `${pkgManager.installCommand} ${packageName} ${pkgManager.flags} ${legacyPeerDepsFlag}`.trim();\n\n try {\n await execAsync(installCommand, { cwd: installDir });\n } catch (e) {\n const { stdout = '', stderr = '' } = (e ?? {}) as {\n stdout?: string;\n stderr?: string;\n };\n fs.writeFileSync(\n join(\n process.cwd(),\n `posthog-wizard-installation-error-${Date.now()}.log`,\n ),\n JSON.stringify({ stdout, stderr }),\n { encoding: 'utf8' },\n );\n sdkInstallSpinner.stop('Installation failed.');\n getUI().log.error(\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `Encountered the following error during installation:\\n\\n${e}\\n\\nThe wizard has created a \\`posthog-wizard-installation-error-*.log\\` file. If you think this issue is caused by the PostHog wizard, create an issue on GitHub and include the log file's content:\\n${ISSUES_URL}`,\n );\n await abort();\n }\n\n sdkInstallSpinner.stop(\n `${alreadyInstalled ? 'Updated' : 'Installed'} ${\n packageNameDisplayLabel ?? packageName\n } with ${pkgManager.label}.`,\n );\n\n analytics.wizardCapture('package installed', {\n package_name: packageName,\n package_manager: pkgManager.name,\n integration,\n });\n\n return { packageManager: pkgManager };\n });\n}\n\n/**\n * Get package.json or abort the wizard if not found.\n * Only use where package.json is required (e.g., package install, overrides).\n * For detection/version-checks, use tryGetPackageJson() instead.\n */\nexport async function getPackageDotJson({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<PackageJson> {\n const pkgPath = join(installDir, 'package.json');\n\n let raw: string;\n try {\n raw = await fs.promises.readFile(pkgPath, 'utf8');\n } catch {\n getUI().log.error(\n 'Could not find package.json. Make sure to run the wizard in the root of your app!',\n );\n await abort();\n return {};\n }\n\n try {\n const parsed = JSON.parse(raw) as PackageJson | null;\n return parsed ?? {};\n } catch {\n getUI().log.error(\n `Unable to parse your package.json. Make sure it has a valid format!`,\n );\n await abort();\n return {};\n }\n}\n\n/**\n * Try to get package.json, returning null if it doesn't exist.\n * Use this for detection purposes where missing package.json is expected (e.g., Python projects).\n */\nexport async function tryGetPackageJson({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<PackageJson | null> {\n try {\n const packageJsonFileContents = await fs.promises.readFile(\n join(installDir, 'package.json'),\n 'utf8',\n );\n return JSON.parse(packageJsonFileContents) as PackageJson;\n } catch {\n return null;\n }\n}\n\nexport async function updatePackageDotJson(\n packageDotJson: PackageJson,\n { installDir }: Pick<WizardRunOptions, 'installDir'>,\n): Promise<void> {\n const pkgPath = join(installDir, 'package.json');\n const serialized = JSON.stringify(packageDotJson, null, 2);\n\n try {\n await fs.promises.writeFile(pkgPath, serialized, {\n encoding: 'utf8',\n flag: 'w',\n });\n return;\n } catch {\n getUI().log.error(`Unable to update your package.json.`);\n await abort();\n }\n}\n\n/**\n * Detect and return the package manager. Pure — no prompts.\n * Falls back to first detected or npm if ambiguous.\n */\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function getPackageManager(\n options: Pick<WizardRunOptions, 'installDir'> & { ci?: boolean },\n): Promise<PackageManager> {\n const detectedPackageManagers = detectAllPackageManagers({\n installDir: options.installDir,\n });\n\n if (detectedPackageManagers.length >= 1) {\n const selected = detectedPackageManagers[0];\n analytics.setTag('package-manager', selected.name);\n return selected;\n }\n\n // No package manager detected — default to npm\n analytics.setTag('package-manager', npm.name);\n return npm;\n}\n\nexport function isUsingTypeScript({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): boolean {\n try {\n fs.accessSync(join(installDir, 'tsconfig.json'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get project data for the wizard via OAuth or CI API key.\n */\nexport async function getOrAskForProjectData(\n _options: Pick<WizardRunOptions, 'signup' | 'ci' | 'apiKey' | 'projectId'> & {\n email?: string;\n region?: CloudRegion;\n /** Explicit base URL override (`--base-url`, from `session.baseUrl`). When\n * set, pins every PostHog origin and bypasses region resolution. */\n baseUrl?: string;\n /** `--local-mcp`: forwarded into the resolved host so `host.mcpUrl` is local. */\n localMcp?: boolean;\n /** Optional — picks the OAuth scope set via\n * `getOAuthScopesForProgram`. Omitted → default\n * `WIZARD_OAUTH_SCOPES`. Threaded into `askForWizardLogin`. */\n programId?: ProgramId | null;\n },\n): Promise<{\n host: HostResolution;\n projectApiKey: string;\n accessToken: string;\n projectId: number;\n roleAtOrganization: string | null;\n user: ApiUser | null;\n project: ApiProject | null;\n}> {\n // CI mode: bypass OAuth, use personal API key for LLM gateway\n if (_options.ci && _options.apiKey) {\n getUI().log.info('Using provided API key (CI mode - OAuth bypassed)');\n\n const host = await HostResolution.fromAccessToken(_options.apiKey, {\n region: _options.region,\n localMcp: _options.localMcp,\n baseUrl: _options.baseUrl,\n });\n const cloudUrl = host.appHost;\n\n const projectData =\n _options.projectId != null\n ? await fetchProjectDataById(\n _options.apiKey,\n _options.projectId,\n cloudUrl,\n )\n : await fetchProjectDataWithApiKey(_options.apiKey, cloudUrl);\n\n // Best-effort user fetch — CI flows may run with project-scoped keys\n // that 403 on /api/users/@me/, so swallow errors and continue with\n // a null user (and null role).\n let user: ApiUser | null = null;\n let roleAtOrganization: string | null = null;\n try {\n user = await fetchUserData(_options.apiKey, cloudUrl);\n roleAtOrganization = user.role_at_organization ?? null;\n } catch (err) {\n logToFile(\n '[ci-auth] user lookup failed:',\n err instanceof Error ? err.message : String(err),\n );\n }\n if (user) {\n analytics.identifyUser(user);\n logToFile(\n '[ci-auth] identified via API key; flags evaluate as the key owner',\n );\n } else {\n getUI().log.warn(\n 'Could not resolve the API key user (key needs user:read scope) — feature flags evaluate anonymously; user-targeted flags will not match.',\n );\n }\n\n return {\n host,\n projectApiKey: projectData.api_token,\n accessToken: _options.apiKey,\n projectId: projectData.id,\n roleAtOrganization,\n user,\n project: projectData.project,\n };\n }\n\n const {\n host,\n projectApiKey,\n accessToken,\n projectId,\n roleAtOrganization,\n user,\n project,\n } = await withProgress('login', () =>\n askForWizardLogin({\n signup: _options.signup,\n email: _options.email,\n region: _options.region,\n baseUrl: _options.baseUrl,\n programId: _options.programId,\n projectId: _options.projectId,\n localMcp: _options.localMcp,\n }),\n );\n\n if (!projectApiKey) {\n const cloudUrl = host.appHost;\n getUI().log.error(`Didn't receive a project token. This shouldn't happen :(\n\nPlease let us know if you think this is a bug in the wizard:\n${ISSUES_URL}`);\n\n getUI().log\n .info(`In the meantime, we'll add a dummy project token (\"${DUMMY_PROJECT_API_KEY}\") for you to replace later.\nYou can find your project token here:\n${cloudUrl}/settings/project#variables`);\n }\n\n return {\n accessToken,\n host,\n projectApiKey: projectApiKey || DUMMY_PROJECT_API_KEY,\n projectId,\n roleAtOrganization: roleAtOrganization ?? null,\n user: user ?? null,\n project: project ?? null,\n };\n}\n\nasync function fetchProjectDataWithApiKey(\n apiKey: string,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number; project: ApiProject }> {\n const userData = await fetchUserData(apiKey, cloudUrl);\n const projectId = userData.team?.id;\n\n if (!projectId) {\n throw new Error(\n 'Could not determine project ID from API key. Please ensure your API key has access to a project in this cloud region.',\n );\n }\n\n const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);\n return {\n api_token: projectData.api_token,\n id: projectId,\n project: projectData,\n };\n}\n\nasync function fetchProjectDataById(\n apiKey: string,\n projectId: number,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number; project: ApiProject }> {\n const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);\n return {\n api_token: projectData.api_token,\n id: projectId,\n project: projectData,\n };\n}\n\nasync function askForWizardLogin(options: {\n signup: boolean;\n email?: string;\n region?: CloudRegion;\n /** Explicit base URL override (`--base-url`); pins every PostHog origin. */\n baseUrl?: string;\n /** Used to pick the right scope set via `getOAuthScopesForProgram`.\n * Omitted → default `WIZARD_OAUTH_SCOPES`. */\n programId?: ProgramId | null;\n /** `--project-id`, if passed. When the user granted access to it on the consent\n * screen we use it directly; otherwise we fall back to the first granted team. */\n projectId?: number;\n /** `--local-mcp`: forwarded into the resolved host so `host.mcpUrl` is local. */\n localMcp?: boolean;\n}): Promise<ProjectData> {\n if (options.signup) {\n return askForProvisioningSignup(\n options.email,\n options.region,\n options.baseUrl,\n options.localMcp,\n );\n }\n\n const tokenResponse = await performOAuthFlow({\n scopes: [...getOAuthScopesForProgram(options.programId)],\n signup: false,\n projectId: options.projectId,\n baseUrl: options.baseUrl,\n });\n\n try {\n assertWizardCompletionScope(tokenResponse.scope);\n } catch (error) {\n const scopeError =\n error instanceof Error ? error : new Error('OAuth scope check failed');\n analytics.captureException(scopeError, {\n step: 'wizard_login',\n missing_scope: 'event_definition:write',\n });\n getUI().log.error(scopeError.message);\n await abort();\n }\n\n // `--project-id`, when provided, is authoritative — but only if the user actually\n // granted access to it on the consent screen. If they authorized a different\n // project, fail loudly instead of silently capturing into the wrong one. With no\n // `--project-id` this falls back to the granted project, unchanged for every program.\n const resolution = resolveGrantedProject(\n options.projectId,\n tokenResponse.scoped_teams,\n );\n if (!resolution.ok) {\n const error = new Error(\n `You authorized project ${resolution.granted}, but setup is targeting project ${resolution.requested}. Re-run and grant access to project ${resolution.requested} on the authorization screen.`,\n );\n analytics.captureException(error, {\n step: 'wizard_login',\n requested_project_id: resolution.requested,\n granted_project_id: resolution.granted,\n });\n getUI().log.error(error.message);\n await abort();\n }\n\n const projectId = resolution.ok ? resolution.projectId : undefined;\n\n if (projectId === undefined) {\n const error = new Error(\n 'No project access granted. Please authorize with project-level access.',\n );\n analytics.captureException(error, {\n step: 'wizard_login',\n has_scoped_teams: !!tokenResponse.scoped_teams,\n });\n getUI().log.error(error.message);\n await abort();\n }\n\n // The issuing region comes with the token; the us/eu @me probe only runs when omitted.\n const host = await HostResolution.fromAccessToken(\n tokenResponse.access_token,\n {\n region: tokenResponse.posthog_region,\n localMcp: options.localMcp,\n baseUrl: options.baseUrl,\n },\n );\n const cloudUrl = host.appHost;\n\n const projectData = await fetchProjectData(\n tokenResponse.access_token,\n projectId!,\n cloudUrl,\n );\n const userData = await fetchUserData(tokenResponse.access_token, cloudUrl);\n\n const data = {\n accessToken: tokenResponse.access_token,\n projectApiKey: projectData.api_token,\n host,\n distinctId: userData.distinct_id,\n projectId: projectId!,\n roleAtOrganization: userData.role_at_organization ?? null,\n user: userData,\n project: projectData,\n };\n\n getUI().log.success('Login complete.');\n analytics.setTag('opened-wizard-link', true);\n analytics.identifyUser(userData);\n\n return data;\n}\n\nasync function askForProvisioningSignup(\n email?: string,\n region?: CloudRegion,\n baseUrl?: string,\n localMcp?: boolean,\n): Promise<ProjectData> {\n if (!email || !email.includes('@')) {\n getUI().log.error(\n 'Email is required for signup. Use --email your@email.com with --signup.',\n );\n await abort();\n throw new Error('unreachable');\n }\n\n const spinner = getUI().spinner();\n spinner.start('Creating your PostHog account...');\n\n try {\n const provisionRegion = (region ?? 'us').toUpperCase() as 'US' | 'EU';\n const { orgName, projectName } = detectOrgAndProject(email);\n const result = await provisionNewAccount(email, '', provisionRegion, {\n orgName,\n projectName,\n baseUrl,\n });\n\n spinner.stop('Account created!');\n getUI().log.success('Welcome to PostHog!');\n\n const host = HostResolution.fromApiHost(result.host, { localMcp });\n\n analytics.setTag('provisioning-signup', true);\n\n return {\n accessToken: result.accessToken,\n projectApiKey: result.projectApiKey,\n host,\n distinctId: email,\n projectId: parseInt(result.projectId, 10) || 0,\n };\n } catch (error) {\n spinner.stop('Account creation failed.');\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n if (message.includes('already associated')) {\n getUI().log.info(\n 'This email already has a PostHog account. Switching to login flow...',\n );\n\n return askForWizardLogin({ signup: false, baseUrl, localMcp });\n }\n\n getUI().log.error(`Failed to create account: ${message}`);\n analytics.captureException(\n error instanceof Error ? error : new Error(message),\n { step: 'provisioning_signup' },\n );\n await abort();\n throw error;\n }\n}\n\n/**\n * Creates a new config file with the given filepath and codeSnippet.\n */\nexport async function createNewConfigFile(\n filepath: string,\n codeSnippet: string,\n { installDir }: Pick<WizardRunOptions, 'installDir'>,\n moreInformation?: string,\n): Promise<boolean> {\n if (!isAbsolute(filepath)) {\n debug(`createNewConfigFile: filepath is not absolute: ${filepath}`);\n return false;\n }\n\n const prettyFilename = relative(installDir, filepath);\n\n try {\n await fs.promises.writeFile(filepath, codeSnippet);\n\n getUI().log.success(`Added new ${prettyFilename} file.`);\n\n if (moreInformation) {\n getUI().log.info(moreInformation);\n }\n\n return true;\n } catch (e) {\n debug(e);\n getUI().log.warn(\n `Could not create a new ${prettyFilename} file. Please create one manually and follow the instructions below.`,\n );\n }\n\n return false;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"task-CsAczwW6.js","names":[],"sources":["../src/lib/agent/runner/harness/pi/task.ts"],"sourcesContent":["/**\n * Orchestrator-mode execution on pi: one fresh pi session per unit of work —\n * the seed plan, or one drained task. The linear pipeline's concerns (skill\n * menu, todo panel, event-plan cleanup) stay in `index.ts`; this module builds\n * the leaner per-task session: gateway model, security fence, the task's\n * allowed coding tools, the wizard env tools, and the in-process orchestrator\n * queue tools.\n *\n * The task's `allowedTools` / `disallowedTools` arrive in the wizard's tool\n * vocabulary (`Read`, `Edit`, `Glob`, …, plus MCP-qualified orchestrator names\n * from `agentRunTools`). pi is where they become real: allowed names decide\n * which pi tool definitions get registered at all, and the disallow list is\n * ALSO handed to the security fence, so a name that never got registered stays\n * blocked even if the model hallucinates it.\n *\n * Loaded lazily from `index.ts` (typebox/ESM constraint, same as tools.ts).\n */\n\nimport { getUI } from '@ui';\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport { WIZARD_REMARK_EVENT_NAME, WIZARD_USER_AGENT } from '@lib/constants';\nimport { AgentErrorType } from '@lib/agent/agent-interface';\nimport { REMARK_INSTRUCTION } from '@lib/agent/signals';\nimport { AgentOutputSignals } from '@lib/agent/output-signals';\nimport { TaskStatus } from '../../sequence/orchestrator/queue';\nimport type { OrchestratorToolsContext } from '../../sequence/orchestrator/queue-tools';\nimport type { AgentResult, TaskRunInputs } from '../types';\nimport { buildGatewayProvider, GATEWAY_PROVIDER } from './gateway';\nimport {\n applyOutroMarkers,\n buildScrubbedEnv,\n extractText,\n lastStatusLine,\n withMode,\n} from './index';\n\n/** wizard tool vocabulary → the pi tool definitions it unlocks. */\nconst CODING_TOOL_MAP: Record<string, readonly string[]> = {\n Read: ['read'],\n Edit: ['edit'],\n Write: ['write'],\n Bash: ['bash'],\n Glob: ['find', 'ls'],\n Grep: ['grep'],\n};\n\n/** `mcp__posthog-wizard__enqueue_task` → `enqueue_task`; native names pass through. */\nfunction shortToolName(name: string): string {\n return name.replace(/^mcp__posthog-wizard__/, '');\n}\n\n/**\n * The pi coding tools a task may use. An empty allow list means \"no\n * restriction\" (mirrors the SDK), so every coding tool registers.\n */\nexport function allowedPiCodingTools(\n allowedTools: readonly string[] | undefined,\n): Set<string> {\n const allowed = (allowedTools ?? []).map(shortToolName);\n const names = allowed.length\n ? allowed.flatMap((name) => CODING_TOOL_MAP[name] ?? [])\n : Object.values(CODING_TOOL_MAP).flat();\n return new Set(names);\n}\n\n/**\n * The orchestrator queue tools this agent gets. Everything not disallowed:\n * the seed's frontmatter disallows `complete_task` (it is not a task), a\n * task's disallows `enqueue_task` (the seed owns the graph).\n */\nexport function allowedOrchestratorTools(\n disallowedTools: readonly string[] | undefined,\n): Set<string> {\n const disallowed = new Set((disallowedTools ?? []).map(shortToolName));\n return new Set(\n ['enqueue_task', 'complete_task', 'read_handoffs'].filter(\n (name) => !disallowed.has(name),\n ),\n );\n}\n\n/**\n * The disallow list for the security fence: the wizard-vocabulary names as\n * given (the fence translates pi built-ins to the same vocabulary) plus the\n * short names, so a disallowed orchestrator tool is blocked under the name pi\n * would call it by.\n */\nexport function fenceDisallowList(\n disallowedTools: readonly string[] | undefined,\n): string[] {\n const names = disallowedTools ?? [];\n return [...new Set([...names, ...names.map(shortToolName)])];\n}\n\n/** Nudges when the session returns without the work reaching a terminal state. */\nconst MAX_TASK_NUDGES = 3;\n\nconst TASK_NUDGE =\n 'You have not called complete_task yet. Finish your task now: if the work is done, call complete_task with your handoff; if it cannot be done, call it with status \"failed\" or \"not needed\" and say why.';\n\nconst SEED_NUDGE =\n 'The queue is still empty. Seed it now with enqueue_task calls for the task graph you planned.';\n\n/** Task-mode runtime notes — the harness constraints that survive into task mode. */\nfunction taskRuntimeNotes(opts: {\n bash: boolean;\n posthogMcp: boolean;\n}): string {\n const lines = [\n '## This runtime',\n 'Below are important guidance on the harness constraints you are bound to. Follow them as commandments.',\n '- When you need several INDEPENDENT operations — reading or searching multiple files — issue them as multiple tool calls in a SINGLE turn. They run in parallel and save round-trips. Only sequence calls when one needs a previous call’s output.',\n '- Explore with the `ls`, `find`, and `grep` tools. `read` is for FILES only — reading a directory errors. NEVER inspect files through `bash`.',\n '- If a tool call is blocked, do NOT retry it or a reworded variant — the fence is deterministic. Change approach or note it in your handoff and move on.',\n '- A `[YARA]` block from the security scanner caught a real problem in the edit you just tried (PII in a `capture()`, a hardcoded secret or host URL). Read the block reason and change the CODE to comply — e.g. move PII off the event and onto the person via `identify()`/`$set`. Never write a PostHog URL or token as a literal in source; read them from environment variables.',\n \"- To inspect or change a project's `.env` files use `check_env_keys` and `set_env_values` — a plain `read`, `edit`, or `write` of any `.env*` file is blocked.\",\n '- Status updates are PLAIN TEXT you write in your reply, NOT a tool call. When you begin a new action, put a line starting with the literal marker [STATUS] and a short present-tense phrase in the SAME turn as a tool call. Never send a turn that is ONLY a [STATUS] line — a turn with no tool call ends the run.',\n '- When you are done, call `complete_task` exactly once with your structured handoff, in the same turn as your closing words. Do not stop before calling it.',\n '- Name events in snake_case (e.g. todo_created), never with spaces.',\n ];\n if (opts.bash) {\n lines.push(\n '- `bash` is ONLY for install/build/typecheck/lint/format commands the project itself defines. Run commands BARE and synchronously: no `cd`, no `&`, `&&`, or pipes, no output redirection. Its full output is returned to you.',\n );\n }\n if (opts.posthogMcp) {\n lines.push(\n '- The PostHog dashboard and insight tools are in your tool list directly, named `posthog_<tool>` (e.g. `posthog_dashboard-create`, `posthog_insight-create`). Use the ones present in your tool list; do not guess names.',\n );\n }\n return lines.join('\\n');\n}\n\n/** Whether this unit of work has reached its terminal state. */\nfunction isSettled(ctx: OrchestratorToolsContext): boolean {\n if (!ctx.currentTaskId) {\n // The seed's job is a seeded queue, not a complete_task call.\n return ctx.store.list().length > 0;\n }\n const task = ctx.store.get(ctx.currentTaskId);\n return (\n !!task &&\n (task.status === TaskStatus.Done ||\n task.status === TaskStatus.Failed ||\n task.status === TaskStatus.Skipped)\n );\n}\n\nexport async function runPiTask(inputs: TaskRunInputs): Promise<AgentResult> {\n const {\n session,\n boot,\n prompt,\n spinner,\n model: modelId,\n effort,\n allowedTools,\n disallowedTools,\n orchestrator,\n spinnerMessage,\n successMessage,\n errorMessage,\n requestRemark,\n analyticsProperties,\n } = inputs;\n\n if (spinnerMessage) spinner.start(spinnerMessage);\n\n const startTime = Date.now();\n const signals = new AgentOutputSignals();\n let assistantTurns = 0;\n const runDurations = () => {\n const durationMs = Date.now() - startTime;\n return {\n duration_ms: durationMs,\n duration_seconds: Math.round(durationMs / 1000),\n };\n };\n const captureAborted = () =>\n analytics.wizardCapture('agent aborted', {\n ...runDurations(),\n model: modelId,\n ...analyticsProperties,\n });\n\n try {\n const sdk = await import('@earendil-works/pi-coding-agent');\n const {\n createAgentSession,\n DefaultResourceLoader,\n SessionManager,\n AuthStorage,\n ModelRegistry,\n getAgentDir,\n createLsToolDefinition,\n createFindToolDefinition,\n createGrepToolDefinition,\n createBashToolDefinition,\n createReadToolDefinition,\n createEditToolDefinition,\n createWriteToolDefinition,\n } = sdk;\n\n const { provider, caps, gatewayUrl } = buildGatewayProvider({\n gatewayUrl: boot.credentials.host.gatewayUrl,\n accessToken: boot.credentials.accessToken,\n wizardMetadata: boot.wizardMetadata,\n wizardFlags: boot.wizardFlags,\n modelId,\n // Per-task agents own their effort via the prompt frontmatter, falling\n // back to the model table.\n effort,\n });\n const registry = ModelRegistry.inMemory(AuthStorage.create());\n registry.registerProvider(GATEWAY_PROVIDER, provider as never);\n const model = registry.find(GATEWAY_PROVIDER, modelId);\n if (!model) {\n return {\n error: AgentErrorType.API_ERROR,\n message: 'pi: gateway model could not be resolved',\n };\n }\n\n // The same fail-closed fence as the linear run, with the task's disallow\n // list layered in (both the wizard-vocabulary and pi-short names).\n const { createSecurityExtension } = await import('./security');\n const security = createSecurityExtension({\n disallowedTools: fenceDisallowList(disallowedTools),\n triageAuth: {\n baseURL: gatewayUrl,\n authToken: boot.credentials.accessToken,\n },\n });\n const { prewarmYaraScanner } = await import('@lib/yara-hooks');\n void prewarmYaraScanner();\n\n // PostHog MCP, best-effort — the dashboard task creates real dashboards\n // through it; every other task simply never calls a posthog_* tool.\n const extensionFactories = [security.factory] as Array<\n (pi: unknown) => void\n >;\n let mcpCleanup: (() => void) | undefined;\n let posthogMcp = false;\n try {\n const { setupPostHogMcp } = await import('./mcp');\n const mcp = await setupPostHogMcp({\n agentDir: getAgentDir(),\n mcpUrl: boot.credentials.host.mcpUrl,\n accessToken: boot.credentials.accessToken,\n userAgent: WIZARD_USER_AGENT,\n });\n extensionFactories.push(mcp.extensionFactory);\n mcpCleanup = mcp.cleanup;\n posthogMcp = true;\n } catch (err) {\n logToFile(`[pi-task] PostHog MCP setup skipped: ${String(err)}`);\n }\n\n const codingTools = allowedPiCodingTools(allowedTools);\n const orchestratorTools = allowedOrchestratorTools(disallowedTools);\n\n const { getWizardCommandments } = await import('@lib/agent/commandments');\n const resourceLoader = new DefaultResourceLoader({\n cwd: session.installDir,\n agentDir: getAgentDir(),\n systemPrompt:\n getWizardCommandments() +\n '\\n' +\n taskRuntimeNotes({ bash: codingTools.has('bash'), posthogMcp }),\n noExtensions: true,\n noSkills: true,\n noContextFiles: true,\n noPromptTemplates: true,\n noThemes: true,\n extensionFactories,\n });\n await resourceLoader.reload();\n\n // The task's coding tools, gated by its allow list. Reads and searches run\n // in parallel; mutating tools stay sequential. Bash subprocesses get the\n // scrubbed env, same as the linear run.\n const dir = session.installDir;\n const codingToolFactories = {\n read: () => withMode(createReadToolDefinition(dir), 'parallel'),\n edit: () => withMode(createEditToolDefinition(dir), 'sequential'),\n write: () => withMode(createWriteToolDefinition(dir), 'sequential'),\n bash: () =>\n withMode(\n createBashToolDefinition(dir, {\n spawnHook: (ctx) => ({ ...ctx, env: buildScrubbedEnv() }),\n }),\n 'sequential',\n ),\n ls: () => withMode(createLsToolDefinition(dir), 'parallel'),\n find: () => withMode(createFindToolDefinition(dir), 'parallel'),\n grep: () => withMode(createGrepToolDefinition(dir), 'parallel'),\n } as const;\n const codingToolDefs = Object.entries(codingToolFactories)\n .filter(([name]) => codingTools.has(name))\n .map(([, make]) => make());\n\n // Wizard env + package-manager tools are always on — their handlers are\n // fenced, and init/build tasks depend on them.\n const { createWizardPiTools } = await import('./tools');\n const wizardTools = createWizardPiTools({\n workingDirectory: dir,\n skillsBaseUrl: boot.skillsBaseUrl,\n }).filter((t) =>\n ['check_env_keys', 'set_env_values', 'detect_package_manager'].includes(\n t.name,\n ),\n );\n\n const { createPiOrchestratorTools } = await import('./orchestrator-tools');\n const queueTools = createPiOrchestratorTools(orchestrator).filter((t) =>\n orchestratorTools.has(t.name),\n );\n\n const { session: agentSession } = await createAgentSession({\n model,\n modelRegistry: registry,\n thinkingLevel: caps.thinkingLevel,\n cwd: dir,\n sessionManager: SessionManager.inMemory(dir),\n resourceLoader,\n noTools: 'builtin',\n customTools: [...codingToolDefs, ...wizardTools, ...queueTools],\n });\n await agentSession.bindExtensions({});\n\n const unsubscribe = agentSession.subscribe((event) => {\n switch (event.type) {\n case 'message_end': {\n // User prompts also emit message_end; only assistant turns count.\n if ((event.message as { role?: string })?.role !== 'assistant') {\n break;\n }\n assistantTurns += 1;\n const assistant = extractText(event.message).trim();\n if (assistant) {\n logToFile(`[pi-task] assistant: ${assistant.slice(0, 1000)}`);\n applyOutroMarkers(assistant);\n const statusText = lastStatusLine(assistant);\n if (statusText) {\n getUI().pushStatus(statusText);\n spinner.message(statusText);\n }\n for (const line of assistant.split('\\n')) signals.push(line);\n }\n break;\n }\n case 'tool_execution_start': {\n const args = JSON.stringify(event.args ?? {}).slice(0, 200);\n logToFile(`[pi-task] → ${event.toolName} ${args}`);\n break;\n }\n case 'tool_execution_end': {\n if (event.isError) {\n logToFile(\n `[pi-task] ✗ ${event.toolName}: ${String(event.result).slice(\n 0,\n 300,\n )}`,\n );\n }\n break;\n }\n default:\n break;\n }\n });\n\n try {\n await agentSession.prompt(prompt);\n\n // pi's prompt() resolves the moment a turn carries no tool call — which\n // an agent mid-plan does emit. While the work has not reached its\n // terminal state (task not reported, seed queue still empty), nudge.\n let nudges = 0;\n while (\n nudges < MAX_TASK_NUDGES &&\n !security.state.criticalViolation &&\n !isSettled(orchestrator)\n ) {\n nudges += 1;\n logToFile(\n `[pi-task] completion guard: not settled, nudge ${nudges}/${MAX_TASK_NUDGES}`,\n );\n await agentSession.prompt(\n orchestrator.currentTaskId ? TASK_NUDGE : SEED_NUDGE,\n );\n }\n\n if (requestRemark && !security.state.criticalViolation) {\n try {\n await agentSession.prompt(REMARK_INSTRUCTION);\n } catch (err) {\n logToFile(`[pi-task] remark request failed: ${String(err)}`);\n }\n }\n } finally {\n unsubscribe();\n mcpCleanup?.();\n }\n\n if (security.state.criticalViolation) {\n spinner.stop('Security violation detected');\n logToFile(\n `[pi-task] terminated: YARA violation (blocked ${security.state.blockedCount} call(s))`,\n );\n captureAborted();\n return { error: AgentErrorType.YARA_VIOLATION };\n }\n\n const remark = signals.remark();\n if (remark) {\n analytics.capture(WIZARD_REMARK_EVENT_NAME, { remark });\n }\n\n const stats = agentSession.getSessionStats();\n const durations = runDurations();\n analytics.wizardCapture('agent completed', {\n ...durations,\n model: modelId,\n num_turns: assistantTurns,\n input_tokens: stats.tokens.input,\n output_tokens: stats.tokens.output,\n cache_creation_input_tokens: stats.tokens.cacheWrite,\n cache_read_input_tokens: stats.tokens.cacheRead,\n ...analyticsProperties,\n });\n // Per-task usage on one parseable line so a run's per-task time and cost are\n // observable from the log, not only from analytics.\n const taskType =\n typeof (analyticsProperties as { task_type?: unknown })?.task_type ===\n 'string'\n ? (analyticsProperties as { task_type: string }).task_type\n : modelId;\n logToFile(\n `[pi-task] usage task=${taskType} model=${modelId} dur=${durations.duration_seconds}s turns=${assistantTurns} in=${stats.tokens.input} out=${stats.tokens.output} cacheR=${stats.tokens.cacheRead} cacheW=${stats.tokens.cacheWrite}`,\n );\n if (successMessage) spinner.stop(successMessage);\n return {};\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logToFile(`[pi-task] run error: ${message}`);\n if (errorMessage || spinnerMessage) {\n spinner.stop(errorMessage ?? 'Task failed');\n }\n captureAborted();\n const lower = message.toLowerCase();\n if (lower.includes('rate limit') || lower.includes('429')) {\n return { error: AgentErrorType.RATE_LIMIT, message };\n }\n return { error: AgentErrorType.API_ERROR, message };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAM,kBAAqD;CACzD,MAAM,CAAC,OAAO;CACd,MAAM,CAAC,OAAO;CACd,OAAO,CAAC,QAAQ;CAChB,MAAM,CAAC,OAAO;CACd,MAAM,CAAC,QAAQ,KAAK;CACpB,MAAM,CAAC,OAAO;CACf;;AAGD,SAAS,cAAc,MAAsB;AAC3C,QAAO,KAAK,QAAQ,0BAA0B,GAAG;;;;;;AAOnD,SAAgB,qBACd,cACa;CACb,MAAM,WAAW,gBAAgB,EAAE,EAAE,IAAI,cAAc;CACvD,MAAM,QAAQ,QAAQ,SAClB,QAAQ,SAAS,SAAS,gBAAgB,SAAS,EAAE,CAAC,GACtD,OAAO,OAAO,gBAAgB,CAAC,MAAM;AACzC,QAAO,IAAI,IAAI,MAAM;;;;;;;AAQvB,SAAgB,yBACd,iBACa;CACb,MAAM,aAAa,IAAI,KAAK,mBAAmB,EAAE,EAAE,IAAI,cAAc,CAAC;AACtE,QAAO,IAAI,IACT;EAAC;EAAgB;EAAiB;EAAgB,CAAC,QAChD,SAAS,CAAC,WAAW,IAAI,KAAK,CAChC,CACF;;;;;;;;AASH,SAAgB,kBACd,iBACU;CACV,MAAM,QAAQ,mBAAmB,EAAE;AACnC,QAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,GAAG,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC;;;AAI9D,MAAM,kBAAkB;AAExB,MAAM,aACJ;AAEF,MAAM,aACJ;;AAGF,SAAS,iBAAiB,MAGf;CACT,MAAM,QAAQ;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AACD,KAAI,KAAK,KACP,OAAM,KACJ,iOACD;AAEH,KAAI,KAAK,WACP,OAAM,KACJ,4NACD;AAEH,QAAO,MAAM,KAAK,KAAK;;;AAIzB,SAAS,UAAU,KAAwC;AACzD,KAAI,CAAC,IAAI,cAEP,QAAO,IAAI,MAAM,MAAM,CAAC,SAAS;CAEnC,MAAM,OAAO,IAAI,MAAM,IAAI,IAAI,cAAc;AAC7C,QACE,CAAC,CAAC,SACD,KAAK,WAAW,WAAW,QAC1B,KAAK,WAAW,WAAW,UAC3B,KAAK,WAAW,WAAW;;AAIjC,eAAsB,UAAU,QAA6C;CAC3E,MAAM,EACJ,SACA,MACA,QACA,SACA,OAAO,SACP,QACA,cACA,iBACA,cACA,gBACA,gBACA,cACA,eACA,wBACE;AAEJ,KAAI,eAAgB,SAAQ,MAAM,eAAe;CAEjD,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,UAAU,IAAI,oBAAoB;CACxC,IAAI,iBAAiB;CACrB,MAAM,qBAAqB;EACzB,MAAM,aAAa,KAAK,KAAK,GAAG;AAChC,SAAO;GACL,aAAa;GACb,kBAAkB,KAAK,MAAM,aAAa,IAAK;GAChD;;CAEH,MAAM,uBACJ,UAAU,cAAc,iBAAiB;EACvC,GAAG,cAAc;EACjB,OAAO;EACP,GAAG;EACJ,CAAC;AAEJ,KAAI;EAEF,MAAM,EACJ,oBACA,uBACA,gBACA,aACA,eACA,aACA,wBACA,0BACA,0BACA,0BACA,0BACA,0BACA,8BAdU,MAAM,OAAO;EAiBzB,MAAM,EAAE,UAAU,MAAM,eAAe,qBAAqB;GAC1D,YAAY,KAAK,YAAY,KAAK;GAClC,aAAa,KAAK,YAAY;GAC9B,gBAAgB,KAAK;GACrB,aAAa,KAAK;GAClB;GAGA;GACD,CAAC;EACF,MAAM,WAAW,cAAc,SAAS,YAAY,QAAQ,CAAC;AAC7D,WAAS,iBAAiB,kBAAkB,SAAkB;EAC9D,MAAM,QAAQ,SAAS,KAAK,kBAAkB,QAAQ;AACtD,MAAI,CAAC,MACH,QAAO;GACL,OAAA;GACA,SAAS;GACV;EAKH,MAAM,EAAE,4BAA4B,MAAM,OAAO;EACjD,MAAM,WAAW,wBAAwB;GACvC,iBAAiB,kBAAkB,gBAAgB;GACnD,YAAY;IACV,SAAS;IACT,WAAW,KAAK,YAAY;IAC7B;GACF,CAAC;EACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AACvC,sBAAoB;EAIzB,MAAM,qBAAqB,CAAC,SAAS,QAAQ;EAG7C,IAAI;EACJ,IAAI,aAAa;AACjB,MAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;GACzC,MAAM,MAAM,MAAM,gBAAgB;IAChC,UAAU,aAAa;IACvB,QAAQ,KAAK,YAAY,KAAK;IAC9B,aAAa,KAAK,YAAY;IAC9B,WAAW;IACZ,CAAC;AACF,sBAAmB,KAAK,IAAI,iBAAiB;AAC7C,gBAAa,IAAI;AACjB,gBAAa;WACN,KAAK;AACZ,aAAU,wCAAwC,OAAO,IAAI,GAAG;;EAGlE,MAAM,cAAc,qBAAqB,aAAa;EACtD,MAAM,oBAAoB,yBAAyB,gBAAgB;EAEnE,MAAM,EAAE,0BAA0B,MAAM,OAAO;EAC/C,MAAM,iBAAiB,IAAI,sBAAsB;GAC/C,KAAK,QAAQ;GACb,UAAU,aAAa;GACvB,cACE,uBAAuB,GACvB,OACA,iBAAiB;IAAE,MAAM,YAAY,IAAI,OAAO;IAAE;IAAY,CAAC;GACjE,cAAc;GACd,UAAU;GACV,gBAAgB;GAChB,mBAAmB;GACnB,UAAU;GACV;GACD,CAAC;AACF,QAAM,eAAe,QAAQ;EAK7B,MAAM,MAAM,QAAQ;EAgBpB,MAAM,iBAAiB,OAAO,QAfF;GAC1B,YAAY,SAAS,yBAAyB,IAAI,EAAE,WAAW;GAC/D,YAAY,SAAS,yBAAyB,IAAI,EAAE,aAAa;GACjE,aAAa,SAAS,0BAA0B,IAAI,EAAE,aAAa;GACnE,YACE,SACE,yBAAyB,KAAK,EAC5B,YAAY,SAAS;IAAE,GAAG;IAAK,KAAK,kBAAkB;IAAE,GACzD,CAAC,EACF,aACD;GACH,UAAU,SAAS,uBAAuB,IAAI,EAAE,WAAW;GAC3D,YAAY,SAAS,yBAAyB,IAAI,EAAE,WAAW;GAC/D,YAAY,SAAS,yBAAyB,IAAI,EAAE,WAAW;GAChE,CACyD,CACvD,QAAQ,CAAC,UAAU,YAAY,IAAI,KAAK,CAAC,CACzC,KAAK,GAAG,UAAU,MAAM,CAAC;EAI5B,MAAM,EAAE,wBAAwB,MAAM,OAAO;EAC7C,MAAM,cAAc,oBAAoB;GACtC,kBAAkB;GAClB,eAAe,KAAK;GACrB,CAAC,CAAC,QAAQ,MACT;GAAC;GAAkB;GAAkB;GAAyB,CAAC,SAC7D,EAAE,KACH,CACF;EAED,MAAM,EAAE,8BAA8B,MAAM,OAAO;EACnD,MAAM,aAAa,0BAA0B,aAAa,CAAC,QAAQ,MACjE,kBAAkB,IAAI,EAAE,KAAK,CAC9B;EAED,MAAM,EAAE,SAAS,iBAAiB,MAAM,mBAAmB;GACzD;GACA,eAAe;GACf,eAAe,KAAK;GACpB,KAAK;GACL,gBAAgB,eAAe,SAAS,IAAI;GAC5C;GACA,SAAS;GACT,aAAa;IAAC,GAAG;IAAgB,GAAG;IAAa,GAAG;IAAW;GAChE,CAAC;AACF,QAAM,aAAa,eAAe,EAAE,CAAC;EAErC,MAAM,cAAc,aAAa,WAAW,UAAU;AACpD,WAAQ,MAAM,MAAd;IACE,KAAK,eAAe;AAElB,SAAK,MAAM,SAA+B,SAAS,YACjD;AAEF,uBAAkB;KAClB,MAAM,YAAY,YAAY,MAAM,QAAQ,CAAC,MAAM;AACnD,SAAI,WAAW;AACb,gBAAU,wBAAwB,UAAU,MAAM,GAAG,IAAK,GAAG;AAC7D,wBAAkB,UAAU;MAC5B,MAAM,aAAa,eAAe,UAAU;AAC5C,UAAI,YAAY;AACd,cAAO,CAAC,WAAW,WAAW;AAC9B,eAAQ,QAAQ,WAAW;;AAE7B,WAAK,MAAM,QAAQ,UAAU,MAAM,KAAK,CAAE,SAAQ,KAAK,KAAK;;AAE9D;;IAEF,KAAK,wBAAwB;KAC3B,MAAM,OAAO,KAAK,UAAU,MAAM,QAAQ,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI;AAC3D,eAAU,eAAe,MAAM,SAAS,GAAG,OAAO;AAClD;;IAEF,KAAK;AACH,SAAI,MAAM,QACR,WACE,eAAe,MAAM,SAAS,IAAI,OAAO,MAAM,OAAO,CAAC,MACrD,GACA,IACD,GACF;AAEH;IAEF,QACE;;IAEJ;AAEF,MAAI;AACF,SAAM,aAAa,OAAO,OAAO;GAKjC,IAAI,SAAS;AACb,UACE,SAAS,mBACT,CAAC,SAAS,MAAM,qBAChB,CAAC,UAAU,aAAa,EACxB;AACA,cAAU;AACV,cACE,kDAAkD,OAAO,GAAG,kBAC7D;AACD,UAAM,aAAa,OACjB,aAAa,gBAAgB,aAAa,WAC3C;;AAGH,OAAI,iBAAiB,CAAC,SAAS,MAAM,kBACnC,KAAI;AACF,UAAM,aAAa,OAAO,mBAAmB;YACtC,KAAK;AACZ,cAAU,oCAAoC,OAAO,IAAI,GAAG;;YAGxD;AACR,gBAAa;AACb,iBAAc;;AAGhB,MAAI,SAAS,MAAM,mBAAmB;AACpC,WAAQ,KAAK,8BAA8B;AAC3C,aACE,iDAAiD,SAAS,MAAM,aAAa,WAC9E;AACD,mBAAgB;AAChB,UAAO,EAAE,OAAA,yBAAsC;;EAGjD,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,OACF,WAAU,QAAQ,0BAA0B,EAAE,QAAQ,CAAC;EAGzD,MAAM,QAAQ,aAAa,iBAAiB;EAC5C,MAAM,YAAY,cAAc;AAChC,YAAU,cAAc,mBAAmB;GACzC,GAAG;GACH,OAAO;GACP,WAAW;GACX,cAAc,MAAM,OAAO;GAC3B,eAAe,MAAM,OAAO;GAC5B,6BAA6B,MAAM,OAAO;GAC1C,yBAAyB,MAAM,OAAO;GACtC,GAAG;GACJ,CAAC;AAQF,YACE,wBALA,OAAQ,qBAAiD,cACzD,WACK,oBAA8C,YAC/C,QAE6B,SAAS,QAAQ,OAAO,UAAU,iBAAiB,UAAU,eAAe,MAAM,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,OAAO,UAAU,MAAM,OAAO,UAAU,UAAU,MAAM,OAAO,aAC1N;AACD,MAAI,eAAgB,SAAQ,KAAK,eAAe;AAChD,SAAO,EAAE;UACF,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAU,wBAAwB,UAAU;AAC5C,MAAI,gBAAgB,eAClB,SAAQ,KAAK,gBAAgB,cAAc;AAE7C,kBAAgB;EAChB,MAAM,QAAQ,QAAQ,aAAa;AACnC,MAAI,MAAM,SAAS,aAAa,IAAI,MAAM,SAAS,MAAM,CACvD,QAAO;GAAE,OAAA;GAAkC;GAAS;AAEtD,SAAO;GAAE,OAAA;GAAiC;GAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"tools-D5pL_r3K.js","names":[],"sources":["../src/lib/agent/runner/harness/pi/tools.ts"],"sourcesContent":["/**\n * Wizard capabilities as pi custom tools (#5). pi does not mount MCP servers,\n * so the tools the wizard prompt depends on — skill discovery/install and\n * fenced `.env` edits — are exposed to pi as native `defineTool` tools backed\n * by the same helpers the claude-agent-sdk path uses (`fetchSkillMenu`,\n * `installSkillById`, `parseEnvKeys`, `mergeEnvValues`). Same tool names as the\n * MCP server so the shared prompt is unchanged. `wizard_ask` is wired here too\n * (same schema, caps, and askBridge as the MCP tool) so interactive programs\n * can interview the user on pi; without a bridge (CI) it errors on call.\n * Sensitive answers are vaulted to `{secretRef}` and resolved host-side by\n * set_env_values — the raw value never enters the model conversation.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { Type } from 'typebox';\nimport { defineTool } from '@earendil-works/pi-coding-agent';\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport { analytics } from '@utils/analytics';\nimport { logToFile } from '@utils/debug';\nimport {\n DEFAULT_ASK_MAX_QUESTIONS,\n ENV_FILE_PATH_DESCRIPTION,\n WIZARD_TOOL_NAMES,\n evaluateAskCap,\n fetchSkillMenu,\n installSkillById,\n mergeEnvValues,\n parseEnvKeys,\n resolveEnvPath,\n resolveEnvSecretRefs,\n vaultSensitiveAnswers,\n} from '@lib/wizard-tools/tools';\nimport { isFullyCancelled, type WizardAskBridge } from '@lib/wizard-ask-bridge';\nimport { createSecretVault } from '@lib/secret-vault';\nimport { withMode } from './index';\nimport {\n detectNodePackageManagers,\n type PackageManagerDetector,\n} from '@lib/detection/package-manager';\n\nfunction text(s: string): {\n content: [{ type: 'text'; text: string }];\n details: unknown;\n} {\n return { content: [{ type: 'text', text: s }], details: {} };\n}\n\nexport interface PiToolsContext {\n workingDirectory: string;\n skillsBaseUrl: string;\n /** Framework's package-manager detector. Defaults to Node detection. */\n detectPackageManager?: PackageManagerDetector;\n /** Drives the `wizard_ask` overlay. Omitted in CI → the tool errors on call. */\n askBridge?: WizardAskBridge;\n /** Per-run cap on wizard_ask calls. Defaults to {@link DEFAULT_ASK_MAX_QUESTIONS}. */\n maxQuestions?: number;\n /** Overlay open/closed signal — the security gate blocks Write/Edit while open. */\n onAskPendingChange?: (pending: boolean) => void;\n /** Program disallow list; gates wizard_ask here since pi tools carry bare names the MCP-prefixed security gate misses. */\n disallowedTools?: readonly string[];\n}\n\nexport function createWizardPiTools(ctx: PiToolsContext): ToolDefinition[] {\n const { workingDirectory, skillsBaseUrl, askBridge, onAskPendingChange } =\n ctx;\n const detectPackageManager =\n ctx.detectPackageManager ?? detectNodePackageManagers;\n const askMaxQuestions = ctx.maxQuestions ?? DEFAULT_ASK_MAX_QUESTIONS;\n // Per-run wizard_ask accounting (total cap + one-time adjacency nudge),\n // mirroring the MCP server's counters.\n let askCallCount = 0;\n let askAdjacencyNudged = false;\n // Session-scoped secret vault, same contract as the MCP server: wizard_ask\n // mints `{secretRef}` for sensitive answers, set_env_values resolves them.\n const secretVault = createSecretVault();\n\n // Fetch the skill menu at most once per run — the agent calls load_skill_menu\n // 2-3× otherwise, each a fresh HTTP round-trip (profiled slowness).\n let menuPromise: ReturnType<typeof fetchSkillMenu> | undefined;\n const getSkillMenu = () => (menuPromise ??= fetchSkillMenu(skillsBaseUrl));\n\n const loadSkillMenu = defineTool({\n name: 'load_skill_menu',\n label: 'Load skill menu',\n description:\n 'Load available PostHog skills for a category. Returns skill IDs and names. Call this first, then install_skill with the chosen ID.',\n promptSnippet:\n 'load_skill_menu(category) — list installable PostHog skills',\n parameters: Type.Object({\n category: Type.String({\n description: 'Skill category, e.g. \"integration\"',\n }),\n }),\n async execute(_id, args) {\n const menu = await getSkillMenu();\n if (!menu) return text('Error: could not load the skill menu.');\n const skills = menu.categories[args.category] ?? [];\n if (skills.length === 0) {\n return text(`No skills found for category \"${args.category}\".`);\n }\n logToFile(`[pi] load_skill_menu: ${skills.length} skills`);\n return text(skills.map((s) => `- ${s.id}: ${s.name}`).join('\\n'));\n },\n });\n\n const installSkill = defineTool({\n name: 'install_skill',\n label: 'Install skill',\n description:\n 'Download and install a PostHog skill by ID into .claude/skills/<skillId>/. Call load_skill_menu first. Then read the installed SKILL.md and follow it.',\n promptSnippet:\n 'install_skill(skillId) — install a skill, then read its SKILL.md',\n parameters: Type.Object({\n skillId: Type.String({ description: 'Skill ID from load_skill_menu' }),\n }),\n async execute(_id, args) {\n const result = await installSkillById(\n args.skillId,\n workingDirectory,\n skillsBaseUrl,\n );\n if (result.kind !== 'ok') {\n logToFile(`[pi] install_skill ${args.skillId}: ${result.kind}`);\n return text(\n `Error installing skill \"${args.skillId}\": ${result.kind}. Use load_skill_menu to see valid IDs.`,\n );\n }\n logToFile(`[pi] install_skill ${args.skillId} -> ${result.path}`);\n return text(\n `Installed \"${args.skillId}\" at ${result.path}. Read ${result.path}/SKILL.md and follow it.`,\n );\n },\n });\n\n const checkEnvKeys = defineTool({\n name: 'check_env_keys',\n label: 'Check env keys',\n description:\n 'Check which environment variable keys are present or missing in a .env file. Never reveals values.',\n promptSnippet: 'check_env_keys(filePath, keys) — see which .env keys exist',\n parameters: Type.Object({\n filePath: Type.String({\n description: ENV_FILE_PATH_DESCRIPTION,\n }),\n keys: Type.Array(Type.String(), {\n description: 'Environment variable key names to check',\n }),\n }),\n async execute(_id, args) {\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n const existing = fs.existsSync(resolved)\n ? parseEnvKeys(await fs.promises.readFile(resolved, 'utf8'))\n : new Set<string>();\n const results: Record<string, 'present' | 'missing'> = {};\n for (const key of args.keys) {\n results[key] = existing.has(key) ? 'present' : 'missing';\n }\n return text(JSON.stringify(results, null, 2));\n },\n });\n\n const setEnvValues = defineTool({\n name: 'set_env_values',\n label: 'Set env values',\n description:\n 'Create or update environment variable keys in a .env file (creates the file if missing). Each value is either a literal string or a secret reference `{ \"secretRef\": \"secret:...\" }` returned by wizard_ask — refs are resolved locally, so the actual value is written to the file but never returned to the agent.',\n promptSnippet:\n 'set_env_values(filePath, values) — write .env keys (never hardcode secrets in source)',\n parameters: Type.Object({\n filePath: Type.String({\n description: ENV_FILE_PATH_DESCRIPTION,\n }),\n values: Type.Record(\n Type.String(),\n Type.Union([Type.String(), Type.Object({ secretRef: Type.String() })]),\n {\n description:\n 'Key → (literal string OR { secretRef } pointing to a vaulted secret)',\n },\n ),\n }),\n async execute(_id, args) {\n const forbidden = Object.keys(args.values).find(\n (k) => k.toUpperCase() === 'POSTHOG_KEY',\n );\n if (forbidden) {\n return text(\n `Error: \"${forbidden}\" is not a valid PostHog env var name. Use the framework-specific key (e.g. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN).`,\n );\n }\n // Resolve secret refs host-side; the value never reaches the agent.\n const resolution = resolveEnvSecretRefs(args.values, secretVault);\n if (!resolution.ok) {\n return text(\n `Error: secret reference \"${resolution.secretRef}\" for key \"${resolution.key}\" is not known to the vault. The ref may have expired, been minted in a different run, or been mistyped.`,\n );\n }\n const resolved = resolveEnvPath(workingDirectory, args.filePath);\n const existing = fs.existsSync(resolved)\n ? await fs.promises.readFile(resolved, 'utf8')\n : '';\n const merged = mergeEnvValues(existing, resolution.values);\n const dir = path.dirname(resolved);\n if (!fs.existsSync(dir))\n await fs.promises.mkdir(dir, { recursive: true });\n await fs.promises.writeFile(resolved, merged, 'utf8');\n logToFile(\n `[pi] set_env_values: ${resolved} keys=${Object.keys(args.values).join(\n ',',\n )}`,\n );\n return text(\n `Wrote ${Object.keys(args.values).length} key(s) to ${args.filePath}.`,\n );\n },\n });\n\n const detectPm = defineTool({\n name: 'detect_package_manager',\n label: 'Detect package manager',\n description:\n \"Detect the project's package manager(s). Returns the name and the install command for each. Call this before installing a dependency, then RUN the returned install command (with the posthog package) via bash — the SDK package must end up in the project manifest, or the app will not build.\",\n promptSnippet:\n 'detect_package_manager() — find the PM + install command, then run it via bash to add the SDK',\n parameters: Type.Object({}),\n async execute() {\n const result = await detectPackageManager(workingDirectory);\n logToFile(\n `[pi] detect_package_manager: ${result.detected.length} detected`,\n );\n return text(JSON.stringify(result, null, 2));\n },\n });\n\n // Native mirror of the MCP `wizard_ask` tool: same name, schema, and\n // askBridge, so the shared prompt is unchanged.\n const wizardAsk = defineTool({\n name: 'wizard_ask',\n label: 'Ask the user',\n description:\n 'Ask the user one or more structured questions and wait for their answers. ' +\n 'Use this whenever you would otherwise inline a question in your text output. ' +\n 'Batch related questions into a single call (up to 8) rather than asking one at ' +\n 'a time; sequential calls are fine when later questions genuinely depend on ' +\n 'earlier answers. A fully cancelled or timed-out response does NOT count against ' +\n 'the per-run cap — treat it as \"the user declined\" and fall back gracefully.',\n promptSnippet:\n 'wizard_ask(questions) — ask the user structured questions and wait for answers',\n parameters: Type.Object({\n questions: Type.Array(\n Type.Object({\n id: Type.String({\n description: 'Stable key for the answer in the response map',\n }),\n prompt: Type.String({\n description: 'Question text shown to the user',\n }),\n kind: Type.Union(\n [\n Type.Literal('single'),\n Type.Literal('multi'),\n Type.Literal('text'),\n ],\n {\n description:\n \"'single' = pick one option, 'multi' = pick any, 'text' = free-form single-line answer\",\n },\n ),\n options: Type.Optional(\n Type.Array(\n Type.Object({\n label: Type.String(),\n value: Type.String(),\n description: Type.Optional(Type.String()),\n }),\n {\n description:\n 'Required for kind=single|multi; ignored for kind=text',\n },\n ),\n ),\n required: Type.Optional(\n Type.Boolean({ description: 'Defaults to true' }),\n ),\n sensitive: Type.Optional(\n Type.Boolean({\n description:\n \"Only valid for kind='text'. The answer is stored in the wizard's secret vault and returned as { secretRef: 'secret:...' } instead of the raw string. Use for API keys and tokens; the ref is resolved only by tools that accept it (e.g. set_env_values).\",\n }),\n ),\n }),\n { minItems: 1, maxItems: 8 },\n ),\n }),\n async execute(_id, args) {\n if (!askBridge) {\n return text(\n 'Error: wizard_ask is not available in this environment (CI / non-interactive). Proceed with sensible defaults or emit [ABORT] requirements-incomplete.',\n );\n }\n\n const cap = evaluateAskCap(\n askCallCount,\n askMaxQuestions,\n askAdjacencyNudged,\n );\n if (cap.kind === 'capped') {\n if (cap.reason === 'adjacency') askAdjacencyNudged = true;\n logToFile(\n `[pi] wizard_ask capped: reason=${cap.reason} count=${askCallCount}`,\n );\n analytics.wizardCapture('wizard_ask capped', {\n reason: cap.reason,\n call_count: askCallCount,\n max_questions: askMaxQuestions,\n });\n return text(cap.message);\n }\n\n // The schema can't enforce per-kind requirements or unique ids.\n const ids = new Set<string>();\n for (const q of args.questions) {\n if ((q.kind === 'single' || q.kind === 'multi') && !q.options?.length) {\n return text(\n `Error: question \"${q.id}\" has kind=\"${q.kind}\" but no options. Provide at least one { label, value }, or use kind=\"text\".`,\n );\n }\n if (q.sensitive && q.kind !== 'text') {\n return text(\n `Error: question \"${q.id}\" sets sensitive=true but kind=\"${q.kind}\". Only kind=\"text\" answers can be sensitive.`,\n );\n }\n if (ids.has(q.id)) {\n return text(\n `Error: duplicate question id \"${q.id}\". Each question needs a unique id.`,\n );\n }\n ids.add(q.id);\n }\n\n // Optimistically take the slot; refund it on a cancellation or a bridge\n // error so a declined/failed ask doesn't burn the budget for later ones.\n askCallCount += 1;\n // Block Write/Edit for as long as the overlay is open, so the agent can't\n // mutate files while it's waiting on the user's answer.\n onAskPendingChange?.(true);\n try {\n const answers = await askBridge.request({ questions: args.questions });\n if (isFullyCancelled(answers)) askCallCount -= 1;\n // Sensitive answers go to the vault; the agent sees an opaque ref\n // (same contract as the MCP wizard_ask).\n const sanitised = vaultSensitiveAnswers(\n args.questions,\n answers,\n secretVault,\n );\n logToFile(\n `[pi] wizard_ask: resolved ${\n Object.keys(answers).length\n } answer(s) for ${args.questions.length} question(s)`,\n );\n return text(JSON.stringify({ answers: sanitised }, null, 2));\n } catch (err) {\n askCallCount -= 1;\n const message = err instanceof Error ? err.message : String(err);\n logToFile(`[pi] wizard_ask: error: ${message}`);\n return text(`Error: wizard_ask failed: ${message}`);\n } finally {\n onAskPendingChange?.(false);\n }\n },\n });\n\n const tools = [\n loadSkillMenu,\n installSkill,\n checkEnvKeys,\n setEnvValues,\n detectPm,\n ];\n // Register wizard_ask only when the program allows it. posthog-integration\n // disallows it (runs without structured user input); self-driving keeps it.\n // Sequential: the ask bridge holds a single in-flight question slot, so a\n // batched turn must never dispatch two asks (or an ask + a write) at once.\n const askDisallowed =\n ctx.disallowedTools?.includes(WIZARD_TOOL_NAMES.wizardAsk) ?? false;\n if (!askDisallowed) tools.push(withMode(wizardAsk, 'sequential'));\n return tools;\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { l as prewarmYaraScanner } from "./yara-hooks-DEHfsDSF.js";
2
- export { prewarmYaraScanner };
@@ -1 +0,0 @@
1
- {"version":3,"file":"yara-hooks-DEHfsDSF.js","names":["EVENTS_AUDIT_REPORT_FILE"],"sources":["../src/lib/programs/audit/types.ts","../src/lib/skill-install.ts","../src/lib/yara-policy.ts","../src/lib/programs/events-audit/constants.ts","../src/lib/programs/posthog-integration/constants.ts","../src/lib/yara-hooks.ts"],"sourcesContent":["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 * 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 on their own, whatever the rule asks for. */\nconst TERMINAL_SEVERITIES = new Set(['critical']);\n\n/** The match that ends the session rather than just warning. */\nexport function isTerminalMatch(match: ScanMatch): boolean {\n return (\n TERMINAL_SEVERITIES.has(match.metadata.severity ?? '') ||\n match.metadata.action === 'block'\n );\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\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","/**\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 { analytics } from '@utils/analytics';\nimport { getUI } from '@ui';\nimport type { WizardSession } from '@lib/wizard-session';\nimport { isSkillInstallCommand } from './skill-install';\nimport { highestSeverityMatch, scanVerdict } 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 * 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 * Scans Bash commands before execution for exfiltration, destructive\n * operations, and supply chain violations ('command'-context rules).\n */\nexport function createPreToolUseYaraHooks(\n // Accepted for API symmetry with createPostToolUseYaraHooks, but\n // intentionally unused: PreToolUse Bash always blocks on any flagged\n // command regardless of triage verdict, so the LLM call would be wasted.\n // Future PreToolUse hooks that need triage can wire this through.\n _llmProvider?: LLMProvider,\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}\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 if (matches.length === 0) return {};\n\n // INTENTIONAL ASYMMETRY: skill-install terminates on ANY\n // post-triage match, while Read/Grep only terminates on\n // critical-or-block. Skills are downloaded code from an\n // external source; any flagged content in them is treated as\n // poisoned. Read/Grep, by contrast, may scan project files\n // the user wrote themselves where a medium-severity match\n // can warrant a warning instead of a terminate.\n // TODO(wizard#593): document / lock in this contract with a test.\n const match = highestSeverityMatch(matches);\n recordMatch(\n 'PostToolUse',\n 'Bash (skill install)',\n match,\n 'aborted',\n );\n\n const reason =\n `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${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 * 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 const fileContents: string[] = [];\n for (const filePath of files) {\n try {\n fileContents.push(fs.readFileSync(filePath, 'utf-8'));\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 [];\n }\n\n logToFile(\n `[YARA] Scanning ${fileContents.length} files in skill directory: ${skillDir}`,\n );\n\n // Pass 1 (sequential): scan each file through the chunk-aware path —\n // full coverage even for oversized files, and every flagged chunk keeps a\n // reference to the exact content its matches came from.\n const flagged: FlaggedChunk[] = [];\n for (const content of fileContents) {\n flagged.push(...(await scanForContext(content, 'input')));\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":"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"}