patchwork-os 0.2.0-beta.1 → 0.2.0-beta.10.canary.100

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 (701) hide show
  1. package/README.bridge.md +14 -14
  2. package/README.md +201 -34
  3. package/deploy/README.md +1 -1
  4. package/deploy/bootstrap-vps.sh +14 -9
  5. package/deploy/deploy-dashboard.sh +25 -1
  6. package/deploy/macos/README.md +153 -0
  7. package/deploy/macos/com.patchwork.bridge.plist.template +54 -0
  8. package/deploy/macos/com.patchwork.tunnel.plist.template +76 -0
  9. package/deploy/macos/install-mac-bridge.sh +244 -0
  10. package/deploy/macos/uninstall-mac-bridge.sh +22 -0
  11. package/dist/activityLog.d.ts +6 -0
  12. package/dist/activityLog.js +63 -26
  13. package/dist/activityLog.js.map +1 -1
  14. package/dist/adapters/claude.js +22 -16
  15. package/dist/adapters/claude.js.map +1 -1
  16. package/dist/adapters/gemini.js +15 -11
  17. package/dist/adapters/gemini.js.map +1 -1
  18. package/dist/adapters/openai.js +9 -9
  19. package/dist/adapters/openai.js.map +1 -1
  20. package/dist/ajv2020.d.ts +25 -0
  21. package/dist/ajv2020.js +33 -0
  22. package/dist/ajv2020.js.map +1 -0
  23. package/dist/analyticsAggregator.js +1 -1
  24. package/dist/analyticsAggregator.js.map +1 -1
  25. package/dist/analyticsConfig.d.ts +29 -0
  26. package/dist/analyticsConfig.js +102 -0
  27. package/dist/analyticsConfig.js.map +1 -0
  28. package/dist/analyticsPrefs.d.ts +38 -2
  29. package/dist/analyticsPrefs.js +148 -23
  30. package/dist/analyticsPrefs.js.map +1 -1
  31. package/dist/analyticsSend.d.ts +17 -1
  32. package/dist/analyticsSend.js +67 -5
  33. package/dist/analyticsSend.js.map +1 -1
  34. package/dist/approvalHttp.d.ts +14 -0
  35. package/dist/approvalHttp.js +212 -9
  36. package/dist/approvalHttp.js.map +1 -1
  37. package/dist/approvalInsights.js +13 -5
  38. package/dist/approvalInsights.js.map +1 -1
  39. package/dist/approvalQueue.d.ts +97 -3
  40. package/dist/approvalQueue.js +193 -19
  41. package/dist/approvalQueue.js.map +1 -1
  42. package/dist/approvalSignals.js +19 -11
  43. package/dist/approvalSignals.js.map +1 -1
  44. package/dist/automation.d.ts +35 -10
  45. package/dist/automation.js +133 -37
  46. package/dist/automation.js.map +1 -1
  47. package/dist/automationSuggestions.js +10 -8
  48. package/dist/automationSuggestions.js.map +1 -1
  49. package/dist/bridge.d.ts +2 -0
  50. package/dist/bridge.js +219 -35
  51. package/dist/bridge.js.map +1 -1
  52. package/dist/bridgeLockDiscovery.d.ts +27 -1
  53. package/dist/bridgeLockDiscovery.js +38 -11
  54. package/dist/bridgeLockDiscovery.js.map +1 -1
  55. package/dist/bridgeToken.js +3 -2
  56. package/dist/bridgeToken.js.map +1 -1
  57. package/dist/claudeDriver.js +23 -8
  58. package/dist/claudeDriver.js.map +1 -1
  59. package/dist/claudeOrchestrator.d.ts +1 -1
  60. package/dist/claudeOrchestrator.js +87 -44
  61. package/dist/claudeOrchestrator.js.map +1 -1
  62. package/dist/commands/analytics.d.ts +8 -0
  63. package/dist/commands/analytics.js +134 -0
  64. package/dist/commands/analytics.js.map +1 -0
  65. package/dist/commands/auditEnv.d.ts +36 -0
  66. package/dist/commands/auditEnv.js +202 -0
  67. package/dist/commands/auditEnv.js.map +1 -0
  68. package/dist/commands/connect.d.ts +47 -0
  69. package/dist/commands/connect.js +419 -0
  70. package/dist/commands/connect.js.map +1 -0
  71. package/dist/commands/dashboard.js +8 -1
  72. package/dist/commands/dashboard.js.map +1 -1
  73. package/dist/commands/doctor.d.ts +35 -0
  74. package/dist/commands/doctor.js +51 -0
  75. package/dist/commands/doctor.js.map +1 -0
  76. package/dist/commands/install.js +3 -0
  77. package/dist/commands/install.js.map +1 -1
  78. package/dist/commands/launchd.js +15 -16
  79. package/dist/commands/launchd.js.map +1 -1
  80. package/dist/commands/patchworkInit.d.ts +5 -0
  81. package/dist/commands/patchworkInit.js +89 -7
  82. package/dist/commands/patchworkInit.js.map +1 -1
  83. package/dist/commands/recipe.d.ts +151 -0
  84. package/dist/commands/recipe.js +649 -9
  85. package/dist/commands/recipe.js.map +1 -1
  86. package/dist/commands/recipeInstall.js +11 -4
  87. package/dist/commands/recipeInstall.js.map +1 -1
  88. package/dist/commands/shadowScan.d.ts +34 -0
  89. package/dist/commands/shadowScan.js +142 -0
  90. package/dist/commands/shadowScan.js.map +1 -0
  91. package/dist/commands/task.d.ts +25 -0
  92. package/dist/commands/task.js +62 -42
  93. package/dist/commands/task.js.map +1 -1
  94. package/dist/commands/tools.d.ts +20 -1
  95. package/dist/commands/tools.js +112 -3
  96. package/dist/commands/tools.js.map +1 -1
  97. package/dist/commands/tracesExport.d.ts +15 -1
  98. package/dist/commands/tracesExport.js +39 -5
  99. package/dist/commands/tracesExport.js.map +1 -1
  100. package/dist/commands/tracesImport.js +21 -4
  101. package/dist/commands/tracesImport.js.map +1 -1
  102. package/dist/commitIssueLinkLog.d.ts +16 -0
  103. package/dist/commitIssueLinkLog.js +110 -24
  104. package/dist/commitIssueLinkLog.js.map +1 -1
  105. package/dist/companions/registry.js +15 -7
  106. package/dist/companions/registry.js.map +1 -1
  107. package/dist/config.d.ts +47 -3
  108. package/dist/config.js +111 -21
  109. package/dist/config.js.map +1 -1
  110. package/dist/connectorRoutes.js +1422 -57
  111. package/dist/connectorRoutes.js.map +1 -1
  112. package/dist/connectors/airtable.d.ts +230 -0
  113. package/dist/connectors/airtable.js +700 -0
  114. package/dist/connectors/airtable.js.map +1 -0
  115. package/dist/connectors/asana.js +13 -9
  116. package/dist/connectors/asana.js.map +1 -1
  117. package/dist/connectors/baseConnector.js +25 -3
  118. package/dist/connectors/baseConnector.js.map +1 -1
  119. package/dist/connectors/caldiy.d.ts +137 -0
  120. package/dist/connectors/caldiy.js +424 -0
  121. package/dist/connectors/caldiy.js.map +1 -0
  122. package/dist/connectors/circleci.d.ts +162 -0
  123. package/dist/connectors/circleci.js +576 -0
  124. package/dist/connectors/circleci.js.map +1 -0
  125. package/dist/connectors/cloudflare.d.ts +132 -0
  126. package/dist/connectors/cloudflare.js +622 -0
  127. package/dist/connectors/cloudflare.js.map +1 -0
  128. package/dist/connectors/confluence.js +35 -0
  129. package/dist/connectors/confluence.js.map +1 -1
  130. package/dist/connectors/connectorRedirectUri.d.ts +30 -0
  131. package/dist/connectors/connectorRedirectUri.js +38 -0
  132. package/dist/connectors/connectorRedirectUri.js.map +1 -0
  133. package/dist/connectors/connectorRegistry.d.ts +60 -0
  134. package/dist/connectors/connectorRegistry.js +354 -0
  135. package/dist/connectors/connectorRegistry.js.map +1 -0
  136. package/dist/connectors/datadog.js +41 -5
  137. package/dist/connectors/datadog.js.map +1 -1
  138. package/dist/connectors/discord.js +14 -10
  139. package/dist/connectors/discord.js.map +1 -1
  140. package/dist/connectors/elasticsearch.d.ts +141 -0
  141. package/dist/connectors/elasticsearch.js +601 -0
  142. package/dist/connectors/elasticsearch.js.map +1 -0
  143. package/dist/connectors/figma.d.ts +130 -0
  144. package/dist/connectors/figma.js +387 -0
  145. package/dist/connectors/figma.js.map +1 -0
  146. package/dist/connectors/github.d.ts +17 -0
  147. package/dist/connectors/github.js +53 -2
  148. package/dist/connectors/github.js.map +1 -1
  149. package/dist/connectors/gitlab.js +12 -6
  150. package/dist/connectors/gitlab.js.map +1 -1
  151. package/dist/connectors/gmail.js +72 -21
  152. package/dist/connectors/gmail.js.map +1 -1
  153. package/dist/connectors/googleCalendar.js +8 -8
  154. package/dist/connectors/googleCalendar.js.map +1 -1
  155. package/dist/connectors/googleDocs.d.ts +103 -0
  156. package/dist/connectors/googleDocs.js +409 -0
  157. package/dist/connectors/googleDocs.js.map +1 -0
  158. package/dist/connectors/googleDrive.d.ts +12 -0
  159. package/dist/connectors/googleDrive.js +35 -8
  160. package/dist/connectors/googleDrive.js.map +1 -1
  161. package/dist/connectors/grafana.d.ts +133 -0
  162. package/dist/connectors/grafana.js +478 -0
  163. package/dist/connectors/grafana.js.map +1 -0
  164. package/dist/connectors/jira.d.ts +25 -0
  165. package/dist/connectors/jira.js +234 -3
  166. package/dist/connectors/jira.js.map +1 -1
  167. package/dist/connectors/linear.js +1 -1
  168. package/dist/connectors/linear.js.map +1 -1
  169. package/dist/connectors/mcpClient.js +37 -23
  170. package/dist/connectors/mcpClient.js.map +1 -1
  171. package/dist/connectors/mcpOAuth.js +78 -16
  172. package/dist/connectors/mcpOAuth.js.map +1 -1
  173. package/dist/connectors/monday.d.ts +217 -0
  174. package/dist/connectors/monday.js +655 -0
  175. package/dist/connectors/monday.js.map +1 -0
  176. package/dist/connectors/mongodb.d.ts +139 -0
  177. package/dist/connectors/mongodb.js +455 -0
  178. package/dist/connectors/mongodb.js.map +1 -0
  179. package/dist/connectors/notion.d.ts +1 -1
  180. package/dist/connectors/notion.js +13 -4
  181. package/dist/connectors/notion.js.map +1 -1
  182. package/dist/connectors/oauthStateStore.d.ts +20 -0
  183. package/dist/connectors/oauthStateStore.js +94 -4
  184. package/dist/connectors/oauthStateStore.js.map +1 -1
  185. package/dist/connectors/obsidian.d.ts +83 -0
  186. package/dist/connectors/obsidian.js +441 -0
  187. package/dist/connectors/obsidian.js.map +1 -0
  188. package/dist/connectors/paystack.d.ts +159 -0
  189. package/dist/connectors/paystack.js +607 -0
  190. package/dist/connectors/paystack.js.map +1 -0
  191. package/dist/connectors/pipedrive.d.ts +189 -0
  192. package/dist/connectors/pipedrive.js +559 -0
  193. package/dist/connectors/pipedrive.js.map +1 -0
  194. package/dist/connectors/postgres.d.ts +127 -0
  195. package/dist/connectors/postgres.js +512 -0
  196. package/dist/connectors/postgres.js.map +1 -0
  197. package/dist/connectors/posthog.d.ts +119 -0
  198. package/dist/connectors/posthog.js +479 -0
  199. package/dist/connectors/posthog.js.map +1 -0
  200. package/dist/connectors/redis.d.ts +140 -0
  201. package/dist/connectors/redis.js +579 -0
  202. package/dist/connectors/redis.js.map +1 -0
  203. package/dist/connectors/resend.d.ts +131 -0
  204. package/dist/connectors/resend.js +529 -0
  205. package/dist/connectors/resend.js.map +1 -0
  206. package/dist/connectors/salesforce.d.ts +136 -0
  207. package/dist/connectors/salesforce.js +603 -0
  208. package/dist/connectors/salesforce.js.map +1 -0
  209. package/dist/connectors/secrets.d.ts +51 -0
  210. package/dist/connectors/secrets.js +102 -0
  211. package/dist/connectors/secrets.js.map +1 -0
  212. package/dist/connectors/sendgrid.d.ts +102 -0
  213. package/dist/connectors/sendgrid.js +423 -0
  214. package/dist/connectors/sendgrid.js.map +1 -0
  215. package/dist/connectors/shopify.d.ts +145 -0
  216. package/dist/connectors/shopify.js +502 -0
  217. package/dist/connectors/shopify.js.map +1 -0
  218. package/dist/connectors/slack.d.ts +1 -1
  219. package/dist/connectors/slack.js +61 -9
  220. package/dist/connectors/slack.js.map +1 -1
  221. package/dist/connectors/snowflake.d.ts +119 -0
  222. package/dist/connectors/snowflake.js +633 -0
  223. package/dist/connectors/snowflake.js.map +1 -0
  224. package/dist/connectors/supabase.d.ts +92 -0
  225. package/dist/connectors/supabase.js +630 -0
  226. package/dist/connectors/supabase.js.map +1 -0
  227. package/dist/connectors/todoist.d.ts +117 -0
  228. package/dist/connectors/todoist.js +485 -0
  229. package/dist/connectors/todoist.js.map +1 -0
  230. package/dist/connectors/tokenStorage.js +56 -14
  231. package/dist/connectors/tokenStorage.js.map +1 -1
  232. package/dist/connectors/twilio.d.ts +118 -0
  233. package/dist/connectors/twilio.js +475 -0
  234. package/dist/connectors/twilio.js.map +1 -0
  235. package/dist/connectors/vercel.d.ts +110 -0
  236. package/dist/connectors/vercel.js +479 -0
  237. package/dist/connectors/vercel.js.map +1 -0
  238. package/dist/connectors/webflow.d.ts +118 -0
  239. package/dist/connectors/webflow.js +393 -0
  240. package/dist/connectors/webflow.js.map +1 -0
  241. package/dist/connectors/woocommerce.d.ts +220 -0
  242. package/dist/connectors/woocommerce.js +464 -0
  243. package/dist/connectors/woocommerce.js.map +1 -0
  244. package/dist/crypto.js +7 -2
  245. package/dist/crypto.js.map +1 -1
  246. package/dist/decisionReplay.d.ts +8 -6
  247. package/dist/decisionReplay.js +50 -17
  248. package/dist/decisionReplay.js.map +1 -1
  249. package/dist/decisionTraceLog.d.ts +28 -0
  250. package/dist/decisionTraceLog.js +156 -29
  251. package/dist/decisionTraceLog.js.map +1 -1
  252. package/dist/drivers/claude/api.js +33 -9
  253. package/dist/drivers/claude/api.js.map +1 -1
  254. package/dist/drivers/claude/envSanitizer.d.ts +0 -6
  255. package/dist/drivers/claude/envSanitizer.js +17 -2
  256. package/dist/drivers/claude/envSanitizer.js.map +1 -1
  257. package/dist/drivers/claude/streamParser.js +5 -4
  258. package/dist/drivers/claude/streamParser.js.map +1 -1
  259. package/dist/drivers/claude/subprocess.js +69 -16
  260. package/dist/drivers/claude/subprocess.js.map +1 -1
  261. package/dist/drivers/gemini/index.d.ts +22 -0
  262. package/dist/drivers/gemini/index.js +256 -129
  263. package/dist/drivers/gemini/index.js.map +1 -1
  264. package/dist/drivers/local/index.d.ts +2 -0
  265. package/dist/drivers/local/index.js +12 -0
  266. package/dist/drivers/local/index.js.map +1 -1
  267. package/dist/drivers/openai/index.js +30 -2
  268. package/dist/drivers/openai/index.js.map +1 -1
  269. package/dist/extensionClient.d.ts +37 -4
  270. package/dist/extensionClient.js +58 -16
  271. package/dist/extensionClient.js.map +1 -1
  272. package/dist/featureFlags.d.ts +91 -0
  273. package/dist/featureFlags.js +174 -3
  274. package/dist/featureFlags.js.map +1 -1
  275. package/dist/fileLock.js +21 -12
  276. package/dist/fileLock.js.map +1 -1
  277. package/dist/fileLockSync.d.ts +67 -0
  278. package/dist/fileLockSync.js +126 -0
  279. package/dist/fileLockSync.js.map +1 -0
  280. package/dist/fp/activityAnalytics.js +26 -12
  281. package/dist/fp/activityAnalytics.js.map +1 -1
  282. package/dist/fp/automationInterpreter.d.ts +15 -1
  283. package/dist/fp/automationInterpreter.js +267 -87
  284. package/dist/fp/automationInterpreter.js.map +1 -1
  285. package/dist/fp/automationProgram.d.ts +30 -0
  286. package/dist/fp/automationProgram.js.map +1 -1
  287. package/dist/fp/automationState.d.ts +24 -5
  288. package/dist/fp/automationState.js +56 -9
  289. package/dist/fp/automationState.js.map +1 -1
  290. package/dist/fp/automationUtils.js +1 -1
  291. package/dist/fp/automationUtils.js.map +1 -1
  292. package/dist/fp/commandDescription.js +7 -1
  293. package/dist/fp/commandDescription.js.map +1 -1
  294. package/dist/fp/extensionSnapshot.js +8 -2
  295. package/dist/fp/extensionSnapshot.js.map +1 -1
  296. package/dist/fp/interpreterContext.d.ts +66 -1
  297. package/dist/fp/interpreterContext.js +91 -1
  298. package/dist/fp/interpreterContext.js.map +1 -1
  299. package/dist/fp/policyParser.js +34 -2
  300. package/dist/fp/policyParser.js.map +1 -1
  301. package/dist/fsWatchWithFallback.d.ts +36 -0
  302. package/dist/fsWatchWithFallback.js +128 -0
  303. package/dist/fsWatchWithFallback.js.map +1 -0
  304. package/dist/haltPushDispatch.d.ts +33 -0
  305. package/dist/haltPushDispatch.js +103 -0
  306. package/dist/haltPushDispatch.js.map +1 -0
  307. package/dist/httpBodyValidation.d.ts +41 -0
  308. package/dist/httpBodyValidation.js +45 -0
  309. package/dist/httpBodyValidation.js.map +1 -0
  310. package/dist/inboxRoutes.d.ts +22 -0
  311. package/dist/inboxRoutes.js +61 -1
  312. package/dist/inboxRoutes.js.map +1 -1
  313. package/dist/index.js +1206 -158
  314. package/dist/index.js.map +1 -1
  315. package/dist/installGuard.js +6 -2
  316. package/dist/installGuard.js.map +1 -1
  317. package/dist/localEndpointGuard.d.ts +25 -0
  318. package/dist/localEndpointGuard.js +101 -0
  319. package/dist/localEndpointGuard.js.map +1 -0
  320. package/dist/lockfile.js +31 -4
  321. package/dist/lockfile.js.map +1 -1
  322. package/dist/oauth.d.ts +9 -0
  323. package/dist/oauth.js +33 -0
  324. package/dist/oauth.js.map +1 -1
  325. package/dist/oauthRoutes.d.ts +1 -1
  326. package/dist/oauthRoutes.js +5 -3
  327. package/dist/oauthRoutes.js.map +1 -1
  328. package/dist/orchestrator/childBridgeClient.d.ts +5 -0
  329. package/dist/orchestrator/childBridgeClient.js +28 -1
  330. package/dist/orchestrator/childBridgeClient.js.map +1 -1
  331. package/dist/orchestrator/childBridgeRegistry.js +29 -11
  332. package/dist/orchestrator/childBridgeRegistry.js.map +1 -1
  333. package/dist/orchestrator/orchestratorBridge.d.ts +33 -0
  334. package/dist/orchestrator/orchestratorBridge.js +70 -9
  335. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  336. package/dist/patchworkConfig.d.ts +44 -1
  337. package/dist/patchworkConfig.js +28 -3
  338. package/dist/patchworkConfig.js.map +1 -1
  339. package/dist/pluginLoader.js +10 -1
  340. package/dist/pluginLoader.js.map +1 -1
  341. package/dist/pluginWatcher.js +12 -14
  342. package/dist/pluginWatcher.js.map +1 -1
  343. package/dist/preToolUseHook.js +3 -2
  344. package/dist/preToolUseHook.js.map +1 -1
  345. package/dist/processTree.d.ts +34 -0
  346. package/dist/processTree.js +105 -0
  347. package/dist/processTree.js.map +1 -0
  348. package/dist/prompts.js +3 -3
  349. package/dist/prompts.js.map +1 -1
  350. package/dist/recipeOrchestration.d.ts +9 -0
  351. package/dist/recipeOrchestration.js +341 -24
  352. package/dist/recipeOrchestration.js.map +1 -1
  353. package/dist/recipeRoutes.d.ts +58 -2
  354. package/dist/recipeRoutes.js +744 -115
  355. package/dist/recipeRoutes.js.map +1 -1
  356. package/dist/recipes/RecipeOrchestrator.d.ts +2 -0
  357. package/dist/recipes/RecipeOrchestrator.js +6 -1
  358. package/dist/recipes/RecipeOrchestrator.js.map +1 -1
  359. package/dist/recipes/agentExecutor.d.ts +25 -5
  360. package/dist/recipes/agentExecutor.js.map +1 -1
  361. package/dist/recipes/chainedRunner.d.ts +2 -0
  362. package/dist/recipes/chainedRunner.js +156 -9
  363. package/dist/recipes/chainedRunner.js.map +1 -1
  364. package/dist/recipes/compiler.js +9 -5
  365. package/dist/recipes/compiler.js.map +1 -1
  366. package/dist/recipes/connectorPreflight.d.ts +66 -0
  367. package/dist/recipes/connectorPreflight.js +169 -0
  368. package/dist/recipes/connectorPreflight.js.map +1 -0
  369. package/dist/recipes/dependencyGraph.d.ts +9 -0
  370. package/dist/recipes/dependencyGraph.js +32 -7
  371. package/dist/recipes/dependencyGraph.js.map +1 -1
  372. package/dist/recipes/githubInstallSource.d.ts +135 -0
  373. package/dist/recipes/githubInstallSource.js +236 -0
  374. package/dist/recipes/githubInstallSource.js.map +1 -0
  375. package/dist/recipes/haltCategory.d.ts +119 -0
  376. package/dist/recipes/haltCategory.js +188 -0
  377. package/dist/recipes/haltCategory.js.map +1 -0
  378. package/dist/recipes/idempotencyKey.d.ts +154 -0
  379. package/dist/recipes/idempotencyKey.js +375 -0
  380. package/dist/recipes/idempotencyKey.js.map +1 -0
  381. package/dist/recipes/installer.js +58 -3
  382. package/dist/recipes/installer.js.map +1 -1
  383. package/dist/recipes/judgeSummary.d.ts +50 -0
  384. package/dist/recipes/judgeSummary.js +47 -0
  385. package/dist/recipes/judgeSummary.js.map +1 -0
  386. package/dist/recipes/judgeVerdict.d.ts +48 -0
  387. package/dist/recipes/judgeVerdict.js +174 -0
  388. package/dist/recipes/judgeVerdict.js.map +1 -0
  389. package/dist/recipes/migrations/index.d.ts +9 -0
  390. package/dist/recipes/migrations/index.js +133 -0
  391. package/dist/recipes/migrations/index.js.map +1 -1
  392. package/dist/recipes/names.d.ts +20 -0
  393. package/dist/recipes/names.js +25 -0
  394. package/dist/recipes/names.js.map +1 -1
  395. package/dist/recipes/parser.js +116 -6
  396. package/dist/recipes/parser.js.map +1 -1
  397. package/dist/recipes/replayRun.js +1 -1
  398. package/dist/recipes/replayRun.js.map +1 -1
  399. package/dist/recipes/runBudget.d.ts +70 -0
  400. package/dist/recipes/runBudget.js +109 -0
  401. package/dist/recipes/runBudget.js.map +1 -0
  402. package/dist/recipes/scheduler.d.ts +30 -0
  403. package/dist/recipes/scheduler.js +69 -19
  404. package/dist/recipes/scheduler.js.map +1 -1
  405. package/dist/recipes/schema.d.ts +65 -2
  406. package/dist/recipes/schemaGenerator.js +119 -7
  407. package/dist/recipes/schemaGenerator.js.map +1 -1
  408. package/dist/recipes/stepObservation.js +9 -0
  409. package/dist/recipes/stepObservation.js.map +1 -1
  410. package/dist/recipes/toolRegistry.js +33 -7
  411. package/dist/recipes/toolRegistry.js.map +1 -1
  412. package/dist/recipes/tools/airtable.d.ts +15 -0
  413. package/dist/recipes/tools/airtable.js +239 -0
  414. package/dist/recipes/tools/airtable.js.map +1 -0
  415. package/dist/recipes/tools/fanOut.d.ts +20 -0
  416. package/dist/recipes/tools/fanOut.js +199 -0
  417. package/dist/recipes/tools/fanOut.js.map +1 -0
  418. package/dist/recipes/tools/file.js +5 -2
  419. package/dist/recipes/tools/file.js.map +1 -1
  420. package/dist/recipes/tools/github.d.ts +1 -1
  421. package/dist/recipes/tools/github.js +75 -1
  422. package/dist/recipes/tools/github.js.map +1 -1
  423. package/dist/recipes/tools/gmail.js +27 -5
  424. package/dist/recipes/tools/gmail.js.map +1 -1
  425. package/dist/recipes/tools/googleDrive.js +64 -0
  426. package/dist/recipes/tools/googleDrive.js.map +1 -1
  427. package/dist/recipes/tools/http.d.ts +10 -0
  428. package/dist/recipes/tools/http.js +176 -0
  429. package/dist/recipes/tools/http.js.map +1 -0
  430. package/dist/recipes/tools/index.d.ts +7 -0
  431. package/dist/recipes/tools/index.js +7 -0
  432. package/dist/recipes/tools/index.js.map +1 -1
  433. package/dist/recipes/tools/resend.d.ts +8 -0
  434. package/dist/recipes/tools/resend.js +152 -0
  435. package/dist/recipes/tools/resend.js.map +1 -0
  436. package/dist/recipes/tools/sendgrid.d.ts +9 -0
  437. package/dist/recipes/tools/sendgrid.js +174 -0
  438. package/dist/recipes/tools/sendgrid.js.map +1 -0
  439. package/dist/recipes/tools/slack.js +1 -1
  440. package/dist/recipes/tools/twilio.d.ts +11 -0
  441. package/dist/recipes/tools/twilio.js +179 -0
  442. package/dist/recipes/tools/twilio.js.map +1 -0
  443. package/dist/recipes/tools/vercel.d.ts +9 -0
  444. package/dist/recipes/tools/vercel.js +145 -0
  445. package/dist/recipes/tools/vercel.js.map +1 -0
  446. package/dist/recipes/validation.d.ts +17 -0
  447. package/dist/recipes/validation.js +141 -11
  448. package/dist/recipes/validation.js.map +1 -1
  449. package/dist/recipes/workspaceRoot.d.ts +37 -0
  450. package/dist/recipes/workspaceRoot.js +73 -0
  451. package/dist/recipes/workspaceRoot.js.map +1 -0
  452. package/dist/recipes/yamlPositions.d.ts +56 -0
  453. package/dist/recipes/yamlPositions.js +183 -0
  454. package/dist/recipes/yamlPositions.js.map +1 -0
  455. package/dist/recipes/yamlRunner.d.ts +185 -8
  456. package/dist/recipes/yamlRunner.js +967 -224
  457. package/dist/recipes/yamlRunner.js.map +1 -1
  458. package/dist/recipesHttp.d.ts +19 -3
  459. package/dist/recipesHttp.js +67 -11
  460. package/dist/recipesHttp.js.map +1 -1
  461. package/dist/resources.js +21 -13
  462. package/dist/resources.js.map +1 -1
  463. package/dist/runLog.d.ts +58 -5
  464. package/dist/runLog.js +98 -21
  465. package/dist/runLog.js.map +1 -1
  466. package/dist/sanitizeParsedJson.d.ts +39 -0
  467. package/dist/sanitizeParsedJson.js +55 -0
  468. package/dist/sanitizeParsedJson.js.map +1 -0
  469. package/dist/schemas/recipe.v1.json +887 -198
  470. package/dist/server.d.ts +160 -3
  471. package/dist/server.js +1099 -122
  472. package/dist/server.js.map +1 -1
  473. package/dist/sessionCheckpoint.d.ts +8 -0
  474. package/dist/sessionCheckpoint.js +33 -3
  475. package/dist/sessionCheckpoint.js.map +1 -1
  476. package/dist/ssrfGuard.d.ts +16 -0
  477. package/dist/ssrfGuard.js +87 -4
  478. package/dist/ssrfGuard.js.map +1 -1
  479. package/dist/streamableHttp.d.ts +9 -4
  480. package/dist/streamableHttp.js +76 -20
  481. package/dist/streamableHttp.js.map +1 -1
  482. package/dist/telemetry.js +37 -19
  483. package/dist/telemetry.js.map +1 -1
  484. package/dist/testing/shadowRun.d.ts +52 -0
  485. package/dist/testing/shadowRun.js +71 -0
  486. package/dist/testing/shadowRun.js.map +1 -0
  487. package/dist/tools/batchLsp.d.ts +3 -0
  488. package/dist/tools/bridgeDoctor.d.ts +11 -0
  489. package/dist/tools/bridgeDoctor.js +48 -2
  490. package/dist/tools/bridgeDoctor.js.map +1 -1
  491. package/dist/tools/bridgeStatus.d.ts +0 -12
  492. package/dist/tools/bridgeStatus.js +2 -28
  493. package/dist/tools/bridgeStatus.js.map +1 -1
  494. package/dist/tools/cancelClaudeTask.d.ts +1 -0
  495. package/dist/tools/clipboard.d.ts +2 -0
  496. package/dist/tools/closeTabs.d.ts +1 -0
  497. package/dist/tools/codeLens.d.ts +1 -0
  498. package/dist/tools/createIssueFromAIComment.d.ts +1 -0
  499. package/dist/tools/ctxGetTaskContext.d.ts +9 -0
  500. package/dist/tools/ctxGetTaskContext.js +50 -2
  501. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  502. package/dist/tools/ctxQueryTraces.js +32 -24
  503. package/dist/tools/ctxQueryTraces.js.map +1 -1
  504. package/dist/tools/ctxSaveTrace.d.ts +1 -0
  505. package/dist/tools/debug.d.ts +4 -0
  506. package/dist/tools/decorations.d.ts +2 -0
  507. package/dist/tools/detectUnusedCode.js +9 -7
  508. package/dist/tools/detectUnusedCode.js.map +1 -1
  509. package/dist/tools/documentLinks.d.ts +1 -0
  510. package/dist/tools/editText.d.ts +1 -0
  511. package/dist/tools/editText.js +2 -1
  512. package/dist/tools/editText.js.map +1 -1
  513. package/dist/tools/enrichCommit.d.ts +1 -0
  514. package/dist/tools/enrichStackTrace.js +11 -5
  515. package/dist/tools/enrichStackTrace.js.map +1 -1
  516. package/dist/tools/explainDiagnostic.d.ts +1 -0
  517. package/dist/tools/explainSymbol.d.ts +1 -0
  518. package/dist/tools/fileOperations.d.ts +3 -0
  519. package/dist/tools/fileOperations.js +2 -1
  520. package/dist/tools/fileOperations.js.map +1 -1
  521. package/dist/tools/fileWatcher.d.ts +2 -0
  522. package/dist/tools/fileWatcher.js +8 -2
  523. package/dist/tools/fileWatcher.js.map +1 -1
  524. package/dist/tools/findFiles.d.ts +1 -0
  525. package/dist/tools/fixAllLintErrors.d.ts +1 -0
  526. package/dist/tools/fixAllLintErrors.js +10 -5
  527. package/dist/tools/fixAllLintErrors.js.map +1 -1
  528. package/dist/tools/foldingRanges.d.ts +1 -0
  529. package/dist/tools/formatDocument.d.ts +1 -0
  530. package/dist/tools/formatDocument.js +10 -5
  531. package/dist/tools/formatDocument.js.map +1 -1
  532. package/dist/tools/generateTests.d.ts +1 -0
  533. package/dist/tools/getAIComments.d.ts +1 -0
  534. package/dist/tools/getAnalyticsReport.js +5 -1
  535. package/dist/tools/getAnalyticsReport.js.map +1 -1
  536. package/dist/tools/getBufferContent.d.ts +1 -0
  537. package/dist/tools/getChangeImpact.d.ts +1 -0
  538. package/dist/tools/getChangeImpact.js +23 -14
  539. package/dist/tools/getChangeImpact.js.map +1 -1
  540. package/dist/tools/getClaudeTaskStatus.d.ts +1 -0
  541. package/dist/tools/getCodeCoverage.d.ts +1 -0
  542. package/dist/tools/getCodeCoverage.js +32 -14
  543. package/dist/tools/getCodeCoverage.js.map +1 -1
  544. package/dist/tools/getCommitsForIssue.d.ts +1 -0
  545. package/dist/tools/getDebugState.d.ts +1 -0
  546. package/dist/tools/getDiagnostics.js +32 -29
  547. package/dist/tools/getDiagnostics.js.map +1 -1
  548. package/dist/tools/getDiffFromHandoff.js +8 -2
  549. package/dist/tools/getDiffFromHandoff.js.map +1 -1
  550. package/dist/tools/getDocumentSymbols.d.ts +1 -0
  551. package/dist/tools/getGitHotspots.d.ts +1 -0
  552. package/dist/tools/getGitStatus.js +6 -1
  553. package/dist/tools/getGitStatus.js.map +1 -1
  554. package/dist/tools/getImportedSignatures.d.ts +1 -0
  555. package/dist/tools/getImportedSignatures.js +18 -14
  556. package/dist/tools/getImportedSignatures.js.map +1 -1
  557. package/dist/tools/getPRTemplate.d.ts +1 -0
  558. package/dist/tools/getProjectContext.js +23 -10
  559. package/dist/tools/getProjectContext.js.map +1 -1
  560. package/dist/tools/getSymbolHistory.d.ts +1 -0
  561. package/dist/tools/getToolCapabilities.d.ts +10 -5
  562. package/dist/tools/getToolCapabilities.js +116 -204
  563. package/dist/tools/getToolCapabilities.js.map +1 -1
  564. package/dist/tools/getTypeSignature.d.ts +1 -0
  565. package/dist/tools/getWorkspaceSettings.d.ts +1 -0
  566. package/dist/tools/gitWrite.d.ts +11 -0
  567. package/dist/tools/github/actions.d.ts +2 -0
  568. package/dist/tools/github/composite.d.ts +4 -1
  569. package/dist/tools/github/composite.js +7 -7
  570. package/dist/tools/github/composite.js.map +1 -1
  571. package/dist/tools/github/issues.d.ts +4 -0
  572. package/dist/tools/github/pr.d.ts +13 -6
  573. package/dist/tools/github/pr.js +18 -11
  574. package/dist/tools/github/pr.js.map +1 -1
  575. package/dist/tools/handoffNote.d.ts +1 -0
  576. package/dist/tools/handoffNote.js +2 -1
  577. package/dist/tools/handoffNote.js.map +1 -1
  578. package/dist/tools/headless/lspClient.js +3 -0
  579. package/dist/tools/headless/lspClient.js.map +1 -1
  580. package/dist/tools/hoverAtCursor.d.ts +1 -0
  581. package/dist/tools/httpClient.d.ts +2 -0
  582. package/dist/tools/httpClient.js +38 -71
  583. package/dist/tools/httpClient.js.map +1 -1
  584. package/dist/tools/index.js +14 -10
  585. package/dist/tools/index.js.map +1 -1
  586. package/dist/tools/inlayHints.d.ts +1 -0
  587. package/dist/tools/launchQuickTask.d.ts +1 -0
  588. package/dist/tools/listClaudeTasks.d.ts +1 -0
  589. package/dist/tools/listClaudeTasks.js +10 -8
  590. package/dist/tools/listClaudeTasks.js.map +1 -1
  591. package/dist/tools/listTerminals.d.ts +1 -0
  592. package/dist/tools/lsp.d.ts +15 -0
  593. package/dist/tools/lsp.js +17 -0
  594. package/dist/tools/lsp.js.map +1 -1
  595. package/dist/tools/navigateToSymbolByName.d.ts +1 -0
  596. package/dist/tools/openDiff.d.ts +1 -0
  597. package/dist/tools/openDiff.js +4 -1
  598. package/dist/tools/openDiff.js.map +1 -1
  599. package/dist/tools/openFile.d.ts +1 -0
  600. package/dist/tools/openFile.js +4 -1
  601. package/dist/tools/openFile.js.map +1 -1
  602. package/dist/tools/openInBrowser.js +6 -1
  603. package/dist/tools/openInBrowser.js.map +1 -1
  604. package/dist/tools/organizeImports.d.ts +1 -0
  605. package/dist/tools/organizeImports.js +5 -3
  606. package/dist/tools/organizeImports.js.map +1 -1
  607. package/dist/tools/performanceReport.js +5 -3
  608. package/dist/tools/performanceReport.js.map +1 -1
  609. package/dist/tools/planPersistence.d.ts +3 -0
  610. package/dist/tools/planPersistence.js +4 -1
  611. package/dist/tools/planPersistence.js.map +1 -1
  612. package/dist/tools/previewEdit.d.ts +1 -0
  613. package/dist/tools/previewEdit.js +31 -5
  614. package/dist/tools/previewEdit.js.map +1 -1
  615. package/dist/tools/recentTracesDigest.js +54 -11
  616. package/dist/tools/recentTracesDigest.js.map +1 -1
  617. package/dist/tools/refactorAnalyze.d.ts +1 -0
  618. package/dist/tools/refactorExtractFunction.js +4 -1
  619. package/dist/tools/refactorExtractFunction.js.map +1 -1
  620. package/dist/tools/refactorPreview.d.ts +1 -0
  621. package/dist/tools/refactorPreview.js +10 -2
  622. package/dist/tools/refactorPreview.js.map +1 -1
  623. package/dist/tools/replaceBlock.d.ts +1 -0
  624. package/dist/tools/replaceBlock.js +2 -1
  625. package/dist/tools/replaceBlock.js.map +1 -1
  626. package/dist/tools/resumeClaudeTask.d.ts +1 -0
  627. package/dist/tools/runClaudeTask.d.ts +1 -0
  628. package/dist/tools/runTests.js +15 -4
  629. package/dist/tools/runTests.js.map +1 -1
  630. package/dist/tools/screenshot.d.ts +1 -0
  631. package/dist/tools/screenshotAndAnnotate.js +6 -2
  632. package/dist/tools/screenshotAndAnnotate.js.map +1 -1
  633. package/dist/tools/searchAndReplace.d.ts +1 -0
  634. package/dist/tools/searchAndReplace.js +21 -12
  635. package/dist/tools/searchAndReplace.js.map +1 -1
  636. package/dist/tools/searchTools.d.ts +26 -0
  637. package/dist/tools/searchTools.js +62 -21
  638. package/dist/tools/searchTools.js.map +1 -1
  639. package/dist/tools/searchWorkspace.d.ts +1 -0
  640. package/dist/tools/selectionRanges.d.ts +1 -0
  641. package/dist/tools/semanticTokens.d.ts +1 -0
  642. package/dist/tools/signatureHelp.d.ts +1 -0
  643. package/dist/tools/spawnWorkspace.js +15 -7
  644. package/dist/tools/spawnWorkspace.js.map +1 -1
  645. package/dist/tools/terminal.d.ts +6 -0
  646. package/dist/tools/testRunners/pytest.js +6 -2
  647. package/dist/tools/testRunners/pytest.js.map +1 -1
  648. package/dist/tools/testRunners/vitestJest.js +3 -1
  649. package/dist/tools/testRunners/vitestJest.js.map +1 -1
  650. package/dist/tools/testTraceToSource.d.ts +1 -0
  651. package/dist/tools/transaction.d.ts +13 -1
  652. package/dist/tools/transaction.js +63 -5
  653. package/dist/tools/transaction.js.map +1 -1
  654. package/dist/tools/typeHierarchy.d.ts +1 -0
  655. package/dist/tools/utils.d.ts +33 -0
  656. package/dist/tools/utils.js +122 -18
  657. package/dist/tools/utils.js.map +1 -1
  658. package/dist/tools/vscodeCommands.d.ts +2 -0
  659. package/dist/tools/vscodeTasks.d.ts +2 -0
  660. package/dist/tools/workspaceSettings.d.ts +1 -0
  661. package/dist/transport.d.ts +3 -1
  662. package/dist/transport.js +126 -52
  663. package/dist/transport.js.map +1 -1
  664. package/dist/winShim.d.ts +34 -0
  665. package/dist/winShim.js +94 -0
  666. package/dist/winShim.js.map +1 -0
  667. package/dist/wireHaltPushDispatch.d.ts +38 -0
  668. package/dist/wireHaltPushDispatch.js +71 -0
  669. package/dist/wireHaltPushDispatch.js.map +1 -0
  670. package/dist/writeFileAtomic.d.ts +23 -0
  671. package/dist/writeFileAtomic.js +121 -0
  672. package/dist/writeFileAtomic.js.map +1 -0
  673. package/package.json +23 -7
  674. package/scripts/postinstall.mjs +42 -2
  675. package/scripts/smoke/run-all.mjs +213 -0
  676. package/scripts/start-all.mjs +572 -0
  677. package/scripts/start-all.ps1 +209 -0
  678. package/scripts/start-all.sh +73 -17
  679. package/scripts/start-orchestrator.ps1 +158 -0
  680. package/scripts/start-remote.mjs +122 -0
  681. package/templates/automation-policies/recipe-authoring.json +1 -1
  682. package/templates/automation-policies/security-first.json +1 -1
  683. package/templates/automation-policies/strict-lint.json +1 -1
  684. package/templates/automation-policies/test-driven.json +1 -1
  685. package/templates/automation-policy.example.json +1 -1
  686. package/templates/co.patchwork-os.bridge.plist +2 -2
  687. package/templates/recipes/approval-queue-ui-test.yaml +1 -1
  688. package/templates/recipes/ctx-loop-test.yaml +1 -1
  689. package/templates/recipes/fix-errors-on-save.yaml +71 -0
  690. package/templates/recipes/morning-brief.yaml +5 -2
  691. package/templates/recipes/project-health-check.yaml +4 -1
  692. package/templates/recipes/sentry-to-linear.yaml +72 -38
  693. package/templates/recipes/webhook/apple-watch-health-log.yaml +145 -0
  694. package/templates/recipes/webhook/customer-escalation.yaml +8 -9
  695. package/templates/recipes/webhook/meeting-prep.yaml +11 -5
  696. package/dist/commands/marketplace.d.ts +0 -16
  697. package/dist/commands/marketplace.js +0 -32
  698. package/dist/commands/marketplace.js.map +0 -1
  699. package/dist/recipes/legacyRecipeCompat.d.ts +0 -10
  700. package/dist/recipes/legacyRecipeCompat.js +0 -131
  701. package/dist/recipes/legacyRecipeCompat.js.map +0 -1
@@ -11,10 +11,12 @@ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
11
11
  import "../recipes/tools/index.js";
12
12
  import { loadFixtureLibrary } from "../connectors/fixtureLibrary.js";
13
13
  import { MockConnector } from "../connectors/mockConnector.js";
14
- import { defaultDeprecationWarn, normalizeRecipeForRuntime, } from "../recipes/legacyRecipeCompat.js";
14
+ import { detectRequiredConnectors, findMissingConnectors, } from "../recipes/connectorPreflight.js";
15
+ import { HALT_CATEGORY_HINTS, HALT_CATEGORY_LABELS, } from "../recipes/haltCategory.js";
16
+ import { defaultDeprecationWarn, normalizeRecipeForRuntime, } from "../recipes/migrations/index.js";
15
17
  import { tryResolveRecipePath } from "../recipes/resolveRecipePath.js";
16
18
  import { generateSchemaSet, writeSchemas } from "../recipes/schemaGenerator.js";
17
- import { getTool, isConnectorNamespace, seedToolOutputPreviewContext, } from "../recipes/toolRegistry.js";
19
+ import { getTool, isConnectorNamespace, listConnectorNamespaces, listTools, seedToolOutputPreviewContext, } from "../recipes/toolRegistry.js";
18
20
  import { validateRecipeDefinition, } from "../recipes/validation.js";
19
21
  import { buildChainedDeps, dispatchRecipe, loadYamlRecipe, render, runYamlRecipe, } from "../recipes/yamlRunner.js";
20
22
  import { findYamlRecipePath } from "../recipesHttp.js";
@@ -113,6 +115,355 @@ export function runNew(options) {
113
115
  export function listTemplates() {
114
116
  return Object.keys(TEMPLATES);
115
117
  }
118
+ // ============================================================================
119
+ // patchwork recipe new --interactive
120
+ //
121
+ // Connector-aware prompt tree. Writes a valid recipe YAML based on
122
+ // user choices. Use a `node:readline/promises` adapter in real CLI;
123
+ // tests inject a stub `InteractivePromptDeps`.
124
+ // ============================================================================
125
+ const RECIPE_NAME_RE = /^[a-z0-9][a-z0-9_-]{1,63}$/;
126
+ const CRON_SHAPE_RE = /^(@every\s+\d+\s*(ms|s|m|h)|\S+\s+\S+\s+\S+\s+\S+\s+\S+)$/i;
127
+ async function askWithValidation(deps, question, validate) {
128
+ for (let attempt = 0; attempt < 5; attempt++) {
129
+ const answer = await deps.ask(question);
130
+ const error = validate(answer);
131
+ if (!error)
132
+ return answer;
133
+ // Surface error and re-ask via the question prompt itself.
134
+ question = `${error} ${question}`;
135
+ }
136
+ throw new Error("Too many invalid answers");
137
+ }
138
+ function yamlScalar(value) {
139
+ // Quote unless the string is a simple identifier-ish token. Conservative
140
+ // — when in doubt, JSON-encode (always parses as a YAML string).
141
+ if (/^[A-Za-z0-9_.\-/:]+$/.test(value))
142
+ return value;
143
+ return JSON.stringify(value);
144
+ }
145
+ /**
146
+ * Prompt the user for the tool's required JSON Schema fields. Optional
147
+ * properties are skipped — the user can hand-edit the YAML afterwards
148
+ * if they want to set defaults. `into` is excluded since the generator
149
+ * supplies its own name.
150
+ *
151
+ * Returns an ordered list of `[key, value]` pairs so the YAML output is
152
+ * stable across runs (insertion order of object keys is sufficient in
153
+ * V8 for string keys, but an array makes the contract explicit).
154
+ */
155
+ async function collectRequiredParams(deps, tool) {
156
+ const schema = tool.paramsSchema;
157
+ if (!schema || typeof schema !== "object")
158
+ return [];
159
+ const required = Array.isArray(schema.required)
160
+ ? schema.required.filter((k) => typeof k === "string" && k !== "into")
161
+ : [];
162
+ if (required.length === 0)
163
+ return [];
164
+ const out = [];
165
+ const properties = schema.properties ?? {};
166
+ for (const key of required) {
167
+ const propMeta = properties[key];
168
+ const hint = propMeta?.description ? ` — ${propMeta.description}` : "";
169
+ const value = await askWithValidation(deps, `${tool.id} → ${key}${hint}`, (a) => (a.trim().length > 0 ? null : "required, cannot be empty."));
170
+ out.push([key, value.trim()]);
171
+ }
172
+ return out;
173
+ }
174
+ /**
175
+ * AI-suggest path. Discovers the running bridge via lock file, POSTs
176
+ * the user's natural-language goal to `/recipes/generate`, writes the
177
+ * bridge's raw YAML response to disk (no form normalization — per
178
+ * memory project_recipe_audit_2026_05_06_part2), and validates
179
+ * post-hoc as warnings only.
180
+ *
181
+ * Failure modes:
182
+ * - No bridge running → clear actionable error
183
+ * - Bridge returns 503 → "AI generation unavailable — start the
184
+ * bridge with --driver subprocess"
185
+ * - Bridge returns 4xx → surface server error message
186
+ * - Bridge returns 200 but `result.yaml` is empty → write nothing,
187
+ * throw with the raw refusal text
188
+ */
189
+ async function runNewAiSuggest(options) {
190
+ const { deps } = options;
191
+ const name = await askWithValidation(deps, "Recipe name", (a) => RECIPE_NAME_RE.test(a)
192
+ ? null
193
+ : 'must match /^[a-z0-9][a-z0-9_-]{1,63}$/ (e.g. "morning-brief").');
194
+ const goal = await askWithValidation(deps, "Describe what the recipe should do (one-paragraph natural language)", (a) => (a.trim().length > 0 ? null : "goal cannot be empty."));
195
+ // Resolve bridge discovery + fetch — production defaults or test injection.
196
+ const findBridge = options.aiSuggest?.findBridge ??
197
+ (() => {
198
+ // Lazy import so non-AI paths don't pay the cost.
199
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
200
+ const mod = require("../bridgeLockDiscovery.js");
201
+ return mod.findBridgeLock();
202
+ });
203
+ const doFetch = options.aiSuggest?.fetch ?? globalThis.fetch;
204
+ const lock = findBridge();
205
+ if (!lock) {
206
+ throw new Error("AI generation requires a running bridge. Start one with `patchwork start` " +
207
+ "(or `claude-ide-bridge --driver subprocess` for the bridge-only mode), " +
208
+ "then retry.");
209
+ }
210
+ let response;
211
+ try {
212
+ response = await doFetch(`http://127.0.0.1:${lock.port}/recipes/generate`, {
213
+ method: "POST",
214
+ headers: {
215
+ "Content-Type": "application/json",
216
+ Authorization: `Bearer ${lock.authToken}`,
217
+ },
218
+ body: JSON.stringify({ prompt: goal.trim() }),
219
+ });
220
+ }
221
+ catch (err) {
222
+ throw new Error(`Could not reach the bridge at port ${lock.port}: ${err instanceof Error ? err.message : String(err)}`);
223
+ }
224
+ let payload;
225
+ try {
226
+ payload = (await response.json());
227
+ }
228
+ catch {
229
+ throw new Error(`Bridge returned non-JSON response (status ${response.status})`);
230
+ }
231
+ if (response.status === 503 || payload.unavailable) {
232
+ throw new Error("AI generation unavailable — start the bridge with `--driver subprocess`.");
233
+ }
234
+ if (!payload.ok) {
235
+ throw new Error(payload.error ?? `Bridge returned ${response.status}`);
236
+ }
237
+ const yamlBody = (payload.yaml ?? "").trim();
238
+ if (!yamlBody) {
239
+ throw new Error("Bridge returned empty YAML — try a more specific goal.");
240
+ }
241
+ // Ensure the SchemaStore pragma is present at line 1 so the file
242
+ // gets autocomplete on first open. If the bridge already prepended
243
+ // one, leave it.
244
+ const hasPragma = /^#\s*yaml-language-server:/.test(yamlBody);
245
+ const content = hasPragma
246
+ ? `${yamlBody}\n`
247
+ : `${RECIPE_SCHEMA_HEADER}\n${yamlBody}\n`;
248
+ if (deps.preview)
249
+ deps.preview(content);
250
+ const shouldWrite = await deps.confirm("Write to disk?");
251
+ if (!shouldWrite) {
252
+ throw new Error("Cancelled by user");
253
+ }
254
+ const outputDir = options.outputDir ?? RECIPES_DIR;
255
+ if (!existsSync(outputDir)) {
256
+ mkdirSync(outputDir, { recursive: true });
257
+ }
258
+ const outputPath = join(outputDir, `${name}.yaml`);
259
+ if (existsSync(outputPath)) {
260
+ throw new Error(`Recipe already exists: ${outputPath}`);
261
+ }
262
+ writeFileSync(outputPath, content);
263
+ // Lint post-hoc. Surface as warnings; never block — the user asked
264
+ // for a draft, the bridge produced it, the file is on disk.
265
+ let warnings = [];
266
+ try {
267
+ const parsed = parseYaml(content);
268
+ const lintResult = validateRecipeDefinition(parsed);
269
+ warnings = lintResult.issues ?? [];
270
+ }
271
+ catch (err) {
272
+ warnings = [
273
+ {
274
+ level: "warning",
275
+ message: `AI-generated YAML did not parse cleanly: ${err instanceof Error ? err.message : String(err)}`,
276
+ },
277
+ ];
278
+ }
279
+ return { path: outputPath, content, warnings };
280
+ }
281
+ export async function runNewInteractive(options) {
282
+ const { deps } = options;
283
+ // Mode pick — Guided (full prompt tree), Template (existing runNew
284
+ // templates: minimal | daily | inbox), or AI-suggest (asks the running
285
+ // bridge's /recipes/generate endpoint and writes the raw response).
286
+ //
287
+ // AI-suggest skips the form normalizer per memory
288
+ // project_recipe_audit_2026_05_06_part2 — applyAiYaml on the dashboard
289
+ // was lossy. Here we writeFileSync the bridge's `result.yaml` verbatim
290
+ // (only the SchemaStore pragma is prepended if absent) and validate
291
+ // post-hoc as warnings, never blocking the write.
292
+ const templates = listTemplates();
293
+ const modeChoices = [
294
+ "Guided — full prompt tree (recommended)",
295
+ `Template — pick from ${templates.length} starters (${templates.join(", ")})`,
296
+ "AI suggest — describe what you want; the bridge drafts a YAML",
297
+ ];
298
+ const modeIdx = await deps.pickFromList("How do you want to start?", modeChoices);
299
+ // AI-suggest mode: route through the running bridge's /recipes/generate.
300
+ if (modeIdx === 3) {
301
+ return runNewAiSuggest(options);
302
+ }
303
+ // Template mode: re-use the existing runNew() flow with the picked template.
304
+ if (modeIdx === 2) {
305
+ const name = await askWithValidation(deps, "Recipe name", (a) => RECIPE_NAME_RE.test(a)
306
+ ? null
307
+ : 'must match /^[a-z0-9][a-z0-9_-]{1,63}$/ (e.g. "morning-brief").');
308
+ const description = await askWithValidation(deps, "One-line description", (a) => (a.trim().length > 0 ? null : "cannot be empty."));
309
+ const templateIdx = await deps.pickFromList("Pick a template", templates);
310
+ const template = templates[templateIdx - 1];
311
+ if (!template)
312
+ throw new Error("Invalid template selection");
313
+ const result = runNew({
314
+ name,
315
+ description,
316
+ template,
317
+ ...(options.outputDir ? { outputDir: options.outputDir } : {}),
318
+ });
319
+ if (deps.preview)
320
+ deps.preview(result.content);
321
+ const shouldWrite = await deps.confirm("Keep this recipe?");
322
+ if (!shouldWrite) {
323
+ // runNew already wrote the file — clean up to honor "cancel."
324
+ try {
325
+ if (existsSync(result.path)) {
326
+ const { unlinkSync } = await import("node:fs");
327
+ unlinkSync(result.path);
328
+ }
329
+ }
330
+ catch {
331
+ // best effort
332
+ }
333
+ throw new Error("Cancelled by user");
334
+ }
335
+ return { path: result.path, content: result.content, warnings: [] };
336
+ }
337
+ // Guided mode (default — modeIdx === 1).
338
+ const name = await askWithValidation(deps, "Recipe name", (a) => RECIPE_NAME_RE.test(a)
339
+ ? null
340
+ : 'must match /^[a-z0-9][a-z0-9_-]{1,63}$/ (e.g. "morning-brief").');
341
+ const description = await askWithValidation(deps, "One-line description", (a) => (a.trim().length > 0 ? null : "cannot be empty."));
342
+ const triggerTypes = ["manual", "cron"];
343
+ const triggerIdx = await deps.pickFromList("Trigger type", triggerTypes.slice());
344
+ const triggerType = triggerTypes[triggerIdx - 1];
345
+ if (!triggerType)
346
+ throw new Error("Invalid trigger selection");
347
+ const triggerLines = [`trigger:`, ` type: ${triggerType}`];
348
+ if (triggerType === "cron") {
349
+ const cron = await askWithValidation(deps, "Cron expression (e.g. '0 8 * * *' for 8am daily)", (a) => CRON_SHAPE_RE.test(a.trim())
350
+ ? null
351
+ : 'expected 5-field cron or "@every Ns|Nm|Nh".');
352
+ triggerLines.push(` at: ${yamlScalar(cron.trim())}`);
353
+ }
354
+ // Step loop. The user adds tool / agent steps until they pick "done".
355
+ // Each tool pick reads its paramsSchema and prompts for required fields,
356
+ // so the generated recipe is closer to runnable than the MVP slice.
357
+ const namespaces = listConnectorNamespaces();
358
+ const stepLines = ["steps:"];
359
+ const intoNames = [];
360
+ let lastAgentInto = null;
361
+ let toolStepCount = 0;
362
+ let agentStepCount = 0;
363
+ const stepKindChoices = [
364
+ "Add a tool step (calls a registered tool — gmail.fetch_unread, github.list_issues, …)",
365
+ "Add an agent step (LLM prompt — drafts, classifies, summarizes)",
366
+ "Done — preview and write",
367
+ ];
368
+ for (let stepIdx = 0; stepIdx < 20; stepIdx++) {
369
+ const kind = await deps.pickFromList(stepIdx === 0
370
+ ? "Build steps — what's next?"
371
+ : "Add another step? (or pick Done)", stepKindChoices);
372
+ if (kind === 3)
373
+ break;
374
+ if (kind === 1) {
375
+ // Tool step
376
+ const nsIdx = await deps.pickFromList("Pick a connector", namespaces.slice());
377
+ const ns = namespaces[nsIdx - 1];
378
+ if (!ns)
379
+ throw new Error("Invalid connector selection");
380
+ const tools = listTools(ns);
381
+ const labels = tools.map((t) => `${t.id} — ${t.description}`);
382
+ const toolIdx = await deps.pickFromList(`Pick a ${ns} tool`, labels);
383
+ const tool = tools[toolIdx - 1];
384
+ if (!tool)
385
+ throw new Error(`Invalid tool selection for ${ns}`);
386
+ toolStepCount += 1;
387
+ const intoName = toolStepCount === 1 ? `${ns}_result` : `${ns}_result_${toolStepCount}`;
388
+ intoNames.push(intoName);
389
+ stepLines.push(` - tool: ${tool.id}`);
390
+ // Prompt for required params from the tool's JSON schema.
391
+ const params = await collectRequiredParams(deps, tool);
392
+ for (const [key, value] of params) {
393
+ stepLines.push(` ${key}: ${yamlScalar(value)}`);
394
+ }
395
+ stepLines.push(` into: ${intoName}`);
396
+ }
397
+ else if (kind === 2) {
398
+ // Agent step
399
+ const prompt = await askWithValidation(deps, "Agent prompt", (a) => a.trim().length > 0 ? null : "cannot be empty.");
400
+ agentStepCount += 1;
401
+ const intoName = agentStepCount === 1
402
+ ? "agent_output"
403
+ : `agent_output_${agentStepCount}`;
404
+ lastAgentInto = intoName;
405
+ stepLines.push(` - agent: true`);
406
+ stepLines.push(` prompt: |`);
407
+ for (const line of prompt.split("\n")) {
408
+ stepLines.push(` ${line}`);
409
+ }
410
+ stepLines.push(` into: ${intoName}`);
411
+ }
412
+ }
413
+ // Always tail with file.write to inbox so the user sees output somewhere.
414
+ // Reference the most recent agent output when one exists, otherwise fall
415
+ // back to the most recent tool result (or a TODO comment if neither).
416
+ const tailRef = lastAgentInto ?? intoNames[intoNames.length - 1] ?? null;
417
+ stepLines.push(` - tool: file.write`);
418
+ stepLines.push(` path: "~/.patchwork/inbox/${name}-{{date}}.md"`);
419
+ stepLines.push(` content: |`);
420
+ stepLines.push(` # ${name} — {{date}}`);
421
+ stepLines.push(``);
422
+ if (tailRef) {
423
+ stepLines.push(` {{${tailRef}}}`);
424
+ }
425
+ else {
426
+ stepLines.push(` (no steps configured — fill in)`);
427
+ }
428
+ const content = `${RECIPE_SCHEMA_HEADER}\n` +
429
+ `version: 1.0.0\n` +
430
+ `name: ${name}\n` +
431
+ `description: ${yamlScalar(description)}\n` +
432
+ `${triggerLines.join("\n")}\n` +
433
+ `${stepLines.join("\n")}\n`;
434
+ // Lint pre-write. validateRecipeDefinition expects parsed YAML.
435
+ let warnings = [];
436
+ try {
437
+ const parsed = parseYaml(content);
438
+ const lintResult = validateRecipeDefinition(parsed);
439
+ warnings = lintResult.issues ?? [];
440
+ }
441
+ catch {
442
+ // Parse failure here is a bug in the generator — surface but don't block.
443
+ warnings = [
444
+ {
445
+ level: "error",
446
+ message: "Generated YAML failed to parse — please report this.",
447
+ },
448
+ ];
449
+ }
450
+ if (deps.preview)
451
+ deps.preview(content);
452
+ const shouldWrite = await deps.confirm("Write to disk?");
453
+ if (!shouldWrite) {
454
+ throw new Error("Cancelled by user");
455
+ }
456
+ const outputDir = options.outputDir ?? RECIPES_DIR;
457
+ if (!existsSync(outputDir)) {
458
+ mkdirSync(outputDir, { recursive: true });
459
+ }
460
+ const outputPath = join(outputDir, `${name}.yaml`);
461
+ if (existsSync(outputPath)) {
462
+ throw new Error(`Recipe already exists: ${outputPath}`);
463
+ }
464
+ writeFileSync(outputPath, content);
465
+ return { path: outputPath, content, warnings };
466
+ }
116
467
  export async function runSchema(outputDir) {
117
468
  const resolvedOutputDir = resolve(outputDir);
118
469
  const schemas = generateSchemaSet();
@@ -183,8 +534,12 @@ export function runLint(recipePath) {
183
534
  const chainIssues = lintChainRefs(parsed, recipePath);
184
535
  if (chainIssues.length > 0) {
185
536
  result.issues.push(...chainIssues);
186
- result.errors += chainIssues.filter((i) => i.level === "error").length;
187
- result.warnings += chainIssues.filter((i) => i.level === "warning").length;
537
+ for (const i of chainIssues) {
538
+ if (i.level === "error")
539
+ result.errors++;
540
+ else if (i.level === "warning")
541
+ result.warnings++;
542
+ }
188
543
  if (result.errors > 0) {
189
544
  result.valid = false;
190
545
  }
@@ -420,6 +775,8 @@ export async function runRecipe(recipeRef, options = {}) {
420
775
  const runnerDeps = {
421
776
  ...options.deps,
422
777
  workdir: options.workdir ?? options.deps?.workdir ?? process.cwd(),
778
+ ...(options.manualRunId && { manualRunId: options.manualRunId }),
779
+ ...(options.ledgerDir && { ledgerDir: options.ledgerDir }),
423
780
  };
424
781
  if (options.dryRun) {
425
782
  throw new Error("runRecipeDryPlan must be used for dry-run execution");
@@ -832,7 +1189,16 @@ export async function runPreflight(recipeRef, options = {}) {
832
1189
  }
833
1190
  const plan = await runRecipeDryPlan(recipeRef, options);
834
1191
  const requireWriteAck = options.requireWriteAck ?? true;
835
- const allowlist = new Set(options.allowWrites ?? []);
1192
+ // Merge the recipe's declared allowWrites (YAML) with any caller-supplied
1193
+ // entries (e.g. --allow-write CLI flag). Either source is sufficient.
1194
+ const recipeForWrites = loadYamlRecipe(recipePath);
1195
+ const recipeAllowWrites = Array.isArray(recipeForWrites.allowWrites)
1196
+ ? recipeForWrites.allowWrites.filter((entry) => typeof entry === "string")
1197
+ : [];
1198
+ const allowlist = new Set([
1199
+ ...recipeAllowWrites,
1200
+ ...(options.allowWrites ?? []),
1201
+ ]);
836
1202
  for (const step of plan.steps) {
837
1203
  if (step.type === "tool" && step.tool && step.resolved === false) {
838
1204
  issues.push({
@@ -898,6 +1264,270 @@ export function runPreflightWatch(options) {
898
1264
  };
899
1265
  return runWatch(watchOptions);
900
1266
  }
1267
+ /**
1268
+ * Map a loaded `YamlRecipe`'s steps into the schema `Step` shape that
1269
+ * `detectRequiredConnectors` consumes. The authoring YAML and the
1270
+ * connector-detection helper disagree on step shape — YAML carries
1271
+ * `tool?: string` plus a nested `agent?: { prompt }` object, while the
1272
+ * detector expects the flat schema form (`agent: false` carries `tool`,
1273
+ * `agent: true` carries `prompt` + optional `tools[]`). This adapter lets
1274
+ * the CLI reuse the exact helper the dashboard install preflight uses
1275
+ * (same `TOOL_NAMESPACE_TO_CONNECTOR` map, same prompt-scan) rather than
1276
+ * re-implementing detection. Tool steps feed the namespace path; agent
1277
+ * steps feed the prompt-scan path (YAML agent steps have no `tools[]`).
1278
+ */
1279
+ function detectConnectorsForYamlRecipe(recipe) {
1280
+ const steps = recipe.steps.map((step, i) => {
1281
+ const id = typeof step.id === "string" ? step.id : `step-${i}`;
1282
+ if (typeof step.tool === "string") {
1283
+ return { id, agent: false, tool: step.tool, params: {} };
1284
+ }
1285
+ if (step.agent && typeof step.agent.prompt === "string") {
1286
+ return { id, agent: true, prompt: step.agent.prompt };
1287
+ }
1288
+ // Steps with neither a tool nor an agent prompt contribute nothing —
1289
+ // model them as an empty agent step so the detector skips them cleanly.
1290
+ return { id, agent: true, prompt: "" };
1291
+ });
1292
+ return detectRequiredConnectors({
1293
+ ...recipe,
1294
+ steps,
1295
+ });
1296
+ }
1297
+ /**
1298
+ * One-command fix hint for an unauthorized connector. Phrasing matches the
1299
+ * runtime `auth_failure` halt hint ("reconnect from /connections") so the
1300
+ * doctor's static and runtime advice line up.
1301
+ */
1302
+ function connectorFixHint(connectorId) {
1303
+ return `authorize from /connections (or run \`patchwork recipe preflight\` after connecting "${connectorId}")`;
1304
+ }
1305
+ /**
1306
+ * `recipe doctor` — one-screen "why is this recipe unhealthy + how do I
1307
+ * fix it" diagnosis. Composes the static `preflight` check (lint + policy
1308
+ * + plan) and static connector detection with the recipe-scoped runtime
1309
+ * halt summary, mapping every finding to an actionable hint. Fail-soft: a
1310
+ * missing bridge degrades to static-only rather than erroring.
1311
+ */
1312
+ export async function runRecipeDoctor(recipeRef, options = {}) {
1313
+ const { fetchHalts, ...preflightOptions } = options;
1314
+ const recipePath = resolveRecipePath(recipeRef);
1315
+ // Lint first — it never throws and returns structured issues even for
1316
+ // recipes that can't load. Then attempt the fuller preflight (policy +
1317
+ // plan); if the recipe is too broken to load/plan, `runPreflight`
1318
+ // throws — fall back to a lint-only static result so doctor still
1319
+ // produces a clean diagnosis instead of a stack trace.
1320
+ const lint = runLint(recipePath);
1321
+ let staticResult;
1322
+ try {
1323
+ const pf = await runPreflight(recipePath, preflightOptions);
1324
+ staticResult = { ok: pf.ok, recipe: pf.recipe, issues: pf.issues };
1325
+ }
1326
+ catch (err) {
1327
+ const issues = lint.issues.map((i) => ({
1328
+ level: i.level,
1329
+ code: i.level === "error" ? "lint-error" : "lint-warning",
1330
+ message: i.message,
1331
+ }));
1332
+ // Guarantee at least one error so the verdict is unhealthy — the
1333
+ // load failure itself is the signal even if lint surfaced only
1334
+ // warnings (or nothing it could attribute to a line).
1335
+ if (!issues.some((i) => i.level === "error")) {
1336
+ issues.push({
1337
+ level: "error",
1338
+ code: "lint-error",
1339
+ message: err instanceof Error ? err.message : String(err),
1340
+ });
1341
+ }
1342
+ staticResult = {
1343
+ ok: false,
1344
+ recipe: basename(recipePath).replace(/\.(ya?ml|json)$/i, ""),
1345
+ issues,
1346
+ planSkipped: true,
1347
+ };
1348
+ }
1349
+ const recipeName = staticResult.recipe;
1350
+ // --- Connector authorization ---------------------------------------
1351
+ // Static detection always runs (no bridge needed). When the recipe was
1352
+ // too broken to load above, fall back to an empty required-set rather
1353
+ // than crashing the whole diagnosis — the lint errors are the signal in
1354
+ // that case.
1355
+ let required = [];
1356
+ if (!staticResult.planSkipped) {
1357
+ try {
1358
+ required = detectConnectorsForYamlRecipe(loadYamlRecipe(recipePath));
1359
+ }
1360
+ catch {
1361
+ // A recipe that lints clean but fails to load here is unusual;
1362
+ // treat connector detection as best-effort and move on.
1363
+ required = [];
1364
+ }
1365
+ }
1366
+ const { fetchConnections } = options;
1367
+ let connectors;
1368
+ if (required.length === 0) {
1369
+ connectors = {
1370
+ required,
1371
+ missing: [],
1372
+ authorized: [],
1373
+ connectionsChecked: false,
1374
+ note: staticResult.planSkipped
1375
+ ? "connector detection skipped — recipe could not be loaded"
1376
+ : "recipe requires no Patchwork connectors",
1377
+ };
1378
+ }
1379
+ else if (fetchConnections) {
1380
+ try {
1381
+ const connections = await fetchConnections();
1382
+ if (connections === null) {
1383
+ connectors = {
1384
+ required,
1385
+ missing: [],
1386
+ authorized: [],
1387
+ connectionsChecked: false,
1388
+ note: "no running bridge — connector auth check skipped",
1389
+ };
1390
+ }
1391
+ else {
1392
+ const missing = findMissingConnectors(required, connections);
1393
+ const missingSet = new Set(missing);
1394
+ connectors = {
1395
+ required,
1396
+ missing,
1397
+ authorized: required.filter((id) => !missingSet.has(id)),
1398
+ connectionsChecked: true,
1399
+ };
1400
+ }
1401
+ }
1402
+ catch (err) {
1403
+ connectors = {
1404
+ required,
1405
+ missing: [],
1406
+ authorized: [],
1407
+ connectionsChecked: false,
1408
+ note: `connector auth check failed: ${err instanceof Error ? err.message : String(err)}`,
1409
+ };
1410
+ }
1411
+ }
1412
+ else {
1413
+ connectors = {
1414
+ required,
1415
+ missing: [],
1416
+ authorized: [],
1417
+ connectionsChecked: false,
1418
+ note: "connector auth check not requested",
1419
+ };
1420
+ }
1421
+ let runtime = null;
1422
+ let runtimeNote;
1423
+ if (fetchHalts) {
1424
+ try {
1425
+ runtime = await fetchHalts(recipeName);
1426
+ if (runtime === null) {
1427
+ runtimeNote = "no running bridge — runtime halt check skipped";
1428
+ }
1429
+ }
1430
+ catch (err) {
1431
+ runtime = null;
1432
+ runtimeNote = `runtime halt check failed: ${err instanceof Error ? err.message : String(err)}`;
1433
+ }
1434
+ }
1435
+ else {
1436
+ runtimeNote = "runtime halt check not requested";
1437
+ }
1438
+ const ok = staticResult.ok &&
1439
+ connectors.missing.length === 0 &&
1440
+ (runtime?.total ?? 0) === 0;
1441
+ return {
1442
+ recipe: recipeName,
1443
+ recipePath,
1444
+ static: staticResult,
1445
+ connectors,
1446
+ runtime,
1447
+ runtimeNote,
1448
+ ok,
1449
+ };
1450
+ }
1451
+ /**
1452
+ * Render a `RecipeDoctorResult` as a one-screen human report. Returns the
1453
+ * text so the CLI handler can write it (and tests can assert on it)
1454
+ * without coupling to process.stdout.
1455
+ */
1456
+ export function formatRecipeDoctorReport(result) {
1457
+ const lines = [];
1458
+ const verdict = result.ok ? "✓ healthy" : "✗ needs attention";
1459
+ lines.push(`Recipe doctor — ${result.recipe} (${verdict})`);
1460
+ lines.push(` ${result.recipePath}`);
1461
+ lines.push("");
1462
+ // --- Static: lint + policy + plan ---
1463
+ const s = result.static;
1464
+ const staticErrors = s.issues.filter((i) => i.level === "error");
1465
+ const staticWarnings = s.issues.filter((i) => i.level === "warning");
1466
+ if (s.issues.length === 0) {
1467
+ lines.push("Static checks: ✓ lint + policy clean");
1468
+ }
1469
+ else {
1470
+ const planNote = s.planSkipped
1471
+ ? " (could not plan — lint-only; fix the errors below first)"
1472
+ : "";
1473
+ lines.push(`Static checks: ${staticErrors.length} error(s), ${staticWarnings.length} warning(s)${planNote}`);
1474
+ for (const issue of s.issues) {
1475
+ const icon = issue.level === "error" ? "✗" : "⚠";
1476
+ const where = issue.stepId ? ` [step "${issue.stepId}"]` : "";
1477
+ lines.push(` ${icon} (${issue.code})${where} ${issue.message}`);
1478
+ }
1479
+ }
1480
+ lines.push("");
1481
+ // --- Connectors: required vs authorized ---
1482
+ const c = result.connectors;
1483
+ if (c.required.length === 0) {
1484
+ lines.push(`Connectors: ✓ none required${c.note ? ` (${c.note})` : ""}`);
1485
+ }
1486
+ else if (!c.connectionsChecked) {
1487
+ // Couldn't classify auth state (no bridge / not requested) — still tell
1488
+ // the user which connectors the recipe needs so they can pre-authorize.
1489
+ lines.push(`Connectors: ${c.required.length} required — auth state unknown (${c.note ?? "unavailable"})`);
1490
+ lines.push(` needs: ${c.required.join(", ")}`);
1491
+ }
1492
+ else if (c.missing.length === 0) {
1493
+ lines.push(`Connectors: ✓ all ${c.required.length} authorized (${c.required.join(", ")})`);
1494
+ }
1495
+ else {
1496
+ lines.push(`Connectors: ${c.missing.length} of ${c.required.length} not authorized`);
1497
+ for (const id of c.missing) {
1498
+ lines.push(` ✗ ${id} — not authorized → ${connectorFixHint(id)}`);
1499
+ }
1500
+ if (c.authorized.length > 0) {
1501
+ lines.push(` ✓ authorized: ${c.authorized.join(", ")}`);
1502
+ }
1503
+ }
1504
+ lines.push("");
1505
+ // --- Runtime: recent halts ---
1506
+ if (result.runtime === null) {
1507
+ lines.push(`Runtime halts: — (${result.runtimeNote ?? "unavailable"})`);
1508
+ }
1509
+ else if (result.runtime.total === 0) {
1510
+ lines.push("Runtime halts: ✓ none in the recent window");
1511
+ }
1512
+ else {
1513
+ lines.push(`Runtime halts: ${result.runtime.total} in the recent window`);
1514
+ const entries = Object.entries(result.runtime.byCategory).sort(([, a], [, b]) => (b ?? 0) - (a ?? 0));
1515
+ for (const [cat, count] of entries) {
1516
+ const category = cat;
1517
+ const label = HALT_CATEGORY_LABELS[category] ?? cat;
1518
+ const hint = HALT_CATEGORY_HINTS[category] ?? "open run trace";
1519
+ lines.push(` ✗ ${label}: ${count} → ${hint}`);
1520
+ }
1521
+ if (result.runtime.recent.length > 0) {
1522
+ lines.push(" most recent:");
1523
+ for (const r of result.runtime.recent.slice(0, 5)) {
1524
+ const reason = r.reason.length > 100 ? `${r.reason.slice(0, 99)}…` : r.reason;
1525
+ lines.push(` · [run #${r.runSeq}] ${reason}`);
1526
+ }
1527
+ }
1528
+ }
1529
+ return `${lines.join("\n")}\n`;
1530
+ }
901
1531
  /**
902
1532
  * Exported for the cli-warns-when-out-of-jail.test regression suite —
903
1533
  * the warn path is what we assert on, not the path-resolution behaviour.
@@ -1088,8 +1718,13 @@ export async function runRecord(recipePath, options = {}) {
1088
1718
  });
1089
1719
  }
1090
1720
  }
1091
- const errors = issues.filter((issue) => issue.level === "error").length;
1092
- const warnings = issues.filter((issue) => issue.level === "warning").length;
1721
+ let errors = 0, warnings = 0;
1722
+ for (const issue of issues) {
1723
+ if (issue.level === "error")
1724
+ errors++;
1725
+ else if (issue.level === "warning")
1726
+ warnings++;
1727
+ }
1093
1728
  return {
1094
1729
  valid: errors === 0,
1095
1730
  issues,
@@ -1200,8 +1835,13 @@ export async function runTest(recipePath, options = {}) {
1200
1835
  });
1201
1836
  }
1202
1837
  }
1203
- const errors = issues.filter((issue) => issue.level === "error").length;
1204
- const warnings = issues.filter((issue) => issue.level === "warning").length;
1838
+ let errors = 0, warnings = 0;
1839
+ for (const issue of issues) {
1840
+ if (issue.level === "error")
1841
+ errors++;
1842
+ else if (issue.level === "warning")
1843
+ warnings++;
1844
+ }
1205
1845
  return {
1206
1846
  valid: errors === 0,
1207
1847
  issues,