Zoom Summary
Read-only access to Zoom AI Companion meeting summaries via the Zoom REST API v2, using a Server-to-Server OAuth app. Wraps the two summary endpoints with curl + jq helpers, and repairs the domain terms the summariser mistranscribes.
Trigger conditions
Invoke this skill when the user says any of:
- "지난 미팅 요약 보여줘" / "what did we discuss in Monday's Zoom" / "last week's Zoom meetings" →
scripts/summaries.sh list - "그 미팅 요약 전체 내용" / "show me the full summary" →
scripts/summaries.sh get <uuid> - "요약 저장해줘" / "save that summary as markdown" →
scripts/summaries.sh save <uuid> - "용어 보정해줘" / "fix the mangled terms" / "그 약어 잘못 받아썼는데" →
scripts/correct.py - "set up Zoom" / "configure Zoom" / credentials file missing → follow the Setup flow below
Prerequisites
Before any operation, check the credentials file exists:
test -f ~/.config/zoom-skill/credentials.json
If it does not exist, run the setup flow below. Dependencies: curl, jq, base64 (scripts exit 127 if missing), plus python3 for correct.py.
The account must have AI Companion meeting summaries enabled, and the summaries must already have been generated and saved. This skill cannot generate a summary for a meeting that never had AI Companion on.
Setup flow (skill-orchestrated)
The Zoom Marketplace app must be created by the user in a browser. You cannot do this step.
- Check whether credentials already exist. If they do, ask before re-registering.
- Give the user these steps verbatim:
- https://marketplace.zoom.us → Develop → Build App → Server-to-Server OAuth
- Scopes tab, add
meeting:read:list_summaries:adminandmeeting:read:summary:admin. If the picker does not offer them, add the classic scopemeeting_summary:read:admin, which covers both. - Activate the app.
- Copy Account ID, Client ID, Client Secret from App Credentials.
These get stored at
~/.config/zoom-skill/credentials.json(mode 600). Only paste them here if you trust this terminal and session transcript. - When the user replies with the three values, run the
--stdinmode so the secret stays out of argv:bash scripts/setup.sh --stdin <<< '{"account_id":"<A>","client_id":"<I>","client_secret":"<S>"}' - Show the script's scope report verbatim. It prints
GRANTEDorMISSINGper scope plus a live probe of the list endpoint. - On failure, show stderr verbatim and ask whether to retry.
Do not echo the client secret back in your own messages. Do not run setup unprompted; the triggers are an explicit request or a script exiting 2.
Known scope gotcha
meeting:read:summary:admin (the summary body scope) is reported missing from the scope picker on some accounts. The symptom is that list works but get returns HTTP 403. If that happens:
- Tell the user which scope the API refused, quoting the error.
- Have them check for the classic
meeting_summary:read:adminscope instead. - If neither is grantable, the zoom.us web portal (Meeting Summary with AI Companion → My Summaries) is the only remaining route. Say so plainly rather than retrying.
Do not auto-retry exit code 3.
Script usage
All scripts live in this skill directory's scripts/ folder.
setup.sh — one-time credential registration
bash scripts/setup.sh # interactive (TTY required)
bash scripts/setup.sh --stdin <<< '<json>'
Verifies by minting a real token, writes credentials mode 600, then reports granted scopes and probes the list endpoint. There is no flag mode: a client secret on argv is visible in /proc.
summaries.sh — the actual work
bash scripts/summaries.sh list [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--json]
bash scripts/summaries.sh get <meetingUUID> [--json]
bash scripts/summaries.sh save <meetingUUID> [--dir <path>] [--force]
listdefaults to the last 30 days and followsnext_page_tokento the end. Output is TSV:start_time,topic,meeting_id,uuid. Use the uuid column forgetandsave, not the meeting ID.getrenders markdown by default;--jsongives the raw body.savewrites to$ZOOM_SUMMARY_DIR(default~/Documents/ZoomSummaries) asYYYY-MM-DD_<topic-slug>.mdand prints the path. It refuses to overwrite without--force.
Meeting UUIDs often contain /, +, and =. Always quote them in shell commands. auth.sh handles Zoom's rule that a UUID starting with / or containing // must be double URL encoded; every other UUID goes through as is.
correct.py — repair mangled domain terms
python3 scripts/correct.py <file.md|-> [--in-place] [--report-only] [--no-appendix] [--glossary PATH]
Zoom's summariser mishears domain jargon consistently: acronyms come back as similar-sounding words, journal names get truncated, personal names get reversed. This applies the glossary and appends an audit table.
Two tiers, and the distinction is the whole point:
high— substituted automatically.low— never substituted, only listed under "검토 필요" for a human to resolve. Terms whose correct form is unknown live here with?as the replacement.
--in-place keeps the untouched summary as <stem>.raw.md. Re-running re-derives from that raw file, so the run is idempotent, the substitution record survives, and glossary edits always apply to pristine text.
--report-only prints the report to stderr and writes nothing. --no-appendix corrects the text without appending the audit table. Reading from - (stdin) writes to stdout and cannot be combined with --in-place.
Glossaries load in order, later overriding earlier by pattern:
references/glossary.tsvin this skill — template only, ships with no real terms~/.config/zoom-skill/glossary.tsv— where every real entry belongs (override the path withZOOM_SKILL_GLOSSARY)- anything passed with
--glossary(repeatable)
Requires python3. Exits 1 when the file is missing or no glossary entry exists anywhere, and 64 when --in-place is given with stdin.
After fetching
Term correction. Run
correct.py --in-placeon every saved summary, then work the "검토 필요" list.Resolve unknowns against the research log before asking the user. For a project registered in
~/.research/,projects/<slug>/compass.mdcarries the canonical vocabulary and paper structure, andjournal.mdcarries verbatim usage. A term earns thehightier when you can cite the line that settles it: a garbled acronym in a sentence about a specific paper section resolves once compass.md says what that paper is about. Put the citation in the note column.Two limits worth knowing before you start digging. The research log holds no personal names at all, so mangled names never resolve there and must go to the user. And the log lags the meeting: it is a decision record, not a transcript, so a term coined in the meeting itself will not be in it.
Everything you cannot cite stays
low. Propose it to the user with your evidence and let them confirm; do not promote on plausibility alone. All real entries go in~/.config/zoom-skill/glossary.tsv. The skill'sreferences/glossary.tsvis a template only: it is version controlled and may be pushed to a public remote, so project vocabulary and personal names must never be written there.Korean translation. Produce
<basename>_ko.mdin the same directory. Skip it when the summary is already Korean, which it usually is when the meeting was. Per the user's global convention, delegate the translation to a Sonnet subagent, preserving the heading structure and any English proper nouns inside Korean sentences.research-log handoff. When the user asks to log the meeting, use the
research-logskill's record workflow. Pass only the decisions and action items, not the full summary text; a journal entry is not a transcript dump.Reporting. Never paraphrase a summary as if it were the API's own words when you have not fetched it. If
listreturned rows butgetfailed, say which step failed. When you relay meeting content to the user, say plainly that AI Companion garbles proper nouns and that only glossary-backed terms have been repaired.
Error handling
Show script stderr to the user verbatim. Exit code reference:
- 0 — success
- 1 — bad argument,
saverefused to overwrite, orcorrect.pyfound no file / no glossary entry - 2 — credentials.json missing (→ run setup.sh)
- 3 — token rejected or scope missing, HTTP 401/403 (→ see the scope gotcha above)
- 4 — meeting UUID not found, HTTP 404 (often a UUID that needed quoting)
- 5 — other API error
- 6 — rate limited, HTTP 429
- 64 — usage error
- 127 — missing dependency
Environment overrides
ZOOM_SKILL_CREDS— credentials path (default~/.config/zoom-skill/credentials.json)ZOOM_SKILL_CACHE— token cache directory (default~/.cache/zoom-skill)ZOOM_SUMMARY_DIR— markdown output directory (default~/Documents/ZoomSummaries)ZOOM_SKILL_GLOSSARY— user glossary path (default~/.config/zoom-skill/glossary.tsv)ZOOM_API_BASE,ZOOM_OAUTH_URL— API and token endpoints. Only for pointing the scripts at a stand-in server during testing; never change them in normal use.
API notes
Base https://api.zoom.us/v2. Tokens come from POST https://zoom.us/oauth/token with grant_type=account_credentials, live one hour, and are cached in ~/.cache/zoom-skill/token.json keyed by a fingerprint of the credentials.
| Purpose | Endpoint |
|---|---|
| List summaries | GET /meetings/meeting_summaries?from&to&page_size&next_page_token |
| Summary body | GET /meetings/{meetingUUID}/meeting_summary |
Body fields: summary_title, summary_overview, summary_details[].{label,summary}, next_steps[], summary_start_time, summary_end_time, summary_created_time, meeting_host_email.
Upstream docs: https://developers.zoom.us/docs/api/meetings/