Communitygithub.com

V4TIME/V4SKILL

Construye aplicaciones Flask de IA en Termux con integración NaraRouter.

¿Qué es V4SKILL?

Esta habilidad permite construir y ejecutar una aplicación web local de IA en un dispositivo Android usando Termux, integrando múltiples APIs de IA como NaraRouter (recomendado), NVIDIA NIM, y opcionalmente Hermes Agent como backend de razonamiento. Está diseñada para desarrolladores que desean tener un asistente local de chat, interfaz de voz o herramienta de IA ejecutándose en su teléfono, aprovechando la flexibilidad de Python y Flask. Soporta plataformas como Termux, entornos móviles Android y, por extensión, cualquier sistema con Python. La habilidad incluye patrones de integración detallados para NaraRouter (con 53+ modelos, plan gratuito) y NVIDIA NIM (82+ modelos), un motor de despacho de APIs por descripción, y recomendaciones para usar la biblioteca estándar `urllib` en lugar de `requests` para evitar timeouts en redes móviles. También enfatiza un diseño minimalista, profesional y sin adornos típicos de IA, siguiendo las preferencias del usuario. Incluye guías de depuración, manejo de puertos, persistencia de datos (con copia de seguridad a GitHub), y pruebas de voz con Web Speech API. Funciona con un patrón de arquitectura donde el navegador (Chrome en Android) se comunica con un backend Flask en Termux, que a su vez se conecta a las APIs de IA, con Hermes Agent opcional como cerebro.

Compatible con✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/V4TIME/V4SKILL/tree/HEAD/termux-flask

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Aplicación Web Local de IA en Termux

Construye y ejecuta una aplicación web local en un dispositivo Android con Termux, integrándola con APIs de IA (especialmente NaraRouter) y opcionalmente Hermes Agent como backend de razonamiento.

Cuándo usarlo

  • El usuario quiere una interfaz de chat/asistente local de IA en su teléfono
  • Construir una aplicación web en Termux con Python/Flask
  • Integrar múltiples APIs de IA con enrutamiento basado en descripción
  • Añadir modo de voz mediante la Web Speech API del navegador
  • Gestionar sesiones y memoria a través de los chats
  • El usuario pega instrucciones de instalación de una herramienta de desarrollo (binario, Rust/cargo, npm/npx, pip) y dice que la instale y use: evalúa primero la viabilidad en el entorno del teléfono/Termux, luego instala con permiso

Patrón de arquitectura

Navegador (Chrome en Android) → Backend Flask (Termux) → API(s) de IA
                                 ↓
                             Hermes Agent (opcional, como cerebro)

Principio clave: La aplicación web es la capa de interfaz. Hermes Agent (si se usa) es el cerebro. El backend Flask los conecta.

Inicio rápido

# En Termux
mkdir ~/sayip && cd ~/sayip
mkdir -p templates static/css static/js storage/sessions
# app.py — Mínimo Flask starter
from flask import Flask, render_template, request, jsonify
from pathlib import Path
import json, time, uuid, os

app = Flask(__name__, template_folder="templates")
HERMES_HOME = Path(os.path.expanduser("~/.hermes"))
SAYIP_STORAGE = Path(os.path.expanduser("~/sayip/storage"))

@app.route("/")
def index():
    return render_template("chat.html")

@app.route("/api/chat", methods=["POST"])
def chat():
    data = request.get_json()
    # ... procesar mensaje, llamar a la API, devolver respuesta

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)
# Ejecutar
python3 app.py
# Abrir en Chrome: http://127.0.0.1:5000

Patrones de integración de APIs

NaraRouter (recomendación principal)

NaraRouter es una puerta de enlace unificada de IA con más de 53 modelos, con plan gratuito disponible.

Formato de clave: sk-nry-... (empieza con sk-nry-)

Endpoints (todos bajo la misma URL base + clave Bearer):

EndpointTipoUso
https://router.bynara.id/v1/chat/completionsCompatible con OpenAIChat, tareas generales
https://router.bynara.id/v1/messagesCompatible con AnthropicSolicitudes en formato Anthropic
https://router.bynara.id/v1/responsesRespuestas OpenAIAPI de respuestas con estado
https://router.bynara.id/v1/embeddingsEmbeddings OpenAIEmbeddings vectoriales
https://router.bynara.id/v1/modelsLISTAObtener modelos disponibles según el plan
https://api-images.bynara.id/v1/images/generationsGeneración de imágenesTexto a imagen
https://api-images.bynara.id/v1/images/editsEdición de imágenesEditar imágenes

Probar una clave antes de usarla:

curl -s -X POST https://router.bynara.id/v1/chat/completions \
  -H "Authorization: Bearer sk-nry-TU_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"model":"agnes-2.5-flash","messages":[{"role":"user","content":"hola"}],"max_tokens":10}'

Modelos conocidos que funcionan en el plan gratuito: agnes-2.5-flash, agnes-2.0-flash

Verificar los modelos de tu plan:

curl -s https://router.bynara.id/v1/models \
  -H "Authorization: Bearer sk-nry-TU_CLAVE" | python3 -c "import sys,json; [print(m['id']) for m in json.load(sys.stdin).get('data',[])]"

IMPORTANTE: No todos los 53 modelos están en todos los planes. deepseek-v4-pro-0813-bynara requiere créditos. deepseek-v4-flash-free puede no estar incluido. Siempre prueba antes de configurar.

API NVIDIA NIM (integrate.api.nvidia.com/v1)

NVIDIA NIM proporciona más de 82 modelos a través de un único endpoint compatible con OpenAI. URL base: https://integrate.api.nvidia.com/v1 Endpoint: https://integrate.api.nvidia.com/v1/chat/completions Formato de clave: nvapi- (ej. nvapi-_qJeGmEP...)

Probar una clave antes de usarla:

curl -s -X POST https://integrate.api.nvidia.com/v1/chat/completions \
  -H "Authorization: Bearer nvapi-..." \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"model":"nvidia/nemotron-3.5-lightning-30b-a3b","messages":[{"role":"user","content":"hola"}],"max_tokens":10,"stream":true}'

Modelos conocidos que funcionan en el plan gratuito (probados en red móvil de Termux):

ID del modeloClaveEstadoNotas
nvidia/nemotron-3.5-lightning-30b-a3bnvapi-...FuncionaRápido (~5-7s para tareas simples), razonamiento potente, soporta modo pensamiento. El más confiable en móvil.
gpt-oss-20bnvapi-...FuncionaCompatible con OpenAI
deepseek-ai/deepseek-v4-pro-0813nvapi-...Riesgo de timeoutFunciona en escritorio, pero frecuentemente da timeout en la red móvil de Termux
moonshotai/kimi-k3nvapi-...Riesgo de timeoutCapaz de visión, pero da timeout en red móvil

Advertencia de asimetría de claves: No todos los modelos están disponibles en todas las claves. La Clave 1 (nvapi-_qJeGmEP...) tiene acceso más amplio. La Clave 2 (nvapi-WDb7Rr...) es más restrictiva; algunos modelos devuelven 404. Siempre prueba un modelo con una clave específica antes de configurarlo.

Indicadores de clave inactiva:

  • 401 Unauthorized — clave inválida/expirada
  • 404 Not Found en un modelo — modelo no disponible en esta cuenta/clave
  • Timeout de conexión (30s+) — API lenta/inaccesible desde esta red
  • Claves sk-teamo-... de Teamorouter → insufficient_balance en el endpoint de OpenAI (https://api.teamorouter.com/v1); modelo no soportado en el endpoint de Anthropic (https://api.teamorouter.com). Inactivas. Proporcionan el modelo deepseek-v4-pro-free pero no usable sin saldo.

Consulta references/nvidia-api.md para detalles completos: patrones específicos de Python para modelos (modo pensamiento Nemotron, DeepSeek sin streaming, Kimi visión multimodal), extracción de reasoning_content en streaming, patrón de timeout duro basado en hilos para urllib, y formato de payload de visión.

Motor de despacho de APIs

Cuando tengas múltiples APIs configuradas, construye un despachador que:

  1. Clasifique la tarea (matemáticas, código, ciencia, general)
  2. Seleccione la mejor API según el texto de descripción
  3. La llame y devuelva el resultado
  4. Recurra a otras APIs si la primera falla

Consulta references/nararouter-api.md para detalles de integración.

Usa la biblioteca estándar urllib, no requests

En Termux, pip install requests puede dar timeout en descargas grandes. Usa la biblioteca estándar urllib de Python, que siempre está disponible y maneja APIs JSON sin problemas:

import urllib.request, json

def call_api(url, payload, api_key):
    data = json.dumps(payload).encode()
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read())

Preferencias de diseño (específicas del usuario)

Aspecto mínimo, profesional y no generado por IA:

  • Tema oscuro, espaciado ajustado, sin degradados, sin sombras decorativas
  • Fuentes monoespaciadas donde sea apropiado (código, claves API, marcas de tiempo)
  • Rectángulos lisos para burbujas de chat (sin bordes redondeados a menos que sean funcionales)
  • Barra lateral: lista simple, sin íconos a menos que tengan significado
  • Buscar implementaciones de referencia en lugar de generar diseños desde cero
  • En caso de duda, eliminar funciones en lugar de añadirlas

Antipatrones concretos (hacen que una web parezca generada por IA; elimínalos):

  • Emojis en cada encabezado/tarjeta/botón (🤖, ⚙, 🎭, 🔌, 🧠, 💬, 🎤) — elimínalos. Solo etiquetas de texto plano.
  • Brillo de acento / sombra de caja al pasar el ratón (box-shadow: 0 0 12px var(--accent-glow)) — elimínalo. Solo colores planos.
  • Animaciones de rebote / pulso / deslizamiento hacia abajo (@keyframes bounce, @keyframes pulse, @keyframes slideDown) — elimínalas. Sin diseño de movimiento.
  • Tarjeta de bienvenida tipo splash con íconos (.welcome-screen con welcome-icon, welcome-hints grid) — reemplázala con un marcador de posición de una línea en el área de entrada.
  • Ícono de marca decorativo junto al logotipo (.brand-icon con emoji) — elimínalo.
  • Cintas, insignias, puntos de estado decorando el encabezado (.health-badge, .health-dot) — elimínalos o hazlos puramente funcionales.
  • Fondos degradados, glassmorphism, superposiciones translúcidas — nunca.

Regla general: abre la página en un navegador y pregúntate: "¿Un desarrollador humano escribiría este CSS a mano para una herramienta utilitaria?" Si la respuesta es no (porque tiene movimiento, brillo o decoración con emojis), elimínalo. Una herramienta normal se parece a una terminal, una aplicación de notas o un chat mínimo, no a una página de aterrizaje.

Modo de voz:

  • Usa Web Speech API (SpeechRecognition + SpeechSynthesis) — gratuita, integrada en el navegador
  • Botón de alternancia para cambiar entre modo texto y voz
  • En modo voz: el micrófono escucha continuamente, voz → texto → enviar, respuesta → hablar
  • La calidad de voz se limita a lo que Chrome en Android proporciona; no prometas "voz de Ultron"

Preferencias de flujo de trabajo (específicas del usuario)

Preguntar antes de instalar paquetes: Antes de ejecutar pip install o apt install, pregunta primero al usuario. Esto no es negociable.

Mostrar progreso continuamente: Si una tarea lleva más de unos minutos, muestra resultados intermedios. No desaparezcas durante largos períodos sin salida. Si estás atascado en algo, dilo y explica qué estás intentando.

Sin lecciones de seguridad: El usuario entiende los riesgos de las claves API. No des lecciones sobre seguridad de claves, exposición o mejores prácticas a menos que te lo pidan explícitamente. Ellos ya proporcionaron su propia explicación cuando fue relevante.

Preferir soluciones funcionales sobre arquitectura perfecta: Primero haz que algo funcione. La prueba de la clave NaraRouter lo demostró: prueba la clave, encuentra un modelo funcional, configúralo. No planifiques en exceso.

El almacenamiento es volátil: haz una copia de seguridad en GitHub temprano: En Termux/Android, el almacenamiento de la aplicación (~/sayip/storage/) puede borrarse al reiniciar el servidor, reiniciar Termux o reiniciar el dispositivo. Después de configurar las claves API y la configuración, sube el proyecto a un repositorio de GitHub mediante SSH inmediatamente para poder recuperar la configuración. Durante la sesión, no asumas que storage/ sobrevive a un reinicio; verifícalo después de cualquier reinicio. El truco de SSH a través del puerto 443 (consulta references/termux-git-ssh.md) suele ser necesario porque las redes móviles bloquean el puerto 22.

El modo de voz debe probarse de extremo a extremo antes de declararlo terminado: La conexión de Web Speech API en chat.js (setupVoice, startListening, speakText) no es lo mismo que el modo de voz funcionando. Pruébalo hablando realmente al micrófono del teléfono y escuchando la respuesta hablada. Chrome en Android es el navegador objetivo; eso es lo que importa, no Chrome de escritorio.

Depuración

El puerto 5000 ya está en uso:

fuser -k 5000/tcp  # Mata lo que esté en el puerto 5000
# O usa ss para comprobar:
ss -tlnp | grep 5000

La aplicación Flask no arranca / se bloquea:

  • Comprueba la sintaxis: python3 -c "import py_compile; py_compile.compile('app.py', doraise=True)"
  • Comprueba si el puerto está libre antes de iniciar
  • Ejecuta con timeout 15 python3 app.py para ver la salida de inicio sin bloquearse

La API devuelve 401/403/payment_required:

  • La clave puede ser inválida, haber expirado o el modelo no estar en tu plan
  • Prueba la clave directamente con curl antes de configurarla en la aplicación
  • Revisa /v1/models para ver qué modelos incluye realmente tu plan

Timeouts en pip install:

  • Termux en móvil puede tener una red lenta/no confiable para paquetes grandes
  • Usa alternativas de la biblioteca estándar cuando sea posible (urllib en lugar de requests)
  • Si debes instalar, usa pip3 install --timeout 30 <paquete> para fallar rápido

Los procesos Flask en segundo plano eliminados a la fuerza dejan el puerto ocupado brevemente:

  • Después de pkill -9 -f "[p]ython3 app.py", espera 2-3s antes de reiniciar.
  • Si obtienes "Address already in use", el proceso anterior no ha liberado completamente el socket; espera más tiempo.

Antes de reiniciar el servidor Flask para aplicar cambios de código, mata primero el proceso anterior:

  • Patrón: pkill -9 -f "[p]ython3 app.py" && sleep 3 && cd ~/sayip && python3 app.py &
  • Omitir el kill provoca "Address already in use" y la nueva instancia no arrancará.
  • Siempre verifica con curl -s --max-time 3 http://127.0.0.1:5000/api/health después del reinicio.

Cuando el usuario informe que la web está rota / el JS tiene errores / no funciona, diagnostica en este orden:

  1. Confirma que el servidor está funcionando: curl -s --max-time 3 http://127.0.0.1:5000/api/health
  2. Comprueba que los archivos estáticos devuelvan 200: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/static/style.css (y app.js, chat.js, voice.js, settings.js)
  3. Comprueba qué HTML sirve realmente el servidor: curl -s http://127.0.0.1:5000/ | grep -E 'static/|style.css|app.js|chat.js|voice.js' — confirma que las rutas en el HTML coinciden con lo que Flask sirve
  4. Si los archivos estáticos devuelven 404 pero los enlaces HTML muestran rutas relativas como style.css en lugar de /static/style.css, ese es el error: corrige ambas plantillas para que usen el prefijo /static/
  5. Lee los archivos JS y verifica cada llamada a document.querySelector / getElementById / .querySelector contra lo que realmente contiene el HTML; un elemento faltante bloquea todo el script
  6. Comprueba fallos por referencia nula: el("hamburger") en una página que no tiene un elemento hamburger, messagesEl.querySelector(".wrap") cuando .wrap no existe, document.querySelector(".composer .wrap") cuando .composer no existe; todos estos bloquean la IIFE y desactivan todo el JS de la página
  7. Protege las búsquedas de elementos: var el = document.querySelector(selector); if (!el) return; antes de usar el resultado

Verifica las correcciones del frontend después de cualquier edición de HTML/plantilla comprobando lo que realmente sirve el servidor:

  • Un curl a la URL de la página y grep para la ruta estática (/static/style.css, /static/js/app.js) confirma que la corrección se aplicó.
  • Un curl directo a la URL del archivo estático con -o /dev/null -w '%{http_code}' confirma que devuelve 200.
  • No asumas que una edición de HTML está activa hasta que ambas comprobaciones pasen contra el servidor en ejecución.

La causa más común de "todo el JS está roto" en este proyecto:

  • Plantillas HTML que usan rutas estáticas relativas (style.css, app.js, chat.js) en lugar del prefijo /static/ de Flask (/static/style.css, /static/js/app.js, /static/js/chat.js).
  • El navegador solicita http://127.0.0.1:5000/style.css → 404. Sin CSS, sin JS cargado → la página no tiene estilo y no funciona.
  • Solución: corrige ambas plantillas para que usen /static/ como prefijo.

Referencias

  • references/nararouter-api.md — Detalles de integración de NaraRouter
  • references/nvidia-api.md — Patrones de Python para NVIDIA NIM (transmisión, pensamiento, visión)
  • references/termux-git-ssh.md — SSH a través del puerto 443 para móvil
  • references/termux-python.md — Consejos de Python en Termux

Individual skills in this repo

This repo contains 12 individual skills — each has its own dedicated page.

Skills relacionados