---
title: @specdiff/cli Reference
description: Command-line interface reference for @specdiff/cli — compare JSON Schema and OpenAPI documents, list rules, and integrate into CI pipelines.
url: https://pr-1-ee19382a0710.thally.app/specdiff/cli
---

# @specdiff/cli Reference

Command-line interface reference for @specdiff/cli — compare JSON Schema and OpenAPI documents, list rules, and integrate into CI pipelines.

## Installation

```bash
npm install -D @specdiff/cli
```

Requires Node.js 22 or later. Depends on `@specdiff/core` and `yaml`.

---

## Commands

### Compare

```
specdiff <before> <after> [options]
```

Compares two specification documents and reports detected changes. Accepts `.json`, `.yaml`, and `.yml` files.

#### Flags

| Flag | Values | Default | Description |
|---|---|---|---|
| `--format` | `text`, `json`, `markdown` | `text` | Output format. |
| `--fail-on` | `breaking`, `warning`, `info`, `none` | `breaking` | Exit with code 1 when changes at or above this severity are found. `none` never fails. |
| `--ignore-rule` | Any rule code | (none) | Exclude a rule from the report. Repeatable. Unknown codes cause exit code 2. |
| `--ignore-path` | JSON pointer prefix | (none) | Exclude changes under a path prefix. Leading `#` is optional. Repeatable. |
| `--kind` | `auto`, `openapi`, `json-schema` | `auto` | Force document kind instead of auto-detecting. |
| `--direction` | `request`, `response`, `neutral` | `neutral` | Set comparison direction for JSON Schema. Ignored for OpenAPI documents (direction is derived from usage context). |
| `--output`, `-o` | File path | (stdout) | Write the report to a file. A notice is printed to stderr. |
| `--color` | | | Force ANSI colour output on. |
| `--no-color` | | | Disable ANSI colour output. |

Colour is enabled automatically for `text` format when the output is a TTY, unless `--no-color` is specified.

Flags accept both `--flag value` and `--flag=value` syntax.

#### Examples

```bash
# Basic comparison
specdiff api-v1.yaml api-v2.yaml

# Markdown output, never fail
specdiff api-v1.yaml api-v2.yaml --format markdown --fail-on none

# JSON output to a file
specdiff before.json after.json --format json -o report.json

# Ignore specific rules and paths
specdiff before.yaml after.yaml \
  --ignore-rule description-changed \
  --ignore-rule deprecated-added \
  --ignore-path "#/paths/~1internal"

# Force JSON Schema mode with response direction
specdiff schema-v1.json schema-v2.json --kind json-schema --direction response
```

### Rules

```
specdiff rules [--json]
```

Lists every rule in the catalogue with its default severity. By default, output is a human-readable table. Pass `--json` to get machine-readable JSON output.

### Explain

```
specdiff explain <code>
```

Displays the full details for a single rule, including its title, description, default severity, and remediation guidance.

```bash
specdiff explain required-parameter-added
```

### Help and Version

```
specdiff --help
specdiff --version
```

---

## Exit Codes

| Code | Constant | Meaning |
|---|---|---|
| 0 | `ok` | No changes at or above the threshold. Also returned by `rules`, `explain`, `--help`, and `--version`. |
| 1 | `thresholdExceeded` | Changes were found that meet or exceed the `--fail-on` threshold. |
| 2 | `usage` | Usage error: unknown flag, invalid value, or unknown rule code. |
| 3 | `inputError` | An input document could not be read or parsed. |

---

## File Parsing

The CLI determines how to parse each file based on its extension:

| Extension | Parser |
|---|---|
| `.json` | JSON |
| `.yaml`, `.yml` | YAML |
| Other | Tries JSON first, falls back to YAML |

The parsed result must be an object. Non-object values (arrays, strings, numbers) are treated as an input error.

---

## Programmatic Usage

The `@specdiff/cli` package exports a `runCli` function so you can embed the CLI in your own tooling without spawning a subprocess.

### runCli

```ts
function runCli(
  argv: readonly string[],
  io: CliIo,
): Promise<number>;
```

Runs a CLI command and returns the exit code as a number. Never calls `process.exit`.

`argv` should contain only the command arguments (without `node` and script path entries).

```ts
import { runCli, type CliIo } from "@specdiff/cli";

const io: CliIo = {
  stdout: (text) => process.stdout.write(text),
  stderr: (text) => process.stderr.write(text),
  cwd: process.cwd(),
  isTty: process.stdout.isTTY === true,
};

const exitCode = await runCli(
  ["before.yaml", "after.yaml", "--format", "json"],
  io,
);
```

### CliIo

```ts
interface CliIo {
  stdout: (text: string) => void;
  stderr: (text: string) => void;
  cwd: string;
  isTty?: boolean;
}
```

| Property | Description |
|---|---|
| `stdout` | Called with output text (the report). |
| `stderr` | Called with diagnostic messages and notices. |
| `cwd` | Working directory used to resolve relative file paths. |
| `isTty` | When `true`, enables ANSI colour for text output (unless `--no-color` is passed). |

### Other Exported Functions

#### parseArgs

```ts
function parseArgs(argv: readonly string[]): ParsedCommand;
```

Parses a raw argv array (without the `node` and script entries) into a typed command object. Throws an `Error` with `name: "UsageError"` on malformed input.

#### loadDocument

```ts
function loadDocument(
  filePath: string,
  cwd: string,
): Promise<unknown>;
```

Reads and parses a JSON or YAML file. Resolves relative paths against `cwd`. Throws `DocumentLoadError` on failure.

#### parseDocumentText

```ts
function parseDocumentText(
  text: string,
  fileName: string,
): unknown;
```

Parses raw text as JSON or YAML based on the file name extension. Follows the same extension rules as file parsing (`.json` for JSON, `.yaml`/`.yml` for YAML, otherwise JSON-first with YAML fallback).

#### createDocumentLoadError

```ts
function createDocumentLoadError(
  filePath: string,
  message: string,
): DocumentLoadError;
```

Creates a `DocumentLoadError` instance.

#### isDocumentLoadError

```ts
function isDocumentLoadError(
  error: unknown,
): error is DocumentLoadError;
```

Type guard that returns `true` when the value is a `DocumentLoadError`.

---

## Exported Types

### OutputFormat

```ts
type OutputFormat = "text" | "json" | "markdown";
```

### KindOption

```ts
type KindOption = "auto" | "openapi" | "json-schema";
```

### CompareCommand

```ts
interface CompareCommand {
  command: "compare";
  before: string;
  after: string;
  format: OutputFormat;
  failOn: FailThreshold;
  ignoreRules: RuleCode[];
  ignorePaths: string[];
  kind: KindOption;
  direction: Direction;
  color: boolean | undefined;
  output: string | undefined;
}
```

### ParsedCommand

```ts
type ParsedCommand =
  | CompareCommand
  | { command: "rules"; json: boolean }
  | { command: "explain"; code: string }
  | { command: "help" }
  | { command: "version" };
```

The discriminated union returned by `parseArgs`. The `command` field determines which shape is present.

### DocumentLoadError

```ts
interface DocumentLoadError extends Error {
  name: "DocumentLoadError";
  filePath: string;
}
```

Thrown by `loadDocument` when a file cannot be read or its contents cannot be parsed.

---

## Exported Constants

### EXIT_CODES

```ts
const EXIT_CODES: {
  readonly ok: 0;
  readonly thresholdExceeded: 1;
  readonly usage: 2;
  readonly inputError: 3;
};
```

### HELP_TEXT

```ts
const HELP_TEXT: string;
```

The full help text printed by `specdiff --help`.

---

## CI Integration

Use `@specdiff/cli` in a GitHub Actions workflow to catch breaking API changes on pull requests:

```yaml
name: API compatibility
on: pull_request
jobs:
  specdiff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Extract the base branch spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > /tmp/openapi-base.yaml
      - name: Fail on breaking changes
        run: npx -y @specdiff/cli /tmp/openapi-base.yaml openapi.yaml --fail-on breaking --format markdown | tee -a "$GITHUB_STEP_SUMMARY"
```

The workflow extracts the spec from the base branch, compares it against the current branch, and appends a Markdown report to the GitHub step summary. The job fails (exit code 1) if any breaking changes are detected.