@overcrow/mcp 0.0.0 → 0.1.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 (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -1
  3. package/dist/audit/index.js +103 -0
  4. package/dist/audit/lexer.js +417 -0
  5. package/dist/audit/project.js +200 -0
  6. package/dist/audit/rules.js +661 -0
  7. package/dist/bootstrap/download.js +229 -0
  8. package/dist/bootstrap/install.js +280 -0
  9. package/dist/bootstrap/pin.js +85 -0
  10. package/dist/bootstrap/pin.json +119 -0
  11. package/dist/bootstrap/zip.js +258 -0
  12. package/dist/cli.js +200 -0
  13. package/dist/confine.js +244 -0
  14. package/dist/content/docs/en/cli.md +353 -0
  15. package/dist/content/docs/en/dev-channel.md +130 -0
  16. package/dist/content/docs/en/elements.md +469 -0
  17. package/dist/content/docs/en/forms.md +328 -0
  18. package/dist/content/docs/en/guide.md +253 -0
  19. package/dist/content/docs/en/index.md +85 -0
  20. package/dist/content/docs/en/limits.md +241 -0
  21. package/dist/content/docs/en/logic.md +446 -0
  22. package/dist/content/docs/en/manifest.md +381 -0
  23. package/dist/content/docs/en/mcp.md +165 -0
  24. package/dist/content/docs/en/package.md +131 -0
  25. package/dist/content/docs/en/publishing.md +162 -0
  26. package/dist/content/docs/en/reference.md +55 -0
  27. package/dist/content/docs/en/result-shapes.md +332 -0
  28. package/dist/content/docs/en/sdk.md +295 -0
  29. package/dist/content/docs/en/security.md +115 -0
  30. package/dist/content/docs/en/service-reference.md +344 -0
  31. package/dist/content/docs/en/services.md +517 -0
  32. package/dist/content/docs/en/style-properties.md +270 -0
  33. package/dist/content/docs/en/style.md +266 -0
  34. package/dist/content/docs/en/testing.md +400 -0
  35. package/dist/content/docs/en/view.md +338 -0
  36. package/dist/content/docs/en/widgets.md +76 -0
  37. package/dist/content/docs/fr/cli.md +367 -0
  38. package/dist/content/docs/fr/dev-channel.md +140 -0
  39. package/dist/content/docs/fr/elements.md +475 -0
  40. package/dist/content/docs/fr/forms.md +341 -0
  41. package/dist/content/docs/fr/guide.md +264 -0
  42. package/dist/content/docs/fr/index.md +90 -0
  43. package/dist/content/docs/fr/limits.md +244 -0
  44. package/dist/content/docs/fr/logic.md +461 -0
  45. package/dist/content/docs/fr/manifest.md +392 -0
  46. package/dist/content/docs/fr/mcp.md +173 -0
  47. package/dist/content/docs/fr/package.md +139 -0
  48. package/dist/content/docs/fr/publishing.md +172 -0
  49. package/dist/content/docs/fr/reference.md +59 -0
  50. package/dist/content/docs/fr/result-shapes.md +333 -0
  51. package/dist/content/docs/fr/sdk.md +301 -0
  52. package/dist/content/docs/fr/security.md +129 -0
  53. package/dist/content/docs/fr/service-reference.md +346 -0
  54. package/dist/content/docs/fr/services.md +540 -0
  55. package/dist/content/docs/fr/style-properties.md +272 -0
  56. package/dist/content/docs/fr/style.md +281 -0
  57. package/dist/content/docs/fr/testing.md +417 -0
  58. package/dist/content/docs/fr/view.md +352 -0
  59. package/dist/content/docs/fr/widgets.md +81 -0
  60. package/dist/content/examples/clock/LICENSE +21 -0
  61. package/dist/content/examples/clock/README.md +75 -0
  62. package/dist/content/examples/clock/listing.json +19 -0
  63. package/dist/content/examples/clock/locales/en.json +4 -0
  64. package/dist/content/examples/clock/locales/fr.json +4 -0
  65. package/dist/content/examples/clock/logic.ts +85 -0
  66. package/dist/content/examples/clock/manifest.json +44 -0
  67. package/dist/content/examples/clock/package.json +16 -0
  68. package/dist/content/examples/clock/style.ocss +26 -0
  69. package/dist/content/examples/clock/tests/hidden.scenario.json +68 -0
  70. package/dist/content/examples/clock/tests/load-logic.mjs +22 -0
  71. package/dist/content/examples/clock/tests/logic.test.mjs +89 -0
  72. package/dist/content/examples/clock/tests/options.scenario.json +108 -0
  73. package/dist/content/examples/clock/tests/preview.scenario.json +25 -0
  74. package/dist/content/examples/clock/tests/render.scenario.json +94 -0
  75. package/dist/content/examples/clock/tests/summer-time.scenario.json +74 -0
  76. package/dist/content/examples/clock/tests/ticks.scenario.json +120 -0
  77. package/dist/content/examples/clock/tsconfig.json +16 -0
  78. package/dist/content/examples/clock/view.ocml +8 -0
  79. package/dist/content/examples/countdown/LICENSE +20 -0
  80. package/dist/content/examples/countdown/listing.json +10 -0
  81. package/dist/content/examples/countdown/locales/en.json +15 -0
  82. package/dist/content/examples/countdown/locales/fr.json +15 -0
  83. package/dist/content/examples/countdown/logic.ts +179 -0
  84. package/dist/content/examples/countdown/manifest.json +34 -0
  85. package/dist/content/examples/countdown/package.json +15 -0
  86. package/dist/content/examples/countdown/style.ocss +94 -0
  87. package/dist/content/examples/countdown/tsconfig.json +16 -0
  88. package/dist/content/examples/countdown/view.ocml +42 -0
  89. package/dist/content/examples/fps/LICENSE +21 -0
  90. package/dist/content/examples/fps/README.md +64 -0
  91. package/dist/content/examples/fps/listing.json +19 -0
  92. package/dist/content/examples/fps/locales/en.json +11 -0
  93. package/dist/content/examples/fps/locales/fr.json +11 -0
  94. package/dist/content/examples/fps/logic.ts +64 -0
  95. package/dist/content/examples/fps/manifest.json +30 -0
  96. package/dist/content/examples/fps/package.json +16 -0
  97. package/dist/content/examples/fps/style.ocss +31 -0
  98. package/dist/content/examples/fps/tests/label.scenario.json +63 -0
  99. package/dist/content/examples/fps/tests/load-logic.mjs +22 -0
  100. package/dist/content/examples/fps/tests/logic.test.mjs +60 -0
  101. package/dist/content/examples/fps/tests/preview.scenario.json +29 -0
  102. package/dist/content/examples/fps/tests/refused.scenario.json +20 -0
  103. package/dist/content/examples/fps/tests/render.scenario.json +96 -0
  104. package/dist/content/examples/fps/tests/stale.scenario.json +91 -0
  105. package/dist/content/examples/fps/tests/unavailable.scenario.json +111 -0
  106. package/dist/content/examples/fps/tsconfig.json +16 -0
  107. package/dist/content/examples/fps/view.ocml +11 -0
  108. package/dist/content/examples/media/LICENSE +21 -0
  109. package/dist/content/examples/media/README.md +147 -0
  110. package/dist/content/examples/media/listing.json +19 -0
  111. package/dist/content/examples/media/locales/en.json +11 -0
  112. package/dist/content/examples/media/locales/fr.json +11 -0
  113. package/dist/content/examples/media/logic.ts +191 -0
  114. package/dist/content/examples/media/manifest.json +35 -0
  115. package/dist/content/examples/media/package.json +16 -0
  116. package/dist/content/examples/media/style.ocss +148 -0
  117. package/dist/content/examples/media/tests/covers.scenario.json +144 -0
  118. package/dist/content/examples/media/tests/gesture.scenario.json +173 -0
  119. package/dist/content/examples/media/tests/load-logic.mjs +22 -0
  120. package/dist/content/examples/media/tests/logic.test.mjs +176 -0
  121. package/dist/content/examples/media/tests/marquee.scenario.json +127 -0
  122. package/dist/content/examples/media/tests/preview.scenario.json +52 -0
  123. package/dist/content/examples/media/tests/read-only.scenario.json +53 -0
  124. package/dist/content/examples/media/tests/refused.scenario.json +22 -0
  125. package/dist/content/examples/media/tests/refused.test.mjs +31 -0
  126. package/dist/content/examples/media/tests/render.scenario.json +136 -0
  127. package/dist/content/examples/media/tests/states.scenario.json +257 -0
  128. package/dist/content/examples/media/tests/unavailable.scenario.json +120 -0
  129. package/dist/content/examples/media/tsconfig.json +16 -0
  130. package/dist/content/examples/media/view.ocml +41 -0
  131. package/dist/content/examples/notes/LICENSE +21 -0
  132. package/dist/content/examples/notes/README.md +219 -0
  133. package/dist/content/examples/notes/listing.json +19 -0
  134. package/dist/content/examples/notes/locales/en.json +25 -0
  135. package/dist/content/examples/notes/locales/fr.json +25 -0
  136. package/dist/content/examples/notes/logic.ts +799 -0
  137. package/dist/content/examples/notes/manifest.json +40 -0
  138. package/dist/content/examples/notes/package.json +16 -0
  139. package/dist/content/examples/notes/style.ocss +469 -0
  140. package/dist/content/examples/notes/tests/checks.scenario.json +237 -0
  141. package/dist/content/examples/notes/tests/create.scenario.json +260 -0
  142. package/dist/content/examples/notes/tests/delete.scenario.json +342 -0
  143. package/dist/content/examples/notes/tests/drafts.scenario.json +714 -0
  144. package/dist/content/examples/notes/tests/editor.scenario.json +232 -0
  145. package/dist/content/examples/notes/tests/frame.scenario.json +93 -0
  146. package/dist/content/examples/notes/tests/interactive.scenario.json +344 -0
  147. package/dist/content/examples/notes/tests/limits.scenario.json +1351 -0
  148. package/dist/content/examples/notes/tests/load-logic.mjs +22 -0
  149. package/dist/content/examples/notes/tests/logic.test.mjs +514 -0
  150. package/dist/content/examples/notes/tests/preview.scenario.json +99 -0
  151. package/dist/content/examples/notes/tests/read-only.scenario.json +105 -0
  152. package/dist/content/examples/notes/tests/refused.scenario.json +33 -0
  153. package/dist/content/examples/notes/tests/save.scenario.json +378 -0
  154. package/dist/content/examples/notes/tests/states.scenario.json +640 -0
  155. package/dist/content/examples/notes/tests/typing.scenario.json +562 -0
  156. package/dist/content/examples/notes/tests/unavailable.scenario.json +246 -0
  157. package/dist/content/examples/notes/tsconfig.json +16 -0
  158. package/dist/content/examples/notes/view.ocml +154 -0
  159. package/dist/content/examples/performance/LICENSE +21 -0
  160. package/dist/content/examples/performance/README.md +134 -0
  161. package/dist/content/examples/performance/listing.json +19 -0
  162. package/dist/content/examples/performance/locales/en.json +20 -0
  163. package/dist/content/examples/performance/locales/fr.json +20 -0
  164. package/dist/content/examples/performance/logic.ts +210 -0
  165. package/dist/content/examples/performance/manifest.json +83 -0
  166. package/dist/content/examples/performance/package.json +16 -0
  167. package/dist/content/examples/performance/style.ocss +75 -0
  168. package/dist/content/examples/performance/tests/fahrenheit.scenario.json +110 -0
  169. package/dist/content/examples/performance/tests/horizontal.scenario.json +96 -0
  170. package/dist/content/examples/performance/tests/load-logic.mjs +22 -0
  171. package/dist/content/examples/performance/tests/logic.test.mjs +226 -0
  172. package/dist/content/examples/performance/tests/menu.scenario.json +170 -0
  173. package/dist/content/examples/performance/tests/no-fps-source.scenario.json +47 -0
  174. package/dist/content/examples/performance/tests/preview.scenario.json +45 -0
  175. package/dist/content/examples/performance/tests/refused.scenario.json +20 -0
  176. package/dist/content/examples/performance/tests/refused.test.mjs +19 -0
  177. package/dist/content/examples/performance/tests/region.scenario.json +89 -0
  178. package/dist/content/examples/performance/tests/render.scenario.json +112 -0
  179. package/dist/content/examples/performance/tests/sources.scenario.json +151 -0
  180. package/dist/content/examples/performance/tests/stale.scenario.json +111 -0
  181. package/dist/content/examples/performance/tests/telemetry-only.scenario.json +44 -0
  182. package/dist/content/examples/performance/tests/thresholds.scenario.json +147 -0
  183. package/dist/content/examples/performance/tests/waiting.scenario.json +105 -0
  184. package/dist/content/examples/performance/tsconfig.json +16 -0
  185. package/dist/content/examples/performance/view.ocml +18 -0
  186. package/dist/content/examples/playervox-journal/LICENSE +21 -0
  187. package/dist/content/examples/playervox-journal/README.md +150 -0
  188. package/dist/content/examples/playervox-journal/listing.json +19 -0
  189. package/dist/content/examples/playervox-journal/locales/en.json +25 -0
  190. package/dist/content/examples/playervox-journal/locales/fr.json +25 -0
  191. package/dist/content/examples/playervox-journal/logic.ts +556 -0
  192. package/dist/content/examples/playervox-journal/manifest.json +19 -0
  193. package/dist/content/examples/playervox-journal/package.json +16 -0
  194. package/dist/content/examples/playervox-journal/style.ocss +142 -0
  195. package/dist/content/examples/playervox-journal/tests/changes.scenario.json +482 -0
  196. package/dist/content/examples/playervox-journal/tests/delete.scenario.json +432 -0
  197. package/dist/content/examples/playervox-journal/tests/frame.scenario.json +169 -0
  198. package/dist/content/examples/playervox-journal/tests/load-logic.mjs +22 -0
  199. package/dist/content/examples/playervox-journal/tests/logic.test.mjs +349 -0
  200. package/dist/content/examples/playervox-journal/tests/paging.scenario.json +428 -0
  201. package/dist/content/examples/playervox-journal/tests/preview.scenario.json +184 -0
  202. package/dist/content/examples/playervox-journal/tests/read-only.scenario.json +157 -0
  203. package/dist/content/examples/playervox-journal/tests/refused.scenario.json +39 -0
  204. package/dist/content/examples/playervox-journal/tests/scale.scenario.json +286 -0
  205. package/dist/content/examples/playervox-journal/tests/signed-out.scenario.json +134 -0
  206. package/dist/content/examples/playervox-journal/tests/states.scenario.json +741 -0
  207. package/dist/content/examples/playervox-journal/tests/unavailable.scenario.json +183 -0
  208. package/dist/content/examples/playervox-journal/tsconfig.json +16 -0
  209. package/dist/content/examples/playervox-journal/view.ocml +52 -0
  210. package/dist/content/examples/playervox-rating/LICENSE +21 -0
  211. package/dist/content/examples/playervox-rating/README.md +166 -0
  212. package/dist/content/examples/playervox-rating/listing.json +19 -0
  213. package/dist/content/examples/playervox-rating/locales/en.json +35 -0
  214. package/dist/content/examples/playervox-rating/locales/fr.json +35 -0
  215. package/dist/content/examples/playervox-rating/logic.ts +581 -0
  216. package/dist/content/examples/playervox-rating/manifest.json +19 -0
  217. package/dist/content/examples/playervox-rating/package.json +16 -0
  218. package/dist/content/examples/playervox-rating/style.ocss +333 -0
  219. package/dist/content/examples/playervox-rating/tests/account.scenario.json +125 -0
  220. package/dist/content/examples/playervox-rating/tests/bounds.scenario.json +73 -0
  221. package/dist/content/examples/playervox-rating/tests/draft.scenario.json +199 -0
  222. package/dist/content/examples/playervox-rating/tests/form.scenario.json +142 -0
  223. package/dist/content/examples/playervox-rating/tests/load-logic.mjs +22 -0
  224. package/dist/content/examples/playervox-rating/tests/logic.test.mjs +346 -0
  225. package/dist/content/examples/playervox-rating/tests/offline.scenario.json +142 -0
  226. package/dist/content/examples/playervox-rating/tests/preview.scenario.json +44 -0
  227. package/dist/content/examples/playervox-rating/tests/publish-keyboard.scenario.json +221 -0
  228. package/dist/content/examples/playervox-rating/tests/publish-pointer.scenario.json +314 -0
  229. package/dist/content/examples/playervox-rating/tests/read-only.scenario.json +70 -0
  230. package/dist/content/examples/playervox-rating/tests/refused.scenario.json +45 -0
  231. package/dist/content/examples/playervox-rating/tests/states.scenario.json +362 -0
  232. package/dist/content/examples/playervox-rating/tsconfig.json +16 -0
  233. package/dist/content/examples/playervox-rating/view.ocml +62 -0
  234. package/dist/content/examples/playervox-reviews/LICENSE +21 -0
  235. package/dist/content/examples/playervox-reviews/README.md +194 -0
  236. package/dist/content/examples/playervox-reviews/listing.json +19 -0
  237. package/dist/content/examples/playervox-reviews/locales/en.json +32 -0
  238. package/dist/content/examples/playervox-reviews/locales/fr.json +32 -0
  239. package/dist/content/examples/playervox-reviews/logic.ts +822 -0
  240. package/dist/content/examples/playervox-reviews/manifest.json +30 -0
  241. package/dist/content/examples/playervox-reviews/package.json +16 -0
  242. package/dist/content/examples/playervox-reviews/style.ocss +260 -0
  243. package/dist/content/examples/playervox-reviews/tests/account.scenario.json +249 -0
  244. package/dist/content/examples/playervox-reviews/tests/bounds.scenario.json +336 -0
  245. package/dist/content/examples/playervox-reviews/tests/changes.scenario.json +515 -0
  246. package/dist/content/examples/playervox-reviews/tests/filter.scenario.json +400 -0
  247. package/dist/content/examples/playervox-reviews/tests/frame.scenario.json +185 -0
  248. package/dist/content/examples/playervox-reviews/tests/keyboard.scenario.json +188 -0
  249. package/dist/content/examples/playervox-reviews/tests/load-logic.mjs +22 -0
  250. package/dist/content/examples/playervox-reviews/tests/logic.test.mjs +559 -0
  251. package/dist/content/examples/playervox-reviews/tests/offline-start.scenario.json +152 -0
  252. package/dist/content/examples/playervox-reviews/tests/offline.scenario.json +311 -0
  253. package/dist/content/examples/playervox-reviews/tests/paging.scenario.json +497 -0
  254. package/dist/content/examples/playervox-reviews/tests/preview.scenario.json +286 -0
  255. package/dist/content/examples/playervox-reviews/tests/reading.scenario.json +591 -0
  256. package/dist/content/examples/playervox-reviews/tests/refused.scenario.json +54 -0
  257. package/dist/content/examples/playervox-reviews/tests/scale.scenario.json +303 -0
  258. package/dist/content/examples/playervox-reviews/tests/states.scenario.json +700 -0
  259. package/dist/content/examples/playervox-reviews/tests/unavailable.scenario.json +192 -0
  260. package/dist/content/examples/playervox-reviews/tsconfig.json +16 -0
  261. package/dist/content/examples/playervox-reviews/view.ocml +110 -0
  262. package/dist/content/examples/playervox-score/LICENSE +21 -0
  263. package/dist/content/examples/playervox-score/README.md +131 -0
  264. package/dist/content/examples/playervox-score/listing.json +19 -0
  265. package/dist/content/examples/playervox-score/locales/en.json +23 -0
  266. package/dist/content/examples/playervox-score/locales/fr.json +23 -0
  267. package/dist/content/examples/playervox-score/logic.ts +430 -0
  268. package/dist/content/examples/playervox-score/manifest.json +47 -0
  269. package/dist/content/examples/playervox-score/package.json +16 -0
  270. package/dist/content/examples/playervox-score/style.ocss +247 -0
  271. package/dist/content/examples/playervox-score/tests/grades.scenario.json +279 -0
  272. package/dist/content/examples/playervox-score/tests/load-logic.mjs +22 -0
  273. package/dist/content/examples/playervox-score/tests/logic.test.mjs +229 -0
  274. package/dist/content/examples/playervox-score/tests/options.scenario.json +302 -0
  275. package/dist/content/examples/playervox-score/tests/preview.scenario.json +68 -0
  276. package/dist/content/examples/playervox-score/tests/pulse.scenario.json +157 -0
  277. package/dist/content/examples/playervox-score/tests/refused.scenario.json +21 -0
  278. package/dist/content/examples/playervox-score/tests/signed-out.scenario.json +46 -0
  279. package/dist/content/examples/playervox-score/tests/states.scenario.json +450 -0
  280. package/dist/content/examples/playervox-score/tests/unavailable.scenario.json +84 -0
  281. package/dist/content/examples/playervox-score/tsconfig.json +16 -0
  282. package/dist/content/examples/playervox-score/view.ocml +51 -0
  283. package/dist/content/examples/session/LICENSE +21 -0
  284. package/dist/content/examples/session/README.md +57 -0
  285. package/dist/content/examples/session/listing.json +19 -0
  286. package/dist/content/examples/session/locales/en.json +3 -0
  287. package/dist/content/examples/session/locales/fr.json +3 -0
  288. package/dist/content/examples/session/logic.ts +26 -0
  289. package/dist/content/examples/session/manifest.json +19 -0
  290. package/dist/content/examples/session/package.json +16 -0
  291. package/dist/content/examples/session/style.ocss +16 -0
  292. package/dist/content/examples/session/tests/hidden.scenario.json +45 -0
  293. package/dist/content/examples/session/tests/load-logic.mjs +22 -0
  294. package/dist/content/examples/session/tests/logic.test.mjs +37 -0
  295. package/dist/content/examples/session/tests/preview.scenario.json +28 -0
  296. package/dist/content/examples/session/tests/refused.scenario.json +20 -0
  297. package/dist/content/examples/session/tests/refused.test.mjs +16 -0
  298. package/dist/content/examples/session/tests/render.scenario.json +95 -0
  299. package/dist/content/examples/session/tests/ticks.scenario.json +139 -0
  300. package/dist/content/examples/session/tests/unknown.scenario.json +37 -0
  301. package/dist/content/examples/session/tsconfig.json +16 -0
  302. package/dist/content/examples/session/view.ocml +11 -0
  303. package/dist/content/examples/stopwatch/LICENSE +21 -0
  304. package/dist/content/examples/stopwatch/README.md +99 -0
  305. package/dist/content/examples/stopwatch/listing.json +19 -0
  306. package/dist/content/examples/stopwatch/locales/en.json +8 -0
  307. package/dist/content/examples/stopwatch/locales/fr.json +8 -0
  308. package/dist/content/examples/stopwatch/logic.ts +93 -0
  309. package/dist/content/examples/stopwatch/manifest.json +19 -0
  310. package/dist/content/examples/stopwatch/package.json +16 -0
  311. package/dist/content/examples/stopwatch/style.ocss +54 -0
  312. package/dist/content/examples/stopwatch/tests/errors.scenario.json +121 -0
  313. package/dist/content/examples/stopwatch/tests/gesture.scenario.json +154 -0
  314. package/dist/content/examples/stopwatch/tests/hidden.scenario.json +57 -0
  315. package/dist/content/examples/stopwatch/tests/hundredths.scenario.json +173 -0
  316. package/dist/content/examples/stopwatch/tests/load-logic.mjs +22 -0
  317. package/dist/content/examples/stopwatch/tests/logic.test.mjs +99 -0
  318. package/dist/content/examples/stopwatch/tests/preview.scenario.json +36 -0
  319. package/dist/content/examples/stopwatch/tests/read-only.scenario.json +42 -0
  320. package/dist/content/examples/stopwatch/tests/read-only.test.mjs +18 -0
  321. package/dist/content/examples/stopwatch/tests/refused.scenario.json +22 -0
  322. package/dist/content/examples/stopwatch/tests/refused.test.mjs +18 -0
  323. package/dist/content/examples/stopwatch/tests/render.scenario.json +105 -0
  324. package/dist/content/examples/stopwatch/tests/states.scenario.json +246 -0
  325. package/dist/content/examples/stopwatch/tests/unknown.scenario.json +40 -0
  326. package/dist/content/examples/stopwatch/tsconfig.json +16 -0
  327. package/dist/content/examples/stopwatch/view.ocml +29 -0
  328. package/dist/content/examples/twitch-chat/LICENSE +21 -0
  329. package/dist/content/examples/twitch-chat/README.md +283 -0
  330. package/dist/content/examples/twitch-chat/listing.json +19 -0
  331. package/dist/content/examples/twitch-chat/locales/en.json +52 -0
  332. package/dist/content/examples/twitch-chat/locales/fr.json +52 -0
  333. package/dist/content/examples/twitch-chat/logic.ts +887 -0
  334. package/dist/content/examples/twitch-chat/manifest.json +33 -0
  335. package/dist/content/examples/twitch-chat/package.json +16 -0
  336. package/dist/content/examples/twitch-chat/style.ocss +544 -0
  337. package/dist/content/examples/twitch-chat/tests/account.scenario.json +325 -0
  338. package/dist/content/examples/twitch-chat/tests/burst.scenario.json +1866 -0
  339. package/dist/content/examples/twitch-chat/tests/channel-refused.scenario.json +372 -0
  340. package/dist/content/examples/twitch-chat/tests/channel.scenario.json +480 -0
  341. package/dist/content/examples/twitch-chat/tests/draft.scenario.json +279 -0
  342. package/dist/content/examples/twitch-chat/tests/fade.scenario.json +269 -0
  343. package/dist/content/examples/twitch-chat/tests/failures.scenario.json +420 -0
  344. package/dist/content/examples/twitch-chat/tests/favorites.scenario.json +376 -0
  345. package/dist/content/examples/twitch-chat/tests/frame.scenario.json +152 -0
  346. package/dist/content/examples/twitch-chat/tests/lifetime.scenario.json +186 -0
  347. package/dist/content/examples/twitch-chat/tests/load-logic.mjs +22 -0
  348. package/dist/content/examples/twitch-chat/tests/logic.test.mjs +1053 -0
  349. package/dist/content/examples/twitch-chat/tests/passive.scenario.json +536 -0
  350. package/dist/content/examples/twitch-chat/tests/preview.scenario.json +147 -0
  351. package/dist/content/examples/twitch-chat/tests/read-only.scenario.json +202 -0
  352. package/dist/content/examples/twitch-chat/tests/read-only.test.mjs +63 -0
  353. package/dist/content/examples/twitch-chat/tests/refused.scenario.json +65 -0
  354. package/dist/content/examples/twitch-chat/tests/refused.test.mjs +43 -0
  355. package/dist/content/examples/twitch-chat/tests/render.scenario.json +347 -0
  356. package/dist/content/examples/twitch-chat/tests/reply.scenario.json +384 -0
  357. package/dist/content/examples/twitch-chat/tests/send-refused.scenario.json +383 -0
  358. package/dist/content/examples/twitch-chat/tests/send.scenario.json +300 -0
  359. package/dist/content/examples/twitch-chat/tests/states.scenario.json +455 -0
  360. package/dist/content/examples/twitch-chat/tests/unavailable.scenario.json +256 -0
  361. package/dist/content/examples/twitch-chat/tests/unread.scenario.json +997 -0
  362. package/dist/content/examples/twitch-chat/tsconfig.json +16 -0
  363. package/dist/content/examples/twitch-chat/view.ocml +141 -0
  364. package/dist/content/examples/warframe-market/LICENSE +21 -0
  365. package/dist/content/examples/warframe-market/README.md +176 -0
  366. package/dist/content/examples/warframe-market/listing.json +19 -0
  367. package/dist/content/examples/warframe-market/locales/en.json +44 -0
  368. package/dist/content/examples/warframe-market/locales/fr.json +44 -0
  369. package/dist/content/examples/warframe-market/logic.ts +1265 -0
  370. package/dist/content/examples/warframe-market/manifest.json +92 -0
  371. package/dist/content/examples/warframe-market/package.json +16 -0
  372. package/dist/content/examples/warframe-market/style.ocss +422 -0
  373. package/dist/content/examples/warframe-market/tests/bounds.scenario.json +60 -0
  374. package/dist/content/examples/warframe-market/tests/cached.scenario.json +70 -0
  375. package/dist/content/examples/warframe-market/tests/detail.scenario.json +251 -0
  376. package/dist/content/examples/warframe-market/tests/errors.scenario.json +105 -0
  377. package/dist/content/examples/warframe-market/tests/fixtures.json +295 -0
  378. package/dist/content/examples/warframe-market/tests/load-logic.mjs +22 -0
  379. package/dist/content/examples/warframe-market/tests/logic.test.mjs +505 -0
  380. package/dist/content/examples/warframe-market/tests/no-clipboard.scenario.json +104 -0
  381. package/dist/content/examples/warframe-market/tests/passive.scenario.json +87 -0
  382. package/dist/content/examples/warframe-market/tests/preview.scenario.json +146 -0
  383. package/dist/content/examples/warframe-market/tests/refresh.scenario.json +56 -0
  384. package/dist/content/examples/warframe-market/tests/refused.scenario.json +74 -0
  385. package/dist/content/examples/warframe-market/tests/render.scenario.json +210 -0
  386. package/dist/content/examples/warframe-market/tests/search.scenario.json +189 -0
  387. package/dist/content/examples/warframe-market/tests/unavailable.scenario.json +54 -0
  388. package/dist/content/examples/warframe-market/tsconfig.json +16 -0
  389. package/dist/content/examples/warframe-market/view.ocml +116 -0
  390. package/dist/content/examples/weather/LICENSE +20 -0
  391. package/dist/content/examples/weather/listing.json +10 -0
  392. package/dist/content/examples/weather/locales/en.json +9 -0
  393. package/dist/content/examples/weather/locales/fr.json +9 -0
  394. package/dist/content/examples/weather/logic.ts +62 -0
  395. package/dist/content/examples/weather/manifest.json +51 -0
  396. package/dist/content/examples/weather/package.json +15 -0
  397. package/dist/content/examples/weather/style.ocss +29 -0
  398. package/dist/content/examples/weather/tsconfig.json +16 -0
  399. package/dist/content/examples/weather/view.ocml +12 -0
  400. package/dist/content/index.json +3601 -0
  401. package/dist/content/reference/errors.json +1712 -0
  402. package/dist/content/reference/limits.json +1154 -0
  403. package/dist/content/reference/manifest.md +223 -0
  404. package/dist/content/reference/permissions.json +413 -0
  405. package/dist/content/templates/blank/locales/en.json +3 -0
  406. package/dist/content/templates/blank/locales/fr.json +3 -0
  407. package/dist/content/templates/blank/logic.ts +7 -0
  408. package/dist/content/templates/blank/manifest.json +16 -0
  409. package/dist/content/templates/blank/style.ocss +10 -0
  410. package/dist/content/templates/blank/tests/example.scenario.json +10 -0
  411. package/dist/content/templates/blank/view.ocml +6 -0
  412. package/dist/content/templates/chart/locales/en.json +3 -0
  413. package/dist/content/templates/chart/locales/fr.json +3 -0
  414. package/dist/content/templates/chart/logic.ts +27 -0
  415. package/dist/content/templates/chart/manifest.json +16 -0
  416. package/dist/content/templates/chart/style.ocss +27 -0
  417. package/dist/content/templates/chart/tests/example.scenario.json +10 -0
  418. package/dist/content/templates/chart/view.ocml +10 -0
  419. package/dist/content/templates/counter/locales/en.json +4 -0
  420. package/dist/content/templates/counter/locales/fr.json +4 -0
  421. package/dist/content/templates/counter/logic.ts +20 -0
  422. package/dist/content/templates/counter/manifest.json +16 -0
  423. package/dist/content/templates/counter/style.ocss +26 -0
  424. package/dist/content/templates/counter/tests/example.scenario.json +11 -0
  425. package/dist/content/templates/counter/view.ocml +13 -0
  426. package/dist/content/templates/list/locales/en.json +8 -0
  427. package/dist/content/templates/list/locales/fr.json +8 -0
  428. package/dist/content/templates/list/logic.ts +35 -0
  429. package/dist/content/templates/list/manifest.json +16 -0
  430. package/dist/content/templates/list/style.ocss +42 -0
  431. package/dist/content/templates/list/tests/example.scenario.json +10 -0
  432. package/dist/content/templates/list/view.ocml +17 -0
  433. package/dist/content/templates/shared/LICENSE +20 -0
  434. package/dist/content/templates/shared/package.json +14 -0
  435. package/dist/content/templates/shared/tsconfig.json +16 -0
  436. package/dist/content.js +68 -0
  437. package/dist/env.js +156 -0
  438. package/dist/fingerprint.js +32 -0
  439. package/dist/ids.js +27 -0
  440. package/dist/index.js +82 -0
  441. package/dist/knowledge.js +222 -0
  442. package/dist/npm.js +149 -0
  443. package/dist/prompts.js +47 -0
  444. package/dist/resources.js +82 -0
  445. package/dist/run.js +120 -0
  446. package/dist/server.js +43 -0
  447. package/dist/session.js +205 -0
  448. package/dist/sources.js +218 -0
  449. package/dist/text.js +80 -0
  450. package/dist/texts.js +44 -0
  451. package/dist/tools/audit.js +105 -0
  452. package/dist/tools/build.js +453 -0
  453. package/dist/tools/common.js +122 -0
  454. package/dist/tools/docs.js +196 -0
  455. package/dist/tools/preview.js +87 -0
  456. package/dist/tools/project.js +178 -0
  457. package/dist/tools/setup.js +146 -0
  458. package/dist/tools/submission.js +369 -0
  459. package/dist/version.js +3 -0
  460. package/npm-shrinkwrap.json +402 -0
  461. package/package.json +50 -4
@@ -0,0 +1,122 @@
1
+ // Helpers shared by the tools: results with structured content and a text
2
+ // copy, errors the model can act on, the confinement of each request, and
3
+ // project data marked as data.
4
+ import { CliError } from "../cli.js";
5
+ import { lstat } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { ConfinementError } from "../confine.js";
8
+ import { NpmError } from "../npm.js";
9
+ import { RunError } from "../run.js";
10
+ import { SetupError } from "../session.js";
11
+ import { bound, DATA_NOTICE } from "../text.js";
12
+ /** Most characters a text result carries; Claude Code moves larger results to a file. */
13
+ export const MAX_RESULT_CHARS = 40_000;
14
+ /**
15
+ * A successful result: a one-line summary for people, then the structured
16
+ * content as JSON (clients that ignore `structuredContent` still get it).
17
+ * Results that carry project content are marked as data.
18
+ */
19
+ export function ok(structured, summary, options = {}) {
20
+ const redact = (text) => (options.redactor ? options.redactor.text(text) : text);
21
+ const structuredContent = options.redactor
22
+ ? redactDeep(structured, options.redactor)
23
+ : structured;
24
+ const json = JSON.stringify(structuredContent);
25
+ const body = options.data
26
+ ? `${DATA_NOTICE}\n<data>\n${json.replaceAll("</data>", "<\\/data>")}\n</data>`
27
+ : json;
28
+ return {
29
+ content: [
30
+ { type: "text", text: bound(`${redact(summary)}\n\n${body}`, MAX_RESULT_CHARS) },
31
+ ...(options.extra ?? []),
32
+ ],
33
+ structuredContent,
34
+ };
35
+ }
36
+ /** Replaces the user's paths in every string of a JSON value. */
37
+ function redactDeep(value, redactor) {
38
+ if (typeof value === "string")
39
+ return redactor.text(value);
40
+ if (Array.isArray(value))
41
+ return value.map((item) => redactDeep(item, redactor));
42
+ if (typeof value === "object" && value !== null) {
43
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactDeep(item, redactor)]));
44
+ }
45
+ return value;
46
+ }
47
+ /** A failure the model can correct: what went wrong and what to do. */
48
+ export function fail(message, redactor) {
49
+ return {
50
+ content: [{ type: "text", text: bound(redactor ? redactor.text(message) : message, 4000) }],
51
+ isError: true,
52
+ };
53
+ }
54
+ /** Turns the expected errors into tool failures; anything else is rethrown. */
55
+ export function failure(error, redactor) {
56
+ if (error instanceof ConfinementError ||
57
+ error instanceof SetupError ||
58
+ error instanceof NpmError ||
59
+ error instanceof RunError) {
60
+ return fail(error.message, redactor);
61
+ }
62
+ if (error instanceof CliError) {
63
+ const detail = error.stderr
64
+ .split("\n")
65
+ .filter((line) => line.trim() !== "")
66
+ .slice(0, 8)
67
+ .join("\n");
68
+ return fail(`${error.message}${detail ? `\n${DATA_NOTICE}\n${detail}` : ""}`, redactor);
69
+ }
70
+ throw error;
71
+ }
72
+ export const TOOLS_MISSING = "The creator tools (the widget CLI and its test runtime) are not installed on this computer yet: run the setup tool first. It downloads them once (about 15 MB) and checks them.";
73
+ /**
74
+ * Runs `handler` with the project folders of this request. When the
75
+ * client's roots are needed and not known yet, returns the input request
76
+ * that asks for them; the client then retries the call.
77
+ */
78
+ export async function withRoots(env, ctx, handler) {
79
+ const answer = await env.session.roots(ctx, env.server);
80
+ if ("input" in answer)
81
+ return answer.input;
82
+ if ("error" in answer)
83
+ return fail(answer.error);
84
+ const redactor = env.session.redactor(answer.confinement);
85
+ try {
86
+ return await handler(answer.confinement, redactor);
87
+ }
88
+ catch (error) {
89
+ return failure(error, redactor);
90
+ }
91
+ }
92
+ /** Sends a progress notification when the client asked for them. */
93
+ export function progress(ctx) {
94
+ const token = ctx.mcpReq._meta
95
+ ?.progressToken;
96
+ if (token === undefined)
97
+ return undefined;
98
+ return (done, total, message) => {
99
+ ctx.mcpReq
100
+ .notify({
101
+ method: "notifications/progress",
102
+ params: { progressToken: token, progress: done, total, ...(message ? { message } : {}) },
103
+ })
104
+ .catch(() => undefined);
105
+ };
106
+ }
107
+ /** Annotations, spelled out for every tool (no default is relied on). */
108
+ export const READ_ONLY = {
109
+ readOnlyHint: true,
110
+ destructiveHint: false,
111
+ idempotentHint: true,
112
+ openWorldHint: false,
113
+ };
114
+ /** A widget folder inside the project: it must hold a manifest.json. */
115
+ export async function widgetDirectory(confinement, directory) {
116
+ const target = await confinement.resolve(directory, "directory");
117
+ const manifest = await lstat(join(target, "manifest.json")).catch(() => undefined);
118
+ if (!manifest?.isFile()) {
119
+ throw new ConfinementError(`${directory} is not a widget folder: it has no manifest.json.`);
120
+ }
121
+ return target;
122
+ }
@@ -0,0 +1,196 @@
1
+ // explain_permission, explain_error, search_docs, read_doc and read_example:
2
+ // answers from the documentation and examples shipped in the package.
3
+ import { z } from "zod";
4
+ import { docPages, projectFile, projects } from "../content.js";
5
+ import { explainError, explainPermission, permissionNames, readDoc, searchDocs, similarCodes, } from "../knowledge.js";
6
+ import { bound } from "../text.js";
7
+ import { DESCRIPTIONS } from "../texts.js";
8
+ import { fail, MAX_RESULT_CHARS, ok, READ_ONLY } from "./common.js";
9
+ const LOCALE = z
10
+ .enum(["en", "fr"])
11
+ .default("en")
12
+ .describe("Language of the documentation: en or fr.");
13
+ export function registerDocTools(env) {
14
+ const { server } = env;
15
+ server.registerTool("explain_permission", {
16
+ title: "Explain a permission",
17
+ description: DESCRIPTIONS.explain_permission,
18
+ inputSchema: z.strictObject({
19
+ name: z
20
+ .string()
21
+ .min(1)
22
+ .max(64)
23
+ .describe("network, storage, clipboardWrite, gameEvents, or a capability such as media.read."),
24
+ locale: LOCALE,
25
+ }),
26
+ outputSchema: z.object({
27
+ name: z.string(),
28
+ kind: z.enum(["permission", "capability"]),
29
+ meaning: z.string(),
30
+ withSensitiveCapability: z.string().nullable(),
31
+ services: z.array(z.object({ service: z.string(), when: z.string() })),
32
+ sensitive: z.boolean().nullable(),
33
+ account: z.string().nullable(),
34
+ advice: z.string(),
35
+ docs: z.string(),
36
+ }),
37
+ annotations: { title: "Explain a permission", ...READ_ONLY },
38
+ }, async ({ name, locale }) => {
39
+ const info = explainPermission(name, locale);
40
+ if (!info)
41
+ return fail(`Unknown permission or capability: ${name.slice(0, 64)}. Known: ${permissionNames().join(", ")}.`);
42
+ return ok({ ...info }, `${info.name}: ${info.meaning}`);
43
+ });
44
+ server.registerTool("explain_error", {
45
+ title: "Explain an error code",
46
+ description: DESCRIPTIONS.explain_error,
47
+ inputSchema: z.strictObject({
48
+ code: z
49
+ .string()
50
+ .regex(/^[a-z_]+(\.[a-z_]+)?$/)
51
+ .max(64)
52
+ .describe("A diagnostic code (view.unknown_attribute) or a service error code (permission_denied)."),
53
+ locale: LOCALE,
54
+ }),
55
+ outputSchema: z.object({
56
+ code: z.string(),
57
+ kind: z.enum(["diagnostic", "service", "domain"]),
58
+ meaning: z.string(),
59
+ page: z.string(),
60
+ url: z.string(),
61
+ }),
62
+ annotations: { title: "Explain an error code", ...READ_ONLY },
63
+ }, async ({ code, locale }) => {
64
+ const info = explainError(code, locale);
65
+ if (!info) {
66
+ const similar = similarCodes(code);
67
+ return fail(`Unknown code ${code}.${similar.length ? ` Close codes: ${similar.join(", ")}.` : " Use search_docs."}`);
68
+ }
69
+ return ok({ ...info }, `${info.code}: ${info.meaning}`);
70
+ });
71
+ server.registerTool("search_docs", {
72
+ title: "Search the documentation",
73
+ description: DESCRIPTIONS.search_docs,
74
+ inputSchema: z.strictObject({
75
+ query: z.string().min(1).max(200),
76
+ locale: LOCALE,
77
+ limit: z.number().int().min(1).max(10).default(5),
78
+ }),
79
+ outputSchema: z.object({
80
+ results: z.array(z.object({
81
+ slug: z.string(),
82
+ page: z.string(),
83
+ section: z.string(),
84
+ url: z.string(),
85
+ resource: z.string(),
86
+ snippet: z.string(),
87
+ score: z.number(),
88
+ })),
89
+ }),
90
+ annotations: { title: "Search the documentation", ...READ_ONLY },
91
+ }, async ({ query, locale, limit }) => {
92
+ const results = searchDocs(query, locale, limit);
93
+ return ok({ results }, results.length === 0
94
+ ? "No match: try other words, or read_doc with a page slug."
95
+ : `${results.length} section(s) found; read one with read_doc.`);
96
+ });
97
+ server.registerTool("read_doc", {
98
+ title: "Read the documentation",
99
+ description: DESCRIPTIONS.read_doc,
100
+ inputSchema: z.strictObject({
101
+ slug: z
102
+ .string()
103
+ .max(64)
104
+ .describe('Page slug, such as "guide", "manifest", "services", "limits"; "index" for the overview.'),
105
+ section: z
106
+ .string()
107
+ .max(200)
108
+ .optional()
109
+ .describe("A heading of the page, to read only that section."),
110
+ locale: LOCALE,
111
+ }),
112
+ outputSchema: z.object({
113
+ slug: z.string(),
114
+ title: z.string(),
115
+ url: z.string(),
116
+ headings: z.array(z.string()),
117
+ markdown: z.string().nullable(),
118
+ pages: z.array(z.object({ slug: z.string(), title: z.string(), group: z.string() })),
119
+ }),
120
+ annotations: { title: "Read the documentation", ...READ_ONLY },
121
+ }, async ({ slug, section, locale }) => {
122
+ const pageSlug = slug === "index" ? "" : slug;
123
+ const pages = docPages().map((page) => ({
124
+ slug: page.slug || "index",
125
+ title: page.titles[locale] ?? page.slug,
126
+ group: page.group,
127
+ }));
128
+ const doc = readDoc(pageSlug, locale, section);
129
+ if (!doc)
130
+ return fail(`No page ${slug.slice(0, 64)}. Pages: ${pages.map((page) => page.slug).join(", ")}.`);
131
+ const markdown = doc.markdown === null ? null : bound(doc.markdown, MAX_RESULT_CHARS - 4000);
132
+ return ok({ slug, title: doc.title, url: doc.url, headings: doc.headings, markdown, pages }, doc.markdown === null
133
+ ? `No section "${section}" in ${doc.title}: see headings.`
134
+ : `${doc.title} (${doc.url})`);
135
+ });
136
+ server.registerTool("read_example", {
137
+ title: "Read an example",
138
+ description: DESCRIPTIONS.read_example,
139
+ inputSchema: z.strictObject({
140
+ name: z
141
+ .string()
142
+ .max(64)
143
+ .optional()
144
+ .describe("A reference widget (warframe-market, clock…), a template (counter…) or a documentation example (weather, countdown)."),
145
+ file: z
146
+ .string()
147
+ .max(256)
148
+ .optional()
149
+ .describe("A file of that project, such as logic.ts or manifest.json."),
150
+ }),
151
+ outputSchema: z.object({
152
+ projects: z.array(z.object({
153
+ name: z.string(),
154
+ kind: z.string(),
155
+ title: z.string().nullable(),
156
+ id: z.string().nullable(),
157
+ })),
158
+ files: z.array(z.string()),
159
+ file: z.string().nullable(),
160
+ text: z.string().nullable(),
161
+ resource: z.string().nullable(),
162
+ }),
163
+ annotations: { title: "Read an example", ...READ_ONLY },
164
+ }, async ({ name, file }) => {
165
+ const all = projects().filter((project) => project.name !== "shared");
166
+ const list = all.map((project) => ({
167
+ name: project.name,
168
+ kind: project.kind === "templates"
169
+ ? "template"
170
+ : project.source === "docs"
171
+ ? "documentation example"
172
+ : "reference widget",
173
+ title: project.title ?? null,
174
+ id: project.id ?? null,
175
+ }));
176
+ if (!name) {
177
+ return ok({ projects: list, files: [], file: null, text: null, resource: null }, `${list.length} projects; read one with name.`);
178
+ }
179
+ const project = all.find((candidate) => candidate.name === name);
180
+ if (!project)
181
+ return fail(`No example named ${name}. Names: ${all.map((p) => p.name).join(", ")}.`);
182
+ if (!file) {
183
+ return ok({ projects: [], files: project.files, file: null, text: null, resource: null }, `${name}: ${project.files.length} files${project.images.length ? ` (and ${project.images.length} images, not included)` : ""}.`);
184
+ }
185
+ const text = projectFile(project.kind, project.name, file);
186
+ if (text === undefined)
187
+ return fail(`${name} has no text file ${file.slice(0, 256)}. Files: ${project.files.join(", ")}.`);
188
+ return ok({
189
+ projects: [],
190
+ files: [],
191
+ file,
192
+ text: bound(text, MAX_RESULT_CHARS - 4000),
193
+ resource: `overcrow://${project.kind}/${project.name}/${file}`,
194
+ }, `${name}/${file}`, { data: true });
195
+ });
196
+ }
@@ -0,0 +1,87 @@
1
+ // use_preview: the marketplace preview of a widget is a PNG under assets/,
2
+ // usually rendered by a test scenario at 150 %. An assistant's file tools
3
+ // write text, not images: this copies one reference image into
4
+ // assets/preview.png, inside the widget, and never overwrites a file.
5
+ import { constants } from "node:fs";
6
+ import { copyFile, lstat, mkdir, open } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { z } from "zod";
9
+ import { DESCRIPTIONS } from "../texts.js";
10
+ import { fail, ok, widgetDirectory, withRoots } from "./common.js";
11
+ const NAME = z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
12
+ const MAX_PREVIEW_BYTES = 256 * 1024;
13
+ async function pngSize(path) {
14
+ const handle = await open(path, "r");
15
+ try {
16
+ const header = Buffer.alloc(24);
17
+ const { bytesRead } = await handle.read(header, 0, 24, 0);
18
+ if (bytesRead < 24 ||
19
+ header.readUInt32BE(0) !== 0x89504e47 ||
20
+ header.toString("latin1", 12, 16) !== "IHDR") {
21
+ return null;
22
+ }
23
+ return { width: header.readUInt32BE(16), height: header.readUInt32BE(20) };
24
+ }
25
+ finally {
26
+ await handle.close();
27
+ }
28
+ }
29
+ export function registerPreviewTool(env) {
30
+ env.server.registerTool("use_preview", {
31
+ title: "Use an image as the preview",
32
+ description: DESCRIPTIONS.use_preview,
33
+ inputSchema: z.strictObject({
34
+ directory: z
35
+ .string()
36
+ .min(1)
37
+ .max(1024)
38
+ .describe("The widget folder, relative to the project folder."),
39
+ scenario: NAME.describe("The scenario whose reference image to use (tests/reference/<scenario>/)."),
40
+ image: NAME.describe("The image name, without .png."),
41
+ }),
42
+ outputSchema: z.object({
43
+ directory: z.string(),
44
+ preview: z.string(),
45
+ bytes: z.number(),
46
+ width: z.number(),
47
+ height: z.number(),
48
+ fourByThree: z.boolean(),
49
+ next: z.string(),
50
+ }),
51
+ annotations: {
52
+ title: "Use an image as the preview",
53
+ readOnlyHint: false,
54
+ destructiveHint: false,
55
+ idempotentHint: false,
56
+ openWorldHint: false,
57
+ },
58
+ }, async ({ directory, scenario, image }, ctx) => withRoots(env, ctx, async (confinement, redactor) => {
59
+ const target = await widgetDirectory(confinement, directory);
60
+ const source = await confinement.resolve(join(target, "tests", "reference", scenario, `${image}.png`), "file");
61
+ const info = await lstat(source);
62
+ if (info.size > MAX_PREVIEW_BYTES) {
63
+ return fail(`That image is ${Math.round(info.size / 1024)} KiB: a preview may be 256 KiB at most.`);
64
+ }
65
+ const size = await pngSize(source);
66
+ if (!size)
67
+ return fail("That file is not a PNG image.");
68
+ // The destination folder must stay inside the project (no link out).
69
+ const assets = await confinement.resolve(join(target, "assets"), "new-directory");
70
+ const destination = join(assets, "preview.png");
71
+ if (await lstat(destination).catch(() => undefined)) {
72
+ return fail("assets/preview.png already exists: this tool never replaces a file. Keep it, or delete it yourself first.");
73
+ }
74
+ await mkdir(assets, { recursive: true });
75
+ await copyFile(source, destination, constants.COPYFILE_EXCL);
76
+ const fourByThree = Math.abs(size.width * 3 - size.height * 4) <= Math.max(size.width, size.height) * 0.01;
77
+ return ok({
78
+ directory: confinement.display(target),
79
+ preview: "assets/preview.png",
80
+ bytes: info.size,
81
+ width: size.width,
82
+ height: size.height,
83
+ fourByThree,
84
+ next: `Run check, package and prepare_submission. If the widget has a listing.json, add "preview": "assets/preview.png" to it.${fourByThree ? "" : " The image is not 4:3: the catalog shows it in a 4:3 box."}`,
85
+ }, `Copied ${scenario}/${image}.png to assets/preview.png (${size.width}×${size.height}, ${Math.round(info.size / 1024)} KiB).`, { redactor });
86
+ }));
87
+ }
@@ -0,0 +1,178 @@
1
+ // list_templates, create_widget and install_sdk: starting a widget project.
2
+ import { readdir } from "node:fs/promises";
3
+ import { join, relative, sep } from "node:path";
4
+ import { z } from "zod";
5
+ import { Cli } from "../cli.js";
6
+ import { projects } from "../content.js";
7
+ import { installPackages } from "../npm.js";
8
+ import { cleanLine } from "../text.js";
9
+ import { DESCRIPTIONS, TEMPLATE_SUMMARIES } from "../texts.js";
10
+ import { ID_FORMS, idProblem, isPlaceholderId } from "../ids.js";
11
+ import { fail, ok, READ_ONLY, TOOLS_MISSING, withRoots } from "./common.js";
12
+ const TEMPLATES = ["blank", "counter", "list", "chart"];
13
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: the point is to refuse them
14
+ const REFUSED_IN_NAME = /[\u0000-\u001f\u007f<>]/;
15
+ export function nameProblem(name) {
16
+ if (name !== name.trim() ||
17
+ name.length === 0 ||
18
+ [...name].length > 48 ||
19
+ REFUSED_IN_NAME.test(name)) {
20
+ return "The name must be 1 to 48 characters, without spaces at either end, control characters, < or >.";
21
+ }
22
+ return undefined;
23
+ }
24
+ async function listFiles(directory) {
25
+ const out = [];
26
+ const walk = async (current) => {
27
+ for (const entry of await readdir(current, { withFileTypes: true })) {
28
+ const path = join(current, entry.name);
29
+ if (entry.isDirectory())
30
+ await walk(path);
31
+ else if (entry.isFile())
32
+ out.push(relative(directory, path).split(sep).join("/"));
33
+ }
34
+ };
35
+ await walk(directory);
36
+ return out.sort();
37
+ }
38
+ export function registerProjectTools(env) {
39
+ const { server, session } = env;
40
+ server.registerTool("list_templates", {
41
+ title: "List templates",
42
+ description: DESCRIPTIONS.list_templates,
43
+ inputSchema: z.strictObject({}),
44
+ outputSchema: z.object({
45
+ templates: z.array(z.object({
46
+ name: z.string(),
47
+ summary: z.string(),
48
+ files: z.array(z.string()),
49
+ resource: z.string(),
50
+ })),
51
+ }),
52
+ annotations: { title: "List templates", ...READ_ONLY },
53
+ }, async () => {
54
+ const shared = projects("templates").find((project) => project.name === "shared")?.files ?? [];
55
+ const templates = TEMPLATES.map((name) => {
56
+ const project = projects("templates").find((candidate) => candidate.name === name);
57
+ return {
58
+ name,
59
+ summary: TEMPLATE_SUMMARIES[name] ?? "",
60
+ files: [
61
+ ...(project?.files ?? []),
62
+ ...shared.map((file) => (file === "gitignore" ? ".gitignore" : file)),
63
+ ].sort(),
64
+ resource: `overcrow://templates/${name}/logic.ts`,
65
+ };
66
+ });
67
+ return ok({ templates }, `${templates.length} templates: ${templates.map((t) => `${t.name} (${t.summary})`).join("; ")}`);
68
+ });
69
+ server.registerTool("create_widget", {
70
+ title: "Create a widget",
71
+ description: DESCRIPTIONS.create_widget,
72
+ inputSchema: z.strictObject({
73
+ directory: z
74
+ .string()
75
+ .min(1)
76
+ .max(1024)
77
+ .describe("A new folder, relative to the project folder."),
78
+ template: z.enum(TEMPLATES).default("blank"),
79
+ id: z
80
+ .string()
81
+ .min(3)
82
+ .max(128)
83
+ .describe("The widget's ID, final: <handle>.<name> with the user's publisher handle in the OverCrow creator space (such as nova.lol-timers), or a reverse domain the user can verify (such as gg.nova.lol-timers). Never com.playervox.*."),
84
+ name: z
85
+ .string()
86
+ .min(1)
87
+ .max(48)
88
+ .describe("The widget's name shown to users, 1 to 48 characters."),
89
+ }),
90
+ outputSchema: z.object({
91
+ directory: z.string(),
92
+ template: z.string(),
93
+ id: z.string(),
94
+ files: z.array(z.string()),
95
+ warnings: z.array(z.string()),
96
+ next: z.string(),
97
+ }),
98
+ annotations: {
99
+ title: "Create a widget",
100
+ readOnlyHint: false,
101
+ destructiveHint: false,
102
+ idempotentHint: false,
103
+ openWorldHint: false,
104
+ },
105
+ }, async ({ directory, template, id, name }, ctx) => withRoots(env, ctx, async (confinement, redactor) => {
106
+ const problem = idProblem(id) ?? nameProblem(name);
107
+ if (problem)
108
+ return fail(problem);
109
+ const tools = await session.installedTools();
110
+ if (!tools)
111
+ return fail(TOOLS_MISSING);
112
+ const target = await confinement.resolve(directory, "new-directory");
113
+ const existing = await readdir(target).catch(() => []);
114
+ if (existing.length > 0)
115
+ return fail(`${directory} already exists and is not empty: create_widget never overwrites files. Choose a new folder.`);
116
+ const result = await new Cli(tools).init(target, template, id, name, confinement.roots[0]?.path ?? target);
117
+ if (!result.ok) {
118
+ const reason = cleanLine(result.stderr.split("\n").find((line) => line.trim() !== "") ?? `exit ${result.code}`);
119
+ return fail(`The widget could not be created: ${reason}`, redactor);
120
+ }
121
+ const warnings = isPlaceholderId(id)
122
+ ? [`The ID ${id} is a placeholder: before submitting, replace it with ${ID_FORMS}.`]
123
+ : [];
124
+ return ok({
125
+ directory: confinement.display(target),
126
+ template,
127
+ id,
128
+ files: await listFiles(target),
129
+ warnings,
130
+ next: "Ask the user before installing packages, then run install_sdk with confirm: true, then check.",
131
+ }, `Created ${confinement.display(target)} from the ${template} template.`, { redactor });
132
+ }));
133
+ server.registerTool("install_sdk", {
134
+ title: "Install the SDK",
135
+ description: DESCRIPTIONS.install_sdk,
136
+ inputSchema: z.strictObject({
137
+ directory: z
138
+ .string()
139
+ .min(1)
140
+ .max(1024)
141
+ .describe("The widget folder, relative to the project folder."),
142
+ confirm: z
143
+ .literal(true)
144
+ .describe("Must be true: the user agreed to download the packages from npm."),
145
+ }),
146
+ outputSchema: z.object({
147
+ directory: z.string(),
148
+ installed: z.array(z.object({
149
+ name: z.string(),
150
+ version: z.string().nullable(),
151
+ integrityMatches: z.boolean(),
152
+ })),
153
+ next: z.string(),
154
+ }),
155
+ annotations: {
156
+ title: "Install the SDK",
157
+ readOnlyHint: false,
158
+ destructiveHint: false,
159
+ idempotentHint: true,
160
+ openWorldHint: true,
161
+ },
162
+ }, async ({ directory }, ctx) => withRoots(env, ctx, async (confinement, redactor) => {
163
+ const target = await confinement.resolve(directory, "directory");
164
+ const pins = session.options.pin.npm;
165
+ const cliSdk = session.cliSdkVersion;
166
+ if (cliSdk && cliSdk !== pins.sdk.version) {
167
+ return fail(`The installed CLI uses @overcrow/sdk ${cliSdk} but this server pins ${pins.sdk.version}: update @overcrow/mcp.`);
168
+ }
169
+ const checks = await installPackages(target, pins, ctx.mcpReq.signal);
170
+ // Exactly the advertised fields: clients check the output schema strictly.
171
+ const installed = checks.map(({ name, version, integrityMatches }) => ({
172
+ name,
173
+ version,
174
+ integrityMatches,
175
+ }));
176
+ return ok({ directory: confinement.display(target), installed, next: "Run check." }, `Installed @overcrow/sdk ${pins.sdk.version} and TypeScript ${pins.typescript.version}; their integrity matches the published packages.`, { redactor });
177
+ }));
178
+ }