@7ots/cli 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.
Files changed (97) hide show
  1. package/.env.example +140 -0
  2. package/LICENSE +21 -0
  3. package/README.md +655 -0
  4. package/brand/apple-touch-icon.png +0 -0
  5. package/brand/favicon.svg +4 -0
  6. package/brand/icon-512.png +0 -0
  7. package/brand/mark-mono.svg +3 -0
  8. package/brand/mark.svg +4 -0
  9. package/brand/og-es.png +0 -0
  10. package/brand/og-pt.png +0 -0
  11. package/brand/og.png +0 -0
  12. package/brand/ots.json +11 -0
  13. package/brand/tokens.css +59 -0
  14. package/brand/wordmark.svg +5 -0
  15. package/cli/7ots.mjs +400 -0
  16. package/cli/lib/brain.mjs +143 -0
  17. package/cli/lib/config.mjs +70 -0
  18. package/cli/lib/hooks.mjs +151 -0
  19. package/cli/lib/i18n.mjs +480 -0
  20. package/cli/lib/identity.mjs +51 -0
  21. package/cli/lib/install.mjs +79 -0
  22. package/cli/lib/lines.mjs +73 -0
  23. package/cli/lib/meet.mjs +166 -0
  24. package/cli/lib/open.mjs +19 -0
  25. package/cli/lib/orquesta.mjs +84 -0
  26. package/cli/lib/paths.mjs +49 -0
  27. package/cli/lib/pet-core.mjs +184 -0
  28. package/cli/lib/pet-server.mjs +296 -0
  29. package/cli/lib/prompts.mjs +75 -0
  30. package/cli/lib/terminal.mjs +145 -0
  31. package/cli/lib/ui.mjs +234 -0
  32. package/cli/lib/wizard.mjs +279 -0
  33. package/cli/pet/electron/main.cjs +86 -0
  34. package/cli/pet/pet.html +404 -0
  35. package/dist/7ots.esm.js +627 -0
  36. package/dist/7ots.esm.js.map +7 -0
  37. package/dist/7ots.iife.js +627 -0
  38. package/dist/7ots.iife.js.map +7 -0
  39. package/llms.txt +57 -0
  40. package/package.json +73 -0
  41. package/server/admin.mjs +213 -0
  42. package/server/apuchat-relay.mjs +170 -0
  43. package/server/channels/agent.mjs +136 -0
  44. package/server/channels/apuchat.mjs +364 -0
  45. package/server/channels/apumail.mjs +211 -0
  46. package/server/channels/index.mjs +69 -0
  47. package/server/contact.mjs +151 -0
  48. package/server/demo.mjs +154 -0
  49. package/server/i18n.mjs +54 -0
  50. package/server/identity.mjs +108 -0
  51. package/server/llm.mjs +413 -0
  52. package/server/platform/auth.mjs +290 -0
  53. package/server/platform/crypto.mjs +80 -0
  54. package/server/platform/db.mjs +130 -0
  55. package/server/platform/routes.mjs +388 -0
  56. package/server/platform/runtime.mjs +180 -0
  57. package/server/platform/store.mjs +331 -0
  58. package/server/sdk.mjs +125 -0
  59. package/server/server.mjs +456 -0
  60. package/server/settings.mjs +177 -0
  61. package/server/tts.mjs +186 -0
  62. package/skills/7ots/SKILL.md +59 -0
  63. package/src/actions/ActionRegistry.js +254 -0
  64. package/src/actions/PageTools.js +270 -0
  65. package/src/actions/builtins.js +565 -0
  66. package/src/auth/AuthManager.js +128 -0
  67. package/src/avatar/AvatarStage.js +473 -0
  68. package/src/character/AvatarEditor.js +829 -0
  69. package/src/character/Character.js +1191 -0
  70. package/src/character/motion.js +794 -0
  71. package/src/character/parts.js +559 -0
  72. package/src/context/ContextManager.js +472 -0
  73. package/src/core/AgentBrain.js +341 -0
  74. package/src/core/AgentWidget.js +1059 -0
  75. package/src/core/EventBus.js +61 -0
  76. package/src/core/Proactivity.js +363 -0
  77. package/src/core/storage.js +35 -0
  78. package/src/i18n/index.js +193 -0
  79. package/src/i18n/messages/character.js +543 -0
  80. package/src/i18n/messages/identity.js +141 -0
  81. package/src/i18n/messages/platform.js +114 -0
  82. package/src/i18n/messages/server.js +630 -0
  83. package/src/i18n/messages/widget.js +362 -0
  84. package/src/identity/ContactCard.js +351 -0
  85. package/src/identity/Face.js +148 -0
  86. package/src/identity/random.js +112 -0
  87. package/src/identity/schema.js +262 -0
  88. package/src/index.js +117 -0
  89. package/src/integrations/apuchat.js +212 -0
  90. package/src/integrations/apumail.js +88 -0
  91. package/src/llm/ProxyLLM.js +87 -0
  92. package/src/mcp/McpClient.js +153 -0
  93. package/src/ui/Companion.js +498 -0
  94. package/src/ui/UIManager.js +425 -0
  95. package/src/ui/VirtualPointer.js +746 -0
  96. package/src/ui/markdown.js +94 -0
  97. package/src/voice/VoiceEngine.js +227 -0
package/README.md ADDED
@@ -0,0 +1,655 @@
1
+ <p align="center"><img src="brand/wordmark.svg" alt="7ots" width="220"></p>
2
+
3
+ <p align="center"><b>Tu web, con alguien dentro.</b><br>
4
+ Un agente web open source que ve la página, habla y actúa por tu visitante. Se instala con una etiqueta <code>&lt;script&gt;</code>.<br>
5
+ <a href="https://7ots.com">7ots.com</a> · <a href="docs/ARCHITECTURE.md">Arquitectura</a> · <a href="docs/BRAND.md">Marca</a></p>
6
+
7
+ 7ots mete en tu web un agente con avatar 3D que *entiende la página*, *actúa sobre ella*
8
+ (navega, pulsa, rellena formularios, resalta, abre modales), usa las APIs y el **MCP
9
+ autenticado** de tu sitio con la sesión del usuario, y cuando hace falta deriva a una persona
10
+ por **apuchat.com** o abre un ticket por **apumail.com**.
11
+
12
+ ```
13
+ ┌──────────── tu sitio ────────────┐ ┌──── server/ (tu proxy) ────┐
14
+ │ <ots-agent> (Shadow DOM) │ /chat │ Claude u OpenAI │
15
+ │ avatar 3D · chat · voz │ ───────► │ TTS (OpenAI / ElevenLabs) │
16
+ │ ContextManager ← DOM, rutas │ │ apumail · apuchat │
17
+ │ ActionRegistry → click, forms… │ │ claves en .env │
18
+ │ McpClient ──► /mcp (JWT) │ └────────────────────────────┘
19
+ └───────────────────────────────────┘
20
+ ```
21
+
22
+ - **Cero dependencias en el navegador.** Web Component nativo con Shadow DOM: no rompe tu CSS ni el tuyo lo rompe.
23
+ - **Las claves nunca llegan al navegador.** Todo pasa por un proxy Node sin estado (`server/`).
24
+ - **Proactivo, sin ser pesado.** Saluda, detecta errores de formulario, dudas largas en una sección… con límites de frecuencia y un botón de silencio.
25
+ - **Seguro por diseño.** Solo ejecuta herramientas registradas, valida argumentos contra JSON Schema, pide confirmación real antes de acciones con efectos, no lee campos de contraseña ni bloques `data-ots-private`, y trata el contenido de la página como datos, no como órdenes.
26
+
27
+ > Estado: `0.1.0` — prototipo funcional. APIs sujetas a cambios.
28
+
29
+ ---
30
+
31
+ ## Inicio rápido
32
+
33
+ ```bash
34
+ npm install
35
+ cp .env.example .env # LLM_PROVIDER=mock funciona sin ninguna clave
36
+ npm run dev # → http://localhost:8787/examples/
37
+ ```
38
+
39
+ La demo (**Nimbus**, un SaaS ficticio) incluye login con JWT, un MCP autenticado, acciones
40
+ propias, plantillas y reglas proactivas. En modo `mock` prueba a escribir:
41
+
42
+ | Escribe | Qué pasa |
43
+ |---|---|
44
+ | `precios` | resalta la sección de precios |
45
+ | `saldo` (tras iniciar sesión) | acción propia autenticada con el JWT |
46
+ | `/tool nimbus__listar_pedidos {}` | herramienta del MCP autenticado |
47
+ | `/tool navigate {"url":"./docs.html"}` | navega y retoma la conversación en la otra página |
48
+ | `/tool iniciar_prueba {"plan":"Pro"}` | diálogo de confirmación antes de ejecutar |
49
+
50
+ Para respuestas reales, en `.env`:
51
+
52
+ ```bash
53
+ LLM_PROVIDER=anthropic # usa ANTHROPIC_API_KEY · modelo por defecto claude-opus-5-5
54
+ # o
55
+ LLM_PROVIDER=openai # usa OPENAI_API_KEY · LLM_BASE_URL para compatibles (Groq, Ollama…)
56
+ ```
57
+
58
+ Otros scripts: `npm run build` (genera `dist/7ots.esm.js` y `dist/7ots.iife.js`), `npm run check` (sintaxis).
59
+
60
+ ---
61
+
62
+ ## Integración en tu sitio
63
+
64
+ ```html
65
+ <!-- Solo si quieres avatar 3D: TalkingHead importa "three" por nombre -->
66
+ <script type="importmap">
67
+ { "imports": {
68
+ "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
69
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
70
+ } }
71
+ </script>
72
+
73
+ <script src="/7ots/7ots.iife.js"></script>
74
+ <script>
75
+ const agent = SevenOts.init({
76
+ endpoint: 'https://api.tusitio.com/api/agent', // tu proxy (server/)
77
+ siteKey: 'pk_tusitio', // identificador público
78
+ agent: { name: 'Ana', role: 'asesora', siteName: 'Acme', instructions: 'Recomienda el plan anual.' },
79
+ avatar: { url: '/avatars/ana.glb', body: 'F' },
80
+ auth: { getToken: () => localStorage.getItem('jwt') },
81
+ mcp: [{ url: '/mcp', name: 'acme', requiresAuth: true }],
82
+ });
83
+ </script>
84
+ ```
85
+
86
+ O como módulo: `import { init } from '@7ots/cli'`. O declarativo:
87
+ `<ots-agent endpoint="/api/agent" site-key="pk_x" name="Ana"></ots-agent>`.
88
+
89
+ ### Configuración
90
+
91
+ | Clave | Descripción |
92
+ |---|---|
93
+ | `endpoint` | URL del proxy. Obligatoria. |
94
+ | `siteKey` | Identificador público que el proxy puede exigir (`SITE_KEYS`). |
95
+ | `identity` | `true`, URL o ficha — quién es el agente: nombre, aspecto, voz, contacto (ver «Identidad del agente»). |
96
+ | `agent` | `{ name, role, siteName, language, instructions, expressive }` — personalidad e instrucciones del negocio. |
97
+ | `avatar` | `{ url, body: 'F'\|'M', mood, cameraView }` o `false`. Sin WebGL cae a un avatar 2D. |
98
+ | `voice` | `{ tts: 'proxy'\|'browser'\|false, stt: true, lang }` o `false`. |
99
+ | `auth` | `{ token }`, `{ getToken, getUser }` o `{ credentials: 'include' }` (cookies), `exposeClaims`. |
100
+ | `mcp` | `[{ url, name, requiresAuth, filter, confirm }]` — servidores MCP (Streamable HTTP). |
101
+ | `actions` | Acciones propias (ver abajo). |
102
+ | `templates` | `{ nombre: (data, { escape }) => html }` — HTML de confianza para `show_modal` / `open_sidebar`. |
103
+ | `navigation` | `{ allowedOrigins, router }` — `router` para SPAs (`url => router.push(url)`). |
104
+ | `proactive` | `{ level: 'quiet'\|'normal'\|'bold', greetDelayMs, dwellMs, idleMs, cooldownMs, maxPerSession, rules, … }` o `false` (ver «Proactividad»). |
105
+ | `context` | `{ privateSelectors, ignoreSelectors, extra: () => ({...}) }`. |
106
+ | `contact` | `{ apuchat: true, apumail: true \| { categories } }` o `false`. |
107
+ | `builtins` | `{ exclude: ['click', …], confirmClicks: 'submit'\|'all'\|'none' }`. |
108
+ | `pointer` | `{ speed, visible }` o `false` — ratón y teclado virtuales (ver abajo). |
109
+ | `pageTools` | `true` (defecto) — convierte formularios/botones con `data-ots-tool` en herramientas. |
110
+ | `mode` | `'panel'` (defecto) o `'companion'` — el avatar sale del chat y se pasea por la página. |
111
+ | `companion` | `{ size: 150, idleHomeMs: 30000, follow: true, wanderMs, watchCursor }` (solo en modo compañero). |
112
+ | `theme` | `{ primary, radius, position: 'right'\|'left', font }`. |
113
+
114
+ La referencia completa está comentada en [`src/index.js`](src/index.js).
115
+
116
+ ### API en tiempo de ejecución
117
+
118
+ ```js
119
+ agent.registerAction({...}); // añade una herramienta
120
+ agent.registerMcp({ url, name }); // conecta otro MCP
121
+ agent.setAuthToken(jwt); // al hacer login/logout (null)
122
+ agent.ask('¿qué plan me conviene?'); // como si lo escribiera el usuario
123
+ agent.notify('El carrito tiene 3 productos'); // evento: el agente decide si habla
124
+ agent.say('¡Bienvenido de nuevo!'); // mensaje directo, sin LLM
125
+ agent.setProactivity('bold'); // 'quiet' | 'normal' | 'bold'
126
+ agent.setIdentity({ name: 'Brisa', look: { color: '#e11d48' } }); // identidad en caliente
127
+ agent.open(); agent.close(); agent.reset(); agent.destroy();
128
+ agent.on('action:end', ({ name, result }) => analytics.track(name));
129
+ ```
130
+
131
+ Eventos: `ready`, `user:message`, `agent:message`, `agent:thinking`, `agent:error`,
132
+ `action:start`, `action:end`, `handoff:start`, `handoff:message`, `handoff:end`,
133
+ `context:route`, `context:section`, `context:alert`, `proactive:trigger`, `proactive:level`, `identity:change`, `auth:change`,
134
+ `voice:start`, `voice:end`, `widget:open`, `widget:close`.
135
+
136
+ ---
137
+
138
+ ## Identidad del agente: cara, voz y contacto
139
+
140
+ Muchos agentes necesitan lo mismo: un nombre, un aspecto reconocible, una voz propia y formas de
141
+ localizarlo. 7ots lo resuelve con **una sola ficha declarativa**. La usan el widget, la voz del
142
+ proxy, los canales (correo, chat, meet) y una tarjeta pública. Puedes usar este módulo sin el chat
143
+ y con tu propio cerebro.
144
+
145
+ ```js
146
+ {
147
+ id: 'nube', name: 'Nube', role: 'asesora de Nimbus', tagline: 'Te ayudo con tu almacenamiento',
148
+ bio: '…', language: 'es', languages: ['es', 'en'],
149
+ personality: { tone: 'cercano y claro', traits: ['paciente'], instructions: '…' }, // instructions: privado
150
+ look: { color: '#4f46e5', accent: '#22d3ee', emoji: '☁️', image: 'https://…/retrato.png',
151
+ avatar: { url: '/avatars/nube.glb', body: 'F', mood: 'happy', cameraView: 'upper' },
152
+ face: { skin: '#…', eyes: '#…' }, meetAvatar: 'vivi', meetScene: '' },
153
+ voice: { provider: 'auto'|'openai'|'elevenlabs'|'browser', voiceId: 'coral', model: '',
154
+ style: 'cálida y clara', lang: 'es-ES', rate: 1.05, pitch: 1, browserVoice: '' },
155
+ contact: { site: 'https://nimbus.example', email: 'nube@nimbus.example', apuchat: 'nube',
156
+ meet: true, call: true, hours: 'L-V 9-18' },
157
+ }
158
+ ```
159
+
160
+ `defineIdentity()` normaliza la ficha y nunca lanza excepciones. Descarta los colores que no son
161
+ hex, las URLs que no son http(s) y los valores fuera de rango. **La ficha no contiene secretos.**
162
+ Las claves de TTS, apumail y apuchat siguen en el entorno del proxy.
163
+
164
+ **De dónde sale.** Cada fuente pisa a la anterior: valores por defecto ← entorno (`AGENT_NAME`,
165
+ `AGENT_ROLE`, `APUCHAT_AGENT_AVATAR`, `APUMAIL_AGENT_INBOX`…) ← `data/identity.json`. La variable
166
+ `IDENTITY_FILE` cambia la ruta del fichero, y el backoffice lo escribe. Hay un ejemplo en
167
+ [`examples/identity.example.json`](examples/identity.example.json).
168
+
169
+ **Tarjeta pública.** Rutas del proxy (todas con CORS `*`):
170
+
171
+ | Ruta | Qué es |
172
+ |---|---|
173
+ | `GET /api/agent/identity` · `GET /.well-known/7ots-agent.json` | Tarjeta JSON (`@type: 7ots/agent`). Lleva solo campos de una lista blanca y nunca las instrucciones. `contact.answers` dice qué canales atiende el agente ahora mismo. |
174
+ | `GET /api/agent/identity.vcf` | vCard 4.0, para «guardar contacto». |
175
+ | `POST /api/agent/identity/call` | Abre una videollamada de meet.apuchat.com con el agente ya dentro y devuelve `{ call_url }`. Necesita apuchat; tiene límite por IP y pasa por `SITE_KEYS`. |
176
+
177
+ **En el widget.** La opción `identity` acepta tres formas: `true` (pide `{endpoint}/identity`), una
178
+ URL o el objeto de la ficha. La identidad fija nombre, colores, avatar y voz, pero lo que pongas
179
+ explícitamente en `agent`, `avatar`, `voice` o `theme` manda sobre ella. Para cambiarla en caliente
180
+ usa `agent.setIdentity(ficha)`, que emite el evento `identity:change`.
181
+
182
+ **Solo la cara y la voz (`createFace`).** Sirve si ya tienes tu chat o tu agente:
183
+
184
+ ```js
185
+ import { createFace } from '@7ots/cli';
186
+ const face = await createFace(document.querySelector('#cara'), { identity: '/api/agent/identity', endpoint: '/api/agent' });
187
+ await face.say('Hola, soy Nube'); // voz de la identidad con lip-sync
188
+ face.mood('happy'); face.gesture('thumbup');
189
+ face.morph('wings'); face.effect('hearts'); // solo con el personaje 2D
190
+ const texto = await face.listen(); // micrófono → texto
191
+ face.setIdentity(otraFicha); // en caliente
192
+ ```
193
+
194
+ **Tarjeta de contacto (`<ots-identity>`).** Muestra retrato, nombre y rol, y botones para chatear
195
+ (si hay widget), escribir un correo, copiar el @handle de apuchat, iniciar una videollamada,
196
+ escucharle y guardar la vCard:
197
+
198
+ ```html
199
+ <ots-identity src="/api/agent/identity" face greeting="Hola, soy Nube."></ots-identity>
200
+ ```
201
+
202
+ Emite los eventos `ots-identity:load`, `ots-identity:call` y `ots-identity:error`. También puedes
203
+ asignarle `el.identity = ficha` para no pedir nada a la red.
204
+
205
+ **En tu servidor, con tu propio cerebro (`7ots/server`):**
206
+
207
+ ```js
208
+ import { createAgentIdentity } from '7ots/server';
209
+
210
+ const nube = createAgentIdentity({
211
+ identity: { name: 'Nube', voice: { provider: 'openai', voiceId: 'coral' }, contact: { email: 'nube@nimbus.example' } },
212
+ brain: async ({ text, channel, history, identity }) => miAgente.responder(text), // sin brain: el LLM de 7ots
213
+ apumail: { inbox: 'nube@nimbus.example', token: process.env.NUBE_MAIL_TOKEN }, // contesta su correo
214
+ apuchat: { identityKey: process.env.NUBE_APUCHAT_KEY }, // mensajes y llamadas
215
+ });
216
+ await nube.start();
217
+ http.createServer(async (req, res) => (await nube.handler(req, res)) || miApp(req, res));
218
+
219
+ nube.card(); nube.vcard(); await nube.speak('Hola'); // tarjeta, vCard, audio con su voz
220
+ await nube.respond('¿Qué planes hay?', { channel: 'dm' });
221
+ await nube.call('@ana', 'Tu copia de seguridad ha terminado'); // hace sonar la app de apuchat
222
+ ```
223
+
224
+ Para probarlo, abre la demo [`/examples/identidad.html`](examples/identidad.html). Al editar la ficha
225
+ cambian a la vez la cara, la tarjeta, el widget y la tarjeta pública.
226
+
227
+ ## Backoffice: configúralo todo sin tocar código
228
+
229
+ El proxy sirve un panel en **`/backoffice/`** (por ejemplo `http://localhost:8787/backoffice/`):
230
+
231
+ | Sección | Qué configura |
232
+ |---|---|
233
+ | Resumen | Estado del modelo, la voz y los canales; botón «Probar el modelo». |
234
+ | Identidad | Nombre, papel, lema, biografía, idiomas, tono, rasgos e instrucciones privadas. |
235
+ | Aspecto | **Editor de avatar** (ver abajo), colores, emoji, retrato, ánimo, encuadre 3D y avatar de videollamada. |
236
+ | Voz | Proveedor, voz, modelo, acento, velocidad, tono y estilo, con botón «Escuchar». |
237
+ | Contacto | Web, correo, @apuchat, horario, videollamadas y el menú «hablar con una persona». |
238
+ | Comportamiento | Proactividad (nivel, saludo, límites, señales), modo compañero, puntero, acciones y micrófono. |
239
+ | Widget y marca | Lado, redondeo, tipografía, color, y JSON avanzado de `init()`. |
240
+ | Privacidad y navegación | `privateSelectors`, `ignoreSelectors` y `navigation.allowedOrigins`. |
241
+ | Servidor y claves | Todas las variables del proxy: LLM, TTS, apumail/apuchat, límites, orígenes… |
242
+ | Instalar | El snippet de una línea (`embed.js`) y los de código, tarjeta y editor. |
243
+
244
+ - **Vista previa en vivo**: los cambios sin guardar se ven al momento en un widget de prueba.
245
+ - **Guardar** (o Ctrl+S) escribe `data/identity.json` y `data/config.json`. El snippet
246
+ `<script src="…/api/agent/embed.js">` carga esa configuración, así que tu web no se toca.
247
+ - **Claves**: se escriben en `data/secrets.json` (permisos 0600) y nunca vuelven al navegador; solo
248
+ se ve si están puestas. Lo guardado manda sobre `.env`, y casi todo se aplica sin reiniciar
249
+ (el panel avisa de lo que necesita reinicio: `PORT`, `TRUST_PROXY`, `DEMO`…).
250
+ - **Acceso**: sin `ADMIN_PASSWORD` solo abre desde la propia máquina (localhost). Con ella se entra
251
+ desde fuera con una cookie firmada (HttpOnly, SameSite=Strict). Las escrituras exigen el mismo
252
+ origen y la cabecera `X-7ots-Admin`. Rutas: `/api/agent/admin/*`.
253
+ - Lo que no son datos (acciones con `handler`, `auth`, plantillas) sigue en tu página, en
254
+ `window.SevenOtsConfig` o en `init()`.
255
+
256
+ ## Personaje 2D y editor de avatar
257
+
258
+ El avatar por defecto es un **personaje 2D vectorial al estilo Pou**, animado (parpadea, mira el
259
+ cursor, mueve la boca con la voz, tiene ánimos y gestos). Se describe con un objeto JSON y se edita
260
+ visualmente:
261
+
262
+ - **Cuerpo**: 16 formas de partida; luego se arrastran sus 16 puntos (con simetría opcional).
263
+ - **Cara**: 33 ojos, 28 bocas, 9 cejas, 8 mejillas; posición y tamaño arrastrando.
264
+ - **Color y acabado**: color libre, 7 acabados (mate, brillo, neón…), 10 estampados, contorno.
265
+ - **Accesorios**: ~70 (gorros, coronas, lentes, pipas, barbas, capas, alas…), cada uno movible,
266
+ escalable (rueda), rotable (Mayús+rueda), con colores propios. Hasta 16 a la vez.
267
+ - **Estilos completos**: 26 presets (pirata, rey, reina, mago, chef, vaquero, astronauta, robot,
268
+ vikingo, ninja, fantasma…) que se pueden aplicar conservando tu forma y color.
269
+ - **Realista**: la misma pestaña permite elegir un avatar 3D `.glb` (TalkingHead) o un retrato.
270
+ - Deshacer/rehacer, exportar SVG/PNG y copiar JSON.
271
+
272
+ En la identidad se guarda en `look.kind` (`character` | `realistic` | `image`) y `look.character`.
273
+ Todo el arte es propio (MIT).
274
+
275
+ ```js
276
+ import { createAvatarEditor, createCharacter } from '@7ots/cli';
277
+
278
+ // El editor, en tu propia app (también como <ots-avatar-editor>):
279
+ const ed = createAvatarEditor(el, { value: identity.look, onChange: (v) => guardar(v) });
280
+
281
+ // Solo el personaje, animado:
282
+ const ch = createCharacter(el, { preset: 'pirate' });
283
+ ch.mood('happy'); ch.gesture('jump'); ch.mouth(0.6); ch.lookAtPoint(x, y);
284
+
285
+ // Gestos (CHARACTER_GESTURES): jump, bounce, wiggle, nod, shake, spin, wave, think, celebrate,
286
+ // surprise, shrug, sneeze, hiccup, laugh, dance, yawn, shiver, clap, thumbsup, thumbsdown, point,
287
+ // bow, fly, stomp, dizzy. Alias: handup, hi, index, ok, yes, no, thumbup, side, namaste, hop, cheer.
288
+ ch.gesture('celebrate'); ch.gesture('point', { mirror: true, ms: 2000 });
289
+
290
+ // Transformaciones temporales del cuerpo (CHARACTER_MORPHS) y efectos (CHARACTER_EFFECTS):
291
+ ch.morph('wings', { ms: 3000 }); // spikes, wings, hands, horns, puff, squish, stretch, melt, jelly
292
+ ch.morph('hands', { pose: 'wave', hold: true }); ch.unmorph('hands');
293
+ ch.morph('shape:heart'); // cambia de forma un momento y vuelve
294
+ ch.effect('hearts'); // sparkles, sweat, blush, hearts, zzz, exclaim, question, anger…
295
+
296
+ // Lip-sync:
297
+ const s = ch.speak('Hola, ¿qué tal?', { durationMs: 1800 }); // visemas del texto; s.stop(), s.sync(charIndex)
298
+ const stop = ch.lipsync(analyserNode, { text }); // audio real (AnalyserNode) + visemas
299
+ ch.viseme('O'); // X A E I O U M F S C
300
+ ```
301
+
302
+ Demo: [`/examples/avatar.html`](examples/avatar.html) (editor más galería de estilos) y
303
+ [`/examples/character-lab.html`](examples/character-lab.html) (gestos, transformaciones, efectos y lip-sync).
304
+
305
+ ## Idiomas (i18n)
306
+
307
+ Todo el texto que ve una persona sale de catálogos de traducción: el widget, el editor de avatar,
308
+ la tarjeta de contacto, el backoffice y los mensajes del proxy. Vienen **español, inglés y
309
+ portugués** completos.
310
+
311
+ - **Widget**: `init({ locale: 'en' })`. Sin `locale` se usa `<html lang>` y luego el idioma del
312
+ navegador; un idioma que no existe cae a inglés. En caliente: `agent.setLocale('pt')`.
313
+ El agente responde en el idioma en que le escribe el visitante.
314
+ - **Componentes**: `createAvatarEditor(el, { locale })`, `<ots-avatar-editor lang="en">`,
315
+ `<ots-identity lang="pt">`, `createFace(el, { locale })`.
316
+ - **Proxy**: responde en el idioma de la cabecera `Accept-Language` (el widget la envía). Los avisos
317
+ al equipo (correo, apuchat) usan `LOCALE` o, si no está, el idioma de la identidad.
318
+ - **Backoffice**: selector de idioma en la barra lateral.
319
+
320
+ **Añadir un idioma o cambiar textos:**
321
+
322
+ ```js
323
+ import { addMessages } from '@7ots/cli'; // o SevenOts.i18n.addMessages con <script>
324
+ addMessages('fr', { widget: { /* … */ } }); // claves que falten → inglés
325
+ addMessages('es', { widget: { /* … */ } }); // sobrescribe solo esas claves
326
+ ```
327
+
328
+ Los catálogos de serie están en `src/i18n/messages/` y son buena plantilla para traducir. Formato:
329
+ `{nombre}` para variables y `{ one: '# mensaje', other: '# mensajes' }` para plurales
330
+ (`Intl.PluralRules`).
331
+
332
+ ## Acciones propias
333
+
334
+ Cada acción es una herramienta que el LLM puede llamar. El handler corre **en el navegador**,
335
+ con la sesión del usuario:
336
+
337
+ ```js
338
+ agent.registerAction({
339
+ name: 'agregar_al_carrito', // [a-zA-Z0-9_-], único
340
+ description: 'Añade un producto al carrito del usuario.',
341
+ parameters: {
342
+ type: 'object',
343
+ properties: { sku: { type: 'string' }, cantidad: { type: 'integer', minimum: 1 } },
344
+ required: ['sku'],
345
+ },
346
+ requiresAuth: true, // solo se ofrece con sesión
347
+ confirm: ({ sku }) => `¿Añadir **${sku}** al carrito?`, // diálogo real antes de ejecutar
348
+ handler: async ({ sku, cantidad = 1 }, ctx) => {
349
+ const r = await ctx.auth.fetch('/api/cart', { // adjunta el JWT; el LLM nunca lo ve
350
+ method: 'POST', body: JSON.stringify({ sku, cantidad }),
351
+ });
352
+ if (!r.ok) return { ok: false, error: 'No se pudo añadir' };
353
+ ctx.ui.toast({ message: 'Añadido ✓', type: 'success' });
354
+ return r.json(); // vuelve al LLM como resultado
355
+ },
356
+ });
357
+ ```
358
+
359
+ `ctx` = `{ auth, ui, context, bus, agent, signal, pointer }`. Los errores lanzados vuelven al LLM como
360
+ resultado de error; nunca rompen la conversación.
361
+
362
+ **Acciones incluidas:** `get_page_context`, `find_on_page`, `read_element`, `navigate`,
363
+ `scroll_to`, `click`, `hover`, `type_text`, `press_key`, `drag_and_drop`, `scroll`, `fill_form`,
364
+ `highlight`, `show_toast`, `show_modal`, `open_sidebar`, `close_panels`, más
365
+ `send_email_ticket` (apumail) y `escalate_to_human` (apuchat).
366
+
367
+ El agente prefiere siempre las **herramientas internas** (tus acciones, tu MCP, tus
368
+ `data-ots-tool`): son exactas. El ratón y el teclado quedan para lo que no tiene herramienta o
369
+ cuando el usuario pide *«enséñame cómo se hace»*.
370
+
371
+ ## Ratón y teclado
372
+
373
+ Con `pointer` activo (por defecto) el agente mueve un **cursor visible con su nombre** y opera la
374
+ página como una persona: `click` (doble, derecho), `hover`, `type_text` (tecla a tecla, compatible
375
+ con React/Vue), `press_key` (Enter, Tab, flechas, Escape, `Control+a`…), `drag_and_drop` (HTML5 y
376
+ ratón) y `scroll`.
377
+
378
+ - **Esc** (del usuario) detiene al instante lo que esté haciendo y corta el turno.
379
+ - Comprueba qué hay realmente bajo el punto: si otro elemento tapa el objetivo, falla y lo dice.
380
+ - Con `prefers-reduced-motion` o la pestaña oculta, actúa sin animación.
381
+ - Nunca escribe en contraseñas, campos de tarjeta, códigos OTP ni `data-ots-private`.
382
+ - Enter o click que **envía un formulario** pide confirmación. El sitio decide con
383
+ `data-ots-confirm` en el botón o en el `<form>`: `"false"` = sin preguntar, o el texto de la pregunta.
384
+ - Funciona también dentro de `<dialog>` modales del sitio (cursor y confirmaciones van en la capa superior).
385
+
386
+ Límite: son eventos sintéticos (`isTrusted: false`). Cubren formularios, menús, pestañas,
387
+ listas y drag & drop normales, pero el navegador no deja simular acciones protegidas: abrir el
388
+ selector de archivos, pantalla completa, copiar al portapapeles o pasar un CAPTCHA.
389
+
390
+ ## Herramientas declaradas en el HTML (`data-ots-tool`)
391
+
392
+ Un formulario o botón que ya existe se convierte en herramienta con un atributo, sin JS:
393
+
394
+ ```html
395
+ <form data-ots-tool="invitar_miembro"
396
+ data-ots-description="Invita a una persona al equipo"
397
+ data-ots-confirm="¿Invitar a {email} como {rol}?"
398
+ data-ots-auth>
399
+ <input name="email" type="email" required>
400
+ <select name="rol"><option>lector</option><option>editor</option></select>
401
+ <button>Invitar</button>
402
+ </form>
403
+ <button data-ots-tool="exportar_csv">Exportar</button>
404
+ ```
405
+
406
+ Los campos con `name` son los parámetros (select/radio → enum, checkbox → boolean,
407
+ number/range → número; `required` se respeta). El agente rellena, valida y envía. Tu código
408
+ puede devolverle el resultado con
409
+ `form.dispatchEvent(new CustomEvent('ots-result', { detail: {...} }))`; si no, recibe el texto
410
+ de `[role=status]`/`[role=alert]`. La lista se actualiza sola al cambiar la página.
411
+
412
+ Demo completa: [`examples/dashboard.html`](examples/dashboard.html) — panel con ajustes,
413
+ carpetas, drag & drop e invitaciones, manejable por herramientas internas o con el ratón.
414
+
415
+ ## Modo compañero (`mode: 'companion'`)
416
+
417
+ Un modo más lúdico: en vez de quedarse en el chat, el avatar flota sobre la página, camina
418
+ hasta los elementos, los **señala** (gesto + flecha + resaltado) y habla en un bocadillo.
419
+
420
+ ```js
421
+ init({ mode: 'companion', companion: { size: 150 }, avatar: { cameraView: 'head', /* … */ } });
422
+ ```
423
+
424
+ - Pulsarlo abre o cierra el chat; arrastrarlo lo deja donde quieras.
425
+ - El modelo tiene tres acciones más: `point_at {target, message}`, `tour {steps:[{target, message}]}`
426
+ (visita guiada, máx. 8 pasos) y `go_home`.
427
+ - Con `follow: true` se acerca a lo que toca el ratón virtual (click, type_text, drag…), así se
428
+ ve quién está actuando.
429
+ - Con el chat cerrado, las respuestas salen en su bocadillo (y en voz alta si la voz está activa).
430
+ - Vuelve a su rincón tras `idleHomeMs` sin actividad. Respeta `prefers-reduced-motion`.
431
+ - Con `wanderMs` se da paseos solo: se acerca con curiosidad a un título, imagen o botón visible y
432
+ vuelve. Con `watchCursor` sigue tu ratón con la mirada. Ambos los fija el nivel de proactividad.
433
+
434
+ Pruébalo en las demos con `?modo=companion` (`examples/?modo=companion`,
435
+ `examples/dashboard.html?modo=companion`) o con el enlace «Modo compañero» del menú.
436
+
437
+ ## Proactividad: cuánto se atreve el agente
438
+
439
+ El agente no espera a que le escriban: observa la página y, cuando tiene sentido, **se mueve,
440
+ señala, resalta y despliega tarjetas de información** junto a lo que estás mirando. El nivel de
441
+ iniciativa lo fija el sitio (`proactive.level`) y el visitante puede cambiarlo en el menú
442
+ «⋯ → Iniciativa» (Discreta · Normal · Atrevida); su elección se recuerda en la sesión.
443
+
444
+ | Señal | `quiet` | `normal` (defecto) | `bold` |
445
+ |---|---|---|---|
446
+ | Saludo inicial | sí | sí | sí, con tarjeta de bienvenida |
447
+ | Cambio de ruta (`onRoute`) | — | sí | sí |
448
+ | Entrar en una sección (`sectionEnterMs`) | — | — | a los 1,5 s |
449
+ | Permanencia en una sección (`dwellMs`) | — | 25 s | 12 s |
450
+ | Inactividad (`idleMs`) | 120 s, solo formularios | 45 s, solo formularios | 20 s, en cualquier parte |
451
+ | Duda sobre un botón o enlace (`hesitationMs`) | — | 4,5 s | 2,5 s |
452
+ | Clics de frustración (`rageClicks`) | sí | sí | sí |
453
+ | Texto seleccionado (`selection`) | — | — | sí |
454
+ | Intención de salir (`exitIntent`) | — | sí | sí |
455
+ | Enfriamiento / máx. por sesión | 120 s / 3 | 40 s / 8 | 10 s / 40 |
456
+ | Paseo del compañero (`wanderMs`) / mira el cursor | — / no | 45 s / sí | 18 s / sí |
457
+
458
+ Cualquier valor del nivel se puede sobrescribir en `proactive` (p. ej. `{ level: 'bold', exitIntent: false }`).
459
+ Cada evento le dice al modelo el nivel de iniciativa: en `bold` prefiere **mostrar** (`point_at`,
460
+ `show_card`, `tour`) a preguntar «¿te ayudo?». Siempre puede contestar `NOOP` y no pasa nada.
461
+
462
+ **Tarjetas (`show_card`).** Acción integrada: `{ target?, title, content, facts: [{label, value}],
463
+ questions, tone, seconds }`. Se ancla al lado del elemento (o del compañero), lo trae a la vista si
464
+ hace falta, y las preguntas son chips que el visitante pulsa para seguir la conversación.
465
+
466
+ Pruébalo: `examples/?modo=companion`, menú «⋯ → Iniciativa → Atrevida», y baja hasta «Precios».
467
+
468
+ ## MCP autenticado
469
+
470
+ Si tu sitio expone un servidor MCP (transporte Streamable HTTP), sus herramientas aparecen solas
471
+ con prefijo: `mcp: [{ url: '/mcp', name: 'acme' }]` → `acme__listar_pedidos`, `acme__cambiar_plan`…
472
+
473
+ - El JWT del usuario viaja como `Authorization: Bearer` (o cookies con `credentials: 'include'`).
474
+ - Las herramientas sin `readOnlyHint: true` piden confirmación al usuario antes de ejecutarse.
475
+ - Al cambiar la sesión (`setAuthToken`) se reconecta y se vuelve a listar.
476
+
477
+ ## Autenticación
478
+
479
+ El widget se engancha a la sesión que **ya tiene** tu sitio; no gestiona logins.
480
+
481
+ - El token se usa en `ctx.auth.fetch()` y en el MCP. **Nunca se envía al LLM.**
482
+ - Al LLM solo llegan los claims de `exposeClaims` (p. ej. nombre y plan) o lo que devuelva `getUser()`.
483
+ - Llama a `agent.setAuthToken(token)` al hacer login y `setAuthToken(null)` al salir.
484
+
485
+ ## Contacto: apuchat y apumail
486
+
487
+ **apuchat.com — hablar con una persona.** Cuando el usuario lo pide (o el agente no puede
488
+ resolver), `escalate_to_human` crea un canal efímero en el hub de apuchat y avisa al operador por DM
489
+ con un resumen y el enlace. El chat del widget pasa a ser un chat en vivo con el operador, con
490
+ opción de videollamada (el visitante entra por `call_url_public` y "llama a la puerta").
491
+
492
+ El proxy hace de **relé** entre el widget y el canal (`/contact/apuchat/{send,wait,end}`): el hub no
493
+ acepta CORS de otros sitios y el canal exige una `identity_key` que no debe salir del servidor. El
494
+ navegador solo recibe un id y un token opacos; el enlace de operador (con claves y PIN) **nunca** le llega.
495
+
496
+ ```bash
497
+ APUCHAT_HUB=https://apuchat.com
498
+ APUCHAT_NOTIFIER_IDENTITY_KEY=... # identidad que envía el aviso por DM
499
+ APUCHAT_OPERATOR_HANDLE=soporte # quién lo recibe (sin @; con @ el DM se pierde en silencio)
500
+ ```
501
+
502
+ > - El aviso por DM se limita a 4096 caracteres (se recorta el resumen, nunca el enlace).
503
+ > - El hub permite 15 videollamadas nuevas por minuto **por IP**: todos tus visitantes comparten
504
+ > la IP del proxy.
505
+ > - El estado del relé está en memoria: con varias instancias del proxy usa sesiones *sticky* (o
506
+ > cambia el `Map` por Redis). Reiniciar el proxy cierra las conversaciones abiertas.
507
+
508
+ **apumail.com — correo / ticket.** `send_email_ticket` redacta un ticket con asunto, categoría y
509
+ (opcionalmente) la transcripción, lo enseña al usuario para confirmar y lo envía por el proxy.
510
+
511
+ El correo sale de **un buzón tuyo de apumail** hacia el buzón del equipo; responder a ese correo
512
+ llega directamente al visitante (`reply_to`). El destinatario es fijo en el servidor: el LLM no lo elige.
513
+
514
+ ```bash
515
+ APUMAIL_INBOX=soporte@apumail.com # buzón remitente (permanente o de dominio propio)
516
+ APUMAIL_INBOX_TOKEN=... # token del buzón o PAT acct_… de la cuenta dueña
517
+ APUMAIL_TO=equipo@tuempresa.com # quién recibe los tickets
518
+ ```
519
+
520
+ > Los buzones gratis de apumail caducan a las 24 h sin actividad y envían como mucho 5 destinatarios
521
+ > al día; para producción usa un buzón permanente o de dominio propio (100 envíos/h).
522
+
523
+ ## Hablar con el agente por apumail, apuchat y meet
524
+
525
+ Además del widget, el mismo agente puede atender **fuera de la web**. Cada canal se activa solo si
526
+ están sus variables; sin ellas el proxy arranca igual. El agente usa el mismo LLM y
527
+ `SERVER_INSTRUCTIONS`, recuerda los últimos mensajes de cada contacto (24 h, en memoria) y, como
528
+ aquí no ve la página, manda a la gente a la web para cualquier cosa de su cuenta.
529
+
530
+ | Cómo le hablas | Qué pasa |
531
+ | --- | --- |
532
+ | Correo a su buzón de apumail | Contesta en el mismo hilo (`Re:`), sin citas ni firmas automáticas. |
533
+ | Mensaje a su @handle en la app de apuchat | Contesta por mensaje, corto y cercano. |
534
+ | "Llámame" por mensaje | Crea una videollamada de meet con su avatar, entra y te manda el enlace: **suena la app de apuchat** del móvil. |
535
+ | En meet.apuchat.com, 📞 *Ring* a su @handle | Le llega la invitación (Channel id / Token / PIN), entra en tu llamada, pone su avatar y te responde por voz. |
536
+
537
+ ```bash
538
+ # Persona (común a los canales)
539
+ AGENT_NAME=Nube
540
+ AGENT_ROLE="asistente de Nimbus"
541
+ AGENT_SITE_URL=https://nimbus.ejemplo.com
542
+ AGENT_LANG=es
543
+
544
+ # apumail: buzón propio del agente
545
+ APUMAIL_AGENT_INBOX=nube@tudominio.com
546
+ APUMAIL_AGENT_TOKEN=... # token del buzón o PAT de la cuenta dueña
547
+ APUMAIL_AGENT_WEBHOOK_SECRET=... # opcional: webhook en vez de long-poll
548
+ APUMAIL_AGENT_DAILY=20 # respuestas al día como máximo
549
+
550
+ # apuchat / meet: identidad propia del agente
551
+ APUCHAT_AGENT_IDENTITY_KEY=... # su X-Identity-Key (handle permanente)
552
+ APUCHAT_AGENT_AVATAR=vivi # avatar en meet
553
+ APUCHAT_AGENT_SCENE=office # opcional: fondo de la llamada
554
+ APUCHAT_AGENT_ALLOW= # opcional: @handles permitidos (coma)
555
+ ```
556
+
557
+ - **Webhook de apumail:** si defines `APUMAIL_AGENT_WEBHOOK_SECRET`, registra en apumail la URL
558
+ pública `https://tu-proxy/api/agent/channels/apumail`. Se verifica `X-Apumail-Signature` (HMAC
559
+ SHA-256 del cuerpo) y se responde al momento. Sin secreto, el proxy hace long-poll a `/wait` y no
560
+ necesita URL pública.
561
+ - **Avatares de meet:** shibu, shino, fumiriya, victoria, vita, vivi, kuro, maya, ingrid, kenji,
562
+ viktor, marcus, leo, mei, claire, doctor, amira. **Escenas:** studio, beach, castle, space, sunset,
563
+ forest, night, office, neon.
564
+ - **Voz:** en meet tú hablas y meet lo transcribe; el agente contesta en texto corto y tu navegador
565
+ lo lee con el avatar. Si eliges idioma en meet (`[lang]`), responde en ese idioma.
566
+ - **"Teléfono"** es la llamada de la app de apuchat (push VoIP), no telefonía clásica. El agente solo
567
+ llama a quien le está escribiendo.
568
+ - **Límites:** 0,5 mensajes/s (cola en el proxy), 30 mensajes por persona y hora, 5 correos por
569
+ remitente y día. Llamadas: `AGENT_MAX_CALLS=3` simultáneas, `AGENT_CALL_MAX_MINUTES=15`,
570
+ `AGENT_CALL_MAX_TURNS=40`. Cuelga si nadie entra en 2 min o tras 3 min de silencio.
571
+ - **No contesta** correos automáticos, rebotes ni listas, ni correos o mensajes antiguos al
572
+ arrancar. Tampoco entra en invitaciones de hace más de 3 min.
573
+ - Usa un buzón y un handle **permanentes**: los gratis caducan.
574
+ - Estado en `GET /api/agent/health` → `channels`.
575
+
576
+ ## Avatar y voz
577
+
578
+ - Avatar 3D con [TalkingHead](https://github.com/met4citizen/TalkingHead) (Three.js): modelos `.glb`
579
+ con blendshapes ARKit + visemas Oculus (p. ej. exportados de Avaturn o Ready Player Me).
580
+ - Lip-sync: TalkingHead no trae módulo de español; se usa el finés (`fi`), fonéticamente cercano.
581
+ - Expresiones: con `agent.expressive: true` el modelo puede añadir `[[happy]]`, `[[thumbup]]`…
582
+ (lista cerrada), que el widget quita del texto y aplica al avatar.
583
+ - TTS: `TTS_PROVIDER=openai|elevenlabs` en el proxy; si no hay, usa la voz del navegador.
584
+ - STT: Web Speech API (botón de micrófono) si el navegador la soporta.
585
+ - Por defecto, y sin WebGL o si el modelo 3D falla, se usa el personaje 2D (boca sincronizada con el audio).
586
+
587
+ ## Proveedores de LLM
588
+
589
+ | `LLM_PROVIDER` | Notas |
590
+ |---|---|
591
+ | `anthropic` | Claude vía `@anthropic-ai/sdk`. `LLM_EFFORT` (`low` por defecto: chat en vivo) controla profundidad vs. latencia. Conserva los bloques de razonamiento entre pasos de un mismo turno. Activa los *fallbacks* del lado del servidor (si una petición es rechazada se reintenta en otro modelo); desactívalos con `LLM_FALLBACKS=off` en Bedrock/Vertex/Foundry. |
592
+ | `openai` | Chat Completions. `LLM_BASE_URL` para APIs compatibles (DeepSeek, Groq, Ollama, OpenRouter…). |
593
+ | `mock` | Sin claves. Determinista, para desarrollar UI y acciones. |
594
+
595
+ ## Modelo de seguridad
596
+
597
+ 1. **Claves solo en el servidor.** El navegador recibe `siteKey` (público) y nada más.
598
+ 2. **El proxy es abusable si no lo cierras.** El `system` lo envía el widget, así que en producción
599
+ configura `ALLOWED_ORIGINS`, `SITE_KEYS` y `RATE_LIMIT_PER_MIN`, y si quieres fija instrucciones
600
+ con `SERVER_INSTRUCTIONS` (se anteponen siempre).
601
+ 3. **La página es dato, no orden.** El contenido del DOM entra marcado como no confiable; el prompt
602
+ indica ignorar instrucciones que aparezcan en él.
603
+ 4. **Lo privado no sale.** No se leen `input[type=password]`, campos de tarjeta, ni nada bajo
604
+ `data-ots-private` o `privateSelectors`.
605
+ 5. **Confirmación humana** para envíos de formularios, herramientas MCP con efectos, correo,
606
+ derivación a humano y cualquier acción con `confirm`.
607
+ 6. **Navegación acotada** a tu origen y a `navigation.allowedOrigins`.
608
+ 7. **HTML de confianza.** Los modales solo muestran markdown saneado o plantillas que tú defines.
609
+
610
+ ## Estructura
611
+
612
+ ```
613
+ src/
614
+ index.js init(), exports, window.SevenOts
615
+ core/AgentWidget.js <ots-agent>: Shadow DOM, UI, orquesta todos los módulos
616
+ core/AgentBrain.js loop de tool calling, historial, prompt de sistema
617
+ core/Proactivity.js niveles quiet/normal/bold y señales (ruta, sección, duda, salida…) → notify()
618
+ core/EventBus.js · core/storage.js
619
+ context/ContextManager.js snapshot semántico del DOM + MutationObserver + rutas SPA
620
+ actions/ActionRegistry.js registro, validación, confirmación, MCP
621
+ actions/builtins.js navegar, click, teclado, arrastrar, formularios, modales…
622
+ actions/PageTools.js [data-ots-tool] del HTML → herramientas
623
+ ui/VirtualPointer.js cursor, ratón y teclado virtuales (eventos sintéticos)
624
+ ui/Companion.js modo compañero: el avatar se pasea y señala (point_at, tour)
625
+ mcp/McpClient.js cliente MCP Streamable HTTP
626
+ auth/AuthManager.js JWT / cookies, claims expuestos
627
+ ui/UIManager.js capa de UI sobre la página (toasts, modales, sidebar, foco)
628
+ avatar/AvatarStage.js TalkingHead 3D + fallback 2D
629
+ voice/VoiceEngine.js TTS (proxy/navegador) + STT
630
+ integrations/apumail.js · integrations/apuchat.js
631
+ identity/schema.js ficha de identidad: defineIdentity, publicIdentity, vCard (navegador y Node)
632
+ identity/Face.js createFace: cara + voz sin chat
633
+ identity/ContactCard.js <ots-identity>: tarjeta de contacto
634
+ i18n/index.js traducciones: t, translator, addMessages, resolveLocale (navegador y Node)
635
+ i18n/messages/ catálogos es/en/pt por módulo (widget, character, identity, server)
636
+ character/Character.js personaje 2D estilo Pou: render, animación, ánimos y gestos
637
+ character/parts.js catálogo: formas, ojos, bocas, cejas, accesorios, estilos
638
+ character/AvatarEditor.js <ots-avatar-editor>: editor visual del personaje (y 3D/retrato)
639
+ backoffice/ panel /backoffice/: index.html, app.js, app.css, preview.html
640
+ server/ proxy Node (sin frameworks): llm, tts, contact, demo
641
+ identity.mjs identidad vigente: entorno + data/identity.json
642
+ settings.mjs data/config.json (widget + servidor) y data/secrets.json sobre el entorno
643
+ admin.mjs API del backoffice (/api/agent/admin/*): sesión, estado, guardar, pruebas
644
+ sdk.mjs 7ots/server: createAgentIdentity (identidad y canales para cualquier agente)
645
+ channels/ el agente por apumail, apuchat y meet (fuera de la web)
646
+ examples/ sitio de demo "Nimbus" (index, docs, dashboard) e identidad.html
647
+ site/ la web de 7ots.com (landing es/en/pt con demo, editor y agente en vivo)
648
+ brand/ logo, favicon, tokens.css y la mascota Ots (ver docs/BRAND.md)
649
+ ```
650
+
651
+ Detalles de diseño en [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
652
+
653
+ ## Licencia
654
+
655
+ MIT
Binary file