@cometchat/skills 4.4.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (489) hide show
  1. package/.claude-plugin/marketplace.json +55 -0
  2. package/.claude-plugin/plugin.json +14 -0
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +41 -163
  5. package/RULES.md +114 -0
  6. package/bin/cometchat-skills.mjs +1180 -0
  7. package/bin/legacy-skills.json +95 -0
  8. package/catalogs/android-v6.json +1646 -0
  9. package/catalogs/flutter-v6.json +651 -0
  10. package/catalogs/ios-v5.json +1383 -0
  11. package/catalogs/rn-calls-v5.json +134 -0
  12. package/catalogs/rn-calls-v5.symbols.txt +58 -0
  13. package/catalogs/rn-sdk-v4.json +672 -0
  14. package/catalogs/rn-v5.json +449 -0
  15. package/catalogs/rn-v5.symbols.txt +216 -0
  16. package/catalogs/sdk-android-v5.json +514 -0
  17. package/contracts.android-calls-v5.json +122 -0
  18. package/contracts.android-v6.json +135 -0
  19. package/contracts.angular-v5.json +694 -0
  20. package/contracts.flutter-calls-v5.json +77 -0
  21. package/contracts.flutter-v6.json +126 -0
  22. package/contracts.ios-calls-v5.json +67 -0
  23. package/contracts.ios-v5.json +436 -0
  24. package/contracts.json +82 -0
  25. package/contracts.rn-calls-v5.json +77 -0
  26. package/contracts.rn-v5.json +727 -0
  27. package/contracts.sdk-android-v5.json +15 -0
  28. package/contracts.web-calls-v5.json +45 -0
  29. package/features.android-calls-v5.json +246 -0
  30. package/features.android-v6.json +65 -0
  31. package/features.angular-v5.json +521 -0
  32. package/features.flutter-calls-v5.json +286 -0
  33. package/features.flutter-v6.json +484 -0
  34. package/features.ios-calls-v5.json +236 -0
  35. package/features.ios-v5.json +573 -0
  36. package/features.json +61 -0
  37. package/features.rn-calls-v5.json +187 -0
  38. package/features.rn-v5.json +614 -0
  39. package/features.sdk-android-v5.json +40 -0
  40. package/features.sdk-jsv4.json +24 -0
  41. package/features.web-calls-v5.json +108 -0
  42. package/package.json +151 -74
  43. package/peers.yaml +312 -0
  44. package/registry/README.md +31 -0
  45. package/registry/android-calls-v5.json +54 -0
  46. package/registry/android-v6.json +137 -0
  47. package/registry/angular-v5.json +114 -0
  48. package/registry/flutter-calls-v5.json +54 -0
  49. package/registry/flutter-v6.json +119 -0
  50. package/registry/ios-calls-v5.json +59 -0
  51. package/registry/ios-v5.json +97 -0
  52. package/registry/rn-calls-v5.json +59 -0
  53. package/registry/rn-v5.json +129 -0
  54. package/registry/sdk-android-v5.json +53 -0
  55. package/registry/sdk-jsv4.json +53 -0
  56. package/registry/web-calls-v5.json +54 -0
  57. package/registry/web-v7.json +99 -0
  58. package/skills/cometchat/SKILL.md +42 -2096
  59. package/skills/cometchat-android-v5-calls-sdk/SKILL.md +206 -0
  60. package/skills/cometchat-android-v5-calls-sdk/references/build-truth.md +63 -0
  61. package/skills/cometchat-android-v5-calls-sdk/references/docs-map.md +119 -0
  62. package/skills/cometchat-android-v5-calls-sdk/references/method-map.md +53 -0
  63. package/skills/cometchat-android-v5-calls-sdk/references/pitfalls.md +72 -0
  64. package/skills/cometchat-android-v5-calls-sdk/references/ringing.md +92 -0
  65. package/skills/cometchat-android-v5-calls-sdk/references/user-switch.md +90 -0
  66. package/skills/cometchat-android-v5-sdk/SKILL.md +127 -0
  67. package/skills/cometchat-android-v5-sdk/references/app-surface.md +88 -0
  68. package/skills/cometchat-android-v5-sdk/references/docs-map.md +91 -0
  69. package/skills/cometchat-android-v6-builder-settings/SKILL.md +51 -137
  70. package/skills/cometchat-android-v6-calls/SKILL.md +128 -411
  71. package/skills/cometchat-android-v6-compose-components/SKILL.md +84 -194
  72. package/skills/cometchat-android-v6-compose-customization/SKILL.md +66 -411
  73. package/skills/cometchat-android-v6-compose-placement/SKILL.md +154 -237
  74. package/skills/cometchat-android-v6-core/SKILL.md +236 -672
  75. package/skills/cometchat-android-v6-core/references/anti-patterns.md +31 -0
  76. package/skills/cometchat-android-v6-core/references/component-props.md +168 -0
  77. package/skills/cometchat-android-v6-core/references/docs-map.md +153 -0
  78. package/skills/cometchat-android-v6-core/references/layout.md +112 -0
  79. package/skills/cometchat-android-v6-core/references/lifecycle.md +75 -0
  80. package/skills/cometchat-android-v6-core/references/setup-credentials.md +105 -0
  81. package/skills/cometchat-android-v6-core/references/troubleshooting.md +36 -0
  82. package/skills/cometchat-android-v6-events/SKILL.md +46 -184
  83. package/skills/cometchat-android-v6-extensions/SKILL.md +39 -179
  84. package/skills/cometchat-android-v6-features/SKILL.md +74 -143
  85. package/skills/cometchat-android-v6-kotlin-components/SKILL.md +75 -193
  86. package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +67 -419
  87. package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +77 -223
  88. package/skills/cometchat-android-v6-migration/SKILL.md +67 -378
  89. package/skills/cometchat-android-v6-production/SKILL.md +40 -179
  90. package/skills/cometchat-android-v6-push/SKILL.md +39 -289
  91. package/skills/cometchat-android-v6-testing/SKILL.md +50 -265
  92. package/skills/cometchat-android-v6-troubleshooting/SKILL.md +70 -254
  93. package/skills/cometchat-angular-v5-calls/SKILL.md +193 -0
  94. package/skills/cometchat-angular-v5-components/SKILL.md +131 -0
  95. package/skills/cometchat-angular-v5-components/references/catalog.md +111 -0
  96. package/skills/cometchat-angular-v5-components/references/host-composed.md +46 -0
  97. package/skills/cometchat-angular-v5-core/SKILL.md +300 -0
  98. package/skills/cometchat-angular-v5-core/references/anti-patterns.md +49 -0
  99. package/skills/cometchat-angular-v5-core/references/component-props.md +55 -0
  100. package/skills/cometchat-angular-v5-core/references/dependencies.md +66 -0
  101. package/skills/cometchat-angular-v5-core/references/docs-map.md +132 -0
  102. package/skills/cometchat-angular-v5-core/references/layout.md +64 -0
  103. package/skills/cometchat-angular-v5-core/references/lifecycle.md +202 -0
  104. package/skills/cometchat-angular-v5-core/references/setup-credentials.md +137 -0
  105. package/skills/cometchat-angular-v5-core/references/theming.md +70 -0
  106. package/skills/cometchat-angular-v5-core/references/troubleshooting.md +27 -0
  107. package/skills/cometchat-angular-v5-customization/SKILL.md +94 -0
  108. package/skills/cometchat-angular-v5-features/SKILL.md +107 -0
  109. package/skills/cometchat-angular-v5-features/references/custom-messages.md +133 -0
  110. package/skills/cometchat-angular-v5-features/references/formatters.md +28 -0
  111. package/skills/cometchat-angular-v5-features/references/host-composed-extensions.md +85 -0
  112. package/skills/cometchat-angular-v5-migration/SKILL.md +40 -0
  113. package/skills/cometchat-angular-v5-migration/references/migration-guide.md +165 -0
  114. package/skills/cometchat-angular-v5-patterns/SKILL.md +107 -0
  115. package/skills/cometchat-angular-v5-placement/SKILL.md +133 -0
  116. package/skills/cometchat-angular-v5-placement/references/combined-app.md +174 -0
  117. package/skills/cometchat-angular-v5-placement/references/core-surface.md +199 -0
  118. package/skills/cometchat-angular-v5-production/SKILL.md +101 -0
  119. package/skills/cometchat-angular-v5-push/SKILL.md +61 -0
  120. package/skills/cometchat-angular-v5-testing/SKILL.md +112 -0
  121. package/skills/cometchat-angular-v5-troubleshooting/SKILL.md +96 -0
  122. package/skills/cometchat-flutter-v5-sdk/SKILL.md +188 -0
  123. package/skills/cometchat-flutter-v5-sdk/references/doc-corrections.md +55 -0
  124. package/skills/cometchat-flutter-v5-sdk/references/docs-map.md +211 -0
  125. package/skills/cometchat-flutter-v5-sdk/references/platform-notes.md +48 -0
  126. package/skills/cometchat-flutter-v5-sdk/references/transcription.md +87 -0
  127. package/skills/cometchat-flutter-v6-calls/SKILL.md +75 -387
  128. package/skills/cometchat-flutter-v6-components/SKILL.md +88 -361
  129. package/skills/cometchat-flutter-v6-core/SKILL.md +154 -593
  130. package/skills/cometchat-flutter-v6-core/references/anti-patterns.md +55 -0
  131. package/skills/cometchat-flutter-v6-core/references/docs-map.md +143 -0
  132. package/skills/cometchat-flutter-v6-core/references/layout.md +45 -0
  133. package/skills/cometchat-flutter-v6-core/references/lifecycle.md +87 -0
  134. package/skills/cometchat-flutter-v6-core/references/setup-credentials.md +84 -0
  135. package/skills/cometchat-flutter-v6-core/references/troubleshooting.md +26 -0
  136. package/skills/cometchat-flutter-v6-customization/SKILL.md +70 -712
  137. package/skills/cometchat-flutter-v6-events/SKILL.md +79 -174
  138. package/skills/cometchat-flutter-v6-features/SKILL.md +55 -394
  139. package/skills/cometchat-flutter-v6-migration/SKILL.md +68 -367
  140. package/skills/cometchat-flutter-v6-migration/references/migration-guide.md +131 -0
  141. package/skills/cometchat-flutter-v6-patterns/SKILL.md +61 -0
  142. package/skills/cometchat-flutter-v6-placement/SKILL.md +94 -381
  143. package/skills/cometchat-flutter-v6-production/SKILL.md +58 -615
  144. package/skills/cometchat-flutter-v6-push/SKILL.md +46 -455
  145. package/skills/cometchat-flutter-v6-testing/SKILL.md +68 -299
  146. package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +54 -878
  147. package/skills/cometchat-ios-calls/SKILL.md +105 -511
  148. package/skills/cometchat-ios-components/SKILL.md +44 -834
  149. package/skills/cometchat-ios-core/SKILL.md +181 -1068
  150. package/skills/cometchat-ios-core/references/anti-patterns.md +80 -0
  151. package/skills/cometchat-ios-core/references/docs-map.md +123 -0
  152. package/skills/cometchat-ios-core/references/install.md +23 -0
  153. package/skills/cometchat-ios-core/references/layout.md +186 -0
  154. package/skills/cometchat-ios-core/references/setup-credentials.md +197 -0
  155. package/skills/cometchat-ios-core/references/swiftui.md +91 -0
  156. package/skills/cometchat-ios-core/references/troubleshooting.md +100 -0
  157. package/skills/cometchat-ios-customization/SKILL.md +33 -968
  158. package/skills/cometchat-ios-features/SKILL.md +29 -738
  159. package/skills/cometchat-ios-migration/SKILL.md +39 -0
  160. package/skills/cometchat-ios-placement/SKILL.md +51 -729
  161. package/skills/cometchat-ios-push/SKILL.md +24 -602
  162. package/skills/cometchat-ios-v5-sdk/SKILL.md +161 -0
  163. package/skills/cometchat-ios-v5-sdk/references/docs-map.md +326 -0
  164. package/skills/cometchat-js-v5-sdk/SKILL.md +95 -0
  165. package/skills/cometchat-js-v5-sdk/references/docs-map.md +114 -0
  166. package/skills/cometchat-onboarding/SKILL.md +89 -0
  167. package/skills/cometchat-onboarding/references/archetypes.md +51 -0
  168. package/skills/cometchat-onboarding/references/calling-first.md +77 -0
  169. package/skills/cometchat-onboarding/references/not-in-this-pack.md +8 -0
  170. package/skills/cometchat-onboarding/references/plan-artifact.md +133 -0
  171. package/skills/cometchat-onboarding/references/platforms.md +90 -0
  172. package/skills/cometchat-react-native-bare-patterns/SKILL.md +120 -0
  173. package/skills/cometchat-react-native-calls/SKILL.md +200 -0
  174. package/skills/cometchat-react-native-components/SKILL.md +142 -0
  175. package/skills/cometchat-react-native-core/SKILL.md +313 -0
  176. package/skills/cometchat-react-native-core/references/anti-patterns.md +79 -0
  177. package/skills/cometchat-react-native-core/references/component-props.md +105 -0
  178. package/skills/cometchat-react-native-core/references/dependencies.md +62 -0
  179. package/skills/cometchat-react-native-core/references/docs-map.md +126 -0
  180. package/skills/cometchat-react-native-core/references/layout.md +61 -0
  181. package/skills/cometchat-react-native-core/references/lifecycle.md +64 -0
  182. package/skills/cometchat-react-native-core/references/setup-credentials.md +39 -0
  183. package/skills/cometchat-react-native-customization/SKILL.md +198 -0
  184. package/skills/cometchat-react-native-expo-patterns/SKILL.md +116 -0
  185. package/skills/cometchat-react-native-features/SKILL.md +122 -0
  186. package/skills/cometchat-react-native-migration/SKILL.md +103 -0
  187. package/skills/cometchat-react-native-placement/SKILL.md +158 -0
  188. package/skills/cometchat-react-native-production/SKILL.md +78 -0
  189. package/skills/cometchat-react-native-push/SKILL.md +88 -0
  190. package/skills/cometchat-react-native-sdk/SKILL.md +126 -0
  191. package/skills/cometchat-react-native-sdk/references/method-map.md +239 -0
  192. package/skills/cometchat-react-native-sdk/references/page-map.md +96 -0
  193. package/skills/cometchat-react-native-testing/SKILL.md +94 -0
  194. package/skills/cometchat-react-native-troubleshooting/SKILL.md +116 -0
  195. package/skills/cometchat-react-native-v5-sdk/SKILL.md +150 -0
  196. package/skills/cometchat-react-native-v5-sdk/references/docs-map.md +127 -0
  197. package/skills/cometchat-react-native-v5-sdk/references/ringing-voip.md +369 -0
  198. package/skills/cometchat-react-v7-calls/SKILL.md +75 -0
  199. package/skills/cometchat-react-v7-components/SKILL.md +72 -0
  200. package/skills/cometchat-react-v7-core/SKILL.md +101 -0
  201. package/skills/cometchat-react-v7-core/references/anti-patterns.md +19 -0
  202. package/skills/cometchat-react-v7-core/references/component-props.md +82 -0
  203. package/skills/cometchat-react-v7-core/references/dependencies.md +23 -0
  204. package/skills/cometchat-react-v7-core/references/docs-map.md +117 -0
  205. package/skills/cometchat-react-v7-core/references/i18n-rtl-a11y.md +22 -0
  206. package/skills/cometchat-react-v7-core/references/layout.md +158 -0
  207. package/skills/cometchat-react-v7-core/references/lifecycle.md +74 -0
  208. package/skills/cometchat-react-v7-core/references/setup-credentials.md +58 -0
  209. package/skills/cometchat-react-v7-core/references/ssr.md +11 -0
  210. package/skills/cometchat-react-v7-core/references/theming.md +52 -0
  211. package/skills/cometchat-react-v7-core/references/troubleshooting.md +28 -0
  212. package/skills/cometchat-react-v7-customization/SKILL.md +84 -0
  213. package/skills/cometchat-react-v7-features/SKILL.md +92 -0
  214. package/skills/cometchat-react-v7-migration/SKILL.md +39 -0
  215. package/skills/cometchat-react-v7-migration/references/migration-guide.md +119 -0
  216. package/skills/cometchat-react-v7-patterns/SKILL.md +43 -0
  217. package/skills/cometchat-react-v7-placement/SKILL.md +106 -0
  218. package/skills/cometchat-react-v7-placement/references/recipes.md +354 -0
  219. package/skills/cometchat-react-v7-push/SKILL.md +53 -0
  220. package/test-suite/README.md +70 -0
  221. package/test-suite/catalogs/android-calls-v5.json +830 -0
  222. package/test-suite/catalogs/android-calls-v5.symbols.txt +499 -0
  223. package/test-suite/catalogs/android-v6.json +1646 -0
  224. package/test-suite/catalogs/android-v6.symbols.txt +298 -0
  225. package/test-suite/catalogs/angular-v5.json +7587 -0
  226. package/test-suite/catalogs/flutter-calls-v5.json +196 -0
  227. package/test-suite/catalogs/flutter-calls-v5.symbols.txt +59 -0
  228. package/test-suite/catalogs/flutter-v6.json +651 -0
  229. package/test-suite/catalogs/ios-calls-v5.json +158 -0
  230. package/test-suite/catalogs/ios-calls-v5.symbols.txt +70 -0
  231. package/test-suite/catalogs/ios-v5.json +1383 -0
  232. package/test-suite/catalogs/ios-v5.symbols.txt +699 -0
  233. package/test-suite/catalogs/rn-calls-v5.json +134 -0
  234. package/test-suite/catalogs/rn-sdk-v4.json +672 -0
  235. package/test-suite/catalogs/rn-v5.json +449 -0
  236. package/test-suite/catalogs/sdk-android-v5.json +514 -0
  237. package/test-suite/catalogs/sdk-android-v5.symbols.txt +254 -0
  238. package/test-suite/catalogs/sdk-jsv4.json +800 -0
  239. package/test-suite/catalogs/web-calls-v5.json +195 -0
  240. package/test-suite/catalogs/web-v7.json +1243 -0
  241. package/test-suite/scripts/build-registry.mjs +113 -0
  242. package/test-suite/scripts/catalog-refresh-dart.mjs +151 -0
  243. package/test-suite/scripts/catalog-refresh.mjs +100 -0
  244. package/test-suite/scripts/lint-skills.mjs +909 -0
  245. package/test-suite/scripts/measure-tokens.mjs +169 -0
  246. package/test-suite/scripts/typecheck-fences-dart.mjs +437 -0
  247. package/test-suite/scripts/typecheck-fences-kotlin.mjs +269 -0
  248. package/test-suite/scripts/typecheck-fences.mjs +206 -0
  249. package/test-suite/scripts/verify-catalog.mjs +242 -0
  250. package/test-suite/scripts/verify-contract-fences.mjs +94 -0
  251. package/test-suite/scripts/verify-detection.mjs +178 -0
  252. package/test-suite/scripts/verify-docs-base.mjs +91 -0
  253. package/test-suite/scripts/verify-family-agnostic.mjs +130 -0
  254. package/test-suite/scripts/verify-golden-path.mjs +138 -0
  255. package/test-suite/scripts/verify-oracle-components.mjs +133 -0
  256. package/test-suite/scripts/verify-template-bindings.mjs +148 -0
  257. package/test-suite/typecheck/angular-v5/package.json +21 -0
  258. package/test-suite/typecheck/angular-v5/tsconfig.json +21 -0
  259. package/test-suite/typecheck/flutter-v6/analysis_options.yaml +25 -0
  260. package/test-suite/typecheck/flutter-v6/pubspec.yaml +21 -0
  261. package/test-suite/typecheck/web-v7/package.json +18 -0
  262. package/test-suite/typecheck/web-v7/tsconfig.json +19 -0
  263. package/bin/install.js +0 -1003
  264. package/skills/.claude-plugin/marketplace.json +0 -86
  265. package/skills/cometchat/references/asking-questions.md +0 -48
  266. package/skills/cometchat-a11y/SKILL.md +0 -477
  267. package/skills/cometchat-android-v5/SKILL.md +0 -121
  268. package/skills/cometchat-android-v5-calls/SKILL.md +0 -427
  269. package/skills/cometchat-android-v5-calls/references/README.md +0 -53
  270. package/skills/cometchat-android-v5-calls/references/add-calls-to-existing-chat.md +0 -197
  271. package/skills/cometchat-android-v5-calls/references/audio-controls.md +0 -91
  272. package/skills/cometchat-android-v5-calls/references/background-handling.md +0 -168
  273. package/skills/cometchat-android-v5-calls/references/call-layouts.md +0 -131
  274. package/skills/cometchat-android-v5-calls/references/call-logs.md +0 -113
  275. package/skills/cometchat-android-v5-calls/references/call-session.md +0 -274
  276. package/skills/cometchat-android-v5-calls/references/custom-ui.md +0 -150
  277. package/skills/cometchat-android-v5-calls/references/event-listeners.md +0 -123
  278. package/skills/cometchat-android-v5-calls/references/group-calls.md +0 -205
  279. package/skills/cometchat-android-v5-calls/references/idle-timeout.md +0 -117
  280. package/skills/cometchat-android-v5-calls/references/in-call-chat.md +0 -134
  281. package/skills/cometchat-android-v5-calls/references/join-session.md +0 -120
  282. package/skills/cometchat-android-v5-calls/references/migration-v4-to-v5.md +0 -222
  283. package/skills/cometchat-android-v5-calls/references/participant-management.md +0 -110
  284. package/skills/cometchat-android-v5-calls/references/picture-in-picture.md +0 -106
  285. package/skills/cometchat-android-v5-calls/references/raise-hand.md +0 -248
  286. package/skills/cometchat-android-v5-calls/references/recording.md +0 -101
  287. package/skills/cometchat-android-v5-calls/references/ringing-integration.md +0 -152
  288. package/skills/cometchat-android-v5-calls/references/screen-sharing.md +0 -78
  289. package/skills/cometchat-android-v5-calls/references/server-fcm-voip.md +0 -215
  290. package/skills/cometchat-android-v5-calls/references/session-settings.md +0 -121
  291. package/skills/cometchat-android-v5-calls/references/setup.md +0 -137
  292. package/skills/cometchat-android-v5-calls/references/share-invite.md +0 -156
  293. package/skills/cometchat-android-v5-calls/references/video-controls.md +0 -87
  294. package/skills/cometchat-android-v5-calls/references/voip-calling.md +0 -526
  295. package/skills/cometchat-android-v5-components/SKILL.md +0 -431
  296. package/skills/cometchat-android-v5-core/SKILL.md +0 -486
  297. package/skills/cometchat-android-v5-customization/SKILL.md +0 -469
  298. package/skills/cometchat-android-v5-extensions/SKILL.md +0 -127
  299. package/skills/cometchat-android-v5-features/SKILL.md +0 -131
  300. package/skills/cometchat-android-v5-placement/SKILL.md +0 -277
  301. package/skills/cometchat-android-v5-production/SKILL.md +0 -95
  302. package/skills/cometchat-android-v5-push/SKILL.md +0 -619
  303. package/skills/cometchat-android-v5-testing/SKILL.md +0 -188
  304. package/skills/cometchat-android-v5-theming/SKILL.md +0 -167
  305. package/skills/cometchat-android-v5-troubleshooting/SKILL.md +0 -74
  306. package/skills/cometchat-android-v6/SKILL.md +0 -108
  307. package/skills/cometchat-android-v6-calls/references/add-calls-to-existing-chat.md +0 -97
  308. package/skills/cometchat-android-v6-calls/references/advanced-features.md +0 -87
  309. package/skills/cometchat-android-v6-calls/references/call-layouts.md +0 -100
  310. package/skills/cometchat-android-v6-calls/references/call-session.md +0 -163
  311. package/skills/cometchat-android-v6-calls/references/device-management.md +0 -172
  312. package/skills/cometchat-android-v6-calls/references/group-calls.md +0 -191
  313. package/skills/cometchat-android-v6-calls/references/idle-timeout.md +0 -110
  314. package/skills/cometchat-android-v6-calls/references/in-call-chat.md +0 -141
  315. package/skills/cometchat-android-v6-calls/references/raise-hand.md +0 -188
  316. package/skills/cometchat-android-v6-calls/references/ringing-integration.md +0 -176
  317. package/skills/cometchat-android-v6-calls/references/server-fcm-voip.md +0 -127
  318. package/skills/cometchat-android-v6-calls/references/share-invite.md +0 -105
  319. package/skills/cometchat-android-v6-compose-theming/SKILL.md +0 -250
  320. package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +0 -233
  321. package/skills/cometchat-angular-calls/SKILL.md +0 -378
  322. package/skills/cometchat-angular-calls/references/add-calls-to-existing-chat.md +0 -96
  323. package/skills/cometchat-angular-calls/references/call-layouts.md +0 -123
  324. package/skills/cometchat-angular-calls/references/call-session.md +0 -167
  325. package/skills/cometchat-angular-calls/references/custom-ui.md +0 -239
  326. package/skills/cometchat-angular-calls/references/device-management.md +0 -154
  327. package/skills/cometchat-angular-calls/references/group-calls.md +0 -433
  328. package/skills/cometchat-angular-calls/references/idle-timeout.md +0 -161
  329. package/skills/cometchat-angular-calls/references/in-call-chat.md +0 -148
  330. package/skills/cometchat-angular-calls/references/lazy-loading-pitfalls.md +0 -205
  331. package/skills/cometchat-angular-calls/references/migration-v4-to-v5.md +0 -53
  332. package/skills/cometchat-angular-calls/references/ngzone-and-async-callbacks.md +0 -184
  333. package/skills/cometchat-angular-calls/references/picture-in-picture.md +0 -285
  334. package/skills/cometchat-angular-calls/references/raise-hand.md +0 -172
  335. package/skills/cometchat-angular-calls/references/recording-screen-share.md +0 -260
  336. package/skills/cometchat-angular-calls/references/ringing-integration.md +0 -133
  337. package/skills/cometchat-angular-calls/references/server-web-push-vapid.md +0 -100
  338. package/skills/cometchat-angular-calls/references/share-invite.md +0 -122
  339. package/skills/cometchat-angular-calls/references/virtual-background.md +0 -29
  340. package/skills/cometchat-angular-components/SKILL.md +0 -235
  341. package/skills/cometchat-angular-core/SKILL.md +0 -386
  342. package/skills/cometchat-angular-customization/SKILL.md +0 -616
  343. package/skills/cometchat-angular-features/SKILL.md +0 -329
  344. package/skills/cometchat-angular-patterns/SKILL.md +0 -256
  345. package/skills/cometchat-angular-placement/SKILL.md +0 -314
  346. package/skills/cometchat-angular-production/SKILL.md +0 -564
  347. package/skills/cometchat-angular-push/SKILL.md +0 -389
  348. package/skills/cometchat-angular-testing/SKILL.md +0 -605
  349. package/skills/cometchat-angular-theming/SKILL.md +0 -290
  350. package/skills/cometchat-angular-troubleshooting/SKILL.md +0 -364
  351. package/skills/cometchat-astro-patterns/SKILL.md +0 -708
  352. package/skills/cometchat-calls/SKILL.md +0 -364
  353. package/skills/cometchat-calls/references/use-case-broadcast.md +0 -199
  354. package/skills/cometchat-calls/references/use-case-marketplace.md +0 -157
  355. package/skills/cometchat-calls/references/use-case-support.md +0 -189
  356. package/skills/cometchat-calls/references/use-case-team.md +0 -180
  357. package/skills/cometchat-calls/references/use-case-telehealth.md +0 -153
  358. package/skills/cometchat-components/SKILL.md +0 -1142
  359. package/skills/cometchat-core/SKILL.md +0 -1048
  360. package/skills/cometchat-customization/SKILL.md +0 -580
  361. package/skills/cometchat-customization/references/component-catalog.md +0 -236
  362. package/skills/cometchat-features/SKILL.md +0 -861
  363. package/skills/cometchat-flutter-v5/SKILL.md +0 -173
  364. package/skills/cometchat-flutter-v5-calls/SKILL.md +0 -453
  365. package/skills/cometchat-flutter-v5-calls/references/add-calls-to-existing-chat.md +0 -193
  366. package/skills/cometchat-flutter-v5-calls/references/call-layouts.md +0 -103
  367. package/skills/cometchat-flutter-v5-calls/references/call-session.md +0 -285
  368. package/skills/cometchat-flutter-v5-calls/references/device-management.md +0 -124
  369. package/skills/cometchat-flutter-v5-calls/references/group-calls.md +0 -181
  370. package/skills/cometchat-flutter-v5-calls/references/idle-timeout.md +0 -99
  371. package/skills/cometchat-flutter-v5-calls/references/in-call-chat.md +0 -135
  372. package/skills/cometchat-flutter-v5-calls/references/migration-v4-to-v5.md +0 -210
  373. package/skills/cometchat-flutter-v5-calls/references/raise-hand.md +0 -255
  374. package/skills/cometchat-flutter-v5-calls/references/ringing-integration.md +0 -196
  375. package/skills/cometchat-flutter-v5-calls/references/server-push-bridge.md +0 -101
  376. package/skills/cometchat-flutter-v5-calls/references/share-invite.md +0 -148
  377. package/skills/cometchat-flutter-v5-conversations/SKILL.md +0 -118
  378. package/skills/cometchat-flutter-v5-core/SKILL.md +0 -239
  379. package/skills/cometchat-flutter-v5-customization/SKILL.md +0 -358
  380. package/skills/cometchat-flutter-v5-events/SKILL.md +0 -156
  381. package/skills/cometchat-flutter-v5-messages/SKILL.md +0 -259
  382. package/skills/cometchat-flutter-v5-production/SKILL.md +0 -216
  383. package/skills/cometchat-flutter-v5-push/SKILL.md +0 -285
  384. package/skills/cometchat-flutter-v5-testing/SKILL.md +0 -401
  385. package/skills/cometchat-flutter-v5-theming/SKILL.md +0 -161
  386. package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +0 -130
  387. package/skills/cometchat-flutter-v5-users-groups/SKILL.md +0 -152
  388. package/skills/cometchat-flutter-v6/SKILL.md +0 -446
  389. package/skills/cometchat-flutter-v6-calls/references/add-calls-to-existing-chat.md +0 -136
  390. package/skills/cometchat-flutter-v6-calls/references/advanced-features.md +0 -89
  391. package/skills/cometchat-flutter-v6-calls/references/call-layouts.md +0 -105
  392. package/skills/cometchat-flutter-v6-calls/references/call-session.md +0 -229
  393. package/skills/cometchat-flutter-v6-calls/references/device-management.md +0 -105
  394. package/skills/cometchat-flutter-v6-calls/references/group-calls.md +0 -190
  395. package/skills/cometchat-flutter-v6-calls/references/idle-timeout.md +0 -88
  396. package/skills/cometchat-flutter-v6-calls/references/in-call-chat.md +0 -125
  397. package/skills/cometchat-flutter-v6-calls/references/raise-hand.md +0 -275
  398. package/skills/cometchat-flutter-v6-calls/references/ringing-integration.md +0 -171
  399. package/skills/cometchat-flutter-v6-calls/references/server-push-bridge.md +0 -93
  400. package/skills/cometchat-flutter-v6-calls/references/share-invite.md +0 -102
  401. package/skills/cometchat-flutter-v6-conversations/SKILL.md +0 -238
  402. package/skills/cometchat-flutter-v6-messages/SKILL.md +0 -384
  403. package/skills/cometchat-flutter-v6-theming/SKILL.md +0 -293
  404. package/skills/cometchat-flutter-v6-users-groups/SKILL.md +0 -224
  405. package/skills/cometchat-i18n/SKILL.md +0 -419
  406. package/skills/cometchat-ios/SKILL.md +0 -325
  407. package/skills/cometchat-ios-calls/references/add-calls-to-existing-chat.md +0 -174
  408. package/skills/cometchat-ios-calls/references/audio-modes-and-controls.md +0 -45
  409. package/skills/cometchat-ios-calls/references/avaudiosession-routing.md +0 -188
  410. package/skills/cometchat-ios-calls/references/call-layouts.md +0 -76
  411. package/skills/cometchat-ios-calls/references/call-session.md +0 -218
  412. package/skills/cometchat-ios-calls/references/callkit-and-pushkit.md +0 -357
  413. package/skills/cometchat-ios-calls/references/custom-ui.md +0 -322
  414. package/skills/cometchat-ios-calls/references/device-management.md +0 -171
  415. package/skills/cometchat-ios-calls/references/group-calls.md +0 -359
  416. package/skills/cometchat-ios-calls/references/idle-timeout.md +0 -184
  417. package/skills/cometchat-ios-calls/references/in-call-chat.md +0 -179
  418. package/skills/cometchat-ios-calls/references/migration-v4-to-v5.md +0 -196
  419. package/skills/cometchat-ios-calls/references/picture-in-picture.md +0 -246
  420. package/skills/cometchat-ios-calls/references/raise-hand.md +0 -26
  421. package/skills/cometchat-ios-calls/references/recording-screen-share.md +0 -252
  422. package/skills/cometchat-ios-calls/references/ringing-integration.md +0 -143
  423. package/skills/cometchat-ios-calls/references/server-apns-pushkit.md +0 -267
  424. package/skills/cometchat-ios-calls/references/share-invite.md +0 -148
  425. package/skills/cometchat-ios-calls/references/swiftui-uikit-hosting.md +0 -246
  426. package/skills/cometchat-ios-calls/references/voip-cert-setup.md +0 -166
  427. package/skills/cometchat-ios-production/SKILL.md +0 -738
  428. package/skills/cometchat-ios-testing/SKILL.md +0 -376
  429. package/skills/cometchat-ios-theming/SKILL.md +0 -667
  430. package/skills/cometchat-ios-troubleshooting/SKILL.md +0 -1003
  431. package/skills/cometchat-native-bare-patterns/SKILL.md +0 -550
  432. package/skills/cometchat-native-calls/SKILL.md +0 -669
  433. package/skills/cometchat-native-calls/references/add-calls-to-existing-chat.md +0 -203
  434. package/skills/cometchat-native-calls/references/call-layouts.md +0 -125
  435. package/skills/cometchat-native-calls/references/call-session.md +0 -182
  436. package/skills/cometchat-native-calls/references/custom-ui.md +0 -254
  437. package/skills/cometchat-native-calls/references/device-management.md +0 -158
  438. package/skills/cometchat-native-calls/references/expo-vs-bare.md +0 -179
  439. package/skills/cometchat-native-calls/references/group-calls.md +0 -296
  440. package/skills/cometchat-native-calls/references/idle-timeout.md +0 -128
  441. package/skills/cometchat-native-calls/references/in-call-chat.md +0 -145
  442. package/skills/cometchat-native-calls/references/migration-v4-to-v5.md +0 -72
  443. package/skills/cometchat-native-calls/references/picture-in-picture.md +0 -276
  444. package/skills/cometchat-native-calls/references/raise-hand.md +0 -205
  445. package/skills/cometchat-native-calls/references/recording-screen-share.md +0 -169
  446. package/skills/cometchat-native-calls/references/ringing-integration.md +0 -118
  447. package/skills/cometchat-native-calls/references/server-push-bridge.md +0 -135
  448. package/skills/cometchat-native-calls/references/server-push-payloads.md +0 -193
  449. package/skills/cometchat-native-calls/references/share-invite.md +0 -146
  450. package/skills/cometchat-native-calls/references/voip-push-end-to-end.md +0 -319
  451. package/skills/cometchat-native-components/SKILL.md +0 -628
  452. package/skills/cometchat-native-core/SKILL.md +0 -679
  453. package/skills/cometchat-native-customization/SKILL.md +0 -716
  454. package/skills/cometchat-native-expo-patterns/SKILL.md +0 -552
  455. package/skills/cometchat-native-features/SKILL.md +0 -638
  456. package/skills/cometchat-native-placement/SKILL.md +0 -509
  457. package/skills/cometchat-native-production/SKILL.md +0 -700
  458. package/skills/cometchat-native-push/SKILL.md +0 -594
  459. package/skills/cometchat-native-testing/SKILL.md +0 -582
  460. package/skills/cometchat-native-theming/SKILL.md +0 -681
  461. package/skills/cometchat-native-troubleshooting/SKILL.md +0 -501
  462. package/skills/cometchat-nextjs-patterns/SKILL.md +0 -899
  463. package/skills/cometchat-placement/SKILL.md +0 -1360
  464. package/skills/cometchat-production/SKILL.md +0 -1027
  465. package/skills/cometchat-react-calls/SKILL.md +0 -749
  466. package/skills/cometchat-react-calls/references/add-calls-to-existing-chat.md +0 -146
  467. package/skills/cometchat-react-calls/references/call-layouts.md +0 -160
  468. package/skills/cometchat-react-calls/references/call-session.md +0 -235
  469. package/skills/cometchat-react-calls/references/custom-ui.md +0 -247
  470. package/skills/cometchat-react-calls/references/device-management.md +0 -210
  471. package/skills/cometchat-react-calls/references/group-calls.md +0 -257
  472. package/skills/cometchat-react-calls/references/idle-timeout.md +0 -188
  473. package/skills/cometchat-react-calls/references/in-call-chat.md +0 -212
  474. package/skills/cometchat-react-calls/references/migration-v4-to-v5.md +0 -170
  475. package/skills/cometchat-react-calls/references/picture-in-picture.md +0 -245
  476. package/skills/cometchat-react-calls/references/raise-hand.md +0 -238
  477. package/skills/cometchat-react-calls/references/recording-screen-share.md +0 -144
  478. package/skills/cometchat-react-calls/references/ringing-integration.md +0 -255
  479. package/skills/cometchat-react-calls/references/server-web-push-vapid.md +0 -241
  480. package/skills/cometchat-react-calls/references/share-invite.md +0 -175
  481. package/skills/cometchat-react-calls/references/testing-calls-on-web.md +0 -216
  482. package/skills/cometchat-react-calls/references/virtual-background.md +0 -32
  483. package/skills/cometchat-react-calls/references/voip-and-web-push.md +0 -165
  484. package/skills/cometchat-react-patterns/SKILL.md +0 -628
  485. package/skills/cometchat-react-push/SKILL.md +0 -634
  486. package/skills/cometchat-react-router-patterns/SKILL.md +0 -758
  487. package/skills/cometchat-react-testing/SKILL.md +0 -413
  488. package/skills/cometchat-theming/SKILL.md +0 -498
  489. package/skills/cometchat-troubleshooting/SKILL.md +0 -294
@@ -0,0 +1,1180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cometchat-skills — the deterministic "hands" layer of the pack.
4
+ *
5
+ * The v5 pivot kept the brain (skills/) and the materials (catalog/features/
6
+ * contracts/peers) but dropped the CLI the legacy pack had. This restores a
7
+ * small, dependency-free, DETERMINISTIC CLI so the steps an LLM should NOT
8
+ * improvise — detect the project, install skills, run the verification gates,
9
+ * refresh the oracle — are real commands with stable JSON contracts.
10
+ *
11
+ * npx @cometchat/skills <command> [options]
12
+ *
13
+ * Commands:
14
+ * detect Probe the CWD → framework / installed UI Kit / version_conflict (tools/detect.md contract)
15
+ * add [--ide <a>] Install THIS project's family skills for an agent (claude default; cursor/codex/copilot/… or `all`)
16
+ * list List the skills this pack ships (from peers.yaml + skills/)
17
+ * info <slug> Show one skill's triggers + compatibility
18
+ * verify [tier] Run the safety-net gates: lint | catalog | fences | predicate | all (default all offline)
19
+ * catalog [--check] Refresh the Tier-1 symbol catalog from the installed kit (oracle refresh)
20
+ * doctor Environment health check (node, kit install, catalog freshness, review state)
21
+ * version Print the pack version
22
+ * help This message
23
+ *
24
+ * Scope: every family in peers.yaml. Family-scoped paths/labels resolve from
25
+ * peers.yaml + families.json (RULES.md §22) — never a hardcoded family.
26
+ * Dependency-free (Node >=18 built-ins only). Every command supports --json.
27
+ */
28
+ import {
29
+ readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, cpSync, statSync, rmSync,
30
+ } from "node:fs";
31
+ import { join, dirname, resolve } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ import { spawnSync } from "node:child_process";
34
+ import { homedir } from "node:os";
35
+
36
+ const PACK_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
+ const CWD = process.cwd();
38
+ const argv = process.argv.slice(2);
39
+ const CMD = argv[0];
40
+ const JSON_OUT = argv.includes("--json");
41
+ const has = (f) => argv.includes(f);
42
+ const flagVal = (f, d) => {
43
+ const i = argv.indexOf(f);
44
+ if (i >= 0) return argv[i + 1];
45
+ const eq = argv.find((a) => a.startsWith(f + "=")); // also accept --flag=value (e.g. --ide=cursor)
46
+ return eq ? eq.slice(f.length + 1) : d;
47
+ };
48
+ // A value-taking flag given WITHOUT a value — a bare trailing `--ide`, or `--ide --global` —
49
+ // used to fall through to the default and silently install for Claude. Refuse it instead.
50
+ function requireFlagValue(f, supported) {
51
+ const i = argv.indexOf(f);
52
+ if (i < 0) return;
53
+ const v = argv[i + 1];
54
+ if (v === undefined || v.startsWith("-")) {
55
+ console.error(`${f} needs a value\nsupported: ${supported.join(", ")}`);
56
+ process.exit(2);
57
+ }
58
+ }
59
+
60
+ function out(obj, human) {
61
+ if (JSON_OUT) console.log(JSON.stringify(obj, null, 0));
62
+ else human();
63
+ }
64
+ function readJSON(p) { try { return JSON.parse(readFileSync(p, "utf8")); } catch { return null; } }
65
+ // Resolve which family the CURRENT workspace belongs to, from families.json — the same single
66
+ // source the pack tools read (RULES.md §22). detect() already knows the framework; this maps it
67
+ // onto a family so no command has to name one.
68
+ // detect() -> the family THIS project actually uses. families.json is the dev
69
+ // oracle but is NOT packed (see package.json "files"), so an installed pack must
70
+ // resolve from peers.yaml, which ships. Hardcoding a family here is what put
71
+ // "React v7 UI Kit" atop every React Native AGENTS.md (AUDIT-101).
72
+ const FRAMEWORK_PLATFORM = {
73
+ "react-native": "react-native", expo: "react-native",
74
+ reactjs: "react", nextjs: "react", astro: "react", "react-router": "react",
75
+ angular: "angular",
76
+ // Native families detect() already recognises (pubspec / gradle / xcodeproj). Without
77
+ // these, `add` in a native iOS/Android/Flutter project resolved no family and exited 3
78
+ // with a false "no family" message even though detect() knew the framework.
79
+ flutter: "flutter", android: "android", ios: "ios",
80
+ };
81
+ // NO "nearest family" fallback. Defaulting to react is what titled an Angular
82
+ // project's router "React v7 UI Kit" and installed 9 React skills into it — the
83
+ // same silent-substitution defect as AUDIT-101, one framework over (AUDIT-105).
84
+ // An unresolved family is reported, never guessed. `--family <platform>` is the
85
+ // explicit escape hatch for a monorepo root, where detect() sees no framework.
86
+ function resolvePeer(fw) {
87
+ const live = (peers().peers || []).filter((p) => !p.parked && p.dirPrefix);
88
+ requireFlagValue("--family", live.map((p) => p.platform));
89
+ const forced = flagVal("--family");
90
+ if (forced) {
91
+ const hit = live.find((p) => p.platform === forced);
92
+ if (!hit) {
93
+ console.error(`unknown --family: ${forced}\nshippable: ${live.map((p) => p.platform).join(", ")}`);
94
+ process.exit(2);
95
+ }
96
+ return hit;
97
+ }
98
+ return live.find((p) => p.platform === FRAMEWORK_PLATFORM[fw])
99
+ || (live.length === 1 ? live[0] : null)
100
+ || null;
101
+ }
102
+ const FAMILIES_JSON = join(PACK_ROOT, ".claude/skills/cometchat-skill-reviewer/tools/families.json");
103
+ // The DEV-ONLY reviewer family for a peer (families.json is not packed → null on an npm install).
104
+ // Matched by CATALOG key — the one field both registries share. Matching by `platform` picked
105
+ // whichever family sorted first: an Angular project resolved to web-react-v7 (families.json's
106
+ // Angular platform is "web"), and React Native to rn-calls-v5 instead of rn-uikit-v5.
107
+ function reviewerFamily(peer) {
108
+ const F = readJSON(FAMILIES_JSON);
109
+ if (!peer || !F || !F.families) return null;
110
+ const key = (f) => (f.catalog || "").replace(/^catalog\//, "").replace(/\.json$/, "");
111
+ const hit = Object.entries(F.families)
112
+ .find(([, f]) => !/^template\b/i.test(f._status || "") && key(f) === peer.catalog);
113
+ if (!hit) return null;
114
+ const [name, f] = hit;
115
+ return { ...f, name, key: key(f), typecheckProject: f.typecheckProject || `test-suite/typecheck/${key(f)}` };
116
+ }
117
+ function resolveFamily() {
118
+ const det = detect() || {};
119
+ const fw = det.framework || "";
120
+ const peer = resolvePeer(fw);
121
+ if (!peer) failUnresolvedFamily(fw, det);
122
+ const base = { label: peer.label, dirPrefix: peer.dirPrefix, kit_package: peer.kit_package, major: peer.major };
123
+ const fam = reviewerFamily(peer);
124
+ if (fam) return { ...fam, ...base };
125
+ // ponytail: packed install — peers.yaml is the only shipped registry.
126
+ return { platform: peer.platform, key: null, typecheckProject: null, ...base };
127
+ }
128
+
129
+ function failUnresolvedFamily(fw, det = {}) {
130
+ {
131
+ const live = (peers().peers || []).filter((x) => !x.parked && x.dirPrefix);
132
+ console.error(
133
+ `cannot resolve a CometChat family for this project.\n` +
134
+ ` detected framework: ${fw || "(none)"}\n` +
135
+ (det.hint ? ` hint: ${det.hint}\n` : "") +
136
+ ` shippable families: ${live.map((x) => `${x.platform} (${x.label})`).join(", ") || "(none)"}\n\n` +
137
+ (fw
138
+ ? `There is no CometChat skills family for "${fw}" in this pack. Installing the nearest one\n` +
139
+ `would hand you another framework's APIs — so nothing was written.\n`
140
+ : `No framework was detected (a monorepo root looks like this). Run this inside the app\n` +
141
+ `package, or name the family explicitly.\n`) +
142
+ `\n npx @cometchat/skills add --ide <agent> --family <${live.map((x) => x.platform).join("|")}>`
143
+ );
144
+ process.exit(3);
145
+ }
146
+ }
147
+
148
+ function packVersion() { return (readJSON(join(PACK_ROOT, "package.json")) || {}).version || "0.0.0"; }
149
+
150
+ // ── detect — tools/detect.md contract (deterministic, offline) ───────────────
151
+ const TARGET_MAJOR = 7; // react UI Kit major these skills target (fallback when peers.yaml is absent)
152
+
153
+ // The UI Kit major EACH family targets, from peers.yaml `kit_package: "<npm-name>@<major>"`
154
+ // (react → 7, angular → 5, react-native → 5). "Not v7" is only a conflict for React: Angular v5
155
+ // and RN v5 are the CORRECT majors for their own families.
156
+ function jsKitTargets() {
157
+ const t = {};
158
+ for (const p of peers().peers || []) {
159
+ const m = /^(@cometchat\/chat-uikit-[a-z-]+)@(\d+)$/.exec(p.kit_package || "");
160
+ if (m && !p.parked) t[m[1]] = parseInt(m[2], 10);
161
+ }
162
+ if (!Object.keys(t).length) t["@cometchat/chat-uikit-react"] = TARGET_MAJOR;
163
+ return t;
164
+ }
165
+ const FRAMEWORK_KIT = { angular: "@cometchat/chat-uikit-angular", "react-native": "@cometchat/chat-uikit-react-native", expo: "@cometchat/chat-uikit-react-native" };
166
+
167
+ function ccVersion(pkg, dep) {
168
+ const v = (pkg.dependencies && pkg.dependencies[dep]) || (pkg.devDependencies && pkg.devDependencies[dep]);
169
+ if (!v) return null;
170
+ const m = v.match(/(\d+)\.(\d+)\.(\d+)/) || v.match(/(\d+)/);
171
+ return { range: v, major: m ? parseInt(m[1], 10) : null };
172
+ }
173
+
174
+ // A REAL product package -- kit/sdk/calls, any peer, from peers.yaml -- never
175
+ // the AI-tooling package itself. The old check used "any @cometchat/* dependency
176
+ // or a .cometchat/ dir", and BOTH halves were tool-installation signals, not
177
+ // integration signals: @cometchat/skills always matches "@cometchat/*" (every
178
+ // project that can run this CLI has it installed), and .cometchat/skills is
179
+ // literally NEUTRAL_SKILLS_DIR -- the tool's OWN install target, created by any
180
+ // --ide antigravity/cursor/codex/... install regardless of whether the product
181
+ // was ever integrated. So existing_cometchat had read true from the moment the
182
+ // TOOL was installed, on every project, forever -- which matters because it is
183
+ // the load-bearing signal for the dispatcher's "already integrated, skip
184
+ // onboarding" branch (AUDIT-108).
185
+ function productPackageNames() {
186
+ const y = existsSync(join(PACK_ROOT, "peers.yaml")) ? readFileSync(join(PACK_ROOT, "peers.yaml"), "utf8") : "";
187
+ const raw = [...y.matchAll(/(?:kit|sdk|calls)_package:\s*"([^"]+)"/g)].map((m) => m[1]);
188
+ return new Set(raw.map((pkg) => pkg.replace(/@[^@/]+$/, ""))); // strip trailing @<major>, keep the leading @scope
189
+ }
190
+
191
+ function detect() {
192
+ const pkgPath = join(CWD, "package.json");
193
+ const result = {
194
+ framework: null, flutter_version: null, android_variant: null,
195
+ installed_uikit: null, version_conflict: null, env_file: null, existing_cometchat: false,
196
+ // CometChat-relevant repo signals (for cometchat-onboarding). Additive: null when
197
+ // the evidence is absent (graceful degradation) — existing consumers ignore them.
198
+ auth: null, styling: null, state: null, routing: null, ssr: null,
199
+ };
200
+
201
+ // JS ecosystem
202
+ if (existsSync(pkgPath)) {
203
+ const pkg = readJSON(pkgPath) || {};
204
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
205
+ const hasDep = (d) => Object.prototype.hasOwnProperty.call(deps, d);
206
+ const hasAnyDep = (...ds) => ds.some(hasDep);
207
+ // some deps are namespaced (e.g. @clerk/*, @react-router/*) — match by prefix too.
208
+ const depKeys = Object.keys(deps);
209
+ const hasDepPrefix = (p) => depKeys.some((d) => d === p || d.startsWith(p));
210
+ const fileExists = (rel) => existsSync(join(CWD, rel));
211
+ const globish = (names) => names.some(fileExists);
212
+
213
+ let fwDep = null; // the dependency that decided the framework
214
+ if (hasDep("@angular/core")) { result.framework = "angular"; fwDep = "@angular/core"; }
215
+ else if (hasDep("expo")) { result.framework = "expo"; result.env_file = ".env"; fwDep = "expo"; }
216
+ else if (hasDep("react-native")) { result.framework = "react-native"; result.env_file = ".env"; fwDep = "react-native"; }
217
+ else if (hasDep("astro")) { result.framework = "astro"; result.env_file = ".env"; fwDep = "astro"; }
218
+ else if (hasDep("next")) { result.framework = "nextjs"; result.env_file = ".env.local"; fwDep = "next"; }
219
+ else if (hasDep("react-router-dom") || hasDep("@react-router/dev")) { result.framework = "react-router"; result.env_file = ".env"; fwDep = hasDep("react-router-dom") ? "react-router-dom" : "@react-router/dev"; }
220
+ else if (hasDep("react")) { result.framework = "reactjs"; result.env_file = ".env"; fwDep = "react"; }
221
+
222
+ if (result.framework === "angular") result.env_file = "src/environments/environment.ts";
223
+
224
+ // A WORKSPACE ROOT (package.json `workspaces`, or pnpm-workspace.yaml) whose framework dep is
225
+ // only a devDependency is tooling (storybook, lint, test config) — not an app. Auto-picking
226
+ // React there installed the React skills at a monorepo root. Report it; the user runs inside
227
+ // the app package or names the family. A root that lists the dep in `dependencies` IS an app.
228
+ const wsRoot = !!pkg.workspaces || fileExists("pnpm-workspace.yaml");
229
+ const inProdDeps = fwDep && pkg.dependencies && Object.prototype.hasOwnProperty.call(pkg.dependencies, fwDep);
230
+ if (result.framework && wsRoot && !inProdDeps) {
231
+ result.workspace_root = true;
232
+ result.hint = `workspace root (${pkg.workspaces ? 'package.json "workspaces"' : "pnpm-workspace.yaml"}) with ` +
233
+ `${fwDep} only in devDependencies — that is tooling, not an app. Run inside the app package ` +
234
+ `(e.g. apps/web), or pass --family <platform>.`;
235
+ result.framework = null; result.env_file = null;
236
+ }
237
+
238
+ // ── CometChat-relevant repo signals (deterministic, offline, graceful) ─────
239
+ // auth
240
+ if (hasDepPrefix("@auth/") || hasDep("next-auth")) result.auth = "nextauth";
241
+ else if (hasDepPrefix("@clerk/")) result.auth = "clerk";
242
+ else if (hasDep("firebase") || hasDepPrefix("@firebase/")) result.auth = "firebase";
243
+ else if (hasDep("auth0") || hasDepPrefix("@auth0/")) result.auth = "auth0";
244
+ else if (hasDep("@supabase/supabase-js") || hasDepPrefix("@supabase/")) result.auth = "supabase";
245
+ // else null — custom auth isn't inferable from deps alone.
246
+
247
+ // styling / design system
248
+ if (hasDep("tailwindcss") || globish(["tailwind.config.js", "tailwind.config.ts", "tailwind.config.cjs", "tailwind.config.mjs"])) result.styling = "tailwind";
249
+ else if (hasDepPrefix("@mui/")) result.styling = "mui";
250
+ else if (hasDep("styled-components")) result.styling = "styled-components";
251
+ else if (hasDepPrefix("@chakra-ui/")) result.styling = "chakra";
252
+ // css-modules is a convention, not a dep — leave to onboarding's repo read; null here.
253
+
254
+ // state management
255
+ if (hasDep("@reduxjs/toolkit") || hasDep("react-redux") || hasDep("redux")) result.state = "redux";
256
+ else if (hasDep("zustand")) result.state = "zustand";
257
+ else if (hasDep("mobx") || hasDepPrefix("mobx-react")) result.state = "mobx";
258
+ else if (hasDep("recoil")) result.state = "recoil";
259
+ else if (result.framework && /^(reactjs|nextjs|react-router|expo|react-native)$/.test(result.framework)) result.state = "context"; // React's built-in default
260
+ // else null (e.g. angular has its own DI; leave null unless a lib is present)
261
+
262
+ // routing
263
+ if (result.framework === "nextjs") result.routing = fileExists("app") || fileExists("src/app") ? "next-app-router" : (fileExists("pages") || fileExists("src/pages") ? "next-pages-router" : "next-app-router");
264
+ else if (hasDep("react-router-dom") || hasDepPrefix("@react-router/")) result.routing = "react-router";
265
+ else if (hasDepPrefix("@tanstack/react-router")) result.routing = "tanstack-router";
266
+ else if (hasDep("vue-router")) result.routing = "vue-router";
267
+ else if (hasDep("expo-router")) result.routing = "expo-router";
268
+ else if (hasDep("@angular/router") || result.framework === "angular") result.routing = "angular-router";
269
+ // else null
270
+
271
+ // SSR/CSR boundary
272
+ if (hasDep("next") || hasDep("nuxt") || hasDep("@remix-run/react") || hasDepPrefix("@sveltejs/kit")) result.ssr = "ssr";
273
+ else if (hasDep("astro")) result.ssr = "islands";
274
+ else if (result.framework && /^(reactjs|react-router|expo|react-native)$/.test(result.framework)) result.ssr = "csr";
275
+ // else null
276
+
277
+ if (result.workspace_root) { result.auth = result.styling = result.state = result.routing = result.ssr = null; }
278
+
279
+ // installed UI Kit — each JS family's kit, checked against THAT family's target major.
280
+ const kits = [];
281
+ for (const [name, target] of Object.entries(jsKitTargets())) {
282
+ const v = ccVersion(pkg, name);
283
+ if (!v) continue;
284
+ // prefer the actually-installed version if node_modules is present
285
+ const inst = readJSON(join(CWD, "node_modules", name, "package.json"));
286
+ const version = inst && inst.version ? inst.version : v.range;
287
+ const major = inst && inst.version ? parseInt(inst.version.split(".")[0], 10) : v.major;
288
+ kits.push({ name, target, version, major });
289
+ }
290
+ if (kits.length) {
291
+ const k = kits.find((x) => x.name === (FRAMEWORK_KIT[result.framework] || "@cometchat/chat-uikit-react")) || kits[0];
292
+ result.installed_uikit = { package: k.name, version: k.version };
293
+ if (k.major && k.major !== k.target) {
294
+ result.version_conflict = { detail: `project has ${k.name}@${k.major} but these skills target v${k.target}` };
295
+ }
296
+ // two different UI Kits (e.g. react + angular) declared at different majors
297
+ if (kits.length > 1 && new Set(kits.map((x) => x.major)).size > 1) {
298
+ const short = (x) => x.name.replace("@cometchat/chat-uikit-", "");
299
+ result.version_conflict = { detail: `two UI Kit majors declared (${kits.map((x) => `${short(x)} v${x.major}`).join(" + ")}) — pick one cohort` };
300
+ }
301
+ }
302
+ // A REAL product package as a project dependency is the only reliable "already
303
+ // integrated" signal. Do NOT also treat a `.cometchat/` dir as proof: it is this
304
+ // TOOL's own install target (NEUTRAL_SKILLS_DIR), created on every `add`, so it made
305
+ // every project read as integrated the moment the tooling was installed — the other
306
+ // half of the AUDIT-108 false positive (the dependency half was already narrowed to
307
+ // productPackageNames; this removes the dir half).
308
+ result.existing_cometchat =
309
+ Object.keys(deps).some((d) => productPackageNames().has(d));
310
+ }
311
+
312
+ // Non-JS scaffolds (light-touch — enough to fire version_conflict / routing)
313
+ if (!result.framework && existsSync(join(CWD, "pubspec.yaml"))) {
314
+ result.framework = "flutter";
315
+ const pub = readFileSync(join(CWD, "pubspec.yaml"), "utf8");
316
+ if (/cometchat_chat_uikit/.test(pub)) {
317
+ result.existing_cometchat = true;
318
+ const m = pub.match(/cometchat_chat_uikit:\s*["']?\^?(\d+)/);
319
+ if (m) result.flutter_version = parseInt(m[1], 10) >= 6 ? "v6" : "v5";
320
+ // installed_uikit was never populated on this branch, and version_conflict is
321
+ // derived from it — so a Flutter project sitting on the wrong major could never
322
+ // raise a conflict at all. Prefer the RESOLVED version in pubspec.lock over the
323
+ // declared range: "^6.1.0" says what is allowed, the lock says what is installed.
324
+ const declared = pub.match(/cometchat_chat_uikit:\s*["']?([^"'\s]+)/);
325
+ let version = declared ? declared[1] : null;
326
+ const lockPath = join(CWD, "pubspec.lock");
327
+ if (existsSync(lockPath)) {
328
+ const lock = readFileSync(lockPath, "utf8");
329
+ const lm = lock.match(/^\s{2}cometchat_chat_uikit:[\s\S]*?^\s{4}version:\s*["']?([\d.]+)/m);
330
+ if (lm) version = lm[1];
331
+ }
332
+ result.installed_uikit = { package: "cometchat_chat_uikit", version };
333
+ const major = version ? parseInt(String(version).replace(/^\D+/, ""), 10) : null;
334
+ if (major && major !== 6) {
335
+ result.version_conflict = {
336
+ detail: `project has cometchat_chat_uikit@${major} but these skills target v6`,
337
+ };
338
+ }
339
+ }
340
+ // The Flutter kit reads its credentials from a registered settings ASSET, not a dotenv
341
+ // file (commit a placeholder — a registered asset must exist for a fresh clone to build;
342
+ // cometchat-flutter-v6-core). Same reason the Android branch below names one. Leaving this
343
+ // null pointed the user at nothing on the one step that must precede any render.
344
+ result.env_file = "cometchat-settings.json";
345
+ }
346
+ // Android. BOTH DSLs — Groovy (.gradle) and Kotlin (.gradle.kts). Checking only the Groovy
347
+ // names missed every Kotlin-DSL project, which is the majority AND what this pack's own install
348
+ // block prescribes: `framework` came back null and the router could not route to Android at all.
349
+ const GRADLE_MARKERS = ["settings.gradle", "settings.gradle.kts", "app/build.gradle",
350
+ "app/build.gradle.kts", "build.gradle", "build.gradle.kts"];
351
+ if (!result.framework && GRADLE_MARKERS.some((f) => existsSync(join(CWD, f)))) {
352
+ result.framework = "android";
353
+ // Android credentials are NOT a dotenv file — they are the gitignored settings asset that
354
+ // `CometChatUIKit.initFromSettings` reads. Reporting "(n/a)" left the user with no pointer at
355
+ // all, on the one setup step that must happen before anything renders.
356
+ result.env_file = "app/src/main/assets/cometchat-settings.json";
357
+ const gradle = ["app/build.gradle", "app/build.gradle.kts", "build.gradle", "build.gradle.kts",
358
+ "gradle/libs.versions.toml"]
359
+ .map((f) => join(CWD, f)).filter(existsSync).map((f) => readFileSync(f, "utf8")).join("\n");
360
+
361
+ // Cohort. A bare /compose/ test matched "composer", the kit's own artifact name, and comments.
362
+ // Score STRUCTURAL signals instead, and report `mixed`/`unknown` honestly rather than guessing —
363
+ // emitting the wrong cohort is unrecoverable for the consumer.
364
+ const composeHits = [
365
+ /buildFeatures\s*\{[^}]*\bcompose\s*[= ]\s*true/s, // buildFeatures { compose = true }
366
+ /org\.jetbrains\.kotlin\.plugin\.compose/, // the compose compiler plugin
367
+ /androidx\.compose[.:]/, // androidx.compose.* dependency
368
+ /compose-bom/,
369
+ ].filter((re) => re.test(gradle)).length;
370
+ const viewsHits = [
371
+ /buildFeatures\s*\{[^}]*viewBinding\s*[= ]\s*true/s,
372
+ /buildFeatures\s*\{[^}]*dataBinding\s*[= ]\s*true/s,
373
+ /com\.android\.databinding/,
374
+ ].filter((re) => re.test(gradle)).length
375
+ + (existsSync(join(CWD, "app/src/main/res/layout")) ? 1 : 0); // XML layouts on disk
376
+
377
+ result.android_variant =
378
+ composeHits && !viewsHits ? "compose" :
379
+ viewsHits && !composeHits ? "views" :
380
+ composeHits && viewsHits ? "mixed" : "unknown";
381
+ result.android_cohort_signals = { composeHits, viewsHits };
382
+
383
+ // Existing CometChat dependency + major — drives version_conflict and the upgrade offer.
384
+ const kit = gradle.match(/com\.cometchat:(chatuikit-(?:kotlin|compose|core)-android|chat-uikit-android)[:"'\s]*([0-9]+)?/);
385
+ if (kit) {
386
+ result.existing_cometchat = true;
387
+ result.installed_uikit = kit[1];
388
+ const major = kit[2] ? parseInt(kit[2], 10) : null;
389
+ if (major) result.android_uikit_major = `v${major}`;
390
+ if (major && major < 6) {
391
+ result.version_conflict = { detail: `android UI Kit v${major} installed; this pack targets v6` };
392
+ }
393
+ }
394
+ }
395
+ // iOS. peers.yaml lists Package.swift / *.xcodeproj / Podfile / *.xcworkspace; checking only the
396
+ // Podfile + .xcodeproj left an SPM-only app and an .xcworkspace-only checkout detecting as nothing.
397
+ // A root Package.swift is AMBIGUOUS on its own — a Swift server/CLI package has one too — so it
398
+ // counts only with an iOS signal: an `.iOS(` platform, or a CometChat iOS product dependency.
399
+ if (!result.framework) {
400
+ const entries = readdirSync(CWD);
401
+ const read = (rel) => { try { return readFileSync(join(CWD, rel), "utf8"); } catch { return ""; } };
402
+ const xcode = entries.filter((f) => f.endsWith(".xcodeproj") || f.endsWith(".xcworkspace"));
403
+ const spm = existsSync(join(CWD, "Package.swift")) ? read("Package.swift") : null;
404
+ const IOS_CC = /CometChatUIKitSwift|CometChatCallsSDK|CometChatSDK|cometchat-uikit-ios|chat-sdk-ios|calls-sdk-ios/;
405
+ const spmIsIos = spm !== null && (/\.iOS\s*\(/.test(spm) || IOS_CC.test(spm));
406
+ if (existsSync(join(CWD, "Podfile")) || xcode.length || spmIsIos) {
407
+ result.framework = "ios";
408
+ result.env_file = "Secrets.xcconfig";
409
+ // Existing integration: a CometChat iOS product in the Podfile, Package.swift, the Xcode
410
+ // project, or a resolved SPM pin.
411
+ const evidence = [read("Podfile"), spm || "", read("Package.resolved"),
412
+ ...xcode.flatMap((x) => [read(`${x}/project.pbxproj`),
413
+ read(`${x}/xcshareddata/swiftpm/Package.resolved`),
414
+ read(`${x}/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`)])].join("\n");
415
+ if (IOS_CC.test(evidence)) {
416
+ result.existing_cometchat = true;
417
+ // The kit id comes from peers.yaml (RULES §22 — no hardcoded family keys in tools).
418
+ const iosKit = ((peers().peers || []).find((p) => p.platform === "ios") || {}).kit_package;
419
+ if (iosKit && (evidence.includes(iosKit) || /cometchat-uikit-ios/.test(evidence))) result.installed_uikit = iosKit;
420
+ }
421
+ } else if (spm !== null) {
422
+ result.ambiguous = "ios";
423
+ result.hint = "Package.swift found, but no iOS signal (no .iOS(...) platform, no CometChat iOS dependency) — " +
424
+ "this looks like a Swift server/CLI package. If it IS an iOS app, pass --family ios.";
425
+ }
426
+ }
427
+ return result;
428
+ }
429
+
430
+ // ── registry (derived from peers.yaml) ───────────────────────────────────────
431
+ function peers() {
432
+ const p = join(PACK_ROOT, "peers.yaml");
433
+ if (!existsSync(p)) return { entry: "cometchat", peers: [] };
434
+ const y = readFileSync(p, "utf8");
435
+ const entry = (y.match(/^entry:\s*(\S+)/m) || [])[1] || "cometchat";
436
+ const list = [];
437
+ for (const block of y.split(/^\s*-\s*platform:/m).slice(1)) {
438
+ const platform = (block.match(/^\s*(\S+)/) || [])[1];
439
+ const major = (block.match(/major:\s*(\S+)/) || [])[1];
440
+ const kit = (block.match(/kit_package:\s*"([^"]+)"/) || [])[1];
441
+ const skills = (block.match(/skills:\s*\[([^\]]*)\]/) || [])[1];
442
+ const verified = /^\s*verified:\s*true\b/m.test(block); // line-anchored: don't match "verified:true" inside a trailing comment on a `verified: false` line
443
+ const label = (block.match(/label:\s*"([^"]+)"/) || [])[1];
444
+ const dirPrefix = (block.match(/dir_prefix:\s*"([^"]+)"/) || [])[1];
445
+ const parked = /^\s*parked:\s*true\b/m.test(block); // line-anchored, like `verified`
446
+ list.push({ platform, major, kit_package: kit, label, dirPrefix, parked, skills: skills ? skills.split(",").map((s) => s.trim()) : [], verified });
447
+ }
448
+ // `catalog` + `env_file` make doctor/detect family-aware instead of hardcoding one family.
449
+ // Enriched in a SECOND pass, deliberately: the loop body above is shared with the React Native
450
+ // branch (PR #170), and editing it there turns a clean 3-way merge into a hand-resolved conflict.
451
+ y.split(/^\s*-\s*platform:/m).slice(1).forEach((block, i) => {
452
+ if (!list[i]) return;
453
+ list[i].catalog = (block.match(/catalog:\s*(\S+)/) || [])[1];
454
+ list[i].env_file = (block.match(/env_file:\s*"([^"]+)"/) || [])[1];
455
+ list[i].features = (block.match(/features:\s*(\S+\.json)/) || [])[1];
456
+ list[i].contracts = (block.match(/contracts:\s*(\S+\.json)/) || [])[1];
457
+ });
458
+ return { entry, peers: list };
459
+ }
460
+
461
+ function shippedSkillDirs() {
462
+ const dir = join(PACK_ROOT, "skills");
463
+ if (!existsSync(dir)) return [];
464
+ return readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
465
+ }
466
+
467
+ // ── commands ──────────────────────────────────────────────────────────────────
468
+ function cmdDetect() {
469
+ const r = detect();
470
+ out(r, () => {
471
+ console.log(`framework: ${r.framework || "(none detected)"}`);
472
+ // The react branch sets `{package, version}`; the android branch sets a bare artifact STRING
473
+ // (there is no lockfile to read a version from), so the object form printed
474
+ // "undefined@undefined" on every Android project that already had the kit.
475
+ if (r.installed_uikit) {
476
+ const u = r.installed_uikit;
477
+ console.log(`installed UI Kit: ${typeof u === "string" ? u : `${u.package}@${u.version}`}`);
478
+ }
479
+ console.log(`env file: ${r.env_file || "(n/a)"}`);
480
+ console.log(`existing CometChat: ${r.existing_cometchat}`);
481
+ // repo signals for onboarding (only print the ones we found)
482
+ const sig = [["auth", r.auth], ["styling", r.styling], ["state", r.state], ["routing", r.routing], ["ssr", r.ssr]].filter(([, v]) => v);
483
+ if (sig.length) console.log(`repo signals: ${sig.map(([k, v]) => `${k}=${v}`).join(" ")}`);
484
+ if (r.version_conflict) console.log(`⚠️ version_conflict: ${r.version_conflict.detail} → STOP & reconcile (RULES §3)`);
485
+ if (r.hint) console.log(`hint: ${r.hint}`);
486
+ });
487
+ }
488
+
489
+ function cmdList() {
490
+ const reg = peers();
491
+ const dirs = shippedSkillDirs();
492
+ out({ entry: reg.entry, peers: reg.peers, skills: dirs }, () => {
493
+ console.log(`Entry: /${reg.entry}`);
494
+ // Parked peers are a routing SPEC only (no installable skill) — listing them with a
495
+ // "verified" badge and "declared but not shipped" rows read as a broken pack.
496
+ const parked = reg.peers.filter((p) => p.parked);
497
+ for (const p of reg.peers.filter((x) => !x.parked)) {
498
+ console.log(`\n${p.platform} ${p.major} (${p.kit_package}) ${p.verified ? "✓ verified" : "unverified"}`);
499
+ for (const s of p.skills) {
500
+ // `dir_prefix` is the peer's own answer and always wins. Without it, composing
501
+ // `cometchat-<platform>-<major>-<skill>` invents names: the SDK peer carries its
502
+ // consumption in `platform` (android-sdk) while the dir does not, and a platform with only
503
+ // one live major ships no <major> segment at all (NAMING.md) — e.g. cometchat-ios-core.
504
+ const base = p.platform.replace(/-(sdk|uikit)$/, "");
505
+ const direct = `cometchat-${p.platform}-${p.major}-${s}`;
506
+ const slug = [p.dirPrefix && `${p.dirPrefix}${s}`, direct,
507
+ `cometchat-${base}-${p.major}-${s}`,
508
+ `cometchat-${p.platform}-${s}`, `cometchat-${base}-${s}`]
509
+ .filter(Boolean).find((c) => dirs.includes(c)) || null;
510
+ console.log(slug ? ` - ${slug}` : ` - ${direct} ⚠️ declared in peers.yaml but not shipped`);
511
+ }
512
+ }
513
+ if (parked.length) console.log(`\nparked (routing spec only — no installable skills): ${parked.map((p) => `${p.platform} (${p.label || p.kit_package})`).join(", ")}`);
514
+ });
515
+ }
516
+
517
+ function cmdInfo() {
518
+ const slug = argv[1];
519
+ if (!slug) { console.error("usage: cometchat-skills info <skill-dir>"); process.exit(2); }
520
+ const file = join(PACK_ROOT, "skills", slug, "SKILL.md");
521
+ if (!existsSync(file)) { console.error(`no such skill: ${slug}`); process.exit(2); }
522
+ const b = (readFileSync(file, "utf8").match(/^---\n([\s\S]*?)\n---/) || [])[1] || "";
523
+ const g = (k) => (b.match(new RegExp("^" + k + ':\\s*"?(.+?)"?$', "m")) || [])[1] || "";
524
+ out({ name: g("name"), description: g("description"), compatibility: g("compatibility") }, () => {
525
+ console.log(`${g("name")}\n\n${g("description")}\n\ncompatibility: ${g("compatibility")}`);
526
+ });
527
+ }
528
+
529
+ // Shared pack-root data files that skills reference by BARE name (RULES.md,
530
+ // features.json, …). The copy step used to ship only skill directories, so these
531
+ // references resolved to nothing post-install — the skills pointed at files that
532
+ // were never installed. Ship a copy alongside every skill so bare refs resolve.
533
+ // Discovered, not hard-coded. A per-family pack has one oracle pair PER FAMILY
534
+ // (features.json + contracts.json for web-react, features.rn-v5.json +
535
+ // contracts.rn-v5.json for react-native, …). A fixed list ships only the first family's,
536
+ // so every later family's skills point at oracles that were never installed — the exact
537
+ // bug this comment block was written about, recurring one family later.
538
+ const SHARED_ROOT_FILES = ["RULES.md", "peers.yaml"];
539
+ function sharedRootFiles() {
540
+ const dynamic = readdirSync(PACK_ROOT).filter(
541
+ (f) => /^(features|contracts)([.-][a-z0-9-]+)?\.json$/i.test(f)
542
+ );
543
+ return [...SHARED_ROOT_FILES, ...dynamic];
544
+ }
545
+
546
+ // Same problem as sharedRootFiles(), one level down: a skill's own prose can bake a
547
+ // literal runtime-fetchable path ("catalogs/rn-v5.json") as its anti-hallucination
548
+ // oracle -- "anything not in catalogs/rn-v5.json does not exist, do not emit it".
549
+ // sharedRootFiles()'s regex only ever matches top-level FILENAMES, so a directory
550
+ // referenced this way could never be discovered by it and never installed at all
551
+ // (AUDIT-107). Whole-directory shared items are rare and structural -- there is
552
+ // exactly one today -- so this is a short literal list, not a second regex scan.
553
+ const SHARED_ROOT_DIRS = ["catalogs"];
554
+ function sharedRootDirs() {
555
+ return SHARED_ROOT_DIRS.filter((d) => existsSync(join(PACK_ROOT, d)));
556
+ }
557
+
558
+ // Only THIS project's family (+ the two platform-neutral skills) is installed.
559
+ // Installing every family let a React skill out-match its RN twin: React's
560
+ // features triggers are unqualified ('add polls'), RN's say "React Native", so an
561
+ // RN user asking "add polls" matched the WRONG framework's skill (AUDIT-101).
562
+ // The prefix match deliberately co-installs a family's headless-SDK sibling where the prefixes
563
+ // overlap: cometchat-ios-calls/-push route VoIP push to cometchat-ios-v5-sdk, and the Android
564
+ // calls-sdk skill's 1:1 ringing leans on the cometchat-android-v5-sdk recipe (and vice versa).
565
+ function familySkillDirs(fam) {
566
+ const all = shippedSkillDirs();
567
+ if (!fam || !fam.dirPrefix) return all;
568
+ const neutral = new Set([peers().entry || "cometchat", "cometchat-onboarding"]);
569
+ return all.filter((d) => neutral.has(d) || d.startsWith(fam.dirPrefix));
570
+ }
571
+
572
+ // Remove-on-add: drop the skill folders an EARLIER generation shipped, so a v4→v5 upgrade
573
+ // never leaves stale skills behind (v4's cometchat-core/features/theming lingering next to
574
+ // the v5 set, with cometchat-core still advertising "React UI Kit v6"). Scoped to the exact
575
+ // legacy names in bin/legacy-skills.json — NOT every `cometchat*` dir: a current-pack skill
576
+ // may belong to another family, a calls peer, or a --global install from another project
577
+ // (installing a second family must not wipe the first), and any other cometchat-* dir is the
578
+ // user's own. The authoring/meta skills are never pruned either (dev tooling, not installed by `add`).
579
+ const PROTECTED_SKILLS = new Set(["cometchat-skill-creator", "cometchat-skill-reviewer", "cometchat-skill-explorer"]);
580
+ function legacySkillNames() {
581
+ try { return new Set(JSON.parse(readFileSync(join(PACK_ROOT, "bin", "legacy-skills.json"), "utf8")).skills || []); }
582
+ catch { return new Set(); }
583
+ }
584
+ function pruneLegacySkills(target) {
585
+ if (!existsSync(target)) return [];
586
+ const legacy = legacySkillNames();
587
+ const current = new Set(shippedSkillDirs());
588
+ const removed = [];
589
+ for (const name of readdirSync(target)) {
590
+ if (!legacy.has(name) || current.has(name) || PROTECTED_SKILLS.has(name)) continue;
591
+ const p = join(target, name);
592
+ try { if (statSync(p).isDirectory()) { rmSync(p, { recursive: true, force: true }); removed.push(name); } } catch { /* ignore */ }
593
+ }
594
+ return removed;
595
+ }
596
+
597
+ function copySkillsTo(target, fam) {
598
+ const src = join(PACK_ROOT, "skills");
599
+ mkdirSync(target, { recursive: true });
600
+ const removed = pruneLegacySkills(target); // remove-on-add: drop stale earlier-generation skills
601
+ const shared = sharedRootFiles().filter((f) => existsSync(join(PACK_ROOT, f)));
602
+ const sharedDirs = sharedRootDirs();
603
+ for (const f of shared) cpSync(join(PACK_ROOT, f), join(target, f)); // tree-root copy so <target>/RULES.md (the router's ref) resolves
604
+ for (const d of sharedDirs) cpSync(join(PACK_ROOT, d), join(target, d), { recursive: true });
605
+ // Per-skill ORACLES. The root features.json/contracts.json are the WEB pack's, so copying them
606
+ // into every skill shipped `family: "web-react", major: "v7"` into all 19 Android skills — while
607
+ // the correct android files sat uninstalled. The skills cite these as ground truth, so an Android
608
+ // agent was reading React's feature list (down to CometChatErrorBoundary, a web-only component).
609
+ // Resolve each skill's family from its peer's dir_prefix and install THAT peer's oracles under
610
+ // the bare names the skills reference.
611
+ const peerList = (peers().peers || []).filter((p) => p.dirPrefix);
612
+ const oracleFor = (skillDir) => {
613
+ // Resolve to the peer that actually SHIPS this skill dir — dirPrefix + a slug in its
614
+ // `skills` list — so android-sdk vs android-calls (which share `cometchat-android-v5-`)
615
+ // and react-native vs react-native-calls each get their OWN oracle, not whichever peer
616
+ // sorts first. Fall back to the longest matching prefix for shared/neutral dirs.
617
+ const owns = (p) => skillDir.startsWith(p.dirPrefix)
618
+ && (p.skills || []).includes(skillDir.slice(p.dirPrefix.length));
619
+ const peer = peerList.find(owns)
620
+ || peerList.filter((p) => skillDir.startsWith(p.dirPrefix))
621
+ .sort((a, b) => b.dirPrefix.length - a.dirPrefix.length)[0];
622
+ return {
623
+ "features.json": peer && peer.features,
624
+ "contracts.json": peer && peer.contracts,
625
+ };
626
+ };
627
+ // Per-skill copies are what a skill's RELATIVE refs resolve against ("features.json",
628
+ // "catalogs/android-v6.json"). Copy only what this install's skills actually cite — plus the
629
+ // bare family-correct oracles and RULES/peers — not all 25 oracle files and every catalog into
630
+ // every folder (that made a React install 8.9 MB and an Android one 14 MB). The tree root above
631
+ // keeps the full set.
632
+ const names = familySkillDirs(fam);
633
+ const cited = citedSharedFiles(names.map((n) => join(src, n)));
634
+ const always = new Set([...SHARED_ROOT_FILES, "features.json", "contracts.json"]);
635
+ const perSkill = shared.filter((f) => always.has(f) || cited.files.has(f));
636
+ const installed = [];
637
+ for (const name of names) {
638
+ const dest = join(target, name);
639
+ cpSync(join(src, name), dest, { recursive: true }); // overlay, not rm+copy: files a user added inside survive a re-install
640
+ const oracles = oracleFor(name);
641
+ for (const f of perSkill) {
642
+ const override = oracles[f];
643
+ const from = override && existsSync(join(PACK_ROOT, override)) ? override : f;
644
+ cpSync(join(PACK_ROOT, from), join(dest, f)); // bare name, family-correct content
645
+ }
646
+ for (const d of sharedDirs) { // AUDIT-107: a skill's "catalogs/<file>" reference resolves same-folder
647
+ const files = [...(cited.dirs.get(d) || [])].filter((f) => existsSync(join(PACK_ROOT, d, f)));
648
+ if (!files.length) continue;
649
+ mkdirSync(join(dest, d), { recursive: true });
650
+ for (const f of files) cpSync(join(PACK_ROOT, d, f), join(dest, d, f));
651
+ }
652
+ installed.push(name);
653
+ }
654
+ return { installed, removed };
655
+ }
656
+
657
+ // Every named oracle ("features.rn-v5.json") and shared-dir file ("catalogs/rn-v5.json") that the
658
+ // given skill dirs cite anywhere in their markdown.
659
+ function citedSharedFiles(skillDirs) {
660
+ const files = new Set();
661
+ const dirs = new Map(SHARED_ROOT_DIRS.map((d) => [d, new Set()]));
662
+ const dirRe = new RegExp(`\\b(${SHARED_ROOT_DIRS.join("|")})/([A-Za-z0-9._-]+\\.(?:json|txt))`, "g");
663
+ const walk = (p) => {
664
+ for (const e of readdirSync(p, { withFileTypes: true })) {
665
+ const q = join(p, e.name);
666
+ if (e.isDirectory()) { walk(q); continue; }
667
+ if (!e.name.endsWith(".md")) continue;
668
+ const text = readFileSync(q, "utf8");
669
+ for (const m of text.matchAll(/\b((?:features|contracts)\.[a-z0-9-]+\.json)\b/g)) files.add(m[1]);
670
+ for (const m of text.matchAll(dirRe)) dirs.get(m[1]).add(m[2]);
671
+ }
672
+ };
673
+ for (const d of skillDirs) if (existsSync(d)) walk(d);
674
+ return { files, dirs };
675
+ }
676
+
677
+ // ── multi-agent install targets ──────────────────────────────────────────────
678
+ // The v4 pack shipped a three-mode IDE_TARGETS map (multi / single / cli-only).
679
+ // v5 ports it forward, refreshed to 2026 conventions: AGENTS.md is now the
680
+ // near-universal single-file entrypoint, so Codex / Antigravity / Cursor /
681
+ // Continue read it and NO agent is left "CLI-only".
682
+ // • "multi" agents get the native SKILL.md folder tree (full progressive
683
+ // disclosure) at their own skills dir.
684
+ // • "single" agents get a short (~4KB) orienting ROUTER file at their rules
685
+ // location PLUS the full tree copied to a neutral ./.cometchat/skills so the
686
+ // router points at REAL files the agent reads ON DEMAND (keeps the token win;
687
+ // never a mega-concatenation — that burns tokens every turn).
688
+ // The skill CONTENT is identical across every target — only the delivery differs.
689
+ const NEUTRAL_SKILLS_DIR = ".cometchat/skills";
690
+ const IDE_TARGETS = {
691
+ claude: { mode: "multi", dir: ".claude/skills", globalDir: join(homedir(), ".claude", "skills") },
692
+ kiro: { mode: "multi", dir: ".kiro/skills" },
693
+ replit: { mode: "multi", dir: ".agents/skills" },
694
+ cursor: { mode: "single", base: ".", file: "AGENTS.md" },
695
+ codex: { mode: "single", base: ".", file: "AGENTS.md" },
696
+ antigravity: { mode: "single", base: ".", file: "AGENTS.md" },
697
+ continue: { mode: "single", base: ".", file: "AGENTS.md" },
698
+ windsurf: { mode: "single", base: ".", file: ".windsurfrules" },
699
+ copilot: { mode: "single", base: ".github", file: "copilot-instructions.md" },
700
+ cline: { mode: "single", base: ".clinerules", file: "cometchat.md" },
701
+ gemini: { mode: "single", base: ".", file: "GEMINI.md" },
702
+ aider: { mode: "single", base: ".", file: "CONVENTIONS.md" },
703
+ };
704
+
705
+ // ─── MCP auto-wiring (RULES.md "Canonical doc channel" tier 1) ──────────────
706
+ // Beyond placing skill files, wire the CometChat docs MCP into each agent's OWN
707
+ // config so live docs load automatically. Each agent's mechanism differs — JSON
708
+ // vs TOML, `url` vs `httpUrl` vs `url`+`type` — so there is NO single snippet.
709
+ // MERGE, never clobber: preserve the user's existing servers/settings, only add
710
+ // or refresh `cometchat-docs`. Skipped with `--no-mcp`. Claude also auto-connects
711
+ // via the plugin manifest (plugin-root .mcp.json); this covers the CLI path too.
712
+ const MCP_NAME = "cometchat-docs";
713
+ const MCP_URL = "https://mcp.cometchat.com/mcp";
714
+ // Claude's USER-level MCP lives in ~/.claude.json — a large file Claude Code itself rewrites
715
+ // while running, so the installer never edits it: `--global` prints the `claude mcp add
716
+ // --scope user` command instead of writing a project .mcp.json into whatever folder you ran from.
717
+ const CLAUDE_USER_MCP_CMD = `claude mcp add --scope user --transport http ${MCP_NAME} ${MCP_URL}`;
718
+ const MCP_TARGETS = {
719
+ claude: { kind: "json", proj: ".mcp.json", glob: null, shape: () => ({ type: "http", url: MCP_URL }) },
720
+ cursor: { kind: "json", proj: ".cursor/mcp.json", glob: join(homedir(), ".cursor", "mcp.json"), shape: () => ({ url: MCP_URL }) }, // Cursor: bare `url`, transport inferred
721
+ gemini: { kind: "json", proj: ".gemini/settings.json", glob: join(homedir(), ".gemini", "settings.json"), shape: () => ({ httpUrl: MCP_URL, timeout: 5000 }) }, // Gemini: `httpUrl` (its `url` = SSE!)
722
+ codex: { kind: "toml", proj: ".codex/config.toml", glob: join(homedir(), ".codex", "config.toml") }, // Codex: TOML + needs the RMCP flag
723
+ };
724
+ const isPlainObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
725
+
726
+ // How to add the server by hand when we could not (or must not) write the agent's config.
727
+ function manualMcpHint(ide, file) {
728
+ if (ide === "claude") return `claude mcp add --scope ${file ? "project" : "user"} --transport http ${MCP_NAME} ${MCP_URL}`;
729
+ if (ide === "codex") return `add to ${file}: experimental_use_rmcp_client = true (root) + [mcp_servers.${MCP_NAME}] url = "${MCP_URL}"`;
730
+ const m = MCP_TARGETS[ide];
731
+ return `add "${MCP_NAME}": ${JSON.stringify(m.shape())} under "mcpServers" in ${file}`;
732
+ }
733
+
734
+ // Merge the server into a JSON `mcpServers` map without touching anything else. Anything that is
735
+ // not a JSON OBJECT at the top level (null, a string, a number, an array) — or whose `mcpServers`
736
+ // is not an object — is left untouched and reported, never "fixed": assigning a property onto
737
+ // `null` crashed the install AFTER the skills were copied, and onto a string/array it silently did
738
+ // nothing while reporting "added".
739
+ function mergeJsonMcp(file, serverShape) {
740
+ let cfg = {};
741
+ if (existsSync(file)) {
742
+ let parsed;
743
+ try { parsed = JSON.parse(readFileSync(file, "utf8")); }
744
+ catch (e) { return { file, status: "skipped-unparseable", reason: e && e.code === "EISDIR" ? "is a directory" : "not valid JSON" }; } // never corrupt a file we can't parse
745
+ if (!isPlainObject(parsed)) return { file, status: "skipped-unparseable", reason: `top-level JSON is ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : `a ${typeof parsed}`}, not an object` };
746
+ cfg = parsed;
747
+ }
748
+ if (cfg.mcpServers === undefined || cfg.mcpServers === null) cfg.mcpServers = {};
749
+ else if (!isPlainObject(cfg.mcpServers)) return { file, status: "skipped-unparseable", reason: `"mcpServers" is ${Array.isArray(cfg.mcpServers) ? "an array" : `a ${typeof cfg.mcpServers}`}, not an object` };
750
+ const existed = !!cfg.mcpServers[MCP_NAME];
751
+ cfg.mcpServers[MCP_NAME] = serverShape;
752
+ mkdirSync(dirname(file), { recursive: true });
753
+ writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n", "utf8");
754
+ return { file, status: existed ? "updated" : "added" };
755
+ }
756
+
757
+ // Codex is TOML AND gates HTTP transport behind a ROOT-level flag that must
758
+ // precede the first table — so prepend the flag if missing, append the server
759
+ // table if missing. Idempotent; leaves an existing entry untouched.
760
+ function mergeCodexToml(file) {
761
+ const FLAG = "experimental_use_rmcp_client = true";
762
+ const TABLE = `[mcp_servers.${MCP_NAME}]`;
763
+ let txt = existsSync(file) ? readFileSync(file, "utf8") : "";
764
+ const hadTable = txt.includes(TABLE);
765
+ if (!/experimental_use_rmcp_client\s*=/.test(txt)) txt = `${FLAG}\n\n${txt}`; // root flag before any table
766
+ if (!hadTable) txt = `${txt.replace(/\s*$/, "")}\n\n${TABLE}\nurl = "${MCP_URL}"\n`;
767
+ mkdirSync(dirname(file), { recursive: true });
768
+ writeFileSync(file, txt, "utf8");
769
+ return { file, status: hadTable ? "present" : "added" };
770
+ }
771
+
772
+ function wireMcpForIde(ide, global) {
773
+ const m = MCP_TARGETS[ide];
774
+ if (!m) return null; // this agent has no supported HTTP-MCP wiring
775
+ if (global && !m.glob) return { file: null, status: "manual", command: CLAUDE_USER_MCP_CMD };
776
+ const file = global ? m.glob : join(CWD, m.proj);
777
+ // Never let a config-write failure (EACCES, a directory where the file should be, …) crash the
778
+ // run after the skills are already copied — report it with the by-hand fallback instead.
779
+ try { return m.kind === "toml" ? mergeCodexToml(file) : mergeJsonMcp(file, m.shape()); }
780
+ catch (e) { return { file, status: "skipped-error", reason: e.code || e.message }; }
781
+ }
782
+
783
+ // Read a shipped skill's frontmatter (name + description → summary + triggers).
784
+ // Drives the orienting router table from real data so it can't drift.
785
+ function skillMeta(name) {
786
+ const file = join(PACK_ROOT, "skills", name, "SKILL.md");
787
+ const b = existsSync(file) ? (readFileSync(file, "utf8").match(/^---\n([\s\S]*?)\n---/) || [])[1] || "" : "";
788
+ const g = (k) => (b.match(new RegExp("^" + k + ':\\s*"?(.+?)"?\\s*$', "m")) || [])[1] || "";
789
+ const desc = g("description");
790
+ const triggers = (desc.match(/Triggers?:\s*(.+)$/) || [])[1] || "";
791
+ const summary = desc.split(/Triggers?:/)[0].replace(/\s+/g, " ").trim();
792
+ return { name: g("name") || name, dir: name, summary, triggers };
793
+ }
794
+
795
+ // The short (~4KB) orienting router written for "single" agents. refBase is the
796
+ // on-disk location of the full skill tree the agent reads on demand.
797
+ function generateOrientingMd(reg, dirs, refBase, fam) {
798
+ const entry = reg.entry || "cometchat";
799
+ const label = (fam && fam.label) || "UI Kit";
800
+ const kit = (fam && fam.kit_package) || "";
801
+ // Scope the table to THIS family + the platform-neutral router/gate. Listing
802
+ // every family's skills is what let an RN agent read React rows (AUDIT-101).
803
+ const shared = new Set([entry, "cometchat-onboarding"]);
804
+ const scoped = (fam && fam.dirPrefix)
805
+ ? dirs.filter((d) => shared.has(d) || d.startsWith(fam.dirPrefix))
806
+ : dirs;
807
+ const metas = scoped.map(skillMeta);
808
+ let md = `# CometChat — AI Agent Instructions (${label})\n\n`;
809
+ md += `> Auto-generated by \`npx @cometchat/skills add --ide <agent>\` between the \`cometchat-skills\` markers — edits inside them are overwritten on re-run; anything outside them is yours and is kept.\n`;
810
+ md += `> ${metas.length} task-shaped skills ship as Markdown under \`${refBase}/\`. Read them ON DEMAND — do not inline them all (that burns tokens every turn).\n\n`;
811
+
812
+ md += `## For any CometChat chat / messaging / calls task\n\n`;
813
+ md += `This project integrates **CometChat** via the **${label}**${kit ? ` (\`${kit}\`)` : ""}. OPEN the matching skill file below and follow it — do **not** answer from training memory (the UI Kit API changes across majors; these skills are verified against the pinned kit).\n\n`;
814
+ md += `| When the user wants… | Read this skill |\n|---|---|\n`;
815
+ for (const m of metas) {
816
+ const purpose = (m.triggers || m.summary).replace(/\|/g, "\\|"); // ponytail: no slice — a cut trigger cannot match (AUDIT-102)
817
+ md += `| ${purpose} | \`${refBase}/${m.dir}/SKILL.md\` |\n`;
818
+ }
819
+ md += `\nEach skill lazily pulls deeper detail from its own \`references/*.md\` — open those only when the skill points you to them.\n\n`;
820
+
821
+ md += `## Start here\n\n`;
822
+ md += `1. **Detect** — \`npx @cometchat/skills detect --json\` (framework, installed UI Kit, version_conflict). If \`version_conflict\` is non-null, STOP and reconcile before touching code.\n`;
823
+ md += `2. **Already integrated?** If \`detect\` says \`existing_cometchat: true\`, this is a TASK, not an integration — go **straight** to the matching skill in the table above and skip onboarding. "Change my avatar", "add a logout button", "add polls", "restyle the bubble" are all tasks. Do NOT re-run the plan/approve journey, and do NOT fall back to reading \`node_modules\` when the phrasing is not an exact match — pick the closest row and open it.\n`;
824
+ md += `3. **Route** — open \`${refBase}/${entry}/SKILL.md\` (the dispatcher); it points to the right task skill for the request.\n`;
825
+ md += `4. **Follow the skill** — it carries the exact init→login→render, the component props, and the verify steps.\n\n`;
826
+
827
+ md += `## Hard rules (full text in \`${refBase}/RULES.md\`)\n\n`;
828
+ md += `- **APPEND, never replace** the user's code — additive diffs; confirm before bulk edits.\n`;
829
+ md += `- **One package per platform**; never mix UI Kit majors (v5/v6/v7).\n`;
830
+ md += `- **Never hardcode credentials** — the Auth Key is dev-only; use a server-minted auth token for production.\n`;
831
+ md += `- **Give the chat container real height + width** or it collapses; **never render a raw localization key** as a label.\n`;
832
+ md += `- **Render custom controls in the component's own view slot**, not as a sibling on top of it.\n`;
833
+ md += `- **Verify against the catalog + a real build** before "done"; fetch signatures from live docs, never from memory or \`node_modules\`.\n`;
834
+ md += `- **Need a CometChat API that is not in the table above? FETCH THE DOCS** — open the family \`core\` skill's \`references/docs-map.md\` and follow it. **Never** grep \`node_modules\` and **never** read \`*.d.ts\`: they carry internal, unsupported and version-drifting surface. If the docs do not have it, say so — do not infer it from types.\n\n`;
835
+
836
+ md += `## Want the native Claude Code install instead?\n\n`;
837
+ md += "```bash\n";
838
+ md += `npx @cometchat/skills add --ide claude # writes the .claude/skills tree (auto progressive disclosure)\n`;
839
+ md += "```\n";
840
+ return md;
841
+ }
842
+
843
+ // The single-file targets (AGENTS.md, GEMINI.md, CONVENTIONS.md, .windsurfrules,
844
+ // .github/copilot-instructions.md, .clinerules/cometchat.md) are often the USER's own file —
845
+ // AGENTS.md especially is shared by many tools. Overwriting it wholesale destroyed their content.
846
+ // So the router lives in a MANAGED BLOCK: re-runs replace only what is between the markers.
847
+ // Every one of these files is Markdown (or read raw as Markdown-ish rules text), where an HTML
848
+ // comment is invisible when rendered and inert when read raw — a safe fence for all of them.
849
+ const MARK_START = "<!-- cometchat-skills:start -->";
850
+ const MARK_END = "<!-- cometchat-skills:end -->";
851
+ // The unmarked router an earlier installer version wrote (whole-file, "Do not hand-edit"): replace
852
+ // exactly that span on upgrade instead of appending a second router next to it.
853
+ const LEGACY_ROUTER_RE = /^# CometChat — AI Agent Instructions[^\r\n]*\r?\n[\s\S]*?\r?\n## Want the native Claude Code install instead\?\r?\n(?:\r?\n)*```bash\r?\n[^\r\n]*\r?\n```[ \t]*(?:\r?\n)?/m;
854
+ // The router the PUBLISHED v4.2–4.4.x installer wrote (npm `latest` users have it on disk): from its
855
+ // "Auto-generated … Do not hand-edit" header to its closing "That writes N per-skill markdown files"
856
+ // line. Anything a user appended after that line is left alone. (4.0/4.1 wrote no router file.)
857
+ const LEGACY_V4_ROUTER_RE = /^# CometChat — AI Agent Instructions\r?\n\r?\n> Auto-generated by `npx @cometchat\/skills add`\. Do not hand-edit;[\s\S]*?\r?\nThat writes \d+ per-skill markdown files to `\.claude\/skills\/cometchat-\*\/SKILL\.md`\.[^\r\n]*(?:\r?\n)?/m;
858
+ function writeManagedBlock(dest, body) {
859
+ const block = (eol) => [MARK_START, body.replace(/\n+$/, ""), MARK_END].join("\n").replace(/\n/g, eol);
860
+ if (!existsSync(dest)) { writeFileSync(dest, block("\n") + "\n", "utf8"); return "created"; }
861
+ if (statSync(dest).isDirectory()) return "skipped-not-a-file"; // e.g. an AGENTS.md FOLDER — never write into or over it
862
+ const text = readFileSync(dest, "utf8");
863
+ const legacy = [LEGACY_ROUTER_RE, LEGACY_V4_ROUTER_RE].find((re) => re.test(text));
864
+ const eol = /\r\n/.test(text) ? "\r\n" : "\n"; // keep a CRLF file CRLF
865
+ const s = text.indexOf(MARK_START);
866
+ const e = text.indexOf(MARK_END);
867
+ let next, status;
868
+ if (s >= 0 && e > s) { next = text.slice(0, s) + block(eol) + text.slice(e + MARK_END.length); status = "updated"; }
869
+ else if (s >= 0 || e >= 0) return "skipped-malformed"; // one marker without its partner: don't guess where the block ends
870
+ else if (legacy) { next = text.replace(legacy, block(eol) + eol); status = "migrated"; }
871
+ else { next = text.replace(/(\r?\n)*$/, "") + (text.trim() ? eol + eol : "") + block(eol) + eol; status = "appended"; }
872
+ if (next === text) return "unchanged";
873
+ writeFileSync(dest, next, "utf8");
874
+ return status;
875
+ }
876
+
877
+ // Install for one agent. "multi" → native SKILL.md tree; "single" → orienting
878
+ // router at the agent's rules file + the full tree at the neutral dir.
879
+ function installForIde(ide, global) {
880
+ const t = IDE_TARGETS[ide];
881
+ const fam = resolveFamily();
882
+ if (t.mode === "multi") {
883
+ const target = global && t.globalDir ? t.globalDir : join(CWD, t.dir);
884
+ const { installed, removed } = copySkillsTo(target, fam);
885
+ return { ide, mode: "multi", target, count: installed.length, removed };
886
+ }
887
+ const neutral = join(CWD, NEUTRAL_SKILLS_DIR);
888
+ const { installed, removed } = copySkillsTo(neutral, fam);
889
+ const base = join(CWD, t.base);
890
+ let dest = join(base, t.file), fileStatus;
891
+ try {
892
+ if (t.base !== "." && existsSync(base) && !statSync(base).isDirectory()) {
893
+ // The rules "folder" is a FILE. Cline's original single-file form is a `.clinerules` file, and
894
+ // Cline still reads it — so put the block there. Any other target: leave the user's file alone.
895
+ if (t.base === ".clinerules") dest = base;
896
+ else fileStatus = "skipped-not-a-directory";
897
+ } else mkdirSync(base, { recursive: true });
898
+ fileStatus ||= writeManagedBlock(dest, generateOrientingMd(peers(), shippedSkillDirs(), NEUTRAL_SKILLS_DIR, fam));
899
+ } catch (e) {
900
+ fileStatus = "skipped-error"; // never crash after the skills are already copied
901
+ return { ide, mode: "single", target: dest, neutral, count: installed.length, removed, file_status: fileStatus, reason: e.code || e.message };
902
+ }
903
+ return { ide, mode: "single", target: dest, neutral, count: installed.length, removed, file_status: fileStatus };
904
+ }
905
+
906
+ function cmdAdd() {
907
+ const global = has("--global");
908
+ const supported = Object.keys(IDE_TARGETS);
909
+ requireFlagValue("--ide", [...supported, "all"]);
910
+ requireFlagValue("--target", [...supported, "all"]);
911
+ const ide = (flagVal("--ide") || flagVal("--target") || "claude").toLowerCase();
912
+ if (ide !== "all" && !IDE_TARGETS[ide]) {
913
+ console.error(`unknown --ide: ${ide}\nsupported: ${supported.join(", ")}, all`);
914
+ process.exit(2);
915
+ }
916
+ let ides = ide === "all" ? supported : [ide];
917
+ // --global needs a USER-level location. Only claude has one here (~/.claude/skills); kiro/replit
918
+ // and the single-file agents (AGENTS.md, GEMINI.md, …) are per-project by nature. They used to
919
+ // ignore --global and write into whatever folder you ran from — now they are skipped, loudly.
920
+ const noGlobal = global ? ides.filter((n) => !(IDE_TARGETS[n].mode === "multi" && IDE_TARGETS[n].globalDir)) : [];
921
+ if (noGlobal.length) {
922
+ ides = ides.filter((n) => !noGlobal.includes(n));
923
+ const msg = `--global: no user-level install location for ${noGlobal.join(", ")} — skipped (nothing written to ${CWD}). ` +
924
+ `Run \`npx @cometchat/skills add --ide <agent>\` inside the project for ${noGlobal.length > 1 ? "those" : "it"}.`;
925
+ if (!ides.length) {
926
+ if (JSON_OUT) console.log(JSON.stringify({ ok: false, error: msg, skipped: noGlobal }));
927
+ else console.error(msg);
928
+ process.exit(2);
929
+ }
930
+ if (!JSON_OUT) console.error(`↷ ${msg}`);
931
+ }
932
+ // Surface a version-conflict up front (non-fatal): a project already on a different UI Kit
933
+ // major still gets skills, but the agent must reconcile before emitting code (RULES §3).
934
+ const vc = (detect() || {}).version_conflict || null;
935
+ if (vc && !JSON_OUT) console.error(`⚠️ version_conflict: ${vc.detail} — reconcile before generating code (RULES §3).`);
936
+ const results = [];
937
+ const writtenSingles = new Set();
938
+ for (const name of ides) {
939
+ const t = IDE_TARGETS[name];
940
+ if (t.mode === "single") { // dedupe agents that share a file (cursor/codex/antigravity/continue → AGENTS.md)
941
+ const key = join(t.base, t.file);
942
+ if (writtenSingles.has(key)) { results.push({ ide: name, mode: "single", target: join(CWD, key), deduped: true }); continue; }
943
+ writtenSingles.add(key);
944
+ }
945
+ results.push(installForIde(name, global));
946
+ }
947
+
948
+ // Wire the docs MCP into each agent's own config (tier 1 auto-connect), unless opted out.
949
+ const mcpResults = [];
950
+ if (!has("--no-mcp")) {
951
+ const wroteMcp = new Set();
952
+ for (const name of ides) {
953
+ const m = MCP_TARGETS[name];
954
+ if (!m) continue; // agent has no supported HTTP-MCP wiring (e.g. copilot/windsurf/aider)
955
+ const file = global ? (m.glob || `user:${name}`) : join(CWD, m.proj);
956
+ if (wroteMcp.has(file)) continue;
957
+ wroteMcp.add(file);
958
+ const r = wireMcpForIde(name, global);
959
+ if (r) mcpResults.push({ ide: name, ...r });
960
+ }
961
+ }
962
+
963
+ const FILE_VERB = { created: "created", updated: "updated (managed block)", migrated: "updated (replaced an earlier unmarked router)",
964
+ appended: "appended (your content kept)", unchanged: "unchanged" };
965
+ out({ ok: true, results, mcp: mcpResults, version_conflict: vc, ...(noGlobal.length ? { skipped: noGlobal } : {}) }, () => {
966
+ for (const r of results) {
967
+ if (r.deduped) console.log(`✅ ${r.ide.padEnd(11)} → shares ${r.target} (already written)`);
968
+ else if (r.mode === "multi") console.log(`✅ ${r.ide.padEnd(11)} → ${r.count} skill(s) in ${r.target}`);
969
+ else if (r.file_status === "skipped-malformed") console.log(`⚠️ ${r.ide.padEnd(11)} → ${r.target} has an unmatched cometchat-skills start/end marker — router NOT written; fix or remove the marker and re-run (+ ${r.count} skills in ${r.neutral})`);
970
+ else if (String(r.file_status).startsWith("skipped")) {
971
+ const why = { "skipped-not-a-file": "is a folder, not a file", "skipped-not-a-directory": "sits where a folder is expected" }[r.file_status] || `could not be written (${r.reason})`;
972
+ console.log(`⚠️ ${r.ide.padEnd(11)} → ${r.target.replace(CWD + "/", "")} ${why} — router NOT written; the skills are installed (+ ${r.count} skills in ${r.neutral})`);
973
+ }
974
+ else console.log(`✅ ${r.ide.padEnd(11)} → router ${r.target} ${FILE_VERB[r.file_status] || r.file_status} (+ ${r.count} skills in ${r.neutral})`);
975
+ if (r.removed && r.removed.length) console.log(` removed ${r.removed.length} stale skill(s) from an earlier version: ${r.removed.join(", ")}`);
976
+ }
977
+ for (const r of mcpResults) {
978
+ const rel = r.file ? r.file.replace(CWD + "/", "") : null;
979
+ if (r.status === "manual") console.log(`🔌 MCP ${r.ide.padEnd(8)} → --global: user-level MCP lives in ~/.claude.json (owned by Claude Code, not edited). Run:\n ${r.command}`);
980
+ else if (r.status.startsWith("skipped")) console.log(`⚠️ MCP ${r.ide.padEnd(8)} → ${rel} left untouched (${r.reason}) — add by hand: ${manualMcpHint(r.ide, rel)}`);
981
+ else console.log(`🔌 MCP ${r.ide.padEnd(8)} → ${MCP_NAME} ${r.status} in ${rel}`);
982
+ }
983
+ // Only point at the neutral tree when a router-file agent actually got one — a claude-only
984
+ // install has no .cometchat/skills, and naming it sent people to a path that doesn't exist.
985
+ const neutralDir = (results.find((r) => r.neutral) || {}).neutral;
986
+ console.log(`\nEntry: /cometchat add chat to my app` +
987
+ (neutralDir ? ` (agents without slash-skills: open ${neutralDir.replace(CWD + "/", "")}/cometchat/SKILL.md)` : ""));
988
+ if (mcpResults.length) console.log(`Docs MCP: ${MCP_URL} (auto-loads live docs; restart the agent if it doesn't connect. Skip with --no-mcp.)`);
989
+ // Claude Code gates a PROJECT .mcp.json server behind a one-time trust prompt (`claude mcp list`
990
+ // shows "Pending approval" until then) — say so, or "auto-loads" reads as broken on first run.
991
+ if (mcpResults.some((r) => r.ide === "claude" && (r.status === "added" || r.status === "updated")))
992
+ console.log(`Claude Code: approve "${MCP_NAME}" when \`claude\` asks on first start in this project — until then it shows "Pending approval" in \`claude mcp list\`. Skills fall back to the docs site if you decline.`);
993
+ });
994
+ }
995
+
996
+ function runNode(scriptRel, extra = []) {
997
+ const r = spawnSync("node", [join(PACK_ROOT, scriptRel), ...extra], { stdio: "inherit" });
998
+ return r.status ?? 1;
999
+ }
1000
+
1001
+ // The Tier-2 fence for a family: which engine, against which pinned-kit project, and whether that
1002
+ // project is set up. A packed (npm) install has no families.json, so `key`/`typecheckProject` are
1003
+ // null — this returns { skip } instead of handing the engine `--project null` (which printed
1004
+ // "npm --prefix null ci" and exited 1 on every npm install).
1005
+ function fencePlan(f) {
1006
+ const proj = f && f.key && f.typecheckProject && existsSync(join(PACK_ROOT, f.typecheckProject)) ? f.typecheckProject : null;
1007
+ if (!proj) return { skip: `no pinned-kit typecheck project for ${(f && f.label) || "this family"} in this install` };
1008
+ const P = join(PACK_ROOT, proj);
1009
+ const has = (rel) => existsSync(join(P, rel));
1010
+ let plan;
1011
+ if (has("tsconfig.json")) {
1012
+ const fam = f.fenceNeedles && f.skillDirPattern;
1013
+ if (!fam) return { skip: `no TypeScript fence declared for ${f.key}` };
1014
+ plan = { engine: "typecheck-fences.mjs", args: ["--project", proj, "--tsconfig", "tsconfig.json", "--discover", f.key],
1015
+ ready: has("node_modules/@cometchat"), setup: `npm --prefix ${proj} ci` };
1016
+ } else if (has("pubspec.yaml")) {
1017
+ plan = { engine: "typecheck-fences-dart.mjs", args: ["--project", proj, "--discover", f.key],
1018
+ ready: has(".dart_tool/package_config.json"), setup: `(cd ${proj} && flutter pub get)` };
1019
+ } else if (has("settings.gradle.kts") || has("settings.gradle")) {
1020
+ plan = { engine: "typecheck-fences-kotlin.mjs", args: ["--project", proj, "--discover", f.key],
1021
+ ready: has("local.properties"), setup: `write sdk.dir=<Android SDK path> into ${proj}/local.properties` };
1022
+ } else return { skip: `no fence engine for ${proj}` };
1023
+ if (!existsSync(join(PACK_ROOT, "test-suite/scripts", plan.engine))) return { skip: `${plan.engine} not in this install` };
1024
+ return { ...plan, proj };
1025
+ }
1026
+
1027
+ function cmdVerify() {
1028
+ const tier = argv[1] && !argv[1].startsWith("-") ? argv[1] : "all";
1029
+ // The predicate (review done-oracle) belongs to the reviewer, which is INTERNAL-DEV-ONLY
1030
+ // and NOT shipped in the published package (no e2e harness on npm). When it's absent —
1031
+ // i.e. running from an npm install rather than the dev repo — skip it cleanly instead of
1032
+ // erroring, so the end-user CLI never fails on the missing dev harness.
1033
+ const PREDICATE = ".claude/skills/cometchat-skill-reviewer/tools/predicate.mjs";
1034
+ const hasReviewer = existsSync(join(PACK_ROOT, PREDICATE));
1035
+ const gates = {
1036
+ lint: () => runNode("test-suite/scripts/lint-skills.mjs"),
1037
+ catalog: () => runNode("test-suite/scripts/verify-catalog.mjs"),
1038
+ // Family resolved from families.json (RULES.md §22). This hardcoded web-v7, so `fences`
1039
+ // silently checked only React regardless of which family the workspace actually used.
1040
+ fences: () => {
1041
+ if (!resolvePeer((detect() || {}).framework || "")) {
1042
+ console.log("↷ fences — DEV-ONLY Tier-2 gate: no family resolved for this directory (pass --family <platform>); skipped.");
1043
+ return 0;
1044
+ }
1045
+ const p = fencePlan(resolveFamily());
1046
+ if (p.skip) { console.log(`↷ fences — DEV-ONLY Tier-2 gate (${p.skip}); skipped.`); return 0; }
1047
+ if (!p.ready) { console.log(`↷ fences — DEV-ONLY Tier-2 gate: pinned kit not set up in ${p.proj}; skipped. Set up: ${p.setup}`); return 0; }
1048
+ return runNode(`test-suite/scripts/${p.engine}`, p.args);
1049
+ },
1050
+ predicate: () => {
1051
+ if (!hasReviewer) { console.log("↷ predicate — DEV-ONLY review gate (e2e harness not present in this install); skipped."); return 0; }
1052
+ return runNode(PREDICATE);
1053
+ },
1054
+ };
1055
+ let seq;
1056
+ if (tier === "all") seq = ["lint", "catalog", "predicate"]; // offline gates (fences needs the kit)
1057
+ else if (tier === "ci") seq = ["lint", "catalog", "fences", "predicate"];
1058
+ else if (gates[tier]) seq = [tier];
1059
+ else { console.error(`unknown tier: ${tier} (lint|catalog|fences|predicate|all|ci)`); process.exit(2); }
1060
+ let bad = 0;
1061
+ for (const g of seq) { const code = gates[g](); if (code !== 0) bad++; }
1062
+ process.exit(bad ? 1 : 0);
1063
+ }
1064
+
1065
+ function cmdCatalog() {
1066
+ process.exit(runNode("test-suite/scripts/catalog-refresh.mjs", has("--check") ? ["--check"] : []));
1067
+ }
1068
+
1069
+ function cmdDoctor() {
1070
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
1071
+ // Family-aware. Reporting the WEB catalog (405 symbols) and "npm --prefix cometchat-react-app ci"
1072
+ // inside an Android or iOS project was worse than noise: it told the user their oracle was fine
1073
+ // while reading React's. Resolve the peer the project actually is.
1074
+ // Resolved exactly like `add` (FRAMEWORK_PLATFORM + --family): Expo → react-native, Next/Astro →
1075
+ // react. The old platform-string match fell through to React for Expo, and with NO family it
1076
+ // printed "harness/null". No family ⇒ say so; never substitute React's catalog.
1077
+ const det = detect();
1078
+ const peer = resolvePeer(det.framework || "");
1079
+ const fam = reviewerFamily(peer); // dev-only (families.json); null on an npm install
1080
+ const noFamily = "n/a — no family resolved (run inside the app package, or pass --family <platform>)";
1081
+ const catalog = peer && peer.catalog ? readJSON(join(PACK_ROOT, `test-suite/catalogs/${peer.catalog}.json`)) : null;
1082
+ // The pinned kit (Tier-2 typecheck project) and the review app/harness (Tier-3) for THIS family.
1083
+ let kit;
1084
+ if (!peer) kit = { ok: false, detail: noFamily };
1085
+ else if (!fam) kit = { ok: false, detail: "n/a — dev-only (pinned kit + review harness are not in this install)" };
1086
+ else {
1087
+ const plan = fencePlan({ ...fam, label: peer.label });
1088
+ const dirs = [plan.proj, fam.appDir].filter((d, i, a) => d && a.indexOf(d) === i);
1089
+ const installed = (d) => {
1090
+ const P = join(PACK_ROOT, d);
1091
+ if (!existsSync(P)) return false;
1092
+ if (existsSync(join(P, "package.json"))) return existsSync(join(P, "node_modules", "@cometchat"));
1093
+ if (existsSync(join(P, "pubspec.yaml"))) return existsSync(join(P, ".dart_tool"));
1094
+ return true; // native harness: present = usable (its own gate reports toolchain gaps)
1095
+ };
1096
+ const hit = dirs.find(installed);
1097
+ kit = hit ? { ok: true, detail: hit }
1098
+ : { ok: false, detail: plan.setup ? `not set up — ${plan.setup}` : dirs.length ? `${dirs.join(" / ")} not set up` : "no pinned kit or harness declared for this family" };
1099
+ }
1100
+ // Review state for THIS family only (reviews/<family>/review-state.*.json) — never React's as a fallback.
1101
+ let state = null;
1102
+ const reviewDir = fam ? join(PACK_ROOT, "reviews", fam.name) : null;
1103
+ if (reviewDir && existsSync(reviewDir)) {
1104
+ for (const f of readdirSync(reviewDir).filter((n) => n.startsWith("review-state."))) {
1105
+ const j = readJSON(join(reviewDir, f));
1106
+ if (j && (!state || ((j.totals || {}).passed || 0) > ((state.totals || {}).passed || 0))) state = j;
1107
+ }
1108
+ }
1109
+ // The pinned kit and the review harness/state are INTERNAL-DEV concerns — neither ships
1110
+ // in the published package. Mark those checks devOnly so `doctor` stays green on an npm
1111
+ // install (they surface as advisory ⚠️, not failures).
1112
+ const reviewerPresent = existsSync(join(PACK_ROOT, ".claude/skills/cometchat-skill-reviewer"));
1113
+ const checks = [
1114
+ { name: "node >= 18", ok: nodeMajor >= 18, detail: `v${process.versions.node}` },
1115
+ { name: `family`, ok: !!peer, detail: peer ? `${peer.platform} (${peer.label})` : noFamily, advisory: true },
1116
+ { name: "Tier-1 catalog present", ok: !!catalog,
1117
+ detail: !peer ? noFamily : catalog ? `${peer.catalog}: ${catalog.symbolCount} symbols` : `test-suite/catalogs/${peer.catalog}.json missing (npm run catalog:refresh)`,
1118
+ advisory: !peer },
1119
+ { name: "pinned kit / harness (dev-only Tier-2/3)", ok: kit.ok, detail: kit.detail, devOnly: true },
1120
+ { name: "review state (dev-only)", ok: !!state,
1121
+ detail: state ? `${(state.totals || {}).passed || 0}/${(state.totals || {}).count || 0} passed`
1122
+ : !peer ? noFamily : !reviewerPresent ? "n/a — e2e review harness is dev-only, not in this install" : "no review yet", devOnly: true },
1123
+ ];
1124
+ const ok = checks.every((c) => c.ok || c.devOnly || c.advisory); // kit-install + review state are dev-only; no-family is advisory
1125
+ out({ ok, checks }, () => {
1126
+ console.log(`cometchat-skills doctor (v${packVersion()})`);
1127
+ for (const c of checks) console.log(` ${c.ok ? "✅" : "⚠️ "} ${c.name} — ${c.detail}`);
1128
+ });
1129
+ process.exit(ok ? 0 : 1);
1130
+ }
1131
+
1132
+ function help() {
1133
+ console.log(`cometchat-skills v${packVersion()} — deterministic CLI for the CometChat skills pack
1134
+
1135
+ Usage: npx @cometchat/skills <command> [--json]
1136
+
1137
+ detect Probe the current project (framework, UI Kit, version_conflict)
1138
+ add [--ide <a>] Install the skills for an AI agent (default: claude)
1139
+ --ide claude|kiro|replit → native SKILL.md tree
1140
+ --ide cursor|codex|antigravity|continue|windsurf|copilot|cline|gemini|aider
1141
+ → orienting router + ./.cometchat/skills tree
1142
+ (router goes in a <!-- cometchat-skills:start/end -->
1143
+ block; your own content in that file is kept)
1144
+ --ide all → every supported agent
1145
+ --global → user-level dir: claude only (~/.claude/skills; prints
1146
+ the \`claude mcp add --scope user\` command instead of
1147
+ writing .mcp.json). Agents with no user-level location
1148
+ (kiro, replit, the router-file agents) are skipped.
1149
+ (also wires the docs MCP into the agent's config so live
1150
+ docs auto-load: claude .mcp.json · cursor .cursor/mcp.json ·
1151
+ codex .codex/config.toml · gemini .gemini/settings.json)
1152
+ --no-mcp → skip the MCP wiring
1153
+ --family <platform> → force the family (monorepo root / ambiguous detect;
1154
+ e.g. js-calls, ios-calls to add a headless calls skill
1155
+ alongside the UI Kit skills)
1156
+ list List the skills this pack ships
1157
+ info <slug> Show a skill's triggers + compatibility
1158
+ verify [tier] Run gates: lint | catalog | fences | predicate | all | ci
1159
+ catalog [--check] Refresh the Tier-1 catalog from the installed kit (oracle refresh)
1160
+ doctor Environment health check
1161
+ version | help
1162
+
1163
+ Notes:
1164
+ • Credentials/provisioning (auth, provision, config, features) are a
1165
+ SEPARATE published tool, @cometchat/skills-cli — see the core skill's
1166
+ references/setup-credentials.md. This CLI is the skill-management + verify layer.`);
1167
+ }
1168
+
1169
+ switch (CMD) {
1170
+ case "detect": cmdDetect(); break;
1171
+ case "add": case "install": cmdAdd(); break;
1172
+ case "list": cmdList(); break;
1173
+ case "info": cmdInfo(); break;
1174
+ case "verify": cmdVerify(); break;
1175
+ case "catalog": cmdCatalog(); break;
1176
+ case "doctor": cmdDoctor(); break;
1177
+ case "version": case "--version": case "-v": console.log(packVersion()); break;
1178
+ case "help": case "--help": case "-h": case undefined: help(); break;
1179
+ default: console.error(`unknown command: ${CMD}\n`); help(); process.exit(2);
1180
+ }