agentleFS
tengfei0114ustengfei0114us/meeting-scribe/SKILL.md

meeting-scribe

Skill0 starsChanged 4 months agoMITView on GitHub

Use when the user asks to process new recordings or voice memos into meeting minutes/notes — e.g. "process my recordings", "transcribe the new audio", "check my voice memos", "处理录音", "扫一下录音", "转录音频", "处理语音备忘录" — or reports a fresh local recording without giving a path ("本地有录音", "刚录了个会", "I just recorded a meeting"), or when audio files are waiting in the recording inbox, or on first use when ~/.meeting-scribe/config.yaml does not exist yet.

  • Installs packages

4 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions4 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

The file

We split this skill into the rules it gives an agent

11 rules. 0 are common, 6 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 6

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Do NOT add immutable=1 (it ignores the WAL, where the newest recordings live).

    line 85
  2. Distinctive:

    When the user reports a fresh local recording that isn't in any inbox ("本地有录音", "I just recorded a meeting"), locate it — never ask them …

    line 99
  3. Very specific:

    After the run, offer to add that folder to inbox_dirs (or enable the Voice Memos source) so next time it's hands-off.

    line 116
  4. Very specific:

    All transcription/analysis happens in $W on plain local disk.

    line 128
  5. Very specific:

    If the transcript opening reads like multiple people (meeting/discussion), use <skill>/bin/transcribe_speakers.sh "<audio>" [lang] …

    line 176
  6. Very specific:

    every (存疑)/(uncertain) mark in the minutes,

    line 241
  7. Distinctive:

    every SPEAKER→real-name guess not yet confirmed by a namemap.

    line 243
  8. Distinctive:

    Ask once: should any of these minutes be sent to someone?

    line 270
  9. Distinctive:

    Create a DRAFT only:

    line 275
  10. Distinctive:

    If the session has a working email tool the user has set up (MCP/CLI), use it to save into the drafts folder.

    line 276
  11. Distinctive:

    Never keep processing on top of a corrupted batch.

    line 324

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

meeting-scribe

Batch-process raw audio recordings into speaker-attributed transcripts and structured, context-aware meeting minutes (summary / key points / decisions / todos / mind map), then archive everything to the right folder and optionally draft the minutes for delivery. Transcription is 100% local (whisper.cpp); analysis is done by you, informed by the user's glossary, project cards, and scene cards. Recordings come from the user's own devices — computer or phone recordings dropped into the inbox folder(s), the Apple Voice Memos library (optional), or located on demand when the user just says they recorded something (see Step 1-alt).

State lives in $MEETING_SCRIBE_HOME (default ~/.meeting-scribe/), never in this skill directory. Below, $SCRIBE means that state directory and <skill> means the directory containing this SKILL.md.

First-run setup (no $SCRIBE/config.yaml → do this instead of the pipeline)

Interview the user conversationally (their language), then generate their state. Ask:

  1. Inbox: which folder(s) will hold incoming recordings? (offer to create one)
  2. Identity: one line — name, role, company — so analysis knows who "I" is and who todos default to.
  3. Languages: main recording language (whisper code, or auto); minutes language.
  4. Routes: where should minutes be archived? Offer to ls their documents folder and propose route keys per client/project/team. personal and fallback are required.
  5. Context hint: one sentence describing what recordings usually are (feeds the whisper initial prompt).
  6. People: frequent collaborators (name + role) to seed the glossary.
  7. Voice Memos (macOS only): also scan the Apple Voice Memos library? If yes, tell them it may require Full Disk Access for the AI assistant's app.
  8. Timezone: does the device clock differ from the team's timezone? If so record a timezone_note.

Then: mkdir -p $SCRIBE/{projects,scenes,models,_work,logs}; write config.yaml (follow <skill>/config.example.yaml); instantiate <skill>/prompts/glossary.template.md → $SCRIBE/glossary.md filled with their answers (replacing the <skill_dir> placeholder in its maintenance-rule line with this skill's real path); copy <skill>/prompts/projects/_INDEX.md, all of <skill>/prompts/scenes/, and the right output_template.*.md → $SCRIBE/output_template.md; offer to draft their first real project card from a short description of a current project; run python3 <skill>/bin/gen_whisper_prompt.py; finally check the environment (below) and offer <skill>/bin/download-model.sh large-v3-turbo (~1.6GB; --mirror flag for mainland-China networks). Verify each written file exists and is non-empty before declaring setup done.

Environment check (start of every run)

command -v whisper-cli && command -v ffmpeg && ls "$SCRIBE"/models/*.bin

Anything missing → stop and tell the user how to install (brew install whisper-cpp ffmpeg; <skill>/bin/download-model.sh large-v3-turbo).

Always transcribe through <skill>/bin/transcribe.sh — never hand-roll a whisper-cli command. Its model choice, beam size, and thread count are deliberate power/speed tuning (saturating every core once melted a laptop's battery budget for zero quality gain). One-off audio outside the inbox goes through it too.

Main pipeline

Report progress briefly after each step. The pipeline pauses for the user exactly three times — Step 3 (scenes & materials), Step 6 (uncertain terms, auto-skipped when empty), Step 8 (delivery) — everything else runs autonomously.

Step 1 — Scan for new audio

Sources:

  • A. Inbox dirs from config: find <inbox> -type f \( -iname "*.m4a" -o -iname "*.mp3" -o -iname "*.wav" -o -iname "*.aac" -o -iname "*.m4v" \) -not -path "*/.processed/*"
  • B. Apple Voice Memos library (only if voice_memos_source: true): ~/Library/Group Containers/group.com.apple.VoiceMemos.shared/Recordings/ — scan *.m4a AND *.qta (the newer container; mp4 family, ffmpeg reads it).
    • Operation not permitted → skip source B, continue, and in the final report tell the user: System Settings → Privacy & Security → Full Disk Access → add their AI assistant's app. Never interrupt the run for this.
    • Titles live in CloudRecordings.db, not filenames: sqlite3 "file:$VM_DIR/CloudRecordings.db?mode=ro" "SELECT ZCUSTOMLABEL, CAST(ZDURATION AS INT) FROM ZCLOUDRECORDING WHERE ZPATH='<file>'". Do NOT add immutable=1 (it ignores the WAL, where the newest recordings live). ZDATE is a Core Data timestamp: add 978307200 for Unix time. A ZCUSTOMLABEL that is an ISO datetime string means the user never renamed it — not a real title. Any DB error → proceed without titles.
    • IRON RULE: source B is read-only. Only cp out of it — never mv/rm/write. Deleting recordings is the user's job in the app; a corrupted sync library is unrepairable.

Dedup (both sources): key = basename + byte size (stat -f %z). Skip keys already in $SCRIBE/logs/processed.jsonl. Nothing new → report "no new recordings", done.

Step 1-alt — "I just recorded something" (no path given)

When the user reports a fresh local recording that isn't in any inbox ("本地有录音", "I just recorded a meeting"), locate it — never ask them for a path first:

  1. Newest files in inbox_dirs and (if enabled) the Voice Memos library.
  2. Spotlight, whole disk, last 48h (syntax verified): mdfind 'kMDItemContentTypeTree == "public.audio" && kMDItemFSContentChangeDate >= $time.now(-172800)' Widen the window if the user says it was earlier. Spotlight skips most of ~/Library — the Voice Memos check above covers that gap. This also finds audio received via chat apps ("someone sent me the recording").
  3. Spotlight off/empty → find ~/Downloads ~/Desktop ~/Documents -maxdepth 3 for audio extensions with -mtime -2, plus meeting apps' local recording dirs when present (~/Documents/Zoom, ~/Documents/TencentMeeting).
  4. Drop already-processed keys and obvious noise (app resources, system sounds, clips under ~20s unless that's what the user described). Get durations cheaply via mdls -name kMDItemDurationSeconds (ffprobe as fallback). Present candidates newest-first — name, duration, parent folder — and let the user pick; exactly one plausible candidate → state which file you're taking and proceed.
  5. After the run, offer to add that folder to inbox_dirs (or enable the Voice Memos source) so next time it's hands-off. Config growing out of real usage beats asking users to predict their folders upfront.

Then continue with the normal pipeline below.

Copy every to-do file into the local work dir $SCRIBE/_work/<YYYYMMDD>/:

cp "<src>" "$W/" && [ "$(stat -f %z "<src>")" = "$(stat -f %z "$W/<name>")" ] || echo "COPY FAILED, retry"

All transcription/analysis happens in $W on plain local disk. Cloud-synced folders (OneDrive/iCloud/Dropbox) are touched only at the two ends — scan and final archive — and /tmp is never used: sync jitter and tmp-cleaning have both caused files to get silently swapped mid-run. Quote all paths (spaces are common). Recordings of 3h+ take 15-30 min of GPU time — warn the user and suggest running in the background.

Step 2 — Transcribe (+ hallucination rescue ladder)

<skill>/bin/transcribe.sh "$W/<file>"          # inbox files: config transcribe_language
<skill>/bin/transcribe.sh "$W/<file>" auto     # Voice Memos files: language unpredictable

stdout is the .txt path. Exit code 6 = degenerate output (empty/near-empty or loop-repeated) — the run silently failed; retry once, then use the rescue ladder. Other non-zero exits: report stderr, skip the file. After success, confirm with an independent command (wc -c) — don't trust the same command's output.

Transcripts largely in a foreign language stay untranslated (archival value; terms stay exact). Translation happens only in the minutes layer. Gibberish from an inbox file often means a mixed-language meeting forced to one language — retranscribe with auto.

Hallucination check (always):

awk '{if($0==prev)c++; else{if(c>max)max=c; c=0; prev=$0}} END{if(c>max)max=c; print max}' <txt>

max repeat > 50 → whisper looped (common on far-field/noisy audio). Rescue ladder, in order:

  1. whisperX (if installed — check $SCRIBE/.venv-whisperx/): VAD pre-segmentation sidesteps the loop. --compute_type int8 --batch_size 8 --vad_method silero --no_align, output overwrites the bad .txt (expected). Don't pipe the command (| tail eats the real exit code). Skip whisperX for 1h+ files — its memory peak gets SIGKILLed on long audio and it truncates the output file first.
  2. Slice-and-retranscribe (big files): split with ffmpeg into 10-min segments, transcribe each (fresh decoder per segment — a bad segment can't infect the rest), concatenate. Re-transcribing the whole file with the same settings is useless: decoding is deterministic, you get the identical output.
  3. Dedup-degrade (last resort): collapse consecutive identical lines; if ≥80% of real content survives, mark the transcript degraded and continue to analysis.

Still hopeless → move audio to the fallback route dir under _transcribe-failed/, skip.

Step 2.5 — Speaker diarization (multi-speaker recordings; optional module)

If the transcript opening reads like multiple people (meeting/discussion), use <skill>/bin/transcribe_speakers.sh "<audio>" [lang] [known_speaker_count] instead — it produces <name>.speakers.txt ([SPEAKER hh:mm] text). Missing venv or any failure auto-degrades to plain transcription without blocking. Pass the speaker count when known — steadier. Single-voice notes skip this.

  • Capability honesty: long solo passages attribute well; rapid interjections and crosstalk blur — attribute by large blocks, and mark decision-attribution as uncertain rather than guessing.
  • First run yields SPEAKER_00/01…: infer real names from content + glossary people + who-spoke-when; the guesses go into the Step 6 review for user confirmation, and confirmed mappings are saved as <name>.namemap.json next to the audio so recurring voices auto-map next time.

Step 3 — Scene choice + supporting materials (ONE batched exchange, before analysis)

Present a compact list — per file: name, duration, a one-line gist from the transcript opening, and a suggested scene (match $SCRIBE/scenes/ trigger signals). Built-in scenes: sales (client-facing sales meeting), team_weekly (internal team weekly), bd (business development / negotiation), personal_thinking (solo voice notes) — plus any cards the user has added, plus standard (minutes only, no extra deliverable).

Then ask, in one message:

  1. Scene per file — confirm or override the suggestions ("all as suggested" is a perfectly good one-word answer).
  2. Supporting materials — do they have an agenda, chat log, prior minutes, slides, or attendee list for any of these meetings? Paste or drag files (txt/md/docx/pdf), or skip. Materials sharpen term corrections, speaker naming, and todo attribution.

Copy any provided material into $W, note which recording it belongs to. This exchange also serves as the batch gate: if there are more than 5 files, get an explicit go before continuing. If the user has said to run fully automatically (now or in their standing instructions), skip the questions: scenes = auto-match, no materials.

Step 4 — Analysis (context-aware, parallel when ≥3 files)

  • < 3 files: analyze in the main session — Read the transcript (.speakers.txt preferred), then <skill>/prompts/classify.md and follow it (it loads $SCRIBE/glossary.md, matches $SCRIBE/projects/ cards, applies the assigned scene, applies corrections, outputs the JSON). Read any supporting material for the file before analyzing.
  • ≥ 3 files: dispatch one general-purpose subagent per file in a single message (max ~10 concurrent). Each prompt must be self-contained: fill the template in <skill>/prompts/subagent_analysis.md (it has slots for the assigned scene and supporting materials). No subagent capability in this harness → process the files sequentially with the same per-file procedure instead.

Validate every JSON: category/title/routing_hint required; strip /\:*?"<>| from title, cap 30 chars; empty date → infer from transcript or filename timestamps. Honor timezone_note from config when dating minutes.

Step 5 — Minutes document

Subagent path: already written — skip ahead. Main-session path: fill $SCRIBE/output_template.md ({{title}}, {{date}}, {{category}}, {{routing_hint}}, {{routing_reason}}, {{summary}}, {{participants}} joined, {{key_points}}/{{decisions}} as - lists, {{todos_table}} rows, {{mindmap_markdown}}, {{transcript}} — condense very long ones, {{audio_filename}}).

Step 6 — Uncertain-term review with the user (the human half of the flywheel)

Collect across ALL files of the batch:

  • every (存疑)/(uncertain) mark in the minutes,
  • every new_corrections candidate,
  • every SPEAKER→real-name guess not yet confirmed by a namemap.

Nothing collected → skip this step silently. Otherwise present ONE numbered list — each item: the transcribed form, your best guess, one line of context — and let the user confirm / fix / dismiss everything in a single reply. Then apply:

  • Fix the affected minutes files in place (they exist already — edit, don't regenerate).
  • User-confirmed corrections go straight into the glossary tables — human sign-off replaces the usual 100%-certainty bar. Note the month. This is how the personal glossary compounds: every review makes the next transcription better.
  • Confirmed speaker names → <name>.namemap.json.
  • Dismissed items: remove the uncertainty mark if the user says the transcription was right; leave the mark if genuinely unresolvable.

Step 7 — Archive (always the main session's job)

target_dir = route from config for the routing_hint, plus /<date>_<title>/. Copy in: transcript.txt, the audio, .srt if present, any supporting materials the user provided for that meeting. The minutes .md is already there — never overwrite it.

Write-then-verify (mandatory): afterwards run an independent ls -la — every file present and >0 bytes. Cloud-synced targets can fail silently or ENOENT when unhydrated; on failure, land the whole bundle in the fallback route dir instead and tell the user to enable "always keep on this device" for the target.

Step 8 — Delivery (optional; ask AFTER archiving)

Ask once: should any of these minutes be sent to someone? For each file the user wants delivered, ask which channel:

  • Email draft: collect recipients (and cc) and a subject (default: minutes title + date). Body = the minutes minus the raw transcript section, simply formatted. Create a DRAFT only:
    • If the session has a working email tool the user has set up (MCP/CLI), use it to save into the drafts folder.
    • Otherwise on macOS, open a Mail.app draft window via AppleScript: osascript -e 'tell application "Mail" to make new outgoing message with properties {subject:"...", content:"...", visible:true}' (add recipients via make new to recipient). Nothing is sent by this — it opens a compose window.
    • IRON RULE: never trigger the send action, never ask for email passwords or credentials. Tell the user: the draft is ready — review it and hit send yourself.
  • Chat snippet (WeChat / Feishu / Slack …): produce a SHORT plain-text version to copy-paste: no markdown syntax, single line breaks, plain numbered lists, ≤10 lines total — one-line conclusion first, then 3-5 key points, then todos with owners. Print it in the conversation for copy-paste; do not automate the chat app.

"No" ends the step immediately. Close the loop by telling the user where each draft landed (drafts folder / compose window / snippet above).

Step 9 — Finish

Per successfully processed file:

  1. Audio → processed_archive/YYYY-MM/ (month from the recording's date). Inbox originals: mv, then verify with an independent find (cloud-sync can error on mv yet still have moved it — blind retries create duplicates). Voice Memos originals: cp from _work (library untouched, forever).
  2. .txt/.srt also copied there.
  3. Append to $SCRIBE/logs/processed.jsonl: {"key":"<name>+<size>","audio":"...","category":"...","target":"...","title":"...","at":"<ISO8601>"}
  4. Whole batch archived → delete $SCRIBE/_work/<batch>/.

Correction flywheel wrap-up: user-confirmed entries were already merged in Step 6. Remaining auto-candidates (high bar: the wrong form can't legitimately occur AND the right form is 100% certain) may also be merged — grep first to avoid duplicates. New high-frequency terms join the Priority terms section. Any glossary change → python3 <skill>/bin/gen_whisper_prompt.py — without this, the transcription layer never improves.

Step 10 — Report

One markdown table: # | file | scene | title | route | archive path | delivery | status. Prefix fallback rows with ⚠️ and ask the user to file them manually — never guess.

Failure signals — stop and re-verify ground truth

Same path exists-then-doesn't; cp/mv errored but the file is there; two different recordings produce identical transcript openings. Any of these → the filesystem state is lying (usually cloud-sync jitter or a machine fresh out of sleep). Stop, verify with independent find + byte counts + content spot-checks before continuing. Never keep processing on top of a corrupted batch.

Interaction rules

  • Exactly three pause points (Steps 3, 6, 8); each is one batched exchange with a one-word fast path. Everything else runs without asking.
  • User says "fully automatic" / "don't ask" (now or in standing instructions) → scenes auto-match, uncertain terms go into the final report table instead of a review, delivery is skipped.
  • User interrupts mid-run → processed.jsonl already has finished files; next run won't repeat them.
  • Minutes/summaries/todos are written in output_language; transcripts keep their original language.
  • Scene-card iron rules outrank everything — especially: operational numbers in scene deliverables (weekly reports etc.) are [待填]/[TBD] placeholders, never copied from speech — transcribed digits are the least reliable thing in the file.
  • Email drafts are drafts: the skill never sends anything, on any channel.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.