@kyndlo/cftunnel
v1.0.0
Published
CLI + library to manage Cloudflare Tunnels, ingress routes, and DNS records for AI agents
Downloads
131
Maintainers
Readme
cftunnel
A CLI + library to manage Cloudflare Tunnel traffic routing, built for AI agents to programmatically expose local services to the internet.
Built with the official Cloudflare Node.js SDK and Commander.js.
Overview
cftunnel lets you create Cloudflare Tunnels, configure ingress routes (hostname-to-service mappings), manage DNS records, and run cloudflared -- all from a single CLI that outputs JSON for easy parsing by AI agents and scripts. It also exports every operation as a library function for direct use in Node.js/TypeScript code.
What it does
Internet → Cloudflare Edge → Cloudflare Tunnel → cloudflared → localhost:PORT
(DNS CNAME) (ingress route) (connector) (your app)- Tunnel - A secure outbound-only connection from your machine to Cloudflare's edge
- Ingress Route - Maps a public hostname to a local service (e.g.
app.example.com→http://localhost:3000) - DNS CNAME - Points your domain at the tunnel (
app.example.com→<tunnel-id>.cfargotunnel.com) - cloudflared - The connector daemon that maintains the tunnel connection
Installation
npx (zero-install)
npx cftunnel tunnel listGlobal install
npm i -g cftunnelAs a library
npm i cftunnelPrerequisites
- Node.js >= 18
- A Cloudflare account with a registered domain
- A Cloudflare API Key + account email, or an API Token with these permissions:
- Account > Cloudflare Tunnel > Edit
- Zone > DNS > Edit
cloudflaredinstalled (theruncommand will auto-detect from PATH or use thecloudflarednpm package)
Getting your credentials
| Credential | Where to find it | |---|---| | API Key | Cloudflare Dashboard → My Profile → API Tokens → Global API Key | | API Token | Cloudflare Dashboard → My Profile → API Tokens → Create Token | | Account ID | Cloudflare Dashboard → any domain → Overview → right sidebar | | Zone ID | Cloudflare Dashboard → your domain → Overview → right sidebar | | Email | The email address on your Cloudflare account |
Authentication
cftunnel supports two auth methods:
API Token (Bearer auth)
export CLOUDFLARE_API_TOKEN=your-api-token
# or
npx cftunnel tunnel list --api-token your-api-tokenAPI Key + Email (Global key auth)
export CLOUDFLARE_API_KEY=your-api-key
export [email protected]
# or
npx cftunnel tunnel list --api-key your-api-key --api-email [email protected]Account ID
Required for all tunnel operations:
export CLOUDFLARE_ACCOUNT_ID=your-account-id
# or
npx cftunnel tunnel list --account-id your-account-idCLI Commands
Quickstart (all-in-one)
Create a tunnel, configure routing, set up DNS, and get the run token in a single command:
npx cftunnel quickstart \
--name my-app \
--hostname app.example.com \
--service http://localhost:8080 \
--zone-id your-zone-idOutput:
{
"tunnel_id": "uuid",
"tunnel_name": "my-app",
"hostname": "app.example.com",
"service": "http://localhost:8080",
"dns_record": "record-id",
"token": "eyJ...",
"run_cmd": "cloudflared tunnel run --token eyJ...",
"install_cmd": "sudo cloudflared service install eyJ..."
}Tunnel Management
# Create a new tunnel
npx cftunnel tunnel create my-tunnel
npx cftunnel tunnel create my-tunnel --config-src local
# List all tunnels
npx cftunnel tunnel list
npx cftunnel tunnel list --name my-tunnel
# Get tunnel details
npx cftunnel tunnel get <tunnel-id>
# Delete a tunnel
npx cftunnel tunnel delete <tunnel-id>
# Get the cloudflared run token
npx cftunnel tunnel token <tunnel-id>Ingress Route Management
Ingress routes control how traffic arriving at the tunnel is forwarded to local services.
# List current routes
npx cftunnel route list <tunnel-id>
# Add a route (appends before the catch-all 404)
npx cftunnel route add <tunnel-id> --hostname app.example.com --service http://localhost:8080
npx cftunnel route add <tunnel-id> --hostname api.example.com --service http://localhost:3000 --path "/v1/*"
# Remove a route by hostname
npx cftunnel route remove <tunnel-id> --hostname app.example.com
# Replace all routes at once
npx cftunnel route set <tunnel-id> \
--route app.example.com=http://localhost:8080 \
--route api.example.com=http://localhost:3000Supported service protocols: http://, https://, unix://, tcp://, ssh://, rdp://, unix+tls://, smb://, http_status:<code>
DNS Management
# Create a CNAME record pointing to a tunnel
npx cftunnel dns create --zone-id <zone-id> --hostname app.example.com --tunnel-id <tunnel-id>
# List DNS records for a zone
npx cftunnel dns list --zone-id <zone-id>
# Delete a DNS record
npx cftunnel dns delete <record-id> --zone-id <zone-id>Run cloudflared
# Run cloudflared in the foreground
npx cftunnel run <tunnel-id>
# Install cloudflared as a system service (persists across reboots)
npx cftunnel run <tunnel-id> --install-serviceLibrary API
Every CLI operation is also available as a library function:
import {
createClient,
createTunnel,
listTunnels,
getTunnel,
deleteTunnel,
getTunnelToken,
listRoutes,
addRoute,
removeRoute,
setRoutes,
createDNS,
listDNS,
deleteDNS,
quickstart,
} from 'cftunnel';
// Create a client (reads from env vars if no opts provided)
const client = createClient({ apiKey: '...', apiEmail: '...' });
// Create a tunnel
const tunnel = await createTunnel(client, {
accountId: '...',
name: 'my-app',
});
// Add a route
await addRoute(client, {
accountId: '...',
tunnelId: tunnel.id,
hostname: 'app.example.com',
service: 'http://localhost:3000',
});
// Create DNS
await createDNS(client, {
zoneId: '...',
hostname: 'app.example.com',
tunnelId: tunnel.id,
});
// Or do it all at once
const result = await quickstart(client, {
accountId: '...',
name: 'my-app',
hostname: 'app.example.com',
service: 'http://localhost:3000',
zoneId: '...',
onProgress: (msg) => console.error(msg),
});
console.log(result.run_cmd);End-to-End Examples
Expose a local web app
# 1. Start your app
python3 -m http.server 3000 &
# 2. Set credentials
export CLOUDFLARE_API_KEY=your-key
export [email protected]
export CLOUDFLARE_ACCOUNT_ID=your-account-id
# 3. One-shot setup
npx cftunnel quickstart \
--name web-app \
--hostname app.example.com \
--service http://localhost:3000 \
--zone-id your-zone-id
# 4. Run the tunnel connector
npx cftunnel run <tunnel-id-from-output>
# Site is now live at https://app.example.comAdd a second service to an existing tunnel
# Add API backend on a subdomain
npx cftunnel route add <tunnel-id> \
--hostname api.example.com \
--service http://localhost:4000
# Create DNS for it
npx cftunnel dns create \
--zone-id <zone-id> \
--hostname api.example.com \
--tunnel-id <tunnel-id>Multiple services on one tunnel
npx cftunnel route set <tunnel-id> \
--route app.example.com=http://localhost:3000 \
--route api.example.com=http://localhost:4000 \
--route admin.example.com=http://localhost:5000Output Format
All commands output JSON to stdout. Status/progress messages go to stderr. This makes it easy to parse output programmatically:
# Get tunnel ID from create output
TUNNEL_ID=$(npx cftunnel tunnel create my-app | jq -r '.id')
# Get the run token
TOKEN=$(npx cftunnel tunnel token $TUNNEL_ID | jq -r '.token')
# Run it
cloudflared tunnel run --token $TOKENGlobal Flags
| Flag | Env Var | Description |
|---|---|---|
| --api-token | CLOUDFLARE_API_TOKEN | Cloudflare API token (bearer auth) |
| --api-key | CLOUDFLARE_API_KEY | Cloudflare API key (requires --api-email) |
| --api-email | CLOUDFLARE_EMAIL | Cloudflare account email |
| --account-id | CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID |
Architecture
cftunnel/
├── src/
│ ├── index.ts # Library exports (public API)
│ ├── cli.ts # CLI entry point (commander setup)
│ ├── client.ts # Cloudflare client factory + auth
│ ├── utils.ts # printJSON, exitError, parseRoute
│ ├── commands/
│ │ ├── tunnel.ts # CLI: tunnel create/list/get/delete/token
│ │ ├── route.ts # CLI: route list/add/remove/set
│ │ ├── dns.ts # CLI: dns create/list/delete
│ │ ├── run.ts # CLI: run cloudflared
│ │ └── quickstart.ts # CLI: all-in-one
│ └── lib/
│ ├── types.ts # All interfaces
│ ├── tunnel.ts # Core: tunnel operations
│ ├── route.ts # Core: ingress route operations + merge helpers
│ ├── dns.ts # Core: DNS operations
│ └── quickstart.ts # Core: quickstart orchestration
├── package.json
├── tsconfig.json
└── tsup.config.tsBuilt on:
- cloudflare - Official Cloudflare Node.js SDK
- commander - CLI framework
- cloudflared - Cross-platform cloudflared binary management
- tsup - TypeScript bundler (dual ESM/CJS)
License
MIT
