meeting-brief

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Meeting brief

会议简报

Two things that compose: turn recordings into an archived meeting report, and talk back to you in audio instead of text.
Either half works alone.
/meeting-brief
on a folder of
.m4a
files does the first. "answer me in audio from now on" does the second.
它由两部分组成:将录音转换为已归档的会议报告,以及用音频而非文本向您回复。
两部分均可单独使用。对某个
.m4a
文件夹运行
/meeting-brief
可完成第一部分。“从现在起以音频回答我”则启用第二部分。

Hard rules

硬性规则

  • Never upload the user's audio to a public host. Not tmpfiles, not 0x0.st, not transfer.sh, not a quick tunnel. These recordings can contain confidential information: salary negotiations, equity terms, revenue, customer names. The only sanctioned channel is a presigned S3 URL in a private bucket. This rule exists because it was broken once.
  • Presigned only, private ACL. Never
    --acl public-read
    , never a bucket policy change, never a bucket whose name contains
    public
    or
    assets
    .
  • Delete each clip once the user replies. Their next message is the signal they have listened. Run
    unshare.sh
    on the previous object before doing anything else in that turn.
  • Never echo an API key or secret into the transcript. The scripts read them from the environment or a
    .env
    and never print them.
  • 绝不能将用户的音频上传到公共主机。 不得使用 tmpfiles、0x0.st、transfer.sh,也不得使用临时隧道。这些录音可能包含机密信息:薪资谈判、股权条款、营收和客户名称。唯一获准的渠道是私密存储桶中的预签名 S3 URL。之所以制定此规则,是因为该规则曾被违反过。
  • 仅使用预签名链接,并设置 private ACL。 绝不能使用
    --acl public-read
    ,绝不能修改存储桶策略,也绝不能使用名称中包含
    public
    或
    assets
    的存储桶。
  • 用户回复后立即删除每个音频片段。 用户的下一条消息即表示其已经收听。在该轮执行任何其他操作之前,先对上一个对象运行
    unshare.sh
    。
  • 绝不要在转录文本中回显 API key 或 secret。 脚本从环境变量或
    .env
    中读取这些信息,且绝不打印它们。

AWS credentials come from the project, never from a global profile

AWS 凭据来自项目,绝不来自全局配置文件

A project can keep its own
.env
with its own AWS account, so credentials are resolved from the project rather than a global profile.
scripts/aws-env.sh
walks up from the working directory to the first
.env
that yields usable credentials.
Variable names are not always standardised. The resolver tries canonical
AWS_ACCESS_KEY_ID
/
AWS_SECRET_ACCESS_KEY
first, then the short forms
AWS_ACCESS_KEY
/
AWS_ACCESS_SECRET
. Region falls back to
us-east-1
, then gets corrected per bucket via
get-bucket-location
.
Set
MEETING_BRIEF_BUCKET
to the private bucket the audio should go to. It must have public access blocked; this skill uploads with a private ACL and shares only via presigned URL, and never makes anything world-readable.
项目可以在自己的
.env
中配置独立的 AWS 账户,因此凭据应从项目而非全局配置文件解析。
scripts/aws-env.sh
会从当前工作目录向上逐级查找,直到找到第一个可提供可用凭据的
.env
。
变量名称并不总是统一。解析器会先尝试标准名称
AWS_ACCESS_KEY_ID
/
AWS_SECRET_ACCESS_KEY
,然后尝试短格式
AWS_ACCESS_KEY
/
AWS_ACCESS_SECRET
。区域最初回退为
us-east-1
,随后根据存储桶通过
get-bucket-location
进行修正。
将
MEETING_BRIEF_BUCKET
设置为存放音频的私密存储桶。该存储桶必须阻止公共访问;此 skill 使用 private ACL 上传,仅通过预签名 URL 分享,绝不会将任何内容设为全球可读。

Flow A: recordings to archived report

流程 A:从录音到已归档报告

bash
S=.claude/skills/meeting-brief/scripts
OUT=/tmp/claude/<project>/meeting-<date>

$S/transcribe.sh $OUT *.m4a                     # parallel, diarised, resumable
$S/format-transcript.mjs $OUT/raw-<x>.json \
    --map speaker_0=Alice,speaker_1=Bob          # speaker-grouped, timestamped
Then read the transcripts, identify who is who from context, and write the report.
Always pass keyterms.
keyterms.txt
ships with this skill and
transcribe.sh
sends it automatically. Without it Scribe v2 mangles unusual product, company or tool names into similar-sounding common words, which then poisons every downstream summary. Add your project's nouns to that file as they come up.
Diarisation gives
speaker_0
,
speaker_1
, not names. Work out the mapping from the content before writing anything, and say in the summary which label is whom. For load-bearing facts (who owns a follow-up, who is off tomorrow, personal context) confirm WHO with the user before saving if the mapping is at all ambiguous, and write the draft as "I put X on speaker A and Y on speaker B, flip if backwards" so they can correct fast.
bash
S=.claude/skills/meeting-brief/scripts
OUT=/tmp/claude/<project>/meeting-<date>

$S/transcribe.sh $OUT *.m4a                     # parallel, diarised, resumable
$S/format-transcript.mjs $OUT/raw-<x>.json \
    --map speaker_0=Alice,speaker_1=Bob          # speaker-grouped, timestamped
然后读取转录文本,根据上下文确定每个说话人的身份,并撰写报告。
务必传入 keyterms。
keyterms.txt
随此 skill 一同提供,
transcribe.sh
会自动发送它。如果没有该文件,Scribe v2 会将不常见的产品、公司或工具名称误写成发音相近的常见词,进而污染所有下游摘要。出现项目专有名词时,应将其加入该文件。
说话人分离只会生成
speaker_0
、
speaker_1
,而不是姓名。撰写任何内容前,应根据内容推断映射关系,并在摘要中说明哪个标签对应哪个人。对于关键事实(谁负责跟进、谁明天休假、个人背景等),如果映射有任何歧义,保存前应向用户确认具体是谁,并先写出这样的草稿:“我把 X 放在 speaker A,把 Y 放在 speaker B,如果对应反了请告诉我”,以便用户快速纠正。

Quota (
quota_exceeded
)

配额(
quota_exceeded
)

Scribe v2 charges about 1.1 credits per second of audio. On
quota_exceeded
, do not give up: most long meetings have silent gaps (pre-start setup, dead air, long pauses). Use
ffmpeg
to strip silence and retry:
bash
ffmpeg -y -i input.m4a \
  -af "silenceremove=stop_periods=-1:stop_duration=2:stop_threshold=-40dB" \
  -c:a aac -b:a 64k trimmed.m4a
That removes 20-40% of duration with zero speech loss. Re-check duration (
ffprobe
) and recompute credits before resubmitting. Archive the ORIGINAL audio, never the trimmed copy: the trim is a transcription-time artifact, not the source of truth.
Scribe v2 的收费约为每秒音频 1.1 credits。遇到
quota_exceeded
时不要放弃:大多数长会议都存在静音间隙(会前准备、无人发言、长时间停顿)。使用
ffmpeg
去除静音后重试:
bash
ffmpeg -y -i input.m4a \
  -af "silenceremove=stop_periods=-1:stop_duration=2:stop_threshold=-40dB" \
  -c:a aac -b:a 64k trimmed.m4a
这样可以在不丢失任何语音内容的情况下将时长缩短 20% 至 40%。重新提交前,使用
ffprobe
重新检查时长并重新计算 credits。始终归档原始音频,绝不归档裁剪后的副本:裁剪仅是转录阶段的临时产物,不是事实来源。

Ingesting the recording (dating, sequencing, archive-first)

导入录音(确定日期、排序、先归档)

Recordings arrive however the project delivers them: a drop in the workspace root, an export in
~/Downloads
, a phone file, or an auto-upload to the S3 inbox. The name is usually generic (
meet.m4a
,
rec.m4a
,
audio 3.m4a
) and carries no reliable date. Four rules hold regardless of source:
  • Archive BEFORE summarising, always. The moment transcription succeeds, write the
    <audio>
    +
    .txt
    pair into
    arch/transcripts/
    BEFORE the report, action items, or anything else, even if the user only asked "what did they decide". They should never have to ask for the archive. Verify it landed (checksum the copy against the source) rather than assuming.
  • The date is the RECORDING date, never today and never the download date. A file's mtime is the copy/export time: for a fresh same-day drop that equals the meeting date, but an exported or re-shared file can be days off, and several files exported in one session share one mtime. Cross-check against the transcript (weekday mentions, "yesterday we shipped X", a recent-changes log) and the gap since the last
    arch/transcripts/
    file. Only ask the user when the evidence disagrees. If the source is an Apple Voice Memos original (
    20260806 104557.m4a
    ), that filename IS the start time: match the shared copy back to it by byte size then checksum, and a match is proof, so no need to ask.
  • Sequence number: take the highest existing
    rec-NNN
    in
    arch/transcripts/
    and use the next integer; say so in chat so the user can correct a skipped one. If the export name already carries a number (
    Mi grabación 202.m4a
    ->
    rec-202
    ), keep it. Never leave a recording unarchived just because the number is uncertain.
  • A drop box is not storage. When recordings land in a shared drop location (workspace root, a Downloads folder, an S3 inbox), MOVE the file into
    arch/transcripts/
    once the archived copy is checksum-verified, then delete the source so the next drop is unambiguous. Two or more audio files waiting at once means several meetings: process oldest first or ask which, never merge them.
录音可能通过项目提供的任意方式到达:放入工作区根目录、导出到
~/Downloads
、来自手机的文件,或自动上传到 S3 收件箱。文件名通常很通用(
meet.m4a
、
rec.m4a
、
audio 3.m4a
),不包含可靠日期。无论来源为何,都应遵循以下四条规则:
  • 始终先归档,再进行摘要。 转录成功后,应立即将
    <audio>
    +
    .txt
    文件对写入
    arch/transcripts/
    ,然后再处理报告、行动项或其他任何内容,即使用户只问了“我们决定了什么”。用户不应需要另行要求归档。应验证文件确实已落盘(将副本与源文件进行校验和比对),不能想当然地认为已成功。
  • 日期是录音日期,既不是今天,也不是下载日期。 文件的 mtime 是复制或导出的时间:对于当天刚放入的录音,它可能等于会议日期;但导出或转发的文件可能相差数天,而且一次会话中导出的多个文件会共享同一个 mtime。应结合转录内容(星期几的表述、“昨天我们发布了 X”、近期变更日志)以及与
    arch/transcripts/
    中上一个文件的时间间隔进行交叉验证。只有在证据相互矛盾时才询问用户。如果源文件是 Apple Voice Memos 原始文件(
    20260806 104557.m4a
    ),该文件名本身就是开始时间:先按文件大小将共享副本与其匹配,再通过校验和确认;匹配成功即可作为证据,因此无需询问。
  • 序列号: 找出
    arch/transcripts/
    中现有的最大
    rec-NNN
    ,然后使用下一个整数;在聊天中告知用户,以便其纠正跳号。如果导出文件名已经带有编号(
    Mi grabación 202.m4a
    ->
    rec-202
    ),则保留该编号。绝不因为编号不确定而不归档录音。
  • 投放箱不是存储位置。 当录音进入共享投放位置(工作区根目录、Downloads 文件夹或 S3 收件箱)时,在归档副本通过校验和验证后,将文件移动到
    arch/transcripts/
    ,然后删除源文件,以免下一次投放产生歧义。如果同时有两个或更多音频文件等待处理,就表示存在多场会议:应先处理最早的录音或询问用户,绝不能将其合并。

The archive convention

归档约定

Archiving is an optional convention. Keep an
arch/
tree at the workspace root, spanning every repo in that workspace. If an
arch/README.md
already exists in the project, follow its layout; otherwise this is a sensible default:
arch/
  README.md
  meetings/     YYYY-MM-DD-<counterpart>-<topic>.md
  transcripts/  YYYY-MM-DD-rec-<N>.txt  +  the original .m4a alongside
  reports/      YYYY-MM-DD-<topic>.md
A typical report skeleton: TL;DR, Action items, Decisions, Things to remember, Open questions, What's done / in progress, Topics. Two conventions are worth keeping:
  • No em dashes anywhere. Hyphens, colons, periods, parentheses.
  • Every action item needs a named owner. "Someone should" is not an action item.
  • Never paste a full transcript into the report. Link to the file under
    transcripts/
    .
Get real durations with
ffprobe
rather than estimating from file size:
bash
ffprobe -v error -show_entries format=duration -of csv=p=0 file.m4a
归档是一种可选约定。在工作区根目录维护一个
arch/
目录树,涵盖该工作区中的每个仓库。如果项目中已经存在
arch/README.md
,应遵循其布局;否则可采用以下合理默认结构:
arch/
  README.md
  meetings/     YYYY-MM-DD-<counterpart>-<topic>.md
  transcripts/  YYYY-MM-DD-rec-<N>.txt  +  the original .m4a alongside
  reports/      YYYY-MM-DD-<topic>.md
典型报告结构:TL;DR、行动项、决策、需记住的事项、待解决的问题、已完成或进行中、讨论主题。有两项约定值得保留:
  • 任何位置都不得使用 em dash。 使用连字符、冒号、句号或圆括号。
  • 每个行动项都必须有明确的负责人。 “有人应该……”不算行动项。
  • 不要在报告中粘贴完整转录文本。应链接到
    transcripts/
    下的文件。
使用
ffprobe
获取真实时长,不要根据文件大小估算:
bash
ffprobe -v error -show_entries format=duration -of csv=p=0 file.m4a

Flow C: ingest from the S3 inbox (browser extension)

流程 C:从 S3 收件箱导入(浏览器扩展)

The
extension/
folder here is a browser extension that auto-records web meetings (Google Meet, Zoom web, Teams web) and uploads them to a private S3 inbox, so you never have to phone-record or hand over files. A recording lands as
inbox/<workspace>/<ts>-<title>.webm
plus a
.json
sidecar (platform, title, meeting url, participants, start/end, mic-captured flag, contentType). See
extension/README.md
for the capture side and
ingest/presign-lambda/
for the endpoint.
The inbox is the FIRST place to look whenever the user refers to a call, not only when they say "check the inbox". "I just had a call", "what did we decide on the call", "transcribe my last meeting", "process my meetings" all start here, before asking for a file. Only if the inbox has nothing that fits do you fall back to the project's dropped-file locations (Flow A) and then ask.
bash
I=.claude/skills/meeting-brief/ingest/ingest.sh   # set MEETING_INBOX_BUCKET + AWS creds (env or AWS_PROFILE)

$I list                                        # pending clips, newest first: title | start | participants
$I pull inbox/<ws>/<file>.webm /tmp/claude/<project>/meeting-in
Pick the clip by time first (a call "just had" ends within the last hour or two;
startedAt
/
endedAt
are in the sidecar), then by title/participants matching who was named. If two or more clips fit, list them and ask which, do not guess.
Always convert to
.m4a
first, then run the normal Flow A on the
.m4a
:
bash
ffmpeg -v error -i clip.webm -vn -c:a aac -b:a 64k clip.m4a   # then transcribe clip.m4a
Two reasons it is not optional. A call whose tab was closed mid-call (sidecar
reason: tab_closed
or
stalled
) arrives as a webm with no proper ending ("File ended prematurely"): ffmpeg decodes it fine, Scribe may not. And a manual recording with video on is
video/webm
;
-vn
drops the video. Keep the original video only if the user wants to rewatch. The sidecar's
capture
says how it was recorded:
page
is automatic (call audio + the user's mic taken inside the meeting page),
tab
is a manual popup recording.
Routing: the meeting's subject decides the archive, not the session you are in and not the tag. The
workspace
tag is just the default set on the browser that recorded it (one browser can record calls for several projects), so a call can easily arrive tagged for the wrong one. Decide from what the meeting is about: the people on it, the title, the topics in the transcript. Say in chat which project you filed it under and why, then archive straight into that project's
arch/
.
If you cannot tell from the sidecar and do not want to spend credits transcribing it in the wrong project, hand it over untouched:
$I retag <key> <workspace>
. Transcode to
.m4a
with
ffmpeg
before archiving so the archive matches the usual
.m4a
convention. When the pair is archived and checksum-verified:
bash
$I done inbox/<ws>/<file>.webm                 # deletes the clip + sidecar from the inbox
The inbox is a drop box, not storage (same rule as any dropped recording in Flow A): delete each clip only after its
arch/transcripts/
copy is verified.
此处的
extension/
文件夹是一个浏览器扩展,用于自动录制 web 会议(Google Meet、Zoom web、Teams web),并将其上传到私密 S3 收件箱,因此您无需使用手机录音或手动移交文件。录音会生成
inbox/<workspace>/<ts>-<title>.webm
以及一个
.json
sidecar 文件(平台、标题、会议 URL、参与者、开始/结束时间、麦克风捕获标志、contentType)。捕获端详情参见
extension/README.md
,端点参见
ingest/presign-lambda/
。
只要用户提到通话,收件箱就是第一检查位置,而不只是当用户明确说“查看收件箱”时。“我刚开完会”、“我们在通话中决定了什么”、“转录我最近的会议”、“处理我的会议”等请求都应先从这里开始,然后再向用户索要文件。只有当收件箱中没有匹配内容时,才回退到项目投放文件的位置(流程 A),随后再询问用户。
bash
I=.claude/skills/meeting-brief/ingest/ingest.sh   # set MEETING_INBOX_BUCKET + AWS creds (env or AWS_PROFILE)

$I list                                        # pending clips, newest first: title | start | participants
$I pull inbox/<ws>/<file>.webm /tmp/claude/<project>/meeting-in
按时间优先选择片段(“刚结束”的通话通常在最近一两个小时以内;
startedAt
/
endedAt
位于 sidecar 中),然后根据用户点名的人物匹配标题或参与者。如果有两个或更多片段符合条件,应列出并询问用户,不要猜测。
始终先转换为
.m4a
,然后对
.m4a
运行正常的流程 A:
bash
ffmpeg -v error -i clip.webm -vn -c:a aac -b:a 64k clip.m4a   # then transcribe clip.m4a
这一点不可选,原因有二。如果通话过程中关闭了标签页(sidecar 中为
reason:\ntab_closed
或
stalled
),到达的 webm 文件没有正常结尾(“File ended prematurely”):ffmpeg 可以正常解码,但 Scribe 可能无法处理。而手动开启视频的录音是
video/webm
;
-vn
会丢弃视频。除非用户想回看,否则只保留原始视频。sidecar 的
capture
字段说明录音方式:
page
表示自动录制(在会议页面内捕获通话音频和用户麦克风),
tab
表示手动弹窗录制。
路由:由会议主题决定归档位置,而不是由当前会话或标签决定。
workspace
标签只是录制该通话的浏览器上的默认设置(同一个浏览器可以为多个项目录制通话),因此通话很容易被错误标记。应根据会议内容决定:参会人员、标题和转录文本中的主题。在聊天中说明将其归入哪个项目及原因,然后直接归档到该项目的
arch/
。
如果无法从 sidecar 判断,又不想浪费 credits 将其转录到错误的项目中,应原样移交:
$I retag <key> <workspace>
。归档前使用
ffmpeg
将其转码为
.m4a
,使归档符合惯用的
.m4a
约定。当文件对完成归档并通过校验和验证后:
bash
$I done inbox/<ws>/<file>.webm                 # deletes the clip + sidecar from the inbox
收件箱是投放箱,不是存储位置(与流程 A 中任何投放的录音遵循同一规则):只有在
arch/transcripts/
中的副本验证通过后,才删除收件箱中的每个片段。

The capture side: install, ship a change, debug

捕获端:安装、发布变更、调试

  • How it records, and the one thing not to undo. Automatic recordings happen inside the meeting page (
    extension/capture.main.js
    , a
    world: MAIN
    content script that wraps
    RTCPeerConnection
    and
    getUserMedia
    ). Never move auto-start back to
    chrome.tabCapture
    : Chrome only allows tab capture after a click on the extension, so it cannot start on its own, and v0.1/v0.2 recorded nothing on the first real Meet call because of it. Tab capture is only the popup's manual Start, which is also the only way to get video. Full design in
    extension/README.md
    .
  • Install or update: run the install one-liner your deployment prints (
    curl -fsSL <your-endpoint>/install | sh
    ), then
    chrome://extensions
    -> Load unpacked (first time;
    Cmd+Shift+G
    , paste) or the reload arrow (update). Loaded unpacked from a stable folder, the extension keeps the same ID across updates, so the token in Options survives updates. Never load it from Downloads.
  • Ship a change to the extension: edit
    extension/
    , bump
    manifest.json
    version
    , then
    cd ingest/presign-lambda && BUCKET=<your-inbox-bucket> TOKEN=<current> ./deploy.sh
    (with AWS creds for your account in the environment; the token is in the Lambda env, see
    extension/README.md
    ). That re-bundles the zip served at
    /extension
    ; re-run the install line and hit reload. Prove a recorder change on a real WebRTC call before calling it done (the README describes the Chromium harness).
  • "Did my call record?" and the inbox is empty: ask the endpoint, not the code.
    bash
    aws cloudwatch get-metric-statistics --region us-east-1 --namespace AWS/ApiGateway \
      --metric-name Count --dimensions Name=ApiId,Value=<your-api-id> --statistics Sum --period 300 \
      --start-time $(date -u -d '-3 hours' +%FT%TZ) --end-time $(date -u +%FT%TZ)
    No requests during the call: the browser never tried (auto-record off, no token, an old build still on tab capture, or the page hook missed the call). 4xx: token or endpoint wrong. A presign with nothing in S3: the PUT failed. The popup's last-upload line is the browser's side of the same story.
  • Status: confirmed on a real Google Meet call (2026-10-01, v0.3.0): started with no click (even alone in the call), mic captured, stopped on hang-up, uploaded, transcribed cleanly. Zoom web and Teams web use the same recorder but are not yet confirmed live. For a
    meet.new
    call the title is just the meeting code; a calendar meeting carries its name. Participant names are best-effort and often empty, so identify people from the transcript.
  • 录制方式,以及唯一不能撤销的改动。 自动录制发生在会议页面内部(
    extension/capture.main.js
    ,一个
    world: MAIN
    content script,会包装
    RTCPeerConnection
    和
    getUserMedia
    )。绝不能将自动启动改回
    chrome.tabCapture
    :Chrome 仅允许在用户点击扩展后捕获标签页,因此无法自行启动;v0.1/v0.2 在第一次真实 Meet 通话中没有录到任何内容,正是因为这一点。标签页捕获仅用于弹窗中的手动 Start,也是录制视频的唯一方式。完整设计参见
    extension/README.md
    。
  • 安装或更新: 运行部署输出的单行安装命令(
    curl -fsSL <your-endpoint>/install | sh
    ),然后进入
    chrome://extensions
    -> Load unpacked(首次安装时;
    Cmd+Shift+G
    ,粘贴),或点击重新加载箭头(更新时)。从未稳定目录以 unpacked 方式加载后,扩展在更新前后会保持相同 ID,因此 Options 中的 token 在更新后仍然有效。绝不要从 Downloads 加载。
  • 发布扩展变更: 编辑
    extension/
    ,递增
    manifest.json
    中的
    version
    ,然后运行
    cd ingest/presign-lambda && BUCKET=<your-inbox-bucket> TOKEN=<current> ./deploy.sh
    (环境中需配置对应 AWS 账户的凭据;token 位于 Lambda 环境中,参见
    extension/README.md
    )。这会重新打包由
    /extension
    提供的 zip;重新运行安装命令并点击 reload。完成前,应在真实的 WebRTC 通话上验证录音器变更(README 中介绍了 Chromium harness)。
  • “我的通话录下来了吗?”以及收件箱为空: 应查询端点,而不是代码。
    bash
    aws cloudwatch get-metric-statistics --region us-east-1 --namespace AWS/ApiGateway \
      --metric-name Count --dimensions Name=ApiId,Value=<your-api-id> --statistics Sum --period 300 \
      --start-time $(date -u -d '-3 hours' +%FT%TZ) --end-time $(date -u +%FT%TZ)
    通话期间没有请求:浏览器从未尝试过(自动录制关闭、没有 token、仍是基于标签页捕获的旧版本,或页面 hook 未捕获到通话)。4xx:token 或端点错误。预签名成功但 S3 中没有内容:PUT 失败。弹窗中的最后一行上传信息反映的是浏览器端的同一情况。
  • 状态: 已在一次真实的 Google Meet 通话中确认(2026-10-01,v0.3.0):无需点击即可开始(即使通话中只有自己)、可捕获麦克风、挂断时停止、成功上传且转录清晰。Zoom web 和 Teams web 使用同一个录音器,但尚未进行真实验证。对于
    meet.new
    通话,标题只是会议代码;日历会议则带有名称。参与者姓名仅供参考,而且通常为空,因此应从转录文本中识别人。

Flow B: audio-reply mode

流程 B:音频回复模式

The user reads on their phone and often cannot skim a long answer. In this mode every substantive reply is spoken.
bash
S=.claude/skills/meeting-brief/scripts
用户经常在手机上阅读,可能无法快速浏览长回答。在此模式下,每个实质性回复都以语音形式交付。
bash
S=.claude/skills/meeting-brief/scripts

1. delete the clip from the previous turn; they have replied so they have heard it

1. delete the clip from the previous turn; they have replied so they have heard it

$S/unshare.sh "s3://<bucket>/<previous-key>" <project-dir> <previous-local-file>
$S/unshare.sh "s3://<bucket>/<previous-key>" <project-dir> <previous-local-file>

2. write the reply as a SPOKEN script, then

2. write the reply as a SPOKEN script, then

$S/say.sh reply.txt /tmp/claude/<project>/reply-<n>.mp3 1.5
$S/say.sh reply.txt /tmp/claude/<project>/reply-<n>.mp3 1.5

3. private link

3. private link

$S/share.sh /tmp/claude/<project>/reply-<n>.mp3 <project-dir>

Then post the URL with **two or three lines of text at most**: what the clip covers and anything they must act on. Not a transcript of the audio. The point of the mode is that they do not have to read.

Keep a note of the returned `s3://` URI and the local path so the next turn can delete them.

**Default speed is 1.5.** ElevenLabs' own `speed` setting maxes out at 1.2, so `say.sh` renders at normal speed and applies the change with `ffmpeg atempo`, chaining filters for anything outside 0.5-2.0. Pitch is preserved.
$S/share.sh /tmp/claude/<project>/reply-<n>.mp3 <project-dir>

然后发布 URL,并**最多附带两到三行文本**:说明音频涵盖的内容以及用户必须处理的事项。不要提供音频的文字稿。该模式的目的在于让用户无需阅读。

保留返回的 `s3://` URI 和本地路径,以便下一轮删除它们。

**默认速度为 1.5。** ElevenLabs 自身的 `speed` 设置最高只能到 1.2,因此 `say.sh` 会先以正常速度渲染,再使用 `ffmpeg atempo` 调整速度;对于 0.5-2.0 范围之外的速度,会串联滤镜。音高保持不变。

Writing for the ear

为听觉而写

Reading markdown aloud is unlistenable. Convert before calling
say.sh
:
  • Spell numbers out: "four hundred ninety-nine a month", not "$499/mo".
  • No file paths, no URLs, no code identifiers. Those go in the text message beside the link.
  • No bullet characters, no headers, no bold. Use "First", "Second", "The next thing" instead.
  • Say acronyms as letters where that is how they are said: "M R R", "U X", "U I".
  • Warn about known transcription mangling up front if the summary depends on it.
  • Blank lines between paragraphs.
    say.sh
    chunks on those boundaries and stitches with
    previous_text
    /
    next_text
    so prosody carries across the joins.
Default voice is a calm broadcast read (
onwK4e9ZLuTAKqWW03F9
). Override with
MEETING_BRIEF_VOICE
. Model defaults to
eleven_multilingual_v2
via
MEETING_BRIEF_MODEL
.
直接朗读 Markdown 难以收听。调用
say.sh
前应进行转换:
  • 将数字拼写出来:“每月四百九十九美元”,而不是“$499/mo”。
  • 不要出现文件路径、URL 或代码标识符。这些内容应放在链接旁边的文字消息中。
  • 不要使用项目符号字符、标题或粗体。改用“首先”、“其次”、“下一件事”等表达。
  • 如果缩写的读法就是逐字母发音,则按字母朗读,例如“M R R”、“U X”、“U I”。
  • 如果摘要依赖某个已知容易转写错误的术语,应在开头提醒。
  • 段落之间保留空行。
    say.sh
    会根据空行对内容分块,并使用
    previous_text
    /
    next_text
    拼接,以保证连接处的韵律自然。
默认声音是平稳的播音式朗读(
onwK4e9ZLuTAKqWW03F9
)。可通过
MEETING_BRIEF_VOICE
覆盖。模型默认通过
MEETING_BRIEF_MODEL
设置为
eleven_multilingual_v2
。

Scripts

脚本

ScriptDoes
scripts/transcribe.sh <outdir> <audio>...
Scribe v2, diarised, keyterms, parallel, skips existing output
scripts/format-transcript.mjs <raw.json> [--map s0=Name,...]
Timestamped, speaker-grouped transcript
scripts/say.sh <script.txt> <out.mp3> [speed] [voice]
Chunked TTS, stitched, speed-adjusted
scripts/share.sh <file> [project-dir] [expires]
Private S3 upload, presigned URL (7 day max)
scripts/unshare.sh <s3-uri> [project-dir] [local-file]
Deletes the object and optionally the local file
scripts/aws-env.sh
Per-project credential resolution, sourced by the above
scripts/lib.sh
ElevenLabs key loading
scripts/extension-icons.mjs
Regenerates the extension's toolbar icons (idle + recording)
ingest/ingest.sh {list,pull,done,retag}
The S3 meeting inbox (Flow C)
ingest/presign-lambda/deploy.sh
Redeploys the endpoint and re-bundles the extension zip served at
<your-endpoint>/extension
ELEVENLABS_API_KEY
is read from the environment.
ELEVENLABS_ENV_FILE
can point at a local
.env
that defines it when it is not already in the environment.
脚本作用
scripts/transcribe.sh <outdir> <audio>...
使用 Scribe v2 进行说话人分离转录,支持 keyterms、并行处理,并跳过已有输出
scripts/format-transcript.mjs <raw.json> [--map s0=Name,...]
生成带时间戳、按说话人分组的转录文本
scripts/say.sh <script.txt> <out.mp3> [speed] [voice]
分块 TTS、拼接并调整速度
scripts/share.sh <file> [project-dir] [expires]
上传至私密 S3,生成预签名 URL(最长 7 天)
scripts/unshare.sh <s3-uri> [project-dir] [local-file]
删除对象,并可选删除本地文件
scripts/aws-env.sh
按项目解析凭据,供上述脚本加载
scripts/lib.sh
加载 ElevenLabs key
scripts/extension-icons.mjs
重新生成扩展工具栏图标(空闲与录制状态)
ingest/ingest.sh {list,pull,done,retag}
S3 会议收件箱(流程 C)
ingest/presign-lambda/deploy.sh
重新部署端点,并重新打包由
<your-endpoint>/extension
提供的扩展 zip
ELEVENLABS_API_KEY
从环境变量读取。当环境变量中不存在该 key 时,可通过
ELEVENLABS_ENV_FILE
指向一个定义了它的本地
.env
文件。

Environment notes

环境说明

  • Backgrounding with
    &
    or
    nohup
    may not survive the tool call.
    Use the Bash tool's
    run_in_background
    instead, which is the reliable way to hold a process.
  • A presigned URL is signed per HTTP method.
    curl -I
    against a GET-signed URL returns 403 and that is correct, not a failure. Verify with a ranged GET:
    curl -r 0-2000 "$URL"
    .
  • ffmpeg
    and
    ffprobe
    are required (install them with your package manager).
  • Keep scratch files out of the repo (e.g. under
    /tmp/claude/<project>/
    ).
  • 使用
    &
    或
    nohup
    进行后台处理可能无法在工具调用结束后继续运行。应改用 Bash 工具的
    run_in_background
    ,这是保持进程运行的可靠方式。
  • 预签名 URL 按 HTTP 方法签名。对 GET 签名的 URL 执行
    curl -I
    会返回 403,这是正确行为,并非失败。应使用分段 GET 验证:
    curl -r 0-2000 "$URL"
    。
  • 需要安装
    ffmpeg
    和
    ffprobe
    ,可使用系统的包管理器进行安装。
  • 临时文件应放在仓库之外,例如
    /tmp/claude/<project>/
    。

When not to use

不适用场景

  • A short factual answer. Do not make the user play a clip to hear one sentence.
  • Anything they need to copy and paste: paths, URLs, commands, IDs. Those must be text.
  • Code review or diffs. Audio is useless for reading code.
  • 简短的事实性回答。不要让用户为了听一句话而播放音频片段。
  • 任何需要复制粘贴的内容:路径、URL、命令、ID。这些内容必须以文本形式提供。
  • 代码审查或 diff。音频不适合用于阅读代码。