diff options
50 files changed, 2260 insertions, 2027 deletions
@@ -1,5 +1,9 @@ /target/ /logs/ -# Enlaces a los pesos, que se recrean con scripts/bootstrap.sh +# Links to the weights, recreated by scripts/bootstrap.sh /models/* !/models/.gitkeep + +# Cloned voices: they are someone's voice, so they stay local. +# Create one with scripts/clone-voice.sh. +/assets/voices/ diff --git a/.gitmodules b/.gitmodules index 2a8747c..b07c676 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,8 @@ -# Los tres motores, fijados al commit contra el que se midió el sistema. +# The three engines, pinned to the commit the system was measured against. # -# «ignore = dirty» es a propósito: scripts/bootstrap.sh aplica sobre cada uno -# el parche de vendor/patches/, así que su árbol de trabajo queda modificado -# siempre. Los cambios se versionan ahí, no como commits del submódulo. +# «ignore = dirty» is on purpose: scripts/bootstrap.sh applies the patch from +# vendor/patches/ on each of them, so their working tree is always modified. +# The changes are versioned there, not as submodule commits. [submodule "canary-rs"] path = vendor/canary-rs url = https://github.com/mmende/canary-rs.git @@ -9,10 +9,10 @@ members = [ "crates/asist-tools", "crates/asist-app", ] -# Los submódulos son dependencias por ruta, no miembros del workspace: sin esto -# cargo los adopta por estar dentro del directorio, y `cargo test` acabaría -# ejecutando las pruebas de integración de canary-rs, que esperan un modelo en -# su propia ruta por defecto y fallan aquí. +# The submodules are path dependencies, not workspace members: without this +# cargo adopts them for being inside the directory, and `cargo test` would end +# up running canary-rs's integration tests, which expect a model at their own +# default path and fail here. exclude = ["vendor"] [workspace.package] @@ -31,10 +31,10 @@ asist-tts = { path = "crates/asist-tts" } asist-tools = { path = "crates/asist-tools" } canary-rs = { path = "vendor/canary-rs", default-features = false, features = ["cpu", "ort-defaults"] } -# canary-rs pide "2.0.0-rc.12", que en semver de prelanzamientos también admite -# rc.13 — y ahí el módulo CoreML pasó a estar tras una feature, así que sus -# reexportaciones incondicionales dejan de compilar. Se fija la versión contra -# la que canary-rs se construye de verdad. +# canary-rs asks for "2.0.0-rc.12", which under prerelease semver also admits +# rc.13, and there the CoreML module moved behind a feature, so its +# unconditional re-exports stop compiling. Pin the version canary-rs really +# builds against. ort = "=2.0.0-rc.12" anyhow = "1" @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Elvis Claros +Copyright (c) 2026 Elvis Claros Castro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.es.md b/README.es.md new file mode 100644 index 0000000..3bd21ac --- /dev/null +++ b/README.es.md @@ -0,0 +1,217 @@ +# asist-p — asistente de voz local + +[English](README.md) + +Escucha por el micrófono, piensa y contesta hablando. Todo corre en la +máquina; sólo la búsqueda en internet, que es opcional, sale a la red. + +Une tres motores que ya existían —**Canary** para oír, **llama.cpp** para +pensar y **qwentts.cpp** para hablar— en un pipeline de hilos y canales en +Rust donde ninguna etapa espera a la siguiente. + +``` + micrófono ──muestras──> segmentador ──intervención──> ASR ──texto──┐ + (cpal, tiempo real) (VAD, turnos) (Canary) │ + v + altavoz <──muestras── síntesis <──frases── conversación <──────────┘ + (cpal, anillo) (qwentts) (llama.cpp + herramientas) +``` + +Cada caja es un hilo; cada flecha, un canal. Dos decisiones sostienen el +resto: + +- **Nada bloquea al que va delante.** El micrófono nunca espera al + reconocedor, y el modelo nunca espera al sintetizador. Quien va sobrado + descarta trabajo en lugar de acumular retraso. +- **Se responde por frases, no por respuestas.** Cada frase sale hacia el + sintetizador en cuanto se cierra, así que el asistente empieza a hablar + mientras el modelo sigue escribiendo. Es lo que separa medio segundo de + cinco. + +## Puesta en marcha + +```bash +git clone --recursive https://git.all.ar/pub/asist-p.git && cd asist-p +scripts/bootstrap.sh # submódulos, parches, binarios y modelos +cargo run --release -- check # comprueba que está todo en su sitio +cargo run --release -- run # a hablar +``` + +`bootstrap.sh` es idempotente y no copia modelos: los enlaza desde donde ya +estén (`~/GIT-MIRRO`, `~/HF`). Si no encuentra los motores construidos, los +construye, y eso sí tarda. + +```bash +cargo run --release -- devices # listar dispositivos de audio +cargo run --release -- run --barge-in # permitir cortar al asistente +cargo run --release -- run --shell # habilitar órdenes del sistema +cargo run --release -- run --no-manage # no lanzar los servidores +``` + +Durante el desarrollo conviene dejar los servidores levantados aparte —cargar +los modelos cuesta más de un minuto— y reiniciar sólo el binario de Rust: + +```bash +scripts/servers.sh start +cargo run --release -- run --no-manage +``` + +## Cómo está organizado + +| Crate | De qué se ocupa | +|---|---| +| `asist-core` | Configuración, eventos, errores, cliente HTTP, telemetría y el registro de herramientas | +| `asist-audio` | Captura y reproducción con cpal, detección de voz, troceado en turnos | +| `asist-asr` | Canary en ONNX: ventana deslizante y transcripción final | +| `asist-llm` | llama-server: streaming SSE, historial, llamadas a herramientas | +| `asist-tts` | qwentts: síntesis en streaming y voces clonadas | +| `asist-tools` | Buscar en internet, mirar por la cámara y ver la pantalla | +| `asist-app` | Supervisor de procesos, orquestador y binario `asistente` | + +Los tres motores viven en `vendor/` como submódulos fijados a un commit +concreto. Los cambios locales sobre ellos están en `vendor/patches/`, y +`bootstrap.sh` los aplica: sin ellos el reconocedor no se comporta como el +que se midió. + +## Configuración + +Todo está en [`config/asistente.toml`](config/asistente.toml), comentado. Los +valores que llevan una cifra en el comentario salen de una medición concreta, +no de una suposición; el detalle está en +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). + +Lo que más se toca: + +```toml +[vad] +silence_hold = 0.8 # cuánto silencio cierra tu intervención +barge_in = false # cortar al asistente hablando encima + +[general] +history_turns = 8 # memoria de la conversación +system_prompt = "..." # carácter y estilo + +[tools] +shell = false # ejecutar órdenes del sistema +``` + +## Interrumpir y el eco del altavoz + +Por defecto el asistente es **media dúplex**: mientras habla, el micrófono +está cerrado. No es pereza: con altavoces abiertos el micrófono se oye a sí +mismo, el asistente se transcribe y se responde solo. + +Con auriculares, `--barge-in` deja hablar encima para cortarlo. El umbral se +eleva (`vad.barge_in_factor`) para que el eco no baste y una voz de verdad sí. + +## Latencia + +Un turno real, medido de punta a punta: + +| Etapa | Tiempo | +|---|---| +| Reconocimiento (2,5 s de voz) | 200 ms | +| Modelo, primer token | 1660 ms | +| Modelo, resto | 1362 ms | +| Herramientas | 1 ms | +| **Del final de tu frase al primer audio** | **3213 ms** | + +El binario mide esto en cada turno y señala la etapa más lenta. Tres ajustes +de configuración valieron más que cualquier cambio de código: + +- El TTS decodificaba en bloques de 24 s: **4948 ms → 585 ms** al primer audio. +- La plantilla del modelo dejaba `<think>` abierto: **8630 ms → 413 ms** al + primer token. +- El prompt de estilo anulaba las llamadas a herramientas: **0/8 → 8/8**. +- La cámara capturaba a 720p: **7,8 s → 2,9 s** por imagen a 640x480. +- Las herramientas se ejecutaban en silencio: **~8,4 s → 4,1 s** hasta oír algo. +- La pantalla a 640 px inventaba el texto: **1 de 3 aciertos → 3 de 3** a 1280 px. + +Todos, con el porqué y cómo se midieron, en +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). + +## Herramientas + +El asistente puede llamar a funciones. Vienen cuatro: + +| Herramienta | Qué hace | Coste medido | +|---|---|---| +| `hora_actual` | Fecha y hora del sistema | 0 ms | +| `buscar_en_internet` | Busca y resume | 0,5–3,6 s | +| `mirar_por_la_camara` | Hace una foto y responde sobre lo que ve | 4,1–5,2 s | +| `mirar_la_pantalla` | Captura la pantalla y responde sobre lo que hay | 7,6–12,4 s | +| `ejecutar_comando` | Órdenes del sistema, **apagada** por defecto | — | + +Mientras una herramienta trabaja, el asistente dice «Déjame que lo busque» o +«Voy a mirar». No es adorno: sin eso el turno pasa seis segundos en silencio y +se lee como un cuelgue. Con el acuse, el primer audio llega en 3–4 s. + +**Buscar** admite tres buscadores: `tavily` (con clave, devuelve una respuesta +ya redactada), `ddgs` (sin clave, la misma librería que hay bajo +`duckduckgo-mcp`) y `searxng`. El de comando ejecuta un guion que escriba JSON, +así que meter otro es escribir un guion, no tocar Rust. + +**Ver** —cámara y pantalla— aprovecha que el servidor ya carga el proyector +multimodal: el mismo modelo que conversa describe la imagen. Ni la foto ni la +captura se guardan en disco, y no entran en el historial. + +La pantalla usa 1280 px y la cámara 640, y la diferencia importa: una escena se +entiende, pero un texto hay que leerlo. Medido, a 640 px el modelo no dice que +no lee la pantalla, **se inventa lo que pone** —contestó que la reunión era «a +las 10:00» cuando ponía 15:30—. Por eso el triple de coste. + +`ejecutar_comando` está apagada a conciencia: darle una shell a un modelo que +obedece a lo que oye por el micrófono es un cambio de postura de seguridad. +Cuando se enciende, sólo pasan las órdenes de una lista blanca, sin shell que +interprete metacaracteres y con un plazo máximo. + +Añadir una propia son unas veinte líneas: +[docs/EXTENDER.md](docs/EXTENDER.md). + +## Pruebas + +Más de cien pruebas unitarias que no necesitan ni modelos ni micrófono, y +pruebas de integración contra los servidores de verdad. + +```bash +cargo test # unitarias, sin modelos +scripts/servers.sh start +cargo test --release -p asist-app --test integration -- --nocapture +cargo test --release -p asist-app --test integration -- --ignored # bucle completo +``` + +Las de integración se saltan solas si no hay servidores escuchando, y se +turnan la GPU con un mutex: los dos servidores comparten una tarjeta de 4 GB y +en paralelo miden contención en vez de latencia. + +La prueba marcada `--ignored` cierra el bucle sin micrófono: sintetiza una +frase y comprueba que el reconocedor la recupera. + +## Requisitos + +- Rust 1.85 o posterior +- CUDA para los motores en C++ (funcionan en CPU, pero muy justos) +- PipeWire o ALSA +- ~6 GB de disco para los modelos +- `ffmpeg` para la cámara y para reducir las capturas +- Para la pantalla: `grim` (Wayland) o `maim`/ImageMagick (X11) +- Para buscar: una clave de Tavily en `$TAVILY_API_KEY`, o `uv tool install ddgs` + +## Voz + +El repositorio no trae ninguna voz clonada: una voz es de alguien. Para que el +asistente hable con una, grabá unos segundos de audio con su transcripción y +extraé los latentes: + +```bash +scripts/clone-voice.sh grabacion.wav transcripcion.txt +``` + +Quedan en `assets/voices/` (fuera de git). Sin voz clonada, quitá la sección +`[tts.reference]` de `config/asistente.toml` y el asistente usa una voz del +propio modelo. + +## Licencia + +MIT + @@ -1,197 +1,224 @@ -# asist-p — asistente de voz local +# asist-p — local voice assistant -Escucha por el micrófono, piensa y contesta hablando. Todo en la máquina: no -sale un byte a internet. +[Español](README.es.md) -Une tres motores que ya existían —**Canary** para oír, **llama.cpp** para -pensar y **qwentts.cpp** para hablar— en un pipeline de hilos y canales en -Rust donde ninguna etapa espera a la siguiente. +It listens through the microphone, thinks and answers out loud. Everything +runs on the machine; only the optional web search goes online. + +It joins three existing engines (**Canary** to hear, **llama.cpp** to think +and **qwentts.cpp** to speak) in a Rust pipeline of threads and channels +where no stage waits for the next. ``` - micrófono ──muestras──> segmentador ──intervención──> ASR ──texto──┐ - (cpal, tiempo real) (VAD, turnos) (Canary) │ - v - altavoz <──muestras── síntesis <──frases── conversación <──────────┘ - (cpal, anillo) (qwentts) (llama.cpp + herramientas) + microphone ──samples──> segmenter ──utterance──> ASR ──text──┐ + (cpal, real time) (VAD, turns) (Canary) │ + v + speaker <──samples── synthesis <──sentences── conversation <─┘ + (cpal, ring) (qwentts) (llama.cpp + tools) ``` -Cada caja es un hilo; cada flecha, un canal. Dos decisiones sostienen el -resto: +Each box is a thread; each arrow, a channel. Two decisions carry the rest: + +- **Nothing blocks the stage ahead.** The microphone never waits for the + recognizer, and the model never waits for the synthesizer. Whoever has + spare capacity drops work instead of accumulating delay. +- **Answers go out per sentence, not per answer.** Each sentence goes to the + synthesizer as soon as it closes, so the assistant starts talking while the + model is still writing. That is what separates half a second from five. -- **Nada bloquea al que va delante.** El micrófono nunca espera al - reconocedor, y el modelo nunca espera al sintetizador. Quien va sobrado - descarta trabajo en lugar de acumular retraso. -- **Se responde por frases, no por respuestas.** Cada frase sale hacia el - sintetizador en cuanto se cierra, así que el asistente empieza a hablar - mientras el modelo sigue escribiendo. Es lo que separa medio segundo de - cinco. +The assistant speaks Spanish: its prompts, tool descriptions and spoken +acknowledgements are in Spanish on purpose. The code is in English. -## Puesta en marcha +## Getting started ```bash -git clone --recursive <este-repo> && cd asist-p -scripts/bootstrap.sh # submódulos, parches, binarios y modelos -cargo run --release -- check # comprueba que está todo en su sitio -cargo run --release -- run # a hablar +git clone --recursive https://git.all.ar/pub/asist-p.git && cd asist-p +scripts/bootstrap.sh # submodules, patches, binaries and models +cargo run --release -- check # check that everything is in place +cargo run --release -- run # start talking ``` -`bootstrap.sh` es idempotente y no copia modelos: los enlaza desde donde ya -estén (`~/GIT-MIRRO`, `~/HF`). Si no encuentra los motores construidos, los -construye, y eso sí tarda. +`bootstrap.sh` is idempotent and does not copy models: it links them from +wherever they already are (`$ASIST_MIRROR`, default `~/GIT-MIRRO`, and +`$ASIST_HF`, default `~/HF`). If it cannot find the engines built, it builds +them, and that does take a while. ```bash -cargo run --release -- devices # listar dispositivos de audio -cargo run --release -- run --barge-in # permitir cortar al asistente -cargo run --release -- run --shell # habilitar órdenes del sistema -cargo run --release -- run --no-manage # no lanzar los servidores +cargo run --release -- devices # list audio devices +cargo run --release -- run --barge-in # allow interrupting the assistant +cargo run --release -- run --shell # enable system commands +cargo run --release -- run --no-manage # do not start the servers ``` -Durante el desarrollo conviene dejar los servidores levantados aparte —cargar -los modelos cuesta más de un minuto— y reiniciar sólo el binario de Rust: +While developing, keep the servers running separately (loading the models +takes more than a minute) and restart only the Rust binary: ```bash -scripts/servidores.sh arrancar +scripts/servers.sh start cargo run --release -- run --no-manage ``` -## Cómo está organizado +## Layout -| Crate | De qué se ocupa | +| Crate | What it does | |---|---| -| `asist-core` | Configuración, eventos, errores, cliente HTTP, telemetría y el registro de herramientas | -| `asist-audio` | Captura y reproducción con cpal, detección de voz, troceado en turnos | -| `asist-asr` | Canary en ONNX: ventana deslizante y transcripción final | -| `asist-llm` | llama-server: streaming SSE, historial, llamadas a herramientas | -| `asist-tts` | qwentts: síntesis en streaming y voces clonadas | -| `asist-tools` | Buscar en internet, mirar por la cámara y ver la pantalla | -| `asist-app` | Supervisor de procesos, orquestador y binario `asistente` | +| `asist-core` | Configuration, events, errors, HTTP client, telemetry and the tool registry | +| `asist-audio` | Capture and playback with cpal, voice detection, splitting into turns | +| `asist-asr` | Canary on ONNX: sliding window and final transcription | +| `asist-llm` | llama-server: SSE streaming, history, tool calls | +| `asist-tts` | qwentts: streaming synthesis and cloned voices | +| `asist-tools` | Web search, looking through the camera and at the screen | +| `asist-app` | Process supervisor, orchestrator and the `asistente` binary | -Los tres motores viven en `vendor/` como submódulos fijados a un commit -concreto. Los cambios locales sobre ellos están en `vendor/patches/`, y -`bootstrap.sh` los aplica: sin ellos el reconocedor no se comporta como el -que se midió. +The three engines live in `vendor/` as submodules pinned to a specific +commit. The local changes on them are in `vendor/patches/`, and +`bootstrap.sh` applies them: without them the recognizer does not behave like +the one that was measured. -## Configuración +## Configuration -Todo está en [`config/asistente.toml`](config/asistente.toml), comentado. Los -valores que llevan una cifra en el comentario salen de una medición concreta, -no de una suposición; el detalle está en -[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). +Everything is in [`config/asistente.toml`](config/asistente.toml), with +comments. Values that carry a number in their comment come from a specific +measurement, not an assumption; the details are in +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md) (in Spanish). -Lo que más se toca: +The most tweaked settings: ```toml [vad] -silence_hold = 0.8 # cuánto silencio cierra tu intervención -barge_in = false # cortar al asistente hablando encima +silence_hold = 0.8 # how much silence ends your utterance +barge_in = false # interrupt the assistant by talking over it [general] -history_turns = 8 # memoria de la conversación -system_prompt = "..." # carácter y estilo +history_turns = 8 # conversation memory +system_prompt = "..." # personality and style [tools] -shell = false # ejecutar órdenes del sistema +shell = false # run system commands ``` -## Interrumpir y el eco del altavoz +## Interrupting and speaker echo -Por defecto el asistente es **media dúplex**: mientras habla, el micrófono -está cerrado. No es pereza: con altavoces abiertos el micrófono se oye a sí -mismo, el asistente se transcribe y se responde solo. +By default the assistant is **half duplex**: while it talks, the microphone +is closed. That is not laziness: with open speakers the microphone hears +itself, and the assistant transcribes itself and answers itself. -Con auriculares, `--barge-in` deja hablar encima para cortarlo. El umbral se -eleva (`vad.barge_in_factor`) para que el eco no baste y una voz de verdad sí. +With headphones, `--barge-in` lets you talk over it to interrupt. The +threshold is raised (`vad.barge_in_factor`) so the echo is not enough but a +real voice is. -## Latencia +## Latency -Un turno real, medido de punta a punta: +A real turn, measured end to end: -| Etapa | Tiempo | +| Stage | Time | |---|---| -| Reconocimiento (2,5 s de voz) | 200 ms | -| Modelo, primer token | 1660 ms | -| Modelo, resto | 1362 ms | -| Herramientas | 1 ms | -| **Del final de tu frase al primer audio** | **3213 ms** | +| Recognition (2.5 s of speech) | 200 ms | +| Model, first token | 1660 ms | +| Model, rest | 1362 ms | +| Tools | 1 ms | +| **From the end of your sentence to the first audio** | **3213 ms** | -El binario mide esto en cada turno y señala la etapa más lenta. Tres ajustes -de configuración valieron más que cualquier cambio de código: +The binary measures this on every turn and flags the slowest stage. A few +configuration settings were worth more than any code change: -- El TTS decodificaba en bloques de 24 s: **4948 ms → 585 ms** al primer audio. -- La plantilla del modelo dejaba `<think>` abierto: **8630 ms → 413 ms** al - primer token. -- El prompt de estilo anulaba las llamadas a herramientas: **0/8 → 8/8**. -- La cámara capturaba a 720p: **7,8 s → 2,9 s** por imagen a 640x480. -- Las herramientas se ejecutaban en silencio: **~8,4 s → 4,1 s** hasta oír algo. -- La pantalla a 640 px inventaba el texto: **1 de 3 aciertos → 3 de 3** a 1280 px. +- The TTS decoded in 24 s blocks: **4948 ms → 585 ms** to first audio. +- The model template left `<think>` open: **8630 ms → 413 ms** to first token. +- The style prompt suppressed tool calls: **0/8 → 8/8**. +- The camera captured at 720p: **7.8 s → 2.9 s** per image at 640x480. +- Tools ran in silence: **~8.4 s → 4.1 s** until something is heard. +- The screen at 640 px made the text up: **1 of 3 right → 3 of 3** at 1280 px. -Los tres, con el porqué y cómo se midieron, en -[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). +All of them, with the reasons and how they were measured, are in +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md) (in Spanish). -## Herramientas +## Tools -El asistente puede llamar a funciones. Vienen cuatro: +The assistant can call functions. It ships with these: -| Herramienta | Qué hace | Coste medido | +| Tool | What it does | Measured cost | |---|---|---| -| `hora_actual` | Fecha y hora del sistema | 0 ms | -| `buscar_en_internet` | Busca y resume | 0,5–3,6 s | -| `mirar_por_la_camara` | Hace una foto y responde sobre lo que ve | 4,1–5,2 s | -| `mirar_la_pantalla` | Captura la pantalla y responde sobre lo que hay | 7,6–12,4 s | -| `ejecutar_comando` | Órdenes del sistema, **apagada** por defecto | — | - -Mientras una herramienta trabaja, el asistente dice «Déjame que lo busque» o -«Voy a mirar». No es adorno: sin eso el turno pasa seis segundos en silencio y -se lee como un cuelgue. Con el acuse, el primer audio llega en 3–4 s. - -**Buscar** admite tres buscadores: `tavily` (con clave, devuelve una respuesta -ya redactada), `ddgs` (sin clave, la misma librería que hay bajo -`duckduckgo-mcp`) y `searxng`. El de comando ejecuta un guion que escriba JSON, -así que meter otro es escribir un guion, no tocar Rust. - -**Ver** —cámara y pantalla— aprovecha que el servidor ya carga el proyector -multimodal: el mismo modelo que conversa describe la imagen. Ni la foto ni la -captura se guardan en disco, y no entran en el historial. +| `hora_actual` | System date and time | 0 ms | +| `buscar_en_internet` | Searches and summarizes | 0.5–3.6 s | +| `mirar_por_la_camara` | Takes a photo and answers about what it sees | 4.1–5.2 s | +| `mirar_la_pantalla` | Captures the screen and answers about what is on it | 7.6–12.4 s | +| `ejecutar_comando` | System commands, **off** by default | — | + +The tool names are Spanish because the model reads them in a Spanish +conversation. + +While a tool works, the assistant says «Déjame que lo busque» or «Voy a +mirar». It is not decoration: without it the turn spends six seconds in +silence and reads as a hang. With the acknowledgement, the first audio +arrives in 3–4 s. + +**Search** supports three backends: `tavily` (with a key, returns an already +written answer), `ddgs` (no key, the same library behind `duckduckgo-mcp`) +and `searxng`. The command backend runs a script that writes JSON, so adding +another engine means writing a script, not touching Rust. + +**Vision** (camera and screen) takes advantage of the server already loading +the multimodal projector: the same model that converses describes the image. +Neither the photo nor the capture is saved to disk, and they do not enter the +history. + +The screen uses 1280 px and the camera 640, and the difference matters: a +scene can be understood, but text has to be read. Measured, at 640 px the +model does not say it cannot read the screen, **it makes up what it says** +(it answered that the meeting was «at 10:00» when it said 15:30). Hence the +threefold cost. + +`ejecutar_comando` is off on purpose: giving a shell to a model that obeys +what it hears through the microphone is a change of security posture. When it +is on, only allowlisted commands get through, with no shell to interpret +metacharacters and with a deadline. + +Adding your own takes about twenty lines: [docs/EXTENDER.md](docs/EXTENDER.md) +(in Spanish). + +## Voice + +The repository ships no cloned voice: a voice belongs to someone. To make the +assistant speak with one, record a few seconds of audio with its transcript +and extract the latents: -La pantalla usa 1280 px y la cámar |