Skip to main content

CLI — pc

pc is the command-line tool for Proxima Console. It provides native terminal access, host management, and file transfer from your local shell — no browser needed.

Installation​

curl -sSL https://install.prxm.uz/pc | bash

Detects your OS and architecture, downloads the latest binary, verifies the SHA-256 checksum, and installs to /usr/local/bin/pc.

Build from Source​

cd cli && go build -o pc ./cmd/pc
sudo mv pc /usr/local/bin/

Authentication​

pc login requires you to choose an authentication method with --auth. There is no default — running bare pc login errors and lists the methods this proxy supports.

pc login --auth <google|local|apikey> [--user <email>] [--no-browser]
[--proxy <addr>] [--insecure] [--ca-cert <path>] [--json|--quiet]
MethodCommandUse it for
googlepc login --auth googleGoogle SSO. Delivery (browser vs. paste-the-code) is auto-detected. MFA is handled in the browser/paste flow.
localpc login --auth localEmail + password (+ TOTP/recovery code if enrolled), entered in the terminal.
apikeypc login --auth apikeyA service-account API key, for automation and CI.

The --user flag pre-fills the email for --auth local; --no-browser forces the paste-the-code delivery for --auth google. Each is only valid with its own method — using --user with a non-local method, or --no-browser with a non-google method, is a usage error.

The TLS cert-pinning / trust prompt (see below) runs before method dispatch, exactly as on every login.

--auth google — Google SSO​

pc login --auth google

Authenticates via Google SSO. The CLI auto-detects how to deliver the login:

  • Browser + loopback callback when a display is available (macOS, Windows, or Linux/BSD with $DISPLAY or $WAYLAND_DISPLAY set). pc opens your browser and listens on localhost for the callback. If the browser can't open or the listener can't bind, pc automatically falls back to the paste-the-code flow.
  • Paste-the-code on a headless or remote shell (no display), or whenever you pass --no-browser. pc prints the SSO URL — open it on any device, sign in, and paste the one-time code back:
pc login --auth google --no-browser
Open this URL in a browser on any device to log in:
https://api-console.prxm.uz/api/v1/auth/google?cli_manual=1

Paste the code shown in your browser: A1B2C3D4
Logged in as [email protected]

The code is single-use and expires after 5 minutes. If your account has TOTP MFA enabled, the second-factor page is presented in the browser (or the device you opened the paste URL on) right after Google sign-in — enter your authenticator or recovery code there, and the CLI is logged in once it passes.

--no-browser is the single delivery override; you rarely need it, because the headless case is detected automatically.

--auth local — Email + Password​

pc login --auth local                       # prompts for email
pc login --auth local --user [email protected] # pre-fill the email

Prompts for the email (unless --user is given) and password. If TOTP MFA is enabled, prompts for a 2FA code (or recovery code) in the terminal:

Email: [email protected]
Password: ********
MFA required (methods: totp)
Enter 2FA code (or recovery code): 123456
Logged in as [email protected]

This flow works fully headless — no browser or display required.

--auth apikey — Service Account Key​

pc login --auth apikey

Authenticates with a service-account API key. The key is read from, in order:

  1. the PC_API_KEY environment variable,
  2. piped stdin (echo "$KEY" | pc login --auth apikey), or
  3. an interactive hidden prompt (input is not echoed).
export PC_API_KEY=prxm_your_api_key
pc login --auth apikey

# or pipe it (CI):
echo "$PC_API_KEY" | pc login --auth apikey

API keys bypass MFA and don't expire. This is the method to use in CI and automation.

Logout​

pc logout            # revoke the server session, then delete local creds
pc logout --local # delete local creds only (offline / already-revoked)

For token-based logins (google / local), pc logout first calls POST /api/v1/auth/logout to revoke the server session and refresh token, then deletes the stored credentials (the pinned server certificate is kept for TOFU continuity). The server call is best-effort: if it fails (offline, network error), pc prints a warning and still deletes the local credentials, so you're never stranded.

For apikey logins there is no server session to revoke, so pc logout deletes locally only.

Pass --local to skip the server call entirely — useful when you're offline or the session is already revoked.

Migration from the old flags​

The old pc login flags (--browser, --manual, --email, --api-key) have been removed in favor of the --auth method selector. Map your old command as follows:

Old flagNew command
pc login --browserpc login --auth google
pc login --manualpc login --auth google --no-browser
pc login --email <e>pc login --auth local --user <e>
pc login --api-key <k>pc login --auth apikey (key via $PC_API_KEY or the hidden prompt)

Bare pc login no longer defaults to an API-key prompt — --auth is now required.

TLS Certificate Pinning​

On first login, pc fetches the server's TLS certificate and asks you to verify it:

Server certificate fingerprint:
SHA256:2e:26:57:3b:aa:e0:22:6a:...
Issuer: E7
Subject: api-console.prxm.uz
Expires: 2026-07-09
Trust this server? [y/N]: y
Certificate pinned.

After pinning, every connection — both the HTTPS API calls and the gRPC terminal/port-forward streams — verifies against the stored certificate, not just pc login. If the certificate changes unexpectedly, pc fails closed and refuses to connect, protecting against man-in-the-middle attacks.

If the certificate is rotated legitimately (for example, a renewed certificate), delete the pinned copy and log in again to re-pin the new one:

rm ~/.config/pc/trusted-certs/api-console.prxm.uz.pem
pc login --auth google

For enterprise deployments with private CAs:

pc login --auth google --ca-cert /etc/ssl/corp-ca.pem

The --insecure flag skips TLS verification (prints a warning every time — use only for development).

Commands​

pc ls — List Hosts​

pc ls                          # all accessible hosts
pc ls --env production # filter by environment
pc ls --project acme-corp # filter by project
pc ls --status connected # filter by agent status
pc ls --wide # show project + environment columns
pc ls --format json # machine-readable output

Output includes live CPU, RAM, and disk metrics. Kubernetes nodes are marked with [k8s]:

ID          HOSTNAME                       STATUS    CPU    RAM    DISK    LAST SEEN
f006bdbb master01-rke2.prxm.uz [k8s] online 25% 70% 9% 5s ago
5d03345f web-prod-01 online 26% 43% 77% 13s ago

Use --wide for project and environment columns:

ID          HOSTNAME                       PROJECT     ENV            STATUS    CPU    RAM    DISK    LAST SEEN
f006bdbb master01-rke2.prxm.uz [k8s] Proxima Production online 25% 70% 9% 5s ago
5d03345f web-prod-01 Internal Development online 26% 43% 77% 13s ago

Host IDs are truncated to 8 characters in table view (full UUIDs in --format json).

pc ssh — Terminal Access​

pc ssh root@hostname
pc ssh deploy@web-01
pc ssh hostname # logs in as your local OS user (like ssh)
pc ssh root@5d03345f-xxxx-xxxx-xxxx-xxxxxxxxxxxx # by host ID

Opens an interactive terminal session via gRPC bidirectional streaming. All standard terminal features work: colors, vim, tmux, Ctrl+C, tab completion, window resizing.

If you omit user@, pc uses your local OS username as the remote login, mirroring ssh. The login you request must be permitted by your role's terminal policy — see RBAC. If it isn't, the session is rejected:

Error: session rejected: access denied: role "Team Lead" cannot use login "root" on this host

Running a single command (non-interactive):

Pass a command after the host (like ssh host cmd) to run it without a PTY and exit with the command's remote exit code — ideal for scripts and automation:

pc ssh web-01 uptime
pc ssh root@web-01 -- systemctl is-active nginx # use -- for commands with their own flags
echo "$DATA" | pc ssh web-01 -- 'cat > /tmp/out' # stdin is forwarded
pc ssh web-01 -- 'ls /etc' 2>/dev/null # remote stdout/stderr stay separate
  • Remote stdout and stderr are kept separate, so redirects and pipes behave like real ssh.
  • pc exits with the remote command's exit code — a non-zero remote exit is not a pc error (pc ssh host -- false; echo $? prints 1).
  • stdin is forwarded and closed at EOF, so echo … | pc ssh host -- cat works.
  • Use -- before the command when it contains its own flags (pc ssh host -- ls -la).
  • Combine with -q to suppress the connection banner for clean script output.
  • The same RBAC applies as interactive sessions — your role must permit the login on the host.
  • Interactive programs that need a TTY (e.g. top, vim) aren't supported in command mode yet; open an interactive shell (pc ssh host) for those.

Duplicate hostnames: When multiple hosts share the same hostname, pc ssh shows an interactive picker:

Multiple hosts match "web-01":
1) 3a7f2b1c production connected
2) 9c4e1d8f staging connected
Select [1-2]: 1

In non-interactive mode (piped input), it prints the matching host IDs and exits.

Port forwarding:

pc ssh -L 8080:localhost:80 root@web-server              # terminal + forward
pc ssh -L 5432:localhost:5432 -N root@db-server # forward only, no terminal
pc ssh -L 8080:localhost:80 -L 5432:localhost:5432 root@host # multiple tunnels

The -L localPort:remoteHost:remotePort flag forwards a local port to a remote endpoint through the agent. Multiple -L flags can be combined. The -N flag disables the terminal (forward-only mode — stays alive until Ctrl+C).

Each tunnel supports multiple concurrent TCP connections (e.g. a browser opening parallel HTTP connections).

Choosing a remote login​

pc ssh web-01 connects as your local OS username unless you write user@host explicitly. When that default is not a login the host permits and the host permits exactly one, pc substitutes that login and says so on stderr — turning a refusal into a working session. It never overrides an explicit user@host, and when several logins would work it changes nothing and lets the refusal list them, rather than picking on your behalf.

pc access — Check Terminal Permission​

Ask whether your role permits a terminal on a host, and as which logins, without attempting a connection.

pc access web-01
web-01: terminal access allowed
role: sre
logins: ops, root
max session: 8h
idle timeout: 30m
max sessions: 3

A denial states why:

db-02: terminal access denied
role: viewer
reason: your role does not allow terminal access to this host

Accepts a hostname or a host UUID, and supports --json like every other command.

Allowed with no logins is a real state

A role can permit terminal access while its terminal_spec resolves to no usable login. pc access says so explicitly rather than printing an empty list, which would read as "any login works".

pc ssh uses this automatically on failure. When a connection is refused on permission grounds, pc ssh calls the same endpoint and appends the reason, your role, and the logins you do hold. It is not called on the success path, so it adds no latency to a working connection.

pc scp — File Transfer​

pc scp localfile.conf host:/etc/app/config.conf        # upload
pc scp host:/var/log/app.log ./app.log # download
pc scp localfile.conf user@host:/tmp/file # with login
pc scp localfile.conf 5d03345f-xxxx:/etc/app/config # by host ID

Maximum file size: 768 KB. For larger files, use scp or rsync directly.

Uploaded nginx.conf → host:/etc/nginx/nginx.conf (2.3 KB, sha256:a4f28b...)
Downloaded host:/var/log/app.log → ./app.log (156 KB, sha256:7de13a...)

pc sessions — Session Management​

pc sessions ls                 # list active sessions
pc sessions ls --all # include ended sessions
pc sessions kill <session-id> # terminate a session
SESSION ID  LOGIN   STARTED   PEERS  RECORDING
47fa6c5a root 5m ago 0 recording
b2c3d4e5 deploy 22m ago 1 recording

pc sessions join — Join Active Session​

pc sessions join <session-id>                     # observe (read-only)
pc sessions join <session-id> --mode participate # can type
pc sessions join 47fa6c5a # short ID prefix

Join an active terminal session as an observer or participant via gRPC. Observers see all terminal output but cannot type. Participants can send input to the session's shell.

Short session ID prefixes work — pc resolves them to the full UUID automatically (errors if ambiguous).

Press q or Ctrl+C to leave the session.

pc play — Play Session Recording​

pc play <session-id>                    # play at 1x
pc play <session-id> --speed 2 # start at 2x speed
pc play <session-id> --skip-idle # compress idle gaps > 2s

Downloads and plays back a terminal session recording in the terminal with interactive controls:

KeyAction
spacePause / resume
+ or ]Speed up (1x → 2x → 4x → 8x)
- or [Slow down (8x → 4x → 2x → 1x → 0.5x)
q or Ctrl+CQuit

A status bar at the bottom shows elapsed time, total duration, current speed, and playback state.

Recordings must be in uploaded status (check with pc sessions ls --all). The recording is downloaded from R2 storage, decompressed, and played locally — no streaming required.

pc status — Login Status​

pc status

Shows current authentication state: user email, proxy address, auth method, token expiry, and pinned certificate info.

pc version​

pc version
# pc v0.5.1 (commit: abc1234, built: 2026-05-27)

pc rollout — Agent Updates​

Drive agent-update rollouts across the fleet from the terminal. Reading needs agents:read; creating or controlling a rollout needs agents:write.

pc rollout versions                 # backend, minimum supported, recommended
pc rollout agents # every agent with its version skew
pc rollout agents --outdated # exactly what --all-outdated would target
pc rollout ls # rollouts, newest first
pc rollout show 3f2a1b # per-agent progress for one rollout

Starting one requires an explicit agent selection — there is no implicit "everything":

pc rollout create --version v0.6.24 --all-outdated
pc rollout create --version v0.6.24 --agents 3f2a1b8c-…,9c11ff02-…
pc rollout create --version v0.6.24 --all-outdated --canary 1 --batch 5

--all-outdated selects host agents whose skew is outdated or update_available. Three exclusions, each deliberate:

  • ahead_of_backend — rolling an agent that is ahead would be a downgrade.
  • unknown — the backend could not compare the two versions at all.
  • anything that is not a host agent. Cluster collectors and k8s node monitors are updated with their deployment; the backend refuses a rollout containing one, and refuses the whole request rather than skipping it. On a real fleet these are common enough that selecting on skew alone produces a request that always fails.

Preview the exact set with pc rollout agents --outdated, which applies the same rule, so what it lists is what a rollout would target.

Control a running rollout:

pc rollout pause 3f2a1b
pc rollout resume 3f2a1b
pc rollout abort 3f2a1b # agents already updated stay updated

Rollout IDs may be shortened to any unique prefix, as with pc play. An ambiguous prefix is an error rather than a guess, because these actions apply across the fleet.

create and abort prompt for confirmation. In --json/--quiet mode, or with no TTY, they refuse rather than assuming yes — pass --yes to confirm non-interactively.

Scripting & machine-readable output​

Two global flags (available on every command) make pc automation-friendly:

FlagBehaviour
--jsonEmit machine-readable JSON to stdout. On failure, prints {"error":"…"} to stderr and exits non-zero.
--quiet / -qSuppress banners, progress, and prompts (all on stderr). If a command would prompt — an ambiguous hostname or missing credentials — it errors instead of waiting, so commands stay fully non-interactive.
pc ls --json | jq -r '.[] | select(.status=="online") | .hostname'
pc status --json | jq -r .auth_method
pc sessions ls --json
pc version --json

JSON output is a bare value — an array for list commands, an object for single results (the existing pc ls --format json output is unchanged). Failures are uniform and carry a non-zero exit code:

$ pc ls --json        # when not logged in
{
"error": "not logged in — run 'pc login' first"
}
$ echo $?
1

For running remote commands in scripts, pc ssh host -- <cmd> (see the pc ssh section above) streams raw stdout/stderr and propagates the remote exit code, so it composes directly with shell pipelines.

Configuration​

Flag / EnvDefaultDescription
--proxy / PC_PROXYapi-console.prxm.uz:443Proxy address (gRPC + HTTP via nginx)
--insecure / PC_INSECUREfalseSkip TLS verification (dev only)
--ca-cert—Path to CA certificate for TLS (login only)
--jsonfalseMachine-readable JSON output (global)
--quiet / -qfalseSuppress chrome/prompts; never prompt (global)

Credentials stored at ~/.config/pc/credentials.json (mode 0600). Pinned certificates stored at ~/.config/pc/trusted-certs/.

Token Refresh​

For SSO and password-authenticated users, pc automatically refreshes expired JWT tokens before each command. No manual re-login needed unless the refresh token itself expires.

API key users are not affected — API keys don't expire.

Architecture​

pc CLI ──gRPC/TLS──▶ istio Gateway (api-console.prxm.uz:443, HTTP/2) ──▶ Backend gRPC (:9090) ──NATS──▶ Agent ──PTY
pc CLI ──HTTPS──────▶ istio Gateway (api-console.prxm.uz:443) ──▶ Backend HTTP (:8080)

Both gRPC (terminal streaming) and HTTP (host listing, metrics, file transfer, auth) arrive on the same host and port. Routing is done by the istio Gateway API: a longest-prefix HTTPRoute sends proxima.terminal.v1.TerminalService to the backend's gRPC port and everything else to REST on :8080. TLS is terminated at the gateway with a cert-manager-issued certificate. There is no nginx in this path — the nginx vhosts in the repo are installed only by the retired single-box deploy job.

Session recording, RBAC, audit logging, and host user creation all work identically to the browser terminal.