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
33 changes: 33 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,36 @@ and dispatch to the corresponding decoder.
gives a single `loadImageFile(path) → RawImageData` surface for the
print-image command without spreading format knowledge across the
codebase.

## D6 — `--host` walks the drivers; every failure is a decline

**Plan said (plan 17 D7):** walk installed drivers on `--host` without
`--printer`; typed "not mine" errors mean move on, anything else is that
driver's error.
**Chose:** every rejection or synchronous throw from `openPrinter` is a
decline, whatever its type. The walk only fails when all drivers decline,
and then prints each driver's reason.

**Why:** the published labelwriter-node throws a plain `Error` when
`deviceKey` is missing on TCP, and future drivers may throw anything. A
walk that rethrows untyped errors would make a Brother print die on an
unrelated installed driver. The reasons are not lost: they are all shown
when nobody succeeds, and `--printer <family>` still surfaces one
driver's error verbatim. Sequential, not concurrent, so at most one
driver holds a 9100 socket; the cost today is one SNMP timeout (only
brother-ql identifies over the network).

`selectPrinter` now opens the printer itself so there is one open site
for the walk, the `--printer` path and the discovered path. Discovered
network printers are re-opened with the discovered device key so the
driver does not identify twice.

## D7 — `list` shows a Serial column instead of a `serial=` suffix

**Plan said (plan 17 D7):** render network rows as
`brother-ql QL-820NWBc tcp 192.168.1.67:9100 serial=M5G679125`.
**Chose:** a fifth `Serial` column on every row, blank when unknown.

**Why:** USB rows carry serials too, and a column keeps the table
aligned; the information is the same. The "multiple printers found"
message keeps the `serial=` suffix, it is a sentence not a table.
14 changes: 14 additions & 0 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,17 @@ Tracks completion of the steps in `PLAN.md` §10.
- [x] Test fixtures drop `colorCapable: false` (`status.test.ts`, `print-text.test.ts`, `print-image.test.ts`)
- [x] Gates green (typecheck, lint, format, test, build)

## Step — Network printers (plan 17 step 5, 0.6.0)

> Plan: `~/thermal-label/plans/backlog/17-network-discovery-snmp.md` D7

- [x] `--device`, `--media`, `--community` on `status`, `print text`, `print image`
- [x] `--host` without `--printer` walks drivers; every failure declines; `DeviceIdentificationRequiredError` rendered with candidates and a copy line
- [x] `selectPrinter` opens; discovered network printers re-open with `deviceKey`
- [x] `--media` resolved through `listMedia()`; `print` stops swallowing `getStatus()` failures
- [x] `list`: host:port rows, Serial column, SNMP-broadcast hint
- [x] Tests: `select.test.ts` (walk, re-open, media), status/list/print-text suites extended — 91 tests, coverage 95/89/85/95
- [x] Docs: `docs/index.md` network section + flags, README
- [x] Version 0.6.0
- [ ] Pin `@thermal-label/contracts ^0.6.2` and `@thermal-label/brother-ql-node ^0.6.2` + lockfile — after both are on npm
- [ ] Bench B7–B12 (maintainer, QL-820NWBc at 192.168.1.67)
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@ thermal-label list # detect connected printers
thermal-label status # readiness + media + errors
thermal-label print text "Hello World" # quick text print
thermal-label print image logo.png # PNG / JPEG print
thermal-label print text "hi" --host 192.168.1.67 # network printer, driver auto-picked
thermal-label print text "hi" --host 192.168.1.67 --device QL_820NWBc --media 251 # when it cannot identify itself
```

Network printers show up in `list` when the driver can find them on the
LAN (Brother QL: SNMP broadcast); `--host` reaches the ones it cannot.
`--device <key>` and `--media <id>` take over when identification or
media detection is not possible.

## Documentation

Full docs at **<https://thermal-label.github.io/cli/>**.

- Command + flag reference
- TCP / WebUSB usage
- Network printers: `--host`, `--device`, `--media`, `--community`
- thermal-label-cli vs burnmark-cli — when to use which

## Philosophy
Expand Down
90 changes: 83 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ Lists discovered printers across every installed driver.

```
$ thermal-label list
Family Model Transport Connection
brother-ql QL-820NWB usb Bus 003 Device 010
labelwriter LabelWriter 450 usb Bus 001 Device 004
Family Model Transport Connection Serial
brother-ql QL-820NWBc tcp 192.168.1.67:9100 M5G679125
brother-ql QL-800 usb 3:10
labelwriter LabelWriter 450 usb 1:4
```

USB rows come from the drivers' USB enumeration. Network rows come from
each driver's own LAN scan (Brother QL: one SNMP broadcast, about a
second); a printer on another subnet, or with SNMP disabled, is not
listed but still reachable with `--host`.

`--drivers` shows which known driver packages are installed:

```
Expand All @@ -84,12 +90,23 @@ Media: 62mm continuous (62mm, continuous)
Errors: none
```

Over TCP:
Over TCP (the driver is found by asking the printer what it is, see
[Network printers](#network-printers)):

```
$ thermal-label status --printer brother-ql --host 192.168.1.42
$ thermal-label status --host 192.168.1.67
Printer: QL-820NWBc (brother-ql)
Status: Ready
Media: 62mm continuous (62mm, continuous)
Errors: none
Printer state: idle
Two-colour: not detectable over network
Transport: TCP 192.168.1.67:9100
```

Rows under `Errors:` are the driver's detail rows; warnings are shown in
yellow.

### `print text <text>`

Renders text to a label and prints.
Expand All @@ -98,9 +115,20 @@ Renders text to a label and prints.
thermal-label print text "BIN-42"
thermal-label print text "FRAGILE" --invert --scale-x 2
thermal-label print text "Hello" --printer brother-ql --density dark
thermal-label print text "Label" --host 192.168.1.42 --printer brother-ql --copies 3
thermal-label print text "Label" --host 192.168.1.67 --copies 3
thermal-label print text "Label" --host 192.168.1.67 --device QL_820NWBc --media 251
```

Before printing, the CLI queries status once (drivers size the job from
the detected media and the error rows are echoed as warnings). If that
query fails the print stops, unless `--media` is given: then it warns and
prints with the media you named, sending the job blind (`confirm: false`):
a driver that would normally confirm the print over the same channel
(Brother QL over TCP checks the SNMP page counter) cannot, so "Printed"
then means "sent", not "came out". `--no-confirm` forces the same blind
send when the status query worked but the driver still cannot confirm
(the page counter is unreadable, or SNMP drops out between the two).

Rendering uses [`@mbtech-nl/bitmap`](https://www.npmjs.com/package/@mbtech-nl/bitmap)'s
pixel font — simple by design. For typography, barcodes, or logos, render
externally and use `print image`.
Expand All @@ -122,9 +150,12 @@ thermal-label print image label.png --rotate 90 --printer labelwriter
| Flag | Description |
|---|---|
| `--printer <family>` | Restrict to a driver family: `brother-ql`, `labelwriter`, `labelmanager`. |
| `--host <ip>` | Use TCP transport to the given host. Requires `--printer`. |
| `--host <ip>` | Use TCP transport to the given host. Without `--printer`, every installed driver is asked in turn. |
| `--port <port>` | TCP port (default `9100`). |
| `--serial <sn>` | Target a specific printer by serial number. |
| `--device <key>` | Registry key of the model (`QL_820NWBc`, `LW_550`, …) for drivers that cannot identify it themselves. Wins over identification. |
| `--media <id>` | Media id or name from the driver's catalog (`251`, `"62mm continuous"`). Overrides detected media and lets a print go out when status cannot be read. |
| `--community <name>` | SNMP community for network printers opened with `--host` (default `public`). The LAN scan behind `list` and flag-less selection always uses the driver's default community. |

### `print text`

Expand All @@ -135,6 +166,7 @@ thermal-label print image label.png --rotate 90 --printer labelwriter
| `--scale-y <n>` | `1` | Vertical scale factor. |
| `--density <d>` | `normal` | Driver-specific density (`light`, `normal`, `dark`, …). |
| `--copies <n>` | `1` | Number of copies. |
| `--no-confirm` | confirm on | Send without out-of-band print confirmation (network printers whose SNMP page counter cannot be read). |

### `print image`

Expand All @@ -146,6 +178,50 @@ thermal-label print image label.png --rotate 90 --printer labelwriter
| `--rotate <deg>` | `0` | Rotation: `0`, `90`, `180`, or `270`. |
| `--density <d>` | `normal` | Driver-specific density. |
| `--copies <n>` | `1` | Number of copies. |
| `--no-confirm` | confirm on | Send without out-of-band print confirmation. |

## Network printers

`--host <ip>` without `--printer` walks the installed drivers in a fixed
order (brother-ql, labelwriter, labelmanager) and hands the printer to the
first one that opens it. A driver that cannot speak to that address
declines and the walk moves on, so an installed-but-unrelated driver never
breaks a print on another. Only when every driver declines does the CLI
fail, and then it prints each driver's reason:

```
$ thermal-label status --host 192.168.1.67
No installed driver could open 192.168.1.67:
brother-ql: No SNMP answer from 192.168.1.67 (Read timed out after 2000ms); the model cannot be identified and status is unavailable, so pass media too. Pass deviceKey, one of: PT_E550W, PT_P750W, …, QL_820NWBc.
candidates (key name):
PT_E550W PT-E550W
PT_P750W PT-P750W
…
QL_820NWBc QL-820NWBc
copy, swapping the key for your model:
thermal-label status --host 192.168.1.67 --printer brother-ql --device PT_E550W --media <id>
labelwriter: TCP open requires `deviceKey` — port 9100 carries no model signal, so the model must be declared. Available TCP-capable LabelWriter keys: LW_550_TURBO, LW_5XL, LW_WIRELESS.
labelmanager: No compatible device found

Pass --printer <family> to see one driver's error, or --device <key> to name the model.
```

How a driver identifies a network printer is its own business; Brother QL
asks over SNMP (model, serial, state, loaded media) because port 9100 is
write-only. When that is not possible (SNMP disabled, printer on another
subnet with no broadcast, a model the driver does not list) `--device`
names the model and `--media` names the roll, and the job goes out
without a status read. `--community` covers a printer whose SNMP
community is not `public`, but only on the `--host` path: the LAN scan
that `list` and flag-less selection run goes through each driver's
default discovery and asks with `public`, so a printer with a custom
community is reached with `--host`.

Two-colour rolls (Brother DK-22251) cannot be told apart from plain
62 mm rolls over the network; the status shows a `Two-colour: not
detectable over network` warning and a job sized for the plain roll is
rejected by the printer. Pass `--media 251` on those rolls. See the
driver's troubleshooting page for the details.

## When multiple printers are connected

Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thermal-label-cli",
"version": "0.5.0",
"version": "0.6.0",
"description": "Unified CLI for thermal label printers — auto-detects all installed drivers",
"keywords": [
"thermal-label",
Expand All @@ -13,7 +13,7 @@
"type": "module",
"author": "Mannes Brak",
"license": "MIT",
"homepage": "https://github.com/thermal-label/cli",
"homepage": "https://thermal-label.github.io/cli/",
"repository": {
"type": "git",
"url": "https://github.com/thermal-label/cli.git"
Expand Down Expand Up @@ -64,16 +64,17 @@
"prettier": "@mbtech-nl/prettier-config",
"dependencies": {
"@mbtech-nl/bitmap": "^1.3.0",
"@thermal-label/contracts": "^0.5.0",
"@thermal-label/contracts": "^0.6.2",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"jpeg-js": "^0.4.4",
"pngjs": "^7.0.0"
"pngjs": "^7.0.0",
"usb": "^2.14.0"
},
"peerDependencies": {
"@thermal-label/brother-ql-node": ">=0.5.0",
"@thermal-label/labelmanager-node": ">=0.5.0",
"@thermal-label/labelwriter-node": ">=0.5.0"
"@thermal-label/brother-ql-node": ">=0.6.2",
"@thermal-label/labelmanager-node": ">=0.6.0",
"@thermal-label/labelwriter-node": ">=0.6.4"
},
"peerDependenciesMeta": {
"@thermal-label/brother-ql-node": {
Expand Down
Loading
Loading