zugzbot 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.opencode/plugins/sdd-bridge.ts +83 -1
  2. package/.utils/docs_opencode/acp.md +165 -0
  3. package/.utils/docs_opencode/acp.pdf +0 -0
  4. package/.utils/docs_opencode/agents.md +803 -0
  5. package/.utils/docs_opencode/agents.pdf +0 -0
  6. package/.utils/docs_opencode/commands.md +354 -0
  7. package/.utils/docs_opencode/commands.pdf +0 -0
  8. package/.utils/docs_opencode/custom-tools.md +209 -0
  9. package/.utils/docs_opencode/custom-tools.pdf +0 -0
  10. package/.utils/docs_opencode/ecosystem.md +81 -0
  11. package/.utils/docs_opencode/ecosystem.pdf +0 -0
  12. package/.utils/docs_opencode/formatters.md +142 -0
  13. package/.utils/docs_opencode/formatters.pdf +0 -0
  14. package/.utils/docs_opencode/keybinds.md +205 -0
  15. package/.utils/docs_opencode/keybinds.pdf +0 -0
  16. package/.utils/docs_opencode/lsp.md +202 -0
  17. package/.utils/docs_opencode/lsp.pdf +0 -0
  18. package/.utils/docs_opencode/mcp-servers.md +565 -0
  19. package/.utils/docs_opencode/mcp-servers.pdf +0 -0
  20. package/.utils/docs_opencode/models.md +234 -0
  21. package/.utils/docs_opencode/models.pdf +0 -0
  22. package/.utils/docs_opencode/permissions.md +248 -0
  23. package/.utils/docs_opencode/permissions.pdf +0 -0
  24. package/.utils/docs_opencode/plugins.md +409 -0
  25. package/.utils/docs_opencode/plugins.pdf +0 -0
  26. package/.utils/docs_opencode/rules.md +189 -0
  27. package/.utils/docs_opencode/rules.pdf +0 -0
  28. package/.utils/docs_opencode/sdk.md +522 -0
  29. package/.utils/docs_opencode/sdk.pdf +0 -0
  30. package/.utils/docs_opencode/server.md +324 -0
  31. package/.utils/docs_opencode/server.pdf +0 -0
  32. package/.utils/docs_opencode/skills.md +235 -0
  33. package/.utils/docs_opencode/skills.pdf +0 -0
  34. package/.utils/docs_opencode/themes.md +378 -0
  35. package/.utils/docs_opencode/themes.pdf +0 -0
  36. package/.utils/docs_opencode/tools.md +364 -0
  37. package/.utils/docs_opencode/tools.pdf +0 -0
  38. package/.utils/export_opencode_session.py +242 -0
  39. package/.utils/toggle_model.py +441 -0
  40. package/README.md +39 -0
  41. package/bin/init.js +25 -6
  42. package/models.json +8 -0
  43. package/opencode.json +4 -5
  44. package/package.json +3 -1
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import os
4
+ import json
5
+ import re
6
+ import curses
7
+
8
+ # ==============================================================================
9
+ # LISTA DE MODELOS DISPONIBLES EN OPENCODE
10
+ # ==============================================================================
11
+ AVAILABLE_MODELS = [
12
+ "opencode/big-pickle",
13
+ "opencode/deepseek-v4-flash-free",
14
+ "opencode/mimo-v2.5-free",
15
+ "opencode/nemotron-3-ultra-free",
16
+ "opencode/north-mini-code-free",
17
+ "deepseek/deepseek-chat",
18
+ "deepseek/deepseek-reasoner",
19
+ "deepseek/deepseek-v4-flash",
20
+ "deepseek/deepseek-v4-pro",
21
+ "google/gemini-2.5-flash",
22
+ "google/gemini-2.5-flash-image",
23
+ "google/gemini-2.5-flash-lite",
24
+ "google/gemini-2.5-flash-preview-tts",
25
+ "google/gemini-2.5-pro",
26
+ "google/gemini-2.5-pro-preview-tts",
27
+ "google/gemini-3-flash-preview",
28
+ "google/gemini-3-pro-image-preview",
29
+ "google/gemini-3.1-flash-image-preview",
30
+ "google/gemini-3.1-flash-lite",
31
+ "google/gemini-3.1-pro-preview",
32
+ "google/gemini-3.1-pro-preview-customtools",
33
+ "google/gemini-3.5-flash",
34
+ "google/gemini-embedding-001",
35
+ "google/gemini-flash-latest",
36
+ "google/gemini-flash-lite-latest",
37
+ "google/gemma-4-26b-a4b-it",
38
+ "google/gemma-4-31b-it",
39
+ "minimax/MiniMax-M2",
40
+ "minimax/MiniMax-M2.1",
41
+ "minimax/MiniMax-M2.5",
42
+ "minimax/MiniMax-M2.5-highspeed",
43
+ "minimax/MiniMax-M2.7",
44
+ "minimax/MiniMax-M2.7-highspeed",
45
+ "minimax/MiniMax-M3",
46
+ "minimax-coding-plan/MiniMax-M2",
47
+ "minimax-coding-plan/MiniMax-M2.1",
48
+ "minimax-coding-plan/MiniMax-M2.5",
49
+ "minimax-coding-plan/MiniMax-M2.5-highspeed",
50
+ "minimax-coding-plan/MiniMax-M2.7",
51
+ "minimax-coding-plan/MiniMax-M2.7-highspeed",
52
+ "minimax-coding-plan/MiniMax-M3",
53
+ ]
54
+
55
+ AGENTS_LIST = ["sdd-orchestrator", "sdd-spec-writer", "sdd-coder", "sdd-tester", "sdd-deployer"]
56
+
57
+
58
+ def get_paths():
59
+ script_dir = os.path.dirname(os.path.abspath(__file__))
60
+ project_root = os.path.dirname(script_dir)
61
+ opencode_json = os.path.join(project_root, "opencode.json")
62
+ agents_dir = os.path.join(project_root, ".opencode", "agents")
63
+ models_json = os.path.join(project_root, "models.json")
64
+ return opencode_json, agents_dir, models_json
65
+
66
+
67
+ def load_models_config():
68
+ opencode_json, agents_dir, models_json = get_paths()
69
+ default_config = {
70
+ "global": "deepseek/deepseek-v4-flash",
71
+ "sdd-orchestrator": "",
72
+ "sdd-spec-writer": "",
73
+ "sdd-coder": "",
74
+ "sdd-tester": "",
75
+ "sdd-deployer": ""
76
+ }
77
+
78
+ if os.path.exists(models_json):
79
+ try:
80
+ with open(models_json, 'r', encoding='utf-8') as f:
81
+ return json.load(f)
82
+ except Exception:
83
+ pass
84
+
85
+ # Guardamos el default si no existe
86
+ try:
87
+ with open(models_json, 'w', encoding='utf-8') as f:
88
+ json.dump(default_config, f, indent=2)
89
+ except Exception:
90
+ pass
91
+ return default_config
92
+
93
+
94
+ def save_models_config(config):
95
+ opencode_json, agents_dir, models_json = get_paths()
96
+ try:
97
+ with open(models_json, 'w', encoding='utf-8') as f:
98
+ json.dump(config, f, indent=2)
99
+ except Exception as e:
100
+ print(f"Error guardando models.json: {e}", file=sys.stderr)
101
+
102
+
103
+ def search_model(query):
104
+ query = query.lower()
105
+ matches = [m for m in AVAILABLE_MODELS if query in m.lower()]
106
+ return matches
107
+
108
+
109
+ def apply_model(agent_models):
110
+ opencode_json, agents_dir, models_json = get_paths()
111
+
112
+ if not os.path.exists(opencode_json):
113
+ print(f"Error: No se encontró {opencode_json}", file=sys.stderr)
114
+ sys.exit(1)
115
+
116
+ # 1. Actualizar opencode.json
117
+ with open(opencode_json, 'r', encoding='utf-8') as f:
118
+ data = json.load(f)
119
+
120
+ agents_config = data.get("agent", {})
121
+ for agent_name, agent_cfg in agents_config.items():
122
+ if agent_name in agent_models:
123
+ agent_cfg["model"] = agent_models[agent_name]
124
+
125
+ with open(opencode_json, 'w', encoding='utf-8') as f:
126
+ json.dump(data, f, indent=2)
127
+ print(" -> opencode.json actualizado con éxito.")
128
+
129
+ # 2. Actualizar frontmatter de agentes (.md)
130
+ if os.path.exists(agents_dir):
131
+ for filename in os.listdir(agents_dir):
132
+ if filename.endswith(".md"):
133
+ # Quitar extensión .md para comparar con el nombre del agente
134
+ agent_name = filename[:-3]
135
+ if agent_name in agent_models:
136
+ target_model = agent_models[agent_name]
137
+ filepath = os.path.join(agents_dir, filename)
138
+
139
+ with open(filepath, 'r', encoding='utf-8') as f:
140
+ content = f.read()
141
+
142
+ frontmatter_pattern = re.compile(
143
+ r"^---$(.*?)^---$", re.MULTILINE | re.DOTALL
144
+ )
145
+ match = frontmatter_pattern.search(content)
146
+ if match:
147
+ frontmatter = match.group(1)
148
+ if "model:" in frontmatter:
149
+ new_frontmatter = re.sub(
150
+ r"^model:.*?$",
151
+ f"model: {target_model}",
152
+ frontmatter,
153
+ flags=re.MULTILINE,
154
+ )
155
+ new_content = content.replace(
156
+ frontmatter, new_frontmatter, 1
157
+ )
158
+ with open(filepath, 'w', encoding='utf-8') as f:
159
+ f.write(new_content)
160
+ print(f" -> {filename} actualizado a: {target_model}")
161
+ else:
162
+ print(f"Advertencia: Directorio de agentes {agents_dir} no existe.")
163
+
164
+
165
+ def print_available_models():
166
+ print("\nModelos disponibles:")
167
+ for m in AVAILABLE_MODELS:
168
+ print(f" - {m}")
169
+ print()
170
+
171
+
172
+ # ==============================================================================
173
+ # INTERFAZ INTERACTIVA TUI (CURSES)
174
+ # ==============================================================================
175
+
176
+ def select_model_tui(stdscr, title, current_val, is_global=False):
177
+ # Habilitar modo keypad para recibir flechas de cursor
178
+ stdscr.keypad(True)
179
+ curses.curs_set(0) # Ocultar cursor físico
180
+
181
+ # Colores
182
+ curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) # Seleccionado
183
+ curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) # Buscador
184
+ curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) # Éxito / Info
185
+
186
+ query = ""
187
+ selected_idx = 0
188
+
189
+ while True:
190
+ stdscr.clear()
191
+
192
+ # Obtener lista de modelos y añadir "Heredar del Global" si no es la config global
193
+ options = []
194
+ if not is_global:
195
+ options.append("") # Representa [Heredar del Global]
196
+
197
+ # Filtrar modelos según query
198
+ filtered_models = [m for m in AVAILABLE_MODELS if query.lower() in m.lower()]
199
+ options.extend(filtered_models)
200
+
201
+ # Validar límites de selección
202
+ if selected_idx >= len(options):
203
+ selected_idx = max(0, len(options) - 1)
204
+
205
+ # Títulos
206
+ stdscr.addstr(1, 2, f"=== {title} ===", curses.A_BOLD)
207
+ stdscr.addstr(2, 2, "Escribe caracteres para buscar en tiempo real.", curses.A_DIM)
208
+ stdscr.addstr(3, 2, "Usa [↑/↓] para navegar y [Enter] para confirmar. [ESC] para volver.", curses.A_DIM)
209
+
210
+ # Caja de búsqueda
211
+ stdscr.addstr(5, 2, "Buscador: ", curses.A_BOLD)
212
+ stdscr.addstr(5, 12, f" {query}█ ", curses.color_pair(2) | curses.A_BOLD)
213
+ stdscr.addstr(6, 2, "-" * 60, curses.A_DIM)
214
+
215
+ # Mostrar opciones con paginación simple de terminal
216
+ start_row = 8
217
+ max_rows = curses.LINES - start_row - 2
218
+
219
+ if not options:
220
+ stdscr.addstr(start_row, 4, "No hay modelos que coincidan con la búsqueda.", curses.color_pair(2))
221
+ else:
222
+ # Mostrar solo lo que quepa en la pantalla
223
+ for i in range(min(len(options), max_rows)):
224
+ opt = options[i]
225
+ row = start_row + i
226
+
227
+ # Formatear el label
228
+ if opt == "":
229
+ label = "[ Heredar del Global ]"
230
+ else:
231
+ label = opt
232
+
233
+ # Indicar si es el activo actual
234
+ if opt == current_val:
235
+ label += " (activo)"
236
+
237
+ # Resaltar la opción seleccionada
238
+ if i == selected_idx:
239
+ stdscr.attron(curses.color_pair(1))
240
+ stdscr.addstr(row, 2, f" > {label:<50} ")
241
+ stdscr.attroff(curses.color_pair(1))
242
+ else:
243
+ stdscr.addstr(row, 2, f" {label:<50}")
244
+
245
+ stdscr.refresh()
246
+
247
+ # Leer tecla
248
+ try:
249
+ key = stdscr.getch()
250
+ except KeyboardInterrupt:
251
+ return None
252
+
253
+ if key in (27, curses.KEY_CANCEL): # ESC
254
+ return None
255
+ elif key in (10, 13, curses.KEY_ENTER): # Enter
256
+ if options:
257
+ return options[selected_idx]
258
+ return None
259
+ elif key == curses.KEY_UP:
260
+ if selected_idx > 0:
261
+ selected_idx -= 1
262
+ elif key == curses.KEY_DOWN:
263
+ if selected_idx < len(options) - 1:
264
+ selected_idx += 1
265
+ elif key in (127, 8, curses.KEY_BACKSPACE): # Backspace
266
+ query = query[:-1]
267
+ selected_idx = 0
268
+ elif 32 <= key <= 126: # Carácter imprimible
269
+ query += chr(key)
270
+ selected_idx = 0
271
+
272
+
273
+ def main_tui(stdscr):
274
+ # Habilitar keypad
275
+ stdscr.keypad(True)
276
+ curses.curs_set(0)
277
+
278
+ # Inicializar pares de colores
279
+ curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) # Selección
280
+ curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) # Alertas
281
+ curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) # Éxito
282
+ curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Warnings
283
+
284
+ config = load_models_config()
285
+ selected_row = 0
286
+
287
+ while True:
288
+ stdscr.clear()
289
+
290
+ stdscr.addstr(1, 2, "🤖 ZUGZBOT: CONFIGURACIÓN INTERACTIVA DE MODELOS", curses.A_BOLD)
291
+ stdscr.addstr(2, 2, "Navega con [↑/↓], pulsa [Enter] para editar un agente.", curses.A_DIM)
292
+ stdscr.addstr(3, 2, "Presiona [G] para Guardar y Aplicar, o [Q]/[ESC] para Salir.", curses.A_DIM)
293
+ stdscr.addstr(4, 2, "═" * 70)
294
+
295
+ # Construir items del menú interactivo
296
+ global_val = config.get("global", "deepseek/deepseek-v4-flash")
297
+
298
+ menu_items = [
299
+ ("global", f"Modelo Global: {global_val}"),
300
+ ]
301
+
302
+ for agent in AGENTS_LIST:
303
+ val = config.get(agent, "")
304
+ label = val if val else f"[Hereda del global: {global_val}]"
305
+ menu_items.append((agent, f"{agent}: {label}"))
306
+
307
+ # Añadir opciones de acción al menú
308
+ menu_items.append(("save", "[✓] GUARDAR Y APLICAR CAMBIOS"))
309
+ menu_items.append(("cancel", "[✗] CANCELAR Y SALIR"))
310
+
311
+ start_row = 6
312
+ for idx, (key, text) in enumerate(menu_items):
313
+ row = start_row + idx
314
+
315
+ # Resaltar la opción actual
316
+ if idx == selected_row:
317
+ stdscr.attron(curses.color_pair(1))
318
+ stdscr.addstr(row, 2, f" > {text:<65} ")
319
+ stdscr.attroff(curses.color_pair(1))
320
+ else:
321
+ if key == "save":
322
+ stdscr.addstr(row, 2, f" {text}", curses.color_pair(3) | curses.A_BOLD)
323
+ elif key == "cancel":
324
+ stdscr.addstr(row, 2, f" {text}", curses.color_pair(2))
325
+ elif key == "global":
326
+ stdscr.addstr(row, 2, f" {text}", curses.color_pair(3))
327
+ else:
328
+ stdscr.addstr(row, 2, f" {text}")
329
+
330
+ stdscr.refresh()
331
+
332
+ # Leer acción del usuario
333
+ try:
334
+ key = stdscr.getch()
335
+ except KeyboardInterrupt:
336
+ break
337
+
338
+ if key in (ord('q'), ord('Q'), 27): # Q o ESC
339
+ break
340
+ elif key in (ord('g'), ord('G')): # G de Guardar
341
+ apply_and_exit(config)
342
+ break
343
+ elif key == curses.KEY_UP:
344
+ if selected_row > 0:
345
+ selected_row -= 1
346
+ elif key == curses.KEY_DOWN:
347
+ if selected_row < len(menu_items) - 1:
348
+ selected_row += 1
349
+ elif key in (10, 13, curses.KEY_ENTER):
350
+ # Obtener el item seleccionado
351
+ action_key, _ = menu_items[selected_row]
352
+
353
+ if action_key == "save":
354
+ apply_and_exit(config)
355
+ break
356
+ elif action_key == "cancel":
357
+ break
358
+ elif action_key == "global":
359
+ # Editar el modelo global
360
+ new_val = select_model_tui(stdscr, "SELECCIONAR MODELO GLOBAL POR DEFECTO", global_val, is_global=True)
361
+ if new_val is not None:
362
+ config["global"] = new_val
363
+ save_models_config(config)
364
+ else:
365
+ # Editar un agente específico
366
+ current_agent_val = config.get(action_key, "")
367
+ new_val = select_model_tui(stdscr, f"SELECCIONAR MODELO PARA {action_key}", current_agent_val, is_global=False)
368
+ if new_val is not None:
369
+ config[action_key] = new_val
370
+ save_models_config(config)
371
+
372
+
373
+ def apply_and_exit(config):
374
+ # Guardar la configuración
375
+ save_models_config(config)
376
+
377
+ # Aplicar el mapeo de modelos final en cascada
378
+ global_model = config.get("global", "deepseek/deepseek-v4-flash")
379
+ final_agent_models = {}
380
+
381
+ print("\nMapeo de modelos resultante:")
382
+ for agent_name in AGENTS_LIST:
383
+ custom_val = config.get(agent_name, "")
384
+ final_model = custom_val if custom_val else global_model
385
+ final_agent_models[agent_name] = final_model
386
+ origin = "personalizado" if custom_val else "global"
387
+ print(f" * {agent_name} -> {final_model} ({origin})")
388
+
389
+ print("\nAplicando cambios...")
390
+ apply_model(final_agent_models)
391
+ print("\nModelos configurados exitosamente! 🎉")
392
+
393
+
394
+ # ==============================================================================
395
+ # MAIN ENTRY POINT
396
+ # ==============================================================================
397
+
398
+ def main():
399
+ args = sys.argv[1:]
400
+
401
+ # Cargar la configuración actual del JSON
402
+ config = load_models_config()
403
+
404
+ # Si se pide listar modelos por CLI
405
+ if args and args[0] in ("--list", "-l", "list"):
406
+ print_available_models()
407
+ sys.exit(0)
408
+
409
+ # Si se pasan argumentos por parámetro, corre en modo CLI no-interactivo rápido
410
+ if args:
411
+ query = args[0]
412
+ matches = search_model(query)
413
+
414
+ if not matches:
415
+ print(
416
+ f"Error: Ningún modelo coincide con '{query}'. Use 'list' para ver todos.",
417
+ file=sys.stderr,
418
+ )
419
+ sys.exit(1)
420
+ elif len(matches) == 1:
421
+ global_model_selected = matches[0]
422
+ else:
423
+ # Si hay múltiples coincidencias, tomamos la primera pero listamos las demás
424
+ global_model_selected = matches[0]
425
+ print(f"Coincidencias encontradas para '{query}':")
426
+ for m in matches:
427
+ print(f" * {m}")
428
+ print(
429
+ f"Seleccionando automáticamente la primera: {global_model_selected}\n"
430
+ )
431
+
432
+ # Actualizar el global_model en config y persistir
433
+ config["global"] = global_model_selected
434
+ apply_and_exit(config)
435
+ else:
436
+ # Si NO se pasan argumentos, inicia la TUI interactiva
437
+ curses.wrapper(main_tui)
438
+
439
+
440
+ if __name__ == "__main__":
441
+ main()
package/README.md CHANGED
@@ -85,6 +85,45 @@ Para evitar la pérdida de contexto entre turnos y agentes, el arnés implementa
85
85
 
86
86
  ---
87
87
 
88
+ ## 🎯 Configuración Rápida de Modelos de IA
89
+
90
+ El arnés de Zugzbot incluye un sistema de asignación de modelos centralizado, ágil e interactivo. La configuración se almacena en el archivo `models.json` en la raíz de tu proyecto.
91
+
92
+ ### 1. El Archivo `models.json`
93
+ El formato de este archivo es muy simple e intuitivo:
94
+ ```json
95
+ {
96
+ "global": "deepseek/deepseek-v4-flash",
97
+ "sdd-orchestrator": "",
98
+ "sdd-spec-writer": "",
99
+ "sdd-coder": "google/gemini-2.5-pro",
100
+ "sdd-tester": "",
101
+ "sdd-deployer": ""
102
+ }
103
+ ```
104
+ * **Modelo Global**: Define el modelo predeterminado (`"global"`) que usarán los agentes por defecto.
105
+ * **Heredar del Global**: Si dejas el modelo de un agente vacío (`""`), el sistema le asignará automáticamente el modelo `"global"`.
106
+ * **Personalización**: Si quieres que un agente en particular use un modelo diferente (ej. un modelo de razonamiento o de programación más potente), escribe el identificador del modelo directamente en su clave.
107
+
108
+ ### 2. Sincronización Automática
109
+ ¡No tienes que hacer nada manual! Al abrir la interfaz de **OpenCode**, el plugin integrado detecta y lee de forma automática tu `models.json`, actualizando instantáneamente `opencode.json` y los markdown de tus agentes en disco con total transparencia.
110
+
111
+ ### 3. Interfaz Visual en Terminal (TUI)
112
+ Para cambiar de modelos de forma cómoda y sin riesgo de cometer errores tipográficos en los identificadores de IA, puedes abrir la **interfaz visual interactiva** en tu terminal.
113
+
114
+ Ejecuta el script sin argumentos:
115
+ ```bash
116
+ python3 .utils/toggle_model.py
117
+ ```
118
+
119
+ * **Menú Principal**: Muévete con las flechas `[↑/↓]` y presiona `[Enter]` para editar el modelo global o el de un agente específico.
120
+ * **Selector con Buscador**: Dentro del selector de modelos, puedes empezar a escribir para filtrar la lista en tiempo real por palabra clave (ej. *"gemini"* o *"deepseek"*).
121
+ * **Guardar y Aplicar**: Selecciona `[✓ GUARDAR Y APLICAR CAMBIOS]` o pulsa la tecla `[G]` para persistir la configuración e inyectarla en todos tus agentes al instante.
122
+
123
+ *Nota: Sigue siendo compatible con la CLI tradicional rápida. Por ejemplo, `python3 .utils/toggle_model.py google/gemini-3.5-flash` actualizará de inmediato tu modelo global en modo lote no interactivo.*
124
+
125
+ ---
126
+
88
127
  ## 🔄 Modo Autopiloto (/loop)
89
128
 
90
129
  El arnés de Zugzbot viene preparado de forma nativa para soportar el **Modo Autopiloto** mediante el comando `/loop` en OpenCode.
package/bin/init.js CHANGED
@@ -15,14 +15,28 @@ const yellow = '\x1b[33m';
15
15
  const red = '\x1b[31m';
16
16
  const bold = '\x1b[1m';
17
17
 
18
- console.log(`\n${bold}${cyan}🤖 Zugzbot Harness Installer${reset}\n`);
19
-
20
18
  const pkgRoot = join(__dirname, '..');
21
19
  const targetDir = process.cwd();
22
20
 
21
+ const banner = `
22
+ ${bold}${cyan}███████╗██╗ ██╗ ██████╗ ███████╗
23
+ ╚══███╔╝██║ ██║██╔════╝ ╚══███╔╝
24
+ ███╔╝ ██║ ██║██║ ███╗ ███╔╝
25
+ ███╔╝ ██║ ██║██║ ██║ ███╔╝
26
+ ███████╗╚██████╔╝╚██████╔╝███████╗
27
+ ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝${reset}
28
+ ${bold}${yellow} Harness Installer v1.0.7${reset}\n`;
29
+
30
+ console.log(banner);
31
+ console.log(`${bold}${cyan}🔍 Detectando entorno de trabajo...${reset}`);
32
+ console.log(` ${green}✔ Directorio destino identificado: ${targetDir}${reset}\n`);
33
+ console.log(`${bold}${cyan}📦 Instalando arnés de desarrollo SDD...${reset}`);
34
+
23
35
  // Define items to copy
24
36
  const itemsToCopy = [
25
37
  { name: '.opencode', src: join(pkgRoot, '.opencode'), dest: join(targetDir, '.opencode'), type: 'dir' },
38
+ { name: '.utils', src: join(pkgRoot, '.utils'), dest: join(targetDir, '.utils'), type: 'dir' },
39
+ { name: 'models.json', src: join(pkgRoot, 'models.json'), dest: join(targetDir, 'models.json'), type: 'file' },
26
40
  { name: 'opencode.json', src: join(pkgRoot, 'opencode.json'), dest: join(targetDir, 'opencode.json'), type: 'file' },
27
41
  { name: 'tui.json', src: join(pkgRoot, 'tui.json'), dest: join(targetDir, 'tui.json'), type: 'file' }
28
42
  ];
@@ -47,11 +61,16 @@ for (const item of itemsToCopy) {
47
61
 
48
62
  const relativeDest = relative(targetDir, item.dest);
49
63
 
64
+ if (item.src === item.dest) {
65
+ copiedCount++;
66
+ continue;
67
+ }
68
+
50
69
  try {
51
70
  if (fs.existsSync(item.dest)) {
52
- console.log(`${yellow}⚠️ Updating existing: ${relativeDest}...${reset}`);
71
+ console.log(` ${yellow} Updated existing: ${relativeDest}${reset}`);
53
72
  } else {
54
- console.log(`${green}📦 Copying: ${relativeDest}...${reset}`);
73
+ console.log(` ${green} Copied: ${relativeDest}${reset}`);
55
74
  }
56
75
 
57
76
  if (item.type === 'dir') {
@@ -87,8 +106,8 @@ try {
87
106
  }
88
107
 
89
108
  if (copiedCount > 0) {
90
- console.log(`\n${bold}${green}✨ Zugzbot Harness successfully installed/updated!${reset}`);
91
- console.log(`Run ${cyan}opencode${reset} or your configured command to start building.\n`);
109
+ console.log(`\n${bold}${green}✨ ¡Arnés de Zugzbot instalado y actualizado con éxito!${reset}`);
110
+ console.log(`🚀 Ejecuta ${bold}${cyan}opencode${reset} para iniciar tu sesión de desarrollo autónomo.\n`);
92
111
  } else {
93
112
  console.log(`\n${red}❌ No files were installed.${reset}\n`);
94
113
  }
package/models.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "global": "deepseek/deepseek-v4-flash",
3
+ "sdd-orchestrator": "",
4
+ "sdd-spec-writer": "",
5
+ "sdd-coder": "",
6
+ "sdd-tester": "",
7
+ "sdd-deployer": ""
8
+ }
package/opencode.json CHANGED
@@ -20,8 +20,7 @@
20
20
  }
21
21
  },
22
22
  "lsp": true,
23
- "instructions": [
24
- ],
23
+ "instructions": [],
25
24
  "plugin": [
26
25
  "opencode-dynamic-context-pruning",
27
26
  "opencode-notificator"
@@ -45,7 +44,7 @@
45
44
  "mcp"
46
45
  ],
47
46
  "enabled": true,
48
- "purpose": "Librer\u00eda UI objetivo. Usar para browse/search/install de componentes y blocks (dashboards, landings, auth, forms)."
47
+ "purpose": "Librería UI objetivo. Usar para browse/search/install de componentes y blocks (dashboards, landings, auth, forms)."
49
48
  },
50
49
  "context7": {
51
50
  "type": "local",
@@ -67,7 +66,7 @@
67
66
  "--isolated"
68
67
  ],
69
68
  "enabled": true,
70
- "purpose": "Playwright MCP. Artefactos (screenshots, traces, videos, downloads) van a .openspec/.playwright/<call-id>/. Para evidencia por sesi\u00f3n SDD, promueve los artefactos relevantes a .openspec/specs/<activeContract>/playwright/ con .opencode/tools/save-playwright-artifacts.sh. --isolated evita ensuciar el perfil del browser en disco."
69
+ "purpose": "Playwright MCP. Artefactos (screenshots, traces, videos, downloads) van a .openspec/.playwright/<call-id>/. Para evidencia por sesión SDD, promueve los artefactos relevantes a .openspec/specs/<activeContract>/playwright/ con .opencode/tools/save-playwright-artifacts.sh. --isolated evita ensuciar el perfil del browser en disco."
71
70
  },
72
71
  "next-devtools": {
73
72
  "type": "local",
@@ -91,7 +90,7 @@
91
90
  "--stdio"
92
91
  ],
93
92
  "enabled": true,
94
- "purpose": "Permite buscar, filtrar y obtener ejemplos de uso JSX de m\u00e1s de 1,500 iconos de Lucide React de forma exacta mediante herramientas del MCP."
93
+ "purpose": "Permite buscar, filtrar y obtener ejemplos de uso JSX de más de 1,500 iconos de Lucide React de forma exacta mediante herramientas del MCP."
95
94
  }
96
95
  },
97
96
  "agent": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Fácil instalador del arnés SDD de Zugzbot para proyectos OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,8 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  ".opencode/",
12
+ ".utils/",
13
+ "models.json",
12
14
  "opencode.json",
13
15
  "tui.json"
14
16
  ],