smith plugin
Code review inside your editor. You ask for a review in the chat you already use, the agent runs the checks and reads the diff, and the Smith server decides whether the change is blocked.
Two editors are supported. Cursor is first below because it needs one more install step than Claude Code and gets it wrong more easily. If you use Claude Code, skip to Claude Code.
Every command on this page was run on macOS before it was written down — the install and command
sections on 2026-09-07, the rest on 2026-08-21. The smith
commands were run in an empty environment: nothing but HOME, and a PATH holding node and the
system tools. What was observed and what is still an assumption is listed at the end, in
What was observed.
Cursor
Everything you need is in this section: install, first review, and what to do when it does not answer.
1. Install
One command in a terminal, then one pick inside Cursor. Nothing to clone.
cursor-agent plugin marketplace add https://github.com/sebospc/ai-apps
Then in Cursor, type /plugins and install smith from the list.
To update later, remove the marketplace and add it again:
cursor-agent plugin marketplace remove smith
cursor-agent plugin marketplace add https://github.com/sebospc/ai-apps
Then /plugins and install smith again.
Reinstalling from /plugins is the part that matters, and it is not optional. Cursor keeps two
copies: the index, under ~/.cursor/plugins/marketplaces/, and the plugin it actually loads,
under ~/.cursor/plugins/cache/. remove and add move the first. Only reinstalling moves the
second — measured 2026-09-08, where the index went to the new commit and the loaded plugin stayed
three commits behind.
So check the one that runs, not the one that was fetched:
ls ~/.cursor/plugins/cache/smith/smith/
That prints the commit you are on. Compare it with the head of the repository, or ask the question
without reading a hash — this answers for the version that added smith plan --preview:
grep -q -- --preview ~/.cursor/plugins/cache/smith/smith/*/bin/smith \
&& echo "up to date" || echo "OUT OF DATE"
marketplace update does not fetch at all, whatever it prints. It answered
✓ Updated marketplace smith: 1 plugin indexed and left even the index on the commit it already
had. A tick from that command is not evidence of anything.
The same collision bites on add: if a smith marketplace already exists pointing somewhere else,
the name collides, the old one wins silently, and marketplace list still shows the old URL after an
add that printed a tick. Removing first is what makes both cases behave.
Keeping a team up to date
The marketplace add above is the quickest way in and it has one property worth knowing before you
hand it to other people: a marketplace added this way stays pinned to the commit it had when it was
added. Cursor's plugin documentation says so — a plugin added from GitHub without going through the
Marketplace "stays pinned to the commit it had when you added it and won't pull new commits or
releases from GitHub on its own". Every update then costs each developer a remove, an add and a
reinstall, and the check below to prove it took.
For a team, do it from the other side once instead:
- Dashboard → Plugins
- Team Marketplaces → Add Marketplace → Import from Repo
- Marketplace Settings → Auto Refresh
Auto Refresh needs the Cursor GitHub App installed on the repository. With it, Cursor re-indexes on push, at most once every ten minutes. Developers install once and stop thinking about it.
One trap in that flow: if plugins were added to the marketplace individually, Auto Refresh only updates the ones already there — re-import the repository URL to pick up a new one.
From a checkout instead
Only when you are changing the plugin itself, or the machine cannot reach GitHub. From the root of your checkout, all three lines:
rm -rf ~/.cursor/plugins/local/smith
mkdir -p ~/.cursor/plugins/local
cp -R "$PWD/plugin" ~/.cursor/plugins/local/smith
The first line is not decoration. cp -R onto a directory that already exists copies into it, so
running the third line twice on its own leaves you with a plugin/ folder nested inside the
install, and Cursor then loads whichever of the two it reads first. Run all three every time you
pull, and it does not matter what was there before.
A copy, not a symlink. Cursor rejects a plugin whose target lies outside
~/.cursor/plugins/local, so a symlink to your checkout loads nothing and tells you nothing.
2. Your first review
Type it:
/smith-review
It asks for your Smith server and your API key the first time, and saves them. You never type them again, in this repository or any other. Your key comes from your project lead, who issues it on the project's settings page in the Smith web UI.
That is the whole thing. The agent finds the CLI inside the plugin directory, runs the review and
tells you the verdict and what to fix. The credentials are stored user-only in
~/.smith/config.json, mode 600, and sent as a bearer token. The server keeps only an argon2 hash
of the key, so nobody can read it back out.
Nothing else in this plugin runs on its own. Ask for a code review in your own words and you get whatever your editor normally does — this plugin stays out of it until you type the command. That is deliberate: a review tool that answers questions nobody asked it is a review tool people uninstall.
The other command is /smith-apply, which builds a feature from Smith's implementation catalog. It
needs a server with the catalog on it; against one without, it answers Not Found.
3. When it does not answer
Do not type /review. That one belongs to Cursor. It answers with a menu of /review-bugbot
and /review-security and never reaches this plugin. The command carries the smith- prefix for
exactly that reason.
If /smith-review is not in the list, the install did not land. Check its shape:
ls ~/.cursor/plugins/local/smith # a checkout install
You should see bin, commands, plugin.json. If you see skills, you are on a version from
before 2026-09-08 and the update did not land — remove the marketplace and add it again, per
Install. If you also see plugin, the nested copy happened: run the three install
lines again, all of them.
A checkout install shadows a marketplace one, and Cursor loads both. That cost a walk an hour here:
a copy left in ~/.cursor/plugins/local/smith from an earlier install was read instead of the
current plugin, and the session behaved like the old version because it was the old version.
This plugin ships commands and no skills, on purpose. A skill sits in Cursor's "Agent Decides" list and is matched against what a developer types, which is how a review plugin ends up answering questions about something else. If you ask for a review in your own words and nothing from Smith happens, that is the design working, not a broken install.
Claude Code
Two commands, and nothing to clone:
claude plugin marketplace add https://github.com/sebospc/ai-apps.git
claude plugin install smith@smith
Then type /smith-review. It asks for your server and key the first time, and saves them.
Give it the full https:// URL, not the owner/repo shorthand. The shorthand clones over SSH, so on
a machine with no GitHub key it fails on a repository that is public and that git clone would have
read without asking anyone.
From a checkout instead, when you are changing the plugin itself, the same two commands with a path in place of the repository:
claude plugin marketplace add "$PWD"
claude plugin install smith@smith
Claude Code puts the plugin's bin/ on PATH by itself, so there is nothing else to set up. To run
against a checkout without installing anything: claude --plugin-dir ./plugin.
What it does, and what it never does
bin/smith collects the diff, talks to the server and prints JSON. It does no reasoning. The agent
in your editor does that, using the plan the server returns. Node standard library only, so there is
nothing to install and no version to keep up with.
No hooks, no status line, no background monitors, no state files. Nothing in this plugin runs
between reviews. If you are not running a review, it is doing nothing at all. The only file it ever
writes is ~/.smith/config.json, and only when smith auth runs.
Put smith on PATH
Optional, and it changes nothing about a review. Without it the agent runs
node ~/.cursor/plugins/local/smith/bin/smith, which works and which you never see. Do this only if
you want to type the commands in the next section yourself, and run it from the root of your
checkout:
ln -sf "$PWD/plugin/bin/smith" "$(dirname "$(command -v node)")/smith"
It lands next to node, which is on PATH by definition because bin/smith starts with
#!/usr/bin/env node. No sudo, no shell config to edit.
One thing to know: if you manage Node with nvm and switch versions, the new version's bin/ has no
smith and the line has to be run again. Any directory already on your PATH works the same way, if
you prefer one that outlives a version switch.
By hand, or from CI
smith auth --url <server> --key <key> # what the agent runs for you on the first review
smith status # configuration and reachability
smith plan # → review plan as JSON
smith plan --base main # review the branch against main instead of uncommitted work
smith submit 42 < findings.json # → verdict; exits 1 when it blocks
smith respond 42 < answers.json # → verdict after the developer's answers to the findings
smith review 42 # → the review again, when the conversation lost its plan
smith plan reviews uncommitted work when there is any, otherwise the branch against its base.
Deleted, vendored, binary and oversized files are left out of the payload.
--base main compares main...HEAD, which is committed work only. Uncommitted edits are not in it,
and on a branch with nothing committed yet it exits 1 with no changes to review.
Exit codes are the point of this section: submit and respond exit 1 while the verdict blocks,
plan exits 1 when there is nothing to review, and everything else exits 0. A blocking verdict is
not a failure of the command, and smith never prints a stack trace or a status code — every
failure is one sentence on stderr starting with smith:.
What was observed
Everything in this list was checked by running it on macOS on 2026-08-21, against the Cursor Agent
CLI 2026.08.11-e8db854 and Claude Code 2.1.238. Nothing here comes from reading the loaders.
Observed:
- Both install sections above run clean. The Cursor copy was run twice in a row and produced the
same tree both times; the two Claude Code commands were run in a fresh config directory and
answered
Successfully installed plugin: smith@smith (scope: user). cp -Ronto an existing install nests the plugin. A second plain copy leftplugin/insidesmith/. That is why the install is three lines and not one.- One prompt in Cursor gets a verdict, starting from nothing. In a throwaway repository holding
real code, with
~/.smithempty, the prompt above made the agent runsmith auth ... && smith planon its own, read the whole diff, submit, and then report the verdict first and five numbered findings, closing with one question. It never showed an id, a piece of JSON, or the key it had just been given. The transcript isoutput/first-review-cursor-2026-08-21.txt. - The agent reaches the CLI through the plugin directory even when
smithis on PATH. Every command in that run wasnode ~/.cursor/plugins/local/smith/bin/smith ..., with a workingsmithsitting on PATH the whole time. It reads the path out of the command; it does not go looking. - That conversation is asserted, not just read.
node scripts/walk_skill.mjs cursorandnode scripts/walk_skill.mjs clauderun the same session in each editor, with credentials already saved, and check the same twelve things about what the developer was shown. Twelve green in both, so the two editors cannot drift apart quietly. - The whole CLI works from a plain shell.
auth,status,plan,plan --base,submit,respondandreviewwere each run against a live server in a shell with an empty environment, and the exit codes documented above are the ones they returned. - A plugin command is typed, in both editors. Measured 2026-09-07 against a probe plugin: a
command file at
commands/smith-review.mdanswered to/smith-reviewin Claude Code, and to the namespaced/probe:smith-reviewas well. The older finding here — that Cursor gives a plugin skill no name, so selection is by description alone — was true when it was measured in August and is not true now: Cursor's plugin reference documents both acommands/directory and/skill-nameinvocation. That is why this plugin ships commands and deleted its skills. /reviewis Cursor's own. Typed with this plugin installed, it answers "Which review should I run?" and offers/review-bugbotand/review-security. Nothing of this plugin is among them, which is why its command is/smith-review.- Cursor rejects a symlink pointing outside
~/.cursor/plugins/local. The same directory loaded nothing when symlinked and loaded when copied in. --plugin-dir <checkout>/pluginloads the plugin straight from a checkout, in both editors, with no copy.- The PATH line is executed by the suite, not only written down here.
node --test plugin/testreads that block out of this file, runs it against a throwawaynode, and fails if thesmithit installs does not answersmith status. It caught this rewrite once, when the block still carried a/path/to/...placeholder and produced a dangling symlink. - Cursor does not put the plugin's
bin/on PATH; Claude Code does. Asked to runcommand -v smithwith the plugin loaded and the Claude Code plugin directories stripped from PATH, the Cursor agent printedNOT_ON_PATH. That is why the section above is optional. - Cursor's agent shell runs Node 18, not whatever
node -vsays in your terminal. Node 18 tries only the first address a name resolves to, and on macOSlocalhostresolves to::1before127.0.0.1, so a server bound to127.0.0.1was reported as not answering whilecurlreached it from the same shell.bin/smithnow tries both families the way curl does, so a server onlocalhostworks from either editor. - A copy in
~/.cursor/plugins/localis loaded as well as the one--plugin-dirnames, and the agent picks whichever skill it reads first. A stale install reviews with a stale CLI. - A manifest
variablesschema cannot carry the server URL or the API key. A throwaway plugin declaringPROBE_URLandPROBE_KEYloaded, and then the command file still readMARKER_PROBE_URL=${PROBE_URL}when the agent opened it,envin the agent's shell had noPROBE_*, and${CURSOR_PLUGIN_ROOT}and${CLAUDE_PLUGIN_ROOT}came back literal too. The loader substitutes variables into the MCP server configuration and nowhere else, and a skill reaches the agent as a path, so what the agent reads is the file on disk, unexpanded.
Assumptions, not observed. Treat these as unproven:
- The Cursor IDE, as opposed to its CLI. Everything above is the CLI. The IDE is assumed to load the same directory the same way, and nobody has checked.
- Which manifest name the Cursor loader prefers. Both
.cursor-plugin/plugin.jsonand.claude-plugin/plugin.jsonwere present in every run, so no run can tell them apart. That both are accepted comes from reading an older loader, not from a run. - The root
plugin.json. It follows the Agent Plugin standard and is kept for other clients, but nothing on this machine has loaded it, and neither editor appears to read it. - Anything other than macOS. Every run was macOS. The commands are POSIX and are expected to work on Linux; no Linux run exists.
Test
node --test plugin/test