@rubytech/create-maxy-code 0.1.26 → 0.1.27

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 (449) hide show
  1. package/dist/index.js +28 -11
  2. package/package.json +1 -1
  3. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -95
  4. package/payload/platform/plugins/brochures/.claude-plugin/plugin.json +8 -0
  5. package/payload/platform/plugins/brochures/PLUGIN.md +36 -0
  6. package/payload/platform/plugins/brochures/commands/make-brochure.md +11 -0
  7. package/payload/platform/plugins/brochures/skills/a4-print-documents/SKILL.md +478 -0
  8. package/payload/platform/plugins/brochures/skills/brand-design/SKILL.md +192 -0
  9. package/payload/platform/plugins/brochures/skills/make-brochure/SKILL.md +354 -0
  10. package/payload/platform/plugins/brochures/skills/make-brochure/references/seller-brief-template.md +115 -0
  11. package/payload/platform/plugins/brochures/skills/property-brochure/SKILL.md +119 -0
  12. package/payload/platform/plugins/brochures/skills/property-brochure/references/build.md +270 -0
  13. package/payload/platform/plugins/brochures/skills/property-brochure/references/copy.md +211 -0
  14. package/payload/platform/plugins/brochures/skills/property-brochure/references/images.md +166 -0
  15. package/payload/platform/plugins/brochures/skills/property-brochure/references/index-landing.md +376 -0
  16. package/payload/platform/plugins/brochures/skills/property-brochure/references/index.html +1288 -0
  17. package/payload/platform/plugins/brochures/skills/property-brochure/references/placeholders.md +250 -0
  18. package/payload/platform/plugins/brochures/skills/property-brochure/references/registers.md +47 -0
  19. package/payload/platform/plugins/brochures/skills/property-brochure/references/seller-brief.md +56 -0
  20. package/payload/platform/plugins/brochures/skills/property-brochure/references/structure.md +249 -0
  21. package/payload/platform/plugins/brochures/skills/property-brochure/references/template.html +2370 -0
  22. package/payload/platform/plugins/brochures/skills/property-extract/SKILL.md +372 -0
  23. package/payload/platform/plugins/buyers/.claude-plugin/plugin.json +8 -0
  24. package/payload/platform/plugins/buyers/PLUGIN.md +35 -0
  25. package/payload/platform/plugins/buyers/skills/buyer-feedback/SKILL.md +109 -0
  26. package/payload/platform/plugins/buyers/skills/buyer-management/SKILL.md +42 -0
  27. package/payload/platform/plugins/buyers/skills/buyer-management/references/buyer-qualification-questions.md +16 -0
  28. package/payload/platform/plugins/buyers/skills/buyer-management/references/buyer-qualification.md +59 -0
  29. package/payload/platform/plugins/buyers/skills/buyer-management/references/buyer-scripts.md +63 -0
  30. package/payload/platform/plugins/buyers/skills/buyer-management/references/buyer-working-scripts.md +54 -0
  31. package/payload/platform/plugins/buyers/skills/buyer-management/references/feedback-collection.md +42 -0
  32. package/payload/platform/plugins/buyers/skills/buyer-management/references/offer-capture.md +38 -0
  33. package/payload/platform/plugins/buyers/skills/buyer-management/references/viewing-booking.md +32 -0
  34. package/payload/platform/plugins/buyers/skills/buyer-management/references/viewing-management.md +52 -0
  35. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/SKILL.md +407 -0
  36. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/care-fees-guide.md +68 -0
  37. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/divorce-sales-guide.md +61 -0
  38. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/downsizing-guide.md +45 -0
  39. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/first-time-buyers.md +92 -0
  40. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/first-time-sellers.md +78 -0
  41. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/probate-guide.md +53 -0
  42. package/payload/platform/plugins/buyers/skills/buyer-seller-guides/references/upsizing-guide.md +41 -0
  43. package/payload/platform/plugins/buyers/skills/property-enquiry/SKILL.md +126 -0
  44. package/payload/platform/plugins/buyers/skills/viewing-management/SKILL.md +111 -0
  45. package/payload/platform/plugins/estate-business/.claude-plugin/plugin.json +8 -0
  46. package/payload/platform/plugins/estate-business/PLUGIN.md +65 -0
  47. package/payload/platform/plugins/estate-business/skills/business-growth/SKILL.md +133 -0
  48. package/payload/platform/plugins/estate-business/skills/business-growth/references/buy-back-your-time.md +37 -0
  49. package/payload/platform/plugins/estate-business/skills/business-growth/references/firewave-gost-scorecards.md +14 -0
  50. package/payload/platform/plugins/estate-business/skills/business-growth/references/keller-org-model.md +17 -0
  51. package/payload/platform/plugins/estate-business/skills/business-growth/references/lencioni-team-models.md +22 -0
  52. package/payload/platform/plugins/estate-business/skills/business-growth/references/listing-management-system.md +11 -0
  53. package/payload/platform/plugins/estate-business/skills/business-growth/references/net-figure-form.md +11 -0
  54. package/payload/platform/plugins/estate-business/skills/business-growth/references/serhant-bizinbox-notes.md +13 -0
  55. package/payload/platform/plugins/estate-business/skills/business-growth/references/team-roles-commission.md +14 -0
  56. package/payload/platform/plugins/estate-business/skills/business-growth/references/va-2026-ops.md +43 -0
  57. package/payload/platform/plugins/estate-business/skills/business-growth/references/wingman-structure.md +13 -0
  58. package/payload/platform/plugins/estate-business/skills/business-operations/SKILL.md +32 -0
  59. package/payload/platform/plugins/estate-business/skills/business-operations/references/crm-systems.md +57 -0
  60. package/payload/platform/plugins/estate-business/skills/business-operations/references/hiring-guide.md +59 -0
  61. package/payload/platform/plugins/estate-business/skills/business-operations/references/impact-framework.md +47 -0
  62. package/payload/platform/plugins/estate-business/skills/business-operations/references/minutes-equal-money.md +55 -0
  63. package/payload/platform/plugins/estate-business/skills/business-operations/references/team-management.md +48 -0
  64. package/payload/platform/plugins/estate-business/skills/commission-calculator/SKILL.md +40 -0
  65. package/payload/platform/plugins/estate-business/skills/exp-partnership/SKILL.md +52 -0
  66. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/12-reasons.md +39 -0
  67. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/95-5-system.md +66 -0
  68. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/agent-attraction-scripts.md +90 -0
  69. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/business-partnership.md +92 -0
  70. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/exp-model-overview.md +66 -0
  71. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/model-comparison.md +66 -0
  72. package/payload/platform/plugins/estate-business/skills/exp-partnership/references/revenue-share-explained.md +57 -0
  73. package/payload/platform/plugins/estate-business/skills/month-end-close/SKILL.md +69 -0
  74. package/payload/platform/plugins/estate-business/skills/payment-batch-stager/SKILL.md +42 -0
  75. package/payload/platform/plugins/estate-business/skills/period-reconciler/SKILL.md +42 -0
  76. package/payload/platform/plugins/estate-business/skills/personal-branding/SKILL.md +117 -0
  77. package/payload/platform/plugins/estate-business/skills/personal-branding/references/attraction-agent-notes.md +31 -0
  78. package/payload/platform/plugins/estate-business/skills/personal-branding/references/attraction-agent.md +58 -0
  79. package/payload/platform/plugins/estate-business/skills/personal-branding/references/authenticity-boundaries.md +28 -0
  80. package/payload/platform/plugins/estate-business/skills/personal-branding/references/become-a-brand-leader-notes.md +19 -0
  81. package/payload/platform/plugins/estate-business/skills/personal-branding/references/blast-formula.md +42 -0
  82. package/payload/platform/plugins/estate-business/skills/personal-branding/references/brand-leader.md +48 -0
  83. package/payload/platform/plugins/estate-business/skills/personal-branding/references/brand-strategy-system.md +59 -0
  84. package/payload/platform/plugins/estate-business/skills/personal-branding/references/content-engine.md +49 -0
  85. package/payload/platform/plugins/estate-business/skills/personal-branding/references/firewave-blast-and-blogging.md +23 -0
  86. package/payload/platform/plugins/estate-business/skills/personal-branding/references/gary-v-content.md +52 -0
  87. package/payload/platform/plugins/estate-business/skills/personal-branding/references/gary-v-principles.md +20 -0
  88. package/payload/platform/plugins/estate-business/skills/personal-branding/references/oversubscribed-positioning.md +18 -0
  89. package/payload/platform/plugins/estate-business/skills/personal-branding/references/platforms.md +41 -0
  90. package/payload/platform/plugins/estate-business/skills/personal-branding/references/priestley-oversubscribed.md +54 -0
  91. package/payload/platform/plugins/estate-business/skills/personal-branding/references/storeys-style-examples.md +25 -0
  92. package/payload/platform/plugins/estate-business/skills/personal-branding/references/visual-identity.md +27 -0
  93. package/payload/platform/plugins/estate-coaching/.claude-plugin/plugin.json +8 -0
  94. package/payload/platform/plugins/estate-coaching/PLUGIN.md +55 -0
  95. package/payload/platform/plugins/estate-coaching/skills/agent-performance/SKILL.md +371 -0
  96. package/payload/platform/plugins/estate-coaching/skills/agent-performance/references/atomic-habits.md +52 -0
  97. package/payload/platform/plugins/estate-coaching/skills/agent-performance/references/daily-routine-scorecard.md +104 -0
  98. package/payload/platform/plugins/estate-coaching/skills/agent-performance/references/hp6-model.md +63 -0
  99. package/payload/platform/plugins/estate-coaching/skills/agent-performance/references/twelve-week-year.md +71 -0
  100. package/payload/platform/plugins/estate-coaching/skills/bespoke-coaching/SKILL.md +36 -0
  101. package/payload/platform/plugins/estate-coaching/skills/bespoke-coaching/references/coaching-boundaries.md +56 -0
  102. package/payload/platform/plugins/estate-coaching/skills/bespoke-coaching/references/feedback-framework.md +61 -0
  103. package/payload/platform/plugins/estate-coaching/skills/bespoke-coaching/references/performance-framework.md +109 -0
  104. package/payload/platform/plugins/estate-coaching/skills/coaching-toolkit/SKILL.md +421 -0
  105. package/payload/platform/plugins/estate-coaching/skills/coaching-toolkit/references/coaching-exercises.md +86 -0
  106. package/payload/platform/plugins/estate-coaching/skills/coaching-toolkit/references/goal-setting.md +78 -0
  107. package/payload/platform/plugins/estate-coaching/skills/coaching-toolkit/references/one-to-one-framework.md +92 -0
  108. package/payload/platform/plugins/estate-coaching/skills/coaching-toolkit/references/soi-workbook.md +103 -0
  109. package/payload/platform/plugins/estate-coaching/skills/serhant-training/SKILL.md +410 -0
  110. package/payload/platform/plugins/estate-coaching/skills/serhant-training/references/agent-training-guide.md +70 -0
  111. package/payload/platform/plugins/estate-coaching/skills/serhant-training/references/business-in-a-box.md +72 -0
  112. package/payload/platform/plugins/estate-coaching/skills/serhant-training/references/buyers-guide.md +53 -0
  113. package/payload/platform/plugins/estate-coaching/skills/serhant-training/references/codo-method.md +72 -0
  114. package/payload/platform/plugins/estate-coaching/skills/serhant-training/references/website-planning-guide.md +79 -0
  115. package/payload/platform/plugins/estate-onboarding/.claude-plugin/plugin.json +8 -0
  116. package/payload/platform/plugins/estate-onboarding/PLUGIN.md +31 -0
  117. package/payload/platform/plugins/estate-onboarding/skills/bootstrap/SKILL.md +26 -0
  118. package/payload/platform/plugins/estate-onboarding/skills/bootstrap/references/onboarding-flow.md +63 -0
  119. package/payload/platform/plugins/estate-sales/.claude-plugin/plugin.json +8 -0
  120. package/payload/platform/plugins/estate-sales/PLUGIN.md +53 -0
  121. package/payload/platform/plugins/estate-sales/skills/chase-progression/SKILL.md +107 -0
  122. package/payload/platform/plugins/estate-sales/skills/negotiation/SKILL.md +35 -0
  123. package/payload/platform/plugins/estate-sales/skills/negotiation/references/deal-saving.md +47 -0
  124. package/payload/platform/plugins/estate-sales/skills/negotiation/references/negotiation-deep-guide.md +64 -0
  125. package/payload/platform/plugins/estate-sales/skills/negotiation/references/negotiation-prep-principles.md +29 -0
  126. package/payload/platform/plugins/estate-sales/skills/negotiation/references/negotiation-techniques.md +42 -0
  127. package/payload/platform/plugins/estate-sales/skills/negotiation/references/offer-presentation.md +43 -0
  128. package/payload/platform/plugins/estate-sales/skills/risk-scorer/SKILL.md +42 -0
  129. package/payload/platform/plugins/estate-sales/skills/sales-closer/SKILL.md +24 -0
  130. package/payload/platform/plugins/estate-sales/skills/sales-closer/references/serhant-emotion-stages.md +36 -0
  131. package/payload/platform/plugins/estate-sales/skills/sales-discovery/SKILL.md +30 -0
  132. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/chris-voss-discovery.md +88 -0
  133. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/firewave-gost-journey.md +68 -0
  134. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/phil-jones-openers.md +78 -0
  135. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/pre-listing-checklist.md +77 -0
  136. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/serhant-improv.md +22 -0
  137. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/tom-ferry-discovery.md +103 -0
  138. package/payload/platform/plugins/estate-sales/skills/sales-discovery/references/vendor-motivation-competitor.md +52 -0
  139. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/SKILL.md +29 -0
  140. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/references/chris-voss-negotiation.md +70 -0
  141. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/references/phil-jones-price-words.md +40 -0
  142. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/references/serhant-negotiation-plus.md +55 -0
  143. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/references/tom-panos-commission-pricing.md +57 -0
  144. package/payload/platform/plugins/estate-sales/skills/sales-negotiation/references/tony-morris-questioning.md +54 -0
  145. package/payload/platform/plugins/estate-sales/skills/sales-progression/SKILL.md +27 -0
  146. package/payload/platform/plugins/estate-sales/skills/sales-progression/references/conveyancing-guide.md +54 -0
  147. package/payload/platform/plugins/estate-sales/skills/sales-progression/references/transaction-tracking.md +66 -0
  148. package/payload/platform/plugins/estate-teaching/.claude-plugin/plugin.json +8 -0
  149. package/payload/platform/plugins/estate-teaching/PLUGIN.md +31 -0
  150. package/payload/platform/plugins/estate-teaching/skills/content-directory/SKILL.md +39 -0
  151. package/payload/platform/plugins/estate-teaching/skills/content-directory/references/module-delivery.md +65 -0
  152. package/payload/platform/plugins/estate-teaching/skills/content-directory/references/progress-tracking.md +47 -0
  153. package/payload/platform/plugins/leads/.claude-plugin/plugin.json +8 -0
  154. package/payload/platform/plugins/leads/PLUGIN.md +62 -0
  155. package/payload/platform/plugins/leads/skills/chain-progression-tracker/SKILL.md +51 -0
  156. package/payload/platform/plugins/leads/skills/diary-builder/SKILL.md +38 -0
  157. package/payload/platform/plugins/leads/skills/enquiry-triage/SKILL.md +36 -0
  158. package/payload/platform/plugins/leads/skills/lead-nurturing/SKILL.md +137 -0
  159. package/payload/platform/plugins/leads/skills/lead-nurturing/references/buyer-search-letter.md +28 -0
  160. package/payload/platform/plugins/leads/skills/lead-nurturing/references/buyer-search-letters.md +37 -0
  161. package/payload/platform/plugins/leads/skills/lead-nurturing/references/database-reactivation.md +30 -0
  162. package/payload/platform/plugins/leads/skills/lead-nurturing/references/email-nurture-sequences.md +45 -0
  163. package/payload/platform/plugins/leads/skills/lead-nurturing/references/facebook-referrals.md +30 -0
  164. package/payload/platform/plugins/leads/skills/lead-nurturing/references/firewave-email-nurture-sequences.md +41 -0
  165. package/payload/platform/plugins/leads/skills/lead-nurturing/references/keller-33-touch.md +34 -0
  166. package/payload/platform/plugins/leads/skills/lead-nurturing/references/neighbour-letters.md +31 -0
  167. package/payload/platform/plugins/leads/skills/lead-nurturing/references/neighbour-notification-letter.md +20 -0
  168. package/payload/platform/plugins/leads/skills/lead-nurturing/references/ofi-follow-up-dialogue.md +22 -0
  169. package/payload/platform/plugins/leads/skills/lead-nurturing/references/ofi-follow-up.md +26 -0
  170. package/payload/platform/plugins/leads/skills/lead-nurturing/references/serhant-three-fs-plus.md +21 -0
  171. package/payload/platform/plugins/leads/skills/lead-nurturing/references/sharran-10x10x10.md +18 -0
  172. package/payload/platform/plugins/leads/skills/lead-nurturing/references/sms-templates.md +40 -0
  173. package/payload/platform/plugins/leads/skills/lead-nurturing/references/sphere-of-influence-notes.md +34 -0
  174. package/payload/platform/plugins/leads/skills/lead-nurturing/references/sphere-of-influence.md +60 -0
  175. package/payload/platform/plugins/leads/skills/lead-nurturing/references/tom-panos-sms-templates.md +59 -0
  176. package/payload/platform/plugins/leads/skills/morning-round/SKILL.md +72 -0
  177. package/payload/platform/plugins/leads/skills/prospecting/SKILL.md +33 -0
  178. package/payload/platform/plugins/leads/skills/prospecting/references/database-matching.md +30 -0
  179. package/payload/platform/plugins/leads/skills/prospecting/references/database-value.md +53 -0
  180. package/payload/platform/plugins/leads/skills/prospecting/references/prospecting-dialogues.md +24 -0
  181. package/payload/platform/plugins/leads/skills/prospecting/references/reactivation.md +34 -0
  182. package/payload/platform/plugins/listings/.claude-plugin/plugin.json +8 -0
  183. package/payload/platform/plugins/listings/PLUGIN.md +103 -0
  184. package/payload/platform/plugins/listings/skills/comparable-finder/SKILL.md +52 -0
  185. package/payload/platform/plugins/listings/skills/epc-checker/SKILL.md +38 -0
  186. package/payload/platform/plugins/listings/skills/home-preparation/SKILL.md +28 -0
  187. package/payload/platform/plugins/listings/skills/home-preparation/references/kerb-appeal.md +38 -0
  188. package/payload/platform/plugins/listings/skills/home-preparation/references/photo-day.md +59 -0
  189. package/payload/platform/plugins/listings/skills/home-preparation/references/situational-tips.md +50 -0
  190. package/payload/platform/plugins/listings/skills/home-preparation/references/staging-guide.md +52 -0
  191. package/payload/platform/plugins/listings/skills/listing-copy-writer/SKILL.md +55 -0
  192. package/payload/platform/plugins/listings/skills/listing-presentation/SKILL.md +286 -0
  193. package/payload/platform/plugins/listings/skills/listing-presentation/references/booking-script.md +51 -0
  194. package/payload/platform/plugins/listings/skills/listing-presentation/references/objection-scripts.md +193 -0
  195. package/payload/platform/plugins/listings/skills/listing-presentation/references/penhaul-presentation.md +123 -0
  196. package/payload/platform/plugins/listings/skills/listing-presentation/references/pre-listing-kit.md +139 -0
  197. package/payload/platform/plugins/listings/skills/listing-presentation/references/set-to-sell.md +55 -0
  198. package/payload/platform/plugins/listings/skills/listing-presentation/references/sharran-frameworks.md +107 -0
  199. package/payload/platform/plugins/listings/skills/local-market-stats/SKILL.md +33 -0
  200. package/payload/platform/plugins/listings/skills/new-instruction/SKILL.md +78 -0
  201. package/payload/platform/plugins/listings/skills/particulars-builder/SKILL.md +48 -0
  202. package/payload/platform/plugins/listings/skills/portal-launch-scheduler/SKILL.md +49 -0
  203. package/payload/platform/plugins/listings/skills/pricing-scenario-builder/SKILL.md +35 -0
  204. package/payload/platform/plugins/listings/skills/property-marketing/SKILL.md +337 -0
  205. package/payload/platform/plugins/listings/skills/property-marketing/references/auction-report-template.md +41 -0
  206. package/payload/platform/plugins/listings/skills/property-marketing/references/coming-soon-campaign.md +43 -0
  207. package/payload/platform/plugins/listings/skills/property-marketing/references/direct-mail-templates.md +121 -0
  208. package/payload/platform/plugins/listings/skills/property-marketing/references/eoi-form-template.md +62 -0
  209. package/payload/platform/plugins/listings/skills/property-marketing/references/monthly-scorecard.md +63 -0
  210. package/payload/platform/plugins/listings/skills/supplier-booker/SKILL.md +39 -0
  211. package/payload/platform/plugins/listings/skills/talk-track-composer/SKILL.md +36 -0
  212. package/payload/platform/plugins/listings/skills/terms-of-business-drafter/SKILL.md +54 -0
  213. package/payload/platform/plugins/listings/skills/valuation-prep/SKILL.md +69 -0
  214. package/payload/platform/plugins/loop/.claude-plugin/plugin.json +17 -0
  215. package/payload/platform/plugins/loop/PLUGIN.md +108 -0
  216. package/payload/platform/plugins/loop/mcp/dist/index.d.ts +2 -0
  217. package/payload/platform/plugins/loop/mcp/dist/index.d.ts.map +1 -0
  218. package/payload/platform/plugins/loop/mcp/dist/index.js +293 -0
  219. package/payload/platform/plugins/loop/mcp/dist/index.js.map +1 -0
  220. package/payload/platform/plugins/loop/mcp/dist/lib/crypto.d.ts +10 -0
  221. package/payload/platform/plugins/loop/mcp/dist/lib/crypto.d.ts.map +1 -0
  222. package/payload/platform/plugins/loop/mcp/dist/lib/crypto.js +88 -0
  223. package/payload/platform/plugins/loop/mcp/dist/lib/crypto.js.map +1 -0
  224. package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.d.ts +82 -0
  225. package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.d.ts.map +1 -0
  226. package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.js +427 -0
  227. package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.js.map +1 -0
  228. package/payload/platform/plugins/loop/mcp/dist/lib/neo4j.d.ts +5 -0
  229. package/payload/platform/plugins/loop/mcp/dist/lib/neo4j.d.ts.map +1 -0
  230. package/payload/platform/plugins/loop/mcp/dist/lib/neo4j.js +40 -0
  231. package/payload/platform/plugins/loop/mcp/dist/lib/neo4j.js.map +1 -0
  232. package/payload/platform/plugins/loop/mcp/dist/tools/customer-preferences.d.ts +10 -0
  233. package/payload/platform/plugins/loop/mcp/dist/tools/customer-preferences.d.ts.map +1 -0
  234. package/payload/platform/plugins/loop/mcp/dist/tools/customer-preferences.js +24 -0
  235. package/payload/platform/plugins/loop/mcp/dist/tools/customer-preferences.js.map +1 -0
  236. package/payload/platform/plugins/loop/mcp/dist/tools/feedback.d.ts +16 -0
  237. package/payload/platform/plugins/loop/mcp/dist/tools/feedback.d.ts.map +1 -0
  238. package/payload/platform/plugins/loop/mcp/dist/tools/feedback.js +35 -0
  239. package/payload/platform/plugins/loop/mcp/dist/tools/feedback.js.map +1 -0
  240. package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.d.ts +5 -0
  241. package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  242. package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.js +19 -0
  243. package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.js.map +1 -0
  244. package/payload/platform/plugins/loop/mcp/dist/tools/key-list.d.ts +4 -0
  245. package/payload/platform/plugins/loop/mcp/dist/tools/key-list.d.ts.map +1 -0
  246. package/payload/platform/plugins/loop/mcp/dist/tools/key-list.js +14 -0
  247. package/payload/platform/plugins/loop/mcp/dist/tools/key-list.js.map +1 -0
  248. package/payload/platform/plugins/loop/mcp/dist/tools/key-register.d.ts +9 -0
  249. package/payload/platform/plugins/loop/mcp/dist/tools/key-register.d.ts.map +1 -0
  250. package/payload/platform/plugins/loop/mcp/dist/tools/key-register.js +60 -0
  251. package/payload/platform/plugins/loop/mcp/dist/tools/key-register.js.map +1 -0
  252. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts +13 -0
  253. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -0
  254. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.js +41 -0
  255. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.js.map +1 -0
  256. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts +9 -0
  257. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts.map +1 -0
  258. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-batch.js +16 -0
  259. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-batch.js.map +1 -0
  260. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts +15 -0
  261. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts.map +1 -0
  262. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-request.js +11 -0
  263. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match-request.js.map +1 -0
  264. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match.d.ts +10 -0
  265. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match.d.ts.map +1 -0
  266. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match.js +39 -0
  267. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-match.js.map +1 -0
  268. package/payload/platform/plugins/loop/mcp/dist/tools/people-detail.d.ts +9 -0
  269. package/payload/platform/plugins/loop/mcp/dist/tools/people-detail.d.ts.map +1 -0
  270. package/payload/platform/plugins/loop/mcp/dist/tools/people-detail.js +125 -0
  271. package/payload/platform/plugins/loop/mcp/dist/tools/people-detail.js.map +1 -0
  272. package/payload/platform/plugins/loop/mcp/dist/tools/people-search.d.ts +18 -0
  273. package/payload/platform/plugins/loop/mcp/dist/tools/people-search.d.ts.map +1 -0
  274. package/payload/platform/plugins/loop/mcp/dist/tools/people-search.js +87 -0
  275. package/payload/platform/plugins/loop/mcp/dist/tools/people-search.js.map +1 -0
  276. package/payload/platform/plugins/loop/mcp/dist/tools/property-detail.d.ts +10 -0
  277. package/payload/platform/plugins/loop/mcp/dist/tools/property-detail.d.ts.map +1 -0
  278. package/payload/platform/plugins/loop/mcp/dist/tools/property-detail.js +82 -0
  279. package/payload/platform/plugins/loop/mcp/dist/tools/property-detail.js.map +1 -0
  280. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.d.ts +12 -0
  281. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.d.ts.map +1 -0
  282. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.js +32 -0
  283. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.js.map +1 -0
  284. package/payload/platform/plugins/loop/mcp/dist/tools/property-request.d.ts +15 -0
  285. package/payload/platform/plugins/loop/mcp/dist/tools/property-request.d.ts.map +1 -0
  286. package/payload/platform/plugins/loop/mcp/dist/tools/property-request.js +11 -0
  287. package/payload/platform/plugins/loop/mcp/dist/tools/property-request.js.map +1 -0
  288. package/payload/platform/plugins/loop/mcp/dist/tools/property-search.d.ts +16 -0
  289. package/payload/platform/plugins/loop/mcp/dist/tools/property-search.d.ts.map +1 -0
  290. package/payload/platform/plugins/loop/mcp/dist/tools/property-search.js +41 -0
  291. package/payload/platform/plugins/loop/mcp/dist/tools/property-search.js.map +1 -0
  292. package/payload/platform/plugins/loop/mcp/dist/tools/supplier.d.ts +13 -0
  293. package/payload/platform/plugins/loop/mcp/dist/tools/supplier.d.ts.map +1 -0
  294. package/payload/platform/plugins/loop/mcp/dist/tools/supplier.js +49 -0
  295. package/payload/platform/plugins/loop/mcp/dist/tools/supplier.js.map +1 -0
  296. package/payload/platform/plugins/loop/mcp/dist/tools/team-availability.d.ts +7 -0
  297. package/payload/platform/plugins/loop/mcp/dist/tools/team-availability.d.ts.map +1 -0
  298. package/payload/platform/plugins/loop/mcp/dist/tools/team-availability.js +19 -0
  299. package/payload/platform/plugins/loop/mcp/dist/tools/team-availability.js.map +1 -0
  300. package/payload/platform/plugins/loop/mcp/dist/tools/team-info.d.ts +5 -0
  301. package/payload/platform/plugins/loop/mcp/dist/tools/team-info.d.ts.map +1 -0
  302. package/payload/platform/plugins/loop/mcp/dist/tools/team-info.js +32 -0
  303. package/payload/platform/plugins/loop/mcp/dist/tools/team-info.js.map +1 -0
  304. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-create.d.ts +14 -0
  305. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-create.d.ts.map +1 -0
  306. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-create.js +11 -0
  307. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-create.js.map +1 -0
  308. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-detail.d.ts +9 -0
  309. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-detail.d.ts.map +1 -0
  310. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-detail.js +85 -0
  311. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-detail.js.map +1 -0
  312. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-search.d.ts +13 -0
  313. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-search.d.ts.map +1 -0
  314. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-search.js +44 -0
  315. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-search.js.map +1 -0
  316. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-update.d.ts +14 -0
  317. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-update.d.ts.map +1 -0
  318. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-update.js +18 -0
  319. package/payload/platform/plugins/loop/mcp/dist/tools/viewing-update.js.map +1 -0
  320. package/payload/platform/plugins/loop/mcp/package-lock.json +2549 -0
  321. package/payload/platform/plugins/loop/mcp/package.json +21 -0
  322. package/payload/platform/plugins/loop/mcp/src/__tests__/loop-swagger.snapshot.json +26467 -0
  323. package/payload/platform/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +153 -0
  324. package/payload/platform/plugins/loop/mcp/src/index.ts +444 -0
  325. package/payload/platform/plugins/loop/mcp/src/lib/crypto.ts +105 -0
  326. package/payload/platform/plugins/loop/mcp/src/lib/loop-api.ts +604 -0
  327. package/payload/platform/plugins/loop/mcp/src/lib/neo4j.ts +51 -0
  328. package/payload/platform/plugins/loop/mcp/src/tools/customer-preferences.ts +66 -0
  329. package/payload/platform/plugins/loop/mcp/src/tools/feedback.ts +86 -0
  330. package/payload/platform/plugins/loop/mcp/src/tools/key-deregister.ts +27 -0
  331. package/payload/platform/plugins/loop/mcp/src/tools/key-list.ts +19 -0
  332. package/payload/platform/plugins/loop/mcp/src/tools/key-register.ts +95 -0
  333. package/payload/platform/plugins/loop/mcp/src/tools/marketing-enquiry.ts +113 -0
  334. package/payload/platform/plugins/loop/mcp/src/tools/marketing-match-batch.ts +53 -0
  335. package/payload/platform/plugins/loop/mcp/src/tools/marketing-match-request.ts +42 -0
  336. package/payload/platform/plugins/loop/mcp/src/tools/marketing-match.ts +84 -0
  337. package/payload/platform/plugins/loop/mcp/src/tools/people-detail.ts +245 -0
  338. package/payload/platform/plugins/loop/mcp/src/tools/people-search.ts +180 -0
  339. package/payload/platform/plugins/loop/mcp/src/tools/property-detail.ts +145 -0
  340. package/payload/platform/plugins/loop/mcp/src/tools/property-listed.ts +88 -0
  341. package/payload/platform/plugins/loop/mcp/src/tools/property-request.ts +42 -0
  342. package/payload/platform/plugins/loop/mcp/src/tools/property-search.ts +92 -0
  343. package/payload/platform/plugins/loop/mcp/src/tools/supplier.ts +129 -0
  344. package/payload/platform/plugins/loop/mcp/src/tools/team-availability.ts +52 -0
  345. package/payload/platform/plugins/loop/mcp/src/tools/team-info.ts +95 -0
  346. package/payload/platform/plugins/loop/mcp/src/tools/viewing-create.ts +41 -0
  347. package/payload/platform/plugins/loop/mcp/src/tools/viewing-detail.ts +171 -0
  348. package/payload/platform/plugins/loop/mcp/src/tools/viewing-search.ts +92 -0
  349. package/payload/platform/plugins/loop/mcp/src/tools/viewing-update.ts +53 -0
  350. package/payload/platform/plugins/loop/mcp/tsconfig.json +20 -0
  351. package/payload/platform/plugins/loop/mcp/vitest.config.ts +9 -0
  352. package/payload/platform/plugins/loop/skills/compliance-flag-checker/SKILL.md +53 -0
  353. package/payload/platform/plugins/loop/skills/priority-ranker/SKILL.md +40 -0
  354. package/payload/platform/plugins/loop/skills/tone-matched-drafter/SKILL.md +53 -0
  355. package/payload/platform/plugins/loop/skills/variance-narrator/SKILL.md +50 -0
  356. package/payload/platform/plugins/loop/skills/vendor-research/SKILL.md +54 -0
  357. package/payload/platform/plugins/teaching/.claude-plugin/plugin.json +8 -0
  358. package/payload/platform/plugins/teaching/PLUGIN.md +57 -0
  359. package/payload/platform/plugins/teaching/skills/interactive-tutor/SKILL.md +59 -0
  360. package/payload/platform/plugins/teaching/skills/interactive-tutor/references/assessment.md +70 -0
  361. package/payload/platform/plugins/teaching/skills/interactive-tutor/references/classroom-conduct.md +43 -0
  362. package/payload/platform/plugins/teaching/skills/interactive-tutor/references/teaching-modes.md +83 -0
  363. package/payload/platform/plugins/teaching/skills/lesson-planner/SKILL.md +48 -0
  364. package/payload/platform/plugins/teaching/skills/lesson-planner/references/context-gathering.md +41 -0
  365. package/payload/platform/plugins/teaching/skills/lesson-planner/references/plan-structure.md +94 -0
  366. package/payload/platform/plugins/teaching/skills/study-pack-builder/SKILL.md +52 -0
  367. package/payload/platform/plugins/teaching/skills/study-pack-builder/references/disaggregation.md +49 -0
  368. package/payload/platform/plugins/teaching/skills/study-pack-builder/references/materials.md +116 -0
  369. package/payload/platform/plugins/vendors/.claude-plugin/plugin.json +8 -0
  370. package/payload/platform/plugins/vendors/PLUGIN.md +34 -0
  371. package/payload/platform/plugins/vendors/skills/vendor-communication/SKILL.md +42 -0
  372. package/payload/platform/plugins/vendors/skills/vendor-communication/references/fee-protection-and-agenda.md +28 -0
  373. package/payload/platform/plugins/vendors/skills/vendor-communication/references/listing-scripts.md +44 -0
  374. package/payload/platform/plugins/vendors/skills/vendor-communication/references/negotiation-deep-guide.md +70 -0
  375. package/payload/platform/plugins/vendors/skills/vendor-communication/references/price-alignment-scripts.md +33 -0
  376. package/payload/platform/plugins/vendors/skills/vendor-communication/references/price-alignment.md +34 -0
  377. package/payload/platform/plugins/vendors/skills/vendor-communication/references/scenario-scripts.md +38 -0
  378. package/payload/platform/plugins/vendors/skills/vendor-communication/references/seller-engagement.md +51 -0
  379. package/payload/platform/plugins/vendors/skills/vendor-communication/references/valuation-booking.md +76 -0
  380. package/payload/platform/plugins/vendors/skills/vendor-communication/references/vendor-scripts.md +63 -0
  381. package/payload/platform/plugins/vendors/skills/vendor-communication/references/vendor-updates.md +41 -0
  382. package/payload/platform/plugins/vendors/skills/vendor-updates/SKILL.md +153 -0
  383. package/payload/platform/plugins/writer-craft/.claude-plugin/plugin.json +8 -0
  384. package/payload/platform/plugins/writer-craft/PLUGIN.md +87 -0
  385. package/payload/platform/plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +92 -0
  386. package/payload/platform/plugins/writer-craft/skills/citation-style/SKILL.md +94 -0
  387. package/payload/platform/plugins/writer-craft/skills/citation-style/references/book-and-chapter-models.md +77 -0
  388. package/payload/platform/plugins/writer-craft/skills/citation-style/references/citation-rules.md +103 -0
  389. package/payload/platform/plugins/writer-craft/skills/citation-style/references/journal-article-models.md +74 -0
  390. package/payload/platform/plugins/writer-craft/skills/citation-style/references/other-source-models.md +146 -0
  391. package/payload/platform/plugins/writer-craft/skills/citation-style/references/reference-list-rules.md +70 -0
  392. package/payload/platform/plugins/writer-craft/skills/editorial-practice/SKILL.md +108 -0
  393. package/payload/platform/plugins/writer-craft/skills/editorial-practice/references/copyediting.md +73 -0
  394. package/payload/platform/plugins/writer-craft/skills/editorial-practice/references/developmental-editing.md +85 -0
  395. package/payload/platform/plugins/writer-craft/skills/editorial-practice/references/genre-specific-editing.md +78 -0
  396. package/payload/platform/plugins/writer-craft/skills/editorial-practice/references/line-editing.md +55 -0
  397. package/payload/platform/plugins/writer-craft/skills/editorial-practice/references/self-editing.md +89 -0
  398. package/payload/platform/plugins/writer-craft/skills/persuasive-storytelling/SKILL.md +114 -0
  399. package/payload/platform/plugins/writer-craft/skills/persuasive-storytelling/references/audience-analysis.md +73 -0
  400. package/payload/platform/plugins/writer-craft/skills/persuasive-storytelling/references/crafting-persuasive-story.md +76 -0
  401. package/payload/platform/plugins/writer-craft/skills/persuasive-storytelling/references/persuasion-case-studies.md +67 -0
  402. package/payload/platform/plugins/writer-craft/skills/persuasive-storytelling/references/transformation-framework.md +86 -0
  403. package/payload/platform/plugins/writer-craft/skills/point-of-view/SKILL.md +97 -0
  404. package/payload/platform/plugins/writer-craft/skills/point-of-view/references/indirect-narration.md +72 -0
  405. package/payload/platform/plugins/writer-craft/skills/point-of-view/references/pov-types-and-voice.md +91 -0
  406. package/payload/platform/plugins/writer-craft/skills/point-of-view/references/protagonist-filter.md +71 -0
  407. package/payload/platform/plugins/writer-craft/skills/point-of-view/references/tense-and-person.md +85 -0
  408. package/payload/platform/plugins/writer-craft/skills/prose-craft/SKILL.md +100 -0
  409. package/payload/platform/plugins/writer-craft/skills/prose-craft/references/punctuation-and-grammar.md +72 -0
  410. package/payload/platform/plugins/writer-craft/skills/prose-craft/references/repetition.md +71 -0
  411. package/payload/platform/plugins/writer-craft/skills/prose-craft/references/sound-and-rhythm.md +64 -0
  412. package/payload/platform/plugins/writer-craft/skills/prose-craft/references/word-economy.md +93 -0
  413. package/payload/platform/plugins/writer-craft/skills/reader-engagement/SKILL.md +100 -0
  414. package/payload/platform/plugins/writer-craft/skills/reader-engagement/references/cause-effect-setup-payoff.md +79 -0
  415. package/payload/platform/plugins/writer-craft/skills/reader-engagement/references/conflict-escalation.md +81 -0
  416. package/payload/platform/plugins/writer-craft/skills/reader-engagement/references/hooking-readers.md +67 -0
  417. package/payload/platform/plugins/writer-craft/skills/reader-engagement/references/neurochemistry-of-engagement.md +94 -0
  418. package/payload/platform/plugins/writer-craft/skills/review-manuscript/SKILL.md +111 -0
  419. package/payload/platform/plugins/writer-craft/skills/review-manuscript/references/review-manuscript-checklist.md +119 -0
  420. package/payload/platform/plugins/writer-craft/skills/review-prose/SKILL.md +99 -0
  421. package/payload/platform/plugins/writer-craft/skills/review-prose/references/prose-review-checklist.md +112 -0
  422. package/payload/platform/plugins/writer-craft/skills/review-scene/SKILL.md +99 -0
  423. package/payload/platform/plugins/writer-craft/skills/review-scene/references/scene-analysis-framework.md +95 -0
  424. package/payload/platform/plugins/writer-craft/skills/story-architecture/SKILL.md +106 -0
  425. package/payload/platform/plugins/writer-craft/skills/story-architecture/references/blueprinting-and-scene-cards.md +118 -0
  426. package/payload/platform/plugins/writer-craft/skills/story-architecture/references/inner-issue-and-protagonist-goal.md +66 -0
  427. package/payload/platform/plugins/writer-craft/skills/story-architecture/references/misbelief-desire-worldview.md +87 -0
  428. package/payload/platform/plugins/writer-craft/skills/story-architecture/references/origin-scenes-and-escalation.md +82 -0
  429. package/payload/platform/plugins/writer-craft/skills/story-blueprint/SKILL.md +133 -0
  430. package/payload/platform/plugins/writer-craft/skills/story-blueprint/references/blueprinting-exercises.md +118 -0
  431. package/payload/platform/plugins/writer-craft/skills/story-blueprint/references/blueprinting-process.md +128 -0
  432. package/payload/platform/services/claude-session-manager/dist/config.d.ts +6 -0
  433. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  434. package/payload/platform/services/claude-session-manager/dist/config.js +60 -1
  435. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  436. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +9 -0
  437. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  438. package/payload/platform/services/claude-session-manager/dist/http-server.js +34 -0
  439. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  440. package/payload/platform/services/claude-session-manager/dist/index.js +12 -0
  441. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  442. package/payload/platform/services/claude-session-manager/dist/public-tool-audit.d.ts +33 -0
  443. package/payload/platform/services/claude-session-manager/dist/public-tool-audit.d.ts.map +1 -0
  444. package/payload/platform/services/claude-session-manager/dist/public-tool-audit.js +149 -0
  445. package/payload/platform/services/claude-session-manager/dist/public-tool-audit.js.map +1 -0
  446. package/payload/platform/services/claude-session-manager/dist/spawn-rate-limiter.d.ts +28 -0
  447. package/payload/platform/services/claude-session-manager/dist/spawn-rate-limiter.d.ts.map +1 -0
  448. package/payload/platform/services/claude-session-manager/dist/spawn-rate-limiter.js +77 -0
  449. package/payload/platform/services/claude-session-manager/dist/spawn-rate-limiter.js.map +1 -0
@@ -0,0 +1,105 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { homedir } from "node:os";
5
+
6
+ const ALGORITHM = "aes-256-gcm";
7
+ const KEY_LENGTH = 32; // 256 bits
8
+ const IV_LENGTH = 12; // 96 bits — recommended for GCM
9
+ const AUTH_TAG_LENGTH = 16; // 128 bits
10
+
11
+ /**
12
+ * Resolve the config directory from brand.json (same pattern as anthropic-key, email).
13
+ * Falls back to ".maxy" if brand.json is unreadable.
14
+ */
15
+ function resolveConfigDir(): string {
16
+ try {
17
+ const platformRoot = process.env.PLATFORM_ROOT;
18
+ if (platformRoot) {
19
+ const brandPath = resolve(platformRoot, "config/brand.json");
20
+ const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
21
+ if (brand.configDir) return brand.configDir;
22
+ }
23
+ } catch {
24
+ // Fall back to default
25
+ }
26
+ return ".maxy";
27
+ }
28
+
29
+ function keyFilePath(): string {
30
+ return resolve(homedir(), resolveConfigDir(), ".loop-encryption-key");
31
+ }
32
+
33
+ /**
34
+ * Load or generate the encryption key. Generated on first use,
35
+ * stored at ~/.{configDir}/.loop-encryption-key with mode 0o600.
36
+ */
37
+ function loadOrGenerateKey(): Buffer {
38
+ const path = keyFilePath();
39
+
40
+ if (existsSync(path)) {
41
+ const hex = readFileSync(path, "utf-8").trim();
42
+ const buf = Buffer.from(hex, "hex");
43
+ if (buf.length !== KEY_LENGTH) {
44
+ throw new Error(
45
+ `Encryption key at ${path} is ${buf.length} bytes, expected ${KEY_LENGTH}. ` +
46
+ `Delete the file and re-register all Loop team keys.`
47
+ );
48
+ }
49
+ return buf;
50
+ }
51
+
52
+ // First use — generate a new key
53
+ const key = randomBytes(KEY_LENGTH);
54
+ writeFileSync(path, key.toString("hex"), { mode: 0o600 });
55
+ console.error(`[loop] encryption key generated at ${path}`);
56
+ return key;
57
+ }
58
+
59
+ /**
60
+ * Encrypt a plaintext string. Returns "iv:ciphertext:authTag" (hex-encoded).
61
+ */
62
+ export function encrypt(plaintext: string): string {
63
+ const key = loadOrGenerateKey();
64
+ const iv = randomBytes(IV_LENGTH);
65
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
66
+
67
+ let encrypted = cipher.update(plaintext, "utf-8", "hex");
68
+ encrypted += cipher.final("hex");
69
+ const authTag = cipher.getAuthTag().toString("hex");
70
+
71
+ return `${iv.toString("hex")}:${encrypted}:${authTag}`;
72
+ }
73
+
74
+ /**
75
+ * Decrypt a value produced by encrypt(). Expects "iv:ciphertext:authTag" (hex-encoded).
76
+ * Throws on tampered/corrupt data or wrong key.
77
+ */
78
+ export function decrypt(encryptedValue: string): string {
79
+ const parts = encryptedValue.split(":");
80
+ if (parts.length !== 3) {
81
+ throw new Error("Malformed encrypted value — expected iv:ciphertext:authTag");
82
+ }
83
+
84
+ const [ivHex, ciphertextHex, authTagHex] = parts;
85
+
86
+ const path = keyFilePath();
87
+ if (!existsSync(path)) {
88
+ throw new Error(
89
+ `Encryption key not found at ${path}. ` +
90
+ `All stored Loop API keys are unrecoverable. Re-register keys.`
91
+ );
92
+ }
93
+
94
+ const key = loadOrGenerateKey();
95
+ const iv = Buffer.from(ivHex, "hex");
96
+ const authTag = Buffer.from(authTagHex, "hex");
97
+
98
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
99
+ decipher.setAuthTag(authTag);
100
+
101
+ let decrypted = decipher.update(ciphertextHex, "hex", "utf-8");
102
+ decrypted += decipher.final("utf-8");
103
+
104
+ return decrypted;
105
+ }
@@ -0,0 +1,604 @@
1
+ import { getSession } from "./neo4j.js";
2
+ import { decrypt } from "./crypto.js";
3
+
4
+ // ─────────────────────────────────────────────────────────────────
5
+ // Loop API V2 HTTP Client + Cross-Team Aggregation Engine
6
+ //
7
+ // ┌─ Load LoopTeamKey nodes (Neo4j) ─┐
8
+ // │ │
9
+ // ▼ │
10
+ // For each key: │
11
+ // 1. Decrypt API key │
12
+ // 2. Check permissions │
13
+ // 3. HTTP GET/POST/PUT → Loop API V2 │
14
+ // │ │
15
+ // ▼ │
16
+ // Promise.allSettled → merge results │
17
+ // Log aggregate summary │
18
+ // ─────────────────────────────────────────────────────────────────
19
+
20
+ const LOOP_BASE_URL = "https://api.loop.software";
21
+ const REQUEST_TIMEOUT_MS = 10_000;
22
+ const TIMEOUT_RETRY_BACKOFF_MS = 2_000;
23
+ const DEFAULT_LIMIT_PER_TEAM = 50;
24
+ const DEFAULT_LIMIT_TOTAL = 200;
25
+
26
+ // One bounded retry on transient timeout — recovers from intermittent
27
+ // Loop-API latency spikes that would otherwise drop a team from coverage.
28
+ function isTimeoutError(err: unknown): boolean {
29
+ if (!(err instanceof Error)) return false;
30
+ return (
31
+ err.name === "TimeoutError" ||
32
+ err.name === "AbortError" ||
33
+ /aborted due to timeout/i.test(err.message)
34
+ );
35
+ }
36
+
37
+ // ─── Loop API V2 Envelope ────────────────────────────────────────
38
+ //
39
+ // Every Loop API V2 response is wrapped in a status envelope:
40
+ //
41
+ // { statusCode: "success", httpResponseCode: 200,
42
+ // errors: [], warnings: [], data: <payload> }
43
+ //
44
+ // unwrapEnvelope detects this shape and returns the inner `data`,
45
+ // so tool handlers receive bare arrays/objects — not the wrapper.
46
+ // Non-envelope responses (if any) pass through unchanged.
47
+ // ──────────────────────────────────────────────────────────────────
48
+
49
+ interface LoopEnvelope<T> {
50
+ statusCode: string;
51
+ httpResponseCode: number;
52
+ errors?: string[];
53
+ warnings?: string[];
54
+ data: T;
55
+ }
56
+
57
+ function isEnvelope(val: unknown): val is LoopEnvelope<unknown> {
58
+ return (
59
+ val !== null &&
60
+ typeof val === "object" &&
61
+ !Array.isArray(val) &&
62
+ "statusCode" in val &&
63
+ "httpResponseCode" in val &&
64
+ "data" in val
65
+ );
66
+ }
67
+
68
+ function unwrapEnvelope<T>(parsed: unknown, toolName: string, teamName: string): T {
69
+ if (!isEnvelope(parsed)) {
70
+ return parsed as T;
71
+ }
72
+
73
+ if (parsed.statusCode !== "success") {
74
+ const errMsg = parsed.errors?.length
75
+ ? parsed.errors.join("; ")
76
+ : `statusCode=${parsed.statusCode}`;
77
+ throw new Error(
78
+ `Loop API error for team=${teamName} tool=${toolName}: ${errMsg} (httpResponseCode=${parsed.httpResponseCode})`
79
+ );
80
+ }
81
+
82
+ if (parsed.warnings?.length) {
83
+ console.error(
84
+ `[loop] ${toolName} team=${teamName} warnings: ${parsed.warnings.join("; ")}`
85
+ );
86
+ }
87
+
88
+ return parsed.data as T;
89
+ }
90
+
91
+ export interface LoopTeamKey {
92
+ teamName: string;
93
+ encryptedKey: string;
94
+ teamAddress: string;
95
+ agentId: string;
96
+ permissions: string[];
97
+ createdAt: string;
98
+ updatedAt: string;
99
+ }
100
+
101
+ export interface TeamResult<T> {
102
+ teamName: string;
103
+ data: T[];
104
+ }
105
+
106
+ export interface AggregationFailure {
107
+ teamName: string;
108
+ reason: string;
109
+ kind: "timeout" | "other";
110
+ }
111
+
112
+ export interface AggregationResult<T> {
113
+ results: TeamResult<T>[];
114
+ failures: AggregationFailure[];
115
+ skipped: { teamName: string; reason: string }[];
116
+ totalTeams: number;
117
+ }
118
+
119
+ /**
120
+ * Load all LoopTeamKey nodes for an account from Neo4j.
121
+ */
122
+ export async function loadTeamKeys(accountId: string): Promise<LoopTeamKey[]> {
123
+ const session = getSession();
124
+ try {
125
+ const result = await session.run(
126
+ `MATCH (k:LoopTeamKey {accountId: $accountId})
127
+ RETURN k.teamName AS teamName, k.encryptedKey AS encryptedKey,
128
+ k.teamAddress AS teamAddress, k.agentId AS agentId,
129
+ k.permissions AS permissions, k.createdAt AS createdAt,
130
+ k.updatedAt AS updatedAt
131
+ ORDER BY k.teamName`,
132
+ { accountId }
133
+ );
134
+ return result.records.map((r) => ({
135
+ teamName: r.get("teamName"),
136
+ encryptedKey: r.get("encryptedKey"),
137
+ teamAddress: r.get("teamAddress") ?? "",
138
+ agentId: r.get("agentId") ?? "",
139
+ permissions: r.get("permissions") ?? [],
140
+ createdAt: r.get("createdAt") ?? "",
141
+ updatedAt: r.get("updatedAt") ?? "",
142
+ }));
143
+ } finally {
144
+ await session.close();
145
+ }
146
+ }
147
+
148
+ // Truncate to 200 chars, escape newlines, single line — `[loop] <tool> team=<t>
149
+ // status=<n> body="<...>"`. Emitted at the wrapper boundary before throwing so
150
+ // the body is visible regardless of how downstream catch sites format the error
151
+ // (the aggregator at line ~388 reduces LoopApiError to "HTTP <status>" and
152
+ // would otherwise discard the body).
153
+ function logErrorBody(
154
+ toolName: string,
155
+ teamName: string,
156
+ status: number,
157
+ body: string
158
+ ): void {
159
+ const truncated = body.slice(0, 200).replace(/\n/g, "\\n");
160
+ console.error(
161
+ `[loop] ${toolName} team=${teamName} status=${status} body="${truncated}"`
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Make a GET request to the Loop API V2.
167
+ */
168
+ export async function loopGet<T>(
169
+ apiKey: string,
170
+ path: string,
171
+ toolName: string,
172
+ teamName: string
173
+ ): Promise<T> {
174
+ const url = `${LOOP_BASE_URL}${path}`;
175
+ const start = Date.now();
176
+
177
+ const response = await fetch(url, {
178
+ headers: { "X-Api-Key": apiKey, "Accept": "application/json" },
179
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
180
+ });
181
+
182
+ const duration = Date.now() - start;
183
+ console.error(
184
+ `[loop] ${toolName} team=${teamName} endpoint=GET ${path} status=${response.status} duration=${duration}ms`
185
+ );
186
+
187
+ if (response.status === 204) {
188
+ return [] as unknown as T;
189
+ }
190
+
191
+ if (!response.ok) {
192
+ const errorBody = await response.text().catch(() => "");
193
+ logErrorBody(toolName, teamName, response.status, errorBody);
194
+ throw new LoopApiError(response.status, teamName, path, errorBody);
195
+ }
196
+
197
+ const text = await response.text();
198
+ try {
199
+ const parsed = JSON.parse(text);
200
+ return unwrapEnvelope<T>(parsed, toolName, teamName);
201
+ } catch (err) {
202
+ if (err instanceof SyntaxError) {
203
+ throw new Error(
204
+ `Loop returned non-JSON response for team=${teamName} path=${path} ` +
205
+ `(status=${response.status}, length=${text.length})`
206
+ );
207
+ }
208
+ throw err;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Make a POST request to the Loop API V2.
214
+ * For write operations: create viewings, record feedback, submit enquiries, etc.
215
+ * Returns parsed JSON response. Treats 204 as success with empty result.
216
+ */
217
+ export async function loopPost<T>(
218
+ apiKey: string,
219
+ path: string,
220
+ body: unknown,
221
+ toolName: string,
222
+ teamName: string
223
+ ): Promise<T> {
224
+ const url = `${LOOP_BASE_URL}${path}`;
225
+ const start = Date.now();
226
+
227
+ const response = await fetch(url, {
228
+ method: "POST",
229
+ headers: {
230
+ "X-Api-Key": apiKey,
231
+ "Accept": "application/json",
232
+ "Content-Type": "application/json",
233
+ },
234
+ body: JSON.stringify(body),
235
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
236
+ });
237
+
238
+ const duration = Date.now() - start;
239
+ console.error(
240
+ `[loop] WRITE ${toolName} team=${teamName} endpoint=POST ${path} status=${response.status} duration=${duration}ms`
241
+ );
242
+
243
+ if (response.status === 204) {
244
+ return { success: true } as unknown as T;
245
+ }
246
+
247
+ if (!response.ok) {
248
+ const errorBody = await response.text().catch(() => "");
249
+ logErrorBody(toolName, teamName, response.status, errorBody);
250
+ throw new LoopApiError(response.status, teamName, path, errorBody);
251
+ }
252
+
253
+ const text = await response.text();
254
+ try {
255
+ const parsed = JSON.parse(text);
256
+ return unwrapEnvelope<T>(parsed, toolName, teamName);
257
+ } catch (err) {
258
+ if (err instanceof SyntaxError) {
259
+ throw new Error(
260
+ `Loop returned non-JSON response for team=${teamName} path=${path} ` +
261
+ `(status=${response.status}, length=${text.length})`
262
+ );
263
+ }
264
+ throw err;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Make a PUT request to the Loop API V2.
270
+ * For update operations: batch matching, submit quotes, update preferences, etc.
271
+ */
272
+ export async function loopPut<T>(
273
+ apiKey: string,
274
+ path: string,
275
+ body: unknown,
276
+ toolName: string,
277
+ teamName: string
278
+ ): Promise<T> {
279
+ const url = `${LOOP_BASE_URL}${path}`;
280
+ const start = Date.now();
281
+
282
+ const response = await fetch(url, {
283
+ method: "PUT",
284
+ headers: {
285
+ "X-Api-Key": apiKey,
286
+ "Accept": "application/json",
287
+ "Content-Type": "application/json",
288
+ },
289
+ body: JSON.stringify(body),
290
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
291
+ });
292
+
293
+ const duration = Date.now() - start;
294
+ console.error(
295
+ `[loop] WRITE ${toolName} team=${teamName} endpoint=PUT ${path} status=${response.status} duration=${duration}ms`
296
+ );
297
+
298
+ if (response.status === 204) {
299
+ return { success: true } as unknown as T;
300
+ }
301
+
302
+ if (!response.ok) {
303
+ const errorBody = await response.text().catch(() => "");
304
+ logErrorBody(toolName, teamName, response.status, errorBody);
305
+ throw new LoopApiError(response.status, teamName, path, errorBody);
306
+ }
307
+
308
+ const text = await response.text();
309
+ try {
310
+ const parsed = JSON.parse(text);
311
+ return unwrapEnvelope<T>(parsed, toolName, teamName);
312
+ } catch (err) {
313
+ if (err instanceof SyntaxError) {
314
+ throw new Error(
315
+ `Loop returned non-JSON response for team=${teamName} path=${path} ` +
316
+ `(status=${response.status}, length=${text.length})`
317
+ );
318
+ }
319
+ throw err;
320
+ }
321
+ }
322
+
323
+ export class LoopApiError extends Error {
324
+ constructor(
325
+ public readonly status: number,
326
+ public readonly teamName: string,
327
+ public readonly path: string,
328
+ public readonly responseBody?: string
329
+ ) {
330
+ const detail = responseBody ? ` — ${responseBody.slice(0, 200)}` : "";
331
+ super(`Loop API returned ${status} for team=${teamName} path=${path}${detail}`);
332
+ this.name = "LoopApiError";
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Fan out a request across all registered teams for an account, merging results.
338
+ *
339
+ * Each tool provides a `buildRequest` function that takes a decrypted API key
340
+ * and returns the data for that team. The aggregation engine handles:
341
+ * - Key loading and decryption
342
+ * - Permission checking
343
+ * - Concurrent fan-out via Promise.allSettled
344
+ * - Result merging with teamName tags
345
+ * - Aggregate logging
346
+ *
347
+ * When `teamName` is provided, only that team is queried (no fan-out).
348
+ */
349
+ export async function aggregateAcrossTeams<T>(
350
+ accountId: string,
351
+ endpointGroup: string,
352
+ toolName: string,
353
+ buildRequest: (apiKey: string, teamName: string) => Promise<T[]>,
354
+ options?: { teamName?: string; limitPerTeam?: number; limitTotal?: number }
355
+ ): Promise<AggregationResult<T>> {
356
+ const allKeys = await loadTeamKeys(accountId);
357
+
358
+ if (allKeys.length === 0) {
359
+ return { results: [], failures: [], skipped: [], totalTeams: 0 };
360
+ }
361
+
362
+ // Filter to specific team if requested
363
+ const keys = options?.teamName
364
+ ? allKeys.filter((k) => k.teamName === options.teamName)
365
+ : allKeys;
366
+
367
+ if (keys.length === 0 && options?.teamName) {
368
+ return {
369
+ results: [],
370
+ failures: [{ teamName: options.teamName, reason: "Team not found", kind: "other" }],
371
+ skipped: [],
372
+ totalTeams: allKeys.length,
373
+ };
374
+ }
375
+
376
+ const limitPerTeam = options?.limitPerTeam ?? DEFAULT_LIMIT_PER_TEAM;
377
+ const limitTotal = options?.limitTotal ?? DEFAULT_LIMIT_TOTAL;
378
+
379
+ const results: TeamResult<T>[] = [];
380
+ const failures: AggregationFailure[] = [];
381
+ const skipped: { teamName: string; reason: string }[] = [];
382
+
383
+ // Check permissions and build per-team request thunks. Thunks (not stored
384
+ // promises) are required so a timed-out attempt can be re-invoked on retry.
385
+ const tasks: { key: LoopTeamKey; run: () => Promise<T[]> }[] = [];
386
+
387
+ for (const key of keys) {
388
+ if (!key.permissions.includes(endpointGroup)) {
389
+ console.error(
390
+ `[loop] ${toolName} team=${key.teamName} — permission denied (requires: ${endpointGroup})`
391
+ );
392
+ skipped.push({
393
+ teamName: key.teamName,
394
+ reason: `No ${endpointGroup} permission`,
395
+ });
396
+ continue;
397
+ }
398
+
399
+ let apiKey: string;
400
+ try {
401
+ apiKey = decrypt(key.encryptedKey);
402
+ } catch (err) {
403
+ const msg = err instanceof Error ? err.message : String(err);
404
+ console.error(`[loop] decrypt failed for team=${key.teamName}: ${msg}`);
405
+ failures.push({
406
+ teamName: key.teamName,
407
+ reason: "Decryption failed",
408
+ kind: "other",
409
+ });
410
+ continue;
411
+ }
412
+
413
+ const teamName = key.teamName;
414
+ tasks.push({
415
+ key,
416
+ run: async () => {
417
+ try {
418
+ return await buildRequest(apiKey, teamName);
419
+ } catch (err) {
420
+ if (!isTimeoutError(err)) throw err;
421
+ console.error(
422
+ `[loop] ${toolName} team=${teamName} timed out — retrying in ${TIMEOUT_RETRY_BACKOFF_MS}ms`
423
+ );
424
+ await new Promise((resolve) => setTimeout(resolve, TIMEOUT_RETRY_BACKOFF_MS));
425
+ return await buildRequest(apiKey, teamName);
426
+ }
427
+ },
428
+ });
429
+ }
430
+
431
+ // Fan out concurrent requests
432
+ const settled = await Promise.allSettled(tasks.map((t) => t.run()));
433
+
434
+ for (let i = 0; i < settled.length; i++) {
435
+ const outcome = settled[i];
436
+ const teamName = tasks[i].key.teamName;
437
+
438
+ if (outcome.status === "fulfilled") {
439
+ const data = Array.isArray(outcome.value)
440
+ ? outcome.value.slice(0, limitPerTeam)
441
+ : [];
442
+ results.push({ teamName, data });
443
+ } else {
444
+ const reason =
445
+ outcome.reason instanceof LoopApiError
446
+ ? `HTTP ${outcome.reason.status}`
447
+ : outcome.reason instanceof Error
448
+ ? outcome.reason.message
449
+ : String(outcome.reason);
450
+ const kind: AggregationFailure["kind"] = isTimeoutError(outcome.reason)
451
+ ? "timeout"
452
+ : "other";
453
+ console.error(`[loop] ${toolName} team=${teamName} failed: ${reason}`);
454
+ failures.push({ teamName, reason, kind });
455
+ }
456
+ }
457
+
458
+ // Apply total limit across all teams
459
+ let totalItems = 0;
460
+ for (const r of results) {
461
+ const remaining = limitTotal - totalItems;
462
+ if (remaining <= 0) {
463
+ r.data = [];
464
+ } else if (r.data.length > remaining) {
465
+ r.data = r.data.slice(0, remaining);
466
+ }
467
+ totalItems += r.data.length;
468
+ }
469
+
470
+ const timedOutCount = failures.filter((f) => f.kind === "timeout").length;
471
+ const erroredCount = failures.length - timedOutCount;
472
+
473
+ console.error(
474
+ `[loop] aggregate tool=${toolName} teams=${keys.length} ` +
475
+ `succeeded=${results.length} failed=${failures.length} skipped=${skipped.length}`
476
+ );
477
+ console.error(
478
+ `[loop] aggregate-coverage tool=${toolName} requested=${keys.length} ` +
479
+ `answered=${results.length} timed_out=${timedOutCount} errored=${erroredCount}`
480
+ );
481
+
482
+ return { results, failures, skipped, totalTeams: allKeys.length };
483
+ }
484
+
485
+ /**
486
+ * Resolve a single team's API key with permission checking.
487
+ * Handles key loading, decryption, and permission validation.
488
+ * Used for both reads and writes that target a specific team — no fan-out.
489
+ */
490
+ export async function withTeamKey<T>(
491
+ accountId: string,
492
+ teamName: string,
493
+ endpointGroup: string,
494
+ toolName: string,
495
+ execute: (apiKey: string) => Promise<T>
496
+ ): Promise<T> {
497
+ const allKeys = await loadTeamKeys(accountId);
498
+ if (allKeys.length === 0) {
499
+ throw new Error("No Loop teams registered. Use loop-key-register to add team API keys.");
500
+ }
501
+
502
+ const key = allKeys.find((k) => k.teamName === teamName);
503
+ if (!key) {
504
+ const available = allKeys.map((k) => k.teamName).join(", ");
505
+ throw new Error(`Team "${teamName}" not found. Available teams: ${available}`);
506
+ }
507
+
508
+ if (!key.permissions.includes(endpointGroup)) {
509
+ throw new Error(
510
+ `Team "${teamName}" does not have ${endpointGroup} permission. ` +
511
+ `Current permissions: ${key.permissions.join(", ")}`
512
+ );
513
+ }
514
+
515
+ const apiKey = decrypt(key.encryptedKey);
516
+ return execute(apiKey);
517
+ }
518
+
519
+ /**
520
+ * Format an aggregation result into a human-readable text response.
521
+ */
522
+ export function formatAggregationResult<T>(
523
+ result: AggregationResult<T>,
524
+ formatItem: (item: T, teamName: string) => string,
525
+ entityName: string
526
+ ): string {
527
+ if (result.totalTeams === 0) {
528
+ return "No Loop teams registered. Use loop-key-register to add team API keys.";
529
+ }
530
+
531
+ const allSkippedOrFailed =
532
+ result.results.length === 0 && result.failures.length + result.skipped.length > 0;
533
+
534
+ if (allSkippedOrFailed) {
535
+ const reasons = [
536
+ ...result.failures.map((f) => `${f.teamName}: ${f.reason}`),
537
+ ...result.skipped.map((s) => `${s.teamName}: ${s.reason}`),
538
+ ];
539
+ return `All teams failed or were skipped:\n${reasons.join("\n")}`;
540
+ }
541
+
542
+ const lines: string[] = [];
543
+ let totalItems = 0;
544
+
545
+ for (const teamResult of result.results) {
546
+ if (teamResult.data.length === 0) continue;
547
+ totalItems += teamResult.data.length;
548
+ lines.push(`\n## ${teamResult.teamName}`);
549
+ for (const item of teamResult.data) {
550
+ lines.push(formatItem(item, teamResult.teamName));
551
+ }
552
+ }
553
+
554
+ // Coverage = teams that had a chance to answer (succeeded + failed). Skipped
555
+ // teams are excluded from the denominator: they were configured-out, not lost.
556
+ const answered = result.results.length;
557
+ const failed = result.failures.length;
558
+ const totalCovered = answered + failed;
559
+ const coverageNote = formatCoverageNote(result.failures);
560
+
561
+ if (totalItems === 0) {
562
+ if (failed > 0) {
563
+ return `No ${entityName} found across ${answered} of ${totalCovered} teams (${coverageNote}).`;
564
+ }
565
+ const teamCount =
566
+ result.results.length === 1
567
+ ? result.results[0].teamName
568
+ : `${result.results.length} teams`;
569
+ return `No ${entityName} found across ${teamCount}.`;
570
+ }
571
+
572
+ const teamCountFragment =
573
+ failed > 0 ? `${answered} of ${totalCovered} team(s)` : `${answered} team(s)`;
574
+ let header = `${totalItems} ${entityName} across ${teamCountFragment}:`;
575
+ const parts: string[] = [header, ...lines];
576
+
577
+ if (failed > 0) {
578
+ parts.push(`\n---\nNote: ${coverageNote}.`);
579
+ }
580
+ if (result.skipped.length > 0) {
581
+ parts.push(
582
+ `Note: ${result.skipped.length} team(s) skipped (insufficient permissions).`
583
+ );
584
+ }
585
+
586
+ return parts.join("\n");
587
+ }
588
+
589
+ function formatCoverageNote(failures: AggregationFailure[]): string {
590
+ const timedOut = failures.filter((f) => f.kind === "timeout");
591
+ const errored = failures.filter((f) => f.kind !== "timeout");
592
+ const fragments: string[] = [];
593
+ if (timedOut.length > 0) {
594
+ fragments.push(
595
+ `${timedOut.length} timed out: ${timedOut.map((f) => f.teamName).join(", ")}`
596
+ );
597
+ }
598
+ if (errored.length > 0) {
599
+ fragments.push(
600
+ `${errored.length} errored: ${errored.map((f) => f.teamName).join(", ")}`
601
+ );
602
+ }
603
+ return fragments.join("; ");
604
+ }