The official command-line interface for episki.
brew install episki/tap/episkicurl -sSf https://cli.episki.com/install.sh | shRun without installing:
nix run github:episki/episki-cli -- auth statusInstall into your profile:
nix profile install github:episki/episki-cliRequires Nix with flakes enabled.
Requires Go 1.25+.
go install 'github.com/episki/episki-cli/cmd/episki@latest'The binary lands in $(go env GOPATH)/bin. Add that to your PATH if commands aren't found.
episki auth login # browser sign-in (Google by default)
episki workspaces list # workspaces you belong to
episki workspaces use <id|slug> # pick the active workspace
episki work-items list # you're in businessepiski [resource] <command> [flags...]| Resource | Commands |
|---|---|
auth |
login, logout, status, whoami, refresh |
workspaces (ws) |
list, current, use <id|slug> |
frameworks |
list, get <id> |
controls |
list, get <id|ref> |
programs |
list, get <id> |
work-items (wi, tasks) |
list [--kind K] [--archived], get <id|ref>, update <id|ref> --status/--due/--name, archive, restore |
evidence |
list, get <id>, upload <file> |
policies |
list, get <id> |
risks |
list, get <id|ref> |
vendors |
list, get <id> |
obligations |
list, get <id|ref> |
exceptions |
list, get <id> |
goals |
list, get <id> |
Assessments, reviews, decisions and the rest of the work-item kinds are
work-items list --kind <kind> — they all live in one table.
List commands take --limit N (default 50, server caps at 1000) and --json
for scripting; get always prints JSON. For help on any command, append --help.
episki evidence upload <file> puts a local file into the workspace's
evidence store — the point of a CLI for this is piping artifacts straight out
of a CI job:
episki evidence upload ./scan-report.pdf --type export --source "ci: nightly-scan"
episki evidence upload ./soc2.pdf --evidence 7f3c… # attach to an existing recordBy default a new evidence record is created, named after the file; --evidence
attaches to an existing one instead. Files are de-duplicated by SHA-256 across
the workspace, so re-uploading bytes that are already there reports the
existing record and uploads nothing — safe to run on every CI build.
The bucket accepts PDF, ZIP, PNG/JPEG/WebP/GIF, Word/Excel, and plain text/CSV/Markdown, up to 50 MiB. The bytes go straight from your machine to storage on a short-lived signed URL; the app only ever sees the file's name, size, and hash.
The CLI talks to the episki Supabase Data API (PostgREST) using the signed-in user's JWT. All authorization is enforced by Row Level Security policies in Postgres — the CLI grants no permissions of its own; whatever your user can see and do in app.episki.com is what you can see and do here.
episki auth login runs an OAuth PKCE flow against episki's auth in your
browser. Tokens are stored in your OS keychain and refreshed automatically.
episki auth login --email you@example.com signs in with a 6-digit code
emailed to you, which the CLI then prompts for — no browser round-trip. Add
--code <n> to pass a code from a previous send non-interactively, which is
the shape CI wants.
Credentials are resolved in this order:
--api-key <jwt>flagEPISKI_API_KEYenvironment variable (a user-scoped access token)- OAuth session from
episki auth login(stored in your OS keychain)
Run episki auth status to see which credential is active.
The
--api-key/EPISKI_API_KEYpaths are intended for non-interactive use (CI scripts) and expect a user-scoped access token, never a service-role or secret key.
Everything in episki is scoped to a workspace, and the scoping lives in
your token: the active workspace id is a claim on your JWT
(app_metadata.workspace_id) that every RLS policy reads. Without it, all
entity commands would return nothing — so the CLI errors with a hint instead.
episki workspaces use <id|slug>asks the episki app to stamp the claim, then refreshes your session so the new token carries it.- If you switch workspaces in the web app instead, run
episki auth refreshto pick the change up. episki auth statusandepiski workspaces currentshow the active workspace.
--api-key— user access token for non-interactive use.--debug— Enable debug logging.--version,-v— Show the CLI version.--help— Show command-line usage.
| Variable | Description |
|---|---|
EPISKI_API_KEY |
User access token for non-interactive use. |
SUPABASE_URL |
Override the Supabase project URL (e.g. local dev). |
SUPABASE_KEY |
Override the publishable key (sb_publishable_*). |
SUPABASE_ANON_KEY |
Legacy alias for SUPABASE_KEY; wins if both are set. |
EPISKI_APP_URL |
Override the web app origin used by workspaces use. |
EPISKI_INSTALL_DIR |
Override the install dir for episki upgrade. |
EPISKI_NO_UPDATE_CHECK |
Set to 1 to silence the daily update notice. |
User config lives at ~/.config/episki/config.toml (or
$XDG_CONFIG_HOME/episki/config.toml). The defaults baked into the binary
point at the production episki project (https://api.episki.com); override
per-host as needed:
app_url = "https://app.episki.com"
[supabase]
url = "https://api.episki.com"
anon_key = "sb_publishable_..." # publishable key; field name is historical
provider = "google" # default OAuth provider for `episki auth login`For local development against supabase start, set SUPABASE_URL=http://127.0.0.1:54321
and SUPABASE_KEY to your local publishable key.
episki upgrade # latest
episki upgrade --version 0.3.1 # pin a version
episki upgrade --force # reinstall currentSet EPISKI_INSTALL_DIR to override the install location.
When a newer release is available, episki prints a one-line notice on
stderr after the command runs (at most once per day). To disable:
export EPISKI_NO_UPDATE_CHECK=1./scripts/run auth status # run the CLI from source
go test ./... # unit tests
go vet ./...Dependencies are vendored (the Nix flake relies on it) — after changing
go.mod, re-run go mod vendor and commit the result.
Releases are automated: release-please opens a version PR off main;
merging it tags a release and goreleaser builds darwin/linux (amd64/arm64)
archives, publishes them to GitHub Releases, and pushes a Homebrew formula
to episki/homebrew-tap.
One-time setup before the first release:
- Create the
episki/episki-cliGitHub repo and pushmain. - Create an empty
episki/homebrew-taprepo. - Add a
TAP_GITHUB_TOKENrepo secret (a token with push access to the tap) — the defaultGITHUB_TOKENcan't write to other repos. - Serve
bin/install.shathttps://cli.episki.com/install.sh(theepiski upgradecommand and the curl installer both point there).