Communitygithub.com

d1-cli — Tiendas D1 desde la línea de comandos

Busca productos y precios en Tiendas D1 (Colombia, d1.com.co) desde la terminal, revisa la tienda más cercana, cotiza una canasta contra el stock real de tu tienda local y obtén la URL para finalizar el pago. Utiliza la API pública de VTEX IO sin necesidad de clave de administrador.

¿Qué es d1-cli — Tiendas D1 desde la línea de comandos?

Esta skill permite a un agente de IA (como Claude Code) interactuar con Tiendas D1, la cadena de supermercados hard-discount más grande de Colombia. La habilidad resuelve dos problemas críticos que hacen que la automatización ingenua falle: la disponibilidad está regionalizada (una consulta sin región devuelve el catálogo nacional, no lo que realmente puede comprar el usuario) y los precios llegan en dos unidades diferentes (búsqueda reporta pesos enteros, checkout reporta centésimas, un factor silencioso de 100x). d1-cli construye y cotiza canastas de compra, pero deliberadamente no puede pagar: entrega una URL de checkout para que un humano complete la transacción. Es útil para encontrar productos y precios de D1, verificar si D1 entrega en una dirección, armar o costear una canasta, comparar artículos o revisar pedidos anteriores. No es para otros retailers colombianos (Éxito, Jumbo, Ara, Alkosto) ni para pagar.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/broomva/skills/tree/HEAD/skills/commerce/d1-cli

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace d1-cli — Tiendas D1 desde la línea de comandos?

D1 es la cadena de supermercados hard-discount más grande de Colombia. Su tienda en línea funciona con VTEX IO (cuenta d1tiendas), lo que significa que la API pública del sitio es la superficie de integración — sin scraping, sin credenciales de administrador.

Invocar

cd skills/commerce/d1-cli

# 1. Resuelve tu punto de entrega PRIMERO — todo lo demás depende de esto
bun run src/cli.ts region --lat 4.6486 --lng=-74.0628

# 2. Luego busca: los precios y el stock ahora son de tu tienda, no del 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. Cotiza una canasta sin tocar tu carrito
bun run src/cli.ts quote 262:2 892:1

# 3b. O deja que un presupuesto decida qué incluir — términos, no SKUs
bun run src/cli.ts basket --budget 30000 arroz leche aceite huevos

# 4. O construye una real, luego pásala a un humano para 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 una URL; no paga

Agrega --json a cualquier comando para obtener salida legible por el agente. Los códigos de salida son un contrato, y esta lista es la única copia de él:

códigosignificado¿reintentar?
0funcionó—
1D1 rechazó o fue inalcanzable — punto no entregable, SKU no disponible o desconocido, interrupciónsí, puede ayudar
2el comando se invocó incorrectamentenunca ayuda
3funcionó y la respuesta es "ninguno" (substitute, basket)nunca ayuda

Un agente que no pueda separar estos o reintenta un error tipográfico para siempre o abandona una interrupción transitoria. 3 existe porque una categoría vacía no es ninguna de las otras.

Ten en cuenta que cart add establece la línea a --qty; no agrega a ella. Esa es la semántica propia de D1, verificada en vivo.

Las dos cosas que hacen que la automatización ingenua de D1 sea incorrecta

La disponibilidad está regionalizada. Si consultas el catálogo sin región, D1 responde desde el catálogo nacional, reportando cómodamente 10,000 unidades contra el vendedor ficticio 1. Pon el mismo SKU a través de una simulación de checkout para una dirección real y regresa withoutStock, porque la tienda física que lo enviaría no lo tiene. Siempre ejecuta d1 region primero; el CLI advierte en texto plano cada vez que te muestra precios nacionales.

Resolver una región también es la parte menos adivinable de la API: las coordenadas se envían como geoCoordinates={lon};{lat} — separadas por punto y coma, longitud primero. Una coma, que es lo que los propios cuerpos JSON de VTEX usan para el mismo par, se rechaza con CHK0119 ("las direcciones deben tener un código postal o geocoordenadas") — un mensaje que parece indicar que el parámetro falta en lugar de estar mal formado. test/region.test.ts fija esto para que una limpieza no pueda deshacerlo.

Los precios llegan en dos unidades. Para el SKU 262 (Leche Entera Latti 900 ml, COP 3,500 en el estante):

APICampoValorUnidad
intelligent-searchPrice3500pesos enteros
catalog_systemPrice3500.0pesos enteros
checkout orderFormsellingPrice350000centésimas

Ninguno etiqueta su unidad. catalog.ts normaliza todo a centésimas en el límite para que el resto del código tenga exactamente una unidad, y una prueba afirma que un precio obtenido de búsqueda se renderiza idéntico a uno obtenido de checkout.

Lo que no hará

d1 construye y cotiza canastas. No paga. d1 cart checkout imprime la URL donde un humano revisa el total y completa el pago.

Ese límite se verifica en dos lugares desde una sola lista (src/endpoints.ts): en tiempo de ejecución, D1Client.request rechaza cualquier solicitud cuya URL resuelta no sea el origen propio de D1 más uno de los 18 caminos aprobados antes de que la solicitud salga del proceso; estáticamente, test/safety.test.ts falla si algún literal /api/ en cualquier lugar bajo src/ no está aprobado. Agregar un endpoint — de pago o de otro tipo — falla hasta que se liste, por lo que la decisión se toma en lugar de asumirse por defecto.

La mitad en tiempo de ejecución es crítica y se agregó después de que una revisión mostrara que un escaneo estático no es suficiente: --facets '../../../..' resolvió fuera del endpoint de búsqueda hacia la liquidación de pedidos mientras todos los literales de origen permanecían aprobados.

Dicho con precisión, porque versiones anteriores de este archivo exageraban: nada aquí impide que un colaborador con acceso de escritura edite la lista de permitidos. Ninguna prueba dentro del repositorio puede hacerlo.

La única credencial que almacena es un token de sesión de la tienda — lo mismo que tiene un navegador con sesión iniciada, limitado a los pedidos y carrito de su propietario — escrito en ~/.config/d1-cli/session.json con modo 0600. Ningún appKey/appToken de administrador de VTEX se lee en ningún lado. El inicio de sesión es mediante un código enviado por correo electrónico de un solo uso; la autenticación por contraseña no está implementada deliberadamente.

La puerta de checkout — termina siempre aquí

Finaliza cada canasta con d1 cart checkout y actúa según su código de salida. No es una formalidad: es el único comando que vuelve a verificar todo el carrito contra D1 tal como está ahora, y los carritos se vuelven obsoletos de maneras que nada más revela.

0  cada línea es entregable — la URL es segura para entregar
1  al menos una línea NO se puede entregar; la URL se imprime pero el carrito está roto
2  el comando se invocó incorrectamente

Esto existe porque cada una de estas situaciones ocurrió en una canasta real:

  • Una línea se agotó durante la noche. PAN ARTESANAL INTEGRAL quedó no tiene inventario horas después de agregarse. El carrito todavía se renderizaba, todavía tenía un total — un total que silenciosamente era 6.490 más bajo, porque la línea muerta había desaparecido de él.
  • Un cambio de dirección dejó varadas todas las líneas. Nueve artículos regresaron cannotBeDelivered mientras cada uno aún tenía un SLA válido y el carrito aún mostraba un total pagable.
  • El envío se subestimó 12 veces por una asignación por línea que parecía un total.

En los tres casos, el carrito parecía estar bien. Una cotización tomada antes en una sesión no es evidencia sobre el carrito ahora, así que nunca entregues una URL de checkout que obtuviste antes de la última mutación — vuelve a ejecutar la puerta.

Cuando devuelve 1, --json da undeliverable[] nombrando las líneas, y readyToCheckout: false. Elimina o reemplaza esas líneas y ejecútalo de nuevo.

Reemplazar una línea que D1 no puede suministrar

d1 substitute 192 --limit 5        # clasifica productos en stock de la misma categoría

Salida 0 significa que hay algo que proponer; 3 significa que no lo hay — uno de los dos comandos que usan 3, junto con d1 basket.

Tres, no uno, y la distinción es el punto: 1 significa "D1 rechazó o no pudo ser contactado" en cualquier otro lugar de este CLI, e invita a reintentar. Una categoría vacía nunca se vuelve no vacía al reintentar, por lo que un agente con una política de reintentar en 1 se repetiría para siempre. La salida 0 con una lista vacía es la otra respuesta incorrecta — se lee como éxito y se actúa sobre ella.

Cada candidato nombra qué cambia: marca, tamaño de empaque, $/kg o $/L, precio del empaque, y cualquier advertencia de la Ley 2120 ganada o perdida. Eso es la salida, no la puntuación. Un -12% por litro para los mismos 900 ml y la misma etiqueta es una propuesta diferente de una que llega con Exceso en Azúcares adjunto, y solo los deltas las distinguen.

Propónlo; no lo tomes. substitute nunca escribe — imprime el d1 cart add para que un humano lo apruebe. Esto no es precaución por sí misma: el cliente prefirió dos veces que se eliminara una línea en lugar de un sustituto incorrecto, lo dijo explícitamente, y tuvo razón en ambas ocasiones. Presenta los candidatos y las compensaciones; deja que la persona decida, y estate dispuesto a eliminar la línea.

Tres cosas que te dirá que vale la pena leer:

  • "Esto todavía está en stock en tu tienda." La disponibilidad es por tienda, por lo que una línea que falló en otro lugar puede estar bien aquí. Verifica antes de reemplazar cualquier cosa. Sin --lat/--lng lo dice en lugar de adivinar — no hay tienda sobre la cual hacer una afirmación por tienda.
  • "ampliado al nivel 2 de 3" — la categoría hoja no tenía nada en stock, por lo que las sugerencias provienen de un pasillo más amplio y son correspondientemente menos estrictas.
  • "solo N fueron comparados" — la categoría es más grande que una página. Aumenta --count. Esto se imprime también en el resultado vacío, porque "nada aquí está en stock" afirmado sobre un barrido parcial necesita la advertencia aún más, no menos.

Encontrar cosas cuando la solicitud es vaga

Para "necesitamos arroz" en lugar de "compra el SKU 1092", dos ejes importan y ninguno es el predeterminado:

Ordena por precio unitario, no por precio de empaque. --sort per-unit utiliza los datos PUM que la ley colombiana exige que D1 publique. Las dos clasificaciones realmente discrepan: ARROZ ESTÁNDAR 500 GRS es el empaque más barato a $1.550 y cuesta $3.100/kg, mientras que el mejor valor en los mismos resultados es $2.775/kg — en una bolsa de $5.550 que no aparece en ningún lugar del top cinco por precio de empaque. Ordenar por precio de empaque da una respuesta peor mientras parece correcta.

Debido a que la búsqueda de D1 no puede ordenar por esto, --sort per-unit ordena la página que obtuviste, no todo el conjunto de resultados. Aumenta --count (máx. 50) para ampliarlo. La salida lo dice en lugar de implicar un superlativo que no puede respaldar, y nombra cuántos resultados no publican tamaño y por lo tanto no se pueden comparar.

El tamaño declarado de un multipaquete es el empaque, no un artículo — no lo "corrijas". Un censo de todos los 1,600 productos encontró que entre los multipaquetes con nombre coincidente, 44 de los 46 donde D1 dice lo suficiente para decidir declaran el total del empaque. Analizar N UN de un nombre de producto y multiplicar corrompería esos 44 para arreglar 2. El recuento en un nombre no es evidencia sobre lo que significa el PUM.

resolvePackSize por lo tanto lee descripciones en lugar de nombres. En todos los 1,548 productos que llevan un PUM, actúa en los 10 cuya descripción establece tanto un tamaño por artículo como un recuento: tres se corrigen (SKU 718, 510, 1008), siete se confirman como ya declarando el empaque, y todo lo demás se deja como está.

Lee las etiquetas de advertencia. Los productos llevan las advertencias frontales de Colombia (Exceso en Azúcares, Exceso en sodio, Exceso en grasas saturadas, Exceso en grasas trans, Contiene Edulcorantes) en warnings[] bajo --json. La cobertura es parcial — aproximadamente 70–90% por categoría — por lo que la ausencia significa "no declarado", nunca "seguro".

Luego reduce con d1 facets <query> (categoría, marca, subcategoría con recuentos) antes de paginar: la búsqueda tiene un límite de 50 páginas, por lo que una consulta amplia es mejor cortarla por faceta que recorrerla.

Comandos

ComandoQué hace
region --lat --lngresuelve y recuerda el punto de entrega
search <query>encuentra productos (--facets --sort --count --page --available)
suggest <parcial> · trendingautocompletar · qué está buscando Colombia
categories · facets [query]árbol de departamentos · filtros para una consulta
substitute <sku>qué comprar en su lugar, y qué cambia (--limit --count)
quote <sku>[:qty]...cotiza una canasta, sin mutar el carrito
`cart [showadd
login --email / --from-cookie · whoami · logoutcuenta
orders · order <id>historial de pedidos (el detalle está redactado a una lista de permitidos; --raw opta por mostrarlo completo)
stores neartiendas D1 cerca de un punto — solo ubicaciones, D1 no ofrece recogida en tienda

Tests

bun test        # unidades monetarias, la trampa del punto y coma, cantidad en cotización,
                # vacío de SKU desconocido, normalización de carrito, redacción de pedidos, y
                # la lista de endpoints permitidos que limita la superficie de pago
bun run lint && bun run typecheck

Los códigos de salida son un contrato, no una decoración — la tabla bajo Invocar arriba es la única copia de él.

Consulta README.md para el mapa completo de endpoints, el comportamiento de los niveles de envío y lo que la API pública no te dará.

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

broomva/skills

Local TTS, voice cloning, voice design, and video dubbing via the OmniVoice Studio MCP server (open-source ElevenLabs alternative; nothing leaves the machine, runs on MPS/CUDA/CPU). Use when: (1) generating speech from text in any of 646 languages, (2) cloning a voice from a 3-second reference clip, (3) designing a voice by gender/age/accent/pitch/style, (4) dubbing a video into another language, (5) listing voice profiles or personality presets, (6) producing narration where privacy, cost, or absent API keys matter, (7) non-English narration where Edge TTS/kokoro fall short, (8) batch audio for blog posts or content pipelines. Triggers: 'omnivoice', 'voice clone', 'clone this voice', 'tts', 'narrate', 'generate speech', 'voice synthesis', 'dub video', 'voice design', 'local tts', 'multilingual voice', 'narrate this post', 'elevenlabs alternative'.

broomva/skills

>- Speak an explanation out loud while working in any project — tiered text-to-speech with a pluggable backend (ElevenLabs by default and quota-guarded, macOS `say` via `--fast` for free instant local speech, local OmniVoice as an unlimited private tier). Markdown-aware, so code fences, URLs and deep paths collapse to short spoken placeholders instead of being dictated character by character, while snake_case identifiers survive intact so the listener can still search for them. Every utterance is saved to disk for later replay. Also carries a **talk mode** toggle: turn it on and the agent speaks a full readback of every turn, for as long as that session lasts — the whole response, not a summary of it, with `brief` and `marker` levels for when you want less. Talk mode is off by default and scoped to the single session that enabled it, so parallel agents in other worktrees stay silent. Use when the user asks to hear something rather than read it — an explanation of a change, a walkthrough of what just happen...

broomva/skills

Stateful, local-first household toxics inventory + swap engine. Identify the items in a home that carry endocrine disruptors and persistent chemicals (BPA/BPS, phthalates, PFAS/PTFE, parabens, flame retardants, VOCs, microplastics), score each by *real* exposure (severity x presence x how it's used x condition), and track the swap to a safer alternative from "flagged" -> "sourced" -> "swapped". Ships a grounded, cited knowledge graph of ~20 hazards, ~40 item-classes, and ~40 alternatives. Hands sourcing off to the `procurer` skill. The skill's state is the source of truth — the agent is the app.

broomva/skills

Tekton — the shared architecture-intent substrate for co-designing systems with the agent. One typed graph across six tiers (system / journey / data / infra / decisions / qualities); views are queries, not separate diagrams. The canonical artifact is a diff-friendly YAML model both human and agent read and write; it renders to Mermaid (agent-legible, GitHub-native) and a self-contained tabbed HTML viewer (human-visual). Cross-tier traceability (`tekton query <from> <to>`) answers "which infra does this user-journey step touch?" as a path query. USE WHEN: designing or thinking deeply about architecture, a system, a data model, user journeys/flows, or a technical plan WITH the agent; when a Category-C HTML doc isn't enough because you need to see AND edit AND traverse the design across tiers; "let's design X", "architect this", "model the system", "draw the flow", "how does this fit together", "diagram this", "/tekton". NOT FOR: a one-off throwaway diagram (use Mermaid inline); prose-only ADRs (write the ADR...

broomva/skills

Generate a polished Remotion video and X thread showcasing the full agent skills inventory. Use when creating social media content about skills, rendering category-based skill visualizations, or producing animated showcases of agent capabilities. Triggers on "skills video", "showcase skills", "skills thread", "render skills", "social content for skills", or requests to visualize the skills inventory.

broomva/skills

OpenCaptions extension for Content Engine — adds intent-driven CWI (Caption With Intent) captions to the post-production pipeline. Hooks into the grade → caption → final stage. Generates captions that understand video intent (pitch, volume, emotion, emphasis) and style themselves accordingly with variable font weight, size, and color. Uses the OpenCaptions CLI or MCP server. Triggers on: 'add captions', 'opencaptions', 'CWI captions', 'intent captions'.

broomva/skills

Produce polished product launch videos using the Liquid Glass aesthetic — dark void backgrounds, 3D perspective floating UI panels, particle effects, spring animations, and cinematic pacing. Built on Remotion with Imagen 4.0 for frames and Veo 3.1 for B-roll. Use when: (1) creating a product demo or launch video, (2) showcasing a UI/app/tool with cinematic polish, (3) building a social-ready video from screenshots and renders, (4) applying the liquid glass floating panel style, (5) composing Remotion videos with 3D transforms and spring animations. Triggers on: 'launch video', 'product video', 'liquid glass video', 'demo video', 'showcase video', 'remotion video', 'floating panel', 'glass aesthetic'.

Skills relacionados