Tungent — agent onboarding
Hand this file to an agent (Claude Code, Codex, ADK…). No account is needed to start. Every command below uses this deployment's own base URL, so it can be copied as-is.
Tungent gives your agent a stable HTTPS hostname with minted Basic-auth credentials for any service it runs — on a laptop, a VM, a sandbox — plus an audit of what is open. Hostnames never rotate. Your device key is your identity: the first time a key shows up, a free workspace is created for it. Signing in later and claiming attaches that workspace to an account; nothing about it changes.
0. Four words this page uses
| Word | What it means |
|---|---|
| workspace | The thing that owns your hostnames and your monthly allowance. One is created for you the first time your key appears — you do not make one. |
| slug | The workspace's short name, and the second half of every hostname it publishes: in api-witty-island-c54a, the slug is witty-island-c54a. |
| endpoint | One published thing: a hostname pointing at one port of yours. 10 on the free tier. |
| device key | The Ed25519 key the CLI writes to ~/.tungent/id_ed25519. It is your identity — there is no username. |
1. Fastest: the CLI (zero signup)
Install it. Needs Node 18+ and ssh; npm i -g tungent once it is published.
curl -fsSL https://app.dev.tungent.ai/cli/install.sh | sh
Publish a local port. This prints the URL, the user and the password, and holds the tunnel open until you stop it.
tungent up 3000 --name my-app
Endpoints are open by default: anyone with the link can open them. That suits the common case — a demo, a preview, a page you want someone to just look at — and the hostname is not guessable at a glance, but it is a link, not a secret.
Require a password with --auth. The command then prints a URL with the
credentials already in it, so sharing is still one copy-paste.
tungent up 3000 --name private --auth
--public <path> requires a password except on the paths you name, matched
exactly — for webhook receivers and OAuth callbacks that cannot send one. Naming a
path implies --auth, since an exemption means nothing on an endpoint that is
already open.
tungent up 3000 --name hooks --public /webhook --public /oauth/callback
GET /api/v1/audit lists everything currently reachable without a password —
worth checking before you finish a task.
The same key over WireGuard instead of ssh -R. An ssh -R session forwards one
port; as a WireGuard peer your machine gets an address on our side, so the edge can
reach several ports on it — and anything else that machine can route to — without a
separate session for each.
tungent up 3000 --name my-app --wg
Attach this workspace to an account whenever you like. Prints a signed link. Claiming renames nothing, moves nothing, and doubles the free egress allowance. Merging it into a workspace you already own also keeps every address you have handed out — see §4 for what that looks like.
tungent claim
The rest: list, remove, identity, print the token.
tungent ls
tungent rm <name>
tungent whoami
tungent token
First run generates ~/.tungent/id_ed25519, registers it, stores the workspace token in ~/.tungent/config.json.
The same Ed25519 key is used for ssh -R, derived into the WireGuard key, and signs the claim link.
Second machine? Join, don't start over
On the machine that already works, mint a code. It is signed by that device and valid ten minutes.
tungent invite
On the second machine, join with it rather than starting a new workspace.
tungent up 8080 --name admin --join tgi_…
A CI box with a workspace token joins without a code.
TUNGENT_TOKEN=tg_… tungent up 3000 --name build
Invites never add allowance: every device shares the workspace's endpoints and bandwidth. Agents can mint codes
with POST /api/v1/invites.
2. Nothing to install: plain ssh
First contact prints your workspace, your device port and a claim link.
ssh tunnel@ssh.dev.tungent.ai -p 2222
Then publish, using the port it gave you. As with the CLI, the endpoint is open
unless you ask otherwise: --auth requires a password and --public <path>
requires one except on that path.
ssh -R <device port>:localhost:3000 tunnel@ssh.dev.tungent.ai -p 2222 expose my-app 3000
ssh -R <device port>:localhost:3000 tunnel@ssh.dev.tungent.ai -p 2222 expose my-app 3000 --auth
The second command prints the URL and credentials and keeps the tunnel open. Any OpenSSH key works (ed25519 recommended — it doubles as your WireGuard key).
3. API (agents with an existing token, or after tungent token)
Every call: Authorization: Bearer $TUNGENT_TOKEN, JSON bodies. Sanity: GET /api/v1/whoami.
| Goal | Call |
|---|---|
| Register this device, get a token (no auth) | POST /api/v1/devices/register {public_key, name} → {token (once), workspace{slug, claim_url}, device{port, wg_address}} |
| Join an existing workspace instead | same call with {invite: "tgi_…", join_sig: <invite signed by this key>}; mint codes with POST /api/v1/invites |
| Expose something the edge can reach directly | POST /api/v1/services/expose {name, upstream:"http://10.0.0.5:8080", public_paths:[…], persistent, ttl_seconds} |
| Tunnel a local port over ssh | POST /api/v1/tunnels {name, local_port} → {ssh_command, service{url,user,password}}, run the command |
| WireGuard config for this device | GET /api/v1/wg/peers (the device: true one) → GET /api/v1/wg/peers/<id>/config, fill PrivateKey (CLI does it) |
| Expose a port through a WireGuard peer | POST /api/v1/wg/expose {peer_id, name, port, public_paths} |
| List (passwords withheld) | GET /api/v1/services |
| Require a password at creation | add auth_disabled: false to any expose call above (new endpoints are open unless you do) |
| Open a path / switch auth off / TTL | PATCH /api/v1/services/<name> {public_paths, auth_disabled, persistent, ttl_seconds} |
| Remove | DELETE /api/v1/services/<name> |
| What is open right now | GET /api/v1/audit — check before finishing a task |
| Bandwidth this month | GET /api/v1/usage |
Response of any expose: { name, hostname, url, user, password, expires_at, audit_url }.
The password is returned once; store it. Never guess it later — re-expose keeps the same credentials, a new
auth_user rotates them.
One laptop, several workspaces (consultants)
List the workspaces this device belongs to; the current one is marked.
tungent workspaces
Switch, or pass --workspace globex on any single command.
tungent use globex
Over plain ssh: expose globex/api 3000. A device joins more workspaces with invites; each has its own port and token.
Teammates
Owner → dashboard → Invite member (or POST /api/workspaces/<id>/invites) → a link valid 7 days. Whoever opens it
signed in becomes a member: full use of the workspace's services, tokens and tunnels; the owner keeps billing and
invites. Members are unlimited on every plan and never add allowance.
4. Claiming (the human, once, any time)
tungent claim prints a link signed by the device key (valid 1 h). Open it while signed in — or sign up and you
come straight back to it.
- If you had never signed up before, signing up hands you an empty account workspace named after your email. Claiming folds your working workspace straight into it, so you end up with one workspace: your existing addresses keep working, and what you publish next is named after the account. No choice to make; keeping the two apart is one click if you want it.
- If you already own a workspace with things in it, the page offers to Merge into … it, or to keep the claimed one on its own.
Merging folds one workspace into another. The clearest way to think about it: the old workspace name becomes an alias living inside the new one. Everything you have already published keeps the address it already has — old slug and all — and anything you publish from now on is named after the workspace you merged into.
Say you had been working without an account, in a workspace called witty-island-c54a, and you own one called
acme:
| Address | |
|---|---|
| Published before the merge | https://api-witty-island-c54a.tungent.ai |
| The same thing, after the merge | https://api-witty-island-c54a.tungent.ai — unchanged |
| The next thing you publish | https://dashboard-acme.tungent.ai |
Nothing you have handed out stops working, which is the point: a link you gave a colleague, a webhook URL you registered with Stripe, an OAuth redirect you configured — all of them keep resolving. Passwords, open paths and idle timers come across untouched, and so do your devices, tunnel ports, WireGuard peers and tokens. The page lists what you are about to fold in before you confirm. The dashboard then shows credentials, tunnels, peers, members, audit, usage and billing.
5. Limits and plans
Only two things are metered: live endpoints and egress. API tokens, devices and members are unlimited on every plan.
New anonymous workspaces are rate-limited per source address — a few dozen a day — to keep the free tier from being farmed. Existing workspaces and claimed accounts are unaffected; if you hit it, claim or reuse a workspace, or try tomorrow. The 429 message says which.
A tunnel that goes idle is retired after 2 hours (idle_ttl_seconds: 7200) and
comes back under the same hostname; pass --persistent, or persistent: true, to
keep it live indefinitely. The name is never recycled either way — that is the
part that does not expire.
- Free — 10 live endpoints, 1 day of logs, hostnames on
tungent.ai. Egress is 1 GB a month before the workspace is claimed and 2 GB after — claiming it is free and doubles the allowance. - Solo, $4 — 10 endpoints, 25 GB, 30 days of logs, 1 custom domain.
- Builder, $12 — 50 endpoints, 200 GB, 90 days of logs, 1 custom domain, 1 delegated zone.
- Team, $39 — 250 endpoints, 1 TB, 365 days of logs, 5 custom domains, 5 delegated zones.
Upgrade from the dashboard. Passing the egress cap returns 402 to browsers and 429
with Retry-After to API clients, rather than silently dropping traffic.
6. Rules for agents
- Treat
user/passwordas secrets — tool result or the user's secret store, never logs, commits or chatty output. - Prefer
public_pathsoverauth_disabled; if you open a host, say so and point the user toGET /api/v1/audit. - Never fabricate a credentialed URL. If the password is gone: re-expose with a new
auth_user, or the dashboard shows it. - Clean up:
DELETEone-off hostnames, or let the TTL retire them. Keeptungent up/ the ssh command running for as long as the URL must work.
WireGuard config naming
tungent up <port> --wg writes the readable config as ~/.tungent/tungent-<workspace>.conf
and brings the tunnel up through a short sibling symlink tg-<6 hex>.conf, because
wg-quick names the interface after the file and Linux caps interface names at 15
characters while workspace slugs run to 20. The CLI prints the interface, the config
name and the exact wg-quick down command.
Two workspaces on one machine therefore no longer collide on the interface name. One
case remains manual: two separate device keys on the same host each route the edge
at 10.77.0.1/32, and only one can own that route in the main table, so the second
wg-quick up reports RTNETLINK answers: File exists. Add Table = off to that
config plus a PostUp that puts the route in its own table with an ip rule from <peer address>. A single device in several workspaces is unaffected: it shares one
peer and one address.
Keeping a tunnel up across restarts
tungent up reconnects by itself: it notices a dead server in about 30 seconds
(ServerAliveInterval=15, ServerAliveCountMax=2), then retries with jittered
exponential backoff capped at 30 seconds, re-registering the tunnel each attempt
so an idle TTL or a lost registry row cannot strand it. The hostname and
credentials are stable across a reconnect because the device port is derived
from the device key, not the session. Pass --no-reconnect to opt out.
For something that must also survive a reboot, let a supervisor own it. This writes a systemd user unit on Linux, or a launchd job on macOS:
tungent install-service 3000 --name my-app
It writes the unit with --no-reconnect, so the supervisor does the restarting
rather than both of them at once, and prints the commands to enable it. On Linux
run loginctl enable-linger "$USER" so it keeps running while you are logged out.
WireGuard needs none of this: peers re-handshake on their own.