Skip to content
Docs menu
Tool · CLI

CLI

Pre-deploy risk scanner. The same classes of issues nreactive's runtime SDK reports post-deploy — but caught by static analysis on your repo before they ship. Zero install, zero signup for the first scan.

npx nreactive scan

That's the whole getting-started story. The CLI packs the current repo with Repomix, POSTs it to https://nreactive.com/api/scan, and prints the top risks inline.

What it catches

Static-analysis variants of the runtime errors nreactive reports from production:

  • Unhandled promise rejections / missing await
  • Hardcoded secrets and credentials
  • Missing input validation on routes
  • Race conditions and shared-state bugs
  • Mis-typed env access (process.env.X where X isn't declared)
  • Common N+1 patterns

Each finding comes back with severity, file:line, a short description, and a suggested fix.

Install

Zero-install with npx is the easiest path. If you want nreactive on your $PATH:

npm  i -g @nreactive/cli   # or
pnpm add -g @nreactive/cli # or
bun  add -g @nreactive/cli

The package is published as @nreactive/cli but the binary it installs is named nreactive — same command either way:

nreactive scan

CI mode

1

Add it to your pipeline

npx nreactive scan --ci --min-severity high

--ci makes the command exit non-zero if any finding at or above --min-severity exists. Drop into GitHub Actions:

# .github/workflows/scan.yml
- run: npx nreactive scan --ci --min-severity high

Or GitLab CI:

scan:
  script:
    - npx nreactive scan --ci --min-severity high
2

Sign in for dashboard history (optional)

The first scan works anonymously. To link scans to your dashboard, export a token from the Apps page:

export NREACTIVE_TOKEN=...
npx nreactive scan

Anonymous scans are deleted after analysis. Signed-in scans show up in your dashboard history with a reportUrl deep link.

Flags

| Flag | Default | Description | |---|---|---| | --endpoint | https://nreactive.com | Backend base URL. Override with NREACTIVE_ENDPOINT. | | --token | (none) | Bearer token. First scan works anonymously; signed-in scans link to your dashboard. Override with NREACTIVE_TOKEN. | | --ci | false | Exit non-zero on findings ≥ --min-severity. | | --min-severity | high | One of critical, high, medium, low. | | --max-bytes | 5000000 | Hard cap on the packed-repo size. | | --json | false | Emit the raw ScanResponse JSON on stdout (for piping into other tools). | | --cwd | process.cwd() | Repository root to scan. |

Exit codes

| Code | Meaning | |---|---| | 0 | OK (or non-CI mode regardless of findings) | | 1 | CI mode: findings ≥ --min-severity | | 2 | Usage error (bad flag) | | 3 | Network error reaching the backend | | 70 | Internal error |

CI distinguishes "scanner found bugs" (1) from "scanner is broken" (3, 70) so you can fail loud on real findings without breaking on flakes.

How it works

  1. The repo is packed locally with Repomix (XML format, default ignore rules).
  2. The pack is POSTed to {endpoint}/api/scan?mode=cli.
  3. The backend runs the same analyzers that power the dashboard and returns a compact response (scanId, top risks, summary, optional reportUrl).
  4. Output is rendered inline with chalk; --json emits the raw response for piping into other tools.

No code is stored unless you're signed in and explicitly opt in (your dashboard tracks scan history). Anonymous scans are deleted after analysis.

Every request includes an x-nreactive-self: 1 header so the runtime SDK's HTTP-client integration skips it when both ship in the same project — no self-loop scans.

Wire contract

Public, stable across patch versions:

// POST {endpoint}/api/scan?mode=cli
interface ScanRequest {
  pack: string;                          // Repomix XML output
  meta: {
    cliVersion: string;
    repoName?: string;                   // "owner/repo"
    branch?: string;
    commit?: string;
  };
}
 
interface Risk {
  id: string;
  severity: "critical" | "high" | "medium" | "low";
  title: string;
  file?: string;
  line?: number;
  description: string;
  suggestedFix?: string;
}
 
interface ScanResponse {
  scanId: string;
  risks: Risk[];
  summary: { totalRisks: number; bySeverity: Partial<Record<Severity, number>> };
  reportUrl?: string;
}

Next steps