diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e1fdd22..f77b43d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -111,7 +111,6 @@ Static sections, SSR-rendered: | Server specs | `Server.astro` | None | | Privacy | `Privacy.astro` | None | | Model voting | `ModelVoting.astro` | None | -| Agents platform | `Agents.astro` | None | | Founder | `Founder.astro` | None | | Pricing | `Pricing.astro` | None | | CTA + waitlist form | `Cta.astro` + `WaitlistForm.tsx` | `client:load` | @@ -128,7 +127,6 @@ The community-tier signup page (`/community`) reuses `LanguageSwitcher.astro` an | `/docs/models` | Available model specs | | `/docs/examples` | Code snippets (Python, Node.js, curl) | | `/docs/api` | OpenAI-compatible API reference (endpoints, auth, rate limits) | -| `/docs/agents` | Agents platform reference (microVM specs, Hermes, lifecycle) | ### API Endpoints @@ -199,7 +197,6 @@ website/ │ │ ├── models.astro # Model specs │ │ ├── examples.astro # Code snippets │ │ ├── api.astro # OpenAI-compatible API reference -│ │ └── agents.astro # Agents platform reference │ ├── components/ │ │ ├── ui/ │ │ │ └── Button.astro # Primary/secondary button variants @@ -212,7 +209,6 @@ website/ │ │ │ ├── Server.astro # Hardware specs │ │ │ ├── Privacy.astro # Privacy guarantees section │ │ │ ├── ModelVoting.astro # Community model voting -│ │ │ ├── Agents.astro # Agents platform section (microVM, Hermes) │ │ │ ├── Founder.astro # Founder profile │ │ │ ├── Pricing.astro # Regional pricing cards │ │ │ ├── Cta.astro # CTA section (embeds WaitlistForm) diff --git a/i18n/en.json b/i18n/en.json index a943ebf..21bc3e2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -10,7 +10,7 @@ "notIncludedLabel": "Not included", "notIncluded": { "0": "Inference service access (GPUs)", - "1": "Agents and apps deployment", + "1": "Workspaces and app deployment", "2": "API key for model calls" }, "faqUpgradeQ": "How do I move to the inference plan later?", diff --git a/i18n/es.json b/i18n/es.json index 8e2b7ab..6bcfda8 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -10,7 +10,7 @@ "notIncludedLabel": "Qué NO incluye", "notIncluded": { "0": "Acceso al servicio de inferencia (GPUs)", - "1": "Despliegue de agentes y apps", + "1": "Workspaces y despliegue de apps", "2": "API key para llamadas a modelos" }, "faqUpgradeQ": "¿Cómo paso al plan de inferencia más adelante?", diff --git a/public/docs/agents/agent-running.png b/public/docs/agents/agent-running.png deleted file mode 100644 index 14a3ff3..0000000 Binary files a/public/docs/agents/agent-running.png and /dev/null differ diff --git a/public/docs/agents/agent-sync-tab.png b/public/docs/agents/agent-sync-tab.png deleted file mode 100644 index 031f150..0000000 Binary files a/public/docs/agents/agent-sync-tab.png and /dev/null differ diff --git a/public/docs/agents/create-agent-form.png b/public/docs/agents/create-agent-form.png deleted file mode 100644 index 0f5cafa..0000000 Binary files a/public/docs/agents/create-agent-form.png and /dev/null differ diff --git a/public/docs/agents/http-enabled-tab.png b/public/docs/agents/http-enabled-tab.png deleted file mode 100644 index 7d00123..0000000 Binary files a/public/docs/agents/http-enabled-tab.png and /dev/null differ diff --git a/public/docs/agents/http-result.png b/public/docs/agents/http-result.png deleted file mode 100644 index b3a3d26..0000000 Binary files a/public/docs/agents/http-result.png and /dev/null differ diff --git a/public/docs/agents/obsidian-community-plugins.png b/public/docs/agents/obsidian-community-plugins.png deleted file mode 100644 index 30e183a..0000000 Binary files a/public/docs/agents/obsidian-community-plugins.png and /dev/null differ diff --git a/public/docs/agents/obsidian-couchdb-form.png b/public/docs/agents/obsidian-couchdb-form.png deleted file mode 100644 index e881275..0000000 Binary files a/public/docs/agents/obsidian-couchdb-form.png and /dev/null differ diff --git a/public/docs/agents/obsidian-detect-fix.png b/public/docs/agents/obsidian-detect-fix.png deleted file mode 100644 index e972e36..0000000 Binary files a/public/docs/agents/obsidian-detect-fix.png and /dev/null differ diff --git a/public/docs/agents/obsidian-livesync-installed.png b/public/docs/agents/obsidian-livesync-installed.png deleted file mode 100644 index 577cd59..0000000 Binary files a/public/docs/agents/obsidian-livesync-installed.png and /dev/null differ diff --git a/public/docs/agents/obsidian-livesync-preset.png b/public/docs/agents/obsidian-livesync-preset.png deleted file mode 100644 index 502b0bd..0000000 Binary files a/public/docs/agents/obsidian-livesync-preset.png and /dev/null differ diff --git a/public/docs/agents/obsidian-local-notes.png b/public/docs/agents/obsidian-local-notes.png deleted file mode 100644 index be233ee..0000000 Binary files a/public/docs/agents/obsidian-local-notes.png and /dev/null differ diff --git a/public/docs/agents/obsidian-remote-config.png b/public/docs/agents/obsidian-remote-config.png deleted file mode 100644 index f4adc93..0000000 Binary files a/public/docs/agents/obsidian-remote-config.png and /dev/null differ diff --git a/public/docs/agents/telegram-hermes-chat.jpg b/public/docs/agents/telegram-hermes-chat.jpg deleted file mode 100644 index d5379d3..0000000 Binary files a/public/docs/agents/telegram-hermes-chat.jpg and /dev/null differ diff --git a/public/docs/agents/telegram-nginx-setup.jpg b/public/docs/agents/telegram-nginx-setup.jpg deleted file mode 100644 index 0ae8204..0000000 Binary files a/public/docs/agents/telegram-nginx-setup.jpg and /dev/null differ diff --git a/public/docs/agents/telegram-read-notes.jpg b/public/docs/agents/telegram-read-notes.jpg deleted file mode 100644 index 38d7a94..0000000 Binary files a/public/docs/agents/telegram-read-notes.jpg and /dev/null differ diff --git a/src/content/docs-es/agent-setup.mdx b/src/content/docs-es/agent-setup.mdx index ebd74b0..d833c39 100644 --- a/src/content/docs-es/agent-setup.mdx +++ b/src/content/docs-es/agent-setup.mdx @@ -85,7 +85,7 @@ Cada página de esta sección tiene la misma forma: qué necesitas, la configura name: 'Hermes', href: '/es/docs/hermes', kind: 'Mensajería y terminal', - note: 'El agente de Nous Research. Desplegado en NaN Cloud sale conectado de fábrica.', + note: 'El agente de Nous Research, en tus canales de mensajería y en la terminal.', }, { name: 'Otras herramientas', diff --git a/src/content/docs-es/agents.md b/src/content/docs-es/agents.md deleted file mode 100644 index f5ff763..0000000 --- a/src/content/docs-es/agents.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Agentes -description: "Despliega agentes de IA en una máquina privada en la nube aislada: Hermes, terminal web, subida de ficheros y observabilidad." -order: 19 -group: Guías ---- - -# Agentes. - -NaN Cloud te permite desplegar agentes de IA en tu propia **máquina privada en la nube**: una máquina virtual ligera con su propio kernel, su propio sistema de ficheros y acceso root completo. Aislada del host y del resto de miembros. El primer tipo de agente disponible es **Hermes**. - -## Arquitectura - -Cada agente corre dentro de su propia máquina aislada. En vez de compartir el kernel del host (como haría un contenedor normal), arranca con su propio kernel de Linux. La máquina tiene su propio disco persistente de 20 GiB. Todo lo que hagas dentro (`apt install`, `pip install`, cambios en `/etc`, ficheros que subas) vive en ese disco y sobrevive a los reinicios. - -El apagado es *limpio*: cuando reinicias o borras el agente, el sistema fuerza un `sync` y espera a que el disco termine de volcarse antes de parar la máquina. Sin corrupción. - -## Hermes - -Hermes es un agente de IA conversacional que se conecta a Telegram. Puedes chatear con él, pedirle que gestione notas, que ejecute comandos en su entorno, que genere webs y bastante más. - -### 1. Crea un bot de Telegram - -Necesitas un bot de Telegram. Abre Telegram, busca [@BotFather](https://core.telegram.org/bots/tutorial#obtain-your-bot-token) y sigue las instrucciones para crear uno nuevo. Copia el token que te dé. - -### 2. Crea el agente - -Entra en [cloud.nan.builders/agents/new](https://cloud.nan.builders/agents/new) y rellena: nombre, tipo (Hermes), el token de Telegram, el modelo y, opcionalmente, un *soul* (system prompt) que defina la personalidad de tu agente. - -![Formulario de creación de agente](/docs/agents/create-agent-form.png) - -### 3. Espera a que esté Running - -Después de crear el agente, espera unos 30 segundos a que arranque la máquina, se prepare su disco por primera vez y se siembre el sistema de ficheros. El estado pasa a `Running` y Hermes a `Ready`. - -### 4. Habla con tu agente - -Busca tu bot en Telegram y mándale un mensaje. Hermes responderá con el modelo que hayas configurado. - -![Conversación con Hermes en Telegram](/docs/agents/telegram-hermes-chat.jpg) - -> **Tu agente está listo.** -> Con estos 4 pasos ya tienes Hermes funcionando. Lo que viene a continuación son funciones adicionales del panel del agente: terminal web, subida de ficheros, observabilidad, exposición HTTP, la UI de Hermes y la gestión de variables de entorno. - -## Console: terminal web - -La pestaña **Console** abre una terminal interactiva (`bash --login`) dentro de la máquina de tu agente, sin necesidad de configurar SSH. El stream va por WebSocket con xterm.js: se redimensiona sola al ajustar el panel, tiene una pastilla de estado arriba a la derecha y un botón de reconexión por si se cae la sesión. - -Casos de uso típicos: - -- Instalar paquetes: `apt update && apt install -y nginx` -- Revisar los logs internos del agente -- Mover a su sitio los ficheros que hayas subido -- Usar `htop`, `df -h`, `journalctl`, etc. - -> **Límites operativos** -> 1 sesión simultánea por agente · 10 min de timeout por inactividad · 30 min de duración máxima por sesión. - -## Files: subida de ficheros - -La pestaña **Files** permite subir ficheros a la máquina del agente arrastrándolos o desde el selector. Admite varios a la vez, con cola secuencial y barra de progreso en vivo con MiB/s. Los ficheros aterrizan en `/persist/uploads/` y desde ahí puedes moverlos con la Console. - -- Tamaño máximo: **200 MiB** por fichero. -- Transporte: WebSocket con trozos de 256 KiB y backpressure de extremo a extremo. -- El nombre del fichero se sanea en servidor (sin path traversal). -- Listado en vivo de los ficheros subidos (se refresca cada 5s). - -## Observabilidad - -La pestaña **Observability** agrupa tres sub-pestañas: - -- **Logs**: stream en vivo del stdout y stderr del agente por WebSocket. Búfer de las últimas 500 líneas en el cliente. -- **Events**: eventos del ciclo de vida del agente (planificación, descarga de imágenes, reinicios, back-offs...) con tipo, motivo, mensaje, antigüedad y recuento. Se refresca solo cada 15s. -- **Metrics**: consumo real de CPU, RAM y disco frente a los límites configurados. La CPU y la RAM se miden desde fuera de la máquina y el disco desde dentro. Se refresca cada 10s. - -## Web: exposición pública - -La pestaña **Web** tiene dos sub-pestañas para exponer servicios HTTP del agente: - -### HTTP - -Cualquier servicio que tu agente sirva por HTTP (nginx, una API, un sitio estático) lo puedes exponer públicamente. Por ejemplo, pídele a Hermes que instale nginx con una página HTML propia: - -![Pidiendo a Hermes que instale nginx con una página HTML propia](/docs/agents/telegram-nginx-setup.jpg) - -En la pestaña **Web → HTTP**, pulsa **Enable HTTP**. Por defecto se expone el puerto `80`; si tu servicio escucha en otro, indícalo en **Container Port**. La plataforma genera una URL pública en `*.apps.nan.builders`. - -![Web generada por Hermes vista desde la URL pública](/docs/agents/http-result.png) - -### La UI de Hermes - -Hermes incluye una UI web ligera ([nesquena/hermes-webui](https://github.com/nesquena/hermes-webui)) que corre siempre dentro del agente. Desde **Web → Hermes UI** puedes activar el acceso externo: la plataforma genera una URL del tipo `webui--.apps.nan.builders`, protegida por una contraseña por agente que se muestra en el panel. - -## Variables de entorno - -La pestaña **Env** te permite añadir, editar y borrar variables de entorno del agente sin tener que redesplegarlo tú. Útil para inyectar API keys de terceros, configurar el comportamiento de Hermes, etc. - -Hay dos variables **protegidas** (solo se pueden editar, no borrar): `OPENAI_API_KEY` (tu key del clúster, que gestiona la plataforma) y `TELEGRAM_BOT_TOKEN`. El resto las puedes crear, editar o borrar libremente. - -## Recursos y límites - -La máquina de cada agente se aprovisiona con: - -| Recurso | Request | Límite | -|---|---|---| -| CPU | 0,2 vCPU | 1 vCPU | -| RAM | 512 MiB | 2 GiB | -| Disco | (sin request) | 20 GiB (persistente) | - -La CPU y la RAM son los límites máximos de la máquina; el consumo real suele quedar muy por debajo. El disco es persistente: todo lo que instales o modifiques (paquetes, ficheros, configuraciones) se conserva entre reinicios. Si el disco se llena (por encima del 90%), libéralo desde la Console (`du -sh /persist/*`). - -> **Límite actual** -> Ahora mismo cada miembro puede desplegar **1 agente**. Este límite se ampliará en versiones futuras. diff --git a/src/content/docs-es/apps.md b/src/content/docs-es/apps.md index 09529d0..76c9be4 100644 --- a/src/content/docs-es/apps.md +++ b/src/content/docs-es/apps.md @@ -1,7 +1,7 @@ --- title: Apps description: Despliega tus apps desde GitHub a NaN Cloud en minutos. -order: 20 +order: 19 group: Guías --- diff --git a/src/content/docs-es/hermes.mdx b/src/content/docs-es/hermes.mdx index bb9dbda..527c8b8 100644 --- a/src/content/docs-es/hermes.mdx +++ b/src/content/docs-es/hermes.mdx @@ -1,6 +1,6 @@ --- title: Hermes -description: El agente de Nous Research, con los modelos de NaN. En tu máquina o desplegado en NaN Cloud. +description: El agente de Nous Research, con los modelos de NaN, a través de su proveedor compatible con OpenAI. order: 14 group: Configurar tu agente --- @@ -13,21 +13,9 @@ import Callout from '../../components/docs/Callout.astro'; [Hermes](https://hermes-agent.nousresearch.com/) es el agente de Nous Research, de código abierto y con licencia MIT. No es un agente de código como el resto de esta sección: vive en tus canales de mensajería (Telegram, Discord, Slack, WhatsApp, Signal, correo) además de en la terminal, recuerda lo que hablasteis, se agenda tareas y delega en subagentes. -Hay dos formas de tenerlo, y la primera no requiere configurar nada. +## Conéctalo a NaN -## Camino 1: desplegado en NaN Cloud - -NaN Cloud despliega Hermes por ti en una **máquina privada en la nube propia**, y sale conectado al clúster de fábrica: la plataforma le inyecta tu clave y no tienes que tocar ninguna configuración de proveedor. - - -Creas el agente desde el panel, eliges modelo, le das el token de tu bot de Telegram y ya está hablando. Está explicado paso a paso en [Agentes](/es/docs/agents), con capturas. - - -Además tienes terminal web, subida de ficheros, logs y métricas, y puedes exponerlo en una URL pública. Todo eso solo lo tienes por este camino. - -## Camino 2: en tu propia máquina - -Si prefieres correrlo tú, Hermes trae un proveedor llamado **`custom`**, pensado para cualquier endpoint compatible con OpenAI. Es el que usa NaN. +Hermes trae un proveedor llamado **`custom`**, pensado para cualquier endpoint compatible con OpenAI. Es el que usa NaN. El [CLI de NaN](/es/docs/nan-cli) te lo configura: encuentra tu Hermes, le @@ -94,7 +82,6 @@ Si lo vas a usar para mandarle fotos por Telegram, cualquiera sirve: todos los m
-- **Los dos caminos no se mezclan.** El Hermes de NaN Cloud se configura desde el panel y el tuyo desde `hermes setup`. Si tienes los dos, son dos agentes distintos con dos memorias distintas. - **El proveedor `custom` no autodescubre los modelos.** Como no hay una lista fija detrás, escribe el id exactamente como aparece en [Elige tu modelo](/es/docs/choose-a-model). - **Un agente que vive en tus mensajes consume a su ritmo.** No es una sesión que abres y cierras: si le das tareas agendadas, gasta cuota mientras tú no miras. Revísala en [cloud.nan.builders](https://cloud.nan.builders/). diff --git a/src/content/docs/agent-setup.mdx b/src/content/docs/agent-setup.mdx index 2db131c..4834640 100644 --- a/src/content/docs/agent-setup.mdx +++ b/src/content/docs/agent-setup.mdx @@ -85,7 +85,7 @@ Every page in this section has the same shape: what you need, the exact configur name: 'Hermes', href: '/docs/hermes', kind: 'Messaging and terminal', - note: 'The Nous Research agent. Deployed on NaN Cloud it comes connected out of the box.', + note: 'The Nous Research agent, in your messaging channels and terminal.', }, { name: 'Other tools', diff --git a/src/content/docs/agents.md b/src/content/docs/agents.md deleted file mode 100644 index c031a4b..0000000 --- a/src/content/docs/agents.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Agents -description: "Deploy AI agents in an isolated private cloud machine: Hermes, web terminal, file uploads, and observability." -order: 19 -group: Guides ---- - -# Agents. - -NaN Cloud lets you deploy AI agents in your own **private cloud machine**: a lightweight virtual machine with its own kernel, its own filesystem, and full root access. Isolated from the host and from other members. The first available agent type is **Hermes**. - -## Architecture - -Each agent runs inside its own isolated machine. Instead of sharing the host kernel (like a regular container), it starts with its own Linux kernel. The machine has its own 20 GiB persistent disk. Everything you do inside — `apt install`, `pip install`, edits to `/etc`, files you upload — lives on that disk and survives restarts. - -Shutdown is *graceful*: when you restart or delete the agent, the system forces a `sync` and waits for the disk to finish flushing before stopping the machine. No corruption. - -## Hermes - -Hermes is a conversational AI agent that connects to Telegram. You can chat with it, ask it to manage notes, run commands in its environment, generate websites, and much more. - -### 1. Create a Telegram bot - -You need a Telegram bot. Open Telegram, search for [@BotFather](https://core.telegram.org/bots/tutorial#obtain-your-bot-token) and follow the instructions to create a new bot. Copy the token it gives you. - -### 2. Create the agent - -Go to [cloud.nan.builders/agents/new](https://cloud.nan.builders/agents/new) and fill in: name, type (Hermes), the Telegram token, model, and optionally a *soul* (system prompt) that defines your agent's personality. - -![Agent creation form](/docs/agents/create-agent-form.png) - -### 3. Wait for it to be Running - -After creating the agent, wait ~30 seconds for the machine to start, prepare its disk for the first time, and seed the filesystem. The status changes to `Running` and Hermes to `Ready`. - -### 4. Chat with your agent - -Find your bot on Telegram and send it a message. Hermes will respond using the model you configured. - -![Hermes conversation on Telegram](/docs/agents/telegram-hermes-chat.jpg) - -> **Your agent is ready.** -> With these 4 steps you already have Hermes running. What follows below are additional agent panel features: web terminal, file uploads, observability, HTTP exposure, Hermes UI, and environment variable management. - -## Console — web terminal - -The **Console** tab opens an interactive terminal (`bash --login`) inside your agent's machine, without needing to configure SSH. The stream runs over WebSocket with xterm.js: auto-resize when you adjust the panel, status pill in the top right, and a reconnect button if the session drops. - -Typical use cases: - -- Install packages: `apt update && apt install -y nginx` -- Inspect the agent's internal logs -- Move files you've uploaded to their final location -- Use `htop`, `df -h`, `journalctl`, etc. - -> **Operational limits** -> 1 simultaneous session per agent · 10 min idle timeout · 30 min max duration per session. - -## Files — file uploads - -The **Files** tab allows uploading files to the agent's machine with drag-and-drop or file picker. Multi-file, sequential queue, live progress bar with MiB/s. Files land in `/persist/uploads/` and from there you can move them with the Console. - -- Max size: **200 MiB** per file. -- Transport: WebSocket with 256 KiB chunks and end-to-end backpressure. -- Filename sanitized server-side (no path traversal). -- Live listing of uploaded files (refreshes every 5s). - -## Observability - -The **Observability** tab groups three sub-tabs: - -- **Logs** — live stream of the agent's stdout/stderr via WebSocket. Buffer of the last 500 lines on the client. -- **Events** — lifecycle events for the agent (scheduling, image pulls, restarts, back-offs...) with type, reason, message, age, and count. Auto-refresh every 15s. -- **Metrics** — actual CPU, RAM, and disk usage against configured limits. CPU and RAM are measured from outside the machine, disk usage from inside it. Refreshes every 10s. - -## Web — public exposure - -The **Web** tab has two sub-tabs for exposing HTTP services from the agent: - -### HTTP - -Any service your agent serves over HTTP (nginx, an API, a static site) you can expose publicly. For example, ask Hermes to install nginx with a custom HTML page: - -![Asking Hermes to install nginx with a custom HTML page](/docs/agents/telegram-nginx-setup.jpg) - -In the **Web → HTTP** tab, click **Enable HTTP**. By default port `80` is exposed; if your service listens on another port, specify it in **Container Port**. The platform generates a public URL at `*.apps.nan.builders`. - -![Website generated by Hermes visible from the public URL](/docs/agents/http-result.png) - -### Hermes UI - -Hermes includes a lightweight web UI ([nesquena/hermes-webui](https://github.com/nesquena/hermes-webui)) that always runs inside the agent. From **Web → Hermes UI** you can enable external access: the platform generates a URL like `webui--.apps.nan.builders` protected by a per-agent password shown in the panel. - -## Environment variables - -The **Env** tab lets you add, edit, and delete agent environment variables without redeploying the agent yourself. Useful for injecting third-party API keys, configuring Hermes behavior, etc. - -Two variables are **protected** (edit-only, no delete): `OPENAI_API_KEY` (your cluster key, managed by the platform) and `TELEGRAM_BOT_TOKEN`. The rest are free to create, edit, or delete. - -## Resources and limits - -Each agent's machine is provisioned with: - -| Resource | Request | Limit | -|---|---|---| -| CPU | 0.2 vCPU | 1 vCPU | -| RAM | 512 MiB | 2 GiB | -| Disk | — | 20 GiB (persistent) | - -CPU and RAM are the machine's maximum limits; actual usage is usually well below. Disk is persistent — everything you install or modify (packages, files, configurations) is preserved across restarts. If the disk fills up (90%+), free it from the Console (`du -sh /persist/*`). - -> **Current limit** -> Currently each member can deploy **1 agent**. This limit will be expanded in future versions. diff --git a/src/content/docs/apps.md b/src/content/docs/apps.md index 1dd74bf..c3124b8 100644 --- a/src/content/docs/apps.md +++ b/src/content/docs/apps.md @@ -1,7 +1,7 @@ --- title: Apps description: Deploy your apps from GitHub to NaN Cloud in minutes. -order: 20 +order: 19 group: Guides --- diff --git a/src/content/docs/hermes.mdx b/src/content/docs/hermes.mdx index 96ee3c2..97efc26 100644 --- a/src/content/docs/hermes.mdx +++ b/src/content/docs/hermes.mdx @@ -1,6 +1,6 @@ --- title: Hermes -description: The Nous Research agent, with NaN models. On your own machine or deployed on NaN Cloud. +description: The Nous Research agent, with NaN models, through its OpenAI-compatible provider. order: 14 group: Set up your agent --- @@ -13,21 +13,9 @@ import Callout from '../../components/docs/Callout.astro'; [Hermes](https://hermes-agent.nousresearch.com/) is the Nous Research agent, open source and MIT licensed. It is not a coding agent like the rest of this section: it lives in your messaging channels (Telegram, Discord, Slack, WhatsApp, Signal, email) as well as in the terminal, it remembers what you talked about, it schedules tasks for itself and it delegates to subagents. -There are two ways to have it, and the first one requires configuring nothing. +## Connect it to NaN -## Path 1: deployed on NaN Cloud - -NaN Cloud deploys Hermes for you in **its own private cloud machine**, and it comes connected to the cluster out of the box: the platform injects your key and you do not have to touch any provider configuration. - - -You create the agent from the panel, pick a model, give it your Telegram bot token, and it is already talking. It is explained step by step in [Agents](/docs/agents), with screenshots. - - -You also get a web terminal, file uploads, logs and metrics, and you can expose it on a public URL. All of that only exists on this path. - -## Path 2: on your own machine - -If you would rather run it yourself, Hermes ships a provider called **`custom`**, meant for any OpenAI-compatible endpoint. That is the one NaN uses. +Hermes ships a provider called **`custom`**, meant for any OpenAI-compatible endpoint. That is the one NaN uses. The [NaN CLI](/docs/nan-cli) configures this for you: it finds your Hermes, @@ -94,7 +82,6 @@ If you are going to send it photos over Telegram, any of them will do: every cha
-- **The two paths do not mix.** The NaN Cloud Hermes is configured from the panel and yours from `hermes setup`. If you have both, they are two different agents with two different memories. - **The `custom` provider does not discover models.** Since there is no fixed list behind it, write the id exactly as it appears in [Choose your model](/docs/choose-a-model). - **An agent that lives in your messages spends at its own pace.** It is not a session you open and close: if you give it scheduled tasks, it spends quota while you are not looking. Check it at [cloud.nan.builders](https://cloud.nan.builders/). diff --git a/src/middleware.ts b/src/middleware.ts index 8dd6f08..fc480ea 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -94,21 +94,28 @@ function hackatonRedirect(context: Parameters[0]): Response | * * `/docs/mcp` documentaba el servidor MCP de NaN, cuya única herramienta era la * búsqueda web; se retiraron los dos (el endpoint `/mcp` y `POST /v1/search`). - * Los enlaces siguen circulando, así que se mandan con 301 a la página desde la - * que se llegaba a ella, en vez de dejar un 404. Se conserva el prefijo `/es`. + * `/docs/agents` documentaba los agentes v1 que se gestionaban desde el panel; + * se retiraron y los sustituyen los Workspaces, que no tienen página de docs, así + * que se manda a la raíz de las docs (destino `''`). + * + * Los enlaces siguen circulando, así que se mandan con 301 en vez de dejar un + * 404. Se conserva el prefijo `/es`, y cualquier subruta (las capturas de + * `/docs/agents/*.png` que se enlazaron) va al mismo destino. */ -const REMOVED_DOCS: Readonly> = { +export const REMOVED_DOCS: Readonly> = { mcp: 'agent-setup', + agents: '', }; function removedDocsRedirect(context: Parameters[0]): Response | null { const { url } = context; - const m = /^(\/es)?\/docs\/([^/]+)\/?$/.exec(url.pathname); + const m = /^(\/es)?\/docs\/([^/]+)(?:\/.*)?$/.exec(url.pathname); if (!m) return null; const [, prefix = '', slug] = m; + if (!Object.hasOwn(REMOVED_DOCS, slug)) return null; const target = REMOVED_DOCS[slug]; - if (!target) return null; - return context.redirect(`${prefix}/docs/${target}${url.search}`, 301); + const path = target ? `${prefix}/docs/${target}` : `${prefix}/docs`; + return context.redirect(`${path}${url.search}`, 301); } /** diff --git a/src/tests/layouts/removedDocs.test.ts b/src/tests/layouts/removedDocs.test.ts new file mode 100644 index 0000000..8ffde2c --- /dev/null +++ b/src/tests/layouts/removedDocs.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from 'vitest'; +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, resolve } from 'node:path'; +import { REMOVED_DOCS } from '../../middleware'; + +/** + * Retired docs pages are redirected by the middleware (`REMOVED_DOCS`). These + * tests keep the redirect and the content in agreement: a retired slug must not + * come back as a live page (the redirect would shadow it), and nothing on the + * site should keep linking to it. + */ +const here = dirname(fileURLToPath(import.meta.url)); +const src = resolve(here, '../..'); +const root = resolve(src, '..'); + +function walk(dir: string): string[] { + return readdirSync(dir).flatMap((name) => { + const p = join(dir, name); + return statSync(p).isDirectory() ? walk(p) : [p]; + }); +} + +const slugs = Object.keys(REMOVED_DOCS); + +const copyFiles = [ + ...walk(join(src, 'content')), + ...walk(join(src, 'pages')), + ...walk(join(src, 'components')), + ...walk(join(src, 'layouts')), + ...walk(join(root, 'i18n')), +].filter((p) => /\.(md|mdx|astro|ts|tsx|json)$/.test(p)); + +describe('retired docs pages', () => { + test('agents (v1) is retired', () => { + expect(slugs).toContain('agents'); + }); + + test.each(slugs)('%s has no live page in either locale', (slug) => { + for (const dir of ['content/docs', 'content/docs-es']) { + for (const ext of ['md', 'mdx']) { + expect(existsSync(join(src, dir, `${slug}.${ext}`)), `${dir}/${slug}.${ext}`).toBe(false); + } + } + }); + + test.each(slugs)('nothing links to /docs/%s', (slug) => { + const link = new RegExp(`/docs/${slug}(?![\\w-])`); + const hits = copyFiles.filter((f) => link.test(readFileSync(f, 'utf8'))); + expect(hits.map((f) => f.slice(root.length + 1))).toEqual([]); + }); + + test('the retired v1 agents screenshots are gone', () => { + expect(existsSync(join(root, 'public/docs/agents'))).toBe(false); + }); + + test('no copy points members at the retired portal agents page', () => { + const hits = copyFiles.filter((f) => /cloud\.nan\.builders\/agents\b/.test(readFileSync(f, 'utf8'))); + expect(hits.map((f) => f.slice(root.length + 1))).toEqual([]); + }); + + test('no copy promises an agent deployed on NaN Cloud (the v1 path)', () => { + const v1Promise = /(deployed|desplegado) (on|en) NaN Cloud/i; + const hits = copyFiles.filter((f) => v1Promise.test(readFileSync(f, 'utf8'))); + expect(hits.map((f) => f.slice(root.length + 1))).toEqual([]); + }); +}); diff --git a/src/tests/legal/noInfraDisclosure.test.ts b/src/tests/legal/noInfraDisclosure.test.ts index 29523fb..dcb5840 100644 --- a/src/tests/legal/noInfraDisclosure.test.ts +++ b/src/tests/legal/noInfraDisclosure.test.ts @@ -45,11 +45,4 @@ describe('member-facing copy does not disclose infrastructure internals', () => const hit = text.match(FORBIDDEN); expect(hit?.[0] ?? null).toBeNull(); }); - - test('the agents guide describes the machine in product terms', () => { - const en = readFileSync(join(src, 'content/docs/agents.md'), 'utf8'); - const es = readFileSync(join(src, 'content/docs-es/agents.md'), 'utf8'); - expect(en).toContain('your own **private cloud machine**'); - expect(es).toContain('tu propia **máquina privada en la nube**'); - }); }); diff --git a/src/tests/lib/middleware.test.ts b/src/tests/lib/middleware.test.ts index d86bc5f..43f49bb 100644 --- a/src/tests/lib/middleware.test.ts +++ b/src/tests/lib/middleware.test.ts @@ -126,6 +126,39 @@ describe('middleware — páginas de docs retiradas', () => { expect(r.location).toBeNull(); expect(r.passedThrough).toBe(true); }); + + test('/docs/agents (agentes v1, retirados) lleva a /docs con 301', async () => { + const r = await run('https://nan.builders/docs/agents'); + expect(r.status).toBe(301); + expect(r.location).toBe('/docs'); + expect(r.passedThrough).toBe(false); + }); + + test('/docs/agents con barra final y en español', async () => { + expect((await run('https://nan.builders/docs/agents/')).location).toBe('/docs'); + expect((await run('https://nan.builders/es/docs/agents')).location).toBe('/es/docs'); + }); + + test('las subrutas de /docs/agents (capturas enlazadas) van al mismo destino', async () => { + const r = await run('https://nan.builders/docs/agents/create-agent-form.png'); + expect(r.status).toBe(301); + expect(r.location).toBe('/docs'); + expect((await run('https://nan.builders/es/docs/agents/x/y')).location).toBe('/es/docs'); + }); + + test('no confunde páginas cuyo slug empieza por "agent"', async () => { + for (const href of ['https://nan.builders/docs/agent-setup', 'https://nan.builders/docs/agentsx']) { + const r = await run(href); + expect(r.location).toBeNull(); + expect(r.passedThrough).toBe(true); + } + }); + + test('una clave heredada de Object no se trata como página retirada', async () => { + const r = await run('https://nan.builders/docs/constructor'); + expect(r.location).toBeNull(); + expect(r.passedThrough).toBe(true); + }); }); describe('middleware — cabeceras de seguridad', () => {