Skip to main content

Agent-facing control plane for CircuitPython boards

Project description

Patchcord

Patchcord is a command-line workflow for developing CircuitPython projects without a dedicated IDE. It keeps your application, library list, and hardware description in a normal Git repository, then gives you one CLI for deploying to a board, watching serial output, controlling execution, and validating the project's hardware description.

Patchcord currently supports CircuitPython boards that expose the standard CIRCUITPY USB drive and serial console.

Install

Patchcord requires:

  • Python 3.11 or newer;
  • uv 0.11.32 or newer; and
  • a board with CircuitPython already installed.

Install the latest stable version from PyPI:

uv tool install patchcord
patchcord --version

To try the unreleased development version instead:

uv tool install git+https://github.com/totocaster/patchcord.git

To run Patchcord from a source checkout instead:

git clone https://github.com/totocaster/patchcord.git
cd patchcord
uv sync --locked
uv run patchcord --help

The examples below assume patchcord is installed as a tool. From a source checkout, replace it with uv run patchcord.

Use patchcord --help or patchcord COMMAND --help to inspect the available commands and options.

Quick start

Connect a CircuitPython board, then initialize a project:

patchcord init my-board
cd my-board

Patchcord creates:

my-board/
├── AGENTS.md            # Patchcord and project workflow for coding agents
├── device/
│   └── code.py          # Application files deployed to CIRCUITPY
├── hardware.yaml        # Intended board, parts, nets, and interfaces
├── requirements.txt     # CircuitPython library bundle names
└── .gitignore

If discovery is unambiguous, patchcord init records the connected board ID in hardware.yaml. Otherwise, fill in board.id before deploying.

AGENTS.md teaches coding agents how to operate Patchcord, work safely with the initialized project, and use small Git checkpoints as a durable backup and restore history for hardware iterations. The board remains a deployment target; the Git-managed local project is the source of truth. As with other initialized project files, Patchcord preserves an existing AGENTS.md instead of overwriting it.

Project commands can be run from the project root or any directory below it; Patchcord finds the nearest project automatically. Runtime locks and logs are created under the Git-ignored .patchcord/ directory when needed.

Put the project under Git so the local device/ tree remains your recoverable source of truth:

git init
git add .
git commit -m "Initialize CircuitPython project"

Check the selected board and validate the project:

patchcord status
patchcord hardware validate --offline

Edit device/code.py, then deploy and watch its output:

patchcord deploy
patchcord monitor --seconds 10

That is the core Patchcord loop: edit locally, validate, deploy, and inspect the board's output.

The everyday development workflow

1. Edit locally

Keep every file that should appear on CIRCUITPY under device/. Patchcord copies regular files from this tree and creates their parent directories; it does not use the board as the source of truth.

device/
├── code.py
├── helpers.py
└── assets/
    └── config.json

2. Validate the project

Run offline validation before touching the board:

patchcord hardware validate --offline

This checks hardware.yaml, endpoint references, I²C declarations, and library cross-references against requirements.txt. The --offline flag guarantees that Patchcord will not connect to or interrupt hardware.

3. Deploy

patchcord deploy

Before copying, Patchcord requires:

  • a valid hardware.yaml;
  • a selected CIRCUITPY drive and serial port;
  • an exact match between hardware.yaml's board.id and the selected drive; and
  • a device/code.py entry point.

Deployment interrupts the running program, copies support files before code.py, soft-resets the board, and captures five seconds of startup output. It returns a failure if startup contains an uncaught traceback.

Change the capture duration when the program needs longer to start:

patchcord deploy --capture 15

Deployment creates or updates files but never deletes unrelated files already on the board. Root boot.py and settings.toml require explicit permission:

patchcord deploy --allow-boot
patchcord deploy --allow-settings

An allowed settings.toml is copied opaquely; Patchcord does not display or publish a digest of its contents. device/settings.toml is ignored by Git when Patchcord initializes a project.

4. Observe and repeat

Stream until you press Ctrl-C:

patchcord monitor

Or capture a bounded window:

patchcord monitor --seconds 10

All monitor sessions are appended to .patchcord/logs/serial.log. To append the raw board output to another file as well:

patchcord monitor --seconds 30 --output startup.txt

Read previous sessions without opening the serial port:

patchcord logs
patchcord logs --tail 50
patchcord logs --since 10m

patchcord logs shows the last 200 lines by default. Durations accept seconds, minutes, hours, or days, such as 30s, 10m, 2h, and 1d.

Selecting a board

Patchcord selects a drive or serial port automatically only when that target component is unambiguous. Use global overrides when multiple boards are attached:

patchcord \
  --mount /Volumes/CIRCUITPY \
  --port /dev/cu.usbmodem101 \
  status

Global options go before the command name. The same pattern works with deploy, monitor, reset, repl, libs, and hardware validate.

Patchcord v0.1 selects the drive and serial port independently; it cannot prove that two explicit overrides belong to the same physical board. When overriding both, make sure they identify the same device.

Serial control and REPL

Stop the running CircuitPython program and capture its console response:

patchcord interrupt

Soft-reset the board and capture startup:

patchcord reset
patchcord reset --capture 10

Open an interactive serial REPL:

patchcord repl

Interactive mode uses pyserial's miniterm and does not save a transcript. Use monitor --output when you need a separate raw output file.

Patchcord also defines bounded execution and probe commands:

patchcord repl --eval "print('hello')"
patchcord repl --file tools/check_sensor.py
patchcord probe pins
patchcord probe i2c

In the current v0.1 release, these bounded commands are unavailable because the pinned execution backend did not pass Patchcord's isolation and reset checks. They return execution_backend_unavailable. Interactive repl, deployment, monitoring, serial control, library management, and offline hardware validation remain available.

Managing CircuitPython libraries

List the project's CircuitPython bundle libraries in requirements.txt, one per line:

adafruit_bus_device
adafruit_requests
adafruit_ssd1306

Install the complete file on the selected board:

patchcord libs install

Install one or more named bundle packages:

patchcord libs install adafruit_requests adafruit_ssd1306

Or let circup inspect code.py currently deployed on the board:

patchcord libs install --auto

Named packages and --auto are mutually exclusive. --auto examines the board's deployed code, not the local device/ tree. A named install does not edit requirements.txt; update the file yourself or freeze the board afterward to make the environment repeatable.

Record the board's installed libraries back into the project:

patchcord libs freeze

This validates circup's generated requirements and atomically replaces requirements.txt.

Describing and validating hardware

hardware.yaml records the intended board and the project-specific parts and connections around it. A minimal document looks like this:

schema_version: 1

board:
  id: adafruit_feather_rp2040

parts: {}
nets: {}

A part can declare pins, an expected I²C address, and libraries:

parts:
  oled:
    kind: display
    model: SSD1306 OLED
    pins:
      SDA:
        role: i2c_sda
      SCL:
        role: i2c_scl
    interfaces:
      - kind: i2c
        bus: default
        address: 0x3c
    libraries:
      - adafruit_ssd1306

nets:
  i2c_sda:
    role: signal
    endpoints:
      - board.SDA
      - oled.SDA
  i2c_scl:
    role: signal
    endpoints:
      - board.SCL
      - oled.SCL

Every library named by a part must also be present in requirements.txt.

Use offline validation during editing and in CI:

patchcord hardware validate --offline
patchcord hardware validate --offline --json

Without --offline, Patchcord also attempts connected pin and I²C checks when a drive and serial port are selected. Connected validation interrupts and resets the board. It is currently blocked by the bounded-execution limitation described above.

The complete format is documented in the hardware.yaml v1 specification.

JSON output

Most bounded commands accept --json:

patchcord status --json
patchcord deploy --json
patchcord reset --json
patchcord logs --tail 50 --json
patchcord hardware validate --offline --json

JSON mode writes one result document to stdout with stable result and error codes. monitor --json must be bounded with --seconds:

patchcord monitor --seconds 10 --json

Interactive patchcord repl does not support JSON output.

Diagnose a setup

doctor performs read-only checks for project files, connected drives and serial ports, permissions, installed dependencies, and available capabilities:

patchcord doctor
patchcord doctor --json

Useful first checks when a command cannot find or open a board are:

  1. Confirm that the CIRCUITPY drive is mounted.
  2. Confirm that the board's serial device is present.
  3. Run patchcord doctor.
  4. Pass explicit --mount and --port values if discovery is ambiguous.
  5. Check the operating system's drive and serial-port permissions.

Patchcord reports missing permissions or drivers but does not install or change them automatically.

Further documentation

Development

uv sync --locked
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run basedpyright
uv build --no-sources

Bug reports and contributions are welcome through GitHub Issues.

Patchcord is released under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

patchcord-0.1.0.tar.gz (69.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

patchcord-0.1.0-py3-none-any.whl (63.5 kB view details)

Uploaded Python 3

File details

Details for the file patchcord-0.1.0.tar.gz.

File metadata

  • Download URL: patchcord-0.1.0.tar.gz
  • Upload date:
  • Size: 69.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for patchcord-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3fe53b520cf403cee9411be420863b60e18b2ea13111fb732c33100da739c898
MD5 0be08b834ae035fcd51a24c1b907d967
BLAKE2b-256 d61fdbec4b9a9a24e6e28ebf9a870b7cad5dca9ecca7076cea9a9a0017d21ff5

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcord-0.1.0.tar.gz:

Publisher: release.yml on totocaster/patchcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcord-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: patchcord-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 63.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for patchcord-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c4fb6eba0487e7c2f47455eff89281850d91904f33a493c9c19380f3a75e8547
MD5 1225bb298459997c82b563dc6a08dea8
BLAKE2b-256 d1a69de550ec1153e4267953e062e24a6c03e3e588a2cbef4dd4a793ac12ab33

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcord-0.1.0-py3-none-any.whl:

Publisher: release.yml on totocaster/patchcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page