@danieltmn/tamnora 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/SECURITY.md +31 -0
- package/harness/packages/cli/bin/tamnora.mjs +11 -0
- package/harness/packages/cli/package.json +10 -0
- package/harness/packages/cli/src/cli.js +90 -0
- package/harness/packages/cli/src/commands/autostart.js +121 -0
- package/harness/packages/cli/src/commands/bridge.js +274 -0
- package/harness/packages/cli/src/commands/doctor.js +73 -0
- package/harness/packages/cli/src/commands/eval.js +183 -0
- package/harness/packages/cli/src/commands/init.js +31 -0
- package/harness/packages/cli/src/commands/mcp.js +101 -0
- package/harness/packages/cli/src/commands/repl.js +172 -0
- package/harness/packages/cli/src/commands/run.js +165 -0
- package/harness/packages/cli/src/commands/serve.js +1 -0
- package/harness/packages/cli/src/commands/server.js +345 -0
- package/harness/packages/cli/src/commands/tui.js +110 -0
- package/harness/packages/cli/src/commands/update.js +76 -0
- package/harness/packages/cli/src/commands/user.js +135 -0
- package/harness/packages/cli/src/lib/core.js +2 -0
- package/harness/packages/cli/src/lib/daemon.js +153 -0
- package/harness/packages/cli/src/lib/qr.js +203 -0
- package/harness/packages/cli/src/lib/runtime.js +110 -0
- package/harness/packages/cli/src/lib/tunnel.js +113 -0
- package/harness/packages/cli/src/lib/util.js +24 -0
- package/harness/packages/cli/src/tui/app.js +302 -0
- package/harness/packages/cli/src/tui/keys.js +78 -0
- package/harness/packages/cli/src/tui/layout.js +130 -0
- package/harness/packages/cli/src/tui/screen.js +13 -0
- package/harness/packages/cli/src/ui/color.js +15 -0
- package/harness/packages/cli/test/release.test.js +34 -0
- package/harness/packages/cli/test/tui.test.js +99 -0
- package/harness/packages/cli/test/tunnel.test.js +20 -0
- package/harness/packages/core/package.json +14 -0
- package/harness/packages/core/src/agent/context.js +66 -0
- package/harness/packages/core/src/agent/modes.js +26 -0
- package/harness/packages/core/src/agent/runAgent.js +257 -0
- package/harness/packages/core/src/config/app.js +49 -0
- package/harness/packages/core/src/config/home.js +98 -0
- package/harness/packages/core/src/config/identity.js +41 -0
- package/harness/packages/core/src/config/index.js +69 -0
- package/harness/packages/core/src/config/providers.js +24 -0
- package/harness/packages/core/src/env.js +19 -0
- package/harness/packages/core/src/eval/dataset.js +47 -0
- package/harness/packages/core/src/eval/judge.js +39 -0
- package/harness/packages/core/src/eval/report.js +48 -0
- package/harness/packages/core/src/eval/runTask.js +71 -0
- package/harness/packages/core/src/eval/runner.js +150 -0
- package/harness/packages/core/src/eval/scorers.js +217 -0
- package/harness/packages/core/src/eval/task.js +59 -0
- package/harness/packages/core/src/index.js +72 -0
- package/harness/packages/core/src/mcp/common.js +82 -0
- package/harness/packages/core/src/mcp/http.js +149 -0
- package/harness/packages/core/src/mcp/index.js +167 -0
- package/harness/packages/core/src/mcp/stdio.js +218 -0
- package/harness/packages/core/src/providers/index.js +119 -0
- package/harness/packages/core/src/providers/ollama.js +76 -0
- package/harness/packages/core/src/providers/openaiCompatible.js +57 -0
- package/harness/packages/core/src/providers/openrouter.js +57 -0
- package/harness/packages/core/src/providers/presets.js +19 -0
- package/harness/packages/core/src/providers/resilience.js +74 -0
- package/harness/packages/core/src/providers/stream.js +200 -0
- package/harness/packages/core/src/providers/utils.js +66 -0
- package/harness/packages/core/src/storage/sqlite.js +453 -0
- package/harness/packages/core/src/tools/diff.js +90 -0
- package/harness/packages/core/src/tools/executors.js +277 -0
- package/harness/packages/core/src/tools/glob.js +67 -0
- package/harness/packages/core/src/tools/index.js +28 -0
- package/harness/packages/core/src/tools/paths.js +29 -0
- package/harness/packages/core/src/tools/permissions.js +69 -0
- package/harness/packages/core/src/tools/registry.js +44 -0
- package/harness/packages/core/src/tools/schema.js +177 -0
- package/harness/packages/core/test/agent.test.js +237 -0
- package/harness/packages/core/test/config.test.js +33 -0
- package/harness/packages/core/test/context.test.js +44 -0
- package/harness/packages/core/test/diff.test.js +20 -0
- package/harness/packages/core/test/eval.test.js +144 -0
- package/harness/packages/core/test/git-tools.test.js +44 -0
- package/harness/packages/core/test/home.test.js +62 -0
- package/harness/packages/core/test/mcp.test.js +158 -0
- package/harness/packages/core/test/providers.test.js +80 -0
- package/harness/packages/core/test/resilience.test.js +61 -0
- package/harness/packages/core/test/storage.test.js +55 -0
- package/harness/packages/core/test/stream.test.js +91 -0
- package/harness/packages/core/test/tools.test.js +96 -0
- package/harness/packages/server/bin/tamnora-server.mjs +11 -0
- package/harness/packages/server/package.json +10 -0
- package/harness/packages/server/public/app.js +2188 -0
- package/harness/packages/server/public/img/fondo-agentes.webp +0 -0
- package/harness/packages/server/public/index.html +368 -0
- package/harness/packages/server/public/landing/index.html +523 -0
- package/harness/packages/server/public/landing/landing.js +89 -0
- package/harness/packages/server/public/markdown.js +174 -0
- package/harness/packages/server/public/styles.css +923 -0
- package/harness/packages/server/src/auth.js +146 -0
- package/harness/packages/server/src/cli.js +61 -0
- package/harness/packages/server/src/index.js +1 -0
- package/harness/packages/server/src/lib/approvals.js +51 -0
- package/harness/packages/server/src/lib/core.js +2 -0
- package/harness/packages/server/src/lib/http.js +74 -0
- package/harness/packages/server/src/lib/modelCache.js +32 -0
- package/harness/packages/server/src/lib/prompt.js +48 -0
- package/harness/packages/server/src/lib/workspace.js +74 -0
- package/harness/packages/server/src/routes/auth.js +56 -0
- package/harness/packages/server/src/routes/chat.js +222 -0
- package/harness/packages/server/src/routes/eval.js +100 -0
- package/harness/packages/server/src/routes/files.js +117 -0
- package/harness/packages/server/src/routes/folders.js +80 -0
- package/harness/packages/server/src/routes/mcp.js +50 -0
- package/harness/packages/server/src/routes/models.js +26 -0
- package/harness/packages/server/src/routes/providers.js +135 -0
- package/harness/packages/server/src/routes/sessions.js +157 -0
- package/harness/packages/server/src/server.js +249 -0
- package/harness/packages/server/src/static.js +67 -0
- package/harness/packages/server/test/markdown.test.js +36 -0
- package/harness/packages/server/test/modelCache.test.js +72 -0
- package/harness/packages/server/test/server.test.js +809 -0
- package/harness/scripts/example-dataset.jsonl +4 -0
- package/harness/scripts/mcp-echo-server.mjs +108 -0
- package/harness/scripts/mock-provider.mjs +134 -0
- package/harness/tamnora.example.json +18 -0
- package/package.json +70 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todos los cambios relevantes de este proyecto se documentan en este archivo.
|
|
4
|
+
|
|
5
|
+
El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/)
|
|
6
|
+
y el proyecto usa [Versionado Semantico](https://semver.org/lang/es/).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-10-01
|
|
9
|
+
|
|
10
|
+
### Agregado
|
|
11
|
+
|
|
12
|
+
- Casa global de Tamnora (`TAMNORA_HOME` / datos del sistema) con `app.json`,
|
|
13
|
+
base SQLite global, logs y runtime.
|
|
14
|
+
- `tamnora server --init|--start|--reload|--stop|--status|--logs`: daemon en
|
|
15
|
+
segundo plano que sobrevive al cierre de la consola.
|
|
16
|
+
- Login real (cookie firmada + CSRF + lockout) y `tamnora user` (scrypt).
|
|
17
|
+
- `tamnora bridge --start|--reload|--stop|--status`: tunel publico (tunnelmole)
|
|
18
|
+
con QR para abrir la URL desde el celular.
|
|
19
|
+
- `tamnora doctor`, `tamnora update` y `tamnora autostart install|remove`.
|
|
20
|
+
- `scripts/release.mjs`: release con guard de credenciales, changelog, tag, push
|
|
21
|
+
y `npm publish` (con `--dry-run`).
|
|
22
|
+
- Reintentos con backoff ante errores transitorios (429/5xx/red) y cancelacion
|
|
23
|
+
real entre rondas/tools en el loop del agente (evento `retry`).
|
|
24
|
+
- Gestion de contexto: recorte de tool-results viejos y descarte de turnos
|
|
25
|
+
antiguos cuando la sesion se acerca al limite del modelo (evento `trim`).
|
|
26
|
+
- Consola responsive: en movil la barra lateral es un drawer con backdrop, el
|
|
27
|
+
topbar envuelve y la conversacion usa todo el ancho.
|
|
28
|
+
- Tools de Git (`git_status`, `git_diff`, `git_log`, `git_commit`) y diff
|
|
29
|
+
coloreado en la consola para los cambios de `write`/`edit`.
|
|
30
|
+
- Acciones por mensaje en la consola: copiar, editar, regenerar y borrar desde
|
|
31
|
+
ese punto (`POST /api/sessions/:id/truncate`).
|
|
32
|
+
- Ajustes del agente (modo, proveedor, modelo y auto) en un modal; el topbar
|
|
33
|
+
queda con solo los botones de panel y tema.
|
|
34
|
+
- Busqueda global (sesiones y mensajes, `GET /api/search`) y command palette
|
|
35
|
+
(`Ctrl/Cmd+K`) con comandos rapidos.
|
|
36
|
+
- MCP completo: recursos y prompts (tools sinteticas `list_/read_resource`,
|
|
37
|
+
`list_/get_prompt`), reconexion de stdio y refresco por `tools/list_changed`.
|
|
38
|
+
- TUI: edicion con cursor (flechas/home/end/delete), scroll (pgup/pgdn),
|
|
39
|
+
bracketed paste, contador de contexto, `--mode plan` y restauracion de la
|
|
40
|
+
terminal ante salida/senal.
|
|
41
|
+
- CI multi-plataforma y analisis de la consola web del harness.
|
|
42
|
+
|
|
43
|
+
### Cambiado
|
|
44
|
+
|
|
45
|
+
- El chat SPA se separo a su propio repositorio (`tamnora-chat`).
|
|
46
|
+
- La raiz del repo pasa a ser el paquete publicable `@danieltmn/tamnora`.
|
|
47
|
+
- Puerto default `4517`; la base SQLite es global (migra la legacy por proyecto).
|
|
48
|
+
- La landing pasa de login cosmetico a autenticacion real.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tamnora
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Tamnora Harness
|
|
2
|
+
|
|
3
|
+
Harness local para IAs: un agente **CLI-first** (estilo opencode) en Node puro,
|
|
4
|
+
con TUI, consola web, tools locales, MCP y evaluacion de modelos.
|
|
5
|
+
|
|
6
|
+
- **Sin dependencias**: solo modulos de Node (`node:sqlite`, `node:crypto`, ...).
|
|
7
|
+
- **Local**: corre en tu maquina y guarda el estado en tu casa local.
|
|
8
|
+
- **Multi-proveedor**: OpenRouter, OpenAI-compatible (DeepSeek, Groq, Mistral,
|
|
9
|
+
xAI, Together, Fireworks, Cerebras, Perplexity) y Ollama.
|
|
10
|
+
|
|
11
|
+
## Instalacion
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @danieltmn/tamnora --help
|
|
15
|
+
# o global
|
|
16
|
+
npm i -g @danieltmn/tamnora
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requiere **Node.js >= 22.5** (por `node:sqlite`).
|
|
20
|
+
|
|
21
|
+
## Uso rapido
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
tamnora init # crea la configuracion (proveedor, modelo)
|
|
25
|
+
tamnora # consola TUI full-screen (REPL si no hay TTY)
|
|
26
|
+
tamnora run "hola" # tarea headless
|
|
27
|
+
tamnora eval <dataset.jsonl> # evaluacion de modelos
|
|
28
|
+
tamnora mcp [--json] # servidores MCP: tools, recursos y prompts
|
|
29
|
+
tamnora serve # consola web
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Consola TUI
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tamnora # TUI full-screen (REPL si no hay TTY)
|
|
36
|
+
tamnora tui --mode plan # modo plan: solo lectura, sin escritura ni bash
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Atajos: `enter` enviar · `↑/↓` historial · `←/→` cursor · `home/end` inicio/fin ·
|
|
40
|
+
`pgup/pgdn` scroll · `ctrl+l` limpiar · `ctrl+c` cancelar/salir · `/salir`,
|
|
41
|
+
`/limpiar`. El pegado de bloques multilinea entra como un solo texto y el header
|
|
42
|
+
muestra el contexto usado (`ctx <tokens>`).
|
|
43
|
+
|
|
44
|
+
### Server como servicio
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
tamnora server --init # crea la casa y arranca (segundo plano)
|
|
48
|
+
tamnora server --start # arranca (default)
|
|
49
|
+
tamnora server --reload # reinicia aplicando config/codigo
|
|
50
|
+
tamnora server --stop
|
|
51
|
+
tamnora server --status
|
|
52
|
+
tamnora server --logs --follow
|
|
53
|
+
tamnora doctor # verifica Node, casa, puerto y base
|
|
54
|
+
tamnora update # busca/instala una version nueva
|
|
55
|
+
tamnora autostart install # arranque automatico del server
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`tamnora server` corre en segundo plano y **sobrevive al cierre de la consola**
|
|
59
|
+
(puerto default `4517`). La configuracion de la app vive en la **casa** de
|
|
60
|
+
Tamnora (override con `--home` o `TAMNORA_HOME`):
|
|
61
|
+
|
|
62
|
+
- Windows: `%APPDATA%\tamnora`
|
|
63
|
+
- macOS: `~/Library/Application Support/tamnora`
|
|
64
|
+
- Linux: `~/.config/tamnora`
|
|
65
|
+
|
|
66
|
+
### Login
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
tamnora user --name <n> --password <p> # crea/cambia el usuario local
|
|
70
|
+
tamnora user # muestra el usuario
|
|
71
|
+
tamnora user remove # el server deja de pedir login
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Con un usuario configurado, **todas** las rutas `/api/*` piden sesion (cookie
|
|
75
|
+
firmada + CSRF) y la landing pasa a ser el login real; sin usuario, el server
|
|
76
|
+
corre en modo local sin login. Al cambiar el usuario se detiene el server
|
|
77
|
+
(volvelo a arrancar con `tamnora server --start`).
|
|
78
|
+
|
|
79
|
+
### Acceso remoto
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
tamnora bridge --start # abre un tunel publico (tunnelmole) + QR
|
|
83
|
+
tamnora bridge --status # URL publica
|
|
84
|
+
tamnora bridge --reload # reinicia el tunel
|
|
85
|
+
tamnora bridge --stop
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`tamnora bridge` abre un tunel (tunnelmole) hacia tu server local y muestra la
|
|
89
|
+
URL publica **y un QR** para abrirlo desde el celular. Trabaja protegido por el
|
|
90
|
+
login del server: configura un usuario antes de exponerlo (ver
|
|
91
|
+
[`SECURITY.md`](SECURITY.md)).
|
|
92
|
+
|
|
93
|
+
## MCP
|
|
94
|
+
|
|
95
|
+
Los servidores MCP se configuran en `tamnora.json` (`mcp[]`) y soportan HTTP y
|
|
96
|
+
stdio. Las tools se exponen como `mcp__<servidor>__<tool>` y, si el servidor los
|
|
97
|
+
anuncia, tambien sus **recursos** y **prompts** (`list_resources`,
|
|
98
|
+
`read_resource`, `list_prompts`, `get_prompt`).
|
|
99
|
+
|
|
100
|
+
## Documentacion
|
|
101
|
+
|
|
102
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — historial de versiones.
|
|
103
|
+
- [`AGENTS.md`](AGENTS.md) — arquitectura del harness, tools, MCP, evaluacion y
|
|
104
|
+
convenciones.
|
|
105
|
+
- [`harness/tamnora.example.json`](harness/tamnora.example.json) — plantilla de
|
|
106
|
+
configuracion.
|
|
107
|
+
- [`SECURITY.md`](SECURITY.md) — modelo de amenaza y reporte de vulnerabilidades.
|
|
108
|
+
|
|
109
|
+
> El chat SPA (Tamnora Chat) vive en su propio repositorio: `tamnora-chat`.
|
|
110
|
+
|
|
111
|
+
## Versiones
|
|
112
|
+
|
|
113
|
+
El proyecto sigue [Versionado Semantico](https://semver.org/lang/es/). Cada
|
|
114
|
+
version se documenta en [`CHANGELOG.md`](CHANGELOG.md) y se publica como un tag
|
|
115
|
+
`vX.Y.Z` en git mas `npm publish`.
|
|
116
|
+
|
|
117
|
+
## Desarrollo
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pnpm install
|
|
121
|
+
pnpm tamnora # TUI / REPL
|
|
122
|
+
pnpm serve # consola web
|
|
123
|
+
pnpm mock # proveedor local de prueba (OpenAI-compatible + Ollama)
|
|
124
|
+
pnpm test # tests (node:test)
|
|
125
|
+
pnpm lint # ESLint
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Licencia
|
|
129
|
+
|
|
130
|
+
MIT.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Seguridad
|
|
2
|
+
|
|
3
|
+
## Reportar una vulnerabilidad
|
|
4
|
+
|
|
5
|
+
No abras un issue publico para vulnerabilidades. Escribi a **security@tamnora.com**
|
|
6
|
+
con: descripcion, pasos para reproducir, impacto y version afectada. Respondemos
|
|
7
|
+
dentro de 72 horas.
|
|
8
|
+
|
|
9
|
+
## Modelo de amenaza
|
|
10
|
+
|
|
11
|
+
Tamnora es un harness **local**: corre en tu maquina y, por defecto, escucha en
|
|
12
|
+
`127.0.0.1`. Ejecuta comandos y escribe archivos dentro del workspace que elijas,
|
|
13
|
+
asi que se lo debe tratar como codigo con acceso a tu usuario.
|
|
14
|
+
|
|
15
|
+
- **Local (loopback)**: quien ya tiene tu sesion de usuario ya puede correr
|
|
16
|
+
comandos; el server no agrega una barrera adicional mas alla del login.
|
|
17
|
+
- **Acceso remoto por tunel (tunnelmole)**: la URL publica es accesible desde
|
|
18
|
+
Internet. Es obligatorio (a) una passphrase fuerte, (b) login real en toda la
|
|
19
|
+
API, (c) limite de intentos y (d) restringir el picker de carpetas a roots
|
|
20
|
+
permitidos. Nunca expongas un server sin passphrase.
|
|
21
|
+
- **Credenciales**: las API keys de proveedores se guardan en la casa local del
|
|
22
|
+
usuario (fuera del repo) y se cifran con una clave derivada de la passphrase.
|
|
23
|
+
Nunca se commitean y se devuelven enmascaradas al cliente.
|
|
24
|
+
|
|
25
|
+
## Buenas practicas
|
|
26
|
+
|
|
27
|
+
- Mantene Node actualizado (`node:sqlite` requiere Node >= 22.5).
|
|
28
|
+
- No commitees `.env`, `tamnora.json`, bases `.db` ni `.npmrc`; estan en
|
|
29
|
+
`.gitignore`.
|
|
30
|
+
- Rota cualquier credencial que se haya compartido en texto plano.
|
|
31
|
+
- Cierra el tunel (`tamnora bridge --stop`) cuando no lo uses.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from '../src/cli.js';
|
|
3
|
+
|
|
4
|
+
runCli(process.argv.slice(2))
|
|
5
|
+
.then((code) => {
|
|
6
|
+
process.exitCode = typeof code === 'number' ? code : 0;
|
|
7
|
+
})
|
|
8
|
+
.catch((err) => {
|
|
9
|
+
console.error(err?.stack ?? String(err));
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runCommand } from './commands/run.js';
|
|
5
|
+
import { replCommand } from './commands/repl.js';
|
|
6
|
+
import { evalCommand } from './commands/eval.js';
|
|
7
|
+
import { initCommand } from './commands/init.js';
|
|
8
|
+
import { mcpCommand } from './commands/mcp.js';
|
|
9
|
+
import { serveCommand } from './commands/serve.js';
|
|
10
|
+
import { serverCommand } from './commands/server.js';
|
|
11
|
+
import { userCommand } from './commands/user.js';
|
|
12
|
+
import { bridgeCommand } from './commands/bridge.js';
|
|
13
|
+
import { updateCommand } from './commands/update.js';
|
|
14
|
+
import { autostartCommand } from './commands/autostart.js';
|
|
15
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
16
|
+
import { tuiCommand } from './commands/tui.js';
|
|
17
|
+
import { color } from './ui/color.js';
|
|
18
|
+
|
|
19
|
+
const COMMANDS = {
|
|
20
|
+
run: runCommand,
|
|
21
|
+
repl: replCommand,
|
|
22
|
+
tui: tuiCommand,
|
|
23
|
+
eval: evalCommand,
|
|
24
|
+
init: initCommand,
|
|
25
|
+
mcp: mcpCommand,
|
|
26
|
+
serve: serveCommand,
|
|
27
|
+
server: serverCommand,
|
|
28
|
+
user: userCommand,
|
|
29
|
+
bridge: bridgeCommand,
|
|
30
|
+
update: updateCommand,
|
|
31
|
+
autostart: autostartCommand,
|
|
32
|
+
doctor: doctorCommand,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function readVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../../package.json');
|
|
38
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
39
|
+
return pkg.version || '0.0.0';
|
|
40
|
+
} catch {
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function runCli(argv) {
|
|
46
|
+
const [command, ...rest] = argv;
|
|
47
|
+
|
|
48
|
+
if (command === '-h' || command === '--help' || command === 'help') {
|
|
49
|
+
printHelp();
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
if (command === '-v' || command === '--version') {
|
|
53
|
+
console.log(`tamnora ${readVersion()}`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handler = command && COMMANDS[command];
|
|
58
|
+
if (handler) return handler(rest);
|
|
59
|
+
|
|
60
|
+
if (command && !command.startsWith('-')) {
|
|
61
|
+
console.error(color.red(`Comando desconocido: ${command}`));
|
|
62
|
+
printHelp();
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// No subcommand → full-screen console on a TTY, else the line REPL
|
|
67
|
+
if (process.stdin.isTTY && process.stdout.isTTY) return tuiCommand(argv);
|
|
68
|
+
return replCommand(argv);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function printHelp() {
|
|
72
|
+
console.log(`tamnora — harness local para IAs
|
|
73
|
+
|
|
74
|
+
Uso:
|
|
75
|
+
tamnora Consola full-screen (REPL si no hay TTY)
|
|
76
|
+
tamnora run "<prompt>" Ejecutar una tarea headless
|
|
77
|
+
tamnora eval <dataset.jsonl> Correr un dataset de evaluación
|
|
78
|
+
tamnora mcp Listar servidores y herramientas MCP
|
|
79
|
+
tamnora server [--init|--start|--reload|--stop|--status|--logs] Server en segundo plano
|
|
80
|
+
tamnora user Usuario local (login del server)
|
|
81
|
+
tamnora bridge [--start|--reload|--stop|--status] Tunel publico (tunnelmole) + QR
|
|
82
|
+
tamnora autostart install|remove Arranque automatico del server
|
|
83
|
+
tamnora serve Consola web local (primer plano)
|
|
84
|
+
tamnora doctor Verificar Node, casa, puerto y base
|
|
85
|
+
tamnora update Buscar/instalar una version nueva
|
|
86
|
+
tamnora init Crear tamnora.json
|
|
87
|
+
|
|
88
|
+
También: tamnora repl · tamnora tui
|
|
89
|
+
Corré "tamnora <comando> --help" para ver las opciones de cada comando.`);
|
|
90
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { homeDir } from '../../../core/src/index.js';
|
|
6
|
+
import { parse } from '../lib/util.js';
|
|
7
|
+
|
|
8
|
+
const OPTIONS = { home: { type: 'string' }, help: { type: 'boolean', short: 'h' } };
|
|
9
|
+
|
|
10
|
+
function autostartPath() {
|
|
11
|
+
if (process.platform === 'win32') {
|
|
12
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
13
|
+
return path.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', 'Tamnora.lnk');
|
|
14
|
+
}
|
|
15
|
+
if (process.platform === 'darwin') {
|
|
16
|
+
return path.join(os.homedir(), 'Library', 'LaunchAgents', 'net.tamnora.server.plist');
|
|
17
|
+
}
|
|
18
|
+
return path.join(os.homedir(), '.config', 'systemd', 'user', 'tamnora.service');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function psQuote(s) {
|
|
22
|
+
return `'${String(s).replace(/'/g, "''")}'`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function vbsStr(s) {
|
|
26
|
+
return `"${String(s).replace(/"/g, '""')}"`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function autostartCommand(argv) {
|
|
30
|
+
const { positionals, values } = parse(argv, OPTIONS);
|
|
31
|
+
if (values.help) {
|
|
32
|
+
console.log('Uso: tamnora autostart install|remove\n\nInstala/quita el arranque automatico del server.');
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
const action = String(positionals[0] || '').toLowerCase();
|
|
36
|
+
if (action !== 'install' && action !== 'remove') {
|
|
37
|
+
console.error('Uso: tamnora autostart install|remove');
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bin = process.argv[1];
|
|
42
|
+
const node = process.execPath;
|
|
43
|
+
const target = autostartPath();
|
|
44
|
+
const homeArg = process.env.TAMNORA_HOME ? ` --home "${homeDir()}"` : '';
|
|
45
|
+
const workdir = os.homedir();
|
|
46
|
+
|
|
47
|
+
if (action === 'remove') {
|
|
48
|
+
let removed = false;
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(target);
|
|
51
|
+
removed = true;
|
|
52
|
+
} catch {
|
|
53
|
+
/* no estaba */
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
unlinkSync(path.join(homeDir(), 'autostart.vbs'));
|
|
57
|
+
} catch {
|
|
58
|
+
/* nada */
|
|
59
|
+
}
|
|
60
|
+
console.log(removed ? `autostart quitado: ${target}` : 'no habia autostart instalado.');
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
65
|
+
|
|
66
|
+
if (process.platform === 'win32') {
|
|
67
|
+
// Lanzador oculto: wscript.exe corre un .vbs que abre node sin consola.
|
|
68
|
+
const wscript = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'wscript.exe');
|
|
69
|
+
const vbsPath = path.join(homeDir(), 'autostart.vbs');
|
|
70
|
+
const vbsCmd = `"${node}" "${bin}" server --start${homeArg}`;
|
|
71
|
+
const vbs = ['Set sh = CreateObject("WScript.Shell")', `sh.Run ${vbsStr(vbsCmd)}, 0, False`, ''].join('\r\n');
|
|
72
|
+
writeFileSync(vbsPath, vbs);
|
|
73
|
+
const ps = [
|
|
74
|
+
'$ws = New-Object -ComObject WScript.Shell;',
|
|
75
|
+
`$sc = $ws.CreateShortcut(${psQuote(target)});`,
|
|
76
|
+
`$sc.TargetPath = ${psQuote(wscript)};`,
|
|
77
|
+
`$sc.Arguments = ${psQuote(`"${vbsPath}"`)};`,
|
|
78
|
+
`$sc.WorkingDirectory = ${psQuote(workdir)};`,
|
|
79
|
+
'$sc.WindowStyle = 1;',
|
|
80
|
+
'$sc.Save();',
|
|
81
|
+
].join(' ');
|
|
82
|
+
const r = spawnSync('powershell', ['-NoProfile', '-Command', ps], { encoding: 'utf8', windowsHide: true });
|
|
83
|
+
if (r.status !== 0) {
|
|
84
|
+
console.error(`No se pudo crear el acceso directo: ${(r.stderr || '').trim()}`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
console.log(`autostart instalado: ${target}`);
|
|
88
|
+
console.log(` (arranca \`tamnora server --start\` en ${workdir})`);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (process.platform === 'darwin') {
|
|
93
|
+
const plist =
|
|
94
|
+
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
95
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' +
|
|
96
|
+
'<plist version="1.0"><dict>' +
|
|
97
|
+
'<key>Label</key><string>net.tamnora.server</string>' +
|
|
98
|
+
'<key>ProgramArguments</key><array>' +
|
|
99
|
+
`<string>${node}</string><string>${bin}</string>` +
|
|
100
|
+
'<string>server</string><string>--stream</string>' +
|
|
101
|
+
(process.env.TAMNORA_HOME ? `<string>--home</string><string>${homeDir()}</string>` : '') +
|
|
102
|
+
'</array>' +
|
|
103
|
+
`<key>WorkingDirectory</key><string>${workdir}</string>` +
|
|
104
|
+
'<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>' +
|
|
105
|
+
'</dict></plist>\n';
|
|
106
|
+
writeFileSync(target, plist);
|
|
107
|
+
} else {
|
|
108
|
+
const unit =
|
|
109
|
+
'[Unit]\nDescription=Tamnora Harness\nAfter=network-online.target\n\n' +
|
|
110
|
+
'[Service]\nType=simple\n' +
|
|
111
|
+
`ExecStart=${node} ${bin} server --stream${homeArg}\n` +
|
|
112
|
+
`WorkingDirectory=${workdir}\nRestart=on-failure\n\n` +
|
|
113
|
+
'[Install]\nWantedBy=default.target\n';
|
|
114
|
+
writeFileSync(target, unit);
|
|
115
|
+
spawnSync('systemctl', ['--user', 'daemon-reload'], { windowsHide: true });
|
|
116
|
+
spawnSync('systemctl', ['--user', 'enable', '--now', 'tamnora.service'], { windowsHide: true });
|
|
117
|
+
}
|
|
118
|
+
console.log(`autostart instalado: ${target}`);
|
|
119
|
+
console.log(` (arranca \`tamnora server\` en ${workdir})`);
|
|
120
|
+
return 0;
|
|
121
|
+
}
|