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

tailsacle-cli

v1.260826.11425

Published

Safe TypeScript CLI for real Tailscale deployment workflows. Official binaries are `tailsacle-cli` and `tscli`.

Readme

tailsacle-cli

Safe TypeScript CLI for real Tailscale deployment workflows. Official binaries are tailsacle-cli and tscli.

What it does

tailsacle-cli now provides a production-oriented local deployment path:

  • resolves configuration and credentials without logging raw secrets;
  • discovers the installed Tailscale binary and can explicitly update it;
  • creates a Tailscale auth key through the API when TS_AUTH_KEY is not supplied;
  • joins/configures the current machine with tailscale up and verifies BackendState=Running;
  • auto-provisions the tailnet so a tagged deployment can connect: adds missing tagOwners, adds the funnel node attribute and enables tailnet HTTPS certificates when --yes is passed, each change is warned, validated, backed up and ETag-protected;
  • configures private Serve or public Funnel exposures;
  • reads tailnet DNS settings;
  • performs guarded HuJSON policy validation/diff/write with backup, ETag and confirmation;
  • finds and removes only exact matching offline devices after a confirmation gate;
  • exposes stable JSON envelopes for automation and an agent manifest.

Policy and cleanup writes are intentionally guarded. Missing permissions and API failures are reported as failures; the CLI never claims a remote side effect it did not verify.

Requirements

  • Node.js 22 or newer
  • Tailscale installed and available on PATH for up, status, Serve and Funnel
  • For API-driven auth-key/policy/DNS/cleanup operations, one of:
    • TS_API_KEY
    • TS_ACCESS_TOKEN
    • TS_OAUTH_CLIENT_ID + TS_OAUTH_CLIENT_SECRET
  • Or TS_AUTH_KEY for a pre-created node auth key

Install and run

npm install
npm run build
node dist/cli.js doctor --detect-credentials --json
node dist/cli.js status --json

The published package exposes tailsacle-cli, tscli and tailscale-cli-opencode.

OpenCode funnel flow (tailscale-cli-opencode)

One command turns a machine into a public opencode endpoint: resolve/install opencode, grant it full permissions, serve it, join the tailnet and publish it through a Tailscale Funnel, then verify the live endpoint before printing the public URL.

export TS_AUTH_KEY='tskey-auth-...'        # or OAuth trust credential / TS_API_KEY
npx tailscale-cli-opencode --yes --apply-policy --enable-https --json
# -> OPencode URL: https://<hostname>.<tailnet>.ts.net/
  • If opencode is not on PATH, it is resolved through npx -y opencode-ai (--install forces this even when a binary exists).
  • Full permissions: writes opencode.json with "permission": "allow" — the headless equivalent of opencode --auto (serve has no --auto flag); nothing is blocked. Override the file with --opencode-config <path>.
  • opencode serve runs detached on --port <number> (default 3000) and is tracked in the cache pidfile (daemon stop-style); its log is cache/opencode-serve.log.
  • The Funnel exposure maps the public port 443 to the local serve port (auto-provisions tagOwners, the funnel node attribute and tailnet HTTPS behind --apply-policy / --enable-https), then verifies public DNS + a live TLS handshake (--no-verify skips this).
  • --stop tears down the tracked opencode serve and the userspace tailscaled.

PostgreSQL TCP Relays & NexQL MCP (relay-mcp-postgres)

Relay one or more PostgreSQL databases across machines/Tailnets and simultaneously serve a nexql-mcp HTTP MCP server so AI agents (Cursor, Claude Desktop, Antigravity, OpenCode) can explore live schema and query any relayed database:

# Multi-DB relay from config file + MCP on :8787 accessible via Tailnet
tailsacle-cli relay-mcp-postgres \
  --file examples/relay-mcp-postgres/multi-db.jsonc \
  --mcp-port 8787 \
  --mcp-bind 0.0.0.0 \
  --token "$MCP_TOKEN" \
  --allow-partial \
  --primary-fallback

See docs/relay-mcp-postgres.md for the complete guide, flag reference, and client connection setups; see examples/relay-mcp-postgres/ for ready-to-use sample configs and service scripts.

Examples

  • examples/relay-mcp-postgres/ — Complete configuration examples for 1 DB, multi-DB, systemd/Windows services, and AI Agent MCP client setup.
  • examples/colab/opencode-funnel-colab.sh — one-cell Google Colab script: installs Node.js 22+ and tailsacle-cli, then delegates everything to a single tailscale-cli-opencode --install --yes --apply-policy --enable-https run (resolve opencode, grant full permissions, serve, join the tailnet, publish the Funnel, verify DNS + TLS) and prints the public Funnel URL.

GitHub Packages mirror (fast CI installs)

Every tag release is also published to GitHub Packages as @ongtrieuphuchieu689-7u/tailsacle-cli. Installing it with a repo scoped token inside GitHub Actions avoids the npmjs round-trip and is noticeably faster:

- uses: actions/setup-node@v7
  with:
    node-version: 24
    registry-url: https://npm.pkg.github.com
- run: npm i -D @ongtrieuphuchieu689-7u/tailsacle-cli
  env:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

For private packages, keep an .npmrc with @ongtrieuphuchieu689-7u:registry=https://npm.pkg.github.com and token auth. tailsacle-cli on npmjs remains the canonical package; the scoped copy is a fast mirror for CI only. GitHub Packages requires the token with packages: read scope.

Common deployment

export TS_TAILNET='example.com'
export TS_HOSTNAME='web-01'
export TS_TAGS='prod,web'
export TS_API_KEY='...'

npx tailsacle-cli doctor --detect-credentials --json
npx tailsacle-cli deploy --json

For a pre-created auth key:

export TS_AUTH_KEY='tskey-auth-...'
npx tailsacle-cli up --json

Dry-run first when changing deployment intent:

npx tailsacle-cli deploy --dry-run --expose 3000 --json

Serve, Funnel & TCP Relay

npx tailsacle-cli serve 3000 --https 443 --path api --json
npx tailsacle-cli funnel 3000 --https 443 --path api --yes --json

TCP Forwarding / Relay (Postgres / MySQL / Raw TCP)

Phương án 1 — Tailscale Native Serve/Funnel Proxy: Forward port từ máy này sang máy khác qua tailnet:

# Expose port 5432 trên máy hiện tại forward thẳng tới IP Tailscale của máy DB target:
npx tailsacle-cli serve --tcp 5432 tcp://100.x.y.z:5432

Phương án 2 — Node.js TCP Relay (Trạm trung chuyển đơn cổng, đa cổng & config file): Chạy trạm trung chuyển chuyển tiếp TCP traffic sang máy khác (có thể mở rộng kèm --serve hoặc --funnel):

# 1. Đơn cổng: Relay local port 5432 -> target machine 192.168.50.79:5433
npx tailsacle-cli relay --listen 5432 --target 192.168.50.79:5433

# 2. Đa cổng (Multi-port) trong 1 tiến trình:
npx tailsacle-cli relay --target-host 192.168.50.79 --map 5432:5432 --map 5433:5433 --map 5434:5434 --serve

# 3. Chạy qua file cấu hình JSON/JSONC (phù hợp chạy Service/Daemon):
npx tailsacle-cli relay --file ./relays.json

Ví dụ file relays.json:

[
  { "listen": 5432, "target": "192.168.50.79:5432", "serve": true },
  { "listen": 5433, "target": "192.168.50.79:5433", "serve": true },
  { "listen": 10000, "target": "192.168.50.79:5433", "funnel": true }
]

Funnel ports are validated before execution; supported public HTTPS ports are 443, 8443 and 10000.

Phương án 3 — Relay + PostgreSQL MCP (nexql-mcp HTTP): Chạy relay tới PostgreSQL và đồng thời mở MCP HTTP endpoint để agent truy cập toàn bộ database trên instance (chọn database runtime qua setup_connection, không cần 1 config MCP cho từng DB):

# Relay local :15433 -> Postgres 192.168.50.79:5433, MCP HTTP trên :8787
PGPASSWORD=*** npx tailsacle-cli relay-mcp-postgres \
  --map 15433:192.168.50.79:5433 --map 15434:192.168.50.79:5434 \
  --mcp-port 8787 --mcp-bind 0.0.0.0 --token "$MCP_TOKEN" --database postgres

Bảo mật: password DB chỉ đi qua --password/PGPASSWORD/TS_PGPASSWORD vào env PGPASSWORD của child; token MCP đi qua NEXQL_MCP_HTTP_TOKEN hoặc --token; output và pidfile luôn mask. Luôn chạy: MCP server không thoát khi máy Postgres chưa bật — supervisor respawn mỗi --db-retry-interval ms (mặc định 5000) cho tới khi DB kết nối được; nếu DB chết giữa chừng rồi quay lại, MCP tự phục hồi. Giới hạn: setup_connection chỉ target được các port đã khai báo relay sẵn (--map/--file/--listen) — agent không tự mở port mới runtime. Mỗi mapping có thể mang credentials riêng (user/password/database) khi dùng --file:

[
  { "listen": 5433, "target": "192.168.50.79:5433", "user": "postgres", "password": "pw-a", "database": "postgres" },
  { "listen": 5431, "target": "localhost:5432", "user": "report", "password": "pw-b", "database": "reporting" }
]

Mapping đầu tiên là MCP primary; nếu target của nó không kết nối được lúc khởi động, CLI tự chọn mapping reachable đầu tiên làm primary (cảnh báo PRIMARY_FALLBACK + primaryMappingIndex/primaryReason), MCP lên ngay thay vì respawn liên tục. Log lỗi lặp lại được dedupe/throttle để không spam console khi DB down lâu.

Service management (service — chạy relay như systemd / Windows service)

# Sinh file cấu hình mẫu, chỉnh sửa rồi cài đặt:
npx tailsacle-cli service init --name tailsacle-relay --out .tailsacle-service.jsonc
sudo npx tailsacle-cli service install --file .tailsacle-service.jsonc --yes   # Linux systemd
npx tailsacle-cli service install --file .tailsacle-service.jsonc --user --yes  # Linux rootless
npx tailsacle-cli service install --file .tailsacle-service.jsonc --yes         # Windows SCM (admin)
npx tailsacle-cli service install --file .tailsacle-service.jsonc --scheduler --yes  # Windows Task Scheduler (no admin)

npx tailsacle-cli service status --name tailsacle-relay --json
npx tailsacle-cli service logs --name tailsacle-relay --follow
npx tailsacle-cli service start|stop|restart --name tailsacle-relay
npx tailsacle-cli service list
npx tailsacle-cli service uninstall --name tailsacle-relay --yes

Ví dụ args chạy relay multi-port dưới dạng daemon: ["relay", "--file", "/etc/tailsacle/relays.json"]. Xem docs/service-install-plan.md (thiết kế) và docs/service-install-verification.md (kiểm chứng).

With --yes, funnel and deploy auto-provision what the tailnet is missing (tagged auth keys, tagOwners, the funnel node attribute, and tailnet HTTPS certificates) with warnings, so a trust credential can reach a working connection end to end.

Policy

Policy sync is deliberately destructive-operation gated:

npx tailsacle-cli policy --file ./policy.hujson --dry-run --json
npx tailsacle-cli policy --file ./policy.hujson --sync --yes --json

The workflow fetches the current policy, shows a diff, validates the candidate HuJSON remotely, writes a local backup of the fetched policy, then performs an ETag-protected update and re-reads the policy to verify the write.

Cleanup

npx tailsacle-cli cleanup --dry-run --json
npx tailsacle-cli cleanup --yes --json

Cleanup is restricted to devices that are offline beyond TS_CLEANUP_OFFLINE_AFTER and match the configured hostname/tag set. Protected device names can be supplied through TS_PROTECTED_DEVICES.

Configuration

| Variable | Purpose | |---|---| | TS_TAILNET | Tailnet/domain used by API calls (custom non-*.ts.net domains warn; Funnel relies on a Tailscale-hosted domain) | | TS_HOSTNAME | Node hostname | | TS_TAGS | Comma-separated node tags without or with tag: prefix | | TS_PROFILE | ci, container, vm, windows, dev, funnel-app, subnet-router, exit-node (funnel-app defaults to non-ephemeral) | | TS_SSH | Enable SSH intent, default true | | TS_ACCEPT_DNS | Accept DNS, default true | | TS_ACCEPT_ROUTES | Accept subnet/exit routes when enabled by profile | | TS_PREAUTHORIZED | Create pre-authorized auth keys, default true | | TS_KEY_EXPIRY | Auth-key lifetime in seconds, max or unlimited (default max). max/unlimited use the documented 90-day ceiling — this is NOT an API-discovered maximum. Set TS_KEY_EXPIRY=3600 to pin a shorter lifetime; explicit values above the ceiling are clamped with a warning. This is the auth-key expiry, not the node key-expiry policy | | TS_EPHEMERAL | Override ephemeral node behavior (ephemeral nodes cannot publish public Funnel DNS) | | TS_REUSABLE | Override auth-key reuse behavior | | TS_TAG_BASE | Base for the deterministic auto-tag when TS_TAGS is unset (tag:<base>; on CI the GITHUB_REPOSITORY/GITLAB_PROJECT_PATH is preferred) | | TS_AUTH_KEY | Pre-created node auth key | | TS_API_KEY | Tailscale API key | | TS_ACCESS_TOKEN | Bearer access token | | TS_CLIENT_SECRET | Tailscale OAuth trust credential (tskey-client-…); client ID is derived from it | | TS_CREDENTIAL_ENV | Name of the env var holding the OAuth trust credential — the explicit env-level equivalent of --credential-env (the config file credentialEnv key sets it); beats TS_CLIENT_SECRET and auto-detection | | TS_CLIENT_ID | OAuth client ID when the secret is not a self-describing trust credential | | TS_OAUTH_CLIENT_ID | OAuth client ID (alternative to TS_CLIENT_ID) | | TS_OAUTH_CLIENT_SECRET | OAuth client secret | | TS_POLICY_FILE | Default policy file path | | TS_CLEANUP_OFFLINE_AFTER | Cleanup threshold in seconds; default 3600 | | TS_NO_CLEANUP | true/1 disables the automatic cleanup step at the end of a --cleanup deploy | | TS_TAG_OWNER | Comma-separated owners used when auto-provisioning missing tagOwners (otherwise a single existing owner set is reused; mixed-owner policies require this or --tag-owner) | | TS_PROTECTED_DEVICES | Comma-separated protected device IDs/names | | TS_BIN_DIR | Binary cache directory for update-bin/auto-download | | TS_BIN_VERSION | Pin a specific stable version for update-bin/auto-download (e.g. 1.76.0); unset = latest stable | | TS_TAILSCALE_BIN | Explicit Tailscale binary path | | TS_TAILSCALE_SOCKET | Explicit path to the tailscaled Unix socket (default: /var/run/tailscale/tailscaled.sock) | | TS_TAILSCALED_STATE | State path for a userspace tailscaled (default /var/lib/tailscale/tailscaled.state) | | TS_STATE_DIR | State directory for a userspace tailscaled; state file is <dir>/tailscaled.state (overrides the cache-based default) | | TS_CLI_YES | true/1 answers every confirmation like --yes (for embedded automation) | | TS_UNATTENDED | Windows unattended join intent |

Config file

Instead of exporting environment variables, place a tailscale-cli.config.json in your project root (or pass --config <path>). Config file values are used as defaults that environment variables override:

{
  "profile": "vm",
  "hostname": "web-01",
  "tags": ["prod", "web"],
  "keyExpiry": "max",
  "ephemeral": false,
  "credentialEnv": "CI_TAILSCALE_TRUST"
}

Any env var whose value starts with tskey-client- is auto-detected as an OAuth trust credential; use --credential-env <name>, TS_CREDENTIAL_ENV, or the config file credentialEnv key to select the exact env var when several are present (the value itself must still come from the environment — the config file only names the variable). Secrets are never returned raw by doctor and are not written to logs; server error text is scrubbed for tskey-… and Authorization material.

Interactive menu and safety flags

Running tailsacle-cli with no arguments in a TTY opens an interactive menu that prompts for the profile, target/port, policy action and binary update, then prints the equivalent non-interactive command before executing it.

Safety scoping: --yes only skips confirmation prompts. Auto-provisioning side effects are gated behind explicit flags so a plain --yes never quietly rewrites tailnet policy:

| Flag | Effect | |---|---| | --apply-policy | Allow HuJSON-preserving tagOwners/nodeAttrs provisioning (with plan + warning) | | --enable-https | Allow enabling tailnet-wide HTTPS (required for Serve/Funnel) | | --ssh / --no-ssh | Enable/disable Tailscale SSH on the joined node (default true; overrides TS_SSH) | | --state-dir <path> | State directory for a userspace tailscaled (overrides TS_STATE_DIR) | | --backup-dir <path> | Directory for policy backups, default ./.tailscale-cli (overrides the side-by-side ${file}.bak default) | | --update-bin (global flag) | Download the latest stable Tailscale client into the package cache, e.g. tailsacle-cli --update-bin (same as the update-bin subcommand; TS_BIN_VERSION pins a version) | | --key-expiry <value> | Auth-key expiry for this run (max, unlimited, seconds; overrides TS_KEY_EXPIRY). max/unlimited map to the documented 90-day ceiling — not an API-discovered maximum; explicit seconds above the ceiling are clamped with a KEY_EXPIRY_CLAMPED warning | | --tag-owner <owner...> | Owner(s) for auto-provisioned tagOwners (overrides TS_TAG_OWNER) | | --cleanup | Run the exact-match offline device cleanup at the end of a deploy on any profile (auto-cleanup otherwise defaults to CI/container) | | --verify-timeout <sec> | Funnel public-DNS + live-endpoint verification timeout (default 120s) | | --config <path> | Path to tailscale-cli.config.json config file (default: auto-detect in cwd) | | status --show-resolution | Show credential resolution source and masked value in status output | | dns --dry-run | Preview MagicDNS enablement without applying changes | | funnel --tcp 10000:5432 | TCP Funnel instead of HTTPS (public endpoint is verified too) | | funnel --expose 443=3000 --expose 443/api=3001 | Multiple HTTPS Funnel targets/paths (all are reported in resolved.exposures) | | dns --enable-magicdns --yes | Enable MagicDNS on the tailnet (post + read-after-write verify) | | update-bin --force --skip-checksum | Force a fresh verified download; skip the SHA256 check (not recommended) | | daemon status | Report the local tailscaled state and any userspace daemon tracked in the pidfile | | daemon stop | Stop a userspace tailscaled started and tracked by this tool |

funnel refuses ephemeral nodes (they never publish public DNS), auto-detects the target from $PORT when none is given, and verifies both the public A record and the live endpoint before reporting success: HTTPS funnels get a TLS handshake + HTTP probe per public port, TCP funnels get a raw TCP connect, each retried up to --verify-timeout (default 120s). A published DNS record alone is never reported as success — FUNNEL_ENDPOINT_UNREACHABLE is raised when the record exists but the connection fails.

With deploy --funnel --yes, the funnel node attribute is checked before the local funnel command runs: it is auto-provisioned when --apply-policy is present, otherwise the deploy fails fast with FUNNEL_ATTR_REQUIRED. Enable tailnet HTTPS for Funnel with --enable-https; tailnet HTTPS is never enabled implicitly.

On Windows there is no portable binary: update-bin/auto-download resolve the MSI from the stable index, verify its SHA256 and install it silently when running as Administrator; without Administrator privileges the exact msiexec /i "<cache>\…msi" /qn command is returned.

Development

npm run typecheck
npm test
npm run check
npm run build
npm run pack:check
npm run version:bump:verify   # bump version to now (Asia/Ho_Chi_Minh, 1.YYMMDD.1HHmm)

CI tests Linux and Windows on Node 22 and Node 24. Release publishing is tag-based: every tag v*.*.* runs release.yml, which builds with cached node_modules on node24-runtime actions, publishes to npmjs with npm provenance (Trusted Publisher OIDC) and mirrors the package to GitHub Packages for fast CI installs. Versions use 1.YYMMDD.1HHmm (Asia/Ho_Chi_Minh, 24h), e.g. 1.260816.11417.