Skip to content

Commit 441e651

Browse files
authored
feat: Migrate zday CLI to cli-core-yo foundation (#34)
7 commits: cli-core-yo core swap, output standardization, global JSON mode, printer simulator, ZPL-first scanner, mypy fixes, documentation updates. 334/334 tests pass, 0 mypy errors, ruff clean.
2 parents 2abfcab + ff34f4f commit 441e651

27 files changed

+2067
-815
lines changed

AGENTS.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# AGENTS.md — zebra_day Project Directives
2+
3+
## What This Repo Is
4+
5+
`zebra_day` is a Python library + CLI + web GUI for managing fleets of Zebra label printers.
6+
It speaks ZPL over TCP (port 9100), provides a FastAPI web UI, and optionally stores
7+
config in AWS DynamoDB with S3 backups.
8+
9+
## Architecture Quick Reference
10+
11+
| Layer | Key Files | Notes |
12+
|-------|-----------|-------|
13+
| CLI | `zebra_day/cli/__init__.py` | Built on `cli-core-yo` (`create_app(spec)` + plugin `register()` pattern) |
14+
| Core | `zebra_day/print_mgr.py`, `zebra_day/cmd_mgr.py` | `zpl()` class, ZPL TCP communication |
15+
| Backends | `zebra_day/backends/` | `ConfigBackend` protocol → `LocalBackend` (files) or `DynamoBackend` (AWS) |
16+
| Web | `zebra_day/web/` | FastAPI app, Jinja2 templates in `zebra_day/templates/modern/` |
17+
| Simulator | `zebra_day/simulator.py` | Mock ZPL printer for testing (TCP 9100 + HTTP) |
18+
| Paths | `zebra_day/paths.py` | XDG Base Directory helpers |
19+
20+
## CLI Rules
21+
22+
- The CLI uses **cli-core-yo** as its foundation. All command modules expose a `register(registry, spec)` function.
23+
- **Global `--json/-j` flag** lives on the root callback. Do NOT add per-command `--json` flags.
24+
- Use `output.*` primitives (`heading`, `success`, `warning`, `error`, `action`, `detail`, `bullet`, `emit_json`, `print_text`) — never raw `print()` or `console.print()` for user-facing output.
25+
- In JSON mode, `output.error()` and other display primitives are **auto-suppressed**. Use `output.emit_json()` to emit machine-readable payloads.
26+
- Pin cli-core-yo, typer, and rich versions per `pyproject.toml` ranges.
27+
28+
## Testing
29+
30+
- **Framework**: `pytest` + `pytest-cov`
31+
- **Test files**: `tests/test_*.py` (13 files, 334+ tests)
32+
- **AWS mocks**: Use `moto[dynamodb,s3]` — never real AWS credentials in tests
33+
- **Run all**: `pytest tests/ -v --tb=short`
34+
- **Run one file**: `pytest tests/test_cli.py -v`
35+
- **Linting**: `ruff check zebra_day tests && ruff format --check zebra_day tests`
36+
- **Type checking**: `mypy zebra_day --ignore-missing-imports`
37+
38+
## Quality Gates (must pass before merge)
39+
40+
```bash
41+
ruff check zebra_day tests
42+
ruff format --check zebra_day tests
43+
mypy zebra_day --ignore-missing-imports
44+
pytest tests/ -v --tb=short
45+
```
46+
47+
## Config Format
48+
49+
- Printer fleet config is **YAML** (`zebra-day-config.yaml`) for historical reasons.
50+
- All new config/data interchange should prefer **JSON**.
51+
- DynamoDB stores config as JSON-encoded strings.
52+
53+
## Network Scanner
54+
55+
- Default discovery: **ZPL port 9100** (`~HI` query).
56+
- Optional HTTP fallback via `scan_http_port` parameter.
57+
- The `notes` field records discovery method: `"zpl"`, `"http(port)"`, or `"zpl+http(port)"`.
58+
59+
## Web UI
60+
61+
- FastAPI + Jinja2 templates in `zebra_day/templates/modern/`.
62+
- API routes: `zebra_day/web/routers/api.py` (JSON), `zebra_day/web/routers/ui.py` (HTML).
63+
- Default port: **8118**. HTTPS by default if mkcert certs exist.
64+
- Follow the API documentation exposure rules from global `~/.augment/rules/01_http_https_api_rules.md`.
65+
66+
## Versioning
67+
68+
- `setuptools_scm` — no hardcoded version. Tags are `X.Y.Z` (no `v` prefix).
69+
- `_version.py` is generated — do not edit or commit it.
70+
71+
## Environment Variables
72+
73+
| Variable | Default | Purpose |
74+
|----------|---------|---------|
75+
| `ZEBRA_DAY_CONFIG_BACKEND` | `local` | `local` or `dynamodb` |
76+
| `ZEBRA_DAY_DYNAMO_TABLE` | `zebra-day-config` | DynamoDB table name |
77+
| `ZEBRA_DAY_DYNAMO_REGION` | `us-west-2` | AWS region |
78+
| `ZEBRA_DAY_S3_BACKUP_BUCKET` | _(none)_ | Required for dynamodb backend |
79+
| `AWS_PROFILE` | _(none)_ | Never pass `"default"` explicitly |
80+
81+
## Do NOT
82+
83+
- Add per-command `--json` flags (use the global one).
84+
- Use `datetime.UTC` (use `datetime.timezone.utc` for mypy compat).
85+
- Use `console.print()` for user-facing output (use `output.*` primitives).
86+
- Store real patient data or PHI in tests or examples.
87+
- Edit `_version.py` manually.

MODERNIZE.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ This document tracks the modernization of zebra_day from 0.5.0 to 2.2.0.
7272
- [x] Added 60-second caching for printer status queries
7373
- [x] 152+ tests passing
7474

75+
### Phase 8 - cli-core-yo Migration + Simulator + ZPL-First Scanner
76+
- [x] Migrated CLI from raw Typer to cli-core-yo foundation (`create_app(spec)` + plugin system)
77+
- [x] Converted all command modules to `register()` plugin pattern (8 modules)
78+
- [x] Standardized output: `console.print()``output.*` primitives (heading, success, warning, error, etc.)
79+
- [x] Global `--json/-j` flag via RuntimeContext (replaced per-command `--json` flags)
80+
- [x] Added mock Zebra printer simulator (`zday simulator start/stop/list`)
81+
- ZPL TCP server (port 9100) + HTTP status server (port 18080)
82+
- Configurable model, serial, firmware, and error conditions
83+
- [x] Refactored network scanner to ZPL-first discovery (port 9100 default)
84+
- Optional HTTP fallback via `--scan-http-port`
85+
- Discovery method tracked in `notes` field: "zpl", "http(port)", "zpl+http(port)"
86+
- [x] Fixed 27 pre-existing mypy errors (0 remaining)
87+
- [x] ruff check + ruff format clean on all modified files
88+
- [x] 334 tests passing across 13 test files
89+
7590
---
7691

7792
## Commands Reference
@@ -84,13 +99,15 @@ ruff check zebra_day tests # Lint
8499
ruff format --check zebra_day tests # Format check
85100
mypy zebra_day # Type check
86101

87-
# CLI
102+
# CLI (global --json/-j flag available on all commands)
88103
zday --help # Show all commands
89104
zday info # Show config paths and status
90105
zday bootstrap # First-time setup
91106
zday gui start # Start web server (HTTPS by default)
92107
zday gui start --no-https # Start without HTTPS
93108
zday gui stop # Stop web server
109+
zday simulator start --foreground # Mock printer for testing
110+
zday printer scan --ip-stub 192.168.1 # ZPL-first scanner
94111

95112
# Build
96113
python -m build # Build wheel and sdist

README.md

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,24 @@ zday gui stop
120120

121121
The `zday` CLI provides a comprehensive interface for managing your Zebra printer fleet.
122122

123+
#### Global Flags
124+
125+
| Flag | Description |
126+
|------|-------------|
127+
| `--json` / `-j` | Emit machine-readable JSON output (applies to all commands) |
128+
| `--help` | Show help for any command or subcommand |
129+
123130
```bash
124131
# Get help on any command
125132
zday --help
126133
zday gui --help
127134
zday printer --help
128135

136+
# JSON output for any command
137+
zday --json info
138+
zday --json printer list --live
139+
zday -j dynamo status
140+
129141
# Core commands
130142
zday info # Show version, config paths, server status
131143
zday status # Show printer fleet status, service health
@@ -138,11 +150,12 @@ zday gui status
138150
zday gui logs [--tail N] [--follow]
139151
zday gui restart
140152

141-
# Printer management
142-
zday printer scan [--ip-stub IP] # Scan network for printers
143-
zday printer list [--lab LAB] # List configured printers (static config)
144-
zday printer list --live # Query live status from printers (Status + State)
145-
zday printer test PRINTER_NAME # Send test print
153+
# Printer management (ZPL-first discovery, port 9100)
154+
zday printer scan [--ip-stub IP] # Scan via ZPL port 9100 (default)
155+
zday printer scan --ip-stub 192.168.1 --scan-http-port 80 # Also probe HTTP
156+
zday printer list [--lab LAB] # List configured printers (static config)
157+
zday printer list --live # Query live status (Status + State)
158+
zday printer test PRINTER_NAME # Send test print
146159

147160
# Template management
148161
zday template list # List ZPL templates
@@ -152,7 +165,7 @@ zday template show TEMPLATE # Display template contents
152165

153166
# Configuration management
154167
zday config init # Initialize config from template
155-
zday config show # Display current config (YAML)
168+
zday config show # Display current config
156169
zday config path # Print path to config file
157170
zday config validate # Validate config schema
158171
zday config edit # Open config in $EDITOR
@@ -177,6 +190,11 @@ zday dynamo backup # Trigger immediate S3 backup snapshot
177190
zday dynamo restore # Restore DynamoDB from S3 backup
178191
zday dynamo destroy # Delete DynamoDB table (preserves S3 backups)
179192

193+
# Printer simulator (for testing without physical printers)
194+
zday simulator start [--foreground] [--model MODEL] [--serial SN]
195+
zday simulator stop [--all]
196+
zday simulator list
197+
180198
# Interactive documentation browser
181199
zday man # Interactive topic menu
182200
zday man quickstart # View specific topic
@@ -191,7 +209,7 @@ Opt-in shared configuration via AWS DynamoDB with S3 backup snapshots. Local-fil
191209
| Command | Description | Key Options |
192210
|---------|-------------|-------------|
193211
| `zday dynamo init` | Create DynamoDB table and S3 bucket | `--table-name`, `--region`, `--s3-bucket`, `--profile`, `--cost-center`, `--project`, `--skip-checks` |
194-
| `zday dynamo status` | Show table and S3 backup status | `--json`, `--create-s3-if-missing` |
212+
| `zday dynamo status` | Show table and S3 backup status | `--create-s3-if-missing` (use global `--json` flag) |
195213
| `zday dynamo bootstrap` | Push local config + templates to DynamoDB | `--config-file`, `--templates-dir`, `--include-package/--no-include-package`, `--create-s3-if-missing` |
196214
| `zday dynamo export` | Pull DynamoDB config + templates to local files | `--output-dir`, `--format json|yaml` |
197215
| `zday dynamo backup` | Trigger immediate S3 backup snapshot | `--create-s3-if-missing` |
@@ -239,6 +257,68 @@ zday man --list # List all topics with descriptions
239257
zday man --search "template" # Full-text search across all docs
240258
```
241259

260+
#### Printer Simulator (`zday simulator`)
261+
262+
A mock Zebra printer simulator for testing and development without physical printers.
263+
The simulator responds to standard ZPL queries (`~HI`, `~HS`, `~HQSN`, `~HQOD`, `~HQES`)
264+
on a configurable TCP port (default 9100) and serves a Zebra-like HTTP page.
265+
266+
```bash
267+
# Start a simulator in the foreground (Ctrl+C to stop)
268+
zday simulator start --foreground --model "ZT411-300dpi ZPL" --serial DEMO001
269+
270+
# Start in the background (default host 0.0.0.0)
271+
zday simulator start --model "ZD620-203dpi ZPL" --serial LAB01
272+
273+
# Simulate error conditions
274+
zday simulator start --foreground --paper-out # Paper-out condition
275+
zday simulator start --foreground --paused # Paused state
276+
277+
# List running simulators
278+
zday simulator list
279+
280+
# Stop all running simulators
281+
zday simulator stop --all
282+
283+
# Stop a specific simulator
284+
zday simulator stop --host 0.0.0.0 --zpl-port 9100
285+
```
286+
287+
| Option | Default | Description |
288+
|--------|---------|-------------|
289+
| `--host` / `-b` | `0.0.0.0` | Bind address |
290+
| `--zpl-port` / `-z` | `9100` | ZPL TCP port |
291+
| `--http-port` / `-p` | `18080` | HTTP status port |
292+
| `--model` / `-m` | `ZD620-203dpi ZPL` | Reported model string |
293+
| `--serial` / `-s` | `SIM1001` | Reported serial number |
294+
| `--foreground` / `-f` | `false` | Block until Ctrl+C |
295+
| `--paper-out` | `false` | Simulate paper-out error |
296+
| `--ribbon-out` | `false` | Simulate ribbon-out error |
297+
| `--head-up` | `false` | Simulate head-up error |
298+
| `--paused` | `false` | Simulate paused state |
299+
300+
#### Network Scanner (ZPL-First)
301+
302+
The printer scanner probes **port 9100 (ZPL)** by default, sending the `~HI` host identification
303+
query. This is more reliable than HTTP-based discovery since port 9100 is the standard
304+
Zebra printer protocol port.
305+
306+
```bash
307+
# Default: ZPL-only scan (port 9100)
308+
zday printer scan --ip-stub 192.168.1
309+
310+
# With optional HTTP fallback (also probe port 80)
311+
zday printer scan --ip-stub 192.168.1 --scan-http-port 80
312+
313+
# JSON output
314+
zday --json printer scan --ip-stub 192.168.1
315+
```
316+
317+
The `notes` field in discovered printers records the discovery method:
318+
- `"zpl"` — found via ZPL port 9100 only
319+
- `"http(80)"` — found via HTTP only
320+
- `"zpl+http(80)"` — found via both ZPL and HTTP
321+
242322
#### Migration from 0.5.x
243323

244324
The old commands `zday_start` and `zday_quickstart` still work but are deprecated:
@@ -591,7 +671,10 @@ import zebra_day.print_mgr as zdpm
591671
592672
zlab = zdpm.zpl()
593673
594-
zlab.probe_zebra_printers_add_to_printers_json('192.168.1') # REPLACE the IP stub with the correct value for your network. This may take a few min to run. !! This command is not required if you've sucessuflly run the quickstart already, also, won't hurt.
674+
# Scan via ZPL port 9100 (default). REPLACE the IP stub with your network.
675+
zlab.probe_zebra_printers_add_to_printers_json('192.168.1')
676+
# Optional: also probe HTTP port 80 for web-based discovery
677+
# zlab.probe_zebra_printers_add_to_printers_json('192.168.1', scan_http_port=80)
595678
596679
print(zlab.printers) # This should print out the config dict of all detected zebra printers. An empty dict, {}, is a failure of autodetection, and manual creation of the config file may be needed. If successful, the lab name assigned is 'default', this may be edited later.
597680
@@ -666,8 +749,8 @@ Returns a list of all configured lab identifiers.
666749
#### `zd.query_printers(lab: str) -> Dict[str, Dict]`
667750
Returns a dictionary of printers for the specified lab. Raises `KeyError` if lab doesn't exist.
668751
669-
#### `zd.scan(ip_stub: str = "192.168.1", lab: str = "default") -> None`
670-
Scans the network range (`{ip_stub}.0` to `{ip_stub}.255`) for Zebra printers and adds them to the specified lab.
752+
#### `zd.scan(ip_stub: str = "192.168.1", lab: str = "default", scan_http_port: int | None = None) -> None`
753+
Scans the network range (`{ip_stub}.0` to `{ip_stub}.255`) for Zebra printers via ZPL port 9100 and adds them to the specified lab. Optionally pass `scan_http_port=80` for HTTP fallback discovery.
671754
672755
#### `zd.print_zpl(lab, printer_name, label_zpl_style, uid_barcode='', alt_a='', ..., alt_f='') -> str`
673756
Sends a print job to the specified printer. Returns the ZPL string sent.

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ dependencies = [
3737
"jinja2>=3.1.0",
3838
"pydantic>=2.0.0",
3939
"python-multipart>=0.0.6",
40-
"typer>=0.9.0",
41-
"rich>=13.0.0",
40+
"cli-core-yo>=0.2.1",
41+
"typer>=0.21.0,<0.22.0",
42+
"rich>=14.0.0,<15.0.0",
4243
"pillow>=10.0.0",
4344
"zint-bindings>=1.2.0",
4445
"httpx",

0 commit comments

Comments
 (0)