Repository navigation
feat: save meeting attachments such as screen snapshots - #3
Conversation
Adds meetings attachments, which wraps get_meeting_attachment and saves each returned image to a file. Text blocks are printed as the server sent them, and images that succeed are kept when other attachments fail.
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 8db75722e5ef
Profile: claude-monit-reviewer - Posting as: monit-reviewer
Summary
| Reviewer | Findings |
|---|---|
| documentation:docs | 0 |
| architecture:solid-reviewer-agnostic | 1 |
| security:code-auditor | 0 |
architecture:solid-reviewer-agnostic (1 finding)
Minor - cmd/attachments.go:49
Project manifest drift (docs/development.md, Layout section; U-S2). The manifest says every tool command "is a thin wrapper that builds the tool's arguments and calls
runTool", and that result rendering belongs tointernal/mcpclient/. This command is the first one to skiprunToolon its main path. It opens its own session, callsCallTool, and hassaveAttachmentsdo the rendering itself: it classifies content blocks, decides when an error result counts as a tool error, and writes files. The design is reasonable. Images need a different sink than text, and keeping the filesystem side incmd/keepsmcpclientfree of IO. But the manifest now describes a layout the code no longer follows, and the next contributor will either copy this pattern without guidance or force a similar command throughrunTool. Fix: add a line to docs/development.md saying that commands whose results carry binary content may callwithSessiondirectly and render with a local helper, and thatmcpclient.RenderText/CheckErrorstay the source of truth for text and errors. Optionally, haveRunEreuse thecall %s: %wwrapping fromcallAndPrintso error wording cannot diverge. The command itself does not need to change.
Reviewer Coverage
documentation:docs— complete (constrained); inspected 2 assigned files (9 inspected across reviewers):README.md,testdata/README.md; skipped: none; constraints: nonearchitecture:solid-reviewer-agnostic— complete (broad); inspected 7 assigned files (9 inspected across reviewers):cmd/attachments.go,cmd/cli_test.go,cmd/names.go,testdata/call_get_meeting_attachment.json,testdata/call_get_meeting_attachment_not_found.json,testdata/call_get_meeting_attachment_partial.json,testdata/tools_list.json; skipped: none; constraints: Rango test ./cmd/with the Makefile's GOFLAGS: passes. Did not run golangci-lint or the fullmake check. docs/development.md (the project manifest) is not in the changed set, so the manifest-drift finding is anchored on the command that diverges from it.security:code-auditor— complete (broad); inspected 1 assigned file (9 inspected across reviewers):cmd/attachments.go; skipped: none; constraints: none
Inspected files (9)
README.mdcmd/attachments.gocmd/cli_test.gocmd/names.gotestdata/README.mdtestdata/call_get_meeting_attachment.jsontestdata/call_get_meeting_attachment_not_found.jsontestdata/call_get_meeting_attachment_partial.jsontestdata/tools_list.json
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 2m 07s | ~$1.57 (est.) | claude-sonnet-5, claude-opus-5-5 | cr 0.10.319
| Field | Value |
|---|---|
| Model | claude-sonnet-5, claude-opus-5-5 |
| Reviewers | documentation:docs, architecture:solid-reviewer-agnostic, security:code-auditor |
| Engine | claude_cli · claude-sonnet-5, claude-opus-5-5 |
| Reviewed by | cr · monit-reviewer |
| Duration | 2m 07s wall · 3m 24s compute |
| Cost | ~$1.57 (est.) |
| Pricing basis | anthropic-public-2026-09-23 |
| Tokens | 52 in / 10.4k out |
Per-workstream usage
orchestrator-selection— claude-sonnet-5- In: 6
- Out: 2.1k
- Cache read: 90.2k
- Cache create: 40.5k
- Cost: ~$0.20 (est.)
- Duration: 25s
documentation:docs— claude-sonnet-5- In: 20
- Out: 3.0k
- Cache read: 530.0k
- Cache create: 46.6k
- Cost: ~$0.32 (est.)
- Duration: 1m 23s
architecture:solid-reviewer-agnostic— claude-opus-5-5- In: 12
- Out: 3.3k
- Cache read: 243.8k
- Cache create: 70.8k
- Cost: ~$0.68 (est.)
- Duration: 51s
security:code-auditor— claude-sonnet-5- In: 8
- Out: 1.6k
- Cache read: 158.9k
- Cache create: 40.5k
- Cost: ~$0.21 (est.)
- Duration: 32s
orchestrator-rollup— claude-sonnet-5- In: 6
- Out: 437
- Cache read: 143.6k
- Cache create: 31.3k
- Cost: ~$0.16 (est.)
- Duration: 11s
Granola's MCP server now offers
get_meeting_attachment, which returns a meeting's image attachments, such as the snapshots Granola takes of shared screens (https://docs.granola.ai/help-center/taking-notes/capture-shared-screens). The CLI could only reach it throughtools call, and printed[image content, ...]in place of the image.granola meetings attachments <meeting-id> <attachment-id>...calls the tool for up to 5 attachments and saves each image as<meeting-id>-<n>.<ext>in--out-dir(default., created if missing, files mode 0600). Text blocks are printed as the server sent them, in order, so eachSavedline sits next to the metadata that follows its image. The command does not parse the metadata.Error handling:
--out-diris not created.--jsonprints the raw result with base64 images and saves nothing.testdata/tools_list.jsongains the live tool definition. The error fixture's text matches a live response. The success and partial-failure layouts follow the tool's description, because no meeting on the test account had an attachment yet;testdata/README.mdsays so.Tests: saved bytes, file names, modes and output order; partial failure; an error with no images;
--json; usage errors for too few and too many IDs.make checkpasses. Against the live server, an unknown attachment ID exits 5 with the server's error and creates no directory, and a missing attachment ID exits 2.