Spooligan Home

The manual

Spooligan

Your Personal Paper Pusher.

Spooligan prints short stacks of blocks on 80 mm or 58 mm thermal receipt paper. People type short commands; scripts and agents send whole documents in YAML or JSON. Both go through one engine, so spooligan list milk eggs and a YAML list print the same receipt.

The Spooligan mascot, a paper roll with round glasses and a green tie, waving
spooligan hello
1

Shortcuts

Built-in templates as commands: note, list, todo, qr and more.

spooligan note "Back in 5" --alert
2

Documents

A YAML or JSON file: a version, a few settings and a list of blocks.

spooligan print day.yaml
3

Templates

Your own YAML templates with {{params}}, and x- blocks made of blocks.

spooligan standup "Ship it"

Step one

Getting started

Install it

Spooligan is a single program, the spooligan command, with nothing else to install. On a Mac, download and open Spooligan.pkg (macOS 13 or later, Apple silicon and Intel): it adds the signed, notarized command to /usr/local/bin. On Linux, or in a Mac terminal, run:

curl -fsSL https://spooligan.ai/install.sh | sh

It picks the build for your computer, checks it against the published checksums and puts it in /usr/local/bin, or in ~/.local/bin when it can't write there. The guide for AI agents is built in, so tell your AI to run spooligan agents.

Say hello

spooligan hello prints a welcome page with the mascot, today's date and where to go next. Every command prints on your default printer, unless you add -o:

  • -o FILE.png writes the receipt as a 1-bit picture, exactly what the printer would get.
  • -o FILE.bin writes the ESC/POS bytes.
  • -n (--dry-run) renders and lints, and writes nothing.
$ spooligan hello -o hello.png
wrote hello.png (576×753 dots, 94 mm long)
$ spooligan print day.yaml -n
ok: day renders (576×515 dots, 64 mm long)

Note Every command in this manual ran when it was generated, in a fresh home and project folder whose printer is a file. The output shown is what it printed.

A rendered receipt

Shortcuts

The built-in templates double as commands. Bare words fill the main param; --title, key=value and flags like --alert set the rest. Plain text on stdin works too.

$ echo "Back in 5" | spooligan note -o note.png
wrote note.png (576×508 dots, 64 mm long)

Demos

spooligan demo lists seven demos that, between them, use every block. spooligan demo all -o DIR writes one picture per demo.

$ spooligan demo
text       Type and textlists      Lists and tablesdata       Numbers and chartsshop       Receipts and codespictures   Pictureslayout     Layoutdraw       Drawingspooligan demo NAME prints one (-o FILE.png to look first); spooligan demo all -o DIR writes them all.
$ spooligan demo all -o demos
wrote demos/text.png (576×2381 dots, 298 mm long)wrote demos/lists.png (576×2186 dots, 273 mm long)wrote demos/data.png (576×1074 dots, 134 mm long)wrote demos/shop.png (576×1462 dots, 183 mm long)wrote demos/pictures.png (576×2132 dots, 266 mm long)wrote demos/layout.png (576×1649 dots, 206 mm long)wrote demos/draw.png (576×1084 dots, 136 mm long)

The format

Documents

A document is YAML or JSON: version: 1, optional settings (style, lang, paper, brand, seed, tune, labels) and a list of blocks. A bare string in the list is a paragraph.

day.yaml
version: 1lang: en          # labels and datespaper: 80         # or 58blocks:  - {type: header, icon: list-checks, kicker: "@todo", title: Saturday}  - {type: section, text: Produce, count: auto}  - type: checklist    items: [Milk, {text: Eggs, qty: 12}, {text: Bread, done: true}]  - {type: kv, rows: {Network: Home, Password: hunter2}}  - A bare string is a paragraph of **text**.  - {type: end, text: Have a good day}
$ spooligan print day.yaml -o day.png
wrote day.png (576×515 dots, 64 mm long)

Text fields take **bold** and *italic*. @key prints a label from the locale (@todo, @cut_here) or the document's labels:. {today}, {today:long}, {time} and {date:+1d} print dates in the document's language.

A rendered receipt

Validate

Mistakes come back all at once, each with its path, line and a hint. --json gives agents the same.

bad.yaml
version: 1blocks:  - {type: heding, text: Hi}  - type: checklist    items: [Milk, {txt: Eggs}]  - {type: text, text: Big, size: huge}
$ spooligan validate bad.yaml
bad.yaml: invalid  blocks[0].type: unknown block type "heding" (did you mean heading?) [line 3]  blocks[1].items[1].txt: unknown field (did you mean text?) [line 5]  blocks[1].items[1].text: required [line 5]  blocks[2].size: "huge" isn't a size (use xs, s, m, lead, l, xl, xxl or a number of dots) [line 6]
exit 2
$ spooligan validate bad.yaml --json | head -12
{  "ok": false,  "results": [    {      "checked": "document",      "errors": [        {          "hint": "did you mean heading?",          "line": 3,          "message": "unknown block type \"heding\"",          "path": "blocks[0].type"        },
exit 2

Editors and agents can check documents against the JSON Schema.

$ spooligan schema > spooligan-v1.schema.json

Fields every block takes

FieldTypeWhat it does
idtextA name for this block, used in error messages and warnings. It also seeds hand-drawn strokes, so they stay put when the block moves.
alignleft · center · rightHorizontal alignment where the block supports it.
spacetextExtra space before the block: xs, s, m, l, xl or dots.
upperbooleanPrint the text in capitals (Greek capitals drop their accents).
faceauto · sans · serif · mono · condensed · handTypeface family for this block and its children; auto (the default) inherits.
ifanyTemplate condition. A false or empty value drops the block.

Side by side

Anatomy of a receipt

One document, rendered by the real binary. Each note sits beside the rows its block printed, measured by the renderer, with the YAML that made it. Hover or tap a note to match it to its rows.

The whole document
# The document the manual's "Anatomy of a receipt" section renders and annotates, block by block.version: 1blocks:  - {type: header, icon: coffee, kicker: Weekly report, meta: "Week 44", title: Corner Café}  - "A **good week**: more regulars, a busier Saturday and the new *oat flat white* is a hit. Numbers in `mono` read at a glance."  - type: metric    items:      - {label: Sales, value: $8.4k, delta: +12%}      - {label: Orders, value: "1,206", delta: +9%}      - {label: Waste, value: 2.1%, delta: −0.6}  - {type: chart, kind: sparkline, unit: k, data: [{label: Mon, value: 0.9}, 1.0, 1.1, 1.0, 1.3, {label: Sat, value: 1.9}, {label: Sun, value: 1.2}]}  - {type: section, text: Best sellers, count: 4 items}  - {type: chart, data: [{label: Flat white, value: 412}, {label: Croissant, value: 288}, {label: Oat latte, value: 241}, {label: Tea, value: 120}]}  - {type: progress, label: Monthly goal, value: 72, text: $26k of $36k}  - type: table    columns: [Shift, Staff, Sales]    rows:      - [Morning, 3, "$4,120"]      - [Lunch, 2, "$2,830"]      - [Afternoon, 2, "$1,450"]    total: [Week, "", "$8,400"]  - {type: callout, tone: alert, title: Fridge 2, text: Service visit on Tuesday at 7:00.}  - type: timeline    marker: time    items:      - {time: "Mon", title: New menu boards, text: Printed and up by 8.}      - {time: "Thu", title: Supplier call}      - {time: "Sat", title: Latte art class, text: 10 seats.}  - {type: end, text: Thanks team, small: corner.example}
  1. 1header133 dots

    The top of a receipt: an icon and small-caps kicker, a date or meta on the right, a big title and a heavy rule.

    - type: header  icon: coffee  kicker: Weekly report  meta: "Week 44"  title: Corner Café
  2. 2text102 dots

    A paragraph.

    - "A **good week**: more regulars, a busier Saturday and the new *oat flat white* is a hit. Numbers in `mono` read at a glance."
  3. 3metric141 dots

    Big numbers with a label, a change (+ or −) and a note, in boxes side by side.

    - type: metric  items:    - {label: Sales, value: $8.4k, delta: +12%}    - {label: Orders, value: "1,206", delta: +9%}    - {label: Waste, value: 2.1%, delta: −0.6}
  4. 4chart168 dots

    A bar chart, a column chart or a sparkline from numbers or {label, value} points.

    - type: chart  kind: sparkline  unit: k  data:    - {label: Mon, value: 0.9}    - 1.0    - 1.1    - 1.0    - 1.3    - {label: Sat, value: 1.9}    - {label: Sun, value: 1.2}
  5. 5section55 dots

    A section title in dark capitals with a rule to the right and an optional count.

    - {type: section, text: Best sellers, count: 4 items}
  6. 6chart142 dots

    A bar chart, a column chart or a sparkline from numbers or {label, value} points.

    - type: chart  data:    - {label: Flat white, value: 412}    - {label: Croissant, value: 288}    - {label: Oat latte, value: 241}    - {label: Tea, value: 120}
  7. 7progress69 dots

    A progress bar or row of segments with a label and a value.

    - type: progress  label: Monthly goal  value: 72  text: $26k of $36k
  8. 8table180 dots

    A table with a header row; numbers align right; an optional total row.

    - type: table  columns: [Shift, Staff, Sales]  rows:    - [Morning, 3, "$4,120"]    - [Lunch, 2, "$2,830"]    - [Afternoon, 2, "$1,450"]  total: [Week, "", "$8,400"]
  9. 9callout106 dots

    A boxed aside: note (dashed, serif), tip (rounded, bulb), goal (side bar) or alert (inverted).

    - type: callout  tone: alert  title: Fridge 2  text: Service visit on Tuesday at 7:00.
  10. 10timeline214 dots

    Numbered steps or timed events joined by a line, each with a title, text and note.

    - type: timeline  marker: time  items:    - time: "Mon"      title: New menu boards      text: Printed and up by 8.    - {time: "Thu", title: Supplier call}    - {time: "Sat", title: Latte art class, text: 10 seats.}
  11. 11end78 dots

    The bottom of a receipt: three dots and an optional line of text.

    - {type: end, text: Thanks team, small: corner.example}

Reference

Blocks

All 34 block types. Each example is the YAML on the left and what the binary printed for exactly that YAML on the right, at one printer dot per pixel. spooligan blocks TYPE shows the same fields in the terminal.

Show every example in

Text

Titles, paragraphs and the top and bottom of a receipt.

header

base

The top of a receipt: an icon and small-caps kicker, a date or meta on the right, a big title and a heavy rule.

A receipt's top
- type: header  icon: list-checks  kicker: To do  meta: "Sat 31 Oct"  title: Saturday
A receipt's top, modern

576 × 144 dots · 80 mm · tap to zoom

No icon, no rule
- type: header  kicker: Weekly report  title: Week 44  rule: false
No icon, no rule, modern

576 × 112 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
icon text —Icon name, e.g. list-checks.
kicker text —Small-caps label beside the icon.
meta true or text —Right-hand text; true prints today's date.
title text —The big title. It shrinks to fit one line, then wraps.
size text xxlTitle size: a step or dots.
rule boolean trueDraw the heavy rule under the header.

Plus the fields every block takes: id, align, space, upper, face, if.

kicker

base

A small-caps label with an optional icon: mark (a short bar under it), bar (inverted full width), rules or plain.

The four styles
- type: kicker  text: Do not disturb  icon: bell- type: kicker  text: Kitchen  icon: utensils  style: rules- type: kicker  text: Out of office  style: bar- type: kicker  text: Plain label  style: plain
Lint warning
blocks[1]: no icon "utensils"; leaving it out (run `spooligan icons` for the list)
The four styles, modern

576 × 246 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
textrequired text —The label.
icon text —Icon name.
style mark · bar · rules · plain markmark: a short bar under it; bar: inverted full width; rules: lines either side; plain: text only.

Plus the fields every block takes: id, align, space, upper, face, if.

heading

base

A heading, level 1 to 3.

Levels 1 to 3
- {type: heading, text: Network}- {type: heading, text: Guest Wi-Fi, level: 2}- type: heading  text: Ask at the desk  level: 3
Levels 1 to 3, modern

576 × 149 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
textrequired text —
level integer 11 (34 dots), 2 (28) or 3 (24).
size text —Overrides the level's size: a step or dots.

Plus the fields every block takes: id, align, space, upper, face, if.

text

base

A paragraph. **bold** and *italic* work.

Rich text
- type: text  text: "Hello **there**. Text wraps to the paper, takes *italic* and **bold**, and prints `{today:long}` as a date: {today:long}."- type: text  text: Small print in the small size.  size: s
Rich text, modern

576 × 174 dots · 80 mm · tap to zoom

A bare string is a paragraph
- The shortest block is just a string. It prints as **text**.
A bare string is a paragraph, modern

576 × 78 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
textrequired text —The words. **bold** and *italic* work; @key prints a label.
size text mA step (xs, s, m, lead, l, xl, xxl) or dots.
weight regular · medium · semibold · bold · black · italic regular
lh number 1.38Line height as a multiple of the size.

Plus the fields every block takes: id, align, space, upper, face, if.

fit

big base

Text set as large as it fits in a few lines.

As large as it fits
- {type: fit, text: Back in 10 minutes}
As large as it fits, modern

576 × 444 dots · 80 mm · tap to zoom

One huge word
- {type: big, text: OPEN, lines: 1}
One huge word, modern

576 × 176 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
textrequired text —
max number 150Largest size in dots.
min number 36Smallest size in dots; below it the text wraps past lines.
lines integer 6Most lines to use.

Plus the fields every block takes: id, align, space, upper, face, if.

section

base

A section title in dark capitals with a rule to the right and an optional count.

Titles with counts
- {type: section, text: Produce, count: auto}- type: checklist  items:    - Apples    - {text: Basil, done: true}    - Lemons- type: section  text: Bakery  icon: croissant  count: 2 items
Lint warning
blocks[2]: no icon "croissant"; leaving it out (run `spooligan icons` for the list)
Titles with counts, modern

576 × 278 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
textrequired text —The label.
count auto, text or {label, n} —auto counts the next list (left/total); or any text.
icon text —Icon before the label.

Plus the fields every block takes: id, align, space, upper, face, if.

signature

base

A sign-off: "— name" and an optional time.

A sign-off
- type: text  text: "Dinner's in the oven. Back by eight."- {type: signature, name: Sam, time: "18:05"}
A sign-off, modern

576 × 112 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
name text —Who it's from; printed as "— name".
time true or text —true prints the time now; or any text.
prefix text — Text before the name.

Plus the fields every block takes: id, align, space, upper, face, if.

end

base

The bottom of a receipt: three dots and an optional line of text.

The bottom of a receipt
- type: end  text: Have a good day  small: "#0042 · thanks"
The bottom of a receipt, modern

576 × 86 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
text text —A closing line. **bold** works.
small text —A small mono line under it.
mark boolean trueDraw the three dots.

Plus the fields every block takes: id, align, space, upper, face, if.

Lists

Things to tick off, look up or follow in order.

list

numbered base

A list with bullets, numbers, rings, Q1 Q2 or icons, and optional answer boxes on the right.

Markers
- type: list  marker: number  items:    - Preheat the oven    - Mix the batter    - Bake 25 min- type: list  marker: ring  items: [Passport, Charger]
Markers, modern

576 × 188 dots · 80 mm · tap to zoom

Questions with answer boxes
- type: list  marker: q  trailing: box  items:    - "Is the lab booked?"    - "Who brings the projector?"
Questions with answer boxes, modern

576 × 80 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
itemsrequired list —
marker bullet · number · ring · q · icon · check · none bullet
trailing box · none nonebox adds an empty tick box on the right of each item.
size text m
start integer 1First number for marker: number or q.

Plus the fields every block takes: id, align, space, upper, face, if.

checklist

base

Tick boxes with optional quantities, notes, done and priority marks; or sections of them.

Quantities, notes and done marks
- type: checklist  items:    - Milk    - {text: Eggs, qty: 12}    - text: Sourdough      note: from the corner bakery    - {text: Coffee beans, done: true}
Quantities, notes and done marks, modern

576 × 248 dots · 80 mm · tap to zoom

Sections
- type: checklist  sections:    - title: Produce      items: [Apples, Spinach]    - title: Dairy      items: [Yoghurt, {text: Butter, done: true}]
Sections, modern

576 × 326 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
items list —The items. Use this or sections.
sections list —Titled groups of items, each with a count.
rules boolean trueDotted lines between items.
count boolean trueShow left/total beside section titles.
size text 27Item text size: a step or dots.

Plus the fields every block takes: id, align, space, upper, face, if.

kv

base

Key and value rows with dot leaders.

Key and value rows
- type: kv  rows:    Network: Home    Password: hunter2    Band: 5 GHz
Key and value rows, modern

576 × 98 dots · 80 mm · tap to zoom

Mono values, no leaders
- type: kv  mono: true  leader: false  rows:    - [Order, "#10442"]    - [Placed, "30 Oct 2026"]
Mono values, no leaders, modern

576 × 68 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
rowsrequiredor items map or list of pairs —
leader boolean trueDot leaders between key and value.
mono boolean —Values in the monospace face.
size text 21

Plus the fields every block takes: id, align, space, upper, face, if.

table

base

A table with a header row; numbers align right; an optional total row.

Numbers align right, with a total
- type: table  columns: [Item, Qty, Price]  rows: [[Coffee, 2, 7.00], [Bagel, 1, 3.50]]  total: [Total, "", 10.50]
Numbers align right, with a total, modern

576 × 155 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
columnsrequired list —
rowsrequired list —
total list —A bold row under a rule.
size text 20
lines boolean trueDotted lines between rows.

Plus the fields every block takes: id, align, space, upper, face, if.

tracker

data

Rows to tick off as you go: an exercise, habit or task with circles or boxes on the right, one per set or day.

Sets to tick off
- type: tracker  items:    - text: Squats      sets: 3      reps: 10      weight: 60 kg      rest: 90s    - {text: Plank, time: 45s, count: 3}
Sets to tick off, modern

576 × 144 dots · 80 mm · tap to zoom

A habit week in boxes
- type: tracker  marks: box  count: 7  items: [Water plants, Read 20 min]
A habit week in boxes, modern

576 × 92 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
itemsrequired list —
marks circle · box circle
count integer —Marks per row when a row doesn't say (default 1).
rules boolean trueDotted lines between rows.
size text 25Name size.

Plus the fields every block takes: id, align, space, upper, face, if.

timeline

data

Numbered steps or timed events joined by a line, each with a title, text and note.

Numbered steps
- type: timeline  items:    - {title: Preheat, text: Oven to 220°C.}    - title: Bake      text: 25 minutes.      note: Until golden.
Numbered steps, modern

576 × 209 dots · 80 mm · tap to zoom

Timed events
- type: timeline  marker: time  items:    - {time: "09:00", title: Stand-up}    - time: "13:30"      title: Design review      text: Room 4.
Timed events, modern

576 × 139 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
itemsrequired list —
marker number · dot · time numbernumber: each number in a heavy ring (a filled circle with --tune knockout=0); dot: small rings; time: each item's time on the left.
start integer 1
line boolean trueJoin the markers with a line.

Plus the fields every block takes: id, align, space, upper, face, if.

Data

Numbers that read at a glance, on paper.

chips

data

A row of rounded labels that wraps: outlined, filled (dark with a dot), or the first one filled.

Outlined and first filled
- type: chips  items: [45 min, Serves 4, Vegetarian]- type: chips  style: first  items:    - Board pack    - {text: On track, icon: circle-check-big}    - 3 risks
Outlined and first filled, modern

576 × 82 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
itemsrequired list —
style outline · inverted · first outlinefirst fills the first chip only.
size text 17

Plus the fields every block takes: id, align, space, upper, face, if.

metric

kpi data

Big numbers with a label, a change (+ or −) and a note, in boxes side by side.

KPI tiles with deltas
- type: metric  items:    - label: Revenue      value: $48.2k      delta: +12%      note: vs Sept    - label: Churn      value: 2.1%      delta: −0.4
KPI tiles with deltas, modern

576 × 170 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
itemsrequired list —
cols integer —Boxes per row. Default: up to 3 on 80 mm, 2 on 58 mm.
size text xlLargest value size; long values shrink to fit.
boxed boolean trueDraw a box around each number.

Plus the fields every block takes: id, align, space, upper, face, if.

chart

barssparkline data

A bar chart, a column chart or a sparkline from numbers or {label, value} points.

Bars
- type: chart  unit: "%"  data:    - {label: Rent, value: 42}    - {label: Food, value: 27}    - {label: Travel, value: 18}    - {label: Other, value: 13}
Bars, modern

576 × 150 dots · 80 mm · tap to zoom

Columns and a sparkline
- type: chart  kind: columns  data:    - {label: Mon, value: 4}    - {label: Tue, value: 7}    - {label: Wed, value: 3}    - {label: Thu, value: 6}    - {label: Fri, value: 8}- type: sparkline  unit: "°"  data: [11, 12, 14, 17, 19, 18, 16, 13]
Columns and a sparkline, modern

576 × 368 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
kind bar · columns · sparkline bar
datarequired list —
height number —Plot height in dots for columns and sparkline (default 150 and 70).
max number —The value a full bar stands for. Default: the largest value.
unit text —Appended to printed values, e.g. % or k.
values boolean truePrint the values.

Plus the fields every block takes: id, align, space, upper, face, if.

progress

data

A progress bar or row of segments with a label and a value.

A bar and segments
- {type: progress, label: Savings, value: 62}- type: progress  label: Chapters  value: 7  max: 10  text: 7 of 10  style: segments
A bar and segments, modern

576 × 146 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
valuerequired number —
max number 100
label text —
text text —Printed on the right instead of the percentage, e.g. 3 of 5.
style bar · segments bar
segments integer 10How many segments for style: segments.

Plus the fields every block takes: id, align, space, upper, face, if.

lines

data

Ruled lines to write on.

Ruled lines to write on
- {type: section, text: Notes}- {type: lines, count: 3}
Ruled lines to write on, modern

576 × 191 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
count integer 4
style dotted · solid · dashed dotted
gap number 38Dots between lines.

Plus the fields every block takes: id, align, space, upper, face, if.

Commerce

Orders, codes and the brand at the top.

line-items

data

An order's items with prices, quantities and chips, then subtotal, shipping, discount, tax and a boxed total.

An order with totals
- type: line-items  currency: $  shipping: 4.5  items:    - {name: Notebook, price: 12, qty: 2}    - name: Pen      meta: Blue · fine      price: 3.25
An order with totals, modern

576 × 366 dots · 80 mm · tap to zoom

Fields 9
FieldTypeDefaultWhat it does
itemsrequired list —
currency text $$ and £ print first (\$1,234.56); others after (1.234,56 €).
header boolean trueThe ITEM · N ITEMS / PRICE header.
check boolean trueA tick box before each item, for packing.
shipping number —
discount number —Subtracted from the total.
tax number —
total number —Overrides the computed total.
totals boolean truePrint subtotal, extras and the total box.

Plus the fields every block takes: id, align, space, upper, face, if.

barcode

data

A Code 128 or EAN-13 barcode with the text under it.

Code 128 and EAN-13
- {type: barcode, data: SPOOL-2026}- type: barcode  kind: ean13  data: "400638133393"  height: 70
Code 128 and EAN-13, modern

576 × 256 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
datarequired text —
kind code128 · ean13 code128ean13 takes 12 digits (the check digit is added) or 13.
height number 90Bar height in dots.
module number —Dots per bar module. Default: as wide as fits, up to 4.
text boolean truePrint the data under the bars.

Plus the fields every block takes: id, align, space, upper, face, if.

Media

Pictures, QR codes and banners.

image

base

A picture from a file path or a data: URI, dithered to black and white.

A greyscale photo, dithered
- type: image  src: "spooligan:photo"  width: 80%  dither: atkinson  caption: The view
A greyscale photo, dithered, modern

576 × 305 dots · 80 mm · tap to zoom

A black-and-white picture, dot for dot
- type: image  src: "spooligan:mascot"  width: native
A black-and-white picture, dot for dot, modern

576 × 272 dots · 80 mm · tap to zoom

Fields 7
FieldTypeDefaultWhat it does
srcrequiredor path text —A file path (beside the file that names it, then assets/), a data: URI, or a built-in picture: spooligan:mascot, spooligan:photo.
width full, dots or N% fullfull, dots at 80 mm, a percentage, or native: one picture pixel per dot.
dither auto · floyd · atkinson · ordered · threshold · none autoauto passes black-and-white pictures through untouched and dithers the rest with floyd.
threshold integer 150Cut-off for dither: threshold (0-255).
invert boolean —Swap black and white.
caption text —A small line under the picture.
edge boolean —Run to the paper edge (into the margin).

Plus the fields every block takes: id, align, space, upper, face, if.

qr

base

A QR code with an optional caption.

Below or beside
- type: qr  data: "https://example.com"  caption: example.com  size: 160- type: qr  data: "https://example.com/menu"  layout: side  title: Today's menu  caption: example.com/menu  size: 140
Below or beside, modern

576 × 343 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
datarequired text —The text or URL to encode.
size number 220Side in dots.
caption text —A mono line under the code.
ec L · M · Q · H MError correction.
title text —A bold line beside (layout: side) or above the caption.
layout below · side belowside puts the title and caption to the right of the code.

Plus the fields every block takes: id, align, space, upper, face, if.

banner

base

Huge sideways lettering that runs along the paper, edge to edge, for signs and banners. Lines stack across the paper.

Sideways lettering
- {type: banner, text: Hi!, ornament: heart}

A banner runs along the paper, so the picture is long. Shown here at a short length.

Sideways lettering, modern

576 × 993 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
textrequired text —New lines stack the words across the paper.
style bold · outline · inverted · serif bold
border boolean trueDouble rails along both edges.
ornament star · sparkle · diamond · heart · dot · none —At both ends. Default: star, or sparkle for serif.
direction down · up downWhich way the text reads as the paper comes out.
caps boolean —Capitals. Default: on, off for serif.

Plus the fields every block takes: id, align, space, upper, face, if.

Drawing

Shapes, paths, text and icons in a box.

draw

base

Free drawing: lines, polylines, polygons, rectangles, circles, ellipses, SVG-style paths, text and icons in a box. Coordinates are dots at 80 mm, or units of view: [w, h] stretched to the width. The handwritten style (or face: hand) draws it with a pen wobble.

Shapes, paths, text and icons
- type: draw  view: [520, 200]  stroke: rule  shapes:    - shape: line      points: [[0, 180], [520, 180]]      stroke: bold    - shape: rect      x: 60      y: 90      w: 140      h: 90      radius: 6    - shape: polygon      points: [[50, 92], [130, 30], [210, 92]]      fill: black    - shape: circle      x: 430      y: 50      r: 28      fill: black    - shape: path      d: "M 260 180 Q 300 120 340 180"      dash: true    - shape: text      x: 430      y: 104      text: Home      size: l      font: black      align: center    - shape: icon      icon: heart      x: 300      y: 20      size: 40
Shapes, paths, text and icons, modern

576 × 216 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
shapesrequiredor items list —Drawn in order; later shapes paint over earlier ones.
view list —[width, height] of the coordinate space, stretched to the drawing's width. Without it coordinates are dots at 80 mm.
height number —Height in dots at 80 mm. Default: from view, or the lowest shape.
width full, dots or N% fullfull, dots, or a percentage of the column.
stroke object ruleDefault line width for the shapes.
exact boolean —Keep lines exact in the handwritten style.

Plus the fields every block takes: id, align, space, upper, face, if.

Asides

Boxed notes and pull quotes.

callout

base

A boxed aside: note (dashed, serif), tip (rounded, bulb), goal (side bar) or alert (inverted).

Note, tip and goal
- type: callout  text: The lift is out until Monday.- type: callout  tone: tip  text: Soak the beans overnight.- type: callout  tone: goal  text: "**Ask:** approve the budget by Friday."
Note, tip and goal, modern

576 × 235 dots · 80 mm · tap to zoom

Alert
- type: callout  tone: alert  title: Do not disturb  text: Recording in progress.
Alert, modern

576 × 88 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
textrequired text —
tone note · tip · goal · alert note
title text —A bold lead-in; tip defaults to the Tip label.
icon text —Overrides the tone's icon.

Plus the fields every block takes: id, align, space, upper, face, if.

quote

base

A pull quote: a big mark, centred serif italic set to fit, a short rule, and who said it.

A pull quote
- type: quote  text: Simplicity is the ultimate sophistication.  cite: Leonardo da Vinci
A pull quote, modern

576 × 363 dots · 80 mm · tap to zoom

Fields 6
FieldTypeDefaultWhat it does
textrequired text —
citeor author text —Who said it.
source text —Where it's from, in italics.
date true or text —true prints today's date under it; or any text.
mark boolean trueDraw the big quotation mark.
lines integer 7Most lines before the size drops.

Plus the fields every block takes: id, align, space, upper, face, if.

Layout

Columns, frames, turns, cuts and space.

row

base

Columns side by side. Each column is a list of blocks.

Two columns
- type: row  ratio: [2, 1]  rule: true  valign: middle  cols:    - - type: heading        text: Guest Wi-Fi        level: 2      - type: kv        rows: {Network: Guest, Password: welcome}    - - type: qr        data: "WIFI:S:Guest;T:WPA;P:welcome;;"        size: 130
Two columns, modern

576 × 141 dots · 80 mm · tap to zoom

Fields 5
FieldTypeDefaultWhat it does
colsrequiredor columns list of block lists —
ratio list —Relative column widths, e.g. [2, 1]. Equal by default.
gap number 20Dots between columns.
rule boolean —A vertical line between columns.
valign top · middle · bottom top

Plus the fields every block takes: id, align, space, upper, face, if.

frame

base

A border around blocks: door (thick rounded), tape (dashed with tape), double, dashed, inverted or none.

Door and tape frames
- type: frame  style: door  blocks:    - {type: kicker, text: Back soon}    - {type: fit, text: Out for lunch}- type: frame  style: tape  blocks: [{type: text, text: Taped to the fridge.}]
Door and tape frames, modern

576 × 786 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
blocksrequired blocks —
style door · tape · double · dashed · inverted · none door
pad number 26Dots between the border and the blocks.
bleed number 10Dots the frame reaches into the margin.

Plus the fields every block takes: id, align, space, upper, face, if.

rotate

base

Blocks turned upside down (180) or sideways (90, 270), with an optional hint line.

Upside down, for the answer
- type: text  text: "What has keys but can't open locks?"  weight: bold- type: rotate  angle: 180  label: "@flip_it"  blocks: [{type: text, text: A piano.}]
Upside down, for the answer, modern

576 × 109 dots · 80 mm · tap to zoom

Fields 4
FieldTypeDefaultWhat it does
blocksrequired blocks —
angle integer 180180, 90 or 270 degrees.
label text —A hint printed upright above, between arrows, e.g. @flip_it.
length number —For 90 and 270: how long the turned blocks may run, in dots (default: the box width).

Plus the fields every block takes: id, align, space, upper, face, if.

cut

base

A dashed cut-here line with scissors.

A cut line
- {type: text, text: Keep this half.}- {type: cut, label: "@cut_here"}- {type: text, text: Hand in this half.}
A cut line, modern

576 × 132 dots · 80 mm · tap to zoom

Fields 1
FieldTypeDefaultWhat it does
label text —Small text on the line, e.g. @cut_here.

Plus the fields every block takes: id, align, space, upper, face, if.

divider

rule base

A horizontal rule: solid, dotted, dashed, double, zigzag, wave or stars.

Every style
- {type: divider}- {type: divider, style: dotted}- {type: divider, style: dashed}- {type: divider, style: double}- {type: divider, style: heavy}- {type: divider, style: zigzag}- {type: divider, style: wave}- {type: rule, style: stars}
Every style, modern

576 × 262 dots · 80 mm · tap to zoom

Fields 3
FieldTypeDefaultWhat it does
style solid · dotted · dashed · double · zigzag · wave · stars · heavy solid
weight number 2Line weight in dots.
pad number 14Space above and below in dots.

Plus the fields every block takes: id, align, space, upper, face, if.

spacer

base

Empty space.

Space between blocks
- {type: text, text: Above}- {type: spacer, size: xl}- {type: text, text: Below}
Space between blocks, modern

576 × 124 dots · 80 mm · tap to zoom

Fields 1
FieldTypeDefaultWhat it does
sizeor height text mxs, s, m, l, xl or dots.

Plus the fields every block takes: id, align, space, upper, face, if.

Look and feel

Styles and legibility

modern is clean sans serif. handwritten writes in Playpen Sans and Caveat and draws every rule, box, checkbox and frame with a light wobble, as if by pen. Codes, prices and other mono text stay in JetBrains Mono. Set style: in the document or -s on the command line.

$ spooligan styles
modern       sans serif display, the defaulthandwritten  Playpen Sans and Caveat, pen-drawn rules and boxes (alias hand)

Another hand

The wobble is seeded by the document's seed and each block's path, so a preview and a print match dot for dot, and editing one block leaves the others' lines alone. --seed picks another hand.

58 mm paper

--paper 58 prints 384 dots across instead of 576. Sizes scale by two thirds, and layouts adapt: stat cards wrap, a header's date drops below a long kicker, and QR codes keep 3-dot modules so they scan.

Legibility settings

Thermal heat bleeds into small white-on-black type, so section titles print as dark capitals with a rule, filled chips print dark with a dot, nothing prints white on black under 20-dot capitals, and handwriting never prints under 20 dots. tune: in a document, or --tune key=value,..., changes that for one document.

KeyDefaultOther valuesWhat it does
sectionruletab · bar · outline · bold · pillSection titles: a rule, a heavy underline tab, a left bar, an outlined pill, a bigger knockout pill, or the old small black pill.
chipdotheavy · bold · solidFilled chips: a dot before dark text, a heavy outline, a bigger knockout, or the old small knockout.
knockout20dots, 0 for offThe smallest white-on-black capital. Blocks print dark or bigger below it; anything left warns.
hand20dots, 0 for offThe smallest handwritten size; Caveat under 28 dots switches to Playpen.
rule
A rendered receipt
section: rule, chip: dot (the default)
tab
A rendered receipt
section: tab, chip: heavy
bar
A rendered receipt
section: bar, chip: bold
outline
A rendered receipt
section: outline, chip: heavy
bold
A rendered receipt
section: bold, chip: bold
pill
A rendered receipt
section: pill, chip: solid, knockout: 0, hand: 0 (the old look)
$ spooligan print titles.yaml --tune section=pill,chip=solid,knockout=0,hand=0 -o old.png
wrote old.png (576×126 dots, 16 mm long)

Ready to print

Templates

Templates are YAML documents with params. The built-ins are also shortcut commands. Params come from bare words, key=value, --key value, flags, a data file (-d day.json, or -d - for stdin) and --example NAME. Later sources win, and values that look like YAML lists are lists.

$ spooligan templates
banner        built-in  Huge sideways lettering along the paper for a sign or a party bannercrib          built-in  A crib sheet for a meeting or appointment, with who, when and where, a goal, key facts, talking points, questions, things to bring and lines for notesdoc           built-in  A header, any blocks you like, and a closing linejoke          built-in  A joke with the punchline under a zigzag, upside down if you likelist          built-in  A shopping or to-do list with tick boxes, optional sections, quantities, notes and done marksmessage       built-in  A short message or letter with a signaturenote          built-in  Big text set as large as it fits, for a door, the fridge or a deskpacking_slip  built-in  A packing slip with your logo from a brand kit, the order number, ship-to and from, items with chips and prices, totals, a note and a QR code to the orderqr            built-in  A QR code with an optional title and captionquote         built-in  A pull quote in big serif italics, set to fit, with who said itshopping      built-in  list with kind=shoppingsteps         built-in  Step-by-step instructions or a recipe, with chips, what you'll need, numbered steps joined by a line, and a tiptext          built-in  Wrapped text, or set as large as it fits with --bigtodo          built-in  list with kind=todoworkout       built-in  A workout of the day, in parts (warm-up, main, finisher), each exercise with sets × reps, load and rest, and a circle to tick per setspooligan templates show NAME lists its params, examples and file; spooligan blocks lists your x- blocks.
$ spooligan templates show note
note: Big text set as large as it fits, for a door, the fridge or a desk. Styles are door (a framed sign), fridge (a taped sticky note), alert (an inverted bar) and plain.usage: spooligan note TEXT... [key=value] [--flags]params:  text *    text                                     The note. Short notes print huge.  style     door|fridge|alert|plain         = door   door, fridge, alert or plain. Each is also a flag: --alert.  kicker    string                          = @note  Small label above, e.g. Back soon.  icon      string                                   Icon beside the kicker, e.g. clock, bell, house.  sub       text                                     A smaller line under the big text.  from      string                                   Signature, e.g. Sam.  time      date                                     true prints the time now; or any text.  max_size  integer 1-576                   = 150    Largest text size in dots.  font      auto|sans|condensed|serif|hand  = auto   Typeface for the big text; auto follows the style.  upper     boolean                         = false  Print the big text in capitals.examples (spooligan preview note --example NAME):  door: {style: door, kicker: Back soon, icon: clock, text: Back in 15 min, sub: "Packages → leave with the concierge", from: Sam, time: "14:30"}  fridge: {style: fridge, kicker: Reminder, icon: refrigerator, text: "Take vitamins & drink water", from: Me}  quiet: {style: door, kicker: Please knock softly, icon: triangle-alert, text: "Baby sleeping", upper: true, font: condensed}  alert: {style: alert, kicker: Do not disturb, icon: bell, text: On a call, sub: "Until 3:30 pm"}
$ spooligan note "Back in 15" kicker="Back soon" --icon clock from=Sam -o note.png
wrote note.png (576×576 dots, 72 mm long)
$ spooligan list items="[milk, eggs, bread]" -o list.png
wrote list.png (576×273 dots, 34 mm long)
$ spooligan list -d day.json -o list.png
wrote list.png (576×277 dots, 35 mm long)
$ spooligan preview note --example door
wrote note.png (576×788 dots, 98 mm long)

Make it yours

Your own templates, blocks and brands

Config layers

Settings come in layers, and the first layer that sets a key wins: flags, then environment variables (SPOOLIGAN_STYLE, SPOOLIGAN_LANG, SPOOLIGAN_PAPER, SPOOLIGAN_PRINTER, SPOOLIGAN_MASCOT), then --config FILE, then the project's .spooligan/ (this folder or the nearest parent), then the global ~/.config/spooligan/, then ~/.config/receipt/config.yaml (printer settings only), then the built-ins. Flags and env beat what a document says; config files only fill in what it leaves out.

$ spooligan init
created  ~/.config/spooligan/config.yamlcreated  ~/.config/spooligan/README.mdcreated  ~/.config/spooligan/templates/created  ~/.config/spooligan/blocks/created  ~/.config/spooligan/brands/created  ~/.config/spooligan/locales/created  ~/.config/spooligan/assets/next: edit ~/.config/spooligan/config.yaml, then run spooligan config doctor
$ spooligan init --project
created  /private~/my-project/.spooligan/config.yamlcreated  /private~/my-project/.spooligan/README.mdcreated  /private~/my-project/.spooligan/templates/created  /private~/my-project/.spooligan/blocks/created  /private~/my-project/.spooligan/brands/created  /private~/my-project/.spooligan/locales/created  /private~/my-project/.spooligan/assets/next: edit /private~/my-project/.spooligan/config.yaml, then run spooligan config doctor
$ spooligan config path
flags     command line                                                                                                            env       environment                                                                                                             setproject   /private~/my-project/.spooligan/config.yaml  foundglobal    ~/.config/spooligan/config.yaml                                                                                         foundreceipt   ~/.config/receipt/config.yaml                                                                                           missingbuilt-in  built-in                                                                                                                always
$ spooligan config which lang
lang  en  built-in  built-in
$ spooligan config doctor
info  env: SPOOLIGAN_OFFLINE sets fonts.offlineok    project: /private~/my-project/.spooligan/config.yamlok    /private~/my-project/.spooligan/templates/standup.yaml: template loadsok    /private~/my-project/.spooligan/blocks/card.yaml: block loadsok    /private~/my-project/.spooligan/brands/northwind.yaml: brand loadsok    global: ~/.config/spooligan/config.yamlok    printer: default printer desk (file:/private~/my-project/.spooligan/spool.bin)no problems found (doctor never contacts a printer; spooligan printer status does)

Templates

A config layer's templates/ folder holds your templates; one named like a built-in replaces it. A template is a YAML file with kind: template, params: and blocks that use {{param}}, plus three small tools: each, if and !join.

$ spooligan templates eject note
wrote /private~/my-project/.spooligan/templates/note.yaml (a copy of the built-in, project layer)try: spooligan preview note --example door -o note.png
$ spooligan config which template note
template note -> /private~/my-project/.spooligan/templates/note.yaml (project)  shadows built-in note (built-in)
$ spooligan templates reset note
note now uses the built-in again; your copy is at /private~/my-project/.spooligan/templates/note.yaml.bak
$ spooligan templates new standup
wrote /private~/my-project/.spooligan/templates/standup.yaml (a starter, project layer)try: spooligan preview standup -o standup.png
$ spooligan standup "Ship it" "Review PR" -o standup.png
wrote standup.png (576×280 dots, 35 mm long)
.spooligan/templates/standup.yaml
version: 1kind: templatename: standupdescription: My own template. Edit the params and blocks; spooligan blocks lists what you can use.positional: itemsparams:  title: {default: standup, desc: "The heading."}  items: {type: list, desc: "One line each."}  note: {type: text, desc: "A line at the bottom."}blocks:  - {type: header, title: "{{title}}", meta: "{today}"}  - {type: checklist, if: items, items: "{{items}}"}  - {type: callout, if: note, text: "{{note}}"}  - {type: end}examples:  hello: {title: Hello, items: [Write a template, Preview it, Print it], note: "spooligan templates show standup"}
A rendered receipt

x- blocks

A layer's blocks/ folder holds x- blocks: small templates that stand in for a run of blocks, used as {type: x-NAME, ...} with the template's params as the other keys. They can use each other, up to 8 deep.

$ spooligan templates new x-card
wrote /private~/my-project/.spooligan/blocks/card.yaml (a starter, project layer)use it in a document as {type: x-card, title: ...}
.spooligan/blocks/card.yaml
version: 1kind: templatename: x-carddescription: "A card with a title, a line of text and an optional icon. Use it as {type: x-card, title: ...}."params:  title: {type: string, required: true}  text: {type: text}  icon: {desc: "An icon name, e.g. star (spooligan icons lists them)."}blocks:  - type: frame    style: double    blocks:      - {type: kicker, if: icon, text: "{{title}}", icon: "{{icon}}"}      - {type: heading, if: "!icon", text: "{{title}}"}      - {type: text, if: text, text: "{{text}}"}
cards.yaml
version: 1blocks:  - {type: x-card, title: Wi-Fi, icon: wifi, text: "Network **Home**, password on the fridge."}  - {type: x-card, title: Bins go out on Thursday}
A rendered receipt

Brand kits

A brand kit is brands/NAME.yaml in any config layer, with its images beside it. Set brand: on the document (or a logo block): {type: logo} prints the logo, show: mark the small mark with the URL, show: name the name in type. config doctor checks every kit.

The mark prints 128 dots square on 80 mm and 96 on 58 mm. Export it at those sizes as NAME-128.png and NAME-96.png next to the mark file (acme-mark-128.png for mark: acme-mark.png) and each prints dot for dot; otherwise the mark file is scaled to fit.

The logo prints 300 dots wide on 80 mm, scaled down on 58 mm. To fill the paper, export it at the full paper width as NAME-576.png (80 mm) and NAME-384.png (58 mm) next to the logo file: {type: logo} then prints the file for the paper dot for dot, edge to edge, ignoring the page margins, so draw the side margins into the file. A width: on the block, a logo inside a row or frame, or a kit without the files keeps the usual size.

.spooligan/brands/northwind.yaml
# A fictional brand kit for the golden tests. Real kits live in brands/NAME.yaml in any config layer.name: Northwindlogo: northwind-logo.pngmark: northwind-mark.pngtagline: Thanks for shopping with usurl: northwind.example
branded.yaml
version: 1brand: northwindblocks:  - {type: logo, width: 220}  - {type: text, text: Thanks for your order!, align: center}  - {type: logo, show: mark}
A rendered receipt

The mascot is swappable too: --mascot FILE, SPOOLIGAN_MASCOT or mascot: in the config replaces spooligan:mascot everywhere. hello prints it one pixel per dot (up to the paper width) on both papers, so a black-and-white PNG about 256 dots wide prints sharpest.

Everywhere

Languages and fonts

lang: (or -l) sets the labels and dates. English and Greek ship complete; Spanish is partial, and a missing label falls back to English key by key. Your own locales/ folder can add more.

Language demo: the same to-do, in Greek

$ spooligan todo "Call Sam" "Pay rent" --lang el --paper 58 -o todo.png
wrote todo.png (384×155 dots, 19 mm long)

The bundled fonts cover Latin, Greek and Cyrillic. For other scripts Spooligan borrows a glyph one character at a time: from the other bundled fonts, then fonts.scripts, then fonts.fallback, then the system fonts. fonts fetch downloads pinned, checksummed Noto fonts; --offline and fonts.mirror cover machines that can't download them.

$ spooligan fonts which "Ångström Γειά Привет"
Latin          sans  ÅöCommon         sans  ngstrmGreek          sans  ΓειάCyrillic       sans  Привет

A character no font has prints as a box with a warning; --strict makes it exit 5.

$ spooligan text "こんにちは" --strict -n
spooligan: --strict: no font has こ U+3053, ち U+3061, に U+306B, は U+306F, ん U+3093; they print as boxes. add a font for Chinese or Japanese text to fonts.scripts.han or fonts.fallback in the config
exit 5

Right to left. Hebrew and Arabic text is ordered with the Unicode bidi algorithm and Arabic letters join. Mirrored block layouts are being reworked, so this manual keeps to the basics for now.

A rendered receipt
Language demo · --lang el
spooligan fonts
$ spooligan fonts
Bundled (always there)  Inter, Noto Serif Display, Noto Sans Condensed, JetBrains Mono, Playpen Sans, CaveatDownloadable (spooligan fonts fetch NAME|SCRIPT|all)    noto-sans-hebrew       hebrew      v3.001    37 KB  not installed    noto-sans-arabic       arabic      v2.013   281 KB  not installed    noto-sans-thai         thai        v2.002    44 KB  not installed    noto-sans-devanagari   devanagari  v2.007   363 KB  not installed    noto-sans-bengali      bengali     v3.011   205 KB  not installed    noto-sans-tamil        tamil       v2.004    95 KB  not installed    noto-sans-armenian     armenian    v2.008    41 KB  not installed    noto-sans-georgian     georgian    v2.005    73 KB  not installed    noto-sans-ethiopic     ethiopic    v2.102   568 KB  not installed    noto-sans-symbols2     symbols     v2.008   660 KB  not installedInstalled in ~/Library/Application Support/spooligan/fonts

Before it prints badly

Lint warnings

A document that renders can still print badly. Rendering lints it and prints warnings; they never stop a print. -n lints without writing anything.

WarningWhen
QR modules are 2 dots wideThe code is too small for its data to scan reliably.
barcode bars are 1 dot wideCode 128 data too long for the width.
the text only fits at the smallest sizeA fit block holds a paragraph, not a few big words.
N mm of solid black12 mm or more of unbroken black across most of the width; long fills print faded and can stick to the head.
small white-on-black textAn inverted frame or bar with capitals under the knockout floor.
no glyph for …A character no font in the chain has.
$ spooligan print lint.yaml -n
warning: blocks[1]: the text doesn't fit 6 lines even at 36 dots; shorten it or allow more lineswarning: blocks[2]: 13 mm of solid black; long fills can print faded or streaky and stick to the head (use an outline or a lighter block)ok: lint renders (576×722 dots, 90 mm long)
A rendered receipt

Design by tapping

Studio

spooligan studio designs a receipt in the browser. The binary serves the page itself, with nothing from the network, and the receipt on screen is the real renderer's output, dot for dot. It works on a phone as well as a desktop.

spooligan studio day.yaml
  • Add blocks from the palette, or ready-made sections such as a report top, a KPI strip or a shop footer. The + buttons between blocks pick where they go.
  • Click any text to change it where it's printed, including text inside a row, a frame or a turned block. On a phone, the first tap picks the block and the second edits. Fields lists everything the block takes, from the schema, and opens the inner block for text inside another.
  • Names match the palette everywhere (Receipt header, Numbered list), with the YAML type small beside them. Document settings holds the reading direction: Auto, Left to right or Right to left.
  • Move blocks with Up and Down, the drag handle or a long press, or drag new ones in from the palette (on a phone, hold, then drag). Undo and redo keep up.
  • YAML changes both ways and keeps your comments. Modern/Handwritten and 80/58 mm are one tap.
  • File: start blank, from a template or a YAML file; save, save as a template, download YAML or a PNG, or copy the command that prints it.
  • Tabs: each browser tab edits one file, named in its address (/?file=receipts/day.yaml), its header and its title, and Save writes that file and no other. Open a file… lists the YAML files in the studio's folder and its subfolders and opens one in a new tab, or here; New file… makes an empty one. Import a copy… opens a file from anywhere as an untitled copy, and Save then asks for a name (Save as…) and asks again before it replaces a file. The same file in two tabs says Also open in another tab.
  • One studio: spooligan studio other.yaml while a studio is running for that folder opens the file there in a new tab instead of starting another; --new starts a separate one.
  • Edits from elsewhere, such as an agent changing the file, reload in the open tab, and Save warns before it would overwrite changes made on disk.
  • Print asks first and names the printer and its paper.

Each block gets an id: the first time you add or move one, and the handwritten strokes follow the id, so a block keeps its look when it moves. Blocks without an id draw as they always did.

The studio's folder is the one you start it in, or the file's own folder when the file lives elsewhere. Tabs open and save only .yaml and .yml files inside it; hidden files aren't listed, and paths that lead out of it are refused.

Important The studio listens on 127.0.0.1, and the link it prints carries a random token that every request needs. --lan makes it reachable from a phone on the same network: it prints a warning and a link per address, and anyone with the link can edit, save and print. --port N picks the port (7878, or a free one) and --no-open skips opening the browser.

On paper

Printing and config

Without -o a receipt goes to the default printer from the config. Transports are usb, network, serial, cups, file and windows. Jobs are raster only (ESC @, GS v 0 bands, feeds and the cut) and never contain speed, density or buzzer commands.

# ~/.config/spooligan/config.yamlprinter: desk                   # the default for print and the shortcutsprinters:  desk: {type: usb, vendor_id: 0x0483, product_id: 0x5840}  kitchen: {type: network, host: 192.168.1.40, paper: 58}  dump: {type: file, path: ./spool.bin}   # relative to this fileoutput: {cut: true, feed_after: 0}
$ spooligan printer list
* desk     file:/private~/my-project/.spooligan/spool.bin           project  kitchen  file:/private~/my-project/.spooligan/kitchen.bin  58 mm  project
$ spooligan print day.yaml
printed day on desk (file:/private~/my-project/.spooligan/spool.bin) (576×515 dots, 64 mm long)
$ spooligan print day.yaml -P kitchen
printed day on kitchen (file:/private~/my-project/.spooligan/kitchen.bin) (576×515 dots, 64 mm long)
$ spooligan print day.yaml -o day.bin
wrote day.bin (576×515 dots, 64 mm long)
$ spooligan printer send day.bin -P kitchen
sent day.bin to kitchen (file:/private~/my-project/.spooligan/kitchen.bin) (37110 bytes)
$ spooligan printer test -o test.png
wrote test.png
A rendered receipt

Tip Also: spooligan printer setup finds a USB receipt printer and saves it as the default (it changes nothing when one is set up), spooligan printer list --scan lists USB printers and their ids, and spooligan printer status NAME reads the paper sensor (exit 3 when the paper is out). spooligan printer buzzer off turns a NETUM NT-861's beeps off (on brings them back): it is the only command that writes a setting into a printer, runs only on a printer it knows is a NETUM, shows the bytes and asks first (-n sends nothing). Power-cycle the printer afterwards. For USB debugging, SPOOLIGAN_USB_TRACE=1 logs every transfer and SPOOLIGAN_USB_DUMP=FILE saves the bytes the printer acknowledged.

What's new

Updates

spooligan update --check says whether a newer Spooligan is out: Spooligan 0.2.0 is out (you have 0.1.0): run spooligan update, or Spooligan 0.1.0 is up to date. spooligan update installs it, and spooligan update -n says what it would do without changing anything. It reads a small release note from spooligan.ai and believes it only when its signature matches the release key built into Spooligan, so a damaged or forged note is ignored. It sends nothing about you or your computer. When a release has notes, it links them under "What's new"; not every release does.

When you use Spooligan at a terminal, it also checks at most once a day, in the background while your command runs, and tells you after the command when there's something new: Spooligan 0.2.0 is available (you have 0.1.0). Run: spooligan update, with a "What's new" link when the release has notes. It never slows a command down noticeably: on a slow connection the check finishes in the background after the command, and the next command tells you. It says nothing when it can't reach spooligan.ai, and tries again an hour later. It stays quiet in pipes, scripts and --json output, and you can turn it off with update_check: false in your config or SPOOLIGAN_NO_UPDATE_CHECK=1; it's off when CI is set.

Before anything is replaced, the download must match the size and SHA-256 in the release note, carry Spooligan's Developer ID signature on a Mac, and run as the new version. The old copy is kept as spooligan.old, and the new one takes its place in one step, so a failed update leaves the old one working. spooligan update --rollback puts the previous version back, and running it again switches forward.

If Spooligan is in a folder you can't write, such as /usr/local/bin, it says why and asks Installing into /usr/local/bin needs your password. Continue? [y/N]. Yes runs only the file steps with sudo, which asks for your password itself; --yes skips the question, and without a terminal it changes nothing and tells you what to run. A copy installed with Spooligan.pkg is updated with the new signed package (sudo installer) and has no rollback; for an older version, use the download page. A copy from Homebrew is left to brew upgrade spooligan.

It exits 0 when it's done or you're up to date, 10 when a newer release is out (--check, -n), 6 when spooligan.ai can't be reached, 7 when the note can't be trusted, 8 when the download fails a check (nothing changes) and 9 when it needs you (you said no, or there's no terminal to ask). --json gives status (up_to_date, update_available, newer, installed or rolled_back), current, latest, released and, when there are notes, notes_url.

Releases

0.1.1Update checks wait longer on slow connections, and the daily notice still finds a new release when connecting is slow.
0.1.0The first release.

Every command and flag

CLI reference

From spooligan --help at generation time.

Commands

print SRC [params]print a document, template file or built-in template (or -o FILE)
preview SRC [params]render to SRC.png, or -o FILE
validate SRC...check documents; exit 2 if any is invalid
blocks [TYPE]list block types, or one type's fields
schema [TYPE]the JSON Schema for documents, or one block
templates [show NAME]list built-in templates, or one template's params
demo [NAME|all]list the demos, or print one: together they show every block
helloprint a hello page with the mascot (--mascot FILE swaps it)
styleslist styles
iconslist icon names for icon: fields
fonts [list]bundled fonts and the Noto packs you can fetch
fonts which TEXTwhich font draws each script in TEXT
fonts doctorcheck fonts.scripts, fonts.fallback and fetched packs
fonts fetch SCRIPT...download pinned, checksummed Noto fonts (hebrew, thai, ... or all)
fonts mirror DIRcopy the packs into DIR, for fonts.mirror on machines without internet
licenses [NAME]third-party fonts, icons and modules, or one licence in full
printer setupfind the USB receipt printer and make it the default
printer [list|status|test|send|buzzer]configured printers (--scan for USB), paper status, a test page, a .bin file; buzzer off|on turns a NETUM NT-861's beeps off or on (see printer buzzer --help)
init [--project]write a commented config to ~/.config/spooligan or ./.spooligan
config [which|doctor|path|migrate]where each setting comes from; check the config files
studio [FILE]design a receipt in the browser, on 127.0.0.1 (--port N, --no-open; --lan for your phone, with a token in the link)
update [--check|-n]install the newest release (--check: only say if one is out; -n: show what it would do)
update --rollbackgo back to the version the last update replaced
agents [TOPIC]the guide for AI agents, or one topic of it (--list the topics, --all everything; also help agents)
help [COMMAND [SUB]]this summary, or one command's help (also COMMAND --help or -h)
note list todo shopping text qr message quote joke doc crib steps workout banner packing_slipshortcuts for spooligan print NAME (packing-slip works too)

Flags

-o, --output FILEwrite FILE.png (a picture) or FILE.bin (ESC/POS bytes) instead of printing; - is stdout
-P, --printer NAMEa printer from printers: in the config (SPOOLIGAN_PRINTER)
--no-cutdon't cut after the job
--screenwith -o FILE.png: the same layout for screens, 3 pixels a dot in smooth gray; never printed
--config FILEuse this config file (SPOOLIGAN_CONFIG)
--project-dir DIR, --no-projectchoose or skip the .spooligan/ project folder
-n, --dry-runrender and lint, write nothing
-s, --style NAMEmodern or handwritten (hand)
--seed TEXTanother hand for the handwritten strokes; the same seed draws the same lines
-l, --lang TAGlabels and dates in a language, e.g. el
--dir rtl|ltr|autoreading direction; rtl mirrors the layout (auto: from lang or the first letters)
-p, --paper MM80 or 58
-d, --data FILEtemplate params from a YAML or JSON file, or - for stdin
--example NAMEstart from one of the template's examples
--tune KEY=VALUE,...legibility settings: section=rule|tab|bar|outline|bold|pill, chip=dot|heavy|bold|solid, knockout=DOTS, hand=DOTS (0 turns a floor off; see the guide)
--mascot FILEa picture to use for spooligan:mascot (SPOOLIGAN_MASCOT, mascot: in the config)
key=value, --key value set one template param; [a, b] is a list
--offlinenever download fonts (SPOOLIGAN_OFFLINE); a local fonts.mirror folder still works
--jsonmachine-readable output and errors
-h, --helphelp: spooligan --help, spooligan printer buzzer --help, spooligan note -h

Exit codes

0OK
1Usage
2Invalid document
3Printer: none set up, can't connect, or the paper is out
4Input, such as a missing file
5Missing glyphs with --strict
6update: spooligan.ai can't be reached
7update: the release note can't be trusted
8update: the download failed a check; nothing changed
9update: nothing installed because it needs you
10update --check or -n: a newer release is out
The full --help
spooligan prints blocks on thermal paper.  spooligan note "Back in 5"                   print on the default printer  spooligan note "Back in 5" --alert -o note.png   or write a picture; --fridge, --plain  spooligan list milk eggs --title Groceries -o list.png  spooligan todo "Call Sam" "Pay rent" -o todo.png  spooligan print brief.yaml -o brief.png      any document or template file  spooligan preview note --example door        render an example to note.pngcommands  print SRC [params]     print a document, template file or built-in template (or -o FILE)  preview SRC [params]   render to SRC.png, or -o FILE  validate SRC...        check documents; exit 2 if any is invalid  blocks [TYPE]          list block types, or one type's fields  schema [TYPE]          the JSON Schema for documents, or one block  templates [show NAME]  list built-in templates, or one template's params  demo [NAME|all]        list the demos, or print one: together they show every block  hello                  print a hello page with the mascot (--mascot FILE swaps it)  styles                 list styles  icons                  list icon names for icon: fields  fonts [list]           bundled fonts and the Noto packs you can fetch  fonts which TEXT       which font draws each script in TEXT  fonts doctor           check fonts.scripts, fonts.fallback and fetched packs  fonts fetch SCRIPT...  download pinned, checksummed Noto fonts (hebrew, thai, ... or all)  fonts mirror DIR       copy the packs into DIR, for fonts.mirror on machines without internet  licenses [NAME]        third-party fonts, icons and modules, or one licence in full  printer setup          find the USB receipt printer and make it the default  printer [list|status|test|send|buzzer]                         configured printers (--scan for USB), paper status, a test page, a .bin file;                         buzzer off|on turns a NETUM NT-861's beeps off or on (see printer buzzer --help)  init [--project]       write a commented config to ~/.config/spooligan or ./.spooligan  config [which|doctor|path|migrate]                         where each setting comes from; check the config files  studio [FILE]          design a receipt in the browser, on 127.0.0.1 (--port N, --no-open;                         --lan for your phone, with a token in the link)  update [--check|-n]    install the newest release (--check: only say if one is out; -n: show what it would do)  update --rollback      go back to the version the last update replaced  agents [TOPIC]         the guide for AI agents, or one topic of it (--list the topics, --all everything;                         also help agents)  help [COMMAND [SUB]]   this summary, or one command's help (also COMMAND --help or -h)  note list todo shopping text qr message quote joke doc crib steps workout banner packing_slip                         shortcuts for spooligan print NAME (packing-slip works too)flags  -o, --output FILE      write FILE.png (a picture) or FILE.bin (ESC/POS bytes) instead of printing; - is stdout  -P, --printer NAME     a printer from printers: in the config (SPOOLIGAN_PRINTER)  --no-cut               don't cut after the job  --screen               with -o FILE.png: the same layout for screens, 3 pixels a dot in smooth gray; never printed  --config FILE          use this config file (SPOOLIGAN_CONFIG)  --project-dir DIR, --no-project                         choose or skip the .spooligan/ project folder  -n, --dry-run          render and lint, write nothing  -s, --style NAME       modern or handwritten (hand)  --seed TEXT            another hand for the handwritten strokes; the same seed draws the same lines  -l, --lang TAG         labels and dates in a language, e.g. el  --dir rtl|ltr|auto     reading direction; rtl mirrors the layout (auto: from lang or the first letters)  -p, --paper MM         80 or 58  -d, --data FILE        template params from a YAML or JSON file, or - for stdin  --example NAME         start from one of the template's examples  --tune KEY=VALUE,...   legibility settings: section=rule|tab|bar|outline|bold|pill, chip=dot|heavy|bold|solid,                         knockout=DOTS, hand=DOTS (0 turns a floor off; see the guide)  --mascot FILE          a picture to use for spooligan:mascot (SPOOLIGAN_MASCOT, mascot: in the config)  key=value, --key value set one template param; [a, b] is a list  --offline              never download fonts (SPOOLIGAN_OFFLINE); a local fonts.mirror folder still works  --json                 machine-readable output and errors  -h, --help             help: spooligan --help, spooligan printer buzzer --help, spooligan note -hSRC - reads stdin: a document, or plain text.