@fevernova90/beckon
v0.5.0
Published
AI-native local tunnels on your own domain — the beckon CLI
Maintainers
Readme
beckon
AI-native local tunnels on your own domain.
beckon upexposes your local dev ports to stable, deterministic public HTTPS URLs — driven from one command (or your AI coding agent).
npm i -g @fevernova90/beckonPrerequisites: Node ≥ 20 and a system ssh client (built in on macOS/Linux). beckon connects to
a sish edge you control.
Usage
beckon login # authenticate in the browser (OAuth); generates + registers your tunnel key
beckon up # read ./tunnel.yaml, open tunnels, print live URLs as JSON
beckon up --detach # open tunnels in a background supervisor and return immediately
beckon status # list live tunnels (prunes ones whose supervisor has died)
beckon down # tear down this repo's tunnelsup runs in the foreground by default (Ctrl-C tears the tunnels down). up --detach (-d)
forks a background supervisor that outlives your shell — its pid is recorded immediately, so a
following beckon status / status --json lists it with no race. status verifies each
supervisor is still alive and prunes dead entries, so the list is always truthful — which is
what makes it a reliable list primitive for an automating agent. beckon down stops the
supervisor (SIGTERM) and clears the entry.
Parallel sessions — --instance
By default the hostname is the stable {app}-{service}-{slug} — register it in Clerk/Stripe once.
For parallel worktrees or agents that would otherwise collide on that one host, add
--instance <id> to namespace the session:
beckon up --detach --instance pr1234 # → {app}-{service}-pr1234-{slug}.<base-domain>
beckon status # lists both the stable session and pr1234 separately
beckon down --instance pr1234 # tears down only that instanceInstances are deliberately a per-invocation flag, not a tunnel.yaml field — the committed
config stays identical across every worktree. Omit --instance and you get the canonical,
predict-before-start URL. The requested subdomain is {app}-{service}[-{instance}]; your slug is
appended by the edge itself (not by the CLI) once you're authenticated — see "Auth" below — so
the slug is always the last segment and the composed label is still capped at 63 chars (DNS
limit), rejected at up if it overflows.
beckon login <token> also works for a pasted token (CI / headless) — it still needs network
access to register the generated key. Override the edge with --edge, --ssh-port,
--base-domain, or pin your own transport key instead of the auto-generated one with
--identity.
Auth
beckon login generates an ed25519 keypair (~/.config/beckon/id_ed25519, reused on repeat
logins) and registers the public key with the dashboard under your token. That key — not the
token — is what authenticates beckon up's SSH connection; the token only authenticates the
one-time key registration. The edge itself enforces that you can only ever bind hostnames under
your own slug (it force-appends your authenticated slug to every request), so namespacing can't
be bypassed client-side.
tunnel.yaml
app: themebuilder
tunnels:
web: 5173 # → themebuilder-web-<slug>.<base-domain>
api: 3000
hook: 4000 # webhook receiver — bodies + signature headers pass through byte-exactHostnames are a pure function of {app}-{service}-{slug}, so a URL is predictable before the tunnel
starts — write it into .env / Clerk / Stripe once.
License
MIT
