@unbox-plus/cli 0.20.4

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 (358) hide show
  1. package/LICENSE.md +46 -0
  2. package/README.md +175 -0
  3. package/bin/cli.js +439 -0
  4. package/package.json +43 -0
  5. package/src/colors.js +100 -0
  6. package/src/presets.js +241 -0
  7. package/src/theme.js +253 -0
  8. package/template/.claude/agents/assets-marca.md +40 -0
  9. package/template/.claude/agents/avaliador-visual.md +48 -0
  10. package/template/.claude/agents/branding-briefing.md +401 -0
  11. package/template/.claude/agents/conteudo-secao.md +46 -0
  12. package/template/.claude/agents/paginas-legais.md +44 -0
  13. package/template/.claude/settings.json +3 -0
  14. package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
  15. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  16. package/template/.claude/skills/frontend-design/SKILL.md +55 -0
  17. package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
  18. package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
  19. package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
  20. package/template/.env.example +158 -0
  21. package/template/.mcp.json.example +14 -0
  22. package/template/.nvmrc +1 -0
  23. package/template/CLAUDE.md +333 -0
  24. package/template/COVERAGE.md +80 -0
  25. package/template/DEPLOY.md +179 -0
  26. package/template/QA.md +108 -0
  27. package/template/README.md +152 -0
  28. package/template/agents/CONSTRUCAO.md +207 -0
  29. package/template/agents/MANAGER.md +192 -0
  30. package/template/agents/PADROES.md +255 -0
  31. package/template/agents/definitions/00-scaffold.md +56 -0
  32. package/template/agents/definitions/01-layout.md +47 -0
  33. package/template/agents/definitions/02-homepage.md +70 -0
  34. package/template/agents/definitions/03-catalog.md +86 -0
  35. package/template/agents/definitions/04-pdp.md +97 -0
  36. package/template/agents/definitions/05-cart.md +116 -0
  37. package/template/agents/definitions/06-checkout.md +146 -0
  38. package/template/agents/definitions/07-auth.md +130 -0
  39. package/template/agents/definitions/08-customer.md +55 -0
  40. package/template/agents/definitions/09-promotions.md +62 -0
  41. package/template/agents/definitions/10-feedback.md +128 -0
  42. package/template/agents/definitions/11-seo-infra.md +159 -0
  43. package/template/agents/definitions/12-deploy.md +173 -0
  44. package/template/agents/definitions/13-cro.md +275 -0
  45. package/template/agents/definitions/14-seo.md +388 -0
  46. package/template/agents/definitions/15-branding.md +26 -0
  47. package/template/agents/definitions/16-qa-visual.md +138 -0
  48. package/template/agents/definitions/17-aeo.md +257 -0
  49. package/template/app/(loja)/busca/page.tsx +72 -0
  50. package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
  51. package/template/app/(loja)/carrinho/page.tsx +160 -0
  52. package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
  53. package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
  54. package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
  55. package/template/app/(loja)/checkout/page.tsx +80 -0
  56. package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
  57. package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
  58. package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
  59. package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
  60. package/template/app/(loja)/conta/entrar/page.tsx +119 -0
  61. package/template/app/(loja)/conta/page.tsx +45 -0
  62. package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
  63. package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
  64. package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
  65. package/template/app/(loja)/devolucoes/page.tsx +36 -0
  66. package/template/app/(loja)/layout.tsx +22 -0
  67. package/template/app/(loja)/oferta/page.tsx +57 -0
  68. package/template/app/(loja)/page.tsx +86 -0
  69. package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
  70. package/template/app/(loja)/privacidade/page.tsx +113 -0
  71. package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
  72. package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
  73. package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
  74. package/template/app/(loja)/produtos/loading.tsx +16 -0
  75. package/template/app/(loja)/produtos/page.tsx +33 -0
  76. package/template/app/(loja)/termos/page.tsx +104 -0
  77. package/template/app/acesso/page.tsx +129 -0
  78. package/template/app/api/account/addresses/route.ts +39 -0
  79. package/template/app/api/account/exists/route.ts +21 -0
  80. package/template/app/api/account/me/route.ts +25 -0
  81. package/template/app/api/account/otp/route.ts +26 -0
  82. package/template/app/api/account/preferences/route.ts +19 -0
  83. package/template/app/api/account/signin/route.ts +27 -0
  84. package/template/app/api/account/signout/route.ts +9 -0
  85. package/template/app/api/acesso/route.ts +171 -0
  86. package/template/app/api/capi/route.ts +34 -0
  87. package/template/app/api/cart/coupon/route.ts +49 -0
  88. package/template/app/api/cart/items/route.ts +45 -0
  89. package/template/app/api/cart/link/route.ts +69 -0
  90. package/template/app/api/cart/route.ts +113 -0
  91. package/template/app/api/cart/share/route.ts +21 -0
  92. package/template/app/api/cep/[code]/route.ts +18 -0
  93. package/template/app/api/checkout/address/route.ts +24 -0
  94. package/template/app/api/checkout/email/route.ts +41 -0
  95. package/template/app/api/checkout/installments/route.ts +16 -0
  96. package/template/app/api/checkout/route.ts +110 -0
  97. package/template/app/api/checkout/shipping/route.ts +65 -0
  98. package/template/app/api/checkout-destination/route.ts +31 -0
  99. package/template/app/api/order/[ref]/route.ts +17 -0
  100. package/template/app/api/payment-link/route.ts +28 -0
  101. package/template/app/api/revalidate/route.ts +52 -0
  102. package/template/app/api/shipping/quote/route.ts +29 -0
  103. package/template/app/api/subscriptions/[id]/route.ts +52 -0
  104. package/template/app/api/track/route.ts +28 -0
  105. package/template/app/api/unbox/catalogo/route.ts +136 -0
  106. package/template/app/api/unbox/paginas/route.ts +167 -0
  107. package/template/app/api/unbox/vitrine/route.ts +84 -0
  108. package/template/app/api/webhooks/unbox/route.ts +97 -0
  109. package/template/app/apple-icon.svg +5 -0
  110. package/template/app/error.tsx +15 -0
  111. package/template/app/globals.css +457 -0
  112. package/template/app/icon.svg +4 -0
  113. package/template/app/layout.tsx +90 -0
  114. package/template/app/llms.txt/route.ts +43 -0
  115. package/template/app/manifest.ts +21 -0
  116. package/template/app/not-found.tsx +18 -0
  117. package/template/app/opengraph-image.tsx +35 -0
  118. package/template/app/robots.ts +31 -0
  119. package/template/app/sitemap.ts +42 -0
  120. package/template/bootstrap.sh +87 -0
  121. package/template/components/account/account-shell.tsx +47 -0
  122. package/template/components/account/address-book.tsx +147 -0
  123. package/template/components/account/preferences-form.tsx +77 -0
  124. package/template/components/account/reorder-button.tsx +47 -0
  125. package/template/components/account/signout-button.tsx +20 -0
  126. package/template/components/account/subscription-actions.tsx +204 -0
  127. package/template/components/account-nav.tsx +41 -0
  128. package/template/components/address-fields.tsx +102 -0
  129. package/template/components/analytics/data-layer-ready.tsx +16 -0
  130. package/template/components/analytics/purchase-tracker.tsx +32 -0
  131. package/template/components/brand-search.tsx +34 -0
  132. package/template/components/cart/cart-provider.tsx +340 -0
  133. package/template/components/cart/mini-cart.tsx +228 -0
  134. package/template/components/cart-button.tsx +35 -0
  135. package/template/components/catalog/catalog-client.tsx +523 -0
  136. package/template/components/catalog/grid-skeleton.tsx +19 -0
  137. package/template/components/catalog/pager.tsx +66 -0
  138. package/template/components/catalog/product-grid-card.tsx +52 -0
  139. package/template/components/catalog/product-grid.tsx +27 -0
  140. package/template/components/checkout/checkout-client.tsx +1333 -0
  141. package/template/components/chrome/announce-bar.tsx +57 -0
  142. package/template/components/chrome/chrome-recipe.ts +13 -0
  143. package/template/components/chrome/footers/captura-botao.tsx +28 -0
  144. package/template/components/chrome/footers/colunas.tsx +110 -0
  145. package/template/components/chrome/footers/conversao.tsx +90 -0
  146. package/template/components/chrome/footers/editorial.tsx +98 -0
  147. package/template/components/chrome/footers/minimal.tsx +55 -0
  148. package/template/components/chrome/header-bar-mobile.tsx +64 -0
  149. package/template/components/chrome/headers/centralizado.tsx +71 -0
  150. package/template/components/chrome/headers/classico.tsx +64 -0
  151. package/template/components/chrome/headers/compacto.tsx +66 -0
  152. package/template/components/chrome/headers/equilibrado.tsx +73 -0
  153. package/template/components/chrome/headers/imersivo.tsx +62 -0
  154. package/template/components/chrome/registry.ts +70 -0
  155. package/template/components/chrome/solid-on-scroll.tsx +39 -0
  156. package/template/components/empty-state.tsx +22 -0
  157. package/template/components/home/combo-card-compact.tsx +67 -0
  158. package/template/components/home/combos-home.tsx +64 -0
  159. package/template/components/home/combos-section.tsx +177 -0
  160. package/template/components/home/home-recipe.ts +41 -0
  161. package/template/components/home/sections/attributes-marquee.tsx +178 -0
  162. package/template/components/home/sections/benefits.tsx +80 -0
  163. package/template/components/home/sections/bloco-html.tsx +47 -0
  164. package/template/components/home/sections/catalogo.ts +165 -0
  165. package/template/components/home/sections/category-pills.tsx +32 -0
  166. package/template/components/home/sections/combos-carousel.tsx +141 -0
  167. package/template/components/home/sections/comparison.tsx +73 -0
  168. package/template/components/home/sections/founder-story.tsx +40 -0
  169. package/template/components/home/sections/hero.tsx +187 -0
  170. package/template/components/home/sections/kits.tsx +11 -0
  171. package/template/components/home/sections/media-cards.tsx +70 -0
  172. package/template/components/home/sections/newsletter.tsx +62 -0
  173. package/template/components/home/sections/product-showcase.tsx +175 -0
  174. package/template/components/home/sections/purchase-hero.tsx +187 -0
  175. package/template/components/home/sections/quote-banner.tsx +50 -0
  176. package/template/components/home/sections/registry.ts +186 -0
  177. package/template/components/home/sections/reviews-carousel.tsx +66 -0
  178. package/template/components/home/sections/reviews.tsx +68 -0
  179. package/template/components/home/sections/ritual.tsx +96 -0
  180. package/template/components/home/sections/savings.tsx +47 -0
  181. package/template/components/home/sections/social-row.tsx +65 -0
  182. package/template/components/home/sections/spec-table.tsx +117 -0
  183. package/template/components/home/sections/stars.tsx +12 -0
  184. package/template/components/home/sections/stats-grid.tsx +54 -0
  185. package/template/components/home/sections/trust-bar.tsx +56 -0
  186. package/template/components/home/sections/trust-strip.tsx +40 -0
  187. package/template/components/home/sections/video-wall.tsx +59 -0
  188. package/template/components/landing/landing-recipe.ts +20 -0
  189. package/template/components/landing/oferta-sections.tsx +40 -0
  190. package/template/components/landing/product-picker.tsx +257 -0
  191. package/template/components/mobile-nav.tsx +71 -0
  192. package/template/components/order-status.tsx +163 -0
  193. package/template/components/powered-by-unbox.tsx +27 -0
  194. package/template/components/product/pdp/buy-box.tsx +604 -0
  195. package/template/components/product/pdp/catalog-grid.tsx +113 -0
  196. package/template/components/product/pdp/faq-modelo.ts +86 -0
  197. package/template/components/product/pdp/gallery.tsx +87 -0
  198. package/template/components/product/pdp/interactive.tsx +200 -0
  199. package/template/components/product/pdp/newsletter.tsx +64 -0
  200. package/template/components/product/pdp/payment-chips.tsx +22 -0
  201. package/template/components/product/pdp/pdp-view.tsx +227 -0
  202. package/template/components/product/pdp/recommendations.tsx +144 -0
  203. package/template/components/product/pdp/sections.tsx +173 -0
  204. package/template/components/quantity-stepper.tsx +36 -0
  205. package/template/components/search-box.tsx +37 -0
  206. package/template/components/site-footer.tsx +59 -0
  207. package/template/components/site-header.tsx +64 -0
  208. package/template/components/ui/accordion.tsx +72 -0
  209. package/template/components/ui/alert-dialog.tsx +187 -0
  210. package/template/components/ui/alert.tsx +76 -0
  211. package/template/components/ui/aspect-ratio.tsx +22 -0
  212. package/template/components/ui/avatar.tsx +109 -0
  213. package/template/components/ui/badge.tsx +52 -0
  214. package/template/components/ui/breadcrumb.tsx +125 -0
  215. package/template/components/ui/button.tsx +58 -0
  216. package/template/components/ui/card.tsx +103 -0
  217. package/template/components/ui/carousel.tsx +242 -0
  218. package/template/components/ui/command.tsx +196 -0
  219. package/template/components/ui/dialog.tsx +160 -0
  220. package/template/components/ui/dropdown-menu.tsx +268 -0
  221. package/template/components/ui/foto.tsx +54 -0
  222. package/template/components/ui/input-group.tsx +158 -0
  223. package/template/components/ui/input-otp.tsx +87 -0
  224. package/template/components/ui/input.tsx +20 -0
  225. package/template/components/ui/label.tsx +20 -0
  226. package/template/components/ui/navigation-menu.tsx +168 -0
  227. package/template/components/ui/pagination.tsx +132 -0
  228. package/template/components/ui/popover.tsx +90 -0
  229. package/template/components/ui/progress.tsx +83 -0
  230. package/template/components/ui/radio-group.tsx +38 -0
  231. package/template/components/ui/scroll-area.tsx +55 -0
  232. package/template/components/ui/select.tsx +201 -0
  233. package/template/components/ui/separator.tsx +25 -0
  234. package/template/components/ui/sheet.tsx +138 -0
  235. package/template/components/ui/skeleton.tsx +13 -0
  236. package/template/components/ui/sonner.tsx +44 -0
  237. package/template/components/ui/switch.tsx +32 -0
  238. package/template/components/ui/table.tsx +116 -0
  239. package/template/components/ui/tabs.tsx +82 -0
  240. package/template/components/ui/textarea.tsx +18 -0
  241. package/template/components/ui/toggle-group.tsx +89 -0
  242. package/template/components/ui/toggle.tsx +45 -0
  243. package/template/components/ui/tooltip.tsx +66 -0
  244. package/template/components.json +25 -0
  245. package/template/eslint.config.mjs +36 -0
  246. package/template/gitignore +21 -0
  247. package/template/lib/analytics.ts +369 -0
  248. package/template/lib/api.ts +60 -0
  249. package/template/lib/capi.ts +87 -0
  250. package/template/lib/cart-link.ts +51 -0
  251. package/template/lib/cart-normalize.ts +142 -0
  252. package/template/lib/cart-recovery.ts +23 -0
  253. package/template/lib/cart-response.ts +48 -0
  254. package/template/lib/catalog-map.ts +54 -0
  255. package/template/lib/catalog.ts +3 -0
  256. package/template/lib/checkout-lock.ts +21 -0
  257. package/template/lib/checkout-nav.ts +22 -0
  258. package/template/lib/config.ts +51 -0
  259. package/template/lib/crm.ts +40 -0
  260. package/template/lib/customer-session.ts +21 -0
  261. package/template/lib/dataloader.ts +23 -0
  262. package/template/lib/editable/config.ts +11 -0
  263. package/template/lib/editable/document.ts +1762 -0
  264. package/template/lib/editable/index.ts +3 -0
  265. package/template/lib/editable/primitives.tsx +821 -0
  266. package/template/lib/editable/provider.tsx +861 -0
  267. package/template/lib/editable/rastreio-navegacao.tsx +65 -0
  268. package/template/lib/editable/rastreio.tsx +168 -0
  269. package/template/lib/editable/server.ts +189 -0
  270. package/template/lib/editable/tokens.ts +22 -0
  271. package/template/lib/editable/verify.ts +24 -0
  272. package/template/lib/enrichment/combos.ts +184 -0
  273. package/template/lib/enrichment/index.ts +188 -0
  274. package/template/lib/enrichment/products.json +1 -0
  275. package/template/lib/env-check.ts +47 -0
  276. package/template/lib/format.ts +130 -0
  277. package/template/lib/icons.ts +67 -0
  278. package/template/lib/json-ld.ts +9 -0
  279. package/template/lib/llms-txt.ts +75 -0
  280. package/template/lib/mockup.ts +24 -0
  281. package/template/lib/newsletter.ts +12 -0
  282. package/template/lib/orders.ts +160 -0
  283. package/template/lib/queries.ts +66 -0
  284. package/template/lib/ratelimit.ts +47 -0
  285. package/template/lib/rotas-editaveis.ts +192 -0
  286. package/template/lib/sanitize.ts +36 -0
  287. package/template/lib/schemas.ts +78 -0
  288. package/template/lib/session.ts +98 -0
  289. package/template/lib/store-config.ts +67 -0
  290. package/template/lib/unbox/client.ts +829 -0
  291. package/template/lib/unbox/customer.ts +224 -0
  292. package/template/lib/unbox/errors.ts +97 -0
  293. package/template/lib/unbox/index.ts +10 -0
  294. package/template/lib/unbox/store.ts +113 -0
  295. package/template/lib/unbox/types.ts +195 -0
  296. package/template/lib/unbox/webhooks.ts +66 -0
  297. package/template/lib/utils.ts +6 -0
  298. package/template/lib/vitrine.ts +242 -0
  299. package/template/lib/webhook-store.ts +22 -0
  300. package/template/middleware.ts +178 -0
  301. package/template/next.config.ts +63 -0
  302. package/template/package.json +62 -0
  303. package/template/postcss.config.mjs +5 -0
  304. package/template/public/brand/coll/default.png +0 -0
  305. package/template/public/brand/hero-desktop.svg +12 -0
  306. package/template/public/brand/hero-mobile.svg +12 -0
  307. package/template/public/brand/heros/boutique-desktop.svg +11 -0
  308. package/template/public/brand/heros/boutique-mobile.svg +11 -0
  309. package/template/public/brand/heros/editorial-desktop.svg +11 -0
  310. package/template/public/brand/heros/editorial-mobile.svg +11 -0
  311. package/template/public/brand/heros/essencial-desktop.svg +12 -0
  312. package/template/public/brand/heros/essencial-mobile.svg +12 -0
  313. package/template/public/brand/heros/promocional-desktop.svg +12 -0
  314. package/template/public/brand/heros/promocional-mobile.svg +12 -0
  315. package/template/public/brand/logo-chrome.svg +6 -0
  316. package/template/public/brand/logo-white.svg +6 -0
  317. package/template/public/brand/logo.svg +7 -0
  318. package/template/public/brand/pay-amex.webp +0 -0
  319. package/template/public/brand/pay-elo.webp +0 -0
  320. package/template/public/brand/pay-mastercard.webp +0 -0
  321. package/template/public/brand/pay-pix.webp +0 -0
  322. package/template/public/brand/pay-visa.webp +0 -0
  323. package/template/public/brand/ph/avatar-a.svg +5 -0
  324. package/template/public/brand/ph/avatar-b.svg +5 -0
  325. package/template/public/brand/ph/avatar-c.svg +5 -0
  326. package/template/public/brand/ph/avatar-d.svg +5 -0
  327. package/template/public/brand/ph/photo-a.svg +7 -0
  328. package/template/public/brand/ph/photo-b.svg +6 -0
  329. package/template/public/brand/ph/photo-c.svg +6 -0
  330. package/template/public/brand/ph/poster-a.svg +6 -0
  331. package/template/public/brand/ph/poster-b.svg +6 -0
  332. package/template/public/brand/ph/poster-c.svg +6 -0
  333. package/template/public/unbox/powered-by-fundo-preto.png +0 -0
  334. package/template/public/unbox/powered-by-transparente.png +0 -0
  335. package/template/public/unbox/powered-by.png +0 -0
  336. package/template/scripts/abandoned-cart.ts +82 -0
  337. package/template/scripts/check-editable.mjs +467 -0
  338. package/template/scripts/check-honestidade.mjs +111 -0
  339. package/template/scripts/check-placeholder.mjs +200 -0
  340. package/template/scripts/check-recipe.mjs +76 -0
  341. package/template/scripts/check-unbox-brand.mjs +298 -0
  342. package/template/scripts/contraste.js +177 -0
  343. package/template/scripts/dump-catalog.ts +95 -0
  344. package/template/scripts/load-env.ts +13 -0
  345. package/template/scripts/medir-sistema.mjs +56 -0
  346. package/template/scripts/place-order-pix.ts +61 -0
  347. package/template/scripts/qa-screenshots.mjs +150 -0
  348. package/template/scripts/sistema.py +231 -0
  349. package/template/scripts/subscribe-webhook.ts +36 -0
  350. package/template/scripts/test-live.ts +186 -0
  351. package/template/scripts/vocabulario.py +264 -0
  352. package/template/tsconfig.json +27 -0
  353. package/tools/LEIA-ME.md +39 -0
  354. package/tools/check-template-neutro.mjs +111 -0
  355. package/tools/notion-doc.mjs +191 -0
  356. package/tools/tokenize-neutrals.mjs +0 -0
  357. package/tools/tokenize-radius.mjs +52 -0
  358. package/tools/workflow-storefront.legado.js +1207 -0
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env python3
2
+ """Cobertura do vocabulário gráfico — a medida do degrau 1 da escada.
3
+
4
+ POR QUE ISTO EXISTE
5
+ "Cara de AI" não é falta de capricho: é variedade sem sistema. O olho lê repetição
6
+ como intenção e variedade como acidente. Em TRÊS marcas diferentes, a virada de "podia
7
+ ser de qualquer um" para "tem dono" veio do mesmo movimento — um punhado de marcas
8
+ gráficas próprias, repetidas em toda seção:
9
+
10
+ Bicafé .pilula .selo .micro .numeral .regua ("é barato e é o que faz o site
11
+ ter dono")
12
+ Zé Tona código de cor por tampa em TODO componente que lista produto
13
+ Oddie o estilo adesivo com contorno
14
+
15
+ E o defeito que este script pega não é a AUSÊNCIA do vocabulário — é ele existir e não
16
+ ter sido distribuído. Medido na Pipó em 04/09/2026:
17
+
18
+ pipo-rotulo 24 usos <- voz tipográfica: distribuída
19
+ pipo-display 16 usos
20
+ pipo-filete 1 uso <- filete dourado 44x2px: a marca da casa
21
+ pipo-roda 1 uso usada UMA vez cada
22
+ pipo-pop 1 uso
23
+ pipo-parallax 0 usos <- definida e nunca usada
24
+
25
+ Uma linha dourada numa seção é decoração. A mesma linha em nove seções é assinatura.
26
+
27
+ O QUE ELE MEDE
28
+ 1. o vocabulário declarado (classes `.<prefixo>-*` no CSS da marca)
29
+ 2. quantas vezes cada uma é de fato usada, e quais estão mortas
30
+ 3. COBERTURA POR SEÇÃO: quantas seções da receita carregam pelo menos uma marca da casa
31
+ — uma seção sem nenhuma é uma seção que poderia estar em qualquer loja
32
+ 4. cobertura por página (home / catálogo / produto / carrinho), que é onde a assinatura
33
+ costuma parar: o vocabulário chega na home e não atravessa para as internas
34
+
35
+ USO
36
+ vocabulario.py <dir-do-projeto> [--prefixo pipo] [--cobertura 0.7] [--minimo 3]
37
+
38
+ CÓDIGOS DE SAÍDA
39
+ 0 o vocabulário está distribuído
40
+ 1 existe vocabulário, mas ele não cobre a loja
41
+ 2 O GATE NÃO RODOU (caminho errado, sem CSS, ou nenhuma classe de marca encontrada)
42
+
43
+ O 2 é o mesmo do escala.py: gate que varre o vazio e diz "limpo" aprova sem ter olhado.
44
+ """
45
+ import re
46
+ import sys
47
+ import pathlib
48
+ from collections import Counter
49
+
50
+ # Ancorado na RAIZ do projeto de proposito: `marca/` (briefing) se ignora, mas
51
+ # `components/marca/` e onde vivem os componentes de marca — foi la que a primeira
52
+ # versao deste script perdeu a `pipo-roda` e a declarou morta.
53
+ IGNORAR_RAIZ = {"node_modules", ".next", ".git", "shots", "reviews", "public", "marca"}
54
+
55
+ # Classes utilitárias do próprio framework não são vocabulário de marca: elas resolvem
56
+ # layout, não identidade. Quem assina a página é o filete, a roda, o selo — não o
57
+ # container da seção.
58
+ SUFIXO_UTILITARIO = re.compile(r"-(secao|tela|trilho|container|grid|wrap|layout|sr)$")
59
+
60
+ RE_CLASSE_CSS = re.compile(r"^\.([a-z][a-z0-9]*)-([a-z0-9-]+)\s*(?:,|\{|::|:)", re.M)
61
+
62
+ # Nem toda marca da casa e uma classe CSS. Na Pipo, a chuva de pipoca e a roda
63
+ # tipografica sao COMPONENTES em components/marca/ — a primeira versao deste script
64
+ # so contava classes e reportou o vocabulario como muito menos distribuido do que
65
+ # ele esta: a chuva aparece em 8 lugares, nao em 1.
66
+ DIR_COMPONENTES_MARCA = "components/marca"
67
+ RE_COMPONENTE = re.compile(r"export\s+(?:default\s+)?function\s+([A-Z][A-Za-z0-9]*)")
68
+
69
+
70
+ def arquivos(raiz, exts):
71
+ for p in raiz.rglob("*"):
72
+ if p.suffix.lower() not in exts:
73
+ continue
74
+ rel = p.relative_to(raiz).parts
75
+ if rel and rel[0] in IGNORAR_RAIZ:
76
+ continue
77
+ yield p
78
+
79
+
80
+ def descobrir_vocabulario(raiz, prefixo=None):
81
+ """Lê o CSS e devolve (prefixo, {classe: descricao curta})."""
82
+ css = list(arquivos(raiz, {".css"}))
83
+ if not css:
84
+ return None, {}
85
+
86
+ achadas = Counter()
87
+ por_prefixo = {}
88
+ for p in css:
89
+ t = p.read_text(encoding="utf-8", errors="ignore")
90
+ for m in RE_CLASSE_CSS.finditer(t):
91
+ pre, resto = m.group(1), m.group(2)
92
+ achadas[pre] += 1
93
+ por_prefixo.setdefault(pre, set()).add(f"{pre}-{resto}")
94
+
95
+ if prefixo is None:
96
+ # O prefixo da casa é o que tem mais classes distintas — `pipo-`, `bic-`, `ze-`.
97
+ # Prefixos de framework (tw, sm, md) não chegam perto em variedade.
98
+ candidatos = sorted(por_prefixo.items(), key=lambda kv: -len(kv[1]))
99
+ if not candidatos or len(candidatos[0][1]) < 3:
100
+ return None, {}
101
+ prefixo = candidatos[0][0]
102
+
103
+ return prefixo, sorted(por_prefixo.get(prefixo, set()))
104
+
105
+
106
+ def componentes_de_marca(raiz):
107
+ """Componentes exportados de components/marca/ — marcas da casa que nao sao classe."""
108
+ base = raiz / DIR_COMPONENTES_MARCA
109
+ nomes = set()
110
+ if base.is_dir():
111
+ for p in base.rglob("*.tsx"):
112
+ nomes |= set(RE_COMPONENTE.findall(p.read_text(encoding="utf-8", errors="ignore")))
113
+ return sorted(nomes)
114
+
115
+
116
+ def usos(raiz, classes, comps=()):
117
+ """Conta uso de cada marca (classe OU componente) e mapeia arquivo -> marcas."""
118
+ contagem = Counter()
119
+ por_arquivo = {}
120
+ base_marca = (raiz / DIR_COMPONENTES_MARCA).resolve()
121
+ for p in arquivos(raiz, {".tsx", ".jsx"}):
122
+ # a definicao do componente nao conta como uso dele
123
+ dentro_da_definicao = str(p.resolve()).startswith(str(base_marca))
124
+ t = p.read_text(encoding="utf-8", errors="ignore")
125
+ presentes = set()
126
+ for c in classes:
127
+ n = len(re.findall(r"\b" + re.escape(c) + r"\b", t))
128
+ if n:
129
+ contagem[c] += n
130
+ presentes.add(c)
131
+ if not dentro_da_definicao:
132
+ for c in comps:
133
+ n = len(re.findall(r"<" + re.escape(c) + r"\b", t))
134
+ if n:
135
+ contagem[c] += n
136
+ presentes.add(c)
137
+ if presentes:
138
+ por_arquivo[p] = presentes
139
+ return contagem, por_arquivo
140
+
141
+
142
+ def secoes_da_receita(raiz):
143
+ for nome in ("components/home/home-recipe.ts", "components/home/home-recipe.tsx"):
144
+ p = raiz / nome
145
+ if p.exists():
146
+ t = p.read_text(encoding="utf-8", errors="ignore")
147
+ return re.findall(r"\bsection:\s*[\"']([^\"']+)[\"']", t)
148
+ return []
149
+
150
+
151
+ def arquivo_da_secao(raiz, nome):
152
+ for cand in (
153
+ raiz / "components/home/sections" / f"{nome}.tsx",
154
+ raiz / "components/home" / f"{nome}.tsx",
155
+ ):
156
+ if cand.exists():
157
+ return cand
158
+ return None
159
+
160
+
161
+ PAGINAS = {
162
+ "home": ["components/home"],
163
+ "catálogo (PLP)": ["components/catalog", "app/(loja)/produtos"],
164
+ "produto (PDP)": ["components/product", "app/(loja)/produto"],
165
+ "carrinho": ["components/cart", "app/(loja)/carrinho"],
166
+ }
167
+
168
+
169
+ def main():
170
+ args = sys.argv[1:]
171
+ if not args:
172
+ sys.exit(__doc__)
173
+ raiz = pathlib.Path(args[0]).resolve()
174
+ prefixo = args[args.index("--prefixo") + 1] if "--prefixo" in args else None
175
+ alvo_cob = float(args[args.index("--cobertura") + 1]) if "--cobertura" in args else 0.7
176
+ minimo = int(args[args.index("--minimo") + 1]) if "--minimo" in args else 3
177
+
178
+ if not raiz.is_dir():
179
+ print(f"ERRO: {raiz} nao e um diretorio — o gate NAO RODOU", file=sys.stderr)
180
+ sys.exit(2)
181
+
182
+ prefixo, classes = descobrir_vocabulario(raiz, prefixo)
183
+ if not classes:
184
+ print(
185
+ "ERRO: nenhum vocabulario de marca encontrado no CSS — o gate NAO RODOU.\n"
186
+ " Ou a loja nao tem vocabulario proprio (que ja e o achado do degrau 1),\n"
187
+ " ou o prefixo precisa vir em --prefixo.",
188
+ file=sys.stderr,
189
+ )
190
+ sys.exit(2)
191
+
192
+ comps = componentes_de_marca(raiz)
193
+ contagem, por_arquivo = usos(raiz, classes, comps)
194
+
195
+ # marcas gráficas = o vocabulário menos os utilitários de layout, mais os componentes
196
+ marcas = [c for c in classes if not SUFIXO_UTILITARIO.search(c)] + list(comps)
197
+ mortas = [c for c in marcas if contagem[c] == 0]
198
+ unicas = [c for c in marcas if contagem[c] == 1]
199
+
200
+ print(f"Vocabulario grafico: {len(classes)} classes `{prefixo}-*` + {len(comps)} componentes de marca\n")
201
+ for c in sorted(marcas, key=lambda x: -contagem[x]):
202
+ n = contagem[c]
203
+ estado = "MORTA " if n == 0 else ("uso unico" if n == 1 else "")
204
+ print(f" {c:<28}{n:>4} usos {estado}")
205
+
206
+ ok = True
207
+
208
+ # ── cobertura por seção da home ───────────────────────────────────────────
209
+ secoes = secoes_da_receita(raiz)
210
+ if secoes:
211
+ sem_marca = []
212
+ for s in secoes:
213
+ f = arquivo_da_secao(raiz, s)
214
+ if f is None:
215
+ continue
216
+ if not (por_arquivo.get(f, set()) & set(marcas)):
217
+ sem_marca.append(s)
218
+ cobertas = len(secoes) - len(sem_marca)
219
+ frac = cobertas / len(secoes) if secoes else 0
220
+ print(f"\nCobertura da home: {cobertas}/{len(secoes)} secoes carregam ao menos uma marca da casa")
221
+ if sem_marca:
222
+ print(" sem nenhuma marca (poderiam estar em qualquer loja):")
223
+ for s in sem_marca:
224
+ print(f" - {s}")
225
+ if frac < alvo_cob:
226
+ print(f" ABAIXO DO ALVO ({frac:.0%} < {alvo_cob:.0%})")
227
+ ok = False
228
+ else:
229
+ print("\n(sem receita de home encontrada — cobertura por secao nao medida)")
230
+
231
+ # ── cobertura por página ──────────────────────────────────────────────────
232
+ print("\nCobertura por pagina:")
233
+ for pagina, dirs in PAGINAS.items():
234
+ marcas_ali = set()
235
+ for d in dirs:
236
+ base = raiz / d
237
+ if not base.exists():
238
+ continue
239
+ for f, cs in por_arquivo.items():
240
+ try:
241
+ f.relative_to(base)
242
+ except ValueError:
243
+ continue
244
+ marcas_ali |= cs & set(marcas)
245
+ estado = "" if len(marcas_ali) >= minimo else f" <- abaixo de {minimo}"
246
+ print(f" {pagina:<18}{len(marcas_ali):>3} marcas distintas{estado}")
247
+ if len(marcas_ali) < minimo:
248
+ ok = False
249
+
250
+ if mortas or unicas:
251
+ print()
252
+ if mortas:
253
+ print(f" {len(mortas)} marca(s) MORTA(S): " + ", ".join(mortas))
254
+ ok = False
255
+ if unicas:
256
+ print(f" {len(unicas)} marca(s) de USO UNICO: " + ", ".join(unicas))
257
+ print(" uma marca usada uma vez e decoracao; usada em toda secao e assinatura.")
258
+ ok = False
259
+
260
+ sys.exit(0 if ok else 1)
261
+
262
+
263
+ if __name__ == "__main__":
264
+ main()
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,39 @@
1
+ # tools/ — utilitários de desenvolvimento do CLI
2
+
3
+ Nada aqui é publicado: o `package.json` só empacota `bin`, `src` e `template`.
4
+
5
+ - `tokenize-neutrals.mjs` / `tokenize-radius.mjs` — scripts de migração usados uma vez na
6
+ tokenização da foundation (v0.6).
7
+ - `workflow-storefront.legado.js` — orquestrador multiagente da era "construir um storefront
8
+ do zero, sem o CLI". **Saiu do `template/` na v0.15.0** por três motivos: o próprio
9
+ `agents/MANAGER.md` já dizia que ele não se aplica a quem usa o CLI; ele custava ~11k tokens
10
+ de contexto se o agente o abrisse por engano, com "Regras de Ouro" divergentes das do
11
+ MANAGER; e as linhas 13-15 tinham caminhos absolutos da máquina do desenvolvedor, que
12
+ viajavam para dentro do projeto de todo cliente.
13
+
14
+ Fica aqui como referência: o padrão dele (agentes em paralelo que NÃO escrevem em disco,
15
+ devolvem `{path, content}` por schema, e um passo serial grava) é o que a fase multiagente
16
+ do briefing deve adotar.
17
+
18
+ ## `notion-doc.mjs` — a página do Notion
19
+
20
+ Gera o conteúdo da página **create-unbox-store — CLI de geração de loja**, em
21
+ `Documentações → Lojas Store` do Notion de tecnologia:
22
+
23
+ `https://app.notion.com/p/3cbf44e324da8125a0afd7fbe6916247`
24
+
25
+ ```bash
26
+ node tools/notion-doc.mjs
27
+ ```
28
+
29
+ A versão sai do `package.json` e o changelog é extraído do `README.md` — as duas únicas
30
+ fontes da verdade, então a página não pode divergir do pacote. A prosa de apresentação é
31
+ curada dentro do script (uma página para o time não é um despejo do README); as entradas
32
+ `### vX.Y.Z` viram toggles, e a era "Beta" fica condensada em uma linha por versão dentro de
33
+ um toggle único, com o texto integral seguindo no README.
34
+
35
+ **Depois de todo release:** rode o comando e substitua o conteúdo da página com a saída. O
36
+ próprio topo da página diz isso, para quem chegar por lá não editar o changelog à mão.
37
+
38
+ O link fica aqui e não no `README.md` de propósito: aquele README viaja dentro do zip
39
+ entregue ao cliente, e não deve apontar para o Notion interno da Unbox.
@@ -0,0 +1,111 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════════════
2
+ // GATE DE NEUTRALIDADE DO TEMPLATE — roda no `prepack`, e o pack FALHA se reprovar.
3
+ //
4
+ // Por que existe: a foundation nasceu de uma loja de temperos e, por duas varreduras, ainda
5
+ // entregava loja de outro ramo com "polvilhe sobre o alimento", "escolha o molho", aba
6
+ // nutricional com traços e selo "Sem glúten" inventado. As varreduras anteriores olharam COR
7
+ // e NOME DE CLIENTE; o que vazava era VOCABULÁRIO DE DOMÍNIO. Este gate olha isso.
8
+ //
9
+ // Por que aqui e NÃO no projeto gerado: uma loja de alimento pode e deve dizer "polvilhe"
10
+ // depois do briefing. O que não pode é a FOUNDATION dizer. Então a checagem é no repositório
11
+ // do CLI, antes de empacotar — e nunca viaja para o cliente.
12
+ //
13
+ // Escopo: código e docs que o agente lê ou o cliente vê. Comentário conta: comentário
14
+ // ensina o agente a escrever igual.
15
+ // ═══════════════════════════════════════════════════════════════════════════════════════
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ const RAIZ = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
21
+ const TEMPLATE = path.join(RAIZ, "template");
22
+ const EXTS = new Set([".ts", ".tsx", ".md", ".json", ".css", ".svg", ".txt", ".mjs"]);
23
+ const IGNORAR_DIRS = new Set(["node_modules", ".next", "qa"]);
24
+
25
+ // [regex, por quê]. Só termos SEM uso legítimo numa foundation neutra. "alimento",
26
+ // "nutricional", "glúten" ficam de fora de propósito: aparecem em código CONDICIONAL correto
27
+ // ("tabela nutricional só se for alimento") e o gate não pode punir isso.
28
+ const PROIBIDOS = [
29
+ [/\bbadia\b|\bblessy\b|\bzetona\b|\bzétona\b|\boddie\b/i, "nome de cliente"],
30
+ [/polvilh/i, "instrução de uso de tempero"],
31
+ [/\btemperos?\b|\btempere\b|\btemperad[oa]s?\b/i, "vocabulário de tempero"],
32
+ [/\bespeciarias?\b|\bcondimentos?\b/i, "vocabulário de tempero"],
33
+ [/p[aá]prica|or[eé]gano|cominho|a[cç]afr[aã]o|chimichurri|lemon pepper|sriracha|garlic|alho em p[oó]/i, "produto de tempero"],
34
+ [/\bmolhos?\b(?! de contato)/i, "produto de alimentação"],
35
+ [/marinad[ao]s?|churrasco|festa junina/i, "contexto de alimentação"],
36
+ [/\bsabor(es)?\b/i, "copy de alimentação ('mais sabor')"],
37
+ [/porção 1 ?g\b/i, "porção de tempero cravada"],
38
+ [/Cliente [A-C]\. .*(tempero|prato|receita de)/i, "depoimento de alimentação"],
39
+ ];
40
+
41
+ // Trechos permitidos: a explicação do próprio bug, em comentário, precisa poder citar a
42
+ // palavra uma vez. Mantém a lista CURTA — todo item aqui é uma exceção que alguém revisou.
43
+ const PERMITIDOS = [
44
+ "tools/", // este diretório
45
+ "README.md::### v0.15", // changelog descreve o bug
46
+ ];
47
+
48
+ const achados = [];
49
+ function varrer(dir) {
50
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
51
+ const p = path.join(dir, ent.name);
52
+ if (ent.isDirectory()) { if (!IGNORAR_DIRS.has(ent.name)) varrer(p); continue; }
53
+ if (!EXTS.has(path.extname(ent.name))) continue;
54
+ const rel = path.relative(RAIZ, p);
55
+ const linhas = fs.readFileSync(p, "utf8").split("\n");
56
+ linhas.forEach((linha, i) => {
57
+ for (const [re, motivo] of PROIBIDOS) {
58
+ if (!re.test(linha)) continue;
59
+ const chave = `${rel}::${linha.trim().slice(0, 30)}`;
60
+ if (PERMITIDOS.some((ok) => rel.startsWith(ok) || chave.startsWith(ok))) continue;
61
+ achados.push({ rel, n: i + 1, motivo, texto: linha.trim().slice(0, 110) });
62
+ break;
63
+ }
64
+ });
65
+ }
66
+ }
67
+ varrer(TEMPLATE);
68
+
69
+ // ── Travessão no texto que o CLI IMPRIME ─────────────────────────────────────
70
+ // O template tem gate próprio (scripts/check-unbox-brand.mjs). Aqui pega o outro lado: as
71
+ // perguntas, avisos e mensagens do wizard, e os cabeçalhos que o CLI escreve dentro do projeto.
72
+ // Comentário de código fica de fora: é nota de engenharia, não texto para quem lê a tela.
73
+ function semComentario(linha, dentroDeBloco) {
74
+ let out = "", aspas = null, bloco = dentroDeBloco;
75
+ for (let i = 0; i < linha.length; i++) {
76
+ const c = linha[i], prox = linha[i + 1];
77
+ if (bloco) { if (c === "*" && prox === "/") { bloco = false; i++; } continue; }
78
+ if (aspas) {
79
+ if (c === "\\") { out += c + (prox ?? ""); i++; continue; }
80
+ if (c === aspas) aspas = null;
81
+ out += c; continue;
82
+ }
83
+ if (c === '"' || c === "'" || c === "`") { aspas = c; out += c; continue; }
84
+ if (c === "/" && prox === "/") break;
85
+ if (c === "/" && prox === "*") { bloco = true; i++; continue; }
86
+ out += c;
87
+ }
88
+ return { texto: out, bloco };
89
+ }
90
+ for (const arquivo of ["bin/cli.js", "src/theme.js", "src/presets.js", "src/colors.js"]) {
91
+ const caminho = path.join(RAIZ, arquivo);
92
+ if (!fs.existsSync(caminho)) continue;
93
+ let bloco = false;
94
+ fs.readFileSync(caminho, "utf8").split("\n").forEach((linha, i) => {
95
+ const r = semComentario(linha, bloco);
96
+ bloco = r.bloco;
97
+ if (r.texto.includes("\u2014")) {
98
+ achados.push({ rel: arquivo, n: i + 1, motivo: "travessão no texto impresso pelo CLI", texto: linha.trim().slice(0, 110) });
99
+ }
100
+ });
101
+ }
102
+
103
+ if (achados.length) {
104
+ console.error(`\n✗ PACK BLOQUEADO — a foundation tem ${achados.length} resíduo(s) de domínio:\n`);
105
+ for (const a of achados) console.error(` ${a.rel}:${a.n} [${a.motivo}]\n ${a.texto}`);
106
+ console.error("\n A foundation é neutra: nada de vocabulário de um ramo específico, nem em comentário.");
107
+ console.error(" E o texto que o CLI imprime não usa travessão: vírgula, dois-pontos, ponto ou \"·\".");
108
+ console.error(" Loja de alimento escreve isso DEPOIS, no briefing. Corrija e rode de novo.\n");
109
+ process.exit(1);
110
+ }
111
+ console.log("✓ Foundation neutra (sem vocabulário de domínio nem nome de cliente)");
@@ -0,0 +1,191 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════════════
2
+ // GERADOR DA PÁGINA DO NOTION — node tools/notion-doc.mjs
3
+ //
4
+ // Emite o conteúdo da página "create-unbox-store — CLI de geração de loja" em Notion-flavored
5
+ // Markdown, pronto pra ser passado ao update-page do MCP do Notion.
6
+ //
7
+ // Existe pra que "manter a página atualizada" seja um comando, não um trabalho manual que
8
+ // ninguém lembra de fazer no dia do release. A versão vem do package.json e o changelog é
9
+ // extraído do README.md — as duas únicas fontes da verdade. Rode depois de todo release e
10
+ // atualize a página com a saída.
11
+ //
12
+ // A prosa de apresentação é curada aqui (uma página de Notion para o time não é um despejo
13
+ // do README). O changelog é mecânico: toda entrada "### vX.Y.Z" do README vira um toggle.
14
+ // ═══════════════════════════════════════════════════════════════════════════════════════
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const RAIZ = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
20
+ const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, "package.json"), "utf8"));
21
+ const readme = fs.readFileSync(path.join(RAIZ, "README.md"), "utf8");
22
+ const VERSAO = pkg.version;
23
+
24
+ // ── Changelog: extrai as entradas "### vX" / "### Beta vX" do README ────────────────────
25
+ function extrairChangelog() {
26
+ const linhas = readme.split("\n");
27
+ const ini = linhas.findIndex((l) => l.trim() === "## Changelog");
28
+ if (ini === -1) throw new Error("seção '## Changelog' não encontrada no README.md");
29
+
30
+ const entradas = [];
31
+ let atual = null;
32
+ for (const linha of linhas.slice(ini + 1)) {
33
+ const m = linha.match(/^### (.+)$/);
34
+ if (m) {
35
+ if (atual) entradas.push(atual);
36
+ atual = { titulo: m[1].trim(), corpo: [] };
37
+ } else if (atual) {
38
+ atual.corpo.push(linha);
39
+ }
40
+ }
41
+ if (atual) entradas.push(atual);
42
+ return entradas;
43
+ }
44
+
45
+ // Markdown do README → Notion-flavored. As diferenças que importam:
46
+ // • parágrafo quebrado em várias linhas vira UMA linha (no Notion, \n encerra o bloco);
47
+ // • item de lista com continuação indentada é juntado no mesmo item;
48
+ // • o resto (negrito, código inline, links) é compatível.
49
+ function paraNotion(corpo) {
50
+ const out = [];
51
+ for (const bruta of corpo) {
52
+ const linha = bruta.replace(/\s+$/, "");
53
+ if (!linha.trim()) { out.push(""); continue; }
54
+ const ehItem = /^\s*[-*]\s+/.test(linha);
55
+ const ehTitulo = /^#{1,6}\s/.test(linha);
56
+ const ehCodigo = /^\s*```/.test(linha);
57
+ const continuacao = /^\s+\S/.test(linha) && !ehItem && !ehCodigo;
58
+ if ((continuacao || (!ehItem && !ehTitulo && !ehCodigo)) && out.length && out[out.length - 1]) {
59
+ // continua o bloco anterior em vez de abrir um novo
60
+ const ant = out[out.length - 1];
61
+ if (!/^\s*```/.test(ant)) { out[out.length - 1] = ant + " " + linha.trim(); continue; }
62
+ }
63
+ out.push(ehItem ? "- " + linha.replace(/^\s*[-*]\s+/, "") : linha.trim());
64
+ }
65
+ return escaparAngulos(out.join("\n").replace(/\n{3,}/g, "\n\n").trim());
66
+ }
67
+
68
+ // O Notion lê "<algo>" fora de crase como bloco/tag XML. Escapa só o que está solto —
69
+ // dentro de crase (código inline e blocos) o conteúdo é literal e não pode ser tocado.
70
+ function escaparAngulos(texto) {
71
+ const partes = texto.split(/(```[\s\S]*?```|`[^`\n]*`)/g);
72
+ return partes.map((p, i) => (i % 2 ? p : p.replace(/</g, "\\<").replace(/>/g, "\\>"))).join("");
73
+ }
74
+
75
+ const entradas = extrairChangelog();
76
+ // Detalhe integral só na era da arquitetura atual (v0.12.13+, quando o versionamento foi
77
+ // unificado). O histórico anterior vira uma linha por versão dentro de um toggle: ninguém lê
78
+ // 35 changelogs completos, e o texto integral continua no README do pacote.
79
+ const DETALHADAS = entradas.findIndex((e) => /^Beta v/.test(e.titulo));
80
+ const corte = DETALHADAS === -1 ? entradas.length : DETALHADAS;
81
+
82
+ const detalhadas = entradas.slice(0, corte).map((e, i) => {
83
+ const corpo = paraNotion(e.corpo);
84
+ const marca = i === 0 ? " · atual" : "";
85
+ return `### ${e.titulo}${marca} {toggle="true"}\n` +
86
+ corpo.split("\n").map((l) => (l ? "\t" + l : "")).join("\n");
87
+ }).join("\n");
88
+
89
+ // Uma linha por versão antiga: título + primeira frase do corpo.
90
+ const antigas = entradas.slice(corte).map((e) => {
91
+ const texto = paraNotion(e.corpo)
92
+ .split("\n").map((l) => l.replace(/^-\s*/, "").trim()).filter(Boolean)[0] ?? "";
93
+ let resumo = texto.split(/(?<=\.)\s/)[0].slice(0, 170).replace(/[\s:—-]+$/, "");
94
+ if (resumo.length >= 170) resumo += "…";
95
+ return `- **${e.titulo}** — ${resumo}`;
96
+ }).join("\n");
97
+
98
+ const changelog = detalhadas + `
99
+
100
+ <details>
101
+ <summary>Histórico anterior (${entradas.length - corte} versões, era "Beta")</summary>
102
+ ` + antigas.split("\n").map((l) => "\t" + l).join("\n") + `
103
+ \t
104
+ \tO texto integral de cada uma está no \`README.md\` dentro do pacote.
105
+ </details>`;
106
+
107
+ // ── A página ────────────────────────────────────────────────────────────────────────────
108
+ const pagina = `<callout icon="📦" color="blue_bg">
109
+ **Versão atual: v${VERSAO}** — entregue como \`CLI - Unbox v${VERSAO}.zip\`, que contém \`create-unbox-store.tgz\` e o README completo.
110
+ O nome do tarball é sempre \`create-unbox-store.tgz\`, **sem versão** — a versão fica dentro do pacote. Zips antigos traziam a versão no nome do arquivo e isso gerava erro de \`npx\` quando o comando não batia com o nome real.
111
+ </callout>
112
+
113
+ <callout icon="🔄" color="gray_bg">
114
+ Esta página é gerada a partir do \`README.md\` e do \`package.json\` do CLI. Para atualizá-la depois de um release, rode \`node tools/notion-doc.mjs\` no repositório do CLI e substitua o conteúdo com a saída. Não edite o changelog aqui à mão: ele será sobrescrito.
115
+ </callout>
116
+
117
+ ## O que é
118
+
119
+ CLI de dependência zero (só \`prompts\`) que gera um **storefront Next.js 15 completo**, integrado com a API headless da Unbox, já com a marca do cliente aplicada: layout, fontes, neutros, cores e nome da loja.
120
+
121
+ O que sai do CLI é **fundação**, não loja pronta. A personalização de verdade acontece logo depois, no briefing de marca conduzido pelo Claude Code — e é de propósito: o cliente é entrevistado **antes** de ver qualquer coisa, para não ancorar a expectativa dele no padrão.
122
+
123
+ ## Como usar
124
+
125
+ Único pré-requisito da máquina: **Node.js** ([nodejs.org](https://nodejs.org), botão LTS). Coloque o \`create-unbox-store.tgz\` na pasta onde quer criar o projeto e rode:
126
+
127
+ \`\`\`bash
128
+ npx --package=./create-unbox-store.tgz create-unbox-store
129
+ \`\`\`
130
+
131
+ <callout icon="⚠️" color="yellow_bg">
132
+ Use sempre a forma com \`--package=\`. O \`npx ./create-unbox-store.tgz\` sem ela dá erro de permissão em algumas versões do npm.
133
+ </callout>
134
+
135
+ O CLI pergunta 12 coisas: nome do projeto e da loja, cor primária e de CTA, site e Instagram da marca, objetivo em uma frase, **estilo visual** (Essencial, Promocional, Editorial ou Boutique), credenciais da Unbox, tipo de checkout, o segredo de captcha (só com key de parceiro) e se roda \`npm install\`. Tudo menos as credenciais fica em \`marca/briefing.json\`, para o agente de marca não reperguntar.
136
+
137
+ Sem credenciais em mãos, responda "não": o projeto sobe em **modo mockup** com o layout completo e sem dados reais. Preenche o \`.env.local\` depois.
138
+
139
+ ### O passo seguinte não é \`npm run dev\`
140
+
141
+ \`\`\`bash
142
+ cd <nome-do-projeto>
143
+ claude
144
+ \`\`\`
145
+
146
+ O Claude Code abre direto no briefing de marca e conduz a personalização. Encerrado o briefing, o próprio agente remove a chave que força esse modo e as sessões seguintes voltam ao normal.
147
+
148
+ ### Sem interação (CI ou testes)
149
+
150
+ \`\`\`bash
151
+ npx --package=./create-unbox-store.tgz create-unbox-store minha-loja --yes --no-install
152
+ \`\`\`
153
+
154
+ Use \`--estilo <essencial|promocional|editorial|boutique>\` para escolher o preset sem interação.
155
+
156
+ ## O que o projeto gerado traz
157
+
158
+ Next.js 15 (App Router) + Tailwind v4 + shadcn/ui sobre Base UI, com catálogo, carrinho, checkout, área do cliente (login por OTP, pedidos, assinaturas, endereços), páginas legais, PWA e SEO básico já implementados. \`DEPLOY.md\` e \`QA.md\` dentro do projeto trazem o checklist de publicação na Vercel.
159
+
160
+ O projeto também vem com um \`CLAUDE.md\` que mapeia "quero mudar X, mexo em Y", e com \`agents/MANAGER.md\`, que descreve a ordem dos passos: **15 (Branding) primeiro**, depois os opcionais 13 (CRO) e 14 (SEO), e por último 12 (Deploy).
161
+
162
+ ### Portões de qualidade
163
+
164
+ | Comando | O que faz |
165
+ | --- | --- |
166
+ | \`npm run typecheck\` | o gate mais barato, rode primeiro |
167
+ | \`npm run build\` | dispara o prebuild de marca (bloqueia sem o selo Powered by Unbox ou sem o GTM) |
168
+ | \`npm run unbox:honestidade\` | promessa comercial ou prova social sem lastro |
169
+ | \`npm run unbox:receita\` | variedade da composição da home (mede, não bloqueia) |
170
+ | \`npm run unbox:qa\` | screenshots do QA com emulação de dispositivo |
171
+ | \`npm run unbox:dump\` | imprime o catálogo e as promoções **reais** da loja |
172
+
173
+ ## Antes de publicar — decisões que não são do CLI
174
+
175
+ A fundação traz placeholders que funcionam mas **precisam de uma decisão consciente**. O CLI não resolve isso sozinho de propósito: seria tomar a decisão no lugar de quem publica.
176
+
177
+ 1. **Prova social: a foundation não traz nenhuma.** Sem avaliação real em \`lib/enrichment/products.json\`, a loja não mostra nota, estrelas, contagem nem depoimento (o bloco some, não cai em placeholder). Decida antes de publicar: popular com dados reais ou publicar sem prova social. O agente de Branding pede esse material no briefing; o \`unbox:honestidade\` lista qualquer número ou promessa que entrar sem lastro.
178
+ 2. **CRO: 1 de 10 módulos na fundação.** Só a barra de frete grátis vem pronta. Os outros nove são do agente 13, que o briefing oferece ao terminar (o wizard não pergunta mais sobre isso).
179
+ 3. **SEO avançado: o básico está, os 7 módulos não.** JSON-LD de produto, Organization + WebSite/SearchAction, imagem Open Graph gerada e canonical por página já vêm. Breadcrumbs, FAQ schema, OG por produto e templates de título/description são do agente 14.
180
+ 4. **Kits e combos vêm vazios.** A seção só aparece depois que alguém popular os kits reais.
181
+
182
+ <callout icon="🧭" color="gray_bg">
183
+ A regra que orienta tudo isso: **um campo vazio declarado é melhor que um campo plausível inventado.** Quando falta material real, o certo é registrar a pendência, não preencher com algo verossímil. É no briefing e na fundação que um valor plausível vira lei sem ninguém questionar.
184
+ </callout>
185
+
186
+ ## Changelog
187
+
188
+ ${changelog}
189
+ `;
190
+
191
+ process.stdout.write(pagina);
Binary file