Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PostToolBatch": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"PermissionDenied": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
],
"StopFailure": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js\"",
"async": true
}
]
}
]
}
}
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ jobs:
run: npm test --prefix packages/tiny-engineer-cursor
- name: Antigravity package tests
run: npm test --prefix packages/tiny-engineer-antigravity
- name: Claude Code package tests
run: npm test --prefix packages/tiny-engineer-claude-code
59 changes: 55 additions & 4 deletions docs/integration.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Integrating Tiny Engineer

Tiny Engineer is a Wi-Fi desk robot. Drive it from any tool that can make HTTP requests, or use the Cursor-specific helper that maps agent hook events to poses.
Tiny Engineer is a Wi-Fi desk robot. Drive it from any tool that can make HTTP requests, or use one of the dedicated helpers (Cursor, Antigravity, Claude Code) that map agent hook events to poses.

Robot must be on the same network. Base URL: `http://tiny-engineer.local` (or the IP shown on the OLED). Full HTTP reference: [`api.md`](api.md).

Three integration paths:
Four integration paths:

| Path | Best for | How |
|---|---|---|
| **REST API** | Any AI IDE, script, CI, custom agent | `POST /anim?name=…` |
| **Cursor CLI** | Cursor project hooks | `npx` → `tiny-engineer-cursor` |
| **Antigravity CLI** | Antigravity CLI lifecycle hooks | `tiny-engineer-antigravity` |
| **Claude Code CLI** | Claude Code project hooks | `tiny-engineer-claude-code` |

```mermaid
flowchart TB
Expand All @@ -28,6 +29,11 @@ flowchart TB
AgHook --> AgCli["tiny-engineer-antigravity"]
AgCli --> Post
end
subgraph claudeCodePath [Claude Code]
CcHook[Claude Code settings.json]
CcHook --> CcCli["tiny-engineer-claude-code"]
CcCli --> Post
end
Post --> Robot[Tiny Engineer on Wi-Fi]
```

Expand Down Expand Up @@ -197,17 +203,62 @@ To run globally across all projects on your machine, configure `~/.gemini/config

---

## 4. Claude Code dedicated script

For [Claude Code](https://code.claude.com/): a small Node CLI reads Claude Code hook JSON on stdin, picks an animation, and POSTs `/anim`. Same shape as the Cursor CLI — one command for every hook, no animation args.

### Event Mapping
- **`SessionStart`** (`startup`, a fresh launch) → `welcome`
- **`SessionStart`** (`resume`, `clear`, `compact`, `fork`, or no `source`) → `wakeup`
- **`SessionEnd`** → `sleep` (skipped for `clear` and `resume`, where the next `SessionStart` wakes the robot)
- **`UserPromptSubmit`** → `reading`
- **`PreToolUse`** (Reading tools: `Read`, `Grep`, `Glob`, `WebFetch`, `WebSearch`) → `reading`
- **`PreToolUse`** (Writing tools: `Bash`, `PowerShell`, `Edit`, `Write`, `NotebookEdit`) → `typing`
- **`SubagentStart`** → `thinking`
- **`PostToolBatch`** (tool results go back to the model) → `thinking`
- **`PostToolUseFailure`** (a tool call failed) → `error`
- **`PreCompact`** → `thinking`
- **`PermissionRequest`** → `attention`
- **`PermissionDenied`** (auto mode denied a tool call) → `abort`
- **`Notification`** (permission prompt, or waiting for input) → `attention`
- **`Stop`** (turn finished) → `ring` (rings the physical desk bell!)
- **`StopFailure`** → `error`

To change the map, edit [`packages/tiny-engineer-claude-code/src/map.js`](../packages/tiny-engineer-claude-code/src/map.js).

### Setup

Inside this firmware repository, [`.claude/settings.json`](../.claude/settings.json) is pre-configured: open the repo in Claude Code (Node.js 18+) and the hooks run `node packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js`.

- Every hook is `"async": true`, so Claude Code runs it in the background — an offline robot never stalls a tool call on the 2s HTTP timeout.
- Robot address: `TINY_ENGINEER_URL` or `--url http://192.168.x.x` (default `http://tiny-engineer.local`).
- Auth: if the device has an `access_token`, set `TINY_ENGINEER_TOKEN` in the process env or a project-root `.env` file. The CLI sends `Authorization: Bearer …`. No token → no header (auth disabled on device). When the token comes from the process env, a `TINY_ENGINEER_URL` in the project `.env` is ignored, so a project can't redirect your token; set the URL in the env or with `--url`.
- The CLI never writes to stdout in hook mode — Claude Code adds `SessionStart` / `UserPromptSubmit` hook stdout to the model's context.
- If a hook never fires, run `/hooks` inside Claude Code to confirm the project settings loaded.

To use it in every project, copy the `hooks` block into `~/.claude/settings.json` and replace `$CLAUDE_PROJECT_DIR/packages/…` with the absolute path to this repo's bin.

### Smoke test

```bash
node packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js --help
echo '{"hook_event_name":"Stop"}' | node packages/tiny-engineer-claude-code/bin/tiny-engineer-claude-code.js
```

---

## Which to choose?

- **Building for one IDE / custom agent** → REST. One `POST`, zero Node dependency.
- **Using Cursor and want zero mapping code** → Cursor CLI + hooks.
- **Using Antigravity CLI** → Antigravity CLI + hooks.
- **All three** are fine together: the CLIs are thin clients of the same `/anim` API.
- **Using Claude Code** → Claude Code CLI + hooks.
- **All four** are fine together: the CLIs are thin clients of the same `/anim` API.

Prerequisites for any path: flash firmware, join 2.4 GHz Wi-Fi, confirm `http://tiny-engineer.local/health` (or the OLED IP) responds.

---

## Optional extras

Same `POST /anim` API; not part of the three paths above. On macOS you can run a small host helper that POSTs `sleep` / `wakeup` when the screen locks or unlocks: [`macos-lock-unlock.md`](macos-lock-unlock.md). The robot does not need it.
Same `POST /anim` API; not part of the four paths above. On macOS you can run a small host helper that POSTs `sleep` / `wakeup` when the screen locks or unlocks: [`macos-lock-unlock.md`](macos-lock-unlock.md). The robot does not need it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
import { run } from "../src/cli.js";

await run(process.argv.slice(2));
22 changes: 22 additions & 0 deletions packages/tiny-engineer-claude-code/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@tiny-engineer/claude-code",
"version": "0.1.0",
"type": "module",
"description": "Claude Code hooks CLI that drives Tiny Engineer robot animations over HTTP",
"main": "src/cli.js",
"bin": {
"tiny-engineer-claude-code": "./bin/tiny-engineer-claude-code.js"
},
"scripts": {
"test": "node --test"
},
"files": [
"bin",
"src"
],
"engines": {
"node": ">=18"
},
"keywords": ["claude-code", "robot", "tiny-engineer"],
"license": "MIT"
}
Loading
Loading