| 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 <temp-config.yaml> ... 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 <command> --help against the installed binary.
Command Map
portr [--config <path>] [--help] [--version] <command>
--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
portr auth set --token <token> --remote <domain-or-url>
portr auth set -t <token> -r <domain-or-url>
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
portr admin users add <email>
portr admin users add <email> --team <team-slug>
portr admin users add <email> --team <team-slug> --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:
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:
portr http <local-port>
portr http <local-port> --subdomain <subdomain>
portr http <local-port> -s <subdomain>
- Exposes
localhost:<local-port> as a public HTTP/WebSocket tunnel.
- If
--subdomain is omitted, Portr generates one.
- Starts local request capture when request logging is enabled.
TCP:
portr tcp <local-port>
- Exposes
localhost:<local-port> as a public TCP tunnel.
- Use for databases and raw TCP protocols.
- TCP traffic is not available through
portr logs.
Stub:
portr stub --subdomain <subdomain> \
--response-format application/json \
--response-tmpl '{"ok": true}'
portr stub --subdomain <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:
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:
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
portr logs <subdomain>
portr logs <subdomain> <url-substring-filter>
portr logs <subdomain> --count 100
portr logs <subdomain> -n 100
portr logs <subdomain> --since 2026-04-04
portr logs <subdomain> --since 2026-04-04T10:30:00Z
portr logs <subdomain> --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
portr replay <request-id>
portr replay --latest --subdomain <subdomain>
portr replay --latest --subdomain <subdomain> --filter /api/orders --since 2026-04-04
portr replay <request-id> --method POST --header 'Content-Type: application/json' --body '{"message":"hello"}'
cat payload.json | portr replay <request-id> --method POST --header 'Content-Type: application/json' --stdin
portr replay <request-id> --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 <name>: remove inherited header. Repeatable.
--body <value>: inline body override.
--body-file <path>: 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
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:
{
"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:
{
"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:
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.