O que d1-cli — Tiendas D1 a partir da linha de comando faz?
D1 é a maior rede de descontos da Colômbia. Sua loja virtual funciona com
VTEX IO (conta d1tiendas), o que significa que a própria API pública do site é a superfície
de integração — sem scraping, sem credenciais de administrador.
Invocar
cd skills/commerce/d1-cli
# 1. Defina seu ponto de entrega PRIMEIRO — todo o resto depende disso
bun run src/cli.ts region --lat 4.6486 --lng=-74.0628
# 2. Depois pesquise: preços e estoque agora são da sua loja, não do país
bun run src/cli.ts search leche entera --count 5
bun run src/cli.ts search --facets category-1/lacteos-y-huevos --sort price:asc
# 3. Calcule o preço de uma cesta sem tocar no carrinho
bun run src/cli.ts quote 262:2 892:1
# 3b. Ou deixe um orçamento decidir o que entra — termos, não SKUs
bun run src/cli.ts basket --budget 30000 arroz leche aceite huevos
# 4. Ou monte uma cesta de verdade e depois passe para um humano pagar
bun run src/cli.ts cart add 262 --qty 2
bun run src/cli.ts cart deliver-to
bun run src/cli.ts cart checkout # imprime uma URL; não efetua pagamento
Adicione --json a qualquer comando para obter saída legível por agentes. Os códigos de saída são um
contrato, e esta lista é a única cópia dele:
| código | significado | repetir? |
|---|---|---|
0 | funcionou | — |
1 | D1 recusou ou estava inacessível — ponto não entregável, SKU indisponível ou desconhecido, falha | sim, pode ajudar |
2 | o comando foi chamado incorretamente | nunca ajuda |
3 | funcionou e a resposta é "nenhum" (substitute, basket) | nunca ajuda |
Um agente que não consegue separar esses casos ou repete um erro de digitação para sempre ou desiste
de uma falha transitória. 3 existe porque uma categoria vazia não é nenhuma das opções.
Observe que cart add define a linha para --qty; não adiciona a ela. Essa
é a semântica da própria D1, verificada ao vivo.
As duas coisas que tornam a automação ingênua da D1 errada
A disponibilidade é regionalizada. Consulte o catálogo sem região e a D1
responde com o catálogo nacional, relatando confortáveis 10.000 unidades contra o vendedor
virtual 1. Coloque o mesmo SKU em uma simulação de checkout para um endereço real e ele
retorna withoutStock, porque a loja física que o entregaria não o tem. Sempre use d1 region primeiro; a CLI avisa
em texto simples sempre que está mostrando preços nacionais.
Resolver uma região também é a parte menos intuitiva da API: as coordenadas vão como
geoCoordinates={lon};{lat} — separados por ponto e vírgula, longitude primeiro. Uma vírgula, que é o que os próprios corpos JSON da VTEX usam para o
mesmo par, é rejeitada com CHK0119 ("endereços devem ter um código postal ou
geocoordenadas") — uma mensagem que parece que o parâmetro está faltando em vez de malformado. test/region.test.ts fixa isso para que uma limpeza não possa desfazer.
Os preços chegam em duas unidades. Para o SKU 262 (Leche Entera Latti 900 ml, COP 3.500 na prateleira):
| API | Campo | Valor | Unidade |
|---|---|---|---|
| intelligent-search | Price | 3500 | pesos inteiros |
| catalog_system | Price | 3500.0 | pesos inteiros |
| checkout orderForm | sellingPrice | 350000 | centésimos |
Nenhum deles rotula sua unidade. catalog.ts normaliza tudo para centésimos na
fronteira para que o resto do código tenha exatamente uma unidade, e um teste afirma que um preço
originado da pesquisa é renderizado de forma idêntica a um originado do checkout.
O que ele não fará
d1 constrói e precifica cestas. Ele não paga. d1 cart checkout imprime a URL
onde um humano revisa o total e conclui o pagamento.
Essa fronteira é verificada em dois lugares a partir de uma lista (src/endpoints.ts):
em tempo de execução, D1Client.request recusa qualquer requisição cuja URL resolvida não seja a própria origem da D1
mais um de 18 caminhos aprovados antes que a requisição saia do processo; estaticamente,
test/safety.test.ts falha se qualquer literal /api/ em qualquer lugar em src/ não for
aprovado. Adicionar um endpoint — de pagamento ou outro — falha até que ele seja listado,
portanto a decisão é tomada em vez de ser assumida por padrão.
A metade em tempo de execução é essencial e foi adicionada após uma revisão mostrar que uma verificação estática
não é suficiente: --facets '../../../..' resolveu do endpoint de pesquisa para o
fechamento do pedido enquanto todos os literais de origem permaneciam aprovados.
Dito de forma precisa, porque versões anteriores deste arquivo exageravam: nada aqui impede um contribuidor com acesso de escrita de editar a lista de permissões. Nenhum teste no repositório pode.
A única credencial que ele armazena é um token de sessão da loja virtual — a mesma coisa que um
navegador logado possui, com escopo restrito aos próprios pedidos e carrinho do seu dono — gravado em
~/.config/d1-cli/session.json com modo 0600. Nenhum appKey/appToken de administrador VTEX é lido
em nenhum lugar. O login é feito por um código enviado por e-mail de uso único; a autenticação por senha
deliberadamente não é implementada.
O portão do checkout — sempre termine aqui
Finalize cada cesta com d1 cart checkout e aja de acordo com seu código de saída. Não como
uma formalidade: é o único comando que verifica novamente todo o carrinho contra a D1 como ele está
agora, e os carrinhos ficam desatualizados de maneiras que nada mais revela.
0 toda linha é entregável — a URL é segura para passar adiante
1 pelo menos uma linha NÃO PODE ser entregue; a URL é impressa, mas o carrinho está quebrado
2 o comando foi chamado incorretamente
Isso existe porque cada um desses casos aconteceu em uma cesta real:
- Uma linha esgotou durante a noite.
PAN ARTESANAL INTEGRALficouno tiene inventariohoras depois de ser adicionada. O carrinho ainda renderizava, ainda tinha um total — um total que estava silenciosamente 6.490 a menos, porque a linha morta havia saído dele. - Uma mudança de endereço tornou todas as linhas inviáveis. Nove itens retornaram
cannotBeDeliveredenquanto cada um ainda tinha um SLA válido e o carrinho ainda mostrava um total pagável. - O frete foi subnotificado em 12x por uma alocação por linha que parecia um total.
Em todos os três, o carrinho parecia bem. Uma cotação feita anteriormente em uma sessão não é evidência sobre o carrinho agora, então nunca entregue uma URL de checkout que você obteve antes da última mutação — execute o portão novamente.
Quando ele retorna 1, --json fornece undeliverable[] nomeando as linhas, e
readyToCheckout: false. Remova ou substitua essas linhas e execute novamente.
Substituindo uma linha que a D1 não pode fornecer
d1 substitute 192 --limit 5 # classifica produtos em estoque da mesma categoria
Saída 0 significa que há algo a propor; 3 significa que não há — um dos
dois comandos que usam 3, junto com d1 basket.
Três, não um, e a distinção é o ponto: 1 significa "D1 recusou, ou não pôde ser
acessada" em todos os outros lugares desta CLI, e convida a uma repetição. Uma categoria vazia nunca se torna
não vazia com uma repetição, então um agente com uma política de repetir no código 1 ficaria em loop
para sempre. Saída 0 com uma lista vazia é a outra resposta errada — ela é lida como sucesso e é atendida.
Cada candidato nomeia o que muda: marca, tamanho da embalagem, $/kg ou $/L, preço da
embalagem, e qualquer aviso da Ley 2120 ganho ou perdido. Essa é a saída, não a
pontuação. Uma -12% por litro para o mesmo 900 ml e o mesmo rótulo é uma proposta
diferente de uma que chega com Exceso en Azúcares anexado, e apenas os deltas as diferenciam.
Proponha; não assuma. substitute nunca escreve — ele imprime o
d1 cart add para um humano aprovar. Isso não é cautela por si só: o
cliente duas vezes preferiu uma linha removida a um substituto errado, disse isso
explicitamente, e estava certo em ambas as vezes. Apresente os candidatos e as compensações;
deixe a pessoa decidir, e esteja disposto a remover a linha.
Três coisas que ele lhe dirá que valem a pena ler:
- "Este item ainda está em estoque na sua loja." A disponibilidade é por loja, então uma
linha que falhou em outro lugar pode estar bem aqui. Verifique antes de substituir qualquer coisa.
Sem
--lat/--lngele diz isso em vez de adivinhar — não há loja para fazer uma afirmação por loja. - "ampliado para nível 2 de 3" — a categoria folha não tinha nada em estoque, então as sugestões vêm de um corredor mais amplo e são correspondentemente menos precisas.
- "apenas N foram comparados" — a categoria é maior que uma página. Aumente
--count. Isso é impresso também no resultado vazio, porque "nada aqui está em estoque" afirmado sobre uma varredura parcial precisa da ressalva mais, não menos.
Encontrando coisas quando o pedido é vago
Para "precisamos de arroz" em vez de "compre o SKU 1092", dois eixos importam e nenhum é padrão:
Classificar por preço unitário, não por preço da embalagem. --sort per-unit usa os dados PUM
que a lei colombiana exige que a D1 publique. As duas classificações realmente discordam:
ARROZ ESTÁNDAR 500 GRS é a embalagem mais barata a $1.550 e custa $3.100/kg,
enquanto o melhor valor nos mesmos resultados é $2.775/kg — em uma sacola de $5.550 que não
aparece em nenhum lugar dos cinco primeiros por preço de embalagem. Classificar por preço de embalagem dá
uma resposta pior enquanto parece correta.
Como a pesquisa da D1 não pode classificar por isso, --sort per-unit ordena a página que você
buscou, não todo o conjunto de resultados. Aumente --count (máx. 50) para ampliá-lo. A
saída diz isso em vez de sugerir um superlativo que não pode sustentar, e nomeia
quantos resultados não publicam tamanho e, portanto, não podem ser comparados.
O tamanho declarado de um multipacote é o pacote, não um item — não o "corrija".
Um censo de todos os 1.600 produtos descobriu que, entre os multipacotes com nome correspondente, 44
dos 46 onde a D1 diz o suficiente para decidir declaram o total do pacote. Analisar N UN
de um nome de produto e multiplicar corromperia esses 44 para corrigir 2. A contagem
em um nome não é evidência sobre o que o PUM significa.
resolvePackSize portanto lê descrições em vez de nomes. Em todos os
1.548 produtos que carregam um PUM, ele age sobre os 10 cuja descrição afirma tanto um
tamanho por item quanto uma contagem: três são corrigidos (SKU 718, 510, 1008), sete são
confirmados como já declarando o pacote, e todo o resto é deixado como declarado.
Leia os rótulos de advertência. Os produtos carregam as advertências frontais da Colômbia
(Exceso en Azúcares, Exceso en sodio, Exceso en grasas saturadas,
Exceso en grasas trans, Contiene Edulcorantes) em warnings[] sob
--json. A cobertura é parcial — aproximadamente 70–90% por categoria — então ausência significa
"não declarado", nunca "seguro".
Em seguida, restrinja com d1 facets <query> (categoria, marca, subcategoria com contagens)
antes de paginar: a pesquisa tem um limite de 50 páginas, então uma consulta ampla é melhor dividida por faceta
do que percorrida.
Comandos
| Comando | O que faz |
|---|---|
region --lat --lng | resolver e lembrar o ponto de entrega |
search <query> | encontrar produtos (--facets --sort --count --page --available) |
suggest <partial> · trending | autocompletar · o que a Colômbia está pesquisando |
categories · facets [query] | árvore de departamentos · filtros para uma consulta |
substitute <sku> | o que comprar em vez disso, e o que muda (--limit --count) |
quote <sku>[:qty]... | precificar uma cesta, sem mutação no carrinho |
| `cart [show | add |
login --email / --from-cookie · whoami · logout | conta |
orders · order <id> | histórico de pedidos (detalhe é ocultado para uma lista de permissões; --raw opta por incluir) |
stores near | lojas D1 perto de um ponto — apenas localizações, a D1 não oferece coleta |
Testes
bun test # unidades monetárias, a pegadinha do ponto e vírgula, quantidade em cotação,
# vacuidade de SKU desconhecido, normalização do carrinho, ocultação de pedidos, e
# a lista de permissão de endpoints que limita a superfície de pagamento
bun run lint && bun run typecheck
Os códigos de saída são um contrato, não decoração — a tabela em Invocar acima é a única cópia dele.
Consulte README.md para o mapa completo de endpoints, o comportamento do nível de frete, e
o que a API pública não fornecerá.