@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.
- package/LICENSE.md +46 -0
- package/README.md +175 -0
- package/bin/cli.js +439 -0
- package/package.json +43 -0
- package/src/colors.js +100 -0
- package/src/presets.js +241 -0
- package/src/theme.js +253 -0
- package/template/.claude/agents/assets-marca.md +40 -0
- package/template/.claude/agents/avaliador-visual.md +48 -0
- package/template/.claude/agents/branding-briefing.md +401 -0
- package/template/.claude/agents/conteudo-secao.md +46 -0
- package/template/.claude/agents/paginas-legais.md +44 -0
- package/template/.claude/settings.json +3 -0
- package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.claude/skills/frontend-design/SKILL.md +55 -0
- package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
- package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
- package/template/.env.example +158 -0
- package/template/.mcp.json.example +14 -0
- package/template/.nvmrc +1 -0
- package/template/CLAUDE.md +333 -0
- package/template/COVERAGE.md +80 -0
- package/template/DEPLOY.md +179 -0
- package/template/QA.md +108 -0
- package/template/README.md +152 -0
- package/template/agents/CONSTRUCAO.md +207 -0
- package/template/agents/MANAGER.md +192 -0
- package/template/agents/PADROES.md +255 -0
- package/template/agents/definitions/00-scaffold.md +56 -0
- package/template/agents/definitions/01-layout.md +47 -0
- package/template/agents/definitions/02-homepage.md +70 -0
- package/template/agents/definitions/03-catalog.md +86 -0
- package/template/agents/definitions/04-pdp.md +97 -0
- package/template/agents/definitions/05-cart.md +116 -0
- package/template/agents/definitions/06-checkout.md +146 -0
- package/template/agents/definitions/07-auth.md +130 -0
- package/template/agents/definitions/08-customer.md +55 -0
- package/template/agents/definitions/09-promotions.md +62 -0
- package/template/agents/definitions/10-feedback.md +128 -0
- package/template/agents/definitions/11-seo-infra.md +159 -0
- package/template/agents/definitions/12-deploy.md +173 -0
- package/template/agents/definitions/13-cro.md +275 -0
- package/template/agents/definitions/14-seo.md +388 -0
- package/template/agents/definitions/15-branding.md +26 -0
- package/template/agents/definitions/16-qa-visual.md +138 -0
- package/template/agents/definitions/17-aeo.md +257 -0
- package/template/app/(loja)/busca/page.tsx +72 -0
- package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
- package/template/app/(loja)/carrinho/page.tsx +160 -0
- package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
- package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
- package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
- package/template/app/(loja)/checkout/page.tsx +80 -0
- package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
- package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
- package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
- package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
- package/template/app/(loja)/conta/entrar/page.tsx +119 -0
- package/template/app/(loja)/conta/page.tsx +45 -0
- package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
- package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
- package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
- package/template/app/(loja)/devolucoes/page.tsx +36 -0
- package/template/app/(loja)/layout.tsx +22 -0
- package/template/app/(loja)/oferta/page.tsx +57 -0
- package/template/app/(loja)/page.tsx +86 -0
- package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
- package/template/app/(loja)/privacidade/page.tsx +113 -0
- package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
- package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
- package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
- package/template/app/(loja)/produtos/loading.tsx +16 -0
- package/template/app/(loja)/produtos/page.tsx +33 -0
- package/template/app/(loja)/termos/page.tsx +104 -0
- package/template/app/acesso/page.tsx +129 -0
- package/template/app/api/account/addresses/route.ts +39 -0
- package/template/app/api/account/exists/route.ts +21 -0
- package/template/app/api/account/me/route.ts +25 -0
- package/template/app/api/account/otp/route.ts +26 -0
- package/template/app/api/account/preferences/route.ts +19 -0
- package/template/app/api/account/signin/route.ts +27 -0
- package/template/app/api/account/signout/route.ts +9 -0
- package/template/app/api/acesso/route.ts +171 -0
- package/template/app/api/capi/route.ts +34 -0
- package/template/app/api/cart/coupon/route.ts +49 -0
- package/template/app/api/cart/items/route.ts +45 -0
- package/template/app/api/cart/link/route.ts +69 -0
- package/template/app/api/cart/route.ts +113 -0
- package/template/app/api/cart/share/route.ts +21 -0
- package/template/app/api/cep/[code]/route.ts +18 -0
- package/template/app/api/checkout/address/route.ts +24 -0
- package/template/app/api/checkout/email/route.ts +41 -0
- package/template/app/api/checkout/installments/route.ts +16 -0
- package/template/app/api/checkout/route.ts +110 -0
- package/template/app/api/checkout/shipping/route.ts +65 -0
- package/template/app/api/checkout-destination/route.ts +31 -0
- package/template/app/api/order/[ref]/route.ts +17 -0
- package/template/app/api/payment-link/route.ts +28 -0
- package/template/app/api/revalidate/route.ts +52 -0
- package/template/app/api/shipping/quote/route.ts +29 -0
- package/template/app/api/subscriptions/[id]/route.ts +52 -0
- package/template/app/api/track/route.ts +28 -0
- package/template/app/api/unbox/catalogo/route.ts +136 -0
- package/template/app/api/unbox/paginas/route.ts +167 -0
- package/template/app/api/unbox/vitrine/route.ts +84 -0
- package/template/app/api/webhooks/unbox/route.ts +97 -0
- package/template/app/apple-icon.svg +5 -0
- package/template/app/error.tsx +15 -0
- package/template/app/globals.css +457 -0
- package/template/app/icon.svg +4 -0
- package/template/app/layout.tsx +90 -0
- package/template/app/llms.txt/route.ts +43 -0
- package/template/app/manifest.ts +21 -0
- package/template/app/not-found.tsx +18 -0
- package/template/app/opengraph-image.tsx +35 -0
- package/template/app/robots.ts +31 -0
- package/template/app/sitemap.ts +42 -0
- package/template/bootstrap.sh +87 -0
- package/template/components/account/account-shell.tsx +47 -0
- package/template/components/account/address-book.tsx +147 -0
- package/template/components/account/preferences-form.tsx +77 -0
- package/template/components/account/reorder-button.tsx +47 -0
- package/template/components/account/signout-button.tsx +20 -0
- package/template/components/account/subscription-actions.tsx +204 -0
- package/template/components/account-nav.tsx +41 -0
- package/template/components/address-fields.tsx +102 -0
- package/template/components/analytics/data-layer-ready.tsx +16 -0
- package/template/components/analytics/purchase-tracker.tsx +32 -0
- package/template/components/brand-search.tsx +34 -0
- package/template/components/cart/cart-provider.tsx +340 -0
- package/template/components/cart/mini-cart.tsx +228 -0
- package/template/components/cart-button.tsx +35 -0
- package/template/components/catalog/catalog-client.tsx +523 -0
- package/template/components/catalog/grid-skeleton.tsx +19 -0
- package/template/components/catalog/pager.tsx +66 -0
- package/template/components/catalog/product-grid-card.tsx +52 -0
- package/template/components/catalog/product-grid.tsx +27 -0
- package/template/components/checkout/checkout-client.tsx +1333 -0
- package/template/components/chrome/announce-bar.tsx +57 -0
- package/template/components/chrome/chrome-recipe.ts +13 -0
- package/template/components/chrome/footers/captura-botao.tsx +28 -0
- package/template/components/chrome/footers/colunas.tsx +110 -0
- package/template/components/chrome/footers/conversao.tsx +90 -0
- package/template/components/chrome/footers/editorial.tsx +98 -0
- package/template/components/chrome/footers/minimal.tsx +55 -0
- package/template/components/chrome/header-bar-mobile.tsx +64 -0
- package/template/components/chrome/headers/centralizado.tsx +71 -0
- package/template/components/chrome/headers/classico.tsx +64 -0
- package/template/components/chrome/headers/compacto.tsx +66 -0
- package/template/components/chrome/headers/equilibrado.tsx +73 -0
- package/template/components/chrome/headers/imersivo.tsx +62 -0
- package/template/components/chrome/registry.ts +70 -0
- package/template/components/chrome/solid-on-scroll.tsx +39 -0
- package/template/components/empty-state.tsx +22 -0
- package/template/components/home/combo-card-compact.tsx +67 -0
- package/template/components/home/combos-home.tsx +64 -0
- package/template/components/home/combos-section.tsx +177 -0
- package/template/components/home/home-recipe.ts +41 -0
- package/template/components/home/sections/attributes-marquee.tsx +178 -0
- package/template/components/home/sections/benefits.tsx +80 -0
- package/template/components/home/sections/bloco-html.tsx +47 -0
- package/template/components/home/sections/catalogo.ts +165 -0
- package/template/components/home/sections/category-pills.tsx +32 -0
- package/template/components/home/sections/combos-carousel.tsx +141 -0
- package/template/components/home/sections/comparison.tsx +73 -0
- package/template/components/home/sections/founder-story.tsx +40 -0
- package/template/components/home/sections/hero.tsx +187 -0
- package/template/components/home/sections/kits.tsx +11 -0
- package/template/components/home/sections/media-cards.tsx +70 -0
- package/template/components/home/sections/newsletter.tsx +62 -0
- package/template/components/home/sections/product-showcase.tsx +175 -0
- package/template/components/home/sections/purchase-hero.tsx +187 -0
- package/template/components/home/sections/quote-banner.tsx +50 -0
- package/template/components/home/sections/registry.ts +186 -0
- package/template/components/home/sections/reviews-carousel.tsx +66 -0
- package/template/components/home/sections/reviews.tsx +68 -0
- package/template/components/home/sections/ritual.tsx +96 -0
- package/template/components/home/sections/savings.tsx +47 -0
- package/template/components/home/sections/social-row.tsx +65 -0
- package/template/components/home/sections/spec-table.tsx +117 -0
- package/template/components/home/sections/stars.tsx +12 -0
- package/template/components/home/sections/stats-grid.tsx +54 -0
- package/template/components/home/sections/trust-bar.tsx +56 -0
- package/template/components/home/sections/trust-strip.tsx +40 -0
- package/template/components/home/sections/video-wall.tsx +59 -0
- package/template/components/landing/landing-recipe.ts +20 -0
- package/template/components/landing/oferta-sections.tsx +40 -0
- package/template/components/landing/product-picker.tsx +257 -0
- package/template/components/mobile-nav.tsx +71 -0
- package/template/components/order-status.tsx +163 -0
- package/template/components/powered-by-unbox.tsx +27 -0
- package/template/components/product/pdp/buy-box.tsx +604 -0
- package/template/components/product/pdp/catalog-grid.tsx +113 -0
- package/template/components/product/pdp/faq-modelo.ts +86 -0
- package/template/components/product/pdp/gallery.tsx +87 -0
- package/template/components/product/pdp/interactive.tsx +200 -0
- package/template/components/product/pdp/newsletter.tsx +64 -0
- package/template/components/product/pdp/payment-chips.tsx +22 -0
- package/template/components/product/pdp/pdp-view.tsx +227 -0
- package/template/components/product/pdp/recommendations.tsx +144 -0
- package/template/components/product/pdp/sections.tsx +173 -0
- package/template/components/quantity-stepper.tsx +36 -0
- package/template/components/search-box.tsx +37 -0
- package/template/components/site-footer.tsx +59 -0
- package/template/components/site-header.tsx +64 -0
- package/template/components/ui/accordion.tsx +72 -0
- package/template/components/ui/alert-dialog.tsx +187 -0
- package/template/components/ui/alert.tsx +76 -0
- package/template/components/ui/aspect-ratio.tsx +22 -0
- package/template/components/ui/avatar.tsx +109 -0
- package/template/components/ui/badge.tsx +52 -0
- package/template/components/ui/breadcrumb.tsx +125 -0
- package/template/components/ui/button.tsx +58 -0
- package/template/components/ui/card.tsx +103 -0
- package/template/components/ui/carousel.tsx +242 -0
- package/template/components/ui/command.tsx +196 -0
- package/template/components/ui/dialog.tsx +160 -0
- package/template/components/ui/dropdown-menu.tsx +268 -0
- package/template/components/ui/foto.tsx +54 -0
- package/template/components/ui/input-group.tsx +158 -0
- package/template/components/ui/input-otp.tsx +87 -0
- package/template/components/ui/input.tsx +20 -0
- package/template/components/ui/label.tsx +20 -0
- package/template/components/ui/navigation-menu.tsx +168 -0
- package/template/components/ui/pagination.tsx +132 -0
- package/template/components/ui/popover.tsx +90 -0
- package/template/components/ui/progress.tsx +83 -0
- package/template/components/ui/radio-group.tsx +38 -0
- package/template/components/ui/scroll-area.tsx +55 -0
- package/template/components/ui/select.tsx +201 -0
- package/template/components/ui/separator.tsx +25 -0
- package/template/components/ui/sheet.tsx +138 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +44 -0
- package/template/components/ui/switch.tsx +32 -0
- package/template/components/ui/table.tsx +116 -0
- package/template/components/ui/tabs.tsx +82 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +89 -0
- package/template/components/ui/toggle.tsx +45 -0
- package/template/components/ui/tooltip.tsx +66 -0
- package/template/components.json +25 -0
- package/template/eslint.config.mjs +36 -0
- package/template/gitignore +21 -0
- package/template/lib/analytics.ts +369 -0
- package/template/lib/api.ts +60 -0
- package/template/lib/capi.ts +87 -0
- package/template/lib/cart-link.ts +51 -0
- package/template/lib/cart-normalize.ts +142 -0
- package/template/lib/cart-recovery.ts +23 -0
- package/template/lib/cart-response.ts +48 -0
- package/template/lib/catalog-map.ts +54 -0
- package/template/lib/catalog.ts +3 -0
- package/template/lib/checkout-lock.ts +21 -0
- package/template/lib/checkout-nav.ts +22 -0
- package/template/lib/config.ts +51 -0
- package/template/lib/crm.ts +40 -0
- package/template/lib/customer-session.ts +21 -0
- package/template/lib/dataloader.ts +23 -0
- package/template/lib/editable/config.ts +11 -0
- package/template/lib/editable/document.ts +1762 -0
- package/template/lib/editable/index.ts +3 -0
- package/template/lib/editable/primitives.tsx +821 -0
- package/template/lib/editable/provider.tsx +861 -0
- package/template/lib/editable/rastreio-navegacao.tsx +65 -0
- package/template/lib/editable/rastreio.tsx +168 -0
- package/template/lib/editable/server.ts +189 -0
- package/template/lib/editable/tokens.ts +22 -0
- package/template/lib/editable/verify.ts +24 -0
- package/template/lib/enrichment/combos.ts +184 -0
- package/template/lib/enrichment/index.ts +188 -0
- package/template/lib/enrichment/products.json +1 -0
- package/template/lib/env-check.ts +47 -0
- package/template/lib/format.ts +130 -0
- package/template/lib/icons.ts +67 -0
- package/template/lib/json-ld.ts +9 -0
- package/template/lib/llms-txt.ts +75 -0
- package/template/lib/mockup.ts +24 -0
- package/template/lib/newsletter.ts +12 -0
- package/template/lib/orders.ts +160 -0
- package/template/lib/queries.ts +66 -0
- package/template/lib/ratelimit.ts +47 -0
- package/template/lib/rotas-editaveis.ts +192 -0
- package/template/lib/sanitize.ts +36 -0
- package/template/lib/schemas.ts +78 -0
- package/template/lib/session.ts +98 -0
- package/template/lib/store-config.ts +67 -0
- package/template/lib/unbox/client.ts +829 -0
- package/template/lib/unbox/customer.ts +224 -0
- package/template/lib/unbox/errors.ts +97 -0
- package/template/lib/unbox/index.ts +10 -0
- package/template/lib/unbox/store.ts +113 -0
- package/template/lib/unbox/types.ts +195 -0
- package/template/lib/unbox/webhooks.ts +66 -0
- package/template/lib/utils.ts +6 -0
- package/template/lib/vitrine.ts +242 -0
- package/template/lib/webhook-store.ts +22 -0
- package/template/middleware.ts +178 -0
- package/template/next.config.ts +63 -0
- package/template/package.json +62 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/brand/coll/default.png +0 -0
- package/template/public/brand/hero-desktop.svg +12 -0
- package/template/public/brand/hero-mobile.svg +12 -0
- package/template/public/brand/heros/boutique-desktop.svg +11 -0
- package/template/public/brand/heros/boutique-mobile.svg +11 -0
- package/template/public/brand/heros/editorial-desktop.svg +11 -0
- package/template/public/brand/heros/editorial-mobile.svg +11 -0
- package/template/public/brand/heros/essencial-desktop.svg +12 -0
- package/template/public/brand/heros/essencial-mobile.svg +12 -0
- package/template/public/brand/heros/promocional-desktop.svg +12 -0
- package/template/public/brand/heros/promocional-mobile.svg +12 -0
- package/template/public/brand/logo-chrome.svg +6 -0
- package/template/public/brand/logo-white.svg +6 -0
- package/template/public/brand/logo.svg +7 -0
- package/template/public/brand/pay-amex.webp +0 -0
- package/template/public/brand/pay-elo.webp +0 -0
- package/template/public/brand/pay-mastercard.webp +0 -0
- package/template/public/brand/pay-pix.webp +0 -0
- package/template/public/brand/pay-visa.webp +0 -0
- package/template/public/brand/ph/avatar-a.svg +5 -0
- package/template/public/brand/ph/avatar-b.svg +5 -0
- package/template/public/brand/ph/avatar-c.svg +5 -0
- package/template/public/brand/ph/avatar-d.svg +5 -0
- package/template/public/brand/ph/photo-a.svg +7 -0
- package/template/public/brand/ph/photo-b.svg +6 -0
- package/template/public/brand/ph/photo-c.svg +6 -0
- package/template/public/brand/ph/poster-a.svg +6 -0
- package/template/public/brand/ph/poster-b.svg +6 -0
- package/template/public/brand/ph/poster-c.svg +6 -0
- package/template/public/unbox/powered-by-fundo-preto.png +0 -0
- package/template/public/unbox/powered-by-transparente.png +0 -0
- package/template/public/unbox/powered-by.png +0 -0
- package/template/scripts/abandoned-cart.ts +82 -0
- package/template/scripts/check-editable.mjs +467 -0
- package/template/scripts/check-honestidade.mjs +111 -0
- package/template/scripts/check-placeholder.mjs +200 -0
- package/template/scripts/check-recipe.mjs +76 -0
- package/template/scripts/check-unbox-brand.mjs +298 -0
- package/template/scripts/contraste.js +177 -0
- package/template/scripts/dump-catalog.ts +95 -0
- package/template/scripts/load-env.ts +13 -0
- package/template/scripts/medir-sistema.mjs +56 -0
- package/template/scripts/place-order-pix.ts +61 -0
- package/template/scripts/qa-screenshots.mjs +150 -0
- package/template/scripts/sistema.py +231 -0
- package/template/scripts/subscribe-webhook.ts +36 -0
- package/template/scripts/test-live.ts +186 -0
- package/template/scripts/vocabulario.py +264 -0
- package/template/tsconfig.json +27 -0
- package/tools/LEIA-ME.md +39 -0
- package/tools/check-template-neutro.mjs +111 -0
- package/tools/notion-doc.mjs +191 -0
- package/tools/tokenize-neutrals.mjs +0 -0
- package/tools/tokenize-radius.mjs +52 -0
- 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();
|