--- name: portr-cli description: "Use when an AI agent or harness needs to operate the Portr CLI: authenticate a client, add users to teams, create HTTP/WebSocket/TCP/stub tunnels, run config-defined tunnels, inspect request logs, replay captured requests, or control tunnels through the local app-server API. This skill is compatible with Claude Code and Codex through vercel-labs/skills." --- # Portr CLI Use this skill to operate an installed `portr` binary from an AI agent, test harness, or automation script. Portr manages team membership, exposes local services through public HTTP/WebSocket or TCP tunnels, can serve stubbed templated responses, stores local HTTP request logs, can replay stored requests, and can run a local API for programmatic tunnel lifecycle control. ## Agent Rules - Prefer `portr --config ...` for automation so user config, auth tokens, and local request logs are not accidentally changed. - Treat `portr http`, `portr tcp`, `portr stub`, `portr start`, and `portr app-server` as long-running processes. Keep their process/session IDs so the harness can stop them. - Prefer `--json` for `portr logs` and `portr replay` when a harness needs to parse output. - Use `portr app-server` for programmatic lifecycle management instead of scraping TUI output. - Do not overwrite `~/.portr/config.yaml`, `~/.portr/db.sqlite`, or auth tokens unless the user explicitly asks. - Do not create public tunnels for production services or sensitive local ports unless the user explicitly asks. - Treat a generated user password as a secret. Show it only in the requested command output and never repeat, log, or persist it elsewhere. - Adding a user changes server state. Confirm the email, team slug, and role from the user's request before running the command; use `default-team` and `member` only when those values were omitted. - If a command's flags are uncertain, run `portr --help` against the installed binary. ## Command Map ```bash portr [--config ] [--help] [--version] ``` - `--config`, `-c`: YAML config path. Defaults to `~/.portr/config.yaml`. - `--help`, `-h`: show help. - `--version`, `-v`: print version. Commands: - `portr help [command]`: show general help or command-specific help. - `portr auth set`: configure client auth. - `portr admin users add`: add a user to a team. - `portr config edit`: open the default config in the OS editor. - `portr config pull`: replace the local tunnels and groups with the team template. - `portr http`: expose a local HTTP/WebSocket port. - `portr tcp`: expose a local TCP port. - `portr stub`: serve a templated response through a public HTTP tunnel without a local server. - `portr start`: start one or more tunnels from config. - `portr logs`: read local stored HTTP request logs. - `portr replay`: replay a stored HTTP request. - `portr app-server`: start a local HTTP API for harness-controlled tunnels. ## Auth And Config ```bash portr auth set --token --remote portr auth set -t -r portr config edit portr config pull ``` - `--token`, `-t`: Portr client secret token from the server/admin UI. Required. - `--remote`, `-r`: Portr server domain or URL. Required. Bare domains become HTTPS; `localhost:*` becomes HTTP unless a scheme is already provided. - `config edit` only edits the default config path. For harnesses, write a temp config file and pass `--config`. - `config pull` overwrites the default config's `tunnels` and `groups` with the team template configured in the admin dashboard, and fails if the team has no template. Everything else in the file, including `secret_key` and comments, is left alone. It also only acts on the default config path. ## Team Administration ```bash portr admin users add portr admin users add --team portr admin users add --team --role admin ``` - Uses the `server_url` and `secret_key` in the selected config. The credential must belong to an administrator of the target team; a superuser credential can target any team. - `--team`: target team slug. Defaults to `default-team` when omitted. - `--role`: `member` or `admin`. Defaults to `member`. - When GitHub auto signup is disabled, a newly created user receives an initial password and the CLI prints a `Password:` line. - When GitHub auto signup is enabled, new users are passwordless and the CLI omits the password line. - Existing users keep their credentials, so adding them to another team never resets or prints their password. - A missing default team is an error; do not create it implicitly. Minimal automation config: ```yaml server_url: example.com ssh_url: example.com:2222 tunnel_url: example.com secret_key: your-secret-key disable_tui: true disable_dashboard: true disable_update_check: true enable_request_logging: true ``` Global config keys: - `server_url`: admin/server URL. - `ssh_url`: SSH tunnel server address. - `tunnel_url`: public tunnel host suffix. - `secret_key`: client auth secret. - `use_localhost`: use HTTP rather than HTTPS for server/tunnel URLs. - `debug`: enable debug behavior. - `use_vite`: use the Vite-backed local UI path when supported by the running binary. - `dashboard_port`: local inspector port. Default `7777`. - `disable_dashboard`: skip the local inspector dashboard. - `enable_request_logging`: store HTTP request logs locally. Default `true`. - `connection_log_retention_days`: auto-delete old connection logs; `0` disables cleanup. - `health_check_interval`: health check interval in seconds. Default `3`. - `health_check_max_retries`: max health check retry count. Default `10`. - `disable_tui`: run without the interactive terminal UI. - `disable_update_check`: suppress release update checks. - `insecure_skip_host_key_verification`: skip SSH host key verification. Default `true`. ## One-Off Tunnels HTTP/WebSocket: ```bash portr http portr http --subdomain portr http -s ``` - Exposes `localhost:` as a public HTTP/WebSocket tunnel. - If `--subdomain` is omitted, Portr generates one. - Starts local request capture when request logging is enabled. TCP: ```bash portr tcp ``` - Exposes `localhost:` as a public TCP tunnel. - Use for databases and raw TCP protocols. - TCP traffic is not available through `portr logs`. Stub: ```bash portr stub --subdomain \ --response-format application/json \ --response-tmpl '{"ok": true}' portr stub --subdomain \ --response-format application/json \ --response-tmpl-file ./response.json ``` - `--subdomain`, `-s`: public stub subdomain. Required. - `--response-format`: response `Content-Type`, such as `application/json`, `application/yml`, or `text/plain`. Required. - `--response-tmpl`: inline response template. - `--response-tmpl-file`: path to response template file. - Use exactly one of `--response-tmpl` or `--response-tmpl-file`. - Stub templates can read request values using Portr's template placeholders. ## Config-Defined Tunnels Config tunnel fields: ```yaml tunnels: - name: app type: http host: localhost port: 3000 subdomain: app-dev pool_size: 2 - name: pg type: tcp host: localhost port: 5432 subdomain: pg-dev - name: mock type: stub subdomain: mock-dev response_format: application/json response_tmpl_file: ./response.json groups: frontend: [app, mock] ``` - `name`: identifier used by `portr start`. - `type`: `http`, `tcp`, or `stub`. Defaults to `http`. - `host`: local host. Defaults to `localhost` except stubs. - `port`: local port for `http` and `tcp`. - `subdomain`: public subdomain. Required for `stub`; generated for one-off HTTP when omitted. - `pool_size`: worker count for non-stub tunnels. Defaults to `2`; stubs use `1`. - `response_format`, `response_tmpl`, `response_tmpl_file`: stub response settings. Top-level `groups` maps a group name to tunnel names. A group name must not match a tunnel name, and every member must be an existing tunnel name. Start configured tunnels: ```bash portr start portr start app portr start app pg mock portr start frontend portr start frontend pg ``` - No names starts all configured tunnels. - Passing names starts only those tunnel entries. - A group name expands to its tunnel names, and can be mixed with tunnel names. ## Request Logs ```bash portr logs portr logs portr logs --count 100 portr logs -n 100 portr logs --since 2026-04-04 portr logs --since 2026-04-04T10:30:00Z portr logs --json ``` - Reads local HTTP request logs from `~/.portr/db.sqlite`. - Logs are only available for traffic captured on the current machine with request logging enabled. - Results are newest first. - Positional filter is a case-insensitive URL substring. - `--count`, `-n`: max records to return. Default `20`. - `--since`: RFC3339 timestamp or `YYYY-MM-DD`. - `--json`: emit full records. Binary body fields stay base64-encoded; UTF-8 payloads also include text fields. - Use log IDs from this output with `portr replay`. ## Request Replay ```bash portr replay portr replay --latest --subdomain portr replay --latest --subdomain --filter /api/orders --since 2026-04-04 portr replay --method POST --header 'Content-Type: application/json' --body '{"message":"hello"}' cat payload.json | portr replay --method POST --header 'Content-Type: application/json' --stdin portr replay --json ``` - Replays a stored HTTP request through the original tunnel host. - `--latest`: choose the newest matching stored request instead of passing an ID. - `--subdomain`: required with `--latest`. - `--filter`: case-insensitive URL substring filter for `--latest`. - `--since`: RFC3339 timestamp or `YYYY-MM-DD` filter for `--latest`. - `--method`: override HTTP method. - `--path`: override path and query. Must start with `/`. - `--header 'Key: Value'`: add or override a header. Repeatable. - `--drop-header `: remove inherited header. Repeatable. - `--body `: inline body override. - `--body-file `: read body override from file. - `--stdin`: read body override from stdin. - `--body-encoding`: `utf8` or `base64`. - `--json`: emit replay details, effective request, response, and structured errors. - Body sources are mutually exclusive: use only one of `--body`, `--body-file`, or `--stdin`. - Changing the body does not change the method; set `--method POST`, `PUT`, or `PATCH` when the body must matter. ## App Server For Harnesses ```bash PORTR_APP_SERVER_TOKEN=change-me portr app-server portr app-server --host 127.0.0.1 --port 7778 --token change-me ``` - Starts a local HTTP API around Portr tunnel lifecycle management. - Default bind address is `127.0.0.1:7778`. - `--host`: bind host. - `--port`: bind port. - `--token`: bearer token required by the API. - `PORTR_APP_SERVER_TOKEN`: env var alternative for `--token`. - If no token is configured, the local API is unauthenticated. Prefer a token in harnesses. API endpoints: | Method | Path | Purpose | | --- | --- | --- | | `GET` | `/api/v1/health` | Health check. | | `POST` | `/api/v1/tunnels` | Start a managed tunnel. | | `GET` | `/api/v1/tunnels` | List managed tunnels. | | `GET` | `/api/v1/tunnels/{id}` | Get one tunnel status. | | `DELETE` | `/api/v1/tunnels/{id}` | Stop one tunnel. | | `POST` | `/api/v1/tunnels/{id}/shutdown` | Stop one tunnel. | | `GET` | `/api/v1/events` | List lifecycle events. | | `GET` | `/api/v1/events?tunnel_id={id}` | List lifecycle events for one tunnel. | Create tunnel JSON: ```json { "name": "app", "type": "http", "host": "localhost", "port": 3000, "subdomain": "app-dev", "pool_size": 2, "callback_url": "http://127.0.0.1:9000/portr-events", "callback_urls": ["https://automation.example.com/webhooks/portr"] } ``` Stub tunnel JSON: ```json { "name": "mock", "type": "stub", "subdomain": "mock-dev", "response_format": "application/json", "response_tmpl": "{\"ok\": true}" } ``` Useful response fields: - `id`: app-server tunnel ID for status and shutdown. - `status`: lifecycle state such as `starting`, `running`, `stopped`, or `error`. - `type`, `host`, `port`, `subdomain`, `remote_port`, `tunnel_url`: tunnel addressing. - `callback_urls`: configured lifecycle callbacks. - `last_error`: error text when startup or shutdown fails. Harness pattern: ```bash APP_SERVER=http://127.0.0.1:7778 TOKEN=change-me curl -sS -H "Authorization: Bearer $TOKEN" "$APP_SERVER/api/v1/health" TUNNEL_ID="$(curl -sS -X POST "$APP_SERVER/api/v1/tunnels" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"app","type":"http","host":"localhost","port":3000,"subdomain":"app-dev"}' \ | jq -r '.id')" curl -sS -H "Authorization: Bearer $TOKEN" "$APP_SERVER/api/v1/tunnels/$TUNNEL_ID" curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" "$APP_SERVER/api/v1/tunnels/$TUNNEL_ID" ``` ## Choosing The Right Interface - Use `portr http`, `portr tcp`, or `portr stub` for quick one-off terminal workflows. - Use `portr start` when a harness already has a prepared config file and wants multiple named tunnels. - Use `portr app-server` when a harness must create, inspect, and stop tunnels programmatically. - Use `portr logs --json` to inspect captured traffic. - Use `portr replay --json` to replay captured traffic and parse the result.