ratacode 0.2.5

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 (58) hide show
  1. package/CREDITS.md +80 -0
  2. package/LICENSE +21 -0
  3. package/README.md +289 -0
  4. package/apreton/README.md +54 -0
  5. package/apreton/handshake.md +33 -0
  6. package/apreton/headless.md +58 -0
  7. package/apreton/mcp.md +221 -0
  8. package/apreton/navegador.md +58 -0
  9. package/bin/instalacion.js +148 -0
  10. package/bin/ratacode.js +1251 -0
  11. package/fabrica/settings.yaml +247 -0
  12. package/mcp/README.md +253 -0
  13. package/mcp/bin/ratacode-mcp.js +215 -0
  14. package/mcp/lib/actividad.js +49 -0
  15. package/mcp/lib/casa.js +126 -0
  16. package/mcp/lib/claves.js +148 -0
  17. package/mcp/lib/espacios.js +117 -0
  18. package/mcp/lib/http.js +184 -0
  19. package/mcp/lib/lectura.js +210 -0
  20. package/mcp/lib/modelos.js +235 -0
  21. package/mcp/lib/nucleo.js +332 -0
  22. package/mcp/lib/registro.js +21 -0
  23. package/mcp/lib/seguridad.js +283 -0
  24. package/mcp/lib/servidor.js +397 -0
  25. package/mcp/lib/tareas.js +406 -0
  26. package/mcp/package.json +22 -0
  27. package/mcp/tunel.mjs +209 -0
  28. package/modos/arquitecto/agent.cordis.yml +120 -0
  29. package/modos/arquitecto/preset.yml +3 -0
  30. package/modos/capataz/agent.cordis.yml +120 -0
  31. package/modos/capataz/preset.yml +3 -0
  32. package/modos/faro/agent.cordis.yml +120 -0
  33. package/modos/faro/preset.yml +3 -0
  34. package/modos/gepeto/agent.cordis.yml +191 -0
  35. package/modos/gepeto/preset.yml +3 -0
  36. package/modos/hero/agent.cordis.yml +198 -0
  37. package/modos/hero/preset.yml +3 -0
  38. package/modos/modo-rata/agent.cordis.yml +198 -0
  39. package/modos/modo-rata/preset.yml +3 -0
  40. package/modos/nex/agent.cordis.yml +213 -0
  41. package/modos/nex/preset.yml +3 -0
  42. package/modos/nex/skills/cordis-plugin-development/SKILL.md +420 -0
  43. package/modos/nex/skills/editing-cordis-compositions/SKILL.md +165 -0
  44. package/modos/pix/agent.cordis.yml +207 -0
  45. package/modos/pix/preset.yml +3 -0
  46. package/modos/tirita/agent.cordis.yml +130 -0
  47. package/modos/tirita/preset.yml +3 -0
  48. package/package.json +49 -0
  49. package/piel/activos/ratacode-emblema.svg +14 -0
  50. package/piel/activos/ratacode-es.js +1331 -0
  51. package/piel/activos/ratacode-identidad.css +50 -0
  52. package/piel/activos/ratacode-piel.css +217 -0
  53. package/piel/activos/ratacode-piel.js +487 -0
  54. package/piel/activos/ratacode-vida.js +424 -0
  55. package/piel/cordis.patch.yml +6 -0
  56. package/piel/lib/cliente.js +1020 -0
  57. package/piel/lib/index.js +1230 -0
  58. package/piel/package.json +36 -0
package/apreton/mcp.md ADDED
@@ -0,0 +1,221 @@
1
+ # MCP: cómo conectar tu app a RATACODE
2
+
3
+ RATACODE es una terminal de trabajo con IA con la cara de RATACODE, montada sobre un motor libre que no se toca. El MCP (Model Context Protocol) deja que cualquier app con MCP (Claude Code, Codex, OpenClaw, Rowboat, ChatGPT web con túnel) le mande trabajo a los modelos que ya tienes configurados en RATACODE —incluidos los locales, con Ollama o LM Studio—. Los modelos baratos hacen el trabajo; tú planificas, revisas y cierras.
4
+
5
+ **Es la vía recomendada** para cualquier app que hable MCP: cuesta menos que mirar la pantalla (dos llamadas y texto, sin capturas), tiene estado explícito (`run_task` con `esperar_segundos` + `get_task_status`) y **cada tarea sale en la barra lateral del panel**, con su conversación.
6
+
7
+ ## Cómo darlo de alta (por app)
8
+
9
+ El servidor es `ratacode mcp` (subcomando del mismo binario). Si `ratacode` no está en el PATH,
10
+ usa `node <ruta>\bin\ratacode.js mcp`.
11
+
12
+ **Las claves NO van por aquí.** Están en un solo sitio —RATACODE › Ajustes › Models— y el MCP las
13
+ lee de ahí (preguntándole al motor). No hay que pasarle ninguna variable de entorno: su alta es
14
+ `ratacode mcp` y nada más. `<casa>` es tu casa (`%USERPROFILE%\.ratacode` si no le has dicho otra
15
+ cosa con `--home`).
16
+
17
+ ### Claude Code — COMPROBADO
18
+ Documentación oficial: <https://code.claude.com/docs/en/mcp> (opción 3, servidor stdio local).
19
+ Todo lo que va detrás de `--` es el comando del servidor, sin tocarlo.
20
+
21
+ ```sh
22
+ claude mcp add --transport stdio ratacode -- ratacode mcp
23
+ ```
24
+
25
+ Lo que NO hay que hacer es terminar el turno con la tarea en marcha: por stdio el servidor vive lo
26
+ que vive el cliente (mira «La regla del tropiezo 17», abajo).
27
+
28
+ ### Codex — COMPROBADO
29
+ Documentación oficial: <https://learn.chatgpt.com/docs/extend/mcp> (Codex CLI → *Configure with the CLI*).
30
+
31
+ ```toml
32
+ [mcp_servers.ratacode]
33
+ command = "ratacode"
34
+ args = ["mcp"]
35
+ ```
36
+
37
+ Ni `--env` ni `env_vars`: la clave no se le pasa al servidor, se lee de Ajustes › Models.
38
+
39
+ **Codex y las aprobaciones.** Codex no ejecuta herramientas MCP con la política de aprobación en
40
+ «never»: sale `MCP tool call requires approval, but approval policy is never`, y el encargo no llega
41
+ a RATACODE. Se arregla aprobando: `codex exec --approve-for-me "…"` (comprobado el 26-sep), o
42
+ aprobando la herramienta en la sesión interactiva. Sin una de las dos cosas, Codex se queda mirando.
43
+
44
+ ### OpenClaw — COMPROBADO
45
+ Documentación oficial: <https://docs.openclaw.ai/cli/mcp/registry>. Aquí no hay `--`: cada cosa va
46
+ con su bandera (`--command`, un `--arg` por argumento, `--cwd`).
47
+
48
+ ```sh
49
+ openclaw mcp add ratacode --command ratacode --arg mcp
50
+ ```
51
+
52
+ ### Rowboat — SOSPECHA
53
+ Lo único que dice la documentación oficial (<https://github.com/rowboatlabs/rowboat>, *Extend Rowboat
54
+ with tools (MCP)*) es: **Settings → MCP Servers**, añadir la entrada al objeto `mcpServers` y *Save*.
55
+ El ejemplo que publican es de un servidor HTTP (`url`), así que **no está comprobado que su editor
56
+ acepte un servidor stdio** con `command`/`args`. Si lo acepta, la entrada sería:
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "ratacode": {
62
+ "command": "ratacode",
63
+ "args": ["mcp"]
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ **SOSPECHA:** no he encontrado un `rowboat mcp add` por línea de órdenes ni un fichero de
70
+ configuración documentado; compruébalo en tu versión de Rowboat.
71
+
72
+ ### Genérico (cualquier cliente MCP por stdio)
73
+ Añade esto a la configuración MCP de tu app:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "ratacode": {
79
+ "command": "ratacode",
80
+ "args": [
81
+ "mcp",
82
+ "--home",
83
+ "C:\\Users\\tu-usuario\\.ratacode"
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ **Las claves están en la casa, no en tu entorno.** El servidor no mira las variables del cliente ni
91
+ abre ficheros de claves: le pregunta al motor si la credencial de esa ruta está puesta. Si no lo
92
+ está, se para y dice: `Falta la clave de B.AI. Pégala en RATACODE › Ajustes › Models.`
93
+
94
+ ## Lo que una tarea puede LEER (y por qué el HTTP pide permiso)
95
+
96
+ Escríbelo en tu cabeza antes de abrir el túnel: **una tarea MCP lee y escribe solo dentro de las
97
+ carpetas que tú autorizas** (`mcp.workspaces`). No es un descuido del motor: DSH no tiene ningún
98
+ modo que acote la lectura —`read-only`, `workspace-write` y `danger-full-access` son ejes de
99
+ ESCRITURA, y su propio código lo dice: «Reads pass through untouched: every mode permits
100
+ reading»—, y su encierre de Windows restringe el token a la escritura («`WRITE_RESTRICTED`
101
+ intersects only write accesses»). Por eso la lectura la encierra RATACODE, con el gancho
102
+ `tools/pre-execute` del motor: cada herramienta que lleve una ruta fuera de tus carpetas se para
103
+ y el agente ve una línea: «Fuera de la carpeta autorizada: `<ruta>`».
104
+
105
+ Y no hay puertas por detrás: en una tarea del MCP no hay terminal, ni trabajos en segundo plano,
106
+ ni red, ni subagentes, ni guiones.
107
+
108
+ Consecuencias, claras:
109
+
110
+ - Lo que la tarea lea **viaja al proveedor del modelo** que hayas elegido, así que la carpeta
111
+ autorizada es, de verdad, todo lo que ese chat puede ver. No pongas ahí claves de otros sitios.
112
+ - `ratacode mcp --http` y `node mcp/tunel.mjs` **ya no piden nada**: antes exigían
113
+ `--acepto-lectura-total` porque la lectura no estaba encerrada; desde R25 sí lo está, y esa
114
+ bandera se acepta como no-op (para no romper los comandos viejos).
115
+ - Por stdio, igual: la superficie la controlas tú (es tu propio cliente local el que arranca el
116
+ servidor).
117
+ - Si además quieres aislarlo del sistema operativo (un usuario o una máquina virtual solo para
118
+ esto), mejor: son dos cierres, no uno.
119
+
120
+ ## ChatGPT web (y cualquier app que hable MCP por HTTP): el túnel
121
+
122
+ ChatGPT web no arranca procesos: necesita una **URL** de MCP por HTTP. Por eso existe
123
+ `mcp/tunel.mjs`: expone el MCP de tu PC a Internet con Cloudflare mientras corre.
124
+
125
+ ```sh
126
+ # 1) el MCP por HTTP (local), con espacios declarados en `mcp.workspaces`
127
+ ratacode mcp --http
128
+
129
+ # 2) el túnel, en otra ventana
130
+ node mcp/tunel.mjs --home <casa>
131
+ ```
132
+
133
+ `tunel.mjs` te imprime la **URL pública completa** (dominio + `/mcp/<clave>`): esa es la que se
134
+ pega en ChatGPT (modo desarrollador → conector MCP) o en la app que sea. Al abrirlo **estrena
135
+ clave** (el servidor que ya corre la adopta sin reiniciar; con `--misma-clave` reutiliza la
136
+ anterior). La clave vive en `<casa>\mcp\http-url.txt` y `<casa>\mcp\http-secret.txt`, con permisos
137
+ de sólo-dueño; no se imprime en los registros. Con `Ctrl+C` se cierra el túnel y el puerto deja de
138
+ estar expuesto.
139
+
140
+ Si tienes dado de alta el túnel nombrado `mcp.mod-rat.com` en tu Cloudflare, lo usa con ese
141
+ hostname fijo (por fichero de configuración, nunca pasando la URL por argumentos); si no, abre un
142
+ quick tunnel con URL efímera.
143
+
144
+ **Lo que implica abrirlo:** el túnel expone el MCP a Internet, así que quien tenga esa URL puede
145
+ mandar tareas. Lo que **no** puede es salirse de tus carpetas: la tarea lee y escribe solo dentro
146
+ de `mcp.workspaces` y no tiene terminal ni red (mira el apartado de arriba). No lo dejes abierto
147
+ más de lo que dure el trabajo, y cámbiale la clave (basta con volver a lanzar `tunel.mjs`) cuando
148
+ cierres.
149
+
150
+ **Rowboat.** Su editor documenta servidores por `url`, no por `command`; con el túnel o con el MCP
151
+ por HTTP puedes darle la URL con la clave y no depender de que acepte un servidor stdio (que es lo
152
+ que sigue sin estar comprobado, ver arriba).
153
+
154
+ ## Cómo usarlo
155
+
156
+ 1. **`list_models`** — llama primero para ver qué modelos hay, con proveedor, id, contexto, capacidades y estado.
157
+ 2. **`run_task`** — lanza el encargo. Sin `esperar_segundos` devuelve `task_id` al momento y el trabajo sigue en segundo plano. **Con `esperar_segundos` (1-600), la llamada ESPERA y devuelve el resultado completo en esa misma respuesta**, sin llamar a nada más.
158
+ 3. **`get_task_status`** — consulta el estado: `queued`, `running`, `completed`, `failed`, `cancelled`.
159
+ 4. **`get_task_result`** — recoge la respuesta, modelo, proveedor, tokens, coste (si está declarado), duración y errores.
160
+
161
+ **Si el usuario eligió un modelo, no lo cambies.** Si no eligió, se usa el modelo por defecto de la casa y se te dice cuál.
162
+
163
+ ### La regla del tropiezo 17: no dejes la tarea a medias
164
+
165
+ Por stdio, **las tareas viven lo que vive el cliente**: si el cliente se cierra, el servidor se va y la
166
+ tarea muere donde esté (así quedó un `hola.txt` con un «hola» y nada más: el cliente había terminado su
167
+ turno con «despiertas en 60 s», y a los 60 s ya no había nadie). Con un cliente de una sola vuelta
168
+ (`claude -p`, `codex exec`, una llamada suelta), haz UNA de estas dos cosas:
169
+
170
+ - lanza `run_task` **con `esperar_segundos`** (p. ej. 300) y recoge el resultado de esa misma respuesta; o
171
+ - **no termines tu turno hasta que `get_task_status` diga `completed` o `failed`**.
172
+
173
+ Con `esperar_segundos`, si la tarea no acaba dentro del plazo, la respuesta lo dice (`espera.agotada:
174
+ true`) y queda el `task_id` para seguir preguntando. El máximo son 600 s (10 minutos); si tu cliente
175
+ corta las llamadas largas antes, usa un valor por debajo de ese corte.
176
+
177
+ ## Cómo escribir un encargo
178
+ Usa esta plantilla (funcionó a la primera):
179
+
180
+ ```
181
+ Soy [tu nombre], [tu rol]. Lee [fichero o carpeta].
182
+ LÍMITES: escribe SOLO en [carpeta]. [Lo que NO toques]. No leas ficheros de credenciales. Sin git, sin publicar.
183
+ TAREA:
184
+ 1) [Primer paso]
185
+ 2) [Segundo paso]
186
+ 3) ...
187
+ Comprueba cada paso o marca SOSPECHA.
188
+ ENTREGA: [fichero con tope de líneas].
189
+ HAZLO sin pedir permiso.
190
+ ```
191
+
192
+ ## Cómo comprobar que llegó
193
+ - Con `esperar_segundos`, la respuesta de `run_task` ya trae `estado` y el `resultado` completo: si `estado` es `completed`, no hay nada más que preguntar.
194
+ - `get_task_status` pasa de `queued` a `running` a `completed`.
195
+ - `get_task_result` devuelve la respuesta con tokens y duración.
196
+ - Si falla, `get_task_result` devuelve el error.
197
+
198
+ ## Cómo vigilar sin capturas
199
+ No mires la pantalla. Usa `get_task_status` y `get_task_result` directamente. Una captura gasta cuota; una llamada MCP no.
200
+
201
+ ## Trampas que ya costaron (tropiezos 2, 4, 6, 7, 11, 12, 13, 16, 17, 18)
202
+
203
+ - **T2 · Espacio de trabajo:** el servidor arranca con `--home <casa>`; el espacio ya viene puesto.
204
+ - **T4 · Capturas gastan cuota:** usa `get_task_result`, no capturas de pantalla.
205
+ - **T6 · No aplica:** en MCP no hay caja de chat.
206
+ - **T7 · No aplica:** en MCP no hay caja de chat.
207
+ - **T11 · Credenciales:** nunca leas `.credentials.yaml`, `.env` ni bóvedas. Si falta una clave, el servidor se para y lo dice.
208
+ - **T12 · Las claves NO van por el entorno:** están en RATACODE › Ajustes › Models, y el MCP las lee de ahí. Si falta una, la llamada sale con `Falta la clave de <proveedor>. Pégala en RATACODE › Ajustes › Models.` No hay que pasarle ninguna variable al servidor, y la clave NO se escribe en la configuración de tu app.
209
+ - **T13 · Tutor equivocado:** si el tutor dice algo falso, corrígelo con la prueba (fichero:línea); no obedezcas una premisa falsa.
210
+ - **T16 · No aplica:** el MCP no usa `url.txt` (eso es el panel). Aquí no hay URL que caduque.
211
+ - **T17 · Cliente de una sola vuelta:** no termines el turno con la tarea en marcha. Usa `run_task` con `esperar_segundos`, o no pares hasta que `get_task_status` diga `completed`/`failed`. Por stdio la tarea vive lo que vive el cliente.
212
+ - **T18 · Un encargo grande, en sesión nueva:** cada tarea del MCP es una sesión nueva del motor (no arrastra historial), pero TU chat acumula: con muchos turnos y pasos, el proveedor acaba devolviendo un 400 (`read body failed`). Encargos en fichero y chat nuevo cuando toque.
213
+
214
+ ## El ciclo (diagnóstico → revisión → arreglo → cierre)
215
+
216
+ 1. **Diagnóstico** (tu sesión MCP): solo mirar, sin tocar ficheros. Lee lo que te den, pasa pruebas, busca fallos y comprueba cada uno. Lo no comprobado, SOSPECHA.
217
+ 2. **Revisión** (tú): comprueba los fallos que más pesen. Marca falsos positivos.
218
+ 3. **Arreglo** (tu sesión MCP): de uno en uno, cambio mínimo, misma prueba antes/después. Sin commit.
219
+ 4. **Cierre** (tú): lee el diff, repite la prueba, abre la app y lo ve. Commit solo si toca.
220
+
221
+ **No arranques servidores. No publiques. Si falta algo, dilo.**
@@ -0,0 +1,58 @@
1
+ # Navegador: cómo manejar RATACODE desde tu chat
2
+
3
+ RATACODE es una terminal de trabajo con IA con la cara de RATACODE, montada sobre un motor libre que no se toca. Se abre en el navegador (puerto 3777) y recibe encargos de cualquier agente. Los modelos baratos —o los tuyos, en local— hacen el trabajo; tú planificas, revisas y cierras.
4
+
5
+ ## Dónde está la URL
6
+ Lee `<casa>\url.txt` y ábrela en tu navegador. La casa es la carpeta desde la que se arrancó `ratacode` (o `%USERPROFILE%\.ratacode` por defecto).
7
+
8
+ `ratacode` borra ese fichero al empezar y sólo lo escribe cuando el puerto ya escucha de verdad, así
9
+ que la URL que leas es la de ESTA vez (no la de la vez anterior). Si no carga, o el fichero todavía no
10
+ está: espera 15 s, vuelve a leerlo y, si el puerto no escucha, arranca `ratacode` otra vez. El motivo
11
+ de cada cierre del motor queda en `<casa>\ratacode.log` (código de salida y señal).
12
+
13
+ ## Cómo escribir un encargo
14
+ Usa esta plantilla (funcionó a la primera):
15
+
16
+ ```
17
+ Soy [tu nombre], [tu rol]. Lee [fichero o carpeta].
18
+ LÍMITES: escribe SOLO en [carpeta]. [Lo que NO toques]. No leas ficheros de credenciales. Sin git, sin publicar.
19
+ TAREA:
20
+ 1) [Primer paso]
21
+ 2) [Segundo paso]
22
+ 3) ...
23
+ Comprueba cada paso o marca SOSPECHA.
24
+ ENTREGA: [fichero con tope de líneas].
25
+ HAZLO sin pedir permiso.
26
+ ```
27
+
28
+ **Antes de escribir**, comprueba que la caja del chat está visible y es la del título correcto (el usuario puede tener ajustes abiertos, la caja puede estar fuera de pantalla).
29
+ **Después de enviar**, comprueba que la sesión avanza (fichero de sesión o respuesta). Si no avanza, mira por qué antes de repetir.
30
+
31
+ ## Cómo comprobar que llegó
32
+ - El fichero de sesión crece (cada turno añade líneas).
33
+ - El encargo aparece en la lista de tareas de la web.
34
+ - Si el modelo contesta, la respuesta sale en el panel.
35
+
36
+ ## Cómo vigilar sin capturas
37
+ No mires la pantalla. Lee los ficheros de sesión directamente: estado, herramientas usadas, permisos pedidos, último mensaje. Una captura cada vez gasta cuota; un fichero no.
38
+
39
+ ## Trampas que ya costaron (tropiezos 2, 4, 6, 7, 11, 13, 16, 17, 18)
40
+
41
+ - **T2 · Espacio de trabajo:** el diálogo nativo de Windows no lo ves. Arranca `ratacode` desde la carpeta de trabajo; el espacio ya viene puesto.
42
+ - **T4 · Capturas gastan cuota:** pide la entrega en un fichero y vigila ese fichero, no la pantalla.
43
+ - **T6 · La caja no recibe:** el usuario puede tener los Ajustes abiertos o la caja fuera de pantalla. Antes de escribir, comprueba que la caja es la correcta y está visible.
44
+ - **T7 · Caja fuera de pantalla:** haz clic en la caja, escribe con el teclado y pulsa Enter. Si no llega, el div editable no cuenta como texto escrito; usa el teclado, no el formulario.
45
+ - **T11 · Credenciales:** nunca leas `.credentials.yaml`, `.env` ni bóvedas. Si falta una clave, para y dilo.
46
+ - **T13 · Tutor equivocado:** si el tutor dice algo falso, corrígelo con la prueba (fichero:línea); no obedezcas una premisa falsa.
47
+ - **T16 · La URL a medias y el cierre sin rastro:** `url.txt` se borra al empezar y se escribe SÓLO cuando el puerto contesta, así que si lo lees y no carga, espera 15 s y vuelve a leerlo; si el puerto no escucha, arranca `ratacode` otra vez. Si el panel se cierra solo, el motivo está en `<casa>\ratacode.log` (código de salida y señal): no lo cierres tú antes de leerlo.
48
+ - **T17 · El panel es el cliente:** aquí la tarea vive lo que viva el panel. Si cierras la ventana con una tarea en marcha, se queda sin quien la recoja. Pide la entrega en un fichero y no cierres hasta que esté.
49
+ - **T18 · Un encargo grande, en chat nuevo:** un chat con 6 turnos y 519 pasos acumulados acaba con un 400 del proveedor (`read body failed`). Los encargos van en ficheros y no necesitan historial: abre chat nuevo para cada encargo grande.
50
+
51
+ ## El ciclo (diagnóstico → revisión → arreglo → cierre)
52
+
53
+ 1. **Diagnóstico** (tu chat): solo mirar, sin tocar ficheros. Lee lo que te den, pasa pruebas, busca fallos y comprueba cada uno. Lo no comprobado, SOSPECHA.
54
+ 2. **Revisión** (tú): comprueba los fallos que más pesen. Marca falsos positivos.
55
+ 3. **Arreglo** (tu chat): de uno en uno, cambio mínimo, misma prueba antes/después. Sin commit.
56
+ 4. **Cierre** (tú): lee el diff, repite la prueba, abre la app y lo ve. Commit solo si toca.
57
+
58
+ **No arranques servidores. No publiques. Si falta algo, dilo.**
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * instalación de RATACODE · el paso que npm 11 deja «pendiente».
4
+ *
5
+ * ── QUÉ PASA (medido el 24-sep-2026, npm 11.16.0 / Node 24.18.0) ────────────
6
+ * npm 11 estrenó la política `allow-scripts` (RFC npm/rfcs#868). Al instalar,
7
+ * recorre el árbol y lista los paquetes con guiones de instalación que NO están
8
+ * cubiertos por `allowScripts`:
9
+ *
10
+ * npm warn allow-scripts 5 packages have install scripts not yet covered by allowScripts:
11
+ * npm warn allow-scripts @deepseek-ai/dsh-subprocess-local@0.1.5-rc.3 (postinstall: …)
12
+ * npm warn allow-scripts koffi@3.3.1 (install: node ./cnoke.cjs …)
13
+ * npm warn allow-scripts node-pty@1.2.0-beta.15 (install: …; postinstall: …)
14
+ *
15
+ * Con la configuración por defecto (`strict-allow-scripts=false`) esos guiones
16
+ * SÍ se ejecutan: el aviso es una lista de «pendientes de aprobar», no un
17
+ * salto. Medido en una instalación global recién hecha y sin `--foreground-scripts`:
18
+ * `node-pty` arranca un PTY y `koffi` llama a kernel32 (trabajo\S1\comprobar-nativos.mjs).
19
+ *
20
+ * ── POR QUÉ HACE FALTA ESTE FICHERO ─────────────────────────────────────────
21
+ * La capa `package.json#allowScripts` se ignora en las instalaciones globales
22
+ * (`npm.global === true` ⇒ npm no mira el package.json del «proyecto»; medido:
23
+ * un paquete con `allowScripts` en su tarball sigue saliendo en el aviso). En
24
+ * cambio, los guiones del PAQUETE RAÍZ sí corren siempre en `npm i -g`.
25
+ *
26
+ * Así que aquí, en el postinstall de RATACODE, se comprueba cada uno de los
27
+ * tres y se ejecuta SU guion de instalación si le falta el fruto. Es
28
+ * idempotente (si ya está, no hace nada), best-effort (nunca tumba la
29
+ * instalación) y no imprime ningún valor de credencial.
30
+ *
31
+ * ── LO QUE SE COMPRUEBA, PAQUETE A PAQUETE ──────────────────────────────────
32
+ * · node-pty → `build/Release/conpty/conpty.dll` (lo copia su
33
+ * postinstall desde `third_party/conpty`). Los
34
+ * `.node` viajan en `prebuilds/<plataforma>-<arch>/`.
35
+ * · koffi → `build/koffi/<toolchain>/koffi.node`, o el
36
+ * paquete de plataforma `@koromix/koffi-*` (que es
37
+ * lo normal: por eso su guion no construye nada).
38
+ * · dsh-subprocess-local → `chmod 0755` sobre el `spawn-helper` de node-pty
39
+ * (sólo POSIX: en Windows no existe y es un no-op).
40
+ */
41
+ import { spawnSync } from 'node:child_process';
42
+ import { createRequire } from 'node:module';
43
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
44
+ import { dirname, join } from 'node:path';
45
+
46
+ const require = createRequire(import.meta.url);
47
+
48
+ /**
49
+ * Desde dónde se busca cada paquete. `koffi` y `node-pty` cuelgan de la raíz,
50
+ * pero `@deepseek-ai/dsh-subprocess-local` cuelga DENTRO de `@deepseek-ai/dsh`,
51
+ * así que hay que resolver también desde ahí o no se encuentra.
52
+ */
53
+ const BASES = [require];
54
+ try {
55
+ BASES.push(createRequire(require.resolve('@deepseek-ai/dsh/package.json')));
56
+ } catch { /* sin motor instalado: las otras bases bastan */ }
57
+
58
+ /**
59
+ * La carpeta de un paquete del árbol, resolviendo su entrada y subiendo hasta
60
+ * el `package.json` que lleva su nombre (algunos no exportan `./package.json`,
61
+ * como `koffi`). Devuelve null si no está.
62
+ */
63
+ function carpetaDe(nombre) {
64
+ for (const base of BASES) {
65
+ let entrada = null;
66
+ try {
67
+ entrada = base.resolve(nombre);
68
+ } catch {
69
+ try { entrada = base.resolve(nombre + '/package.json'); } catch { continue; }
70
+ }
71
+ let dir = dirname(entrada);
72
+ for (let saltos = 0; saltos < 6; saltos += 1) {
73
+ const manifiesto = join(dir, 'package.json');
74
+ if (existsSync(manifiesto)) {
75
+ try {
76
+ if (JSON.parse(readFileSync(manifiesto, 'utf8')).name === nombre) return dir;
77
+ } catch { /* package.json ilegible: se sigue subiendo */ }
78
+ }
79
+ const padre = dirname(dir);
80
+ if (padre === dir) break;
81
+ dir = padre;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /** ¿Está ya el fruto del guion de instalación de este paquete? */
88
+ function yaEsta(nombre, carpeta) {
89
+ if (carpeta === null) return false;
90
+ if (nombre === 'node-pty') {
91
+ return existsSync(join(carpeta, 'build', 'Release', 'conpty', 'conpty.dll'));
92
+ }
93
+ if (nombre === 'koffi') {
94
+ if (existsSync(join(carpeta, 'build'))) {
95
+ for (const toolchain of readdirSync(join(carpeta, 'build'))) {
96
+ if (existsSync(join(carpeta, 'build', toolchain, 'koffi.node'))) return true;
97
+ }
98
+ }
99
+ // El paquete de plataforma es la vía normal y suficiente.
100
+ for (const plataforma of ['win32', 'linux', 'darwin', 'freebsd', 'openbsd', 'android']) {
101
+ if (carpetaDe('@koromix/koffi-' + plataforma + '-' + process.arch)) return true;
102
+ }
103
+ return false;
104
+ }
105
+ // dsh-subprocess-local sólo repara permisos de un fichero POSIX.
106
+ return process.platform === 'win32';
107
+ }
108
+
109
+ const PENDIENTES = [
110
+ { nombre: 'node-pty', guion: 'install' },
111
+ { nombre: 'koffi', guion: 'install' },
112
+ { nombre: '@deepseek-ai/dsh-subprocess-local', guion: 'postinstall' },
113
+ ];
114
+
115
+ const hechos = [];
116
+ const fallos = [];
117
+
118
+ for (const { nombre, guion } of PENDIENTES) {
119
+ const carpeta = carpetaDe(nombre);
120
+ if (carpeta === null) {
121
+ fallos.push(nombre + ': no está en el árbol (¿instalación incompleta?)');
122
+ continue;
123
+ }
124
+ if (yaEsta(nombre, carpeta)) {
125
+ hechos.push(nombre + ': ya estaba');
126
+ continue;
127
+ }
128
+ // `npm run <guion>` dentro de la carpeta del paquete: es exactamente lo que
129
+ // npm habría ejecutado, con su mismo entorno (npm_config_* incluidos).
130
+ const r = spawnSync('npm', ['run', guion], {
131
+ cwd: carpeta,
132
+ stdio: 'inherit',
133
+ shell: true,
134
+ windowsHide: true,
135
+ });
136
+ if (r.status === 0 && yaEsta(nombre, carpeta)) hechos.push(nombre + ': ejecutado ahora');
137
+ else if (r.status === 0) hechos.push(nombre + ': ejecutado (su guion no deja fruto en esta plataforma)');
138
+ else fallos.push(nombre + ': su guion «' + guion + '» salió con ' + r.status);
139
+ }
140
+
141
+ process.stdout.write('RATACODE · instalación: ' + hechos.join(' · ') + '\n');
142
+ if (fallos.length > 0) {
143
+ process.stdout.write('RATACODE · instalación: AVISO, no pude completar: ' + fallos.join(' · ') + '\n');
144
+ process.stdout.write('RATACODE · instalación: RATACODE arranca igual; si la terminal falla, '
145
+ + 'ejecuta a mano «npm run install» en la carpeta de cada paquete.\n');
146
+ }
147
+ // Nunca se tumba la instalación por esto: el panel web no necesita estos guiones.
148
+ process.exitCode = 0;