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

lizardtail

v0.1.0

Published

Run a dev server command, detect its port, expose it with Tailscale Serve or Funnel, and print the URL.

Readme

lizardtail

lizardtail runs a command, watches its output for a localhost server port, exposes that port with Tailscale Serve, and prints the private tailnet URL. Pass --public to use Tailscale Funnel intentionally.

lizardtail pnpm dev
Local: http://localhost:5173

lizardtail: detected local server on http://127.0.0.1:5173
lizardtail: serving via Tailscale: https://my-host.tailabc.ts.net:8443

Use it when your dev server is running on a remote machine and you want to open it from another device on your tailnet without manually copying ports or reconfiguring Tailscale Serve.

Features

  • Runs any command you pass it, such as pnpm dev, npm run dev, bun run dev, or python -m http.server.
  • Streams the child command's stdout/stderr normally.
  • Detects common dev-server output formats, including http://localhost:5173, http://127.0.0.1:3000, started server on 0.0.0.0:8080, and PORT=4321.
  • Ignores timing output like ready in 500 ms so it does not accidentally expose port 500.
  • Waits briefly after the first candidate port so multi-process commands, such as Laravel plus Vite, can print the better app-server URL.
  • Waits for the detected port to accept local connections before exposing it.
  • Runs tailscale serve --bg --https <tailscale-port> http://127.0.0.1:<port>.
  • Prints the HTTPS MagicDNS URL for the current Tailscale device.
  • Supports an explicit --port when automatic detection is not possible.
  • Supports --tailscale-port when you want the MagicDNS URL to include a specific HTTPS port.
  • Detects Laravel + Vite dev output, exposes both servers, rewrites Laravel's public/hot file to the Tailscale Vite URL, and proxies Vite assets with CORS headers so module scripts can load cross-origin.
  • Uses a stable alternate Tailscale HTTPS port by default: first free port from 8443 upward.
  • Stays private to your tailnet by default.
  • Supports --public / --funnel for intentional public internet sharing through Tailscale Funnel.
  • Cleans up the Tailscale Serve/Funnel mappings it created when the child command exits or you press Ctrl+C.
  • Has editable blocked-port guardrails with default entries for common HTTP/HTTPS ingress ports.

Requirements

  • Node.js 20 or newer.

  • Tailscale installed and available as tailscale on PATH.

  • The device must be logged into Tailscale.

  • Tailscale Serve must be available for the device/tailnet.

  • For --public, Tailscale Funnel must be enabled for the device/tailnet.

  • Your user must be allowed to update Tailscale Serve/Funnel config. If tailscale serve or tailscale funnel says access is denied, run this once:

    sudo tailscale set --operator=$USER

Check Tailscale before using lizardtail:

tailscale status
tailscale serve --help
# Optional, only for --public:
tailscale funnel --help

lizardtail exposes services to your private tailnet via Tailscale Serve by default. It only uses Tailscale Funnel, which publishes to the public internet, when you explicitly pass --public or --funnel.

Installation

From source

git clone https://github.com/dasomji/lizardtail.git
cd lizardtail
npm install
npm run build
npm link

Then run:

lizardtail --help

During development

You can run the TypeScript source directly:

npm run dev -- pnpm dev

Or build and run the compiled CLI:

npm run build
node dist/index.js pnpm dev

Usage

lizardtail [options] -- <command> [args...]
lizardtail [options] <command> [args...]
lizardtail help [topic]
lizardtail config init

Use -- when the command itself has flags that could be confused for lizardtail options:

lizardtail -- npm run dev -- --host 0.0.0.0

Help

lizardtail help
lizardtail help config

These commands are meant for both humans and coding agents: they describe usage, safety behavior, public/private exposure, and config file shape without needing to open the README.

Options

| Option | Default | Description | | --- | --- | --- | | --port <port> | auto-detect | Expose this port instead of reading one from command output. | | --host <host> | 127.0.0.1 | Local host to pass to Tailscale Serve/Funnel. | | --timeout <ms> | 30000 | How long to wait for a port to appear in command output. | | --tailscale-port <port> | first free 8443+ | Expose the main app on this Tailscale HTTPS port and print it in the MagicDNS URL. Alias: --https-port. | | --vite-tailscale-port <port> | first free 8443+ | Expose a detected Laravel Vite asset server on this Tailscale HTTPS port. Alias: --vite-https-port. | | --public, --funnel | disabled | Use Tailscale Funnel for public internet access instead of private tailnet-only Serve. | | --no-open-check | enabled | Skip waiting for the local port to accept connections before calling Tailscale. | | -h, --help | | Show help. |

Examples

Vite / frontend dev server

lizardtail pnpm dev

If Vite is configured to bind to another host:

lizardtail --host localhost pnpm dev

npm script with extra flags

lizardtail -- npm run dev -- --host 0.0.0.0

Known port

lizardtail --port 3000 npm run dev

MagicDNS URL with an explicit port

By default, lizardtail uses the first free Tailscale HTTPS port from 8443 upward, so multiple projects can be served at the same time:

https://my-host.tailabc.ts.net:8443

You can also choose the Tailscale HTTPS port explicitly:

lizardtail --tailscale-port 8450 pnpm dev

That prints a URL like:

https://my-host.tailabc.ts.net:8450

Laravel / composer run dev

Laravel development commands often start both the PHP app server and the Vite asset server. When lizardtail sees both, it:

  1. exposes the Laravel app server;
  2. starts a small local proxy in front of Vite that adds CORS headers;
  3. exposes that Vite proxy on a separate Tailscale HTTPS port;
  4. writes public/hot to the Tailscale Vite URL so Laravel renders assets from the reachable Vite server.
lizardtail composer run dev

You can choose the Vite Tailscale port explicitly:

lizardtail --vite-tailscale-port 8453 composer run dev

lizardtail also sets __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS for the child command when it can read your Tailscale MagicDNS name. The local proxy handles CORS for module scripts loaded from the Vite Tailscale URL.

If your app server lands on a known port and you only want to expose that server, you can force it:

lizardtail --port 8001 composer run dev

Public internet sharing

By default, URLs are only reachable from devices in your tailnet. To intentionally publish through Tailscale Funnel:

lizardtail --public pnpm dev

or:

lizardtail --funnel pnpm dev

This prints a public HTTPS URL such as:

https://my-host.tailabc.ts.net:8443

Use this only for apps you are comfortable exposing publicly. Stop lizardtail with Ctrl+C to remove the Funnel mapping it created.

Editable blocked ports

Lizard Tail ships with a small default blocked-port list for common HTTP/HTTPS ingress ports:

{
  "blockedPorts": [
    {
      "port": 80,
      "scope": "both",
      "reason": "Common HTTP ingress/proxy port. Blocking prevents dev exposure from replacing a production web route."
    },
    {
      "port": 443,
      "scope": "both",
      "reason": "Common HTTPS ingress/proxy port. Lizard Tail defaults to high explicit Tailscale HTTPS ports instead."
    }
  ]
}

Create an editable config file:

lizardtail config init

Lizard Tail searches the current working directory for:

  1. lizardtail.config.json
  2. .lizardtail.json

You can also set LIZARDTAIL_CONFIG=/path/to/config.json.

Each blocked-port entry has:

  • port: number from 1 to 65535
  • scope: "local", "tailscale", or "both" — defaults to "both"
  • reason: explanation shown when the rule blocks an action

If a config file exists, its blockedPorts list replaces the built-in default list. Keep, edit, or remove entries based on your own host.

Longer startup timeout

lizardtail --timeout 60000 pnpm dev

How it works

  1. lizardtail starts the command you provide.

  2. It streams the command output to your terminal.

  3. It scans recent output for a local port.

  4. Once it finds a port, it waits for 127.0.0.1:<port> or the configured --host to accept connections.

  5. It chooses the first free Tailscale HTTPS port from 8443 upward, unless --tailscale-port was provided. Ports in the configured blocked-port list are refused or skipped.

  6. It runs Tailscale Serve for private tailnet-only access:

    tailscale serve --bg --https <tailscale-port> http://<host>:<port>

    With --public / --funnel, it runs Tailscale Funnel for public internet access:

    tailscale funnel --bg --https <tailscale-port> http://<host>:<port>

    On older Tailscale versions, if that form fails for 127.0.0.1/localhost, it falls back to the same command with just <port> as the target.

  7. It reads tailscale status --json, extracts the current device's MagicDNS name, and prints:

    https://<device-name>.<tailnet>.ts.net:<tailscale-port>

Shutdown behavior

When the child command exits, or when you press Ctrl+C, lizardtail removes the Tailscale mappings it created for that run:

tailscale serve --https=<port> off
# or, with --public:
tailscale funnel --https=<port> off

It only tracks ports created by the current lizardtail process.

Troubleshooting

No port detected

If the server does not print a recognizable port, pass it explicitly:

lizardtail --port 5173 pnpm dev

Tailscale command fails

Verify Tailscale is running and logged in:

tailscale status

Then check Serve support and current mappings:

tailscale serve --help
tailscale serve status

Access denied: serve config denied

Some Tailscale installs only allow root, or the configured Tailscale operator, to change Serve config. If you see:

Access denied: serve config denied
Use 'sudo tailscale serve ...'
To not require root, use 'sudo tailscale set --operator=$USER' once.

run:

sudo tailscale set --operator=$USER

Then rerun lizardtail. This is a one-time local machine setup step.

Browser cannot load assets

Some frameworks, especially full-stack apps with separate backend and Vite dev servers, need more than one port exposed. lizardtail detects Laravel + Vite and exposes both automatically. For other multi-port setups, run a second lizardtail --port <port> ... command or configure Tailscale Serve manually with explicit high ports.

Host checks or CORS failures

Some dev servers reject requests from the Tailscale hostname. Configure your dev server to allow the Tailscale MagicDNS host or to bind with the right host/CORS options. For example, Vite may need --host 0.0.0.0 and framework-specific allowed-host settings.

Development

npm install
npm run typecheck
npm test
npm run build

The test suite uses Node's built-in test runner through tsx and includes:

  • unit tests for argument parsing and port detection;
  • an integration-style CLI test with a fake tailscale executable and a real temporary HTTP server.

Contributing

Issues and pull requests are welcome. Please include tests for behavior changes and run:

npm test

before opening a pull request.

License

MIT. See LICENSE.