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,364 @@
1
+ # Herramientas
2
+
3
+ Administre las herramientas que puede usar un LLM.
4
+
5
+ Las herramientas permiten que LLM realice acciones en su código base. OpenCode viene con un conjunto de herramientas integradas, pero puede ampliarlo con [herramientas personalizadas](https://opencode.ai/docs/custom-tools) o [servidores MCP](https://opencode.ai/docs/mcp-servers).
6
+
7
+ De forma predeterminada, todas las herramientas están **habilitadas** y no necesitan permiso para ejecutarse. Puede controlar el comportamiento de la herramienta a través de [permisos](https://opencode.ai/docs/permissions).
8
+
9
+ ---
10
+
11
+ ## [Configuración](#configuración)
12
+
13
+ Utilice el campo `permission` para controlar el comportamiento de la herramienta. Puede permitir, denegar o exigir aprobación para cada herramienta.
14
+
15
+ **File**: opencode.json
16
+
17
+ ```json
18
+ {
19
+ "$schema": "https://opencode.ai/config.json",
20
+ "permission": {
21
+ "edit": "deny",
22
+ "bash": "ask",
23
+ "webfetch": "allow"
24
+ }
25
+ }
26
+ ```
27
+
28
+ También puedes utilizar comodines para controlar varias herramientas a la vez. Por ejemplo, para solicitar aprobación para todas las herramientas de un servidor MCP:
29
+
30
+ **File**: opencode.json
31
+
32
+ ```json
33
+ {
34
+ "$schema": "https://opencode.ai/config.json",
35
+ "permission": {
36
+ "mymcp_*": "ask"
37
+ }
38
+ }
39
+ ```
40
+
41
+ [Más información](https://opencode.ai/docs/permissions) sobre la configuración de permisos.
42
+
43
+ ---
44
+
45
+ ## [Integradas](#integradas)
46
+
47
+ Aquí están todas las herramientas integradas disponibles en OpenCode.
48
+
49
+ ---
50
+
51
+ ### [bash](#bash)
52
+
53
+ Ejecute comandos de shell en el entorno de su proyecto.
54
+
55
+ **File**: opencode.json
56
+
57
+ ```json
58
+ {
59
+ "$schema": "https://opencode.ai/config.json",
60
+ "permission": {
61
+ "bash": "allow"
62
+ }
63
+ }
64
+ ```
65
+
66
+ Esta herramienta permite que LLM ejecute comandos de terminal como `npm install`, `git status` o cualquier otro comando de shell.
67
+
68
+ ---
69
+
70
+ ### [edit](#edit)
71
+
72
+ Modifique archivos existentes utilizando reemplazos de cadenas exactas.
73
+
74
+ **File**: opencode.json
75
+
76
+ ```json
77
+ {
78
+ "$schema": "https://opencode.ai/config.json",
79
+ "permission": {
80
+ "edit": "allow"
81
+ }
82
+ }
83
+ ```
84
+
85
+ Esta herramienta realiza ediciones precisas de archivos reemplazando coincidencias de texto exactas. Es la forma principal en que LLM modifica el código.
86
+
87
+ ---
88
+
89
+ ### [write](#write)
90
+
91
+ Cree nuevos archivos o sobrescriba los existentes.
92
+
93
+ **File**: opencode.json
94
+
95
+ ```json
96
+ {
97
+ "$schema": "https://opencode.ai/config.json",
98
+ "permission": {
99
+ "edit": "allow"
100
+ }
101
+ }
102
+ ```
103
+
104
+ Utilice esto para permitir que LLM cree nuevos archivos. Sobrescribirá los archivos existentes si ya existen.
105
+
106
+ > [!NOTE]
107
+ > La herramienta `write` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`).
108
+
109
+ ---
110
+
111
+ ### [read](#read)
112
+
113
+ Lea el contenido del archivo desde su base de código.
114
+
115
+ **File**: opencode.json
116
+
117
+ ```json
118
+ {
119
+ "$schema": "https://opencode.ai/config.json",
120
+ "permission": {
121
+ "read": "allow"
122
+ }
123
+ }
124
+ ```
125
+
126
+ Esta herramienta lee archivos y devuelve su contenido. Admite la lectura de rangos de líneas específicos para archivos grandes.
127
+
128
+ ---
129
+
130
+ ### [grep](#grep)
131
+
132
+ Busque contenidos de archivos utilizando expresiones regulares.
133
+
134
+ **File**: opencode.json
135
+
136
+ ```json
137
+ {
138
+ "$schema": "https://opencode.ai/config.json",
139
+ "permission": {
140
+ "grep": "allow"
141
+ }
142
+ }
143
+ ```
144
+
145
+ Búsqueda rápida de contenido en su base de código. Admite sintaxis de expresiones regulares completa y filtrado de patrones de archivos.
146
+
147
+ ---
148
+
149
+ ### [glob](#glob)
150
+
151
+ Encuentre archivos por coincidencia de patrones.
152
+
153
+ **File**: opencode.json
154
+
155
+ ```json
156
+ {
157
+ "$schema": "https://opencode.ai/config.json",
158
+ "permission": {
159
+ "glob": "allow"
160
+ }
161
+ }
162
+ ```
163
+
164
+ Busque archivos usando patrones globales como `**/*.js` o `src/**/*.ts`. Devuelve rutas de archivos coincidentes ordenadas por hora de modificación.
165
+
166
+ ---
167
+
168
+ ### [lsp (experimental)](#lsp-experimental)
169
+
170
+ Interactúe con sus servidores LSP configurados para obtener funciones de inteligencia de código como definiciones, referencias, información de desplazamiento y jerarquía de llamadas.
171
+
172
+ > [!NOTE]
173
+ > Esta herramienta solo está disponible cuando `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (o `OPENCODE_EXPERIMENTAL=true`).
174
+
175
+ **File**: opencode.json
176
+
177
+ ```json
178
+ {
179
+ "$schema": "https://opencode.ai/config.json",
180
+ "permission": {
181
+ "lsp": "allow"
182
+ }
183
+ }
184
+ ```
185
+
186
+ Las operaciones admitidas incluyen `goToDefinition`, `findReferences`, `hover`, `documentSymbol`, `workspaceSymbol`, `goToImplementation`, `prepareCallHierarchy`, `incomingCalls` y `outgoingCalls`.
187
+
188
+ Para configurar qué servidores LSP están disponibles para su proyecto, consulte [Servidores LSP](https://opencode.ai/docs/lsp).
189
+
190
+ ---
191
+
192
+ ### [patch](#patch)
193
+
194
+ Aplicar parches a los archivos.
195
+
196
+ **File**: opencode.json
197
+
198
+ ```json
199
+ {
200
+ "$schema": "https://opencode.ai/config.json",
201
+ "permission": {
202
+ "edit": "allow"
203
+ }
204
+ }
205
+ ```
206
+
207
+ Esta herramienta aplica archivos de parche a su código base. Útil para aplicar diferencias y parches de diversas fuentes.
208
+
209
+ > [!NOTE]
210
+ > La herramienta `patch` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`).
211
+
212
+ ---
213
+
214
+ ### [skill](#skill)
215
+
216
+ Cargue una [habilidad](https://opencode.ai/docs/skills) (un archivo `SKILL.md`) y devuelva su contenido en la conversación.
217
+
218
+ **File**: opencode.json
219
+
220
+ ```json
221
+ {
222
+ "$schema": "https://opencode.ai/config.json",
223
+ "permission": {
224
+ "skill": "allow"
225
+ }
226
+ }
227
+ ```
228
+
229
+ ---
230
+
231
+ ### [todowrite](#todowrite)
232
+
233
+ Administre listas de tareas pendientes durante las sesiones de codificación.
234
+
235
+ **File**: opencode.json
236
+
237
+ ```json
238
+ {
239
+ "$schema": "https://opencode.ai/config.json",
240
+ "permission": {
241
+ "todowrite": "allow"
242
+ }
243
+ }
244
+ ```
245
+
246
+ Crea y actualiza listas de tareas para realizar un seguimiento del progreso durante operaciones complejas. El LLM usa esto para organizar tareas de varios pasos.
247
+
248
+ > [!NOTE]
249
+ > Esta herramienta está deshabilitada para los subagentes de forma predeterminada, pero puede habilitarla manualmente. [Más información](https://opencode.ai/docs/agents/#permissions)
250
+
251
+ ---
252
+
253
+ ### [webfetch](#webfetch)
254
+
255
+ Obtener contenido web.
256
+
257
+ **File**: opencode.json
258
+
259
+ ```json
260
+ {
261
+ "$schema": "https://opencode.ai/config.json",
262
+ "permission": {
263
+ "webfetch": "allow"
264
+ }
265
+ }
266
+ ```
267
+
268
+ Permite que LLM busque y lea páginas web. Útil para buscar documentación o investigar recursos en línea.
269
+
270
+ ---
271
+
272
+ ### [websearch](#websearch)
273
+
274
+ Busque información en la web.
275
+
276
+ > [!NOTE]
277
+ > Esta herramienta solo está disponible cuando se utiliza el proveedor OpenCode o cuando la variable de entorno `OPENCODE_ENABLE_EXA` está configurada en cualquier valor verdadero (por ejemplo, `true` o `1`).
278
+ > Para habilitar al iniciar OpenCode:
279
+ > **File**: Ventana de terminal
280
+ > ```bash
281
+ > OPENCODE_ENABLE_EXA=1 opencode
282
+ > ```
283
+
284
+ **File**: opencode.json
285
+
286
+ ```json
287
+ {
288
+ "$schema": "https://opencode.ai/config.json",
289
+ "permission": {
290
+ "websearch": "allow"
291
+ }
292
+ }
293
+ ```
294
+
295
+ Realiza búsquedas web utilizando Exa AI para encontrar información relevante en línea. Útil para investigar temas, encontrar eventos actuales o recopilar información más allá del límite de datos de entrenamiento.
296
+
297
+ No se requiere ninguna clave API: la herramienta se conecta directamente al servicio MCP alojado de Exa AI sin autenticación.
298
+
299
+ > [!TIP]
300
+ > Utilice `websearch` cuando necesite encontrar información (descubrimiento) y `webfetch` cuando necesite recuperar contenido de una URL específica (recuperación).
301
+
302
+ ---
303
+
304
+ ### [question](#question)
305
+
306
+ Haga preguntas al usuario durante la ejecución.
307
+
308
+ **File**: opencode.json
309
+
310
+ ```json
311
+ {
312
+ "$schema": "https://opencode.ai/config.json",
313
+ "permission": {
314
+ "question": "allow"
315
+ }
316
+ }
317
+ ```
318
+
319
+ Esta herramienta permite que LLM haga preguntas al usuario durante una tarea. Es útil para:
320
+
321
+ - Recopilar preferencias o requisitos del usuario.
322
+ - Aclarar instrucciones ambiguas
323
+ - Tomar decisiones sobre las opciones de implementación.
324
+ - Ofrecer opciones sobre qué dirección tomar.
325
+
326
+ Cada pregunta incluye un encabezado, el texto de la pregunta y una lista de opciones. Los usuarios pueden seleccionar entre las opciones proporcionadas o escribir una respuesta personalizada. Cuando hay varias preguntas, los usuarios pueden navegar entre ellas antes de enviar todas las respuestas.
327
+
328
+ ---
329
+
330
+ ## [Herramientas personalizadas](#herramientas-personalizadas)
331
+
332
+ Las herramientas personalizadas le permiten definir sus propias funciones a las que LLM puede llamar. Estos están definidos en su archivo de configuración y pueden ejecutar código arbitrario.
333
+
334
+ [Más información](https://opencode.ai/docs/custom-tools) sobre la creación de herramientas personalizadas.
335
+
336
+ ---
337
+
338
+ ## [Servidores MCP](#servidores-mcp)
339
+
340
+ Los servidores MCP (Model Context Protocol) le permiten integrar herramientas y servicios externos. Esto incluye acceso a bases de datos, integraciones API y servicios de terceros.
341
+
342
+ [Más información](https://opencode.ai/docs/mcp-servers) sobre la configuración de servidores MCP.
343
+
344
+ ---
345
+
346
+ ## [Internos](#internos)
347
+
348
+ Internamente, herramientas como `grep` y `glob` usan [ripgrep](https://github.com/BurntSushi/ripgrep) bajo el capó. De forma predeterminada, ripgrep respeta los patrones `.gitignore`, lo que significa que los archivos y directorios enumerados en su `.gitignore` se excluirán de las búsquedas y listados.
349
+
350
+ ---
351
+
352
+ ### [Ignorar patrones](#ignorar-patrones)
353
+
354
+ Para incluir archivos que normalmente se ignorarían, cree un archivo `.ignore` en la raíz de su proyecto. Este archivo puede permitir explícitamente ciertas rutas.
355
+
356
+ **File**: .ignore
357
+
358
+ ```text
359
+ !node_modules/
360
+ !dist/
361
+ !build/
362
+ ```
363
+
364
+ Por ejemplo, este archivo `.ignore` permite que ripgrep busque dentro de los directorios `node_modules/`, `dist/` y `build/` incluso si figuran en `.gitignore`.
Binary file
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python3
2
+ import sqlite3
3
+ import os
4
+ import json
5
+ import sys
6
+ from datetime import datetime
7
+
8
+ DB_PATH = "/Users/wavesbyte/.local/share/opencode/opencode.db"
9
+
10
+ # Escribe aquí el ID de la sesión que deseas exportar (ejemplo: "ses_1234...")
11
+ # Si se deja vacío, el script requerirá el ID como argumento al ejecutarlo
12
+ TARGET_SESSION_ID = "ses_12f8"
13
+
14
+
15
+ def format_timestamp(ts):
16
+ if not ts:
17
+ return "N/A"
18
+ try:
19
+ # DB timestamp is in milliseconds
20
+ dt = datetime.fromtimestamp(ts / 1000.0)
21
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
22
+ except Exception:
23
+ return str(ts)
24
+
25
+ def sanitize_filename(name):
26
+ # Keep only alphanumeric, spaces, hyphens, and underscores
27
+ return "".join(c for c in name if c.isalnum() or c in (" ", "-", "_")).rstrip().replace(" ", "_")
28
+
29
+ def get_session_tree(cursor, session_id):
30
+ """Recursively fetch child sessions and build a flat list with structure info."""
31
+ cursor.execute("SELECT * FROM session WHERE id = ?", (session_id,))
32
+ main = cursor.fetchone()
33
+ if not main:
34
+ return []
35
+
36
+ sessions = []
37
+
38
+ def traverse(sid, depth=0):
39
+ cursor.execute("SELECT * FROM session WHERE id = ?", (sid,))
40
+ s = cursor.fetchone()
41
+ if not s:
42
+ return
43
+
44
+ s_dict = dict(s)
45
+ s_dict['depth'] = depth
46
+ sessions.append(s_dict)
47
+
48
+ # Find children
49
+ cursor.execute("SELECT id FROM session WHERE parent_id = ? ORDER BY time_created ASC", (sid,))
50
+ children = cursor.fetchall()
51
+ for child in children:
52
+ traverse(child['id'], depth + 1)
53
+
54
+ traverse(session_id)
55
+ return sessions
56
+
57
+ def export_session_messages(cursor, session, output_dir, file_index):
58
+ sid = session['id']
59
+ title = session['title'] or f"Subagent_{sid[:8]}"
60
+ agent = session['agent'] or "unknown"
61
+
62
+ filename = f"{file_index:02d}_{sanitize_filename(title)}.md"
63
+ filepath = os.path.join(output_dir, filename)
64
+
65
+ # Fetch messages and their parts
66
+ cursor.execute("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC", (sid,))
67
+ messages = cursor.fetchall()
68
+
69
+ with open(filepath, "w", encoding="utf-8") as f:
70
+ f.write(f"# {title}\n\n")
71
+ f.write(f"* **ID de Sesión**: `{sid}`\n")
72
+ if session['parent_id']:
73
+ f.write(f"* **ID Padre**: `{session['parent_id']}`\n")
74
+ f.write(f"* **Agente**: `{agent}`\n")
75
+ f.write(f"* **Modelo**: `{session['model']}`\n")
76
+ f.write(f"* **Creado**: {format_timestamp(session['time_created'])}\n")
77
+ f.write(f"* **Costo estimado**: ${session['cost'] or 0.0:.6f}\n")
78
+ f.write(f"* **Tokens**: Entrada: {session['tokens_input'] or 0} | Salida: {session['tokens_output'] or 0} | Razonamiento: {session['tokens_reasoning'] or 0}\n\n")
79
+ f.write("---\n\n")
80
+
81
+ for msg in messages:
82
+ msg_id = msg['id']
83
+ msg_data = {}
84
+ try:
85
+ msg_data = json.loads(msg['data'])
86
+ except Exception:
87
+ pass
88
+
89
+ role = msg_data.get('role', 'System')
90
+ role_emoji = "👤 Usuario" if role == "user" else "🤖 Asistente"
91
+ f.write(f"## {role_emoji} ({format_timestamp(msg['time_created'])})\n\n")
92
+
93
+ # Fetch parts for this message
94
+ cursor.execute("SELECT * FROM part WHERE message_id = ? ORDER BY time_created ASC", (msg_id,))
95
+ parts = cursor.fetchall()
96
+
97
+ for part in parts:
98
+ part_data = {}
99
+ try:
100
+ part_data = json.loads(part['data'])
101
+ except Exception:
102
+ continue
103
+
104
+ p_type = part_data.get('type')
105
+
106
+ if p_type == 'text':
107
+ f.write(part_data.get('text', '') + "\n\n")
108
+
109
+ elif p_type == 'reasoning':
110
+ f.write("<details>\n<summary>💭 Pensamiento (Reasoning)</summary>\n\n")
111
+ f.write(part_data.get('text', '') + "\n")
112
+ f.write("\n</details>\n\n")
113
+
114
+ elif p_type == 'tool':
115
+ tool_name = part_data.get('tool', 'unknown')
116
+ state = part_data.get('state', {})
117
+ status = state.get('status', 'unknown')
118
+
119
+ # Tool Call Input
120
+ f.write(f"⚙️ **Llamada a Herramienta**: `{tool_name}` (Estado: *{status}*)\n\n")
121
+ tool_input = state.get('input', {})
122
+ f.write("```json\n")
123
+ f.write(json.dumps(tool_input, indent=2) + "\n")
124
+ f.write("```\n\n")
125
+
126
+ # Tool Call Output
127
+ tool_output = state.get('output', '')
128
+ f.write("📥 **Resultado de la Herramienta**:\n")
129
+ try:
130
+ parsed_out = json.loads(tool_output)
131
+ f.write("```json\n" + json.dumps(parsed_out, indent=2) + "\n```\n\n")
132
+ except Exception:
133
+ if "\n" in tool_output:
134
+ f.write("```\n" + tool_output + "\n```\n\n")
135
+ else:
136
+ f.write(f"`{tool_output}`\n\n")
137
+
138
+ print(f" Exportado: {filename}")
139
+ return filename
140
+
141
+ def list_sessions(cursor):
142
+ cursor.execute("""
143
+ SELECT id, title, agent, time_created
144
+ FROM session
145
+ WHERE parent_id IS NULL OR parent_id = ''
146
+ ORDER BY time_created DESC
147
+ LIMIT 15
148
+ """)
149
+ sessions = cursor.fetchall()
150
+ print("Últimas sesiones de OpenCode disponibles:")
151
+ print("-" * 85)
152
+ for s in sessions:
153
+ title = s['title'] or "(Sin título)"
154
+ created = format_timestamp(s['time_created'])
155
+ print(f"ID: {s['id']} | {created} | {s['agent']} - {title[:35]}")
156
+ print("-" * 85)
157
+
158
+ def main():
159
+ if not os.path.exists(DB_PATH):
160
+ print(f"Error: No se encontró la base de datos de OpenCode en {DB_PATH}")
161
+ sys.exit(1)
162
+
163
+ conn = sqlite3.connect(DB_PATH)
164
+ conn.row_factory = sqlite3.Row
165
+ cursor = conn.cursor()
166
+
167
+ target_session_id = TARGET_SESSION_ID
168
+
169
+ # If not configured in the script, look for command line arguments
170
+ if not target_session_id:
171
+ if len(sys.argv) < 2:
172
+ list_sessions(cursor)
173
+ print("\nUso: python3 export_opencode_session.py <session_id>")
174
+ print("O configura la variable 'TARGET_SESSION_ID' al inicio de este script.")
175
+ conn.close()
176
+ sys.exit(0)
177
+ target_session_id = sys.argv[1]
178
+
179
+ # Resolver el ID si es parcial/prefijo (ej. "ses_1619")
180
+ # Prioriza sesiones raíz (sin padre) para no agarrar un subagente por error
181
+ cursor.execute("""
182
+ SELECT id FROM session
183
+ WHERE id = ? OR id LIKE ?
184
+ ORDER BY (parent_id IS NULL OR parent_id = '') DESC, time_created DESC
185
+ LIMIT 1
186
+ """, (target_session_id, f"{target_session_id}%"))
187
+ row = cursor.fetchone()
188
+ if row:
189
+ target_session_id = row['id']
190
+
191
+ # 1. Fetch entire tree
192
+ session_tree = get_session_tree(cursor, target_session_id)
193
+
194
+ if not session_tree:
195
+ print(f"Error: No se encontró la sesión con ID '{target_session_id}'")
196
+ conn.close()
197
+ sys.exit(1)
198
+
199
+ main_session = session_tree[0]
200
+ safe_title = sanitize_filename(main_session['title'] or "session")
201
+ export_dir_name = f"export_{target_session_id[:8]}_{safe_title}"
202
+ output_dir = os.path.abspath(export_dir_name)
203
+
204
+ os.makedirs(output_dir, exist_ok=True)
205
+ print(f"\nExportando sesión principal y subagentes a: {output_dir}\n")
206
+
207
+ # 2. Export each session
208
+ exported_files = []
209
+ for idx, s in enumerate(session_tree):
210
+ fname = export_session_messages(cursor, s, output_dir, idx)
211
+ exported_files.append((s, fname))
212
+
213
+ # 3. Create README index file
214
+ readme_path = os.path.join(output_dir, "README.md")
215
+ with open(readme_path, "w", encoding="utf-8") as f:
216
+ f.write(f"# Índice de Sesión de OpenCode\n\n")
217
+ f.write(f"Este directorio contiene los registros completos y ordenados de la sesión y todos sus subagentes.\n\n")
218
+
219
+ f.write("## Datos de la Sesión Principal\n")
220
+ f.write(f"* **ID**: `{main_session['id']}`\n")
221
+ f.write(f"* **Título**: {main_session['title']}\n")
222
+ f.write(f"* **Agente Principal**: `{main_session['agent']}`\n")
223
+ f.write(f"* **Creado**: {format_timestamp(main_session['time_created'])}\n")
224
+ f.write(f"* **Última Actualización**: {format_timestamp(main_session['time_updated'])}\n\n")
225
+
226
+ f.write("## 🌳 Árbol de Ejecución de Subagentes\n")
227
+ f.write("Haz clic en los enlaces para abrir el historial detallado de cada subagente:\n\n")
228
+
229
+ for s, fname in exported_files:
230
+ indent = " " * s['depth']
231
+ bullet = "•" if s['depth'] > 0 else "📂"
232
+ agent_label = f"({s['agent']})" if s['agent'] else ""
233
+ title_text = s['title'] or f"Subagente {s['id'][:8]}"
234
+ f.write(f"{indent}{bullet} [{title_text}]({fname}) {agent_label}\n")
235
+
236
+ f.write("\n\n---\n*Exportación generada automáticamente el: {}*\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
237
+
238
+ print(f"\n¡Listo! Se ha creado un archivo README.md indexado en:\n{readme_path}\n")
239
+ conn.close()
240
+
241
+ if __name__ == "__main__":
242
+ main()