earnie CLI (command-line interface) lets pipelines, scripts, developers, and coding agents work with Earnie without opening the web app. Use it to:
- run a scan from a CI pipeline and fail the build when a policy blocks
- check your staged changes before you commit
- read a project’s policies, or a scan’s verdict, from a script
- export an SBOM
- scan an AI model file that doesn’t live in a repository
- connect a coding agent such as Claude Code or Cursor to Earnie
- It never picks a project for you. You say which project a command applies to, or the CLI works it out from your Git repository. See Choosing the Project.
- Its JSON output is versioned. Automation built on version 1 keeps working when the CLI adds fields. See JSON Output.
Before You Start
You need three things:- Your Earnie API URL: the address of your organisation’s Earnie, for example
https://your-company.earnie.dev. It’s the address you open the web app at. - An API key: the credential the CLI uses to act for you. An Admin or Operator creates it in Settings, on the API Keys tab. Pick the preset for the job: CI pipeline for a pipeline, or Coding agent (MCP) for an agent. See API keys.
- A project in Earnie for the code you want to check. Most projects come from connecting a repository.
Installing
Choose one of four ways to install the CLI. Homebrew (macOS or Linux)amd64 or arm64. It checks the download against the release’s SHA-256 checksums before installing.
- Where it installs.
~/.local/binby default. SetEARNIE_INSTALL_DIRto install somewhere else. The script never usessudo: if the directory isn’t writable, it stops and asks you to choose another. - A specific version. Set
EARNIE_VERSIONto a version number without thev, for exampleEARNIE_VERSION=0.3.1. - Signature check. If
cosignis on yourPATH, the script also verifies the release’s signature onchecksums.txt, and stops if it’s bad. Withoutcosign, it prints that it skipped the signature check, with the exactcosign verify-blobcommand to run yourself.
checksums.txt file of SHA-256 sums:
- Linux and macOS, for
amd64andarm64, as.tar.gzarchives - Windows, for
amd64, as a.ziparchive
checksums.txt, and put the earnie binary (earnie.exe on Windows) in a directory on your PATH.
Container image
The image is ghcr.io/scanoss/earnie-cli:X.Y.Z, where X.Y.Z is a release number such as 0.3.1. Its entry point is earnie, so the container’s arguments are the command, such as scan path .. The container has no system keychain, so pass the API URL and key as the EARNIE_API_URL and EARNIE_API_KEY environment variables at run time. Don’t build credentials into an image.
Checking the Version
- the version of Earnie’s API
- the oldest CLI version it supports
- the JSON schema versions it accepts
earnie --json version returns the same data as JSON.
If your CLI is older than the oldest version your Earnie supports, every command that talks to Earnie stops with exit 8 and tells you which version to install.
A released CLI may print an update notice on standard error after a
successful command. The check is best effort: it never changes the exit code,
and never installs anything. Turn it off for one command with
--no-update-check, or set EARNIE_NO_UPDATE_CHECK=1 in automation.Signing In
The CLI needs to know which Earnie to talk to and which API key to use. On your own machine, sign in interactively. The CLI asks for the key and hides it as you type:Signed in to and the URL. It stores:
- the API URL in
~/.config/earnie/config.toml - the API key in your operating system’s keychain, or in
~/.config/earnie/credentials.json, readable only by you, where no keychain is available
auth login doesn’t check the key with Earnie. The first command that calls Earnie does, and exits 4 if the key is invalid or expired.
Instead of signing in, you can set the EARNIE_API_URL and EARNIE_API_KEY environment variables. EARNIE_API_KEY overrides a stored key and is never saved. This suits CI and containers. See Options and Environment Variables.
Running a Scan
A scan uploads source code to Earnie, which checks it against the project’s policies and returns a gate: Earnie’s decision for the scan, which ispass, warn, require (needs an approval), block, or none when no policy applies. Three commands choose what to scan:
For example, to scan the current directory and see why the gate is what it is, replacing
my-project with your project’s slug:
--project.
earnie scan staged creates a Self-check, a quick check that creates no findings and doesn’t change the project. The other two create a scan, listed in the project’s Scan history.
A scan never sends symlinks or special files such as sockets, and the CLI never follows a link. If a link’s target is inside the scanned tree, the CLI scans the target at its own path. It reads nothing outside the scanned path, and refuses a scan path that is itself a symlink. scan staged and scan diff also leave out Git submodules.
When earnie scan staged finds nothing staged, or earnie scan diff finds no changed source files, there’s nothing to check. The command sends nothing to Earnie, prints nothing to check: and the reason on standard error, and exits 0.
Choosing the Project
The CLI works out which project to use from the first of these that’s set:- the
--projectflag, with the project’s slug or ID - the
EARNIE_PROJECTenvironment variable - the Git remote
originof the repository you’re in, when Earnie has a project for that repository
8. It never falls back to a default project.
The Git remote works only for github.com and gitlab.com. On GitHub Enterprise Server or a self-managed GitLab, set --project or EARNIE_PROJECT.
Choosing Scanners
By default, a scan runs every scanner your organisation has enabled that your key may use. Pass--scanners to run only some of them for this one scan, as a comma-separated list from oss, crypto, and ai:
not_applicable and doesn’t decide the gate. If a scanner the scan did run fails, the command exits 3.
Output
Progress and diagnostics go to standard error, so they never mix with the result on standard output. When the output isn’t a terminal, for example in a CI log, the CLI writes plain lines.
Exit Codes
By default, a scan command waits for the scan and its policy check to finish, then exits with a code based on the gate. A pipeline can use the code to pass or fail a build:
Exits
6, 7, and 130 stop the CLI waiting, but never cancel the scan. It keeps running on Earnie, and the CLI prints its ID and the command to pick it up again: earnie scan wait <id>.
If Earnie is unreachable or answers with a server error, the CLI waits out the outage instead of failing at once. Its compatibility check and each poll for a result retry up to 8 times, waiting 1 second and doubling up to 30 seconds, about 90 seconds in all. A client error, such as a missing project, fails at once.
When Earnie answers with a server error, the message includes a request ID, for example Quote request ID <id> when you report this. Give that ID to your administrator, who can find the cause in Earnie’s logs.
Non-Blocking and Follow-Up Commands
To keep a pipeline step from waiting, start the scan without waiting and check on it later:scan status, scan list, and scan audit only report. They exit 0 whenever the query succeeds, whatever the scan’s state or gate.
JSON Output
With--format json, the CLI prints one JSON document with schema_version: "1". Its main members are:
project: the project, and how the CLI found it (resolution)scan: the scan, with its status, coverage, and where it came fromeffective: what the scan actually ran, including its scanners, enrichments, and policy snapshotgateandgate_reason: the decision, and whypolicies: one entry for each policy revision that was checkedfindings: a summary of the findingslinks: links to the scan, project, findings, and approval in the web apperror: on failure, a typed error whosecode,category,retryable, andrequest_idfields are stable for automation
included: false, a total (null when the key can’t read findings), and a findings_url. Add --include-findings to fetch every finding, with its verdicts and evidence. If the CLI can’t fetch them all, the command fails rather than return a partial list.
An AI model finding also carries a model member, with its status, purl, name, match method, and confidence band. --details shows the same in the human output.
earnie model scan --format json adds a model member. See Scanning an AI Model.
earnie scan audit <id> --format json adds an audit member. Each event carries its type, its time, the actor as recorded at the time, and a fixed set of payload keys.
Version 1 may gain new optional fields. No field is removed or renamed without a new version.
Running in a Pipeline
In GitHub Actions and GitLab CI,earnie scan path and earnie scan diff read details of the pipeline they run in, and send them with the scan: the repository, the pull or merge request, the commit, the branch, and a link to the run. The project’s Scan history then shows the run as CI, with its pull or merge request.
- Only a pull request pipeline names a pull request. On GitHub, that’s the
pull_requestevent. On GitLab, it’s a merge request pipeline. Any other pipeline, includingpull_request_target, is a branch pipeline. - A scan that names a pull or merge request has partial coverage. It checks the change, and never changes the project’s findings.
- The job’s exit code comes from the gate. See Exit Codes.
- Keep the API key secret. Store it as a repository secret or a masked CI/CD variable, never in the pipeline file.
GitHub Actions
-
Add a repository secret named
EARNIE_API_KEY, holding a key with the CI pipeline preset. -
Add a workflow file, such as
.github/workflows/earnie.yml, that checks out the code and runs the Earnie action, pinned to a CLI release. ReplacevX.Y.Zwith the release tag, such asv0.3.1, and the URL with your Earnie’s:On a pull request, this checks out the pull request’s own commit, so Earnie scans the change. On a push tomain, it scans the branch.
earnie scan with its args input. Its inputs are:
GitLab CI
AddEARNIE_API_KEY (masked) and EARNIE_API_URL as CI/CD variables. Then include the Earnie template in .gitlab-ci.yml, replacing vX.Y.Z with the release tag:
ghcr.io/scanoss/earnie-cli image. It runs on merge request pipelines, and on branch pipelines of branches with no open merge request. Its args input changes the arguments to earnie scan (path . by default), and its stage input sets the stage (test by default).
Self-hosted Git. The CLI can’t work out the project from a GitHub
Enterprise Server or self-managed GitLab remote, so set
EARNIE_PROJECT, or
the action’s project input. On GitHub Enterprise Server, uses: scanoss/earnie-cli@vX.Y.Z works once your administrator has enabled GitHub
Connect or synced the action with actions-sync. On a GitLab instance that
can’t reach the internet, copy earnie-scan.yml into a project of your own,
include it from there with include: project, and mirror the
ghcr.io/scanoss/earnie-cli image it names.8, before uploading anything: ci_ambiguous when both GitHub Actions and GitLab CI variables are set, and ci_event_unreadable when a pull or merge request pipeline doesn’t name its pull or merge request. earnie scan staged ignores the pipeline and always runs a Self-check.
Checking Before You Commit
A pre-commit hook is a script Git runs before each commit. Earnie’s hook checks your staged changes against the project’s policies, so you find a problem before you push, not when the pull request is checked.Installing the Hook
The hook uses your stored sign-in, and finds the project the same wayscan does, so set those up first. Then run this once, from inside the repository:
earnie scan staged --format hook, so it runs every scanner your organisation has enabled. To run only some, add --scanners to that command in your own hook.
- It uses Git’s own hooks directory, including a configured
core.hooksPathand linked worktrees. - You can run it again safely: if Earnie’s hook is already there, it says so and changes nothing.
- If another tool or a hand-written hook already owns
pre-commit, Earnie leaves it untouched, exits8, and tells you to addearnie scan staged --format hookto that hook instead.
What the Hook Checks
The hook reads the Git index, what you’ve staged, not your working files:- It scans added, copied, modified, and renamed files exactly as they’ll be committed.
- For a deleted file, and the old name of a renamed one, it tells Earnie the file is gone, so the check knows what was removed.
- It ignores changes you haven’t staged.
Reading the Result
The hook prints plain text in a fixed format, with no colour or progress animation:- the project
- how many files were submitted and included
- the verdict
- any scanners the check didn’t run, and why
- up to 20 findings, as
path:line - a link to the Self-check in the web app
If your Earnie can’t run a scanner your organisation has enabled, the check runs the others and lists the missing one under the scanners it didn’t run. A policy whose scanner didn’t run is recorded as not evaluated, not as passing.
Each run creates a Self-check, listed with Pre-commit hook as its source. It’s the same page a coding agent’s review lands on. If you run
earnie scan staged yourself, its Self-check is listed as CLI self-check instead.
When the Check Can’t Run
The whole hook run has a 60-second limit, from finding the project and credentials through to the result. If the check can’t finish, for example because of a key, network, or timeout problem, the hook shows the error. By default it is fail-open: it says the Self-check was unavailable, and lets the commit continue. To make these errors stop the commit instead (fail-closed):- For a whole project, an Operator or Admin turns on Pre-commit technical failures in the project’s Scan Configuration.
- For your own checkout only, set
EARNIE_HOOK_FAIL_CLOSED=1. Only the value1counts.
4 for a key problem, or 6 for a network problem or a timeout.
Keep the Earnie check required on your pull requests. The local hook gives
early feedback, and the pull request check decides what merges. See
Merge gate.
Policies (Read-Only)
A policy is a rule that says what Earnie does when a scan finds something, such as blocking a merge. Three commands read a project’s policies. They find the project the same wayscan does. See Choosing the Project.
Take
<slug> from earnie policy list. You can’t change a policy from the CLI. Use the web app. See Setting policies.
Every policy has a revision, a sha256: digest of what it checks and does. A scan’s policy snapshot and earnie verdict report the same value, so you can tell whether the policy that blocked a scan is the one in force now.
All three commands exit 0 whenever the query succeeds, whatever the policies say. An unknown slug exits 8, with the error policy_not_found.
Exporting an SBOM
An SBOM (software bill of materials) lists the components in your software.earnie export writes one for the project to a file:
An SBOM comes from an SBOM snapshot, a fixed record of the scan at the moment it was made. So
earnie export doesn’t make a new snapshot each time. It downloads the scan’s latest snapshot in the format you asked for that holds:
- with
--include, exactly the sections you named - without
--include, the sections Earnie exports by default: open-source components, plus AI models where your plan includes them
--fresh. When it replaces an out-of-date snapshot, it says so on standard error. Generating a snapshot needs a key with scans:write, which the CI pipeline preset has.
A scan that finished before Earnie began keeping SBOM evidence can’t be exported. The command fails with scan_evidence_unavailable: run a new scan, then export again.
Scanning an AI Model
earnie model scan uploads one AI model file to the project and reports what Earnie found out about it. It’s the command-line form of Upload a file on the project’s Models page. See Uploading a Model.
.safetensors, .gguf, or .onnx, or a Hugging Face snapshot as a .tar, .tar.gz, or .tgz archive. To scan a snapshot directory, archive it first. The command finds the project the same way scan does. See Choosing the Project.
The command needs:
- CLI 0.4.0 or later
- AI provenance enabled for your organisation
- a key with
scans:readandscans:write, which the CI pipeline preset has
Its exit codes follow the exit codes above, without a gate:
4, 5, and 6 mean what they mean for every command. A key whose organisation has no AI provenance gets 5, with the error module_disabled.
With --format json, the document has a model member with the upload ID, the model ID, its SHA-256 hash, its status, its scan, and its findings.
Like an upload from the web app, the model gets its own scan, appears on the project’s Models page, and Earnie deletes its weights once the scan ends. Models over the size cap can’t be scanned from the CLI yet.
Connecting a Coding Agent (MCP)
MCP (Model Context Protocol) is a standard way for AI coding agents to call outside tools. When you connect an agent to Earnie over MCP, it can ask what your policies say and have its changes checked while it works.This section is the reference for the
earnie mcp commands. For a
step-by-step guide to connecting an agent, the tools it gets, and
troubleshooting, see Earnie MCP.Creating the Key
Create the key with the Coding agent (MCP) preset. See API keys. It grants:- read access to projects, policies, findings, and scans (
projects:read,policies:read,findings:read,scans:read) - permission to submit a scan (
scans:write)
findings:write. The audit log attributes each decision the agent makes to the key.
Setting Up an Agent
-
Sign in with the key, if you haven’t already:
-
From inside your repository, run setup:
Setup finds the clients you have installed, from their programs and configuration files, and preselects them. Choose the ones to set up.
-
Restart the agent, then check the setup with
earnie mcp doctor. See Checking and Removing the Setup.
EARNIE_API_KEY for the agent.
Setup finds the project the same way scan does: --project, EARNIE_PROJECT, or the Git remote. It needs policies:read, because it fetches the project’s policy instructions before changing any file.
In scripts and other non-interactive runs, pass --client detected or a list such as --client claude,codex. Without --client, a non-interactive setup stops rather than guessing.
Setup checks the whole change first, then writes it as one transaction. If a write fails, the previous files are restored.
Where Setup Writes
For each client, setup writes the identity, meaning the API URL and key, to your user-level configuration. It writes a managed policy block, instructions telling the agent what your policies say, into the repository:
The identity holds your API key, so never commit it. The repository files hold no secrets, and you can commit them.
Earnie changes only the text between its own markers, which start with
<!-- earnie:policies:start and end with <!-- earnie:policies:end -->. In
Cursor’s earnie.mdc, it also owns the front matter that makes the rule apply
to every chat. It leaves the rest of each file alone. If it finds duplicate,
nested, incomplete, or hand-edited markers, it stops rather than guessing.Codex project hooks don’t run until you review and trust them with
/hooks
inside Codex. Setup never bypasses that check.Windows and WSL use different home directories. The Windows build of
earnie writes to your Windows profile, such as %USERPROFILE%\.claude.json,
even when you run it from Git Bash or against an Earnie served from WSL. A
build running inside WSL writes to the Linux home directory. If an agent
doesn’t pick up the setup, check which earnie binary ran.Review Hooks and Self-Checks
A review hook asks Earnie to check an agent’s work at the end of each turn, before anything is committed. The first time you set up a repository, setup turns on end-of-turn review hooks for Claude Code, Cursor, and Codex. VS Code doesn’t have one yet. At the end of each turn:- The hook sends the files the agent changed during the turn to Earnie, once, including documentation-only changes.
- Earnie runs a Self-check, not a scan. It creates no findings and doesn’t change the project’s posture.
- The agent gets a link to the result. The project’s Self-checks page lists every Self-check for 30 days.
- If the result blocks or needs an approval, the agent gets one more turn to correct it. The review after that always ends the loop, and reports whatever findings remain.
earnie mcp review exactly one of these:
--staged checks what you’ve staged, --diff <base> checks your commits since <base>, such as origin/main, and a list of files checks those files.
Set EARNIE_SKIP_REVIEW=1 (or true, yes, or on) to record a visible, user-requested skip instead of a review. Any other value doesn’t skip.
earnie mcp review exits from the review’s gate, the same way a scan does:
Keeping the Policy Block Current
earnie mcp setup (and --hooks) also installs a session-start hook for Claude Code, Cursor, and Codex. It refreshes the managed policy block when the project’s policies change:
- When a Claude Code or Codex session starts or resumes (not on
/clearor compaction), or a Cursor session starts, the hook asks Earnie for the project’s rules. - If their revision differs from the one in the block, it replaces only what’s between the markers. If they match, it changes nothing.
- It never adds a block to a file that doesn’t already have one.
earnie mcp setup again to refresh its block.
Hooks installed by an earlier CLI don’t include the session-start hook. earnie mcp doctor reports this (policy_refresh in --json), and earnie mcp setup --hooks adds it.
How Long a Self-Check Waits
An Operator or Admin can set how long a Self-check waits for a result before returning. It’s in Settings, on the Scan Configuration tab, under Self-check, as Self-check wait budget.- The value is a whole number of seconds, from 0 to 120. Zero uses the default of 45 seconds.
- A positive
wait_secondson a REST API request overrides the setting for that request.
Checking and Removing the Setup
To check an installation without changing anything:earnie mcp setup --check does the same. Doctor reports:
- whether the CLI is compatible with your Earnie
- whether the key, from
EARNIE_API_KEYor the one you stored, is valid - the project it would use, and the state of its policy block and hooks
- each client’s configuration
- the tools the agent would see, from a live request to Earnie. For each tool: whether it’s visible, the scope it needs, and why it’s missing, if it is. Doctor never grants a hidden tool.
- each domain, OSS, Crypto, and AI, as visible or hidden, and why: not enabled for your organisation, or not in this key’s scope. The same reasons explain a scanner missing from a scan, or a tool your agent can’t see.
--global, doctor skips the project and its policy block. It takes the same --client choices as setup, plus configured, to check only clients that already have Earnie set up. Doctor never changes configuration, rules, hooks, or credentials.
Doctor exits 0 when everything is healthy, and 9 when it finds a problem. A repository set up for Codex always reports that its hooks need your action, because doctor can’t see whether you’ve trusted them, so doctor exits 9 there even after you have.
To remove Earnie from a repository:
--client choices as setup.
It doesn’t remove the user-level API URL and key, so your other repositories keep working. To remove the user-level configuration instead, without touching any repository, pass --global. --dry-run previews every removal, and running it more than once is safe.
Options and Environment Variables
These options work with every command:
The CLI reads these environment variables:
Run
earnie <command> --help for every flag of a command.