Metadata-Version: 2.5
Name: fomo-the-arm
Version: 0.1.0
Summary: Fomo: an SO-101 leader arm with a camera head, driven by five Strands @tools — calibrate, photo, move, pose, motion.
Project-URL: Homepage, https://fomo.cagatay.my
Project-URL: Repository, https://github.com/cagataycali/fomo-the-arm
Project-URL: Documentation, https://cagataycali.github.io/fomo-the-arm/
Author: Cagatay Cali
License: MIT
License-File: LICENSE
Keywords: agents,cameraman,digital-twin,feetech,mcp,nicla-vision,robot-arm,so-101,strands,strands-agents,sts3215
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Hardware
Requires-Python: >=3.11
Requires-Dist: pyserial>=3.5
Requires-Dist: strands-agents>=0.1.0
Provides-Extra: dashboard
Requires-Dist: fastapi>=0.110; extra == 'dashboard'
Requires-Dist: uvicorn>=0.29; extra == 'dashboard'
Requires-Dist: websockets>=12; extra == 'dashboard'
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: numpy>=1.26; extra == 'dev'
Requires-Dist: onnxruntime>=1.17; extra == 'dev'
Requires-Dist: pillow>=10; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: uvicorn>=0.29; extra == 'dev'
Requires-Dist: websockets>=12; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: rl
Requires-Dist: numpy>=1.26; extra == 'rl'
Requires-Dist: onnxruntime>=1.17; extra == 'rl'
Requires-Dist: pillow>=10; extra == 'rl'
Description-Content-Type: text/markdown

<h1 align="center">fomo</h1>

<p align="center"><b>An SO-101 leader arm that films.</b><br>
<sub>six servos · a camera on a printed pan/tilt head · five tools: <code>calibrate</code> <code>photo</code> <code>move</code> <code>pose</code> <code>motion</code></sub></p>

<p align="center">
  <a href="https://cagataycali.github.io/fomo-the-arm/">Site — the twin plays the nine motions</a> ·
  <a href="https://cagataycali.github.io/fomo-the-arm/install/">Install</a> ·
  <a href="https://cagataycali.github.io/fomo-the-arm/arm/verbs/">Five verbs</a> ·
  <a href="https://cagataycali.github.io/fomo-the-arm/arm/motions/">Motions</a> ·
  <a href="https://cagataycali.github.io/fomo-the-arm/arm/guard/">The guard</a> ·
  <a href="https://fomo.cagatay.my">Cockpit</a>
</p>

<p align="center">
  <img src="https://img.shields.io/github/actions/workflow/status/cagataycali/fomo-the-arm/ci.yml?label=ci&labelColor=111" alt="ci">
  <img src="https://img.shields.io/badge/python-%E2%89%A5%203.11-111?labelColor=111" alt="python ≥ 3.11">
  <img src="https://img.shields.io/badge/license-MIT-111?labelColor=111" alt="MIT">
</p>

Hi. I'm Fomo.

I'm the leader arm from an SO-101 kit — the one that goes back in the box after teleop. Someone
took my handle off, printed a pan/tilt yoke for an Arduino Nicla Vision, and screwed it to my
wrist. Now I'm a cameraman: six Feetech STS3215 servos on one bus, a camera that knows its own
pose, and an agent that drives me with exactly five verbs.

```python
from strands import Agent
from fomo.tools import calibrate, photo, move, pose, motion

fomo = Agent(tools=[calibrate, photo, move, pose, motion])
fomo("look 40 degrees left, take a photo, then show me you're happy")
```

## Install me — five steps

```sh
pip install "fomo-the-arm[dashboard]"     # tools · CLI · MCP · agent · dash API   (+[mcp], +[rl])
```

pip gives you the five tools, `fomo` and `fomo mcp`, the agent and the dash API. The cockpit's SPA
and the twin's models live in the repo, so for the full panel clone it:

```sh
git clone https://github.com/cagataycali/fomo-the-arm.git && cd fomo-the-arm
pip install -e ".[dashboard]"
fomo doctor                 # read-only: which port is me, six servos, calibration, camera
fomo calibrate --manual --hold 40 --i-know   # torque off, move me by hand, fold me before the timer ends
fomo dash                   # http://127.0.0.1:8091 — the cockpit: agent log, dock, twin, eyes
```

No arm? `FOMO_SIM=1 fomo dash` is six simulated servos ramping at my real speed — the
[site's twin](https://cagataycali.github.io/fomo-the-arm/) is one. The long form, with what each
step refuses and why, is [Install](https://cagataycali.github.io/fomo-the-arm/install/).

Set `FOMO_PORT` when `doctor` finds more than one `usbmodem` (on the bench this was written on it found
five: the servo adapter, a Nicla, a Sticky, a Flipper — two share the CH343 chip, so I refuse to guess).
Nothing moves until you say `--i-know` (you are watching me) or write `{"ok": true}` to `~/.fomo/bench.json`.

## My five verbs

| tool | what it does | honest limits |
|---|---|---|
| `calibrate(mode)` | `report` reads `~/.fomo/calib.json`; `manual` drops torque, samples every joint while you move me by hand (≥ 20 s), makes the fold my **home** and the extremes my **window**; `write_eeprom` burns MIN/MAX into servos 1–4; `--import` adopts a strands-arm calibration | I don't search for my own stops — on this arm the automatic search found the fold contact 30–45° short of the metal, the hand found 185–207° spans |
| `photo(source)` | one JPEG from the Nicla (LAN `/stream`, or USB raw-REPL), else the dashboard, else this machine's webcam; saved to `~/.fomo/photos/`, the source named | 320×240 from the Nicla; if every source fails I say so and name each |
| `move(actions)` | several servos, several actions, one guarded ramped command: `to`, `by`, `rel` (from home), `look {pan,tilt}`, `home`, `stop`, `torque`, `read` (present degrees + what the head feels: ToF, roll/pitch, bench or head) | any angle inside the measured window is one call; a stall holds, never forces |
| `pose(action, name)` | `list show save delete goto nudge`; five built-ins (`home attention look_around_left look_around_right up`); stored **relative to home** so a re-calibration shifts poses instead of breaking them | a pose is a place, not a path |
| `motion(action, name)` | `list show run save delete`; nine built-ins timed on the metal — `approve` (nod) `deny` (shake) `happy` `frustrated` `curious` `wave` `look_around` `up` `sleep`; keyframes from wherever I am, ending folded unless `stay` | every frame is checked against the hard limits before the first write |

Every tool returns the same `{"status", "content": [{"text"}]}` shape, dash-first (when the
dashboard owns the port) with a local-bus fallback. `fomo mcp` serves the same five over MCP.
[Verbs](https://cagataycali.github.io/fomo-the-arm/arm/verbs/) · [Motions](https://cagataycali.github.io/fomo-the-arm/arm/motions/) — the twin plays all nine.

## The guard — hard limits only

Every servo write passes `fomo.guard.Guard`. It **refuses** on six things, each named after a night
something went wrong on the real leader (2026-09-05 → 08), and **executes** everything inside them:

| refuses on | why |
|---|---|
| **1 Gate.** Nothing moves without `i_know` or `~/.fomo/bench.json` `{"ok": true}` | a tool call from a phone moved the head while the owner's hand was on it |
| **3 Head window.** Pan ±160°, tilt ±80° about home | the horns spin freely; the Nicla's USB cable does not |
| **4 Measured window.** Joints 1–4 stay inside what *your hand* found, not the EEPROM's | the base's factory window 111..251 wasted 45° on one side; the hand found 66.4 |
| **7 Upright.** `torque off` is refused until I'm folded | the elbow fell onto its stop at 04:05Z, 2026-09-07 |
| **8 Power.** No lifting away from the fold while the rail reads under 5 V; folding is always allowed | the 5.5 V leader supply could not lift the forearm |
| **9 Stall.** 6° behind a streamed goal and not moving → every joint is re-goaled to where it *is*; a hold, not a refusal | the wrist was driven into the forearm for 2 s, the rail sagging to 5.0 V |

How far a command reaches is never a refusal: inside the limits a target is cut into ≤ 10° legs, goals
streamed from the host at 30 °/s (ceiling 90) so `stop` lands mid-leg, torque drops 2 s after the last
command — only when folded. [The guard](https://cagataycali.github.io/fomo-the-arm/arm/guard/): every number on that page is read from
`fomo/guard.py` and `~/.fomo/calib.json` at build time, never typed.

## Cockpit — fomo.cagatay.my

One screen; the agent is the spine. The log is the narrative — your prompts, my streamed answers,
every tool call as a row in words (`read · joints 0.1 s`, `motion · happy`), a photo inline — and
the eyes (live MJPEG) and the twin (following the servos at 5 Hz) are my face. **STOP** is always in
the bar. Captured 2026-09-09 on the live arm:

![the cockpit: live eyes and twin on the left, the log with a read · joints tool row and the answer on the right](https://raw.githubusercontent.com/cagataycali/fomo-the-arm/main/docs/img/panel/desk-turn.jpg)

Press `d` for the dock — one slider per joint on its *measured* window, look pad, every pose, every
motion, torque, a speed slider capped at the guard's 90 °/s. Nothing in it bypasses the guard, and
manual moves land in the same log so the agent's next turn sees them.

![the dock: sliders on the measured windows, poses, motions, STOP](https://raw.githubusercontent.com/cagataycali/fomo-the-arm/main/docs/img/panel/desk-dock.jpg)

Voice in and out are browser-native; a `think` toggle lets me look and say one line every so often
(off by default, never moving joints 1–4). Control routes need `FOMO_TOKEN` (or, with `FOMO_OWNER` set,
the owner's tiny.technology session); state, `/ws`, eyes and senses are read-only public.
`dashboard/deploy/install.sh` makes it a launchd agent and carries your model credentials
(`AWS_BEARER_TOKEN_BEDROCK` or `AWS_PROFILE`) into it; `cloudflared-fomo.yml` puts it on a name.
[Cockpit](https://cagataycali.github.io/fomo-the-arm/panel/cockpit/) · [Reference](https://cagataycali.github.io/fomo-the-arm/panel/reference/) — every route.

## Where I come from

I'm the fresh codebase of [strands-arm](https://github.com/cagataycali/strands-arm) — the idea
kept, the 14k lines cut to what a cameraman needs. Everything I learnt there — the
measured servo ranges, every incident behind the guard, the nights in `archive/lanes/` — is written
down where it was learnt: the guard page carries the incidents, the install page the hand-measured windows.

## What I won't pretend to do

- Drive a base. That's strands-arm's three-wheel body; I'm the arm on the bench.
- Run an RL policy live — not yet. Two ONNX policies ship and run in shadow (`fomo rl shadow`); stage 1 (head only) is built and stays behind a phrase the owner writes by hand in the journal.
- Lift on 5 V. The leader kit's supply reads 5.6 V on my bus; the elbow does not lift the forearm from the fold on it (rule 9 holds it). The 7.4 V supply does.
- See obstacles. Rule 1 exists because a human must.
- Take a photo I didn't take. If every camera source fails, `photo` says so and names each.

## License

MIT, Cagatay Cali. Built by cagatay and his agents at strands robots.
