Cursor is one IDE supported by the unified tiny-engineer CLI. Project hooks drive poses from Cursor agent events. Config: .cursor/hooks.json. Install / overview: integration.md (npx -y tiny-engineer setup cursor).
Published package: tiny-engineer on npm. Hook command: npx -y tiny-engineer hook cursor.
- Flash firmware and join the board to Wi-Fi so
http://tiny-engineer.localresolves. - Open this repo in Cursor (hooks run from the project root).
- Enable Hooks in Cursor settings if they are off.
- Use Node.js 18+.
Hooks in this repo call the local unified CLI via a portable wrapper (so CLI changes apply without waiting on npm, and without putting a machine-specific Node path in git):
.cursor/hooks/tiny-engineer-cursor.sh
Cursor’s hook runner often has a thin PATH that omits nvm. Bare node … then fails with exit 127 / command not found: node while other hooks (e.g. log-event.sh) still succeed. The wrapper sources nvm when needed, then falls back to common Homebrew/Volta locations, and execs packages/tiny-engineer-cli/bin/tiny-engineer.js hook cursor.
Optional smoke test (robot should ring):
echo '{"hook_event_name":"stop"}' | .cursor/hooks/tiny-engineer-cursor.sh
echo '{"hook_event_name":"stop"}' | node packages/tiny-engineer-cli/bin/tiny-engineer.js hook cursor
node packages/tiny-engineer-cli/bin/tiny-engineer.js --helpOr run the wizard with an explicit local command:
node packages/tiny-engineer-cli/bin/tiny-engineer.js setup cursor --yes \
--command 'node packages/tiny-engineer-cli/bin/tiny-engineer.js'Cursor reloads .cursor/hooks.json on save. If a hook never fires or the robot stays still while event logging works, restart Cursor and check the Hooks output channel for exit 127 / command not found: node.
Robot on Wi-Fi + Node 18+. Easiest:
npx -y tiny-engineer setup cursor --yesOr hand-edit that project’s .cursor/hooks.json. Use this command for every anim hook:
npx -y tiny-engineer hook cursor
Use timeout ≥ 30 (cold npx can exceed 2s).
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"beforeSubmitPrompt": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"afterAgentThought": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"preCompact": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"preToolUse": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"beforeReadFile": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"beforeShellExecution": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"subagentStart": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"afterFileEdit": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
],
"stop": [
{
"command": "npx -y tiny-engineer hook cursor",
"timeout": 30
}
]
}
}Same command every time — no animation args. Cursor pipes event JSON on stdin; the CLI picks the pose.
Smoke test / help:
npx -y tiny-engineer --help
echo '{"hook_event_name":"stop"}' | npx -y tiny-engineer hook cursorOptional robot URL:
npx -y tiny-engineer hook cursor --url http://192.168.1.10-y skips the install prompt. First run downloads from the npm registry; later runs use the npx cache.
That shorthand makes npm fetch via SSH (git@github.com:…). Cursor hooks often have no SSH agent → exit 128, silent fail. Use the published package: npx -y tiny-engineer.
| Cursor event | Animation | When |
|---|---|---|
sessionStart |
reading |
Cursor session starts |
beforeSubmitPrompt |
reading |
You submit a prompt |
afterAgentThought |
thinking |
Agent finishes a thinking block |
preCompact |
thinking |
Context is about to compact |
preToolUse (Read) |
reading |
Agent is about to read a file |
preToolUse (edit/search/shell tools) |
typing |
Agent is about to write, shell, search, task, etc. |
beforeReadFile |
reading |
File is about to be read |
beforeShellExecution |
typing |
Shell command is about to run |
subagentStart |
typing |
Subagent starts |
afterFileEdit |
typing |
Agent finished an edit |
stop (status: completed or missing) |
ring |
Agent turn completes |
stop (status: aborted) |
abort |
Agent turn cancelled |
stop (status: error) |
error |
Agent turn failed |
Each anim hook runs the same command with no animation args. Cursor pipes event JSON on stdin; the CLI reads hook_event_name (plus tool_name for preToolUse, status for stop), maps to a pose in packages/tiny-engineer-cli/src/integrations/cursor/map.js, and POSTs /anim?name=… (2s HTTP timeout, exit 0). Optional --url overrides the default http://tiny-engineer.local. Events whose conversation_id or generation_id is "" are skipped (no POST). Missing IDs still map.
If the robot has an access_token set, put the same value in TINY_ENGINEER_TOKEN (system/process env, or project-root .env). The CLI then sends Authorization: Bearer <token>. Without a token, no auth header is sent.
Config: .cursor/hooks.json. Local anim hook: .cursor/hooks/tiny-engineer-cursor.sh. Event logging (separate): .cursor/hooks/log-event.sh.
- Robot offline → hook still exits 0; no agent stall.
- Animation API holds each pose ≥1s and keeps only the latest pending switch — see
api.md. - The onboard RGB LED follows the active animation (white for typing/reading/thinking/talking/welcome/ring/wakeup, pulsing red for attention/error/dead, solid red for abort, off for
none/sleep) with 1 s fades between non-pulse states — see RGB LED. - To change the map, edit
packages/tiny-engineer-cli/src/integrations/cursor/map.js. - Custom agents or non-Cursor IDEs: call the REST API.