Skip to main content
Glama
00011110

trayfeel

by 00011110
README.md
# TrayFeel

**Plug-and-play CD/DVD tray emotive actuator for AI agents.**

TrayFeel turns a Linux optical drive into a physical body-language channel: open, close, pulse, wiggle, and a catalog of named emotions — exposed as a Python library, CLI, MCP server, and optional HTTP daemon.

Linux-first. MIT licensed. Pure-Python ioctl path (no `eject` binary required).

## Requirements

- Python 3.10+
- Linux with `/dev/sr*` optical drive supporting `OPEN_TRAY` / `CLOSE_TRAY`
- User membership in the `cdrom` group (typical on Ubuntu/Debian)

```bash
sudo usermod -aG cdrom "$USER"
# log out and back in, then:
groups   # should list cdrom
```

## Install

```bash
pip install trayfeel
# or from a checkout:
pip install -e ".[dev]"
```

Optional env (see `.env.example`):

| Variable | Purpose |
|----------|---------|
| `TRAYFEEL_DEVICE` | Force device path (default: first `/dev/sr*`) |
| `TRAYFEEL_TOKEN` | Bearer token for HTTP daemon |
| `TRAYFEEL_CAMERA` | Optional `/dev/videoN` for vision feedback |
| `TRAYFEEL_HOST` / `TRAYFEEL_PORT` | Daemon bind (default `127.0.0.1:8765`) |

## Quick start

```bash
trayfeel list-drives
trayfeel doctor
trayfeel status
trayfeel pulse
trayfeel emote laugh
trayfeel emote nod_yes --dry-run
trayfeel catalog
```

Python:

```python
from trayfeel import Tray

with Tray() as tray:
    print(tray.status())
    tray.run("pulse")
    tray.emote("wave")
```

## Safety

TrayFeel refuses reckless motion by default:

- **Exclusive flock** per device (`~/.cache/trayfeel/…`)
- **Rate limit** (default 30 actions/minute)
- **Minimum interval** between motions (default 200 ms)
- **Disc guard** — slam patterns (`pulse`, `wiggle`, `nod`, …) refused while a disc is present unless `force=True`
- **Cooldown** after many open/close cycles

Override with `--force` / `force=True` when you intentionally accept the risk.

## Emotions

Named choreographies in `trayfeel/emotions/catalog.yaml`:

`laugh`, `giggle`, `gasp`, `surprise`, `shrug`, `think`, `ponder`, `nod_yes`, `shake_no`, `applause`, `sulk`, `pout`, `excited`, `bored`, `angry`, `curious`, `wave`, `heartbeat`, `sigh`, `celebrate`, `confused`, `acknowledge`, `reject`, `greet`, `farewell`, `error`, `success`, `idle_breath`

```bash
trayfeel emote celebrate
trayfeel emote think --dry-run
```

## Primitives

`open`, `close`, `toggle`, `lock`, `unlock`, `status`, `pulse`, `wiggle`, `hold_open`, `nod`, `double_tap`, `spin_up`, `spin_down`, `sleep`, `wait`

## MCP server

```bash
trayfeel mcp
# or: python -m trayfeel mcp
```

Tools: `tray_status`, `tray_list_drives`, `tray_primitive`, `tray_emote`, `tray_catalog`, `tray_doctor`.

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "trayfeel": {
      "command": "trayfeel",
      "args": ["mcp"]
    }
  }
}
```

### Cursor

Add an MCP server entry pointing at the same command (`trayfeel mcp` or `python -m trayfeel mcp`). Ensure the environment has `cdrom` group permissions.

Tool schemas for OpenAI / Claude function calling live in `examples/openai_tools.json` and `examples/claude_tools.json`. Agent skill notes: `examples/agent_skill.md`.

## HTTP daemon

For LAN agents that cannot attach stdio MCP:

```bash
export TRAYFEEL_TOKEN="$(openssl rand -hex 16)"
trayfeel daemon --host 0.0.0.0 --port 8765
```

| Method | Path | Body |
|--------|------|------|
| GET | `/health` | — |
| GET | `/status` | — |
| GET | `/catalog` | — |
| POST | `/primitive` | `{"name":"pulse","open_ms":300}` |
| POST | `/emote` | `{"name":"wave"}` |

Send `Authorization: Bearer $TRAYFEEL_TOKEN` when a token is configured.

## Vision (optional)

If `TRAYFEEL_CAMERA=/dev/video0` is set and `ffmpeg` is installed, `trayfeel vision` captures a frame via V4L2 and returns a best-effort brightness/edge heuristic. Missing camera or ffmpeg returns `ok: false` with a reason — it never crashes the process.

## Library layout

```
trayfeel/
  core.py          # Tray, Emoter
  primitives.py    # motion primitives
  safety.py        # flock, rate limits, disc guards
  hardware/linux.py
  emotions/        # catalog.yaml + engine
  mcp_server.py
  daemon.py
  cli.py
```

## License

MIT — Copyright (c) 2026 TrayFeel contributors