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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (581) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +315 -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/approvalHttp.d.ts +49 -2
  14. package/dist/approvalHttp.js +217 -21
  15. package/dist/approvalHttp.js.map +1 -1
  16. package/dist/approvalInsights.d.ts +49 -0
  17. package/dist/approvalInsights.js +97 -0
  18. package/dist/approvalInsights.js.map +1 -0
  19. package/dist/approvalQueue.d.ts +27 -1
  20. package/dist/approvalQueue.js +123 -3
  21. package/dist/approvalQueue.js.map +1 -1
  22. package/dist/approvalSignals.d.ts +124 -0
  23. package/dist/approvalSignals.js +512 -0
  24. package/dist/approvalSignals.js.map +1 -0
  25. package/dist/automation.d.ts +57 -0
  26. package/dist/automation.js +156 -59
  27. package/dist/automation.js.map +1 -1
  28. package/dist/automationSuggestions.d.ts +79 -0
  29. package/dist/automationSuggestions.js +150 -0
  30. package/dist/automationSuggestions.js.map +1 -0
  31. package/dist/bridge.d.ts +3 -0
  32. package/dist/bridge.js +174 -143
  33. package/dist/bridge.js.map +1 -1
  34. package/dist/bridgeToken.js +57 -19
  35. package/dist/bridgeToken.js.map +1 -1
  36. package/dist/ccPermissions.d.ts +15 -0
  37. package/dist/ccPermissions.js +21 -4
  38. package/dist/ccPermissions.js.map +1 -1
  39. package/dist/claudeDriver.js +74 -16
  40. package/dist/claudeDriver.js.map +1 -1
  41. package/dist/claudeOrchestrator.d.ts +1 -1
  42. package/dist/claudeOrchestrator.js +14 -8
  43. package/dist/claudeOrchestrator.js.map +1 -1
  44. package/dist/commands/dashboard.js +1 -1
  45. package/dist/commands/dashboard.js.map +1 -1
  46. package/dist/commands/launchd.d.ts +2 -0
  47. package/dist/commands/launchd.js +94 -0
  48. package/dist/commands/launchd.js.map +1 -0
  49. package/dist/commands/patchworkInit.d.ts +8 -0
  50. package/dist/commands/patchworkInit.js +77 -11
  51. package/dist/commands/patchworkInit.js.map +1 -1
  52. package/dist/commands/recipe.d.ts +289 -0
  53. package/dist/commands/recipe.js +1359 -0
  54. package/dist/commands/recipe.js.map +1 -0
  55. package/dist/commands/recipeInstall.d.ts +150 -0
  56. package/dist/commands/recipeInstall.js +647 -0
  57. package/dist/commands/recipeInstall.js.map +1 -0
  58. package/dist/commands/tracesExport.d.ts +83 -0
  59. package/dist/commands/tracesExport.js +269 -0
  60. package/dist/commands/tracesExport.js.map +1 -0
  61. package/dist/commands/tracesImport.d.ts +56 -0
  62. package/dist/commands/tracesImport.js +161 -0
  63. package/dist/commands/tracesImport.js.map +1 -0
  64. package/dist/config.d.ts +22 -1
  65. package/dist/config.js +108 -9
  66. package/dist/config.js.map +1 -1
  67. package/dist/connectorRoutes.d.ts +43 -0
  68. package/dist/connectorRoutes.js +1609 -0
  69. package/dist/connectorRoutes.js.map +1 -0
  70. package/dist/connectors/asana.d.ts +198 -0
  71. package/dist/connectors/asana.js +679 -0
  72. package/dist/connectors/asana.js.map +1 -0
  73. package/dist/connectors/baseConnector.d.ts +153 -0
  74. package/dist/connectors/baseConnector.js +336 -0
  75. package/dist/connectors/baseConnector.js.map +1 -0
  76. package/dist/connectors/confluence.d.ts +111 -0
  77. package/dist/connectors/confluence.js +406 -0
  78. package/dist/connectors/confluence.js.map +1 -0
  79. package/dist/connectors/datadog.d.ts +116 -0
  80. package/dist/connectors/datadog.js +385 -0
  81. package/dist/connectors/datadog.js.map +1 -0
  82. package/dist/connectors/discord.d.ts +150 -0
  83. package/dist/connectors/discord.js +543 -0
  84. package/dist/connectors/discord.js.map +1 -0
  85. package/dist/connectors/fixtureLibrary.d.ts +21 -0
  86. package/dist/connectors/fixtureLibrary.js +70 -0
  87. package/dist/connectors/fixtureLibrary.js.map +1 -0
  88. package/dist/connectors/fixtureRecorder.d.ts +1 -0
  89. package/dist/connectors/fixtureRecorder.js +35 -0
  90. package/dist/connectors/fixtureRecorder.js.map +1 -0
  91. package/dist/connectors/github.js +17 -18
  92. package/dist/connectors/github.js.map +1 -1
  93. package/dist/connectors/gitlab.d.ts +180 -0
  94. package/dist/connectors/gitlab.js +582 -0
  95. package/dist/connectors/gitlab.js.map +1 -0
  96. package/dist/connectors/gmail.d.ts +4 -1
  97. package/dist/connectors/gmail.js +149 -27
  98. package/dist/connectors/gmail.js.map +1 -1
  99. package/dist/connectors/googleCalendar.d.ts +4 -1
  100. package/dist/connectors/googleCalendar.js +88 -25
  101. package/dist/connectors/googleCalendar.js.map +1 -1
  102. package/dist/connectors/googleDrive.d.ts +34 -0
  103. package/dist/connectors/googleDrive.js +321 -0
  104. package/dist/connectors/googleDrive.js.map +1 -0
  105. package/dist/connectors/htmlEscape.d.ts +5 -0
  106. package/dist/connectors/htmlEscape.js +13 -0
  107. package/dist/connectors/htmlEscape.js.map +1 -0
  108. package/dist/connectors/hubspot.d.ts +112 -0
  109. package/dist/connectors/hubspot.js +408 -0
  110. package/dist/connectors/hubspot.js.map +1 -0
  111. package/dist/connectors/intercom.d.ts +102 -0
  112. package/dist/connectors/intercom.js +402 -0
  113. package/dist/connectors/intercom.js.map +1 -0
  114. package/dist/connectors/jira.d.ts +98 -0
  115. package/dist/connectors/jira.js +379 -0
  116. package/dist/connectors/jira.js.map +1 -0
  117. package/dist/connectors/linear.js +30 -19
  118. package/dist/connectors/linear.js.map +1 -1
  119. package/dist/connectors/mcpOAuth.d.ts +3 -0
  120. package/dist/connectors/mcpOAuth.js +64 -10
  121. package/dist/connectors/mcpOAuth.js.map +1 -1
  122. package/dist/connectors/mockConnector.d.ts +28 -0
  123. package/dist/connectors/mockConnector.js +81 -0
  124. package/dist/connectors/mockConnector.js.map +1 -0
  125. package/dist/connectors/notion.d.ts +143 -0
  126. package/dist/connectors/notion.js +424 -0
  127. package/dist/connectors/notion.js.map +1 -0
  128. package/dist/connectors/oauthStateStore.d.ts +31 -0
  129. package/dist/connectors/oauthStateStore.js +52 -0
  130. package/dist/connectors/oauthStateStore.js.map +1 -0
  131. package/dist/connectors/pagerduty.d.ts +160 -0
  132. package/dist/connectors/pagerduty.js +464 -0
  133. package/dist/connectors/pagerduty.js.map +1 -0
  134. package/dist/connectors/sentry.js +5 -13
  135. package/dist/connectors/sentry.js.map +1 -1
  136. package/dist/connectors/slack.d.ts +16 -1
  137. package/dist/connectors/slack.js +155 -32
  138. package/dist/connectors/slack.js.map +1 -1
  139. package/dist/connectors/stripe.d.ts +116 -0
  140. package/dist/connectors/stripe.js +379 -0
  141. package/dist/connectors/stripe.js.map +1 -0
  142. package/dist/connectors/tokenStorage.d.ts +35 -0
  143. package/dist/connectors/tokenStorage.js +484 -0
  144. package/dist/connectors/tokenStorage.js.map +1 -0
  145. package/dist/connectors/zendesk.d.ts +104 -0
  146. package/dist/connectors/zendesk.js +442 -0
  147. package/dist/connectors/zendesk.js.map +1 -0
  148. package/dist/cors.d.ts +10 -0
  149. package/dist/cors.js +29 -0
  150. package/dist/cors.js.map +1 -0
  151. package/dist/decisionReplay.d.ts +72 -0
  152. package/dist/decisionReplay.js +92 -0
  153. package/dist/decisionReplay.js.map +1 -0
  154. package/dist/decisionTraceLog.d.ts +6 -0
  155. package/dist/decisionTraceLog.js +54 -2
  156. package/dist/decisionTraceLog.js.map +1 -1
  157. package/dist/drivers/gemini/index.d.ts +5 -1
  158. package/dist/drivers/gemini/index.js +39 -5
  159. package/dist/drivers/gemini/index.js.map +1 -1
  160. package/dist/drivers/index.d.ts +5 -0
  161. package/dist/drivers/index.js +1 -1
  162. package/dist/drivers/index.js.map +1 -1
  163. package/dist/featureFlags.d.ts +79 -0
  164. package/dist/featureFlags.js +208 -0
  165. package/dist/featureFlags.js.map +1 -0
  166. package/dist/fp/automationInterpreter.js +26 -21
  167. package/dist/fp/automationInterpreter.js.map +1 -1
  168. package/dist/fp/automationProgram.d.ts +1 -1
  169. package/dist/fp/automationProgram.js.map +1 -1
  170. package/dist/fp/automationState.js +4 -1
  171. package/dist/fp/automationState.js.map +1 -1
  172. package/dist/fp/policyParser.js +21 -1
  173. package/dist/fp/policyParser.js.map +1 -1
  174. package/dist/inboxRoutes.d.ts +22 -0
  175. package/dist/inboxRoutes.js +114 -0
  176. package/dist/inboxRoutes.js.map +1 -0
  177. package/dist/index.js +1400 -201
  178. package/dist/index.js.map +1 -1
  179. package/dist/installGuard.d.ts +25 -0
  180. package/dist/installGuard.js +48 -0
  181. package/dist/installGuard.js.map +1 -0
  182. package/dist/mcpRoutes.d.ts +37 -0
  183. package/dist/mcpRoutes.js +76 -0
  184. package/dist/mcpRoutes.js.map +1 -0
  185. package/dist/oauth.d.ts +7 -1
  186. package/dist/oauth.js +201 -39
  187. package/dist/oauth.js.map +1 -1
  188. package/dist/oauthRoutes.d.ts +32 -0
  189. package/dist/oauthRoutes.js +124 -0
  190. package/dist/oauthRoutes.js.map +1 -0
  191. package/dist/orchestrator/orchestratorBridge.js +2 -2
  192. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  193. package/dist/patchworkConfig.d.ts +16 -0
  194. package/dist/patchworkConfig.js +1 -1
  195. package/dist/patchworkConfig.js.map +1 -1
  196. package/dist/pluginLoader.d.ts +28 -0
  197. package/dist/pluginLoader.js +77 -11
  198. package/dist/pluginLoader.js.map +1 -1
  199. package/dist/pluginWatcher.js +8 -3
  200. package/dist/pluginWatcher.js.map +1 -1
  201. package/dist/preToolUseHook.d.ts +12 -0
  202. package/dist/preToolUseHook.js +23 -0
  203. package/dist/preToolUseHook.js.map +1 -1
  204. package/dist/recipeOrchestration.d.ts +121 -0
  205. package/dist/recipeOrchestration.js +955 -0
  206. package/dist/recipeOrchestration.js.map +1 -0
  207. package/dist/recipeRoutes.d.ts +180 -0
  208. package/dist/recipeRoutes.js +1345 -0
  209. package/dist/recipeRoutes.js.map +1 -0
  210. package/dist/recipes/RecipeOrchestrator.d.ts +40 -0
  211. package/dist/recipes/RecipeOrchestrator.js +51 -0
  212. package/dist/recipes/RecipeOrchestrator.js.map +1 -0
  213. package/dist/recipes/agentExecutor.d.ts +29 -0
  214. package/dist/recipes/agentExecutor.js +49 -0
  215. package/dist/recipes/agentExecutor.js.map +1 -0
  216. package/dist/recipes/chainedRunner.d.ts +191 -0
  217. package/dist/recipes/chainedRunner.js +759 -0
  218. package/dist/recipes/chainedRunner.js.map +1 -0
  219. package/dist/recipes/compiler.js +3 -3
  220. package/dist/recipes/compiler.js.map +1 -1
  221. package/dist/recipes/dependencyGraph.d.ts +39 -0
  222. package/dist/recipes/dependencyGraph.js +199 -0
  223. package/dist/recipes/dependencyGraph.js.map +1 -0
  224. package/dist/recipes/disabledMarkers.d.ts +48 -0
  225. package/dist/recipes/disabledMarkers.js +52 -0
  226. package/dist/recipes/disabledMarkers.js.map +1 -0
  227. package/dist/recipes/installer.js +3 -3
  228. package/dist/recipes/installer.js.map +1 -1
  229. package/dist/recipes/legacyRecipeCompat.d.ts +10 -0
  230. package/dist/recipes/legacyRecipeCompat.js +131 -0
  231. package/dist/recipes/legacyRecipeCompat.js.map +1 -0
  232. package/dist/recipes/manifest.d.ts +47 -0
  233. package/dist/recipes/manifest.js +156 -0
  234. package/dist/recipes/manifest.js.map +1 -0
  235. package/dist/recipes/migrationWarnings.d.ts +12 -0
  236. package/dist/recipes/migrationWarnings.js +44 -0
  237. package/dist/recipes/migrationWarnings.js.map +1 -0
  238. package/dist/recipes/migrations/index.d.ts +24 -0
  239. package/dist/recipes/migrations/index.js +55 -0
  240. package/dist/recipes/migrations/index.js.map +1 -0
  241. package/dist/recipes/migrations/types.d.ts +28 -0
  242. package/dist/recipes/migrations/types.js +2 -0
  243. package/dist/recipes/migrations/types.js.map +1 -0
  244. package/dist/recipes/migrations/v1.d.ts +11 -0
  245. package/dist/recipes/migrations/v1.js +18 -0
  246. package/dist/recipes/migrations/v1.js.map +1 -0
  247. package/dist/recipes/names.d.ts +40 -0
  248. package/dist/recipes/names.js +66 -0
  249. package/dist/recipes/names.js.map +1 -0
  250. package/dist/recipes/nestedRecipeStep.d.ts +58 -0
  251. package/dist/recipes/nestedRecipeStep.js +95 -0
  252. package/dist/recipes/nestedRecipeStep.js.map +1 -0
  253. package/dist/recipes/outputRegistry.d.ts +28 -0
  254. package/dist/recipes/outputRegistry.js +52 -0
  255. package/dist/recipes/outputRegistry.js.map +1 -0
  256. package/dist/recipes/parser.js +4 -1
  257. package/dist/recipes/parser.js.map +1 -1
  258. package/dist/recipes/replayRun.d.ts +62 -0
  259. package/dist/recipes/replayRun.js +97 -0
  260. package/dist/recipes/replayRun.js.map +1 -0
  261. package/dist/recipes/resolveRecipePath.d.ts +69 -0
  262. package/dist/recipes/resolveRecipePath.js +202 -0
  263. package/dist/recipes/resolveRecipePath.js.map +1 -0
  264. package/dist/recipes/scheduler.d.ts +23 -7
  265. package/dist/recipes/scheduler.js +225 -45
  266. package/dist/recipes/scheduler.js.map +1 -1
  267. package/dist/recipes/schema.d.ts +17 -2
  268. package/dist/recipes/schemaGenerator.d.ts +28 -0
  269. package/dist/recipes/schemaGenerator.js +565 -0
  270. package/dist/recipes/schemaGenerator.js.map +1 -0
  271. package/dist/recipes/stepObservation.d.ts +44 -0
  272. package/dist/recipes/stepObservation.js +232 -0
  273. package/dist/recipes/stepObservation.js.map +1 -0
  274. package/dist/recipes/templateEngine.d.ts +62 -0
  275. package/dist/recipes/templateEngine.js +201 -0
  276. package/dist/recipes/templateEngine.js.map +1 -0
  277. package/dist/recipes/toolRegistry.d.ts +186 -0
  278. package/dist/recipes/toolRegistry.js +309 -0
  279. package/dist/recipes/toolRegistry.js.map +1 -0
  280. package/dist/recipes/tools/asana.d.ts +16 -0
  281. package/dist/recipes/tools/asana.js +524 -0
  282. package/dist/recipes/tools/asana.js.map +1 -0
  283. package/dist/recipes/tools/calendar.d.ts +6 -0
  284. package/dist/recipes/tools/calendar.js +61 -0
  285. package/dist/recipes/tools/calendar.js.map +1 -0
  286. package/dist/recipes/tools/confluence.d.ts +6 -0
  287. package/dist/recipes/tools/confluence.js +254 -0
  288. package/dist/recipes/tools/confluence.js.map +1 -0
  289. package/dist/recipes/tools/datadog.d.ts +6 -0
  290. package/dist/recipes/tools/datadog.js +239 -0
  291. package/dist/recipes/tools/datadog.js.map +1 -0
  292. package/dist/recipes/tools/diagnostics.d.ts +6 -0
  293. package/dist/recipes/tools/diagnostics.js +36 -0
  294. package/dist/recipes/tools/diagnostics.js.map +1 -0
  295. package/dist/recipes/tools/discord.d.ts +18 -0
  296. package/dist/recipes/tools/discord.js +254 -0
  297. package/dist/recipes/tools/discord.js.map +1 -0
  298. package/dist/recipes/tools/file.d.ts +12 -0
  299. package/dist/recipes/tools/file.js +174 -0
  300. package/dist/recipes/tools/file.js.map +1 -0
  301. package/dist/recipes/tools/git.d.ts +6 -0
  302. package/dist/recipes/tools/git.js +63 -0
  303. package/dist/recipes/tools/git.js.map +1 -0
  304. package/dist/recipes/tools/github.d.ts +6 -0
  305. package/dist/recipes/tools/github.js +116 -0
  306. package/dist/recipes/tools/github.js.map +1 -0
  307. package/dist/recipes/tools/gitlab.d.ts +11 -0
  308. package/dist/recipes/tools/gitlab.js +285 -0
  309. package/dist/recipes/tools/gitlab.js.map +1 -0
  310. package/dist/recipes/tools/gmail.d.ts +6 -0
  311. package/dist/recipes/tools/gmail.js +434 -0
  312. package/dist/recipes/tools/gmail.js.map +1 -0
  313. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  314. package/dist/recipes/tools/googleDrive.js +55 -0
  315. package/dist/recipes/tools/googleDrive.js.map +1 -0
  316. package/dist/recipes/tools/hubspot.d.ts +6 -0
  317. package/dist/recipes/tools/hubspot.js +232 -0
  318. package/dist/recipes/tools/hubspot.js.map +1 -0
  319. package/dist/recipes/tools/index.d.ts +30 -0
  320. package/dist/recipes/tools/index.js +33 -0
  321. package/dist/recipes/tools/index.js.map +1 -0
  322. package/dist/recipes/tools/intercom.d.ts +6 -0
  323. package/dist/recipes/tools/intercom.js +226 -0
  324. package/dist/recipes/tools/intercom.js.map +1 -0
  325. package/dist/recipes/tools/jira.d.ts +14 -0
  326. package/dist/recipes/tools/jira.js +369 -0
  327. package/dist/recipes/tools/jira.js.map +1 -0
  328. package/dist/recipes/tools/linear.d.ts +7 -0
  329. package/dist/recipes/tools/linear.js +307 -0
  330. package/dist/recipes/tools/linear.js.map +1 -0
  331. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  332. package/dist/recipes/tools/meetingNotes.js +701 -0
  333. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  334. package/dist/recipes/tools/notion.d.ts +6 -0
  335. package/dist/recipes/tools/notion.js +278 -0
  336. package/dist/recipes/tools/notion.js.map +1 -0
  337. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  338. package/dist/recipes/tools/pagerduty.js +451 -0
  339. package/dist/recipes/tools/pagerduty.js.map +1 -0
  340. package/dist/recipes/tools/sentry.d.ts +12 -0
  341. package/dist/recipes/tools/sentry.js +73 -0
  342. package/dist/recipes/tools/sentry.js.map +1 -0
  343. package/dist/recipes/tools/slack.d.ts +6 -0
  344. package/dist/recipes/tools/slack.js +82 -0
  345. package/dist/recipes/tools/slack.js.map +1 -0
  346. package/dist/recipes/tools/stripe.d.ts +6 -0
  347. package/dist/recipes/tools/stripe.js +265 -0
  348. package/dist/recipes/tools/stripe.js.map +1 -0
  349. package/dist/recipes/tools/zendesk.d.ts +6 -0
  350. package/dist/recipes/tools/zendesk.js +245 -0
  351. package/dist/recipes/tools/zendesk.js.map +1 -0
  352. package/dist/recipes/validation.d.ts +13 -0
  353. package/dist/recipes/validation.js +617 -0
  354. package/dist/recipes/validation.js.map +1 -0
  355. package/dist/recipes/yamlRunner.d.ts +116 -1
  356. package/dist/recipes/yamlRunner.js +1000 -401
  357. package/dist/recipes/yamlRunner.js.map +1 -1
  358. package/dist/recipesHttp.d.ts +137 -6
  359. package/dist/recipesHttp.js +941 -29
  360. package/dist/recipesHttp.js.map +1 -1
  361. package/dist/riskTier.js +7 -1
  362. package/dist/riskTier.js.map +1 -1
  363. package/dist/runLog.d.ts +100 -1
  364. package/dist/runLog.js +258 -5
  365. package/dist/runLog.js.map +1 -1
  366. package/dist/schemas/dry-run-plan.v1.json +139 -0
  367. package/dist/schemas/recipe.v1.json +684 -0
  368. package/dist/server.d.ts +121 -8
  369. package/dist/server.js +538 -735
  370. package/dist/server.js.map +1 -1
  371. package/dist/ssrfGuard.d.ts +54 -0
  372. package/dist/ssrfGuard.js +122 -0
  373. package/dist/ssrfGuard.js.map +1 -0
  374. package/dist/streamableHttp.d.ts +39 -1
  375. package/dist/streamableHttp.js +128 -17
  376. package/dist/streamableHttp.js.map +1 -1
  377. package/dist/tokenUsageTracker.d.ts +33 -0
  378. package/dist/tokenUsageTracker.js +146 -0
  379. package/dist/tokenUsageTracker.js.map +1 -0
  380. package/dist/tools/activityLog.d.ts +2 -0
  381. package/dist/tools/addLinearComment.d.ts +1 -0
  382. package/dist/tools/addLinearComment.js +4 -2
  383. package/dist/tools/addLinearComment.js.map +1 -1
  384. package/dist/tools/batchLsp.d.ts +3 -0
  385. package/dist/tools/bridgeDoctor.d.ts +1 -0
  386. package/dist/tools/bridgeDoctor.js +2 -2
  387. package/dist/tools/bridgeDoctor.js.map +1 -1
  388. package/dist/tools/bridgeStatus.d.ts +1 -0
  389. package/dist/tools/cancelClaudeTask.d.ts +2 -0
  390. package/dist/tools/cancelClaudeTask.js +1 -0
  391. package/dist/tools/cancelClaudeTask.js.map +1 -1
  392. package/dist/tools/checkDocumentDirty.d.ts +1 -0
  393. package/dist/tools/clipboard.d.ts +2 -0
  394. package/dist/tools/closeTabs.d.ts +2 -0
  395. package/dist/tools/codeLens.d.ts +1 -0
  396. package/dist/tools/contextBundle.d.ts +1 -0
  397. package/dist/tools/createIssueFromAIComment.d.ts +1 -0
  398. package/dist/tools/createLinearIssue.d.ts +1 -0
  399. package/dist/tools/ctxGetTaskContext.d.ts +1 -0
  400. package/dist/tools/ctxQueryTraces.d.ts +1 -0
  401. package/dist/tools/ctxSaveTrace.d.ts +1 -0
  402. package/dist/tools/debug.d.ts +4 -0
  403. package/dist/tools/decorations.d.ts +2 -0
  404. package/dist/tools/documentLinks.d.ts +1 -0
  405. package/dist/tools/editText.d.ts +1 -0
  406. package/dist/tools/enrichCommit.d.ts +1 -0
  407. package/dist/tools/enrichStackTrace.d.ts +1 -0
  408. package/dist/tools/explainDiagnostic.d.ts +1 -0
  409. package/dist/tools/explainSymbol.d.ts +1 -0
  410. package/dist/tools/fetchCalendarEvents.d.ts +1 -0
  411. package/dist/tools/fetchGithubIssue.d.ts +1 -0
  412. package/dist/tools/fetchGithubPR.d.ts +1 -0
  413. package/dist/tools/fetchLinearIssue.d.ts +1 -0
  414. package/dist/tools/fetchSentryIssue.d.ts +1 -0
  415. package/dist/tools/fetchSlackProfile.d.ts +1 -0
  416. package/dist/tools/fetchSlackProfile.js +4 -1
  417. package/dist/tools/fetchSlackProfile.js.map +1 -1
  418. package/dist/tools/fileOperations.d.ts +3 -0
  419. package/dist/tools/fileWatcher.d.ts +2 -0
  420. package/dist/tools/findFiles.d.ts +1 -0
  421. package/dist/tools/findRelatedTests.d.ts +1 -0
  422. package/dist/tools/fixAllLintErrors.d.ts +1 -0
  423. package/dist/tools/foldingRanges.d.ts +1 -0
  424. package/dist/tools/formatDocument.d.ts +1 -0
  425. package/dist/tools/generateTests.d.ts +1 -0
  426. package/dist/tools/getAIComments.d.ts +1 -0
  427. package/dist/tools/getAnalyticsReport.d.ts +1 -0
  428. package/dist/tools/getArchitectureContext.d.ts +1 -0
  429. package/dist/tools/getBufferContent.d.ts +1 -0
  430. package/dist/tools/getChangeImpact.d.ts +1 -0
  431. package/dist/tools/getClaudeTaskStatus.d.ts +2 -0
  432. package/dist/tools/getClaudeTaskStatus.js +1 -0
  433. package/dist/tools/getClaudeTaskStatus.js.map +1 -1
  434. package/dist/tools/getCodeCoverage.d.ts +1 -0
  435. package/dist/tools/getCommitsForIssue.d.ts +1 -0
  436. package/dist/tools/getConnectorStatus.d.ts +1 -0
  437. package/dist/tools/getCurrentSelection.d.ts +2 -0
  438. package/dist/tools/getDebugState.d.ts +1 -0
  439. package/dist/tools/getDependencyTree.d.ts +1 -0
  440. package/dist/tools/getDiagnostics.d.ts +1 -0
  441. package/dist/tools/getDiffFromHandoff.d.ts +1 -0
  442. package/dist/tools/getDocumentSymbols.d.ts +25 -0
  443. package/dist/tools/getDocumentSymbols.js +74 -8
  444. package/dist/tools/getDocumentSymbols.js.map +1 -1
  445. package/dist/tools/getFileTree.d.ts +1 -0
  446. package/dist/tools/getGitDiff.d.ts +1 -0
  447. package/dist/tools/getGitHotspots.d.ts +1 -0
  448. package/dist/tools/getGitLog.d.ts +1 -0
  449. package/dist/tools/getGitStatus.d.ts +1 -0
  450. package/dist/tools/getImportTree.d.ts +1 -0
  451. package/dist/tools/getImportedSignatures.d.ts +1 -0
  452. package/dist/tools/getOpenEditors.d.ts +1 -0
  453. package/dist/tools/getPRTemplate.d.ts +1 -0
  454. package/dist/tools/getProjectContext.d.ts +1 -0
  455. package/dist/tools/getProjectInfo.d.ts +1 -0
  456. package/dist/tools/getSecurityAdvisories.d.ts +1 -0
  457. package/dist/tools/getSecurityAdvisories.js +10 -1
  458. package/dist/tools/getSecurityAdvisories.js.map +1 -1
  459. package/dist/tools/getSessionUsage.d.ts +4 -0
  460. package/dist/tools/getSessionUsage.js +3 -0
  461. package/dist/tools/getSessionUsage.js.map +1 -1
  462. package/dist/tools/getSymbolHistory.d.ts +1 -0
  463. package/dist/tools/getToolCapabilities.d.ts +1 -0
  464. package/dist/tools/getTypeSignature.d.ts +1 -0
  465. package/dist/tools/getWorkspaceFolders.d.ts +1 -0
  466. package/dist/tools/getWorkspaceSettings.d.ts +1 -0
  467. package/dist/tools/gitHistory.d.ts +2 -0
  468. package/dist/tools/gitWrite.d.ts +11 -0
  469. package/dist/tools/github/actions.d.ts +2 -0
  470. package/dist/tools/github/actions.js +4 -2
  471. package/dist/tools/github/actions.js.map +1 -1
  472. package/dist/tools/github/composite.d.ts +342 -0
  473. package/dist/tools/github/composite.js +343 -0
  474. package/dist/tools/github/composite.js.map +1 -0
  475. package/dist/tools/github/index.d.ts +1 -0
  476. package/dist/tools/github/index.js +1 -0
  477. package/dist/tools/github/index.js.map +1 -1
  478. package/dist/tools/github/issues.d.ts +4 -0
  479. package/dist/tools/github/issues.js +8 -4
  480. package/dist/tools/github/issues.js.map +1 -1
  481. package/dist/tools/github/pr.d.ts +7 -0
  482. package/dist/tools/github/pr.js +50 -12
  483. package/dist/tools/github/pr.js.map +1 -1
  484. package/dist/tools/handoffNote.d.ts +4 -0
  485. package/dist/tools/handoffNote.js +2 -0
  486. package/dist/tools/handoffNote.js.map +1 -1
  487. package/dist/tools/hoverAtCursor.d.ts +1 -0
  488. package/dist/tools/httpClient.d.ts +2 -0
  489. package/dist/tools/index.d.ts +8 -0
  490. package/dist/tools/index.js +47 -8
  491. package/dist/tools/index.js.map +1 -1
  492. package/dist/tools/inlayHints.d.ts +1 -0
  493. package/dist/tools/launchQuickTask.d.ts +2 -0
  494. package/dist/tools/launchQuickTask.js +1 -0
  495. package/dist/tools/launchQuickTask.js.map +1 -1
  496. package/dist/tools/listClaudeTasks.d.ts +2 -0
  497. package/dist/tools/listClaudeTasks.js +1 -0
  498. package/dist/tools/listClaudeTasks.js.map +1 -1
  499. package/dist/tools/listTerminals.d.ts +1 -0
  500. package/dist/tools/lsp.d.ts +14 -0
  501. package/dist/tools/navigateToSymbolByName.d.ts +1 -0
  502. package/dist/tools/openDiff.d.ts +1 -0
  503. package/dist/tools/openFile.d.ts +1 -0
  504. package/dist/tools/openInBrowser.d.ts +1 -0
  505. package/dist/tools/organizeImports.d.ts +1 -0
  506. package/dist/tools/performanceReport.d.ts +1 -0
  507. package/dist/tools/planPersistence.d.ts +5 -0
  508. package/dist/tools/previewEdit.d.ts +1 -0
  509. package/dist/tools/refactorAnalyze.d.ts +1 -0
  510. package/dist/tools/refactorPreview.d.ts +2 -0
  511. package/dist/tools/refactorPreview.js +1 -0
  512. package/dist/tools/refactorPreview.js.map +1 -1
  513. package/dist/tools/replaceBlock.d.ts +1 -0
  514. package/dist/tools/resumeClaudeTask.d.ts +2 -0
  515. package/dist/tools/resumeClaudeTask.js +1 -0
  516. package/dist/tools/resumeClaudeTask.js.map +1 -1
  517. package/dist/tools/runClaudeTask.d.ts +2 -0
  518. package/dist/tools/runClaudeTask.js +1 -0
  519. package/dist/tools/runClaudeTask.js.map +1 -1
  520. package/dist/tools/runCommand.d.ts +1 -0
  521. package/dist/tools/runTests.d.ts +1 -0
  522. package/dist/tools/saveDocument.d.ts +1 -0
  523. package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
  524. package/dist/tools/searchAndReplace.d.ts +1 -0
  525. package/dist/tools/searchTools.d.ts +1 -0
  526. package/dist/tools/searchTools.js +1 -1
  527. package/dist/tools/searchTools.js.map +1 -1
  528. package/dist/tools/searchWorkspace.d.ts +1 -0
  529. package/dist/tools/selectionRanges.d.ts +1 -0
  530. package/dist/tools/semanticTokens.d.ts +1 -0
  531. package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
  532. package/dist/tools/signatureHelp.d.ts +1 -0
  533. package/dist/tools/slackListChannels.d.ts +1 -0
  534. package/dist/tools/slackListChannels.js.map +1 -1
  535. package/dist/tools/slackPostMessage.d.ts +1 -0
  536. package/dist/tools/slackPostMessage.js +11 -6
  537. package/dist/tools/slackPostMessage.js.map +1 -1
  538. package/dist/tools/terminal.d.ts +6 -0
  539. package/dist/tools/testTraceToSource.d.ts +1 -0
  540. package/dist/tools/testTraceToSource.js +2 -2
  541. package/dist/tools/testTraceToSource.js.map +1 -1
  542. package/dist/tools/transaction.d.ts +23 -0
  543. package/dist/tools/transaction.js +29 -0
  544. package/dist/tools/transaction.js.map +1 -1
  545. package/dist/tools/typeHierarchy.d.ts +1 -0
  546. package/dist/tools/updateLinearIssue.d.ts +1 -0
  547. package/dist/tools/updateLinearIssue.js +20 -6
  548. package/dist/tools/updateLinearIssue.js.map +1 -1
  549. package/dist/tools/utils.d.ts +2 -0
  550. package/dist/tools/utils.js.map +1 -1
  551. package/dist/tools/vscodeCommands.d.ts +2 -0
  552. package/dist/tools/vscodeTasks.d.ts +2 -0
  553. package/dist/tools/workspaceSettings.d.ts +1 -0
  554. package/dist/traceEncryption.d.ts +46 -0
  555. package/dist/traceEncryption.js +124 -0
  556. package/dist/traceEncryption.js.map +1 -0
  557. package/dist/transport.d.ts +46 -1
  558. package/dist/transport.js +173 -19
  559. package/dist/transport.js.map +1 -1
  560. package/package.json +30 -8
  561. package/scripts/mcp-stdio-shim.cjs +19 -3
  562. package/scripts/start-all.sh +30 -1
  563. package/templates/automation-policies/recipe-authoring.json +25 -0
  564. package/templates/automation-policy.example.json +6 -0
  565. package/templates/co.patchwork-os.bridge.plist +34 -0
  566. package/templates/policies/README.md +72 -0
  567. package/templates/policies/conservative.json +14 -0
  568. package/templates/policies/developer.json +14 -0
  569. package/templates/policies/headless-ci.json +24 -0
  570. package/templates/policies/personal-assistant.json +15 -0
  571. package/templates/policies/regulated-industry.json +18 -0
  572. package/templates/recipes/lint-on-save.yaml +1 -2
  573. package/templates/recipes/morning-brief-slack.yaml +57 -0
  574. package/templates/recipes/morning-brief.yaml +2 -2
  575. package/templates/recipes/project-health-check.yaml +50 -0
  576. package/templates/recipes/webhook/README.md +70 -0
  577. package/templates/recipes/webhook/capture-thought.yaml +26 -0
  578. package/templates/recipes/webhook/customer-escalation.yaml +49 -0
  579. package/templates/recipes/webhook/incident-intake.yaml +46 -0
  580. package/templates/recipes/webhook/meeting-prep.yaml +48 -0
  581. package/templates/recipes/webhook/morning-brief.yaml +57 -0
@@ -0,0 +1,679 @@
1
+ /**
2
+ * Asana connector — read workspaces, projects, tasks, and current user, plus
3
+ * writes (createTask, updateTask, completeTask, addTaskComment).
4
+ *
5
+ * OAuth 2.0 Authorization Code Grant. Asana is a confidential client (bridge
6
+ * holds client secret), so PKCE is not used. Refresh tokens are issued; access
7
+ * tokens expire after 1 hour (3600s).
8
+ *
9
+ * Auth: standard OAuth 2.0 with `client_id` + `client_secret` + `redirect_uri`.
10
+ * - Env vars: ASANA_CLIENT_ID, ASANA_CLIENT_SECRET (mirrors discord/slack)
11
+ * - Stored: getSecretJsonSync("asana") → AsanaTokens
12
+ * - Header: Authorization: Bearer <access_token>
13
+ *
14
+ * Scope note: Asana's only OAuth scope is `default`, which grants read+write
15
+ * combined — there is no read-only-only scope. Defense lives at the recipe-tool
16
+ * layer where each tool declares `isWrite: true|false`. Newer Asana accounts
17
+ * may also expose granular scopes; we set `default` for compatibility.
18
+ *
19
+ * Read tools: getCurrentUser, listWorkspaces, listProjects, listTasks, getTask.
20
+ * Write tools: createTask, updateTask, completeTask, addTaskComment.
21
+ *
22
+ * Idempotency: Asana doesn't honor an Idempotency-Key header. Writes simply
23
+ * throw on error — callers must dedupe via app-level checks if needed.
24
+ *
25
+ * HTTP routes (wired in src/server.ts):
26
+ * GET /connections/asana/auth — redirect to Asana consent
27
+ * GET /connections/asana/callback — exchange code for tokens
28
+ * POST /connections/asana/test — ping Asana API
29
+ * DELETE /connections/asana — clear stored tokens
30
+ *
31
+ * Asana wraps every API response in `{ data: ... }`. We unwrap consistently
32
+ * inside this class so recipe-tool wrappers see clean arrays/objects.
33
+ *
34
+ * Extends BaseConnector for unified auth, retry, rate-limit, error handling.
35
+ * Token refresh is delegated to BaseConnector.refreshToken() via apiCall.
36
+ */
37
+ import crypto from "node:crypto";
38
+ import { BaseConnector, } from "./baseConnector.js";
39
+ import { escHtml } from "./htmlEscape.js";
40
+ import { deleteSecretJsonSync, getSecretJsonSync, storeSecretJsonSync, } from "./tokenStorage.js";
41
+ const ASANA_API_BASE = "https://app.asana.com/api/1.0";
42
+ const ASANA_AUTH_URL = "https://app.asana.com/-/oauth_authorize";
43
+ const ASANA_TOKEN_URL = "https://app.asana.com/-/oauth_token";
44
+ const SCOPES = ["default"];
45
+ // ── Config ───────────────────────────────────────────────────────────────────
46
+ function clientId() {
47
+ return process.env.ASANA_CLIENT_ID ?? "";
48
+ }
49
+ function clientSecret() {
50
+ return process.env.ASANA_CLIENT_SECRET ?? "";
51
+ }
52
+ function redirectUri() {
53
+ const base = (process.env.PATCHWORK_BRIDGE_URL ??
54
+ `http://localhost:${process.env.PATCHWORK_BRIDGE_PORT ?? "3101"}`).replace(/\/$/, "");
55
+ return `${base}/connections/asana/callback`;
56
+ }
57
+ function isConfigured() {
58
+ return Boolean(clientId() && clientSecret());
59
+ }
60
+ // ── Token persistence ────────────────────────────────────────────────────────
61
+ export function loadTokens() {
62
+ return getSecretJsonSync("asana");
63
+ }
64
+ export function saveTokens(tokens) {
65
+ storeSecretJsonSync("asana", tokens);
66
+ }
67
+ export function clearTokens() {
68
+ try {
69
+ deleteSecretJsonSync("asana");
70
+ }
71
+ catch {
72
+ // ignore
73
+ }
74
+ }
75
+ export function isConnected() {
76
+ return loadTokens() !== null;
77
+ }
78
+ // ── State (CSRF) ─────────────────────────────────────────────────────────────
79
+ // In-memory map keyed by hex random — short-lived (5 min). Mirrors discord's
80
+ // approach (Set + setTimeout).
81
+ import { createOAuthStateStore } from "./oauthStateStore.js";
82
+ const STATE_TTL_MS = 5 * 60 * 1000;
83
+ const pendingStates = createOAuthStateStore({ ttlMs: STATE_TTL_MS });
84
+ function generateState() {
85
+ const state = crypto.randomBytes(32).toString("hex");
86
+ if (!pendingStates.add(state)) {
87
+ throw new Error("OAuth state store full — too many concurrent authorize requests");
88
+ }
89
+ return state;
90
+ }
91
+ function consumeState(state) {
92
+ return pendingStates.consume(state);
93
+ }
94
+ // ── Connector class ──────────────────────────────────────────────────────────
95
+ export class AsanaConnector extends BaseConnector {
96
+ providerName = "asana";
97
+ getOAuthConfig() {
98
+ // Resolve credentials from env first, then fall back to credentials we
99
+ // stored at auth time (so refresh keeps working even if env is unset).
100
+ const tokens = loadTokens();
101
+ const id = clientId() || tokens?._client_id || "";
102
+ const secret = clientSecret() || tokens?._client_secret || "";
103
+ if (!id || !secret)
104
+ return null;
105
+ return {
106
+ clientId: id,
107
+ clientSecret: secret,
108
+ tokenEndpoint: ASANA_TOKEN_URL,
109
+ scopes: SCOPES,
110
+ };
111
+ }
112
+ async authenticate() {
113
+ const tokens = loadTokens();
114
+ if (!tokens) {
115
+ throw new Error("Asana not connected. Visit /connections/asana/auth to authorize.");
116
+ }
117
+ return {
118
+ token: tokens.access_token,
119
+ refreshToken: tokens.refresh_token,
120
+ expiresAt: tokens.expires_at ? new Date(tokens.expires_at) : undefined,
121
+ scopes: tokens.scope ? tokens.scope.split(" ") : SCOPES,
122
+ };
123
+ }
124
+ /**
125
+ * Persist refreshed tokens after BaseConnector.refreshToken() updates
126
+ * `this.auth`. BaseConnector calls `saveTokens()` (its own method on the
127
+ * tokenStorage StoredToken shape); we additionally mirror to our
128
+ * Asana-specific JSON so loadTokens() keeps working for HTTP probes.
129
+ */
130
+ async saveTokens() {
131
+ await super.saveTokens();
132
+ if (!this.auth)
133
+ return;
134
+ const existing = loadTokens();
135
+ saveTokens({
136
+ access_token: this.auth.token,
137
+ refresh_token: this.auth.refreshToken,
138
+ expires_at: this.auth.expiresAt
139
+ ? this.auth.expiresAt.getTime()
140
+ : undefined,
141
+ scope: this.auth.scopes?.join(" "),
142
+ token_type: existing?.token_type ?? "Bearer",
143
+ _client_id: existing?._client_id,
144
+ _client_secret: existing?._client_secret,
145
+ username: existing?.username,
146
+ user_gid: existing?.user_gid,
147
+ email: existing?.email,
148
+ connected_at: existing?.connected_at ?? new Date().toISOString(),
149
+ });
150
+ }
151
+ async healthCheck() {
152
+ try {
153
+ const result = await this.apiCall(async (token) => {
154
+ const res = await fetch(`${ASANA_API_BASE}/users/me`, {
155
+ headers: this.buildHeaders(token),
156
+ });
157
+ if (!res.ok)
158
+ throw res;
159
+ return res.json();
160
+ });
161
+ if ("error" in result)
162
+ return { ok: false, error: result.error };
163
+ return { ok: true };
164
+ }
165
+ catch (err) {
166
+ return { ok: false, error: this.normalizeError(err) };
167
+ }
168
+ }
169
+ normalizeError(error) {
170
+ if (error instanceof Response) {
171
+ const s = error.status;
172
+ // Asana 4xx payloads have an `errors: [{message, help}]` array — surface
173
+ // the first message when we can. We can't await here because the body
174
+ // may already be consumed; callers that want detail should pre-read.
175
+ const detail = error
176
+ ._asanaDetail;
177
+ const tag = detail ? `: ${detail}` : "";
178
+ if (s === 401)
179
+ return {
180
+ code: "auth_expired",
181
+ message: `Asana authentication failed — token expired or revoked${tag}`,
182
+ retryable: true,
183
+ suggestedAction: "Reconnect via /connections/asana/auth",
184
+ };
185
+ if (s === 403)
186
+ return {
187
+ code: "permission_denied",
188
+ message: `Insufficient Asana permissions for this resource${tag}`,
189
+ retryable: false,
190
+ };
191
+ if (s === 404)
192
+ return {
193
+ code: "not_found",
194
+ message: `Asana resource not found${tag}`,
195
+ retryable: false,
196
+ };
197
+ if (s === 429) {
198
+ const retryAfter = error.headers.get("retry-after");
199
+ return {
200
+ code: "rate_limited",
201
+ message: `Asana API rate limit exceeded${retryAfter ? ` (retry after ${retryAfter}s)` : ""}${tag}`,
202
+ retryable: true,
203
+ suggestedAction: retryAfter
204
+ ? `Wait ${retryAfter}s and retry`
205
+ : "Wait and retry",
206
+ providerDetail: retryAfter ? { retryAfter } : undefined,
207
+ };
208
+ }
209
+ return {
210
+ code: "provider_error",
211
+ message: `Asana API error: HTTP ${s}${tag}`,
212
+ retryable: s >= 500,
213
+ };
214
+ }
215
+ if (error instanceof Error) {
216
+ if (error.message.includes("ENOTFOUND") ||
217
+ error.message.includes("ECONNREFUSED")) {
218
+ return {
219
+ code: "network_error",
220
+ message: `Cannot connect to Asana: ${error.message}`,
221
+ retryable: true,
222
+ };
223
+ }
224
+ }
225
+ return {
226
+ code: "provider_error",
227
+ message: error instanceof Error ? error.message : String(error),
228
+ retryable: false,
229
+ };
230
+ }
231
+ getStatus() {
232
+ const tokens = loadTokens();
233
+ return {
234
+ id: "asana",
235
+ status: tokens ? "connected" : "disconnected",
236
+ lastSync: tokens?.connected_at,
237
+ workspace: tokens?.username,
238
+ };
239
+ }
240
+ // ── API Methods ────────────────────────────────────────────────────────────
241
+ async getCurrentUser() {
242
+ const result = await this.apiCall(async (token) => {
243
+ const res = await fetch(`${ASANA_API_BASE}/users/me`, {
244
+ headers: this.buildHeaders(token),
245
+ });
246
+ this.captureRateLimit(res);
247
+ if (!res.ok)
248
+ throw await this.attachErrorDetail(res);
249
+ const json = (await res.json());
250
+ return json.data;
251
+ });
252
+ if ("error" in result)
253
+ throw new Error(result.error.message);
254
+ return result.data;
255
+ }
256
+ async listWorkspaces(params = {}) {
257
+ const result = await this.apiCall(async (token) => {
258
+ const qs = new URLSearchParams({
259
+ limit: String(Math.min(Math.max(params.limit ?? 50, 1), 100)),
260
+ });
261
+ const res = await fetch(`${ASANA_API_BASE}/workspaces?${qs}`, {
262
+ headers: this.buildHeaders(token),
263
+ });
264
+ this.captureRateLimit(res);
265
+ if (!res.ok)
266
+ throw await this.attachErrorDetail(res);
267
+ const json = (await res.json());
268
+ return json.data;
269
+ });
270
+ if ("error" in result)
271
+ throw new Error(result.error.message);
272
+ return result.data;
273
+ }
274
+ async listProjects(params) {
275
+ if (!params.workspaceGid) {
276
+ throw new Error("listProjects requires workspaceGid");
277
+ }
278
+ const result = await this.apiCall(async (token) => {
279
+ const qs = new URLSearchParams({
280
+ workspace: params.workspaceGid,
281
+ limit: String(Math.min(Math.max(params.limit ?? 50, 1), 100)),
282
+ });
283
+ const res = await fetch(`${ASANA_API_BASE}/projects?${qs}`, {
284
+ headers: this.buildHeaders(token),
285
+ });
286
+ this.captureRateLimit(res);
287
+ if (!res.ok)
288
+ throw await this.attachErrorDetail(res);
289
+ const json = (await res.json());
290
+ return json.data;
291
+ });
292
+ if ("error" in result)
293
+ throw new Error(result.error.message);
294
+ return result.data;
295
+ }
296
+ async listTasks(params = {}) {
297
+ // Asana's GET /tasks requires either `project` OR (`assignee` + `workspace`).
298
+ // Asana itself returns a 400 when this is wrong; we surface a clearer error
299
+ // up front so recipe authors see the real problem instantly.
300
+ const hasProject = Boolean(params.projectGid);
301
+ const hasAssigneePair = Boolean(params.assignee && params.workspaceGid);
302
+ if (!hasProject && !hasAssigneePair) {
303
+ throw new Error("listTasks requires either projectGid, or assignee + workspaceGid");
304
+ }
305
+ const result = await this.apiCall(async (token) => {
306
+ const qs = new URLSearchParams({
307
+ limit: String(Math.min(Math.max(params.limit ?? 50, 1), 100)),
308
+ });
309
+ if (params.projectGid)
310
+ qs.set("project", params.projectGid);
311
+ if (params.assignee)
312
+ qs.set("assignee", params.assignee);
313
+ if (params.workspaceGid)
314
+ qs.set("workspace", params.workspaceGid);
315
+ const res = await fetch(`${ASANA_API_BASE}/tasks?${qs}`, {
316
+ headers: this.buildHeaders(token),
317
+ });
318
+ this.captureRateLimit(res);
319
+ if (!res.ok)
320
+ throw await this.attachErrorDetail(res);
321
+ const json = (await res.json());
322
+ return json.data;
323
+ });
324
+ if ("error" in result)
325
+ throw new Error(result.error.message);
326
+ return result.data;
327
+ }
328
+ async getTask(taskGid) {
329
+ if (!taskGid)
330
+ throw new Error("getTask requires taskGid");
331
+ const result = await this.apiCall(async (token) => {
332
+ const res = await fetch(`${ASANA_API_BASE}/tasks/${encodeURIComponent(taskGid)}`, { headers: this.buildHeaders(token) });
333
+ this.captureRateLimit(res);
334
+ if (!res.ok)
335
+ throw await this.attachErrorDetail(res);
336
+ const json = (await res.json());
337
+ return json.data;
338
+ });
339
+ if ("error" in result)
340
+ throw new Error(result.error.message);
341
+ return result.data;
342
+ }
343
+ // ── Write methods ──────────────────────────────────────────────────────────
344
+ // Asana wraps both request and response bodies in `{ data: ... }`. We
345
+ // unwrap the response consistently here so recipe-tool wrappers see the
346
+ // clean object. Required-param validation happens up front so recipe
347
+ // authors get a clear error instead of an Asana 400.
348
+ async createTask(params) {
349
+ if (!params.name)
350
+ throw new Error("createTask requires name");
351
+ if (!params.workspaceGid) {
352
+ throw new Error("createTask requires workspaceGid");
353
+ }
354
+ const result = await this.apiCall(async (token) => {
355
+ const data = {
356
+ workspace: params.workspaceGid,
357
+ name: params.name,
358
+ };
359
+ if (params.projectGid)
360
+ data.projects = [params.projectGid];
361
+ if (params.notes !== undefined)
362
+ data.notes = params.notes;
363
+ if (params.assigneeGid)
364
+ data.assignee = params.assigneeGid;
365
+ if (params.dueOn)
366
+ data.due_on = params.dueOn;
367
+ if (params.parentTaskGid)
368
+ data.parent = params.parentTaskGid;
369
+ const res = await fetch(`${ASANA_API_BASE}/tasks`, {
370
+ method: "POST",
371
+ headers: this.buildHeaders(token),
372
+ body: JSON.stringify({ data }),
373
+ });
374
+ this.captureRateLimit(res);
375
+ if (!res.ok)
376
+ throw await this.attachErrorDetail(res);
377
+ const json = (await res.json());
378
+ return json.data;
379
+ });
380
+ if ("error" in result)
381
+ throw new Error(result.error.message);
382
+ return result.data;
383
+ }
384
+ async updateTask(taskGid, updates) {
385
+ if (!taskGid)
386
+ throw new Error("updateTask requires taskGid");
387
+ // Strip undefined keys — Asana interprets `undefined`-stringified values
388
+ // differently than missing keys. Only forward fields that were explicitly
389
+ // provided by the caller.
390
+ const data = {};
391
+ if (updates.name !== undefined)
392
+ data.name = updates.name;
393
+ if (updates.notes !== undefined)
394
+ data.notes = updates.notes;
395
+ if (updates.completed !== undefined)
396
+ data.completed = updates.completed;
397
+ if (updates.assigneeGid !== undefined)
398
+ data.assignee = updates.assigneeGid;
399
+ if (updates.dueOn !== undefined)
400
+ data.due_on = updates.dueOn;
401
+ if (Object.keys(data).length === 0) {
402
+ throw new Error("updateTask requires at least one field to update");
403
+ }
404
+ const result = await this.apiCall(async (token) => {
405
+ const res = await fetch(`${ASANA_API_BASE}/tasks/${encodeURIComponent(taskGid)}`, {
406
+ method: "PUT",
407
+ headers: this.buildHeaders(token),
408
+ body: JSON.stringify({ data }),
409
+ });
410
+ this.captureRateLimit(res);
411
+ if (!res.ok)
412
+ throw await this.attachErrorDetail(res);
413
+ const json = (await res.json());
414
+ return json.data;
415
+ });
416
+ if ("error" in result)
417
+ throw new Error(result.error.message);
418
+ return result.data;
419
+ }
420
+ /**
421
+ * Convenience wrapper around updateTask that flips `completed: true`. Lower
422
+ * risk than other updates since it's a single, well-known state transition.
423
+ */
424
+ async completeTask(taskGid) {
425
+ if (!taskGid)
426
+ throw new Error("completeTask requires taskGid");
427
+ return this.updateTask(taskGid, { completed: true });
428
+ }
429
+ async addTaskComment(taskGid, options) {
430
+ if (!taskGid)
431
+ throw new Error("addTaskComment requires taskGid");
432
+ if (!options.text)
433
+ throw new Error("addTaskComment requires non-empty text");
434
+ const result = await this.apiCall(async (token) => {
435
+ const res = await fetch(`${ASANA_API_BASE}/tasks/${encodeURIComponent(taskGid)}/stories`, {
436
+ method: "POST",
437
+ headers: this.buildHeaders(token),
438
+ body: JSON.stringify({
439
+ data: { text: options.text, type: "comment" },
440
+ }),
441
+ });
442
+ this.captureRateLimit(res);
443
+ if (!res.ok)
444
+ throw await this.attachErrorDetail(res);
445
+ const json = (await res.json());
446
+ return json.data;
447
+ });
448
+ if ("error" in result)
449
+ throw new Error(result.error.message);
450
+ return result.data;
451
+ }
452
+ // ── Helpers ────────────────────────────────────────────────────────────────
453
+ buildHeaders(token) {
454
+ return {
455
+ Authorization: `Bearer ${token}`,
456
+ Accept: "application/json",
457
+ "Content-Type": "application/json",
458
+ };
459
+ }
460
+ captureRateLimit(res) {
461
+ this.updateRateLimitFromHeaders({
462
+ "x-ratelimit-remaining": res.headers.get("x-ratelimit-remaining") ?? undefined,
463
+ "x-ratelimit-reset": res.headers.get("x-ratelimit-reset") ?? undefined,
464
+ "retry-after": res.headers.get("retry-after") ?? undefined,
465
+ });
466
+ }
467
+ /**
468
+ * Read the response body once and stash the first Asana `errors[].message`
469
+ * onto a sidecar property so normalizeError can include it without a second
470
+ * read. Returns the same Response for `throw` to consume.
471
+ */
472
+ async attachErrorDetail(res) {
473
+ try {
474
+ const body = (await res.clone().json());
475
+ const first = body?.errors?.[0]?.message;
476
+ if (first) {
477
+ res._asanaDetail = first;
478
+ }
479
+ }
480
+ catch {
481
+ // body wasn't JSON or already consumed — proceed without detail
482
+ }
483
+ return res;
484
+ }
485
+ }
486
+ // ── Singleton ────────────────────────────────────────────────────────────────
487
+ let _instance = null;
488
+ function resetAsanaConnector() {
489
+ _instance = null;
490
+ }
491
+ export function getAsanaConnector() {
492
+ if (!_instance) {
493
+ _instance = new AsanaConnector();
494
+ }
495
+ return _instance;
496
+ }
497
+ export { getAsanaConnector as asana };
498
+ /**
499
+ * GET /connections/asana/auth — redirect to Asana consent screen.
500
+ */
501
+ export function handleAsanaAuthorize() {
502
+ if (!isConfigured()) {
503
+ return {
504
+ status: 503,
505
+ contentType: "application/json",
506
+ body: JSON.stringify({
507
+ ok: false,
508
+ error: "Asana connector not configured. Set ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.",
509
+ }),
510
+ };
511
+ }
512
+ const state = generateState();
513
+ const params = new URLSearchParams({
514
+ client_id: clientId(),
515
+ redirect_uri: redirectUri(),
516
+ response_type: "code",
517
+ scope: SCOPES.join(" "),
518
+ state,
519
+ });
520
+ return {
521
+ status: 302,
522
+ body: "",
523
+ redirect: `${ASANA_AUTH_URL}?${params.toString()}`,
524
+ };
525
+ }
526
+ /**
527
+ * GET /connections/asana/callback — exchange code for tokens.
528
+ */
529
+ export async function handleAsanaCallback(code, state, error) {
530
+ if (error) {
531
+ return {
532
+ status: 400,
533
+ contentType: "text/html",
534
+ body: `<html><body><h2>Asana connect failed</h2><pre>${escHtml(error)}</pre></body></html>`,
535
+ };
536
+ }
537
+ if (!code || !state) {
538
+ return {
539
+ status: 400,
540
+ contentType: "text/html",
541
+ body: `<html><body><h2>Asana connect failed</h2><pre>missing code or state</pre></body></html>`,
542
+ };
543
+ }
544
+ if (!consumeState(state)) {
545
+ return {
546
+ status: 400,
547
+ contentType: "text/html",
548
+ body: `<html><body><h2>Asana connect failed</h2><pre>invalid or expired state</pre></body></html>`,
549
+ };
550
+ }
551
+ try {
552
+ const params = new URLSearchParams({
553
+ grant_type: "authorization_code",
554
+ code,
555
+ redirect_uri: redirectUri(),
556
+ client_id: clientId(),
557
+ client_secret: clientSecret(),
558
+ });
559
+ const res = await fetch(ASANA_TOKEN_URL, {
560
+ method: "POST",
561
+ headers: {
562
+ "Content-Type": "application/x-www-form-urlencoded",
563
+ Accept: "application/json",
564
+ },
565
+ body: params.toString(),
566
+ });
567
+ if (!res.ok) {
568
+ const body = await res.text();
569
+ throw new Error(`Token exchange HTTP ${res.status}: ${body}`);
570
+ }
571
+ const json = (await res.json());
572
+ if (!json.access_token) {
573
+ throw new Error("Token exchange returned no access_token");
574
+ }
575
+ // Asana's token response includes `data: { gid, name, email }` for the
576
+ // authorizing user — use that if present, otherwise fall back to /users/me.
577
+ let username = json.data?.name;
578
+ let userGid = json.data?.gid ??
579
+ (typeof json.data?.id === "number" ? String(json.data.id) : undefined);
580
+ let userEmail = json.data?.email;
581
+ if (!username) {
582
+ try {
583
+ const userRes = await fetch(`${ASANA_API_BASE}/users/me`, {
584
+ headers: { Authorization: `Bearer ${json.access_token}` },
585
+ });
586
+ if (userRes.ok) {
587
+ const u = (await userRes.json());
588
+ username = u.data?.name;
589
+ userGid = u.data?.gid ?? userGid;
590
+ userEmail = u.data?.email ?? userEmail;
591
+ }
592
+ }
593
+ catch {
594
+ // best-effort
595
+ }
596
+ }
597
+ const expiresAt = typeof json.expires_in === "number" && json.expires_in > 0
598
+ ? Date.now() + json.expires_in * 1000
599
+ : undefined;
600
+ saveTokens({
601
+ access_token: json.access_token,
602
+ refresh_token: json.refresh_token,
603
+ expires_at: expiresAt,
604
+ scope: json.scope,
605
+ token_type: json.token_type ?? "Bearer",
606
+ _client_id: clientId() || undefined,
607
+ _client_secret: clientSecret() || undefined,
608
+ username,
609
+ user_gid: userGid,
610
+ email: userEmail,
611
+ connected_at: new Date().toISOString(),
612
+ });
613
+ resetAsanaConnector();
614
+ return {
615
+ status: 200,
616
+ contentType: "text/html",
617
+ body: `<html><body><h2>Asana connected${username ? ` as ${escHtml(username)}` : ""}</h2><script>try { window.opener.postMessage('patchwork:asana:connected', '*'); } catch(_) {} window.close();</script></body></html>`,
618
+ };
619
+ }
620
+ catch (err) {
621
+ return {
622
+ status: 400,
623
+ contentType: "text/html",
624
+ body: `<html><body><h2>Asana connect failed</h2><pre>${escHtml(err instanceof Error ? err.message : String(err))}</pre></body></html>`,
625
+ };
626
+ }
627
+ }
628
+ /**
629
+ * POST /connections/asana/test — verify stored token works.
630
+ */
631
+ export async function handleAsanaTest() {
632
+ const tokens = loadTokens();
633
+ if (!tokens) {
634
+ return {
635
+ status: 400,
636
+ contentType: "application/json",
637
+ body: JSON.stringify({ ok: false, error: "Asana not connected" }),
638
+ };
639
+ }
640
+ try {
641
+ const connector = getAsanaConnector();
642
+ const check = await connector.healthCheck();
643
+ return {
644
+ status: check.ok ? 200 : 401,
645
+ contentType: "application/json",
646
+ body: JSON.stringify(check.ok
647
+ ? { ok: true, username: tokens.username }
648
+ : { ok: false, error: check.error?.message }),
649
+ };
650
+ }
651
+ catch (err) {
652
+ return {
653
+ status: 500,
654
+ contentType: "application/json",
655
+ body: JSON.stringify({
656
+ ok: false,
657
+ error: err instanceof Error ? err.message : String(err),
658
+ }),
659
+ };
660
+ }
661
+ }
662
+ /**
663
+ * DELETE /connections/asana — clear stored tokens.
664
+ *
665
+ * Asana does not expose a public OAuth revocation endpoint, so we just drop
666
+ * local credentials. The token remains valid at Asana until it naturally
667
+ * expires (1h) or the user revokes the integration in their Asana account
668
+ * settings.
669
+ */
670
+ export async function handleAsanaDisconnect() {
671
+ clearTokens();
672
+ resetAsanaConnector();
673
+ return {
674
+ status: 200,
675
+ contentType: "application/json",
676
+ body: JSON.stringify({ ok: true }),
677
+ };
678
+ }
679
+ //# sourceMappingURL=asana.js.map