You are working in a Bespok3d plugin repo. Bespok3d is a printer-agnostic plugin manager for Klipper
printers that runs on stock firmware, with no custom-firmware flashing. This repo publishes one or more
plugins as signed .b3 packages that the Bespok3d desktop app installs onto a printer through the
on-printer daemon. This file is the contract for any LLM or agent that edits this repo. Contributors
here often work with AI assistance, so the rules and the design intent are written down and enforced in
the gate, not left implicit. The human reviewer rejects a PR that ignores them.
If you are a non-Claude tool, AGENTS.md points you here.
This repo is a co-repo of small Bespok3d plugins that exist as the reference implementation for shipping Python dependencies in a plugin without ever running pip on the printer. It ships two plugins, each carrying Python:
status-feed: an own-service venv plugin that ships arequirements.txt; the daemon provisions a per-plugin venv and installs the baked wheels offline.print-time-human: a Klipper extra that ships aklipper_requirements.txt; the daemon symlinks the baked packages into system site-packages so the extra can import them.
Read README.md for the repo's layout, build, and release mechanics before you change anything.
A Bespok3d plugin is declarative. Each plugin's manifest.json declares WHAT the printer should end up
with (files placed at a destination class, plus a restart hook), never a path, a raw shell command,
or a setup script that runs on the printer. The on-printer daemon reads the manifest and realizes it: it
templates and places the files, wires the symlinks, and restarts the named service.
- No plugin scripts. Do not add a shell script, a
postinstall, or any code meant to run on the printer to do setup. If the daemon cannot express what a plugin needs declaratively, that is a daemon or adapter change, not a script smuggled into a plugin. - Plugin isolation. A plugin owns its own
/userdata/bespok3d/<plugin>directory and integrates by symlink. Teardown removes the plugin's own files and leaves the user's data intact. - The printer is never left broken. Every change keeps the printer usable. The daemon's auto-deactivate safety net peels off a plugin that breaks Klipper or Moonraker; do not defeat it.
manifest.jsonis the release contract. Bump itsversionto cut a release. Do not hand-editindex.json, the.atom.json,index.json.sig, or anything underdist/: those are generated and signed by theb3-builderCI Action.
- RULE ZERO: no em-dash or en-dash, anywhere (code, comments, docs, commit messages). Use a comma, colon, semicolon, parentheses, or two sentences. A hyphen in a compound word is fine. The gate's em-dash guard fails the build on a violation.
- Every identifier carries domain meaning. A name says what the thing is in the domain, never its
type, its position, or a role-free abbreviation. No
a/b,tmp,data, single letters. - Nesting beyond one level is suspicious. Flatten by default: guard clauses, early returns, an extracted named function, a named lookup instead of a nested ternary.
- Rule of three. The third copy of a block, shape, or constant gets extracted. Duplication is a bug; "no premature abstraction" forbids generalizing for one caller, it does not excuse copy-paste.
- Extend upstream minimally and additively. Several plugins repackage or patch upstream source (Klipper, Moonraker, a web UI). Never delete or rewrite an upstream method; add alongside it, so the change survives a re-vendor of the upstream code.
- Never commit a real secret or a real LAN value. Tokens, keys, real IP addresses, and real UUIDs stay out of the tree. Fixtures are obviously fake.
- Understand first. Read the plugin's
manifest.json, itsfiles/, and itsdoc/README.md. Do not invent structure; if the intent is unclear, ask one specific question and stop. - Scope it to a user story. "As a [role], I want [capability] so that [value]." Implement only what the story needs: no speculative features, no defensive code for cases that cannot happen.
- Write the change to the rules above.
- Run the gate and make it green:
bash scripts/check.sh. The gate needs thelib_bespok3dsubmodule; if you cloned without it, rungit submodule sync --recursive && git submodule update --init --recursivefirst (CONTRIBUTING.md has the whole setup). This repo ships config, patches, assets, and shell, so the gate runs the shared workspace detectors: workflow-pinning, the em-dash guard, and shellcheck. It also runs ruff, mypy, and pytest over the plugin's Python (seescripts/check.shfor the exact targets). - On a gate failure, fix the cause. Never hand-wave a real smell away. If a detector is genuinely
wrong about a line, the fix is a per-instance justified allow at the smell
(
# gate-allow <metric>: <reason>, with a reason that survives "why is THIS one ok?"), never a blanket mute to make a number go down. - Add a regression test where the repo has a Python test layer for the behavior, in the same change: it fails on the old behavior and passes on the fix.
- Keep the docs current. If the change alters what a plugin does or how it is configured, update
that plugin's
doc/README.mdand itsdoc/CHANGELOG.md.
- Never run git. The maintainer commits. Leave the tree green and hand over exact commands if a git action is needed.
- Never SSH-mutate or reconfigure a live printer without explicit per-action authorization. A serial port or GPIO on a printer may be a live Klipper MCU link; read-only diagnosis is fine, but propose any device-changing step and wait for a yes.
- The gate must be green before a change is considered done.
Ask one specific question and stop. Do not guess and implement, and do not "try something reasonable." The architecture is the maintainer's; your job is to implement it to the rules above.