find-call — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited find-call (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are helping the user pull deep, cited context from their past calls. Many calendars — especially those using a notes/transcription bot (e.g. notes.bot, Sembly, Fireflies, Otter) — have an automatically-appended "Meeting Resources" block in the calendar event description. The canonical block has up to 4 named links: Transcription (sometimes with sub-links like This Call and Project Calls), Meeting Notes, Video, Parent Folder.
The investigation flow is read-only — it never modifies Calendar, Drive, or transcripts. The only state this skill ever writes is its own plugin-local preferences file ~/.claude/find-call/config.json, and only through the --config wizard. Accuracy and source attribution dominate speed.
Any person, project, or meeting names anywhere in this document are illustrative placeholders. Real ones resolve against the user's ~/.claude/shared/identity.json teammate roster — this skill ships with no real names baked in.$ARGUMENTS first)| Flag | Mode | Where |
|---|---|---|
| (none) / seed text | Investigate a past call | Workflow below (Steps 1–8) |
--status | Read-only health check — providers + preferences + identity inheritance | ## Mode: --status |
--config | Interactive source-provider wizard (writes config.json) | ## Mode: --config |
Precedence on conflict: --status > --config > default. Positional text with no flag is the call query.
Topic:, Date:, Short Summary, Key Discussion Points, Action Points (per attendee), Meeting Resources. For action-item / decision questions, notes alone usually answer.mcp__sembly-ai__*), query it IN PARALLEL with the calendar query; if none is connected — or the user pinned transcripts: off — skip it silently and rely on Meeting Notes. See references/sources.md.Read ~/.claude/shared/identity.json — the same file /clickup and /g-event write. This skill is a read-only consumer; it never writes this file. Extract:
user.name — used in sub-agent prompts and output ("the user's commitments"). Substitute it wherever this doc says {user.name}.user.email — the organizer; auto-excluded from attendee lists; its domain is the implicit "internal" domain.teammates[] (first_name, latin_alias, full_name, email, active) — the attendee-name resolver input. When the user names someone by first name, match against these.trusted_domains[] — internal-org domains; used only to label attendees as internal vs external in output. Has no security gate here (read-only skill).Optional, soft dependency on `/g-event`: if ~/.claude/g-event/config.json exists — or, for users who have not yet migrated from the old plugin name, the legacy ~/.claude/gevent/config.json (read the new path first; fall back to the legacy one only when the new path is absent) — read defaults.calendar (use it as the calendar ID instead of primary) and always_include[] (the notes_bot entry's email tells you which bot appends the Meeting Resources block). If neither file is present, default the calendar to primary and treat the notes bot as unknown — neither is required.
Read ~/.claude/find-call/config.json if it exists, section sources. This is the only file the skill ever writes, and only via the --config wizard — the investigation flow only reads it. It sets, per source, the user's preferred provider order — NOT a hard restriction. No file (or no sources block) → every source is auto. Full model + schema + examples: references/sources.md. Summary:
sources.calendar — auto (default) | cli | mcp. auto = default order (prefer npx @googleworkspace/cli, then a connected Google Calendar MCP). cli = try CLI first, then fall back to MCP. mcp = try MCP first, then fall back to CLI. All values fall back; the difference is only which is tried first.sources.docs — auto (default) | cli | mcp | off. Same preference+fallback as calendar; off = never fetch Meeting Notes (summarize from calendar + transcripts only).sources.transcripts — auto (default) | off | ["sembly", …]. auto = use every connected notetaker MCP (Sembly today; future ones picked up automatically). off = notes-only, never read a transcript even on interpretive queries. A list sets a preference order — try those first, then fall back to any other connected notetaker.Guiding principle — get the data. The config is a preference, never a wall. Try the preferred provider first; if it's unavailable, unauthenticated, errors, or returns nothing, fall through to the next available provider for that source. Only report a source as unavailable when every provider for it failed. Detect provider availability from the session tool list — don't assume. The two genuine hard limits: off disables a source on purpose, and WebFetch is NEVER a provider for Google URLs (it can't supply the auth they need).
If `~/.claude/shared/identity.json` is missing or has no `user`: degrade gracefully. Calendar search still works against primary; attendee-name resolution falls back to matching the literal name against event attendee displayName/email. Surface a one-line banner once:
ℹ No ~/.claude/shared/identity.json found — teammate-name resolution is limited. Run `/clickup:onboard identity` or `/g-event:onboard identity` once to set up your profile + roster (shared across all three plugins).Do not HALT — this skill is useful even with zero config.
Which tool you use for each source is resolved from sources.* (Step 0) as a preference order — try the preferred provider first, fall back to the next available one until the data is retrieved.
npx @googleworkspace/cli calendar events list --params '<JSON>'.mcp__*Google_Calendar*__list_events).sources.calendar (auto/cli → CLI first; mcp → MCP first). If the first choice is unavailable/unauthenticated/errors, fall back to the other. Only fail if both fail — then tell the user how to enable one.WebFetch for any Google URL under any provider (Google URLs need auth WebFetch can't supply).sources.docs, ordered the same way; falls back between paths):npx @googleworkspace/cli drive files export --params '{"fileId":"<id>","mimeType":"text/plain"}' --output ./.tmp/find-call/<id>.txt. The --output path MUST be inside the current working directory — /tmp/... is rejected by the CLI. Then Read the local file.off: skip notes entirely (the one value that does NOT fall back — it's a deliberate disable).sources.transcripts): try the preferred notetaker(s) first, then fall back to any other connected one. Sembly — mcp__sembly-ai__list_meetings for index, mcp__sembly-ai__get_meeting for content. Read-only methods only. If no notetaker is connected (or transcripts: off), skip transcripts and say so.subagent_type: general-purpose, model: sonnet. Never opus for transcript reading; never haiku.Extract from the user's message:
first_name/latin_alias/full_name from identity.json, then against attendee displayName and email username.Run BOTH in parallel via parallel tool calls in the same message, each through its resolved provider (Step 0). Skip the transcript query if transcripts: off or no notetaker MCP is connected.
Calendar (provider cli/auto shown; under mcp, call the Calendar MCP's list-events with the same range):
npx @googleworkspace/cli calendar events list --params '{
"calendarId":"<defaults.calendar or primary>",
"timeMin":"<range_start_minus_1day>T00:00:00Z",
"timeMax":"<range_end_plus_1day>T23:59:59Z",
"singleEvents":true,
"maxResults":50,
"orderBy":"startTime"
}'Pad ±1 day for local-time/UTC boundary. Filter out eventType in ('workingLocation','focusTime','outOfOffice').
Transcripts (e.g. Sembly, when connected and not off):
mcp__sembly-ai__list_meetings(start_at=range_start, finish_at=range_end)For each event, compute a relevance score:
score = (3 if exact phrase substring in title else 0)
+ (2 per attendee-name match if user named someone)
+ (1 per significant keyword match in title or description preamble)
+ (recency: this-week=2, this-month=1, older=0)
- (1 if the user's responseStatus == "declined")# TUNE-ME — these constants are v1 starting points; revisit after a few weeks of real use.Decision tree:
top_score / second_score > 1.5 AND top is within last 7 days → silent auto-pick top, but show inline "Picked '<title>' (also considered: <Y>, <Z>); say 'no, <Y>' to switch."AskUserQuestion with dated options.AskUserQuestion with options grouped by week, plus an "All <N> (parallel investigation)" option capped at 10 events. If N>10, ask user to narrow first.AskUserQuestion format:
question: "Found <N> matches for '<query>' — which did you mean?"
options:
- "<Apr 28> — <Title> (<duration>)"
- "<Apr 25> — <Title> (<duration>)"
- "All <N> (parallel)"
- Other (user types narrower phrase)For each selected event:
https://docs\.google\.com/document/d/([A-Za-z0-9_-]+) — capture all and label by adjacent link text (This Call, Project Calls, Open).https://drive\.google\.com/file/d/([A-Za-z0-9_-]+)https://drive\.google\.com/drive/folders/([A-Za-z0-9_-]+)?usp=drivesdk, ?tab=t.0) before passing to the CLI.drive files export → ./.tmp/find-call/<docId>.txt).list_meetings results for date+title fuzzy match → get_meeting if hit.get_meeting if its date+title fuzzy-matches any item in the parallel list_meetings result.For each matched event, decide whether to spawn a transcript sub-agent:
Spawn transcript sub-agent when ANY of:
tone, react, push back, hesitate, lash, rant, verbatim, exact words, mood, defensive, confident, convince, justify, explain, defend, body language (liberal — when in doubt, include).Cap: maximum 5 transcript sub-agents per query. If matches > 5 and the cap is hit, deep-read the top-5-ranked and notes-only the rest. Tell the user explicitly which is which.
Sub-agent prompt template (sonnet, general-purpose):
You are a transcript reader for one call. Read this transcript and answer:
1. What did {user.name} personally say or commit to? (quote verbatim with line refs if available)
2. What decisions were made and by whom? (cite the speaker)
3. What action items exist for {user.name}? (cite the moment)
4. <the user's specific question if any>
Source files (read these and ONLY these):
- Notes: <local path>
- Transcript: <local path or Sembly meeting id>
Sembly meeting id (optional): <id>
RULES:
- Cite every claim with a quote or line reference. Never paraphrase a commitment without a quote.
- If the transcript doesn't contain an answer, say "transcript does not contain this" — do not invent.
- Output ≤ 1500 tokens.Answer the user's actual question first, directly. The output exists to answer what was asked — not to fill a template. Lead with the answer; cite it; stop.
Hard rules regardless of shape: every claim carries a citation (Doc URL / transcript line / meeting ID); state your sources read; if a transcript was NOT read for a call, say so explicitly; never pad with boilerplate, hedging, or generic framing.
Fallback structure for open-ended summaries (drop empty sections — do not emit a header with "none"):
## <Date> — <Event Title> (<duration>)
Sources read: [Meeting Notes](<doc url>) [• [Transcript](<doc url>)] [• <provider>: <meeting_id>]
**Summary:** <2-3 sentences — only what's grounded in sources>
**Key decisions:** <cite each> · **{user.name}'s commitments:** <cite verbatim where possible> · **Open questions / blockers:** <only if present>
<If transcript was NOT read for this match:>
> _Notes-only for this call — say "deep dive on this one" if you want me to read the full transcript._For multiple matched calls, add a 1-paragraph cross-cutting synthesis at the end ONLY if it surfaces a real pattern supported by ≥2 sources — otherwise skip it:
**Across these <N> calls:** <synthesis — only patterns supported by ≥2 sources>v1 SHIPS STATELESS. Skip this step. The infrastructure below is documented for v1.1.
After successful disambiguation, IF v1.1 has memory enabled, propose:
AskUserQuestion: "Want me to remember '<phrase>' = <project>/<event-pattern> for next time?"
options: ["Yes, remember", "No, ask me each time"]On Yes → append to references/aliases.md (plugin-local) with learned: <ISO date> stamp.
Action Points section or are spoken verbatim in the transcript.| Code | Symptom | Handler |
|---|---|---|
| F1 | No Meeting Resources block in event description | Try Sembly (if connected) → if miss, return calendar description + offer adjacent-event search |
| F2 | Doc rotated; current Open link is the new active file, doesn't contain this meeting | Search exported text for Topic: <title> or event date — if not found, fall back to Parent Folder, search archived files by name |
| F3 | Sembly has it, Calendar doesn't | Sembly query runs in parallel; merge by datetime+title fuzzy |
| F4 | Phrase matches >1 events | Tiered disambiguation (Step 4) |
| F5 | Description is HTML, not plain | Use the regex set in Step 5; do not parse as HTML |
| F6 | Timezone boundary misses a borderline event | Pad timeMin/timeMax by ±1 day, filter post-hoc |
| F7 | Title [PFX] Roadmap Review (External) doesn't match "Roadmap Review call" | Match against title substring AND attendee names AND description content |
| F8 | Drive export 403 | Catch error, fall back to Sembly for that meeting, surface "permission needed" warning |
| F9 | Notes section <500 tokens | Auto-escalate to transcript sub-agent for that single event |
sources.* is mcp, or as the fallback when cli/auto prefers the CLI but the CLI can't deliver. Never refuse a working MCP fallback.drive files export mimeType=text/plain is the path.g-event/config.json — including the legacy gevent/config.json — or clickup/config.json). This skill is a read-only consumer of shared/other-plugin state; /clickup and /g-event own those writes. The ONLY file this skill ever writes is its own ~/.claude/find-call/config.json, and ONLY via the --config wizard through scripts/config_io.py.Names below are illustrative placeholders. In practice the person references resolve against the user's ~/.claude/shared/identity.json teammate roster.Example 1 — single match, action-item query (notes sufficient):
User: "what did I commit to in the AI Assessment Bot meeting yesterday?"
Skill:
AI Assessment Bot (yesterday).Action Points → {user.name} section verbatim, with the Doc URL.Example 2 — multi-match, "all of them":
User: "summarize my calls with Dana this week"
Skill:
dana.* (resolved via identity.json) → 3 matches.Example 3 — no Meeting Resources block:
User: "what did Sam and I talk about in our 1-on-1 last week?"
Skill:
1on1 - {user.name} / Sam (last week) — no Meeting Resources block (1-on-1, no notes bot).summary, decisions, tasks fields.Sembly meeting <id>. If Sembly is not connected, return the calendar description and offer adjacent-event search.Example 4 — interpretive query forces transcript:
User: "did the client push back when I proposed the limited rollout?"
Skill:
/clickup directly — let the user initiate.Read-only. Writes nothing. Shows which provider each source will resolve to, what's connected, and where identity/calendar defaults come from.
python <plugin-root>/scripts/config_io.py --show (e.g. python plugins/find-call/scripts/config_io.py --show). It returns JSON: current sources preferences, whether the config file exists, any corruption error, and whether the npx CLI binary is present (cli_binary_available). Auth is NOT probed here — note that the CLI may be present but unauthenticated.mcp__*Google_Calendar*), a Drive MCP, and notetakers (mcp__sembly-ai__*). The script can't see session MCPs — this step is yours.~/.claude/shared/identity.json exists (identity inherited from /clickup or /g-event) and ~/.claude/g-event/config.json — falling back to the legacy ~/.claude/gevent/config.json when the new path is absent — (calendar default).auto and say "not set — all auto"./find-call:status
calendar : auto → cli (googleworkspace CLI present ✓; auth not probed)
docs : auto → cli ✓
transcripts: auto → sembly connected ✓
identity : inherited from g-event ✓ (~/.claude/shared/identity.json)
calendar id: primary (no g-event defaults.calendar)
config : ~/.claude/find-call/config.json — not set (all auto)Interactive wizard. This is the ONLY write this skill performs — and it writes ONLY ~/.claude/find-call/config.json, via the guarded helper. Never touches identity.json, g-event/config.json (or the legacy gevent/config.json), or clickup/config.json.
python <plugin-root>/scripts/config_io.py --show. Pre-select the current value for each source.AskUserQuestion — one question per source, marking the current value. Make clear these are preferences, not restrictions — the skill always falls back to whatever works (except off):auto (default order) / cli (prefer the googleworkspace CLI, fall back to MCP) / mcp (prefer a Calendar MCP, fall back to CLI).auto / cli (prefer CLI) / mcp (prefer MCP) / off (never fetch Meeting Notes — the only non-fallback value).auto (every connected notetaker) / off (notes-only, never read a transcript) / sembly (prefer Sembly, fall back to other connected notetakers).flock + tmp + os.replace): python <plugin-root>/scripts/config_io.py --set calendar=<v> docs=<v> transcripts=<v>Only pass the keys the user changed. The script rejects invalid values rather than writing them.
/find-call:status to verify resolution."Never write any file other than config.json. If the script exits non-zero, surface its stderr and do not retry blindly.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.