<img src="https://cdn.jsdelivr.net/npm/@grum-os/cli/brand.png?v=43346b25a840" alt="Grum" width="224" height="59" />

# Grum

Keep a repository on the design contract your team reviewed in [Grum](https://app.grum.so).

Grum reads the tokens the repository declares (CSS custom properties, a Tailwind v4 `@theme`, SCSS variables), and the button and text input designs it defines. The team can add tokens in Grum before the code has them, and move others outside the contract. That reviewed set is what `grum` checks against. It compares your working tree to the contract, lists only drift that is new on this branch, and can send a new item to the project's owners for review, with a picture of how it renders.

Requires Node.js 22 or newer.

## Quick start

Open a terminal in the repository, and run with no arguments:

```sh
npx @grum-os/cli
```

Grum opens a wizard. The arrow keys move, Enter selects, and 1 to 4 jump straight to a row. `q` quits the menu. After you pick a row, `c` cancels that command and returns to the menu.

```text
  1  Check       Check your changes against the design contract
  2  Quick wins  Pick and run the latest scan's quick wins
  3  Setup       Sign in, link this repository, set up Cursor
  4  Login       Sign in to Grum
```

Choose **setup**. Grum walks through the rest:

1. **Sign in.** It prints a code and a link. Approve the code in the browser. The token stays in your home directory (`~/.config/grum/credentials/`), never in the repository. Grum's Settings page lists every computer that is signed in.
2. **Link this repository** to its project in Grum. If several workspaces use it, you pick one. If none does yet, Grum offers to create a workspace, asks for the live site, and waits while the first scan runs. A new workspace needs GitHub connected in Grum.
3. **Set up Cursor.** It asks `Setup for cursor agents?` Press Enter to accept. That writes the hooks, the tools, and the rule described below. `n` skips it. It then asks `Add a pre-commit check?` Enter skips that. `y` adds `grum check --staged` to the hook this repository already uses.

Then choose **check**. New drift is listed with a one-line fix, and Grum offers to send each item for review. You do not need to type a command for any of that.

Once you are signed in, the last row is **Logout**. Run `grum` again and choose **setup** to change the link or turn Cursor off. Enter keeps each current setting.

## Rescan a workspace

After changing code, run `grum scan` in the linked repository to start a fresh server-side scan and follow its progress in the terminal. Add `--root <dir>` when the workspace is a folder inside a monorepo. The scan runs in Grum and may take a few minutes. Ctrl+C stops waiting, but the scan keeps running. Only workspace owners can start scans. When the scan finishes, Grum lists its [quick wins](#quick-wins).

## Quick wins

After setup and after each scan, Grum lists a few quick wins: the changes from the latest scan that make the biggest difference with little effort, such as missing design guidelines or hand-typed values that already match a token. They are the same items as the Quick wins list in Grum, without the ones your team dismissed there.

Choose **quick wins** in the menu (or run `grum wins`) to pick which ones to do, then where to send them: Claude Code or Codex start right there with the prompt, Cursor opens with it in the chat box, or copy or print it. Several picks go as one prompt, done one at a time. When one holds a draft (for example, when to use each button style), the agent shows it to you to correct first.

```sh
grum wins       # pick and send them
grum wins 2     # print the prompt for the second one
```

In Claude Code and Cursor, type `/quick-wins`, or just ask for "quick wins". Codex has no project commands, so ask it for "quick wins". The agent shows the list and asks which ones to do.

## Set up with your agent

A coding agent can do this setup. You paste one prompt into the chat and approve a code in the browser. You do not need to open a terminal.

The page you open is [app.grum.so/agent/human](https://app.grum.so/agent/human). The prompt there points the agent at [app.grum.so/agent](https://app.grum.so/agent), which is the instructions in markdown. The agent runs:

```sh
npx --yes --prefer-online @grum-os/cli@latest setup --agent cursor --json
```

Use `codex`, `claude` (Claude Code) or `generic` instead of `cursor` for those agents. The command prints JSON. Exit 0 means ready, exit 2 means the agent should take the next action and run it again, and exit 1 is a fault. `grum doctor --agent cursor --json` reports the same checks and changes nothing.

Rerunning setup refreshes existing integration files while preserving your own hooks and settings. The agent finishes verification with `grum_doctor` through MCP, which confirms the connection in that chat. If a shell check cannot verify the tools, it asks the agent to discover and call the tool before sending you to settings. Codex is ready when its MCP connection is confirmed and a hook has successfully run. Changed hook commands require review in Codex; new Grum releases using the same commands keep their existing trust.

Grum's files for each agent (`.cursor/rules/grum.mdc` and the Grum blocks in `AGENTS.md` and `CLAUDE.md`) are short and rarely change. The detailed rules arrive as `guidance` in every `grum_contract` and `grum_check` result, and in `grum contract`, so a new Grum release does not need to edit them. When one of these files is from an older version, Grum updates it only after a successful `grum_contract` or `grum_check` call in MCP, and only when you already have other uncommitted work in the repository. Opening your editor, connecting Grum and hooks never write it, so a clean repository stays clean. Grum never stages the change; the tool result names the files it updated. A file that is staged, has your own edits, is a link or is not tracked is left alone, and the first result that skips it says so once. `grum doctor` lists every file it would skip. Run setup to update one yourself.

Agent setup asks where your app runs locally. Repository URLs and `index.html` are suggestions; the agent must ask before selecting one. An older saved address is confirmed once. A local HTTP page must return 200; redirects, 404, access denial, connection refusal and timeouts keep setup incomplete. After a successful probe, the agent opens the page in its visible browser (Codex uses its browser panel), checks rendering, and reports any problems. Setup returns `preview-local-app` until the agent acknowledges the shown URL with `localPreviewed` in `grum_setup`, or `--local-previewed <url>` in the CLI. If browser control is unavailable, the agent asks you to open the URL and confirm it renders. A new address requires a new preview. An explicitly chosen static HTML file also needs preview; file existence does not prove rendering. Choose `none` only if you want to skip the local app.

Agents may also open a page in their visible browser when they want you to see the page they are referring to or asking you about. Grum setup operations still use the CLI; when browser control is available, the agent opens the page itself.

The menu and the commands above stay as they are. `--agent` is an extra way in, not a replacement.

## Cursor

Accepting Cursor in the menu puts Grum inside the agent. The agent follows the same contract a person sees in Grum, and it can ask the owners to allow something the contract does not have.

**Before it writes UI**, it reads the contract: color, typography, spacing, and radius tokens, plus button components and looks, approved text inputs, checkboxes, radios, switches, selects, action menus and tabs, and the rules for using them.

**Before it finishes**, it checks what it changed. Drift already on the default branch stays off the list. New drift comes back with a fix.

**When nothing in the contract fits**, it stops and asks you once:

1. Use an existing token or button.
2. Keep the new style and request review.

If you keep it, Grum captures how the element renders and sends the picture to the Review page. Owners approve or reject it there. A rejected style is not offered again: the agent changes the code to use the contract.

**While you edit**, Cursor hooks check the block that changed. They read the contract cached in `.grum/` and do not call the network, so an edit never waits on Grum. With [blocking](#fail-the-check) turned on, the agent is sent back while that drift stands (twice in one conversation), and `git commit` and `git push` from the agent are refused until it is resolved.

Reload the Cursor window once after setup, or turn **grum** on under Settings, Tools and MCP, so the agent sees the tools. Until then it reads `.grum/contract.json`.

These are the files setup writes. They merge into what is already there, and a second run changes nothing.

| File                     | What it does                                                                                                                                                                                  |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.cursor/mcp.json`       | Tools the agent calls: `grum_contract`, `grum_check`, `grum_request_review`, `grum_add_to_contract`, `grum_snooze_review`, `grum_review_status`, `grum_setup`, and `grum_doctor`.             |
| `.cursor/rules/grum.mdc` | Tells the agent to read the contract before writing UI and to check before it stops. The contract itself stays in Grum, so this file does not go stale.                                       |
| `.cursor/hooks.json`     | After an edit, checks that block. The stop hook sends the agent back on blocking drift. The shell hook can refuse `git commit` and `git push`.                                                |
| pre-commit               | Optional. When you say yes, adds `grum check --staged`. Husky's hook is committed with the repo. Git's own pre-commit hook stays on this computer. A lefthook repo gets the snippet to paste. |

`.grum/` is added to `.gitignore` the first time Grum writes there. Choose **setup** again and answer `n` to the Cursor question to remove what Grum added. Answer `n` to the pre-commit question to drop only that check.

`grum check --cursor` opens the findings in Cursor instead of walking through them in the terminal.

## Codex

`grum init codex` (or `grum setup`, when the repository has a `.codex` folder) puts the same contract inside Codex: the CLI, the desktop app and the IDE extension, on this computer. The agent reads the contract before it writes UI, checks before it finishes, and asks you once when nothing in the contract fits, exactly as Cursor's does.

Codex keeps project configuration and hooks under your control. Grum will not appear in MCP or Hooks until you do both of these:

1. Trust this folder when Codex asks. Then in **Settings > MCP servers**, turn on **grum**. If Codex labels the server with this folder's name, pick the one whose command runs `grum.mjs`.
2. Open **Settings > Hooks** for this project and check Grum's 3 commands (they say "need review" and run `grum.mjs hook ...`). Turn on any that are off; this is separate from MCP. In the terminal app, run `/hooks` to review the list.

Without step 2 the tools still work and the agent still follows the guidance, but nothing checks when Codex stops and `git commit` and `git push` are not refused. Until Grum observes a successful hook run, it reports that hook status is unconfirmed.

| File                 | What it does                                                                                                                                            |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.codex/config.toml` | Adds the same eight tools. Only Grum's own block is written; the rest of the file is left as it is, and a file that is not valid TOML stops setup.      |
| `.codex/hooks.json`  | Three hooks beside any of yours: after an edit, before a shell command, and at stop. They behave as Cursor's do, including [blocking](#fail-the-check). |
| `AGENTS.md`          | A marked block that tells the agent to read the contract, check before it stops, and ask before keeping an off-contract style.                          |

`grum init codex --remove` takes out exactly these. A repository set up for more than one agent keeps the `.gitignore` line and the pre-commit check until the last one is removed.

## Claude Code

`grum init claude` (or `grum setup`, when the repository has a `.claude` folder) puts the same contract inside Claude Code: the CLI, the desktop app and the IDE extensions, on this computer. The agent reads the contract before it writes UI, checks before it finishes, and asks you once when nothing in the contract fits, exactly as Cursor's does.

Claude Code runs a project's tools and hooks only in a folder you trust, so one step is yours: start a new Claude Code session in the repository and trust the folder when it asks. If it asks about the **grum** MCP server, approve it. A session that was already open keeps its old hooks and tools until you start a new one.

| File                    | What it does                                                                                                                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.mcp.json`             | Adds the same eight tools. Other servers in the file are left as they are.                                                                                                                 |
| `.claude/settings.json` | Three hooks beside any of yours: after an edit, before a shell command, and at stop. They behave as Cursor's do, including [blocking](#fail-the-check). Also approves the **grum** server. |
| `CLAUDE.md`             | A marked block that tells the agent to read the contract, check before it stops, and ask before keeping an off-contract style.                                                             |

`grum init claude --remove` takes out exactly these; the rest of each file stays.

## What a check shows

Choosing **check** in the menu prints the same report as `grum check`.

New drift is grouped by file. Each line names the value, the kind of drift, whether anyone has asked for review, and a fix:

```text
src/pages/Hero.tsx
  18: Hard-coded #3C3C3C instead of --text-primary  [Hard-coded value, Not sent for review]
      fix: Use --text-primary (#1B1B1B) instead of #3C3C3C.

1 new drift item against Acme's contract: 1 not sent for review (42 files checked).
```

Drift the latest scan already has on the default branch is counted, not listed. So is drift someone dismissed on the Drift page. A branch that only repeats those looks clean:

```text
No new drift against Acme's contract (42 files checked). 3 known issues are on Acme's Drift page.
```

Using a known value in more places than the default branch does is new drift.

In the terminal, the check then offers each new item for review. A request covers the branch it was asked on, until the change reaches the default branch and a scan counts the drift as existing. A full check also marks a rejected review **Addressed** when the drift is gone, and reopens it when the drift comes back.

A check prints the report and exits 0, including when you are signed out, offline, or the repository has never been scanned. It fails the process only when you ask it to.

### What it looks for

| Area       | New drift                                                                                                                                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Color      | A hard-coded color, a color nearly identical to a contract color, or a color token the contract does not have                                                                                                    |
| Typography | A size, weight, or line height off the contract scale                                                                                                                                                            |
| Spacing    | A margin, padding, or gap off the scale                                                                                                                                                                          |
| Radius     | A radius off the scale                                                                                                                                                                                           |
| Buttons    | A look the contract does not have, a look whose state changed, a missing state, a raw `<button>` outside the button component, or a variant the component lacks                                                  |
| Inputs     | A text input that does not match an approved input, or differs from one in structure, appearance, or states                                                                                                      |
| Checkboxes | A checkbox that overrides an approved checkbox (its box, mark, label, or a checked, indeterminate, disabled, or focus state), or one whose approved state is gone                                                |
| Radios     | A radio that overrides an approved radio (its option, mark, label, or a checked, disabled, or focus state), or one whose approved state is gone. How a group of radios is laid out is not checked                |
| Switches   | A switch that overrides an approved switch (its track, thumb, label, thumb travel, or a checked, disabled, or focus state), or one whose approved state is gone. The settings row around a switch is not checked |

## Review one item yourself

The menu's check already offers this. To send one finding directly, use its key (`grum check --json` prints it):

```sh
grum request "color:raw:#3C3C3C" \
  --reason "The marketing hero uses this gray" \
  --url http://localhost:3000
```

Captures default to the local app. Use `grum request <key> [<key>...] --live` to capture the configured live site after a release. Grum uses the route identified by the finding's source file when it can, otherwise the site's configured URL. This leaves the saved local-app URL unchanged and adds no confirmation prompt. The target URL is printed before capture.

`--url` is the page where the element renders, or an HTML file in the repo (`./index.html`) when there is no dev server. An explicit `--url` takes priority over `--live`, including when a shared component needs a specific page. A page behind a login is captured through the browser extension in your own browser session. `--browser chrome` or `--browser safari` picks the browser. `--no-capture` sends the request with no picture. For a modal finding, `--open-modal-id <id>` and `--open-trigger-id <id>` explicitly identify the requested modal and its opener. The existing **Scan for review** action opens the requested modal if needed, then captures it. If the modal is already visible, it captures without clicking the opener. It clicks only one unique, visible, enabled button with that id, then captures only after the requested modal is observed open. Submit controls, missing or duplicate ids, and clients without opening support are refused; manual opening and scan retry remain available.

```sh
grum capture reset
```

Clears the capture-browser sign-in saved for this repository.

## Read the contract

```sh
grum contract
```

Prints the contract packet agents read before writing UI: tokens by category (with a separate dark value when there is one), button components and looks, approved text inputs, checkboxes, radios, switches, selects, action menus and tabs, team guidelines, quick wins, and review decisions.

## Fail the check

Reporting is the default, so a missing sign-in cannot break a commit. To fail when drift was never sent for review, or was rejected:

```sh
grum check --ci
```

`--strict` also fails while a review is still pending. A pending review passes `--ci`.

The same rule can live in `.grum/config.json`:

```json
{
  "root": "apps/web",
  "block": true,
  "denyGit": ["commit", "push"],
  "maxFollowups": 2
}
```

| Field          | Meaning                                                                                                                                                                      |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root`         | Directory to scan when the app is not at the repository root. `grum check --root` does the same for one run.                                                                 |
| `block`        | `true` makes `grum check` fail on open or rejected drift, and makes Cursor, Codex and Claude Code refuse the git commands below while that drift is in what they would send. |
| `denyGit`      | `commit`, `push`, or both. With `block` and no `denyGit`, both are refused.                                                                                                  |
| `maxFollowups` | How many times the Cursor, Codex or Claude Code stop hook sends the agent back in one conversation. Default is 2.                                                            |

`block` does not fail a check that is still waiting on review. Use `--strict` for that.

Grum adds `.grum/` to `.gitignore` the first time it writes there, so `config.json` stays on this computer. To commit just that file, ignore the directory's contents and keep it: `.grum/*`, then `!.grum/config.json`.

## Continuous integration

Sign in once from the menu, then copy the `token` field from the credentials file for this Grum. For [app.grum.so](https://app.grum.so) that file is:

```text
~/.config/grum/credentials/https_app_grum_so.json
```

Store it as a secret. Do not commit it. The token acts as you, in every workspace you belong to. Settings in Grum lists these sign-ins and can revoke one.

```yaml
# GitHub Actions
- name: Check the design contract
  run: npx --yes --prefer-online @grum-os/cli@latest check --ci
  env:
    GRUM_TOKEN: ${{ secrets.GRUM_TOKEN }}
```

`GRUM_TOKEN` is used instead of the token saved on the machine.

## Commands

The menu covers check, setup, and sign-in. These are the same actions as flags, for scripts and CI.

| Command                                                                                   | What it does                                                                                                                                                                       |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `grum`                                                                                    | The menu. Check, setup (including Cursor), or sign in.                                                                                                                             |
| `grum login` / `grum logout`                                                              | Sign this computer in to Grum, or out.                                                                                                                                             |
| `grum setup [--root <dir>]`                                                               | Sign in, link this repository, and ask about Cursor and the pre-commit check. Use `--agent <agent> --json` for agent setup.                                                        |
| `grum doctor [--agent <agent> --json]`                                                    | Check setup readiness without changing anything.                                                                                                                                   |
| `grum scan [--root <dir>]`                                                                | Start a fresh scan for the linked workspace and follow its progress.                                                                                                               |
| `grum check [flags]`                                                                      | Check the working tree. See the flags below.                                                                                                                                       |
| `grum request <key> [<key>...] [--reason "..."] [--url <page>] [--live] [--browser chrome | safari] [--open-modal-id <id> --open-trigger-id <id>] [--no-capture]`                                                                                                              | Ask the owners to allow drift items; items on one page capture together. |
| `grum add-token ...` / `grum add-button ...`                                              | For owners, add a token or approved button design to the contract.                                                                                                                 |
| `grum add-icon <ref> [--name <name>]`                                                     | For owners, add an icon `grum check --json` lists under `discovered.icons`, from this checkout's source.                                                                           |
| `grum snooze <key> [<key>...]`                                                            | Pause questions about existing drift on this branch for the configured period.                                                                                                     |
| `grum uninstall [--yes]`                                                                  | Remove Grum from this repository and this computer. See [Uninstall](#uninstall).                                                                                                   |
| `grum capture reset`                                                                      | Clear this repository's saved capture-browser sign-ins.                                                                                                                            |
| `grum contract`                                                                           | Print the contract packet.                                                                                                                                                         |
| `grum wins [<number>]`                                                                    | Pick quick wins and send them to your agent, or print one's prompt. See [Quick wins](#quick-wins).                                                                                 |
| `grum feedback "<what happened>" [--kind <kind>]`                                         | Tell the Grum team about a question, confusion or problem. `<kind>` is `question`, `confused`, `problem` or `idea`. If Grum can't be reached it waits and goes with the next send. |
| `grum init cursor [--remove] [--force] [--local-only] [--pre-commit]`                     | Write or remove the Cursor files without the menu. `--pre-commit` adds the commit check. `--no-pre-commit` removes it.                                                             |
| `grum init codex [--remove] [--force] [--local-only] [--pre-commit]`                      | The same for Codex: `.codex/config.toml`, `.codex/hooks.json` and the `AGENTS.md` block.                                                                                           |
| `grum init claude [--remove] [--force] [--local-only] [--pre-commit]`                     | The same for Claude Code: `.mcp.json`, `.claude/settings.json` and the `CLAUDE.md` block.                                                                                          |

```sh
grum check --staged                 # drift in the blocks staged for commit
grum check --files src/Hero.tsx     # only these paths
grum check --json                   # project, cache time, known count, findings, discovered icons
grum check --no-review              # print the report and do not ask
grum check --cursor                 # open the findings in Cursor
grum check --offline                # the cached contract, no network
grum check --root apps/web          # the app lives in a subdirectory
grum check --report                 # also post this run to Grum
grum check --ci                     # fail on open or rejected drift, and post the run
grum check --strict                 # like --ci, and also fail while a review is pending
```

`--ci` and `--report` show up on the Drift page under Recent checks, one latest run per branch. CI, JSON, staged, and agent runs never open the menu and never prompt.

`grum hook` and `grum mcp` are the entry points Cursor, Codex and Claude Code run. You do not call them yourself.

**Feedback.** When you're stuck, confused, or ask your agent how to use Grum, the agent sends the Grum team a short summary in its own words, with the few turns about Grum that led there, and a Grum tool that fails reports itself. Each report also includes the last few Grum tool calls in that chat (what the agent asked Grum and what Grum answered, cut short), so the team can see where things went wrong. It runs in the background and never slows your work. The agent is told never to include code, file contents, or secrets, and your sign-in token is never sent; your home folder, emails, and secret-looking strings are taken out first. Set `GRUM_FEEDBACK=off` to stop it.

For `setup` and `doctor`, `<agent>` is `cursor`, `codex`, `claude` or `generic`.

## Uninstall

```sh
npx @grum-os/cli uninstall
```

Run it in the repository. It asks first (Enter means no), then removes:

- **From this repository:** Grum's Cursor, Codex and Claude Code entries (only Grum's; the rest of each file stays), the pre-commit check, the `.gitignore` line, and `.grum/`.
- **From this computer:** every sign-in, ended in Grum too so the token stops working, and `~/.config/grum` (sign-ins, capture browser data, the log).

Your workspace, contract and reviews stay in Grum. Other repositories you set up keep their files: run it in each. `--yes` skips the question, for scripts and agents.

If you installed the CLI globally, also run `npm uninstall -g @grum-os/cli`.

## Environment

| Variable          | What it does                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------------------------ |
| `GRUM_TOKEN`      | Sign in for CI. Overrides the token saved on this computer.                                                        |
| `GRUM_API_ORIGIN` | Talk to a Grum other than `https://app.grum.so`. An `http` or `https` origin, with no user or password in the URL. |
| `GRUM_NO_BROWSER` | Do not open a browser during sign-in. The code and link are still printed.                                         |
| `GRUM_FEEDBACK`   | `off` sends no feedback or tool-error reports to the Grum team.                                                    |
