{
  "markdown": "# Meet Live Assist\n\n**Your own agent session, in the call with you.** It reads the Meet or Zoom transcript as it happens and\nanswers in a side panel while the call is still going: what was decided, who owns what, the risk you just\nagreed to, and the sentence to say next, in the meeting's language.\n\n<p align=\"center\">\n  <img src=\"docs/media/call-live.gif\" width=\"820\"\n       alt=\"A release review: someone shares an August calendar and promises the 15th, and the panel docked beside the call flags that the date is inside the agreed release freeze, offers the sentence to push back with, and records the decision to move to the 18th.\" />\n  <br />\n  <em>A release review, fourteen seconds, panel docked beside the call. The conversation is invented and the\n  cameras are off, as they are on most working calls. The interface is the shipping one.</em>\n</p>\n\n**[Install from the Chrome Web Store](https://chromewebstore.google.com/detail/meet-live-assist/elhddnpchalnmgcgdicegfggifojbadk)** · **[the site](https://meet-live-assist.github.io/)** ·\none command for the server: `npx meet-live-assist-server`\n\nThe store link is one click and updates itself; the repo works loaded unpacked and never does. Either way\nthe server and the skill below are still yours to run, and without an agent attached the advice pane stays\nempty by design.\n\nDuring a Google Meet or Zoom call this captures the transcript, shows it in a side panel, and hands it to\nan assistant running on your own computer. The assistant answers back in the panel while the call is still\ngoing: what to say, what was decided, who owns what, what you just agreed to that you should not have.\n\nTranscripts, screenshots and chat are written to a folder on your disk and served by a server on\n`127.0.0.1` that only you can reach. There is no account, no telemetry, and no server of ours anywhere.\n\n**Where the words actually go, stated once and plainly.** The brain is your own Claude Code session, so\nwhatever you route to it - the transcript batches, your questions - leaves your machine for Anthropic under\n*your* account, exactly as any other Claude Code session does. What never leaves is the stored record: the\nfiles, the screenshots, the chat history. If a page tells you a live meeting assistant runs entirely on your\nmachine, it is either using a local model or it is not telling you the truth; this one borrows a brain you\nalready pay for, and that is the trade. See [Data flow](#data-flow) for the three-line version.\n\n## Read this before you install\n\n**You need an AI coding agent that speaks MCP - [Claude Code](https://claude.com/claude-code) is the one\nthis is tested with.** This ships the eyes, ears and hands - the capture, the panel, the local bridge - but\nnot the brain. The brain is an agent session on your own machine, reading the call through MCP tools. Without\none you get a working transcript recorder and an empty advice pane, which is not what the screenshots promise.\n\nThe adapter is plain JSON-RPC over stdio with nothing vendor-specific in it, so other MCP clients (Cursor,\nCline, Continue, Zed, Codex) can drive it - Codex CLI was checked and completes tool calls against the bridge.\nOne requirement is genuinely harder to meet elsewhere: the client has to hold a **persistent background\nloop**, because MCP is client-pull and nothing on the server can\never start a turn. [`MCP-CLIENTS.md`](MCP-CLIENTS.md) explains what to check and what is unverified.\n\n**Your meeting is other people's conversation too.** Turning on captions is invisible to everyone else,\nunlike recording, which Meet badges. So by default this posts one line into the meeting chat when capture\nstarts, saying an assistant is transcribing locally. You can edit that line or turn it off in Options.\nSome jurisdictions require everyone's consent before a conversation may be recorded or transcribed; that\nis your call to make, and `PRIVACY.md` says so plainly.\n\nRequirements: Node 20+, Chrome 116+, Claude Code. Speaking *into* the call is macOS-only (it uses `say`\nand `afplay`); elsewhere advice still appears in the panel and the server says why it cannot speak. Local\nspeech-to-text works anywhere `ffmpeg` and `whisper.cpp` do, and the panel's setup row names which of the\ntwo is missing instead of failing quietly.\n\n## Install (three steps)\n\n```sh\ngit clone https://github.com/krystiangw/meet-live-assist-extension.git\ncd meet-live-assist-extension\n./install.sh          # installs the skill, registers the MCP tools, prepares the data dir\n```\n\nThen:\n\n1. **Start the bridge server**, leave it running:\n   `node server/transcript-server.js --pair`\n2. **Load the extension**: `chrome://extensions` → Developer mode → *Load unpacked* → pick this folder.\n   Pin it, click the icon. The panel collects its token from the pairing window on its own; there is\n   nothing to copy. (Window expired? Run step 1's command again - it works against a running server too.)\n3. **Open Claude Code** and ask it to assist your meeting.\n\n`./install.sh` takes `MLA_USER`, `MLA_LANGUAGE`, `MLA_DOMAIN` and `MLA_TRANSCRIPTS_DIR` so the assistant\naddresses you by name, in your language, and knows roughly what your meetings are about.\n\n### Pick a profile\n\n`MLA_PROFILE` decides which meeting types the assistant knows about and what it leads with. Everything else -\nthe markers, the modes, the board - is the same, because those turned out to be domain-neutral.\n\n| Profile | For | It leads with |\n| --- | --- | --- |\n| `engineering` (default) | standups, incidents, refinement, QA | decisions, scope creep, risk, action items |\n| `second-language` | any meeting not in your first language | 🟢 SAY - the sentence, ready to speak, in the meeting's language |\n| `research` | user interviews, usability sessions | silence, leading-question warnings, guide coverage |\n| `generic` | anything else | responding well in real time |\n\nThey are plain files in [`skill/profiles/`](skill/profiles/) - about a dozen lines each. Write your own and\npass its name; the installer refuses a profile it cannot find; it never falls back to the default.\n\n## What it stores, and how to get rid of it\n\nEverything lives in one folder - `./transcripts` unless you set `MLA_TRANSCRIPTS_DIR`:\n\n| File | What it is |\n| --- | --- |\n| `<date>_<meeting-code>.txt` | the transcript |\n| `<...>.chat.txt`, `<...>.summary.md` | your chat with the assistant, and the post-call summary |\n| `snapshots/<session>/*.jpg` | screenshots taken during screen-share |\n| `.state/` | live meeting state, so restarting the server mid-call loses nothing |\n| `.mla-token` | the shared secret the extension and the assistant authenticate with |\n\nFiles are owner-only (`0600`), the directory is `0700`, and anything older than 14 days is purged\nautomatically (`RETENTION_DAYS`, `0` keeps everything forever). The 🗑 button in the panel wipes a single\nmeeting - transcript, chat, summary, snapshots and state - immediately. To remove the whole thing: delete\nthat folder, delete `~/.claude/skills/meet-live-assist`, run `claude mcp remove meet-live-assist`, and\nremove the extension from Chrome.\n\n## Data flow\n\n| What | Where it goes |\n| --- | --- |\n| Transcript, screenshots, panel chat, summaries | **Your disk, nowhere else.** Owner-only files, one folder, served by a process bound to `127.0.0.1`. |\n| The call content you route to the assistant, and your questions | **Anthropic, via your own Claude Code session** - same path as anything else you do in Claude Code, your account, your terms. |\n| Anything else | Nothing. There is no third party here. |\n\nOn Free / Pro / Max, whether your sessions improve the model is a setting you control and it changes the\nretention period; [Anthropic's consumer terms](https://www.anthropic.com/news/updates-to-our-consumer-terms)\ncover it and say explicitly that it includes Claude Code. Work and API accounts are on different terms.\nWorth knowing before you point this at a conversation that is not only yours.\n\n## What it does during a call\n\n- **Live transcript** - captions scraped, streamed to the panel instantly, de-duplicated + monologue\n  forced-flush before hitting the file/brain, with a conservative ASR glossary.\n- **Colour-coded advice** from the brain (🟢SAY/🔵INFO/🟡SUMMARY/🟣EXPLAIN/🔴RISK/🟠ACTION), rich\n  (links/images/diagrams/lists), each **copyable**; RISK fires an audible + notification cue.\n- **Brain-liveness** pill (is a Claude session actually attached?), **capture watchdog** (warns if captions break).\n- **Decisions & action-items board** with a one-click **Draft <your tracker>** (whatever you named in\n  Options, or a plain note if you named nothing); **recap** quick-asks; two-way **chat**.\n- **Autopilot** - flip 🤖 Auto-create and it stops proposing and starts doing: the ticket or the note is\n  created as the action item comes up, no per-item confirm. 📣 Post links to chat shares the link with\n  the room. Both are off until you turn them on.\n- **Snapshots** (auto on screen-share + on demand), **TTS into the call**, **local STT** (whisper),\n  **meeting modes** + type-awareness, **live presentation edits** + **debug** of the shared tab.\n- **Talk-time**, **muted-mic** + **personal-mention** alerts; **post-call summary** export.\n\n<p align=\"center\">\n  <img src=\"docs/media/planning-live.gif\" width=\"820\"\n       alt=\"A sprint planning call: the assistant corrects a sizing claim against the tracker, records the decision, then files a ticket and drafts the planning note without being asked.\" />\n  <br />\n  <em>Sprint planning with autopilot on. Nobody typed anything into the panel: the correction, the decision,\n  the ticket and the note all arrive while the meeting carries on.</em>\n</p>\n\n**A note on trust.** The assistant is your own Claude Code session with your own tools, and it reads a live,\nuntrusted audio feed. It is instructed to let *only what you type* authorize an action - no spoken line, under\nany name, can make it act - but that is the model following its skill, not a wall. If you have connected\ndestructive tools to Claude Code, set their trust accordingly. `PRIVACY.md` says this plainly too.\n\n**Landing page:** [`docs/index.html`](docs/index.html), also hosted at\n**https://meet-live-assist.github.io/**.\n\nBuilt by [Krystian Gwizdała](https://krystian-gwizdala.github.io/).\n\n*Meet Live Assist is an independent project. It is not affiliated with, endorsed by or sponsored by Google or Zoom; \"Google Meet\" and \"Zoom\" are their owners' trademarks and are used here only to say which products this works with.*\n\n## Licence, in plain words\n\n[PolyForm Internal Use 1.0.0](LICENSE.md). Free, and **yes, you may use it at work** - \"internal business\noperations of you and your company\" is a permitted purpose, whether or not your company is for-profit. You\nmay modify it for your own use.\n\nWhat you may not do is redistribute it, fork it publicly, or sell it - as a product, a hosted service, or a\npart of either. This is source-available, not open source, and the difference is deliberate: it is free to\nuse and stays owned.\n\nIf you want to do something the licence does not allow, ask. That is a conversation, not a refusal.\n\n## Architecture (why it's shaped this way)\n\n- **Streaming + state live in the side panel, not the service worker.** The SW is event-driven and\n  gets torn down (~30s idle / 5-min cap); durable state is in `chrome.storage.session`, and the\n  panel re-hydrates via a `restore` message on (re)connect.\n- **Keep-alive:** the side panel holds a `runtime.connect` port and pings it every 20s; a\n  `chrome.alarms` heartbeat (30s) wakes the SW even after it was unloaded.\n- **Server POST happens in the SW** (has `127.0.0.1:8848` host permission), not the content script.\n\n## The bridge server\n\nThe extension talks to a small local Node server (`127.0.0.1:8848`) that is the \"brain\" bridge:\ntranscript sink (`/append`), advice (`/advice`), board (`/items`), chat (`/chat`), snapshots\n(`/snapshot`, `/snapshot-request`), TTS (`/speak`, `/voices`), STT (`/stt`), meeting mode (`/mode`),\npresentation edits (`/edit`, `/dom*`), debug (`/debug*`), brain heartbeat (`/brain-ping`), summary\n(`/summary`), per-meeting wipe (`/clear`), and health (`/health`).\n\n**The assistant reaches it through MCP, not HTTP.** `server/mcp-server.js` is a zero-dependency stdio MCP\nadapter over the same API, 13 tools. Register it once:\n\n```bash\nclaude mcp add meet-live-assist --scope user -- node <repo>/server/mcp-server.js\n```\n\nIt asks the running server where its data dir is (`/health` needs no token) and reads the token from there,\nso it needs no environment. The keystone tool is `poll`: one call returns the transcript batch worth a turn,\nthe panel's state, and any pending results, with the read offset held server-side per assistant. That\nreplaces four or five `curl` calls a turn plus a byte offset kept in a shell variable - and it works with no\nfilesystem in reach, which is what a hosted deployment needs.\n\nWhat MCP does **not** do is wake the assistant: the protocol is client-pull, so nothing on the server can\nstart a turn. A client-side loop polling `/poll?...&format=text` remains the wake source; it prints only\nwhen something happened - a batch worth a turn, a panel state change, a message typed in the panel chat, a\nfailed meeting-chat delivery, or its own inability to reach the server. The state change matters most: it is\nthe only way pressing Stop can reach an assistant at all, since capture ends there and no later caption\nwould arrive.\n\nRead positions are per **reader**, held server-side, and a reader nobody has seen before starts at the *end*\nof the channel - `backlog=1` is how the wake loop asks for the meeting so far on its first read. The loop and\nthe `poll` tool are deliberately separate readers: reading is destructive, so sharing a position let a\nmid-turn tool call swallow a wake the loop still owed. Positions are bounded per meeting and evicted\nleast-recently-seen, which never touches a loop that is polling.\n\n**Multi-tenancy seam.** State is keyed by `(user, session)`; see `server/scope.js`. On a local install the\nsingle user resolves to the data dir itself, so nothing about the layout changes. The seam exists so a\nhosted profile can namespace users without a second code path, and so the guarantee that one user cannot\nreach another's meeting is stated in one place and tested directly.\n\n**Auth:** every route except `/health` requires an `X-MLA-Token` header. The server generates the token\ninto `<transcripts>/.mla-token` on first start; the brain reads that file, and the extension **pairs** for\nit, so no human ever handles a copy. Without the token any website you visit could reach the\nlocalhost server.\n\n**Pairing.** `GET /pair` returns the token exactly once, and only while a window is open - the server's\nfirst ever boot, or a run with `--pair`, which against an already-running server just re-opens the window\non it. A claim must carry `X-MLA-Pair: 1`, which a web page cannot send without a preflight that betrays\nits origin, and any `Origin` present must be `chrome-extension://`. The first claim closes the window and\nthe extension id that took it is logged. This does not stop another extension of yours that already holds\na `127.0.0.1` permission from racing you inside those two minutes, which is exactly why the window is not\nleft open. Manual paste still works and is still there in Options.\n\n**Two files per meeting.** `/append` writes every caption to `<session>.txt` - the complete record, nothing\ndropped - and only appends a batch to `<session>.wake` when the batch is worth waking the brain for\n(decisions, blockers, your name, real questions, accumulated substance). The assistant reads the wake channel\n(through `poll`), never the raw transcript: that is what keeps a 40-minute call from costing hundreds of brain\nturns. A held-back batch is never lost - it rides along with the next wake, and a force-flush fires after\n`WAKE_FORCE_MS` regardless. `poll` reads that channel and deliberately does not force it: flushing on a\n2-second poll would hand back everything the gate was holding, which is the gate deleted.\n\n### Stand up the server\n\nThe server stands alone as [`meet-live-assist-server`](https://www.npmjs.com/package/meet-live-assist-server)\n(zero dependencies), so someone who only wants to run it needs neither this repo nor a clone:\n\n```bash\nnpx meet-live-assist-server\n```\n\nFrom a clone it is:\n\n```bash\nnode server/transcript-server.js --pair\n```\n\nThe extension is on the [Chrome Web Store](https://chromewebstore.google.com/detail/meet-live-assist/elhddnpchalnmgcgdicegfggifojbadk), unlisted: the link installs it in one click and it\nupdates itself, but store search will not surface it, because it is useless to anyone who has not also run\nthe server and attached an agent. Loading the repo unpacked works exactly the same and never auto-updates -\nrun one or the other, not both, since Chrome gives them different extension IDs.\n\nThe MCP half is listed in the [MCP Server Registry](https://registry.modelcontextprotocol.io/) as\n`io.github.krystiangw/meet-live-assist`, so a registry-aware client can add it without being told the\ncommand. Clients that still want it spelled out: `npx -p meet-live-assist-server meet-live-assist-mcp`\n(the package ships two binaries, and `npx meet-live-assist-server` starts the bridge, not the adapter).\n\nIt writes to\n`~/meet-live-assist/transcripts` unless `TRANSCRIPTS_DIR` says otherwise. **Node 20+ is the only hard\nrequirement**; `ffmpeg` and `whisper-cli` are optional and only local STT depends on them. Binary paths\nresolve from Homebrew, `/usr/local`, `/usr/bin` and then `PATH`, so Linux works as well as either Mac\narchitecture. **Text-to-speech is macOS-only** (`say` + `afplay`); elsewhere advice still shows as text in\nthe panel and only spoken output is missing. Details: [`server/README.md`](server/README.md).\n\nPublishing a new version is `cd server && npm publish`. Run `npm pack --dry-run` first and read the list: it\nshould be **eight files, ~61 kB** - the server, the MCP adapter, the session-name sanitiser, the state store,\nthe wake-channel cut helper, a README, the licence and the manifest. Anything else in that list is something\nyou did not mean to publish.\n\n### Autostart it on a Mac (launchd)\n\nFrom a clone, if you want it to come back after a reboot:\n\n```bash\ngit clone https://github.com/krystiangw/meet-live-assist-extension.git\ncd meet-live-assist-extension\nMLA_DRY_RUN=1 ./server/install-server.sh   # optional: see the plan + generated plist, change nothing\n./server/install-server.sh                 # install as a launchd agent + start it\n```\n\nIt resolves the machine-specific bits itself (node binary via `process.execPath` - a bare `which node` under\nfnm/nvm points at a per-shell shim that dies with the shell; Homebrew prefix for `ffmpeg`/`whisper-cli`, so\nIntel and Apple Silicon both work), writes `~/Library/LaunchAgents/com.mla.meet-transcript-server.plist`,\nwaits for `/health`, then tells you where the token lives. To pair an extension against the job it just\ninstalled: `node server/transcript-server.js --pair`.\n\n- **Only Node 20+ is required.** `ffmpeg` and `whisper-cli` are optional (`brew install ffmpeg whisper-cpp`);\n  without them the server still runs - TTS-into-the-call and local STT are the parts that go dark.\n- **Re-run it after `git pull`** - it is idempotent and restarts the service with the new code.\n- Override defaults with env vars: `TRANSCRIPTS_DIR=~/mla PORT=8849 ./server/install-server.sh`.\n  Default transcripts dir is `~/meet-live-assist/transcripts`, deliberately **outside** the repo - meeting\n  text and screenshots are PII and must not risk being committed.\n- The **brain** reaches the server through the MCP adapter, which asks it where its data is, so changing\n  `TRANSCRIPTS_DIR` needs no change on the assistant's side. Only the launchd plist and the extension's\n  token need to agree.\n\nManual run instead of launchd (handy for debugging - logs to your terminal, `Ctrl-C` stops it for real):\n\n```bash\nPORT=8899 TRANSCRIPTS_DIR=/tmp/mla node server/transcript-server.js\ncurl -s http://127.0.0.1:8899/health\n```\n\n**Operating it**\n\n| | |\n| --- | --- |\n| health | `curl -s http://127.0.0.1:8848/health` |\n| what the panel is asking of the brain | `curl -s -H \"X-MLA-Token: $(cat <transcripts>/.mla-token)\" \"http://127.0.0.1:8848/status?session=<session>\"` |\n| logs | `~/Library/Logs/meet-live-assist-server.log` |\n| restart | `launchctl kickstart -k gui/$UID/com.mla.meet-transcript-server` |\n| stop for real | `launchctl unload -w ~/Library/LaunchAgents/com.mla.meet-transcript-server.plist` |\n\n`KeepAlive` is on, so `kill`/`pkill` does **not** stop it - launchd restarts it within seconds.\n\n**A restart mid-call is survivable.** Advice, the decisions board, chat, the wrap-up, the wake buffer and\neach assistant's read position are snapshotted to `<transcripts>/.state/` and reloaded on boot, so a bounce\ncosts at most the last couple of seconds (`STATE_SNAPSHOT_MS`, default 2000), never the meeting. Two\nthings deliberately do **not** come back, because they are answers about one call and a recurring series\nreuses its meet code: the panel's Stop/pause state, and consent (🕹 drive, autopilot). You will still see a\nbrief capture gap while the process is down.\n\nOnly one process may write a given data dir. A second server on the same `TRANSCRIPTS_DIR` serves normally\nbut does not persist (it logs why), so a sandbox run beside the launchd job cannot rewind the live meeting.\n\n**Config** (all optional, set in the plist's `EnvironmentVariables` or on the manual command line):\n\n| var | default | what it does |\n| --- | --- | --- |\n| `PORT` | `8848` | the extension has host permission for `127.0.0.1:8848` - changing it needs a manifest change |\n| `TRANSCRIPTS_DIR` | `<server-dir>/../transcripts` | where transcripts, snapshots and `.mla-token` live |\n| `RETENTION_DAYS` | `14` | purge transcripts + snapshots older than this (`0` = keep forever) |\n| `WAKE_BASE_MS` / `WAKE_MAX_MS` | `10000` / `90000` | wake-gate backoff window: starts here, doubles on an empty batch up to the max |\n| `WAKE_FORCE_MS` | `180000` | flush whatever is buffered after this long, gate or no gate |\n| `WAKE_MAX_CHARS` | `4000` | flush early once a batch gets this big |\n| `WAKE_MIN_GAP_MS` | `8000` | floor between two wakes |\n| `MLA_URGENT_NAMES` | *(empty)* | comma-separated names that wake the assistant immediately - include the manglings your captions produce |\n| `WAKE_ALL` | `0` | `1` delivers every line with no gating, for a call where nothing is small talk (~4x the turns) |\n| `FFMPEG` / `WHISPER_CLI` / `WHISPER_MODEL` / `TTS_VOICE` | Homebrew paths / `Zosia` | TTS + STT plumbing |\n\nThe launchd plist is generated by `install-server.sh` from your machine's actual paths - there is no\ntemplate to edit, because a checked-in one would carry someone else's absolute paths and fail on yours.\n\n## Two builds, and the skill that matches each\n\n`./build.sh` zips everything this repo can do. `./build.sh --public` produces the **store** zip: it drops the\n`debugger` permission and strips the surface that acts on pages (DOM edits, agent-driven clicks,\nnetwork/console reads), leaving the assistant that only sees and hears. The store reviews for a single\npurpose, and the full build reads as a remote control. The cut is driven by `mla:pro-start` / `mla:pro-end`\nmarkers in `src/`; the build refuses to ship a dangling reference to anything it removed.\n\nThe bundled skill is cut the same way and, more importantly, is a **template** - it addresses the user by\nname, answers in their language and pre-briefs against their domain, none of which can be hardcoded for\nsomeone else. `install.sh` fills it:\n\n```bash\nMLA_USER=\"Ada Lovelace\" MLA_LANGUAGE=Polish MLA_DOMAIN=\"backend eng, payments\" ./install.sh\nMLA_PRO=1 ./install.sh      # keep the page-control sections (pairs with the full build)\n```\n\nIt refuses to run on the author's machine without `MLA_FORCE=1`, because there the destination is the\ncanonical personal skill, not a copy of the template.\n\n## Checks\n\n```bash\nnpm run lint            # node --check over src/ and server/\nnpm test                # all five suites below, ~296 checks\nnpm run test:scope      # the (user, session) rules in isolation\nnpm run test:server     # auth gate, session guard, round-trips, restart survival\nnpm run test:panel      # every request sidepanel.js makes, replayed without a browser\nnpm run test:mcp        # the MCP adapter over stdio JSON-RPC\nnpm run test:limits     # the size caps, with non-ASCII text\nnpm run test:retention  # the retention sweep, and content not leaking between meetings\n```\n\nBoth run in CI on every push, along with both builds.\n\nThe suites are split by what they protect, not by layer. `test:panel` exists because the panel is the half\nof the product a server test never touches - a renamed route or a cursor that stops advancing looks fine\nfrom the assistant's side and leaves the user staring at an empty panel. `test:limits` is the only suite that\nuses non-ASCII text, and three real defects were hiding behind English-only fixtures. `test:retention` needs\nfile timestamps and restarts with a gap, so it does not belong in the fast path.\n\n## Load it (unpacked)\n\n1. Make sure the transcript server is running - `curl -s http://127.0.0.1:8848/health` → `{\"ok\":true,...}`.\n   On a fresh Mac install it first: `./server/install-server.sh` (see *Stand up the server on a Mac* above).\n2. `chrome://extensions` → enable **Developer mode** → **Load unpacked** → pick this folder.\n3. Pin the extension; click its toolbar icon to open the side panel.\n4. The panel pairs itself with the server if a pairing window is open (`--pair`). If it is not, the pill\n   says so and you can still paste the token by hand: right-click the icon → **Options** →\n   `cat <TRANSCRIPTS_DIR>/.mla-token`. The token is per-machine; one from another Mac will be rejected.\n   Optionally set TTS voices and your name(s) (for mention alerts). The panel's ⚙ shows a setup checklist.\n5. If you ever ran the predecessor Tampermonkey userscript\n   (`server/legacy-userscript.meet-captions-to-file.user.js`), disable it - both capturing at once\n   duplicates every line.\n\n## Does it actually work? Check these on a real call\n\nAutomated tests cover the server; these cover the half that only a live meeting exercises.\n\n- [ ] Join a call → within a few seconds the panel shows `capturing` and live lines, and\n      `<TRANSCRIPTS_DIR>/<date>_<code>.txt` starts growing.\n- [ ] `server ✓` is green and the 🧠 pill names your attached assistant. A pill that says nobody is\n      attached while a session is running means the skill never armed - that is the failure mode worth\n      catching, because everything else looks fine while no advice will ever arrive.\n- [ ] Ask the assistant something in the panel chat and get an answer back **in the panel**.\n- [ ] **Service-worker death:** let the SW go idle (or Stop it in `chrome://serviceworker-internals`) and\n      keep talking. Capture resumes and the panel re-hydrates without a reload. A 10-minute call should\n      have no gaps.\n- [ ] Share your screen → snapshots start on their own; the 📷 pill shows how stale the assistant's view is.\n- [ ] Leave the call → the panel shows `call ended`.\n\n**How the halves talk.** The brain POSTs advice (`POST /advice {session, marker, text}`) and the panel\npolls `GET /advice?session=&since=`, rendering each with its colour marker. Snapshots go to\n`<TRANSCRIPTS_DIR>/snapshots/<session>/` (~40 kept) for the assistant to read on demand. Anything the\nassistant does outside the panel - a ticket, a message - follows the tiers in the skill, and the panel\nitself is display-only.\n\n### Permissions\nThe `<all_urls>` host is **optional**, requested at runtime on a user gesture (starting co-pilot, turning on\n🐞 Debug, or the setup checklist's *Grant* button), so the host prompt stays limited to **Meet + Zoom +\nlocalhost**. `debugger` stays a **required** permission - Chrome forbids listing it as optional - so it's in\nthe install prompt (heavier review; a public build can drop it, the code degrades gracefully). Token auth\ncloses the \"any website can drive the localhost server\" hole. Build the store zip with `./build.sh`\n(→ `dist/`, extension files only). Full listing + justifications: `STORE.md`.\n\n> Reloading an already-installed copy will drop the now-optional `<all_urls>` grant - re-grant once from the\n> panel (co-pilot / 🐞 / ⚙ setup → Grant).\n\n## Roadmap\n\n- **Mute-aware mic capture** (deferred 2026-07-28 - idea worth keeping). Muting yourself in Zoom does not stop\n  the OS microphone, so the `mic` STT channel keeps recording asides nobody in the call heard, and the brain\n  treats them as things you said in the meeting. Don't drop them - **label them** `You (muted):` so muting\n  becomes a deliberate private voice channel to the assistant (still authorizes actions; never quotable as\n  something said to the room). Open question is only how to read the state: the toolbar button is localized\n  (\"Wyłącz wyciszenie\" / \"Unmute\"), so it needs a state attribute or an icon class, not a text match - the same\n  fragility that already bit the caption selectors.\n\n- **Auth north-star (product):** user authorizes a provider (Claude, later ChatGPT) in the extension's\n  settings and it \"just works.\" Reality: the Agent SDK / API is **API-key based, not account-OAuth**, and a\n  pure extension can't run the MCP agent brain (needs Node). So the full-brain path needs a local bridge or a\n  hosted backend; the pure-extension path is BYO-key (weaker, no MCP). Revisit in Phase 3.\n- **Phase 2:** replace caption scraping with `chrome.tabCapture` audio → streaming STT; auto-detect \"key\n  moments\" for snapshots; in-panel chat.\n- **Phase 3 (optional):** Agent SDK brain (`@anthropic-ai/claude-agent-sdk`, `settingSources:[\"user\"]` to\n  inherit CLAUDE.md + MCP) via local bridge, or backend proxy / BYO-key (Path B) for sharing; privacy policy;\n  Workspace private store.\n",
  "bytes": 29977,
  "sha": "a0cc60501861de82872ac6d06560d3e25c56c4bcd20dd0bdb4a64e65ba365c0b",
  "repo_slug": "krystiangw/meet-live-assist-extension",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_krystiangw_meet_live_assist_0edd911a/readme"
}