| name | setup |
| description | End-to-end installer playbook for this repo โ wires the cursor-composer plugin into Hermes, brings up the local cursor-sdk proxy as a managed systemd --user service, sets the CURSOR_API_KEY, and verifies the whole path works. Use when the user says "set up", "install", "get this running", "wire it into Hermes", "make it work on a fresh machine", "/setup", or when symlinks/proxy are broken after the repo was moved or renamed. Drives the steps for the agent; not a prose explainer. Skip for SDK-internal debugging (that's the cursor-sdk skill) or for understanding *why* the architecture is a proxy (that's the README). |
setup: get the cursor-composer plugin running in Hermes
This is an executable playbook, not documentation. Run the steps in order. Each
step says how to check whether it's already done so re-running is safe (idempotent).
The whole thing is two moving parts:
- The plugin โ a declarative
ProviderProfile symlinked into ~/.hermes/plugins/.
- The proxy โ a local FastAPI service (
hermes-cursor-proxy) that makes Composer
look like an OpenAI chat-completions endpoint. The plugin is useless without it.
Always derive the repo root fresh โ never assume the directory name. This repo has
been renamed before (hermes-plugins โ cursor-sdk-for-hermes), and the committed
systemd unit still carries the old path. Step 4 exists specifically to catch that.
REPO="$(git -C "$(pwd)" rev-parse --show-toplevel)"
Step 0 โ prerequisites
command -v python3
command -v hermes
python3 -c 'import sys; assert sys.version_info >= (3,10)'
You also need a Cursor API key (CURSOR_API_KEY). If the user doesn't have one,
point them at https://cursor.com/ โ the plugin can't authenticate without it. Don't
print the key back to the terminal once you have it.
Step 1 โ install the proxy into its own venv
A venv is NOT relocatable. Every script in .venv/bin/ has an absolute shebang
and the editable install writes an absolute .pth โ so if this repo was ever moved
or renamed, an existing .venv is silently poisoned: ls shows the entrypoint, it's
even +x, but running it gives No such file or directory (dead shebang) and the
module won't import. Don't trust file existence โ test that the shebang points into
this venv and the package imports. If not, rebuild from scratch:
cd "$REPO/proxy"
NEED_BUILD=1
if [ -x .venv/bin/hermes-cursor-proxy ]; then
SHEBANG="$(head -1 .venv/bin/hermes-cursor-proxy)"
if printf '%s' "$SHEBANG" | grep -qF "$REPO/proxy/.venv" \
&& .venv/bin/python -c 'import hermes_cursor_proxy' 2>/dev/null; then
NEED_BUILD=0; echo "โ venv healthy (skip)"
else
echo "โ stale venv (shebang/import points elsewhere) โ rebuilding"
fi
fi
if [ "$NEED_BUILD" = 1 ]; then
rm -rf .venv
python3 -m venv .venv
.venv/bin/pip install -q -e .
fi
head -1 .venv/bin/hermes-cursor-proxy
.venv/bin/python -c 'import hermes_cursor_proxy, cursor_sdk, fastapi, uvicorn, tiktoken; print("โ imports ok")'
(Note: hermes-cursor-proxy --help is NOT a valid health check โ the entrypoint has
no --help; it just tries to boot. Use the import test above.) The pin is
cursor-sdk==0.1.5 (pre-1.0, API moves) โ see the cursor-sdk skill before bumping it.
Step 2 โ symlink the plugin + unit + scaffold the env file
"$REPO/scripts/install-symlink.sh"
This is idempotent โ it re-points three things and creates a 0600 env file:
~/.hermes/plugins/model-providers/cursor-composer โ this repo's plugin dir
~/.config/systemd/user/hermes-cursor-proxy.service โ this repo's unit
~/.config/hermes-cursor-proxy/env (only if absent โ never clobbers a real key)
Verify the plugin symlink actually resolves (this is the #1 thing that silently
breaks after a move โ a dangling symlink looks present in ls but cat fails):
cat ~/.hermes/plugins/model-providers/cursor-composer/plugin.yaml \
|| echo "โ DANGLING โ symlink points at a path that no longer exists; re-run step 2"
Step 3 โ set the API key + workspace in the env file
Edit ~/.config/hermes-cursor-proxy/env:
CURSOR_API_KEY=<the key> (required)
HERMES_CURSOR_PROXY_CWD=<dir Composer may edit> โ the install script picks a
narrow default (~/Project or a locked-down fallback). Point it at the repo the
user actually wants Composer working in. Composer can read/write under this root,
so keep it tight.
Use Edit on the file (don't echo the key into shell history). Leave the other
commented options alone unless the user asks.
Step 4 โ โ fix the unit's ExecStart path (the rename trap)
The committed unit hardcodes ExecStart=%h/tools/hermes-plugins/proxy/.venv/....
If this repo lives anywhere else (e.g. cursor-sdk-for-hermes), the service will
fail to start even though everything else looks fine. Check, and if it's wrong,
write a drop-in override pointing at the real venv (don't edit the symlinked unit โ
a drop-in survives re-symlinking):
WANT="$REPO/proxy/.venv/bin/hermes-cursor-proxy"
HAVE="$(systemctl --user cat hermes-cursor-proxy.service 2>/dev/null | sed -n 's/^ExecStart=//p')"
if [ "$HAVE" != "$WANT" ]; then
mkdir -p ~/.config/systemd/user/hermes-cursor-proxy.service.d
printf '[Service]\nExecStart=\nExecStart=%s\n' "$WANT" \
> ~/.config/systemd/user/hermes-cursor-proxy.service.d/10-execstart.conf
echo "โ ExecStart overridden โ $WANT"
fi
systemctl --user daemon-reload
(The empty ExecStart= line is required โ systemd needs it to clear the inherited
value before setting a new one.)
If you'd rather fix it properly: the root cause is the hardcoded path in
systemd/hermes-cursor-proxy.service:26. Making it relocatable is a repo change,
out of scope for setup โ flag it to the user.
Step 5 โ kill any orphan proxy, then start the managed service
A proxy started before the rename keeps running from a now-deleted venv path. The
new service will collide with it on port 8765. Clear it first.
โ Do NOT use pkill -f 'bin/hermes-cursor-proxy' โ -f matches the whole command
line, and the shell running this very step contains that string, so pkill kills its
own session (you'll see exit 144 = SIGTERM). Target the process holding the port
instead, which is unambiguous and can't match the shell:
HOLDER="$(ss -H -tlnp 'sport = :8765' 2>/dev/null | grep -oP 'pid=\K[0-9]+' | head -1)"
if [ -n "$HOLDER" ]; then echo "port 8765 held by PID $HOLDER โ killing orphan"; kill "$HOLDER" 2>/dev/null; sleep 2; fi
systemctl --user enable --now hermes-cursor-proxy.service
systemctl --user status hermes-cursor-proxy.service --no-pager -l | head -20
(If the holder turns out to be the systemd service itself, systemctl restart later
in this step supersedes it โ killing first just guarantees a clean bind.)
If it's not running, read the journal โ the ExecStart path (step 4) and a missing
CURSOR_API_KEY are the two usual culprits:
journalctl --user -u hermes-cursor-proxy.service -n 40 --no-pager
Step 6 โ verify end to end
curl -s http://127.0.0.1:8765/health | python3 -m json.tool
"$REPO/scripts/test-roundtrip.sh"
test-roundtrip.sh exercises models listing, blocking + streaming completions, the
OpenAI error envelope, reasoning_effort forwarding, and workspace rejection. It
pulls CURSOR_API_KEY from the env file automatically. All-pass = the proxy is good.
Then confirm Hermes itself sees the provider:
hermes chat -q "Reply with one word: PONG" --provider cursor --model composer-2.5
If Hermes says the provider is unknown, the plugin symlink (step 2) didn't resolve โ
go back and check cat ...plugin.yaml.
Troubleshooting cheatsheet
| Symptom | Cause | Fix |
|---|
| Hermes: "unknown provider cursor" | plugin symlink dangling (repo moved/renamed) | step 2, then verify cat ...plugin.yaml |
systemctl start fails instantly | unit ExecStart points at old/wrong venv path | step 4 drop-in override |
/health works but service is not-found/inactive | proxy is an orphan from before a rename, not managed by systemd | step 5 (pkill then enable --now) |
hermes-cursor-proxy: No such file or directory (but file exists + +x) | venv poisoned by a repo move โ dead shebang / editable .pth | step 1 rebuild (rm -rf .venv then recreate) |
| 401 from proxy | CURSOR_API_KEY empty/wrong in env file | step 3 |
400 on a request with x-workspace | path not under the allowlist | widen HERMES_CURSOR_PROXY_CWD / _WORKSPACE_ALLOWLIST in env file |
| port 8765 in use | old proxy still bound | kill the port holder (ss -tlnp 'sport = :8765'), then restart โ never pkill -f (it kills your shell) |
| Composer can't edit files | HERMES_CURSOR_PROXY_CWD too narrow, or systemd ProtectHome=read-only blocks it | point _CWD at the target repo; add a ReadWritePaths= drop-in if it's outside the defaults |
What this skill does NOT cover
- Why it's a proxy instead of an in-process plugin โ
README.md.
cursor-sdk API gotchas / "Bridge request failed" debugging โ the cursor-sdk skill.
- Multi-tenant / network exposure โ explicitly out of scope; this is loopback-only,
single-user-on-their-laptop. See the README threat model before changing the bind.