Client Weekly Report
ChatGPT plugin that turns a metrics CSV into this week's client report. It is not a generic PDF maker. It ranks week-over-week movers, writes what to do next, and exports a one-page Friday PDF after review.
Trigger phrases the skill listens for:
- write this week's client report
- Friday client report
- turn this CSV into a client report
CSV
metric,value,previous,unit,note
Paid search sessions,1840,1210,sessions,Brand campaign launched Tuesday
Email clicks,210,260,clicks,Send went out late
metric and value are required. previous is what makes the delta real. Missing priors are reported as no baseline, not guessed. A prior of 0 is reported as new from 0.
Header aliases: name/kpi for metric, current/this_week for value, prior/last_week for previous, notes for note. Values may include $, % and thousands separators.
The PDF always fits on one page. Past about 20 metrics, the table ends with "+ N more metrics not shown".
Local use
node src/cli.js examples/acme-weekly.csv Acme "Week of Sep 28"
That writes acme-weekly-report.pdf next to the working directory.
mcp.json points clients at the hosted endpoint below. To run the tools locally over stdio instead (one JSON-RPC message per line):
npm run stdio
Tools:
draft_client_reportreturns the headline, movers, next actions, and table.render_client_report_pdfrenders the confirmed draft.
Hosted endpoint
ChatGPT connects over streamable HTTP, not stdio:
npm start # node src/http.js, listens on $PORT (default 8080)
POST /mcpis the MCP endpoint.GET /is the plugin's website, the listing'swebsiteURL.GET /healthzreturnsok.GET /.well-known/openai-apps-challengereturns$OPENAI_APPS_CHALLENGEfor directory domain verification, and 404s when it is unset.GET /statsreturns daily tool-call counts as JSON whenSTATS_TOKENis set and sent asAuthorization: Bearer <token>, and 404s otherwise. Counts only, never report contents; they reset on restart.render_client_report_pdfreturns adownload_urlunder/files/instead of base64. Links are held in memory, expire after 15 minutes, and only the newest 500 are kept. SetPUBLIC_URLif the host does not forwardX-Forwarded-Proto.- Each client gets 30 tool calls a minute, keyed on DigitalOcean's
do-connecting-ipheader. Past that, tool calls return a readableisErrorresult saying when to retry.
Plugin package
npm run package
That zips the committed manifests, mcp.json, and the skill into dist/client-weekly-report-plugin.zip for the ChatGPT plugin importer. Commit first; it packages HEAD.
Directory submission
Public listing needs the hosted endpoint on a domain you verify. Do not point the directory at a placeholder URL.
Development
Node 22 or newer. There are no runtime dependencies and nothing to install.
npm test # unit and integration tests
npm run test:coverage # fails under 90% lines, 85% branches, 85% functions
npm run check # syntax check
Install qpdf so the PDF structure test runs instead of skipping. Coding agents should read AGENTS.md for the rules that keep the plugin working.
CI
.github/workflows/ci.yml runs on pushes to main, on every pull request, and on demand.
- test on Node 22 and 24: syntax check, the full test suite with
qpdfrequired, and the coverage gate on Node 24. - smoke: starts the real stdio and HTTP servers, runs a draft and render round trip over HTTP, downloads the PDF, and runs the CLI against the example.
Protect main so both jobs must pass before merging.
Git
main is the release branch. Work on a branch and open a pull request.