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

joinin

v1.0.6

Published

Realtime peer-to-peer shared TUI. Runs a single app in your terminal and lets friends connect over HyperDHT to see and type in the same session. No ports, no servers, no popups.

Downloads

373

Readme

joinin

Realtime, peer-to-peer shared terminal. Run a single TUI app and let friends connect over HyperDHT to see the same screen and type into the same session — no ports, no servers, no firewall config, no pop-up windows. Built on hyperssh (the holepunch project).

# You (the host) — runs with NO parameters:
npx joinin
# → prints an invite command to give a friend

# Your friend:
npx joinin --connect <seed>

Both of you now share one live terminal. Multiple friends can each run the connect command and join the same session at once.

Friends without a terminal can also join from a browser, no install — see Browser / GitHub Pages client below.


Why joinin

  • Realtime, not atomic. Unlike remote-shell tools that ship command output after the fact, joinin streams the PTY byte-for-byte in both directions, so server and every client see the exact same view and can type live.
  • No popups. The shared app runs right in your current terminal.
  • One app, one session. It runs a single named app directly (no shell wrapper). When the app exits, joinin closes.
  • Many clients, one session. Multiple people can connect and interact with the same PTY. Session lifecycle is modelled with an xstate state machine, so teardown happens exactly once no matter how clients join/leave or the app exits.
  • Encrypted P2P. HyperDHT gives peer discovery, NAT traversal (UDP holepunching) and end-to-end Noise encryption. The shared seed is the address + secret.

Usage

Share a specific app (server)

npx joinin htop                 # share a specific app directly
npx joinin vim                  # share another app
npx joinin --app "vim -c help"  # app with arguments
npx joinin --key <seed>         # fixed seed → stable invite command
npx joinin --shell              # share your login shell instead

You name the app and joinin runs that app directly (no shell wrapper) in your terminal, advertises it on HyperDHT, and prints the invite command. When the app exits, joinin closes. Give the invite command to a friend.

Join a session (client)

npx joinin --connect <seed>
# or, when the seed looks like a key:
npx joinin <seed>

bunx joinin ... is equivalent to npx joinin ....


Browser / GitHub Pages client

npx joinin --web htop     # also reachable from a browser over WebRTC

The host prints a second invite — a link, not a command:

https://anentrypoint.github.io/sharesies/#<seed>

Anyone who opens that link joins the same live session as CLI clients, straight from the browser: no install, no extension. Under the hood the browser client uses wireweave for a peer-to-peer RTCDataChannel, signaled over public nostr relays, and renders the shared PTY with xterm.js. The CLI server joins the same WebRTC room directly (via node-datachannel's native WebRTC binding) — there is no separate relay process to keep alive, and one seed is a single invite for both transports.

--web is opt-in: plain npx joinin <app> stays HyperDHT-only with zero extra native dependencies pulled in at install/run time.

--web needs a native binary. The WebRTC transport uses node-datachannel, whose prebuilt .node binary is fetched by its install script. If that step is skipped or fails (occasionally happens on a brand-new Node release), --web prints failed to start RTC transport: Cannot find module ... node_datachannel.node and falls back to HyperDHT-only. Fetch the prebuilt binary directly: cd node_modules/node-datachannel && npx prebuild-install -r napi (npm rebuild node-datachannel does not reliably re-fetch it on newer Node versions — it reports success while producing no binary). The plain (non---web) CLI never needs this.

Privacy. The GitHub Pages URL itself is public — anyone can load the page. What's private is the session: joining requires the invite seed, exactly like the CLI's --connect <seed>. The page never lists or discovers other sessions. Treat the seed like a password: whoever has it can see and type into your terminal.

Local development

npm run dev:web    # rebuild web/bundle.js on change
npm run build:web  # one-off production build

Then serve web/ with any static file server and open it with #<seed> matching a locally running npx joinin --web <app>.

NAT traversal tuning

The CLI server is usually the peer most likely sitting behind a restrictive NAT — a home router, carrier-grade NAT, a corporate firewall — since browser clients' own OS network stacks tend to be more permissive. --web goes beyond a plain polyfilled RTCPeerConnection: it constructs node-datachannel's native peer directly, unlocking tuning the standard WebRTC API doesn't expose:

npx joinin --web --rtc-port-range 50000-51000 htop
# pin ICE to a fixed UDP range — port-forward that range on a strict NAT

npx joinin --web --rtc-udp-mux htop
# share one UDP port across every browser peer — fewer ports to open on a
# firewall when several friends join at once. Verified: this is safe across
# separate processes/machines (the real deployment shape); it specifically
# breaks same-process self-connections, which is why it's opt-in rather than
# the default — don't combine it with running two joinin --web instances
# on the same host.

npx joinin --web --rtc-proxy socks5://user:pass@proxyhost:1080 htop
# route WebRTC ICE through a SOCKS5/HTTP proxy — for networks that block
# direct UDP/TCP egress entirely

The server logs how each browser peer actually connected:

joinin: browser peer a1b2c3d4e5f6 connected direct (host/prflx)
joinin: browser peer f6e5d4c3b2a1 connected via TURN relay

direct means the punch succeeded; via TURN relay means it fell back to a relay (still works, just extra latency/bandwidth cost on the relay operator).


How it works

[host terminal] ──stdin──┐
                         ├─▶ [single PTY running the app]
[client A] ──stdin───────┤        │  stdout/stderr
[client B] ──stdin───────┘        ├─▶ [host terminal]  (mirrored)
                                  └─▶ [client A] + [client B]  (broadcast)
  1. Both sides derive the same Curve25519 keypair from the shared seed (sha256(seed) → hyperdht.keyPair).
  2. The host advertises that public key on the DHT; the client connects to it.
  3. A protomux channel using the hypershell protocol carries stdin / stdout / stderr / exit / resize.
  4. The host keeps one PTY. Its output is mirrored to the host terminal and broadcast to every client; input from the host and every client is merged into the PTY.
  5. A resize from any participant resizes the PTY and tells the others to match, preserving "the same view".
  6. When the app exits, the exit code is sent to all clients, channels close, and joinin exits.

With --web, a second transport runs alongside: the server derives a room id from sha256("sharesies:" + seed) and joins it as a wireweave DataSession peer. Each browser client that joins the same room gets its own RTCDataChannel; since a data channel is one raw binary pipe (no protomux-style multiplexing), stdin / stdout / stderr / exit / resize are carried as a small 1-byte-type-prefixed frame instead (src/rtc-protocol.js). Both transports feed the same SharedSession, so a HyperDHT client and a browser client see and affect the identical live PTY.


Security

  • HyperDHT traffic is end-to-end encrypted via the Noise protocol.
  • WebRTC traffic (--web) is encrypted via DTLS/SRTP per the WebRTC spec; peer discovery/signaling happens over public nostr relays (see wireweave), using a fresh, in-memory-only nostr identity generated per server/client process — never persisted, never your real identity.
  • The seed is effectively a password for both transports: only someone with it can derive the HyperDHT public key or the WebRTC room id. Generate a fresh seed per session; never log or commit it.
  • The local host process is the only place the app runs.

SDK

import { runServer, runClient, deriveKeyPair, createSharedSession } from 'joinin'

await runServer({ command: 'htop' })        // host
await runClient('my-shared-seed-hex')        // join

await runServer({ command: 'htop', web: true })   // host, also reachable from a browser

CI / publishing

Pushing to main runs tests and publishes a patched version to npm (.github/workflows/publish.yml). A NPM_TOKEN repository secret is required for publishing.

Pushing changes under web/ (or the shared src/rtc-protocol.js framing) rebuilds and deploys the browser client to GitHub Pages (.github/workflows/pages.yml), independent of the npm publish flow.


License

MIT