diff --git a/README.md b/README.md index fa04963..e08a4bb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # zfy [![CI](https://github.com/EssentialsDev/zfy-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/EssentialsDev/zfy-cli/actions/workflows/ci.yml) +[![npm](https://img.shields.io/npm/v/@devessentials/zfy-cli.svg)](https://www.npmjs.com/package/@devessentials/zfy-cli) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) A third-party CLI, TypeScript SDK, and **MCP server** for the [Zeffy API](https://www.zeffy.com/integration/api) — built so nonprofits can automate donation reporting and AI agents can answer questions like *"draft tax receipts for everyone who gave over $250 in 2025."* @@ -18,51 +19,109 @@ Read-only by design — `zfy` cannot modify your Zeffy data, because the officia ## Contents -- [Install](#install) -- [Authenticate](#authenticate) -- [CLI usage](#cli-usage) - - [Quickstart](#quickstart) +- [Prerequisites](#prerequisites) +- [Quick start (5 minutes)](#quick-start-5-minutes) +- [CLI commands](#cli-commands) - [End-of-year report](#end-of-year-report) - [Logo spec for `--logo`](#logo-spec-for---logo) -- [MCP server](#mcp-server-use-from-claude--agents) +- [Optional: use from Claude or other AI agents (MCP)](#optional-use-from-claude-or-other-ai-agents-mcp) - [SDK usage](#sdk-usage) +- [Troubleshooting](#troubleshooting) - [Development](#development) - [License](#license) -## Install +## Prerequisites + +You'll need two things before you start: + +1. **Node.js 20 or newer.** Check with: + ```bash + node --version + ``` + If that prints `command not found` or a version below `v20`, install the LTS from [nodejs.org](https://nodejs.org/) or via your package manager (`brew install node` on macOS). + +2. **A Zeffy account.** You don't need a paid plan — Zeffy is free for nonprofits. The API key generator lives in your dashboard's **Settings → Integrations** section. + +## Quick start (5 minutes) + +### Step 1 — Install zfy + +```bash +npm install -g @devessentials/zfy-cli +``` + +If `npm install -g` fails with a permissions error, you either need to fix your npm permissions or use [a Node version manager like nvm](https://github.com/nvm-sh/nvm). Don't run with `sudo` — it works but creates permission headaches later. + +Verify it installed: ```bash -npm i -g @devessentials/zfy-cli -# or run without installing: -npx --package=@devessentials/zfy-cli zfy --help +zfy --version +# 0.1.0 ``` -The package is `@devessentials/zfy-cli` on npm; the binary is `zfy` and the MCP entry point is `zfy-mcp`. Node.js 20+ required. +### Step 2 — Get your Zeffy API key -## Authenticate +1. Sign in at [zeffy.com](https://www.zeffy.com). +2. Open your organization's **Settings → Integrations** page. +3. Click **Generate API key**, copy the key (it starts with `sk_…`). You'll only see it once — store it somewhere safe. -1. In your Zeffy dashboard, go to **Settings → Integrations** and generate an API key. -2. Save it locally: +### Step 3 — Tell zfy your key ```bash -zfy auth set # prompts for the key, stores it in ~/.config/zfy/config.json (mode 0600) -zfy auth status # verifies the key by calling the Zeffy API -zfy auth clear # removes the stored key +zfy auth set +# pastes the prompt: paste your sk_… key, press Enter ``` -Or pass it inline for CI / one-offs: +This stores the key in your user config directory with restrictive permissions (mode 0600 — only you can read it). On most systems that's `~/.config/zfy/config.json`; if you set `XDG_CONFIG_HOME`, it lives under there instead. Run `zfy auth path` any time to see the exact location. + +Verify the key works: ```bash -ZEFFY_API_KEY=sk_xxx zfy payments list --from 2025-01-01 +zfy auth status +# Authenticated. +# Sample campaign: Annual Fund 2025 ``` -The `ZEFFY_API_KEY` env var always takes precedence over the stored key. +If you see that, you're done with setup. + +### Step 4 — Run your first command + +```bash +# Pull a single donation as a sanity check +zfy payments list --limit 1 +``` + +You should see JSON like this (trimmed): + +```json +[ + { + "id": "pay_abc123", + "amount": 100, + "currency": "USD", + "status": "succeeded", + "type": "donation", + "contact": { "email": "donor@example.com", "name": "Jane Doe" }, + "campaign": { "name": "Annual Fund 2025" }, + "created": "2025-06-15T14:30:00Z" + } +] +``` + +That's it — every other command follows the same pattern. The output is always JSON so you can pipe it into [`jq`](https://jqlang.org/), an LLM, or your own scripts. + +### Step 5 — Generate your first end-of-year report -## CLI usage +```bash +zfy report eoy --year 2025 --format pdf --out ./receipts/ \ + --org "Your Organization Name" +``` -Every command outputs JSON to stdout — pipe it into `jq`, an LLM, a spreadsheet, or further commands. +This drops one PDF per donor in `./receipts/`. Open the directory and you'll see files like `2025-jane-doe.pdf`, each with the donor's annual total and itemized gifts — ready to mail or attach to an email. -### Quickstart +## CLI commands + +Every command outputs JSON to stdout unless you use `--out` to write to a file. ```bash # Donations in a date range @@ -81,9 +140,17 @@ zfy contacts list --email donor@example.com zfy campaigns list ``` +Pass `ZEFFY_API_KEY=sk_xxx` inline if you'd rather not store credentials — useful for CI: + +```bash +ZEFFY_API_KEY=sk_xxx zfy payments list --from 2025-01-01 +``` + +The environment variable always wins over the stored key. + ### End-of-year report -Generate a per-donor annual report in any format. Defaults: excludes refunded payments, uses your system timezone for year boundaries. +Generates a per-donor annual report. Defaults: excludes refunded payments, uses your system timezone for year boundaries. ```bash # JSON to stdout (default — best for piping to agents) @@ -95,15 +162,13 @@ zfy report eoy --year 2025 --format csv --out eoy-2025.csv # Top-50 donor markdown summary (fits in an LLM context) zfy report eoy --year 2025 --format md --out eoy-2025.md --top 50 -# One PDF receipt per donor +# One PDF receipt per donor (with optional logo) zfy report eoy --year 2025 --format pdf --out ./receipts/ \ --org "Friends of the Library" \ --logo ./logo.png --logo-size 64 \ --timezone America/Los_Angeles ``` -Useful flags (`zfy report eoy --help` for the full list): - | Flag | Notes | | --- | --- | | `--year ` | Required. Calendar year. | @@ -115,12 +180,14 @@ Useful flags (`zfy report eoy --help` for the full list): | `--top ` | Markdown: limit donor table. | | `--org ` | PDF: organization name in the header. | | `--logo ` | PDF: square PNG/JPEG mark — see spec below. | -| `--logo-size ` | PDF: edge length of the logo slot (default 64 pt). | +| `--logo-size ` | PDF: edge length of the logo slot in [PDF points](https://en.wikipedia.org/wiki/Point_(typography)) (default 64; 72 pt ≈ 1 inch). | | `--receipt-text ` | PDF: override the default tax-receipt boilerplate. | +Run `zfy report eoy --help` for the complete list. + ### Logo spec for `--logo` -The PDF renderer reserves a small square slot for an org mark. To keep batches consistent and prevent multi-gigabyte receipt runs, logos must meet: +The PDF renderer reserves a small square slot for an org mark. Logos must meet these constraints — otherwise zfy prints a warning to stderr and falls back to text-only (no crash, batch keeps running): | Constraint | Limit | | --- | --- | @@ -129,11 +196,13 @@ The PDF renderer reserves a small square slot for an org mark. To keep batches c | Minimum dimensions | 64 × 64 px | | Shape | Square within ±10% (e.g. 512×512, or 500×510 — but not 800×200) | -Recommended: a 512×512 PNG with a transparent or white background. If the file fails any check, `zfy` prints a single warning to stderr and continues without the logo — a bad asset never blocks a 500-donor receipt run. +**Recommended:** a 512×512 PNG with a transparent or white background. -## MCP server (use from Claude / agents) +## Optional: use from Claude or other AI agents (MCP) -`zfy-mcp` exposes the same operations as MCP tools over stdio. Add to `~/.claude.json` (or Claude Desktop's `claude_desktop_config.json`): +> Skip this section if you're only using the CLI directly. This is for plugging Zeffy into AI assistants that support the [Model Context Protocol](https://modelcontextprotocol.io/). + +`zfy-mcp` is a stdio MCP server bundled with the package. Add it to `~/.claude.json` (or Claude Desktop's `claude_desktop_config.json`): ```json { @@ -155,7 +224,7 @@ Tools exposed: | `zeffy_list_campaigns` | List campaigns | | `zeffy_eoy_report` | Per-donor annual summary (JSON or Markdown) | -After installing and configuring, ask your agent things like: +Then ask your agent things like: - "How much did we raise in Q4 2025?" - "Who were our top 10 donors last year, and what did each give?" @@ -165,7 +234,7 @@ PDF and CSV output aren't exposed via MCP (binary data is awkward over stdio) ## SDK usage -Everything the CLI does is available as a typed library, including the EOY aggregation and the format renderers. +Everything the CLI does is available as a typed TypeScript library, including the EOY aggregation and the format renderers. ```ts import { Zeffy } from "@devessentials/zfy-cli"; @@ -192,18 +261,47 @@ console.log(formatMarkdown(report, 25)); await writePdfReceipts(report, "./receipts", { orgName: "Friends of the Library" }); ``` -The client handles 429 rate-limit responses (token-bucket capped at 90 req/min, with `Retry-After` honored on backoff) and validates every response against [zod](https://zod.dev) schemas — bad shapes throw with the raw response attached for debugging. +The client handles 429 rate-limit responses (token bucket capped at 90 req/min, with `Retry-After` honored on backoff) and validates every response against [zod](https://zod.dev) schemas — bad shapes throw with the raw response attached for debugging. + +## Troubleshooting + +**`zsh: command not found: zfy`** (or `bash: zfy: command not found`) +Your shell can't find the binary that `npm install -g` just placed on disk. Run `npm prefix -g` to print your global install prefix (e.g. `/usr/local` or `~/.nvm/versions/node/v22.x.x`), then make sure `/bin` is on your `PATH`. If you used `nvm`, switching Node versions changes the prefix — re-run the install after switching. + +**`No Zeffy API key configured.`** +You haven't run `zfy auth set` yet, and `ZEFFY_API_KEY` isn't set in your environment. Run `zfy auth set` and paste the key when prompted. + +**`API error 401: Invalid API key`** (or `Zeffy API error 401: ...` from other commands) +The key zfy has stored doesn't match what Zeffy expects. Either the key was regenerated (in which case run `zfy auth clear && zfy auth set` with the new one) or you copy/pasted with extra whitespace. `zfy auth status` is the simplest way to confirm — it hits the API and reports the failure. + +**`Zeffy API error 429: Too Many Requests`** +You're hitting Zeffy's 100-requests-per-minute cap. zfy already throttles to 90/min and backs off on 429s, so this usually means something else on the same key is also calling the API. Wait a minute and retry. + +**"Where did my PDFs go?"** +`zfy report eoy --format pdf` writes to the directory passed via `--out`, or — if `--out` is omitted — to `./eoy--receipts/` in your current working directory. The CLI prints the destination to stderr after it finishes. + +**`zfy: skipping logo — image is not square (400×100); supply a square PNG/JPEG (e.g. 512×512)`** +Your `--logo` image violates the [logo spec](#logo-spec-for---logo). zfy continues without the logo so a bad asset doesn't block the rest of the report. Crop your image to a square in any image editor and rerun. Other variants of this message (file too large, too small, unsupported format) all skip the logo the same way. + +**Want more detail on an error?** +Re-run with `DEBUG=1`: +```bash +DEBUG=1 zfy payments list --from 2025-01-01 +``` +This prints the full response body Zeffy returned, which usually pinpoints the issue. ## Development ```bash +git clone https://github.com/EssentialsDev/zfy-cli.git +cd zfy-cli pnpm install pnpm build # tsup → dist/ pnpm test # vitest pnpm typecheck # tsc --noEmit ``` -CI runs `typecheck`, `test`, and `build` on every push and PR against Node 20 and 22. +CI runs `typecheck`, `test`, and `build` on every push and PR against Node 20 and 22. Releases publish automatically to npm when a `v*` tag is pushed. ## License