Volver a la lista

Blog

30 segundos para conectar: cómo construimos la ruta de distribución de `npx tacticremote`

La historia técnica detrás de `npx tacticremote`, desde binarios compilados con Bun y un lanzador npm ligero hasta el guardado automático del QR y una primera ejecución más rápida.

Publicado Etiquetas: engineering / cli / distribution / onboarding
Blog

Sigue las novedades de producto e ingeniería de esta categoría.

Ver categoría

El objetivo era fácil de decir y sorprendentemente difícil de ejecutar: un usuario nuevo debería poder lanzar un comando, escanear un código QR y ver una terminal en vivo en su teléfono, todo en menos de 30 segundos.

Este artículo explica cómo lo conseguimos.

El problema

Antes de que existiera npx tacticremote, iniciar el servidor era un proceso de varios pasos que cambiaba según la plataforma:

  • macOS: descargar un DMG, montarlo, mover la app a Applications, abrirla y lidiar con Gatekeeper.
  • Linux / CI: clonar el repositorio, ejecutar npm install y averiguar cómo arrancar y mantener el servidor en ese entorno.

Para quienes trabajan en VMs de Linux remotas o dentro de contenedores Docker, esto era realmente incómodo. La ruta de clonar el repo requería git, Node.js y un entorno npm que pudiera acceder al registry. No había un comando único que pudieras pasarle a alguien y decirle "ejecuta esto".

Queríamos npx tacticremote → escanear QR → listo.

Por qué no simplemente npm install

El servidor tiene un conjunto pequeño de dependencias de runtime, pero npm install sigue generando fricción. Hoy ese paquete de runtime incluye ws, bonjour-service y qrcode-terminal.

La resolución de dependencias de npm tarda, y mientras tanto el servidor no hace nada útil. En entornos restringidos, como redes corporativas o CI contenedorizado, también puede fallar por configuración de proxy, mirrors del registry o permisos.

Más importante todavía, la ruta de CLI debe sentirse ligera. Si el paquete publicado llevara todo el árbol de runtime, la primera experiencia seguiría siendo "descargar JavaScript y luego arrancar el servidor", que no era el nivel de inicio que queríamos.

Así que la capa publicada en npm tenía que ser lo más delgada posible.

Enfoque de compilación con Bun

Compilamos el servidor en un binario único y autocontenido con Bun. El resultado es un ejecutable de unos 20 MB antes de comprimirlo con gzip, que incluye:

  • el runtime de Bun
  • todas las dependencias de runtime
  • todo el código fuente del servidor

Sin node_modules. Sin instalación separada de Bun. Sin resolución de dependencias al arrancar. Descargas un binario y lo ejecutas.

GitHub Actions (.github/workflows/build-cli.yml) construye cuatro destinos en cada release:

  • bun-darwin-arm64 - Mac con Apple Silicon
  • bun-darwin-x64 - Mac Intel
  • bun-linux-x64 - Linux x86_64
  • bun-linux-arm64 - Linux ARM (Raspberry Pi, AWS Graviton, etc.)

Cada binario se sube como un asset gzip de GitHub Release.

El wrapper delgado

El paquete npm publicado en npmjs.com no es el servidor en sí. Es un script lanzador de 124 líneas en bin/tacticremote.js. Esto es lo que hace realmente:

  1. Detecta el sistema operativo y la arquitectura de CPU con process.platform y process.arch.
  2. Resuelve el nombre correcto del binario para esa plataforma.
  3. Busca un binario en caché en ~/.tacticremote/bin para la versión actual.
  4. Si no existe caché, descarga un binario gzip desde GitHub Releases, lo descomprime y lo escribe en disco con permisos de ejecución.
  5. Lanza el binario, hereda stdio y reenvía señales como SIGINT y SIGTERM.
const binaryName = `tacticremote-${platform}-${arch}`
const url = `https://github.com/TacticSpaceTech/TacticRemote/releases/download/cli-v${version}/${binaryName}.gz`
const cacheDir = path.join(os.homedir(), '.tacticremote', 'bin')
// Descargar, gunzip, cachear localmente, chmod +x, spawn

La primera ejecución descarga el asset del release y lo guarda en caché local. Las siguientes reutilizan el binario en caché para esa versión del paquete npm. La versión del binario queda ligada a la versión del paquete npm que se está ejecutando.

Este patrón no es nuevo. esbuild lo popularizó, turbo lo usa y prisma hace algo similar. La idea es que npm sea un canal de distribución, no un entorno de compilación. Úsalo para entregar un descargador, no un árbol de dependencias.

Separación de package.json

Mantenemos dos manifiestos de paquete en paralelo:

  • package.json - configuración de desarrollo. Contiene todos los devDependencies (Jest, ESLint, Prettier), scripts de test, scripts de lint y el conjunto completo de dependencias usado al desarrollar localmente.
  • package.npm.json - manifiesto ligero de publicación. Apunta bin a tacticremote.js y omite el árbol de dependencias de runtime.

Esa separación mantiene honestos el flujo de desarrollo y el paquete publicado. También deja un coste claro de mantenimiento: los metadatos que se solapan entre ambos manifiestos tienen que seguir alineados.

Ese traspaso sigue siendo un punto frágil. El repositorio todavía no tiene una validación en tiempo de publicación que garantice que el cambio se hace siempre bien, así que automatizarlo en CI es la siguiente mejora más obvia.

El flujo de inicio en 30 segundos

Con el problema de distribución resuelto, revisamos cada paso del flujo de conexión para eliminar fricción.

0-5 segundos. El usuario ejecuta npx tacticremote en la terminal. En la primera ejecución, el shim detecta que no hay binario en caché y lo descarga. En ejecuciones posteriores, la comprobación de caché tarda milisegundos.

5-10 segundos. Arranca el binario del servidor. Detecta la IP local del equipo, genera una API key aleatoria e imprime un código QR directamente en la terminal usando caracteres Unicode de bloques. Sin navegador y sin UI web que navegar.

10-15 segundos. El usuario abre Tactic Remote en el teléfono. La pantalla principal muestra una tarjeta "Connect Your Computer" con el comando npx tacticremote y un botón "Scan QR". Al tocarlo, se abre la cámara.

15-25 segundos. El usuario escanea el código QR de la terminal. El QR codifica un payload JSON con la IP del servidor, el puerto y la API key. La app decodifica ese payload y guarda la configuración del servidor automáticamente: sin pantalla "Add Server", sin introducir la IP a mano, sin copiar y pegar la API key.

25-30 segundos. El handshake de WebSocket se completa. La salida de la terminal empieza a fluir al teléfono. El indicador de conexión se vuelve verde.

El guardado automático del QR eliminó la mayor parte de la fricción. El flujo anterior requería tocar "Add Server", introducir la IP, introducir el puerto, introducir la API key, tocar Save y tocar Connect. Cinco toques y tres entradas manuales, cualquiera de las cuales podía fallar. El escaneo de QR comprime todo eso en un solo gesto.

Qué haríamos distinto

Hay algunas cosas subóptimas que aún no hemos corregido:

Todavía no hay verificación de firma del artefacto. El lanzador descarga por HTTPS, pero todavía no verifica el binario contra una firma o checksum publicado. Ese sigue siendo el hueco de integridad más importante del enfoque actual.

La transferencia entre manifiestos sigue necesitando automatización. Mantenemos un manifiesto de desarrollo y otro de publicación, pero el repo todavía necesita una protección más estricta en el paso de publicación para verificar que siguen sincronizados.

No hay mirror de respaldo. Si GitHub Releases no está disponible, el lanzador no tiene otra fuente desde la que descargar. Un mirror secundario haría la ruta de descarga más resistente.

El panorama general

La ruta de distribución se construyó sobre un principio: el usuario no debería tener que pensar en infraestructura. No necesita saber qué es Bun, qué arquitectura de CPU usa su máquina Linux ni cómo copiar una API key a mano. El comando inicia el flujo, el lanzador obtiene el binario correcto y el código QR transporta los detalles de conexión.

El servidor en sí, su arquitectura, su diseño de dependencia única y su modelo de seguridad, se explican en el artículo hermano One Dependency: Building a Production Server with Just the ws Package. Este artículo trata de poner ese servidor en manos de los usuarios rápidamente.

Si todavía no has probado el flujo de 30 segundos, entra en /download y pruébalo.


Para la historia de la arquitectura del servidor, consulta One Dependency: Building a Production Server with Just the ws Package. Para instrucciones de configuración, consulta la Getting Started guide.

Prueba Tactic Remote

Controla tus agentes de programación desde el móvil

Conéctate a Claude Code, Codex y otros agentes en tu Mac, Windows o Linux. Consulta el progreso y envía la siguiente instrucción desde el iPhone o iPad.