npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ocremote

v2.1.2

Published

Control opencode on your computer from the OpenCode Remote iOS app: supervisor, pairing QR, relay and tunnel transports

Downloads

733

Readme

oc-remote

Plataformas. El companion requiere Node >= 22.13 y soporta macOS, Linux y Windows: npx ocremote --pair en cualquiera de los tres. El proceso se mantiene en primer plano y la terminal debe seguir abierta. En macOS, mDNS está activo por defecto (--no-mdns lo desactiva; en otros sistemas no se intenta). Los túneles (--tunnel cloudflare) descargan cloudflared para la plataforma correcta. Windows está corregido en código (rutas, .exe, ; en PATH, cloudflared .exe) pero no verificado en una máquina Windows real.

Primer arranque (2.1.1)

npm install -g opencode-ai
opencode auth login
npx ocremote --pair

--pair selecciona el relay alojado por defecto y no necesita VPN ni descubrimiento de red local. El proxy queda en loopback. El QR sólo se muestra cuando la invitación está registrada: abre la app, escanéalo y conserva la terminal abierta mientras la usas. Caduca en 15 minutos y es de un solo uso. Para mostrar otro QR de una instancia en marcha: npx ocremote pair.

La nueva app comprueba la firma del ordenador contra la clave fijada por el QR. Es necesario actualizar app, companion y relay juntos y volver a emparejar las entradas antiguas que no tengan esa clave.

Las secciones de proxy/túneles de abajo describen también el modo directo opcional. Con --relay o --pair, el valor predeterminado de --host es 127.0.0.1, el túnel queda desactivado y Bonjour no se anuncia salvo petición explícita.

Proceso Node sin dependencias externas (solo APIs nativas de Node 22+) que convierte un Mac en un host de opencode accesible desde la app iOS OpenCode Remote, con autenticacion por token, pairing por QR y notificaciones push opcionales.

 iOS ──token──► 0.0.0.0:4190  ┌──────────────┐
                              │  oc-remote    │  inyecta Basic interno
                              │  (proxy+auth) │──────────────► 127.0.0.1:4191
                              └──────────────┘                 opencode serve
                                QR / mDNS / ntfy                (solo loopback)
  • El companion genera un token propio (32 hex) y lo guarda en ~/.config/oc-remote/config.json (0600). Solo rota con --new-token.
  • Arranca opencode serve en 127.0.0.1:<opencode-port> con un password interno aleatorio distinto (OPENCODE_SERVER_PASSWORD) que se regenera en cada arranque y nunca sale del Mac: el companion lo inyecta en cada peticion upstream. La red solo ve el token del companion.
  • Escucha en 0.0.0.0:<port> y hace proxy streaming (SSE sin buffering) a http://127.0.0.1:<opencode-port>.

Requisitos

  • Node 22.13 o superior (node --version).
  • El binario opencode (por defecto ~/.opencode/bin/opencode).
  • Opcional: tailscale (acceso remoto), ntfy (push, no requiere instalar nada en el Mac), dns-sd (viene con macOS).

Arranque manual

node companion/oc-remote.mjs --dir /ruta/al/proyecto

Salida (stderr): hostname, URLs candidatas (LAN, Tailscale, --public-url), token, y el QR ASCII con el deep link de pairing (--print-qr esta activo por defecto; --no-print-qr lo desactiva).

Flags:

| Flag | Default | Descripcion | |---|---|---| | --dir <path> | cwd | Directorio de proyecto que usa opencode (debe existir) | | --port <n> | 4190 | Puerto publico del companion | | --opencode-port <n> | 4191 | Puerto loopback del opencode serve interno | | --host <ip> | 0.0.0.0 | Interfaz de escucha | | --token <str> | — | Fija el token (se persiste en config.json) | | --new-token | — | Rota el token y lo persiste | | --tunnel <mode> | auto | auto | cloudflare | tailscale | none (ver Acceso remoto) | | --ntfy <topic\|url> | — | Activa notificaciones push | | --public-url <url> | — | URL publica a anunciar (tu propio tunel; salta --tunnel) | | --no-auth | — | Desactiva auth (peligroso, avisa por stderr) | | --opencode <path> | ~/.opencode/bin/opencode | Binario de opencode | | --no-mdns | mDNS activo | No publicar _ocremote._tcp por Bonjour | | --print-qr / --no-print-qr | on | Imprimir el QR al arrancar | | --help | | Ayuda |

Variables de entorno: OCREMOTE_CONFIG_DIR (cambia ~/.config/oc-remote), OPENCODE_BIN (alternativa a --opencode), OCREMOTE_CLOUDFLARED (ruta a un binario cloudflared propio), OCREMOTE_TAILSCALE (ruta al CLI tailscale; útil para tests).

Subcomandos

| Comando | Qué hace | |---|---| | oc-remote doctor | Verifica toda la cadena: binario de opencode, token, puerto, auth, upstream, LAN, Tailscale/Funnel, endpoint público y ntfy. Salida ok/FAIL con el arreglo de cada fallo | | oc-remote status | Config, endpoint actual (de endpoint.json) y prueba de auth | | oc-remote funnel on\|off\|status | Activa/desactiva el Funnel de Tailscale (la primera vez imprime el link de aprobación). El proceso en primer plano lo adopta automáticamente | | oc-remote version | Versión |

Push (APNs) — /_ocremote/push

La app envía su token de APNs al companion con POST /_ocremote/push ({"token":"…","pairID":"…"}, autenticado con el token del companion). El companion lo reenvía al relay por el canal de control, que es quien despierta el teléfono cuando la app está cerrada. Si el relay no está configurado con APNs, el endpoint sigue respondiendo 200 y no pasa nada más.

Beacons de endpoint (auto-reparación)

Con --ntfy <topic|url>, además de las notificaciones push el companion publica un beacon con el endpoint actual: al arrancar, cada vez que cambia la URL pública, y como heartbeat cada 5 minutos. Formato (mensaje ntfy con tag ocremote-endpoint):

{"type":"endpoint","id":"1887884bfde3","name":"MacBook","version":"1.2.0",
 "url":"https://mac.tailnet.ts.net","urls":[{"url":"...","label":"tunnel"}],
 "provider":"funnel","auth":true,"port":4190,"ts":1789547880425}
  • id es un hash del token (nunca el token): sirve para que la app ignore beacons de otros Macs.
  • La app guarda esa lista de urls y prueba los candidatos en orden cuando la URL actual falla; si todos fallan, consulta el último beacon del topic (/json?poll=1) y se actualiza sola. El QR incluye el topic (ntfy=...) y la lista inicial (urls=...), así que un solo escaneo lo configura todo.
  • El beacon también se escribe en ~/.config/oc-remote/endpoint.json (0600) para oc-remote status/doctor.

Watchdog

  • opencode serve se relanza solo si muere (backoff exponencial), y un chequeo cada 30 s vía /global/health lo reinicia si queda colgado (3 fallos).
  • cloudflared se relanza solo; el Funnel se re-aplica si deja de servir.
  • El puerto del companion no cambia entre reinicios: la app solo necesita el endpoint actual, no un puerto nuevo.
  • Con auto, el companion se adapta en caliente: si aparece un Funnel activo pasa a la URL estable, y si desaparece vuelve al quick tunnel o a LAN/Tailscale.

Pairing

  1. QR del terminal: contiene el deep link ocremote://pair?v=1&name=<hostname>&url=http%3A%2F%2F<ip>%3A<port>&token=<token>.
  2. Pagina de pairing: http://127.0.0.1:4190/_ocremote/pair (solo loopback sin token; desde otra maquina requiere ?token=<token>). Muestra el QR como PNG, la URL, el token y botones de copiar.
  3. Manual: URL http://<ip>:<port> + token.

La app envia Authorization: Basic base64("opencode:"+token) (llama "token" a la password) y para SSE puede usar ?auth_token=<base64(opencode:token)>. El companion acepta ademas Authorization: Bearer <token> y ?token=<token>. token/auth_token se eliminan de la query antes de reenviar al upstream.

Payload del QR

ocremote://pair?v=1&name=<hostname>&url=http%3A%2F%2F<ip>%3A<port>&token=<token>

| Parametro | Significado | |---|---| | v | Version del esquema de pairing (1) | | name | os.hostname() del Mac, URL-encoded | | url | Base URL a la que conectarse, URL-encoded | | token | Token del companion, URL-encoded |

Endpoints propios

| Endpoint | Auth | Respuesta | |---|---|---| | GET /_ocremote/health | no | {"ok":true} (monitor local) | | GET /_ocremote/status | token | {name, version, directory, uptime, port, opencodePort, clients, tunnel} | | GET /_ocremote/pair | loopback o token | Pagina HTML con QR PNG, URL y token |

Todo lo demas se proxya a opencode. Los Upgrade/WebSocket responden 501 (PTY fuera de alcance). Metodos fuera de GET/HEAD/POST/PUT/PATCH/DELETE/OPTIONS responden 405. Errores de upstream responden 502 JSON.

Acceso remoto

--tunnel decide como se anuncia la URL (y cual va dentro del QR):

| Modo | Que hace | |---|---| | auto (default) | Si cloudflared ya esta instalado lo usa; si no, Tailscale si esta conectado; si no, solo LAN con un aviso | | cloudflare | Quick tunnel: descarga cloudflared la primera vez (a ~/.config/oc-remote/bin/), arranca el tunel y publica la URL HTTPS | | tailscale | Anuncia la IP de Tailscale (URL estable) | | none | Solo LAN |

Cloudflare quick tunnel (cero configuracion)

oc-remote --dir ~/Projects/my-app --tunnel cloudflare
# [oc-remote] cloudflare quick tunnel ready: https://calm-river-1234.trycloudflare.com

No necesita cuenta. La URL cambia en cada reinicio: para una URL fija usa Tailscale o un tunel con nombre propio. Si cloudflared muere, el companion lo relanza con backoff; cuando hay URL nueva reimprime el QR.

Tailscale (URL estable)

Es una VPN mesh: el trafico va cifrado y solo tus dispositivos entran, sin abrir puertos ni exponer nada a Internet.

brew install --cask tailscale   # o la app de la Mac App Store
tailscale up                    # login
tailscale ip -4                 # 100.x.y.z

Con --tunnel tailscale (o auto sin cloudflared) el companion imprime http://100.x.y.z:4190. Desde el iPhone con Tailscale activo:

  • por IP: http://100.x.y.z:4190
  • por MagicDNS (mas comodo): http://<nombre-maquina>.<tailnet>.ts.net:4190

Opcional: --host <tailscale-ip> para escuchar solo en la VPN. La primera vez macOS puede pedir permiso de firewall para node.

Tunel propio

cloudflared tunnel --url http://127.0.0.1:4190
# imprime una URL https://xxxx.trycloudflare.com
oc-remote --public-url https://xxxx.trycloudflare.com

Con --public-url esa URL se usa en el QR, en los logs y en el click de ntfy (y se salta --tunnel). El tunel expone el companion a Internet: el token es la unica barrera, con rate limit de 20 intentos fallidos por minuto y IP (respuesta 429 too_many_requests). Prefiere Tailscale para uso diario.

ntfy (opcional)

node companion/oc-remote.mjs --ntfy ocremote-raul-a8f3k2
  • permission.asked → opencode: permission required (priority 4, tags warning, click a la URL).
  • session.status busy seguido de session.idle → opencode: task finished.
  • Se abre una conexion SSE propia a /event con el Basic interno; si falla, se reintenta cada 5 s y nunca afecta al proxy.
  • Acepta un topic de ntfy.sh o una URL completa (self-hosted): --ntfy https://ntfy.midominio.dev/mi-topic.
  • Los topics de ntfy.sh son publicos: usa uno largo y aleatorio.

Seguridad

  • Token de 32 hex comparado con crypto.timingSafeEqual; el config se escribe 0600 (directorio 0700).
  • El opencode interno escucha solo en 127.0.0.1 con password aleatorio de 32 hex regenerado en cada arranque; la LAN no puede hablar con el directamente.
  • Sin TLS: no expongas el puerto a Internet directamente. Usa Tailscale (recomendado) o un tunel con HTTPS.
  • --no-auth deja el control de opencode al alcance de cualquiera en la red: usalo solo en loopback.
  • GET /config/providers y /provider.env exponen API keys de OpenCode a quien tenga el token: tratalo como un secreto.
  • La pagina /_ocremote/pair solo se sirve sin token a clientes loopback.

Troubleshooting

  • port 4190 is already in use: cambia --port (o --opencode-port). Comprueba con lsof -nP -iTCP:4190 -sTCP:LISTEN.
  • 401: token distinto (rota con --new-token y vuelve a emparejar), o el cliente manda Bearer/Basic mal formado. Prueba curl -H 'Authorization: Bearer <token>' http://127.0.0.1:4190/_ocremote/status.
  • opencode binary not found: pasa --opencode /ruta/al/opencode.
  • opencode did not become healthy within 30s: mira los logs con prefijo [opencode] en stderr; suele ser un puerto ocupado o falta de login.
  • El child muere: el companion lo relanza con backoff exponencial (1 s → 15 s).
  • mDNS: anuncio Bonjour _ocremote._tcp.local via dns-sd -R (el responder del sistema). Comprueba con dns-sd -B _ocremote._tcp local; desactiva con --no-mdns si no lo quieres. Si dns-sd no existe, solo avisa.
  • node --test companion/test/: en Node ≤ 22.13 el runner no expande directorios, por eso companion/test/package.json apunta a index.mjs, que importa la suite. Tambien puedes usar node --test companion/test/proxy.test.mjs.

Limitaciones

  • Sin WebSocket/PTY (501), sin TLS, sin rate limiting, sin HTTP/2.
  • Los clientes SSE se cuentan en status.clients mientras la conexion sigue abierta.
  • mDNS anuncia el servicio del companion (_ocremote._tcp), no el de opencode: el --mdns de opencode no se usa porque anunciaria el puerto interno.