tungent

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.

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.

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

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.