Skip to main content

Single-workspace Zulip CLI toolkit for humans and AI agents — read, write, archive, and grep one Zulip workspace from the shell.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zlp_cli-0.1.0.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zlp_cli-0.1.0-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file zlp_cli-0.1.0.tar.gz.

File metadata

  • Download URL: zlp_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zlp_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 21304c68cff63cfffc4123ad2ba27aa2848ebda938e4dcec8c5beff2987041cb
MD5 42f6122a0960aa864bf749b59d972580
BLAKE2b-256 a7269c2bdb05a29e9487203f52ce176b49bcc0adf143715039f5ff8dd95be4c3

See more details on using hashes here.

Provenance

The following attestation bundles were made for zlp_cli-0.1.0.tar.gz:

Publisher: publish.yml on GiggleLiu/zlp-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zlp_cli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: zlp_cli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zlp_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cf268caf55af17a8895939e92577d3b908bd370d1bd19375e9c6ab9b99fd1a69
MD5 29f1286b699cfe3ad6e07a9d69e802a5
BLAKE2b-256 38ae9f5712b6e0047a32bbfd17230d5b3768ab524343bde39aca9e619bbc61bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for zlp_cli-0.1.0-py3-none-any.whl:

Publisher: publish.yml on GiggleLiu/zlp-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page