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.14

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.
  • Take it back at the keyboardreins attach puts the session you were driving from your phone right back in front of you, mid-command (below).
  • 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 | |-----|--------| | m | machines (list / add / edit / delete) | | o | options (relay URL, port, timeouts) | | i | info (read-only diagnostics) | | d | get apps (desktop / mobile download links) | | t | attach this terminal to a session (below) | | y | copy the pairing ws URL | | r | reconnect to relay | | n | new QR | | a | toggle auto-start (run as a background service) | | U | update reins to the latest version | | c | show / hide the QR (URL text is always visible) | | v | verbose log (↑ / ↓ to scroll) | | q | quit | | PgUp / PgDn | scroll the whole screen when the terminal is too short |

The current screen always lists its own keys at the bottom, so you never have to remember this table.

Small terminals and split panes: reins keeps the current screen and relay status pinned and shows ↑ / ↓ N · PgUp/PgDn when content does not fit. Form focus and confirmation dialogs are revealed automatically; arrow keys continue to control the active list, log, or selector. A normal 80×24 terminal is recommended, but shorter terminals remain usable through paging. If a remote keyboard has no Page keys, resize the pane as the universal fallback.


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).


Take the session back — reins attach

You were driving the agent from your phone on the train. Now you're back at the keyboard. reins attach puts that same session in front of you — same scrollback, same running command, nothing restarted.

Straight from the reins screen — press t. On the machines screen (m), put the cursor on one and press Enter. The TUI steps aside, that terminal becomes the session, and Ctrl-] d brings the TUI back.

Or from another terminal:

reins attach                 # the only machine — or pick one, if there are several
reins attach mymachine       # by name (or any unambiguous part of the id)
reins attach --list          # what's available

The screen redraws exactly as your phone last saw it, and you keep typing.

With more than one machine, attach shows a picker instead of making you copy a UUID. It tells you which sessions are actually live and who's holding them:

reins attach — pick a machine        5 machines · / to filter

    macbook-pro              local     ● live
  ▸ dev-server               ssh       ● live        phone is driving
    raspberry-pi             ssh       ○ no session
    build-box                ssh       ✕ error
    feat/login-redesign      worktree  ● live        another window

  ↑↓ move   enter attach   / filter   q quit

● live means there's already a screen to take over. ○ no session means attach will start one. Press / and type to filter by name or id.

Handing it back to the phone — press Ctrl-] p, or just open the session on the phone. This window drops to read-only and says so:

── phone took over ─────────────────────────────
  Your keystrokes now go to the phone, not here.
  T take it back    Ctrl-] d quit

Press T to take it back. If the phone was typing in the last 10 seconds, T asks once more before pulling it away — the same rule the desktop app uses. Go back and forth as often as you like.

It tells you how to leave, first thing. Attaching shows a short screen; press any key to drop into the session:

── dev-server — you are driving this terminal ──────────
  Ctrl-] d    detach — the session keeps running
  Ctrl-] p    hand the session back to the phone
  Ctrl-] ?    all keys

  Ctrl-C goes to the program, not to reins.

  press any key to continue      n  don't show this again

Press n once you know the keys. If you later forget them, reins attach --guide brings it back; --no-guide skips it just this once.

Ctrl-C does not end attach. It goes straight to the program, so pressing it to leave stops the agent you were running, not reins. The key to leave is Ctrl-] d. Hit Ctrl-C a few times in a row and reins says so in one line.

Keys while attached

| Key | Action | |-----|--------| | Ctrl-] d | detach — leave the session running, give this terminal back | | Ctrl-] p | hand the session to the phone | | Ctrl-] r | redraw the screen | | Ctrl-] ? | show these keys | | Ctrl-] Ctrl-] | send a literal Ctrl-] to the shell |

Ctrl-] is vim's tag-jump. If you live in vim, pick another prefix — reins attach --escape ^A — or --escape none to switch the prefix off entirely and send every key straight through.

If the agent restarts (reins service restart, or a self-update) the session doesn't die. attach waits, reconnects, and redraws. Press q if you'd rather not wait.

When it won't connect

| Message | What to do | |---------|-----------| | no agent is running on this computer | Start one — reins, or reins service install | | refused the attach handshake | That agent is older than your reins (no attach endpoint), or another program holds the port. Update the agent and restart it. | | is already attached from another window | Detach there with Ctrl-] d, or take it: reins attach <machine> --force | | the agent rejected this connection | The agent runs as a different user, so it reads a different session token. Run attach as that user (reins service status). |

If a crash ever leaves your terminal looking wrong — no echo, stuck colors, a cursor in the wrong place — run reset. reins restores the terminal on every exit path it controls, including Ctrl-C and a closed window, so you shouldn't need it often.

Exit codes, if you wrap attach in a script: 0 ok · 2 usage · 3 no agent · 4 auth · 5 busy · 6 no such machine · 7 version mismatch · 8 session ended.

attach needs a real terminal (it won't run through a pipe), and it doesn't open a port of its own — so it's fine to run while reins is already running on the same machine. Windows isn't verified yet; if the prefix key or resizing misbehaves there, --escape none is the workaround.


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 t attach · 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, and Enter to attach the highlighted one. 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.


Troubleshooting

The QR never appears / relay says disconnected. reins needs a relay secret to reach the cloud. Without one it still prints a LAN-fallback QR that works on the same Wi-Fi. See Configuration.

Phone paired, but the terminal is blank. Open the session again from the phone, or press Ctrl-] r if you're attached — both force a full redraw.

reins service … says it's Linux-only. It is, for now. On macOS and Windows, keep reins running in a terminal (or your own launchd / Task Scheduler entry).

Your terminal looks broken after a crash. Run reset.

Which reins am I actually running? If you have both a global install and a project copy, they can disagree. reins --version prints the one on your PATH; the pairing screen and i (info) show what the running host reports.


Source & contributing

reins is the headless host from the ai_remote_vibe_agent repo, which also builds the desktop app and shares one engine between them. Issues, build instructions, and contribution rules live there.


Reference