Metadata-Version: 2.4
Name: arm101-cli
Version: 0.22.1
Summary: Agent and CLI for controlling SO-ARM101 robotic arm grippers
Project-URL: Homepage, https://github.com/agentculture/arm101-cli
Project-URL: Issues, https://github.com/agentculture/arm101-cli/issues
Author: AgentCulture
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Requires-Python: >=3.12
Provides-Extra: mac
Requires-Dist: pyserial>=3.5; extra == 'mac'
Provides-Extra: seeed
Requires-Dist: feetech-servo-sdk>=1.0; extra == 'seeed'
Provides-Extra: win
Requires-Dist: pyserial>=3.5; extra == 'win'
Description-Content-Type: text/markdown

# arm101-cli

Agent and CLI for controlling SO-ARM101 robotic arm grippers

## What you get

- **An agent-first CLI** cited from [teken](https://github.com/agentculture/teken)
  (`afi-cli`) — the runtime package has no third-party dependencies.
- **A mesh identity** — `culture.yaml` (`suffix` + `backend`) and the matching
  prompt file (`AGENTS.colleague.md` for this agent's `backend: colleague`).
- **The canonical guildmaster skill kit** (11 skills) under `.claude/skills/`,
  vendored cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md).
- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, and
  PyPI Trusted Publishing wired into GitHub Actions.

## Installation

Base install (zero third-party runtime dependencies — introspection only):

```bash
uv sync          # or: pip install arm101-cli
```

For real Feetech STS3215 motor I/O on the Seeed Studio SO-101 kit, add the
`[seeed]` extra (named by the kit provider; the CLI verifies each connected
motor really is a Feetech STS3215 at runtime):

```bash
uv sync --extra seeed          # or: pip install 'arm101-cli[seeed]'
```

This pulls in `feetech-servo-sdk` (import module `scservo_sdk`), which the bus
adapter lazy-imports. The base install stays zero-dep so the CLI and agent
introspection work on any machine without hardware attached.

## Quickstart

```bash
uv sync
uv run pytest -n auto                 # run the test suite
uv run arm101 whoami                 # identity from culture.yaml (console script is 'arm101')
uv run arm101 learn                  # self-teaching prompt (add --json)
uv run teken cli doctor . --strict    # the agent-first rubric gate CI runs
```

## CLI

| Verb | What it does |
|------|--------------|
| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
| `learn` | Print a structured self-teaching prompt. |
| `explain <path>` | Markdown docs for any noun/verb path. |
| `overview` | Read-only descriptive snapshot of the agent. |
| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
| `cli overview` | Describe the CLI surface itself. |
| `arm overview` / `arm read` / `arm flex` / `arm explore` / `arm setup <role>` | Arm-level operations on the SO-101 — see below. |

Every command supports `--json`. Results go to stdout, errors/diagnostics to
stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
error, `3+` reserved.

## Arm verbs

The `arm` noun group carries the SO-101 hardware surface: `arm overview`
(read-only spec snapshot), `arm read` (read-only live joint state), `arm flex`
(gated single-joint/demo motion), `arm explore` (gated reachability mapping —
documented below), and `arm setup <role>` (gated per-role motor bring-up).
`arm flex`/`arm explore`/`arm setup` are gated motion: a TTY prompts for
confirmation, a non-TTY run without `--apply` prints a dry-run plan (zero
motion, zero bus access), and non-TTY with `--apply` proceeds (agent mode).

### Torque safety: hold on success, release on abnormal

Every gated motion verb owns the motors it energizes and **releases them on any
abnormal exit** — an unhandled exception, a bus fault, or Ctrl-C. The arm is
never walked away from hot. The release attempts each motor independently and
survives its own failure, because the bus that just threw is the same bus the
release has to talk to.

A **clean** exit is different: torque is left exactly as the verb left it, so a
successful `gentle_move`'s deliberate stop-and-hold survives and a gripper that
has closed on an object does not drop it when the command returns. A powered arm
at process exit is therefore always a deliberate state, never an accident.

**Known limit.** This protects every failure where the process still *has* a
bus. It cannot protect against physical loss of the bus: the servos are powered
from the 12 V supply while USB carries only data, so unplugging the cable removes
the host's only channel to the arm — every release write fails and the motors
hold their last commanded goal. No software can release torque over a bus that no
longer exists. Whether the servo can safe *itself* on comms loss is tracked in
[#37](https://github.com/agentculture/arm101-cli/issues/37).

### `arm explore` — map the arm's reachable joint-space

Before this verb, nothing persisted where the arm can safely move: limits
were found by manually flexing one joint at a time and eyeballing contacts,
and that knowledge was lost the moment the session ended. `arm explore`
autonomously flood-fills the follower's (or leader's) joint-space at safe
speed — via the same overload-safe `gentle_move` primitive `arm flex
--gentle` uses — detects every self/environment contact from real load, and
writes a resumable, overridable reachability map.

```bash
uv run arm101 arm explore --apply                       # follower, defaults
uv run arm101 arm explore --role leader --map ./bench-a.map.json --apply
uv run arm101 arm explore --threshold 300 --max-moves 500 --apply
```

Flags: `--role {follower,leader}` (default `follower`), `--port` (default
auto-detect), `--map PATH` (resume input if it exists, and the write target;
default `./arm-explore-<role>.map.json`), `--threshold` (contact-load
threshold, default `250`), `--max-moves` (move/probe budget cap, default
`2000` — hardware-tuned open question), `--resolution` (per-joint grid
bucket size in encoder ticks, default `512` — hardware-tuned open question),
`--apply`, `--json`.

Every run writes **two artifacts**: an append-only JSONL event log (the
resumable source of truth — a killed run resumes instead of re-probing
already-mapped cells) and a derived, compact reachability map (per-joint
reachable ranges plus a sparse list of blocked joint-combinations). When a
joint is blocked, a bounded multi-joint escape search perturbs other joints
to find combinations that unblock it, rather than stopping at the first
single-joint contact. A bundled self-collision default map ships and loads
automatically when no user `--map` is present.

**v1 scope:** `arm explore` *produces and stores* the reachability map, and
the map is queryable offline straight from the file — but v1 does **not**
change `arm flex`'s behavior. Consuming the map to gate `arm flex` targets
(refuse/warn on a request outside the discovered envelope) is a documented
follow-up, not part of this verb.

## Make it your own

1. Rename the package `arm101/` and the `arm101-cli`
   CLI/dist name throughout `pyproject.toml`, the package, `tests/`,
   `sonar-project.properties`, and this `README.md`. The name is hard-coded in
   ~100 places, so list every occurrence first — see the `git grep` discovery
   command in [`CLAUDE.md`](CLAUDE.md), the authoritative rename procedure.
2. Edit `culture.yaml` with your `suffix` and `backend`.
3. Rewrite `CLAUDE.md` for your agent and run `/init`.
4. Re-vendor only the skills you need from guildmaster (see
   [`docs/skill-sources.md`](docs/skill-sources.md)).

See [`CLAUDE.md`](CLAUDE.md) for the full conventions (version-bump-every-PR,
the `cicd` PR lane, deploy setup).

## License

Apache 2.0 — see [`LICENSE`](LICENSE).
