The nerlo CLI
Our command-line client for the Nerlo registry: search scanned packages, read their scorecards, install a package into your runtime, gate a build on what your project already uses, and submit new repositories for scanning.
Install
Now available.
nerlo0.3.0 is published on PyPI. Install it withpipx install nerlo. The distribution is namednerlo, notnerlo-cli, which is the GitHub repository name.
Already have it? Upgrade with
pipx upgrade nerlo— orpip install --upgrade nerlo/uv tool upgrade nerlodepending on how you installed it. Runnerlo versionto confirm which build you are on. Full instructions for every platform, including how to check what is current, are on install and upgrade the CLI.
Before you start: you need Python 3.11, 3.12, or 3.13 on Linux, macOS, or Windows. The Docker tab needs no local Python at all.
Pick your platform below. pipx (recommended everywhere it's available)
keeps the CLI in its own environment, separate from your system Python. Every
tab pairs the install command with the nerlo version command that confirms
it worked. A Troubleshooting section follows the tabs.
It is worth confirming before you go further, and note that version is a
command, not a flag:
nerlo version
nerlo 0.3.0
Install pipx so the CLI lives in its own isolated environment, then install nerlo:
sudo apt install pipx pipx ensurepath pipx install nerlo
nerlo version
Troubleshooting
pipx: command not found (older Ubuntu without a pipx package): install it via pip instead.sudo apt install python3-pip python3 -m pip install --user pipx pipx ensurepath
externally-managed-environment): use the pipx steps above. Never pass --break-system-packages. It risks breaking your OS's own Python. See the PEP 668 note below.Fallback: dedicated virtualenv
Prefer an explicit venv? That works too:
python3 -m venv ~/.local/nerlo ~/.local/nerlo/bin/pip install nerlo ln -s ~/.local/nerlo/bin/nerlo ~/.local/bin/nerlo
Troubleshooting
Landed here from a copy-pasted error? Find your exact message below.
error: externally-managed-environment (PEP 668)
Modern Debian/Ubuntu, Fedora, and Homebrew Python refuse pip install into the system interpreter under PEP 668. Install with pipx instead. Do not pass --break-system-packages. It can corrupt your OS's Python. See the Linux (Debian/Ubuntu) or your platform's tab above for the pipx steps.
pipx: command not found / nerlo: command not found
The tool installed but its directory isn't on your PATH yet. Run pipx ensurepath and open a new terminal.
- Debian/Ubuntu (older): no
pipxpackage: Linux (Debian/Ubuntu) tab shows the pip bootstrap. - Arch: PATH not picked up in fish/zsh: see the Linux (Arch) tab.
- macOS: Homebrew bin differs by chip (
/opt/homebrew/binvs/usr/local/bin): see the macOS tab. - Windows: add
%USERPROFILE%\.local\binand%APPDATA%\Python\Python312\Scripts: see the Windows tab.
cannot be loaded because running scripts is disabled on this system (PowerShell execution policy)
PowerShell blocked the pipx install script. Run this one-time step, then retry the install from the Windows tab:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
apk: unable to select package pipx (Alpine has no pipx)
Alpine's repositories don't ship pipx. Use the dedicated-venv path on the Linux (Alpine/generic) tab: apk add python3 py3-pip py3-virtualenv then create a venv.
Consumer commands
Search the registry
We match your keyword across name, description, and author (2–100 characters):
nerlo search "weather"
nerlo search "weather" --json
The ID column is the package's server UUID, and every command that addresses
a package takes it: nerlo rescan uses it directly, and nerlo info and
nerlo install accept it alongside a package name or a skill id.
Inspect a package
Score, badge, artifact type, and per-scanner scoresheets for one package.
Address it by name, by skill id, or by the UUID from nerlo search:
nerlo info weather-mcp
weather-mcp (88417784-98a2-488b-bd25-23213377d135)
repository: https://github.com/lamcearber-spec/weather-mcp
type: mcp_server
badge: Caution
score: 91.38
installed via Nerlo: 0 total (0 in last 30d, CLI installs only)
per-scanner scoresheets:
SCANNER SCORE BADGE FINDINGS
agentshield 100.0 Unrated
cisco-skill-scanner 100.0 Unrated
agent-audit-kit 68.0 Unrated 5
nerlo-behavioral 99.5 Unrated 1
nerlo-install-instruction 100.0 Unrated
capslock 100.0 Unrated
trivy 100.0 Unrated
osv-scanner 100.0 Unrated
trivy_image 100.0 Unrated
govulncheck 100.0 Unrated
The per-scanner table is the point: eleven scanners ran, one scored 68 while
the rest scored at or near 100, and the composite of 91.38 is one weighted view
of that spread rather than a replacement for it. installed via Nerlo counts
CLI installs only and is an engagement signal, not an endorsement.
We show every scanner's verdict side-by-side and label the composite as one view among many — on the registry website and, as of this release, in the CLI.
No findings does not mean no risk. Nerlo aggregates automated scanner output; automated scanning is one layer of defence, not a substitute for review.
Install a package
We write an mcpServers config entry for the target runtime. You need an API
token; name the package however you like — a name, a skill id, or a UUID:
nerlo install weather-mcp-server-2bdf07f --target claude-code --token $NERLO_API_TOKEN
Installed weather-mcp-server-2bdf07f -> ~/.claude.json
note: no runnable package source detected — entry records the repository; finish the command wiring for your platform.
--target takes claude-code, cursor, gemini, or mcp. Installing the
same package twice is refused unless you pass --force.
Install respects the composite badge: Clean proceeds, Caution asks you to confirm, Flagged is refused, and an Unrated package — one nobody has scanned — is not installable either. Those are display names for a scan result, not a guarantee about a package — the CLI is reporting what the scanners returned.
The API is unchanged and still uses the original three values, so ?badge=,
any JSON you parse, and the CLI's own terminal output take Verified for
Clean, Caution for Caution, and Unsafe for Flagged.
nerlo install sends anonymous install telemetry and says so the first time it
runs. Opt out with NERLO_TELEMETRY=0 in your environment, or telemetry=false
in ~/.nerlo/config.
Check a project against the registry
check is the gate. It reads the same platform configs nerlo install writes,
resolves every entry against the registry, and exits non-zero when policy is
violated — so it fails a build whether or not anybody remembers to look at a
dashboard.
With no PATH it scans your per-user locations. Give it a PATH and it scans a
project checkout instead, which is the CI case:
nerlo check
nerlo check .
nerlo check . --fail-on caution
nerlo check . --json
STATUS ARTIFACT PLATFORM SCORE SCANNERS SOURCE
FLAGGED weather-mcp mcp 81.3 11 .mcp.json
1 flagged
FAIL: 1 artifact(s) violate --fail-on flagged.
The split is deliberate: a CI runner's $HOME belongs to an ephemeral machine,
so mixing it into a repository's gate would make the result depend on the runner
rather than on the repository.
Exit codes
0— pass1— policy violated2— usage error3— could not determine (registry unreachable, a local config could not be parsed, or a search too broad to read to the end)
"Could not determine" is never a pass, at any --fail-on level. Handle 3
explicitly in your pipeline. A bare set -e will fail the build on it, which is
the right outcome, but it will report it as if a package had been flagged — and
"we could not check" is a different fact from "we checked and it is bad".
Policy levels
--fail-on selects the threshold and defaults to flagged. The words you type
are the display words, and they line up with what the registry shows:
--fail-on flagged(default) — fails on Flagged only--fail-on caution— fails on Flagged and Caution--fail-on any— also fails on packages the registry has no verdict for: unknown, withheld, or unscored
Already have --fail-on unsafe in a pipeline? Leave it. It is the retired
spelling of the same level and the CLI accepts it permanently — this release
changed which word is documented, not which builds pass. Anything parsing
--json is untouched too: the fail_on and status fields still carry the
original wire words.
any means "fail unless the registry affirmatively verified this". It is the
right setting once a team has submitted its dependency set and wants to keep it
that way; it is not the default because the registry does not yet list every
package in the ecosystem, and a gate that red-builds everything on day one gets
deleted in week one.
A package the registry has never seen reports as UNKNOWN with a pointer to
nerlo submit. Unknown is not a pass — it is nobody having looked.
Continuous integration
The three snippets below are the same gate. Each one treats "could not
determine" as its own outcome rather than letting set -e fold it into a
generic failure.
GitHub Actions
name: Nerlo
on: [pull_request]
jobs:
nerlo-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: pipx install nerlo
- name: Gate on the registry's verdict
shell: bash
run: |
set +e
nerlo check . --fail-on flagged
status=$?
set -e
case "$status" in
0) echo "Nerlo: every configured package passed." ;;
1) echo "::error::Nerlo: a configured package violates the policy."; exit 1 ;;
3) echo "::error::Nerlo: could not determine a verdict. This is not a pass."; exit 1 ;;
*) echo "::error::Nerlo: usage error (exit $status)."; exit 1 ;;
esac
GitLab CI
nerlo-check:
image: python:3.12-slim
before_script:
- pip install nerlo
script:
- |
set +e
nerlo check . --fail-on flagged
status=$?
set -e
case "$status" in
0) echo "Nerlo: every configured package passed." ;;
1) echo "Nerlo: a configured package violates the policy."; exit 1 ;;
3) echo "Nerlo: could not determine a verdict. This is not a pass."; exit 1 ;;
*) echo "Nerlo: usage error (exit $status)."; exit 1 ;;
esac
Jenkins
stage('Nerlo') {
steps {
script {
def status = sh(script: 'nerlo check . --fail-on flagged', returnStatus: true)
if (status == 0) {
echo 'Nerlo: every configured package passed.'
} else if (status == 1) {
error 'Nerlo: a configured package violates the policy.'
} else if (status == 3) {
error 'Nerlo: could not determine a verdict. This is not a pass.'
} else {
error "Nerlo: usage error (exit ${status})."
}
}
}
}
Machine-readable output
--json prints the same result as structured data, with exit_code carried in
the payload so a downstream step does not have to re-derive it:
nerlo check . --json
{
"scope": "project",
"path": ".",
"fail_on": "unsafe",
"fail_on_label": "flagged",
"exit_code": 1,
"summary": {
"total": 1,
"unsafe": 1,
"caution": 0,
"withheld": 0,
"unscored": 0,
"unknown": 0,
"unresolved": 0,
"error": 0,
"verified": 0
},
"unreadable_configs": [],
"artifacts": [
{
"name": "weather-mcp",
"platform": "mcp",
"artifact_type": "mcp_server",
"source": ".mcp.json",
"status": "unsafe",
"badge": "Unsafe",
"score": 81.3,
"server_id": "e32f8cdc-d2e5-4acd-8c37-09c2dc245a11",
"scanners": 11,
"duplicate_matches": 8,
"note": ""
}
]
}
What the CLI cannot gate yet
check gates what your project consumes. It cannot yet gate what you
publish: there is no way to submit your own repository and block a build on
the resulting verdict. nerlo submit queues a scan and returns immediately —
it has no --wait, nothing polls for completion, and nerlo info does not
carry a verdict in its exit code. If you need that today, you would be building
the polling loop yourself against the registry API.
Publisher commands
Submit a repository
We queue your repository for ingestion and scanning (authenticated):
nerlo submit https://github.com/you/your-mcp-server --token $NERLO_API_TOKEN
Add --type to state the artifact kind explicitly — mcp_server,
claude_skill, gemini_extension, or cursor_rule. Omit it and the server
infers it from the repository.
Request a re-scan
By server UUID — the ID column from nerlo search (authenticated):
nerlo rescan 88417784-98a2-488b-bd25-23213377d135 --token $NERLO_API_TOKEN
The command's help also offers a skill slug, and that path works as well. The
UUID is shown because nerlo search prints it, so it is the value already in
front of you.
Tokens
install, submit, and rescan read --token or the NERLO_API_TOKEN
environment variable. search, info, and check need no token. We issue
tokens per account; treat them like passwords, and in CI put them in your
platform's secret store rather than in the workflow file.
Configuration
Point the CLI at a different registry with --api-url or NERLO_API_BASE_URL
(defaults to https://api.nerlo.ai). Every command that talks to the registry
accepts the flag — that is all of them except version, which talks to nothing.