hooky-cli
v2026.6.1
Published
Stable public URLs for webhooks via Cloudflare Tunnel — one config, one command.
Maintainers
Readme
Hooky
Give your local server a stable, public, HTTPS address on a domain you control, backed by Cloudflare Tunnel — so external providers can complete OAuth redirects and deliver webhooks back to it. One config file, three commands.
hooky init # log in to Cloudflare + scaffold hooky.config.json
hooky up # start + expose every service at a stable public URL
hooky down # stop the tunnel and servicesThe challenge
Modern applications rarely run in isolation. The moment you wire up real provider
integrations — Google OAuth, Slack, Microsoft Teams, inbound
webhooks — your app has to be reachable from the outside. A plain
localhost server cannot do that.
| Integration | Why localhost is not enough |
| ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| Google OAuth | Redirect/callback URIs must be a public HTTPS URL on a domain you control. Google won't redirect to http://localhost for most flows, and rejects .localhost subdomains (not on the Public Suffix List). |
| Slack / Teams | Slash commands, interactivity, and event subscriptions are server-to-server callbacks — the provider's servers POST to your URL. They can't reach 127.0.0.1. |
| Webhooks | GitHub, Stripe, payment and tracker webhooks are inbound calls initiated by the provider. There's no way for them to deliver to a machine behind your NAT/firewall. |
| HTTPS / TLS | Almost every provider requires https://. Local servers speak plain http:// and have no trusted certificate. |
| Stable identity | OAuth consent screens, app manifests, and webhook registrations are configured against a fixed hostname. A URL that changes every restart means re-registering everywhere, every time. |
So the real problem is not "run a server" — it's:
Give my local server a stable, public, HTTPS address on a domain I control, so external providers can complete OAuth redirects and deliver webhooks back to it.
The solution
A Cloudflare Tunnel creates an outbound-only connection from the cloudflared
daemon on your machine to Cloudflare's edge. Cloudflare then serves your chosen
hostname over HTTPS and forwards matching requests down the tunnel to your
localhost service.
Provider (Google / Slack / webhook)
│ https://api.example.online
▼
Cloudflare edge ──tunnel──► cloudflared (local) ──► http://localhost:PORTThis gives you everything the integrations demand: a public, provider-reachable URL; valid HTTPS with a trusted certificate (handled by Cloudflare); a stable hostname on your own domain that survives restarts; and no inbound firewall/NAT changes — the tunnel dials out.
Hooky automates all of it. Instead of running cloudflared tunnel login /
create / route dns / writing ingress YAML / run by hand for every service, you
declare your services once and run hooky up.
Install
npm install -g hooky-cli # or: bunx hooky-cliNode ≥ 18 (or Bun). cloudflared is auto-downloaded on first use if it isn't already
on your PATH — nothing to install manually.
Quickstart
# 1. one-time setup — logs in to Cloudflare, writes hooky.config.json
hooky init
# 2. go live — prints your stable public URLs
hooky up
# ✓ tunnel live — public URLs:
# https://api.acme.dev
# 3. stop
hooky downConfig — hooky.config.json
{
"cloudflare": { "type": "native", "zone": "acme.dev", "tunnel": "hooky-acme" },
"services": {
"api": { "command": "bun run api", "port": 4000, "hostname": "api.acme.dev" }, // spawn + expose
"app": { "command": "next dev", "port": 3000, "hostname": "app.acme.dev" },
"web": { "port": 8080, "hostname": "web.acme.dev" } // expose existing
}
}commandpresent → Hooky starts the process for you (injectsPORT).portonly → Hooky exposes an already-running port — composes withconcurrently/turbo/docker-compose.hooky up api appexposes only the named services.
All services share one named tunnel; each gets an ingress rule
(hostname → http://localhost:port) plus a catch-all 404. The mapping is persisted in
~/.hooky/, so the next hooky up reuses the same hostnames — register the URL with each
provider once. Hooky never falls back to a random URL; if something fails, it tells you.
How it works
The tunnel and DNS live in your own Cloudflare account, and your URLs are
‹host›.‹your-domain›.
Prerequisite: the domain must be on a Cloudflare account with nameservers pointed at Cloudflare (zone Active) — a constraint of named tunnels.
hooky inithandles thecloudflaredlogin and tunnel/DNS setup for you.
Development
bun install
bun test # 81 tests
bun run typecheck
bun run build # Node-compatible bundle in dist/
bun run dev -- up # run the CLI from sourceexamples/quick-tunnel-demo/run.sh is a runnable proof of the underlying mechanism (a real
cloudflared quick tunnel exposing a local server).
License
MIT
