zugzbot 1.4.0 → 1.5.0
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.
|
@@ -50,12 +50,13 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
|
|
|
50
50
|
1. **Inicialización Atómica**: Llama obligatoriamente a `sdd_get_initial_session_data` para obtener estado, memorias e Oh-My-Design en un solo turno.
|
|
51
51
|
2. **Brain Pre-carga (OBLIGATORIO)**: Inmediatamente después, invoca `brain_read_memory({ category: "learnings" })` y `brain_read_memory({ category: "errors" })` para extraer errores y lecciones históricas que prevengan repetir problemas conocidos. Guarda el resultado en una variable interna para inyectarlo en cada brief de subagente.
|
|
52
52
|
2.5 **Detección de intención de catálogo externo (CRÍTICO — bugfix sesión 1176)**: Analiza la petición. Si contiene ALGUNA keyword de marketing/landing/auth/composite, animaciones/shaders/WebGL, AI components, o un nombre propio de cualquier catálogo:
|
|
53
|
-
- Keywords marketing/landing (shadcn): `hero`, `landing`, `pricing`, `features`, `faq`, `testimonials`, `footer`, `cta`, `navbar`, `
|
|
53
|
+
- Keywords marketing/landing (shadcn): `hero`, `landing`, `pricing`, `features`, `faq`, `testimonials`, `footer`, `cta`, `navbar`, `logo cloud`, `comparison`, `use cases`, `how it works`, `changelog`, `cookie banner`, `newsletter`, `bento`, `marquee`, `gallery`, `integration`
|
|
54
|
+
- Keywords forms/stats/tables (blocks-so): `form`, `formulario`, `stats`, `métricas`, `KPIs`, `tabla`, `data table`, `data grid`, `file upload`, `subir archivo`, `drag and drop`, `dialog`, `modal`, `command menu`, `command palette`, `onboarding`, `ai component`, `ai chat`, `grid list`, `login page`, `signup page`, `forgot password`
|
|
54
55
|
- Keywords animaciones (reactbits): `shader`, `webgl`, `gsap`, `framer-motion`, `three.js`, `drei`, `animated background`, `animated text`, `gradient mesh`, `magnet`, `floating dock`, `cursor`, `particles`, `noise`, `orbit`, `aurora`, `dither`, `iridescence`, `blob`, `glare`, `staggered`, `magic bento`, `electric border`, `metallic paint`
|
|
55
|
-
- Nombres propios: `akash`, `akash3444`, `shadcnui-blocks`, `shadcn-ui-blocks`, `basecn`, `shadcn blocks`, `reactbits`, `react-bits`, `david haz`
|
|
56
|
-
- **Acción obligatoria**: cargar la skill `shadcn-templates` (que ahora referencia las tools MCP `sdd_catalog_list_blocks`, `sdd_catalog_get_block` y `sdd_catalog_warm_index` — NUNCA uses `webfetch` directo a GitHub/reactbits.dev; esta cacheado).
|
|
57
|
-
- En modo HIL: llama `sdd_catalog_list_blocks({category, limit: 5, registry: 'all'})` y presenta las 2-3 mejores opciones con `preview_url` para que el usuario elija. **Incluir siempre `preview_url` en la respuesta** — para reactbits el demo es animado y el usuario puede ver cómo se ve antes de instalar.
|
|
58
|
-
- En modo autopiloto (`/loop`): llama `sdd_catalog_get_block({name})` sobre 2-3 candidatos (uno de cada registry) y elige el que mejor encaje.
|
|
56
|
+
- Nombres propios: `akash`, `akash3444`, `shadcnui-blocks`, `shadcn-ui-blocks`, `basecn`, `shadcn blocks`, `reactbits`, `react-bits`, `david haz`, `ephraim`, `ephraimduncan`, `blocks.so`, `blocks-so`, `blocks so`
|
|
57
|
+
- **Acción obligatoria**: cargar la skill `shadcn-templates` (que ahora referencia las tools MCP `sdd_catalog_list_blocks`, `sdd_catalog_get_block` y `sdd_catalog_warm_index` — NUNCA uses `webfetch` directo a GitHub/reactbits.dev/blocks.so; esta cacheado).
|
|
58
|
+
- En modo HIL: llama `sdd_catalog_list_blocks({category, limit: 5, registry: 'all'})` y presenta las 2-3 mejores opciones con `preview_url` para que el usuario elija. **Incluir siempre `preview_url` en la respuesta** — para reactbits el demo es animado, para blocks.so es live en `https://blocks.so/<category>/<name>`, y el usuario puede ver cómo se ve antes de instalar.
|
|
59
|
+
- En modo autopiloto (`/loop`): llama `sdd_catalog_get_block({name})` sobre 2-3 candidatos (uno de cada registry) y elige el que mejor encaje. Prioriza `blocks-so` si la keyword cae en sus 11 categorías (stats, login, form-layout, file-upload, tables, dialogs, sidebar, command-menu, ai, onboarding, grid-list).
|
|
59
60
|
- Esto evita el ciclo "ping-pong" de 3+ turnos que vimos en sesión 1176 donde el usuario tuvo que guiar al orquestador hacia el catálogo correcto.
|
|
60
61
|
3. **Detección Fast-Track Dashboard (CRÍTICO — bugfix sesión 1186 y 1180)**: Analiza la petición del usuario. Si contiene ALGUNA de estas keywords: `dashboard`, `admin panel`, `panel de control`, `panel admin`, `crm`, `erp`, `panel`:
|
|
61
62
|
- **NO preguntes NADA**. Asume todos los defaults: Stack = Next.js 16 + Shadcn UI + Tailwind v4, Persistencia = localStorage/mock, Diseño = `default` (zinc nativo de Shadcn, NO inyectar marcas de Oh-My-Design), Verificación = `console`.
|
|
@@ -142,7 +143,7 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
|
|
|
142
143
|
- Anúnciala en 1 línea y delega la siguiente iteración completa a un NUEVO hilo `sdd-orchestrator` con contexto de 0 tokens.
|
|
143
144
|
- Si `loopCurrentIteration >= loopTargetIterations` o modo normal: Concluye con `sdd_set_phase({ phase: "F0_DETECT", loopMode: false })`.
|
|
144
145
|
2. **Brain save OBLIGATORIO**: Sintetiza 1-3 lecciones y regístralas con `brain_save_memory` (categoría: `learnings`, `errors`, `design` o `routing`). BLOQUEANTE — no omitir.
|
|
145
|
-
2.5 **Memory Post-Sesión para Catálogos (CRÍTICO — bugfix sesión 1176)**: Si la sesión tocó cualquier catálogo externo de UI (Akash shadcn-ui-blocks, basecn, reactbits.dev), registra OBLIGATORIAMENTE en `brain.routing`:
|
|
146
|
+
2.5 **Memory Post-Sesión para Catálogos (CRÍTICO — bugfix sesión 1176)**: Si la sesión tocó cualquier catálogo externo de UI (Akash shadcn-ui-blocks, basecn, reactbits.dev, blocks.so), registra OBLIGATORIAMENTE en `brain.routing`:
|
|
146
147
|
- URL canónica del catálogo
|
|
147
148
|
- Comando `npx shadcn@latest add <URL>` listo para usar
|
|
148
149
|
- Nombre del autor/repo (para evitar redescubrimiento en próximas sesiones)
|
|
@@ -164,7 +165,7 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
|
|
|
164
165
|
<mcp_guidelines>
|
|
165
166
|
- **MCPs**: `shadcn` (UI), `context7` (APIs Next.js/FastAPI), `playwright` (Visual tests), `lucide-icons` (Iconos).
|
|
166
167
|
- `next-devtools` deshabilitado por defecto. MCPs invocados exclusivamente por subagentes.
|
|
167
|
-
- **Catálogos externos (Tools internas)**: `sdd_catalog_list_blocks`, `sdd_catalog_get_block`, `sdd_catalog_warm_index` — cachean el catálogo unificado (Akash shadcn-ui-blocks + basecn + reactbits.dev) en `.openspec/cache/` con TTL 7d (Akash/Basecn) y 1d (reactbits). NUNCA uses `webfetch` directo a GitHub o reactbits.dev para descubrir bloques; usa siempre estas tools. Solo se soportan registries con catálogo JSON oficialmente discoverable.
|
|
168
|
+
- **Catálogos externos (Tools internas)**: `sdd_catalog_list_blocks`, `sdd_catalog_get_block`, `sdd_catalog_warm_index` — cachean el catálogo unificado (Akash shadcn-ui-blocks + basecn + reactbits.dev + blocks.so) en `.openspec/cache/` con TTL 7d (Akash/Basecn/blocks-so) y 1d (reactbits). NUNCA uses `webfetch` directo a GitHub o reactbits.dev para descubrir bloques; usa siempre estas tools. Solo se soportan registries con catálogo JSON oficialmente discoverable.
|
|
168
169
|
</mcp_guidelines>
|
|
169
170
|
|
|
170
171
|
<communication_rules>
|
|
@@ -33,14 +33,18 @@ Para optimizar el tiempo de desarrollo, garantizar una experiencia visual premiu
|
|
|
33
33
|
- **Preservación de Datos Dummy**: El archivo de datos del bloque (por ejemplo, `data.json` o constantes locales) debe mantenerse intacto en su ruta inyectada por la CLI en esta Fase A para garantizar que la vista se muestre de inmediato en el navegador exactamente idéntica al demo real.
|
|
34
34
|
- **Sustitución Atómica de Datos en Fase B**: Una vez que el bloque compila e interactúa en el navegador de manera idéntica al demo con los datos de ejemplo, el Coder procederá de forma exclusiva a sustituir los datos mock por lógica y fetchings de datos dinámicos del backend, manteniendo intactos los contenedores estructurales del bloque (grids de diseño, paddings y contenedores flex) sin alteración alguna para evitar aberraciones visuales.
|
|
35
35
|
- **Resolución Activa de Conflictos de Ruteo por Inyección de Bloques**: Si la CLI de Shadcn inyecta automáticamente una estructura de rutas (ej: `src/app/dashboard/page.tsx`) que compite con las rutas del contrato (como un route group `(dashboard)` o una ruta raíz), el Coder **debe** borrar o refactorizar de inmediato el código inyectado por defecto para evitar bucles de redirección infinitos o colisiones en el router.
|
|
36
|
-
- **Catálogo Unificado `sdd_catalog` (
|
|
37
|
-
1. **`shadcn`** (akash3444/shadcn-ui-blocks, GitHub Contents API) — ~540 bloques Radix/Next.js curados. **Default** para marketing, landing,
|
|
36
|
+
- **Catálogo Unificado `sdd_catalog` (4 registries, todos discoverables)**: Antes de inventar cualquier layout/bloque desde cero, el Spec-Writer (F1) y el Coder (F2) **deben** consultar `sdd_catalog_list_blocks({ registry: "all", category: "<cat>" })`. El catálogo cubre 4 fuentes con catálogo JSON oficial en un solo tool:
|
|
37
|
+
1. **`shadcn`** (akash3444/shadcn-ui-blocks, GitHub Contents API) — ~540 bloques Radix/Next.js curados. **Default** para marketing, landing, hero, pricing, features, faq, testimonials, footer, cta, navbar, logo-cloud, bento, marquee, comparison, use-cases, how-it-works, changelog, cookie-banner, newsletter.
|
|
38
38
|
2. **`basecn`** (akash3444/basecn, GitHub Contents API) — ~60 bloques fork Base UI. Usar solo si el proyecto está en Base UI.
|
|
39
39
|
3. **`reactbits`** (reactbits.dev, `/r/registry.json` oficial shadcn-compatible) — 134 primitivas animadas × 4 variantes (JS-CSS/JS-TW/TS-CSS/TS-TW, default `TS-TW`). **Fuente preferente** para backgrounds animados, textos con efecto, cards interactivas, cursors custom, dock flotante. Tratar como third-party trust: revisar siempre el campo `dependencies` (suelen incluir `gsap`, `three`, `@react-three/fiber`, `motion`) y validar compat con el stack target antes de instalar.
|
|
40
|
+
4. **`blocks-so`** (ephraimduncan/blocks, GitHub Contents API sobre `/content/components/`) — 60+ bloques Radix prod-ready con `categories[]` explícito en cada JSON. **Default** para sus 11 categorías: `stats` (15 vars), `login` (9 vars), `form-layout` (5 vars), `file-upload` (6 vars), `tables` (5 vars), `dialogs` (12 vars), `sidebar` (6 vars), `command-menu` (3 vars), `ai` (5 vars), `onboarding` (7 vars), `grid-list` (3 vars). MIT license, autor Ephraim Duncan (1.7k⭐). Para `sidebar` y `login` hay empates con shadcn — el orquestador prefiere blocks-so y solo consulta shadcn si el usuario lo pide explícito o si la categoría quedó sin resultados.
|
|
40
41
|
- **Criterio de inclusion**: solo se soportan registries con catálogo JSON oficialmente discoverable. 21st.dev fue removido porque su catálogo es JS-rendered y rompe el principio de "siempre saber qué hay disponible antes de elegir".
|
|
41
|
-
- El comando install siempre viene en el campo `install_command` del resultado de `sdd_catalog_get_block`. **Prohibido** ejecutar `npx shadcn add` con una URL adivinada; usar siempre el `install_command` literal que devuelve la tool.
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
- El comando install siempre viene en el campo `install_command` del resultado de `sdd_catalog_get_block`. **Prohibido** ejecutar `npx shadcn add` con una URL adivinada; usar siempre el `install_command` literal que devuelve la tool. Formatos válidos por registry:
|
|
43
|
+
- shadcn/basecn: `npx shadcn@latest add https://shadcnui-blocks.com/r/radix/<name>.json --yes` o `https://basecn.dev/r/<name>.json`
|
|
44
|
+
- reactbits: `npx shadcn@latest add https://reactbits.dev/r/<Name>-TS-TW.json --yes`
|
|
45
|
+
- blocks-so: `npx shadcn@latest add https://blocks.so/r/<name>.json --yes` (o `@blocks-so/<name>` si `components.json` del proyecto lo tiene declarado).
|
|
46
|
+
- El catálogo guarda caché local en `.openspec/cache/` con TTL diferenciado (7d shadcn/basecn/blocks-so, 1d reactbits) — **NUNCA** usar `webfetch` directo a GitHub o reactbits.dev, está cacheado.
|
|
47
|
+
- Todos los items exponen `preview_url`. Para blocks.so el preview es live (`https://blocks.so/<category>/<name>`); para reactbits también (`https://reactbits.dev/<BaseName>`). El orchestrator debe **siempre** presentar el `preview_url` al usuario en modo HIL para que vea cómo se ve antes de elegir.
|
|
44
48
|
|
|
45
49
|
## 4. Soporte para Subdirectorios, Monorepos (targetDir) y Estándar Corporativo `src/`
|
|
46
50
|
Si el proyecto o aplicación principal vive en un subdirectorio (ej. `next-app`, `frontend`, `backend`), se deben seguir estas directivas estrictas para mantener la consistencia y el "estándar corporativo con carpeta `src/`":
|
|
@@ -154,54 +154,58 @@ El `@sdd-coder` utiliza el template base `nextjs-shadcn`. Si el contrato no deta
|
|
|
154
154
|
|
|
155
155
|
## 5. Catálogo Unificado de Bloques Externos (`sdd_catalog`)
|
|
156
156
|
|
|
157
|
-
Además del catálogo nativo de Shadcn UI, el arnés expone un catálogo unificado de **
|
|
157
|
+
Además del catálogo nativo de Shadcn UI, el arnés expone un catálogo unificado de **4 registries** externos (todos con catálogo JSON oficial y discoverable programáticamente). Si el usuario pide cualquier interfaz (hero, footer, dashboard, login, AI chat, animated background, stats, form, table, etc.), **TIENES LA OBLIGACIÓN** de explorar este catálogo ANTES de inventar desde cero.
|
|
158
158
|
|
|
159
159
|
### 5.0 Registries Soportados (todos discoverables)
|
|
160
160
|
|
|
161
161
|
| Registry | Fuente de catálogo | Contenido | TTL | Notas |
|
|
162
162
|
|---|---|---|---|---|
|
|
163
|
-
| `shadcn` | akash3444/shadcn-ui-blocks (GitHub Contents API) | ~540 bloques Radix | 7d | **Default** para marketing/landing/
|
|
163
|
+
| `shadcn` | akash3444/shadcn-ui-blocks (GitHub Contents API) | ~540 bloques Radix | 7d | **Default** para marketing/landing/hero/pricing/features/faq/testimonials/footer/cta/navbar/logo-cloud/bento/marquee |
|
|
164
164
|
| `basecn` | akash3444/basecn (GitHub Contents API) | ~60 bloques fork Base UI | 7d | Alternativa si el proyecto usa Base UI |
|
|
165
165
|
| `reactbits` | reactbits.dev/r/registry.json (JSON oficial shadcn) | 134 bases × 4 variantes = 536 items | 1d | Primitivas animadas (backgrounds, text, cards, cursors, etc.) |
|
|
166
|
+
| `blocks-so` | ephraimduncan/blocks (GitHub Contents API sobre `/content/components/`) | 60+ bloques Radix con `categories[]` explícito en cada JSON | 7d | **Default** para `stats` (15), `login` (9), `form-layout` (5), `file-upload` (6), `tables` (5), `dialogs` (12), `sidebar` (6), `command-menu` (3), `ai` (5), `onboarding` (7), `grid-list` (3). MIT, Ephraim Duncan. |
|
|
166
167
|
|
|
167
168
|
> **Criterio de inclusion**: solo se soportan registries con catálogo JSON oficialmente discoverable. 21st.dev fue removido porque su catálogo es JS-rendered y requiere scraping para descubrir items nuevos — eso rompe el principio de "siempre saber qué hay disponible antes de elegir".
|
|
168
169
|
|
|
169
170
|
### 5.1 Herramientas MCP Nativas del Arnés (PREFERENTE — `sdd_catalog_*`)
|
|
170
171
|
|
|
171
|
-
**PROHIBIDO** usar `webfetch` directo a GitHub
|
|
172
|
+
**PROHIBIDO** usar `webfetch` directo a GitHub, reactbits.dev o blocks.so para descubrir bloques. El arnés expone 3 tools MCP dedicadas que cachean en `.openspec/cache/` y evitan redescubrimiento:
|
|
172
173
|
|
|
173
174
|
- `sdd_catalog_list_blocks({ category?, query?, registry?, limit?, force_refresh? })`
|
|
174
|
-
- `registry`: `"shadcn"` | `"basecn"` | `"reactbits"` | `"all"` (default).
|
|
175
|
-
- Filtra por categoria exacta (`hero`, `pricing`, `backgrounds`, `text`, `cards`, `navigation`, ...) o substring libre (sobre `name`, `description`, `tags`).
|
|
175
|
+
- `registry`: `"shadcn"` | `"basecn"` | `"reactbits"` | `"blocks-so"` | `"all"` (default).
|
|
176
|
+
- Filtra por categoria exacta (`hero`, `pricing`, `backgrounds`, `text`, `cards`, `navigation`, `stats`, `login`, `form-layout`, ...) o substring libre (sobre `name`, `description`, `tags`).
|
|
176
177
|
- Para `reactbits`: devuelve las **134 bases** (cada una con `variants[]` y `default_variant`), NO las 536 variantes. Esto evita duplicados y permite ver opciones de un vistazo.
|
|
178
|
+
- Para `blocks-so`: devuelve los **60+ bloques Radix** distribuidos en 11 categorías oficiales. La categoría se infiere del path del directorio GitHub (`content/components/<category>/`), no del nombre del archivo.
|
|
177
179
|
- `sdd_catalog_get_block({ name, registry?, variant?, force_refresh? })`
|
|
178
180
|
- Formatos de `name`:
|
|
179
181
|
- shadcn/basecn: `"hero-06"`, `"sidebar-07"`
|
|
182
|
+
- blocks-so: `"stats-01"`, `"login-03"`, `"form-layout-02"`, `"ai-01"`, etc.
|
|
180
183
|
- reactbits: `"Dither"` (auto-resuelve a `Dither-TS-TW` por default) **o** `"Dither-JS-CSS"` (variante canónica completa)
|
|
181
|
-
- URL directa: `"https://reactbits.dev/r/Dither-TS-TW.json"` o `"https://shadcnui-blocks.com/r/radix/hero-06.json"`
|
|
182
|
-
- Namespace shadcn: `"@acme/button"`
|
|
184
|
+
- URL directa: `"https://reactbits.dev/r/Dither-TS-TW.json"`, `"https://blocks.so/r/stats-01.json"` o `"https://shadcnui-blocks.com/r/radix/hero-06.json"`
|
|
185
|
+
- Namespace shadcn: `"@acme/button"` o `"@blocks-so/stats-01"` (auto-routing a blocks-so)
|
|
183
186
|
- Para reactbits, parámetro opcional `variant`: `"JS-CSS" | "JS-TW" | "TS-CSS" | "TS-TW"` (default `"TS-TW"`). Aplicar solo cuando pasas el nombre base sin sufijo de variante.
|
|
184
187
|
- `registry` autodetecta por el formato; pasalo explícito solo si necesitas forzar.
|
|
185
188
|
- Devuelve `source_files[]`, `dependencies[]`, `preview_url`, `install_command` listo y metadatos `base_name` + `variant` (para reactbits).
|
|
186
189
|
- `sdd_catalog_warm_index({ registry? })`
|
|
187
|
-
- Pre-calienta caches. Llamar al bootstrap (F0) o inicio de F1.
|
|
190
|
+
- Pre-calienta caches. Llamar al bootstrap (F0) o inicio de F1. Para `blocks-so` consume ~12 GitHub API calls (1 para categorias + 11 para listar archivos) — TTL 7d minimiza esto.
|
|
188
191
|
|
|
189
192
|
### 5.2 Flujo de Descubrimiento (Fase F1 - Spec-Writer)
|
|
190
193
|
|
|
191
194
|
Cuando el usuario pide una UI, sigue este orden de prioridad:
|
|
192
195
|
|
|
193
|
-
1. **Primero** `sdd_catalog_list_blocks({ registry: "all", category: "<categoria>", limit: 10 })` — devuelve candidatos de los
|
|
194
|
-
2. **Si
|
|
195
|
-
3. **Si el usuario pide
|
|
196
|
-
4. **Si el usuario
|
|
197
|
-
5. **
|
|
196
|
+
1. **Primero** `sdd_catalog_list_blocks({ registry: "all", category: "<categoria>", limit: 10 })` — devuelve candidatos de los 4 registries simultáneamente.
|
|
197
|
+
2. **Si la keyword cae en las 11 categorías de blocks-so** (stats, login, signup, form-layout, file-upload, tables, dialogs, sidebar, command-menu, ai, onboarding, grid-list), prioriza `registry: "blocks-so"`. Es la fuente más actualizada y curada para esos casos de uso. Ejemplo: si el usuario pide *"un formulario de registro"*, el Spec-Writer debe ir directo a `sdd_catalog_list_blocks({registry: "blocks-so", category: "form-layout", limit: 5})` y mostrar las 5 variantes (`form-layout-01` a `form-layout-05`).
|
|
198
|
+
3. **Si el usuario pide animaciones/shaders/efectos visuales cinematograficos**, prioriza `registry: "reactbits"` (es donde estan las primitivas animadas reales: `Dither`, `Aurora`, `GlareHover`, `Magnet`, `StaggeredMenu`, `FloatingDock`, etc.).
|
|
199
|
+
4. **Si el usuario pide paginas de marketing completas** (landing, pricing, features, hero), prioriza `registry: "shadcn"` (bloques Radix mas completos con texto, CTAs, imagenes placeholder).
|
|
200
|
+
5. **Si el usuario ya dio una URL** (`https://reactbits.dev/...`, `https://blocks.so/...` o `https://shadcnui-blocks.com/...`), ve directo a `sdd_catalog_get_block` con esa URL.
|
|
201
|
+
6. **Presenta opciones con `preview_url`** para que el usuario vea cada una antes de elegir:
|
|
198
202
|
- **Asistido (HIL):** muestra `preview_url` (que el usuario puede abrir en navegador) + `install_command` para los 2-3 mejores candidatos y deja que elija. Si el usuario dice "ese", ejecuta el `install_command`.
|
|
199
203
|
- **Autopiloto (`/loop`):** inspecciona 2-3 candidatos con `sdd_catalog_get_block`, analiza dependencias y compat con el stack del contrato, elige autonomamente.
|
|
200
204
|
|
|
201
205
|
### 5.3 Seleccion de Bloques (Interactiva o Autonoma)
|
|
202
206
|
|
|
203
207
|
Cuando hay multiples opciones para una categoria (ej. 10 heroes):
|
|
204
|
-
- **Modo Asistido (default):** pregunta al usuario con `question` o en chat: *"Tengo X opciones de [categoria]. ¿Quieres revisar alguna? Puedo darte el `preview_url` (ej. `https://shadcnui-blocks.com/blocks/hero-06` o `https://reactbits.dev/Dither`) para que elijas."*
|
|
208
|
+
- **Modo Asistido (default):** pregunta al usuario con `question` o en chat: *"Tengo X opciones de [categoria]. ¿Quieres revisar alguna? Puedo darte el `preview_url` (ej. `https://shadcnui-blocks.com/blocks/hero-06`, `https://blocks.so/stats/stats-04` o `https://reactbits.dev/Dither`) para que elijas."*
|
|
205
209
|
- **Modo Autopiloto:** lee el codigo de 2-3 opciones con `sdd_catalog_get_block` y elige sin preguntar.
|
|
206
210
|
|
|
207
211
|
### 5.4 Inyeccion e Instalacion (Spec-Writer + Coder)
|
|
@@ -211,6 +215,7 @@ Cuando hay multiples opciones para una categoria (ej. 10 heroes):
|
|
|
211
215
|
"frontend": {
|
|
212
216
|
"components": [
|
|
213
217
|
{ "name": "hero-06", "registry": "shadcn", "source": "akash3444/shadcn-ui-blocks" },
|
|
218
|
+
{ "name": "stats-01", "registry": "blocks-so", "source": "ephraimduncan/blocks" },
|
|
214
219
|
{ "name": "Dither", "registry": "reactbits", "variant": "TS-TW", "source": "reactbits.dev/Dither" }
|
|
215
220
|
]
|
|
216
221
|
}
|
|
@@ -223,6 +228,10 @@ Cuando hay multiples opciones para una categoria (ej. 10 heroes):
|
|
|
223
228
|
npx shadcn@latest add https://basecn.dev/r/footer-04.json --yes
|
|
224
229
|
# reactbits (variante TS-TW explicita)
|
|
225
230
|
npx shadcn@latest add https://reactbits.dev/r/Dither-TS-TW.json --yes
|
|
231
|
+
# blocks-so (ephraimduncan/blocks)
|
|
232
|
+
npx shadcn@latest add https://blocks.so/r/stats-01.json --yes
|
|
233
|
+
# blocks-so via namespace (requiere declararlo en components.json)
|
|
234
|
+
npx shadcn@latest add @blocks-so/stats-01 --yes
|
|
226
235
|
```
|
|
227
236
|
La CLI v3 de Shadcn negocia `Accept: application/json` automaticamente, descarga el codigo y resuelve las `registryDependencies`.
|
|
228
237
|
|
|
@@ -263,8 +272,55 @@ reactbits.dev es el registry mas "vivo" del catalogo unificado. Cada componente
|
|
|
263
272
|
| shadcn | `https://shadcnui-blocks.com/blocks/<name>` | iframe del demo oficial |
|
|
264
273
|
| basecn | `https://basecn.dev/blocks/<name>` | iframe del demo oficial |
|
|
265
274
|
| reactbits | `https://reactbits.dev/<BaseName>` | demo vivo con animacion real + codigo a la derecha |
|
|
275
|
+
| blocks-so | `https://blocks.so/<category>/<name>` | demo vivo con el bloque real renderizado (sin animaciones) |
|
|
266
276
|
|
|
267
|
-
Cuando el orchestrator presente opciones al usuario, **siempre incluir el `preview_url`** en el chat. Para reactbits
|
|
277
|
+
Cuando el orchestrator presente opciones al usuario, **siempre incluir el `preview_url`** en el chat. Para reactbits y blocks-so el demo es interactivo y se ve inmediatamente cómo se ve en el navegador del usuario — esto cierra el ciclo "imaginar como se ve → ver real → decidir instalar" sin hacer conjeturas.
|
|
278
|
+
|
|
279
|
+
### 5.8 blocks.so (ephraimduncan/blocks) - Detalles
|
|
280
|
+
|
|
281
|
+
blocks.so es el registry más curado para interfaces funcionales: **60+ bloques Radix prod-ready** con `categories[]` explícito en cada JSON. La diferencia clave frente a akash es que cada bloque de blocks.so **incluye un `categories[]` array** dentro del JSON, lo que elimina la ambigüedad de la heurística por nombre de archivo.
|
|
282
|
+
|
|
283
|
+
**Estructura del repo**:
|
|
284
|
+
```
|
|
285
|
+
ephraimduncan/blocks/
|
|
286
|
+
content/
|
|
287
|
+
components/
|
|
288
|
+
ai/ ai-01.tsx ... ai-05.tsx (5 bloques)
|
|
289
|
+
command-menu/ command-menu-01.tsx ... (3 bloques)
|
|
290
|
+
dialogs/ dialog-01.tsx ... dialog-12.tsx (12 bloques)
|
|
291
|
+
file-upload/ file-upload-01.tsx ... (6 bloques)
|
|
292
|
+
form-layout/ form-layout-01.tsx ... (5 bloques)
|
|
293
|
+
grid-list/ grid-list-01.tsx ... (3 bloques)
|
|
294
|
+
login/ login-01.tsx ... login-09.tsx (9 bloques)
|
|
295
|
+
onboarding/ onboarding-01.tsx ... (7 bloques)
|
|
296
|
+
sidebar/ sidebar-01.tsx ... sidebar-06.tsx (6 bloques)
|
|
297
|
+
stats/ stats-01.tsx ... stats-15.tsx (15 bloques)
|
|
298
|
+
tables/ tables-01.tsx ... tables-05.tsx (5 bloques)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Campos clave del JSON de cada bloque**:
|
|
302
|
+
- `name`: canonical (ej: `"stats-01"`)
|
|
303
|
+
- `type`: `"registry:block"` (estándar shadcn)
|
|
304
|
+
- `title`: `"Stats with Trending"`
|
|
305
|
+
- `description`: `"A stats with trending block."`
|
|
306
|
+
- `author`: `"ephraim duncan <https://ephraimduncan.com>"`
|
|
307
|
+
- `registryDependencies`: ej: `["card"]` — el coder debe pre-instalar via `sdd_bootstrap` antes del `npx shadcn add`.
|
|
308
|
+
- `dependencies`: ej: `[]` — paquetes npm necesarios (rara vez hay, son 100% Radix).
|
|
309
|
+
- `files[]`: cada item tiene `path` (origen en el repo), `type` (`"registry:component"`) y `target` (ruta destino en el proyecto, ej: `"components/stats-01.tsx"`).
|
|
310
|
+
- `categories[]`: ej: `["stats"]` — usado para filtrado.
|
|
311
|
+
|
|
312
|
+
**Patrones de uso en el contrato**:
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"frontend": {
|
|
316
|
+
"components": [
|
|
317
|
+
{ "name": "stats-01", "registry": "blocks-so", "source": "ephraimduncan/blocks" }
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Cuidado con `field` y `FieldGroup`**: algunos bloques de `form-layout` y `onboarding` usan el primitivo `field` (Field, FieldLabel, FieldDescription) que no viene por defecto en `npx shadcn add`. Si `sdd_catalog_get_block` lista `field` en `registryDependencies[]`, el coder debe correr `npx shadcn@latest add field` antes de instalar el bloque.
|
|
268
324
|
|
|
269
325
|
---
|
|
270
326
|
|
|
@@ -20,17 +20,27 @@ import path from "path"
|
|
|
20
20
|
// TS-CSS / TS-TW) = 536 items. Fuente: /r/registry.json
|
|
21
21
|
// oficial shadcn-compatible. Default variant para el stack
|
|
22
22
|
// Next.js 16 + Tailwind v4 es TS-TW. TTL 1d.
|
|
23
|
+
// - blocks-so: https://blocks.so (ephraimduncan/blocks)
|
|
24
|
+
// 60+ bloques Radix prod-ready organizados en 11 categorias
|
|
25
|
+
// (stats, login, form-layout, file-upload, tables, dialogs,
|
|
26
|
+
// command-menu, sidebar, onboarding, ai, grid-list). Cada
|
|
27
|
+
// bloque incluye `categories[]` explicito en el JSON.
|
|
28
|
+
// Fuente: GitHub Contents API sobre /content/components.
|
|
29
|
+
// Default para forms/stats/tables/file-upload/dialogs/login/
|
|
30
|
+
// command-menu/onboarding/ai/grid-list. TTL 7d.
|
|
23
31
|
//
|
|
24
32
|
// Cache local:
|
|
25
33
|
// - .openspec/cache/shadcn-index.json
|
|
26
34
|
// - .openspec/cache/basecn-index.json
|
|
27
35
|
// - .openspec/cache/reactbits-index.json
|
|
36
|
+
// - .openspec/cache/blocks-so-index.json
|
|
28
37
|
// - .openspec/cache/blocks/<registry>/[<author>/]<name>.json
|
|
29
38
|
//
|
|
30
39
|
// Registries configurables via env:
|
|
31
40
|
// - SDD_CATALOG_TTL_DAYS_AKASH (default 7)
|
|
32
41
|
// - SDD_CATALOG_TTL_DAYS_BASECN (default 7)
|
|
33
42
|
// - SDD_CATALOG_TTL_DAYS_REACTBITS (default 1)
|
|
43
|
+
// - SDD_CATALOG_TTL_DAYS_BLOCKSSO (default 7)
|
|
34
44
|
//
|
|
35
45
|
// Tools exportadas (prefijo opencode = "sdd_catalog_<export>"):
|
|
36
46
|
// - list_blocks Lista componentes/bloques por registry, categoria, query.
|
|
@@ -42,6 +52,8 @@ import path from "path"
|
|
|
42
52
|
// - reactbits: "Dither" (base, resuelve a Dither-TS-TW por default)
|
|
43
53
|
// "Dither-JS-CSS" (variante canonica completa)
|
|
44
54
|
// "https://reactbits.dev/r/Dither-TS-TW.json" (URL directa)
|
|
55
|
+
// - blocks-so: "stats-01", "login-03", "form-layout-02", etc.
|
|
56
|
+
// "https://blocks.so/r/stats-01.json" (URL directa)
|
|
45
57
|
// ============================================================================
|
|
46
58
|
|
|
47
59
|
// ---------------------------------------------------------------------------
|
|
@@ -135,6 +147,36 @@ const REACTBITS_CATEGORY_BY_BASE: Map<string, string> = (() => {
|
|
|
135
147
|
return map
|
|
136
148
|
})()
|
|
137
149
|
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// blocks.so (ephraimduncan/blocks)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Repo: ephraimduncan/blocks
|
|
154
|
+
// Public registry URL: https://blocks.so/r/<name>.json
|
|
155
|
+
// Categorias oficiales (11): ai, command-menu, dialogs, file-upload,
|
|
156
|
+
// form-layout, grid-list, login, onboarding, sidebar, stats, tables.
|
|
157
|
+
// Fuente de descubrimiento: GitHub Contents API sobre /content/components.
|
|
158
|
+
// Cada bloque es un .tsx bajo content/components/<category>/; el nombre
|
|
159
|
+
// canonico es `<category>-<NN>` (ej: stats-01, login-03, form-layout-02).
|
|
160
|
+
// Cada JSON de bloque incluye `categories[]` explicito y `target` para
|
|
161
|
+
// inyeccion Zero-Touch en el proyecto Next.js destino.
|
|
162
|
+
const BLOCKSSO_REPO = "ephraimduncan/blocks"
|
|
163
|
+
const BLOCKSSO_CATEGORIES_API =
|
|
164
|
+
`https://api.github.com/repos/${BLOCKSSO_REPO}/contents/content/components`
|
|
165
|
+
const BLOCKSSO_CATEGORY_FILES_API = (category: string) =>
|
|
166
|
+
`https://api.github.com/repos/${BLOCKSSO_REPO}/contents/content/components/${category}`
|
|
167
|
+
const BLOCKSSO_RAW = (name: string) =>
|
|
168
|
+
`https://raw.githubusercontent.com/${BLOCKSSO_REPO}/main/content/components/${name}.json`
|
|
169
|
+
const BLOCKSSO_INSTALL = (name: string) => `https://blocks.so/r/${name}.json`
|
|
170
|
+
const BLOCKSSO_PREVIEW = (category: string, name: string) =>
|
|
171
|
+
`https://blocks.so/${category}/${name}`
|
|
172
|
+
const BLOCKSSO_TTL_DAYS = Number(process.env.SDD_CATALOG_TTL_DAYS_BLOCKSSO || 7)
|
|
173
|
+
|
|
174
|
+
// Categorias oficiales de blocks.so (orden estable para la UI).
|
|
175
|
+
const BLOCKSSO_CATEGORIES = [
|
|
176
|
+
"ai", "command-menu", "dialogs", "file-upload", "form-layout",
|
|
177
|
+
"grid-list", "login", "onboarding", "sidebar", "stats", "tables"
|
|
178
|
+
] as const
|
|
179
|
+
|
|
138
180
|
// ---------------------------------------------------------------------------
|
|
139
181
|
// Categorias (heuristica por nombre de archivo para Akash/Basecn)
|
|
140
182
|
// ---------------------------------------------------------------------------
|
|
@@ -156,7 +198,7 @@ const CATEGORY_PREFIXES = [
|
|
|
156
198
|
// Helpers
|
|
157
199
|
// ============================================================================
|
|
158
200
|
|
|
159
|
-
type RegistryName = "shadcn" | "basecn" | "reactbits"
|
|
201
|
+
type RegistryName = "shadcn" | "basecn" | "reactbits" | "blocks-so"
|
|
160
202
|
|
|
161
203
|
const getRoot = (context: any): string => {
|
|
162
204
|
if (context?.directory && context.directory !== "/") return context.directory
|
|
@@ -505,6 +547,208 @@ const resolveReactbitsSlug = (
|
|
|
505
547
|
}
|
|
506
548
|
}
|
|
507
549
|
|
|
550
|
+
// ---------------------------------------------------------------------------
|
|
551
|
+
// blocks.so index builder
|
|
552
|
+
// ---------------------------------------------------------------------------
|
|
553
|
+
//
|
|
554
|
+
// Estrategia de descubrimiento en 2 niveles (idempotente, TTL 7d):
|
|
555
|
+
// 1. Listar subdirectorios bajo /content/components/ (las 11 categorias).
|
|
556
|
+
// 2. Para cada categoria, listar archivos .tsx y derivar name canonico.
|
|
557
|
+
//
|
|
558
|
+
// Output: IndexEntry[] con category real (de la carpeta GitHub), install_url
|
|
559
|
+
// apuntando a https://blocks.so/r/<name>.json y preview_url al demo vivo.
|
|
560
|
+
//
|
|
561
|
+
// Para no saturar la GitHub API (rate limit 60/h sin token), cacheamos el
|
|
562
|
+
// indice completo durante 7d. Cualquier update en el repo se reflejara
|
|
563
|
+
// solo tras `force_refresh: true` o expiracion del TTL.
|
|
564
|
+
|
|
565
|
+
type GitHubContentEntry = {
|
|
566
|
+
name: string
|
|
567
|
+
path: string
|
|
568
|
+
size: number
|
|
569
|
+
sha: string
|
|
570
|
+
type: "dir" | "file"
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const fetchGithubContents = async (
|
|
574
|
+
apiUrl: string,
|
|
575
|
+
repoLabel: string
|
|
576
|
+
): Promise<GitHubContentEntry[]> => {
|
|
577
|
+
const res = await fetch(apiUrl, {
|
|
578
|
+
headers: {
|
|
579
|
+
"User-Agent": "zugzbot-sdd-catalog/1.0",
|
|
580
|
+
Accept: "application/vnd.github+json"
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
if (!res.ok) {
|
|
584
|
+
if (res.status === 403) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
`GitHub API rate limit hit consultando ${repoLabel}. ` +
|
|
587
|
+
`Espera 1h o usa force_refresh=false con cache existente.`
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
if (res.status === 404) {
|
|
591
|
+
throw new Error(
|
|
592
|
+
`Repositorio ${repoLabel} no encontrado o ruta cambiada. ` +
|
|
593
|
+
`Verifica que ${apiUrl} siga vigente.`
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
throw new Error(`GitHub API ${res.status} ${res.statusText} consultando ${repoLabel}`)
|
|
597
|
+
}
|
|
598
|
+
return (await res.json()) as GitHubContentEntry[]
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const buildBlocksSoIndex = async (
|
|
602
|
+
root: string,
|
|
603
|
+
forceRefresh: boolean
|
|
604
|
+
): Promise<{ index: Index; refreshed: boolean; error?: string }> => {
|
|
605
|
+
const indexPath = getIndexPath(root, "blocks-so")
|
|
606
|
+
const ttl = ttlMs(BLOCKSSO_TTL_DAYS)
|
|
607
|
+
|
|
608
|
+
const cached = readJson<Index>(indexPath)
|
|
609
|
+
if (!forceRefresh && !isCacheStale(cached, ttl)) {
|
|
610
|
+
return { index: cached!, refreshed: false }
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
const entries: IndexEntry[] = []
|
|
615
|
+
|
|
616
|
+
// Paso 1: listar las 11 categorias oficiales.
|
|
617
|
+
// Si la API falla 404 (ej: rama renombrada) usamos la lista hardcodeada
|
|
618
|
+
// como fallback para no romper el resto del catalogo.
|
|
619
|
+
let categories = [...BLOCKSSO_CATEGORIES] as readonly string[]
|
|
620
|
+
try {
|
|
621
|
+
const catEntries = await fetchGithubContents(
|
|
622
|
+
BLOCKSSO_CATEGORIES_API,
|
|
623
|
+
BLOCKSSO_REPO
|
|
624
|
+
)
|
|
625
|
+
const live = catEntries
|
|
626
|
+
.filter((e) => e.type === "dir")
|
|
627
|
+
.map((e) => e.name)
|
|
628
|
+
.filter((n) => !n.startsWith("_") && !n.startsWith("."))
|
|
629
|
+
if (live.length > 0) categories = live
|
|
630
|
+
} catch (e) {
|
|
631
|
+
if (!cached) throw e
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Paso 2: por cada categoria, enumerar .tsx (formato plano) O
|
|
635
|
+
// subdirectorios (formato multi-file, usado por sidebar) y derivar
|
|
636
|
+
// el name canonico. Estructuras mixtas soportadas:
|
|
637
|
+
// - ai/ ai-01.tsx ... ai-05.tsx
|
|
638
|
+
// - command-menu/ command-menu-01.tsx ... -03.tsx
|
|
639
|
+
// - dialogs/ dialog-01.tsx ... -12.tsx (prefijo singular)
|
|
640
|
+
// - login/ login-01.tsx ... -09.tsx
|
|
641
|
+
// - onboarding/ onboarding-01.tsx ... -07.tsx
|
|
642
|
+
// - stats/ stats-01.tsx ... -15.tsx
|
|
643
|
+
// - tables/ table-01.tsx ... -05.tsx (prefijo singular)
|
|
644
|
+
// - form-layout/ form-layout-01.tsx ... -05.tsx
|
|
645
|
+
// - grid-list/ grid-list-01.tsx ... -03.tsx
|
|
646
|
+
// - file-upload/ file-upload-01.tsx ... -06.tsx
|
|
647
|
+
// (+ file-upload-01/ subdir durante transicion)
|
|
648
|
+
// - sidebar/ sidebar-01/ ... sidebar-06/ (SUBDIRS multi-file)
|
|
649
|
+
//
|
|
650
|
+
// Si un mismo name aparece como .tsx Y como subdir (transicion en el
|
|
651
|
+
// repo fuente), preferimos el subdir (multi-file) y descartamos el .tsx
|
|
652
|
+
// huerfano. Esto evita entradas duplicadas en el indice.
|
|
653
|
+
const seenNames = new Set<string>()
|
|
654
|
+
const dedupEntries: IndexEntry[] = []
|
|
655
|
+
|
|
656
|
+
for (const cat of categories) {
|
|
657
|
+
let items: GitHubContentEntry[]
|
|
658
|
+
try {
|
|
659
|
+
items = await fetchGithubContents(
|
|
660
|
+
BLOCKSSO_CATEGORY_FILES_API(cat),
|
|
661
|
+
BLOCKSSO_REPO
|
|
662
|
+
)
|
|
663
|
+
} catch (e) {
|
|
664
|
+
// Si falla una categoria puntual, seguimos con las demas para no
|
|
665
|
+
// abortar el indice completo. Logueamos via error[] abajo.
|
|
666
|
+
continue
|
|
667
|
+
}
|
|
668
|
+
for (const it of items) {
|
|
669
|
+
// Ignorar archivos no-componente (index.ts, .DS_Store, etc.)
|
|
670
|
+
if (it.name.startsWith("index.")) continue
|
|
671
|
+
if (it.name.startsWith("_")) continue
|
|
672
|
+
if (it.name.startsWith(".")) continue
|
|
673
|
+
|
|
674
|
+
// Caso A: archivo .tsx plano. name = file sin extension.
|
|
675
|
+
if (it.type === "file" && it.name.endsWith(".tsx")) {
|
|
676
|
+
const name = it.name.replace(/\.tsx$/, "")
|
|
677
|
+
if (!name) continue
|
|
678
|
+
if (seenNames.has(name)) continue // dedup
|
|
679
|
+
seenNames.add(name)
|
|
680
|
+
dedupEntries.push({
|
|
681
|
+
name,
|
|
682
|
+
filename: it.name,
|
|
683
|
+
category: cat,
|
|
684
|
+
registry: "blocks-so",
|
|
685
|
+
author: "ephraimduncan",
|
|
686
|
+
description: `blocks.so ${cat} block (${name})`,
|
|
687
|
+
tags: [cat, "shadcn-registry", "blocks-so"],
|
|
688
|
+
install_url: BLOCKSSO_INSTALL(name),
|
|
689
|
+
preview_url: BLOCKSSO_PREVIEW(cat, name),
|
|
690
|
+
source: "github"
|
|
691
|
+
})
|
|
692
|
+
continue
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Caso B: subdirectorio (multi-file block, ej. sidebar-XX/).
|
|
696
|
+
// El nombre del subdir ES el canonical name del bloque.
|
|
697
|
+
// Sobrescribe cualquier .tsx huerfano con el mismo name.
|
|
698
|
+
if (it.type === "dir") {
|
|
699
|
+
const name = it.name
|
|
700
|
+
if (!name) continue
|
|
701
|
+
// Si ya vimos un .tsx con este name, lo removemos para
|
|
702
|
+
// reemplazarlo por la version multi-file (mas completa).
|
|
703
|
+
const existingIdx = dedupEntries.findIndex(
|
|
704
|
+
(e) => e.name === name && e.category === cat
|
|
705
|
+
)
|
|
706
|
+
if (existingIdx >= 0) dedupEntries.splice(existingIdx, 1)
|
|
707
|
+
seenNames.add(name)
|
|
708
|
+
dedupEntries.push({
|
|
709
|
+
name,
|
|
710
|
+
filename: `${name}/`,
|
|
711
|
+
category: cat,
|
|
712
|
+
registry: "blocks-so",
|
|
713
|
+
author: "ephraimduncan",
|
|
714
|
+
description: `blocks.so ${cat} block (${name}, multi-file)`,
|
|
715
|
+
tags: [cat, "shadcn-registry", "blocks-so", "multi-file"],
|
|
716
|
+
install_url: BLOCKSSO_INSTALL(name),
|
|
717
|
+
preview_url: BLOCKSSO_PREVIEW(cat, name),
|
|
718
|
+
source: "github"
|
|
719
|
+
})
|
|
720
|
+
continue
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
dedupEntries.sort((a, b) => {
|
|
726
|
+
if (a.category !== b.category) return a.category.localeCompare(b.category)
|
|
727
|
+
return a.name.localeCompare(b.name)
|
|
728
|
+
})
|
|
729
|
+
entries.push(...dedupEntries)
|
|
730
|
+
|
|
731
|
+
const index: Index = {
|
|
732
|
+
cached_at: new Date().toISOString(),
|
|
733
|
+
ttl_days: BLOCKSSO_TTL_DAYS,
|
|
734
|
+
source: `${BLOCKSSO_REPO} (${BLOCKSSO_CATEGORIES_API})`,
|
|
735
|
+
total: entries.length,
|
|
736
|
+
entries
|
|
737
|
+
}
|
|
738
|
+
writeJson(indexPath, index)
|
|
739
|
+
return { index, refreshed: true }
|
|
740
|
+
} catch (e) {
|
|
741
|
+
if (cached) {
|
|
742
|
+
return {
|
|
743
|
+
index: cached,
|
|
744
|
+
refreshed: false,
|
|
745
|
+
error: `Refresh fallo (${(e as Error).message}); usando cache de ${cached.cached_at}`
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
throw e
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
508
752
|
// ---------------------------------------------------------------------------
|
|
509
753
|
// Fetchers compartidos
|
|
510
754
|
// ---------------------------------------------------------------------------
|
|
@@ -550,6 +794,8 @@ const normalizeBlock = (parsed: any, registry: RegistryName, name: string) => {
|
|
|
550
794
|
installUrl = REACTBITS_INSTALL(name)
|
|
551
795
|
} else if (registry === "basecn") {
|
|
552
796
|
installUrl = BASECN_INSTALL(name)
|
|
797
|
+
} else if (registry === "blocks-so") {
|
|
798
|
+
installUrl = BLOCKSSO_INSTALL(name)
|
|
553
799
|
} else {
|
|
554
800
|
installUrl = AKASH_INSTALL(name)
|
|
555
801
|
}
|
|
@@ -591,16 +837,22 @@ type ListResult = {
|
|
|
591
837
|
hints?: string[]
|
|
592
838
|
}
|
|
593
839
|
|
|
594
|
-
const REGISTRIES_ALL: RegistryName[] = ["shadcn", "basecn", "reactbits"]
|
|
840
|
+
const REGISTRIES_ALL: RegistryName[] = ["shadcn", "basecn", "reactbits", "blocks-so"]
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
|
|
595
844
|
|
|
596
845
|
export const list_blocks = tool({
|
|
597
846
|
description:
|
|
598
847
|
"Lista bloques/componentes del catalogo unificado sdd_catalog. " +
|
|
599
|
-
"Soporta
|
|
600
|
-
"Radix), 'basecn' (akash3444/basecn, fork Base UI)
|
|
601
|
-
"primitivas animadas; para cada base se exponen sus 4 variantes JS-CSS/JS-TW/TS-CSS/TS-TW)
|
|
602
|
-
"
|
|
603
|
-
"
|
|
848
|
+
"Soporta 4 registries con catalogo JSON oficial: 'shadcn' (akash3444/shadcn-ui-blocks, " +
|
|
849
|
+
"Radix), 'basecn' (akash3444/basecn, fork Base UI), 'reactbits' (reactbits.dev, " +
|
|
850
|
+
"primitivas animadas; para cada base se exponen sus 4 variantes JS-CSS/JS-TW/TS-CSS/TS-TW) " +
|
|
851
|
+
"y 'blocks-so' (ephraimduncan/blocks, 60+ bloques Radix prod-ready con categories[] explicito; " +
|
|
852
|
+
"default para stats, login, form-layout, file-upload, tables, dialogs, sidebar, " +
|
|
853
|
+
"command-menu, ai, onboarding, grid-list). " +
|
|
854
|
+
"Cachea en .openspec/cache/ con TTL por registry (7d shadcn/basecn/blocks-so, 1d reactbits). " +
|
|
855
|
+
"Filtra por categoria (hero, footer, pricing, backgrounds, text, cards, stats, login, etc) " +
|
|
604
856
|
"y por query libre (substring sobre name/description/tags).",
|
|
605
857
|
args: {
|
|
606
858
|
category: tool.schema
|
|
@@ -615,10 +867,10 @@ export const list_blocks = tool({
|
|
|
615
867
|
.optional()
|
|
616
868
|
.describe("Busqueda libre por substring sobre name/filename/description. Opcional."),
|
|
617
869
|
registry: tool.schema
|
|
618
|
-
.enum(["shadcn", "basecn", "reactbits", "all"])
|
|
870
|
+
.enum(["shadcn", "basecn", "reactbits", "blocks-so", "all"])
|
|
619
871
|
.optional()
|
|
620
872
|
.default("all")
|
|
621
|
-
.describe("Que catalogos consultar. 'all' = shadcn+basecn+reactbits."),
|
|
873
|
+
.describe("Que catalogos consultar. 'all' = shadcn+basecn+reactbits+blocks-so."),
|
|
622
874
|
limit: tool.schema
|
|
623
875
|
.number()
|
|
624
876
|
.optional()
|
|
@@ -661,6 +913,24 @@ export const list_blocks = tool({
|
|
|
661
913
|
"TS-TW (TypeScript + Tailwind v4). Para otra variante, pasa " +
|
|
662
914
|
"`variant='JS-CSS'` a `sdd_catalog_get_block`."
|
|
663
915
|
)
|
|
916
|
+
} else if (reg === "blocks-so") {
|
|
917
|
+
const { index, refreshed, error } = await buildBlocksSoIndex(
|
|
918
|
+
root,
|
|
919
|
+
args.force_refresh || false
|
|
920
|
+
)
|
|
921
|
+
cachedAt[reg] = index.cached_at
|
|
922
|
+
if (refreshed) {
|
|
923
|
+
// ok
|
|
924
|
+
}
|
|
925
|
+
if (error) errors.push(`[${reg}] ${error}`)
|
|
926
|
+
allEntries.push(...index.entries)
|
|
927
|
+
hints.push(
|
|
928
|
+
"[blocks-so] ephraimduncan/blocks (https://blocks.so). " +
|
|
929
|
+
"Default para sus 11 categorias: stats, login, form-layout, " +
|
|
930
|
+
"file-upload, tables, dialogs, sidebar, command-menu, ai, " +
|
|
931
|
+
"onboarding, grid-list. Install: `npx shadcn@latest add " +
|
|
932
|
+
"https://blocks.so/r/<name>.json --yes`."
|
|
933
|
+
)
|
|
664
934
|
} else {
|
|
665
935
|
const { index, refreshed, error } = await buildOrLoadIndex(
|
|
666
936
|
root,
|
|
@@ -758,9 +1028,12 @@ export const get_block = tool({
|
|
|
758
1028
|
"especifico del catalogo unificado. " +
|
|
759
1029
|
"Para shadcn/basecn: `name='hero-06'`. " +
|
|
760
1030
|
"Para reactbits: `name='Dither'` (resuelve a Dither-TS-TW por default) o " +
|
|
761
|
-
"`name='Dither-JS-CSS'` (variante canonica completa).
|
|
762
|
-
"
|
|
763
|
-
"`name='
|
|
1031
|
+
"`name='Dither-JS-CSS'` (variante canonica completa). " +
|
|
1032
|
+
"Para blocks-so: `name='stats-01'`. " +
|
|
1033
|
+
"Tambien acepta URL directa `name='https://reactbits.dev/r/Dither-TS-TW.json'` " +
|
|
1034
|
+
"o `name='https://blocks.so/r/stats-01.json'`, y namespace shadcn " +
|
|
1035
|
+
"`name='@acme/button'` (incluido `@blocks-so/stats-01`). " +
|
|
1036
|
+
"Cachea en .openspec/cache/blocks/<registry>/<name>.json. " +
|
|
764
1037
|
"Devuelve `install_command` listo para ejecutar con `npx shadcn@latest add`.",
|
|
765
1038
|
args: {
|
|
766
1039
|
name: tool.schema
|
|
@@ -768,12 +1041,13 @@ export const get_block = tool({
|
|
|
768
1041
|
.describe(
|
|
769
1042
|
"Identificador del bloque. Formatos aceptados:\n" +
|
|
770
1043
|
" - shadcn/basecn: 'hero-06', 'sidebar-07'\n" +
|
|
1044
|
+
" - blocks-so: 'stats-01', 'login-03', 'form-layout-02'\n" +
|
|
771
1045
|
" - reactbits: 'Dither' (default TS-TW) o 'Dither-JS-CSS' (canonica)\n" +
|
|
772
|
-
" - URL directa: 'https://reactbits.dev/r/Dither-TS-TW.json'\n" +
|
|
773
|
-
" - Namespace shadcn: '@acme/button'"
|
|
1046
|
+
" - URL directa: 'https://reactbits.dev/r/Dither-TS-TW.json' o 'https://blocks.so/r/stats-01.json'\n" +
|
|
1047
|
+
" - Namespace shadcn: '@acme/button' o '@blocks-so/stats-01'"
|
|
774
1048
|
),
|
|
775
1049
|
registry: tool.schema
|
|
776
|
-
.enum(["auto", "shadcn", "basecn", "reactbits"])
|
|
1050
|
+
.enum(["auto", "shadcn", "basecn", "reactbits", "blocks-so"])
|
|
777
1051
|
.optional()
|
|
778
1052
|
.default("auto")
|
|
779
1053
|
.describe(
|
|
@@ -823,6 +1097,18 @@ export const get_block = tool({
|
|
|
823
1097
|
baseName = resolved.baseName
|
|
824
1098
|
variant = resolved.variant
|
|
825
1099
|
directUrl = url
|
|
1100
|
+
} else if (url.includes("blocks.so/r/")) {
|
|
1101
|
+
const m = url.match(/\/r\/([a-zA-Z0-9_.-]+)\.json/)
|
|
1102
|
+
if (!m) {
|
|
1103
|
+
return JSON.stringify({
|
|
1104
|
+
status: "ERROR",
|
|
1105
|
+
message: `URL blocks.so invalida: ${url}. Esperado patron /r/<name>.json.`,
|
|
1106
|
+
block: null
|
|
1107
|
+
}, null, 2)
|
|
1108
|
+
}
|
|
1109
|
+
registry = "blocks-so"
|
|
1110
|
+
canonicalName = m[1]
|
|
1111
|
+
directUrl = url
|
|
826
1112
|
} else {
|
|
827
1113
|
// URL generica shadcn: tratarla como shadcn (1 sola parte)
|
|
828
1114
|
const m = url.match(/\/([a-zA-Z0-9_.-]+)\.json/)
|
|
@@ -838,7 +1124,8 @@ export const get_block = tool({
|
|
|
838
1124
|
directUrl = url
|
|
839
1125
|
}
|
|
840
1126
|
} else if (rawName.startsWith("@")) {
|
|
841
|
-
// Namespace shadcn @
|
|
1127
|
+
// Namespace shadcn @<author>/<item>. Detectamos @blocks-so y lo
|
|
1128
|
+
// redirigimos al registry blocks-so (canonicalName sin prefijo).
|
|
842
1129
|
const m = rawName.match(/^@([a-zA-Z0-9_.-]+)\/(.+)$/)
|
|
843
1130
|
if (!m) {
|
|
844
1131
|
return JSON.stringify({
|
|
@@ -847,8 +1134,13 @@ export const get_block = tool({
|
|
|
847
1134
|
block: null
|
|
848
1135
|
}, null, 2)
|
|
849
1136
|
}
|
|
850
|
-
|
|
851
|
-
|
|
1137
|
+
if (m[1] === "blocks-so" || m[1] === "blocks_so") {
|
|
1138
|
+
registry = "blocks-so"
|
|
1139
|
+
canonicalName = m[2]
|
|
1140
|
+
} else {
|
|
1141
|
+
registry = "shadcn"
|
|
1142
|
+
canonicalName = `${m[1]}/${m[2]}`
|
|
1143
|
+
}
|
|
852
1144
|
} else {
|
|
853
1145
|
// Auto-detectar: si contiene '-JS-' o '-TS-' seguido de CSS/TW, es reactbits canonica.
|
|
854
1146
|
// Si el registry forzado es reactbits, resolver siempre.
|
|
@@ -866,6 +1158,9 @@ export const get_block = tool({
|
|
|
866
1158
|
} else if (requestedReg === "basecn") {
|
|
867
1159
|
registry = "basecn"
|
|
868
1160
|
canonicalName = rawName
|
|
1161
|
+
} else if (requestedReg === "blocks-so") {
|
|
1162
|
+
registry = "blocks-so"
|
|
1163
|
+
canonicalName = rawName
|
|
869
1164
|
} else {
|
|
870
1165
|
registry = "shadcn"
|
|
871
1166
|
canonicalName = rawName
|
|
@@ -897,6 +1192,19 @@ export const get_block = tool({
|
|
|
897
1192
|
installCommand = `npx shadcn@latest add ${installUrl} --yes`
|
|
898
1193
|
category = inferCategory(canonicalName)
|
|
899
1194
|
notes = "Bloque Base UI (fork). Verifica que tu proyecto tenga Base UI instalado."
|
|
1195
|
+
} else if (registry === "blocks-so") {
|
|
1196
|
+
// blocks.so: el JSON crudo vive en /content/components/<category>/<name>.tsx
|
|
1197
|
+
// pero el contrato del arnes siempre expone el `name` canonico
|
|
1198
|
+
// (ej: "stats-01"), NO el path del repo. La URL raw debe apuntar al
|
|
1199
|
+
// archivo .tsx fuente para que `content` contenga el codigo completo.
|
|
1200
|
+
rawUrl = directUrl || BLOCKSSO_RAW(canonicalName)
|
|
1201
|
+
cachePath = path.resolve(getBlocksDir(root, "blocks-so"), `${canonicalName}.json`)
|
|
1202
|
+
installUrl = BLOCKSSO_INSTALL(canonicalName)
|
|
1203
|
+
const resolvedCategory = inferCategory(canonicalName)
|
|
1204
|
+
previewUrl = BLOCKSSO_PREVIEW(resolvedCategory, canonicalName)
|
|
1205
|
+
installCommand = `npx shadcn@latest add ${installUrl} --yes`
|
|
1206
|
+
category = resolvedCategory
|
|
1207
|
+
notes = "Bloque blocks.so (ephraimduncan/blocks). Formato shadcn registry estandar (registry:block). Compatible con stack nextjs-shadcn."
|
|
900
1208
|
} else {
|
|
901
1209
|
rawUrl = AKASH_RAW(canonicalName)
|
|
902
1210
|
cachePath = path.resolve(getBlocksDir(root, "shadcn"), `${canonicalName}.json`)
|
|
@@ -989,10 +1297,10 @@ export const warm_index = tool({
|
|
|
989
1297
|
"Pre-calienta (descarga) los indices de los registries soportados " +
|
|
990
1298
|
"para evitar la latencia del primer list_blocks/get_block. " +
|
|
991
1299
|
"Idempotente: si el cache existe y es fresco, no re-descarga. " +
|
|
992
|
-
"TTL por registry: shadcn/basecn 7d, reactbits 1d.",
|
|
1300
|
+
"TTL por registry: shadcn/basecn/blocks-so 7d, reactbits 1d.",
|
|
993
1301
|
args: {
|
|
994
1302
|
registry: tool.schema
|
|
995
|
-
.enum(["shadcn", "basecn", "reactbits", "all"])
|
|
1303
|
+
.enum(["shadcn", "basecn", "reactbits", "blocks-so", "all"])
|
|
996
1304
|
.optional()
|
|
997
1305
|
.default("all")
|
|
998
1306
|
.describe("Que catalogos pre-calentar.")
|
|
@@ -1016,6 +1324,13 @@ export const warm_index = tool({
|
|
|
1016
1324
|
? `${reg} (re-descargado, ${index.total} bases)`
|
|
1017
1325
|
: `${reg} (cache fresco de ${index.cached_at})`
|
|
1018
1326
|
)
|
|
1327
|
+
} else if (reg === "blocks-so") {
|
|
1328
|
+
const { index, refreshed } = await buildBlocksSoIndex(root, false)
|
|
1329
|
+
warmed.push(
|
|
1330
|
+
refreshed
|
|
1331
|
+
? `${reg} (re-descargado, ${index.total} bloques)`
|
|
1332
|
+
: `${reg} (cache fresco de ${index.cached_at})`
|
|
1333
|
+
)
|
|
1019
1334
|
} else {
|
|
1020
1335
|
const { index, refreshed } = await buildOrLoadIndex(root, reg, false)
|
|
1021
1336
|
warmed.push(
|