Skip to content

Repository files navigation

Perfectscale CLI

pscli is a small Go CLI for querying Perfectscale's public API with a UI-generated service token.

It is optimized for fast terminal exploration and agent-friendly output, with:

  • stored local auth profiles
  • sensible production defaults
  • table, JSON, and JSONL output
  • workload filtering, sorting, aggregation, and CSV export
  • GitHub Actions builds for macOS, Windows, and Linux
  • Homebrew install via brew install doitintl/tap/pscli
  • Scoop install on Windows via scoop bucket add pscli ... && scoop install pscli
  • .deb/.rpm packages for direct install on Linux
  • pscli skill <agent> to install the Perfectscale coding-agent skill

What It Supports

This CLI is intentionally public-API only. Command groups: auth, clusters, namespaces, workloads, nodegroups, unevictable, automation, plus update, skill, and commands — see their dedicated sections below for subcommands and flags.

Authentication

The CLI uses Perfectscale public API service tokens from the Perfectscale UI.

To generate one:

  1. Open app.perfectscale.io
  2. Click the user circle in the bottom left
  3. Open Org Settings
  4. Open API Tokens
  5. Click Generate Token
  6. Assign a Read Only role
  7. Copy the client_id and client_secret

If you run pscli auth without saved credentials, the CLI prints the same setup guide plus a ready-to-copy login example.

Then log in:

pscli auth login

Or pass the credentials directly:

pscli auth login --client-id 'YOUR_CLIENT_ID' --client-secret 'YOUR_CLIENT_SECRET'

The CLI validates the token by calling the public auth endpoint and saves the profile locally.

Defaults

The CLI is designed so common usage does not require flags.

Default profile:

default

Default output:

table

Default public API base URL:

https://api.app.perfectscale.io/public/v1

If you do not export any environment variables and do not pass any global flags, the CLI will talk to production.

Environment Variables

Global flags can be set through environment variables:

  • PERFECTSCALE_PROFILE
  • PERFECTSCALE_OUTPUT
  • PERFECTSCALE_DEBUG
  • PERFECTSCALE_PUBLIC_API_URL

Examples:

export PERFECTSCALE_PROFILE='dev-public'
export PERFECTSCALE_PUBLIC_API_URL='https://api.dev.perfectscale.click/public/v1'
export PERFECTSCALE_OUTPUT='jsonl'

Machine-Readable Output

-o json/-o jsonl (or PERFECTSCALE_OUTPUT=json/jsonl) are explicit, deliberate choices — there's no auto-detection of "an agent is driving this." Agents and humans alike get exactly what they ask for: pass -o (or set PERFECTSCALE_OUTPUT) explicitly for machine-readable output, and leave it unset for the default table.

-o json is a single, pretty-printed document — meant to be read. -o jsonl is a compact stream, one object per line — meant to be parsed (jq -c per line, jq -s to collect). A command whose result isn't list-shaped falls back to a single compact JSON document under -o jsonl, matching jsonl's own convention.

On failure, the error is printed as a JSON object on stderr instead of the usual plain-text message, following the same pretty-vs-compact split:

{"error":{"code":"RESOURCE_NOT_FOUND","message":"cluster \"prod-a\" not found","retryable":false}}

code is a stable machine-readable string; retryable says whether retrying the same request might succeed (true for rate limits and 5xx responses). The process exit code is also mapped to the error category — 2 for a usage/flag mistake, 10/11 for an authentication/authorization failure, 20 for "not found", 21 for a conflict, 30 for a rejected request, 40/41/50 for server/network/rate-limit failures, 1 otherwise — always, not just when output is json/jsonl, so a shell script can branch on $? without parsing any output at all. hint/request_id and a few finer-grained categories are still to come (PSD-9883).

Local Credential Storage

Profiles are stored under the OS config directory.

On macOS, the default profile path is usually:

~/Library/Application Support/perfectscale-cli/profiles/default.json

Storage behavior:

  • profile directory permissions: 0700
  • profile file permissions: 0600
  • auth logout deletes the selected local profile

Installation

Homebrew (macOS and Linux)

brew install doitintl/tap/pscli

To upgrade to the latest release:

brew upgrade pscli

Scoop (Windows)

scoop bucket add pscli https://github.com/doitintl/perfectscale-cli
scoop install pscli

To upgrade to the latest release:

scoop update pscli

deb/rpm (Linux)

Download the package for your architecture from Releases and install it directly:

sudo dpkg -i pscli_<version>_linux_amd64.deb   # Debian/Ubuntu
sudo rpm -Uvh pscli_<version>_linux_amd64.rpm  # Fedora/RHEL

This is a direct package install, not a hosted apt/yum repository — there's no apt install pscli or add-apt-repository step.

From a release archive

Download the archive for your platform from Releases and extract the pscli binary onto your PATH.

Build And Run

Requirements:

  • Go 1.24+

Run directly:

go run . clusters list

Build locally:

make build
./dist/pscli clusters list

Or build the binary directly:

go build -o pscli .
./pscli clusters list

Regenerate the local public API client:

make openapi

Run tests:

go test ./...

Global Options And Short Flags

--profile/-p, --output/-o, --debug/-d, and --public-api-url/-u work at the top level and on leaf commands. Output modes: table (default, human-friendly), json (one document), jsonl (one object per line, for list commands and automation):

pscli -o jsonl workloads list -c prod-a -s waste -r desc -T 10

Short options are consistent across commands:

  • -p profile, -o output, -u public API URL, -d debug
  • -c cluster, -w period window, -n namespace
  • -m workload name, -t workload type
  • -s sort, -r order, -T top, -B bottom
  • -C min-cost, -W min-waste, -V workload view
  • -i id or client-id, -k client-secret or label key (depends on command)
  • -f export format, -F export file
  • -S min-severity, -v label value, -g node group name

Quick Start

pscli auth login
pscli auth status
pscli clusters list

See the dedicated sections below for each command group's full flags and examples.

Workload Filtering

workloads list supports client-side filtering and ranking with:

  • --cluster, -c
  • --period, -w
  • --namespace, -n
  • --name, -m
  • --type, -t
  • --min-cost, -C
  • --min-waste, -W
  • --sort, -s
  • --order, -r
  • --top, -T
  • --bottom, -B
  • --view, -V

Important:

  • the public workloads API is fixed to 30d today
  • the CLI enforces --period 30d
  • non-cluster filters are applied client-side after the workload list is fetched

Examples:

pscli workloads list -c prod-a -n kube-system -s waste -r desc
pscli workloads list -c prod-a -m api -t Deployment -C 25 -W 10
pscli workloads list -c prod-a -s cost -r desc -T 20

Workload Views

workloads list supports view presets through --view or -V.

Available views:

  • default
    • cost, waste, namespace, type, and max-indicator overview
  • capacity
    • replica counts and current vs recommended request totals
  • usage
    • summed container usage percentiles
  • policy
    • optimization policy, resilience, and mute state
  • risk
    • risk severity, risk counts, and waste counts
  • all
    • the broadest enriched workload view

Special behavior:

  • if --view all is used without explicitly setting --output, the CLI switches to jsonl
  • this makes the full enriched workload objects easier to consume in pipelines and by agents

Examples:

pscli workloads list -c prod-a -V default
pscli workloads list -c prod-a -V capacity
pscli workloads list -c prod-a -V usage
pscli workloads list -c prod-a -V policy
pscli workloads list -c prod-a -V risk
pscli workloads list -c prod-a -V all
pscli -o json workloads list -c prod-a -V all

Workload Aggregations

Cluster summary:

pscli workloads summary -c prod-a

Group by namespace:

pscli workloads group-by namespace -c prod-a -s waste -r desc -T 10

Group by workload type:

pscli workloads group-by type -c prod-a -s workloads -r desc

Group by optimization policy:

pscli workloads group-by optimization-policy -c prod-a -s waste -r desc

Group by risk severity:

pscli workloads group-by risk-severity -c prod-a -s workloads -r desc

Group by label value:

pscli workloads group-by label -c prod-a -k team -s waste -r desc

Detailed Workload Commands

Show one workload:

pscli workloads show -c prod-a -i workload-123
pscli workloads show -c prod-a -m api -n backend

Export CSV:

pscli workloads export -c prod-a -F workloads.csv
pscli workloads export -c prod-a -n kube-system -s waste -r desc -T 25

List risky workloads:

pscli workloads risky -c prod-a -S 2 -s severity -r desc -T 10

Explore workload labels:

pscli workloads labels -c prod-a
pscli workloads labels -c prod-a -k app -s waste -r desc -T 20
pscli workloads labels -c prod-a -v production

List muted workloads:

pscli workloads muted -c prod-a -s expires -r asc

Cluster Commands

List clusters:

pscli clusters list

Get cluster details:

pscli clusters get -c prod-a

Show carbon emission metrics:

pscli clusters emission -c prod-a -s value -r desc

Namespace Commands

Namespaces are derived from workloads.

Examples:

pscli namespaces list -c prod-a
pscli namespaces list -c prod-a -s workloads -r desc
pscli namespaces list -c prod-a -n kube -T 5

Node Group Commands

nodegroups list and nodegroups get wrap the public InfraFit node-groups endpoint: node count/pod count, cost and idle breakdown, CPU/memory/GPU utilization percentiles, and instance-type or Karpenter recommendations per node group.

Examples:

pscli nodegroups list -c prod-a
pscli nodegroups list -c prod-a --autoscaler-type karpenter --has-recommendations
pscli nodegroups list -c prod-a --all -o jsonl
pscli nodegroups list -c prod-a -V gpu
pscli nodegroups get -c prod-a -g clickhouse

Notes:

  • -V/--view: default (cost, CPU/memory, recommendation summary) or gpu (GPU architecture/utilization; - in GPU columns for non-GPU groups). -o json/-o jsonl always include the full payload regardless of view.
  • --autoscaler-type, --has-recommendations, --include-muted are server-side filters.
  • recommendations is a discriminated union (standard or karpenter). nodegroups list table output shows a summary (type, count, top instance type — the latter only populated for standard); nodegroups get table output additionally lists each recommended change (Karpenter: recommended value + rationale; standard: instance type + cost/savings). Use -o json/-o jsonl for the full payload, including the current value and Karpenter's raw current_config/recommended_config diff.
  • -o json wraps the list as {"node_groups": [...], "pagination": {...}}; -o jsonl emits one node group per line with no cursor.
  • --page-size is 1–500 (default 50). --page-token consumes the cursor from pagination.next (wire field: meta.pagination.next).
  • --all auto-paginates until no next cursor remains, capped by --page-cap (default 50), always requesting page size 500 regardless of --page-size since the backend recomputes the full set each request.

Unevictable Pod Commands

unevictable list, report, show, and muted wrap the public unevictable-pods endpoints: pods that autoscalers can't evict, why, and what it's costing. All four are served from the latest pre-computed snapshot for the cluster — there's no request-time recompute.

Examples:

pscli unevictable list -c prod-a
pscli unevictable list -c prod-a -n payments --reason pod_disruption_budget
pscli unevictable report -c prod-a -C 5 -s blockedCostHourly -r desc
pscli unevictable show -c prod-a -i a1b2c3d4
pscli unevictable muted -c prod-a

Notes:

  • list/report show one row per pod with reason codes concatenated (e.g. pod_disruption_budget,node_selector); report also carries node and priority. show returns full single-pod detail — remediation (fix summary, risk, confidence, current/recommended spec, unified diff) and sibling pod names — not populated by list/report.
  • -n, --reason, -g, -C/--min-blocked-cost are server-side filters (AND-combined). --reason only works on listreport's filter schema doesn't accept it.
  • --mute controls muted-finding visibility: exclude (default), include, or only.
  • -s/--sort only accepts blockedCostHourly, the only server-side sort key today.
  • muted is read-only — mute/dismissal rules can only be managed via the web app or the user API.
  • A 202 (snapshot processing) or 422 (processing failed) response surfaces as a distinct error, not a generic HTTP failure.
  • Pagination flags (--page-size, --page-token, --all, --page-cap) match nodegroups list. Snapshot metadata (time, algorithm version, summary counts) appears in the table footer and top-level -o json fields, but not in --all mode (it spans multiple snapshot reads).

Automation Commands

automation audit-logs lists the actions Perfectscale's automation took in your clusters. The endpoint is cursor-paginated and returns events from the last 30 days.

Examples:

pscli automation audit-logs
pscli automation audit-logs -c prod-a -c prod-b
pscli automation audit-logs -c prod-a -n kube-system -n default
pscli automation audit-logs --from 2026-04-01T00:00:00Z --to 2026-04-15T00:00:00Z
pscli automation audit-logs --since 24h
pscli automation audit-logs --all -o jsonl
pscli automation audit-logs --page-size 200 --after BASE64CURSOR

Notes:

  • --cluster (-c) and --namespace (-n) are repeatable. Cluster values may be UID or name.
  • --from and --to accept RFC3339 (UTC). --since accepts a relative duration (24h, 7d, 30m) and is shorthand for --from now-since.
  • --page-size is 1–5000 (default 1000).
  • --after / --before consume cursor tokens from a previous response's pagination.next / pagination.prev.
  • --all auto-paginates forward until the server reports has_next=false, capped by --page-cap (default 50) as a safety net.
  • --execution filters client-side to one of regular-eviction, inplace-resize, or cleanup.

Command Catalog

pscli commands -o json
pscli commands -o jsonl
pscli commands

Walks the live command tree (no API call) and prints every visible command with its flags and short-flag aliases. -o json emits one document {version, cli_version, commands}; -o jsonl emits one command object per line; table (default) prints PATH and USAGE.

Each command has path (for example ["workloads","list"]), usage, description, runnable, and flags. Each flag has name, aliases, type, usage, default, env_vars, required, and runtime (true for the global profile/output/debug/public-api-url flags copied onto every command).

Update Command

pscli update

Checks the latest GitHub release against the running version and prints how to update, based on the detected install method: brew upgrade pscli, scoop update pscli, a download+install one-liner for dpkg/rpm, or a link to the releases page otherwise. Doesn't install anything itself.

-o json/-o jsonl print {"current", "latest", "update_available", "instruction"} instead of the plain-text message.

pscli --version/-v prints the same plain-text status too (always plain text, regardless of -o), skipped silently on a lookup failure.

Skill Command

pscli skill cursor
pscli skill --all

Copies the skill compiled into the binary into that agent's user-level skills directory (claude/cursor/gemini/kiro~/.<agent>/skills/perfectscale/, opencode~/.config/opencode/skills/perfectscale/, codex~/.agents/skills/perfectscale/). --all covers every agent whose config dir already exists; --force overwrites local edits after backing them up.

Shell Completion

pscli completion bash
pscli completion zsh
pscli completion fish
pscli completion powershell

Prints a completion script for the given shell to stdout. bash/zsh/ powershell complete dynamically by shelling back into pscli; fish is generated from the full command tree.

Install:

# bash
echo 'eval "$(pscli completion bash)"' >> ~/.bashrc

# zsh
echo 'eval "$(pscli completion zsh)"' >> ~/.zshrc

# fish
pscli completion fish > ~/.config/fish/completions/pscli.fish
# powershell — add to $PROFILE
Invoke-Expression (& pscli completion powershell | Out-String)

Release Workflow

CI (build.yml) runs tests on every push/pull_request — it never creates releases.

Releases are manual: Actions tab → "Release" → "Run workflow", with a bump input (patch/minor/major, default patch). release.yml cuts the version tag, then goreleaser (.goreleaser.yaml) cross-builds macOS/Windows/Linux (amd64/arm64) and publishes:

  • GitHub Release assets: archives, checksums, .deb/.rpm packages
  • Homebrew formula → doitintl/homebrew-tap (needs the HOMEBREW_TAP_APP_CLIENT_ID/HOMEBREW_TAP_APP_PRIVATE_KEY secrets; skipped gracefully until they're set)
  • Scoop manifest → this repo's own bucket/

Current asset names:

  • pscli-darwin-arm64.tar.gz
  • pscli-darwin-amd64.tar.gz
  • pscli-windows-amd64.zip
  • pscli-windows-arm64.zip
  • pscli-linux-amd64.tar.gz
  • pscli-linux-arm64.tar.gz
  • pscli_<version>_linux_amd64.deb / .rpm
  • pscli_<version>_linux_arm64.deb / .rpm

Each release archive contains a pscli binary, or pscli.exe on Windows. checksums.txt is published alongside them. The goreleaser config lives in .goreleaser.yaml. The Perfectscale coding-agent skill is compiled into the binary; install it with pscli skill <agent>.

OpenAPI Generation

The local spec is public-api.yaml; it generates internal/publicapi/client.gen.go via make openapi. Don't hand-edit the generated client — update the spec and regenerate. internal/api/client.go is the handwritten adapter on top: auth headers, response validation, and mapping into CLI types.

Known Limits

  • only service-token auth is supported
  • only the public API is supported
  • workloads are fixed to a 30d period because the public endpoint is fixed-window
  • namespace and many workload filters are client-side
  • CSV is the only export format in v1

About

A cli that uses Perfectscale's public API

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages