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

@tafrahq/wt-tunnel-client

v0.7.0

Published

Private HTTPS URLs for every git worktree, through a self-hosted frp tunnel server behind a sign-in (Google, GitHub, Microsoft or Cloudflare Access): one frpc per worktree, no ports in the URLs, no root on the dev machine.

Readme

wt-tunnel-client

wt-tunnel-client is a command-line tool that gives each git worktree its own private HTTPS URLs through a tunnel server you run yourself. Run wt-tunnel-client worktree add myproject feat-x in a worktree and your dev servers open at https://app.feat-x.dev.example.com in any browser, after a sign-in (Google, GitHub, Microsoft or Cloudflare Access) that only the people you allow can pass.

The URLs have no port, the apps stay plain HTTP on 127.0.0.1, and the dev machine needs no root access and opens no port. Each worktree runs one frpc, which connects out to the server.

browser   https://app.feat-x.dev.example.com
server    Caddy :443   ends TLS, asks the auth service, then hands the request to frps
          frps         routes by Host to the frpc that registered it
frpc      (this worktree) app.feat-x.dev.example.com → http://127.0.0.1:4123

docs/server-mode.md describes the design and the sign-in flow. apps/cli/README.md deploys a server to Fly.io, and apps/server/README.md runs it anywhere else.

Install

npm install -g @tafrahq/wt-tunnel-client   # or add it as a devDependency

The package is on npm from 0.7.0. Earlier versions are on GitHub Packages, which needs a GitHub token to install from.

Up to 0.4.0 the client was published as @tafrahq/wt-tunnel; from 0.5.0 that name is the server setup CLI, so install @tafrahq/wt-tunnel-client.

Requirements:

  • Node.js 22 or later.
  • Linux or macOS, on amd64 or arm64, for the machine that runs the worktrees. Windows is not supported.
  • A running wt-tunnel server, and its tunnel token.

Configure

Put wt-tunnel.json at the repository root and commit it, so every worktree checks it out:

{
  "domain": "dev.example.com",
  "server": { "host": "wt-tunnel.example.com" },
  "apps": {
    "app": { "portEnv": "APP_PORT" },
    "api": { "portEnv": "API_PORT" },
    "db": { "portEnv": "DB_PORT", "route": false }
  }
}

| Field | Default | Meaning | | ------------- | -------- | -------------------------------------------------------------------------------------- | | domain | required | Parent domain of the URLs. It must be one of the server's domains. | | server.host | required | The tunnel server's host name. An IP address also works. | | server.port | 7000 | The port where the server accepts frpc. | | apps | required | One entry per service. portEnv names the variable that receives its port. See below. |

Each entry in apps accepts:

| Field | Default | Meaning | | --------- | -------- | ------------------------------------------------------------------------------------------------------ | | portEnv | required | Environment variable that receives the service's port, such as APP_PORT. | | route | true | false for services that need a port but no URL, such as a database. | | port | reserved | A fixed port, if the service must keep a known one. worktree add fails if another worktree holds it. |

Each app is served at https://<label>.<worktree>.<domain>. An app labelled root is served at the bare worktree host, https://<worktree>.<domain>, which suits a site with a single app:

{
  "domain": "dev.example.com",
  "server": { "host": "wt-tunnel.example.com" },
  "apps": { "root": { "portEnv": "PORT" }, "api": { "portEnv": "API_PORT" } }
}

This gives https://feat-x.dev.example.com and https://api.feat-x.dev.example.com.

Set WT_TUNNEL_SERVER_TOKEN to the server's tunnel token (tunnelToken in its config). wt-tunnel-client passes it to frpc in the environment and never writes it to disk.

Trust the server's certificate

The server signs its certificates with its own root CA, so no worktree name ever appears in a public Certificate Transparency log. Each device that opens the URLs must trust that root once. It is name-constrained to the dev domains, so it can vouch for nothing else.

The easiest way is to open https://<authHost>/ca on the device, where <authHost> is the server's sign-in host (the host the sign-in page redirects to). The page shows the root's fingerprint and permitted names, downloads the certificate (or, on iPhone and iPad, a profile), and lists the steps for each platform with yours first. To do it by hand with the deployment's ca.crt:

  • macOS. sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt.
  • iOS and iPadOS. Open the file in Safari, install the profile from Settings > Profile Downloaded, then turn on full trust in Settings > General > About > Certificate Trust Settings.
  • Linux. On Debian and Ubuntu, copy it to /usr/local/share/ca-certificates/wt-tunnel.crt and run sudo update-ca-certificates. On Fedora and RHEL, copy it to /etc/pki/ca-trust/source/anchors/ and run sudo update-ca-trust. Some browsers keep their own certificate store.
  • Windows. Open the file, select Install Certificate, choose Current User, and place it in Trusted Root Certification Authorities.

Commands

wt-tunnel-client never runs git and never picks names. Create the git worktree yourself, then name the project and the worktree:

git worktree add .worktrees/feat-x && cd .worktrees/feat-x && wt-tunnel-client worktree add myproject feat-x
wt-tunnel-client worktree add <project> <worktree>

Provisions the worktree root, which is the nearest directory at or above the current one that holds wt-tunnel.json. It reserves the worktree's ports, writes .wt-tunnel/frpc.toml with one HTTP proxy per routed app, starts frpc, and waits until the server has accepted every host. It prints the URLs and ports. Both names must be DNS labels (lowercase letters, digits and interior hyphens, at most 63 characters); wt-tunnel-client rejects other names and never rewrites them. Running it again with the same names is safe. If the root is already provisioned under other names, the command fails; run wt-tunnel-client worktree remove first.

If the server refuses a host, for example because another worktree already serves it, the command stops frpc and fails, naming the host and the reason.

wt-tunnel-client worktree remove

Stops frpc, releases the ports, and deletes .wt-tunnel/. It reads the names from the root's state. Then remove the checkout yourself with git worktree remove .worktrees/feat-x.

Run these from inside a provisioned worktree:

wt-tunnel-client up              # provision and start this worktree, for example after a reboot
wt-tunnel-client down            # stop frpc, keep the ports
wt-tunnel-client status          # server, frpc state (running / stopped / stale), URLs
eval "$(wt-tunnel-client env)"   # export APP_PORT=… for your dev servers

These commands fail with run `wt-tunnel-client worktree add <project> <worktree>` first in a root that is not provisioned. If the config has not changed, up leaves a running frpc alone; otherwise it restarts frpc with the new config.

The dev servers must listen on 127.0.0.1 at their allocated ports. For example, a Vite app can use vite --host 127.0.0.1 --port "$APP_PORT" --strictPort. Vite must also accept the hostname: add server.allowedHosts: ['.dev.example.com'] to its config. WebSocket upgrades pass through, so HMR works.

Ports and URLs between services

wt-tunnel-client picks every port at worktree add, keeps it for the life of the worktree, and writes it to .wt-tunnel/env, together with WT_TUNNEL_HOST=<worktree>.<domain>.

Ports are reserved per user in ~/.local/state/wt-tunnel/ports/, one file per port naming the worktree that owns it. A stopped worktree keeps its ports, so another worktree is never handed them. Reserved ports come from 20000-29999, below the range Linux uses for outgoing connections. worktree remove releases them, and reservations of a worktree deleted without remove are reclaimed automatically.

Write service URLs once in mise.toml. mise fills in the ports from the env file, in every worktree:

[env]
_.file = { path = ".wt-tunnel/env", tools = false }
DATABASE_URL = "postgres://127.0.0.1:{{ env.DB_PORT | default(value='') }}/app"
API_URL = "http://127.0.0.1:{{ env.API_PORT | default(value='') }}"
VITE_API_URL = "https://api.{{ env.WT_TUNNEL_HOST | default(value='localhost') }}"

Services on the dev machine talk to each other over 127.0.0.1. The browser uses the https://<app>.<worktree>.<domain> names.

The frpc binary

On first use, wt-tunnel-client downloads frp v0.71.1 for the platform from GitHub. It checks the download against a SHA-256 pinned in the source, then installs frpc in $XDG_CACHE_HOME/wt-tunnel/frp/0.71.1/ (by default ~/.cache on Linux and ~/Library/Caches on macOS). To use your own frpc binary, set WT_TUNNEL_FRPC.

Local state

Local state lives in <worktree root>/.wt-tunnel/, which ignores itself in git. It contains:

  • state.json, with the project and worktree names and the reserved ports
  • env
  • frpc.toml, without the token
  • frpc.pid and frpc.log

The frpc log records each host the server accepted or refused. It is the first place to look when a URL does not load.

Limits

  • Two projects that share a domain cannot use the same worktree name at the same time. The server refuses the second worktree's hosts. Projects with different domains can use the same worktree name.
  • Every URL is reachable from the internet, behind the sign-in. The server admits only the emails and email domains in its allow list.

Development

This package is apps/client in the wt-tunnel monorepo, which describes how to install, check and build it. pnpm build here runs tsdown unbundled: one dist file per source file, with the dependencies external. The workspace-only @tafrahq/wt-tunnel-contract package is compiled into dist/, so it is not a dependency of the published package.

The package ships the built dist/, the TypeScript sources in src/ (source maps point at them), and llms.txt, the same layout Effect uses for its packages.

Releases publish this package together with the server and the wt-tunnel CLI, at one version. CONTRIBUTING.md describes how.