# Tunnel — SDK & CLI

> The clutchcall CLI surface: login, claim, http, p2p, devices, topup — plus config, env vars, and the data-plane engine flags.

The tunnel modality is driven by the **`clutchcall` CLI** and its QUIC
data-plane engine. There is no typed `Tunnel` SDK class today.

> **NOTE:**
> A programmatic client (embed a tunnel in your own process) is **Preview** — see
> [Programmatic API (Preview)](#programmatic-api-preview) at the bottom. For
> shipping work, use the CLI documented here.

## Install

**One-liner:**
```sh
curl -fsSL https://clutchcall.dev/clutchcall/install.sh | sh
```

**Homebrew / manual:**
```sh
# Single static binary, no runtime deps — drop it on $PATH
clutchcall version
```

The CLI is a pure-static binary for Windows, macOS, and Linux (amd64 / arm64). It
launches a separate QUIC engine for the data path; the engine ships next to the
CLI or is pointed at with an env var (see [Config](#config)).

## Sign in

```sh
clutchcall login      # provider device flow — the provider hosts the UI
```

No credentials are stored. The CLI persists only a short-lived session JWT and a
device id under your user config dir (`0600`). Sign out with
`clutchcall logout`.

## Command surface

| Command | What it does |
| --- | --- |
| `clutchcall login` | Sign in via device flow → session token |
| `clutchcall logout` | Forget the saved session |
| `clutchcall status` | Plan, devices (n/5), wallet balance, this-month egress |
| `clutchcall devices` | List your devices |
| `clutchcall devices rm <id>` | Remove a device, freeing a slot on the free plan |
| `clutchcall claim <name>` | Reserve a subdomain → `https://<name>.clutchcall.dev` |
| `clutchcall http <port> [--name n]` | Expose `localhost:<port>` at a public HTTPS URL |
| `clutchcall p2p share <pair-id> --port <p>` | Share a local service direct to a peer (no relay) |
| `clutchcall p2p connect <pair-id> --listen <p>` | Reach a peer's shared service directly |
| `clutchcall topup <amount>` | Add wallet balance |
| `clutchcall version` | Print version |

---

## `http` — expose a local service

```sh
clutchcall http <local-port> [--name <sub>] [--slug <slug>]
```

Dials the nearest edge PoP over QUIC and forwards inbound public traffic to
`localhost:<local-port>`. Prints the public URL and runs until interrupted.

<ParamField path="<local-port>" type="int" required>
  The local TCP port to expose (e.g. `3000`).
</ParamField>
<ParamField path="--name" type="string">
  A label appended to your reserved subdomain: with handle `alice`,
  `--name api` → `https://alice-api.clutchcall.dev`. Requires a claimed handle.
</ParamField>
<ParamField path="--slug" type="string">
  Override the slug explicitly (power users). The edge still enforces your
  namespace from the session token, so a stale slug can't escape it.
</ParamField>

```sh
clutchcall claim alice          # reserve https://alice.clutchcall.dev
clutchcall http 3000            # → https://alice.clutchcall.dev
clutchcall http 8080 --name api # → https://alice-api.clutchcall.dev
```

> **TIP:**
> WebSocket upgrades, server-sent events, and chunked/streaming responses pass
> through unchanged — each public connection is its own QUIC stream.

---

## `p2p` — direct peer-to-peer

Two peers run with the **same pair-id**; they auto-discover via the control-plane
rendezvous and connect **directly**, with the edge brokering candidates but never
carrying bytes. Across NATs, set the `<P>_STUN` env var (where `<P>` is your CLI's
uppercased brand prefix) to a reflector so each side can gather
its public candidate.

**Share (the box with the service):**
```sh
clutchcall p2p share <pair-id> --port <local-port> [--proto tcp|udp|http]
```

**Connect (the box reaching in):**
```sh
clutchcall p2p connect <pair-id> --listen <local-port>
```

<ParamField path="<pair-id>" type="string" required>
  A shared secret both peers know; gates the direct tunnel and keys the rendezvous.
</ParamField>
<ParamField path="--port" type="int">
  (share) The local service port to expose to the peer.
</ParamField>
<ParamField path="--listen" type="int">
  (connect) A local port to listen on; connections to it are piped to the peer's
  shared service.
</ParamField>
<ParamField path="--proto" type="string" default="tcp">
  (share) `tcp`, `udp`, or `http`.
</ParamField>
<ParamField path="--stun" type="string">
  STUN reflector `host:port` for reflexive candidate gathering (or set `<P>_STUN`).
</ParamField>

```sh
# Box A shares its SSH; Box B reaches it on its own port 8022
A$ clutchcall p2p share mylink --port 22
B$ clutchcall p2p connect mylink --listen 8022
B$ ssh -p 8022 localhost       # rides the direct QUIC link
```

> **NOTE:**
> Endpoint-independent (cone) NATs punch through directly. Symmetric NATs fall back
> to the relayed path.

---

## `status` & `devices`

```sh
clutchcall status     # plan, devices (n/5), wallet balance, egress this month
clutchcall devices    # list devices; current device is marked
clutchcall devices rm dev-1a2b…   # free a slot
```

The free plan caps you at 5 devices. Each install gets a stable device id, shown
in `devices` and enforced by the session token's device claim.

---

## Config

CLI state lives at your user config dir, e.g. `~/.config/clutchcall/config.json`
(mode `0600`). It stores only non-secret session state:

```json
{
  "api_base":    "https://api.clutchcall.dev",
  "device_id":   "dev-…",
  "device_name": "my-laptop",
  "token":       "<short-lived session JWT>",
  "expires_at":  "…",
  "handle":      "alice"
}
```

### Environment variables

Env vars use your CLI's brand prefix — shown below as `<P>` (the uppercased binary
name, e.g. `clutchcall` → its uppercase form).

| Var | Purpose | Default |
| --- | --- | --- |
| `<P>_API` | Control-plane base URL | `https://api.clutchcall.dev` |
| `<P>_POP` | QUIC edge `host:port` (data plane) | `<base>:4443` |
| `<P>_STUN` | STUN reflector `host:port` for P2P | — |
| `<P>_TUNNEL_BIN` | Path to the QUIC data-plane engine | next-to-binary / `$PATH` |
| `<P>_BASE_DOMAIN` | Public base domain for printed URLs | `clutchcall.dev` |

### Data-plane engine flags

`clutchcall http` execs the QUIC engine (`clutchcall-engine`) with
the resolved arguments. You rarely call it directly, but the surface is:

```sh
clutchcall-engine http <port> --server <pop> --token <jwt> [--slug <slug>]
clutchcall-engine p2p share <proto> <port> --quic-port <p> --peer <ip:port> --token <pair-id> [--stun <addr>]
clutchcall-engine p2p connect --quic-port <p> --peer <ip:port> --listen <port> --token <pair-id> [--stun <addr>]
```

---

## Remote desktop on the same transport

A remote-desktop client (a fork of an open-source remote-desktop project) runs its
network transport over this **same raw-QUIC relay** instead of its own
rendezvous/relay. Enable it by pointing the client's `quic-relay-server` option at
a `host:port` running the tunnel module:

- The **controlled box** registers its device id as a slug (`proto":"quick"`) and
  parks bidi work streams.
- The **controller** dials the box by slug; the edge splices the two QUIC streams.
- The remote-desktop client's own message framing and end-to-end encryption ride
  unchanged on top — the edge only secures the QUIC hop to the PoP and never sees
  cleartext.

This is the **remote-desktop tunnel type** in the [details page](/modalities/tunnel/details#tunnel-types);
no extra CLI is needed beyond the relay endpoint.

---

## Programmatic API (Preview)

> **WARNING:**
> **Preview / forward-looking.** A typed `Tunnel` client to embed a tunnel inside
> your own process is not in the shipping SDK. Ship with the CLI today. The shape
> below is illustrative and may change.

  <Tab title="TypeScript (Preview)">
```ts
// Preview — not yet shipped
import { Tunnel } from "@clutchcall/sdk/tunnel";

const tunnel = new Tunnel({
  pop:   "edge.clutchcall.dev:4443",
  token: process.env.CLUTCHCALL_CREDENTIALS!,
});

const { url } = await tunnel.http({ port: 3000, slug: "alice" });
console.log("public URL:", url);   // https://alice.clutchcall.dev
await tunnel.close();
```
  </Tab>
  <Tab title="Python (Preview)">
```python
# Preview — not yet shipped
from clutchcall.tunnel import Tunnel

tunnel = Tunnel(pop="edge.clutchcall.dev:4443", token=TOKEN)
handle = tunnel.http(port=3000, slug="alice")
print("public URL:", handle.url)
tunnel.close()
```
  </Tab>

## Related

- [Tunnel overview](/modalities/tunnel/details) — wire model & architecture
- [Cookbook](/modalities/tunnel/cookbook) — task snippets
- [Recipes](/modalities/tunnel/recipes) — worked examples
