patchwork-os 0.2.0-alpha.9 → 0.2.0-beta.1

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 (618) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +318 -35
  3. package/deploy/bootstrap-new-vps.sh +12 -12
  4. package/deploy/bootstrap-vps.sh +187 -0
  5. package/deploy/deploy-dashboard.sh +174 -0
  6. package/deploy/deploy-landing.sh +136 -0
  7. package/dist/activationMetrics.d.ts +67 -0
  8. package/dist/activationMetrics.js +255 -0
  9. package/dist/activationMetrics.js.map +1 -0
  10. package/dist/activityLog.d.ts +49 -0
  11. package/dist/activityLog.js +78 -0
  12. package/dist/activityLog.js.map +1 -1
  13. package/dist/analyticsAggregator.d.ts +5 -1
  14. package/dist/analyticsAggregator.js +15 -4
  15. package/dist/analyticsAggregator.js.map +1 -1
  16. package/dist/analyticsPrefs.d.ts +11 -0
  17. package/dist/analyticsPrefs.js +33 -0
  18. package/dist/analyticsPrefs.js.map +1 -1
  19. package/dist/approvalHttp.d.ts +49 -2
  20. package/dist/approvalHttp.js +217 -21
  21. package/dist/approvalHttp.js.map +1 -1
  22. package/dist/approvalInsights.d.ts +49 -0
  23. package/dist/approvalInsights.js +97 -0
  24. package/dist/approvalInsights.js.map +1 -0
  25. package/dist/approvalQueue.d.ts +27 -1
  26. package/dist/approvalQueue.js +123 -3
  27. package/dist/approvalQueue.js.map +1 -1
  28. package/dist/approvalSignals.d.ts +124 -0
  29. package/dist/approvalSignals.js +512 -0
  30. package/dist/approvalSignals.js.map +1 -0
  31. package/dist/automation.d.ts +57 -0
  32. package/dist/automation.js +156 -59
  33. package/dist/automation.js.map +1 -1
  34. package/dist/automationSuggestions.d.ts +79 -0
  35. package/dist/automationSuggestions.js +150 -0
  36. package/dist/automationSuggestions.js.map +1 -0
  37. package/dist/bridge.d.ts +3 -0
  38. package/dist/bridge.js +194 -153
  39. package/dist/bridge.js.map +1 -1
  40. package/dist/bridgeToken.js +57 -19
  41. package/dist/bridgeToken.js.map +1 -1
  42. package/dist/ccPermissions.d.ts +15 -0
  43. package/dist/ccPermissions.js +21 -4
  44. package/dist/ccPermissions.js.map +1 -1
  45. package/dist/claudeDriver.d.ts +0 -16
  46. package/dist/claudeDriver.js +93 -36
  47. package/dist/claudeDriver.js.map +1 -1
  48. package/dist/claudeMdPatch.d.ts +9 -3
  49. package/dist/claudeMdPatch.js +79 -13
  50. package/dist/claudeMdPatch.js.map +1 -1
  51. package/dist/claudeOrchestrator.d.ts +13 -1
  52. package/dist/claudeOrchestrator.js +16 -8
  53. package/dist/claudeOrchestrator.js.map +1 -1
  54. package/dist/commands/dashboard.js +1 -1
  55. package/dist/commands/dashboard.js.map +1 -1
  56. package/dist/commands/launchd.d.ts +2 -0
  57. package/dist/commands/launchd.js +94 -0
  58. package/dist/commands/launchd.js.map +1 -0
  59. package/dist/commands/marketplace.d.ts +15 -10
  60. package/dist/commands/marketplace.js +27 -115
  61. package/dist/commands/marketplace.js.map +1 -1
  62. package/dist/commands/patchworkInit.d.ts +8 -0
  63. package/dist/commands/patchworkInit.js +77 -11
  64. package/dist/commands/patchworkInit.js.map +1 -1
  65. package/dist/commands/recipe.d.ts +289 -0
  66. package/dist/commands/recipe.js +1359 -0
  67. package/dist/commands/recipe.js.map +1 -0
  68. package/dist/commands/recipeInstall.d.ts +150 -0
  69. package/dist/commands/recipeInstall.js +647 -0
  70. package/dist/commands/recipeInstall.js.map +1 -0
  71. package/dist/commands/tracesExport.d.ts +83 -0
  72. package/dist/commands/tracesExport.js +269 -0
  73. package/dist/commands/tracesExport.js.map +1 -0
  74. package/dist/commands/tracesImport.d.ts +56 -0
  75. package/dist/commands/tracesImport.js +161 -0
  76. package/dist/commands/tracesImport.js.map +1 -0
  77. package/dist/commitIssueLinkLog.d.ts +8 -0
  78. package/dist/commitIssueLinkLog.js +53 -1
  79. package/dist/commitIssueLinkLog.js.map +1 -1
  80. package/dist/config.d.ts +23 -2
  81. package/dist/config.js +119 -9
  82. package/dist/config.js.map +1 -1
  83. package/dist/connectorRoutes.d.ts +43 -0
  84. package/dist/connectorRoutes.js +1300 -0
  85. package/dist/connectorRoutes.js.map +1 -0
  86. package/dist/connectors/asana.d.ts +198 -0
  87. package/dist/connectors/asana.js +679 -0
  88. package/dist/connectors/asana.js.map +1 -0
  89. package/dist/connectors/baseConnector.d.ts +153 -0
  90. package/dist/connectors/baseConnector.js +336 -0
  91. package/dist/connectors/baseConnector.js.map +1 -0
  92. package/dist/connectors/confluence.d.ts +111 -0
  93. package/dist/connectors/confluence.js +406 -0
  94. package/dist/connectors/confluence.js.map +1 -0
  95. package/dist/connectors/datadog.d.ts +116 -0
  96. package/dist/connectors/datadog.js +385 -0
  97. package/dist/connectors/datadog.js.map +1 -0
  98. package/dist/connectors/discord.d.ts +150 -0
  99. package/dist/connectors/discord.js +543 -0
  100. package/dist/connectors/discord.js.map +1 -0
  101. package/dist/connectors/fixtureLibrary.d.ts +21 -0
  102. package/dist/connectors/fixtureLibrary.js +70 -0
  103. package/dist/connectors/fixtureLibrary.js.map +1 -0
  104. package/dist/connectors/fixtureRecorder.d.ts +1 -0
  105. package/dist/connectors/fixtureRecorder.js +35 -0
  106. package/dist/connectors/fixtureRecorder.js.map +1 -0
  107. package/dist/connectors/github.js +17 -18
  108. package/dist/connectors/github.js.map +1 -1
  109. package/dist/connectors/gitlab.d.ts +180 -0
  110. package/dist/connectors/gitlab.js +582 -0
  111. package/dist/connectors/gitlab.js.map +1 -0
  112. package/dist/connectors/gmail.d.ts +4 -1
  113. package/dist/connectors/gmail.js +149 -27
  114. package/dist/connectors/gmail.js.map +1 -1
  115. package/dist/connectors/googleCalendar.d.ts +4 -1
  116. package/dist/connectors/googleCalendar.js +88 -25
  117. package/dist/connectors/googleCalendar.js.map +1 -1
  118. package/dist/connectors/googleDrive.d.ts +34 -0
  119. package/dist/connectors/googleDrive.js +321 -0
  120. package/dist/connectors/googleDrive.js.map +1 -0
  121. package/dist/connectors/htmlEscape.d.ts +5 -0
  122. package/dist/connectors/htmlEscape.js +13 -0
  123. package/dist/connectors/htmlEscape.js.map +1 -0
  124. package/dist/connectors/hubspot.d.ts +112 -0
  125. package/dist/connectors/hubspot.js +408 -0
  126. package/dist/connectors/hubspot.js.map +1 -0
  127. package/dist/connectors/intercom.d.ts +102 -0
  128. package/dist/connectors/intercom.js +402 -0
  129. package/dist/connectors/intercom.js.map +1 -0
  130. package/dist/connectors/jira.d.ts +98 -0
  131. package/dist/connectors/jira.js +396 -0
  132. package/dist/connectors/jira.js.map +1 -0
  133. package/dist/connectors/linear.js +30 -19
  134. package/dist/connectors/linear.js.map +1 -1
  135. package/dist/connectors/mcpOAuth.d.ts +3 -0
  136. package/dist/connectors/mcpOAuth.js +64 -10
  137. package/dist/connectors/mcpOAuth.js.map +1 -1
  138. package/dist/connectors/mockConnector.d.ts +28 -0
  139. package/dist/connectors/mockConnector.js +81 -0
  140. package/dist/connectors/mockConnector.js.map +1 -0
  141. package/dist/connectors/notion.d.ts +143 -0
  142. package/dist/connectors/notion.js +424 -0
  143. package/dist/connectors/notion.js.map +1 -0
  144. package/dist/connectors/oauthStateStore.d.ts +31 -0
  145. package/dist/connectors/oauthStateStore.js +52 -0
  146. package/dist/connectors/oauthStateStore.js.map +1 -0
  147. package/dist/connectors/pagerduty.d.ts +160 -0
  148. package/dist/connectors/pagerduty.js +464 -0
  149. package/dist/connectors/pagerduty.js.map +1 -0
  150. package/dist/connectors/sentry.js +5 -13
  151. package/dist/connectors/sentry.js.map +1 -1
  152. package/dist/connectors/slack.d.ts +16 -1
  153. package/dist/connectors/slack.js +155 -32
  154. package/dist/connectors/slack.js.map +1 -1
  155. package/dist/connectors/stripe.d.ts +116 -0
  156. package/dist/connectors/stripe.js +379 -0
  157. package/dist/connectors/stripe.js.map +1 -0
  158. package/dist/connectors/tokenStorage.d.ts +35 -0
  159. package/dist/connectors/tokenStorage.js +484 -0
  160. package/dist/connectors/tokenStorage.js.map +1 -0
  161. package/dist/connectors/zendesk.d.ts +104 -0
  162. package/dist/connectors/zendesk.js +442 -0
  163. package/dist/connectors/zendesk.js.map +1 -0
  164. package/dist/cors.d.ts +10 -0
  165. package/dist/cors.js +29 -0
  166. package/dist/cors.js.map +1 -0
  167. package/dist/decisionReplay.d.ts +72 -0
  168. package/dist/decisionReplay.js +92 -0
  169. package/dist/decisionReplay.js.map +1 -0
  170. package/dist/decisionTraceLog.d.ts +6 -0
  171. package/dist/decisionTraceLog.js +54 -2
  172. package/dist/decisionTraceLog.js.map +1 -1
  173. package/dist/drivers/claude/subprocess.d.ts +12 -2
  174. package/dist/drivers/claude/subprocess.js +79 -6
  175. package/dist/drivers/claude/subprocess.js.map +1 -1
  176. package/dist/drivers/gemini/api.d.ts +18 -0
  177. package/dist/drivers/gemini/api.js +29 -0
  178. package/dist/drivers/gemini/api.js.map +1 -0
  179. package/dist/drivers/gemini/index.d.ts +5 -1
  180. package/dist/drivers/gemini/index.js +39 -5
  181. package/dist/drivers/gemini/index.js.map +1 -1
  182. package/dist/drivers/index.d.ts +8 -1
  183. package/dist/drivers/index.js +10 -2
  184. package/dist/drivers/index.js.map +1 -1
  185. package/dist/drivers/local/index.d.ts +26 -0
  186. package/dist/drivers/local/index.js +41 -0
  187. package/dist/drivers/local/index.js.map +1 -0
  188. package/dist/featureFlags.d.ts +79 -0
  189. package/dist/featureFlags.js +208 -0
  190. package/dist/featureFlags.js.map +1 -0
  191. package/dist/fp/automationInterpreter.js +26 -21
  192. package/dist/fp/automationInterpreter.js.map +1 -1
  193. package/dist/fp/automationProgram.d.ts +1 -1
  194. package/dist/fp/automationProgram.js.map +1 -1
  195. package/dist/fp/automationState.js +4 -1
  196. package/dist/fp/automationState.js.map +1 -1
  197. package/dist/fp/policyParser.js +21 -1
  198. package/dist/fp/policyParser.js.map +1 -1
  199. package/dist/httpErrorResponse.d.ts +36 -0
  200. package/dist/httpErrorResponse.js +46 -0
  201. package/dist/httpErrorResponse.js.map +1 -0
  202. package/dist/inboxRoutes.d.ts +22 -0
  203. package/dist/inboxRoutes.js +193 -0
  204. package/dist/inboxRoutes.js.map +1 -0
  205. package/dist/index.d.ts +1 -1
  206. package/dist/index.js +1403 -203
  207. package/dist/index.js.map +1 -1
  208. package/dist/installGuard.d.ts +25 -0
  209. package/dist/installGuard.js +48 -0
  210. package/dist/installGuard.js.map +1 -0
  211. package/dist/mcpRoutes.d.ts +37 -0
  212. package/dist/mcpRoutes.js +76 -0
  213. package/dist/mcpRoutes.js.map +1 -0
  214. package/dist/oauth.d.ts +20 -1
  215. package/dist/oauth.js +214 -39
  216. package/dist/oauth.js.map +1 -1
  217. package/dist/oauthRoutes.d.ts +32 -0
  218. package/dist/oauthRoutes.js +119 -0
  219. package/dist/oauthRoutes.js.map +1 -0
  220. package/dist/orchestrator/orchestratorBridge.js +2 -2
  221. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  222. package/dist/patchworkConfig.d.ts +29 -0
  223. package/dist/patchworkConfig.js +100 -5
  224. package/dist/patchworkConfig.js.map +1 -1
  225. package/dist/pluginLoader.d.ts +28 -0
  226. package/dist/pluginLoader.js +77 -11
  227. package/dist/pluginLoader.js.map +1 -1
  228. package/dist/pluginWatcher.js +8 -3
  229. package/dist/pluginWatcher.js.map +1 -1
  230. package/dist/preToolUseHook.d.ts +12 -0
  231. package/dist/preToolUseHook.js +30 -1
  232. package/dist/preToolUseHook.js.map +1 -1
  233. package/dist/prompts.js +4 -0
  234. package/dist/prompts.js.map +1 -1
  235. package/dist/recipeOrchestration.d.ts +121 -0
  236. package/dist/recipeOrchestration.js +965 -0
  237. package/dist/recipeOrchestration.js.map +1 -0
  238. package/dist/recipeRoutes.d.ts +185 -0
  239. package/dist/recipeRoutes.js +1369 -0
  240. package/dist/recipeRoutes.js.map +1 -0
  241. package/dist/recipes/RecipeOrchestrator.d.ts +40 -0
  242. package/dist/recipes/RecipeOrchestrator.js +51 -0
  243. package/dist/recipes/RecipeOrchestrator.js.map +1 -0
  244. package/dist/recipes/agentExecutor.d.ts +38 -0
  245. package/dist/recipes/agentExecutor.js +50 -0
  246. package/dist/recipes/agentExecutor.js.map +1 -0
  247. package/dist/recipes/chainedRunner.d.ts +191 -0
  248. package/dist/recipes/chainedRunner.js +759 -0
  249. package/dist/recipes/chainedRunner.js.map +1 -0
  250. package/dist/recipes/compiler.js +3 -3
  251. package/dist/recipes/compiler.js.map +1 -1
  252. package/dist/recipes/dependencyGraph.d.ts +39 -0
  253. package/dist/recipes/dependencyGraph.js +199 -0
  254. package/dist/recipes/dependencyGraph.js.map +1 -0
  255. package/dist/recipes/disabledMarkers.d.ts +48 -0
  256. package/dist/recipes/disabledMarkers.js +52 -0
  257. package/dist/recipes/disabledMarkers.js.map +1 -0
  258. package/dist/recipes/installer.js +3 -3
  259. package/dist/recipes/installer.js.map +1 -1
  260. package/dist/recipes/legacyRecipeCompat.d.ts +10 -0
  261. package/dist/recipes/legacyRecipeCompat.js +131 -0
  262. package/dist/recipes/legacyRecipeCompat.js.map +1 -0
  263. package/dist/recipes/manifest.d.ts +47 -0
  264. package/dist/recipes/manifest.js +156 -0
  265. package/dist/recipes/manifest.js.map +1 -0
  266. package/dist/recipes/migrationWarnings.d.ts +12 -0
  267. package/dist/recipes/migrationWarnings.js +44 -0
  268. package/dist/recipes/migrationWarnings.js.map +1 -0
  269. package/dist/recipes/migrations/index.d.ts +24 -0
  270. package/dist/recipes/migrations/index.js +55 -0
  271. package/dist/recipes/migrations/index.js.map +1 -0
  272. package/dist/recipes/migrations/types.d.ts +28 -0
  273. package/dist/recipes/migrations/types.js +2 -0
  274. package/dist/recipes/migrations/types.js.map +1 -0
  275. package/dist/recipes/migrations/v1.d.ts +11 -0
  276. package/dist/recipes/migrations/v1.js +18 -0
  277. package/dist/recipes/migrations/v1.js.map +1 -0
  278. package/dist/recipes/names.d.ts +40 -0
  279. package/dist/recipes/names.js +66 -0
  280. package/dist/recipes/names.js.map +1 -0
  281. package/dist/recipes/nestedRecipeStep.d.ts +58 -0
  282. package/dist/recipes/nestedRecipeStep.js +95 -0
  283. package/dist/recipes/nestedRecipeStep.js.map +1 -0
  284. package/dist/recipes/outputRegistry.d.ts +28 -0
  285. package/dist/recipes/outputRegistry.js +52 -0
  286. package/dist/recipes/outputRegistry.js.map +1 -0
  287. package/dist/recipes/parser.js +4 -1
  288. package/dist/recipes/parser.js.map +1 -1
  289. package/dist/recipes/replayRun.d.ts +62 -0
  290. package/dist/recipes/replayRun.js +97 -0
  291. package/dist/recipes/replayRun.js.map +1 -0
  292. package/dist/recipes/resolveRecipePath.d.ts +69 -0
  293. package/dist/recipes/resolveRecipePath.js +202 -0
  294. package/dist/recipes/resolveRecipePath.js.map +1 -0
  295. package/dist/recipes/scheduler.d.ts +23 -7
  296. package/dist/recipes/scheduler.js +225 -45
  297. package/dist/recipes/scheduler.js.map +1 -1
  298. package/dist/recipes/schema.d.ts +17 -2
  299. package/dist/recipes/schemaGenerator.d.ts +28 -0
  300. package/dist/recipes/schemaGenerator.js +565 -0
  301. package/dist/recipes/schemaGenerator.js.map +1 -0
  302. package/dist/recipes/stepObservation.d.ts +44 -0
  303. package/dist/recipes/stepObservation.js +232 -0
  304. package/dist/recipes/stepObservation.js.map +1 -0
  305. package/dist/recipes/templateEngine.d.ts +62 -0
  306. package/dist/recipes/templateEngine.js +201 -0
  307. package/dist/recipes/templateEngine.js.map +1 -0
  308. package/dist/recipes/toolRegistry.d.ts +186 -0
  309. package/dist/recipes/toolRegistry.js +309 -0
  310. package/dist/recipes/toolRegistry.js.map +1 -0
  311. package/dist/recipes/tools/asana.d.ts +16 -0
  312. package/dist/recipes/tools/asana.js +524 -0
  313. package/dist/recipes/tools/asana.js.map +1 -0
  314. package/dist/recipes/tools/calendar.d.ts +6 -0
  315. package/dist/recipes/tools/calendar.js +61 -0
  316. package/dist/recipes/tools/calendar.js.map +1 -0
  317. package/dist/recipes/tools/confluence.d.ts +6 -0
  318. package/dist/recipes/tools/confluence.js +254 -0
  319. package/dist/recipes/tools/confluence.js.map +1 -0
  320. package/dist/recipes/tools/datadog.d.ts +6 -0
  321. package/dist/recipes/tools/datadog.js +239 -0
  322. package/dist/recipes/tools/datadog.js.map +1 -0
  323. package/dist/recipes/tools/diagnostics.d.ts +6 -0
  324. package/dist/recipes/tools/diagnostics.js +36 -0
  325. package/dist/recipes/tools/diagnostics.js.map +1 -0
  326. package/dist/recipes/tools/discord.d.ts +18 -0
  327. package/dist/recipes/tools/discord.js +254 -0
  328. package/dist/recipes/tools/discord.js.map +1 -0
  329. package/dist/recipes/tools/file.d.ts +12 -0
  330. package/dist/recipes/tools/file.js +174 -0
  331. package/dist/recipes/tools/file.js.map +1 -0
  332. package/dist/recipes/tools/git.d.ts +6 -0
  333. package/dist/recipes/tools/git.js +63 -0
  334. package/dist/recipes/tools/git.js.map +1 -0
  335. package/dist/recipes/tools/github.d.ts +6 -0
  336. package/dist/recipes/tools/github.js +116 -0
  337. package/dist/recipes/tools/github.js.map +1 -0
  338. package/dist/recipes/tools/gitlab.d.ts +11 -0
  339. package/dist/recipes/tools/gitlab.js +285 -0
  340. package/dist/recipes/tools/gitlab.js.map +1 -0
  341. package/dist/recipes/tools/gmail.d.ts +6 -0
  342. package/dist/recipes/tools/gmail.js +451 -0
  343. package/dist/recipes/tools/gmail.js.map +1 -0
  344. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  345. package/dist/recipes/tools/googleDrive.js +55 -0
  346. package/dist/recipes/tools/googleDrive.js.map +1 -0
  347. package/dist/recipes/tools/hubspot.d.ts +6 -0
  348. package/dist/recipes/tools/hubspot.js +232 -0
  349. package/dist/recipes/tools/hubspot.js.map +1 -0
  350. package/dist/recipes/tools/index.d.ts +30 -0
  351. package/dist/recipes/tools/index.js +33 -0
  352. package/dist/recipes/tools/index.js.map +1 -0
  353. package/dist/recipes/tools/intercom.d.ts +6 -0
  354. package/dist/recipes/tools/intercom.js +226 -0
  355. package/dist/recipes/tools/intercom.js.map +1 -0
  356. package/dist/recipes/tools/jira.d.ts +14 -0
  357. package/dist/recipes/tools/jira.js +369 -0
  358. package/dist/recipes/tools/jira.js.map +1 -0
  359. package/dist/recipes/tools/linear.d.ts +7 -0
  360. package/dist/recipes/tools/linear.js +307 -0
  361. package/dist/recipes/tools/linear.js.map +1 -0
  362. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  363. package/dist/recipes/tools/meetingNotes.js +701 -0
  364. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  365. package/dist/recipes/tools/notion.d.ts +6 -0
  366. package/dist/recipes/tools/notion.js +278 -0
  367. package/dist/recipes/tools/notion.js.map +1 -0
  368. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  369. package/dist/recipes/tools/pagerduty.js +451 -0
  370. package/dist/recipes/tools/pagerduty.js.map +1 -0
  371. package/dist/recipes/tools/sentry.d.ts +12 -0
  372. package/dist/recipes/tools/sentry.js +73 -0
  373. package/dist/recipes/tools/sentry.js.map +1 -0
  374. package/dist/recipes/tools/slack.d.ts +6 -0
  375. package/dist/recipes/tools/slack.js +82 -0
  376. package/dist/recipes/tools/slack.js.map +1 -0
  377. package/dist/recipes/tools/stripe.d.ts +6 -0
  378. package/dist/recipes/tools/stripe.js +265 -0
  379. package/dist/recipes/tools/stripe.js.map +1 -0
  380. package/dist/recipes/tools/zendesk.d.ts +6 -0
  381. package/dist/recipes/tools/zendesk.js +245 -0
  382. package/dist/recipes/tools/zendesk.js.map +1 -0
  383. package/dist/recipes/validation.d.ts +13 -0
  384. package/dist/recipes/validation.js +617 -0
  385. package/dist/recipes/validation.js.map +1 -0
  386. package/dist/recipes/yamlRunner.d.ts +130 -2
  387. package/dist/recipes/yamlRunner.js +1009 -402
  388. package/dist/recipes/yamlRunner.js.map +1 -1
  389. package/dist/recipesHttp.d.ts +151 -6
  390. package/dist/recipesHttp.js +999 -29
  391. package/dist/recipesHttp.js.map +1 -1
  392. package/dist/riskTier.js +7 -1
  393. package/dist/riskTier.js.map +1 -1
  394. package/dist/runLog.d.ts +100 -1
  395. package/dist/runLog.js +258 -5
  396. package/dist/runLog.js.map +1 -1
  397. package/dist/schemas/dry-run-plan.v1.json +139 -0
  398. package/dist/schemas/recipe.v1.json +684 -0
  399. package/dist/server.d.ts +127 -8
  400. package/dist/server.js +740 -933
  401. package/dist/server.js.map +1 -1
  402. package/dist/ssrfGuard.d.ts +54 -0
  403. package/dist/ssrfGuard.js +122 -0
  404. package/dist/ssrfGuard.js.map +1 -0
  405. package/dist/streamableHttp.d.ts +39 -1
  406. package/dist/streamableHttp.js +128 -17
  407. package/dist/streamableHttp.js.map +1 -1
  408. package/dist/tokenUsageTracker.d.ts +33 -0
  409. package/dist/tokenUsageTracker.js +146 -0
  410. package/dist/tokenUsageTracker.js.map +1 -0
  411. package/dist/tools/activityLog.d.ts +2 -0
  412. package/dist/tools/addLinearComment.d.ts +1 -0
  413. package/dist/tools/addLinearComment.js +4 -2
  414. package/dist/tools/addLinearComment.js.map +1 -1
  415. package/dist/tools/batchLsp.d.ts +3 -0
  416. package/dist/tools/bridgeDoctor.d.ts +1 -0
  417. package/dist/tools/bridgeDoctor.js +2 -2
  418. package/dist/tools/bridgeDoctor.js.map +1 -1
  419. package/dist/tools/bridgeStatus.d.ts +1 -0
  420. package/dist/tools/cancelClaudeTask.d.ts +2 -0
  421. package/dist/tools/cancelClaudeTask.js +1 -0
  422. package/dist/tools/cancelClaudeTask.js.map +1 -1
  423. package/dist/tools/checkDocumentDirty.d.ts +1 -0
  424. package/dist/tools/clipboard.d.ts +2 -0
  425. package/dist/tools/closeTabs.d.ts +2 -0
  426. package/dist/tools/codeLens.d.ts +1 -0
  427. package/dist/tools/contextBundle.d.ts +1 -0
  428. package/dist/tools/createIssueFromAIComment.d.ts +1 -0
  429. package/dist/tools/createLinearIssue.d.ts +1 -0
  430. package/dist/tools/ctxGetTaskContext.d.ts +1 -0
  431. package/dist/tools/ctxQueryTraces.d.ts +1 -0
  432. package/dist/tools/ctxSaveTrace.d.ts +1 -0
  433. package/dist/tools/debug.d.ts +4 -0
  434. package/dist/tools/decorations.d.ts +2 -0
  435. package/dist/tools/documentLinks.d.ts +1 -0
  436. package/dist/tools/editText.d.ts +1 -0
  437. package/dist/tools/enrichCommit.d.ts +1 -0
  438. package/dist/tools/enrichStackTrace.d.ts +1 -0
  439. package/dist/tools/explainDiagnostic.d.ts +1 -0
  440. package/dist/tools/explainSymbol.d.ts +1 -0
  441. package/dist/tools/fetchCalendarEvents.d.ts +1 -0
  442. package/dist/tools/fetchGithubIssue.d.ts +1 -0
  443. package/dist/tools/fetchGithubPR.d.ts +1 -0
  444. package/dist/tools/fetchLinearIssue.d.ts +1 -0
  445. package/dist/tools/fetchSentryIssue.d.ts +1 -0
  446. package/dist/tools/fetchSlackProfile.d.ts +1 -0
  447. package/dist/tools/fetchSlackProfile.js +4 -1
  448. package/dist/tools/fetchSlackProfile.js.map +1 -1
  449. package/dist/tools/fileOperations.d.ts +3 -0
  450. package/dist/tools/fileWatcher.d.ts +2 -0
  451. package/dist/tools/findFiles.d.ts +1 -0
  452. package/dist/tools/findRelatedTests.d.ts +1 -0
  453. package/dist/tools/fixAllLintErrors.d.ts +1 -0
  454. package/dist/tools/foldingRanges.d.ts +1 -0
  455. package/dist/tools/formatDocument.d.ts +1 -0
  456. package/dist/tools/generateTests.d.ts +1 -0
  457. package/dist/tools/getAIComments.d.ts +1 -0
  458. package/dist/tools/getAnalyticsReport.d.ts +1 -0
  459. package/dist/tools/getArchitectureContext.d.ts +1 -0
  460. package/dist/tools/getBufferContent.d.ts +1 -0
  461. package/dist/tools/getChangeImpact.d.ts +1 -0
  462. package/dist/tools/getClaudeTaskStatus.d.ts +2 -0
  463. package/dist/tools/getClaudeTaskStatus.js +1 -0
  464. package/dist/tools/getClaudeTaskStatus.js.map +1 -1
  465. package/dist/tools/getCodeCoverage.d.ts +1 -0
  466. package/dist/tools/getCommitsForIssue.d.ts +1 -0
  467. package/dist/tools/getConnectorStatus.d.ts +1 -0
  468. package/dist/tools/getCurrentSelection.d.ts +2 -0
  469. package/dist/tools/getDebugState.d.ts +1 -0
  470. package/dist/tools/getDependencyTree.d.ts +1 -0
  471. package/dist/tools/getDiagnostics.d.ts +1 -0
  472. package/dist/tools/getDiffFromHandoff.d.ts +1 -0
  473. package/dist/tools/getDocumentSymbols.d.ts +25 -0
  474. package/dist/tools/getDocumentSymbols.js +74 -8
  475. package/dist/tools/getDocumentSymbols.js.map +1 -1
  476. package/dist/tools/getFileTree.d.ts +1 -0
  477. package/dist/tools/getGitDiff.d.ts +1 -0
  478. package/dist/tools/getGitHotspots.d.ts +1 -0
  479. package/dist/tools/getGitLog.d.ts +1 -0
  480. package/dist/tools/getGitStatus.d.ts +1 -0
  481. package/dist/tools/getImportTree.d.ts +1 -0
  482. package/dist/tools/getImportedSignatures.d.ts +1 -0
  483. package/dist/tools/getOpenEditors.d.ts +1 -0
  484. package/dist/tools/getPRTemplate.d.ts +1 -0
  485. package/dist/tools/getProjectContext.d.ts +1 -0
  486. package/dist/tools/getProjectInfo.d.ts +1 -0
  487. package/dist/tools/getSecurityAdvisories.d.ts +1 -0
  488. package/dist/tools/getSecurityAdvisories.js +10 -1
  489. package/dist/tools/getSecurityAdvisories.js.map +1 -1
  490. package/dist/tools/getSessionUsage.d.ts +4 -0
  491. package/dist/tools/getSessionUsage.js +3 -0
  492. package/dist/tools/getSessionUsage.js.map +1 -1
  493. package/dist/tools/getSymbolHistory.d.ts +1 -0
  494. package/dist/tools/getToolCapabilities.d.ts +1 -0
  495. package/dist/tools/getTypeSignature.d.ts +1 -0
  496. package/dist/tools/getWorkspaceFolders.d.ts +1 -0
  497. package/dist/tools/getWorkspaceSettings.d.ts +1 -0
  498. package/dist/tools/gitHistory.d.ts +2 -0
  499. package/dist/tools/gitWrite.d.ts +11 -0
  500. package/dist/tools/github/actions.d.ts +2 -0
  501. package/dist/tools/github/actions.js +4 -2
  502. package/dist/tools/github/actions.js.map +1 -1
  503. package/dist/tools/github/composite.d.ts +342 -0
  504. package/dist/tools/github/composite.js +343 -0
  505. package/dist/tools/github/composite.js.map +1 -0
  506. package/dist/tools/github/index.d.ts +1 -0
  507. package/dist/tools/github/index.js +1 -0
  508. package/dist/tools/github/index.js.map +1 -1
  509. package/dist/tools/github/issues.d.ts +4 -0
  510. package/dist/tools/github/issues.js +8 -4
  511. package/dist/tools/github/issues.js.map +1 -1
  512. package/dist/tools/github/pr.d.ts +7 -0
  513. package/dist/tools/github/pr.js +50 -12
  514. package/dist/tools/github/pr.js.map +1 -1
  515. package/dist/tools/handoffNote.d.ts +4 -0
  516. package/dist/tools/handoffNote.js +2 -0
  517. package/dist/tools/handoffNote.js.map +1 -1
  518. package/dist/tools/hoverAtCursor.d.ts +1 -0
  519. package/dist/tools/httpClient.d.ts +2 -0
  520. package/dist/tools/index.d.ts +8 -0
  521. package/dist/tools/index.js +47 -8
  522. package/dist/tools/index.js.map +1 -1
  523. package/dist/tools/inlayHints.d.ts +1 -0
  524. package/dist/tools/launchQuickTask.d.ts +2 -0
  525. package/dist/tools/launchQuickTask.js +1 -0
  526. package/dist/tools/launchQuickTask.js.map +1 -1
  527. package/dist/tools/listClaudeTasks.d.ts +2 -0
  528. package/dist/tools/listClaudeTasks.js +1 -0
  529. package/dist/tools/listClaudeTasks.js.map +1 -1
  530. package/dist/tools/listTerminals.d.ts +1 -0
  531. package/dist/tools/lsp.d.ts +14 -0
  532. package/dist/tools/navigateToSymbolByName.d.ts +1 -0
  533. package/dist/tools/openDiff.d.ts +1 -0
  534. package/dist/tools/openFile.d.ts +1 -0
  535. package/dist/tools/openInBrowser.d.ts +1 -0
  536. package/dist/tools/organizeImports.d.ts +1 -0
  537. package/dist/tools/performanceReport.d.ts +1 -0
  538. package/dist/tools/planPersistence.d.ts +5 -0
  539. package/dist/tools/previewEdit.d.ts +1 -0
  540. package/dist/tools/refactorAnalyze.d.ts +1 -0
  541. package/dist/tools/refactorPreview.d.ts +2 -0
  542. package/dist/tools/refactorPreview.js +1 -0
  543. package/dist/tools/refactorPreview.js.map +1 -1
  544. package/dist/tools/replaceBlock.d.ts +1 -0
  545. package/dist/tools/resumeClaudeTask.d.ts +2 -0
  546. package/dist/tools/resumeClaudeTask.js +1 -0
  547. package/dist/tools/resumeClaudeTask.js.map +1 -1
  548. package/dist/tools/runClaudeTask.d.ts +2 -0
  549. package/dist/tools/runClaudeTask.js +1 -0
  550. package/dist/tools/runClaudeTask.js.map +1 -1
  551. package/dist/tools/runCommand.d.ts +1 -0
  552. package/dist/tools/runCommand.js +5 -0
  553. package/dist/tools/runCommand.js.map +1 -1
  554. package/dist/tools/runTests.d.ts +1 -0
  555. package/dist/tools/saveDocument.d.ts +1 -0
  556. package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
  557. package/dist/tools/searchAndReplace.d.ts +1 -0
  558. package/dist/tools/searchTools.d.ts +1 -0
  559. package/dist/tools/searchTools.js +1 -1
  560. package/dist/tools/searchTools.js.map +1 -1
  561. package/dist/tools/searchWorkspace.d.ts +1 -0
  562. package/dist/tools/selectionRanges.d.ts +1 -0
  563. package/dist/tools/semanticTokens.d.ts +1 -0
  564. package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
  565. package/dist/tools/signatureHelp.d.ts +1 -0
  566. package/dist/tools/slackListChannels.d.ts +1 -0
  567. package/dist/tools/slackListChannels.js.map +1 -1
  568. package/dist/tools/slackPostMessage.d.ts +1 -0
  569. package/dist/tools/slackPostMessage.js +11 -6
  570. package/dist/tools/slackPostMessage.js.map +1 -1
  571. package/dist/tools/terminal.d.ts +6 -0
  572. package/dist/tools/terminal.js +4 -0
  573. package/dist/tools/terminal.js.map +1 -1
  574. package/dist/tools/testTraceToSource.d.ts +1 -0
  575. package/dist/tools/testTraceToSource.js +2 -2
  576. package/dist/tools/testTraceToSource.js.map +1 -1
  577. package/dist/tools/transaction.d.ts +23 -0
  578. package/dist/tools/transaction.js +29 -0
  579. package/dist/tools/transaction.js.map +1 -1
  580. package/dist/tools/typeHierarchy.d.ts +1 -0
  581. package/dist/tools/updateLinearIssue.d.ts +1 -0
  582. package/dist/tools/updateLinearIssue.js +20 -6
  583. package/dist/tools/updateLinearIssue.js.map +1 -1
  584. package/dist/tools/utils.d.ts +6 -0
  585. package/dist/tools/utils.js +59 -0
  586. package/dist/tools/utils.js.map +1 -1
  587. package/dist/tools/vscodeCommands.d.ts +2 -0
  588. package/dist/tools/vscodeTasks.d.ts +2 -0
  589. package/dist/tools/workspaceSettings.d.ts +1 -0
  590. package/dist/traceEncryption.d.ts +46 -0
  591. package/dist/traceEncryption.js +124 -0
  592. package/dist/traceEncryption.js.map +1 -0
  593. package/dist/transport.d.ts +46 -1
  594. package/dist/transport.js +173 -19
  595. package/dist/transport.js.map +1 -1
  596. package/package.json +30 -8
  597. package/scripts/mcp-stdio-shim.cjs +19 -3
  598. package/scripts/start-all.sh +34 -3
  599. package/templates/automation-policies/recipe-authoring.json +25 -0
  600. package/templates/automation-policy.example.json +6 -0
  601. package/templates/co.patchwork-os.bridge.plist +34 -0
  602. package/templates/policies/README.md +72 -0
  603. package/templates/policies/conservative.json +14 -0
  604. package/templates/policies/developer.json +14 -0
  605. package/templates/policies/headless-ci.json +24 -0
  606. package/templates/policies/personal-assistant.json +15 -0
  607. package/templates/policies/regulated-industry.json +18 -0
  608. package/templates/recipes/approval-queue-ui-test.yaml +205 -0
  609. package/templates/recipes/lint-on-save.yaml +1 -2
  610. package/templates/recipes/morning-brief-slack.yaml +57 -0
  611. package/templates/recipes/morning-brief.yaml +2 -2
  612. package/templates/recipes/project-health-check.yaml +50 -0
  613. package/templates/recipes/webhook/README.md +70 -0
  614. package/templates/recipes/webhook/capture-thought.yaml +26 -0
  615. package/templates/recipes/webhook/customer-escalation.yaml +49 -0
  616. package/templates/recipes/webhook/incident-intake.yaml +46 -0
  617. package/templates/recipes/webhook/meeting-prep.yaml +48 -0
  618. package/templates/recipes/webhook/morning-brief.yaml +57 -0
@@ -0,0 +1,647 @@
1
+ /**
2
+ * patchwork recipe install — download and install a recipe package.
3
+ * patchwork recipe list — list installed recipe packages.
4
+ *
5
+ * Supports:
6
+ * github:owner/repo
7
+ * github:owner/repo/subdir
8
+ * https://github.com/owner/repo
9
+ * ./local/path
10
+ */
11
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
12
+ import https from "node:https";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { disabledMarkerPath, isInstallDirDisabled, } from "../recipes/disabledMarkers.js";
16
+ import { getManifestRecipeFiles, loadManifestFromDir, parseManifest, } from "../recipes/manifest.js";
17
+ export const INSTALL_RECIPES_DIR = path.join(os.homedir(), ".patchwork", "recipes");
18
+ /**
19
+ * Reject path components that aren't a single safe basename — used at every
20
+ * boundary where externally-sourced filenames are joined onto a trusted
21
+ * directory (manifest fields, GitHub API responses, CLI args).
22
+ *
23
+ * Rejects empty/".."/".", any path separator, and control chars (NUL/newline/tab).
24
+ * Exported for testing and reuse.
25
+ */
26
+ export function isSafeBasename(name) {
27
+ if (typeof name !== "string" || name.length === 0)
28
+ return false;
29
+ if (name === "." || name === "..")
30
+ return false;
31
+ if (name.includes("/") || name.includes("\\"))
32
+ return false;
33
+ if (/[\x00-\x1F\x7F]/.test(name))
34
+ return false;
35
+ return true;
36
+ }
37
+ /**
38
+ * Parse a user-supplied source string into a typed InstallSource.
39
+ *
40
+ * Supported forms:
41
+ * github:owner/repo
42
+ * github:owner/repo@<ref> — pin to branch, tag, or commit SHA
43
+ * github:owner/repo/subdir
44
+ * github:owner/repo/subdir@<ref>
45
+ * gh:owner/repo[@<ref>] — short alias for github:
46
+ * https://github.com/owner/repo
47
+ * https://github.com/owner/repo/tree/<ref>/subdir — ref captured from URL
48
+ * ./relative/path
49
+ * /absolute/path
50
+ *
51
+ * `@<ref>` accepts any value that's valid as a git ref (branch, tag, SHA).
52
+ * Empty ref (`...@`) is rejected.
53
+ */
54
+ export function parseInstallSource(source) {
55
+ // Local path: starts with . or /
56
+ if (source.startsWith("./") ||
57
+ source.startsWith("/") ||
58
+ source.startsWith("../")) {
59
+ return { type: "local", path: source };
60
+ }
61
+ // github:/gh: prefix
62
+ if (source.startsWith("github:")) {
63
+ return parseGithubShorthand(source.slice("github:".length));
64
+ }
65
+ if (source.startsWith("gh:")) {
66
+ return parseGithubShorthand(source.slice("gh:".length));
67
+ }
68
+ // Full GitHub URL — captures owner, repo, optional ref (tree/<ref>), optional subdir
69
+ const githubUrlMatch = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+)(?:\/(.+))?)?\/?$/);
70
+ if (githubUrlMatch) {
71
+ const [, owner, repo, ref, subdir] = githubUrlMatch;
72
+ if (!owner || !repo) {
73
+ throw new Error(`Invalid GitHub URL: ${source}`);
74
+ }
75
+ return {
76
+ type: "github",
77
+ owner,
78
+ repo,
79
+ ...(subdir ? { subdir } : {}),
80
+ ...(ref ? { ref } : {}),
81
+ };
82
+ }
83
+ throw new Error(`Unrecognized install source: "${source}"\n` +
84
+ `Supported: github:owner/repo[@ref], github:owner/repo/subdir[@ref], gh:owner/repo[@ref], https://github.com/owner/repo, ./local/path`);
85
+ }
86
+ // ---------------------------------------------------------------------
87
+ // BEGIN A-PR2 EDIT BLOCK — `parseGithubShorthand` strict validation
88
+ // (dogfood R2 M-2). Owner/repo segments are validated against GitHub's own
89
+ // rules (alphanumeric or hyphen/dot/underscore, max 39 chars, must start
90
+ // alphanumeric) so injection attempts via shorthand (`gh:foo@bar:baz/repo`,
91
+ // `gh:owner/<repo>?evil=1`) are rejected before reaching the URL builder.
92
+ // Refs reject userinfo (`@`) and port markers (`:`) — these would otherwise
93
+ // land inside the constructed `https://github.com/.../tree/<ref>/...` URL.
94
+ // ---------------------------------------------------------------------
95
+ const GITHUB_OWNER_REPO_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-._]{0,38})$/;
96
+ function parseGithubShorthand(shorthand) {
97
+ // Extract trailing @<ref> if present. The ref is opaque to us — git accepts
98
+ // branches, tags, and commit SHAs in the same slot, and the GitHub API
99
+ // (which is what we ultimately call with this value) does too.
100
+ let ref;
101
+ const atIdx = shorthand.lastIndexOf("@");
102
+ if (atIdx !== -1) {
103
+ ref = shorthand.slice(atIdx + 1);
104
+ shorthand = shorthand.slice(0, atIdx);
105
+ if (!ref) {
106
+ throw new Error(`Invalid github shorthand: empty ref after "@" in "${shorthand}@"`);
107
+ }
108
+ // Reject embedded URL syntax that would corrupt the constructed
109
+ // https://github.com/<owner>/<repo>/tree/<ref> URL (R2 M-2).
110
+ if (/[@:?#\s]/.test(ref) || ref.includes("..")) {
111
+ throw new Error(`Invalid github shorthand: ref "${ref}" contains disallowed characters`);
112
+ }
113
+ }
114
+ // owner/repo or owner/repo/subdir (may have multiple path segments)
115
+ const parts = shorthand.split("/");
116
+ if (parts.length < 2) {
117
+ throw new Error(`Invalid github shorthand "${shorthand}": expected "owner/repo" or "owner/repo/subdir"`);
118
+ }
119
+ const [owner, repo, ...subdirParts] = parts;
120
+ if (!owner || !repo) {
121
+ throw new Error(`Invalid github shorthand: "${shorthand}"`);
122
+ }
123
+ if (!GITHUB_OWNER_REPO_RE.test(owner)) {
124
+ throw new Error(`Invalid github shorthand: owner "${owner}" is not a valid GitHub username`);
125
+ }
126
+ if (!GITHUB_OWNER_REPO_RE.test(repo)) {
127
+ throw new Error(`Invalid github shorthand: repo "${repo}" is not a valid GitHub repository name`);
128
+ }
129
+ // Subdir segments: each must be a safe path component (no traversal, no
130
+ // control chars). Reuses `isSafeBasename` for consistency with the post-fetch
131
+ // file boundary check.
132
+ for (const seg of subdirParts) {
133
+ if (!isSafeBasename(seg)) {
134
+ throw new Error(`Invalid github shorthand: subdir segment "${seg}" is unsafe`);
135
+ }
136
+ }
137
+ return {
138
+ type: "github",
139
+ owner,
140
+ repo,
141
+ ...(subdirParts.length > 0 ? { subdir: subdirParts.join("/") } : {}),
142
+ ...(ref ? { ref } : {}),
143
+ };
144
+ }
145
+ // END A-PR2 EDIT BLOCK
146
+ // ============================================================================
147
+ // Install name determination
148
+ // ============================================================================
149
+ /**
150
+ * Determine the install directory name from the manifest or source.
151
+ * - Manifest present: strip leading @ and replace / with -- for filesystem safety.
152
+ * - GitHub source (no manifest): "owner/repo" or "owner/repo/subdir".
153
+ * - Local source (no manifest): basename of the directory.
154
+ */
155
+ export function determineInstallName(manifest, source) {
156
+ if (manifest) {
157
+ // Strip leading @ and replace "/" with "--" so it's a valid directory name
158
+ return manifest.name.replace(/^@/, "").replace(/\//g, "--");
159
+ }
160
+ if (source.type === "github") {
161
+ const base = `${source.owner}/${source.repo}`;
162
+ return source.subdir ? `${base}/${source.subdir}` : base;
163
+ }
164
+ return path.basename(path.resolve(source.path));
165
+ }
166
+ // ============================================================================
167
+ // GitHub file fetching via API
168
+ // ============================================================================
169
+ // ---------------------------------------------------------------------
170
+ // BEGIN A-PR2 EDIT BLOCK — `httpsGet` redirect chain hardening
171
+ // (dogfood R2 I-2). Redirect targets must (1) be one of GitHub's known hosts
172
+ // and (2) clear the SSRF guard. Hop count capped at 5 to bound the chain.
173
+ // Origin is also validated up-front: this helper is reached only after
174
+ // `parseGithubShorthand` / GitHub URL parsing, so all callers should already
175
+ // be pointed at github.com / api.github.com / raw.githubusercontent.com.
176
+ // ---------------------------------------------------------------------
177
+ const GITHUB_REDIRECT_HOSTS = new Set([
178
+ "github.com",
179
+ "www.github.com",
180
+ "api.github.com",
181
+ "raw.githubusercontent.com",
182
+ "codeload.github.com",
183
+ "objects.githubusercontent.com",
184
+ "media.githubusercontent.com",
185
+ ]);
186
+ const HTTPS_GET_MAX_REDIRECTS = 5;
187
+ function isAllowedGithubHost(hostname) {
188
+ return GITHUB_REDIRECT_HOSTS.has(hostname.toLowerCase());
189
+ }
190
+ async function httpsGet(url, hops = 0) {
191
+ // Lazy-load the SSRF guard so test harnesses that mock https.get don't have
192
+ // to also stub DNS — the guard fast-paths public hostnames anyway.
193
+ const { isPrivateHost } = await import("../ssrfGuard.js");
194
+ let parsed;
195
+ try {
196
+ parsed = new URL(url);
197
+ }
198
+ catch {
199
+ throw new Error(`Invalid URL: ${url}`);
200
+ }
201
+ if (parsed.protocol !== "https:") {
202
+ throw new Error(`Refusing non-https URL: ${url}`);
203
+ }
204
+ if (!isAllowedGithubHost(parsed.hostname)) {
205
+ throw new Error(`Refusing redirect to non-GitHub host "${parsed.hostname}"`);
206
+ }
207
+ if (isPrivateHost(parsed.hostname)) {
208
+ throw new Error(`Refusing redirect to private host "${parsed.hostname}"`);
209
+ }
210
+ return new Promise((resolve, reject) => {
211
+ const req = https.get(url, {
212
+ headers: {
213
+ "User-Agent": "patchwork-recipe-installer/1.0",
214
+ Accept: "application/vnd.github.v3+json",
215
+ },
216
+ }, (res) => {
217
+ // Follow redirects — bounded chain, allowlisted host, SSRF-guarded.
218
+ if (res.statusCode &&
219
+ res.statusCode >= 300 &&
220
+ res.statusCode < 400 &&
221
+ res.headers.location) {
222
+ if (hops >= HTTPS_GET_MAX_REDIRECTS) {
223
+ reject(new Error(`Too many redirects (>${HTTPS_GET_MAX_REDIRECTS})`));
224
+ return;
225
+ }
226
+ // Resolve relative redirects against the current URL so a relative
227
+ // `Location: /foo` doesn't get treated as an empty hostname.
228
+ let nextUrl;
229
+ try {
230
+ nextUrl = new URL(res.headers.location, url);
231
+ }
232
+ catch {
233
+ reject(new Error(`Invalid redirect location: "${res.headers.location}"`));
234
+ return;
235
+ }
236
+ if (nextUrl.protocol !== "https:") {
237
+ reject(new Error(`Refusing redirect to non-https protocol: "${nextUrl.protocol}"`));
238
+ return;
239
+ }
240
+ httpsGet(nextUrl.toString(), hops + 1)
241
+ .then(resolve)
242
+ .catch(reject);
243
+ return;
244
+ }
245
+ if (res.statusCode && res.statusCode >= 400) {
246
+ reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
247
+ return;
248
+ }
249
+ const chunks = [];
250
+ res.on("data", (chunk) => chunks.push(chunk));
251
+ res.on("end", () => resolve(Buffer.concat(chunks)));
252
+ res.on("error", reject);
253
+ });
254
+ req.on("error", reject);
255
+ req.end();
256
+ });
257
+ }
258
+ async function listGitHubContents(owner, repo, dirPath, ref) {
259
+ const url = `https://api.github.com/repos/${owner}/${repo}/contents/${dirPath}?ref=${ref}`;
260
+ const body = await httpsGet(url);
261
+ const parsed = JSON.parse(body.toString("utf-8"));
262
+ if (!Array.isArray(parsed)) {
263
+ throw new Error(`Expected array from GitHub contents API, got: ${typeof parsed}`);
264
+ }
265
+ return parsed;
266
+ }
267
+ async function fetchGitHubFile(downloadUrl) {
268
+ return httpsGet(downloadUrl);
269
+ }
270
+ /**
271
+ * Download all .yaml/.yml files (and recipe.json if present) from a GitHub
272
+ * directory into `destDir`. Returns list of filenames written.
273
+ */
274
+ async function downloadGitHubDir(owner, repo, dirPath, ref, destDir) {
275
+ const items = await listGitHubContents(owner, repo, dirPath, ref);
276
+ const written = [];
277
+ for (const item of items) {
278
+ if (item.type !== "file")
279
+ continue;
280
+ if (item.name !== "recipe.json" && !/\.ya?ml$/i.test(item.name)) {
281
+ continue;
282
+ }
283
+ if (!item.download_url)
284
+ continue;
285
+ // GitHub Contents API responses are not implicitly trusted: a hostile
286
+ // repo (or a redirect-to-attacker) could supply names like `../etc/x`.
287
+ // The existing extension filter above already blocks the most obvious
288
+ // payloads, but we explicitly reject anything that isn't a single
289
+ // basename so a future change to the filter doesn't reopen the gap.
290
+ if (!isSafeBasename(item.name)) {
291
+ continue;
292
+ }
293
+ const content = await fetchGitHubFile(item.download_url);
294
+ const destPath = path.join(destDir, item.name);
295
+ // Belt-and-suspenders: confirm the resolved write path lives inside destDir.
296
+ if (!path.resolve(destPath).startsWith(`${path.resolve(destDir)}${path.sep}`)) {
297
+ continue;
298
+ }
299
+ writeFileSync(destPath, content);
300
+ written.push(item.name);
301
+ }
302
+ return written;
303
+ }
304
+ /**
305
+ * Install a recipe package from a source into INSTALL_RECIPES_DIR.
306
+ * Returns metadata about what was installed.
307
+ */
308
+ export async function runRecipeInstall(rawSource, options = {}) {
309
+ const source = parseInstallSource(rawSource);
310
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
311
+ // Stage into temp dir first
312
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), "patchwork-recipe-"));
313
+ try {
314
+ if (source.type === "local") {
315
+ await stageLocalSource(source, tmpDir);
316
+ }
317
+ else {
318
+ await stageGitHubSource(source, tmpDir);
319
+ }
320
+ // Read manifest if present
321
+ let manifest = null;
322
+ const manifestPath = path.join(tmpDir, "recipe.json");
323
+ if (existsSync(manifestPath)) {
324
+ manifest = parseManifest(readFileSync(manifestPath, "utf-8"));
325
+ }
326
+ // Determine which files to copy
327
+ let filesToCopy;
328
+ if (manifest) {
329
+ const declared = getManifestRecipeFiles(manifest);
330
+ // Include recipe.json + declared recipe files (that exist in tmpDir)
331
+ filesToCopy = ["recipe.json", ...declared].filter((f) => existsSync(path.join(tmpDir, f)));
332
+ }
333
+ else {
334
+ // No manifest: take all .yaml/.yml files
335
+ filesToCopy = readdirSync(tmpDir).filter((f) => /\.ya?ml$/i.test(f));
336
+ }
337
+ if (filesToCopy.length === 0) {
338
+ throw new Error(`No recipe files found in source "${rawSource}". ` +
339
+ `Expected .yaml/.yml files or a recipe.json manifest.`);
340
+ }
341
+ const installName = determineInstallName(manifest, source);
342
+ const installDir = path.join(recipesDir, installName);
343
+ // Reinstall correctness: detect whether this is an upgrade in place,
344
+ // and snapshot the existing enabled state so the upgrade doesn't
345
+ // silently re-disable a recipe the user explicitly opted into.
346
+ const isReinstall = existsSync(installDir);
347
+ const wasEnabled = isReinstall ? !isInstallDirDisabled(installDir) : false;
348
+ if (isReinstall) {
349
+ // Clear stale files from the previous version so files dropped from
350
+ // the new manifest don't linger. We rebuild the install dir wholesale
351
+ // rather than overlay the new files on top of the old.
352
+ try {
353
+ rmSync(installDir, { recursive: true, force: true });
354
+ }
355
+ catch {
356
+ // best-effort; mkdirSync below will throw with a clearer error
357
+ }
358
+ }
359
+ mkdirSync(installDir, { recursive: true });
360
+ // Copy files
361
+ for (const file of filesToCopy) {
362
+ const src = path.join(tmpDir, file);
363
+ const dest = path.join(installDir, file);
364
+ // Ensure subdirs exist (recipe.json could declare children in subdirs)
365
+ const destParent = path.dirname(dest);
366
+ if (!existsSync(destParent)) {
367
+ mkdirSync(destParent, { recursive: true });
368
+ }
369
+ cpSync(src, dest);
370
+ }
371
+ // Write the disabled-marker policy:
372
+ // - Fresh install: start disabled (per the wave2 plan's safety story).
373
+ // - Reinstall (upgrade in place): preserve whatever the user had set.
374
+ // If the recipe was enabled before, leave it enabled; if disabled,
375
+ // leave it disabled. Don't silently revoke an explicit user opt-in.
376
+ if (!isReinstall || !wasEnabled) {
377
+ writeFileSync(disabledMarkerPath(installDir), "");
378
+ }
379
+ return {
380
+ name: installName,
381
+ version: manifest?.version,
382
+ installDir,
383
+ filesInstalled: filesToCopy,
384
+ manifest,
385
+ };
386
+ }
387
+ finally {
388
+ try {
389
+ rmSync(tmpDir, { recursive: true, force: true });
390
+ }
391
+ catch {
392
+ // best-effort cleanup
393
+ }
394
+ }
395
+ }
396
+ async function stageLocalSource(source, tmpDir) {
397
+ const resolvedPath = path.resolve(source.path);
398
+ if (!existsSync(resolvedPath)) {
399
+ throw new Error(`Local path does not exist: ${resolvedPath}`);
400
+ }
401
+ if (!statSync(resolvedPath).isDirectory()) {
402
+ throw new Error(`Local path is not a directory: ${resolvedPath}`);
403
+ }
404
+ cpSync(resolvedPath, tmpDir, { recursive: true });
405
+ }
406
+ async function stageGitHubSource(source, tmpDir) {
407
+ const ref = source.ref ?? "main";
408
+ const dirPath = source.subdir ?? "";
409
+ try {
410
+ await downloadGitHubDir(source.owner, source.repo, dirPath, ref, tmpDir);
411
+ }
412
+ catch (err) {
413
+ // If main branch fails, try master
414
+ if (ref === "main") {
415
+ try {
416
+ await downloadGitHubDir(source.owner, source.repo, dirPath, "master", tmpDir);
417
+ return;
418
+ }
419
+ catch {
420
+ // fall through to original error
421
+ }
422
+ }
423
+ throw err;
424
+ }
425
+ }
426
+ /**
427
+ * Returns true if the install dir does not contain the disabled marker.
428
+ * Recipes installed before this marker existed have no marker and are
429
+ * therefore considered enabled — preserves backwards compatibility.
430
+ */
431
+ export function isRecipeEnabled(installDir) {
432
+ return !isInstallDirDisabled(installDir);
433
+ }
434
+ /**
435
+ * Locate an installed recipe directory by name. Returns null if not found.
436
+ *
437
+ * Validates `name` is a safe basename to defend against `recipe enable
438
+ * ../../../etc/foo` and similar — even though the on-disk effect would be
439
+ * limited to the `.disabled` filename, an arbitrary-path file write under
440
+ * the user's privilege is still a real attack surface.
441
+ */
442
+ function findInstalledRecipeDir(name, recipesDir) {
443
+ if (!isSafeBasename(name)) {
444
+ throw new Error(`Invalid recipe name "${name}" — must be a single directory name without path separators or control characters.`);
445
+ }
446
+ const direct = path.join(recipesDir, name);
447
+ // Defense-in-depth: even with the basename check above, confirm the resolved
448
+ // path lives under recipesDir. Symlinks inside recipesDir could in principle
449
+ // escape, so this catches that too.
450
+ const resolvedRoot = path.resolve(recipesDir);
451
+ const resolvedDir = path.resolve(direct);
452
+ if (!resolvedDir.startsWith(`${resolvedRoot}${path.sep}`)) {
453
+ throw new Error(`Resolved recipe path escapes recipes directory: "${name}"`);
454
+ }
455
+ if (existsSync(direct) && statSync(direct).isDirectory()) {
456
+ return direct;
457
+ }
458
+ return null;
459
+ }
460
+ /**
461
+ * Resolve an install-dir-name (the directory `runRecipeInstall` created) to
462
+ * the YAML entrypoint inside it. Used by `recipe run <name>` so the user can
463
+ * pass the name they see in `recipe list` rather than having to dig into the
464
+ * install directory layout.
465
+ *
466
+ * Resolution order:
467
+ * 1. `recipe.json` manifest's `recipes.main`, if the manifest exists and
468
+ * the file it points at exists on disk.
469
+ * 2. First `*.yaml` / `*.yml` in the install dir.
470
+ *
471
+ * Returns null if `name` doesn't correspond to an install dir, or the dir
472
+ * exists but contains no resolvable entrypoint. Path-traversal `name` values
473
+ * (e.g. `../../etc`) throw via the underlying `findInstalledRecipeDir` —
474
+ * same defence as enable/disable/uninstall.
475
+ */
476
+ export function findInstalledRecipeEntrypoint(name, options = {}) {
477
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
478
+ const installDir = findInstalledRecipeDir(name, recipesDir);
479
+ if (!installDir)
480
+ return null;
481
+ const manifestPath = path.join(installDir, "recipe.json");
482
+ if (existsSync(manifestPath)) {
483
+ try {
484
+ const m = JSON.parse(readFileSync(manifestPath, "utf-8"));
485
+ if (m.recipes?.main && isSafeBasename(m.recipes.main)) {
486
+ const candidate = path.join(installDir, m.recipes.main);
487
+ if (existsSync(candidate))
488
+ return candidate;
489
+ }
490
+ }
491
+ catch {
492
+ // Malformed manifest → fall through to first-yaml lookup. The
493
+ // scheduler does the same; surfacing the parse error here would
494
+ // shadow the top-level "recipe not found" error from the CLI.
495
+ }
496
+ }
497
+ try {
498
+ for (const entry of readdirSync(installDir)) {
499
+ if (/\.ya?ml$/i.test(entry)) {
500
+ return path.join(installDir, entry);
501
+ }
502
+ }
503
+ }
504
+ catch {
505
+ // unreadable
506
+ }
507
+ return null;
508
+ }
509
+ /**
510
+ * Enable a recipe — removes the .disabled marker so triggers can fire.
511
+ * Idempotent: enabling an already-enabled recipe is a no-op.
512
+ */
513
+ export function runRecipeEnable(name, options = {}) {
514
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
515
+ const installDir = findInstalledRecipeDir(name, recipesDir);
516
+ if (!installDir) {
517
+ throw new Error(`No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`);
518
+ }
519
+ const markerPath = disabledMarkerPath(installDir);
520
+ if (!existsSync(markerPath)) {
521
+ return { name, installDir, alreadyEnabled: true };
522
+ }
523
+ unlinkSync(markerPath);
524
+ return { name, installDir, alreadyEnabled: false };
525
+ }
526
+ /**
527
+ * Disable a recipe — writes the .disabled marker so triggers stop firing.
528
+ * Idempotent: disabling an already-disabled recipe is a no-op.
529
+ */
530
+ export function runRecipeDisable(name, options = {}) {
531
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
532
+ const installDir = findInstalledRecipeDir(name, recipesDir);
533
+ if (!installDir) {
534
+ throw new Error(`No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`);
535
+ }
536
+ const markerPath = disabledMarkerPath(installDir);
537
+ if (existsSync(markerPath)) {
538
+ return { name, installDir, alreadyDisabled: true };
539
+ }
540
+ writeFileSync(markerPath, "");
541
+ return { name, installDir, alreadyDisabled: false };
542
+ }
543
+ /**
544
+ * Uninstall a recipe — removes its install directory entirely.
545
+ *
546
+ * Returns `{ ok: false, error }` when the recipe isn't found rather than
547
+ * throwing, so the CLI can surface a clean error message instead of a
548
+ * stack trace. Path-traversal attempts in `name` still throw via
549
+ * `findInstalledRecipeDir`'s validator (HIGH-2 hardening from #46).
550
+ */
551
+ export function runRecipeUninstall(name, options = {}) {
552
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
553
+ const installDir = findInstalledRecipeDir(name, recipesDir);
554
+ if (!installDir) {
555
+ return {
556
+ ok: false,
557
+ error: `No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`,
558
+ };
559
+ }
560
+ rmSync(installDir, { recursive: true, force: true });
561
+ return { ok: true, installDir };
562
+ }
563
+ export function listInstalledRecipes(options = {}) {
564
+ const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
565
+ if (!existsSync(recipesDir)) {
566
+ return [];
567
+ }
568
+ const entries = [];
569
+ function scanDir(dir, namePrefix) {
570
+ const items = readdirSync(dir);
571
+ for (const item of items) {
572
+ const itemPath = path.join(dir, item);
573
+ if (!statSync(itemPath).isDirectory())
574
+ continue;
575
+ const entryName = namePrefix ? `${namePrefix}/${item}` : item;
576
+ const manifest = loadManifestFromDir(itemPath);
577
+ if (manifest) {
578
+ entries.push({
579
+ name: entryName,
580
+ version: manifest.version,
581
+ description: manifest.description,
582
+ connectors: manifest.connectors,
583
+ mainRecipe: manifest.recipes.main,
584
+ hasManifest: true,
585
+ enabled: isRecipeEnabled(itemPath),
586
+ });
587
+ }
588
+ else {
589
+ const yamlFiles = readdirSync(itemPath).filter((f) => /\.ya?ml$/i.test(f));
590
+ if (yamlFiles.length > 0) {
591
+ entries.push({
592
+ name: entryName,
593
+ yamlFiles,
594
+ hasManifest: false,
595
+ enabled: isRecipeEnabled(itemPath),
596
+ });
597
+ }
598
+ else {
599
+ // Recurse one level for namespaced dirs like "owner/repo"
600
+ if (!namePrefix) {
601
+ scanDir(itemPath, item);
602
+ }
603
+ }
604
+ }
605
+ }
606
+ }
607
+ scanDir(recipesDir, "");
608
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
609
+ }
610
+ // ============================================================================
611
+ // CLI output helpers
612
+ // ============================================================================
613
+ export function printInstallResult(result) {
614
+ const versionStr = result.version ? `@${result.version}` : "";
615
+ console.log(`✓ Installed ${result.name}${versionStr} to ${result.installDir}`);
616
+ if (result.manifest?.connectors && result.manifest.connectors.length > 0) {
617
+ console.log(` Requires connectors: ${result.manifest.connectors.join(", ")}`);
618
+ }
619
+ console.log(` Status: disabled (run \`patchwork recipe enable ${result.name}\` to activate scheduled triggers)`);
620
+ const mainRecipe = result.manifest?.recipes.main ?? result.filesInstalled[0];
621
+ if (mainRecipe) {
622
+ console.log(` Run with: patchwork recipe run ${path.join(result.installDir, mainRecipe)}`);
623
+ }
624
+ }
625
+ export function printInstalledList(entries) {
626
+ if (entries.length === 0) {
627
+ console.log("No recipes installed. Use `patchwork recipe install <source>` to install.");
628
+ return;
629
+ }
630
+ const maxName = Math.max(...entries.map((e) => e.name.length), 4);
631
+ const maxVersion = Math.max(...entries.map((e) => (e.version ?? "—").length), 7);
632
+ const header = `${"Name".padEnd(maxName)} ${"Version".padEnd(maxVersion)} Status Description / Files`;
633
+ console.log(header);
634
+ console.log("-".repeat(Math.min(header.length, 100)));
635
+ for (const entry of entries) {
636
+ const version = (entry.version ?? "—").padEnd(maxVersion);
637
+ const status = (entry.enabled ? "enabled" : "disabled").padEnd(8);
638
+ const detail = entry.hasManifest
639
+ ? (entry.description ?? "")
640
+ : `[${(entry.yamlFiles ?? []).join(", ")}]`;
641
+ console.log(`${entry.name.padEnd(maxName)} ${version} ${status} ${detail}`);
642
+ if (entry.connectors && entry.connectors.length > 0) {
643
+ console.log(`${"".padEnd(maxName)} ${"".padEnd(maxVersion)} ${"".padEnd(8)} connectors: ${entry.connectors.join(", ")}`);
644
+ }
645
+ }
646
+ }
647
+ //# sourceMappingURL=recipeInstall.js.map