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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version

## [Unreleased]

### Fixed

- User-facing docs now match shipped behaviour for `--confirm` on `job` /
`send` (upload still runs; exit `0` `uploaded_not_printed`), the fail-closed
camera streamer (opt-in, not auto-fallback), `doctor` fingerprint/`-v`
output, STEP/STP slice precedence, and sdist-relative links to repo-only
quality docs.

### Removed

- Python 3.9 support. The floor is 3.10 so every install resolves patched
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ already-compromised local machine or a malicious printer on your own LAN
## Related docs

- [AGENTS.md](AGENTS.md) — agent safety checklist
- [docs/quality-roadmap.md](docs/quality-roadmap.md) — security scoreboard
- [docs/quality-roadmap.md](https://github.com/DLANSAMA/platecli/blob/main/docs/quality-roadmap.md) — security scoreboard (GitHub/repo only; not in the sdist)
- [docs/api.md](docs/api.md) — agent JSON contracts
6 changes: 3 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ Validate includes `checks[]`, `ok`, `errors`, `warnings`, `exit_code`, and `stri
Success / dry-run: [`job_ok.json`](schemas/job_ok.json).
Failure: [`job_error.json`](schemas/job_error.json).

Print start requires `--confirm`; without it the job may upload but will not print.
Print start requires `--confirm`. Without it, download → slice → upload still runs and the command exits `0` with `"status": "uploaded_not_printed"`; only the print step is withheld.

### `go`

Expand Down Expand Up @@ -441,5 +441,5 @@ Machine-checkable schemas live in `docs/schemas/`. Contract tests under

- [AGENTS.md](../AGENTS.md) — agent architecture and safety
- [SECURITY.md](../SECURITY.md) — threat model
- [quality-roadmap.md](quality-roadmap.md) — quality scoreboard
- [test-backlog.md](test-backlog.md) — remaining schema/coverage gaps
- [quality-roadmap.md](https://github.com/DLANSAMA/platecli/blob/main/docs/quality-roadmap.md) — quality scoreboard (GitHub/repo only; not in the sdist)
- [test-backlog.md](https://github.com/DLANSAMA/platecli/blob/main/docs/test-backlog.md) — remaining schema/coverage gaps (GitHub/repo only; not in the sdist)
8 changes: 5 additions & 3 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pip install .

`--sim` (simulation mode) replaces the real printer with a **canned** local stub — fixed status, files, and camera bytes. It is not a protocol test of MQTT/FTPS. Use it to develop agents and scripts without hardware.

Destructive and physical actions — starting a print, pausing or resuming a print, stopping a job, deleting a file, or sending raw G-code — are gated behind an explicit `--confirm` flag. An agent that omits `--confirm` gets a refusal (exit code `5`, `"status": "confirmation_required"`) instead of a physical action, so accidental physical operations never happen. Note this is a gate against accidents, not an authorization boundary: anything that can run `plate` can also pass `--confirm`.
Destructive and physical actions — starting a print, pausing or resuming a print, stopping a job, deleting a file, or sending raw G-code — are gated behind an explicit `--confirm` flag. `print`, `stop`, `pause`, `resume`, `delete`, and `gcode` refuse without it (exit code `5`, `"status": "confirmation_required"`) and the printer is untouched. `job` / `send` without `--confirm` still download, slice, and upload, then exit `0` with `"status": "uploaded_not_printed"` — only the print step is withheld. Note this is a gate against accidents, not an authorization boundary: anything that can run `plate` can also pass `--confirm`.

```bash
# Inspect printer state without hardware
Expand All @@ -51,7 +51,7 @@ plate job <url> --json --confirm
- **Printables downloads** — platecli fetches files from Printables *on your behalf*, from your own machine and network — the same file you would get by clicking Download. It identifies itself honestly as `platecli/<version>`, keeps at least one second between requests to the same host, and honors `Retry-After`. Your use is subject to [Printables' terms of service](https://www.printables.com/legal/terms-of-use) and to the individual model's own licence (often a Creative Commons variant with attribution, non-commercial, or no-derivatives conditions). platecli grants you no rights to any downloaded model — check the licence on the model page before printing, remixing, redistributing, or selling. The Printables API used for resolution is undocumented and may change or stop working without notice.
- **Safe extraction** — ZIP archives containing model files are fully supported. Existing files are kept safe by creating a numbered sibling such as `model-1.stl`. URL downloads and ZIP extraction have a 2048 MB safety limit, adjustable via `--max-download-mb`.
- **Modularity** — Run steps individually using `download`, `slice`, `upload`, or `print`.
- **Safety first** — One-shot and print flows will not start a physical print unless `--confirm` is present. Pause, resume, stop, delete, and raw gcode also require `--confirm`, and refuse with exit code `5` without it.
- **Safety first** — `print`, `pause`, `resume`, `stop`, `delete`, and raw `gcode` refuse without `--confirm` (exit code `5`). `job` / `send` without `--confirm` still upload and exit `0` with `"status": "uploaded_not_printed"`; only the print step is withheld.
- **TLS pinning** — Pin the printer’s self-signed cert with `cert_fingerprint` (setup/doctor can capture it). Prefer this over `insecure_tls`.
- **SSRF-hardened downloads** — Private/loopback targets are refused unless you pass `--allow-private-ips` for that invocation.
- **Diagnostics** — Network, FTPS, and MQTT health checking with `doctor` and `preflight`.
Expand Down Expand Up @@ -324,6 +324,8 @@ plate snapshot --json # machine-readable result

Every `--json` response includes `captured_at` (ISO-8601 UTC timestamp) and `sha256` (hex digest of the JPEG bytes). Agents should compare these fields across captures to confirm a fresh frame was received before sending the image to a user. Use `--unique` when taking repeated snapshots — it inserts a UTC timestamp into the filename so successive captures never silently overwrite each other.

P1/A1-class printers are captured directly over TLS port 6000 — no extra software. X1-series cameras are RTSP and need the Docker streamer, which is **opt-in** (`camera_allow_streamer` or `--allow-camera-streamer`) because it does not honour `cert_fingerprint`. A failed direct grab aborts unless you opt in. `camera_port` defaults to loopback-only (`127.0.0.1:1985:1984`); set it to `0.0.0.0:1985:1984` only if you deliberately want the unauthenticated feed on the LAN.

## Global flags

| Flag | Description |
Expand All @@ -336,7 +338,7 @@ Every `--json` response includes `captured_at` (ISO-8601 UTC timestamp) and `sha

## Slicing & AMS

`slice` accepts common mesh formats in the precedence order STL > STEP > OBJ > 3MF > G-code. When mapping filaments to AMS slots, mapping arguments take zero-or-positive slot indexes.
`slice` accepts common mesh formats in the precedence order STL > STEP/STP > OBJ > 3MF > G-code. When mapping filaments to AMS slots, mapping arguments take zero-or-positive slot indexes.

To decide that mapping, read what is actually loaded first: `plate status`
shows each AMS unit's trays (filament type, colour, and remaining %), and
Expand Down
59 changes: 36 additions & 23 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ the `plate doctor` output — but check first that no access code is visible.

```bash
plate preflight # local only: config, OrcaSlicer, profiles, gmsh, xvfb-run, docker, zeroconf
plate doctor # talks to the printer: config -> MQTT -> FTPS, and prints the TLS fingerprint
plate doctor # talks to the printer: config -> MQTT -> FTPS; reports cert-pin status (add -v for LAN IP and full fingerprint)
plate config show # the effective config, with secrets redacted
plate config validate --strict
```
Expand Down Expand Up @@ -130,15 +130,17 @@ control that makes LAN traffic trustworthy.
is expected and benign. Re-pin it:

```bash
plate doctor
plate doctor --json # always includes certificate_fingerprint
```

`doctor` prints the printer's certificate SHA-256 and warns when it does not
match the `cert_fingerprint` in your config. Copy the printed value into
`cert_fingerprint` in `config.json`, or re-run
`plate setup --cert-fingerprint <hex>`. Both the bare-hex and colon-separated
forms are accepted — the value is normalised (lowercased, colons and spaces
stripped) before comparison.
Once a pin is already set, human `plate doctor` is hex-free on a match unless
you pass `-v` (which then prints the live SHA-256; the LAN IP is also
`-v`-only). On a mismatch it warns and shows the first eight hex characters of
the live cert. `plate doctor --json` always includes `certificate_fingerprint`
— use that value (or `plate setup --cert-fingerprint <hex>`) to re-pin after a
firmware update. Both the bare-hex and colon-separated forms are accepted —
the value is normalised (lowercased, colons and spaces stripped) before
comparison.

**If you did NOT just update firmware**, do not blindly re-pin — a mismatch is
also what an on-path attacker looks like. Confirm you are on the network you
Expand Down Expand Up @@ -313,19 +315,25 @@ Setup falls back to manual configuration, which works fine.

The full message tells you to install Docker Desktop (Windows/macOS) or
`docker-ce` (Linux) and retry. But check whether you actually need Docker at
all:
all — the streamer is **opt-in**, not an automatic fallback:

- **P1 and A1 series:** a direct TLS grab from the printer on **port 6000** —
**no Docker involved**. The `--json` output reports `"method": "direct"`.
- **X1 series:** the camera is an RTSP stream, which `plate` cannot decode
itself. It falls back to a small streamer container, so **Docker is required
on X1 only**.
itself. Set `camera_allow_streamer: true` or pass `--allow-camera-streamer`
(the streamer does not honour `cert_fingerprint`). **Docker is then
required on X1 only**.

So if you are on a P1/A1 and you are *seeing* this message, the direct grab
failed first and fell through. Check that port 6000 is reachable
(`nc -vz <printer-ip> 6000`), that LAN mode is on, and re-run with `-v` /
`--verbose` — the direct-path failure is logged at debug level as
"Direct camera grab unavailable ...; trying Docker streamer."
A failed direct grab **aborts by default**. A pin mismatch, and any
`ssl.SSLError` during the handshake when a pin is configured, hard-abort —
they never start the streamer, even if you opted in. `camera_direct_only`
forbids the streamer entirely.

You only see "Docker not found" after the streamer has been allowed. On a
P1/A1 that usually means the direct grab failed *and* you opted in. Check
that port 6000 is reachable (`nc -vz <printer-ip> 6000`), that LAN mode is
on, and re-run with `-v` / `--verbose` — the direct-path failure is logged
at debug level as "Direct camera grab unavailable ...".

The streamer container is published to `127.0.0.1` only by default
(`camera_port` defaults to `127.0.0.1:1985:1984`). Do not point
Expand All @@ -341,9 +349,10 @@ and a mismatch **fails closed** rather than silently falling back to the
unpinned Docker path. After a firmware update, re-pin with `plate doctor` as in
[Certificate fingerprint mismatch](#certificate-fingerprint-mismatch).

If you have no pin at all, `plate` warns that no `cert_fingerprint` is pinned
for the camera connection and tells you to run `plate setup` to pin one. Pin it
— don't set `insecure_tls`.
If you have no pin at all and `insecure_tls` is unset, the direct grab
**refuses** rather than sending the access code over an unverified TLS
connection. The error names the missing pin and points at `plate setup`. Pin
it — don't set `insecure_tls`.

## Snapshot fails and mentions `camera_allow_streamer`

Expand Down Expand Up @@ -582,10 +591,14 @@ Overrides still work in the meantime; unknown keys are warn-but-pass.

## Nothing happens when I run a print command

Working as designed. Anything that physically moves the printer — starting a
print, pausing or resuming one, stopping one, deleting a file, sending raw
G-code — requires an explicit `--confirm`. Without it the command refuses with
exit code `5` and the printer is untouched. Add `--confirm` once you're sure.
Working as designed. `print`, `stop`, `pause`, `resume`, `delete`, and
`gcode` require an explicit `--confirm`. Without it they refuse with exit
code `5` (`"status": "confirmation_required"`) and the printer is untouched.

`job` / `send` are different: omitting `--confirm` still runs download →
slice → upload and exits `0` with `"status": "uploaded_not_printed"`. Only
the print step is withheld — a file is on the printer. Add `--confirm` once
you're sure you want it to print.

If you want to rehearse the whole pipeline with no hardware at all, use
`--sim`:
Expand Down
2 changes: 1 addition & 1 deletion tests/package_contents_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"docs/manual.md": {
"`--json` | Emit JSON for commands that support it; may appear before or after the subcommand",
"`plate --json --version` emits",
"STL > STEP > OBJ > 3MF > G-code",
"STL > STEP/STP > OBJ > 3MF > G-code",
"--max-download-mb",
"zero-or-positive slot indexes",
"Runtime package used by the installed command",
Expand Down