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

@kadi.build/tunnel-client

v0.4.1

Published

Supporting classes for KadiTunnelService — SSH mode, frpc client mode, reconnection, config generation, and output parsing.

Downloads

824

Readme

@kadi.build/tunnel-client

Supporting classes for KadiTunnelService — the self-hosted HTTPS tunnel provider for KĀDI agents.

This package provides the SSH mode, frpc client mode, config generation, output parsing, reconnection management, and utility functions that KadiTunnelService.js depends on.

KadiTunnelService.js itself is NOT part of this package. It is a standalone drop-in file — just like NgrokTunnelService.js and ServeoTunnelService.js — that you copy into your project's services directory.


Integration Guide

Step 1: Install the npm package

npm install @kadi.build/tunnel-client

This provides the supporting classes that KadiTunnelService.js imports.

Step 2: Copy KadiTunnelService.js into your project

Copy client/src/KadiTunnelService.js from the kadi-tunnel repo into your services directory:

src/providers/tunnel/
├── BaseTunnelService.js          ← already in your project
├── TunnelService.js              ← already in your project (service manager / auto-discovery)
├── errors.js                     ← already in your project
└── services/
    ├── NgrokTunnelService.js     ← already in your project
    ├── ServeoTunnelService.js    ← already in your project
    └── KadiTunnelService.js      ← COPY THIS FILE HERE

No import changes needed. The file's imports already match the project structure:

// These resolve to your project's existing files (two levels up from services/)
import { BaseTunnelService } from '../BaseTunnelService.js';
import { TransientTunnelError, PermanentTunnelError, ... } from '../errors.js';

// This resolves to the npm package you installed in Step 1
import { KadiSSHMode, KadiClientMode, ... } from '@kadi.build/tunnel-client';

Step 3: That's it for code

TunnelService.js uses automatic service discovery — it scans the services/ directory for files matching *Service.js and loads them. Since your file is named KadiTunnelService.js and exports a default class extending BaseTunnelService, it will be auto-discovered and registered under the name 'kadi'.

No manual registration or wiring needed.

Step 4: Configure environment variables

Add these KĀDI-specific variables to your .env file (or set them as environment variables):

# ── KĀDI Tunnel Configuration ──────────────────────────────
KADI_TUNNEL_SERVER=broker.kadi.build     # Tunnel server hostname (REQUIRED)
KADI_TUNNEL_TOKEN=<your-token>           # Auth token from server (REQUIRED)
KADI_TUNNEL_DOMAIN=tunnel.kadi.build     # Wildcard tunnel domain (REQUIRED)
KADI_TUNNEL_PORT=7000                    # frp protocol port (default: 7000)
KADI_TUNNEL_SSH_PORT=2200                # SSH gateway port (default: 2200)
KADI_TUNNEL_MODE=auto                    # 'auto' | 'ssh' | 'frpc' (default: auto)
KADI_TUNNEL_TRANSPORT=wss                # 'wss' (default) | 'tcp' — WSS is firewall-friendly
KADI_TUNNEL_WSS_HOST=tunnel-control.kadi.build  # WSS control channel hostname
KADI_AGENT_ID=agent                      # Agent ID for subdomain generation

# ── To make kadi the primary tunnel service ─────────────────
TUNNEL_SERVICE=kadi                      # was 'ngrok' or 'pinggy'
TUNNEL_FALLBACK_SERVICES=serveo,pinggy   # fallback chain

| Env Variable | Required | Default | Description | |-------------|----------|---------|-------------| | KADI_TUNNEL_SERVER | Yes | '' | Tunnel server hostname (e.g. broker.kadi.build) | | KADI_TUNNEL_TOKEN | Yes | '' | Authentication token from the tunnel server | | KADI_TUNNEL_DOMAIN | Yes | '' | Wildcard tunnel domain (e.g. tunnel.kadi.build) | | KADI_TUNNEL_PORT | No | 7000 | frp protocol port | | KADI_TUNNEL_SSH_PORT | No | 2200 | SSH gateway port | | KADI_TUNNEL_MODE | No | auto | 'auto' | 'ssh' | 'frpc' | | KADI_TUNNEL_TRANSPORT | No | wss | 'wss' | 'tcp' — WSS routes frpc through gateway on :443, bypasses firewalls | | KADI_TUNNEL_WSS_HOST | When transport=wss | '' | WSS control channel hostname (e.g. tunnel-control.kadi.build) | | KADI_AGENT_ID | No | agent | Agent identifier for proxy naming / subdomain | | TUNNEL_SERVICE | No | pinggy | Set to kadi to make it the primary tunnel service |

Step 5: Where the token comes from

The KADI_TUNNEL_TOKEN is generated by the tunnel server during first-time setup. It lives in the server's .env file on the VPS.

To retrieve it:

# SSH into the VPS and read the token
ssh root@<your-vps-ip> 'grep KADI_TUNNEL_TOKEN /root/kadi-tunnel/server/.env'

# Or read it from the frps config directly
ssh root@<your-vps-ip> 'grep auth.token /root/kadi-tunnel/server/frps.toml'

The same token is shared by all agents connecting to that server. It's generated once by server/scripts/setup.sh via openssl rand -base64 32.

How config flows through the system

.env / environment variables
    ↓
ConfigManager.load()              ← reads KADI_TUNNEL_* env vars
    ↓
ConfigManager.getTunnelConfig()   ← builds config object with frp.* nested keys
    ↓
LocalRemoteManager                ← calls config.getTunnelConfig()
    ↓
TunnelProvider(config)            ← receives the tunnel config object
    ↓
TunnelService(config)             ← stores config, auto-discovers services
    ↓
new KadiTunnelService(config)     ← reads config.frp.serverAddr, config.frp.token, etc.

ConfigManager.getTunnelConfig() maps environment variables to the config object that KadiTunnelService expects:

// ConfigManager.getTunnelConfig() returns:
{
  service: 'kadi',                         // from TUNNEL_SERVICE
  fallbackServices: 'serveo,pinggy',       // from TUNNEL_FALLBACK_SERVICES
  agentId: 'agent',                        // from KADI_AGENT_ID
  frp: {
    serverAddr: 'broker.kadi.build',       // from KADI_TUNNEL_SERVER
    serverPort: 7000,                      // from KADI_TUNNEL_PORT
    sshPort: 2200,                         // from KADI_TUNNEL_SSH_PORT
    token: '<token>',                      // from KADI_TUNNEL_TOKEN
    tunnelDomain: 'tunnel.kadi.build',     // from KADI_TUNNEL_DOMAIN
    mode: 'auto',                          // from KADI_TUNNEL_MODE
    transport: 'wss',                      // from KADI_TUNNEL_TRANSPORT
    wssControlHost: 'tunnel-control.kadi.build'  // from KADI_TUNNEL_WSS_HOST
  },
  // plus existing ngrok/general keys...
}

Step 6: Changes needed in configManager.js and tunnelProvider.js

Two files in the local-remote-file-manager-ability package need modifications (already done in the reference copy under docs/):

src/configManager.js — 3 changes:

  1. Add defaults for KADI_TUNNEL_SERVER, KADI_TUNNEL_PORT, KADI_TUNNEL_SSH_PORT, KADI_TUNNEL_TOKEN, KADI_TUNNEL_DOMAIN, KADI_TUNNEL_MODE, KADI_AGENT_ID to the defaults object
  2. Update getTunnelConfig() to return agentId and the frp: { ... } nested object
  3. Update save() to write the KĀDI keys to .env

src/providers/tunnelProvider.js — 1 change:

  1. Add kadi to the serviceConfigs object

Step 7: Use it

Once configured, kadi works like any other tunnel service:

// The system uses it automatically when TUNNEL_SERVICE=kadi
// No code changes needed — TunnelProvider handles it through the service manager

// Or via the service manager directly (e.g. in tests)
const tunnelService = new TunnelService(config);
await tunnelService.initialize();
const kadiService = tunnelService.getService('kadi');
const tunnel = await kadiService.connect({ port: 3000 });

console.log(tunnel.url);       // https://<subdomain>.tunnel.kadi.build
console.log(tunnel.tunnelId);  // tunnel_kadi_1234567890_abc123
console.log(tunnel.mode);      // 'ssh' or 'frpc'

How It Works

Connection Modes

| Mode | Binary Needed | How It Works | Reconnection | |------|--------------|--------------|--------------| | SSH (default) | ssh (system) | ssh -R :80:localhost:{port} v0@{serverAddr} -p {sshPort} | Exponential backoff (1s→30s, 5 retries) | | frpc (enhanced) | frpc on $PATH | Spawns frpc -c /tmp/frpc-{tunnelId}.toml | Built-in (frpc handles it) | | auto | either | Tries frpc first, falls back to SSH | Depends on selected mode |

Transport Protocol (frpc mode)

| Transport | Port | How It Works | When to use | |-----------|------|-------------|-------------| | wss (default) | 443 | frpc connects via WebSocket Secure through kadi-gateway (Caddy) | Campus/enterprise networks that block non-standard ports | | tcp | 7000 | frpc connects directly to frps bindPort | Open networks with no port restrictions |

WSS transport routes the frpc control channel through tunnel-control.{domain}:443 — Caddy terminates TLS and proxies to frps:7000 on the internal Docker network. SSH mode is unaffected (always uses port 2200).

Tunnel Lifecycle

connect({ port: 3000 })
  → validates config (serverAddr, token, tunnelDomain required)
  → selects mode (auto → detect frpc → fallback SSH)
  → generates subdomain (or uses provided one)
  → spawns SSH/frpc process
  → waits for connection confirmation (parses stdout)
  → returns { tunnelId, url, mode, ... }
  → sets up reconnection manager (SSH mode only)

disconnect(tunnelId)
  → stops reconnection manager
  → kills process (SIGTERM)
  → cleans up frpc config file (frpc mode)
  → removes from active tunnels

shutdown()
  → disconnects all active tunnels
  → stops all reconnection managers

Events

KadiTunnelService emits standard BaseTunnelService events:

| Event | Payload | When | |-------|---------|------| | tunnelProgress | { phase, message, tunnelId, timestamp } | connecting, connected, disconnecting, disconnected, reconnecting | | tunnelCreated | { tunnelId, url, mode, port, ... } | Tunnel established | | tunnelDestroyed | { tunnelId, timestamp } | Tunnel torn down | | tunnelError | { error, tunnelId, timestamp } | Error occurred |

connect() Return Object

{
  tunnelId: 'tunnel_kadi_1234567890_abc123',  // unique ID for disconnect()
  url: 'https://my-app.tunnel.kadi.build',    // public HTTPS URL
  subdomain: 'my-app',                         // subdomain portion
  localPort: 3000,                              // port being tunneled
  createdAt: Date,                              // when tunnel was created
  status: 'active',
  service: 'kadi',                              // service name
  mode: 'ssh',                                  // actual mode used
  proxyName: 'my-agent-tunnel_kadi_...'         // frps proxy name
}

getStatus() Return Object

{
  serviceName: 'kadi',
  activeTunnels: 1,
  available: true,              // false if serverAddr/token/tunnelDomain missing
  status: 'active',             // 'active' or 'idle'
  mode: 'auto',                 // configured preference
  serverAddr: 'broker.kadi.build',
  sshAvailable: true,
  frpcAvailable: false,         // null if not yet checked
  tunnels: [{ tunnelId, url, localPort, mode, status, proxyName, createdAt }]
}

What this package exports

These are the supporting classes used internally by KadiTunnelService.js. You typically don't import them directly — they're consumed by the drop-in file.

| Export | Description | |--------|-------------| | KadiSSHMode | SSH gateway tunnel mode (ssh -R) — zero binary dependencies | | KadiClientMode | frpc binary tunnel mode — enhanced features, native reconnect | | KadiConfigGenerator | Generates frpc TOML configuration files | | KadiOutputParser | Parses frpc/ssh stdout for connection confirmation | | ReconnectionManager | Exponential backoff reconnection with jitter | | detectFrpcBinary | Detects whether frpc is available on $PATH | | generateSubdomain | Generates an 8-char random subdomain |


Server Setup

The tunnel server must be deployed separately. See the server README for full VPS deployment instructions. The server consists of:

  • frps (frp server) — handles tunnel protocol + SSH gateway
  • Caddy — reverse proxy with automatic Let's Encrypt TLS for *.tunnel.{domain}

Required DNS records: A record for server hostname + wildcard A record for *.tunnel.{domain}.


Development

# Run all tests (unit + integration)
npm test

# Unit tests only
npm run test:unit

# SSH mode tests
npm run test:ssh

# frpc client mode tests
npm run test:frpc

# Integration tests (requires running tunnel server)
npm run test:integration

License

UNLICENSED