Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
eb1fb93
docs: spec for printer profiles, Keychain access code and both printe…
Shavvimal Oct 27, 2026
94130e4
feat: profile and vault foundation: schema, runtime secrets, atomic w…
Shavvimal Oct 27, 2026
7c79264
feat: Keychain vault over security -i with the code on stdin (0.5.0 s…
Shavvimal Oct 27, 2026
6c8dd4e
feat: printers.json load, save, update, select and lock; reworded acc…
Shavvimal Oct 27, 2026
205e99b
Merge branch 'pp/b-vault' into printer-profiles-0.5.0
Shavvimal Oct 27, 2026
53ccbd7
feat: credential sources, staged discovery with identity checks, cert…
Shavvimal Oct 27, 2026
4a0bd62
Merge branch 'pp/c-transport' into printer-profiles-0.5.0
Shavvimal Oct 27, 2026
9ba873f
refactor: code in a 0600 file under the XDG config dir, no Keychain; …
Shavvimal Oct 27, 2026
c012340
fix: core review findings: CA bundle error surfaces, symlink-safe fil…
Shavvimal Oct 27, 2026
a8d8606
feat: printer login, list, forget, --printer and one-shot access code…
Shavvimal Oct 27, 2026
8069f19
fix: D review findings: doctor survives a damaged printers.json, usag…
Shavvimal Oct 27, 2026
54118a0
docs: printer profiles, access code in the config dir, both printer m…
Shavvimal Oct 27, 2026
84bdb69
fix: core round 3: scrubbed vault errors, private profile reads, long…
Shavvimal Oct 27, 2026
289fa6d
Merge branch 'pp/core3' into printer-profiles-0.5.0
Shavvimal Oct 27, 2026
1c4ec45
fix: D round 2: serial pin kept across the prompt, every emitted docu…
Shavvimal Oct 27, 2026
fb3a8ff
Merge branch 'pp/d2' into printer-profiles-0.5.0
Shavvimal Oct 27, 2026
237a8a2
docs: review fixes: when the subnet pass runs, the DHCP-change path, …
Shavvimal Oct 27, 2026
b24fca7
fix: a login refresh uses the profile's canonical serial after a case…
Shavvimal Oct 27, 2026
bda0931
docs: doctor profile line template matches the CLI; spec status; logi…
Shavvimal Oct 27, 2026
69653ae
fix: profile serials compare without regard to letter case; the store…
Shavvimal Oct 27, 2026
50886bb
fix: the credential key is the profile's stored serial inside the log…
Shavvimal Oct 27, 2026
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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shambu",
"version": "0.4.0",
"version": "0.5.0",
"description": "Prepare, inspect and patch Bambu Studio projects (.3mf) from STL files, slice them headless with estimates, and read a Bambu Lab printer's status over LAN (read-only). Built on Bambu Studio's own command line.",
"author": { "name": "Shav Vimalendiran" },
"homepage": "https://github.com/Shavvimal/shambu",
Expand Down
79 changes: 78 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,84 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.5.0] - 2026-10-27

Printer profiles for LAN-only mode and cloud mode. On 2026-10-27 the X2D went to LAN-only mode and regenerated its
LAN access code, Studio's config still held the old code, and shambu had nowhere to keep an IP or a code, so every
read failed until Studio's config was edited by hand. shambu now keeps its own profiles and codes, and both modes use
the same local read.

### Added
- Printer profiles: `printers.json` in shambu's config dir holds one entry per printer (name, serial, last IP, mode,
TLS flag, the dates added and last verified) and the default profile. It never holds an access code.
- `shambu printer login` in three forms: `--ip IP [--serial SERIAL]` (a new printer), `--printer NAME [--ip IP]` (a
known profile, for a new code or a new address) and `--from-studio` (the serial and the code of Studio's selected
printer). It reads the printer once with the code and stores the profile and the code only after that read
succeeds. The code comes from `--access-code-file`, `--access-code-stdin` or a hidden prompt on a terminal.
`--name`, `--default` and `--mode` set the profile's name, the default and the mode.
- `shambu printer list`: one line per profile (name, masked serial, IP, mode, last verified time, `(default)`); exit 1
when there is none. `shambu printer forget --printer NAME [--keep-code]` removes a profile and, unless
`--keep-code`, its stored code.
- `--printer NAME`, `--serial`, `--access-code-file FILE` and `--access-code-stdin` on `doctor`, `check`, `build`,
`sweep` and every `printer` action. The two code flags read the first line and keep the code for this run only.
- `--mode lan|cloud` on `login`: the mode the user set on the touchscreen (shambu does not detect it; a profile
without it says `unknown`). The mode picks the next step that a refused login names.
- A subnet pass in discovery: after the ARP table, the other hosts of the default-route network (/24 or narrower) on
TCP 8883. It runs in `printer discover` (always), in `login` when it has no IP (`--from-studio` without `--ip`), and
on a profile read only when the profile has no IP, which `login` never leaves. A read through a profile goes to the
profile's stored IP and does not search.
- `login --insecure` stores the TLS-off choice on the profile, and every later read through that profile skips the
check; a `login --printer NAME` refresh without `--insecure` clears it. For a new printer, `login --insecure` needs
`--serial`.
- `login --ip` without `--serial` takes the serial from the CN of the printer's certificate (one TLS handshake, no
MQTT, no credential).
- Identity-checked discovery: when the serial came from a flag or a profile and TLS is verified, each discovery hit
must present a certificate that names that serial; a foreign host on 8883 is skipped. A serial from Studio's config
gets the 0.4.0 behaviour: a TCP connect only.

### Changed
- Error text: a missing code is `no access code for serial <masked>: run shambu printer login, or open the printer
once in Bambu Studio's Device tab`. A refused login keeps `refused the MQTT login`, then names the code's source and
the profile, then the next step for the profile's mode (LAN-only: read the code off the touchscreen and run `shambu
printer login`; cloud: open Studio once, then `login --from-studio`; unknown: both). When the printer's address
changes, a profile read fails with `cannot connect ... The profile X expected this address (last login ...); run:
shambu printer login --printer X --ip <new ip>, or shambu printer discover`. With no serial from any source the
error is `no printer selected: run shambu printer login, or select it in Bambu Studio (Device tab)`, plus `has Bambu
Studio run on this Mac?` when Studio's config is absent. The `no host on this LAN` error now lists why: the hosts
checked that are not the printer, the candidates not checked before the timeout and the passes not run.
- A read through a profile goes to the profile's stored IP. When the printer's address changes (a new DHCP lease),
the read fails with the error above and names the profile's last login; `printer discover` finds the new address
and `login --printer NAME --ip <ip>` records it. A read never rewrites the profile.
- The watch log's `login` and `setup` categories name `shambu printer login` beside Studio.
- `doctor` prints one `printer profile ...` line in its config section when a profile is selected (name, masked
serial, mode, last login as `HH:MM UTC`, TLS flag, whether `credentials.json` holds a code for the serial as `code:
stored|none|unreadable`, profile count, config dir). Doctor's `access code stored` field still describes Studio's config only.
A damaged `printers.json` is an error in that line, and the rest of the report still runs; the other commands that
read profiles exit 3 and name the file.
- The UTC label: doctor's profile line (`last login HH:MM UTC`), the `login` hand-back (`read at HH:MM UTC`) and
`printer list` (`verified HH:MM UTC`) carry it. Doctor's `loaded on the printer (read HH:MM ...)` line is unchanged
from 0.4.0 and shows local time.
- `main` scrubs every known access code and every raw serial from an error text before it prints it; a masked serial
stays.
- The config dir is `$SHAMBU_CONFIG_DIR`, else `$XDG_CONFIG_HOME/shambu`, else `~/.config/shambu`. A relative or
empty value counts as unset, so no file lands in a checkout.
- `--printer` now shadows the argparse prefixes `--printer-timeout` and `--printer-preset`: spell both in full.

### Security
- The access code is written only to `credentials.json` (mode 0600) in the config dir. It never goes to
`printers.json`, to Studio's config, to argv or to any output.
- Every code the process read, from any source, is a runtime secret: every CLI document and line, usage message and
error text (the watch header and log, doctor and build warnings, `--json` output) is scrubbed of it. Doctor's
printer error is redacted of IPs as before.
- shambu refuses a symlink, or anything that is not a regular file, at the paths of `printers.json` and
`credentials.json`; `credentials.json` must also belong to the user and carry no group or other permission bit.
- Trust boundary: a first-time `login --ip` without `--serial` binds the profile to whatever Bambu-signed certificate
answers at that IP. A chain-valid certificate proves a Bambu device, not the intended one; `--serial` from the
touchscreen is the stronger binding.
- Discovery sends nothing but a TCP connect to a candidate host, plus a TLS handshake (the identity check) only when
the serial came from a flag or a profile and TLS is verified; a serial from Studio's config gets a TCP connect only.
The subnet pass scans no network wider than /24 and runs only where the Added list says (SECURITY.md has the
contract).

## [0.4.0] - 2026-10-21

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ binary or a printer; integration tests skip unless `SHAMBU_INTEGRATION=1` and a
| A new limit rule or placer | `src/shambu/layout.py` | `references/06-slicer-rules.md`, `08-optimisation.md` section 11 and `00-findings.md` section 10 |
| CLI flag handling | `src/shambu/studio.py` (`CliCommand`) | `references/01-source-cli.md` section 1 |
| Printer fields | `src/shambu/printer.py` | `references/04-app-printer-control.md` |
| Printer profiles and the credentials file | `src/shambu/profiles.py`, `conf.py`, `vault.py` | `docs/specs/2026-10-27-printer-profiles.md` |
| Skill text | `skills/shambu/SKILL.md`, `skills/setup/SKILL.md` | the plugin docs |

Every rule the code encodes about Bambu Studio should cite the reference that proved it. The
Expand Down
12 changes: 9 additions & 3 deletions DISCLAIMER.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ material if its settings are wrong.

## Device and network operations

shambu reads a Bambu Lab printer over the local network using the printer's own LAN access code, which it reads from
Bambu Studio's configuration on the same machine. It subscribes to the printer's status topic and sends one status
request. It never starts, pauses, stops or modifies a print, never uploads files, and never sends any other command.
shambu reads a Bambu Lab printer over the local network using the printer's own LAN access code, which it takes from
a one-shot flag, from the credentials file that `shambu printer login` writes in shambu's config dir, or from Bambu
Studio's configuration on the same machine. It subscribes to the printer's status topic and sends one status request.
It never starts, pauses, stops or modifies a print, never uploads files, and never sends any other command. A read
through a profile goes to the IP the profile stores. To find the printer it connects to TCP 8883 on the hosts in the
ARP table and, in `printer discover`, in `login --from-studio` without `--ip` and on a profile read when the profile
has no IP, on the other hosts of the default-route network when that network is /24 or narrower. A candidate gets
nothing but a TCP connect and, when the serial came from a flag or a profile and TLS is verified, a TLS handshake; a
serial from Studio's config gets a TCP connect only (see `SECURITY.md`).
Protect your access code, your network and anything you capture; do not run the tool against a printer you do not
control.

Expand Down
45 changes: 35 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ spec.json -> check -> build -> patch -> verify -> report.md (+ project
- macOS with [Bambu Studio](https://bambulab.com/en/download/studio) installed (02.08.x tested; the CLI lives in the app bundle).
- [uv](https://docs.astral.sh/uv/) (Python 3.11+ is fetched by uv if needed).
- For the plugin: [Claude Code](https://code.claude.com/).
- A Bambu Lab printer for the status commands (tested on an X2D; the printer must be on the same LAN and selected
once in Bambu Studio's Device tab so its LAN access code is stored).
- A Bambu Lab printer for the status commands (tested on an X2D, in LAN-only and in cloud mode; the printer must be
on the same LAN). shambu needs its LAN access code once: `shambu printer login` stores a profile and the code, or
select the printer once in Bambu Studio's Device tab so Studio stores the code, as before.

## Installation

Expand All @@ -55,17 +56,17 @@ As a Claude Code plugin:
/shambu:setup
```

`/shambu:setup` installs the CLI (`uv tool install "shambu @ git+https://github.com/Shavvimal/shambu@v0.4.0"`) and runs
`/shambu:setup` installs the CLI (`uv tool install "shambu @ git+https://github.com/Shavvimal/shambu@v0.5.0"`) and runs
`shambu doctor`. From then on, ask Claude to "make a plate from these STLs with PETG functional settings" and the
`/shambu` skill does the rest.

CLI only:

```
uv tool install "shambu @ git+https://github.com/Shavvimal/shambu@v0.4.0"
shambu doctor --expect-version 0.4.0
uv tool install "shambu @ git+https://github.com/Shavvimal/shambu@v0.5.0"
shambu doctor --expect-version 0.5.0
# or without installing:
uvx --from "git+https://github.com/Shavvimal/shambu@v0.4.0" shambu doctor --expect-version 0.4.0
uvx --from "git+https://github.com/Shavvimal/shambu@v0.5.0" shambu doctor --expect-version 0.5.0
```

## Usage
Expand Down Expand Up @@ -127,13 +128,34 @@ logged last resort for anything else.
**4. Read the printer (read-only) and measure prints.**

```
shambu printer filaments [--ip <printer-ip>] # the spool in each nozzle and slot, with the preset it resolves to
shambu printer login --ip <printer-ip> --serial <serial> --mode lan # prompts for the LAN access code (not shown)
shambu printer login --printer x2d --ip <new-ip> # a known profile: a new code or a new address
shambu printer login --from-studio --name x2d # the serial and the code of Studio's selected printer
shambu printer list # the profiles: name, masked serial, IP, mode, last verified
shambu printer filaments [--printer x2d] # the spool in each nozzle and slot, with the preset it resolves to
shambu printer status [--ip <printer-ip>]
shambu printer watch --every 300 # until the printer leaves RUNNING/PAUSE
shambu printer watch --wait-for-start --log print.jsonl --gcode3mf plate.gcode.3mf # log one print
shambu calibrate --log print.jsonl --add # store it; `calibrate --show` prints the fit per printer and material
```

`login` reads the printer once with the code and stores the profile and the code only when that read succeeds.
`--access-code-stdin` or `--access-code-file FILE` give the code without a prompt; on any other command they give it
for that run only. Profiles live in `printers.json` and codes in `credentials.json` (mode 0600) in shambu's config
dir: `$SHAMBU_CONFIG_DIR`, else `$XDG_CONFIG_HOME/shambu`, else `~/.config/shambu`. A command that reads the printer
takes the code from a one-shot flag, then from the stored code of the profile (`--printer NAME`, else the default
profile), then from Bambu Studio's config. Both printer modes use the same local read; the mode only says where a new
code comes from when the printer refuses the old one (the touchscreen in LAN-only mode, Studio in cloud mode).
`shambu printer forget --printer NAME` removes a profile and its code (`--keep-code` keeps the code); when the
forgotten profile was the default, the first remaining profile becomes the default.

A read through a profile goes to the IP the profile stores and does not search; `login` always stores the IP it read
from. The subnet pass (the default-route /24 after the ARP table) runs in `printer discover` (always), in `login`
when it has no IP (`--from-studio` without `--ip`), and on a profile read only when the profile has no IP. When the
printer's address changes (a new DHCP lease), a profile read fails with `cannot connect ... The profile x2d expected
this address (last login ...); run: shambu printer login --printer x2d --ip <new ip>, or shambu printer discover`.
Run `shambu printer discover`, then `shambu printer login --printer x2d --ip <ip>` to record the new address.

Every time in a report is "Studio estimate", Studio's own prediction. The watch log stamps each poll and never
holds the IP or the serial. A report adds the measured range of stored prints, and a calibrated time only when
three qualifying prints of that printer model and material agree.
Expand All @@ -150,8 +172,11 @@ Exit codes: 0 ok, 1 a check or verification failed or a patch was refused, 2 usa

- The printer code subscribes to the status topic and publishes exactly one `pushall` request. It never starts,
pauses, stops or changes a print and never uploads a file. Pull requests that add printer control are not merged.
- The LAN access code is read from Bambu Studio's configuration, kept in memory, never printed or logged, and sent
only over TLS verified against Bambu's certificate authority (`--insecure` is an explicit opt-out).
- The LAN access code comes from a one-shot flag, the credentials file (mode 0600) or Bambu Studio's configuration.
It is never printed, logged or passed on a command line, and it is sent only over TLS verified against Bambu's
certificate authority (`--insecure` is an explicit opt-out). `login --insecure` stores that choice on the profile,
and every later read through the profile skips the check; a `login --printer NAME` refresh without `--insecure`
clears it.
- Projects are handed back for the user to open, check and print. The tool never presses Print.

## Rules the code encodes (so nobody has to remember them)
Expand Down Expand Up @@ -195,7 +220,7 @@ Exit codes: 0 ok, 1 a check or verification failed or a patch was refused, 2 usa

| Path | What |
|---|---|
| `src/shambu/` | The package: `presets` (chain of profile sources, flattening), `studio` (binary adapter, `CliCommand` builder), `assemble` (assemble-list model and validation), `layout` (placer and limit rules), `mesh` (STL read/write, orientation scoring), `threemf` + `patches` (3MF read/patch commands, G-code feature usage), `spec` (typed spec, builder), `recipes`, `build` (template-method pipeline), `sweep` (variant builds and the comparison table), `report`, `printer` (discovery, MQTT reader, decoder), `conf`, `facade`, `cli` |
| `src/shambu/` | The package: `presets` (chain of profile sources, flattening), `studio` (binary adapter, `CliCommand` builder), `assemble` (assemble-list model and validation), `layout` (placer and limit rules), `mesh` (STL read/write, orientation scoring), `threemf` + `patches` (3MF read/patch commands, G-code feature usage), `spec` (typed spec, builder), `recipes`, `build` (template-method pipeline), `sweep` (variant builds and the comparison table), `report`, `printer` (discovery, MQTT reader, decoder), `conf` (Studio's config, the config dir), `profiles` (printer profiles), `vault` (the credentials file), `facade`, `cli` |
| `tests/` | Offline pytest suite with fakes for the binary, the profile tree and the MQTT transport |
| `skills/shambu/` | The Claude Code skill and the reverse-engineering references |
| `skills/setup/` | `/shambu:setup`: installs the CLI, runs `shambu doctor` |
Expand Down
Loading
Loading