@jini-ai/agent-plugins 0.6.16 → 0.6.19

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 (335) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/NOTICE +14 -0
  3. package/README.md +35 -14
  4. package/charts-echarts/plugin.json +39 -0
  5. package/charts-echarts/skills/charts-echarts/SKILL.md +110 -0
  6. package/charts-recharts/plugin.json +32 -0
  7. package/charts-recharts/skills/charts-recharts/SKILL.md +108 -0
  8. package/cloudflare/NOTES.md +16 -0
  9. package/cloudflare/form-destinations/cloudflare-d1.mjs +9 -0
  10. package/cloudflare/lib/api.mjs +96 -0
  11. package/cloudflare/lib/assets.mjs +65 -0
  12. package/cloudflare/lib/dns.mjs +32 -0
  13. package/cloudflare/lib/domain-inventory.mjs +61 -0
  14. package/cloudflare/lib/domain-status.mjs +48 -0
  15. package/cloudflare/lib/domains.mjs +95 -0
  16. package/cloudflare/lib/forms.mjs +254 -0
  17. package/cloudflare/lib/pages-upload.mjs +51 -0
  18. package/cloudflare/lib/target.mjs +165 -0
  19. package/cloudflare/lib/workers-upload.mjs +48 -0
  20. package/cloudflare/lib/zones.mjs +25 -0
  21. package/cloudflare/mcp.json +4 -0
  22. package/cloudflare/plugin.json +96 -0
  23. package/cloudflare/skills/cloudflare/SKILL.md +57 -0
  24. package/cloudflare/skills/cloudflare/references/domains.md +31 -0
  25. package/cloudflare/skills/cloudflare/references/pages.md +18 -0
  26. package/cloudflare/skills/cloudflare/references/permissions.md +29 -0
  27. package/cloudflare/skills/cloudflare/references/troubleshooting.md +20 -0
  28. package/cloudflare/skills/cloudflare/references/workers.md +42 -0
  29. package/cloudflare/targets/cloudflare-pages.mjs +12 -0
  30. package/cloudflare/targets/cloudflare-workers.mjs +17 -0
  31. package/cloudflare/tovu-consent.json +6 -0
  32. package/cloudflare/tovu-deploy-targets.json +630 -0
  33. package/cloudflare/tovu-form-destinations.json +12 -0
  34. package/cloudflare/worker/forms-receiver.mjs +249 -0
  35. package/composio/mcp.json +10 -0
  36. package/composio/plugin.json +68 -0
  37. package/composio/skills/composio/SKILL.md +85 -0
  38. package/dist/core/identity-namespace.d.ts +12 -0
  39. package/dist/core/identity-namespace.js +20 -0
  40. package/dist/folder-zip-node.js +29 -7
  41. package/dist/lifecycle/bundled-digests.js +4 -3
  42. package/dist/lifecycle/install.d.ts +7 -0
  43. package/dist/lifecycle/install.js +45 -3
  44. package/dist/lifecycle/manifest.d.ts +2 -2
  45. package/dist/lifecycle/manifest.js +5 -3
  46. package/dist/lifecycle/node.js +2 -0
  47. package/dist/lifecycle/persistent-state.js +1 -1
  48. package/dist/lifecycle/ports.d.ts +8 -0
  49. package/dist/lifecycle/seed-bundled.js +4 -1
  50. package/dist/manifest.d.ts +1 -1
  51. package/dist/manifest.js +3 -2
  52. package/fly/deploy-configs/fly.mjs +115 -0
  53. package/fly/deploy-ops/fly.mjs +102 -0
  54. package/fly/mcp.json +4 -0
  55. package/fly/plugin.json +81 -0
  56. package/fly/scripts/render-deploy-workflow.mjs +150 -0
  57. package/fly/skills/fly/SKILL.md +43 -0
  58. package/fly/skills/fly/references/fly-deploy.template.yml +117 -0
  59. package/fly/skills/fly/references/fly.template.toml +81 -0
  60. package/fly/tovu-credential-schemes.json +11 -0
  61. package/fly/tovu-deploy-configs.json +21 -0
  62. package/fly/tovu-deploy-ops.json +14 -0
  63. package/github/deploy-ops/github-actions.mjs +164 -0
  64. package/github/deployments/github.mjs +415 -0
  65. package/github/mcp.json +4 -0
  66. package/github/plugin.json +105 -0
  67. package/github/skills/github/SKILL.md +249 -0
  68. package/github/skills/github/references/actions.md +224 -0
  69. package/github/skills/github/references/auth-and-tokens.md +140 -0
  70. package/github/skills/github/references/repo-files.md +135 -0
  71. package/github/skills/github/references/site-backup.md +159 -0
  72. package/github/skills/github/references/source-control.md +123 -0
  73. package/github/source-control/backup-push.mjs +242 -0
  74. package/github/source-control/commit-site.mjs +459 -0
  75. package/github/source-control/github.mjs +93 -0
  76. package/github/source-control/write-files.mjs +468 -0
  77. package/github/tovu-deploy-ops.json +13 -0
  78. package/github/tovu-source-control.json +117 -0
  79. package/github-pages/NOTES.md +19 -0
  80. package/github-pages/mcp.json +4 -0
  81. package/github-pages/plugin.json +48 -0
  82. package/github-pages/skills/github-pages/SKILL.md +44 -0
  83. package/github-pages/targets/github-pages.mjs +543 -0
  84. package/github-pages/tovu-deploy-targets.json +327 -0
  85. package/google-login/exports/google-g.svg +1 -0
  86. package/google-login/exports/sign-in-button.css +68 -0
  87. package/google-login/exports/sign-in-button.html +4 -0
  88. package/google-login/mcp.json +4 -0
  89. package/google-login/plugin.json +59 -0
  90. package/google-login/skills/google-login/SKILL.md +96 -0
  91. package/google-login/tovu-login-providers.json +21 -0
  92. package/google-workspace/README.md +132 -0
  93. package/google-workspace/form-destinations/README.md +87 -0
  94. package/google-workspace/form-destinations/dedupe.mjs +43 -0
  95. package/google-workspace/form-destinations/delivery-result.mjs +69 -0
  96. package/google-workspace/form-destinations/mapping.mjs +152 -0
  97. package/google-workspace/form-destinations/sheets.mjs +202 -0
  98. package/google-workspace/lib/auth.mjs +81 -0
  99. package/google-workspace/lib/drive-files.mjs +54 -0
  100. package/google-workspace/lib/form-destination-settings.mjs +72 -0
  101. package/google-workspace/lib/http.mjs +168 -0
  102. package/google-workspace/lib/picker-flow.mjs +171 -0
  103. package/google-workspace/lib/picker-selection.mjs +107 -0
  104. package/google-workspace/lib/product-state.mjs +93 -0
  105. package/google-workspace/lib/request-policy.mjs +163 -0
  106. package/google-workspace/lib/sheets-a1.mjs +79 -0
  107. package/google-workspace/lib/sheets-admin-csv.mjs +82 -0
  108. package/google-workspace/lib/sheets-admin-request-kinds.mjs +183 -0
  109. package/google-workspace/lib/sheets-client.mjs +286 -0
  110. package/google-workspace/lib/surface-actions.mjs +107 -0
  111. package/google-workspace/mcp.json +4 -0
  112. package/google-workspace/plugin.json +77 -0
  113. package/google-workspace/products.json +12 -0
  114. package/google-workspace/skills/google-workspace/SKILL.md +82 -0
  115. package/google-workspace/skills/google-workspace/references/connection.md +102 -0
  116. package/google-workspace/skills/google-workspace/references/form-delivery.md +50 -0
  117. package/google-workspace/skills/google-workspace/references/sheets.md +149 -0
  118. package/google-workspace/skills/google-workspace/references/troubleshooting.md +31 -0
  119. package/google-workspace/tools/account-info.mjs +18 -0
  120. package/google-workspace/tools/sheets/README.md +54 -0
  121. package/google-workspace/tools/sheets/copy-tab.mjs +23 -0
  122. package/google-workspace/tools/sheets/copy.mjs +23 -0
  123. package/google-workspace/tools/sheets/create.mjs +35 -0
  124. package/google-workspace/tools/sheets/find-replace.mjs +38 -0
  125. package/google-workspace/tools/sheets/find.mjs +52 -0
  126. package/google-workspace/tools/sheets/get-by-data-filter.mjs +23 -0
  127. package/google-workspace/tools/sheets/get.mjs +22 -0
  128. package/google-workspace/tools/sheets/values-append.mjs +27 -0
  129. package/google-workspace/tools/sheets/values-batch-clear.mjs +20 -0
  130. package/google-workspace/tools/sheets/values-batch-get.mjs +20 -0
  131. package/google-workspace/tools/sheets/values-batch-update.mjs +29 -0
  132. package/google-workspace/tools/sheets/values-by-data-filter.mjs +62 -0
  133. package/google-workspace/tools/sheets/values-clear.mjs +20 -0
  134. package/google-workspace/tools/sheets/values-get.mjs +21 -0
  135. package/google-workspace/tools/sheets/values-update.mjs +26 -0
  136. package/google-workspace/tools/sheets-admin/README.md +45 -0
  137. package/google-workspace/tools/sheets-admin/basic-filter.mjs +32 -0
  138. package/google-workspace/tools/sheets-admin/batch-update.mjs +25 -0
  139. package/google-workspace/tools/sheets-admin/developer-metadata.mjs +28 -0
  140. package/google-workspace/tools/sheets-admin/export-csv.mjs +67 -0
  141. package/google-workspace/tools/sheets-admin/filter-views.mjs +38 -0
  142. package/google-workspace/tools/sheets-admin/format.mjs +70 -0
  143. package/google-workspace/tools/sheets-admin/named-ranges.mjs +42 -0
  144. package/google-workspace/tools/sheets-admin/open-existing.mjs +34 -0
  145. package/google-workspace/tools/sheets-admin/protected-ranges-get.mjs +18 -0
  146. package/google-workspace/tools/sheets-admin/sort.mjs +18 -0
  147. package/google-workspace/tools/sheets-admin/tab-modify.mjs +36 -0
  148. package/google-workspace/tovu-admin-surfaces.json +63 -0
  149. package/google-workspace/tovu-agent-tools.json +1178 -0
  150. package/google-workspace/tovu-connections.json +35 -0
  151. package/google-workspace/tovu-form-destinations.json +10 -0
  152. package/google-workspace/ui/README.md +73 -0
  153. package/google-workspace/ui/connection-panel.handler.mjs +167 -0
  154. package/google-workspace/ui/connection-panel.html +17 -0
  155. package/google-workspace/ui/connection-panel.mjs +189 -0
  156. package/google-workspace/ui/connection-view.mjs +120 -0
  157. package/google-workspace/ui/destination-panel.handler.mjs +106 -0
  158. package/google-workspace/ui/destination-panel.html +17 -0
  159. package/google-workspace/ui/destination-panel.mjs +150 -0
  160. package/google-workspace/ui/dom.mjs +140 -0
  161. package/google-workspace/ui/panel.css +170 -0
  162. package/google-workspace/ui/picker-flow.mjs +87 -0
  163. package/google-workspace/ui/picker-view.mjs +44 -0
  164. package/google-workspace/ui/picker.handler.mjs +56 -0
  165. package/google-workspace/ui/picker.html +17 -0
  166. package/google-workspace/ui/picker.mjs +88 -0
  167. package/google-workspace/ui/surface-client.mjs +227 -0
  168. package/higgsfield-media/mcp.json +9 -0
  169. package/higgsfield-media/plugin.json +109 -0
  170. package/higgsfield-media/skills/higgsfield-media/SKILL.md +325 -0
  171. package/higgsfield-media/skills/higgsfield-media/references/failure-modes.md +116 -0
  172. package/higgsfield-media/skills/higgsfield-media/references/models-and-plan-gates.md +142 -0
  173. package/jev/mcp.json +4 -0
  174. package/jev/plugin.json +44 -0
  175. package/jev/skills/jev/SKILL.md +308 -0
  176. package/jev/skills/jev/references/cookbooks.md +197 -0
  177. package/jev/skills/jev/references/jaggedness.md +135 -0
  178. package/jev/skills/jev/references/javascript-sdk.md +146 -0
  179. package/jev/skills/jev/references/patterns.md +123 -0
  180. package/jev/skills/jev/references/primitives-reference.md +187 -0
  181. package/langfuse/plugin.json +22 -0
  182. package/meilisearch/mcp.json +4 -0
  183. package/meilisearch/plugin.json +54 -0
  184. package/meilisearch/providers/meilisearch.mjs +651 -0
  185. package/meilisearch/skills/meilisearch/SKILL.md +43 -0
  186. package/meilisearch/tovu-search-providers.json +10 -0
  187. package/netlify/NOTES.md +13 -0
  188. package/netlify/mcp.json +4 -0
  189. package/netlify/plugin.json +47 -0
  190. package/netlify/skills/netlify/SKILL.md +39 -0
  191. package/netlify/targets/netlify.mjs +384 -0
  192. package/netlify/tovu-consent.json +6 -0
  193. package/netlify/tovu-deploy-targets.json +163 -0
  194. package/opentelemetry/mcp.json +4 -0
  195. package/opentelemetry/plugin.json +52 -0
  196. package/opentelemetry/skills/opentelemetry/SKILL.md +39 -0
  197. package/package.json +161 -6
  198. package/posthog/mcp.json +4 -0
  199. package/posthog/plugin.json +46 -0
  200. package/posthog/skills/posthog/SKILL.md +36 -0
  201. package/railway/deploy-configs/railway.mjs +135 -0
  202. package/railway/deploy-ops/railway.mjs +167 -0
  203. package/railway/mcp.json +4 -0
  204. package/railway/plugin.json +45 -0
  205. package/railway/skills/railway/SKILL.md +66 -0
  206. package/railway/tovu-deploy-configs.json +16 -0
  207. package/railway/tovu-deploy-ops.json +11 -0
  208. package/render/deploy-configs/render.mjs +141 -0
  209. package/render/deploy-ops/render.mjs +152 -0
  210. package/render/mcp.json +4 -0
  211. package/render/plugin.json +47 -0
  212. package/render/skills/render/SKILL.md +63 -0
  213. package/render/tovu-deploy-configs.json +16 -0
  214. package/render/tovu-deploy-ops.json +11 -0
  215. package/resend/NOTES.md +52 -0
  216. package/resend/adapters/resend.mjs +161 -0
  217. package/resend/lib/domains.mjs +137 -0
  218. package/resend/lib/errors.mjs +25 -0
  219. package/resend/lib/feedback.mjs +118 -0
  220. package/resend/mcp.json +13 -0
  221. package/resend/plugin.json +192 -0
  222. package/resend/skills/resend/SKILL.md +107 -0
  223. package/resend/tovu-mail-adapters.json +22 -0
  224. package/s3-compatible/NOTES.md +11 -0
  225. package/s3-compatible/mcp.json +4 -0
  226. package/s3-compatible/object-stores/s3-compatible.mjs +287 -0
  227. package/s3-compatible/plugin.json +56 -0
  228. package/s3-compatible/skills/s3-compatible/SKILL.md +39 -0
  229. package/s3-compatible/targets/s3-compatible.mjs +1054 -0
  230. package/s3-compatible/tovu-deploy-targets.json +432 -0
  231. package/s3-compatible/tovu-object-stores.json +29 -0
  232. package/sentry/mcp.json +4 -0
  233. package/sentry/plugin.json +49 -0
  234. package/sentry/skills/sentry/SKILL.md +34 -0
  235. package/shopify/mcp.json +4 -0
  236. package/shopify/plugin.json +22 -0
  237. package/shopify/skills/shopify/SKILL.md +103 -0
  238. package/shopify/skills/shopify/references/catalog-sync.md +74 -0
  239. package/shopify/skills/shopify/references/domains.md +26 -0
  240. package/shopify/skills/shopify/references/live-storefront.md +34 -0
  241. package/shopify/skills/shopify/references/storefront-snippets.md +21 -0
  242. package/site-compliance/mcp.json +4 -0
  243. package/site-compliance/plugin.json +34 -0
  244. package/site-compliance/skills/site-compliance/SKILL.md +220 -0
  245. package/site-compliance/skills/site-compliance/references/ccpa-cpra.md +115 -0
  246. package/site-compliance/skills/site-compliance/references/gdpr-eu-uk.md +143 -0
  247. package/site-compliance/skills/site-compliance/references/wcag-2-2.md +129 -0
  248. package/site-import/mcp.json +4 -0
  249. package/site-import/plugin.json +41 -0
  250. package/site-import/skills/site-import/SKILL.md +232 -0
  251. package/site-import/skills/site-import/references/content-mapping.md +166 -0
  252. package/site-import/skills/site-import/references/discovery-and-classification.md +117 -0
  253. package/site-import/skills/site-import/references/theme-extraction.md +34 -0
  254. package/slide-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
  255. package/slide-design/mcp.json +4 -0
  256. package/slide-design/plugin.json +26 -0
  257. package/slide-design/skills/slide-design/SKILL.md +103 -0
  258. package/slide-design/skills/slide-design/references/charts-and-exhibits.md +100 -0
  259. package/slide-design/skills/slide-design/references/html-deck.md +175 -0
  260. package/slide-design/skills/slide-design/references/layout-and-style.md +131 -0
  261. package/slide-design/skills/slide-design/references/review.md +93 -0
  262. package/slide-design/skills/slide-design/references/storyline.md +118 -0
  263. package/stripe/plugin.json +22 -0
  264. package/supabase/mcp.json +26 -0
  265. package/supabase/plugin.json +78 -0
  266. package/supabase/skills/supabase/SKILL.md +169 -0
  267. package/supabase/skills/supabase/references/failure-modes.md +101 -0
  268. package/ui-ux-design/CHANGELOG.md +28 -0
  269. package/ui-ux-design/LICENSE +201 -0
  270. package/ui-ux-design/NOTICE +14 -0
  271. package/ui-ux-design/README.md +62 -0
  272. package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
  273. package/ui-ux-design/mcp.json +4 -0
  274. package/ui-ux-design/plugin.json +26 -0
  275. package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
  276. package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
  277. package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
  278. package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
  279. package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
  280. package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
  281. package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
  282. package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
  283. package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
  284. package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
  285. package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
  286. package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
  287. package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
  288. package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
  289. package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
  290. package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
  291. package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
  292. package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
  293. package/ui-ux-design/skills/shadcn-ui/SKILL.md +333 -0
  294. package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
  295. package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
  296. package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
  297. package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
  298. package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
  299. package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
  300. package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
  301. package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
  302. package/ui-ux-design/skills/ui-ux-design/SKILL.md +171 -0
  303. package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +78 -0
  304. package/ui-ux-design/skills/ui-ux-design/references/chrome-mobile-test-matrix.md +137 -0
  305. package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +112 -0
  306. package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +54 -0
  307. package/ui-ux-design/skills/ui-ux-design/references/foundations.md +145 -0
  308. package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
  309. package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +104 -0
  310. package/ui-ux-design/skills/ui-ux-design/references/kuinetic-reference.md +184 -0
  311. package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
  312. package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +55 -0
  313. package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +88 -0
  314. package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +103 -0
  315. package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +119 -0
  316. package/ui-ux-design/skills/ui-ux-design/references/ui-animation.md +173 -0
  317. package/ui-ux-design/skills/ui-ux-design/references/ui-loop.md +234 -0
  318. package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +54 -0
  319. package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
  320. package/ui-ux-design/skills/web-compliance/SKILL.md +134 -0
  321. package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
  322. package/vercel/NOTES.md +23 -0
  323. package/vercel/mcp.json +4 -0
  324. package/vercel/object-stores/vercel-blob.mjs +147 -0
  325. package/vercel/plugin.json +47 -0
  326. package/vercel/skills/vercel/SKILL.md +48 -0
  327. package/vercel/targets/vercel.mjs +586 -0
  328. package/vercel/tovu-consent.json +6 -0
  329. package/vercel/tovu-deploy-targets.json +219 -0
  330. package/vercel/tovu-object-stores.json +10 -0
  331. package/wordpress/NOTES.md +33 -0
  332. package/wordpress/plugin.json +30 -0
  333. package/wordpress/skills/wordpress/SKILL.md +43 -0
  334. package/wordpress/skills/wordpress/references/free-site-redirect.md +46 -0
  335. package/wordpress/skills/wordpress/references/subdomain.md +66 -0
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: meilisearch
3
+ description: Run the site's visitor search on a Meilisearch server. Covers what Meilisearch receives (public content only), which two values the host needs (server URL and API key), and what each search failure means in plain words.
4
+ ---
5
+
6
+ # Run the site's search on Meilisearch
7
+
8
+ ## Connection card
9
+
10
+ Choose the card yourself from its `usedFor` list. Reuse a saved key or working sign-in for that
11
+ task. Never offer a replacement unless the person asks.
12
+
13
+ Open **Connect Meilisearch** with `credential_save {kind:"plugin-provider",target:"meilisearch"}`. It collects your server’s HTTPS URL and a masked scoped key, and saves the exact label `Tovu Search — Meilisearch`. The card lists index, document, search, settings and task permissions. Create the key in your own instance/Cloud console; there is no universal instance-specific key-creation URL.
14
+
15
+ ## Scope
16
+
17
+ This plugin owns the Meilisearch provider code; the host runs it only when the host shipped it.
18
+ The host's built-in search stays the default; Meilisearch answers only after the owner picks it for
19
+ the site.
20
+
21
+ **What Meilisearch receives:** the title, plain text, URL, collection and locale of each published,
22
+ publicly visible entry. Members-only or otherwise restricted content is never sent: the host removes it
23
+ from Meilisearch instead, and only the built-in search can find it for the visitors allowed to see
24
+ it. Every result Meilisearch returns is checked again against the site before a visitor sees it.
25
+
26
+ ## Set it up
27
+
28
+ 1. **Get a server.** Either Meilisearch Cloud or a self-hosted Meilisearch v1.x reachable from the host
29
+ over HTTPS.
30
+ 2. **Create a key.** Use a key that may create/delete the site index, add/replace and retrieve documents, update index settings, search and read
31
+ tasks (a scoped key with the actions listed on the card). Never ask the person to paste the key
32
+ into chat; use the plugin card above.
33
+ 3. **Save the key on the site.** Open the plugin card above. Enter this server’s HTTPS URL and the key directly into the card. The host saves the fixed label and passes the URL as `host` and the token as `apiKey`.
34
+ 4. **Switch the site to Meilisearch** in the site's search settings. The host builds a fresh index in the
35
+ background and switches over only when it is complete, so search keeps working meanwhile.
36
+
37
+ ## What a failure means
38
+
39
+ - `not_configured`: the host or API key is missing. Save both.
40
+ - `unauthorized` / `forbidden`: the key is wrong or lacks one of the actions above. Explain the refusal and needed access; replace the key only if asked.
41
+ - `rate_limited`: Meilisearch asked the host to slow down. The host waits and retries.
42
+ - `network`, `timeout`, `provider_unavailable`: the server could not be reached or is down. The host
43
+ reconciles the index once it is back; nothing needs re-entering.
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "providers": [
4
+ {
5
+ "id": "meilisearch",
6
+ "label": "Meilisearch",
7
+ "module": "providers/meilisearch.mjs"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,13 @@
1
+ # Netlify — implementation notes
2
+
3
+ Host-specific notes moved out of the first host's core comments (2026-10-22, vendor-to-plugins). Core describes these
4
+ behaviours generically; the vendor detail lives here.
5
+
6
+ - **Stored `siteId`.** Accepted and stored with a credential although, when written, the deploy target
7
+ took only `{token}` and always found-or-created its site from the publish call's `projectName` label.
8
+ Stored anyway so the credential contract stays stable whichever side ships second.
9
+ - **Account label.** Netlify's verify response has no reviewed account field, so no label is read and
10
+ the account-label heal skips these credentials.
11
+ - **Security headers.** A root `_headers` file (two-space-indented `Name: value` lines under `/*`),
12
+ rendered by `targets/netlify.mjs`; core keeps the same format as a drift oracle in
13
+ `site-export/static-security-headers.ts`. Its 404 convention is `404.html` at the output root.
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {}
4
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "netlify",
4
+ "version": "0.1.0",
5
+ "description": "Publishes the site's static export to Netlify with the workspace's own saved token. Ships the host code itself (a host loads it only because that host chose to ship this plugin) and the know-how an agent needs: where to get a token, how the Netlify site is named, and what each failure means in plain words.",
6
+ "author": "Tovu",
7
+ "license": "Apache-2.0",
8
+ "extensions": {
9
+ "tovu": {
10
+ "maturity": "soon",
11
+ "displayName": "Netlify",
12
+ "summary": "Puts your website online on Netlify. The assistant shows you a preview first.",
13
+ "id": "tovu/netlify",
14
+ "provides": [
15
+ "deploy-target:netlify"
16
+ ],
17
+ "credentialCards": [
18
+ {
19
+ "target": "netlify",
20
+ "title": "Connect Netlify",
21
+ "label": "Netlify connection",
22
+ "category": "hosting",
23
+ "intro": "Connect Netlify to publish and update this site.",
24
+ "usedFor": [
25
+ "Publishes and updates your website on Netlify"
26
+ ],
27
+ "keyHint": "Use a personal access token for an account allowed to create sites and deployments. If the team requires SSO, grant the token access to that team when creating it.",
28
+ "saveKind": "publish-host",
29
+ "helpUrl": "https://app.netlify.com/user/applications#personal-access-tokens",
30
+ "helpLabel": "Create a token",
31
+ "baseUrl": "https://api.netlify.com"
32
+ }
33
+ ]
34
+ }
35
+ },
36
+ "keywords": [
37
+ "netlify",
38
+ "deploy",
39
+ "publish",
40
+ "hosting",
41
+ "static-site",
42
+ "static-export",
43
+ "go-live",
44
+ "website",
45
+ "deploy-target:netlify"
46
+ ]
47
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: netlify
3
+ description: Publish the site's static export to Netlify with the workspace's own saved token. Covers the token, the site name, which files upload, and what each failure means in plain words.
4
+ ---
5
+
6
+ # Netlify
7
+
8
+ ## Connection card
9
+
10
+ Choose the card yourself from its `usedFor` list. Reuse a saved key or working sign-in for that
11
+ task. Never offer a replacement unless the person asks.
12
+
13
+ Open the plugin’s **Connect Netlify** card with `credential_save {kind:"plugin-provider",target:"netlify"}`. It links to token creation and explains team SSO access. The hosting save flow verifies and seals the token.
14
+
15
+ The steps are the same for every host: `deployment_get_static_publish_capabilities` shows whether a
16
+ token is saved, `deployment_preview_static_publish` comes first, and `deployment_execute_static_publish`
17
+ publishes once the person confirms. `ok: "partial"` means not reachable yet: never call it failed or live.
18
+ This file is what is particular to Netlify.
19
+
20
+ - **No hosting account yet?** Netlify is the quickest free start: one token, no config fields.
21
+ - **Token:** a personal access token from https://app.netlify.com/user/applications#personal-access-tokens.
22
+ No config fields: the host finds or creates the Netlify site from the project name.
23
+ - **Site name:** `jini-<project-name>` (lowercase, hyphens). Publishing the same project again updates the
24
+ same site. The prefix is kept for sites published before this plugin existed.
25
+ - **Only changed files upload.** Netlify compares file fingerprints and asks only for what it lacks.
26
+ - **Security headers:** this plugin writes them into a root `_headers` file; a `_headers` file in the
27
+ site itself is replaced.
28
+
29
+ ## Failure messages and what to tell the person
30
+
31
+ | Message | Meaning | Next step |
32
+ |---|---|---|
33
+ | `Netlify token is required.` | No token in the saved credential. | Open the plugin card above. |
34
+ | `Invalid token` / `Access Denied` (401/403) | The token is wrong, expired, or revoked. | Explain the refusal; replace the token only if asked. |
35
+ | `Name has already been taken` | Another Netlify account owns that site name. | Publish under a different project name. |
36
+ | `Netlify deployment error.` / `rejected.` | Netlify refused the deploy after upload. | Retry once; if it repeats, check the Netlify dashboard's deploy log. |
37
+ | `Payload too large` | One file is over Netlify's size limit. | Find and shrink the large file (usually a video or image). |
38
+ | `Netlify returned a non-JSON response.` | Netlify or the network had a hiccup. | Retry in a minute. |
39
+ | `fetch timed out after ...` | Netlify did not answer in time. | Retry in a minute. |
@@ -0,0 +1,384 @@
1
+ // @ts-check
2
+ import { createHash } from "node:crypto";
3
+
4
+ /**
5
+ * @file Netlify deploy target of the `netlify` Agent Plugin.
6
+ *
7
+ * Ported from `@jini-ai/devops` `src/deploy/netlify.ts` (0.3.1). Same Netlify calls and error texts;
8
+ * the only change is the import surface: network, timeouts, reachability and naming come from the
9
+ * host kit the host injects (`DeployHostKit`, below), because an installed plugin has no npm
10
+ * dependencies to import. Plain JS by owner decision (2026-09-29).
11
+ *
12
+ * Flow: find-or-create a site for the project name, create a deploy from a SHA1 file manifest, upload
13
+ * only the files Netlify reports as `required`, poll to a terminal `state`, then wait for the public
14
+ * URL. Built against Netlify's OpenAPI contract (https://open-api.netlify.com).
15
+ *
16
+ * @typedef {import("@jini-ai/devops/deploy").DeployFile} DeployFile
17
+ * @typedef {import("@jini-ai/devops/deploy").DeployTarget} DeployTarget
18
+ * @typedef {Record<string, unknown>} JsonObject
19
+ * @typedef {{ fetch(url: string, init: RequestInit, options: { timeoutMs: number }): Promise<Response>,
20
+ * timeouts: { QUICK: number, DEPLOY: number, UPLOAD: number }, sleep(ms: number): Promise<void>,
21
+ * checkDeploymentUrl: Function, waitForReachableDeploymentUrl: Function, normalizeDeploymentUrl(url: unknown): string,
22
+ * safeDnsLabel(raw: unknown): string, DeployError: typeof import("@jini-ai/devops/deploy").DeployError }} DeployHostKit
23
+ * @typedef {{ files: DeployFile[], projectName: string, responseHeaders?: Readonly<Record<string, string>> }} PublishInput
24
+ */
25
+
26
+ export const NETLIFY_TARGET_ID = "netlify";
27
+
28
+ const NETLIFY_API = "https://api.netlify.com/api/v1";
29
+
30
+ /** The file Netlify reads per-path response headers from. */
31
+ const HEADERS_FILE = "_headers";
32
+
33
+ /**
34
+ * Terminal failure states from Netlify's `GET /sites/{site_id}/deploys` `state` filter. `ready` is the
35
+ * only success state; every other value is in progress and keeps the poll going.
36
+ */
37
+ const NETLIFY_FAILURE_STATES = new Set(["error", "rejected"]);
38
+
39
+ /**
40
+ * Renders a header set in Netlify's `_headers` format: a path pattern, then two-space-indented
41
+ * `Name: value` lines. Byte-identical to what the first host (Tovu) rendered in its core before this moved here.
42
+ *
43
+ * @param {Readonly<Record<string, string>>} headers
44
+ * @returns {string}
45
+ */
46
+ function renderHeadersFile(headers) {
47
+ const lines = Object.entries(headers).map(([name, value]) => ` ${name}: ${value}`);
48
+ return `/*\n${lines.join("\n")}\n`;
49
+ }
50
+
51
+ /**
52
+ * The file set actually deployed: `files`, plus a root `_headers` rendered from `responseHeaders`.
53
+ * The rendered file REPLACES a site-supplied `_headers`: the header set is the host's policy, and the
54
+ * legacy path had the same effect (its later manifest entry for the same path won).
55
+ *
56
+ * @param {DeployFile[]} files
57
+ * @param {Readonly<Record<string, string>> | undefined} responseHeaders
58
+ * @returns {DeployFile[]}
59
+ */
60
+ function withHeadersFile(files, responseHeaders) {
61
+ if (!responseHeaders || Object.keys(responseHeaders).length === 0) return files;
62
+ return [...files.filter((file) => file.file !== HEADERS_FILE), { file: HEADERS_FILE, data: renderHeadersFile(responseHeaders) }];
63
+ }
64
+
65
+ /** @implements {DeployTarget} */
66
+ class NetlifyDeployTarget {
67
+ /**
68
+ * @param {{ token: string }} config
69
+ * @param {DeployHostKit} kit
70
+ */
71
+ constructor(config, kit) {
72
+ this.id = NETLIFY_TARGET_ID;
73
+ this.config = config;
74
+ this.kit = kit;
75
+ }
76
+
77
+ /** Resolve through the existing site lifecycle so a first publish and its upload share one
78
+ * provider-assigned identity. @complexity One bounded ensure-site pass; no file uploads.
79
+ */
80
+ async resolvePublicUrl({ projectName }, _options = {}) {
81
+ if (!this.config.token) throw new this.kit.DeployError("Netlify token is required.", 400);
82
+ this.resolvedSite = await ensureNetlifySite(this, deriveNetlifySiteName(this.kit, projectName));
83
+ const url = netlifyUrlCandidates(this.kit, this.resolvedSite)[0];
84
+ if (!url) throw new this.kit.DeployError("Netlify did not report the site's public URL.", 502);
85
+ return url;
86
+ }
87
+
88
+ /**
89
+ * Publishes `input.files` as a new Netlify deploy (creating the site on first use) and waits for
90
+ * the URL to be reachable.
91
+ *
92
+ * @param {PublishInput} input
93
+ * @throws {DeployError} Missing token, site not found/created, or a terminal failure state.
94
+ */
95
+ async publish(input) {
96
+ const { kit } = this;
97
+ const { site, siteId, siteName } = await resolvePublishSite({ target: this, projectName: input.projectName });
98
+
99
+ const { created, byHash } = await createNetlifyDeploy(this, siteId, withHeadersFile(input.files, input.responseHeaders));
100
+ const deployId = typeof created.id === "string" ? created.id : "";
101
+ if (!deployId) throw new kit.DeployError("Netlify deploy response did not include an id.", 502, created);
102
+
103
+ await uploadRequiredNetlifyFiles({ target: this, deployId, created, byHash });
104
+
105
+ const finalState = await readyNetlifyDeploy({ target: this, deployId });
106
+
107
+ const candidates = netlifyUrlCandidates(kit, finalState, site);
108
+ const link = await kit.waitForReachableDeploymentUrl(candidates, { providerLabel: "Netlify" });
109
+
110
+ return publishResult({ target: this, link, candidates, deployId, siteId, siteName });
111
+ }
112
+
113
+ /** Probes `url` with the shared checker; Netlify has no documented auth wall to detect.
114
+ * @param {string} url */
115
+ async checkReachability(url) {
116
+ return this.kit.checkDeploymentUrl(url);
117
+ }
118
+ }
119
+
120
+ /** The site this publish deploys to: the already-resolved one, or the named site (created on first use).
121
+ * @param {{ target: NetlifyDeployTarget, projectName: string | undefined }} required
122
+ * @throws {DeployError} Missing token, an ungeneratable name, or a site response without an id. */
123
+ async function resolvePublishSite({ target, projectName }, _options = {}) {
124
+ const { kit } = target;
125
+ if (!target.config.token) throw new kit.DeployError("Netlify token is required.", 400);
126
+ const siteName = deriveNetlifySiteName(kit, projectName);
127
+ if (!siteName) throw new kit.DeployError("Netlify site name could not be generated.", 400);
128
+ const site = target.resolvedSite ?? await ensureNetlifySite(target, siteName);
129
+ const siteId = typeof site.id === "string" ? site.id : "";
130
+ if (!siteId) throw new kit.DeployError("Netlify site response did not include an id.", 502, site);
131
+ return { site, siteId, siteName };
132
+ }
133
+
134
+ /** @param {{ target: NetlifyDeployTarget, link: { url?: string, status: unknown, statusMessage?: unknown, reachableAt?: unknown }, candidates: string[], deployId: string, siteId: string, siteName: string }} required */
135
+ function publishResult({ target, link, candidates, deployId, siteId, siteName }, _options = {}) {
136
+ return {
137
+ targetId: target.id,
138
+ url: link.url || candidates[0] || "",
139
+ deploymentId: deployId,
140
+ status: link.status,
141
+ statusMessage: link.statusMessage,
142
+ ...(link.reachableAt !== undefined ? { reachableAt: link.reachableAt } : {}),
143
+ providerMetadata: { siteId, siteName },
144
+ };
145
+ }
146
+
147
+ /** @param {{ target: NetlifyDeployTarget, deployId: string, created: JsonObject, byHash: Map<string, DeployFile> }} required */
148
+ async function uploadRequiredNetlifyFiles({ target, deployId, created, byHash }, _options = {}) {
149
+ const required = Array.isArray(created.required) ? created.required.filter((hash) => typeof hash === "string") : [];
150
+ for (const hash of required) {
151
+ const file = byHash.get(hash);
152
+ // `required` is derived from the manifest just sent, so an unknown hash should never happen;
153
+ // skip it rather than fail the whole publish.
154
+ if (!file) continue;
155
+ await uploadNetlifyFile(target, deployId, file);
156
+ }
157
+ }
158
+
159
+ /** @param {{ target: NetlifyDeployTarget, deployId: string }} required */
160
+ async function readyNetlifyDeploy({ target, deployId }, _options = {}) {
161
+ const finalState = await pollNetlifyDeploy(target, deployId);
162
+ if (finalState && NETLIFY_FAILURE_STATES.has(String(finalState.state))) {
163
+ throw new target.kit.DeployError(
164
+ (typeof finalState.error_message === "string" && finalState.error_message) || `Netlify deployment ${finalState.state}.`,
165
+ 502,
166
+ finalState,
167
+ );
168
+ }
169
+ return finalState;
170
+ }
171
+
172
+ /**
173
+ * A stable, Netlify-safe site name from the project name. Deterministic so repeated publishes land on
174
+ * the same site. The `jini-` prefix is kept byte-identical to the devops original: existing Netlify
175
+ * sites carry it, and a new prefix would silently create a second site on the next publish.
176
+ *
177
+ * @param {DeployHostKit} kit
178
+ * @param {string} projectName
179
+ */
180
+ function deriveNetlifySiteName(kit, projectName) {
181
+ const label = kit.safeDnsLabel(projectName) || "site";
182
+ return kit.safeDnsLabel(`jini-${label}`).slice(0, 63);
183
+ }
184
+
185
+ /** @param {string} token @param {Record<string, string>} [extra] */
186
+ function netlifyHeaders(token, extra = {}) {
187
+ return { Authorization: `Bearer ${token}`, ...extra };
188
+ }
189
+
190
+ /** @param {NetlifyDeployTarget} target @param {Response} resp @returns {Promise<any>} */
191
+ async function readNetlifyJson(target, resp) {
192
+ try {
193
+ return await resp.json();
194
+ } catch {
195
+ throw new target.kit.DeployError("Netlify returned a non-JSON response.", resp.status || 502);
196
+ }
197
+ }
198
+
199
+ /** @param {NetlifyDeployTarget} target @param {JsonObject} json @param {number} status @param {string} fallback */
200
+ function netlifyError(target, json, status, fallback) {
201
+ const message = typeof json?.message === "string" && json.message ? json.message : fallback || `Netlify request failed (${status}).`;
202
+ return new target.kit.DeployError(message, status, json);
203
+ }
204
+
205
+ /** Looks up a site by exact (case-insensitive) name within the caller's own account.
206
+ * @param {NetlifyDeployTarget} target @param {string} name @returns {Promise<JsonObject | undefined>} */
207
+ async function findNetlifySiteByName(target, name) {
208
+ const { kit, config } = target;
209
+ const url = `${NETLIFY_API}/sites?${new URLSearchParams({ name, filter: "all" }).toString()}`;
210
+ const resp = await kit.fetch(url, { headers: netlifyHeaders(config.token) }, { timeoutMs: kit.timeouts.QUICK });
211
+ const body = await readNetlifyJson(target, resp);
212
+ if (!resp.ok) throw netlifyError(target, Array.isArray(body) ? {} : body, resp.status, "Netlify site lookup failed.");
213
+ /** @type {JsonObject[]} */
214
+ const list = Array.isArray(body) ? body : [];
215
+ return list.find((site) => typeof site?.name === "string" && site.name.toLowerCase() === name.toLowerCase());
216
+ }
217
+
218
+ /** @param {NetlifyDeployTarget} target @param {string} name @returns {Promise<JsonObject>} */
219
+ async function createNetlifySite(target, name) {
220
+ const { kit, config } = target;
221
+ const resp = await kit.fetch(
222
+ `${NETLIFY_API}/sites`,
223
+ { method: "POST", headers: netlifyHeaders(config.token, { "Content-Type": "application/json" }), body: JSON.stringify({ name }) },
224
+ { timeoutMs: kit.timeouts.DEPLOY },
225
+ );
226
+ const body = await readNetlifyJson(target, resp);
227
+ if (!resp.ok) throw netlifyError(target, body, resp.status, "Netlify site creation failed.");
228
+ return body;
229
+ }
230
+
231
+ /**
232
+ * Finds the site for `name`, creating it on first use. Site names are globally unique, so a failed
233
+ * create is ambiguous (a benign race inside this account, or another account owns it): re-listing
234
+ * this account's sites tells them apart. Found: use it. Still missing: rethrow the creation error.
235
+ *
236
+ * @param {NetlifyDeployTarget} target @param {string} name @returns {Promise<JsonObject>}
237
+ */
238
+ async function ensureNetlifySite(target, name) {
239
+ const existing = await findNetlifySiteByName(target, name);
240
+ if (existing) return existing;
241
+ try {
242
+ return await createNetlifySite(target, name);
243
+ } catch (error) {
244
+ const retry = await findNetlifySiteByName(target, name);
245
+ if (retry) return retry;
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ /** @param {DeployFile["data"]} data */
251
+ function sha1Hex(data) {
252
+ // Netlify requires SHA-1 file identifiers in its deploy manifest and upload URLs.
253
+ // This is a wire-format requirement; substituting SHA-256 would break uploads.
254
+ return createHash("sha1").update(Buffer.from(data)).digest("hex");
255
+ }
256
+
257
+ /** URL-encodes each path segment so separators survive but special characters do not.
258
+ * @param {string} filePath */
259
+ function encodeNetlifyFilePath(filePath) {
260
+ return filePath.split("/").filter(Boolean).map(encodeURIComponent).join("/");
261
+ }
262
+
263
+ /**
264
+ * Creates a deploy from a SHA1 manifest. Returns the response plus a hash-to-file lookup for the
265
+ * hashes Netlify reports back as `required`.
266
+ *
267
+ * @param {NetlifyDeployTarget} target @param {string} siteId @param {DeployFile[]} files
268
+ * @returns {Promise<{ created: JsonObject, byHash: Map<string, DeployFile> }>}
269
+ */
270
+ async function createNetlifyDeploy(target, siteId, files) {
271
+ const { kit, config } = target;
272
+ /** @type {Record<string, string>} */
273
+ const manifest = {};
274
+ /** @type {Map<string, DeployFile>} */
275
+ const byHash = new Map();
276
+ for (const file of files) {
277
+ const hash = sha1Hex(file.data);
278
+ manifest[`/${file.file}`] = hash;
279
+ if (!byHash.has(hash)) byHash.set(hash, file);
280
+ }
281
+
282
+ const resp = await kit.fetch(
283
+ `${NETLIFY_API}/sites/${encodeURIComponent(siteId)}/deploys`,
284
+ { method: "POST", headers: netlifyHeaders(config.token, { "Content-Type": "application/json" }), body: JSON.stringify({ async: true, files: manifest }) },
285
+ { timeoutMs: kit.timeouts.DEPLOY },
286
+ );
287
+ const created = await readNetlifyJson(target, resp);
288
+ if (!resp.ok) throw netlifyError(target, created, resp.status, "Netlify deploy creation failed.");
289
+ return { created, byHash };
290
+ }
291
+
292
+ /** @param {NetlifyDeployTarget} target @param {string} deployId @param {DeployFile} file */
293
+ async function uploadNetlifyFile(target, deployId, file) {
294
+ const { kit, config } = target;
295
+ const resp = await kit.fetch(
296
+ `${NETLIFY_API}/deploys/${encodeURIComponent(deployId)}/files/${encodeNetlifyFilePath(file.file)}`,
297
+ { method: "PUT", headers: netlifyHeaders(config.token, { "Content-Type": file.contentType || "application/octet-stream" }), body: Buffer.from(file.data) },
298
+ { timeoutMs: kit.timeouts.UPLOAD },
299
+ );
300
+ if (!resp.ok) {
301
+ const body = await readNetlifyJson(target, resp).catch(() => ({}));
302
+ throw netlifyError(target, body, resp.status, `Netlify file upload failed for "${file.file}".`);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Polls a new deploy to a terminal state or a fixed 30-attempt budget (~1s for the first 5 attempts,
308
+ * 2s after: about a minute). A poll body that does not parse is "keep polling", not a failure: the
309
+ * deploy and its files already exist server-side by now. A parsed non-2xx still fails fast.
310
+ *
311
+ * @param {NetlifyDeployTarget} target @param {string} deployId @returns {Promise<JsonObject | null>}
312
+ */
313
+ async function pollNetlifyDeploy(target, deployId) {
314
+ const { kit, config } = target;
315
+ /** @type {JsonObject | null} */
316
+ let last = null;
317
+ for (let i = 0; i < 30; i += 1) {
318
+ await kit.sleep(i < 5 ? 1000 : 2000);
319
+ const resp = await kit.fetch(`${NETLIFY_API}/deploys/${encodeURIComponent(deployId)}`, { headers: netlifyHeaders(config.token) }, { timeoutMs: kit.timeouts.DEPLOY });
320
+ let json;
321
+ try {
322
+ json = await readNetlifyJson(target, resp);
323
+ } catch {
324
+ continue;
325
+ }
326
+ if (!resp.ok) throw netlifyError(target, json, resp.status, "Netlify deploy status check failed.");
327
+ last = json;
328
+ if (json.state === "ready" || NETLIFY_FAILURE_STATES.has(String(json.state))) return json;
329
+ }
330
+ return last;
331
+ }
332
+
333
+ /**
334
+ * Candidate public URLs from the deploy and site responses, HTTPS site URL first.
335
+ *
336
+ * @param {DeployHostKit} kit @param {...(JsonObject | null | undefined)} responses @returns {string[]}
337
+ */
338
+ function netlifyUrlCandidates(kit, ...responses) {
339
+ /** @type {string[]} */
340
+ const urls = [];
341
+ for (const json of responses) {
342
+ if (!json) continue;
343
+ for (const key of ["ssl_url", "url", "deploy_ssl_url", "deploy_url"]) {
344
+ if (typeof json[key] === "string") urls.push(json[key]);
345
+ }
346
+ }
347
+ return [...new Set(urls.map((url) => kit.normalizeDeploymentUrl(url)).filter(Boolean))];
348
+ }
349
+
350
+ /**
351
+ * Reads one credential-check response WITHOUT touching its body: 401/403 means the host refused the
352
+ * credential, any other failure says nothing about it (a 5xx, a rate limit).
353
+ *
354
+ * @param {Response} response
355
+ * @returns {{ ok: true } | { ok: false, reason: "rejected" | "unreachable", statusCode: number }}
356
+ */
357
+ function classifyCredentialResponse(response) {
358
+ if (response.ok) return { ok: true };
359
+ return { ok: false, reason: response.status === 401 || response.status === 403 ? "rejected" : "unreachable", statusCode: response.status };
360
+ }
361
+
362
+ /** The module contract a host's deploy-target registry loads (`DeployTargetModule`). Netlify needs no
363
+ * config fields: the site is found or created from the publish's project name. */
364
+ export default {
365
+ /** @param {{ credential: { token: string }, kit: DeployHostKit }} context */
366
+ create({ credential, kit }) {
367
+ return new NetlifyDeployTarget({ token: credential.token }, kit);
368
+ },
369
+ /**
370
+ * `GET /api/v1/user`: 401 for a bad token. Reads nothing back: the body's only identity fields are
371
+ * `email` and `full_name`, neither of them a public handle.
372
+ * @param {{ credential: { token: string }, kit: any }} context
373
+ */
374
+ async verifyCredential({ credential, kit }) {
375
+ const response = await kit.fetch("https://api.netlify.com/api/v1/user", { headers: { Authorization: `Bearer ${credential.token}` } }, { timeoutMs: kit.timeouts.QUICK });
376
+ return classifyCredentialResponse(response);
377
+ },
378
+ validateConfig() {
379
+ return null;
380
+ },
381
+ basePath() {
382
+ return undefined;
383
+ },
384
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "contributions": [
4
+ { "kind": "region-resolver", "id": "netlify", "header": "x-country" }
5
+ ]
6
+ }