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

@junyoung-kim/reins

v0.1.4

Published

Keep your AI coding agent on a leash, from your phone — a headless TUI (npx @junyoung-kim/reins)

Readme

reins — keep your AI coding agent on a leash, from your phone

Read in other languages: 한국어

You started Claude Code (or Codex / Gemini CLI) on your computer, then walked away. reins lets you watch that session, type into it, and approve what it does — from your phone. It hosts your computer's terminal and links it to the phone app through a cloud relay (or your LAN). Scan a QR once and you're paired.

npx @junyoung-kim/reins

That's it. A pairing QR appears in your terminal — scan it with the phone app and your shell is now mirrored to your pocket.


What you get

  • Mirror your terminal to your phone — see the live session, type, and approve the agent's actions remotely.
  • One command to hostnpx @junyoung-kim/reins, no GUI, no config files required.
  • Pair by QR — scan once. The QR is an HTTPS link, so a phone without the app installed lands on the install page instead of a dead end.
  • Keep it running after you log out — register reins as a background service so the connection survives an SSH disconnect or a reboot (details).
  • Manage machines and settings from the terminal — add/edit SSH or local machines, change the relay, all without leaving the TUI.
  • A full desktop GUI too, if you prefer windows over a terminal (below).

Install & run

You need Node.js ≥ 18. Then, in the real terminal of the computer you want to host from:

npx @junyoung-kim/reins                 # run once, no install
# or
npm i -g @junyoung-kim/reins && reins   # install globally, then just `reins`

A pairing QR shows up. The pairing URL is also printed as plain text right below it, so you can pair even when a camera can't reach the screen (remote SSH, a tiny terminal window) — just open the URL on your phone.

Keys

| Key | Action | |-----|--------| | q | quit | | r | reconnect to relay | | n | new QR | | c | show / hide the QR (URL text is always visible) | | m | machines (list / add / edit / delete) | | o | options (relay URL, port, timeouts) | | i | info (read-only diagnostics) | | a | toggle auto-start (run as a background service) | | v | verbose log (↑ / ↓ to scroll) |


Pairing — how it connects

  1. reins starts a small local server and connects to the cloud relay as a host.
  2. It shows a QR — routed through the cloud once the relay is up, or a LAN fallback QR before that. Either way it's an HTTPS pairing link (https://arv.juny-api.kr/pair?u=…), not a raw socket address.
  3. Scan it with the phone app. Your machine list and terminals appear on the phone, and you can type and approve from there.

The default relay is wss://juny-api.kr/relay. You can point reins at your own relay (see Configuration).


Keep it running after you log out

If you start reins over SSH and then close that SSH session, reins gets killed along with it — the host disappears and your phone can't reconnect. On Linux (systemd), reins can register itself as a background service that survives logout and reboot.

The easy way — press a in the TUI to turn auto-start on. (It enables boot start, then hands off to the background service when you press q to quit, so you never run two copies at once.)

Or from the command line:

npm i -g @junyoung-kim/reins                # a one-off `npx` cache path can't be a stable service
reins service install         # register + start in the background
reins service status          # is it installed / active / enabled?
reins service uninstall       # stop and remove

A couple of things to know:

  • Survive a full logout, not just reboot — run this once (needs root): sudo loginctl enable-linger <your-user>. reins service install reminds you if it's needed.
  • Put your relay secret where the service can find it — the service reads it from ~/.ai_remote_vibe_agent/.env (see Configuration). reins service install warns you if it's missing.
  • Run reins again while the service is up and you won't start a second copy — reins opens a small manager screen instead, showing service status, recent logs, and the pairing QR, with r restart · x stop · u disable · q quit.

macOS and Windows don't have this yet — reins service … will tell you it's Linux-only for now and exit cleanly, without breaking anything.


Managing machines & options

reins is more than a viewer — you can manage everything from the terminal:

  • Machines (m) — list, add (SSH or local; key or password), edit, delete. reins shares the same machine registry as the desktop app, so changes show up on both sides, and live-update when the phone edits the list.
  • Options (o) — change the relay URL (comma-separate for a fallback), the local port, and timeouts. A relay change reconnects right away; port / timeout changes apply on the next start.
  • Info (i) — a read-only screen with the pairing URL, relay status, local server, agent identity, and system info. Your relay secret is only ever shown as set or missing — never the value.

Configuration

reins works out of the box. To connect through the cloud relay (rather than LAN only), give it a relay secret. To use a different relay, override the URL.

| Variable | What it does | |----------|--------------| | MAIN_VITE_RELAY_AGENT_SECRET (or VIBE_RELAY_AGENT_SECRET) | Relay auth secret. Without it, only the LAN-fallback QR works. | | MAIN_VITE_RELAY_URL (or VIBE_RELAY_URL) | Use a different relay (comma-separate for fallbacks). Default: wss://juny-api.kr/relay. |

Set them as shell environment variables, or put them in a .env file:

# ./.env (next to where you run reins), or ~/.ai_remote_vibe_agent/.env
MAIN_VITE_RELAY_AGENT_SECRET=your-secret-here

reins looks for the nearest .env walking up from the current directory, then falls back to ~/.ai_remote_vibe_agent/.env. Your shell env always wins, and only the relay keys above are read from .env (nothing else is injected).

Running as a service? The service starts from /, so it can't find a .env next to you — it reads only ~/.ai_remote_vibe_agent/.env. Put your secret there.

Trust note: because .env is discovered from parent directories, anyone who can place a .env above where you run reins can change where your secret is sent. Don't run npx @junyoung-kim/reins inside a directory tree you don't trust. The boot log prints the actual relay target so you can confirm.

Settings (machines, install id, FCM tokens) are stored at ~/.ai_remote_vibe_agent/. The secret is never written into the package or the service unit — it stays in your environment.


Run your own relay server

reins pairs your phone and computer through a relay — a small WebSocket server that forwards traffic between the host and the phone. The default is wss://juny-api.kr/relay, but you can self-host your own in one command with vibe-remote-relay:

# generate a shared secret and start the relay (WebSocket, port 8091)
RELAY_AGENT_SECRET=$(openssl rand -base64 32) npx vibe-remote-relay

The relay speaks WebSocket only (no built-in TLS), so put a reverse proxy in front of it — e.g. Caddy: relay.example.com { reverse_proxy ws://localhost:8091 }.

Then point reins at it — set the base URL and the same secret (see Configuration):

# ~/.ai_remote_vibe_agent/.env
MAIN_VITE_RELAY_URL=wss://relay.example.com
MAIN_VITE_RELAY_AGENT_SECRET=your-secret-here   # must equal the relay's RELAY_AGENT_SECRET

reins appends /host/<token> to that base and authenticates with Authorization: Bearer <secret>; the phone connects to /mobile/<token>.

The two packages

| Package | Use it when | |---------|-------------| | vibe-remote-relay | Stand up the relay — a CLI / Docker one-liner, or embed it as a NestJS module (RelayModule.forRoot(...)) inside an existing app. | | vibe-remote-protocol | The v3 binary wire format (opcodes, encoders/decoders, message types) shared by relay, host, and phone — only needed if you build your own host or client. |

Full environment variables, Docker Compose, reverse-proxy configs (Caddy / nginx), and the URL / auth contract live in the vibe-remote-relay README.


Prefer a GUI? The desktop app

The same host also ships as a full desktop app (@arva/desktop, Electron + React) with a terminal view, machine list, a cockpit, and settings. Headless reins and the desktop app share the same engine and the same protocol — pick whichever fits the machine.

The phone side is a separate app (claude_code_mobile, Flutter) that talks to either host over the same WebSocket relay / SSH.


For developers

This repo is a pnpm monorepo (packages/*, apps/*):

ai_remote_vibe_agent/
├── apps/
│   ├── desktop/      @arva/desktop — Electron desktop host (GUI)
│   └── headless/     reins         — Ink headless TUI host  → has its own README
├── packages/
│   ├── core/         @arva/core    — createAgentCore: the shared SSH/PTY/WS/relay engine
│   └── shared/       @arva/shared  — protocol types · relay defaults (kept in sync with mobile)
└── docs/requests/    work requests / history (start from INDEX.md)

@arva/core has no Electron dependency — it takes its config/identity via DI, so the desktop (electron-store) and headless (JSON file) hosts inject their own backends. The types in @arva/shared must stay 1:1 with the mobile app ([mobile-impact] commits).

pnpm install                     # whole workspace + desktop electron-rebuild
pnpm dev                         # desktop dev (watch)
pnpm typecheck                   # tsc --noEmit across packages
pnpm test                        # vitest across packages (headless TUI tested with ink-testing-library)
pnpm lint                        # biome check
pnpm --filter @junyoung-kim/reins build        # headless → dist/cli.mjs (tsup)

The workspace's node-pty is electron-rebuild-ed for the desktop, so running pnpm --filter @junyoung-kim/reins dev (plain Node) inside the workspace can crash with an ABI mismatch. Use the clean-install smoke procedure in apps/headless/README.md to test the published form.

Releasing

  • Desktop: electron-builder, via GitLab CI (.gitlab-ci.yml, with the GitHub release.yml kept as fallback). Auto-update uses a self-hosted (arv) generic feed.
  • Headless (reins): pnpm --filter @junyoung-kim/reins publish. prepack builds dist and the tarball ships only dist — no source, secrets, or native modules. Full steps in apps/headless/README.md.

Contribution rules (UI tokens, TSDoc, file-size limits, i18n, cross-platform) live in CLAUDE.md and .claude/rules/.


Reference