@cursor/bdk 0.2.3 → 0.2.4

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 (577) hide show
  1. package/dist/bin/agent-serve.js +9 -1
  2. package/dist/channels/slack/attachments.d.ts +2 -1
  3. package/dist/channels/slack/attachments.d.ts.map +1 -1
  4. package/dist/channels/slack/attachments.js +19 -9
  5. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  6. package/dist/channels/slack/dispatch.js +26 -24
  7. package/dist/channels/slack/thread-context.d.ts +21 -11
  8. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  9. package/dist/channels/slack/thread-context.js +75 -27
  10. package/dist/docs/404.html +2 -2
  11. package/dist/docs/assets/{app.BOIHhrKi.js → app.4kQ8Db_q.js} +4 -4
  12. package/dist/docs/assets/chunks/@localSearchIndexroot.CvuAJ0My.js +1 -0
  13. package/dist/docs/assets/chunks/{VPLocalSearchBox.D49zVZqM.js → VPLocalSearchBox.DY1RwB8Q.js} +1 -1
  14. package/dist/docs/assets/chunks/{arc.hmjb-sc4.js → arc.CxE_fSPK.js} +1 -1
  15. package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.CxEI1KqM.js → architectureDiagram-Q4EWVU46.CPX426ui.js} +1 -1
  16. package/dist/docs/assets/chunks/{baseUniq.DHHvBWoV.js → baseUniq.CB52JrKG.js} +1 -1
  17. package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.DnXW-3bu.js → blockDiagram-DXYQGD6D.J0cNaQCL.js} +1 -1
  18. package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.e-47O2jM.js → c4Diagram-AHTNJAMY.DJn3vEGT.js} +1 -1
  19. package/dist/docs/assets/chunks/channel.BoNycAoN.js +1 -0
  20. package/dist/docs/assets/chunks/{chunk-4BX2VUAB.3ry4pYXB.js → chunk-4BX2VUAB.Dao618F6.js} +1 -1
  21. package/dist/docs/assets/chunks/{chunk-4TB4RGXK.DGIZFRx6.js → chunk-4TB4RGXK.QW8Rhjai.js} +1 -1
  22. package/dist/docs/assets/chunks/{chunk-55IACEB6.DKBnqg9U.js → chunk-55IACEB6.DfRLLKF_.js} +1 -1
  23. package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BIU8Ue5k.js → chunk-EDXVE4YY.CIV2ApKQ.js} +1 -1
  24. package/dist/docs/assets/chunks/{chunk-FMBD7UC4.DP-TFhjI.js → chunk-FMBD7UC4.BEheSuxp.js} +1 -1
  25. package/dist/docs/assets/chunks/{chunk-OYMX7WX6.Ux0OYj5v.js → chunk-OYMX7WX6.CUN88ENB.js} +1 -1
  26. package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BzHMwYox.js → chunk-QZHKN3VN.CgqCs8b_.js} +1 -1
  27. package/dist/docs/assets/chunks/{chunk-YZCP3GAM.D4OGAsSP.js → chunk-YZCP3GAM.iCH0nnTJ.js} +1 -1
  28. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.C4x07Gkj.js +1 -0
  29. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.C4x07Gkj.js +1 -0
  30. package/dist/docs/assets/chunks/clone.Db22cH_-.js +1 -0
  31. package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.Jq_7BUNx.js → cose-bilkent-S5V4N54A.BLcUxhWE.js} +1 -1
  32. package/dist/docs/assets/chunks/{dagre-KV5264BT.Di-skR2x.js → dagre-KV5264BT.Ep7LofOm.js} +1 -1
  33. package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DeREHIdH.js → diagram-5BDNPKRD.Bzf4GSQ4.js} +1 -1
  34. package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.C7Kcqxro.js → diagram-G4DWMVQ6.DD6Jh-eM.js} +1 -1
  35. package/dist/docs/assets/chunks/{diagram-MMDJMWI5.BK3eohyo.js → diagram-MMDJMWI5.oOIl8bWd.js} +1 -1
  36. package/dist/docs/assets/chunks/{diagram-TYMM5635.C6TyymXT.js → diagram-TYMM5635.DHs85Ade.js} +1 -1
  37. package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.DYGG-gGx.js → erDiagram-SMLLAGMA.BIb5ghCk.js} +1 -1
  38. package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.C9nBcSV4.js → flowDiagram-DWJPFMVM.DV4paY9k.js} +1 -1
  39. package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.wAg_ymsp.js → ganttDiagram-T4ZO3ILL.AyEKA6_v.js} +1 -1
  40. package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.B87j9Oeg.js → gitGraphDiagram-UUTBAWPF.BltiCDFo.js} +1 -1
  41. package/dist/docs/assets/chunks/{graph.CuFMj31w.js → graph.DzuEYGdC.js} +1 -1
  42. package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.P_mLGumP.js → infoDiagram-42DDH7IO.BV2l981J.js} +1 -1
  43. package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DyuL_MsT.js → ishikawaDiagram-UXIWVN3A.BA4Izf-m.js} +1 -1
  44. package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CtP09IoJ.js → journeyDiagram-VCZTEJTY.DjaFbFS3.js} +1 -1
  45. package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BoeSYPZo.js → kanban-definition-6JOO6SKY.CfTFieDs.js} +1 -1
  46. package/dist/docs/assets/chunks/{layout.B0lMMtNo.js → layout.DzfWqTBV.js} +1 -1
  47. package/dist/docs/assets/chunks/{linear.HxRBiAFX.js → linear.BO6mpulV.js} +1 -1
  48. package/dist/docs/assets/chunks/{min.BGlFFHwS.js → min.BGgTadgT.js} +1 -1
  49. package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.JyLBD9M8.js → mindmap-definition-QFDTVHPH.DPuIXVWY.js} +1 -1
  50. package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DnSmNZF8.js → pieDiagram-DEJITSTG.BOnl3bBg.js} +1 -1
  51. package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.CUCnbfCa.js → quadrantDiagram-34T5L4WZ.DNB6Ckdc.js} +1 -1
  52. package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.py0IVUn5.js → requirementDiagram-MS252O5E.D5-Qo7vb.js} +1 -1
  53. package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Bkc-HWYp.js → sankeyDiagram-XADWPNL6.8t65cesf.js} +1 -1
  54. package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.Dqn8Mlv2.js → sequenceDiagram-FGHM5R23.CaG07BdS.js} +1 -1
  55. package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.JikkoQUU.js → stateDiagram-FHFEXIEX.TKOweTB6.js} +1 -1
  56. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CuqmyQc4.js +1 -0
  57. package/dist/docs/assets/chunks/{theme.b6HQ6zG_.js → theme.Dx-JhpTB.js} +2 -2
  58. package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.BvpSXiBj.js → timeline-definition-GMOUNBTQ.fE53zQ2s.js} +1 -1
  59. package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.CU7MVnZx.js → vennDiagram-DHZGUBPP.DJsZZaJg.js} +1 -1
  60. package/dist/docs/assets/chunks/{wardley-RL74JXVD.BKwC4ctG.js → wardley-RL74JXVD.844kyrjl.js} +1 -1
  61. package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.BfaF6Uhe.js → wardleyDiagram-NUSXRM2D.CMZ-Ru_t.js} +1 -1
  62. package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.Bg5dcpj4.js → xychartDiagram-5P7HB3ND.XXrhBc7a.js} +1 -1
  63. package/dist/docs/assets/{deployment.md.BJ8i7Q6p.js → deployment.md.UhnJIMr-.js} +2 -2
  64. package/dist/docs/assets/{deployment.md.BJ8i7Q6p.lean.js → deployment.md.UhnJIMr-.lean.js} +1 -1
  65. package/dist/docs/assets/{guides_jev.md.BNDm14Gy.js → guides_jev.md.CRcgmjDb.js} +2 -2
  66. package/dist/docs/assets/{guides_jev.md.BNDm14Gy.lean.js → guides_jev.md.CRcgmjDb.lean.js} +1 -1
  67. package/dist/docs/assets/{reference_agent-config.md.DzGI777n.js → reference_agent-config.md.Cj5uBKp4.js} +1 -1
  68. package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.js → reference_extensions.md.pa2tN-e4.js} +4 -3
  69. package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.lean.js → reference_extensions.md.pa2tN-e4.lean.js} +1 -1
  70. package/dist/docs/building-with-agents.html +35 -35
  71. package/dist/docs/deployment.html +37 -37
  72. package/dist/docs/deployment.md +4 -1
  73. package/dist/docs/evals.html +35 -35
  74. package/dist/docs/guides/agent-to-agent.html +35 -35
  75. package/dist/docs/guides/bitbucket.html +35 -35
  76. package/dist/docs/guides/cloud-agents.html +35 -35
  77. package/dist/docs/guides/convert-automation.html +35 -35
  78. package/dist/docs/guides/github.html +35 -35
  79. package/dist/docs/guides/gitlab.html +35 -35
  80. package/dist/docs/guides/grokbot-agents.html +35 -35
  81. package/dist/docs/guides/hooks.html +35 -35
  82. package/dist/docs/guides/improve.html +35 -35
  83. package/dist/docs/guides/jev.html +37 -37
  84. package/dist/docs/guides/jev.md +7 -0
  85. package/dist/docs/guides/mcp-oauth.html +35 -35
  86. package/dist/docs/guides/opentelemetry.html +35 -35
  87. package/dist/docs/guides/slack.html +35 -35
  88. package/dist/docs/guides/webhooks.html +35 -35
  89. package/dist/docs/hashmap.json +1 -1
  90. package/dist/docs/hillclimbing.html +35 -35
  91. package/dist/docs/index.html +35 -35
  92. package/dist/docs/llms-full.txt +25 -2
  93. package/dist/docs/quickstart.html +35 -35
  94. package/dist/docs/reference/agent-config.html +37 -37
  95. package/dist/docs/reference/agent-config.md +1 -1
  96. package/dist/docs/reference/artifacts.html +35 -35
  97. package/dist/docs/reference/channels.html +35 -35
  98. package/dist/docs/reference/cli.html +35 -35
  99. package/dist/docs/reference/connections.html +35 -35
  100. package/dist/docs/reference/evals.html +35 -35
  101. package/dist/docs/reference/extensions.html +39 -38
  102. package/dist/docs/reference/extensions.md +13 -0
  103. package/dist/docs/reference/hooks.html +35 -35
  104. package/dist/docs/reference/http-api.html +35 -35
  105. package/dist/docs/reference/instructions.html +35 -35
  106. package/dist/docs/reference/playground.html +35 -35
  107. package/dist/docs/reference/project-layout.html +35 -35
  108. package/dist/docs/reference/prompt.html +35 -35
  109. package/dist/docs/reference/schedules.html +35 -35
  110. package/dist/docs/reference/sessions.html +35 -35
  111. package/dist/docs/reference/skills.html +35 -35
  112. package/dist/docs/reference/subagents.html +35 -35
  113. package/dist/docs/reference/tools.html +35 -35
  114. package/dist/docs/templates/agentic-owners.html +35 -35
  115. package/dist/docs/templates/security-reviewer.html +35 -35
  116. package/dist/docs/templates/thermo-quality-review.html +35 -35
  117. package/dist/docs/templates/thermo-review.html +35 -35
  118. package/dist/docs/templates/triage.html +35 -35
  119. package/dist/docs/troubleshooting.html +35 -35
  120. package/dist/extensions/jev/extension.d.ts +3 -0
  121. package/dist/extensions/jev/extension.d.ts.map +1 -1
  122. package/dist/extensions/jev/extension.js +10 -1
  123. package/dist/extensions/jev/lib/evaluate.d.ts +4 -0
  124. package/dist/extensions/jev/lib/evaluate.d.ts.map +1 -1
  125. package/dist/extensions/jev/lib/evaluate.js +6 -2
  126. package/dist/extensions.d.ts +4 -2
  127. package/dist/extensions.d.ts.map +1 -1
  128. package/dist/extensions.js +5 -2
  129. package/dist/internal/cli-deploy.d.ts.map +1 -1
  130. package/dist/internal/cli-deploy.js +19 -5
  131. package/dist/internal/discovery/agent-config.d.ts +6 -5
  132. package/dist/internal/discovery/agent-config.d.ts.map +1 -1
  133. package/dist/internal/discovery/agent-config.js +29 -28
  134. package/dist/internal/discovery/agent.js +1 -1
  135. package/dist/internal/discovery/extensions.d.ts.map +1 -1
  136. package/dist/internal/discovery/extensions.js +12 -0
  137. package/dist/internal/hosting.d.ts +8 -0
  138. package/dist/internal/hosting.d.ts.map +1 -1
  139. package/dist/internal/hosting.js +24 -0
  140. package/dist/types.d.ts +18 -2
  141. package/dist/types.d.ts.map +1 -1
  142. package/docs/deployment.md +4 -1
  143. package/docs/guides/jev.md +7 -0
  144. package/docs/reference/agent-config.md +1 -1
  145. package/docs/reference/extensions.md +13 -0
  146. package/package.json +1 -9
  147. package/dist/docs/assets/chunks/@localSearchIndexroot.D5L-b34G.js +0 -1
  148. package/dist/docs/assets/chunks/channel.BbOGaJQP.js +0 -1
  149. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.Db58HCb1.js +0 -1
  150. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.Db58HCb1.js +0 -1
  151. package/dist/docs/assets/chunks/clone.Co4MBflG.js +0 -1
  152. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CMsKhrW0.js +0 -1
  153. package/src/artifacts.ts +0 -68
  154. package/src/bin/agent-serve.ts +0 -1854
  155. package/src/channels/bitbucket/api.ts +0 -891
  156. package/src/channels/bitbucket/binding.ts +0 -417
  157. package/src/channels/bitbucket/bitbucket-channel.ts +0 -162
  158. package/src/channels/bitbucket/bitbucket-webhook.ts +0 -519
  159. package/src/channels/bitbucket/checks.ts +0 -146
  160. package/src/channels/bitbucket/cursor-account.ts +0 -106
  161. package/src/channels/bitbucket/defaults.ts +0 -100
  162. package/src/channels/bitbucket/events.ts +0 -74
  163. package/src/channels/bitbucket/index.ts +0 -51
  164. package/src/channels/bitbucket/replay.ts +0 -421
  165. package/src/channels/bitbucket/state.ts +0 -59
  166. package/src/channels/bitbucket/types.ts +0 -261
  167. package/src/channels/change-monitors/change-monitors-channel.ts +0 -63
  168. package/src/channels/change-monitors/index.ts +0 -8
  169. package/src/channels/change-monitors/types.ts +0 -42
  170. package/src/channels/checks.ts +0 -185
  171. package/src/channels/deployments/deployments-channel.ts +0 -224
  172. package/src/channels/deployments/index.ts +0 -25
  173. package/src/channels/deployments/types.ts +0 -92
  174. package/src/channels/github/api.ts +0 -526
  175. package/src/channels/github/auth.ts +0 -293
  176. package/src/channels/github/binding.ts +0 -233
  177. package/src/channels/github/checks.ts +0 -145
  178. package/src/channels/github/cursor-account.ts +0 -303
  179. package/src/channels/github/defaults.ts +0 -168
  180. package/src/channels/github/events.ts +0 -67
  181. package/src/channels/github/forward.ts +0 -359
  182. package/src/channels/github/github-channel.ts +0 -2334
  183. package/src/channels/github/index.ts +0 -54
  184. package/src/channels/github/progress.ts +0 -365
  185. package/src/channels/github/replay.ts +0 -372
  186. package/src/channels/github/state.ts +0 -124
  187. package/src/channels/github/types.ts +0 -590
  188. package/src/channels/gitlab/api.ts +0 -329
  189. package/src/channels/gitlab/binding.ts +0 -231
  190. package/src/channels/gitlab/checks.ts +0 -228
  191. package/src/channels/gitlab/cursor-account.ts +0 -105
  192. package/src/channels/gitlab/defaults.ts +0 -80
  193. package/src/channels/gitlab/events.ts +0 -37
  194. package/src/channels/gitlab/gitlab-channel.ts +0 -160
  195. package/src/channels/gitlab/gitlab-webhook.ts +0 -509
  196. package/src/channels/gitlab/index.ts +0 -44
  197. package/src/channels/gitlab/replay.ts +0 -328
  198. package/src/channels/gitlab/state.ts +0 -55
  199. package/src/channels/gitlab/types.ts +0 -247
  200. package/src/channels/issues/index.ts +0 -9
  201. package/src/channels/issues/issues-channel.ts +0 -45
  202. package/src/channels/issues/types.ts +0 -38
  203. package/src/channels/origin/api.ts +0 -507
  204. package/src/channels/origin/binding.ts +0 -114
  205. package/src/channels/origin/checks.ts +0 -250
  206. package/src/channels/origin/cursor-account.ts +0 -58
  207. package/src/channels/origin/defaults.ts +0 -140
  208. package/src/channels/origin/events.ts +0 -35
  209. package/src/channels/origin/index.ts +0 -35
  210. package/src/channels/origin/origin-channel.ts +0 -815
  211. package/src/channels/origin/origin-webhook.ts +0 -296
  212. package/src/channels/origin/reactions.ts +0 -72
  213. package/src/channels/origin/state.ts +0 -54
  214. package/src/channels/origin/types.ts +0 -295
  215. package/src/channels/slack/agentic-delivery.ts +0 -634
  216. package/src/channels/slack/agentic-posted.ts +0 -135
  217. package/src/channels/slack/agentic-signals.ts +0 -231
  218. package/src/channels/slack/api.ts +0 -761
  219. package/src/channels/slack/approvals.ts +0 -279
  220. package/src/channels/slack/assistant.ts +0 -76
  221. package/src/channels/slack/attachments.ts +0 -596
  222. package/src/channels/slack/auth.ts +0 -127
  223. package/src/channels/slack/blocks.ts +0 -131
  224. package/src/channels/slack/bot-mentions.ts +0 -318
  225. package/src/channels/slack/channel-watch.ts +0 -314
  226. package/src/channels/slack/cli-project.ts +0 -249
  227. package/src/channels/slack/code-fences.ts +0 -171
  228. package/src/channels/slack/constants.ts +0 -38
  229. package/src/channels/slack/credentials.ts +0 -88
  230. package/src/channels/slack/defaults.ts +0 -714
  231. package/src/channels/slack/dispatch.ts +0 -1060
  232. package/src/channels/slack/doctor.ts +0 -338
  233. package/src/channels/slack/eval-directive.ts +0 -306
  234. package/src/channels/slack/event-id-dedupe.ts +0 -30
  235. package/src/channels/slack/external-policy.ts +0 -75
  236. package/src/channels/slack/footer.ts +0 -106
  237. package/src/channels/slack/inbound.ts +0 -416
  238. package/src/channels/slack/index.ts +0 -53
  239. package/src/channels/slack/init.ts +0 -169
  240. package/src/channels/slack/interactive.ts +0 -406
  241. package/src/channels/slack/links.ts +0 -23
  242. package/src/channels/slack/live-delivery.ts +0 -489
  243. package/src/channels/slack/log.ts +0 -70
  244. package/src/channels/slack/manifest.ts +0 -240
  245. package/src/channels/slack/message-body.ts +0 -82
  246. package/src/channels/slack/nudge-carrier.ts +0 -224
  247. package/src/channels/slack/nudge-store.ts +0 -334
  248. package/src/channels/slack/paint-start.ts +0 -93
  249. package/src/channels/slack/placeholder.ts +0 -358
  250. package/src/channels/slack/pr-ref.ts +0 -74
  251. package/src/channels/slack/progress-delivery.ts +0 -138
  252. package/src/channels/slack/reasoning-card.ts +0 -125
  253. package/src/channels/slack/reasoning-chip.ts +0 -41
  254. package/src/channels/slack/recording-sink.ts +0 -167
  255. package/src/channels/slack/redact.ts +0 -6
  256. package/src/channels/slack/reply-options.ts +0 -279
  257. package/src/channels/slack/setup.ts +0 -125
  258. package/src/channels/slack/slack-channel.ts +0 -744
  259. package/src/channels/slack/socket-mode.ts +0 -140
  260. package/src/channels/slack/stop.ts +0 -25
  261. package/src/channels/slack/stream.ts +0 -66
  262. package/src/channels/slack/thread-context.ts +0 -485
  263. package/src/channels/slack/tool-approval-events.ts +0 -233
  264. package/src/channels/slack/tool-cards.ts +0 -198
  265. package/src/channels/slack/tool-chip.ts +0 -34
  266. package/src/channels/slack/types.ts +0 -787
  267. package/src/channels/slack/upload.ts +0 -137
  268. package/src/channels.ts +0 -543
  269. package/src/connections.ts +0 -153
  270. package/src/dashboard-session-url.ts +0 -30
  271. package/src/evals/assertions.ts +0 -688
  272. package/src/evals/expect.ts +0 -250
  273. package/src/evals/judge.ts +0 -329
  274. package/src/evals/loaders.ts +0 -111
  275. package/src/evals/matchers.ts +0 -223
  276. package/src/evals/reporters.ts +0 -257
  277. package/src/evals/results.ts +0 -130
  278. package/src/evals/run-facts.ts +0 -306
  279. package/src/evals.ts +0 -709
  280. package/src/extensions/cursor-cloud-agents/extension.ts +0 -131
  281. package/src/extensions/cursor-cloud-agents/lib/backend.ts +0 -386
  282. package/src/extensions/cursor-cloud-agents/lib/channels.ts +0 -11
  283. package/src/extensions/cursor-cloud-agents/lib/fleet.ts +0 -98
  284. package/src/extensions/cursor-cloud-agents/lib/inputs.ts +0 -5
  285. package/src/extensions/cursor-cloud-agents/lib/repos.ts +0 -80
  286. package/src/extensions/cursor-cloud-agents/lib/transcript.ts +0 -19
  287. package/src/extensions/cursor-cloud-agents/skills/handoff.md +0 -37
  288. package/src/extensions/cursor-cloud-agents/tools/cancel.ts +0 -21
  289. package/src/extensions/cursor-cloud-agents/tools/dump.ts +0 -47
  290. package/src/extensions/cursor-cloud-agents/tools/get.ts +0 -18
  291. package/src/extensions/cursor-cloud-agents/tools/launch.ts +0 -62
  292. package/src/extensions/cursor-cloud-agents/tools/list.ts +0 -31
  293. package/src/extensions/cursor-cloud-agents/tools/reply.ts +0 -36
  294. package/src/extensions/cursor-grokbot-agents/extension.ts +0 -132
  295. package/src/extensions/cursor-grokbot-agents/lib/agents.ts +0 -42
  296. package/src/extensions/cursor-grokbot-agents/lib/backend.ts +0 -208
  297. package/src/extensions/cursor-grokbot-agents/lib/contacts.ts +0 -74
  298. package/src/extensions/cursor-grokbot-agents/lib/conversation.ts +0 -260
  299. package/src/extensions/cursor-grokbot-agents/lib/inputs.ts +0 -21
  300. package/src/extensions/cursor-grokbot-agents/lib/lock.ts +0 -26
  301. package/src/extensions/cursor-grokbot-agents/skills/consult.md +0 -40
  302. package/src/extensions/cursor-grokbot-agents/tools/ask.ts +0 -32
  303. package/src/extensions/cursor-grokbot-agents/tools/check.ts +0 -25
  304. package/src/extensions/cursor-grokbot-agents/tools/interrupt.ts +0 -17
  305. package/src/extensions/cursor-grokbot-agents/tools/list.ts +0 -26
  306. package/src/extensions/improve/extension.ts +0 -69
  307. package/src/extensions/improve/skills/yourself.ts +0 -50
  308. package/src/extensions/jev/extension.ts +0 -95
  309. package/src/extensions/jev/lib/evaluate.ts +0 -266
  310. package/src/extensions/jev/skills/gated-write.md +0 -25
  311. package/src/extensions/jev/skills/questions.md +0 -33
  312. package/src/extensions/jev/tools/evaluate.ts +0 -82
  313. package/src/extensions/slack-communication/evals/harness.ts +0 -705
  314. package/src/extensions/slack-communication/evals/slack-communication.eval.ts +0 -830
  315. package/src/extensions/slack-communication/evals/slack-placeholder.eval.ts +0 -172
  316. package/src/extensions/slack-communication/extension.ts +0 -44
  317. package/src/extensions/slack-communication/skills/slack-voice.ts +0 -56
  318. package/src/extensions/slack-communication/tools/delete-message.ts +0 -33
  319. package/src/extensions/slack-communication/tools/edit-message.ts +0 -42
  320. package/src/extensions/slack-communication/tools/post-card.ts +0 -28
  321. package/src/extensions/slack-communication/tools/post-file.ts +0 -82
  322. package/src/extensions/slack-communication/tools/post-update.ts +0 -40
  323. package/src/extensions/slack-communication/tools/react.ts +0 -30
  324. package/src/extensions/slack-communication/tools/set-status.ts +0 -21
  325. package/src/extensions.ts +0 -179
  326. package/src/files-backends/agent-store-presigned-url.ts +0 -11
  327. package/src/files-backends/cursor-hosted.ts +0 -1212
  328. package/src/files-backends/local-fs.ts +0 -163
  329. package/src/files.ts +0 -293
  330. package/src/filesystem.ts +0 -2005
  331. package/src/hooks.ts +0 -30
  332. package/src/index.ts +0 -259
  333. package/src/internal/advertise-tools.ts +0 -370
  334. package/src/internal/approval-gate.ts +0 -103
  335. package/src/internal/artifacts-store.ts +0 -836
  336. package/src/internal/as-of.ts +0 -257
  337. package/src/internal/auth.ts +0 -46
  338. package/src/internal/authored-alias-hooks.ts +0 -138
  339. package/src/internal/authored-loaders.ts +0 -188
  340. package/src/internal/bounded-int.ts +0 -24
  341. package/src/internal/bounded.ts +0 -25
  342. package/src/internal/brand.ts +0 -28
  343. package/src/internal/browser-origin-guard.ts +0 -67
  344. package/src/internal/builtin-tools/artifacts.ts +0 -62
  345. package/src/internal/builtin-tools/index.ts +0 -53
  346. package/src/internal/builtin-tools/reminders.ts +0 -145
  347. package/src/internal/channel-dispatch.ts +0 -129
  348. package/src/internal/channel-route-dispatch.ts +0 -58
  349. package/src/internal/channel-state.ts +0 -83
  350. package/src/internal/chat-client.ts +0 -594
  351. package/src/internal/cli-ax.ts +0 -2318
  352. package/src/internal/cli-bitbucket.ts +0 -438
  353. package/src/internal/cli-convert-automation.ts +0 -335
  354. package/src/internal/cli-cursor.ts +0 -282
  355. package/src/internal/cli-deploy.ts +0 -2519
  356. package/src/internal/cli-docs.ts +0 -192
  357. package/src/internal/cli-github.ts +0 -806
  358. package/src/internal/cli-gitlab.ts +0 -452
  359. package/src/internal/cli-images.ts +0 -672
  360. package/src/internal/cli-manifest.ts +0 -56
  361. package/src/internal/cli-mcp-oauth.ts +0 -396
  362. package/src/internal/cli-mcp.ts +0 -808
  363. package/src/internal/cli-skills.ts +0 -98
  364. package/src/internal/cli-slack-manual.ts +0 -418
  365. package/src/internal/cli-slack.ts +0 -874
  366. package/src/internal/cloud-merge.ts +0 -51
  367. package/src/internal/cloud-turn-cost.ts +0 -159
  368. package/src/internal/connection-env.ts +0 -106
  369. package/src/internal/continuation-channel.ts +0 -233
  370. package/src/internal/continuation-identity.ts +0 -935
  371. package/src/internal/conversation-mirror.ts +0 -423
  372. package/src/internal/convert-automation/convert-workflow.ts +0 -1377
  373. package/src/internal/convert-automation/slug.ts +0 -29
  374. package/src/internal/convert-automation/types.ts +0 -212
  375. package/src/internal/cron.ts +0 -76
  376. package/src/internal/cursor/account-mcp.ts +0 -692
  377. package/src/internal/cursor/backend-client.ts +0 -984
  378. package/src/internal/cursor/bound-secret-file.ts +0 -101
  379. package/src/internal/cursor/credentials.ts +0 -560
  380. package/src/internal/cursor/cursor-api-transport.ts +0 -65
  381. package/src/internal/cursor/github-credentials.ts +0 -228
  382. package/src/internal/cursor/hosted-bound-secrets.ts +0 -71
  383. package/src/internal/cursor/hosted-deployment-secrets.ts +0 -100
  384. package/src/internal/cursor/hosted-store-secrets.ts +0 -152
  385. package/src/internal/cursor/session-token.ts +0 -111
  386. package/src/internal/cursor/store-api-client.ts +0 -323
  387. package/src/internal/cursor-account-mcp-auth.ts +0 -303
  388. package/src/internal/cursor-agent-template.ts +0 -50
  389. package/src/internal/cursor-deployment-relay.ts +0 -315
  390. package/src/internal/cursor-event-relay.ts +0 -675
  391. package/src/internal/cursor-relay-core.ts +0 -563
  392. package/src/internal/deferred-channel-session.ts +0 -61
  393. package/src/internal/deploy-client.ts +0 -1574
  394. package/src/internal/deploy-manifest.ts +0 -361
  395. package/src/internal/deploy-source.ts +0 -144
  396. package/src/internal/describe-error.ts +0 -32
  397. package/src/internal/dev-mode.ts +0 -20
  398. package/src/internal/discovery/agent-config.ts +0 -288
  399. package/src/internal/discovery/agent.ts +0 -381
  400. package/src/internal/discovery/channels.ts +0 -100
  401. package/src/internal/discovery/connections.ts +0 -516
  402. package/src/internal/discovery/context.ts +0 -38
  403. package/src/internal/discovery/cursor-plugin.ts +0 -628
  404. package/src/internal/discovery/extension-overlay.ts +0 -247
  405. package/src/internal/discovery/extensions.ts +0 -490
  406. package/src/internal/discovery/fs.ts +0 -103
  407. package/src/internal/discovery/hooks.ts +0 -63
  408. package/src/internal/discovery/info.ts +0 -174
  409. package/src/internal/discovery/instructions.ts +0 -98
  410. package/src/internal/discovery/modules.ts +0 -107
  411. package/src/internal/discovery/names.ts +0 -45
  412. package/src/internal/discovery/project.ts +0 -319
  413. package/src/internal/discovery/reminders.ts +0 -65
  414. package/src/internal/discovery/runtime.ts +0 -158
  415. package/src/internal/discovery/schedules.ts +0 -120
  416. package/src/internal/discovery/seed-files.ts +0 -34
  417. package/src/internal/discovery/services.ts +0 -151
  418. package/src/internal/discovery/skills.ts +0 -174
  419. package/src/internal/discovery/subagent.ts +0 -138
  420. package/src/internal/discovery/tools.ts +0 -170
  421. package/src/internal/discovery.ts +0 -27
  422. package/src/internal/distribution.ts +0 -126
  423. package/src/internal/docs-site.ts +0 -248
  424. package/src/internal/duration.ts +0 -37
  425. package/src/internal/env-file.ts +0 -83
  426. package/src/internal/eval-judge-model.ts +0 -142
  427. package/src/internal/eval-run-store.ts +0 -585
  428. package/src/internal/eval-runner.ts +0 -823
  429. package/src/internal/evals-client.ts +0 -475
  430. package/src/internal/event-log.ts +0 -342
  431. package/src/internal/event-mapper.ts +0 -811
  432. package/src/internal/expected-delivery-failure.ts +0 -34
  433. package/src/internal/factory-resource-channel.ts +0 -103
  434. package/src/internal/filesystem/errors.ts +0 -207
  435. package/src/internal/filesystem/format.ts +0 -253
  436. package/src/internal/filesystem/merge.ts +0 -192
  437. package/src/internal/filesystem/nearest.ts +0 -112
  438. package/src/internal/filesystem/scratch.ts +0 -239
  439. package/src/internal/filesystem/sdk-schemas.ts +0 -204
  440. package/src/internal/filesystem/tools.ts +0 -804
  441. package/src/internal/filesystem/walk.ts +0 -366
  442. package/src/internal/fixtures/units-server.ts +0 -52
  443. package/src/internal/framework-file-storage.ts +0 -357
  444. package/src/internal/framework-storage-selection.ts +0 -156
  445. package/src/internal/frontmatter.ts +0 -108
  446. package/src/internal/github-fanout.ts +0 -144
  447. package/src/internal/guest-network.ts +0 -391
  448. package/src/internal/handleAgentServeTrigger.ts +0 -286
  449. package/src/internal/host-files.ts +0 -341
  450. package/src/internal/host-kv.ts +0 -81
  451. package/src/internal/host-otel.ts +0 -231
  452. package/src/internal/host-platforms.ts +0 -170
  453. package/src/internal/hosted-admission-adapter.ts +0 -47
  454. package/src/internal/hosted-admission-context.ts +0 -37
  455. package/src/internal/hosted-catch-protocol.ts +0 -112
  456. package/src/internal/hosted-catch.ts +0 -179
  457. package/src/internal/hosted-delivery-protocol.ts +0 -824
  458. package/src/internal/hosted-delivery.ts +0 -1410
  459. package/src/internal/hosted-execution-diag.ts +0 -291
  460. package/src/internal/hosted-execution-flush.ts +0 -136
  461. package/src/internal/hosted-execution-latency.ts +0 -301
  462. package/src/internal/hosted-managed-automation.ts +0 -116
  463. package/src/internal/hosting.ts +0 -114
  464. package/src/internal/http-channel-define.ts +0 -25
  465. package/src/internal/http-channel.ts +0 -669
  466. package/src/internal/http-control-plane-session.ts +0 -110
  467. package/src/internal/ids.ts +0 -39
  468. package/src/internal/incoming-headers-to-fetch.ts +0 -45
  469. package/src/internal/init-interview.ts +0 -360
  470. package/src/internal/init-project.ts +0 -711
  471. package/src/internal/init-scaffold.ts +0 -182
  472. package/src/internal/inline-stdio-define-stub.ts +0 -4
  473. package/src/internal/inline-stdio-mcp.ts +0 -230
  474. package/src/internal/install-cursor-skills.ts +0 -604
  475. package/src/internal/is-record.ts +0 -3
  476. package/src/internal/json-schema.ts +0 -95
  477. package/src/internal/legacy-command-guard.ts +0 -66
  478. package/src/internal/listen-port.ts +0 -85
  479. package/src/internal/local-control-plane.ts +0 -442
  480. package/src/internal/local-env.ts +0 -59
  481. package/src/internal/local-ingress.ts +0 -90
  482. package/src/internal/log-ring.ts +0 -59
  483. package/src/internal/logs-client.ts +0 -477
  484. package/src/internal/map-pool.ts +0 -39
  485. package/src/internal/mcp-endpoint.ts +0 -747
  486. package/src/internal/mcp-host.ts +0 -509
  487. package/src/internal/mcp-oauth-on-demand.ts +0 -337
  488. package/src/internal/mcp-oauth-persist.ts +0 -118
  489. package/src/internal/mcp-oauth.ts +0 -549
  490. package/src/internal/net.ts +0 -29
  491. package/src/internal/open-browser.ts +0 -41
  492. package/src/internal/otel/attrs.ts +0 -16
  493. package/src/internal/otel/collector.ts +0 -937
  494. package/src/internal/otel/config.ts +0 -287
  495. package/src/internal/otel/http.ts +0 -122
  496. package/src/internal/otel/mcp.ts +0 -56
  497. package/src/internal/otel/propagation.ts +0 -17
  498. package/src/internal/otel/runtime.ts +0 -441
  499. package/src/internal/package-inline-stdio.ts +0 -114
  500. package/src/internal/peer-connections.ts +0 -96
  501. package/src/internal/platform-schedule-sync.ts +0 -75
  502. package/src/internal/platform-timers.ts +0 -365
  503. package/src/internal/playground/static.ts +0 -278
  504. package/src/internal/playground/toolchain.ts +0 -270
  505. package/src/internal/playground-proxy.ts +0 -564
  506. package/src/internal/process-memory-telemetry.ts +0 -334
  507. package/src/internal/project-layout.ts +0 -31
  508. package/src/internal/prompt-context.ts +0 -116
  509. package/src/internal/reminder-control-plane-protocol.ts +0 -482
  510. package/src/internal/reminder-runner.ts +0 -1559
  511. package/src/internal/reminder-schedule.ts +0 -104
  512. package/src/internal/reminder-store.ts +0 -176
  513. package/src/internal/request-error-status.ts +0 -95
  514. package/src/internal/request-headers.ts +0 -21
  515. package/src/internal/resolve-prod-target.ts +0 -334
  516. package/src/internal/resolved-connections.ts +0 -190
  517. package/src/internal/router.ts +0 -143
  518. package/src/internal/run-client.ts +0 -354
  519. package/src/internal/runtime-dispatch-runner.ts +0 -50
  520. package/src/internal/sanitize-mcp-tool-name.ts +0 -31
  521. package/src/internal/schedule-runner.ts +0 -154
  522. package/src/internal/scm/pr-url.ts +0 -245
  523. package/src/internal/sdk-runner.ts +0 -959
  524. package/src/internal/serve-restart.ts +0 -113
  525. package/src/internal/server.ts +0 -3147
  526. package/src/internal/session-cost.ts +0 -208
  527. package/src/internal/session-engine.ts +0 -5622
  528. package/src/internal/session-links.ts +0 -84
  529. package/src/internal/session-run-log.ts +0 -535
  530. package/src/internal/session-store.ts +0 -307
  531. package/src/internal/session-urls.ts +0 -32
  532. package/src/internal/sessions-client.ts +0 -239
  533. package/src/internal/skill-catalog.ts +0 -66
  534. package/src/internal/skills-sync.ts +0 -55
  535. package/src/internal/slack-provision-client.ts +0 -544
  536. package/src/internal/slack-thread-key.ts +0 -11
  537. package/src/internal/sleep.ts +0 -3
  538. package/src/internal/start-log-store.ts +0 -164
  539. package/src/internal/steering.ts +0 -17
  540. package/src/internal/storage-coordinator.ts +0 -800
  541. package/src/internal/storage-roles.ts +0 -86
  542. package/src/internal/storage.ts +0 -1
  543. package/src/internal/store-api-protocol.ts +0 -205
  544. package/src/internal/store-mirror.ts +0 -73
  545. package/src/internal/stream-progress.ts +0 -294
  546. package/src/internal/terminal-style.ts +0 -133
  547. package/src/internal/testing/factory-resource-fixtures.ts +0 -84
  548. package/src/internal/testing/scm-continuation-fixtures.ts +0 -285
  549. package/src/internal/tool-catalog.ts +0 -104
  550. package/src/internal/tool-policy.ts +0 -222
  551. package/src/internal/tool-result.ts +0 -50
  552. package/src/internal/trajectory.ts +0 -418
  553. package/src/internal/turn-cost.ts +0 -136
  554. package/src/internal/turn-governor.ts +0 -145
  555. package/src/internal/turn-lease-store.ts +0 -144
  556. package/src/internal/turn-outbox.ts +0 -77
  557. package/src/internal/update-check.ts +0 -517
  558. package/src/internal/v2-hosting.ts +0 -52
  559. package/src/internal/workspace-sync.ts +0 -114
  560. package/src/internal/workspace.ts +0 -505
  561. package/src/kv/memory-kv.ts +0 -94
  562. package/src/kv.ts +0 -18
  563. package/src/memory.ts +0 -487
  564. package/src/otel-public.ts +0 -24
  565. package/src/otel.ts +0 -320
  566. package/src/prompt.ts +0 -80
  567. package/src/reminders.ts +0 -65
  568. package/src/schedules.ts +0 -31
  569. package/src/skills.ts +0 -27
  570. package/src/storage-backends/cursor-hosted-v2.ts +0 -204
  571. package/src/storage-backends/cursor-hosted.ts +0 -388
  572. package/src/storage-backends/file-kv.ts +0 -276
  573. package/src/storage-protocol.ts +0 -261
  574. package/src/storage.ts +0 -408
  575. package/src/tools.ts +0 -166
  576. package/src/types.ts +0 -3776
  577. /package/dist/docs/assets/{reference_agent-config.md.DzGI777n.lean.js → reference_agent-config.md.Cj5uBKp4.lean.js} +0 -0
@@ -1,1377 +0,0 @@
1
- /**
2
- * Pure Workflow → BDK project converter.
3
- *
4
- * Maps an exported Cursor automation (proto3 workflow JSON + enriched MCP
5
- * server list) to project files, warnings, and setup steps. No I/O, no
6
- * clock — `exportedAt` comes from the caller.
7
- */
8
-
9
- import { envPrefixFromName } from "../../channels/slack/credentials.js";
10
- import { CLI_COMMAND_NAME, PACKAGE_NAME } from "../distribution.js";
11
- import { MEMORY_TS } from "../init-scaffold.js";
12
- import { CANONICAL_PROJECT_ROOT, projectPath, type ProjectRoot } from "../project-layout.js";
13
- import { SlugRegistry, slugify } from "./slug.js";
14
- import type {
15
- ActionJson,
16
- ConvertedFile,
17
- ConvertedMcpConnection,
18
- ConvertMcpServer,
19
- ConvertWorkflowInput,
20
- ConvertWorkflowResult,
21
- GitTriggerJson,
22
- PromptJson,
23
- SlackActionJson,
24
- SlackChannelsTriggerJson,
25
- SlackWatchTriggerJson,
26
- TriggerJson,
27
- WorkflowJson,
28
- } from "./types.js";
29
-
30
- /** Mutable result the emitters append to (also the returned value). */
31
- interface ConvertOutput {
32
- projectRoot: ProjectRoot;
33
- files: ConvertedFile[];
34
- warnings: string[];
35
- setupSteps: string[];
36
- mcpConnections: ConvertedMcpConnection[];
37
- }
38
-
39
- export function convertWorkflow(input: ConvertWorkflowInput): ConvertWorkflowResult {
40
- const { automation, workflow, mcpServers, exportedAt } = input;
41
- const projectRoot = input.projectRoot ?? CANONICAL_PROJECT_ROOT;
42
- const out: ConvertOutput = {
43
- projectRoot,
44
- files: [],
45
- warnings: [],
46
- setupSteps: [],
47
- mcpConnections: [],
48
- };
49
-
50
- if (workflow.managedConfig) {
51
- out.warnings.push(
52
- "This is a Cursor-managed automation: its behavior lives in managed configuration that is not exported. The exported project will NOT reproduce what this automation does.",
53
- );
54
- }
55
-
56
- // Pin the model the prompt actually ran with; the workflow-level model is
57
- // only a fallback. Silently swapping models changes cost and behavior.
58
- const promptModel = workflow.prompts?.[0]?.model;
59
- const pinnedModel =
60
- promptModel !== undefined && promptModel !== "" ? promptModel : workflow.model;
61
- if (
62
- promptModel !== undefined &&
63
- promptModel !== "" &&
64
- workflow.model !== undefined &&
65
- workflow.model !== "" &&
66
- promptModel !== workflow.model
67
- ) {
68
- out.warnings.push(
69
- `The automation ran its prompt with model ${JSON.stringify(promptModel)} (pinned in ${projectPath(projectRoot, "agent.ts")}); its workflow-level default was ${JSON.stringify(workflow.model)}.`,
70
- );
71
- }
72
-
73
- out.files.push({
74
- path: projectPath(projectRoot, "instructions.md"),
75
- contents: renderInstructions(automation, workflow, exportedAt, out.warnings, projectRoot),
76
- });
77
- out.files.push({
78
- path: projectPath(projectRoot, "agent.ts"),
79
- contents: renderAgentTs(automation, workflow, pinnedModel),
80
- });
81
-
82
- const triggers = workflow.triggers ?? [];
83
- const scheduleSlugs = new SlugRegistry();
84
- const channelSlugs = new SlugRegistry();
85
-
86
- const gitTriggers: TriggerJson[] = [];
87
- const slackTriggers: TriggerJson[] = [];
88
- const boilerplate: Array<{ trigger: TriggerJson; kind: BoilerplateKind }> = [];
89
-
90
- for (const trigger of triggers) {
91
- if (trigger.cron !== undefined) {
92
- emitSchedule(automation, trigger, scheduleSlugs, out);
93
- } else if (trigger.git !== undefined) {
94
- gitTriggers.push(trigger);
95
- } else if (isSlackTrigger(trigger)) {
96
- slackTriggers.push(trigger);
97
- } else {
98
- const kind = boilerplateKind(trigger);
99
- if (kind === undefined) {
100
- out.warnings.push(
101
- `Unrecognized trigger (keys: ${Object.keys(trigger).join(", ") || "none"}) was skipped — export it manually.`,
102
- );
103
- } else {
104
- boilerplate.push({ trigger, kind });
105
- }
106
- }
107
- }
108
-
109
- if (gitTriggers.length > 0) {
110
- emitGithubChannel(automation, workflow, gitTriggers, channelSlugs, out);
111
- }
112
- if (slackTriggers.length > 0) {
113
- emitSlackChannel(automation, slackTriggers, channelSlugs, out);
114
- }
115
- for (const { trigger, kind } of boilerplate) {
116
- emitBoilerplateChannel(automation, trigger, kind, channelSlugs, out);
117
- }
118
-
119
- emitMcpConnections(automation, mcpServers, out);
120
- const hasSlackAction = (workflow.actions ?? []).some(
121
- action => action.slack !== undefined || action.readSlack !== undefined,
122
- );
123
- const hasSlackConnection = hasSlackAction && emitSlackAccountConnection(automation, out);
124
-
125
- if (workflow.memoryEnabled === true) {
126
- out.files.push({
127
- path: projectPath(projectRoot, "hooks/memory.ts"),
128
- contents: MEMORY_TS,
129
- });
130
- }
131
-
132
- out.setupSteps.push(
133
- ...behaviorSteps(
134
- workflow,
135
- out.warnings,
136
- {
137
- hasSlackChannel: slackTriggers.length > 0,
138
- hasSlackConnection,
139
- },
140
- projectRoot,
141
- ),
142
- );
143
- out.setupSteps.push(
144
- `Disable the source automation before deploying this agent so both do not fire on the same events: ${automation.webUrl}${automation.enabled ? "" : " (it is currently disabled — keep it that way)"}`,
145
- );
146
-
147
- return out;
148
- }
149
-
150
- // ============================================================================
151
- // instructions.md
152
- // ============================================================================
153
-
154
- function renderInstructions(
155
- automation: ConvertWorkflowInput["automation"],
156
- workflow: WorkflowJson,
157
- exportedAt: string,
158
- warnings: string[],
159
- projectRoot: ProjectRoot,
160
- ): string {
161
- const header = [
162
- "<!--",
163
- ` Exported from Cursor automation "${commentSafe(automation.name)}"`,
164
- ` uuid: ${commentSafe(automation.uuid)}`,
165
- ` source: ${commentSafe(automation.webUrl)}`,
166
- ` exported: ${commentSafe(exportedAt)}`,
167
- "-->",
168
- ].join("\n");
169
-
170
- const prompts = workflow.prompts ?? [];
171
- if (prompts.length === 0) {
172
- warnings.push(
173
- `The source automation has no prompts — ${projectPath(projectRoot, "instructions.md")} contains a placeholder.`,
174
- );
175
- return `${header}\n\nTODO: the source automation had no prompt text. Write this agent's instructions.\n`;
176
- }
177
- // Prompt chaining was internal-only and never shipped; export the first
178
- // prompt and warn if a legacy row somehow carries more.
179
- if (prompts.length > 1) {
180
- warnings.push(
181
- `The source automation has ${prompts.length} prompts (a legacy internal feature); only the first was exported to ${projectPath(projectRoot, "instructions.md")}.`,
182
- );
183
- }
184
- const prompt = prompts[0];
185
- const meta = promptMeta(prompt);
186
- const heading = meta === "" ? "" : `<!-- ${meta} -->\n\n`;
187
- return `${header}\n\n${heading}${sanitizePromptText(prompt.prompt ?? "", warnings, projectRoot)}\n`;
188
- }
189
-
190
- /**
191
- * Strip Cursor-backend prompt markup that means nothing in BDK:
192
- * `@[label](action:...)` action tokens become their label text, and
193
- * unresolvable `%%INLINECODE<n>%%` storage placeholders are flagged (their
194
- * content is not in the export).
195
- */
196
- function sanitizePromptText(text: string, warnings: string[], projectRoot: ProjectRoot): string {
197
- let sanitized = text;
198
- if (/@\[[^\]]*\]\(action:[^)]*\)/.test(sanitized)) {
199
- warnings.push(
200
- "The prompt referenced Cursor automation actions (@[...](action:...)); the tokens were replaced with their label text. Wire the equivalent channel/connection (see setup steps) to restore the behavior.",
201
- );
202
- sanitized = sanitized.replace(/@\[([^\]]*)\]\(action:[^)]*\)/g, "$1");
203
- }
204
- if (/%%INLINECODE\d+%%/.test(sanitized)) {
205
- warnings.push(
206
- `${projectPath(projectRoot, "instructions.md")} contains unresolved %%INLINECODE<n>%% placeholders from Cursor's prompt storage — open the automation in the dashboard and paste the original snippet back in.`,
207
- );
208
- }
209
- return sanitized;
210
- }
211
-
212
- const EFFORT_LABELS: Record<string, string> = {
213
- PROMPT_EFFORT_LEVEL_STANDARD: "standard",
214
- 1: "standard",
215
- PROMPT_EFFORT_LEVEL_HARD: "hard",
216
- 10: "hard",
217
- };
218
-
219
- function promptMeta(prompt: PromptJson): string {
220
- const parts: string[] = [];
221
- const effort = EFFORT_LABELS[String(prompt.effortLevel)];
222
- if (effort !== undefined && effort !== "standard") {
223
- parts.push(`effort: ${effort}`);
224
- }
225
- if (prompt.model !== undefined && prompt.model !== "") {
226
- parts.push(`model: ${prompt.model}`);
227
- }
228
- return parts.join("; ");
229
- }
230
-
231
- // ============================================================================
232
- // agent.ts
233
- // ============================================================================
234
-
235
- function renderAgentTs(
236
- automation: ConvertWorkflowInput["automation"],
237
- workflow: WorkflowJson,
238
- pinnedModel: string | undefined,
239
- ): string {
240
- const comments: string[] = [
241
- `// Exported from Cursor automation "${commentSafe(automation.name)}"`,
242
- `// ${commentSafe(automation.webUrl)}`,
243
- ];
244
- if (workflow.gitConfig !== undefined) {
245
- comments.push("// git_config (repo/branch the automation worked in):");
246
- comments.push(...jsonComment(workflow.gitConfig));
247
- }
248
- if (workflow.agentOptions !== undefined) {
249
- comments.push("// agent_options:");
250
- comments.push(...jsonComment(workflow.agentOptions));
251
- }
252
- const body =
253
- pinnedModel !== undefined && pinnedModel !== ""
254
- ? `export default defineAgent({\n model: ${JSON.stringify(pinnedModel)},\n});`
255
- : "export default defineAgent({\n // The source automation used Cursor's default model.\n});";
256
- return `import { defineAgent } from "${PACKAGE_NAME}";\n\n${comments.join("\n")}\n${body}\n`;
257
- }
258
-
259
- // ============================================================================
260
- // Schedules (cron triggers)
261
- // ============================================================================
262
-
263
- function emitSchedule(
264
- automation: ConvertWorkflowInput["automation"],
265
- trigger: TriggerJson,
266
- slugs: SlugRegistry,
267
- out: ConvertOutput,
268
- ): void {
269
- const cron = trigger.cron?.cron;
270
- if (cron === undefined || cron === "") {
271
- out.warnings.push("A cron trigger had no cron expression and was skipped.");
272
- return;
273
- }
274
- const slug = slugs.claim(slugify(automation.name, "schedule"));
275
- const path = projectPath(out.projectRoot, `schedules/${slug}.ts`);
276
- const lines = [
277
- `import { defineSchedule } from "${PACKAGE_NAME}/schedules";`,
278
- "",
279
- `// Exported from Cursor automation "${commentSafe(automation.name)}" cron trigger.`,
280
- "// Cron is evaluated in UTC in both systems.",
281
- "export default defineSchedule({",
282
- ` cron: ${JSON.stringify(cron)},`,
283
- " markdown:",
284
- ` "Scheduled run: follow the instructions in ${projectPath(out.projectRoot, "instructions.md")}.",`,
285
- "});",
286
- "",
287
- ];
288
- out.files.push({ path, contents: lines.join("\n") });
289
- }
290
-
291
- // ============================================================================
292
- // GitHub channel (git triggers)
293
- // ============================================================================
294
-
295
- const GIT_EVENT_WEBHOOKS: Record<string, string[]> = {
296
- pullRequest: ["pull_request"],
297
- push: ["push"],
298
- ciCompleted: ["check_suite"],
299
- issueLabeled: ["issues"],
300
- label: [],
301
- issueComment: ["issue_comment"],
302
- pullRequestReviewComment: ["pull_request_review_comment"],
303
- pullRequestReview: ["pull_request_review"],
304
- reviewThread: ["pull_request_review_thread"],
305
- workflowRun: ["workflow_run"],
306
- pullRequestReviewRequested: ["pull_request"],
307
- issueAssigned: ["issues"],
308
- };
309
-
310
- /**
311
- * `pr.action`-based dispatch condition for one pull_request trigger,
312
- * mirroring the backend matcher (`webhookHandler.ts`): OPENED is non-draft
313
- * opened-or-ready-for-review; UNSPECIFIED is the legacy opened-or-synchronize
314
- * gated by `only_once`; `ignore_draft_prs` applies per trigger.
315
- */
316
- function prConditionFor(
317
- prAction: string,
318
- eventObject: Record<string, unknown>,
319
- ): string | undefined {
320
- const ignoreDrafts = eventObject.ignoreDraftPrs === true;
321
- switch (prAction) {
322
- case "GIT_PULL_REQUEST_ACTION_UNSPECIFIED": {
323
- const base =
324
- eventObject.onlyOnce === true
325
- ? 'pr.action === "opened"'
326
- : '(pr.action === "opened" || pr.action === "synchronize")';
327
- return `(${base}${ignoreDrafts ? " && !pr.draft" : ""})`;
328
- }
329
- case "GIT_PULL_REQUEST_ACTION_OPENED":
330
- return '((pr.action === "opened" || pr.action === "ready_for_review") && !pr.draft)';
331
- case "GIT_PULL_REQUEST_ACTION_PUSHED":
332
- return ignoreDrafts
333
- ? '(pr.action === "synchronize" && !pr.draft)'
334
- : '(pr.action === "synchronize")';
335
- case "GIT_PULL_REQUEST_ACTION_MERGED":
336
- return '(pr.action === "closed" && pr.merged)';
337
- case "GIT_PULL_REQUEST_ACTION_DRAFT_OPENED":
338
- return '(pr.action === "opened" && pr.draft)';
339
- case "GIT_PULL_REQUEST_ACTION_LABELED":
340
- return '(pr.action === "labeled")';
341
- case "GIT_PULL_REQUEST_ACTION_UNLABELED":
342
- return '(pr.action === "unlabeled")';
343
- default:
344
- return undefined;
345
- }
346
- }
347
- const PR_ACTION_NUMBERS: Record<string, string> = {
348
- 0: "GIT_PULL_REQUEST_ACTION_UNSPECIFIED",
349
- 1: "GIT_PULL_REQUEST_ACTION_OPENED",
350
- 2: "GIT_PULL_REQUEST_ACTION_PUSHED",
351
- 3: "GIT_PULL_REQUEST_ACTION_MERGED",
352
- 4: "GIT_PULL_REQUEST_ACTION_COMMENTED",
353
- 5: "GIT_PULL_REQUEST_ACTION_DRAFT_OPENED",
354
- 6: "GIT_PULL_REQUEST_ACTION_LABELED",
355
- 7: "GIT_PULL_REQUEST_ACTION_UNLABELED",
356
- };
357
-
358
- function emitGithubChannel(
359
- automation: ConvertWorkflowInput["automation"],
360
- workflow: WorkflowJson,
361
- triggers: TriggerJson[],
362
- slugs: SlugRegistry,
363
- out: ConvertOutput,
364
- ): void {
365
- const slug = slugs.claim("github");
366
- const path = projectPath(out.projectRoot, `channels/${slug}.ts`);
367
- const webhookEvents: string[] = [];
368
- const repos: string[] = [];
369
- let hasOrgs = false;
370
-
371
- // Mechanical filters recovered from the triggers, turned into real hooks
372
- // below so the exported channel dispatches on the same events the
373
- // automation did instead of shipping inert.
374
- const prConditions: string[] = [];
375
- let hasCommentedAction = false;
376
- let hasIssueCommentTrigger = false;
377
- // COMMENTED / review-comment triggers whose backend gates (PR-author
378
- // provenance via user_allowlist, commenter_allowlist, comment_contains)
379
- // cannot be reproduced by an onComment hook.
380
- let commentGatesLost = false;
381
- let hasUnknownPrAction = false;
382
- const pushBranches: string[] = [];
383
- let hasCheckSuite = false;
384
- let hasWorkflowRun = false;
385
- const issueActions: string[] = [];
386
- const userAllowlist: string[] = [];
387
-
388
- for (const trigger of triggers) {
389
- const git = trigger.git as GitTriggerJson;
390
- userAllowlist.push(...(git.userAllowlist ?? []).filter(user => user !== ""));
391
- for (const [eventKey, events] of Object.entries(GIT_EVENT_WEBHOOKS)) {
392
- const event = git[eventKey as keyof GitTriggerJson];
393
- if (event === undefined || typeof event !== "object") {
394
- continue;
395
- }
396
- webhookEvents.push(...events);
397
- const eventObject = event as Record<string, unknown>;
398
- if (eventKey === "pullRequest") {
399
- const raw = String(eventObject.prAction ?? "");
400
- const prAction =
401
- PR_ACTION_NUMBERS[raw] ?? (raw === "" ? "GIT_PULL_REQUEST_ACTION_UNSPECIFIED" : raw);
402
- if (prAction === "GIT_PULL_REQUEST_ACTION_COMMENTED") {
403
- webhookEvents.push("issue_comment");
404
- hasCommentedAction = true;
405
- if (
406
- (git.userAllowlist ?? []).length > 0 ||
407
- (Array.isArray(eventObject.commenterAllowlist) &&
408
- eventObject.commenterAllowlist.length > 0) ||
409
- (typeof eventObject.commentContains === "string" && eventObject.commentContains !== "")
410
- ) {
411
- commentGatesLost = true;
412
- }
413
- } else {
414
- const condition = prConditionFor(prAction, eventObject);
415
- if (condition === undefined) {
416
- hasUnknownPrAction = true;
417
- out.warnings.push(
418
- `A PR trigger${repoLabel(eventObject)} used unrecognized pr_action ${JSON.stringify(raw)} — add its dispatch condition to onPullRequest in ${path} (exported fail-closed).`,
419
- );
420
- } else {
421
- prConditions.push(condition);
422
- }
423
- }
424
- }
425
- if (eventKey === "pullRequestReviewRequested") {
426
- prConditions.push('(pr.action === "review_requested")');
427
- }
428
- if (eventKey === "issueComment") {
429
- hasIssueCommentTrigger = true;
430
- // A separate commenter_allowlist means BOTH lists must pass
431
- // (intersection), and comment_contains filters the body — neither
432
- // fits the single exported commenter gate.
433
- if (
434
- (Array.isArray(eventObject.commenterAllowlist) &&
435
- eventObject.commenterAllowlist.length > 0) ||
436
- (typeof eventObject.commentContains === "string" && eventObject.commentContains !== "")
437
- ) {
438
- commentGatesLost = true;
439
- }
440
- }
441
- if (eventKey === "pullRequestReviewComment") {
442
- // user_allowlist gates the PR author and commenter_allowlist the
443
- // commenter here — neither identity is visible to onComment — and
444
- // comment_contains filters the body.
445
- if (
446
- (git.userAllowlist ?? []).length > 0 ||
447
- (Array.isArray(eventObject.commenterAllowlist) &&
448
- eventObject.commenterAllowlist.length > 0) ||
449
- (typeof eventObject.commentContains === "string" && eventObject.commentContains !== "")
450
- ) {
451
- commentGatesLost = true;
452
- }
453
- }
454
- if (eventKey === "push") {
455
- if (typeof eventObject.branch === "string" && eventObject.branch !== "") {
456
- pushBranches.push(eventObject.branch);
457
- } else {
458
- // The backend matcher rejects push triggers without a branch —
459
- // they never fired, so exporting a dispatch would invent runs.
460
- out.warnings.push(
461
- `A push trigger${repoLabel(eventObject)} had no branch filter and never fired in the backend — exported without a dispatch.`,
462
- );
463
- }
464
- }
465
- if (eventKey === "ciCompleted") {
466
- hasCheckSuite = true;
467
- }
468
- if (eventKey === "workflowRun") {
469
- hasWorkflowRun = true;
470
- }
471
- if (eventKey === "issueLabeled") {
472
- if (typeof eventObject.labelName === "string" && eventObject.labelName !== "") {
473
- // The label-name filter is not visible to the typed hooks, so a
474
- // dispatch would fire on EVERY label change.
475
- out.warnings.push(
476
- `An issue-label trigger filtered on label ${JSON.stringify(eventObject.labelName)}, which the exported hooks cannot check — exported without a dispatch; re-add the filter in an onEvent hook reading the raw payload.`,
477
- );
478
- } else {
479
- // `unlabeled: true` flips the legacy trigger to label removal.
480
- issueActions.push(eventObject.unlabeled === true ? "unlabeled" : "labeled");
481
- }
482
- }
483
- if (eventKey === "issueAssigned") {
484
- issueActions.push("assigned");
485
- }
486
- // Review events export fail-closed: the backend gated review/thread
487
- // state and PR-author provenance, none of which the export can
488
- // reproduce. Without an onEvent hook the SDK drops the deliveries.
489
- if (eventKey === "pullRequestReview" || eventKey === "reviewThread") {
490
- out.warnings.push(
491
- `A ${eventKey === "pullRequestReview" ? "pull_request_review" : "review_thread"} trigger exported without a dispatch: the backend's review-state and PR-author provenance gates cannot be exported. Add an onEvent hook in ${path} to re-enable it with your own filters.`,
492
- );
493
- }
494
- if (typeof eventObject.repo === "string" && eventObject.repo !== "") {
495
- repos.push(eventObject.repo);
496
- }
497
- if (Array.isArray(eventObject.repos)) {
498
- repos.push(
499
- ...eventObject.repos.filter(
500
- (repo): repo is string => typeof repo === "string" && repo !== "",
501
- ),
502
- );
503
- }
504
- if (Array.isArray(eventObject.orgs) && eventObject.orgs.length > 0) {
505
- hasOrgs = true;
506
- }
507
- }
508
- const label = git.label;
509
- if (label !== undefined) {
510
- const labelObject = label as Record<string, unknown>;
511
- // The backend fires only for the configured direction(s); neither
512
- // flag set means the trigger never fired.
513
- let labelActions = [
514
- ...(labelObject.onAdded === true ? ["labeled"] : []),
515
- ...(labelObject.onRemoved === true ? ["unlabeled"] : []),
516
- ];
517
- if (labelActions.length === 0) {
518
- out.warnings.push(
519
- "A label trigger had neither on_added nor on_removed set and never fired in the backend — exported without a dispatch.",
520
- );
521
- }
522
- if (typeof labelObject.labelName === "string" && labelObject.labelName !== "") {
523
- // Label names are not visible to the typed hooks; dispatching would
524
- // fire on EVERY label change instead of the filtered one.
525
- out.warnings.push(
526
- `A label trigger filtered on label ${JSON.stringify(labelObject.labelName)}, which the exported hooks cannot check — exported without a dispatch; re-add the filter in a hook reading the raw payload.`,
527
- );
528
- labelActions = [];
529
- }
530
- if (label.pullRequests === true || label.issues !== true) {
531
- webhookEvents.push("pull_request");
532
- prConditions.push(
533
- ...labelActions.map(action => `(pr.action === ${JSON.stringify(action)})`),
534
- );
535
- }
536
- if (label.issues === true) {
537
- webhookEvents.push("issues");
538
- issueActions.push(...labelActions);
539
- }
540
- }
541
- }
542
- if (workflow.gitConfig?.repo !== undefined && workflow.gitConfig.repo !== "") {
543
- repos.push(workflow.gitConfig.repo);
544
- }
545
- repos.push(...(workflow.gitConfig?.repos ?? []));
546
-
547
- const uniqueEvents = dedupe(webhookEvents);
548
- const uniqueRepos = dedupe(
549
- dedupe(repos.filter(repo => repo !== "")).flatMap(repo => {
550
- const normalized = normalizeRepoIdentifier(repo, out);
551
- return normalized === undefined ? [] : [normalized];
552
- }),
553
- );
554
-
555
- if (hasOrgs) {
556
- out.warnings.push(
557
- `A git trigger watched whole GitHub orgs; the GitHub channel's cursorAccount scope needs explicit repos. List them in ${path}.`,
558
- );
559
- }
560
- out.setupSteps.push(
561
- `Review the generated hook filters in ${path} — mechanical trigger filters (PR action, push branch, issue action, user allowlist) were exported, but bespoke conditions Cursor's backend enforced (comment matching, CI conclusions, label names) must be re-added by hand.`,
562
- );
563
- if (hasCheckSuite || hasWorkflowRun) {
564
- out.warnings.push(
565
- `A CI trigger (ci_completed / workflow_run) exported FAIL-CLOSED: its backend filters (conclusion, branch, actor allowlist) cannot be exported, so the generated hook in ${path} returns null until you re-add them and dispatch.`,
566
- );
567
- }
568
- if (uniqueRepos.length === 0) {
569
- out.setupSteps.push(
570
- `No repositories could be derived for the GitHub channel — set githubChannel({ cursorAccount: { repos: [...] } }) or GitHub App credentials in ${path}.`,
571
- );
572
- }
573
-
574
- const configLines: string[] = [];
575
- if (uniqueRepos.length > 0) {
576
- configLines.push(" cursorAccount: {");
577
- configLines.push(` repos: [${uniqueRepos.map(repo => JSON.stringify(repo)).join(", ")}],`);
578
- configLines.push(" },");
579
- }
580
- configLines.push(
581
- ` webhookEvents: [${uniqueEvents.map(event => JSON.stringify(event)).join(", ")}],`,
582
- );
583
-
584
- const uniqueAllowlist = dedupe(userAllowlist.map(user => user.toLowerCase()));
585
- if (triggers.length > 1 && uniqueAllowlist.length > 0) {
586
- out.warnings.push(
587
- `user_allowlist applies per trigger in the backend; the export unions every trigger's allowlist across all hooks in ${path} — tighten per hook if the triggers differed.`,
588
- );
589
- }
590
- // The cursorAccount SCM-event relay delivers PR/comment snapshots only —
591
- // no push ref, no issue payload, no CI nodes, and no sender for
592
- // non-comment events — so hooks needing those fields only work with a
593
- // real GitHub App webhook or `github forward`.
594
- const relayLimited = [
595
- ...(dedupe(pushBranches).length > 0 ? ["push branch filters"] : []),
596
- ...(dedupe(issueActions).length > 0 ? ["issue triggers"] : []),
597
- ...(hasCheckSuite || hasWorkflowRun ? ["CI triggers"] : []),
598
- ];
599
- if (uniqueRepos.length > 0 && relayLimited.length > 0) {
600
- out.warnings.push(
601
- `cursorAccount event delivery carries PR/comment data only, so ${relayLimited.join(", ")} will NOT fire through it — deliver real GitHub webhooks to the deployed agent (GitHub App or \`github forward\`) for those triggers.`,
602
- );
603
- }
604
-
605
- const hasCommentEvents =
606
- uniqueEvents.includes("issue_comment") || uniqueEvents.includes("pull_request_review_comment");
607
- const commentGate: CommentGate = !hasCommentEvents
608
- ? "none"
609
- : commentGatesLost
610
- ? "failClosed"
611
- : hasIssueCommentTrigger && uniqueAllowlist.length > 0
612
- ? "commenterAllowlist"
613
- : "mentionDefault";
614
- if (commentGate === "failClosed") {
615
- out.warnings.push(
616
- `A comment trigger's backend gates (PR-author provenance via user_allowlist, commenter_allowlist, comment_contains) cannot be exported — comments dispatch NOTHING until you re-add them in onComment in ${path}.`,
617
- );
618
- }
619
-
620
- const { constLines, hookLines, usesAuth } = renderGithubHooks({
621
- prConditions: dedupe(prConditions),
622
- hasUnknownPrAction,
623
- pushBranches: dedupe(pushBranches),
624
- hasCheckSuite,
625
- hasWorkflowRun,
626
- issueActions: dedupe(issueActions),
627
- userAllowlist: uniqueAllowlist,
628
- hasPullRequestEvents: uniqueEvents.includes("pull_request"),
629
- commentGate,
630
- hasCommentedAction,
631
- });
632
- const imports = usesAuth
633
- ? "import { defaultGitHubAuth, githubChannel }"
634
- : "import { githubChannel }";
635
-
636
- const lines = [
637
- `${imports} from "${PACKAGE_NAME}/channels/github";`,
638
- "",
639
- `// Exported from Cursor automation "${commentSafe(automation.name)}" git trigger(s).`,
640
- "// Original trigger config:",
641
- ...jsonComment(triggers.map(trigger => trigger.git)),
642
- "//",
643
- "// The hooks below reproduce the triggers' mechanical filters. Cursor's",
644
- "// backend also enforced any bespoke conditions (comment matching, CI",
645
- "// conclusions, label names) — re-add those before relying on this.",
646
- ...constLines,
647
- "export default githubChannel({",
648
- ...configLines,
649
- ...hookLines,
650
- "});",
651
- "",
652
- ];
653
- out.files.push({ path, contents: lines.join("\n") });
654
- }
655
-
656
- type CommentGate = "none" | "mentionDefault" | "commenterAllowlist" | "failClosed";
657
-
658
- /** Render the dispatch hooks for {@link emitGithubChannel}. */
659
- function renderGithubHooks(args: {
660
- prConditions: string[];
661
- hasUnknownPrAction: boolean;
662
- pushBranches: string[];
663
- hasCheckSuite: boolean;
664
- hasWorkflowRun: boolean;
665
- issueActions: string[];
666
- userAllowlist: string[];
667
- hasPullRequestEvents: boolean;
668
- commentGate: CommentGate;
669
- hasCommentedAction: boolean;
670
- }): { constLines: string[]; hookLines: string[]; usesAuth: boolean } {
671
- const lines: string[] = [];
672
- const hasAllowlist = args.userAllowlist.length > 0;
673
- const hasPush = args.pushBranches.length > 0;
674
- let allowlistUsed = false;
675
- let usesAuth = false;
676
-
677
- if (args.prConditions.length > 0) {
678
- lines.push(" onPullRequest: (ctx, pr) => {");
679
- if (hasAllowlist) {
680
- allowlistUsed = true;
681
- lines.push(
682
- " if (",
683
- " pr.authorLogin === undefined ||",
684
- " !ALLOWED_USERS.includes(pr.authorLogin.toLowerCase())",
685
- " ) {",
686
- " return null;",
687
- " }",
688
- );
689
- }
690
- lines.push(
691
- " return (",
692
- ...args.prConditions.map(
693
- (condition, index) =>
694
- ` ${condition}${index < args.prConditions.length - 1 ? " ||" : ""}`,
695
- ),
696
- " )",
697
- " ? { auth: defaultGitHubAuth(ctx) }",
698
- " : null;",
699
- " },",
700
- );
701
- usesAuth = true;
702
- } else if (args.hasPullRequestEvents) {
703
- // Without this fail-safe the SDK's default gate would dispatch on
704
- // opened/reopened/synchronize/ready_for_review — broader than any
705
- // exported trigger. COMMENTED triggers get comments via the comment
706
- // gate; unrecognized/label-filtered triggers need their condition
707
- // re-added here.
708
- lines.push(" onPullRequest: () => null,");
709
- }
710
-
711
- if (hasPush) {
712
- lines.push(" onEvent: (ctx, event) => {");
713
- if (hasAllowlist) {
714
- allowlistUsed = true;
715
- lines.push(
716
- " // The backend gated push on the user allowlist (actor).",
717
- " if (",
718
- " ctx.senderLogin === undefined ||",
719
- " !ALLOWED_USERS.includes(ctx.senderLogin.toLowerCase())",
720
- " ) {",
721
- " return null;",
722
- " }",
723
- );
724
- }
725
- const refs = args.pushBranches.map(branch => JSON.stringify(`refs/heads/${branch}`));
726
- lines.push(
727
- ' if (event.eventName === "push") {',
728
- " // Branch filter from the original trigger.",
729
- " const ref = (event.payload as { ref?: string }).ref;",
730
- refs.length === 1
731
- ? ` if (ref !== ${refs[0]}) {`
732
- : ` if (ref === undefined || ![${refs.join(", ")}].includes(ref)) {`,
733
- " return null;",
734
- " }",
735
- " return { auth: defaultGitHubAuth(ctx) };",
736
- " }",
737
- );
738
- usesAuth = true;
739
- lines.push(" return null;", " },");
740
- }
741
-
742
- // check_suite / workflow_run only dispatch through their typed hooks —
743
- // the onEvent catch-all never sees them. They export FAIL-CLOSED: the
744
- // backend filters they relied on (CI conclusion/branch, and for
745
- // workflow_run the actor AND triggering-actor allowlist) cannot be
746
- // reproduced mechanically, and dispatching unfiltered would let any CI
747
- // delivery start an agent turn.
748
- if (args.hasCheckSuite) {
749
- lines.push(
750
- " onCheckSuite: () => {",
751
- " // Fail closed: re-add the original ci_completed filters —",
752
- ' // `suite.action === "completed"`, conclusion/branch, user',
753
- " // allowlist (see the trigger config above) — then dispatch with",
754
- " // `(ctx, suite) => ({ auth: defaultGitHubAuth(ctx) })`.",
755
- " return null;",
756
- " },",
757
- );
758
- }
759
- if (args.hasWorkflowRun) {
760
- lines.push(
761
- " onWorkflowRun: () => {",
762
- " // Fail closed: re-add the original workflow_run filters — the",
763
- ' // backend required `run.action === "completed"`, a push-origin',
764
- " // same-repo (non-fork) run, and gated the user allowlist on the",
765
- " // run's actor AND triggering actor — then dispatch with",
766
- " // `(ctx, run) => ({ auth: defaultGitHubAuth(ctx) })`.",
767
- " return null;",
768
- " },",
769
- );
770
- }
771
-
772
- if (args.issueActions.length > 0) {
773
- const condition =
774
- args.issueActions.length === 1
775
- ? `issue.action === ${JSON.stringify(args.issueActions[0])}`
776
- : `[${args.issueActions.map(action => JSON.stringify(action)).join(", ")}].includes(issue.action)`;
777
- lines.push(" onIssue: (ctx, issue) => {");
778
- if (hasAllowlist) {
779
- allowlistUsed = true;
780
- lines.push(
781
- " // The backend allowlisted the ACTOR (who labeled/assigned),",
782
- " // not the issue author.",
783
- " if (",
784
- " ctx.senderLogin === undefined ||",
785
- " !ALLOWED_USERS.includes(ctx.senderLogin.toLowerCase())",
786
- " ) {",
787
- " return null;",
788
- " }",
789
- );
790
- }
791
- lines.push(` return ${condition} ? { auth: defaultGitHubAuth(ctx) } : null;`, " },");
792
- usesAuth = true;
793
- }
794
-
795
- if (args.commentGate === "failClosed") {
796
- lines.push(
797
- " onComment: () => {",
798
- " // Fail closed: the trigger's backend comment gates (PR-author",
799
- " // provenance via user_allowlist, commenter_allowlist,",
800
- " // comment_contains — see the trigger config above) cannot be",
801
- " // exported. Re-add them here (PR author needs a ctx.github",
802
- " // lookup), then return { auth: defaultGitHubAuth(ctx) }.",
803
- " return null;",
804
- " },",
805
- );
806
- } else if (args.commentGate === "commenterAllowlist" && hasAllowlist) {
807
- // issue_comment triggers gate the COMMENTER on user_allowlist — the one
808
- // comment gate that is faithfully exportable.
809
- allowlistUsed = true;
810
- usesAuth = true;
811
- lines.push(
812
- " // Replaces the default @mention gate: the original fired on ALL",
813
- " // comments by allowlisted commenters.",
814
- " onComment: (ctx, comment) => {",
815
- " const author = comment.author?.login;",
816
- " if (",
817
- " author === undefined ||",
818
- " !ALLOWED_USERS.includes(author.toLowerCase())",
819
- " ) {",
820
- " return null;",
821
- " }",
822
- " return { auth: defaultGitHubAuth(ctx) };",
823
- " },",
824
- );
825
- } else if (args.commentGate === "mentionDefault" && lines.length > 0) {
826
- lines.push(
827
- " // Comments dispatch via the default @mention gate; the original",
828
- " // fired on ALL comments — declare onComment to widen it.",
829
- );
830
- }
831
-
832
- const constLines = allowlistUsed
833
- ? [
834
- "// user_allowlist from the original trigger(s), lowercased — GitHub",
835
- "// logins are case-insensitive and the backend compared them that way.",
836
- `const ALLOWED_USERS = [${args.userAllowlist.map(user => JSON.stringify(user)).join(", ")}];`,
837
- "",
838
- ]
839
- : [];
840
- return { constLines, hookLines: lines, usesAuth };
841
- }
842
-
843
- function repoLabel(eventObject: Record<string, unknown>): string {
844
- const repos = dedupe(
845
- [
846
- typeof eventObject.repo === "string" ? eventObject.repo : "",
847
- ...(Array.isArray(eventObject.repos)
848
- ? eventObject.repos.filter((repo): repo is string => typeof repo === "string")
849
- : []),
850
- ].filter(repo => repo !== ""),
851
- );
852
- return repos.length > 0 ? ` (${repos.join(", ")})` : "";
853
- }
854
-
855
- /**
856
- * Normalize a trigger/git_config repo into a GitHub `owner/repo` identifier
857
- * for `cursorAccount: { repos: [...] }`. github.com URLs (https or ssh) are
858
- * reduced to `owner/repo`; non-GitHub URLs are excluded with a warning;
859
- * plain strings pass through.
860
- */
861
- function normalizeRepoIdentifier(raw: string, out: ConvertOutput): string | undefined {
862
- let candidate = raw;
863
- if (raw.startsWith("git@") && !raw.includes("://")) {
864
- // git@host:path SSH shorthand → a parseable ssh:// URL.
865
- candidate = `ssh://${raw.replace(":", "/")}`;
866
- }
867
- if (!candidate.includes("://")) {
868
- return raw;
869
- }
870
- if (!URL.canParse(candidate)) {
871
- out.warnings.push(
872
- `Repo ${JSON.stringify(raw)} could not be parsed as a URL — excluded from the GitHub channel's cursorAccount repos; list it manually as owner/repo if it is GitHub-hosted.`,
873
- );
874
- return undefined;
875
- }
876
- const url = new URL(candidate);
877
- const host = url.hostname.toLowerCase();
878
- if (host !== "github.com" && host !== "www.github.com") {
879
- out.warnings.push(
880
- `Repo ${JSON.stringify(raw)} is not GitHub-hosted (host: ${url.hostname}) — excluded from the GitHub channel's cursorAccount repos; cursorAccount can only watch GitHub repositories.`,
881
- );
882
- // Losing a trigger repo silently halves coverage for dual-host setups,
883
- // so surface it as a required setup step, not just a warning.
884
- out.setupSteps.push(
885
- `Trigger repo ${JSON.stringify(raw)} is not on GitHub, so its events will NOT reach this agent. Re-deliver them yourself (e.g. a webhook to the deployed agent's /v1/channels/github route) or drop that trigger.`,
886
- );
887
- return undefined;
888
- }
889
- const segments = url.pathname.split("/").filter(s => s !== "");
890
- if (segments.length < 2) {
891
- out.warnings.push(
892
- `Repo URL ${JSON.stringify(raw)} has no owner/repo path — excluded from the GitHub channel's cursorAccount repos.`,
893
- );
894
- return undefined;
895
- }
896
- return `${segments[0]}/${segments[1].replace(/\.git$/, "")}`;
897
- }
898
-
899
- // ============================================================================
900
- // Slack channel
901
- // ============================================================================
902
-
903
- function isSlackTrigger(trigger: TriggerJson): boolean {
904
- return (
905
- trigger.slackMention !== undefined ||
906
- trigger.slackTrigger !== undefined ||
907
- trigger.slackReactionAdded !== undefined ||
908
- trigger.slackAnyReactionAdded !== undefined ||
909
- trigger.slackChannelCreated !== undefined
910
- );
911
- }
912
-
913
- function emitSlackChannel(
914
- automation: ConvertWorkflowInput["automation"],
915
- triggers: TriggerJson[],
916
- slugs: SlugRegistry,
917
- out: ConvertOutput,
918
- ): void {
919
- const slug = slugs.claim("slack");
920
- const path = projectPath(out.projectRoot, `channels/${slug}.ts`);
921
-
922
- const watches = triggers.filter(t => t.slackTrigger !== undefined);
923
- const unsupported = triggers.filter(
924
- t =>
925
- t.slackReactionAdded !== undefined ||
926
- t.slackAnyReactionAdded !== undefined ||
927
- t.slackChannelCreated !== undefined,
928
- );
929
- const socketMode = watches.length > 0 || unsupported.length > 0;
930
-
931
- for (const trigger of unsupported) {
932
- const kind = trigger.slackChannelCreated !== undefined ? "channel-created" : "reaction-added";
933
- out.warnings.push(
934
- `Slack ${kind} trigger has no BDK equivalent — its config is inlined as a comment in ${path}; handle the Slack event yourself on a dedicated Socket Mode app.`,
935
- );
936
- }
937
- let reactionDropped = false;
938
- for (const watch of watches) {
939
- const config = watch.slackTrigger as SlackWatchTriggerJson;
940
- if (config.messageContains !== undefined && config.messageContains !== "") {
941
- out.warnings.push(
942
- `Slack channel-watch message filter (${JSON.stringify(config.messageContains)}) was enforced by Cursor's backend — reimplement it (e.g. onChannelPost) in ${path}.`,
943
- );
944
- }
945
- // The backend defaults a missing/UNSPECIFIED mode to ON (checkmark
946
- // reaction); only explicit OFF means no reaction was added.
947
- const reactionMode = String(config.slackCompletionReactionMode ?? "");
948
- if (reactionMode !== "SLACK_COMPLETION_REACTION_MODE_OFF" && reactionMode !== "2") {
949
- reactionDropped = true;
950
- }
951
- }
952
- if (reactionDropped) {
953
- out.warnings.push(
954
- `The Slack trigger marked triggering messages with a completion reaction; BDK has no completion-reaction option, so that signal is lost unless ${projectPath(out.projectRoot, "instructions.md")} tells the agent to add the reaction itself via Slack tools.`,
955
- );
956
- }
957
- out.setupSteps.push(
958
- socketMode
959
- ? `Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app: run \`${CLI_COMMAND_NAME} slack create --channel-posts\`, then finish in the dashboard wizard.`
960
- : `Slack mentions need a dedicated Socket Mode Slack app: run \`${CLI_COMMAND_NAME} slack create\`, then finish in the dashboard wizard.`,
961
- );
962
-
963
- const header = [
964
- `import { slackChannel } from "${PACKAGE_NAME}/channels/slack";`,
965
- "",
966
- `// Exported from Cursor automation "${commentSafe(automation.name)}" Slack trigger(s).`,
967
- "// Original trigger config:",
968
- ...jsonComment(triggers),
969
- ];
970
-
971
- let body: string[];
972
- if (socketMode) {
973
- const allow = dedupe(watches.flatMap(watch => slackChannelList(watch.slackTrigger)));
974
- const anyThreadReplies = watches.some(
975
- watch => (watch.slackTrigger as SlackWatchTriggerJson).topLevelOnly === false,
976
- );
977
- // "Authenticated Cursor users" watches require a linked Cursor account
978
- // on the owning team, which bots never have — so the backend blocked
979
- // bot posts for them, and the sender gate itself has no SDK equivalent.
980
- // The SDK has ONE bot-post setting per channel, so mixed watches must
981
- // pick a side: keep bots when any watch allowed everyone (alert /
982
- // scorebot watches depend on them), and say so loudly.
983
- const blocksUnauthenticated = watches.some(
984
- watch =>
985
- (watch.slackTrigger as SlackWatchTriggerJson).blockUnauthenticatedSlackUsers === true,
986
- );
987
- const includeBotPosts =
988
- watches.length > 0 &&
989
- watches.some(
990
- watch =>
991
- (watch.slackTrigger as SlackWatchTriggerJson).blockUnauthenticatedSlackUsers !== true,
992
- );
993
- if (blocksUnauthenticated) {
994
- out.warnings.push(
995
- includeBotPosts
996
- ? "Some Slack watches only fired for authenticated Cursor users while others allowed anyone; BDK has one bot-post setting per channel, so the export keeps bot posts ENABLED (the open watches depend on them) and the authenticated-only watches lose both their sender gate and their bot exclusion. Split the channels or add an onChannelPost gate."
997
- : "The Slack trigger only fired for authenticated Cursor users (linked accounts on the owning team); BDK has no equivalent sender gate, so the exported watch dispatches for ANY channel member (bot posts stay excluded). Add an onChannelPost gate in the channel if that matters.",
998
- );
999
- }
1000
- const engagement: string[] = [];
1001
- if (allow.length > 0) {
1002
- engagement.push(" engagement: {");
1003
- engagement.push(" channelPosts: {");
1004
- engagement.push(
1005
- ` allow: [${allow.map(channel => JSON.stringify(channel)).join(", ")}],`,
1006
- );
1007
- if (anyThreadReplies) {
1008
- engagement.push(' posts: "all",');
1009
- }
1010
- if (includeBotPosts) {
1011
- engagement.push(
1012
- " // Cursor's channel-watch dispatched on bot-authored posts",
1013
- " // (alerts, scorebots, ...); the SDK drops them by default and",
1014
- " // always ignores this agent's own posts.",
1015
- " includeBotPosts: true,",
1016
- );
1017
- }
1018
- engagement.push(" },");
1019
- engagement.push(" },");
1020
- }
1021
- body = [
1022
- "//",
1023
- `// Run \`${CLI_COMMAND_NAME} slack create --channel-posts\` for a dedicated Slack app.`,
1024
- ...slackChannelExport(automation.name, engagement),
1025
- ];
1026
- } else {
1027
- body = [
1028
- "//",
1029
- `// Run \`${CLI_COMMAND_NAME} slack create\` for a dedicated Slack app.`,
1030
- ...slackChannelExport(automation.name, []),
1031
- ];
1032
- }
1033
- out.files.push({ path, contents: `${[...header, ...body].join("\n")}\n` });
1034
- }
1035
-
1036
- /**
1037
- * `slack create` writes `<DIR>_SLACK_*` from the project directory. Convert
1038
- * names that directory from the automation (`Nightly Triage` →
1039
- * `nightly-triage` → `NIGHTLY_TRIAGE`). Emit the same prefix so minted
1040
- * tokens bind.
1041
- */
1042
- function slackChannelExport(name: string, optionLines: string[]): string[] {
1043
- const prefix = envPrefixFromName(slugify(name, "automation"));
1044
- const options = [
1045
- ...(prefix === "" ? [] : [` envPrefix: ${JSON.stringify(prefix)},`]),
1046
- ...optionLines,
1047
- ];
1048
- if (options.length === 0) {
1049
- return ["export default slackChannel();"];
1050
- }
1051
- return ["export default slackChannel({", ...options, "});"];
1052
- }
1053
-
1054
- function slackChannelList(trigger: SlackChannelsTriggerJson | undefined): string[] {
1055
- return [...(trigger?.channels ?? []), trigger?.channel].filter(
1056
- (channel): channel is string => typeof channel === "string" && channel !== "",
1057
- );
1058
- }
1059
-
1060
- // ============================================================================
1061
- // Boilerplate webhook channels (Linear / PagerDuty / Sentry / Teams / email / webhook)
1062
- // ============================================================================
1063
-
1064
- type BoilerplateKind = {
1065
- key:
1066
- | "linear"
1067
- | "pagerduty"
1068
- | "sentry"
1069
- | "webhook"
1070
- | "emailReceived"
1071
- | "microsoftTeamsTrigger"
1072
- | "microsoftTeamsChannelCreated";
1073
- slug: string;
1074
- label: string;
1075
- };
1076
-
1077
- const BOILERPLATE_KINDS: BoilerplateKind[] = [
1078
- { key: "linear", slug: "linear", label: "Linear" },
1079
- { key: "pagerduty", slug: "pagerduty", label: "PagerDuty" },
1080
- { key: "sentry", slug: "sentry", label: "Sentry" },
1081
- { key: "webhook", slug: "webhook", label: "webhook" },
1082
- { key: "emailReceived", slug: "email", label: "email received" },
1083
- {
1084
- key: "microsoftTeamsTrigger",
1085
- slug: "teams",
1086
- label: "Microsoft Teams message",
1087
- },
1088
- {
1089
- key: "microsoftTeamsChannelCreated",
1090
- slug: "teams-channel-created",
1091
- label: "Microsoft Teams channel-created",
1092
- },
1093
- ];
1094
-
1095
- function boilerplateKind(trigger: TriggerJson): BoilerplateKind | undefined {
1096
- return BOILERPLATE_KINDS.find(kind => trigger[kind.key] !== undefined);
1097
- }
1098
-
1099
- function emitBoilerplateChannel(
1100
- automation: ConvertWorkflowInput["automation"],
1101
- trigger: TriggerJson,
1102
- kind: BoilerplateKind,
1103
- slugs: SlugRegistry,
1104
- out: ConvertOutput,
1105
- ): void {
1106
- const slug = slugs.claim(kind.slug);
1107
- const path = projectPath(out.projectRoot, `channels/${slug}.ts`);
1108
- out.warnings.push(
1109
- `${kind.label} trigger exported as a boilerplate webhook channel (${path}) — Cursor-managed webhook delivery and event filtering do not carry over.`,
1110
- );
1111
- out.setupSteps.push(
1112
- `Re-register the ${kind.label} webhook against the deployed agent URL (/v1/channels/${slug}) and reimplement event filtering in ${path} — Cursor's backend did both before. The generated route accepts ANY unauthenticated POST and starts a run from it: add auth (e.g. bearerAuth or provider signature verification) before exposing it.`,
1113
- );
1114
- const lines = [
1115
- `import { defineChannel, POST } from "${PACKAGE_NAME}/channels";`,
1116
- 'import { z } from "zod";',
1117
- "",
1118
- `// Exported from Cursor automation "${commentSafe(automation.name)}" ${kind.label} trigger.`,
1119
- "// Original trigger config:",
1120
- ...jsonComment(trigger[kind.key]),
1121
- "//",
1122
- `// TODO: Cursor's backend previously received ${kind.label} webhooks and`,
1123
- "// applied the filtering above. Re-register the provider webhook against",
1124
- `// the deployed agent URL (/v1/channels/${slug}) and reimplement the event`,
1125
- "// filtering in this handler before dispatching.",
1126
- "export default defineChannel({",
1127
- " routes: [",
1128
- ' POST("/", {',
1129
- " bodySchema: z.unknown(),",
1130
- " handler: async (_req, { send, body }) => {",
1131
- " const session = await send(",
1132
- " [",
1133
- ` "Handle this ${kind.label} event:",`,
1134
- ' "",',
1135
- " JSON.stringify(body, null, 2),",
1136
- ' ].join("\\n")',
1137
- " );",
1138
- " return Response.json({ sessionId: session.id });",
1139
- " },",
1140
- " }),",
1141
- " ],",
1142
- "});",
1143
- "",
1144
- ];
1145
- out.files.push({ path, contents: lines.join("\n") });
1146
- }
1147
-
1148
- // ============================================================================
1149
- // MCP connections
1150
- // ============================================================================
1151
-
1152
- function emitMcpConnections(
1153
- automation: ConvertWorkflowInput["automation"],
1154
- mcpServers: ConvertMcpServer[],
1155
- out: ConvertOutput,
1156
- ): void {
1157
- const slugs = new SlugRegistry();
1158
- for (const server of mcpServers) {
1159
- const slug = slugs.claim(slugify(server.name, "mcp-server"));
1160
- if (server.transportType !== "stdio") {
1161
- out.mcpConnections.push({ name: slug, kind: "cursorAccount" });
1162
- const lines = [
1163
- `import { defineConnection } from "${PACKAGE_NAME}/connections";`,
1164
- "",
1165
- `// MCP server "${commentSafe(server.name)}" from Cursor automation "${commentSafe(automation.name)}".`,
1166
- "// Resolved at runtime through your Cursor account: tools list and",
1167
- "// execute on the Cursor backend with the account's stored credentials,",
1168
- "// so no URL or tokens live in this project. Sign this host in",
1169
- `// (\`${CLI_COMMAND_NAME} login\`) and manage the server's authorization`,
1170
- "// in Cursor (dashboard -> MCP).",
1171
- "export default defineConnection({",
1172
- " cursorAccount: true,",
1173
- ` servers: [${JSON.stringify(server.name)}],`,
1174
- "});",
1175
- "",
1176
- ];
1177
- out.files.push({
1178
- path: projectPath(out.projectRoot, `mcp-connections/${slug}.ts`),
1179
- contents: lines.join("\n"),
1180
- });
1181
- continue;
1182
- }
1183
- out.mcpConnections.push({ name: slug, kind: "stdioStub" });
1184
- const reason =
1185
- "it is a stdio server (its command, args, and env stay on Cursor's server). Run the server yourself and point a connection at it.";
1186
- // A comment-only .ts in mcp-connections/ fails discovery (a connection
1187
- // module must default-export defineConnection), so the stub is a .md the
1188
- // framework ignores.
1189
- const connectionPath = projectPath(out.projectRoot, `mcp-connections/${slug}.ts`);
1190
- const stubPath = projectPath(out.projectRoot, `mcp-connections/${slug}.todo.md`);
1191
- out.warnings.push(`MCP server "${server.name}" exported as a stub (${stubPath}): ${reason}`);
1192
- const lines = [
1193
- `# TODO: MCP server "${commentSafe(server.name)}"`,
1194
- "",
1195
- `The source automation used MCP server "${commentSafe(server.name)}", which could not`,
1196
- "be exported as a working connection:",
1197
- "",
1198
- `- ${reason}`,
1199
- "",
1200
- "Credentials are never exported. Once the server is reachable over",
1201
- `HTTP, create \`${connectionPath}\`:`,
1202
- "",
1203
- "```ts",
1204
- `import { defineConnection } from "${PACKAGE_NAME}/connections";`,
1205
- "",
1206
- "export default defineConnection({",
1207
- ' url: "https://example.com/mcp",',
1208
- ' oauth: true, // or headers: { authorization: "Bearer <token>" }',
1209
- "});",
1210
- "```",
1211
- "",
1212
- "This .md file is ignored by the framework; delete it once resolved.",
1213
- "",
1214
- ];
1215
- out.files.push({ path: stubPath, contents: lines.join("\n") });
1216
- }
1217
- }
1218
-
1219
- /**
1220
- * The automation posted to / read Slack via Cursor's built-in Slack action;
1221
- * restore that access with a Cursor-account Slack connection so the exported
1222
- * agent has Slack tools without any token plumbing. Returns whether the
1223
- * project ends up with a Slack MCP connection. An MCP server named "slack"
1224
- * may already have produced an equivalent connection, which counts.
1225
- */
1226
- function emitSlackAccountConnection(
1227
- automation: ConvertWorkflowInput["automation"],
1228
- out: ConvertOutput,
1229
- ): boolean {
1230
- const path = projectPath(out.projectRoot, "mcp-connections/slack.ts");
1231
- if (out.files.some(file => file.path === path)) {
1232
- return true;
1233
- }
1234
- const lines = [
1235
- `import { defineConnection } from "${PACKAGE_NAME}/connections";`,
1236
- "",
1237
- `// Cursor automation "${commentSafe(automation.name)}" used the built-in Slack action.`,
1238
- "// This connection restores Slack access through your Cursor account's",
1239
- "// Slack connector (dashboard -> MCP): tools execute on the Cursor",
1240
- "// backend with the account's stored credentials.",
1241
- "export default defineConnection({",
1242
- " cursorAccount: true,",
1243
- ' servers: ["Slack"],',
1244
- "});",
1245
- "",
1246
- ];
1247
- out.files.push({ path, contents: lines.join("\n") });
1248
- if (!out.mcpConnections.some(connection => connection.name === "slack")) {
1249
- out.mcpConnections.push({ name: "slack", kind: "cursorAccount" });
1250
- }
1251
- return true;
1252
- }
1253
-
1254
- // ============================================================================
1255
- // Built-in actions → behaviors to verify
1256
- // ============================================================================
1257
-
1258
- function actionSteps(projectRoot: ProjectRoot): Partial<Record<keyof ActionJson, string>> {
1259
- const instructionsPath = projectPath(projectRoot, "instructions.md");
1260
- return {
1261
- gitPr:
1262
- 'Verify: git_pr — the automation auto-opened PRs. Have the agent push and open PRs itself; githubChannel needs permissions: "contents-write" to push code.',
1263
- prComment: `Verify: pr_comment — githubChannel's default permissions ("pr-write") covers PR comments and reviews; guide comment behavior in ${instructionsPath}.`,
1264
- manageCheckRun:
1265
- "Verify: manage_check_run — no built-in equivalent; create/update check runs via ctx.github in a github channel hook.",
1266
- requestReviewers:
1267
- "Verify: request_reviewers — no built-in equivalent; request reviewers via ctx.github in a github channel hook.",
1268
- approvePr: `Verify: approve_pr — PR approval needs githubChannel "pr-write" plus explicit approval guidance in ${instructionsPath}.`,
1269
- resolveReviewThreads:
1270
- "Verify: resolve_review_threads — no built-in equivalent; resolve threads via the GitHub GraphQL API in a hook or tool.",
1271
- microsoftTeams:
1272
- "Verify: microsoft_teams post — BDK has no Teams channel; wire the Microsoft Teams API yourself.",
1273
- readMicrosoftTeams:
1274
- "Verify: read_microsoft_teams — BDK has no Teams tools; wire the Microsoft Teams API yourself.",
1275
- };
1276
- }
1277
-
1278
- interface SlackIntegrationState {
1279
- hasSlackChannel: boolean;
1280
- hasSlackConnection: boolean;
1281
- }
1282
-
1283
- function slackActionStep(
1284
- action: SlackActionJson,
1285
- { hasSlackChannel, hasSlackConnection }: SlackIntegrationState,
1286
- projectRoot: ProjectRoot,
1287
- ): string {
1288
- const channels = dedupe(slackChannelList(action));
1289
- const target =
1290
- action.generalized === true
1291
- ? "any channel (generalized)"
1292
- : channels.length > 0
1293
- ? channels.join(", ")
1294
- : "unspecified channel";
1295
- if (hasSlackChannel) {
1296
- return `Verify: slack post (target: ${target}) — the Slack channel replies in the triggering thread; posting to other channels needs Slack tools or a Cursor account MCP connection.`;
1297
- }
1298
- if (hasSlackConnection) {
1299
- return `Verify: slack post (target: ${target}) — ${projectPath(projectRoot, "mcp-connections/slack.ts")} restores Slack through your Cursor account's Slack connector; confirm it is connected in Cursor (dashboard -> MCP) and that ${projectPath(projectRoot, "instructions.md")} names the target channel.`;
1300
- }
1301
- return `Verify: slack post (target: ${target}) — the automation posted to Slack, but the exported project has no Slack integration; wire a Slack channel or a Cursor account MCP connection (defineConnection({ cursorAccount: true, servers: ["Slack"] })) to restore it.`;
1302
- }
1303
-
1304
- function behaviorSteps(
1305
- workflow: WorkflowJson,
1306
- warnings: string[],
1307
- slack: SlackIntegrationState,
1308
- projectRoot: ProjectRoot,
1309
- ): string[] {
1310
- const steps: string[] = [];
1311
- const knownActionSteps = actionSteps(projectRoot);
1312
- for (const action of workflow.actions ?? []) {
1313
- if (action.mcp !== undefined) {
1314
- continue; // Covered by the exported mcpServers list.
1315
- }
1316
- if (action.slack !== undefined) {
1317
- steps.push(slackActionStep(action.slack, slack, projectRoot));
1318
- continue;
1319
- }
1320
- if (action.readSlack !== undefined) {
1321
- steps.push(
1322
- slack.hasSlackConnection
1323
- ? `Verify: read_slack — Slack read access is available through the generated ${projectPath(projectRoot, "mcp-connections/slack.ts")} (Cursor-account Slack connector).`
1324
- : 'Verify: read_slack — expose Slack read access via a Cursor account MCP connection (defineConnection({ cursorAccount: true, servers: ["Slack"] })).',
1325
- );
1326
- continue;
1327
- }
1328
- const step = Object.entries(knownActionSteps).find(
1329
- ([key]) => action[key as keyof ActionJson] !== undefined,
1330
- )?.[1];
1331
- if (step === undefined) {
1332
- warnings.push(
1333
- `Unrecognized action (keys: ${Object.keys(action).join(", ") || "none"}) was skipped — verify its behavior manually.`,
1334
- );
1335
- continue;
1336
- }
1337
- steps.push(step);
1338
- }
1339
- const disabled = workflow.disabledDefaultTools ?? [];
1340
- if (disabled.length > 0) {
1341
- steps.push(
1342
- `Verify: disabled_default_tools (${disabled.join(", ")}) — BDK has no default automation tools; enforce the equivalent restriction in ${projectPath(projectRoot, "instructions.md")}.`,
1343
- );
1344
- }
1345
- return steps;
1346
- }
1347
-
1348
- // ============================================================================
1349
- // Shared helpers
1350
- // ============================================================================
1351
-
1352
- /**
1353
- * Neutralize comment-terminator sequences in server-provided text embedded
1354
- * in generated comments: JS line terminators (\r, \n, U+2028, U+2029) end a
1355
- * `//` comment, `-->` ends an HTML comment, and `*` + `/` ends a block
1356
- * comment. Without this, a crafted automation or MCP server name injects
1357
- * live code into the generated project.
1358
- */
1359
- function commentSafe(text: string): string {
1360
- return text
1361
- .replace(/[\r\n\u2028\u2029]+/g, " ")
1362
- .replace(/-->/g, "-- >")
1363
- .replace(/\*\//g, "* /");
1364
- }
1365
-
1366
- function jsonComment(value: unknown): string[] {
1367
- // JSON.stringify leaves U+2028/U+2029 raw; both terminate a `//` comment.
1368
- return JSON.stringify(value, null, 2)
1369
- .replace(/\u2028/g, "\\u2028")
1370
- .replace(/\u2029/g, "\\u2029")
1371
- .split("\n")
1372
- .map(line => `// ${line}`);
1373
- }
1374
-
1375
- function dedupe(values: string[]): string[] {
1376
- return [...new Set(values)];
1377
- }