---
title: "CLI reference"
description: "The commands you actually need, which of them are shadcn's, and why opsinjs deliberately ships almost no CLI of its own."
url: "https://opsinjs.pensievelabs.org/registry/cli-reference"
source: "https://opsinjs.pensievelabs.org/registry/cli-reference.md"
section: "Registry & distribution"
kind: "guide"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["cli", "commands", "shadcn add", "terminal"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> `<StubNotice>` IS THE EXCEPTION, AND IT IS THE ONE TO READ. It is a
> paired element rather than a self-closing one, and the text between
> its opening and closing tags is prose an author wrote, reproduced
> below word for word. That prose is where this page says whether the
> component has been reviewed. Read the children, not only the
> attributes.

<PageTemplate kind="guide" />

## Overview [#overview]

There is no `opsinjs` command, and the plan is to keep it that way for as long
as possible.

`npx shadcn` already installs items from a registry, resolves dependencies,
previews diffs, decodes preset codes, reads a project's configuration and runs
an MCP server. Re-implementing any of that would produce a second tool with the
same job, a different set of bugs, and one more thing to keep in step with a
specification opsinjs does not own.

This page is therefore mostly a map of somebody else's CLI, annotated with what
each command means here. The short list of things opsinjs genuinely needs a tool
for is at the bottom, and it is specified rather than built.

## Install and add [#install-and-add]

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest add https://opsinjs.pensievelabs.org/r/status-pill.json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest add https://opsinjs.pensievelabs.org/r/status-pill.json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest add https://opsinjs.pensievelabs.org/r/status-pill.json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest add https://opsinjs.pensievelabs.org/r/status-pill.json
    ```
  </CodeBlockTab>
</CodeBlockTabs>

A full URL is self-contained only for an item that depends on nothing else here.
Twenty-one items name their siblings namespaced in `registryDependencies`, so
installing one of those needs an `@opsinjs` entry in `components.json` even by
URL. [The registry overview](./index.mdx) has the list and the block.

The flags worth knowing:

| Flag                | What it does                           | Why it matters here                                                                                          |
| ------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `--dry-run`         | Preview changes without writing        | Always worth running first against a registry you do not control                                             |
| `--diff [path]`     | Show the diff for a file               | The upgrade path for code you have already edited, covered in [Upgrades and diffs](./upgrades-and-diffs.mdx) |
| `--view [path]`     | Print file contents without installing | Read a health component's source before you accept it                                                        |
| `-o, --overwrite`   | Overwrite existing files               | Discards your edits. Use with `--diff` first, never alone                                                    |
| `-p, --path <path>` | Install somewhere specific             | Overrides the alias resolution from `components.json`                                                        |
| `-a, --all`         | Add everything                         | Never a good idea against opsinjs: it lands all sixty items at once, which is rarely what a project wants    |

`--view` deserves emphasis. In a copy-not-dependency model you are accepting
source into your repository, and being able to read it before it lands is the
main safety property of the whole approach.

## Inspect before installing [#inspect-before-installing]

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest view @opsinjs/result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest view @opsinjs/result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest view @opsinjs/result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest view @opsinjs/result-card
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest search @opsinjs -q "range"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest search @opsinjs -q "range"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest search @opsinjs -q "range"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest search @opsinjs -q "range"
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest docs result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest docs result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest docs result-card
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest docs result-card
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`search` matches against name, title and description, which is why opsinjs
writes descriptions in plain English rather than in component vocabulary. The
description "a value shown against a reference range" is findable by somebody who
does not know the word `RangeBar`.

## Themes and presets [#themes-and-presets]

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest apply <code> --only theme
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest apply <code> --only theme
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest apply <code> --only theme
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest apply <code> --only theme
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`apply` takes a preset code and applies it to an existing project;
`--only theme` and `--only font` restrict it. The `preset` command family
decodes and resolves codes without applying anything:

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest preset decode <code>
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest preset decode <code>
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest preset decode <code>
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest preset decode <code>
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest preset resolve
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest preset resolve
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest preset resolve
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest preset resolve
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`preset resolve` prints the preset your project currently matches, which is the
fastest way to answer "what theme is this project actually on". See
[Preset codes](./preset-codes.mdx).

## Read your own project [#read-your-own-project]

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npx shadcn@latest info --json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx shadcn@latest info --json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx shadcn@latest info --json
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x shadcn@latest info --json
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Framework, Tailwind version, aliases, base library, icon library, configured
registries and installed components. This is the same call the
[agent skill](../agents/agent-skill.mdx) makes on activation, and it is a good
first move when something installs into an unexpected directory.

## Commands to be careful with [#commands-to-be-careful-with]

<SafetyCallout severity="watch" evidence="opinion">
  `eject` inlines `shadcn/tailwind.css` into your stylesheet and removes the
  `shadcn` dependency. It is explicitly irreversible, and after it the CLI's
  future updates to that file no longer reach you. opsinjs relies on that import
  being present and in the right position, because the order of `app/globals.css`
  is load-bearing. Eject only with a specific reason and a record of why.
</SafetyCallout>

`migrate` rewrites your code in place: icon library, base colour, `cn`, RTL,
Radix. Each subcommand is useful and each is a large diff. Commit first.

## The repository's own scripts [#the-repositorys-own-scripts]

If you have cloned or forked opsinjs, these are the commands that matter. They
are `package.json` scripts, not a CLI, and they run under plain Node. The `.mts`
scripts are executed by Node's own type stripping, with no `tsx` and no `ts-node`
in the tree.

| Command                    | What it does                                                                                                             |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `pnpm run generate`        | Tokens, registry and generated reference pages                                                                           |
| `pnpm run contrast`        | Measures every token pair with APCA and WCAG 2.2                                                                         |
| `pnpm run check`           | The three generators in `--check` mode, then `check:generated`, `check:ia`, `check:a11y` and `check:llms`, in that order |
| `pnpm run check:generated` | Regenerates and fails on a diff                                                                                          |
| `pnpm run check:ia`        | Frontmatter, section outlines, link and reachability assertions                                                          |
| `pnpm run check:a11y`      | The accessibility rig's static half, over the component sources                                                          |
| `pnpm run check:llms`      | The index and shards against the page tree                                                                               |
| `pnpm run check:freshness` | Reports pages past their review SLA. Does not fail the build                                                             |
| `pnpm run evals`           | The agent eval suite                                                                                                     |

`check:a11y` is the one that reads `registry/` rather than `content/`. It parses
each component source as text and fails on a status carried by colour alone, a
type size in px, a raw colour literal, an element taking colour from both axes, a
banned word, or a class that resolves under the docs chrome and to nothing under
`/view`. It needs no browser and no running server, which is why it sits in the
same one-command sweep as the other three. The half that does need a browser
runs only in `.github/workflows/nightly.yml`, and `--layout` is how you ask for
it. That half covers hit area, 1.3x and 200%.

Node 24 or newer is required, and each script starts with a version guard that
exits with a readable message rather than a stack trace.

## What an opsinjs CLI would be for [#what-an-opsinjs-cli-would-be-for]

<StubNotice
  name="opsinjs-cli"
  issue="prashantonomy/opsinjs#0"
  questions="[
  &#x22;Is check-theme a standalone binary or a subcommand of a larger tool?&#x22;,
  &#x22;Should doctor work on any repository, or only one with a components.json?&#x22;,
  &#x22;What is the exit code when a theme passes WCAG 2.2 and fails APCA?&#x22;,
]"
/>

Two jobs `npx shadcn` genuinely does not do, and they are the only justified
scope:

**`check-theme`** validates a theme's contrast in CI. Specified in
[Validating your theme](../theming/validating-your-theme.mdx) and shipped by
[`@opsinjs/color`](../packages/opsinjs-color.mdx).

**`doctor`** checks the CSS import order, confirms the generated token layer
loaded, and confirms no element carries both colour axes. All three are real
failure modes that currently have no automated check.

Nothing else. A CLI that duplicates `add` would be a maintenance liability with
no user-visible benefit.

## Verify it worked [#verify-it-worked]

<Steps>
  ### The CLI sees your project [#the-cli-sees-your-project]

  `npx shadcn@latest info` prints your configuration. If it prints defaults, it is
  running from the wrong directory.

  ### A dry run does what you expect [#a-dry-run-does-what-you-expect]

  `npx shadcn@latest add <url> --dry-run` lists the files it would write. Read the
  list before dropping the flag.

  ### The scripts run [#the-scripts-run]

  In a fork, `pnpm run check` should complete. It is the closest thing to a
  one-command answer to "is this repository consistent".
</Steps>

## Troubleshooting [#troubleshooting]

**`npx` uses a stale version.** `npx clear-npx-cache`, then retry. This is also
the fix for an MCP server reporting no tools.

**A command works globally and not in a monorepo.** Use `-c, --cwd` to point at
the package rather than the workspace root.

**`add` wrote nothing and reported success.** All sixty ids now carry `files`,
so an empty write points at your `components.json` rather than at the item.
Confirm the `aliases` resolve to a writable path, check `implemented` for that
name in `/r/index.json`, and see [the registry overview](./index.mdx).

**A script fails with a syntax error in a `.mts` file.** Node is older than 24
and cannot strip types. Check `node --version`.

## Next [#next]

* [Upgrades and diffs](./upgrades-and-diffs.mdx) shows `--diff` in anger.
* [Preset codes](./preset-codes.mdx) covers encoding, decoding and applying.
* [MCP server](../agents/mcp-server.mdx) is the same registry, for an assistant.
