Community CLI

Shipmoor Team
June 11, 2026
13 min read

Shipmoor Community CLI is a free local pre-merge check for agent-assisted code. Run it after Claude Code, Codex, Cursor, Copilot, or another coding agent finishes and before you open a PR.

It scans Python, TypeScript, JavaScript, and Go changes, catches high-confidence generated-code failure modes, and emits concise human output, deterministic JSON, SARIF, and stable CI exit codes.

The Community CLI is intentionally local-first:

  • No account required.
  • No telemetry.
  • No source upload.
  • No hidden local history database.
  • No sudo install requirement.
  • No enterprise policy, audit, RBAC, or hosted dashboard features.

Current version: 0.4.0. Since v0.3.0 there is one universal shipmoor binary: the free Community scan needs no login, and a local license unlocks the paid IC features (Claim Check, Agent Skills) in the same executable; you never download a different build to upgrade. Community scan output, the JSON / SARIF contracts, and the exit codes are unchanged from the v0.2.x line.

For the design notes behind the human output, see Shipmoor Community CLI v0.2.1: a scan output you can read in five seconds. For the launch story, including live runs against Flask, Zod, and Cobra, see Introducing Shipmoor Community CLI.

First five minutes

Install the CLI:

curl -fsSL https://dl.shipmoor.dev/install.sh | bash

The installer prints the exact export PATH line if ~/.shipmoor/bin is not already on your shell PATH. After exporting, verify:

shipmoor version
# shipmoor 0.4.0

Run shipmoor from inside any repo to see the welcome banner with detected languages, branch, and the suggested next command:

shipmoor
              ⟨ ◆ ⟩

     ███████ ██   ██ ██ ██████  ███    ███  ██████   ██████  ██████
     ██      ██   ██ ██ ██   ██ ████  ████ ██    ██ ██    ██ ██   ██
     ███████ ███████ ██ ██████  ██ ████ ██ ██    ██ ██    ██ ██████
          ██ ██   ██ ██ ██      ██  ██  ██ ██    ██ ██    ██ ██   ██
     ███████ ██   ██ ██ ██      ██      ██  ██████   ██████  ██   ██

         v0.4.0  community  ·  verify agent work before review
     ───────────────────────────────────────────────────────────────
     metaphor  ·  main  ·  1 changed  ·  ts · js

     next  shipmoor scan --changed    ·    shipmoor help

From a repository where an agent just made changes, run the zero-config preflight:

shipmoor scan --changed

If Shipmoor reports a finding, write a JSON report and ask for the explanation:

shipmoor scan --changed --json --output shipmoor.json
shipmoor explain <finding-id> --from shipmoor.json

When you want CI or code-scanning evidence, write SARIF:

shipmoor scan --changed --sarif --output shipmoor.sarif

That is the core loop: install, scan changed files, understand the finding, then export evidence when you need it.

Install

Install the CLI with one command:

curl -fsSL https://dl.shipmoor.dev/install.sh | bash

The installer detects macOS or Linux, selects the matching CPU architecture (darwin-arm64, linux-amd64, linux-arm64), verifies the downloaded archive’s SHA-256 against the channel manifest, and installs the executable to:

~/.shipmoor/bin/shipmoor

If that directory is not on your PATH, the installer prints the exact shell export line to add. Verify the install:

shipmoor version

Intel Macs (darwin-amd64) are not supported in this release. The older install-community-cli.sh URL remains supported and installs the same universal binary.

What shipmoor scan --changed shows

Output is designed to answer one question: is this change ready to send for review?

✗ Needs work - 1 of 3 findings block review
none detected · 1 file · gate high · degraded resolvers
⊘ blocks the gate  ·  ○ informational
────────────────────────────────────────────────────────
app.py · 3
  ⊘ high     :3   phantom import   python.phantom_import
    Local module 'imaginary_shipmoor_package' is referenced but no file matches under PYTHONPATH.
    → 'imaginary_shipmoor_package' looks local but does not resolve from project module paths. Add the file, fix PYTHONPATH, or remove the import.
  ○ medium   :6   empty body   python.placeholder.empty_body
    `placeholder` - Function 'placeholder' has no meaningful implementation.
    → Either remove the function or implement it before merging.
  ○ medium   :10  mutable default   python.quality.mutable_default
    `mutable` - Function 'mutable' uses a mutable default argument.
    → Replace `def mutable(items=[])` with `def mutable(items=None)` and initialize inside the function body.
────────────────────────────────────────────────────────
✗ gate fail · 1 high blocks at threshold "high"  exit 1
→ fix the 1 blocker, then re-run  shipmoor scan tests/fixtures/python_only --fail-on high
→ drill into one  shipmoor explain SHM-b00b9982e581ed39
2 medium won't block - worth a look.

The output is five blocks. Each one earns its space.

  • Verdict line. One of three states: ✓ Ready (zero findings, green), ○ Needs a look (findings, none at or above the gate, amber), ✗ Needs work (at least one finding meets the gate, red). The count phrase says it twice on purpose: N of M findings block review.
  • Context line. What manifests Shipmoor discovered, how many files were scanned, the active gate threshold, and degraded resolvers when no manifest was found. On a monorepo with nested manifests (backend/package.json, frontend/requirements.txt) every manifest appears here.
  • Legend. ⊘ blocks the gate · ○ informational. The glyphs are not decoration; they let the gate impact of any finding be read at a glance.
  • Findings, grouped by file, blockers first. Files that contain at least one blocker float to the top. Inside each file, blockers come first, then severity order. Every finding is three short lines: the header (⊘ high :3 phantom import python.phantom_import), the evidence (the offending source quoted in backticks), and the fix (→ ...).
  • Footer. ✗ gate fail or ✓ gate pass, with the exit code. Then two lines naming the exact scan rerun command and the exact shipmoor explain <id> to drill into the first blocker. Finally, a one-line “X medium · Y low won’t block — worth a look” note when non-blocking findings exist.

Colors are intentional. Red marks anything that blocks the gate (the , the , the critical and high severity words, the gate fail line). Amber marks medium and the ○ Needs a look verdict. Blue marks low. Teal marks the arrows that introduce a recommendation or a next-step command. Dim gray is metadata. The palette is suppressed automatically when stdout is not a TTY, when NO_COLOR is set, or when --no-color is passed.

A clean scan is short:

✓ Ready - no findings on your change
none detected · 1 file · gate high · degraded resolvers
────────────────────────────────────────────────────────
✓ gate pass · 0 blockers  exit 0

Phantom-import subtypes

python.phantom_import, typescript.phantom_dependency, javascript.phantom_dependency, and go.phantom_import classify every finding by subtype:

SubtypeWhat it means
hallucinated_packageNo such thing on the package registry. Often a real agent hallucination.
missing_manifest_entryThe package exists on the registry, but the project did not declare it.
broken_relative_pathThe import is local, but the target file does not exist on disk.
unresolved_local_moduleThe import looks local but does not match any module under the project’s source roots.

The subtype shows up in JSON under finding.subtype and in the human message text, so a reviewer can tell at a glance whether the agent invented something or just forgot to update a manifest.

What Community catches

The full rule list is available with shipmoor rules. Version 0.4.0 ships 30 rules organized into five categories:

  • Phantom imports / dependencies. The agent invented a package, mis-spelled one, or used one without declaring it in the project manifest. python.phantom_import, typescript.phantom_dependency, javascript.phantom_dependency, go.phantom_import.
  • Placeholder logic. pass, ellipsis, throw new Error("not implemented"), panic("TODO"), constant returns. *.placeholder.*.
  • Trust suppression. any, as any, @ts-ignore, @ts-expect-error. Code that bypasses the type system to make a generated change compile without satisfying the type contract. *.trust.*.
  • Quality signals. Bare except, mutable defaults, ignored errors, fmt.Print / console.log left in production code, large functions. *.quality.*, *.error.*, *.debug.*, *.structure.*.
  • Control flow. Unreachable code after return or throw. *.control_flow.*.

Severities are aligned across languages by a shared severity policy. See Findings & rules. Hallucinated imports are high in every language. Placeholder panics and not-implemented throws are high. Trust suppressions at public boundaries are medium. Debug output is low.

Scan inputs

Scan a whole repository or a single file:

shipmoor scan .
shipmoor scan src/foo.py

Scan Git changes:

shipmoor scan --changed         # staged + unstaged changes
shipmoor scan --staged          # staged only (good for pre-commit)
shipmoor scan --diff main...HEAD

Scan an agent-produced patch without applying it:

shipmoor scan --patch agent.patch

--patch reads the unified diff, materializes the changed files in memory (no on-disk write), and runs the same analyzers. When the working directory contains a project manifest, patch mode uses that context to resolve imports, so import React from "react" does not flag as phantom on a normal Next.js patch.

Patch and diff scans parse changed line ranges, scan whole changed files for context, and classify findings by change_status: introduced (this finding landed in the changed lines), modified, preexisting, or unknown.

Output formats

Human-readable output is the default:

shipmoor scan .

Write deterministic JSON:

shipmoor scan . --json --output shipmoor.json

The JSON contract (shipmoor.scan.v1) is stable enough to script around. Key fields per finding: id, rule_id, severity, path, start_line, message, recommendation, evidence, change_status, subtype, and a stable fingerprint hash.

Write SARIF 2.1.0 for GitHub code scanning:

shipmoor scan . --sarif --output shipmoor.sarif

When --json or --sarif writes to stdout, stdout contains only the machine-readable document. Diagnostics go to stderr.

After every JSON or SARIF run that produced at least one finding, Shipmoor prints the exact shipmoor explain command for one finding ID. Copy it to drill in.

CI gate

Use --fail-on to decide when the scan should block:

shipmoor scan . --fail-on critical
shipmoor scan . --fail-on high     # default in `.shipmoor.yaml`
shipmoor scan . --fail-on medium
shipmoor scan . --fail-on none

Exit codes are stable:

CodeMeaning
0Scan succeeded and did not exceed the threshold.
1Scan succeeded and exceeded the threshold.
2Usage or configuration error.
3Unexpected scan failure.

GitHub Actions

This workflow runs Shipmoor on the merge-base diff, writes SARIF, and uploads it to GitHub code scanning. if: always() makes sure the Security tab is populated even when the gate fails the workflow:

name: Shipmoor

on:
  pull_request:
  push:
    branches: [main]

jobs:
  shipmoor:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install Shipmoor CLI
        run: |
          curl -fsSL https://dl.shipmoor.dev/install.sh | bash
          echo "$HOME/.shipmoor/bin" >> $GITHUB_PATH
      - name: Run Shipmoor
        run: |
          shipmoor scan \
            --diff origin/main...HEAD \
            --sarif --output shipmoor.sarif \
            --markdown-summary "$GITHUB_STEP_SUMMARY" \
            --fail-on high
      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: shipmoor.sarif

The three-dot notation in --diff origin/main...HEAD scopes the gate to “what this branch adds relative to the merge base,” which means PRs do not get blocked on legacy findings the branch did not introduce. See GitHub Actions for the shorter composite-action form.

Configuration

Shipmoor looks for .shipmoor.yaml by default, but it can scan without one. Generate a starter config:

shipmoor init

That creates .shipmoor.yaml and adds .shipmoor/ to .gitignore. The default schema:

schema_version: 1
languages:
  enabled:
    - python
    - typescript
    - javascript
    - go
ignore:
  - .shipmoor/
rules:
  disabled: []
  severity_overrides: {}
thresholds:
  fail_on: high
diff:
  only_introduced: true
output:
  default_format: human

Disable a rule entirely:

rules:
  disabled:
    - python.placeholder.empty_body

Override severity:

rules:
  severity_overrides:
    python.quality.mutable_default: high

Scan with an explicit config:

shipmoor scan . --config .shipmoor.yaml

Config precedence is file > command-line flag > built-in default.

Project context and monorepos

The first line of human output reports which manifests Shipmoor is using:

Project context: package.json (28 deps), tsconfig.json. Scanning 30 files.

If a repo has manifests in subdirectories instead of at the root (common in backend/ and frontend/ layouts), Shipmoor walks one level into the scan root and treats each subdirectory with a manifest as its own resolution context. All of them appear in the context line.

If no manifests are detected, the context line includes degraded resolvers:

none detected · 12 files · gate high · degraded resolvers

“Degraded mode” means the resolvers will run, but phantom-import rules will be conservative; they can only flag truly missing imports, not “imported but not declared” cases.

Rule discovery

List available rules:

shipmoor rules
shipmoor rules --json

Explain a finding from a JSON report:

shipmoor explain <finding-id> --from shipmoor.json

explain renders a progressive view: title, location, why, root cause, fix, evidence. The same glyph and color conventions as the scan output apply.

✗ high · phantom import   python.phantom_import
app.py:3 · SHM-b00b9982e581ed39 · confidence high · phantom_dependency
────────────────────────────────────────────────────────
why
  Local module 'imaginary_shipmoor_package' is referenced but no file matches under PYTHONPATH.
root cause
  No matching local module file could be resolved from project paths.
fix
  → 'imaginary_shipmoor_package' looks local but does not resolve from project module paths. Add the file, fix PYTHONPATH, or remove the import.
evidence
  import_name: imaginary_shipmoor_package
  registry_lookup: not_applicable

Installer controls

Pin a version:

SHIPMOOR_VERSION=0.4.0 curl -fsSL https://dl.shipmoor.dev/install.sh | bash

Use the staging channel:

SHIPMOOR_CHANNEL=staging curl -fsSL https://dl.shipmoor.dev/install.sh | bash

Install to a custom user-writable path:

SHIPMOOR_INSTALL_DIR="$HOME/bin" curl -fsSL https://dl.shipmoor.dev/install.sh | bash

Once installed, the binary keeps itself current:

shipmoor upgrade

Uninstall:

rm -f ~/.shipmoor/bin/shipmoor
rm -rf ~/.shipmoor/lib/shipmoor

The installer never edits your shell profile silently. It prints the export line and you decide where it lives.

shipmoor with no subcommand shows a status banner that adapts to your context:

StateWhen it firesSuggested next command
first_runYour very first ever shipmoor invocation.shipmoor scan --changed
standardInside a git repo with uncommitted changes.shipmoor scan --changed
cleanInside a git repo with no uncommitted changes.shipmoor scan --diff <default-branch>..HEAD
no_gitOutside a git repo.shipmoor scan --patch agent.patch

The banner is suppressed on non-TTY output (CI logs stay uncluttered) and on narrow terminals (compact single-line mode). It also fires once before any subcommand on the very first interactive invocation, so you see a welcome the first time you run any shipmoor command.

What is not in scope

The Community tier deliberately does not do these things; other tools do them better:

  • Style. ruff, ESLint, Prettier, gofmt, golangci-lint.
  • Vulnerabilities. pip-audit, npm audit, govulncheck, Dependabot.
  • SAST. semgrep, CodeQL, Snyk Code.
  • IaC scanning. Dockerfile, Kubernetes, and Terraform rules are on the roadmap, not in this release.
  • Daemons / file watchers / editor integrations. The Community tier is one binary you run on demand.
  • Telemetry, analytics, or accounts. The optional package-registry lookup for the hallucinated_package subtype is the only outbound network call. Disable it with SHIPMOOR_OFFLINE=1.

The paid surfaces build on the same binary and the same local-first model: Claim Check and Agent Skills on the IC plan, the Agent Harness and IDE extension, and shared baselines / team policy on the upcoming Team tier. See Plans & tiers.

Last updated on June 11, 2026

Was this article helpful?

Your response is saved on this device.