specrails-desktop 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/dist/assets/{ActivityFeedPage-C_clKk8d.js → ActivityFeedPage-BLDMBgwL.js} +1 -1
- package/client/dist/assets/AgentsPage-CFaIiyAA.js +86 -0
- package/client/dist/assets/{AnalyticsPage-DZkVU3yd.js → AnalyticsPage-DY8ECq2R.js} +1 -1
- package/client/dist/assets/{BarChart-Dq1h8eYR.js → BarChart-ouivqe5L.js} +2 -2
- package/client/dist/assets/CodePage-DAFtRafz.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CYhXhJoQ.js → DesktopAnalyticsPage-DIMhuuv0.js} +1 -1
- package/client/dist/assets/{DocsDialog-CS3Syf61.js → DocsDialog-DBYhv3SM.js} +2 -2
- package/client/dist/assets/{DocsPage-l3ZcTO2E.js → DocsPage-DeLRn8W_.js} +2 -2
- package/client/dist/assets/{ExportDropdown-DdB2ncYh.js → ExportDropdown-2XwQ08sz.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-CJ_Om1zu.js → IntegrationsPage-DB-a4FDV.js} +3 -3
- package/client/dist/assets/JobDetailPage-C3lljMiV.js +16 -0
- package/client/dist/assets/JobsPage-CzquwJ6G.js +1 -0
- package/client/dist/assets/LoopBuilderPage-TkoalfW5.js +7 -0
- package/client/dist/assets/LoopsPage-ypooVAQ5.js +1 -0
- package/client/dist/assets/{agents-CQlu1nOm.js → agents-4fnKlwcG.js} +1 -1
- package/client/dist/assets/{agents-DcoUUFZl.js → agents-B-rVIhuw.js} +1 -1
- package/client/dist/assets/{agents-DVYpVGur.js → agents-BRUz6RJs.js} +1 -1
- package/client/dist/assets/{agents-DCX9D3-Q.js → agents-CZQvKDIb.js} +1 -1
- package/client/dist/assets/{agents-BYjUOrLD.js → agents-CuRY-Zg_.js} +1 -1
- package/client/dist/assets/{agents-Jc-CjBDB.js → agents-D7nWnLg6.js} +1 -1
- package/client/dist/assets/{agents-CIhRSOna.js → agents-DzhSlNZd.js} +1 -1
- package/client/dist/assets/{agents-1lhqEcXH.js → agents-E-42N3b1.js} +1 -1
- package/client/dist/assets/{cssMode-DSHXamUH.js → cssMode-BijCvCxs.js} +1 -1
- package/client/dist/assets/{dist-js-Ele_sHO7.js → dist-js-D7m9--SN.js} +1 -1
- package/client/dist/assets/{dist-js-D27I-bfi.js → dist-js-nZhsq1qo.js} +1 -1
- package/client/dist/assets/{editor.main-Cd8qBRAT.js → editor.main-DMIcqwXb.js} +2 -2
- package/client/dist/assets/{freemarker2-CkEKTBm2.js → freemarker2-CP2AizpZ.js} +1 -1
- package/client/dist/assets/{handlebars-BRGx0JpL.js → handlebars-8ElanPOm.js} +1 -1
- package/client/dist/assets/{html-BfRIaGgN.js → html-Mu3b73tG.js} +1 -1
- package/client/dist/assets/{htmlMode-BTpSnsDE.js → htmlMode-tHprGCEv.js} +1 -1
- package/client/dist/assets/index-CDEQ4aU_.js +144 -0
- package/client/dist/assets/{javascript-DMmE2kKN.js → javascript-DlG0apUe.js} +1 -1
- package/client/dist/assets/{jsonMode-hYuaUleY.js → jsonMode-Cr0T4SXO.js} +1 -1
- package/client/dist/assets/{lib-qdRfWwE1.js → lib-Bu76JzdB.js} +2 -2
- package/client/dist/assets/{liquid-C-VbuTr7.js → liquid-QgTgbClC.js} +1 -1
- package/client/dist/assets/{lspLanguageFeatures-BALx-vjn.js → lspLanguageFeatures-D-BxkRza.js} +1 -1
- package/client/dist/assets/mcp-BSCAtU6Y.js +1 -0
- package/client/dist/assets/mcp-Cb6fUaFJ.js +1 -0
- package/client/dist/assets/mcp-CyP0quZ7.js +1 -0
- package/client/dist/assets/mcp-D-zQCO5E.js +1 -0
- package/client/dist/assets/mcp-DCTNWpfB.js +1 -0
- package/client/dist/assets/mcp-DEFGJ1OJ.js +1 -0
- package/client/dist/assets/mcp-DFG9OEXM.js +1 -0
- package/client/dist/assets/mcp-DyERgHA2.js +1 -0
- package/client/dist/assets/{mdx-Cs8MNCzn.js → mdx-B2QTaeRe.js} +1 -1
- package/client/dist/assets/{monaco.contribution-CIvCAJs4.js → monaco.contribution-CkdGfXCC.js} +2 -2
- package/client/dist/assets/{python-CGShLxuI.js → python-BlF0qnMQ.js} +1 -1
- package/client/dist/assets/{razor-CvOBQGd5.js → razor--3PSy3lb.js} +1 -1
- package/client/dist/assets/setup-BiQ7ut8d.js +1 -0
- package/client/dist/assets/setup-BxvjG5Xp.js +1 -0
- package/client/dist/assets/setup-CQ-F6Ciq.js +1 -0
- package/client/dist/assets/setup-D2JlpJEz.js +1 -0
- package/client/dist/assets/setup-DG1R6J6o.js +1 -0
- package/client/dist/assets/setup-DuFJZJsm.js +1 -0
- package/client/dist/assets/setup-DyJszDqm.js +1 -0
- package/client/dist/assets/setup-Ynw19V8w.js +1 -0
- package/client/dist/assets/{tsMode-04uOpiiP.js → tsMode-CHfsvcOI.js} +1 -1
- package/client/dist/assets/{typescript-CX0CwyIZ.js → typescript-Bjxf6BOp.js} +1 -1
- package/client/dist/assets/{upload-DPG87J4t.js → upload-x42OZJr2.js} +1 -1
- package/client/dist/assets/{value-BRVOAvhY.js → value-fEDhg5Bd.js} +1 -1
- package/client/dist/assets/{workers-yr2u4c3C.js → workers-CAAvSda9.js} +1 -1
- package/client/dist/assets/{xml-C0OFlt5X.js → xml-iYCSJ4oL.js} +1 -1
- package/client/dist/assets/{yaml-DgabaPs5.js → yaml-DtO-_yDY.js} +1 -1
- package/client/dist/index.html +10 -9
- package/docs/README.md +2 -0
- package/docs/guide/de/integrations/5-mcp-server.md +74 -0
- package/docs/guide/en/integrations/5-mcp-server.md +74 -0
- package/docs/guide/es/integrations/5-mcp-server.md +74 -0
- package/docs/guide/fr/integrations/5-mcp-server.md +74 -0
- package/docs/guide/it/integrations/5-mcp-server.md +74 -0
- package/docs/guide/ja/integrations/5-mcp-server.md +74 -0
- package/docs/guide/pt/integrations/5-mcp-server.md +74 -0
- package/docs/guide/zh/integrations/5-mcp-server.md +74 -0
- package/docs/mcp.md +249 -0
- package/package.json +8 -5
- package/server/dist/index.js +30 -0
- package/server/dist/mcp/guide.js +82 -0
- package/server/dist/mcp/index.js +21 -0
- package/server/dist/mcp/mcp-admin-router.js +71 -0
- package/server/dist/mcp/mcp-server.js +143 -0
- package/server/dist/mcp/mcp-tiers.js +48 -0
- package/server/dist/mcp/mcp-token.js +119 -0
- package/server/dist/mcp/resources.js +24 -0
- package/server/dist/mcp/tools/agents.js +319 -0
- package/server/dist/mcp/tools/analytics.js +168 -0
- package/server/dist/mcp/tools/app-settings.js +81 -0
- package/server/dist/mcp/tools/catalog.js +45 -0
- package/server/dist/mcp/tools/chat.js +147 -0
- package/server/dist/mcp/tools/code.js +145 -0
- package/server/dist/mcp/tools/jira.js +222 -0
- package/server/dist/mcp/tools/jobs.js +258 -0
- package/server/dist/mcp/tools/loops.js +229 -0
- package/server/dist/mcp/tools/meta.js +96 -0
- package/server/dist/mcp/tools/plugins.js +83 -0
- package/server/dist/mcp/tools/projects.js +62 -0
- package/server/dist/mcp/tools/rails.js +181 -0
- package/server/dist/mcp/tools/setup.js +187 -0
- package/server/dist/mcp/tools/specs.js +327 -0
- package/server/dist/mcp/tools/types.js +137 -0
- package/server/dist/mcp/tools/watch.js +93 -0
- package/server/dist/mobile/mobile-event-bus.js +2 -0
- package/server/dist/project-router-tickets.js +2 -2
- package/server/dist/rails-router.js +5 -0
- package/client/dist/assets/AgentsPage-jcPUtPhP.js +0 -86
- package/client/dist/assets/CodePage-BcbZT88K.js +0 -2
- package/client/dist/assets/JobDetailPage-D5Cxw0md.js +0 -16
- package/client/dist/assets/JobsPage-3v_iX83r.js +0 -1
- package/client/dist/assets/LoopBuilderPage-4s_adZy5.js +0 -7
- package/client/dist/assets/LoopsPage-DA94-o0j.js +0 -1
- package/client/dist/assets/index-meTWPBzY.js +0 -144
- package/client/dist/assets/setup-BOmQ4vh2.js +0 -1
- package/client/dist/assets/setup-BwO2-kuK.js +0 -1
- package/client/dist/assets/setup-CUTNWz-0.js +0 -1
- package/client/dist/assets/setup-Cf3hjChU.js +0 -1
- package/client/dist/assets/setup-CsuXrbJ2.js +0 -1
- package/client/dist/assets/setup-Df4cJOqu.js +0 -1
- package/client/dist/assets/setup-Do0fWx6W.js +0 -1
- package/client/dist/assets/setup-GlkDhBlJ.js +0 -1
- /package/client/dist/assets/{abap-CK68cr9-.js → abap-BFJA_Joo.js} +0 -0
- /package/client/dist/assets/{activity-D4dhNEwO.js → activity-BXBMi28x.js} +0 -0
- /package/client/dist/assets/{activity-usnNa7FX.js → activity-By6I70Ws.js} +0 -0
- /package/client/dist/assets/{activity-BbNkUmW4.js → activity-CfedQaof.js} +0 -0
- /package/client/dist/assets/{activity-B2MvEH54.js → activity-D62CDlQz.js} +0 -0
- /package/client/dist/assets/{activity-DHjfmAyT.js → activity-DAFuSwnt.js} +0 -0
- /package/client/dist/assets/{activity-C7VdmeGb.js → activity-Did0FnrH.js} +0 -0
- /package/client/dist/assets/{activity-Di2f50_Z.js → activity-oLgwaVyt.js} +0 -0
- /package/client/dist/assets/{addon-image-biWX2UUm.js → addon-image-bPlduuUZ.js} +0 -0
- /package/client/dist/assets/{addon-ligatures-D4EiDb5r.js → addon-ligatures-BexpF7xb.js} +0 -0
- /package/client/dist/assets/{addon-webgl-CDCIZQ03.js → addon-webgl-Zy4IOieH.js} +0 -0
- /package/client/dist/assets/{addspec-BxMn39wn.js → addspec-B2fTENuq.js} +0 -0
- /package/client/dist/assets/{addspec-DAveBiCE.js → addspec-BmggyuCD.js} +0 -0
- /package/client/dist/assets/{addspec-1HEtmLv1.js → addspec-CFsLeQKV.js} +0 -0
- /package/client/dist/assets/{addspec-DfuR6EAl.js → addspec-CkHI5c5H.js} +0 -0
- /package/client/dist/assets/{addspec-CO1xv6BL.js → addspec-Cvm06hGe.js} +0 -0
- /package/client/dist/assets/{addspec-IozoiiGs.js → addspec-DwoLTocx.js} +0 -0
- /package/client/dist/assets/{addspec-CNyAUGz_.js → addspec-gthH-H1g.js} +0 -0
- /package/client/dist/assets/{agentstudio-Q0Sq_jTg.js → agentstudio-BtASTJl7.js} +0 -0
- /package/client/dist/assets/{agentstudio-9NbsoGXk.js → agentstudio-CIe9C892.js} +0 -0
- /package/client/dist/assets/{agentstudio-B-jQgYV-.js → agentstudio-CYGk-jjo.js} +0 -0
- /package/client/dist/assets/{agentstudio-Bwa72b1S.js → agentstudio-CwiS3BTp.js} +0 -0
- /package/client/dist/assets/{agentstudio-B1y0JWY-.js → agentstudio-D0bQBWpI.js} +0 -0
- /package/client/dist/assets/{agentstudio-DCyDW8sy.js → agentstudio-Dq-bZrI3.js} +0 -0
- /package/client/dist/assets/{agentstudio-1E-bag77.js → agentstudio-n0pmwz2I.js} +0 -0
- /package/client/dist/assets/{aiedit-qTGI_PGs.js → aiedit-1Z-UYKFb.js} +0 -0
- /package/client/dist/assets/{aiedit-CGxeml8D.js → aiedit-BAJM9Cch.js} +0 -0
- /package/client/dist/assets/{aiedit-CfBNanHE.js → aiedit-CYnsp1Iv.js} +0 -0
- /package/client/dist/assets/{aiedit-CvSspQmE.js → aiedit-CZBZAyeK.js} +0 -0
- /package/client/dist/assets/{aiedit-kAU7KADg.js → aiedit-CunYxmW0.js} +0 -0
- /package/client/dist/assets/{aiedit-DUpe7NxI.js → aiedit-s1T1EMK8.js} +0 -0
- /package/client/dist/assets/{aiedit-DSLtsYIu.js → aiedit-uIDh2tlc.js} +0 -0
- /package/client/dist/assets/{analytics-DBGO8B6-.js → analytics-B95FiMuD.js} +0 -0
- /package/client/dist/assets/{analytics-CgEkAUiP.js → analytics-DPIkvuTS.js} +0 -0
- /package/client/dist/assets/{analytics-DDqve9Ma.js → analytics-DPmCEzvJ.js} +0 -0
- /package/client/dist/assets/{analytics-D1ElChhs.js → analytics-DRexho82.js} +0 -0
- /package/client/dist/assets/{analytics-DEsEAJZL.js → analytics-DVFNjjAv.js} +0 -0
- /package/client/dist/assets/{analytics-BDlELxc7.js → analytics-DZKGAUdm.js} +0 -0
- /package/client/dist/assets/{analytics-CQqu1yE5.js → analytics-g9ASAR_r.js} +0 -0
- /package/client/dist/assets/{apex-BHHyAVNb.js → apex-C1f-z6NO.js} +0 -0
- /package/client/dist/assets/{atom-one-dark-DrWoEEbZ.js → atom-one-dark-CT4DolNR.js} +0 -0
- /package/client/dist/assets/{attachments-3CuBBRFu.js → attachments--jr2QLGv.js} +0 -0
- /package/client/dist/assets/{attachments-pW3zm9nu.js → attachments-BgqfeoOx.js} +0 -0
- /package/client/dist/assets/{attachments-BNgH6Tcu.js → attachments-Bj3HHTXV.js} +0 -0
- /package/client/dist/assets/{attachments-D5fVsMgb.js → attachments-C3skjFyH.js} +0 -0
- /package/client/dist/assets/{attachments-UJtEq892.js → attachments-CnPS8YWu.js} +0 -0
- /package/client/dist/assets/{attachments-CppM7de8.js → attachments-DejzxE0C.js} +0 -0
- /package/client/dist/assets/{attachments-BLqqPGyy.js → attachments-FFb2xHc_.js} +0 -0
- /package/client/dist/assets/{azcli-C56lfIEP.js → azcli-Dr0WBJTP.js} +0 -0
- /package/client/dist/assets/{bat-BLamSaf-.js → bat-BKG3k49W.js} +0 -0
- /package/client/dist/assets/{bicep-lrOcEItS.js → bicep-DXs2wTcS.js} +0 -0
- /package/client/dist/assets/{browser-C91MQBxx.js → browser-BS9t1qMc.js} +0 -0
- /package/client/dist/assets/{browser-B7i7Ysc8.js → browser-BpMo-amf.js} +0 -0
- /package/client/dist/assets/{browser-DwE289IS.js → browser-CsC3NOZ-.js} +0 -0
- /package/client/dist/assets/{browser-C__Vz7KR.js → browser-DS6xnywt.js} +0 -0
- /package/client/dist/assets/{browser-DhwTc0ry.js → browser-Dm6F9bqQ.js} +0 -0
- /package/client/dist/assets/{browser-EFmPpkvO.js → browser-Dm_Of27q.js} +0 -0
- /package/client/dist/assets/{browser-BVQgd1_1.js → browser-U3iouSXn.js} +0 -0
- /package/client/dist/assets/{cameligo-BK_p0bgU.js → cameligo-Detknuxp.js} +0 -0
- /package/client/dist/assets/{chat-PVakYp92.js → chat-B2tWTbZL.js} +0 -0
- /package/client/dist/assets/{chat-BnJPyeUK.js → chat-CXOJHDUZ.js} +0 -0
- /package/client/dist/assets/{chat-CE8bx6Rz.js → chat-Cy78lw1n.js} +0 -0
- /package/client/dist/assets/{chat-UMl6H55K.js → chat-D78VMi-v.js} +0 -0
- /package/client/dist/assets/{chat-BXVx6rb5.js → chat-De1RgOXU.js} +0 -0
- /package/client/dist/assets/{chat-C5nJF6L0.js → chat-DyTbH-0F.js} +0 -0
- /package/client/dist/assets/{chat-9RcD5Lcn.js → chat-sqZyn0C4.js} +0 -0
- /package/client/dist/assets/{clojure-BQ7JOykv.js → clojure-CCu4UNKg.js} +0 -0
- /package/client/dist/assets/{clsx-CjLeUJfG.js → clsx-pVRQNNR0.js} +0 -0
- /package/client/dist/assets/{code-u9Sg9ohM.js → code-6LokPmIE.js} +0 -0
- /package/client/dist/assets/{code-OoLqhjoI.js → code-CLTuhuvM.js} +0 -0
- /package/client/dist/assets/{code-Lg4awa35.js → code-CwGlPuwb.js} +0 -0
- /package/client/dist/assets/{code-BPMR8_Vp.js → code-DFCDV5e1.js} +0 -0
- /package/client/dist/assets/{code-eh4vO2-_.js → code-Djq48g0s.js} +0 -0
- /package/client/dist/assets/{code-aCUqkVN_.js → code-Do27r8WG.js} +0 -0
- /package/client/dist/assets/{code-BsOBm1Fb.js → code-Drc7f197.js} +0 -0
- /package/client/dist/assets/{coffee-BD3DiL5s.js → coffee-Bam7Y9IR.js} +0 -0
- /package/client/dist/assets/{commands-B9XEyMj-.js → commands-CxReviyc.js} +0 -0
- /package/client/dist/assets/{commands-Ic1DJDxm.js → commands-CzP1qQyD.js} +0 -0
- /package/client/dist/assets/{commands-BdI0CtOL.js → commands-DGP46UxM.js} +0 -0
- /package/client/dist/assets/{commands-DSX3yJyr.js → commands-DQvoMqa7.js} +0 -0
- /package/client/dist/assets/{commands-yNn4IA4C.js → commands-DeKaz04Q.js} +0 -0
- /package/client/dist/assets/{commands-BI1Ta-E7.js → commands-DwYhwQsF.js} +0 -0
- /package/client/dist/assets/{commands-C2ewlyXo.js → commands-lm6Vlght.js} +0 -0
- /package/client/dist/assets/{common-CzCQ0rpW.js → common-B0crwfK0.js} +0 -0
- /package/client/dist/assets/{common-CNoRVpnA.js → common-BlqmiTj4.js} +0 -0
- /package/client/dist/assets/{common-CcHFVATT.js → common-CIUPtPdu.js} +0 -0
- /package/client/dist/assets/{common-LDwESeCX.js → common-C_-0fZsp.js} +0 -0
- /package/client/dist/assets/{common-C9VyfY7g.js → common-DH9A-wTo.js} +0 -0
- /package/client/dist/assets/{common-Dmpyh323.js → common-DJM41wFl.js} +0 -0
- /package/client/dist/assets/{common-ULlk84G4.js → common-SyTaRc_K.js} +0 -0
- /package/client/dist/assets/{cpp-splnCwCh.js → cpp-BhOkAe-9.js} +0 -0
- /package/client/dist/assets/{csharp-BCpsl-o3.js → csharp-BgHArJNx.js} +0 -0
- /package/client/dist/assets/{csp-tkCWrZmb.js → csp-Xzh56_3-.js} +0 -0
- /package/client/dist/assets/{css-B_xdnReq.js → css-CD7-RYRS.js} +0 -0
- /package/client/dist/assets/{cypher-OL1zq-y3.js → cypher-l2xAdF3X.js} +0 -0
- /package/client/dist/assets/{dart-CtTlOkrm.js → dart-CDOWeyzt.js} +0 -0
- /package/client/dist/assets/{dashboard-C-dXxut-.js → dashboard-B1rs1wki.js} +0 -0
- /package/client/dist/assets/{dashboard-CUjyhohl.js → dashboard-D9QPCmv3.js} +0 -0
- /package/client/dist/assets/{dashboard-C52PElPV.js → dashboard-Dxgb47lO.js} +0 -0
- /package/client/dist/assets/{dashboard-03EpOoMM.js → dashboard-TuElRCZX.js} +0 -0
- /package/client/dist/assets/{dashboard-Ch6H-457.js → dashboard-UWXs9_wy.js} +0 -0
- /package/client/dist/assets/{dashboard-zH9quXks.js → dashboard-ajT8usAi.js} +0 -0
- /package/client/dist/assets/{dashboard-C2owt3eN.js → dashboard-zwN2xswo.js} +0 -0
- /package/client/dist/assets/{dockerfile-DcQ5CNiW.js → dockerfile-Dv8atx7v.js} +0 -0
- /package/client/dist/assets/{ecl-DZpiDRt6.js → ecl-DIYTlbD_.js} +0 -0
- /package/client/dist/assets/{editor.api2-Cwla1nvV.js → editor.api2-CrNrMVfe.js} +0 -0
- /package/client/dist/assets/{elixir-Dzj2lqtK.js → elixir-CR7PLJja.js} +0 -0
- /package/client/dist/assets/{explore-4SD1DxR0.js → explore-BYFCCka4.js} +0 -0
- /package/client/dist/assets/{explore-xQoaak6e.js → explore-CE0wSNV1.js} +0 -0
- /package/client/dist/assets/{explore-CZt4Ncpa.js → explore-CSDcRUNj.js} +0 -0
- /package/client/dist/assets/{explore-DQ0XEjed.js → explore-CvM08PpD.js} +0 -0
- /package/client/dist/assets/{explore-CCcPXrHg.js → explore-Jkw93usp.js} +0 -0
- /package/client/dist/assets/{explore-BxR9q8Hq.js → explore-kK239uoZ.js} +0 -0
- /package/client/dist/assets/{explore-B2n8r9Ye.js → explore-mLI5eXks.js} +0 -0
- /package/client/dist/assets/{flow9-CFENCHIZ.js → flow9-DCK5-6NG.js} +0 -0
- /package/client/dist/assets/{format-command-CMsBTOPN.js → format-command-rVyGPxWE.js} +0 -0
- /package/client/dist/assets/{fsharp-D-4bv7FB.js → fsharp-DJ1xiUVU.js} +0 -0
- /package/client/dist/assets/{go-djxbTJq7.js → go-BEvbHcbb.js} +0 -0
- /package/client/dist/assets/{graphql-DHIFrkZ6.js → graphql-DN1ktXnz.js} +0 -0
- /package/client/dist/assets/{hcl-CXVoowNb.js → hcl-DJaIjIEr.js} +0 -0
- /package/client/dist/assets/{ini-Crzc5kly.js → ini-PlKfLEnb.js} +0 -0
- /package/client/dist/assets/{integrations-A9f08E0o.js → integrations-B8hT0Qf_.js} +0 -0
- /package/client/dist/assets/{integrations-DwSVdCpf.js → integrations-Bdk5dMyv.js} +0 -0
- /package/client/dist/assets/{integrations-B46ac3sf.js → integrations-CYqz7YFS.js} +0 -0
- /package/client/dist/assets/{integrations-9qxUNhPD.js → integrations-Cj_BsXxJ.js} +0 -0
- /package/client/dist/assets/{integrations-B17KyA-i.js → integrations-D8M8eJH2.js} +0 -0
- /package/client/dist/assets/{integrations-DkBi-Gsh.js → integrations-TJuDBl-9.js} +0 -0
- /package/client/dist/assets/{integrations-C7u7SInG.js → integrations-TwQV3sLw.js} +0 -0
- /package/client/dist/assets/{java-CYLaJrdE.js → java-3PUwMlx-.js} +0 -0
- /package/client/dist/assets/{jira-01H1RfQj.js → jira--rI5PfuE.js} +0 -0
- /package/client/dist/assets/{jira-CBU0200d.js → jira-BoRyckbb.js} +0 -0
- /package/client/dist/assets/{jira-BpnpUFlb.js → jira-C95FN3yk.js} +0 -0
- /package/client/dist/assets/{jira-DzIVvNmy.js → jira-CNUUgTs4.js} +0 -0
- /package/client/dist/assets/{jira-Dht8qNck.js → jira-DHFnvEm8.js} +0 -0
- /package/client/dist/assets/{jira-4Fin3fu7.js → jira-NhkbrDXe.js} +0 -0
- /package/client/dist/assets/{jira-0ldfMByG.js → jira-wiIvGma5.js} +0 -0
- /package/client/dist/assets/{jobs-Oz3UtLQn.js → jobs-Bjp7v9aA.js} +0 -0
- /package/client/dist/assets/{jobs-jEQidSIb.js → jobs-Bjt5PyVv.js} +0 -0
- /package/client/dist/assets/{jobs-BF9raUkL.js → jobs-BmTs4Bn3.js} +0 -0
- /package/client/dist/assets/{jobs-DcCONARH.js → jobs-Cchy-18B.js} +0 -0
- /package/client/dist/assets/{jobs-DM9owF1j.js → jobs-D2QVTPbz.js} +0 -0
- /package/client/dist/assets/{jobs-Bw4hqEro.js → jobs-LK1ImiDH.js} +0 -0
- /package/client/dist/assets/{jobs-ym72SIR9.js → jobs-yPz4Dvi-.js} +0 -0
- /package/client/dist/assets/{julia-BJ7tUU-T.js → julia-DC6LnY15.js} +0 -0
- /package/client/dist/assets/{kotlin-CeWcU3lC.js → kotlin-CoGntbZF.js} +0 -0
- /package/client/dist/assets/{less-CviwWNG4.js → less-DCFflTXL.js} +0 -0
- /package/client/dist/assets/{lexon-Dt28jqDS.js → lexon-BTOivnjP.js} +0 -0
- /package/client/dist/assets/{loops-Fv4Bs-9A.js → loops-BFIcYvpy.js} +0 -0
- /package/client/dist/assets/{loops-D_ZxkD4l.js → loops-BU314kSd.js} +0 -0
- /package/client/dist/assets/{loops-DE-2cmLw.js → loops-C3OaXou9.js} +0 -0
- /package/client/dist/assets/{loops-mcoeFMat.js → loops-Cv6Aeszq.js} +0 -0
- /package/client/dist/assets/{loops-B_w7STrw.js → loops-IQe23ef6.js} +0 -0
- /package/client/dist/assets/{loops-BYNUJVyD.js → loops-LqZ4MC6J.js} +0 -0
- /package/client/dist/assets/{loops-B_gS4Pqu.js → loops-SbkOdciY.js} +0 -0
- /package/client/dist/assets/{lua-B6AOaJSi.js → lua-ClKCZMmP.js} +0 -0
- /package/client/dist/assets/{m3-VnMkHJcy.js → m3-25hKFWMb.js} +0 -0
- /package/client/dist/assets/{markdown-8vKNdvIM.js → markdown-BGAj9q0K.js} +0 -0
- /package/client/dist/assets/{mips-DoEcuzan.js → mips-BrVgY2Pf.js} +0 -0
- /package/client/dist/assets/{msdax-CNzvHyEp.js → msdax-7EhcRsm8.js} +0 -0
- /package/client/dist/assets/{mysql-Buk3vUtL.js → mysql-B5_atCBH.js} +0 -0
- /package/client/dist/assets/{nav-_0vAgZ_k.js → nav-B2Cv1vjz.js} +0 -0
- /package/client/dist/assets/{nav-Cw3_40fA.js → nav-Br2AivTc.js} +0 -0
- /package/client/dist/assets/{nav-DNDJ3H-_.js → nav-CFofO7QO.js} +0 -0
- /package/client/dist/assets/{nav-CCz4krPP.js → nav-CyHKTZxn.js} +0 -0
- /package/client/dist/assets/{nav-B5W9IMnf.js → nav-DqyLGJmF.js} +0 -0
- /package/client/dist/assets/{nav-CrG_mYRr.js → nav-_-siCrRv.js} +0 -0
- /package/client/dist/assets/{nav-DbAbG5_k.js → nav-rO4Nqnh-.js} +0 -0
- /package/client/dist/assets/{nav-DVp1nEsi.js → nav-zyBhf29M.js} +0 -0
- /package/client/dist/assets/{objective-c-DfOmda-5.js → objective-c-CLUvUl9T.js} +0 -0
- /package/client/dist/assets/{pascal-DoAA2yjy.js → pascal-BnU1HU42.js} +0 -0
- /package/client/dist/assets/{pascaligo-C65Vn1fu.js → pascaligo-dsKvjFzi.js} +0 -0
- /package/client/dist/assets/{perl-INlfQ_ro.js → perl-CZne1GYG.js} +0 -0
- /package/client/dist/assets/{pgsql-DtwRzglx.js → pgsql-CMa5W_iF.js} +0 -0
- /package/client/dist/assets/{php-DfHqVNet.js → php-Cu4mS35J.js} +0 -0
- /package/client/dist/assets/{pla-D3dNVRYk.js → pla-B7COwnnm.js} +0 -0
- /package/client/dist/assets/{postiats-UKXD1rM4.js → postiats-GLeNtnBQ.js} +0 -0
- /package/client/dist/assets/{powerquery-zE3E3O7s.js → powerquery-BqPf3R1W.js} +0 -0
- /package/client/dist/assets/{powershell-Df9b3zQl.js → powershell-D9btb5su.js} +0 -0
- /package/client/dist/assets/{protobuf-BF0nUVrW.js → protobuf-6GHSuaNm.js} +0 -0
- /package/client/dist/assets/{pug-Bj6Yikv_.js → pug-ct6rYKQd.js} +0 -0
- /package/client/dist/assets/{qsharp-BI4h-raZ.js → qsharp-CIrAQNPy.js} +0 -0
- /package/client/dist/assets/{r-DhpnmaXx.js → r-Bk-a0sON.js} +0 -0
- /package/client/dist/assets/{redis-DwYYPr4d.js → redis-C9yfaWYh.js} +0 -0
- /package/client/dist/assets/{redshift-D2f3Bc-4.js → redshift-DaOT1jRU.js} +0 -0
- /package/client/dist/assets/{restructuredtext-Dkiif6fe.js → restructuredtext-C1t9LQfS.js} +0 -0
- /package/client/dist/assets/{ruby-C6hLmHy0.js → ruby-COiPZJjc.js} +0 -0
- /package/client/dist/assets/{rust-X8WAGtxB.js → rust--gZOJZzj.js} +0 -0
- /package/client/dist/assets/{sb-4M2la0Kr.js → sb-NHf3iazF.js} +0 -0
- /package/client/dist/assets/{scala-CaCXz58E.js → scala-D1nO1TBd.js} +0 -0
- /package/client/dist/assets/{scheme-CMdjpmP7.js → scheme-Bref42s5.js} +0 -0
- /package/client/dist/assets/{scss-QopmvPyp.js → scss-BS9CCLr3.js} +0 -0
- /package/client/dist/assets/{settings-DyBxunCh.js → settings-BN4dufu6.js} +0 -0
- /package/client/dist/assets/{settings-BN7OusER.js → settings-Bap_q3rt.js} +0 -0
- /package/client/dist/assets/{settings-Bi5FFkIw.js → settings-BlYjWMy5.js} +0 -0
- /package/client/dist/assets/{settings-iQe5Dim-.js → settings-C7t_uUz4.js} +0 -0
- /package/client/dist/assets/{settings-fk-WlnUt.js → settings-CIFrUsKt.js} +0 -0
- /package/client/dist/assets/{settings-BA1V6KdK.js → settings-Cbj9VWmI.js} +0 -0
- /package/client/dist/assets/{settings-CRJxyTNN.js → settings-D7saHMU8.js} +0 -0
- /package/client/dist/assets/{settings-0m2iuTzd.js → settings-DrRjM5e8.js} +0 -0
- /package/client/dist/assets/{shell-CDBchS00.js → shell-DT3joMBB.js} +0 -0
- /package/client/dist/assets/{solidity-DvBfi5mC.js → solidity-o2Fm9dvg.js} +0 -0
- /package/client/dist/assets/{sophia-HSnXR17u.js → sophia-Dj1N6Xqb.js} +0 -0
- /package/client/dist/assets/{sparql-DhG_ppup.js → sparql-D2fjpTWm.js} +0 -0
- /package/client/dist/assets/{specs-BvsFNKTs.js → specs-C-WXGBqy.js} +0 -0
- /package/client/dist/assets/{specs-D4ORogcT.js → specs-C57koADY.js} +0 -0
- /package/client/dist/assets/{specs-CnH7dOiF.js → specs-CCXV-eSF.js} +0 -0
- /package/client/dist/assets/{specs-CUE3xSFM.js → specs-CYA5Bv7Q.js} +0 -0
- /package/client/dist/assets/{specs-C2wW5bq8.js → specs-DFCFg51O.js} +0 -0
- /package/client/dist/assets/{specs-C_8j4ELZ.js → specs-Dslng_yW.js} +0 -0
- /package/client/dist/assets/{specs-D6Y76Bgi.js → specs-DwRPiJTc.js} +0 -0
- /package/client/dist/assets/{specs-Bku8SypT.js → specs-DzlROLy7.js} +0 -0
- /package/client/dist/assets/{sql-DHeIS-tf.js → sql-BArhsPUz.js} +0 -0
- /package/client/dist/assets/{st-CMWchiRU.js → st-Bd2-tALj.js} +0 -0
- /package/client/dist/assets/{swift-CDXvBefj.js → swift-DvEpXBKj.js} +0 -0
- /package/client/dist/assets/{systemverilog-0RTukYUE.js → systemverilog-mHEbsnVH.js} +0 -0
- /package/client/dist/assets/{tcl-CbBF_Ttl.js → tcl-DePutp1e.js} +0 -0
- /package/client/dist/assets/{terminal-CB33IsO1.js → terminal-2S-Wsk6L.js} +0 -0
- /package/client/dist/assets/{terminal-Cz_Puz0q.js → terminal-BRQMrU74.js} +0 -0
- /package/client/dist/assets/{terminal-DFEH9XTQ.js → terminal-BTiemy98.js} +0 -0
- /package/client/dist/assets/{terminal-BTanJIt_.js → terminal-CjQeWfpp.js} +0 -0
- /package/client/dist/assets/{terminal-CY_sPfdD.js → terminal-CmP5riTU.js} +0 -0
- /package/client/dist/assets/{terminal-BLh00ouM.js → terminal-CnObBCAh.js} +0 -0
- /package/client/dist/assets/{terminal-CR6t-Oey.js → terminal-DcPKgKMN.js} +0 -0
- /package/client/dist/assets/{terminal-Cj4HzP3p.js → terminal-XciDq6VV.js} +0 -0
- /package/client/dist/assets/{tickets-OrJwUxJR.js → tickets-7kl_0jgM.js} +0 -0
- /package/client/dist/assets/{tickets-68LH1xp6.js → tickets-BSyaZI8d.js} +0 -0
- /package/client/dist/assets/{tickets-blQnNA4N.js → tickets-CcTcvwKS.js} +0 -0
- /package/client/dist/assets/{tickets-D6f3GSYn.js → tickets-D75vyZJj.js} +0 -0
- /package/client/dist/assets/{tickets-PtHNxQo5.js → tickets-D79ZT2SZ.js} +0 -0
- /package/client/dist/assets/{tickets-DBQXMYYA.js → tickets-DbcMWq5L.js} +0 -0
- /package/client/dist/assets/{tickets-jxsTrApR.js → tickets-ZA_2iPd1.js} +0 -0
- /package/client/dist/assets/{tickets-REr-EQx9.js → tickets-mxd23fWX.js} +0 -0
- /package/client/dist/assets/{twig-DCcGd8FE.js → twig-DQ2zIX0b.js} +0 -0
- /package/client/dist/assets/{typespec-HTphp-Uh.js → typespec-DPoaiGTJ.js} +0 -0
- /package/client/dist/assets/{vb-Cb_yUcVd.js → vb-Jo98wdMr.js} +0 -0
- /package/client/dist/assets/{wgsl-DrKU3YXj.js → wgsl-DZc4h_eS.js} +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupTools = setupTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
/**
|
|
7
|
+
* The setup-wizard domain facade. Mixes APP-LEVEL operations (prerequisites,
|
|
8
|
+
* available_providers, add_project — mounted at `/api` with NO `:projectId`)
|
|
9
|
+
* with PER-PROJECT operations (install-config, install, checkpoints, abort,
|
|
10
|
+
* integration-contract, enrich — under `/api/projects/:projectId/*`).
|
|
11
|
+
*
|
|
12
|
+
* LIFECYCLE the caller must respect:
|
|
13
|
+
* 1. prerequisites → must be ok before adding a project
|
|
14
|
+
* 2. add_project → creates the ProjectContext + slug + registry mirror + workspace
|
|
15
|
+
* 3. install_config (one POST per provider for multi-provider projects)
|
|
16
|
+
* 4. install → 202 async; the real result arrives only via WS / checkpoints
|
|
17
|
+
* 5. poll checkpoints (or stream WS) until isInstalling === false
|
|
18
|
+
* 6. optional enrich / enrich_message for the legacy full AI flow
|
|
19
|
+
*
|
|
20
|
+
* The async actions (install, enrich, enrich_message) return immediately (HTTP
|
|
21
|
+
* 202) and do the real, long-running work in a detached child whose only output
|
|
22
|
+
* channel is WebSocket — so they return a hint to poll `checkpoints` (which also
|
|
23
|
+
* exposes isInstalling/isEnriching/logLines/summary) for true completion.
|
|
24
|
+
*/
|
|
25
|
+
function setupTools() {
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
name: 'specrails_setup',
|
|
29
|
+
title: 'Setup & Project Provisioning',
|
|
30
|
+
description: 'Add and provision Specrails projects via the setup wizard, and inspect setup prerequisites/providers. ' +
|
|
31
|
+
'App-level actions ignore projectId: prerequisites (probe node/npm/npx/git + provider CLIs), ' +
|
|
32
|
+
'available_providers (which AI CLIs are detected + install tiers), add_project (register a repo path). ' +
|
|
33
|
+
'Per-project actions: install_config (persist install-config.yaml), install (spawn offline framework scaffold — async/202), ' +
|
|
34
|
+
'checkpoints (poll live setup/install/enrich state — the canonical completion signal when not streaming WS), ' +
|
|
35
|
+
'abort (SIGTERM/SIGKILL the in-progress install+enrich child), integration_contract (read the ticket-provider contract), ' +
|
|
36
|
+
'enrich (spawn the legacy full AI flow — async/202, COST-INCURRING), enrich_message (resume an enrich session with a reply — async, COST-INCURRING).',
|
|
37
|
+
hintTier: 'read',
|
|
38
|
+
tier: (a) => {
|
|
39
|
+
const action = a.action;
|
|
40
|
+
if (action === 'abort')
|
|
41
|
+
return 'destructive';
|
|
42
|
+
if (action === 'install' || action === 'enrich' || action === 'enrich_message')
|
|
43
|
+
return 'ai-spawn';
|
|
44
|
+
if (action === 'add_project' || action === 'install_config')
|
|
45
|
+
return 'write';
|
|
46
|
+
return 'read';
|
|
47
|
+
},
|
|
48
|
+
inputSchema: {
|
|
49
|
+
action: zod_1.z
|
|
50
|
+
.enum([
|
|
51
|
+
'prerequisites',
|
|
52
|
+
'available_providers',
|
|
53
|
+
'add_project',
|
|
54
|
+
'install_config',
|
|
55
|
+
'install',
|
|
56
|
+
'checkpoints',
|
|
57
|
+
'abort',
|
|
58
|
+
'integration_contract',
|
|
59
|
+
'enrich',
|
|
60
|
+
'enrich_message',
|
|
61
|
+
])
|
|
62
|
+
.describe('Setup operation to perform'),
|
|
63
|
+
projectId: zod_1.z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe('Project id for per-project actions (install_config/install/checkpoints/abort/integration_contract/enrich/enrich_message). Ignored by app-level actions.'),
|
|
67
|
+
// prerequisites
|
|
68
|
+
diagnostic: zod_1.z
|
|
69
|
+
.boolean()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('prerequisites: when true, include the resolved PATH segments/sources + per-tool which results'),
|
|
72
|
+
// add_project
|
|
73
|
+
path: zod_1.z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe('add_project: filesystem path of the repo to register (required for add_project)'),
|
|
77
|
+
name: zod_1.z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe('add_project: display name (defaults to the directory basename)'),
|
|
81
|
+
provider: zod_1.z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('add_project: legacy single primary provider; install/enrich: which per-provider install-config to read'),
|
|
85
|
+
providers: zod_1.z
|
|
86
|
+
.array(zod_1.z.string())
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('add_project: providers to install (first = primary/default); de-duped server-side'),
|
|
89
|
+
// install_config
|
|
90
|
+
config: zod_1.z
|
|
91
|
+
.record(zod_1.z.unknown())
|
|
92
|
+
.optional()
|
|
93
|
+
.describe('install_config: the install-config object to serialize (e.g. { tier:"quick", provider, agents }); required for install_config'),
|
|
94
|
+
// enrich_message
|
|
95
|
+
sessionId: zod_1.z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe('enrich_message: the enrich session id to resume (required for enrich_message)'),
|
|
99
|
+
message: zod_1.z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('enrich_message: the user reply to send into the enrich session (required for enrich_message)'),
|
|
103
|
+
},
|
|
104
|
+
async handler(ctx, args) {
|
|
105
|
+
const action = args.action;
|
|
106
|
+
switch (action) {
|
|
107
|
+
// ─── App-level (no projectId) ───────────────────────────────────────
|
|
108
|
+
case 'prerequisites': {
|
|
109
|
+
const qs = args.diagnostic === true ? '?diagnostic=1' : '';
|
|
110
|
+
return (0, types_1.apiCall)(ctx, 'GET', `/setup-prerequisites${qs}`);
|
|
111
|
+
}
|
|
112
|
+
case 'available_providers':
|
|
113
|
+
return (0, types_1.apiCall)(ctx, 'GET', '/available-providers');
|
|
114
|
+
case 'add_project': {
|
|
115
|
+
const path = args.path;
|
|
116
|
+
if (!path)
|
|
117
|
+
throw new Error('add_project requires a "path".');
|
|
118
|
+
const body = { path };
|
|
119
|
+
if (typeof args.name === 'string')
|
|
120
|
+
body.name = args.name;
|
|
121
|
+
if (typeof args.provider === 'string')
|
|
122
|
+
body.provider = args.provider;
|
|
123
|
+
if (Array.isArray(args.providers))
|
|
124
|
+
body.providers = args.providers;
|
|
125
|
+
return (0, types_1.apiCall)(ctx, 'POST', '/projects', body);
|
|
126
|
+
}
|
|
127
|
+
// ─── Per-project ────────────────────────────────────────────────────
|
|
128
|
+
case 'install_config': {
|
|
129
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
130
|
+
const config = args.config;
|
|
131
|
+
if (config === undefined || typeof config !== 'object' || Array.isArray(config)) {
|
|
132
|
+
throw new Error('install_config requires a "config" object.');
|
|
133
|
+
}
|
|
134
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/setup/install-config`, config);
|
|
135
|
+
}
|
|
136
|
+
case 'install': {
|
|
137
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
138
|
+
const body = {};
|
|
139
|
+
if (typeof args.provider === 'string')
|
|
140
|
+
body.provider = args.provider;
|
|
141
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/setup/install`, body);
|
|
142
|
+
return {
|
|
143
|
+
...(typeof r === 'object' && r !== null ? r : { result: r }),
|
|
144
|
+
hint: 'Install is async (HTTP 202). Poll specrails_setup(checkpoints) until isInstalling === false (and watch summary for the result), or stream the setup_install_done / setup_error WS events.',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
case 'checkpoints': {
|
|
148
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
149
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/setup/checkpoints`);
|
|
150
|
+
}
|
|
151
|
+
case 'abort': {
|
|
152
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
153
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/setup/abort`, {});
|
|
154
|
+
}
|
|
155
|
+
case 'integration_contract': {
|
|
156
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
157
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/integration-contract`);
|
|
158
|
+
}
|
|
159
|
+
case 'enrich': {
|
|
160
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
161
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/enrich/start`, {});
|
|
162
|
+
return {
|
|
163
|
+
...(typeof r === 'object' && r !== null ? r : { result: r }),
|
|
164
|
+
hint: 'Enrich is async (HTTP 202) and COST-INCURRING. Poll specrails_setup(checkpoints) until isEnriching === false, or stream the setup_complete / setup_turn_done / setup_error WS events. Use enrich_message to answer clarifying questions.',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
case 'enrich_message': {
|
|
168
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
169
|
+
const sessionId = args.sessionId;
|
|
170
|
+
const message = args.message;
|
|
171
|
+
if (!sessionId)
|
|
172
|
+
throw new Error('enrich_message requires a "sessionId".');
|
|
173
|
+
if (!message || !message.trim())
|
|
174
|
+
throw new Error('enrich_message requires a non-empty "message".');
|
|
175
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/enrich/message`, { sessionId, message });
|
|
176
|
+
return {
|
|
177
|
+
...(typeof r === 'object' && r !== null ? r : { result: r }),
|
|
178
|
+
hint: 'Enrich-message is async (HTTP 202) and COST-INCURRING. Poll specrails_setup(checkpoints) until isEnriching === false, or stream the setup_turn_done / setup_error WS events.',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`Unknown action "${action}".`);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.specsTools = specsTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
// Per-action danger classification for the specs (tickets) facade.
|
|
7
|
+
const DESTRUCTIVE_ACTIONS = new Set(['delete', 'smash_undo', 'delete_epic_children']);
|
|
8
|
+
// `create` routes through Quick Add Spec (generate-spec) so MCP-created specs
|
|
9
|
+
// match the app's "Add Spec → Quick" flow — hence ai-spawn, not write.
|
|
10
|
+
const AI_SPAWN_ACTIONS = new Set(['create', 'generate', 'ai_edit', 'contract_refine', 'smash']);
|
|
11
|
+
// `commit_draft` (POST /tickets/from-draft) may fire-and-forget a Contract
|
|
12
|
+
// Refine spawn as a side-effect, but the call itself is a synchronous store
|
|
13
|
+
// mutation (a write), so it is classified as `write`.
|
|
14
|
+
const WRITE_ACTIONS = new Set([
|
|
15
|
+
'update', 'from_prompt', 'save_draft', 'commit_draft', 'cancel_ai_edit',
|
|
16
|
+
]);
|
|
17
|
+
function resolveSpecsTier(action) {
|
|
18
|
+
if (DESTRUCTIVE_ACTIONS.has(action))
|
|
19
|
+
return 'destructive';
|
|
20
|
+
if (AI_SPAWN_ACTIONS.has(action))
|
|
21
|
+
return 'ai-spawn';
|
|
22
|
+
if (WRITE_ACTIONS.has(action))
|
|
23
|
+
return 'write';
|
|
24
|
+
return 'read';
|
|
25
|
+
}
|
|
26
|
+
const WATCH_HINT = 'Returns 202 immediately; the actual result streams over the WebSocket. ' +
|
|
27
|
+
'Use specrails_watch with the returned requestId/scheduled ref to await completion ' +
|
|
28
|
+
'(WS events: spec_gen_*, ticket_ai_edit_*, explore.contract_refine_*, smash.*).';
|
|
29
|
+
function specsTools() {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
name: 'specrails_specs',
|
|
33
|
+
title: 'Specs (Tickets)',
|
|
34
|
+
description: 'Manage the spec/ticket backlog for a project (the local-tickets.json store). ' +
|
|
35
|
+
'Actions: list, get, ' +
|
|
36
|
+
'create (ai-spawn — THE way to add a spec: routes through Quick Add Spec, so the specrails agent generates a structured spec with AI from the user request, exactly matching the app\'s "Add Spec → Quick" flow; pass the request as title/description, it becomes the idea; async), ' +
|
|
37
|
+
'update, delete (destructive), ' +
|
|
38
|
+
'from_prompt (verbatim insert, no AI — use ONLY when you already have a complete finished spec to store as-is), ' +
|
|
39
|
+
'save_draft (persist an Explore conversation as a draft), commit_draft (flip a draft to a real spec), ' +
|
|
40
|
+
'generate (alias of create — Quick AI spec generation), ai_edit (ai-spawn, AI-edit a ticket title+description), ' +
|
|
41
|
+
'cancel_ai_edit (abort an in-flight ai-edit), contract_refine (ai-spawn, append a Contract Layer — Claude-only), ' +
|
|
42
|
+
'smash (ai-spawn, decompose a spec into N child sub-specs under an epic — Claude-only), ' +
|
|
43
|
+
'smash_undo (destructive, reverse a prior SMASH), delete_epic_children (destructive, delete all children of an epic), ' +
|
|
44
|
+
'spending_summary (per-ticket AI spend), files_touched (provenance of files an AI job touched for this ticket), ' +
|
|
45
|
+
'list_attachments, get_attachment. The generate/ai_edit/contract_refine/smash actions are async (HTTP 202) — ' +
|
|
46
|
+
'results arrive over the WebSocket, not the HTTP response.',
|
|
47
|
+
hintTier: 'read',
|
|
48
|
+
tier: (args) => resolveSpecsTier(args.action),
|
|
49
|
+
inputSchema: {
|
|
50
|
+
action: zod_1.z
|
|
51
|
+
.enum([
|
|
52
|
+
'list',
|
|
53
|
+
'get',
|
|
54
|
+
'create',
|
|
55
|
+
'update',
|
|
56
|
+
'delete',
|
|
57
|
+
'from_prompt',
|
|
58
|
+
'save_draft',
|
|
59
|
+
'commit_draft',
|
|
60
|
+
'generate',
|
|
61
|
+
'ai_edit',
|
|
62
|
+
'cancel_ai_edit',
|
|
63
|
+
'contract_refine',
|
|
64
|
+
'smash',
|
|
65
|
+
'smash_undo',
|
|
66
|
+
'delete_epic_children',
|
|
67
|
+
'spending_summary',
|
|
68
|
+
'files_touched',
|
|
69
|
+
'list_attachments',
|
|
70
|
+
'get_attachment',
|
|
71
|
+
])
|
|
72
|
+
.describe('Operation to perform'),
|
|
73
|
+
projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
|
|
74
|
+
// ── Ticket identity ──────────────────────────────────────────────
|
|
75
|
+
id: zod_1.z
|
|
76
|
+
.number()
|
|
77
|
+
.int()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Numeric ticket id (get/update/delete/contract_refine/smash/smash_undo/delete_epic_children/spending_summary/files_touched/ai_edit)'),
|
|
80
|
+
// ── list filters ─────────────────────────────────────────────────
|
|
81
|
+
status: zod_1.z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('list: filter by status (CSV: draft|todo|in_progress|done|cancelled). create/update: set ticket status'),
|
|
85
|
+
label: zod_1.z.string().optional().describe('list: filter by label (CSV)'),
|
|
86
|
+
q: zod_1.z.string().optional().describe('list: free-text search over title + description'),
|
|
87
|
+
// ── create / update / from_prompt / commit_draft fields ──────────
|
|
88
|
+
title: zod_1.z.string().optional().describe('Ticket title (required for create / commit_draft)'),
|
|
89
|
+
description: zod_1.z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Ticket description. For ai_edit this is the REQUIRED current baseline description'),
|
|
93
|
+
priority: zod_1.z
|
|
94
|
+
.enum(['critical', 'high', 'medium', 'low'])
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Ticket priority (defaults to medium; may be null only for drafts)'),
|
|
97
|
+
labels: zod_1.z.array(zod_1.z.string()).optional().describe('Labels array'),
|
|
98
|
+
assignee: zod_1.z.string().optional().describe('create/update: assignee'),
|
|
99
|
+
prerequisites: zod_1.z.array(zod_1.z.number().int()).optional().describe('create/update: prerequisite ticket ids'),
|
|
100
|
+
metadata: zod_1.z.record(zod_1.z.unknown()).optional().describe('create/update: free-form metadata object'),
|
|
101
|
+
source: zod_1.z.string().optional().describe('create: source tag'),
|
|
102
|
+
acceptanceCriteria: zod_1.z
|
|
103
|
+
.array(zod_1.z.string())
|
|
104
|
+
.optional()
|
|
105
|
+
.describe('update/commit_draft: acceptance-criteria bullets folded into the description'),
|
|
106
|
+
shortSummary: zod_1.z.string().optional().describe('commit_draft: explicit short summary (camelCase key)'),
|
|
107
|
+
short_summary: zod_1.z.string().optional().describe('update: explicit short summary (snake_case key; null clears)'),
|
|
108
|
+
structured: zod_1.z.boolean().optional().describe('from_prompt: lightly structure the verbatim prompt'),
|
|
109
|
+
createLocal: zod_1.z.boolean().optional().describe('commit_draft/generate: keep the spec local (skip Jira promote)'),
|
|
110
|
+
// ── draft linkage ────────────────────────────────────────────────
|
|
111
|
+
conversationId: zod_1.z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe('save_draft (required) / commit_draft: origin Explore conversation id'),
|
|
115
|
+
draftTicketId: zod_1.z.number().int().optional().describe('commit_draft: explicit draft ticket id to flip in place'),
|
|
116
|
+
editTicketId: zod_1.z.number().int().optional().describe('save_draft: existing ticket id to demote to a draft in place'),
|
|
117
|
+
pendingSpecId: zod_1.z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('generate/from_prompt/commit_draft: pending-spec UUID owning pre-creation attachments'),
|
|
121
|
+
// ── generate (Quick AI spec) ─────────────────────────────────────
|
|
122
|
+
idea: zod_1.z.string().optional().describe('generate: the idea/prompt to turn into a spec (required)'),
|
|
123
|
+
model: zod_1.z.string().optional().describe('generate/smash: model override (validated against the provider catalog)'),
|
|
124
|
+
aiEngine: zod_1.z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe('generate: provider/engine override (must be an installed provider; defaults to primary)'),
|
|
128
|
+
contextScope: zod_1.z
|
|
129
|
+
.record(zod_1.z.unknown())
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('generate: context-awareness flags {specrails,openspec,full,mcp,userMcp,contractRefine}'),
|
|
132
|
+
contractRefine: zod_1.z.boolean().optional().describe('generate: enrich the spec with a Contract Layer (Claude-only)'),
|
|
133
|
+
attachmentIds: zod_1.z.array(zod_1.z.string()).optional().describe('generate: attachment ids (requires pendingSpecId)'),
|
|
134
|
+
// ── ai_edit ──────────────────────────────────────────────────────
|
|
135
|
+
instructions: zod_1.z.string().optional().describe('ai_edit: natural-language editing instructions (required)'),
|
|
136
|
+
priorInstructions: zod_1.z.array(zod_1.z.string()).optional().describe('ai_edit: prior refinement turns for an iterative edit'),
|
|
137
|
+
priorProposal: zod_1.z.string().optional().describe('ai_edit: the previous turn proposal to refine'),
|
|
138
|
+
requestId: zod_1.z.string().optional().describe('cancel_ai_edit: requestId of the in-flight ai-edit to abort (required)'),
|
|
139
|
+
// ── smash ────────────────────────────────────────────────────────
|
|
140
|
+
mode: zod_1.z.enum(['simple', 'full']).optional().describe('smash: decomposition mode (default simple)'),
|
|
141
|
+
smashedAt: zod_1.z.string().optional().describe('smash_undo: the smashedAt timestamp of the SMASH to reverse (required)'),
|
|
142
|
+
// ── attachments ──────────────────────────────────────────────────
|
|
143
|
+
ticketId: zod_1.z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe('list_attachments/get_attachment: ticket numeric id or pending-spec UUID'),
|
|
147
|
+
attachmentId: zod_1.z.string().optional().describe('get_attachment: attachment id (required)'),
|
|
148
|
+
},
|
|
149
|
+
async handler(ctx, args) {
|
|
150
|
+
const base = (0, types_1.projectPath)(ctx, args.projectId);
|
|
151
|
+
const action = args.action;
|
|
152
|
+
const requireId = () => {
|
|
153
|
+
const id = args.id;
|
|
154
|
+
if (typeof id !== 'number' || !Number.isInteger(id)) {
|
|
155
|
+
throw new Error(`Action "${action}" requires a numeric "id".`);
|
|
156
|
+
}
|
|
157
|
+
return id;
|
|
158
|
+
};
|
|
159
|
+
switch (action) {
|
|
160
|
+
// ── reads ──────────────────────────────────────────────────────
|
|
161
|
+
case 'list': {
|
|
162
|
+
const qs = new URLSearchParams();
|
|
163
|
+
if (typeof args.status === 'string')
|
|
164
|
+
qs.set('status', args.status);
|
|
165
|
+
if (typeof args.label === 'string')
|
|
166
|
+
qs.set('label', args.label);
|
|
167
|
+
if (typeof args.q === 'string')
|
|
168
|
+
qs.set('q', args.q);
|
|
169
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
|
170
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/tickets${suffix}`);
|
|
171
|
+
}
|
|
172
|
+
case 'get':
|
|
173
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/tickets/${requireId()}`);
|
|
174
|
+
case 'spending_summary':
|
|
175
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/tickets/${requireId()}/spending-summary`);
|
|
176
|
+
case 'files_touched':
|
|
177
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/code/provenance?ticketId=${requireId()}`);
|
|
178
|
+
case 'list_attachments': {
|
|
179
|
+
const ticketId = args.ticketId;
|
|
180
|
+
if (!ticketId)
|
|
181
|
+
throw new Error('list_attachments requires a "ticketId" (numeric id or UUID).');
|
|
182
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/tickets/${encodeURIComponent(ticketId)}/attachments`);
|
|
183
|
+
}
|
|
184
|
+
case 'get_attachment': {
|
|
185
|
+
const ticketId = args.ticketId;
|
|
186
|
+
const attachmentId = args.attachmentId;
|
|
187
|
+
if (!ticketId)
|
|
188
|
+
throw new Error('get_attachment requires a "ticketId".');
|
|
189
|
+
if (!attachmentId)
|
|
190
|
+
throw new Error('get_attachment requires an "attachmentId".');
|
|
191
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${base}/tickets/${encodeURIComponent(ticketId)}/attachments/${encodeURIComponent(attachmentId)}`);
|
|
192
|
+
}
|
|
193
|
+
// ── writes (synchronous store mutations) ────────────────────────
|
|
194
|
+
case 'create': {
|
|
195
|
+
// Route through Quick Add Spec (POST /tickets/generate-spec) so an
|
|
196
|
+
// MCP-created spec matches the app's "Add Spec → Quick" flow: the
|
|
197
|
+
// specrails agent generates a structured spec (source 'propose-spec')
|
|
198
|
+
// following the project's conventions, instead of a raw insert of
|
|
199
|
+
// whatever the external LLM wrote. The user's request becomes the
|
|
200
|
+
// `idea`. For a verbatim insert with no AI, use `from_prompt`.
|
|
201
|
+
const idea = [args.title, args.description]
|
|
202
|
+
.filter((v) => typeof v === 'string' && v.trim().length > 0)
|
|
203
|
+
.join('\n\n')
|
|
204
|
+
.trim();
|
|
205
|
+
if (!idea)
|
|
206
|
+
throw new Error('create requires a "title" and/or "description" to use as the spec idea.');
|
|
207
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, {
|
|
208
|
+
idea,
|
|
209
|
+
model: args.model,
|
|
210
|
+
aiEngine: args.aiEngine,
|
|
211
|
+
provider: args.provider,
|
|
212
|
+
contractRefine: args.contractRefine,
|
|
213
|
+
});
|
|
214
|
+
return { ...r, hint: WATCH_HINT };
|
|
215
|
+
}
|
|
216
|
+
case 'update':
|
|
217
|
+
return (0, types_1.apiCall)(ctx, 'PATCH', `${base}/tickets/${requireId()}`, {
|
|
218
|
+
title: args.title,
|
|
219
|
+
description: args.description,
|
|
220
|
+
status: args.status,
|
|
221
|
+
priority: args.priority,
|
|
222
|
+
labels: args.labels,
|
|
223
|
+
assignee: args.assignee,
|
|
224
|
+
prerequisites: args.prerequisites,
|
|
225
|
+
metadata: args.metadata,
|
|
226
|
+
acceptanceCriteria: args.acceptanceCriteria,
|
|
227
|
+
short_summary: args.short_summary,
|
|
228
|
+
});
|
|
229
|
+
case 'from_prompt':
|
|
230
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/from-prompt`, {
|
|
231
|
+
description: args.description,
|
|
232
|
+
title: args.title,
|
|
233
|
+
labels: args.labels,
|
|
234
|
+
priority: args.priority,
|
|
235
|
+
structured: args.structured,
|
|
236
|
+
pendingSpecId: args.pendingSpecId,
|
|
237
|
+
});
|
|
238
|
+
case 'save_draft': {
|
|
239
|
+
if (!args.conversationId)
|
|
240
|
+
throw new Error('save_draft requires a "conversationId".');
|
|
241
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/save-as-draft`, {
|
|
242
|
+
conversationId: args.conversationId,
|
|
243
|
+
title: args.title,
|
|
244
|
+
description: args.description,
|
|
245
|
+
labels: args.labels,
|
|
246
|
+
editTicketId: args.editTicketId,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
case 'commit_draft':
|
|
250
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/from-draft`, {
|
|
251
|
+
title: args.title,
|
|
252
|
+
description: args.description,
|
|
253
|
+
draftTicketId: args.draftTicketId,
|
|
254
|
+
conversationId: args.conversationId,
|
|
255
|
+
pendingSpecId: args.pendingSpecId,
|
|
256
|
+
labels: args.labels,
|
|
257
|
+
acceptanceCriteria: args.acceptanceCriteria,
|
|
258
|
+
priority: args.priority,
|
|
259
|
+
shortSummary: args.shortSummary,
|
|
260
|
+
createLocal: args.createLocal,
|
|
261
|
+
});
|
|
262
|
+
case 'cancel_ai_edit': {
|
|
263
|
+
if (!args.requestId)
|
|
264
|
+
throw new Error('cancel_ai_edit requires a "requestId".');
|
|
265
|
+
return (0, types_1.apiCall)(ctx, 'DELETE', `${base}/tickets/${requireId()}/ai-edit?requestId=${encodeURIComponent(args.requestId)}`);
|
|
266
|
+
}
|
|
267
|
+
// ── destructive ────────────────────────────────────────────────
|
|
268
|
+
case 'delete':
|
|
269
|
+
return (0, types_1.apiCall)(ctx, 'DELETE', `${base}/tickets/${requireId()}`);
|
|
270
|
+
case 'smash_undo': {
|
|
271
|
+
if (!args.smashedAt)
|
|
272
|
+
throw new Error('smash_undo requires the "smashedAt" timestamp.');
|
|
273
|
+
return (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/smash/undo`, {
|
|
274
|
+
smashedAt: args.smashedAt,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
case 'delete_epic_children':
|
|
278
|
+
return (0, types_1.apiCall)(ctx, 'DELETE', `${base}/tickets/${requireId()}/children`);
|
|
279
|
+
// ── ai-spawn (async 202 → WS) ──────────────────────────────────
|
|
280
|
+
case 'generate': {
|
|
281
|
+
if (!args.idea)
|
|
282
|
+
throw new Error('generate requires an "idea".');
|
|
283
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, {
|
|
284
|
+
idea: args.idea,
|
|
285
|
+
model: args.model,
|
|
286
|
+
aiEngine: args.aiEngine,
|
|
287
|
+
contextScope: args.contextScope,
|
|
288
|
+
contractRefine: args.contractRefine,
|
|
289
|
+
attachmentIds: args.attachmentIds,
|
|
290
|
+
pendingSpecId: args.pendingSpecId,
|
|
291
|
+
createLocal: args.createLocal,
|
|
292
|
+
});
|
|
293
|
+
return { ...r, hint: WATCH_HINT };
|
|
294
|
+
}
|
|
295
|
+
case 'ai_edit': {
|
|
296
|
+
if (!args.instructions)
|
|
297
|
+
throw new Error('ai_edit requires "instructions".');
|
|
298
|
+
if (!args.description)
|
|
299
|
+
throw new Error('ai_edit requires the current "description" baseline.');
|
|
300
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/ai-edit`, {
|
|
301
|
+
instructions: args.instructions,
|
|
302
|
+
description: args.description,
|
|
303
|
+
title: args.title,
|
|
304
|
+
attachmentIds: args.attachmentIds,
|
|
305
|
+
priorInstructions: args.priorInstructions,
|
|
306
|
+
priorProposal: args.priorProposal,
|
|
307
|
+
});
|
|
308
|
+
return { ...r, hint: WATCH_HINT };
|
|
309
|
+
}
|
|
310
|
+
case 'contract_refine': {
|
|
311
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/contract-refine`);
|
|
312
|
+
return { ...r, hint: WATCH_HINT };
|
|
313
|
+
}
|
|
314
|
+
case 'smash': {
|
|
315
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/smash`, {
|
|
316
|
+
mode: args.mode,
|
|
317
|
+
model: args.model,
|
|
318
|
+
});
|
|
319
|
+
return { ...r, hint: WATCH_HINT };
|
|
320
|
+
}
|
|
321
|
+
default:
|
|
322
|
+
throw new Error(`Unknown action "${action}".`);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apiCall = apiCall;
|
|
4
|
+
exports.projectPath = projectPath;
|
|
5
|
+
exports.requireProject = requireProject;
|
|
6
|
+
exports.setActiveProject = setActiveProject;
|
|
7
|
+
exports.getActiveProject = getActiveProject;
|
|
8
|
+
exports.registerTieredTool = registerTieredTool;
|
|
9
|
+
const mcp_tiers_1 = require("../mcp-tiers");
|
|
10
|
+
const auth_1 = require("../../auth");
|
|
11
|
+
/**
|
|
12
|
+
* Calls the app's own REST API over loopback using the MASTER token (held
|
|
13
|
+
* server-side, never exposed to the MCP client). This reuses every router's
|
|
14
|
+
* existing logic/validation rather than re-implementing it in tools — the same
|
|
15
|
+
* loopback-forward pattern the Mobile Gateway uses for REST. `path` is appended
|
|
16
|
+
* after `/api` (e.g. `/projects/<id>/tickets`).
|
|
17
|
+
*/
|
|
18
|
+
async function apiCall(ctx, method, path, body) {
|
|
19
|
+
const url = `http://127.0.0.1:${ctx.desktopPort}/api${path}`;
|
|
20
|
+
const res = await fetch(url, {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${(0, auth_1.loadOrGenerateToken)()}`,
|
|
24
|
+
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
25
|
+
},
|
|
26
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
27
|
+
});
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
let data;
|
|
30
|
+
try {
|
|
31
|
+
data = text ? JSON.parse(text) : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
data = text;
|
|
35
|
+
}
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const detail = typeof data === 'object' && data !== null ? JSON.stringify(data) : String(data);
|
|
38
|
+
throw new Error(`API ${method} ${path} → ${res.status}: ${detail}`);
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
/** Convenience: build the `/projects/<id>` path prefix for a resolved project. */
|
|
43
|
+
function projectPath(ctx, projectId) {
|
|
44
|
+
return `/projects/${requireProject(ctx, projectId).project.id}`;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve a per-project context or throw a clear, LLM-readable error. */
|
|
47
|
+
function requireProject(ctx, projectId) {
|
|
48
|
+
const id = projectId ?? getActiveProject(ctx);
|
|
49
|
+
if (!id) {
|
|
50
|
+
throw new Error('No project specified and no active project selected. Call specrails_select_project or pass projectId.');
|
|
51
|
+
}
|
|
52
|
+
const pc = ctx.registry.getContext(id);
|
|
53
|
+
if (!pc)
|
|
54
|
+
throw new Error(`Unknown projectId "${id}". Use specrails_projects(list) to see valid ids.`);
|
|
55
|
+
return pc;
|
|
56
|
+
}
|
|
57
|
+
// ── Active-project stickiness (per server process) ────────────────────────────
|
|
58
|
+
// The active project is a soft default; an explicit projectId always wins.
|
|
59
|
+
let _activeProjectId = null;
|
|
60
|
+
function setActiveProject(id) {
|
|
61
|
+
_activeProjectId = id;
|
|
62
|
+
}
|
|
63
|
+
function getActiveProject(_ctx) {
|
|
64
|
+
return _activeProjectId;
|
|
65
|
+
}
|
|
66
|
+
function tierAnnotations(tier) {
|
|
67
|
+
switch (tier) {
|
|
68
|
+
case 'read':
|
|
69
|
+
return { readOnlyHint: true, openWorldHint: false };
|
|
70
|
+
case 'write':
|
|
71
|
+
return { readOnlyHint: false, openWorldHint: false };
|
|
72
|
+
case 'ai-spawn':
|
|
73
|
+
return { readOnlyHint: false, openWorldHint: true };
|
|
74
|
+
case 'destructive':
|
|
75
|
+
return { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function toResult(data) {
|
|
79
|
+
const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
80
|
+
return { content: [{ type: 'text', text }] };
|
|
81
|
+
}
|
|
82
|
+
function errorResult(message) {
|
|
83
|
+
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Broadcasts an app-level `mcp.activity` event after a successful mutating tool
|
|
87
|
+
* call so the UI can surface what an external LLM did — regardless of which
|
|
88
|
+
* project is currently active. Deliberately carries NO top-level `projectId`
|
|
89
|
+
* (so the client's per-project WS filter does not drop it); the affected project
|
|
90
|
+
* is in `affectedProjectId` for cache invalidation. Read-tier calls don't emit.
|
|
91
|
+
* A notify failure must never break the tool, so it's wrapped.
|
|
92
|
+
*/
|
|
93
|
+
function emitMcpActivity(ctx, spec, args, tier) {
|
|
94
|
+
try {
|
|
95
|
+
const affectedProjectId = (typeof args.projectId === 'string' ? args.projectId : null) ?? getActiveProject(ctx);
|
|
96
|
+
ctx.broadcast({
|
|
97
|
+
type: 'mcp.activity',
|
|
98
|
+
tool: spec.name,
|
|
99
|
+
action: typeof args.action === 'string' ? args.action : null,
|
|
100
|
+
tier,
|
|
101
|
+
affectedProjectId,
|
|
102
|
+
title: spec.title,
|
|
103
|
+
at: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* a notification failure must never fail the tool */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Registers a tool on the McpServer, wrapping the handler with (1) tier
|
|
112
|
+
* enforcement and (2) uniform success/error result shaping. No tool can forget
|
|
113
|
+
* the tier check because it lives here, not in the handler.
|
|
114
|
+
*/
|
|
115
|
+
function registerTieredTool(server, ctx, spec) {
|
|
116
|
+
const hintTier = spec.hintTier ?? (typeof spec.tier === 'string' ? spec.tier : 'write');
|
|
117
|
+
server.registerTool(spec.name, {
|
|
118
|
+
title: spec.title,
|
|
119
|
+
description: spec.description,
|
|
120
|
+
inputSchema: spec.inputSchema,
|
|
121
|
+
annotations: tierAnnotations(hintTier),
|
|
122
|
+
}, async (args) => {
|
|
123
|
+
const tier = typeof spec.tier === 'function' ? spec.tier(args ?? {}) : spec.tier;
|
|
124
|
+
if (!(0, mcp_tiers_1.isTierEnabled)(ctx.desktopDb, tier)) {
|
|
125
|
+
return errorResult((0, mcp_tiers_1.tierRefusalMessage)(tier));
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const data = await spec.handler(ctx, args ?? {});
|
|
129
|
+
if (tier !== 'read')
|
|
130
|
+
emitMcpActivity(ctx, spec, args ?? {}, tier);
|
|
131
|
+
return toResult(data);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|