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
269 changes: 176 additions & 93 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,142 +1,225 @@
# AGENTS.md — handbook for coding agents (and humans who like clear docs)
# AGENTS.md — handbook for AI coding agents

> If you're an AI coding agent (Claude Code, Cursor, Codex, GPT-CLI, Aider,
> etc.) dispatched into this repo with a task, **read this file first.** It
> tells you how to set up, what's already wired, how to drive the printer
> from terminal, and how to ship a PR that won't get rejected.
>
> Humans benefit too — this is just the no-fluff version of the README.

## What this repo is, in one paragraph

`bambu-ai` is a small Python package that turns a Bambu Lab A1 mini 3D
printer into something an AI coding agent can drive from terminal. It ships
three console scripts (`bambu`, `bambu-watch`, `bambu-poll`) plus a vision
subsystem. It talks directly to the printer's MQTT broker (port 8883, TLS)
and camera — **100% LAN, no Bambu cloud, no Home Assistant.** The whole
point is that an agent with shell access can ask the printer questions and
issue commands using a tiny, predictable surface area.

## You are the target user of this package

This repo was designed under the working assumption that an LLM agent will
be reading it, writing code against it, and driving the resulting CLI on
behalf of a human. That shapes a few choices you should know:

- **Every subcommand has `--help`** — discoverable without external docs.
- **`bambu vision classify --json`** emits machine-parseable verdicts.
More `--json` flags landing as needed.
- **Exit codes are meaningful.** Non-zero on real failures, zero on success
(including `state=IDLE` — that's a state, not an error).
- **The pre-commit hook is strict** on secrets so you can't accidentally
push the user's printer Access Code in a paste.
- **The test suite mocks the printer** — you can run `make test` from a
clean clone without a real printer connected.

## Setup in 5 commands

If you're a coding agent (Claude Code, Cursor, Codex, etc.) dispatched into
this repo with a task, **this file is your first read.** It tells you how to
set up, how to test, where to look, and what not to do. Humans benefit too.
```sh
git clone https://github.com/abe238/bambu-ai && cd bambu-ai
make setup # creates .venv, installs package + dev deps, pre-commit hook
cp .env.example .env # placeholders; the user will fill in real values
make test # 51 unit tests pass without a printer (everything mocked)
make help # shows every other Makefile target
```

If the user has already populated `.env` with real printer values, you can
immediately drive their printer: `bambu status`.

## What this project is, in one paragraph
## Driving the printer from a chat session

`bambu-ai` is a LAN-only CLI/toolkit for Bambu Lab A1 / A1 mini 3D printers.
It talks MQTT (paho-mqtt) and FTP+camera (`bambulabs_api`) directly to the
printer on port 8883 with no cloud dependency, no Home Assistant required,
and no Bambu account needed. Three console scripts ship today: `bambu`
(control), `bambu-watch` (notifications daemon), `bambu-poll` (live state
tail). New features are driven by **real owner pain points** triaged from
r/BambuLab, forum.bambulab.com, ha-bambulab, and HN — the active roadmap
lives in [GitHub Issues](https://github.com/abe238/bambu-ai/issues).
Once `.env` is populated, here's the surface an agent can call. Recipe-style;
copy lines directly into your shell tool.

## Setup, in five commands
### Read-only

```sh
git clone https://github.com/abe238/bambu-ai && cd bambu-ai
make setup # creates .venv, installs package + dev deps, installs pre-commit hook
cp .env.example .env
$EDITOR .env # fill in IP / ACCESS_CODE / SERIAL / NTFY_TOPIC
make test # all unit tests pass without a real printer (everything mocked)
bambu status # one-shot snapshot
bambu-poll # live state tail (Ctrl-C; or pipe to head -N)
bambu vision classify <frame.jpg> --provider mock --dry-run # plumbing smoke test
```

### Control

```sh
bambu pause
bambu resume
bambu cancel --yes # requires --yes (refuses without)
bambu home
bambu light on
bambu light off
```

### Vision (AI failure detection)

```sh
bambu vision watch # long-running daemon
bambu vision watch --provider mock --dry-run # safe rehearsal — no API calls, no pauses
bambu vision watch --confidence 0.8 --max-cost 0.50 # tighter thresholds + cost cap
bambu vision classify <frame> --json # parseable verdict
```

If you don't have a real printer connected, `make test` still passes — the
unit suite mocks the printer and the Claude API. To exercise real
integrations, see the test tiers below.

## Repo layout

| Path | What lives here |
|---|---|
| `src/bambu_ai/` | The package. One module per CLI: `cli.py`, `watcher.py`, `poller.py`. Shared helpers in `config.py`, `notify.py`. |
| `tests/` | pytest suite, mirroring `src/bambu_ai/`. |
| `docs/` | Long-form per-feature docs. |
| `examples/` | Copy-pasteable scripts. |
| `completions/` | zsh / bash tab completion. |
| `install.sh` | Idempotent installer. Used by `make setup`. |
| `Makefile` | Standard task runner — **start here.** `make help` lists targets. |
| `pyproject.toml` | Package definition, entry points, ruff + pytest config. |
| `.gitleaks.toml` | Custom secret patterns. `make secrets` to run. |
| `.pre-commit-config.yaml` | The pre-commit hooks (gitleaks + ruff + sanity checks). |
| `.github/workflows/ci.yml` | Lint + test + gitleaks on every PR. |
| `LICENSE` / `NOTICE` / `SECURITY.md` | Apache-2.0 + attribution + secret-handling policy. |
### Notifications

```sh
bambu-watch # long-running ntfy.sh daemon
```

### Composing for autonomous monitoring

```sh
# Run vision watcher in background, log to file, surface only the alerts:
nohup bambu vision watch > /tmp/vision.log 2>&1 &
tail -F /tmp/vision.log | grep -E "(\[ALERT\]|verdict=)" &

# Or: wait for FINISH and then do something:
bambu-poll | awk '/state=FINISH/ {print; exit}' && bambu light off
```

The CLI is intentionally tiny + composable. If you need to do something
non-trivial (queue prints, classify a folder, etc.) build it as a small
Python script that imports `bambu_ai.*` — see `examples/`.

## TDD is mandatory

Every feature lands with tests first. Three test tiers, declared as pytest
markers in `pyproject.toml`:
Every new feature lands with tests **first**. Three tiers (declared as
markers in `pyproject.toml`):

| Marker | What it tests | Runs in CI? |
| Marker | Tests | Runs in CI? |
|---|---|---|
| (nonedefault) | Pure unit tests. All external deps mocked (printer, Claude API). | **Yes** — every PR. |
| `@pytest.mark.integration` | Hits the real Claude API. Requires `ANTHROPIC_API_KEY`. | No (gated). Run with `make test-integration`. |
| `@pytest.mark.real` | Hits a real Bambu printer over LAN. Requires `.env` with valid values. | **Never.** Run with `make test-real`. |
| (none, default) | Pure unit tests. Printer and Claude API are mocked. | **Yes** — every PR. Must stay fast (< 30s total). |
| `@pytest.mark.integration` | Hits the real Claude API. Requires `ANTHROPIC_API_KEY`. | No (gated). `make test-integration`. |
| `@pytest.mark.real` | Hits a real Bambu printer over LAN. Requires populated `.env`. | **Never** in CI. `make test-real`. |

When you implement a feature:

1. Write the failing default-marker test first.
2. Make it pass with the simplest possible code.
3. Add an integration test if your code calls a real API. Mark `integration`.
4. Add a real-printer test for happy-path verification. Mark `real`.
1. Write the failing default-marker test in `tests/<area>/test_<name>.py`.
2. Implement the smallest thing that passes it.
3. Add an integration test if you call a real external API (mark `integration`).
4. Add a real-printer test for happy-path verification (mark `real`).
5. Run `make check` (lint + test + secrets).
6. Open PR linked to its issue. Self-merge after CI is green (this is a solo repo for now).

## Conventions

- **Python 3.10+.** No 3.9 compatibility shims.
- **Ruff** for lint + format (configured in `pyproject.toml`).
- **Python 3.10+.** No 3.9 shims.
- **Ruff** for lint + format. Pinned to v0.15.14 in both pre-commit and CI.
- **Type hints** on every public function. `from __future__ import annotations` at the top of every module.
- **No license headers per file** — Apache 2.0 with a single root `LICENSE` is sufficient.
- **No `Co-Authored-By: Claude` trailers** on commits (the maintainer doesn't want them).
- **Don't add features beyond what the issue asks for.** Three similar lines beats a premature abstraction. Don't refactor adjacent code unless the issue says to.
- **No license headers per file** — root `LICENSE` is sufficient (Apache 2.0).
- **No `Co-Authored-By: Claude` trailers** on commits. The maintainer doesn't want them.
- **Minimal commits.** Don't refactor adjacent unrelated code. Three similar lines beats a premature abstraction.

## How to add a new CLI subcommand

1. Open or claim a GH issue.
1. Open or claim a GitHub issue. Comment that you're working on it.
2. Branch: `feature/<short-name>`.
3. Write a failing test in `tests/test_<name>.py` that exercises the subcommand via `argparse` directly (mock the printer).
4. Add to `src/bambu_ai/cli.py`:
- one `_cmd_<name>(p, args)` function, decorated with `@_with_printer`
- register in `main()` via `sub.add_parser("<name>")`
5. Update `completions/_bambu` and `completions/bambu.bash` so tab completion knows about the new subcommand.
6. `make check`. Open PR linked to the issue.

## How to add a new VisionProvider (after #20 lands)

`src/bambu_ai/vision/` will hold the abstract base + provider implementations:

1. Subclass `VisionProvider` with `classify(frame: PIL.Image.Image) -> Verdict`.
2. Add unit tests under `tests/vision/test_<name>_provider.py` using stub frames from `tests/fixtures/`.
3. Register the provider in the `--provider` CLI flag.
4. Document the trade-offs (cost, latency, accuracy) in `docs/vision-providers.md`.

The cloud (Claude) and local (MLX-VLM) providers will share the same
interface — your provider should too.
3. Write a failing test in `tests/test_<name>.py` exercising the subcommand
via `argparse` directly (mock the printer).
4. Add the implementation to `src/bambu_ai/cli.py`:
- One `_cmd_<name>(p, args)` function, decorated with `@_with_printer`.
- Register in `main()` via `sub.add_parser("<name>")`.
5. Update **both** `completions/_bambu` and `completions/bambu.bash` so tab
completion knows about the new subcommand.
6. Update README's "What you can do today" section if the command is
user-facing.
7. `make check` → push → PR → self-merge once CI is green.

## How to add a new VisionProvider

1. Subclass `bambu_ai.vision.VisionProvider`.
Implement `classify(frame: PIL.Image.Image) -> Verdict`.
2. Add unit tests under `tests/vision/test_<name>_provider.py` using stub
frames + injected stub clients. The Claude provider is the template.
3. Register the provider in the `--provider` choices in `src/bambu_ai/vision/cli.py`.
4. Document trade-offs (cost, latency, accuracy, offline-capable?) in
`docs/vision-providers.md` (create if missing).

The cloud (Claude) and a future local (MLX-VLM) provider share the same
interface. Your provider should too.

## How to find what to work on

```sh
gh issue list --state open --sort created --limit 30
gh issue view <N> # full details
gh issue view <N> # full DoD lives in each issue body
```

Roadmap themes (read titles for tier prefix):
Roadmap themes read the title prefix for tier:

- **T1** (high frequency × high tractability) — issues #9–#12, #20
- **T2** (frequent, multi-step) — issues #3, #4, #7, #13, #14
- **T3** (specific bug-class) — issues #15, #16
- **Tier 1** (high-frequency × high-tractability): notify-on-temp (#9), scheduled prints (#10), quiet hours (#11), auto-off (#12)
- **Tier 2** (frequent, multi-step): upload+start (#3), camera capture (#4), calibration tracking (#7), print queue (#13), heartbeat alarm (#14)
- **Tier 3** (specific bug-class): timelapse stitching (#15), `bambu doctor` (#16)

If you're unsure where to start, pick the lowest-numbered open issue with
no `in-progress` label. Comment on the issue claiming it before you branch.
If unsure, pick the lowest-numbered open issue with no `in-progress` label,
comment "claiming" on it, and branch.

## Pre-commit hook — what it blocks
## The pre-commit hook — what it blocks

`make setup` installs the pre-commit hook. It runs on `git commit`:
`make setup` installs the hook. Runs on every `git commit`:

- **gitleaks** with custom patterns (`.gitleaks.toml`): Bambu access code, printer serial, NTFY topic, private LAN IP in `IP=…` assignments, plus all upstream-default rules (sk-ant-, AWS, GitHub tokens, etc.).
- **ruff** lint + format (auto-fixes; re-stage and re-commit).
- **trailing whitespace, EOF newlines, YAML/TOML syntax, merge-conflict markers, private keys, large files.**
- **gitleaks** with custom patterns (`.gitleaks.toml`):
- Bambu access code (8-digit `ACCESS_CODE=…` assignment)
- Bambu printer serial (15-char alphanumeric format)
- `NTFY_TOPIC=…` (>12-char value)
- Private LAN IP in `IP=…` assignments
- Plus all gitleaks default rules (sk-ant-, AWS, GitHub tokens, etc.)
- **ruff** lint + format (auto-fix; re-stage and re-commit).
- **Standard sanity**: trailing whitespace, EOF newlines, YAML/TOML syntax, merge-conflict markers, private keys, large files.

If the hook fires for a value you want to allow:
1. First, **verify the value is actually safe to commit** (most of the time it's not).
2. If it really is a placeholder, add it to `.gitleaks.toml`'s `[allowlist].regexes` with a comment explaining why.
3. Don't bypass the hook with `--no-verify`.
If a hook fires for a value that's genuinely safe to commit:

1. First, verify it actually is safe (90% of the time it isn't).
2. If yes, add the value to `.gitleaks.toml`'s `[allowlist].regexes` with a
comment explaining why.
3. **Don't use `--no-verify`.** Fix the cause.

## Don't

- Don't talk to Bambu's cloud or use Bambu Connect. The whole point of this repo is **LAN-only**.
- Don't talk to Bambu's cloud or use Bambu Connect. The whole point is LAN-only.
- Don't require a Bambu account for any feature.
- Don't add paid-SDK dependencies without making them optional via `[project.optional-dependencies]`.
- Don't commit real `.env` values, real chamber-camera frames that show the maintainer's home, or real NTFY topic strings.
- Don't commit real `.env` values, real chamber-camera frames of the maintainer's home, or real NTFY topic strings.
- Don't append `Co-Authored-By: Claude` (or any AI co-author trailer) to commit messages.
- Don't bypass pre-commit with `--no-verify`. If a hook fires, fix the cause.
- Don't reformat unrelated code in a feature PR. Keep diffs minimal.
- Don't add a feature beyond what the issue explicitly asks for.

## When something looks wrong

- **Tests fail on a clean clone?** File an issue with the failure trace. Don't `chmod -R` your way out.
- **Pre-commit blocks a legitimate value?** Add an allowlist entry with a comment, or rethink whether the value really belongs in the repo.
- **Pre-commit blocks a legitimate value?** Add an allowlist entry with a comment, or rethink whether the value belongs in the repo at all.
- **Firmware update broke a feature?** Document in the relevant issue + add a row to `docs/firmware-compat.md` (when it exists, see #16).
- **You're three commits deep and still confused?** Stop, comment on the issue with what you've tried, and let a human pick it up.
- **You're three commits deep and still confused?** Stop, comment on the issue with what you've tried, and let a human or another agent pick it up.

## A note on autonomy

This repo is being built autonomously by a coding agent (Claude Code) and a
human maintainer working in tandem. The patterns that make that work:

1. **Every PR is independently verifiable.** Tests + CI + a self-contained DoD.
2. **GitHub Issues are the single source of truth** for "what's done / what's next."
3. **The pre-commit + CI gates prevent regressions** even when nobody's watching.
4. **`AGENTS.md` (this file) is dense enough** that a fresh agent session can read it cold and contribute.

If you're picking this up from a context reset or a different machine,
those four invariants are how you orient yourself. Good luck. Ship things.
Loading
Loading