Appearance
Printing Press
Printing Press is a registry pattern for small purpose-built CLIs — ESPN scores, weather, currency, anything that fits one binary + a registry.json. Z.E.N.'s pp connector adapter reads a Printing Press registry file at boot, registers each CLI's documented commands as pp.<registry>.<cli>.<command> tools, and they're immediately available to tool: and agent: nodes.
This is for the case where you have a collection of small CLIs that share a common registry shape, not one CLI you want to wire in directly (use the local_cli connector path for that).
Where Z.E.N. looks for the registry
The adapter checks (in order):
~/.printing-press/registry.json~/.zen/printing-press/registry.json- Path set via
ZEN_PP_REGISTRYenv var
The first one found wins. If none exist, the connector reports pp as toolCount: 0 and stays silent. No CLIs registered, no errors.
registry.json shape
json
{
"version": 1,
"clis": [
{
"id": "espn",
"displayName": "ESPN",
"description": "Scores, schedules, standings.",
"bin": "/usr/local/bin/pp-espn",
"commands": [
{
"id": "scoreboard",
"displayName": "Today's scoreboard",
"args": ["scoreboard", "--json"],
"outputFormat": "json"
},
{
"id": "team",
"displayName": "Team details",
"args": ["team"],
"outputFormat": "json",
"schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Team abbreviation, e.g. SF" }
},
"required": ["name"]
}
}
]
}
]
}Each CLI's commands[] becomes one tool. The tool id is pp.espn.scoreboard, pp.espn.team, and so on. The bin is the absolute path; args is the always-on prefix; user's tool_args gets appended.
Verify Z.E.N. picked it up
bash
curl -s http://localhost:3090/api/connectors | jq '.connectors[] | select(.id == "pp")'
curl -s http://localhost:3090/api/tools | jq '.tools[] | select(.connectorId == "pp") | .id'Settings → Connectors shows pp with local_cli chip and the total tool count summed across every CLI in the registry.
Use it from a workflow
yaml
- id: today-games
tool: pp.espn.scoreboard
- id: lakers
tool: pp.espn.team
tool_args:
name: LALThe tool_args shape matches whatever schema the command declared in registry.json. The picker in the workflow editor reads the schema and shows the form inline.
Add a new CLI to your registry
Edit your registry.json, add the CLI entry, restart Z.E.N. The connector re-parses on boot. No need to register each CLI separately.
For a sample registry covering ESPN, OpenWeather, exchange rates, and a few others, see mvanhorn/printing-press.
When this fits
Printing Press fits when you want a curated registry of small consumer-grade CLIs you (or your team) maintain together. One JSON file, many CLIs, all exposed to Z.E.N. as tools.
When it doesn't fit:
- One single CLI → write a local_cli connector manifest instead. Simpler.
- An MCP server → use mcporter. Different protocol, different registry shape.
- A REST API → wrap it with an
http-source-kind connector manifest.
Gotchas
- The
binpath must be absolute. Same daemon-PATH-stripping issue as gws/mcporter. If your CLIs live in~/.local/bin, write the absolute path in the registry. registry.jsonvalidation is light. The adapter parses what it can and skips entries with missing required fields silently. If a CLI you expected isn't registered, runbun run devand watch thepp.bootstraplog lines — they'll tell you which entries got dropped and why.- Reload requires a daemon restart. Editing
registry.jsondoesn't hot-reload.zen serveto pick up changes. - No auth model. Printing Press registry doesn't declare auth — the assumption is these CLIs read their own credentials from env. Set the env vars in
~/.zen/.envso the daemon forwards them.
Reference
- Tools, Connectors — registry concepts
- Printing Press on GitHub: https://github.com/mvanhorn/printing-press