@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
package/LICENSE.md ADDED
@@ -0,0 +1,46 @@
1
+ # Licença do Unbox CLI
2
+
3
+ Copyright (c) 2026 Unbox. Todos os direitos reservados.
4
+
5
+ ## 1. Permissão de uso
6
+
7
+ A Unbox concede a você uma licença gratuita, mundial, não exclusiva e
8
+ intransferível para **instalar e executar** este software (o "CLI") na sua
9
+ forma original e não modificada, exclusivamente para criar storefronts
10
+ integrados à plataforma Unbox — por exemplo, via `npm install` ou `npx`.
11
+
12
+ ## 2. Código gerado
13
+
14
+ Os arquivos **gerados** pelo CLI a partir do template (o projeto de storefront
15
+ criado na sua máquina) são seus. Você pode usá-los, modificá-los, hospedá-los
16
+ e distribuí-los livremente, sem qualquer restrição desta licença.
17
+
18
+ Esta licença aplica-se apenas ao código do CLI em si (incluindo o diretório
19
+ `template/` enquanto parte deste pacote), não ao resultado que ele produz.
20
+
21
+ ## 3. Restrições
22
+
23
+ Salvo o disposto na seção 2, você **não** pode:
24
+
25
+ - modificar o CLI ou criar obras derivadas dele;
26
+ - redistribuir o CLI, modificado ou não, fora dos canais oficiais da Unbox
27
+ (npm registry e distribuições feitas pela própria Unbox);
28
+ - sublicenciar, vender ou oferecer o CLI como parte de outro produto ou
29
+ serviço.
30
+
31
+ ## 4. Marcas
32
+
33
+ Esta licença não concede qualquer direito sobre nomes, logotipos ou marcas da
34
+ Unbox.
35
+
36
+ ## 5. Sem garantias
37
+
38
+ O SOFTWARE É FORNECIDO "NO ESTADO EM QUE SE ENCONTRA", SEM GARANTIAS DE
39
+ QUALQUER TIPO, EXPRESSAS OU IMPLÍCITAS. EM NENHUMA HIPÓTESE A UNBOX SERÁ
40
+ RESPONSÁVEL POR QUALQUER RECLAMAÇÃO, DANO OU OUTRA RESPONSABILIDADE
41
+ DECORRENTE DO USO DO SOFTWARE.
42
+
43
+ ## 6. Rescisão
44
+
45
+ Qualquer violação destas condições encerra automaticamente os direitos
46
+ concedidos por esta licença.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # Unbox CLI
2
+
3
+ CLI que gera um storefront Next.js completo integrado com a API Unbox, a partir da foundation
4
+ oficial (`storefront-foundation`), já com a sua marca aplicada: estilo visual (layout, fontes,
5
+ neutros), cores e nome da loja.
6
+
7
+ ## Uso — sem instalar nada
8
+
9
+ > **Passo 0**: precisa do Node.js instalado (baixe em [nodejs.org](https://nodejs.org), botão
10
+ > "LTS", instalador comum de 2 minutos). É o único pré-requisito da máquina.
11
+
12
+ Navegue até a pasta onde queira criar o projeto e rode:
13
+
14
+ ```bash
15
+ npx @unbox-plus/cli create-unbox-store
16
+ ```
17
+
18
+ O `npx` roda o CLI direto do pacote publicado no NPM, sem instalar nada global.
19
+ Ele vai perguntar:
20
+
21
+ 1. Nome da pasta/projeto
22
+ 2. Nome de exibição da loja
23
+ 3. Cor primária (hex) — botões, preços, links
24
+ 4. Cor de destaque/CTA (hex) — banners de oferta
25
+ 5. Site atual da marca (opcional, Enter pra pular)
26
+ 6. Instagram da marca (opcional, Enter pra pular)
27
+ 7. Objetivo da loja em uma frase (opcional, Enter pra pular)
28
+ 8. **Estilo visual da loja** — o CLI sugere um a partir do objetivo/site/Instagram e você
29
+ confirma ou troca: **Essencial** (completo e equilibrado), **Promocional** (oferta em
30
+ primeiro plano), **Editorial** (clube/assinatura, tipografia de revista) ou **Boutique**
31
+ (minimal premium). O estilo define layout da home, fontes, neutros, chrome e raios já no
32
+ scaffold; o agente de Branding refina depois no briefing.
33
+ 9. Se você já tem as credenciais da loja Unbox (`UNBOX_PARTNER_API_KEY` ou `UNBOX_API_KEY`,
34
+ `UNBOX_USER`, `UNBOX_PASS`)
35
+ - **Não tem ainda?** Sem problema — responda "não" e o projeto sobe em **modo mockup**
36
+ (layout completo, sem dados reais da loja). Preencha `.env.local` depois.
37
+ 10. Checkout customizável (código no projeto) ou padrão hospedado da Unbox
38
+ 11. Se quer que ele já rode `npm install`
39
+
40
+ Tudo (menos credenciais) fica registrado em `marca/briefing.json` do projeto gerado — é a
41
+ "memória" do formulário pro agente de Branding não reperguntar nada.
42
+
43
+ Ao final, o próximo passo é o **briefing de marca no Claude Code** (não `npm run dev`):
44
+
45
+ ```bash
46
+ cd <nome-do-projeto>
47
+ claude
48
+ ```
49
+
50
+ O Claude Code abre direto no briefing (agente 15, via `.claude/settings.json` +
51
+ `.claude/agents/branding-briefing.md` gerados no projeto) e conduz a personalização ANTES de o
52
+ cliente ver a loja padrão — regra "perguntar antes de mostrar". Encerrado o briefing, o próprio
53
+ agente remove a chave `"agent"` do `.claude/settings.json` e as sessões seguintes voltam ao
54
+ Claude normal.
55
+
56
+ ### Alternativa mais universal: instalar global o pacote via NPM
57
+
58
+ Se `npx` der algum problema no ambiente da outra pessoa (versão de npm diferente), esse caminho
59
+ funciona em qualquer npm recente:
60
+
61
+ ```bash
62
+ npm install -g @unbox-plus/cli
63
+ create-unbox-store
64
+ ```
65
+
66
+ ## Uso — sem instalar, clonando a pasta/repositório
67
+
68
+ Se você recebeu a pasta inteira (zip) ou clonou o repositório:
69
+
70
+ ```bash
71
+ cd create-unbox-store
72
+ npm install
73
+ node bin/cli.js
74
+ ```
75
+
76
+ ## Uso não-interativo (scripts/CI)
77
+
78
+ ```bash
79
+ npx @unbox-plus/cli create-unbox-store minha-loja --yes --no-install
80
+ ```
81
+
82
+ Gera com as cores/nome default e o estilo **essencial** — use `--estilo <nome>`
83
+ (essencial, promocional, editorial, boutique) pra escolher outro sem interação. Cores e
84
+ tokens podem ser editados depois em `app/globals.css` (bloco "CONFIGURE"); o layout da home
85
+ em `components/home/home-recipe.ts`.
86
+
87
+ ## Checkout hospedado da Unbox: duas regras
88
+
89
+ Quem escolhe "Padrão da Unbox" no wizard não precisa fazer nada: o CLI monta a URL certa. Quem
90
+ preencher `UNBOX_HOSTED_CHECKOUT_URL` à mão depois precisa das duas:
91
+
92
+ 1. **Caminho completo, não o domínio.** `https://sualoja.com.br/carrinho/finalizar-pedido`. A raiz
93
+ é a home da loja e ignora os parâmetros em silêncio.
94
+ 2. **Loja e checkout no mesmo domínio.** Com a loja em um host e o checkout em outro, o cliente cai
95
+ no `/login`. Com os dois sob o mesmo domínio, funciona.
96
+
97
+ A URL carrega `?id=&token=&freq=&step=1`. O token faz parte do contrato: sem ele, o checkout não
98
+ encontra o carrinho.
99
+
100
+ ## A loja já tem um GTM próprio?
101
+
102
+ Preencha `NEXT_PUBLIC_GTM_ID`. O container da marca convive com o container central da Unbox: são
103
+ dois independentes lendo o mesmo `dataLayer`. Três coisas que não são óbvias:
104
+
105
+ 1. **Com o container preenchido, o Pixel do código é desligado automaticamente.** Pixel no
106
+ container e no código conta `PageView` duas vezes, e a inflação é silenciosa.
107
+ 2. **Quem configura o container precisa saber:** numa SPA, o gatilho "All Pages" só pega o
108
+ carregamento inicial. Para contar troca de rota, a tag de pageview escuta o evento
109
+ personalizado `dataLayerReady`, que a loja emite em toda rota principal (com `pageType` e os
110
+ produtos em vista). O gatilho nativo "History Change" também funciona.
111
+ 3. **Ligar o Pixel derruba o Best Practices do Lighthouse** de 100 para ~77
112
+ (`third-party-cookies`). Não tem conserto no front, é como o Pixel funciona. A mitigação é a
113
+ API de Conversões server-side, que já vem pronta em `app/api/capi/route.ts` e no webhook de
114
+ pagamento.
115
+
116
+ ## Requisitos
117
+
118
+ - Node.js ≥ 20 (recomendado: 22 LTS — veja `.nvmrc` no projeto gerado)
119
+ - Credenciais da loja Unbox (`UNBOX_API_KEY`, `UNBOX_USER`, `UNBOX_PASS`) — opcional pra rodar em
120
+ modo mockup, obrigatório antes de ir pra produção (`npm run unbox:test` confirma a conexão)
121
+
122
+ ## O que o CLI gera
123
+
124
+ Next.js 15 (App Router) + Tailwind v4 + shadcn/ui sobre Base UI, com catálogo, carrinho,
125
+ checkout, área do cliente (OTP login, pedidos, assinaturas, endereços), páginas legais, PWA e
126
+ SEO básico já implementados — ver `DEPLOY.md` e `QA.md` dentro do projeto gerado para o checklist
127
+ de deploy na Vercel.
128
+
129
+ ## ⚠️ Antes de publicar a loja — decisões que ficam para quem usa
130
+
131
+ A foundation inclui alguns placeholders que **funcionam, mas precisam de uma decisão consciente**
132
+ antes de a loja ir ao ar. Nenhum deles quebra o projeto — o CLI não resolve isso automaticamente
133
+ de propósito, para não tomar essa decisão no lugar de quem está publicando.
134
+
135
+ 1. **Prova social: a foundation não traz nenhuma.** Sem avaliação real em
136
+ `lib/enrichment/products.json`, a loja não mostra nota, estrelas, contagem nem depoimento
137
+ (o bloco some, não cai em placeholder). Antes de publicar, decida: popular com dados reais
138
+ ou publicar sem prova social. **O agente 15 (Branding) pede esse material no briefing.**
139
+ O `unbox:honestidade` lista qualquer número ou promessa que entrar sem lastro.
140
+ 2. **Módulos de CRO — só 1 de 10 na foundation.** Dos 10 módulos do "Unbox AI CRO Package"
141
+ (prova social, urgência de estoque, countdown, frete grátis, upsell, recently viewed, sticky
142
+ CTA, trust strip, kits, brindes), só a **barra de frete grátis** vem pronta. Os outros nove
143
+ são construídos pelo agente `13-cro.md`, que o briefing oferece ao terminar — o wizard não
144
+ pergunta mais sobre isso (perguntava, gravava num arquivo que ninguém lia, e o agente 13
145
+ perguntava de novo).
146
+ 3. **SEO avançado — o básico está, os 7 módulos não.** A foundation traz JSON-LD de produto,
147
+ Organization + WebSite/SearchAction na home, imagem Open Graph gerada e canonical por
148
+ página. Breadcrumbs, FAQ schema, OG por produto e templates de título/description por
149
+ página são do agente `14-seo.md`.
150
+ 4. **Kits/combos vêm vazios** (`lib/enrichment/combos.ts`) — a seção só aparece na loja depois de
151
+ alguém popular `COMBOS` com os kits reais.
152
+
153
+ ### Onde continuar depois do setup
154
+
155
+ Todo projeto gerado já vem com `agents/MANAGER.md` — descreve exatamente o que cada agente
156
+ resolve e a ordem recomendada dos passos manuais: **15 (Branding) primeiro**, depois os opcionais
157
+ 13 (CRO) e 14 (SEO), e por último 12 (Deploy).
158
+
159
+ ## Editor da loja (a partir da 0.20.0)
160
+
161
+ A loja gerada nasce editável pelo editor da Unbox (chat e painel visual sobre um documento de conteúdo). O que o template traz, e que não pode ser removido sem desligar o editor em silêncio:
162
+
163
+ - `lib/editable/` (a foundation do editor, cópia byte a byte; correção entra por versão, nunca à mão), com o slug da loja carimbado em `config.ts` no scaffold.
164
+ - `next.config.ts` com `frame-ancestors` para `NEXT_PUBLIC_EDITOR_ORIGIN` (variável de BUILD: precisa existir antes do `next build`).
165
+ - `app/api/revalidate` aceitando o token do editor e devolvendo o recibo com `conteudo`.
166
+ - `middleware.ts` deixando passar o token de prévia e as rotas `/api/unbox/*`.
167
+ - `app/api/unbox/paginas`, `catalogo` e `vitrine`, e `lib/rotas-editaveis.ts` com as tabelas `SO_CHROME` e `CONTAINERS_POR_ROTA`: rota nova entra na tabela ou o gate reprova.
168
+ - `app/layout.tsx` com o `EditableProvider` e a linha única `<Rastreio />` (GTM, GA4, Meta Pixel, TikTok, Pinterest e WhatsApp decididos pela foundation).
169
+ - Home, cabeçalho, rodapé, catálogo e página de produto com primitivos; ids de seção são chaves de arquivo e não têm renomear.
170
+
171
+ Gate: `npm run unbox:editavel` mede toda página que a loja declara. Saída 0 = aprovado; 1 = reprovado dizendo qual página; 2 = não rodou (rota dinâmica sem exemplo, por exemplo sem catálogo), nunca aprovação.
172
+
173
+ Variáveis: `EDITOR_URL`, `NEXT_PUBLIC_EDITOR_ORIGIN`, `UNBOX_EDITOR_SHOP` (opcional: sobrepõe o slug carimbado). `META_CAPI_TOKEN` é segredo de servidor e nunca entra no documento do editor.
174
+
175
+ A seção "Editor: o que não pode quebrar" do `CLAUDE.md` do template lista cada ponto com o que acontece se for tocado.
package/bin/cli.js ADDED
@@ -0,0 +1,439 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { execSync } from "node:child_process";
6
+ import prompts from "prompts";
7
+ import { normalizeHex, deriveBrandTokens } from "../src/colors.js";
8
+ import { applyPreset, applyStoreName } from "../src/theme.js";
9
+ import { PRESETS, PRESET_NAMES, suggestPreset } from "../src/presets.js";
10
+ import crypto from "node:crypto";
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const TEMPLATE_DIR = path.join(__dirname, "..", "template");
14
+ const EXCLUDE = new Set(["bootstrap.sh", "package-lock.json", ".DS_Store"]);
15
+ // Cores default = CANVAS PROVISÓRIA, não escolha estética. Tinta quase-preta + âmbar
16
+ // contido: a base mais neutra possível pra qualquer marca cair bem em cima. Trocar pelas
17
+ // cores REAIS é o primeiro ato do briefing (o build avisa enquanto estiverem aqui).
18
+ const DEFAULT_PRIMARY = "#18181B";
19
+ const DEFAULT_CTA = "#D97706";
20
+ // Removidos do projeto quando o checkout é o PADRÃO hospedado pela Unbox — sem UI de checkout
21
+ // customizada nesse modo, esses arquivos ficariam mortos (ver app/api/checkout-destination).
22
+ const CUSTOM_CHECKOUT_PATHS = ["app/(loja)/checkout", "components/checkout", "app/api/checkout"];
23
+
24
+ const CHECKOUT_WARNING = `
25
+ ⚠️ Checkout customizável escolhido. Leia antes de continuar:
26
+ Ao customizar o checkout, a Unbox deixa de controlar essa experiência: passa a ser
27
+ responsabilidade deste projeto. Checkout é a área mais sensível pra conversão da loja: um erro
28
+ de implementação aqui (frete, pagamento, validação de endereço) pode custar vendas de verdade.
29
+ Teste bem (npm run unbox:test, QA.md) antes de publicar.
30
+ `;
31
+
32
+ function copyTemplate(targetDir) {
33
+ fs.mkdirSync(targetDir, { recursive: true });
34
+ for (const entry of fs.readdirSync(TEMPLATE_DIR)) {
35
+ if (EXCLUDE.has(entry)) continue;
36
+ // "gitignore" (sem ponto) vira ".gitignore" no projeto — o npm pack remove/renomeia
37
+ // arquivos .gitignore de dentro do pacote, então o template o carrega sem o ponto.
38
+ const dest = entry === "gitignore" ? ".gitignore" : entry;
39
+ fs.cpSync(path.join(TEMPLATE_DIR, entry), path.join(targetDir, dest), { recursive: true });
40
+ }
41
+ }
42
+
43
+ // Ctrl+C no meio das perguntas: sem isto o `prompts` devolve undefined e o CLI seguia
44
+ // gerando um projeto com defaults (nome "undefined", checkout custom, sem credenciais).
45
+ const onCancel = () => { console.log("\n Cancelado. Nada foi gerado."); process.exit(1); };
46
+
47
+ function removeCustomCheckout(targetDir) {
48
+ for (const rel of CUSTOM_CHECKOUT_PATHS) {
49
+ fs.rmSync(path.join(targetDir, rel), { recursive: true, force: true });
50
+ }
51
+ }
52
+
53
+ /** "https://minhaloja.com.br/" ou "minhaloja.com.br" → "https://minhaloja.com.br" (sem barra final).
54
+ * Retorna null se não for um domínio válido (espaço, caminho, sem TLD) — nunca gera URL torta. */
55
+ function normalizeDomain(input) {
56
+ let d = String(input ?? "").trim().replace(/\/+$/, "");
57
+ if (!d) return null;
58
+ if (!/^https?:\/\//i.test(d)) d = `https://${d}`;
59
+ try {
60
+ const u = new URL(d);
61
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
62
+ if (u.pathname !== "/" || u.search || u.hash || u.username || u.port) return null;
63
+ if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(u.hostname) || u.hostname.includes("..")) return null;
64
+ return `${u.protocol}//${u.hostname}`;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ const validaDominio = (v) => (normalizeDomain(v) ? true : "Informe só o domínio, ex.: minhaloja.com.br (sem caminho, sem espaço)");
70
+
71
+ function setProjectName(targetDir, name) {
72
+ const pkgPath = path.join(targetDir, "package.json");
73
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
74
+ pkg.name = name;
75
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
76
+ }
77
+
78
+ /** Valor seguro pra linha KEY=... do .env.local. O Next lê o arquivo com dotenv + dotenv-expand:
79
+ * sem aspas, `#` vira comentário (senha truncada), `$X` é expandido (some) e espaço quebra.
80
+ * Caso real: senha com `$` chegava cortada e o signIn falhava sem pista.
81
+ * Medido com o @next/env real (9 valores adversos): aspas simples NÃO protegem `$1` (o expand
82
+ * roda mesmo assim) e aspas duplas não desfazem `\"`. O único formato que devolve todos os
83
+ * valores intactos é CRASE com `$` escapado como `\$`. Só caracteres simples saem crus. */
84
+ function envValue(v) {
85
+ const s = String(v ?? "").replace(/\r?\n/g, "");
86
+ if (s === "" || /^[A-Za-z0-9_.\/:@+=,-]*$/.test(s)) return s;
87
+ const esc = s.replace(/\$/g, "\\$");
88
+ if (!s.includes("`")) return "`" + esc + "`";
89
+ // Valor com crase: aspas duplas (a única perda possível é uma aspa dupla interna, sem escape em dotenv).
90
+ return `"${esc.replace(/"/g, "")}"`;
91
+ }
92
+
93
+ function writeEnvLocal(targetDir, values) {
94
+ const examplePath = path.join(targetDir, ".env.example");
95
+ const lines = fs.readFileSync(examplePath, "utf8").split("\n");
96
+ // Segredos que a loja precisa e ninguém gera à mão: SESSION_SECRET assina o cookie de posse
97
+ // do pedido (lib/session.ts) e REVALIDATE_SECRET protege /api/revalidate e /api/payment-link.
98
+ const all = {
99
+ SESSION_SECRET: crypto.randomBytes(32).toString("hex"),
100
+ REVALIDATE_SECRET: crypto.randomBytes(24).toString("hex"),
101
+ ...values,
102
+ };
103
+ const out = lines.map((line) => {
104
+ const match = line.match(/^([A-Z0-9_]+)=/);
105
+ if (!match) return line;
106
+ const key = match[1];
107
+ if (all[key] !== undefined) return `${key}=${envValue(all[key])}`;
108
+ return line;
109
+ });
110
+ fs.writeFileSync(path.join(targetDir, ".env.local"), out.join("\n"));
111
+ }
112
+
113
+ /**
114
+ * Grava marca/briefing.json com o que o formulário coletou (menos credenciais).
115
+ * É a "memória" do scaffold pro agente 15 (Branding): ele lê isso na Regra 0 e
116
+ * não repergunta nada daqui. Campos opcionais vazios são omitidos.
117
+ */
118
+ function writeBriefing(targetDir, data) {
119
+ const briefing = Object.fromEntries(
120
+ Object.entries(data).filter(([, v]) => v !== undefined && String(v).trim() !== ""),
121
+ );
122
+ const dir = path.join(targetDir, "marca");
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ fs.writeFileSync(path.join(dir, "briefing.json"), JSON.stringify(briefing, null, 2) + "\n");
125
+ }
126
+
127
+ function printHelp() {
128
+ console.log(`
129
+ create-unbox-store: gera um storefront Next.js integrado à Unbox
130
+
131
+ Uso:
132
+ npx --package=./create-unbox-store.tgz create-unbox-store [pasta] [flags]
133
+
134
+ Sem flags, roda o wizard interativo: nome da loja, cores, estilo visual,
135
+ credenciais da Unbox (Enter pra pular = modo mockup) e checkout.
136
+ As credenciais coletadas viram o .env.local do projeto gerado.
137
+
138
+ Flags:
139
+ --yes, -y não pergunta nada (defaults + modo mockup; pasta default "minha-loja")
140
+ --estilo <nome> estilo visual: essencial | promocional | editorial | boutique
141
+ --install roda npm install ao final
142
+ --no-install não roda npm install
143
+ --help, -h esta ajuda
144
+
145
+ Exemplos:
146
+ npx --package=./create-unbox-store.tgz create-unbox-store minha-loja
147
+ npx --package=./create-unbox-store.tgz create-unbox-store loja-x --yes --no-install --estilo boutique
148
+
149
+ Depois de gerar: cd <pasta> && claude (o briefing de marca abre sozinho)
150
+ Docs completas: README.md dentro do zip da beta.
151
+ `);
152
+ }
153
+
154
+ function parseArgs(argv) {
155
+ const flags = { yes: false, install: undefined, target: undefined, estilo: undefined };
156
+ const positional = [];
157
+ for (let i = 0; i < argv.length; i++) {
158
+ const arg = argv[i];
159
+ if (arg === "--help" || arg === "-h") { printHelp(); process.exit(0); }
160
+ else if (arg === "--yes" || arg === "-y") flags.yes = true;
161
+ else if (arg === "--install") flags.install = true;
162
+ else if (arg === "--no-install") flags.install = false;
163
+ else if (arg === "--estilo") flags.estilo = argv[++i];
164
+ else if (arg.startsWith("--estilo=")) flags.estilo = arg.slice("--estilo=".length);
165
+ else if (arg.startsWith("--")) {
166
+ // Flag desconhecida vira erro, não silêncio: "--estylo boutique" ignorado geraria
167
+ // uma loja com o estilo errado sem ninguém perceber.
168
+ console.error(`\n✗ Flag desconhecida: "${arg}". Veja as opções com --help.`);
169
+ process.exit(1);
170
+ }
171
+ else positional.push(arg);
172
+ }
173
+ flags.target = positional[0];
174
+ if (flags.estilo && !PRESET_NAMES.includes(flags.estilo)) {
175
+ console.error(`\n✗ Estilo desconhecido: "${flags.estilo}". Opções: ${PRESET_NAMES.join(", ")}.`);
176
+ process.exit(1);
177
+ }
178
+ return flags;
179
+ }
180
+
181
+ async function main() {
182
+ console.log("");
183
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
184
+ console.log(" create-unbox-store");
185
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
186
+ console.log("");
187
+
188
+ const flags = parseArgs(process.argv.slice(2));
189
+ const nonInteractive = flags.yes || !process.stdin.isTTY;
190
+
191
+ let targetName = flags.target;
192
+ let hasCreds = false;
193
+ let displayName, primary, cta;
194
+ let checkoutMode = "custom";
195
+ let hostedCheckoutUrl = "";
196
+ let site = "", instagram = "", objetivo = "";
197
+ let estilo = flags.estilo ?? "essencial";
198
+
199
+ if (nonInteractive) {
200
+ targetName = targetName || "minha-loja";
201
+ displayName = path.basename(targetName);
202
+ primary = DEFAULT_PRIMARY;
203
+ cta = DEFAULT_CTA;
204
+ } else {
205
+ const answers = await prompts([
206
+ {
207
+ type: targetName ? null : "text",
208
+ name: "targetName",
209
+ message: "Nome da pasta/projeto",
210
+ initial: "minha-loja",
211
+ },
212
+ {
213
+ type: "text",
214
+ name: "displayName",
215
+ message: "Nome de exibição da loja",
216
+ initial: (prev) => prev || "Minha Loja",
217
+ },
218
+ {
219
+ type: "text",
220
+ name: "primary",
221
+ message: "Cor primária (hex): botões, preços, links",
222
+ initial: DEFAULT_PRIMARY,
223
+ validate: (v) => (normalizeHex(v) ? true : "Hex inválido, ex: #15803D"),
224
+ },
225
+ {
226
+ type: "text",
227
+ name: "cta",
228
+ message: "Cor de destaque/CTA (hex): ofertas, banners",
229
+ initial: DEFAULT_CTA,
230
+ validate: (v) => (normalizeHex(v) ? true : "Hex inválido, ex: #F3C012"),
231
+ },
232
+ {
233
+ type: "text",
234
+ name: "site",
235
+ message: "Site atual da marca (URL, Enter se não tiver)",
236
+ },
237
+ {
238
+ type: "text",
239
+ name: "instagram",
240
+ message: "Instagram da marca (@usuario ou URL, Enter pra pular)",
241
+ },
242
+ {
243
+ type: "text",
244
+ name: "objetivo",
245
+ message: "Objetivo da loja em uma frase (ex.: aumentar o ticket médio, Enter pra pular)",
246
+ },
247
+ {
248
+ type: flags.estilo ? null : "select",
249
+ name: "estilo",
250
+ message: "Estilo visual da loja (sugerido a partir do que você contou; layout, fontes e clima mudam com ele)",
251
+ choices: (prev, values) => {
252
+ const sugg = suggestPreset(values);
253
+ return PRESET_NAMES.map((name) => ({
254
+ title: `${PRESETS[name].label}${name === sugg ? " (sugerido)" : ""}: ${PRESETS[name].tagline}`,
255
+ value: name,
256
+ }));
257
+ },
258
+ initial: (prev, values) => PRESET_NAMES.indexOf(suggestPreset(values)),
259
+ },
260
+ {
261
+ type: "confirm",
262
+ name: "hasCreds",
263
+ message: "Você já tem as credenciais da Unbox (key de parceiro ou da loja + UNBOX_USER/UNBOX_PASS)?",
264
+ initial: false,
265
+ },
266
+ {
267
+ type: "select",
268
+ name: "checkoutMode",
269
+ message: "Checkout: customizável (código neste projeto) ou padrão da Unbox (hospedado)?",
270
+ choices: [
271
+ { title: "Customizável (mais flexível, mas você assume o risco)", value: "custom" },
272
+ { title: "Padrão da Unbox (hospedado, sem código aqui)", value: "unbox" },
273
+ ],
274
+ initial: 0,
275
+ },
276
+ {
277
+ type: (prev) => (prev === "unbox" ? "text" : null),
278
+ name: "checkoutDomain",
279
+ message: "Domínio da loja vinculado na Unbox (ex.: minhaloja.com.br). O checkout usa esse mesmo domínio",
280
+ validate: validaDominio,
281
+ },
282
+ ], { onCancel });
283
+ targetName = targetName || answers.targetName;
284
+ displayName = answers.displayName || targetName;
285
+ primary = normalizeHex(answers.primary) || DEFAULT_PRIMARY;
286
+ cta = normalizeHex(answers.cta) || DEFAULT_CTA;
287
+ site = (answers.site ?? "").trim();
288
+ instagram = (answers.instagram ?? "").trim();
289
+ objetivo = (answers.objetivo ?? "").trim();
290
+ estilo = flags.estilo ?? answers.estilo ?? "essencial";
291
+ hasCreds = answers.hasCreds;
292
+ checkoutMode = answers.checkoutMode ?? "custom";
293
+ if (checkoutMode === "unbox") {
294
+ const dom = normalizeDomain(answers.checkoutDomain);
295
+ if (dom) {
296
+ hostedCheckoutUrl = `${dom}/carrinho/finalizar-pedido`;
297
+ } else {
298
+ // Sem domínio válido NÃO dá pra apagar o checkout do projeto: a loja ficaria sem
299
+ // nenhum (o botão "finalizar" cairia em 404). Mantém o customizável e avisa.
300
+ checkoutMode = "custom";
301
+ console.log("\n ⚠ Domínio do checkout hospedado ausente/inválido: mantendo o checkout customizável.");
302
+ console.log(" Pra usar o hospedado depois: preencha UNBOX_HOSTED_CHECKOUT_URL no .env.local.\n");
303
+ }
304
+ }
305
+ if (checkoutMode === "custom") console.log(CHECKOUT_WARNING);
306
+ }
307
+
308
+ const targetDir = path.resolve(process.cwd(), targetName);
309
+
310
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
311
+ console.error(`\n✗ A pasta "${targetName}" já existe e não está vazia. Abortando.`);
312
+ process.exit(1);
313
+ }
314
+
315
+ let credAnswers = {};
316
+ if (hasCreds) {
317
+ credAnswers = await prompts([
318
+ { type: "text", name: "UNBOX_PARTNER_API_KEY", message: "UNBOX_PARTNER_API_KEY: api key única do PARCEIRO (recomendada; Enter se ainda usa key por loja)" },
319
+ { type: "text", name: "UNBOX_API_KEY", message: "UNBOX_API_KEY: key da loja, modelo antigo (Enter pra pular se informou a de parceiro)" },
320
+ { type: "text", name: "UNBOX_USER", message: "UNBOX_USER" },
321
+ { type: "password", name: "UNBOX_PASS", message: "UNBOX_PASS" },
322
+ { type: "text", name: "UNBOX_SHOP_ID", message: "UNBOX_SHOP_ID (opcional, Enter pra deixar em branco)" },
323
+ { type: "text", name: "UNBOX_SHOP_SLUG", message: "UNBOX_SHOP_SLUG (opcional, Enter pra deixar em branco)" },
324
+ {
325
+ // Só faz sentido com a key de parceiro: é o segredo que o header x-captcha-verification
326
+ // espera no placeOrder/OTP (sem ele, a key vai no lugar e o cliente vê CAPTCHA_MALFORMED_ERROR).
327
+ type: (_prev, values) => (values.UNBOX_PARTNER_API_KEY?.trim() ? "password" : null),
328
+ name: "UNBOX_CAPTCHA_BYPASS",
329
+ message: "UNBOX_CAPTCHA_BYPASS: segredo de bypass do reCAPTCHA (pedir à Unbox junto com a key de parceiro; Enter pra pular)",
330
+ },
331
+ { type: "password", name: "UNBOX_MCP_TOKEN", message: "Token do MCP da Unbox (opcional, habilita o .mcp.json pronto; pedir à Unbox; Enter pra pular)" },
332
+ ], { onCancel });
333
+ } else {
334
+ console.log("\n Subindo em modo mockup. Preencha o .env.local depois e rode `npm run unbox:test`.\n");
335
+ }
336
+
337
+ console.log(`\n→ Gerando projeto em ${targetDir} ...`);
338
+ copyTemplate(targetDir);
339
+ setProjectName(targetDir, targetName);
340
+ // MCP da Unbox: com o token informado, o .mcp.json REAL já sai pronto (preenchido com as
341
+ // credenciais coletadas). O .mcp.json.example fica sempre só com placeholders — nunca
342
+ // embarque credencial viva em arquivo de exemplo (ele vai pro git; o .mcp.json não).
343
+ if (credAnswers.UNBOX_MCP_TOKEN) {
344
+ const subst = {
345
+ SEU_UNBOX_MCP_TOKEN: credAnswers.UNBOX_MCP_TOKEN,
346
+ SUA_UNBOX_PARTNER_API_KEY: credAnswers.UNBOX_PARTNER_API_KEY || credAnswers.UNBOX_API_KEY || "",
347
+ SEU_UNBOX_USER: credAnswers.UNBOX_USER || "",
348
+ SUA_UNBOX_PASS: credAnswers.UNBOX_PASS || "",
349
+ };
350
+ // Percorre o JSON e troca os placeholders como VALORES — nunca replace de string no texto
351
+ // (credencial com `$&`, `$'` ou aspas corrompia o arquivo; JSON.stringify escapa tudo).
352
+ const troca = (v) => (typeof v === "string" && v in subst ? subst[v]
353
+ : Array.isArray(v) ? v.map(troca)
354
+ : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, troca(x)]))
355
+ : v);
356
+ const example = JSON.parse(fs.readFileSync(path.join(targetDir, ".mcp.json.example"), "utf8"));
357
+ fs.writeFileSync(path.join(targetDir, ".mcp.json"), JSON.stringify(troca(example), null, 2) + "\n");
358
+ delete credAnswers.UNBOX_MCP_TOKEN; // não é env do app — não vai pro .env.local
359
+ console.log(" ✓ MCP da Unbox configurado (.mcp.json pronto; fora do git)");
360
+ } else {
361
+ delete credAnswers.UNBOX_MCP_TOKEN;
362
+ }
363
+ // Porta de preview: se escopa pelo DOMÍNIO sozinha (liga em *.vercel.app/*.myunbox.com.br,
364
+ // some em domínio próprio e no localhost) — nenhuma env necessária no .env.local.
365
+ writeEnvLocal(targetDir, {
366
+ ...credAnswers,
367
+ NEXT_PUBLIC_SITE_NAME: displayName,
368
+ ...(hostedCheckoutUrl ? { UNBOX_HOSTED_CHECKOUT_URL: hostedCheckoutUrl } : {}),
369
+ });
370
+
371
+ if (checkoutMode === "unbox") {
372
+ removeCustomCheckout(targetDir);
373
+ console.log(` ✓ Checkout padrão da Unbox configurado (${hostedCheckoutUrl})`);
374
+ }
375
+
376
+ const preset = PRESETS[estilo];
377
+ const brandTokens = deriveBrandTokens({ primary, cta }, preset);
378
+ applyPreset(targetDir, estilo, brandTokens);
379
+ applyStoreName(targetDir, displayName);
380
+ console.log(` ✓ Estilo "${preset.label}" aplicado: layout, fontes e neutros do preset + primária ${primary}, CTA ${cta}, loja "${displayName}"`);
381
+
382
+ writeBriefing(targetDir, {
383
+ loja: displayName,
384
+ corPrimaria: primary,
385
+ corCta: cta,
386
+ estilo,
387
+ // Dials de composição (1-10). Default do preset; o agente 15 revalida no briefing.
388
+ // variancia = quão assimétrica/ousada é a composição · motion = quanta animação
389
+ // (0 desliga) · densidade = quanto conteúdo por tela (arejado ↔ compacto).
390
+ dials: PRESETS[estilo]?.dials,
391
+ site,
392
+ instagram,
393
+ objetivo,
394
+ checkout: checkoutMode === "unbox" ? "padrao-unbox" : "customizavel",
395
+ geradoPor: "create-unbox-store",
396
+ criadoEm: new Date().toISOString(),
397
+ });
398
+ console.log(" ✓ Briefing inicial salvo em marca/briefing.json (o agente de marca lê daqui)");
399
+
400
+ let install = flags.install ?? true;
401
+ if (!nonInteractive && flags.install === undefined) {
402
+ const res = await prompts({
403
+ type: "confirm",
404
+ name: "install",
405
+ message: "Rodar `npm install` agora? (pode demorar ~2 min)",
406
+ initial: true,
407
+ }, { onCancel });
408
+ install = res.install;
409
+ }
410
+
411
+ if (install) {
412
+ console.log("\n→ Instalando dependências...");
413
+ execSync("npm install", { cwd: targetDir, stdio: "inherit" });
414
+ }
415
+
416
+ console.log("");
417
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
418
+ console.log(" ✓ Projeto criado!");
419
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
420
+ // "Perguntar antes de mostrar": o próximo passo é o BRIEFING no Claude Code (agente 15),
421
+ // não "npm run dev" — o cliente não deve ver a loja padrão antes da personalização.
422
+ console.log(`
423
+ Próximo passo: dar a cara da sua marca pra loja.
424
+
425
+ cd ${targetName}
426
+ ${install ? "" : "npm install\n "}claude
427
+
428
+ O Claude Code abre direto no briefing de marca e puxa a conversa sozinho.
429
+ Ele entrevista você, aplica logo, cores e conteúdo reais, e só então te
430
+ mostra a loja, já personalizada.
431
+
432
+ (Não tem o Claude Code? npm install -g @anthropic-ai/claude-code)
433
+
434
+ Antes de ir pra produção: npm run unbox:test
435
+ Depois do briefing, os passos opcionais (CRO, SEO, Deploy): agents/MANAGER.md
436
+ `);
437
+ }
438
+
439
+ main();