zugzbot 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/commands/web.md +17 -0
- package/.opencode/plugins/sdd-bridge.ts +83 -1
- package/.utils/docs_opencode/acp.md +165 -0
- package/.utils/docs_opencode/acp.pdf +0 -0
- package/.utils/docs_opencode/agents.md +803 -0
- package/.utils/docs_opencode/agents.pdf +0 -0
- package/.utils/docs_opencode/commands.md +354 -0
- package/.utils/docs_opencode/commands.pdf +0 -0
- package/.utils/docs_opencode/custom-tools.md +209 -0
- package/.utils/docs_opencode/custom-tools.pdf +0 -0
- package/.utils/docs_opencode/ecosystem.md +81 -0
- package/.utils/docs_opencode/ecosystem.pdf +0 -0
- package/.utils/docs_opencode/formatters.md +142 -0
- package/.utils/docs_opencode/formatters.pdf +0 -0
- package/.utils/docs_opencode/keybinds.md +205 -0
- package/.utils/docs_opencode/keybinds.pdf +0 -0
- package/.utils/docs_opencode/lsp.md +202 -0
- package/.utils/docs_opencode/lsp.pdf +0 -0
- package/.utils/docs_opencode/mcp-servers.md +565 -0
- package/.utils/docs_opencode/mcp-servers.pdf +0 -0
- package/.utils/docs_opencode/models.md +234 -0
- package/.utils/docs_opencode/models.pdf +0 -0
- package/.utils/docs_opencode/permissions.md +248 -0
- package/.utils/docs_opencode/permissions.pdf +0 -0
- package/.utils/docs_opencode/plugins.md +409 -0
- package/.utils/docs_opencode/plugins.pdf +0 -0
- package/.utils/docs_opencode/rules.md +189 -0
- package/.utils/docs_opencode/rules.pdf +0 -0
- package/.utils/docs_opencode/sdk.md +522 -0
- package/.utils/docs_opencode/sdk.pdf +0 -0
- package/.utils/docs_opencode/server.md +324 -0
- package/.utils/docs_opencode/server.pdf +0 -0
- package/.utils/docs_opencode/skills.md +235 -0
- package/.utils/docs_opencode/skills.pdf +0 -0
- package/.utils/docs_opencode/themes.md +378 -0
- package/.utils/docs_opencode/themes.pdf +0 -0
- package/.utils/docs_opencode/tools.md +364 -0
- package/.utils/docs_opencode/tools.pdf +0 -0
- package/.utils/export_opencode_session.py +242 -0
- package/.utils/toggle_model.py +441 -0
- package/.utils/zugzweb/client/README.md +73 -0
- package/.utils/zugzweb/client/eslint.config.js +22 -0
- package/.utils/zugzweb/client/index.html +13 -0
- package/.utils/zugzweb/client/package-lock.json +3707 -0
- package/.utils/zugzweb/client/package.json +36 -0
- package/.utils/zugzweb/client/public/favicon.svg +1 -0
- package/.utils/zugzweb/client/public/icons.svg +24 -0
- package/.utils/zugzweb/client/src/App.tsx +1357 -0
- package/.utils/zugzweb/client/src/assets/hero.png +0 -0
- package/.utils/zugzweb/client/src/assets/react.svg +1 -0
- package/.utils/zugzweb/client/src/assets/vite.svg +1 -0
- package/.utils/zugzweb/client/src/index.css +185 -0
- package/.utils/zugzweb/client/src/main.tsx +10 -0
- package/.utils/zugzweb/client/tsconfig.app.json +25 -0
- package/.utils/zugzweb/client/tsconfig.json +7 -0
- package/.utils/zugzweb/client/tsconfig.node.json +24 -0
- package/.utils/zugzweb/client/vite.config.ts +11 -0
- package/.utils/zugzweb/daemon.js +297 -0
- package/README.md +40 -1
- package/bin/init.js +25 -6
- package/models.json +8 -0
- package/opencode.json +5 -7
- 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()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
globals: globals.browser,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>client</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|