@amplitude/wizard 1.0.0-beta.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 (333) hide show
  1. package/LICENSE +47 -0
  2. package/README.md +119 -0
  3. package/dist/bin.d.ts +2 -0
  4. package/dist/bin.js +763 -0
  5. package/dist/package.json +144 -0
  6. package/dist/src/frameworks/android/android-wizard-agent.d.ts +6 -0
  7. package/dist/src/frameworks/android/android-wizard-agent.js +72 -0
  8. package/dist/src/frameworks/android/utils.d.ts +11 -0
  9. package/dist/src/frameworks/android/utils.js +32 -0
  10. package/dist/src/frameworks/django/django-wizard-agent.d.ts +8 -0
  11. package/dist/src/frameworks/django/django-wizard-agent.js +171 -0
  12. package/dist/src/frameworks/django/utils.d.ts +31 -0
  13. package/dist/src/frameworks/django/utils.js +305 -0
  14. package/dist/src/frameworks/fastapi/fastapi-wizard-agent.d.ts +11 -0
  15. package/dist/src/frameworks/fastapi/fastapi-wizard-agent.js +189 -0
  16. package/dist/src/frameworks/fastapi/utils.d.ts +26 -0
  17. package/dist/src/frameworks/fastapi/utils.js +257 -0
  18. package/dist/src/frameworks/flask/flask-wizard-agent.d.ts +8 -0
  19. package/dist/src/frameworks/flask/flask-wizard-agent.js +177 -0
  20. package/dist/src/frameworks/flask/utils.d.ts +28 -0
  21. package/dist/src/frameworks/flask/utils.js +343 -0
  22. package/dist/src/frameworks/flutter/flutter-wizard-agent.d.ts +4 -0
  23. package/dist/src/frameworks/flutter/flutter-wizard-agent.js +57 -0
  24. package/dist/src/frameworks/flutter/utils.d.ts +7 -0
  25. package/dist/src/frameworks/flutter/utils.js +64 -0
  26. package/dist/src/frameworks/generic/generic-wizard-agent.d.ts +2 -0
  27. package/dist/src/frameworks/generic/generic-wizard-agent.js +176 -0
  28. package/dist/src/frameworks/go/go-wizard-agent.d.ts +4 -0
  29. package/dist/src/frameworks/go/go-wizard-agent.js +57 -0
  30. package/dist/src/frameworks/go/utils.d.ts +5 -0
  31. package/dist/src/frameworks/go/utils.js +44 -0
  32. package/dist/src/frameworks/java/java-wizard-agent.d.ts +7 -0
  33. package/dist/src/frameworks/java/java-wizard-agent.js +73 -0
  34. package/dist/src/frameworks/java/utils.d.ts +15 -0
  35. package/dist/src/frameworks/java/utils.js +64 -0
  36. package/dist/src/frameworks/javascript-node/javascript-node-wizard-agent.d.ts +4 -0
  37. package/dist/src/frameworks/javascript-node/javascript-node-wizard-agent.js +57 -0
  38. package/dist/src/frameworks/javascript-web/javascript-web-wizard-agent.d.ts +3 -0
  39. package/dist/src/frameworks/javascript-web/javascript-web-wizard-agent.js +151 -0
  40. package/dist/src/frameworks/javascript-web/utils.d.ts +28 -0
  41. package/dist/src/frameworks/javascript-web/utils.js +153 -0
  42. package/dist/src/frameworks/nextjs/nextjs-wizard-agent.d.ts +7 -0
  43. package/dist/src/frameworks/nextjs/nextjs-wizard-agent.js +98 -0
  44. package/dist/src/frameworks/nextjs/utils.d.ts +12 -0
  45. package/dist/src/frameworks/nextjs/utils.js +51 -0
  46. package/dist/src/frameworks/python/python-wizard-agent.d.ts +7 -0
  47. package/dist/src/frameworks/python/python-wizard-agent.js +193 -0
  48. package/dist/src/frameworks/python/utils.d.ts +28 -0
  49. package/dist/src/frameworks/python/utils.js +146 -0
  50. package/dist/src/frameworks/react-native/react-native-wizard-agent.d.ts +6 -0
  51. package/dist/src/frameworks/react-native/react-native-wizard-agent.js +84 -0
  52. package/dist/src/frameworks/react-native/utils.d.ts +21 -0
  53. package/dist/src/frameworks/react-native/utils.js +82 -0
  54. package/dist/src/frameworks/react-router/react-router-wizard-agent.d.ts +7 -0
  55. package/dist/src/frameworks/react-router/react-router-wizard-agent.js +98 -0
  56. package/dist/src/frameworks/react-router/utils.d.ts +13 -0
  57. package/dist/src/frameworks/react-router/utils.js +160 -0
  58. package/dist/src/frameworks/swift/swift-wizard-agent.d.ts +7 -0
  59. package/dist/src/frameworks/swift/swift-wizard-agent.js +72 -0
  60. package/dist/src/frameworks/swift/utils.d.ts +12 -0
  61. package/dist/src/frameworks/swift/utils.js +82 -0
  62. package/dist/src/frameworks/unity/unity-wizard-agent.d.ts +6 -0
  63. package/dist/src/frameworks/unity/unity-wizard-agent.js +79 -0
  64. package/dist/src/frameworks/unity/utils.d.ts +12 -0
  65. package/dist/src/frameworks/unity/utils.js +66 -0
  66. package/dist/src/frameworks/unreal/unreal-wizard-agent.d.ts +6 -0
  67. package/dist/src/frameworks/unreal/unreal-wizard-agent.js +77 -0
  68. package/dist/src/frameworks/unreal/utils.d.ts +10 -0
  69. package/dist/src/frameworks/unreal/utils.js +29 -0
  70. package/dist/src/frameworks/vue/vue-wizard-agent.d.ts +4 -0
  71. package/dist/src/frameworks/vue/vue-wizard-agent.js +64 -0
  72. package/dist/src/lib/agent-hooks.d.ts +26 -0
  73. package/dist/src/lib/agent-hooks.js +118 -0
  74. package/dist/src/lib/agent-interface.d.ts +175 -0
  75. package/dist/src/lib/agent-interface.js +1217 -0
  76. package/dist/src/lib/agent-runner.d.ts +9 -0
  77. package/dist/src/lib/agent-runner.js +415 -0
  78. package/dist/src/lib/ampli-config.d.ts +105 -0
  79. package/dist/src/lib/ampli-config.js +178 -0
  80. package/dist/src/lib/api.d.ts +107 -0
  81. package/dist/src/lib/api.js +442 -0
  82. package/dist/src/lib/commandments.d.ts +1 -0
  83. package/dist/src/lib/commandments.js +24 -0
  84. package/dist/src/lib/console-query.d.ts +27 -0
  85. package/dist/src/lib/console-query.js +121 -0
  86. package/dist/src/lib/constants.d.ts +124 -0
  87. package/dist/src/lib/constants.js +170 -0
  88. package/dist/src/lib/detect-amplitude.d.ts +31 -0
  89. package/dist/src/lib/detect-amplitude.js +407 -0
  90. package/dist/src/lib/framework-config.d.ts +188 -0
  91. package/dist/src/lib/framework-config.js +21 -0
  92. package/dist/src/lib/health-checks/endpoints.d.ts +3 -0
  93. package/dist/src/lib/health-checks/endpoints.js +45 -0
  94. package/dist/src/lib/health-checks/index.d.ts +4 -0
  95. package/dist/src/lib/health-checks/index.js +22 -0
  96. package/dist/src/lib/health-checks/readiness.d.ts +24 -0
  97. package/dist/src/lib/health-checks/readiness.js +118 -0
  98. package/dist/src/lib/health-checks/statuspage.d.ts +9 -0
  99. package/dist/src/lib/health-checks/statuspage.js +104 -0
  100. package/dist/src/lib/health-checks/types.d.ts +31 -0
  101. package/dist/src/lib/health-checks/types.js +9 -0
  102. package/dist/src/lib/helper-functions.d.ts +1 -0
  103. package/dist/src/lib/helper-functions.js +5 -0
  104. package/dist/src/lib/middleware/benchmark.d.ts +54 -0
  105. package/dist/src/lib/middleware/benchmark.js +48 -0
  106. package/dist/src/lib/middleware/benchmarks/cache-tracker.d.ts +44 -0
  107. package/dist/src/lib/middleware/benchmarks/cache-tracker.js +80 -0
  108. package/dist/src/lib/middleware/benchmarks/compaction-tracker.d.ts +29 -0
  109. package/dist/src/lib/middleware/benchmarks/compaction-tracker.js +59 -0
  110. package/dist/src/lib/middleware/benchmarks/context-size-tracker.d.ts +26 -0
  111. package/dist/src/lib/middleware/benchmarks/context-size-tracker.js +55 -0
  112. package/dist/src/lib/middleware/benchmarks/cost-tracker.d.ts +16 -0
  113. package/dist/src/lib/middleware/benchmarks/cost-tracker.js +75 -0
  114. package/dist/src/lib/middleware/benchmarks/duration-tracker.d.ts +20 -0
  115. package/dist/src/lib/middleware/benchmarks/duration-tracker.js +39 -0
  116. package/dist/src/lib/middleware/benchmarks/index.d.ts +9 -0
  117. package/dist/src/lib/middleware/benchmarks/index.js +67 -0
  118. package/dist/src/lib/middleware/benchmarks/json-writer.d.ts +15 -0
  119. package/dist/src/lib/middleware/benchmarks/json-writer.js +144 -0
  120. package/dist/src/lib/middleware/benchmarks/summary.d.ts +9 -0
  121. package/dist/src/lib/middleware/benchmarks/summary.js +105 -0
  122. package/dist/src/lib/middleware/benchmarks/token-tracker.d.ts +40 -0
  123. package/dist/src/lib/middleware/benchmarks/token-tracker.js +76 -0
  124. package/dist/src/lib/middleware/benchmarks/turn-counter.d.ts +34 -0
  125. package/dist/src/lib/middleware/benchmarks/turn-counter.js +58 -0
  126. package/dist/src/lib/middleware/config.d.ts +24 -0
  127. package/dist/src/lib/middleware/config.js +96 -0
  128. package/dist/src/lib/middleware/index.d.ts +11 -0
  129. package/dist/src/lib/middleware/index.js +17 -0
  130. package/dist/src/lib/middleware/phase-detector.d.ts +8 -0
  131. package/dist/src/lib/middleware/phase-detector.js +63 -0
  132. package/dist/src/lib/middleware/pipeline.d.ts +29 -0
  133. package/dist/src/lib/middleware/pipeline.js +81 -0
  134. package/dist/src/lib/middleware/schemas.d.ts +27 -0
  135. package/dist/src/lib/middleware/schemas.js +84 -0
  136. package/dist/src/lib/middleware/types.d.ts +94 -0
  137. package/dist/src/lib/middleware/types.js +8 -0
  138. package/dist/src/lib/package-manager-detection.d.ts +42 -0
  139. package/dist/src/lib/package-manager-detection.js +292 -0
  140. package/dist/src/lib/registry.d.ts +3 -0
  141. package/dist/src/lib/registry.js +42 -0
  142. package/dist/src/lib/safe-tools.d.ts +2 -0
  143. package/dist/src/lib/safe-tools.js +214 -0
  144. package/dist/src/lib/wizard-session.d.ts +220 -0
  145. package/dist/src/lib/wizard-session.js +127 -0
  146. package/dist/src/lib/wizard-tools.d.ts +82 -0
  147. package/dist/src/lib/wizard-tools.js +499 -0
  148. package/dist/src/run.d.ts +19 -0
  149. package/dist/src/run.js +151 -0
  150. package/dist/src/steps/add-mcp-server-to-clients/MCPClient.d.ts +30 -0
  151. package/dist/src/steps/add-mcp-server-to-clients/MCPClient.js +141 -0
  152. package/dist/src/steps/add-mcp-server-to-clients/clients/claude-code.d.ts +29 -0
  153. package/dist/src/steps/add-mcp-server-to-clients/clients/claude-code.js +180 -0
  154. package/dist/src/steps/add-mcp-server-to-clients/clients/claude.d.ts +20 -0
  155. package/dist/src/steps/add-mcp-server-to-clients/clients/claude.js +63 -0
  156. package/dist/src/steps/add-mcp-server-to-clients/clients/codex.d.ts +28 -0
  157. package/dist/src/steps/add-mcp-server-to-clients/clients/codex.js +77 -0
  158. package/dist/src/steps/add-mcp-server-to-clients/clients/cursor.d.ts +24 -0
  159. package/dist/src/steps/add-mcp-server-to-clients/clients/cursor.js +60 -0
  160. package/dist/src/steps/add-mcp-server-to-clients/clients/visual-studio-code.d.ts +27 -0
  161. package/dist/src/steps/add-mcp-server-to-clients/clients/visual-studio-code.js +101 -0
  162. package/dist/src/steps/add-mcp-server-to-clients/clients/zed.d.ts +26 -0
  163. package/dist/src/steps/add-mcp-server-to-clients/clients/zed.js +102 -0
  164. package/dist/src/steps/add-mcp-server-to-clients/defaults.d.ts +44 -0
  165. package/dist/src/steps/add-mcp-server-to-clients/defaults.js +123 -0
  166. package/dist/src/steps/add-mcp-server-to-clients/index.d.ts +19 -0
  167. package/dist/src/steps/add-mcp-server-to-clients/index.js +110 -0
  168. package/dist/src/steps/add-or-update-environment-variables.d.ts +10 -0
  169. package/dist/src/steps/add-or-update-environment-variables.js +188 -0
  170. package/dist/src/steps/index.d.ts +4 -0
  171. package/dist/src/steps/index.js +20 -0
  172. package/dist/src/steps/run-prettier.d.ts +5 -0
  173. package/dist/src/steps/run-prettier.js +90 -0
  174. package/dist/src/steps/upload-environment-variables/EnvironmentProvider.d.ts +11 -0
  175. package/dist/src/steps/upload-environment-variables/EnvironmentProvider.js +11 -0
  176. package/dist/src/steps/upload-environment-variables/index.d.ts +6 -0
  177. package/dist/src/steps/upload-environment-variables/index.js +37 -0
  178. package/dist/src/steps/upload-environment-variables/providers/vercel.d.ts +15 -0
  179. package/dist/src/steps/upload-environment-variables/providers/vercel.js +145 -0
  180. package/dist/src/telemetry.d.ts +2 -0
  181. package/dist/src/telemetry.js +12 -0
  182. package/dist/src/ui/index.d.ts +8 -0
  183. package/dist/src/ui/index.js +16 -0
  184. package/dist/src/ui/logging-ui.d.ts +56 -0
  185. package/dist/src/ui/logging-ui.js +157 -0
  186. package/dist/src/ui/tui/App.d.ts +6 -0
  187. package/dist/src/ui/tui/App.js +34 -0
  188. package/dist/src/ui/tui/components/AmplitudeLogo.d.ts +5 -0
  189. package/dist/src/ui/tui/components/AmplitudeLogo.js +81 -0
  190. package/dist/src/ui/tui/components/AmplitudeTextLogo.d.ts +3 -0
  191. package/dist/src/ui/tui/components/AmplitudeTextLogo.js +31 -0
  192. package/dist/src/ui/tui/components/ConsoleView.d.ts +23 -0
  193. package/dist/src/ui/tui/components/ConsoleView.js +220 -0
  194. package/dist/src/ui/tui/components/TitleBar.d.ts +6 -0
  195. package/dist/src/ui/tui/components/TitleBar.js +16 -0
  196. package/dist/src/ui/tui/console-commands.d.ts +16 -0
  197. package/dist/src/ui/tui/console-commands.js +31 -0
  198. package/dist/src/ui/tui/context/CommandModeContext.d.ts +2 -0
  199. package/dist/src/ui/tui/context/CommandModeContext.js +3 -0
  200. package/dist/src/ui/tui/flows.d.ts +48 -0
  201. package/dist/src/ui/tui/flows.js +154 -0
  202. package/dist/src/ui/tui/hooks/useScreenInput.d.ts +13 -0
  203. package/dist/src/ui/tui/hooks/useScreenInput.js +18 -0
  204. package/dist/src/ui/tui/hooks/useStdoutDimensions.d.ts +9 -0
  205. package/dist/src/ui/tui/hooks/useStdoutDimensions.js +29 -0
  206. package/dist/src/ui/tui/ink-ui.d.ts +62 -0
  207. package/dist/src/ui/tui/ink-ui.js +142 -0
  208. package/dist/src/ui/tui/primitives/CardLayout.d.ts +12 -0
  209. package/dist/src/ui/tui/primitives/CardLayout.js +9 -0
  210. package/dist/src/ui/tui/primitives/ConfirmationInput.d.ts +13 -0
  211. package/dist/src/ui/tui/primitives/ConfirmationInput.js +35 -0
  212. package/dist/src/ui/tui/primitives/DissolveTransition.d.ts +21 -0
  213. package/dist/src/ui/tui/primitives/DissolveTransition.js +143 -0
  214. package/dist/src/ui/tui/primitives/EventPlanViewer.d.ts +9 -0
  215. package/dist/src/ui/tui/primitives/EventPlanViewer.js +9 -0
  216. package/dist/src/ui/tui/primitives/KagiSmallWebViewer.d.ts +7 -0
  217. package/dist/src/ui/tui/primitives/KagiSmallWebViewer.js +101 -0
  218. package/dist/src/ui/tui/primitives/LoadingBox.d.ts +8 -0
  219. package/dist/src/ui/tui/primitives/LoadingBox.js +9 -0
  220. package/dist/src/ui/tui/primitives/LogViewer.d.ts +11 -0
  221. package/dist/src/ui/tui/primitives/LogViewer.js +55 -0
  222. package/dist/src/ui/tui/primitives/PickerMenu.d.ts +20 -0
  223. package/dist/src/ui/tui/primitives/PickerMenu.js +212 -0
  224. package/dist/src/ui/tui/primitives/ProgressList.d.ts +15 -0
  225. package/dist/src/ui/tui/primitives/ProgressList.js +29 -0
  226. package/dist/src/ui/tui/primitives/PromptLabel.d.ts +11 -0
  227. package/dist/src/ui/tui/primitives/PromptLabel.js +12 -0
  228. package/dist/src/ui/tui/primitives/ReportViewer.d.ts +12 -0
  229. package/dist/src/ui/tui/primitives/ReportViewer.js +99 -0
  230. package/dist/src/ui/tui/primitives/ScreenErrorBoundary.d.ts +26 -0
  231. package/dist/src/ui/tui/primitives/ScreenErrorBoundary.js +29 -0
  232. package/dist/src/ui/tui/primitives/SlashCommandInput.d.ts +21 -0
  233. package/dist/src/ui/tui/primitives/SlashCommandInput.js +85 -0
  234. package/dist/src/ui/tui/primitives/SnakeGame.d.ts +1 -0
  235. package/dist/src/ui/tui/primitives/SnakeGame.js +1 -0
  236. package/dist/src/ui/tui/primitives/SplitView.d.ts +11 -0
  237. package/dist/src/ui/tui/primitives/SplitView.js +8 -0
  238. package/dist/src/ui/tui/primitives/TabContainer.d.ts +18 -0
  239. package/dist/src/ui/tui/primitives/TabContainer.js +30 -0
  240. package/dist/src/ui/tui/primitives/index.d.ts +23 -0
  241. package/dist/src/ui/tui/primitives/index.js +19 -0
  242. package/dist/src/ui/tui/router.d.ts +61 -0
  243. package/dist/src/ui/tui/router.js +104 -0
  244. package/dist/src/ui/tui/screen-registry.d.ts +19 -0
  245. package/dist/src/ui/tui/screen-registry.js +56 -0
  246. package/dist/src/ui/tui/screens/ActivationOptionsScreen.d.ts +12 -0
  247. package/dist/src/ui/tui/screens/ActivationOptionsScreen.js +57 -0
  248. package/dist/src/ui/tui/screens/AuthScreen.d.ts +18 -0
  249. package/dist/src/ui/tui/screens/AuthScreen.js +107 -0
  250. package/dist/src/ui/tui/screens/ChecklistScreen.d.ts +22 -0
  251. package/dist/src/ui/tui/screens/ChecklistScreen.js +122 -0
  252. package/dist/src/ui/tui/screens/DataIngestionCheckScreen.d.ts +24 -0
  253. package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +113 -0
  254. package/dist/src/ui/tui/screens/DataSetupScreen.d.ts +17 -0
  255. package/dist/src/ui/tui/screens/DataSetupScreen.js +73 -0
  256. package/dist/src/ui/tui/screens/IntroScreen.d.ts +16 -0
  257. package/dist/src/ui/tui/screens/IntroScreen.js +86 -0
  258. package/dist/src/ui/tui/screens/LoginScreen.d.ts +15 -0
  259. package/dist/src/ui/tui/screens/LoginScreen.js +65 -0
  260. package/dist/src/ui/tui/screens/LogoutScreen.d.ts +12 -0
  261. package/dist/src/ui/tui/screens/LogoutScreen.js +28 -0
  262. package/dist/src/ui/tui/screens/McpScreen.d.ts +26 -0
  263. package/dist/src/ui/tui/screens/McpScreen.js +148 -0
  264. package/dist/src/ui/tui/screens/OutageScreen.d.ts +10 -0
  265. package/dist/src/ui/tui/screens/OutageScreen.js +17 -0
  266. package/dist/src/ui/tui/screens/OutroScreen.d.ts +11 -0
  267. package/dist/src/ui/tui/screens/OutroScreen.js +69 -0
  268. package/dist/src/ui/tui/screens/RegionSelectScreen.d.ts +17 -0
  269. package/dist/src/ui/tui/screens/RegionSelectScreen.js +40 -0
  270. package/dist/src/ui/tui/screens/RunScreen.d.ts +16 -0
  271. package/dist/src/ui/tui/screens/RunScreen.js +212 -0
  272. package/dist/src/ui/tui/screens/SettingsOverrideScreen.d.ts +10 -0
  273. package/dist/src/ui/tui/screens/SettingsOverrideScreen.js +23 -0
  274. package/dist/src/ui/tui/screens/SetupScreen.d.ts +13 -0
  275. package/dist/src/ui/tui/screens/SetupScreen.js +73 -0
  276. package/dist/src/ui/tui/screens/SlackScreen.d.ts +25 -0
  277. package/dist/src/ui/tui/screens/SlackScreen.js +97 -0
  278. package/dist/src/ui/tui/services/mcp-installer.d.ts +25 -0
  279. package/dist/src/ui/tui/services/mcp-installer.js +82 -0
  280. package/dist/src/ui/tui/start-tui.d.ts +10 -0
  281. package/dist/src/ui/tui/start-tui.js +50 -0
  282. package/dist/src/ui/tui/store.d.ts +231 -0
  283. package/dist/src/ui/tui/store.js +568 -0
  284. package/dist/src/ui/tui/styles.d.ts +31 -0
  285. package/dist/src/ui/tui/styles.js +33 -0
  286. package/dist/src/ui/wizard-ui.d.ts +110 -0
  287. package/dist/src/ui/wizard-ui.js +18 -0
  288. package/dist/src/utils/ampli-settings.d.ts +37 -0
  289. package/dist/src/utils/ampli-settings.js +182 -0
  290. package/dist/src/utils/analytics.d.ts +35 -0
  291. package/dist/src/utils/analytics.js +133 -0
  292. package/dist/src/utils/anthropic-status.d.ts +17 -0
  293. package/dist/src/utils/anthropic-status.js +51 -0
  294. package/dist/src/utils/api-key-store.d.ts +35 -0
  295. package/dist/src/utils/api-key-store.js +176 -0
  296. package/dist/src/utils/bash.d.ts +2 -0
  297. package/dist/src/utils/bash.js +53 -0
  298. package/dist/src/utils/custom-headers.d.ts +9 -0
  299. package/dist/src/utils/custom-headers.js +23 -0
  300. package/dist/src/utils/debug.d.ts +23 -0
  301. package/dist/src/utils/debug.js +86 -0
  302. package/dist/src/utils/environment.d.ts +4 -0
  303. package/dist/src/utils/environment.js +76 -0
  304. package/dist/src/utils/file-utils.d.ts +2 -0
  305. package/dist/src/utils/file-utils.js +16 -0
  306. package/dist/src/utils/get-api-key.d.ts +17 -0
  307. package/dist/src/utils/get-api-key.js +50 -0
  308. package/dist/src/utils/logging.d.ts +9 -0
  309. package/dist/src/utils/logging.js +48 -0
  310. package/dist/src/utils/oauth.d.ts +53 -0
  311. package/dist/src/utils/oauth.js +354 -0
  312. package/dist/src/utils/package-json.d.ts +25 -0
  313. package/dist/src/utils/package-json.js +26 -0
  314. package/dist/src/utils/package-manager.d.ts +21 -0
  315. package/dist/src/utils/package-manager.js +208 -0
  316. package/dist/src/utils/semver.d.ts +21 -0
  317. package/dist/src/utils/semver.js +61 -0
  318. package/dist/src/utils/setup-utils.d.ts +82 -0
  319. package/dist/src/utils/setup-utils.js +467 -0
  320. package/dist/src/utils/shell-completions.d.ts +10 -0
  321. package/dist/src/utils/shell-completions.js +199 -0
  322. package/dist/src/utils/string.d.ts +1 -0
  323. package/dist/src/utils/string.js +8 -0
  324. package/dist/src/utils/types.d.ts +72 -0
  325. package/dist/src/utils/types.js +2 -0
  326. package/dist/src/utils/urls.d.ts +14 -0
  327. package/dist/src/utils/urls.js +69 -0
  328. package/dist/src/utils/vendor/is-unicorn-supported.d.ts +1 -0
  329. package/dist/src/utils/vendor/is-unicorn-supported.js +23 -0
  330. package/dist/src/utils/wizard-abort.d.ts +13 -0
  331. package/dist/src/utils/wizard-abort.js +56 -0
  332. package/man/amplitude-wizard.1 +170 -0
  333. package/package.json +144 -0
@@ -0,0 +1,1217 @@
1
+ "use strict";
2
+ /**
3
+ * Shared agent interface for Amplitude wizards
4
+ * Uses Claude Agent SDK directly with Amplitude LLM gateway
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.AgentErrorType = exports.AgentSignals = void 0;
44
+ exports.checkClaudeSettingsOverrides = checkClaudeSettingsOverrides;
45
+ exports.backupAndFixClaudeSettings = backupAndFixClaudeSettings;
46
+ exports.restoreClaudeSettings = restoreClaudeSettings;
47
+ exports.createStopHook = createStopHook;
48
+ exports.buildWizardMetadata = buildWizardMetadata;
49
+ exports.isSkillInstallCommand = isSkillInstallCommand;
50
+ exports.matchesAllowedPrefix = matchesAllowedPrefix;
51
+ exports.wizardCanUseTool = wizardCanUseTool;
52
+ exports.initializeAgent = initializeAgent;
53
+ exports.getAgent = getAgent;
54
+ exports.runAgentLocally = runAgentLocally;
55
+ exports.runAgent = runAgent;
56
+ const path_1 = __importDefault(require("path"));
57
+ const fs = __importStar(require("fs"));
58
+ const ui_1 = require("../ui");
59
+ const debug_1 = require("../utils/debug");
60
+ const analytics_1 = require("../utils/analytics");
61
+ const constants_1 = require("./constants");
62
+ const wizard_session_1 = require("./wizard-session");
63
+ const wizard_abort_1 = require("../utils/wizard-abort");
64
+ const custom_headers_1 = require("../utils/custom-headers");
65
+ const urls_1 = require("../utils/urls");
66
+ const ampli_settings_1 = require("../utils/ampli-settings");
67
+ const safe_tools_1 = require("./safe-tools");
68
+ const wizard_tools_1 = require("./wizard-tools");
69
+ const commandments_1 = require("./commandments");
70
+ const zod_1 = require("zod");
71
+ const schemas_1 = require("./middleware/schemas");
72
+ const agent_hooks_1 = require("./agent-hooks");
73
+ // Dynamic import cache for ESM module
74
+ let _sdkModule = null;
75
+ async function getSDKModule() {
76
+ if (!_sdkModule) {
77
+ const mod = await import('@anthropic-ai/claude-agent-sdk');
78
+ _sdkModule = { query: mod.query };
79
+ }
80
+ return _sdkModule;
81
+ }
82
+ /**
83
+ * Get the path to the bundled Claude Code CLI from the SDK package.
84
+ * This ensures we use the SDK's bundled version rather than the user's installed Claude Code.
85
+ */
86
+ function getClaudeCodeExecutablePath() {
87
+ // require.resolve finds the package's main entry, then we get cli.js from same dir
88
+ const sdkPackagePath = require.resolve('@anthropic-ai/claude-agent-sdk');
89
+ return path_1.default.join(path_1.default.dirname(sdkPackagePath), 'cli.js');
90
+ }
91
+ exports.AgentSignals = {
92
+ /** Signal emitted when the agent reports progress to the user */
93
+ STATUS: '[STATUS]',
94
+ /** Signal emitted when the agent cannot access the Amplitude MCP server */
95
+ ERROR_MCP_MISSING: '[ERROR-MCP-MISSING]',
96
+ /** Signal emitted when the agent cannot access the setup resource */
97
+ ERROR_RESOURCE_MISSING: '[ERROR-RESOURCE-MISSING]',
98
+ /** Signal emitted when the agent provides a remark about its run */
99
+ WIZARD_REMARK: '[WIZARD-REMARK]',
100
+ /** Signal prefix for benchmark logging */
101
+ BENCHMARK: '[BENCHMARK]',
102
+ };
103
+ /**
104
+ * Error types that can be returned from agent execution.
105
+ * These correspond to the error signals that the agent emits.
106
+ */
107
+ var AgentErrorType;
108
+ (function (AgentErrorType) {
109
+ /** Agent could not access the Amplitude MCP server */
110
+ AgentErrorType["MCP_MISSING"] = "WIZARD_MCP_MISSING";
111
+ /** Agent could not access the setup resource */
112
+ AgentErrorType["RESOURCE_MISSING"] = "WIZARD_RESOURCE_MISSING";
113
+ /** API rate limit exceeded */
114
+ AgentErrorType["RATE_LIMIT"] = "WIZARD_RATE_LIMIT";
115
+ /** Generic API error */
116
+ AgentErrorType["API_ERROR"] = "WIZARD_API_ERROR";
117
+ /** Authentication failed — bearer token invalid or expired */
118
+ AgentErrorType["AUTH_ERROR"] = "WIZARD_AUTH_ERROR";
119
+ })(AgentErrorType || (exports.AgentErrorType = AgentErrorType = {}));
120
+ const BLOCKING_ENV_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'];
121
+ /**
122
+ * Check if .claude/settings.json in the project directory contains env
123
+ * overrides for blocking keys that block the Wizard from accessing the Amplitude LLM Gateway.
124
+ * Returns the list of matched key names, or an empty array if none found.
125
+ */
126
+ function checkClaudeSettingsOverrides(workingDirectory) {
127
+ const candidates = [
128
+ path_1.default.join(workingDirectory, '.claude', 'settings.json'),
129
+ path_1.default.join(workingDirectory, '.claude', 'settings'),
130
+ ];
131
+ const claudeSettingsSchema = zod_1.z.object({
132
+ env: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
133
+ });
134
+ for (const filePath of candidates) {
135
+ try {
136
+ const raw = fs.readFileSync(filePath, 'utf-8');
137
+ const result = claudeSettingsSchema.safeParse(JSON.parse(raw));
138
+ if (result.success) {
139
+ return BLOCKING_ENV_KEYS.filter((key) => key in result.data.env);
140
+ }
141
+ }
142
+ catch {
143
+ // File doesn't exist or isn't valid JSON — skip
144
+ }
145
+ }
146
+ return [];
147
+ }
148
+ /**
149
+ * Copy .claude/settings.json to .wizard-backup (overwriting if it exists),
150
+ * then remove the original so the SDK doesn't load the blocking overrides.
151
+ */
152
+ function backupAndFixClaudeSettings(workingDirectory) {
153
+ for (const name of ['settings.json', 'settings']) {
154
+ const filePath = path_1.default.join(workingDirectory, '.claude', name);
155
+ const backupPath = `${filePath}.wizard-backup`;
156
+ analytics_1.analytics.wizardCapture('backedup-claude-settings');
157
+ try {
158
+ fs.copyFileSync(filePath, backupPath);
159
+ fs.unlinkSync(filePath);
160
+ (0, wizard_abort_1.registerCleanup)(() => {
161
+ try {
162
+ restoreClaudeSettings(workingDirectory);
163
+ }
164
+ catch (error) {
165
+ analytics_1.analytics.captureException(error instanceof Error ? error : new Error(String(error)));
166
+ }
167
+ });
168
+ return true;
169
+ }
170
+ catch {
171
+ // File doesn't exist — try next candidate
172
+ }
173
+ }
174
+ return false;
175
+ }
176
+ /**
177
+ * Restore .claude/settings.json from .wizard-backup.
178
+ * Copies (not moves) so the backup is preserved.
179
+ */
180
+ function restoreClaudeSettings(workingDirectory) {
181
+ for (const name of ['settings.json', 'settings']) {
182
+ const backup = path_1.default.join(workingDirectory, '.claude', `${name}.wizard-backup`);
183
+ try {
184
+ fs.copyFileSync(backup, path_1.default.join(workingDirectory, '.claude', name));
185
+ analytics_1.analytics.wizardCapture('restored-claude-settings');
186
+ return;
187
+ }
188
+ catch (error) {
189
+ analytics_1.analytics.captureException(error instanceof Error ? error : new Error(String(error)));
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Create a stop hook callback that drains the additional feature queue,
195
+ * then collects a remark, then allows stop.
196
+ *
197
+ * Three-phase logic using closure state:
198
+ * Phase 1 — drain queue: block with each feature prompt in order
199
+ * Phase 2 — collect remark (once): block with remark prompt
200
+ * Phase 3 — allow stop: return {}
201
+ *
202
+ * If `isAuthError()` returns true, all phases are skipped and stop is
203
+ * allowed immediately — the agent cannot respond when auth has failed.
204
+ */
205
+ function createStopHook(featureQueue, isAuthError = () => false) {
206
+ let featureIndex = 0;
207
+ let remarkRequested = false;
208
+ return (input) => {
209
+ const stop_hook_active = input.stop_hook_active;
210
+ (0, debug_1.logToFile)('Stop hook triggered', {
211
+ stop_hook_active,
212
+ featureIndex,
213
+ remarkRequested,
214
+ queueLength: featureQueue.length,
215
+ });
216
+ // If an auth error occurred, allow stop immediately — the agent cannot
217
+ // make further API calls to process feature prompts or reflection requests.
218
+ if (isAuthError()) {
219
+ (0, debug_1.logToFile)('Stop hook: allowing stop (auth error detected)');
220
+ return Promise.resolve({});
221
+ }
222
+ // Phase 1: drain feature queue
223
+ if (featureIndex < featureQueue.length) {
224
+ const feature = featureQueue[featureIndex++];
225
+ const prompt = wizard_session_1.ADDITIONAL_FEATURE_PROMPTS[feature];
226
+ (0, debug_1.logToFile)(`Stop hook: injecting feature prompt for ${feature}`);
227
+ return Promise.resolve({ decision: 'block', reason: prompt });
228
+ }
229
+ // Phase 2: collect remark (once)
230
+ if (!remarkRequested) {
231
+ remarkRequested = true;
232
+ (0, debug_1.logToFile)('Stop hook: requesting reflection');
233
+ return Promise.resolve({
234
+ decision: 'block',
235
+ reason: `Before concluding, provide a brief remark about what information or guidance would have been useful to have in the integration prompt or documentation for this run. Specifically cite anything that would have prevented tool failures, erroneous edits, or other wasted turns. Format your response exactly as: ${exports.AgentSignals.WIZARD_REMARK} Your remark here`,
236
+ });
237
+ }
238
+ // Phase 3: allow stop
239
+ (0, debug_1.logToFile)('Stop hook: allowing stop');
240
+ return Promise.resolve({});
241
+ };
242
+ }
243
+ const GATEWAY_LIVENESS_TIMEOUT_MS = 8_000;
244
+ /**
245
+ * Ping the gateway URL with a short timeout.
246
+ * Any HTTP response (even 4xx/5xx) means the gateway is reachable.
247
+ * A timeout or connection error means it's down.
248
+ */
249
+ async function checkGatewayLiveness(gatewayUrl) {
250
+ const controller = new AbortController();
251
+ const id = setTimeout(() => controller.abort(), GATEWAY_LIVENESS_TIMEOUT_MS);
252
+ try {
253
+ await fetch(gatewayUrl, { method: 'HEAD', signal: controller.signal });
254
+ return true;
255
+ }
256
+ catch {
257
+ return false;
258
+ }
259
+ finally {
260
+ clearTimeout(id);
261
+ }
262
+ }
263
+ /**
264
+ * Select wizard metadata from WIZARD_VARIANTS using the variant feature flag.
265
+ * If the flag is missing or the value is not in config, returns the "base" variant (VARIANT: "base").
266
+ */
267
+ function buildWizardMetadata(flags = {}) {
268
+ const variantKey = flags[constants_1.WIZARD_VARIANT_FLAG_KEY];
269
+ const variant = (variantKey && constants_1.WIZARD_VARIANTS[variantKey]) ?? constants_1.WIZARD_VARIANTS['base'];
270
+ return { ...variant };
271
+ }
272
+ /**
273
+ * Build env for the SDK subprocess: process.env plus ANTHROPIC_CUSTOM_HEADERS from wizard metadata/flags.
274
+ */
275
+ function buildAgentEnv(wizardMetadata, wizardFlags) {
276
+ const headers = (0, custom_headers_1.createCustomHeaders)();
277
+ for (const [key, value] of Object.entries(wizardMetadata)) {
278
+ headers.add(key.startsWith(constants_1.AMPLITUDE_PROPERTY_HEADER_PREFIX)
279
+ ? key
280
+ : `${constants_1.AMPLITUDE_PROPERTY_HEADER_PREFIX}${key}`, value);
281
+ }
282
+ for (const [flagKey, variant] of Object.entries(wizardFlags)) {
283
+ if (!flagKey.toLowerCase().startsWith('wizard'))
284
+ continue;
285
+ headers.addFlag(flagKey, variant);
286
+ }
287
+ const encoded = headers.encode();
288
+ (0, debug_1.logToFile)('ANTHROPIC_CUSTOM_HEADERS', encoded);
289
+ return encoded;
290
+ }
291
+ /**
292
+ * Executables that can be used to run build commands.
293
+ * Includes package managers, language build tools, and static site generators.
294
+ */
295
+ const PACKAGE_MANAGERS = [
296
+ // JavaScript / Node
297
+ 'npm',
298
+ 'pnpm',
299
+ 'yarn',
300
+ 'bun',
301
+ 'npx',
302
+ 'deno',
303
+ // Python
304
+ 'pip',
305
+ 'pip3',
306
+ 'poetry',
307
+ 'pipenv',
308
+ 'uv',
309
+ // Ruby
310
+ 'gem',
311
+ 'bundle',
312
+ 'bundler',
313
+ 'rake',
314
+ // PHP
315
+ 'composer',
316
+ // Go
317
+ 'go',
318
+ // Rust
319
+ 'cargo',
320
+ // Java / Kotlin / Android
321
+ 'gradle',
322
+ './gradlew',
323
+ 'mvn',
324
+ './mvnw',
325
+ // .NET
326
+ 'dotnet',
327
+ // Swift
328
+ 'swift',
329
+ // Haskell
330
+ 'stack',
331
+ 'cabal',
332
+ // Elixir
333
+ 'mix',
334
+ // Flutter / Dart
335
+ 'flutter',
336
+ 'dart',
337
+ // Make
338
+ 'make',
339
+ // Static site generators
340
+ 'zola',
341
+ 'hugo',
342
+ 'jekyll',
343
+ 'eleventy',
344
+ 'hexo',
345
+ 'pelican',
346
+ 'mkdocs',
347
+ ];
348
+ /**
349
+ * Commands that are safe to run with no sub-command (the executable alone builds the project).
350
+ */
351
+ const STANDALONE_BUILD_COMMANDS = ['hugo', 'make', 'eleventy'];
352
+ /**
353
+ * Safe sub-commands/scripts that can be run with any executable in PACKAGE_MANAGERS.
354
+ * Uses startsWith matching, so 'build' matches 'build', 'build:prod', etc.
355
+ * Note: Linting tools are in LINTING_TOOLS and checked separately.
356
+ */
357
+ const SAFE_SCRIPTS = [
358
+ // Package / dependency installation
359
+ 'install',
360
+ 'add',
361
+ 'ci',
362
+ 'get',
363
+ 'restore',
364
+ 'fetch',
365
+ 'deps',
366
+ 'update',
367
+ // Build / compile / generate
368
+ 'build',
369
+ 'compile',
370
+ 'assemble',
371
+ 'package',
372
+ 'generate',
373
+ 'bundle',
374
+ // Type checking (various naming conventions)
375
+ 'tsc',
376
+ 'typecheck',
377
+ 'type-check',
378
+ 'check-types',
379
+ 'types',
380
+ // Check / verify
381
+ 'check',
382
+ // Test
383
+ 'test',
384
+ // Serve (for build verification with static site tools)
385
+ 'serve',
386
+ // Module / dependency management sub-commands
387
+ 'mod',
388
+ 'pub',
389
+ // Make targets
390
+ 'all',
391
+ // Linting/formatting script names (actual tools are in LINTING_TOOLS)
392
+ 'lint',
393
+ 'format',
394
+ ];
395
+ /**
396
+ * Dangerous shell operators that could allow command injection.
397
+ * Note: We handle `2>&1` and `| tail/head` separately as safe patterns.
398
+ * Note: `&&` is allowed for specific safe patterns like skill installation.
399
+ */
400
+ const DANGEROUS_OPERATORS = /[;`$()]/;
401
+ /**
402
+ * Check if command is a Amplitude skill installation from MCP.
403
+ * We control the MCP server, so we only need to verify:
404
+ * 1. It installs to .claude/skills/
405
+ * 2. It downloads from our GitHub releases or localhost (dev)
406
+ */
407
+ function isSkillInstallCommand(command) {
408
+ if (!command.startsWith('mkdir -p .claude/skills/'))
409
+ return false;
410
+ const urlMatch = command.match(/curl -sL ['"]([^'"]+)['"]/);
411
+ if (!urlMatch)
412
+ return false;
413
+ const url = urlMatch[1];
414
+ return (url.startsWith('https://github.com/Amplitude/context-mill/releases/') ||
415
+ /^http:\/\/localhost:\d+\//.test(url));
416
+ }
417
+ /**
418
+ * Check if command is an allowed package manager command.
419
+ * Matches: <pkg-manager> [run|exec] <safe-script> [args...]
420
+ */
421
+ function matchesAllowedPrefix(command) {
422
+ const parts = command.split(/\s+/);
423
+ if (parts.length === 0 || !PACKAGE_MANAGERS.includes(parts[0])) {
424
+ return false;
425
+ }
426
+ // Allow tools that are safe to invoke with no sub-command (e.g. `hugo`, `make`)
427
+ if (parts.length === 1 && STANDALONE_BUILD_COMMANDS.includes(parts[0])) {
428
+ return true;
429
+ }
430
+ // Skip 'run' or 'exec' if present
431
+ let scriptIndex = 1;
432
+ if (parts[scriptIndex] === 'run' || parts[scriptIndex] === 'exec') {
433
+ scriptIndex++;
434
+ }
435
+ // Get the script/command portion (may include args)
436
+ const scriptPart = parts.slice(scriptIndex).join(' ');
437
+ // Check if script starts with any safe script name or linting tool
438
+ return (SAFE_SCRIPTS.some((safe) => scriptPart.startsWith(safe)) ||
439
+ safe_tools_1.LINTING_TOOLS.some((tool) => scriptPart.startsWith(tool)));
440
+ }
441
+ /**
442
+ * Permission hook that allows only safe commands.
443
+ * - Package manager install commands
444
+ * - Build/typecheck/lint commands for verification
445
+ * - Piping to tail/head for output limiting is allowed
446
+ * - Stderr redirection (2>&1) is allowed
447
+ * - Amplitude skill installation commands from MCP
448
+ */
449
+ function wizardCanUseTool(toolName, input) {
450
+ // Block direct reads/writes of .env files — use wizard-tools MCP instead
451
+ if (toolName === 'Read' || toolName === 'Write' || toolName === 'Edit') {
452
+ const filePath = typeof input.file_path === 'string' ? input.file_path : '';
453
+ const basename = path_1.default.basename(filePath);
454
+ if (basename.startsWith('.env')) {
455
+ (0, debug_1.logToFile)(`Denying ${toolName} on env file: ${filePath}`);
456
+ return {
457
+ behavior: 'deny',
458
+ message: `Direct ${toolName} of ${basename} is not allowed. Use the wizard-tools MCP server (check_env_keys / set_env_values) to read or modify environment variables.`,
459
+ };
460
+ }
461
+ return { behavior: 'allow', updatedInput: input };
462
+ }
463
+ // Block Grep when it directly targets a .env file.
464
+ // Note: ripgrep skips dotfiles (like .env*) by default during directory traversal,
465
+ // so broad searches like `Grep { path: "." }` are already safe.
466
+ if (toolName === 'Grep') {
467
+ const grepPath = typeof input.path === 'string' ? input.path : '';
468
+ if (grepPath && path_1.default.basename(grepPath).startsWith('.env')) {
469
+ (0, debug_1.logToFile)(`Denying Grep on env file: ${grepPath}`);
470
+ return {
471
+ behavior: 'deny',
472
+ message: `Grep on ${path_1.default.basename(grepPath)} is not allowed. Use the wizard-tools MCP server (check_env_keys) to check environment variables.`,
473
+ };
474
+ }
475
+ return { behavior: 'allow', updatedInput: input };
476
+ }
477
+ // Allow all other non-Bash tools
478
+ if (toolName !== 'Bash') {
479
+ return { behavior: 'allow', updatedInput: input };
480
+ }
481
+ const command = (typeof input.command === 'string' ? input.command : '').trim();
482
+ // Check for Amplitude skill installation command (before dangerous operator check)
483
+ // These commands use && chaining but are generated by MCP with a strict format
484
+ if (isSkillInstallCommand(command)) {
485
+ (0, debug_1.logToFile)(`Allowing skill installation command: ${command}`);
486
+ (0, debug_1.debug)(`Allowing skill installation command: ${command}`);
487
+ return { behavior: 'allow', updatedInput: input };
488
+ }
489
+ // Block definitely dangerous operators: ; ` $ ( )
490
+ if (DANGEROUS_OPERATORS.test(command)) {
491
+ (0, debug_1.logToFile)(`Denying bash command with dangerous operators: ${command}`);
492
+ (0, debug_1.debug)(`Denying bash command with dangerous operators: ${command}`);
493
+ analytics_1.analytics.wizardCapture('bash denied', {
494
+ reason: 'dangerous operators',
495
+ command,
496
+ });
497
+ return {
498
+ behavior: 'deny',
499
+ message: `Bash command not allowed. Shell operators like ; \` $ ( ) are not permitted.`,
500
+ };
501
+ }
502
+ // Normalize: remove safe stderr redirection (2>&1, 2>&2, etc.)
503
+ const normalized = command.replace(/\s*\d*>&\d+\s*/g, ' ').trim();
504
+ // Check for pipe to tail/head (safe output limiting)
505
+ const pipeMatch = normalized.match(/^(.+?)\s*\|\s*(tail|head)(\s+\S+)*\s*$/);
506
+ if (pipeMatch) {
507
+ const baseCommand = pipeMatch[1].trim();
508
+ // Block if base command has pipes or & (multiple chaining)
509
+ if (/[|&]/.test(baseCommand)) {
510
+ (0, debug_1.logToFile)(`Denying bash command with multiple pipes: ${command}`);
511
+ (0, debug_1.debug)(`Denying bash command with multiple pipes: ${command}`);
512
+ analytics_1.analytics.wizardCapture('bash denied', {
513
+ reason: 'multiple pipes',
514
+ command,
515
+ });
516
+ return {
517
+ behavior: 'deny',
518
+ message: `Bash command not allowed. Only single pipe to tail/head is permitted.`,
519
+ };
520
+ }
521
+ if (matchesAllowedPrefix(baseCommand)) {
522
+ (0, debug_1.logToFile)(`Allowing bash command with output limiter: ${command}`);
523
+ (0, debug_1.debug)(`Allowing bash command with output limiter: ${command}`);
524
+ return { behavior: 'allow', updatedInput: input };
525
+ }
526
+ }
527
+ // Block remaining pipes and & (not covered by tail/head case above)
528
+ if (/[|&]/.test(normalized)) {
529
+ (0, debug_1.logToFile)(`Denying bash command with pipe/&: ${command}`);
530
+ (0, debug_1.debug)(`Denying bash command with pipe/&: ${command}`);
531
+ analytics_1.analytics.wizardCapture('bash denied', {
532
+ reason: 'disallowed pipe',
533
+ command,
534
+ });
535
+ return {
536
+ behavior: 'deny',
537
+ message: `Bash command not allowed. Pipes are only permitted with tail/head for output limiting.`,
538
+ };
539
+ }
540
+ // Check if command starts with any allowed prefix (package manager commands)
541
+ if (matchesAllowedPrefix(normalized)) {
542
+ (0, debug_1.logToFile)(`Allowing bash command: ${command}`);
543
+ (0, debug_1.debug)(`Allowing bash command: ${command}`);
544
+ return { behavior: 'allow', updatedInput: input };
545
+ }
546
+ (0, debug_1.logToFile)(`Denying bash command: ${command}`);
547
+ (0, debug_1.debug)(`Denying bash command: ${command}`);
548
+ analytics_1.analytics.wizardCapture('bash denied', {
549
+ reason: 'not in allowlist',
550
+ command,
551
+ });
552
+ return {
553
+ behavior: 'deny',
554
+ message: `Bash command not allowed. Only install, build, typecheck, lint, and formatting commands are permitted.`,
555
+ };
556
+ }
557
+ /**
558
+ * Initialize agent configuration for the LLM gateway
559
+ */
560
+ async function initializeAgent(config, options) {
561
+ // Initialize log file for this run
562
+ (0, debug_1.initLogFile)();
563
+ (0, debug_1.logToFile)('Agent initialization starting');
564
+ (0, debug_1.logToFile)('Install directory:', options.installDir);
565
+ (0, ui_1.getUI)().log.step('Initializing Claude agent...');
566
+ try {
567
+ const useDirectApiKey = !!process.env.ANTHROPIC_API_KEY;
568
+ const useLocalClaude = !config.amplitudeBearerToken && !useDirectApiKey;
569
+ if (useDirectApiKey) {
570
+ (0, debug_1.logToFile)('ANTHROPIC_API_KEY found — bypassing Amplitude gateway');
571
+ }
572
+ else if (useLocalClaude) {
573
+ (0, debug_1.logToFile)('No Amplitude API key — using local claude CLI');
574
+ }
575
+ else {
576
+ // Configure LLM gateway environment variables (inherited by SDK subprocess)
577
+ const gatewayUrl = (0, urls_1.getLlmGatewayUrlFromHost)(config.amplitudeApiHost);
578
+ // Fail fast if the gateway isn't responding rather than hanging indefinitely
579
+ const alive = await checkGatewayLiveness(gatewayUrl);
580
+ if (!alive) {
581
+ throw new Error(`Could not reach the Amplitude LLM gateway (${gatewayUrl}). ` +
582
+ `Check your network connection, or set ANTHROPIC_API_KEY to use the Anthropic API directly.`);
583
+ }
584
+ process.env.ANTHROPIC_BASE_URL = gatewayUrl;
585
+ process.env.ANTHROPIC_AUTH_TOKEN = config.amplitudeBearerToken;
586
+ // Use CLAUDE_CODE_OAUTH_TOKEN to override any stored /login credentials
587
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = config.amplitudeBearerToken;
588
+ // Disable experimental betas (like input_examples) that the LLM gateway doesn't support
589
+ process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
590
+ (0, debug_1.logToFile)('Configured LLM gateway:', gatewayUrl);
591
+ }
592
+ // Configure MCP servers
593
+ const mcpServers = {};
594
+ if (!config.skipAmplitudeMcp) {
595
+ mcpServers['amplitude-wizard'] = {
596
+ type: 'http',
597
+ url: config.amplitudeMcpUrl,
598
+ headers: {
599
+ Authorization: `Bearer ${config.amplitudeBearerToken}`,
600
+ 'User-Agent': constants_1.WIZARD_USER_AGENT,
601
+ },
602
+ };
603
+ }
604
+ for (const [name, { url }] of Object.entries(config.additionalMcpServers ?? {})) {
605
+ mcpServers[name] = { type: 'http', url };
606
+ }
607
+ // Add in-process wizard tools (env files, package manager detection)
608
+ const wizardToolsServer = await (0, wizard_tools_1.createWizardToolsServer)({
609
+ workingDirectory: config.workingDirectory,
610
+ detectPackageManager: config.detectPackageManager,
611
+ skillsBaseUrl: config.skillsBaseUrl,
612
+ });
613
+ mcpServers['wizard-tools'] = wizardToolsServer;
614
+ const agentRunConfig = {
615
+ workingDirectory: config.workingDirectory,
616
+ mcpServers,
617
+ // Gateway expects 'anthropic/claude-sonnet-4-6'; direct Anthropic API expects 'claude-sonnet-4-6'
618
+ model: useDirectApiKey
619
+ ? 'claude-sonnet-4-6'
620
+ : 'anthropic/claude-sonnet-4-6',
621
+ wizardFlags: config.wizardFlags,
622
+ wizardMetadata: config.wizardMetadata,
623
+ useLocalClaude,
624
+ useDirectApiKey,
625
+ };
626
+ (0, debug_1.logToFile)('Agent config:', {
627
+ workingDirectory: agentRunConfig.workingDirectory,
628
+ amplitudeMcpUrl: config.amplitudeMcpUrl,
629
+ useLocalClaude,
630
+ useDirectApiKey,
631
+ bearerTokenPresent: !!config.amplitudeBearerToken,
632
+ });
633
+ if (options.debug) {
634
+ (0, debug_1.debug)('Agent config:', {
635
+ workingDirectory: agentRunConfig.workingDirectory,
636
+ amplitudeMcpUrl: config.amplitudeMcpUrl,
637
+ useLocalClaude,
638
+ useDirectApiKey,
639
+ bearerTokenPresent: !!config.amplitudeBearerToken,
640
+ });
641
+ }
642
+ (0, ui_1.getUI)().log.step(`Verbose logs: ${(0, debug_1.getLogFilePath)()}`);
643
+ (0, ui_1.getUI)().log.success("Agent initialized. Let's get cooking!");
644
+ return agentRunConfig;
645
+ }
646
+ catch (error) {
647
+ (0, debug_1.logToFile)('Agent initialization error:', error);
648
+ (0, debug_1.debug)('Agent initialization error:', error);
649
+ throw error;
650
+ }
651
+ }
652
+ let _agentPromise = null;
653
+ function buildDefaultAgentConfig() {
654
+ const storedToken = (0, ampli_settings_1.getStoredToken)()?.accessToken ?? '';
655
+ const host = (0, urls_1.getHostFromRegion)('us');
656
+ const mcpUrl = process.env.MCP_URL ?? 'https://mcp.amplitude.com/mcp';
657
+ return {
658
+ workingDirectory: process.cwd(),
659
+ amplitudeMcpUrl: mcpUrl,
660
+ amplitudeApiKey: storedToken,
661
+ amplitudeBearerToken: storedToken,
662
+ amplitudeApiHost: host,
663
+ skipAmplitudeMcp: !storedToken,
664
+ detectPackageManager: () => Promise.resolve({ detected: [], primary: null, recommendation: '' }),
665
+ };
666
+ }
667
+ const DEFAULT_WIZARD_OPTIONS = {
668
+ debug: false,
669
+ forceInstall: false,
670
+ installDir: process.cwd(),
671
+ default: false,
672
+ signup: false,
673
+ localMcp: false,
674
+ ci: false,
675
+ menu: false,
676
+ benchmark: false,
677
+ };
678
+ /**
679
+ * Return the already-initialized agent config, or call initializeAgent to create it.
680
+ * Concurrent calls during initialization share the same Promise.
681
+ * On error the cached Promise is cleared so the next call retries.
682
+ *
683
+ * Omitting config/options reads the bearer token from ~/.ampli.json and uses production
684
+ * defaults (MCP disabled if no token found, cwd as working directory).
685
+ */
686
+ async function getAgent(config = buildDefaultAgentConfig(), options = DEFAULT_WIZARD_OPTIONS) {
687
+ if (!_agentPromise) {
688
+ _agentPromise = initializeAgent(config, options).catch((err) => {
689
+ _agentPromise = null;
690
+ throw err;
691
+ });
692
+ }
693
+ return _agentPromise;
694
+ }
695
+ /**
696
+ * Run the agent by spawning the user's local `claude` CLI with --continue.
697
+ * Used when no Amplitude API key is present (local development).
698
+ * Streams stdout line-by-line and forwards text to the spinner.
699
+ */
700
+ async function runAgentLocally(prompt, workingDirectory, spinner, successMessage, errorMessage) {
701
+ const { spawn } = await import('child_process');
702
+ (0, debug_1.logToFile)('Running agent via local claude CLI');
703
+ return new Promise((resolve, reject) => {
704
+ const proc = spawn('claude', ['--continue', prompt], {
705
+ cwd: workingDirectory,
706
+ env: process.env,
707
+ stdio: ['ignore', 'pipe', 'pipe'],
708
+ });
709
+ proc.stdout.setEncoding('utf8');
710
+ proc.stderr.setEncoding('utf8');
711
+ proc.stdout.on('data', (chunk) => {
712
+ const lines = chunk.split('\n').filter((l) => l.trim());
713
+ for (const line of lines) {
714
+ (0, debug_1.logToFile)('claude stdout:', line);
715
+ spinner.message(line.slice(0, 80));
716
+ (0, ui_1.getUI)().pushStatus(line.slice(0, 80));
717
+ }
718
+ });
719
+ proc.stderr.on('data', (chunk) => {
720
+ (0, debug_1.logToFile)('claude stderr:', chunk);
721
+ });
722
+ proc.on('close', (code) => {
723
+ if (code === 0) {
724
+ spinner.stop(successMessage);
725
+ resolve({});
726
+ }
727
+ else {
728
+ spinner.stop(errorMessage);
729
+ reject(new Error(`claude exited with code ${code ?? 'unknown'}`));
730
+ }
731
+ });
732
+ proc.on('error', (err) => {
733
+ spinner.stop(errorMessage);
734
+ reject(err);
735
+ });
736
+ });
737
+ }
738
+ /**
739
+ * Execute an agent with the provided prompt and options
740
+ * Handles the full lifecycle: spinner, execution, error handling
741
+ *
742
+ * @returns An object containing any error detected in the agent's output
743
+ */
744
+ async function runAgent(agentConfig, prompt, options, spinner, config, middleware) {
745
+ const { spinnerMessage = 'Customizing your Amplitude setup...', successMessage = 'Amplitude integration complete', errorMessage = 'Integration failed', } = config ?? {};
746
+ spinner.start(spinnerMessage);
747
+ if (agentConfig.useLocalClaude) {
748
+ return runAgentLocally(prompt, agentConfig.workingDirectory, spinner, successMessage, errorMessage);
749
+ }
750
+ const { query } = await getSDKModule();
751
+ const cliPath = getClaudeCodeExecutablePath();
752
+ (0, debug_1.logToFile)('Starting agent run');
753
+ (0, debug_1.logToFile)('Claude Code executable:', cliPath);
754
+ (0, debug_1.logToFile)('Prompt:', prompt);
755
+ const startTime = Date.now();
756
+ const collectedText = [];
757
+ const recentStatuses = []; // rolling last-3 STATUS messages for heartbeat
758
+ // Track if we received a successful result (before any cleanup errors)
759
+ let receivedSuccessResult = false;
760
+ let lastResultMessage = null;
761
+ // Workaround for SDK bug: stdin closes before canUseTool responses can be sent.
762
+ // The fix is to use an async generator for the prompt that stays open until
763
+ // the result is received, keeping the stdin stream alive for permission responses.
764
+ // signalDone is reassigned each retry attempt — the outer catch always has the latest.
765
+ // See: https://github.com/anthropics/claude-code/issues/4775
766
+ // See: https://github.com/anthropics/claude-agent-sdk-typescript/issues/41
767
+ let signalDone = Function.prototype;
768
+ // Helper to handle successful completion (used in normal path and race condition recovery)
769
+ const completeWithSuccess = (suppressedError) => {
770
+ const durationMs = Date.now() - startTime;
771
+ const durationSeconds = Math.round(durationMs / 1000);
772
+ if (suppressedError) {
773
+ (0, debug_1.logToFile)(`Ignoring post-completion error, agent completed successfully in ${durationSeconds}s`);
774
+ (0, debug_1.logToFile)('Suppressed error:', suppressedError.message);
775
+ }
776
+ else {
777
+ (0, debug_1.logToFile)(`Agent run completed in ${durationSeconds}s`);
778
+ }
779
+ // Extract and capture the agent's reflection on the run
780
+ const outputText = collectedText.join('\n');
781
+ const remarkRegex = new RegExp(`${exports.AgentSignals.WIZARD_REMARK.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*(.+?)(?:\\n|$)`, 's');
782
+ const remarkMatch = outputText.match(remarkRegex);
783
+ if (remarkMatch && remarkMatch[1]) {
784
+ const remark = remarkMatch[1].trim();
785
+ if (remark) {
786
+ analytics_1.analytics.capture(constants_1.WIZARD_REMARK_EVENT_NAME, { remark });
787
+ }
788
+ }
789
+ analytics_1.analytics.wizardCapture('agent completed', {
790
+ duration_ms: durationMs,
791
+ duration_seconds: durationSeconds,
792
+ });
793
+ try {
794
+ if (lastResultMessage) {
795
+ middleware?.finalize(lastResultMessage, durationMs);
796
+ }
797
+ }
798
+ catch (e) {
799
+ (0, debug_1.logToFile)(`${exports.AgentSignals.BENCHMARK} Middleware finalize error:`, e);
800
+ }
801
+ spinner.stop(successMessage);
802
+ return {};
803
+ };
804
+ // Heartbeat interval — every 10s print the last 3 STATUS messages so the
805
+ // user can see progress in the CLI without waiting for the next update.
806
+ const heartbeatInterval = setInterval(() => {
807
+ if (recentStatuses.length > 0) {
808
+ (0, ui_1.getUI)().heartbeat([...recentStatuses]);
809
+ }
810
+ }, 10_000);
811
+ // Event plan file watcher — cleaned up in finally block
812
+ let eventPlanWatcher;
813
+ let eventPlanInterval;
814
+ try {
815
+ // Tools needed for the wizard:
816
+ // - File operations: Read, Write, Edit
817
+ // - Search: Glob, Grep
818
+ // - Commands: Bash (with restrictions via canUseTool)
819
+ // - MCP discovery: ListMcpResourcesTool (to find available skills)
820
+ // - Skills: Skill (to load installed Amplitude skills)
821
+ // MCP tools (Amplitude) come from mcpServers, not allowedTools
822
+ const allowedTools = [
823
+ 'Read',
824
+ 'Write',
825
+ 'Edit',
826
+ 'Glob',
827
+ 'Grep',
828
+ 'Bash',
829
+ 'ListMcpResourcesTool',
830
+ 'Skill',
831
+ ...wizard_tools_1.WIZARD_TOOL_NAMES,
832
+ ];
833
+ // Watch for .amplitude-events.json and feed into the store (set up once, before retries)
834
+ const eventPlanPath = path_1.default.join(agentConfig.workingDirectory, '.amplitude-events.json');
835
+ const eventPlanSchema = zod_1.z.array(zod_1.z.looseObject({
836
+ name: zod_1.z.string().optional(),
837
+ event: zod_1.z.string().optional(),
838
+ eventName: zod_1.z.string().optional(),
839
+ description: zod_1.z.string().optional(),
840
+ eventDescriptionAndReasoning: zod_1.z.string().optional(),
841
+ }));
842
+ const readEventPlan = () => {
843
+ try {
844
+ const content = fs.readFileSync(eventPlanPath, 'utf-8');
845
+ const result = eventPlanSchema.safeParse(JSON.parse(content));
846
+ if (result.success) {
847
+ (0, ui_1.getUI)().setEventPlan(result.data.map((e) => ({
848
+ name: e.name ?? e.event ?? e.eventName ?? '',
849
+ description: e.description ?? e.eventDescriptionAndReasoning ?? '',
850
+ })));
851
+ }
852
+ }
853
+ catch {
854
+ // File doesn't exist or isn't valid JSON yet
855
+ }
856
+ };
857
+ try {
858
+ eventPlanWatcher = fs.watch(eventPlanPath, () => readEventPlan());
859
+ readEventPlan();
860
+ }
861
+ catch {
862
+ // File doesn't exist yet — poll until it appears
863
+ eventPlanInterval = setInterval(() => {
864
+ try {
865
+ fs.accessSync(eventPlanPath);
866
+ readEventPlan();
867
+ clearInterval(eventPlanInterval);
868
+ eventPlanInterval = undefined;
869
+ eventPlanWatcher = fs.watch(eventPlanPath, () => readEventPlan());
870
+ }
871
+ catch {
872
+ // Still waiting
873
+ }
874
+ }, 1000);
875
+ }
876
+ // Retry loop: if the agent stalls (no message for STALL_TIMEOUT_MS), abort and
877
+ // re-run with a fresh AbortController and prompt stream. Up to MAX_RETRIES retries.
878
+ const MAX_RETRIES = 2;
879
+ const STALL_TIMEOUT_MS = 20_000;
880
+ // Tracks whether an authentication failure was detected in the current attempt.
881
+ // Passed to createStopHook so it can skip reflection when auth is broken.
882
+ let authErrorDetected = false;
883
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
884
+ if (attempt > 0) {
885
+ (0, debug_1.logToFile)(`Agent stall retry: attempt ${attempt + 1} of ${MAX_RETRIES + 1}`);
886
+ analytics_1.analytics.wizardCapture('agent stall retry', { attempt });
887
+ // Clear per-attempt output so stale error markers don't affect the fresh run
888
+ collectedText.length = 0;
889
+ recentStatuses.length = 0;
890
+ authErrorDetected = false;
891
+ }
892
+ // Fresh prompt stream per attempt — stdin stays open until result received
893
+ const resultReceived = new Promise((resolve) => {
894
+ signalDone = resolve;
895
+ });
896
+ const createPromptStream = async function* () {
897
+ yield {
898
+ type: 'user',
899
+ session_id: '',
900
+ message: { role: 'user', content: prompt },
901
+ parent_tool_use_id: null,
902
+ };
903
+ await resultReceived;
904
+ };
905
+ // AbortController lets us cancel a stalled query so we can retry
906
+ const controller = new AbortController();
907
+ let staleTimer;
908
+ const resetStaleTimer = () => {
909
+ if (staleTimer)
910
+ clearTimeout(staleTimer);
911
+ staleTimer = setTimeout(() => {
912
+ (0, debug_1.logToFile)(`Agent stalled — no message for ${STALL_TIMEOUT_MS / 1000}s (attempt ${attempt + 1})`);
913
+ analytics_1.analytics.wizardCapture('agent stall detected', {
914
+ attempt: attempt + 1,
915
+ stall_timeout_ms: STALL_TIMEOUT_MS,
916
+ });
917
+ controller.abort('stall');
918
+ }, STALL_TIMEOUT_MS);
919
+ };
920
+ try {
921
+ const response = query({
922
+ prompt: createPromptStream(),
923
+ options: {
924
+ model: agentConfig.model,
925
+ cwd: agentConfig.workingDirectory,
926
+ permissionMode: 'acceptEdits',
927
+ mcpServers: agentConfig.mcpServers,
928
+ // Load skills from project's .claude/skills/ directory
929
+ settingSources: ['project'],
930
+ // Explicitly enable required tools including Skill
931
+ allowedTools,
932
+ systemPrompt: {
933
+ type: 'preset',
934
+ preset: 'claude_code',
935
+ // Append wizard-wide commandments (from YAML) rather than replacing
936
+ // the preset so we keep default Claude Code behaviors.
937
+ append: (0, commandments_1.getWizardCommandments)(),
938
+ },
939
+ env: {
940
+ ...process.env,
941
+ // When using the Amplitude gateway, block ANTHROPIC_API_KEY so it doesn't
942
+ // override the gateway's OAuth token. When using a direct API key, pass it through.
943
+ ...(agentConfig.useDirectApiKey
944
+ ? {}
945
+ : { ANTHROPIC_API_KEY: undefined }),
946
+ ANTHROPIC_CUSTOM_HEADERS: buildAgentEnv(agentConfig.wizardMetadata ?? {}, agentConfig.wizardFlags ?? {}),
947
+ },
948
+ canUseTool: (toolName, input) => {
949
+ (0, debug_1.logToFile)('canUseTool called:', { toolName, input });
950
+ const result = wizardCanUseTool(toolName, input);
951
+ (0, debug_1.logToFile)('canUseTool result:', result);
952
+ return Promise.resolve(result);
953
+ },
954
+ tools: { type: 'preset', preset: 'claude_code' },
955
+ // Capture stderr from CLI subprocess for debugging
956
+ stderr: (data) => {
957
+ (0, debug_1.logToFile)('CLI stderr:', data);
958
+ if (options.debug) {
959
+ (0, debug_1.debug)('CLI stderr:', data);
960
+ }
961
+ },
962
+ hooks: (0, agent_hooks_1.buildHooksConfig)({
963
+ Stop: createStopHook(config?.additionalFeatureQueue ?? [], () => authErrorDetected),
964
+ }),
965
+ // Allow aborting a stalled query so we can retry cleanly
966
+ abortSignal: controller.signal,
967
+ },
968
+ });
969
+ // Start stale timer — reset on each received message
970
+ resetStaleTimer();
971
+ // Process the async generator — validate each message at the boundary
972
+ for await (const rawMessage of response) {
973
+ resetStaleTimer();
974
+ const parsed = (0, schemas_1.safeParseSDKMessage)(rawMessage);
975
+ if (!parsed.ok) {
976
+ (0, debug_1.logToFile)('Skipping malformed SDK message:', parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`));
977
+ continue;
978
+ }
979
+ const message = parsed.message;
980
+ // Pass receivedSuccessResult so handleSDKMessage can suppress user-facing error
981
+ // output for post-success cleanup errors while still logging them to file
982
+ handleSDKMessage(message, options, spinner, collectedText, receivedSuccessResult, recentStatuses);
983
+ try {
984
+ middleware?.onMessage(message);
985
+ }
986
+ catch (e) {
987
+ (0, debug_1.logToFile)(`${exports.AgentSignals.BENCHMARK} Middleware onMessage error:`, e);
988
+ }
989
+ // Detect authentication failures so the stop hook can skip reflection
990
+ if (message.type === 'result' &&
991
+ message.is_error &&
992
+ JSON.stringify(message).includes('authentication_failed')) {
993
+ authErrorDetected = true;
994
+ (0, debug_1.logToFile)('Auth error detected: authentication_failed in result');
995
+ }
996
+ if (message.type === 'system' && message.subtype === 'init') {
997
+ for (const server of message.mcp_servers ?? []) {
998
+ if (server.name === 'amplitude-wizard' &&
999
+ server.status === 'needs-auth') {
1000
+ authErrorDetected = true;
1001
+ (0, debug_1.logToFile)('Auth error detected: amplitude-wizard MCP needs-auth');
1002
+ }
1003
+ }
1004
+ }
1005
+ // Signal completion when result received
1006
+ if (message.type === 'result') {
1007
+ // Track successful results before any potential cleanup errors
1008
+ // The SDK may emit a second error result during cleanup due to a race condition
1009
+ if (message.subtype === 'success' && !message.is_error) {
1010
+ receivedSuccessResult = true;
1011
+ lastResultMessage = message;
1012
+ }
1013
+ signalDone();
1014
+ }
1015
+ }
1016
+ // Clean completion — exit the retry loop
1017
+ clearTimeout(staleTimer);
1018
+ break;
1019
+ }
1020
+ catch (innerError) {
1021
+ clearTimeout(staleTimer);
1022
+ signalDone(); // unblock the prompt stream for this attempt
1023
+ // Stall-aborted with retries remaining — try again
1024
+ if (controller.signal.aborted && attempt < MAX_RETRIES) {
1025
+ (0, debug_1.logToFile)(`Retrying after stall (next attempt: ${attempt + 2} of ${MAX_RETRIES + 1})`);
1026
+ continue;
1027
+ }
1028
+ // Already received a successful result — this is an SDK cleanup race condition
1029
+ if (receivedSuccessResult) {
1030
+ return completeWithSuccess(innerError);
1031
+ }
1032
+ // Re-throw to the outer catch for API error handling / spinner cleanup
1033
+ throw innerError;
1034
+ }
1035
+ }
1036
+ const outputText = collectedText.join('\n');
1037
+ // Auth error takes priority — the agent cannot recover without re-authentication
1038
+ if (authErrorDetected) {
1039
+ (0, debug_1.logToFile)('Agent error: AUTH_ERROR');
1040
+ spinner.stop('Authentication failed');
1041
+ return { error: AgentErrorType.AUTH_ERROR };
1042
+ }
1043
+ // Check for error markers in the agent's output
1044
+ if (outputText.includes(exports.AgentSignals.ERROR_MCP_MISSING)) {
1045
+ (0, debug_1.logToFile)('Agent error: MCP_MISSING');
1046
+ spinner.stop('Agent could not access Amplitude MCP');
1047
+ return { error: AgentErrorType.MCP_MISSING };
1048
+ }
1049
+ if (outputText.includes(exports.AgentSignals.ERROR_RESOURCE_MISSING)) {
1050
+ (0, debug_1.logToFile)('Agent error: RESOURCE_MISSING');
1051
+ spinner.stop('Agent could not access setup resource');
1052
+ return { error: AgentErrorType.RESOURCE_MISSING };
1053
+ }
1054
+ // Check for API errors (rate limits, etc.)
1055
+ // Extract just the API error line(s), not the entire output
1056
+ const apiErrorMatch = outputText.match(/API Error: [^\n]+/g);
1057
+ const apiErrorMessage = apiErrorMatch
1058
+ ? apiErrorMatch.join('\n')
1059
+ : 'Unknown API error';
1060
+ if (outputText.includes('API Error: 429')) {
1061
+ (0, debug_1.logToFile)('Agent error: RATE_LIMIT');
1062
+ spinner.stop('Rate limit exceeded');
1063
+ return { error: AgentErrorType.RATE_LIMIT, message: apiErrorMessage };
1064
+ }
1065
+ if (outputText.includes('API Error:')) {
1066
+ (0, debug_1.logToFile)('Agent error: API_ERROR');
1067
+ spinner.stop('API error occurred');
1068
+ return { error: AgentErrorType.API_ERROR, message: apiErrorMessage };
1069
+ }
1070
+ return completeWithSuccess();
1071
+ }
1072
+ catch (error) {
1073
+ // Signal done to unblock the async generator
1074
+ signalDone();
1075
+ // If we already received a successful result, the error is from SDK cleanup
1076
+ // This happens due to a race condition: the SDK tries to send a cleanup command
1077
+ // after the prompt stream closes, but streaming mode is still active.
1078
+ // See: https://github.com/anthropics/claude-agent-sdk-typescript/issues/41
1079
+ if (receivedSuccessResult) {
1080
+ return completeWithSuccess(error);
1081
+ }
1082
+ // Check if we collected an API error before the exception was thrown
1083
+ const outputText = collectedText.join('\n');
1084
+ // Extract just the API error line(s), not the entire output
1085
+ const apiErrorMatch = outputText.match(/API Error: [^\n]+/g);
1086
+ const apiErrorMessage = apiErrorMatch
1087
+ ? apiErrorMatch.join('\n')
1088
+ : 'Unknown API error';
1089
+ if (outputText.includes('API Error: 429')) {
1090
+ (0, debug_1.logToFile)('Agent error (caught): RATE_LIMIT');
1091
+ spinner.stop('Rate limit exceeded');
1092
+ return { error: AgentErrorType.RATE_LIMIT, message: apiErrorMessage };
1093
+ }
1094
+ if (outputText.includes('API Error:')) {
1095
+ (0, debug_1.logToFile)('Agent error (caught): API_ERROR');
1096
+ spinner.stop('API error occurred');
1097
+ return { error: AgentErrorType.API_ERROR, message: apiErrorMessage };
1098
+ }
1099
+ // No API error found, re-throw the original exception
1100
+ spinner.stop(errorMessage);
1101
+ (0, ui_1.getUI)().log.error(`Error: ${error.message}`);
1102
+ (0, debug_1.logToFile)('Agent run failed:', error);
1103
+ (0, debug_1.debug)('Full error:', error);
1104
+ throw error;
1105
+ }
1106
+ finally {
1107
+ clearInterval(heartbeatInterval);
1108
+ eventPlanWatcher?.close();
1109
+ if (eventPlanInterval)
1110
+ clearInterval(eventPlanInterval);
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Handle SDK messages and provide user feedback
1115
+ *
1116
+ * @param receivedSuccessResult - If true, suppress user-facing error output for cleanup errors
1117
+ * while still logging to file. The SDK may emit a second error
1118
+ * result after success due to cleanup race conditions.
1119
+ */
1120
+ function handleSDKMessage(message, options, spinner, collectedText, receivedSuccessResult = false, recentStatuses) {
1121
+ (0, debug_1.logToFile)(`SDK Message: ${message.type}`, JSON.stringify(message, null, 2));
1122
+ if (options.debug) {
1123
+ (0, debug_1.debug)(`SDK Message type: ${message.type}`);
1124
+ }
1125
+ switch (message.type) {
1126
+ case 'assistant': {
1127
+ // Extract text content from assistant messages
1128
+ const content = message.message?.content;
1129
+ if (Array.isArray(content)) {
1130
+ for (const block of content) {
1131
+ if (block.type === 'text' && typeof block.text === 'string') {
1132
+ collectedText.push(block.text);
1133
+ // Check for [STATUS] markers
1134
+ const statusRegex = new RegExp(`^.*${exports.AgentSignals.STATUS.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*(.+?)$`, 'm');
1135
+ const statusMatch = block.text.match(statusRegex);
1136
+ if (statusMatch) {
1137
+ const statusText = statusMatch[1].trim();
1138
+ spinner.message(statusText);
1139
+ if (recentStatuses) {
1140
+ recentStatuses.push(statusText);
1141
+ if (recentStatuses.length > 3)
1142
+ recentStatuses.shift();
1143
+ }
1144
+ }
1145
+ }
1146
+ // Intercept TodoWrite tool_use blocks for task progression
1147
+ if (block.type === 'tool_use' &&
1148
+ block.name === 'TodoWrite' &&
1149
+ block.input &&
1150
+ Array.isArray(block.input.todos)) {
1151
+ (0, ui_1.getUI)().syncTodos(block.input.todos);
1152
+ }
1153
+ }
1154
+ }
1155
+ break;
1156
+ }
1157
+ case 'result': {
1158
+ // Check is_error flag - can be true even when subtype is 'success'
1159
+ if (message.is_error) {
1160
+ (0, debug_1.logToFile)('Agent result with error:', message.result);
1161
+ if (typeof message.result === 'string') {
1162
+ collectedText.push(message.result);
1163
+ }
1164
+ // Only show errors to user if we haven't already succeeded.
1165
+ // Post-success errors are SDK cleanup noise (telemetry failures, streaming
1166
+ // mode race conditions). Full message already logged above via JSON dump.
1167
+ if (message.errors && !receivedSuccessResult) {
1168
+ for (const err of message.errors) {
1169
+ (0, ui_1.getUI)().log.error(`Error: ${err}`);
1170
+ (0, debug_1.logToFile)('ERROR:', err);
1171
+ }
1172
+ }
1173
+ }
1174
+ else if (message.subtype === 'success') {
1175
+ (0, debug_1.logToFile)('Agent completed successfully');
1176
+ if (typeof message.result === 'string') {
1177
+ collectedText.push(message.result);
1178
+ }
1179
+ }
1180
+ else {
1181
+ (0, debug_1.logToFile)('Agent result with error:', message.result);
1182
+ // Error result - only show to user if we haven't already succeeded.
1183
+ // Full message already logged above via JSON dump.
1184
+ if (message.errors && !receivedSuccessResult) {
1185
+ for (const err of message.errors) {
1186
+ (0, ui_1.getUI)().log.error(`Error: ${err}`);
1187
+ (0, debug_1.logToFile)('ERROR:', err);
1188
+ }
1189
+ }
1190
+ }
1191
+ break;
1192
+ }
1193
+ case 'system': {
1194
+ if (message.subtype === 'init') {
1195
+ const mcpStatuses = message.mcp_servers ?? [];
1196
+ (0, debug_1.logToFile)('Agent session initialized', {
1197
+ model: message.model,
1198
+ tools: message.tools?.length,
1199
+ mcpServers: mcpStatuses,
1200
+ });
1201
+ for (const server of mcpStatuses) {
1202
+ (0, debug_1.logToFile)(`MCP "${server.name}": ${server.status}`);
1203
+ if (server.status !== 'connected') {
1204
+ (0, ui_1.getUI)().log.warn(`MCP server "${server.name}" is not connected (${server.status})`);
1205
+ }
1206
+ }
1207
+ }
1208
+ break;
1209
+ }
1210
+ default:
1211
+ // Log other message types for debugging
1212
+ if (options.debug) {
1213
+ (0, debug_1.debug)(`Unhandled message type: ${message.type}`);
1214
+ }
1215
+ break;
1216
+ }
1217
+ }