Skip to main content

zlp-cli

PyPI Build License: MIT

Single-workspace Zulip CLI for humans and AI agents. zlp is a small, scriptable command-line toolkit that wraps one Zulip account: read streams, post and edit messages, upload files, and keep a lossless local Markdown archive that's easy to grep and easy for an LLM to read.

It deliberately knows nothing about which Zulip workspace it's talking to — one process, one zuliprc, one archive directory. Driving multiple workspaces is the job of an outer layer that keeps per-workspace credentials and cds into each before invoking zlp.

Why

  • Agent-friendly. Predictable subcommands, machine-parseable output (Markdown with YAML frontmatter, JSON via --format json, TSV for status). An agent shell can call zlp messages --stream X and get usable text back without parsing HTML.
  • Local-first archive. Every message lands as its own .md file with attachments next to it, making the chat corpus another grep-able knowledge base alongside your code repo.
  • Real-time. A background daemon tails Zulip's event queue (no polling), so the local archive stays current automatically.
  • One zuliprc, one job. No multi-tenant logic, no implicit globals — easy to drop into containers, CI, or a per-agent sandbox.

Install

pip install zlp-cli        # or: uv pip install zlp-cli

This installs the zlp console script. You'll also need a zuliprc file — create one at https://YOUR-ZULIP-SERVER/api/api-keys and save it.

Quick start

export ZULIP_CONFIG_FILE=/path/to/zuliprc

zlp whoami
zlp streams
zlp messages --stream general --limit 10
zlp send --stream general --topic test --msg 'hello from zlp'

# run one incremental pass over subscribed workspace stream messages
# prints archived file paths for messages written in this pass
zlp pull

# keep the local mirror current in the foreground (Ctrl-C to stop)
zlp sync

# ...or in the background as a daemon
zlp sync --daemon
tail -f .run/_workspace.log                 # daemon log with archived file paths

# or narrow sync to one stream
zlp pull --stream general --import-history
zlp pull --stream general                  # one incremental stream pass
zlp sync --daemon --stream general         # background stream daemon
tail -f .run/general__*.log                 # stream daemon log

For multi-line message bodies pipe stdin: zlp send ... --msg-file -.

Configuration

Three settings, with this precedence: flag > env > default.

Setting Flag Env var Default
zuliprc path --config ZULIP_CONFIG_FILE ./zuliprc
archive root --archive-root ZLP_ARCHIVE_ROOT .
daemon pid/log root --run-root ZLP_RUN_ROOT ./.run

Defaults are CWD-relative. An outer "workspace manager" can cd into a per-workspace directory or set the env vars to point at workspace-specific locations — zlp itself stays workspace-agnostic.

Commands

Command What it does
zlp whoami Print server URL, account email, and full name.
zlp streams List subscribed streams, one per line.
zlp topics --stream S List topics in a stream.
zlp messages --stream S [--topic T] [--limit N] [--format md|json] Fetch recent messages.
zlp search --query Q [--stream S] [--limit N] [--format md|json] Server-side full-text search.
zlp send --stream S --topic T (--msg M | --msg-file F) Send a stream message.
zlp dm --to EMAIL[,EMAIL] (--msg | --msg-file) Send a direct message.
zlp edit --id N (--msg | --msg-file) Edit your own message.
zlp delete --id N Delete your own message.
zlp upload --file F --stream S --topic T [--msg M | --msg-file F] Upload a file and post the link, with an optional message body.
zlp pull [--stream S [--topic T]] [--all-public] [--import-history] [--no-attachments] [--silent] One-shot archive catchup; defaults to subscribed workspace streams, or narrows to one stream. Prints archived file paths unless --silent is set.
zlp sync [--daemon] [--stream S [--topic T]] [--all-public] [--no-attachments] [--silent] Live event-queue sync. Foreground by default (Ctrl-C to stop); pass --daemon to run in the background. Defaults to subscribed workspace streams, or narrows to one stream. Prints archived file paths unless --silent is set.
zlp unsync [--stream S [--topic T]] Stop the workspace sync daemon, or a stream daemon when --stream is set.
zlp sync-status TSV of archive targets and daemon state.
zlp reconcile --stream S [--topic T] [--since 24h] Re-fetch recent archived messages to catch edits and deletes the daemon may have missed.

A Makefile is included for convenience (make whoami, make send STREAM=..., etc.).

Archive layout

Under --archive-root:

<archive-root>/
├── .sync-state.json                                     # workspace cursor
└── <stream-slug>/
    └── <topic-slug | _all>/
        ├── 2026-04-26T02-30-00_alice-chen_id147641.md   # one file per message
        ├── _files/<sha-prefix>__attachment.pdf          # downloaded attachments
        └── .sync-state.json                             # per-stream/topic cursor

Workspace-wide zlp pull and zlp sync --daemon write a workspace-level .sync-state.json at the archive root; individual messages still land under their normal stream/topic directories. By default they follow the account's subscribed stream message feed, including subscribed private streams, and ignore direct messages. --all-public is an advanced mode for public channels beyond the account's subscriptions.

Each .md file has YAML frontmatter (sender, ids, timestamp, permalink, attachments) followed by the message body. Edits rewrite in place; deletions move the file to <name>.md.deleted with _archive.deleted: true.

Archive-writing commands emit stable TSV lines by default:

archived	<stream-slug>/<topic-slug>/<message-file>.md
deleted	<stream-slug>/<topic-slug>/<message-file>.md.deleted

For background daemons, these lines are written to .run/*.log (workspace daemon to .run/_workspace.log, stream daemons to .run/<stream-slug>__<topic-slug>.log). Tail those files directly. Pass --silent to suppress them.

For agents, zlp pull is the normal "what changed since the last pull?" command: each archived\t... line is a file to read for that pass, followed by ok archived=N. The cursor is the archive's .sync-state.json; it tracks last pulled, not a separate per-agent read receipt. Use zlp sync --daemon only when you want continuous background mirroring; tail .run/_workspace.log to inspect that daemon's output.

For agent integrations

The CLI is designed to be the primitive layer below richer integrations:

  • Outer workspace manager. Keep zuliprc files, archive roots, and run dirs outside this package and dispatch zlp per account by setting ZULIP_CONFIG_FILE, ZLP_ARCHIVE_ROOT, ZLP_RUN_ROOT.
  • MCP / function-calling shells. Subcommands map cleanly onto tool schemas; outputs are stable enough to feed back into a model.
  • CI / cron jobs. pull for snapshots and one-shot incremental catchup, sync --daemon for live mirrors, reconcile for catching missed edits.

Development

git clone https://github.com/GiggleLiu/zlp-cli
cd zlp-cli
uv sync
make test

Useful developer targets:

make fmt        # format Python with ruff
make fmt-check  # check formatting
make lint       # run ruff checks
make check      # fmt-check + lint + tests
make build      # build sdist and wheel artifacts

Source layout uses src/zlp/. Agent-facing workflow notes live in AGENTS.md and .claude/CLAUDE.md.

License

MIT — see LICENSE.

Metadata

Release files for zlp-cli 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zlp-cli 0.1.0
File Size Uploaded
zlp_cli-0.1.0.tar.gz 19.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zlp-cli 0.1.0
File Interpreter ABI Platform
zlp_cli-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 35.8 kB

Release files / zlp_cli-0.1.0.tar.gz

Download URL zlp_cli-0.1.0.tar.gz
Size 19.1 kB
Tags Source
SHA-256 checksum
How to use checksums
21304c68cff63cfffc4123ad2ba27aa2848ebda938e4dcec8c5beff2987041cb
BLAKE2b-256 checksum
How to use checksums
a7269c2bdb05a29e9487203f52ce176b49bcc0adf143715039f5ff8dd95be4c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 26, 2026.

Transparency log

Release files / zlp_cli-0.1.0-py3-none-any.whl

Download URL zlp_cli-0.1.0-py3-none-any.whl
Size 16.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cf268caf55af17a8895939e92577d3b908bd370d1bd19375e9c6ab9b99fd1a69
BLAKE2b-256 checksum
How to use checksums
38ae9f5712b6e0047a32bbfd17230d5b3768ab524343bde39aca9e619bbc61bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page