@kikedealba/recap 0.0.0-stage → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +388 -2
  3. package/Resources/ask-prompt.md +49 -0
  4. package/Resources/detect-prompt.md +71 -0
  5. package/Resources/proposals-prompt.md +44 -0
  6. package/Resources/summary-prompt.md +37 -0
  7. package/Resources/wrapup-prompt.md +32 -0
  8. package/commands/recap-ask.md +28 -0
  9. package/commands/recap-list.md +21 -0
  10. package/commands/recap-proposals.md +32 -0
  11. package/commands/recap-start.md +47 -0
  12. package/commands/recap-status.md +14 -0
  13. package/commands/recap-stop.md +69 -0
  14. package/commands/recap-summarize.md +31 -0
  15. package/dist/bin/recap.js +4 -0
  16. package/dist/bita/client.js +155 -0
  17. package/dist/bita/hook.js +81 -0
  18. package/dist/bita/proposals.js +419 -0
  19. package/dist/bita/wrapup.js +254 -0
  20. package/dist/capture/capture.js +109 -0
  21. package/dist/capture/import.js +80 -0
  22. package/dist/capture/recording.js +125 -0
  23. package/dist/cli/args.js +83 -0
  24. package/dist/cli/commands/ask.js +127 -0
  25. package/dist/cli/commands/config.js +40 -0
  26. package/dist/cli/commands/hook.js +94 -0
  27. package/dist/cli/commands/live.js +23 -0
  28. package/dist/cli/commands/media.js +53 -0
  29. package/dist/cli/commands/proposals.js +76 -0
  30. package/dist/cli/commands/query.js +172 -0
  31. package/dist/cli/commands/recording.js +102 -0
  32. package/dist/cli/commands/setup.js +132 -0
  33. package/dist/cli/output.js +24 -0
  34. package/dist/cli/router.js +94 -0
  35. package/dist/core/active.js +46 -0
  36. package/dist/core/config.js +77 -0
  37. package/dist/core/dates.js +30 -0
  38. package/dist/core/fsutil.js +44 -0
  39. package/dist/core/json.js +70 -0
  40. package/dist/core/live-settings.js +140 -0
  41. package/dist/core/lock.js +114 -0
  42. package/dist/core/meeting.js +201 -0
  43. package/dist/core/paths.js +55 -0
  44. package/dist/core/proc.js +129 -0
  45. package/dist/core/record.js +46 -0
  46. package/dist/core/self.js +22 -0
  47. package/dist/core/text.js +89 -0
  48. package/dist/core/tools.js +91 -0
  49. package/dist/errors.js +36 -0
  50. package/dist/live/ask.js +77 -0
  51. package/dist/live/asking.js +185 -0
  52. package/dist/live/context.js +90 -0
  53. package/dist/live/dedupe.js +21 -0
  54. package/dist/live/detector.js +498 -0
  55. package/dist/live/files.js +48 -0
  56. package/dist/live/merger.js +100 -0
  57. package/dist/live/origin.js +91 -0
  58. package/dist/live/stream.js +390 -0
  59. package/dist/live/worker.js +191 -0
  60. package/dist/media/media.js +83 -0
  61. package/dist/media/operations.js +131 -0
  62. package/dist/media/probe.js +50 -0
  63. package/dist/pipeline/claude.js +73 -0
  64. package/dist/pipeline/pipeline.js +275 -0
  65. package/dist/pipeline/resources.js +16 -0
  66. package/dist/pipeline/stages.js +27 -0
  67. package/dist/pipeline/summary.js +56 -0
  68. package/dist/pipeline/transcript.js +116 -0
  69. package/dist/setup/deps.js +119 -0
  70. package/dist/setup/integration.js +112 -0
  71. package/dist/setup/legacy.js +28 -0
  72. package/dist/version.js +6 -0
  73. package/package.json +57 -4
  74. package/skills/recap/SKILL.md +182 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jesus Enrique De Alba Gaytan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,389 @@
1
- # Temporary Holding Version
1
+ # recap
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Record meetings on macOS and turn them into a transcript, a summary, agreements and action items, all stored as local Markdown.
4
+
5
+ | Mode | Flag | Captures | Permissions |
6
+ |---|---|---|---|
7
+ | Remote (Meet, Zoom, Teams) | `--remote` | Screen (1280 px, 2 fps), system audio and microphone, as separate tracks | Screen & System Audio Recording, Microphone |
8
+ | In-person | `--in-person` | Microphone only | Microphone |
9
+
10
+ ## Instalación con npm (núcleo en TypeScript)
11
+
12
+ Desde la 0.9 la CLI `recap` es un paquete de npm, `@kikedealba/recap`, que corre
13
+ en macOS, Windows y Linux. Lo único que sigue siendo exclusivo de macOS es la
14
+ grabación, que hace `Recap.app` (el grabador nativo `recap-capture`).
15
+
16
+ ```sh
17
+ npm install -g @kikedealba/recap # o: pnpm add -g @kikedealba/recap
18
+ recap setup --install-deps
19
+ ```
20
+
21
+ Requisitos: Node 24 o superior, ffmpeg, whisper.cpp (`whisper-cli`) y
22
+ [Claude Code](https://claude.com/claude-code) para las minutas.
23
+
24
+ `recap setup` hace todo lo demás:
25
+
26
+ - con `--install-deps` instala ffmpeg y whisper-cpp con Homebrew en macOS y
27
+ ffmpeg con winget en Windows; en Linux muestra el comando (`sudo apt install
28
+ ffmpeg`) y cómo conseguir whisper.cpp;
29
+ - descarga los modelos de whisper a `~/.local/share/recap/models`
30
+ (`--skip-models` lo omite);
31
+ - en macOS baja `Recap.app` del último release de GitHub a `~/Applications` si
32
+ no está (`--skip-app` lo omite; `RECAP_APP` apunta a otra copia) y pide los
33
+ permisos de micrófono y pantalla (`--skip-permissions` solo los reporta);
34
+ - registra recap en el registro de herramientas de
35
+ [kit](https://github.com/KikeDeAlba/kit) (`~/.config/kikedealba/tools.d`) con
36
+ sus capacidades y, si bita está instalado, la suscripción a sus eventos
37
+ `start`, `stop`, `cancel` y `amend` de las entradas `remote-meeting` e
38
+ `in-person-meeting`. Reemplaza al viejo `bita hooks add` y quita ese hook si
39
+ lo encuentra, para que no se dispare dos veces;
40
+ - instala la skill y los comandos en los agentes que encuentre: Claude Code,
41
+ opencode, Codex y Gemini CLI (`--agents codex,gemini`, `--agents all` o
42
+ `--agents none`). Si el plugin de Claude Code ya está instalado, no lo duplica.
43
+
44
+ En Windows y Linux `recap start` falla con `CAPTURE_UNAVAILABLE`: graba con la
45
+ app que quieras y procesa el archivo con
46
+
47
+ ```sh
48
+ recap import grabacion.m4a --title "Revisión semanal" # presencial por omisión
49
+ recap import llamada.mov --remote # video con dos pistas de audio
50
+ ```
51
+
52
+ Los archivos en disco, los comandos, las opciones y el sobre `--json` son los
53
+ mismos que los del `recap` en Swift, así que las dos versiones leen las mismas
54
+ reuniones. Cuando [inkwell](https://github.com/KikeDeAlba/inkwell) está
55
+ instalado y ya migró los documentos de bita, las páginas, las propuestas y el
56
+ backlog se escriben con inkwell; si no, con `bita docs` y `bita backlog`.
57
+
58
+ Para trabajar en el paquete: `pnpm install`, `pnpm test`, `pnpm typecheck` y
59
+ `node src/bin/recap.ts <comando>`.
60
+
61
+ ## Requirements
62
+
63
+ - macOS 15 or later (ScreenCaptureKit microphone capture), Apple Silicon recommended
64
+ - Xcode command line tools (Swift 6)
65
+ - `brew install ffmpeg whisper-cpp`
66
+ - [Claude Code](https://claude.com/claude-code) for summaries
67
+
68
+ ## Install with bita
69
+
70
+ If you use [bita](https://github.com/KikeDeAlba/bita-cli), its setup installs everything:
71
+
72
+ ```sh
73
+ bita setup
74
+ ```
75
+
76
+ It downloads the latest `Recap.app` release to `~/Applications`, links `recap` into a directory on your `PATH`, installs the Claude Code plugin, and runs `recap setup --install-deps`, which installs ffmpeg and whisper-cpp with Homebrew, downloads the models, registers the bita hook and asks for the permissions. `bita setup --no-recap` skips all of it.
77
+
78
+ ## Install from source
79
+
80
+
81
+ ```sh
82
+ make install
83
+ ```
84
+
85
+ This builds `Recap.app`, signs it with your first `Apple Development` identity (or ad-hoc when there is none, set `RECAP_SIGN_IDENTITY` to pick another one), copies it to `~/Applications` and links `~/.local/bin/recap`. Make sure `~/.local/bin` is on your `PATH`, or install the link elsewhere with `make install PREFIX_BIN=/opt/homebrew/bin`.
86
+
87
+ Then check the dependencies and grant the permissions:
88
+
89
+ ```sh
90
+ recap setup # add --install-deps to brew install a missing ffmpeg or whisper-cpp
91
+ ```
92
+
93
+ ### Why an app bundle
94
+
95
+ The recorder always runs as `Recap.app`, launched with `open`, so macOS grants the microphone and screen permissions to Recap itself no matter who starts the recording: a terminal, Claude Code, bita or the bita menu bar app. With a stable signing identity the permissions survive rebuilds; with ad-hoc signing macOS may ask again after every `make install`.
96
+
97
+ ## Usage
98
+
99
+ ```sh
100
+ recap start --remote "Sprint planning"
101
+ recap status
102
+ recap stop
103
+
104
+ recap start --in-person "1:1 with Ana"
105
+ recap stop
106
+
107
+ recap list
108
+ recap show last
109
+ recap show --bita-entry 812 --json # the meeting behind a bita entry, with paths to every file
110
+ recap discard <meeting>
111
+ ```
112
+
113
+ Free space once a meeting is processed:
114
+
115
+ ```sh
116
+ recap compress-video <meeting> --preset medium # HEVC re-encode: light (1280 px, 2 fps), medium (960 px, 1 fps), max (720 px, 0.5 fps)
117
+ recap strip-video <meeting> # drop the video, keep mic and system audio in recording.m4a
118
+ recap prune <meeting> --intermediates # delete mic.wav, system.wav, the per-channel transcripts and live/chunks
119
+ recap delete <meeting> # delete the whole meeting folder
120
+ ```
121
+
122
+ `compress-video` and `strip-video` work on remote meetings only, accept `--bita-entry` and `--prune-intermediates`, check the new file (duration and audio tracks) with AVFoundation before replacing the original, and refuse while the meeting is being recorded or processed. `compress-video` keeps the original when the result is not smaller (`NOT_SMALLER`). After `strip-video` the `frames` stage is skipped and the existing frames are kept. `recap list --json` reports `hasVideo`, `storage` (`recordingBytes`, `intermediateBytes`, `framesBytes`, `otherBytes`, `totalBytes`), `video` and `videoRemovedAt` for each meeting; `--limit 0` lists them all.
123
+
124
+ `recap stop` processes the meeting in the background (`--no-process` to skip). Re-run the pipeline at any time:
125
+
126
+ ```sh
127
+ recap process last # resume from the first stage that is not done
128
+ recap process <meeting> --from summarize # regenerate the summary only
129
+ recap process <meeting> --only frames
130
+ recap prompt <meeting> # print the summary prompt with transcript and frames
131
+ recap save-summary <meeting> file.md # store minutes written elsewhere
132
+ ```
133
+
134
+ ## Claude Code plugin
135
+
136
+ The repository is also a Claude Code plugin marketplace:
137
+
138
+ ```
139
+ /plugin marketplace add KikeDeAlba/recap
140
+ /plugin install recap@recap
141
+ ```
142
+
143
+ | Command | What it does |
144
+ |---|---|
145
+ | `/recap-start [remota\|presencial] [título]` | Starts a recording; infers the mode from the arguments or the conversation and asks when it cannot |
146
+ | `/recap-stop [--wait]` | Stops the recording; with `--wait` it processes in the foreground and shows agreements and action items |
147
+ | `/recap-status` | Shows whether a recording is running and how far processing got |
148
+ | `/recap-list [meeting]` | Lists meetings or shows one meeting's minutes |
149
+ | `/recap-summarize [meeting] [instructions]` | Rewrites the minutes inside the session, following extra instructions, and stores them with `recap save-summary` |
150
+ | `/recap-ask [question]` | Answers the last question of the meeting being recorded, or the one given, from the bita pages and the project repositories |
151
+ | `/recap-proposals [meeting]` | Reviews the documentation changes proposed by a meeting and accepts, edits or rejects them |
152
+
153
+ The `recap` skill lets Claude answer questions such as "¿qué acordamos en la reunión de ayer?" from the stored minutes and transcripts. The commands call `recap`, so it must be on the `PATH` of the shell Claude Code runs.
154
+
155
+ ## Live assistant
156
+
157
+ While a meeting is being recorded, recap also transcribes it live:
158
+
159
+ - The recorder taps the microphone and system audio, converts each to 16 kHz mono, and cuts it on silence into 3–10 s chunks (`live.maxChunkSeconds`) under `live/chunks/`. The tap runs on its own queue and never blocks the file writer.
160
+ - A detached `recap live-worker` transcribes each chunk with `whisper-cli` (same model and VAD as the pipeline, 4 threads, the previous text as prompt), drops hallucinations and microphone echo, and appends `{startMs, endMs, channel, text}` lines to `live/transcript.jsonl`, with offsets from the start of the recording. It exits once the recording stops and the queue is empty.
161
+ - The transcript after `stop` is still the source of truth; the live one is for answering during the meeting.
162
+
163
+ `recap ask` answers a question with Claude Code (headless, streaming), reading only the bita pages and the repositories registered for the entry's project (`bita project repo ls`, bita 0.16 or later):
164
+
165
+ ```sh
166
+ recap ask --active # the last question in the live transcript
167
+ recap ask --active --question "¿cómo se despliega bita-desktop?"
168
+ recap ask --meeting <meeting> --window 300 --json-stream
169
+ recap ask --sources --project CoDi --json # the docs root and repositories it would read
170
+ ```
171
+
172
+ - The context is the last `--window` seconds (default 180) of the live transcript, the entry title and project, the project page tree and its repositories. Claude may use Read, Grep, Glob and, in each repository, only `git -C <repo> log|show|diff` (one exact `--allowedTools` prefix per repository and subcommand, since headless Claude Code rejects `cd <repo> && git …` and does not match wildcards in the middle of a pattern), and nothing else (`Resources/ask-prompt.md`).
173
+ - Answers are short, give the exact command when there is one, cite the page, `file:line` or commit, and say "No está documentado." instead of guessing.
174
+ - `--json-stream` prints one JSON object per line: `question`, `progress` (the file being read), `delta` (answer text), `source`, then `done` with the whole answer, or `error`.
175
+ - Every answer is appended to `live/answers.jsonl` as `{id, askId, askedAt, question, answer, found, sources}`, plus `"auto": true` when the live worker detected the question (manual answers leave `auto` out). `askId` is the id of the ask's file under `live/asking/`, so a pending ask maps to its answer exactly. A failed ask appends nothing.
176
+ - Several answers can run at once. Each queued or running ask is described by `live/asking/<askId>.json`: `{"id": "…", "auto": true | false, "question": "…" | null, "questionMs": …, "channel": "mic" | "system", "startedAt": "…", "state": "queued" | "running", "pid": …}` (`questionMs` and `channel` only when known; `pid` is the process that owns the entry, and entries of dead processes are ignored and pruned). The file is removed when the ask ends, whether it answered or failed. Manual asks never stop other asks; they simply run alongside them.
177
+ - For older readers, `live/asking.json` mirrors the running ask that started last (same shape) and is removed when no ask is running. Writes to `live/asking/` and `live/asking.json` are serialized with `flock` on `live/asking/.lock`.
178
+
179
+ bita-desktop drives this from its floating window: it opens while recording (`live.openWindow`), runs `recap ask --active --json-stream` from a global shortcut and shows the answers.
180
+
181
+ #### Detected questions
182
+
183
+ With `live.autoAsk` on (the default), the live worker also looks for questions on its own queue, so transcription never waits for it:
184
+
185
+ - It keeps a cursor in `live/detector-state.json` (`{"detectedThroughMs": …, "examinedSegments": …}`): the transcript lines already examined and the end of the last one. While there are lines past the cursor, and at most once every `live.autoAskMinSeconds` (default 5, 3–120), it sends the new lines, plus up to 60 s before the cursor marked as already reviewed, with the project and its page titles to `claude -p --model <live.autoAskModel>` (default `haiku`) with no tools and the same isolation flags as the pipeline (`Resources/detect-prompt.md`). The answer is strict JSON with every distinct question in the new part: `{"questions": [{"question": "…", "at": "HH:MM:SS"}]}` or `{"questions": []}` (the older `{"question": …}` form is still accepted). A follow-up that only refines the previous question is merged into it.
186
+ - The cursor moves only after a successful call, so lines are never lost to a failed call or to answers in progress; it is retried on the next interval even if the transcript does not grow.
187
+ - Only technical questions that the docs or the code can answer count: how to run, deploy or configure something, what changed, how something was done, where something is. Greetings, logistics, opinions and rhetorical questions are ignored. In remote meetings questions from Remotos weigh more; in-person meetings only have Sala, so the content decides. The question comes back rephrased so it stands on its own.
188
+ - Each question gets its origin (`questionMs`, `channel`) from its `at`. A question too similar to one already answered in `live/answers.jsonl`, queued or running in `live/asking/`, or already detected in this meeting (Jaccard ≥ 0.5 on accent-folded words without stopwords), is skipped.
189
+ - Otherwise it is queued, and the worker runs up to `live.autoAskConcurrency` (default 3, 1–6) of them at a time, in order, each as `recap ask --question <q> --auto --ask-id <id>` in a child process. Nothing is dropped: a question waits in the queue (`"state": "queued"`) until a slot frees up. Manual asks do not count against that limit.
190
+ - Detector and answer failures go to `live/worker.log` and never stop the transcription; queued and running detected answers stop with the worker.
191
+
192
+ ### Proposed documentation changes
193
+
194
+ For meetings linked to a bita entry, the `proposals` stage (before `wrapup`) asks Claude Code (`Resources/proposals-prompt.md`) for the explicit, firm changes said about existing pages: the project pages and the pages linked to the entry, never the meeting's own page. Ideas, doubts and statements corrected later are left out, and the text follows bita's writing rule: nothing that reveals the conversation.
195
+
196
+ Each change becomes a commit on the docs branch `proposal/meeting-<entry>` through `bita docs propose`, and is listed in `proposals.json` (its markdown in `proposals/<n>.md`). Nothing reaches `main`, or Confluence, until it is accepted:
197
+
198
+ ```sh
199
+ recap proposals ls <meeting> --json # or --bita-entry <id>
200
+ recap proposals show <meeting> <n> --json # markdown, quotes and the branch diff
201
+ recap proposals accept <meeting> <n> [--md edited.md]
202
+ recap proposals reject <meeting> <n>
203
+ ```
204
+
205
+ `accept` runs `bita docs branch apply`; with `--md` it first proposes the edited text again. If the page changed since the proposal, the merge conflicts and the proposal turns `stale` (the command still succeeds). When no proposal is pending, the branch is dropped. A failure in this stage is recorded in `stages.proposals` and never stops the wrap-up. Turn it off with `recap config set live.proposals false`.
206
+
207
+ ## bita integration
208
+
209
+ With [bita](https://github.com/KikeDeAlba/bita-cli) 0.12 or later, a meeting timer records the meeting while it runs:
210
+
211
+ ```sh
212
+ bita start "Planeación sprint 42" --kind remote-meeting # recap starts recording the screen, system audio and mic
213
+ bita start "1:1 con Ana" --kind in-person-meeting # recap records the mic only
214
+ bita stop # recap stops, processes, and writes the minutes into the entry
215
+ ```
216
+
217
+ `recap setup` registers the hook in bita (`bita hooks add --on start,stop,cancel,amend --kind in-person-meeting,remote-meeting -- …/recap bita-hook`). It works the same whether the timer is started from the terminal, from Claude Code or from bita-desktop.
218
+
219
+ | bita event | recap |
220
+ |---|---|
221
+ | `start` of a meeting kind | starts recording in the matching mode, linked to the entry |
222
+ | `stop` | stops, processes in the background and wraps the meeting up in bita (see below) |
223
+ | `cancel` | discards the recording |
224
+ | `amend --kind <meeting kind>` on a running entry | starts recording |
225
+ | `amend --kind none` while recording | stops without processing; the recording is kept |
226
+
227
+ ### Wrap-up
228
+
229
+ After the summary, the `wrapup` stage asks Claude Code (headless, `Resources/wrapup-prompt.md`) for a title, a project, a documentation page and backlog items, and applies them through the bita CLI:
230
+
231
+ - the timer gets the meeting's real topic as its title when the current one is generic ("Reunión presencial", "Junta", …);
232
+ - if the timer has no project, it gets one only when the conversation makes it clear and the name exists in `bita projects`; otherwise `wrapup.projectResolved` is false and `/bita-stop` asks;
233
+ - a page is created with `bita docs page new --from-entry` in that project and written as formal documentation (no pending sections); if the timer already had a page, a `## Reunión <date>` section is added to it instead;
234
+ - action items and open questions become `bita backlog` items on that page;
235
+ - the minutes still land in the `## Reunión` section of the entry document.
236
+
237
+ `recap wait --bita-entry <id>` blocks until all of that is done and prints the result (`data.wrapup` with `--json`).
238
+
239
+ The hook output goes to `hooks.log` beside the bita database. Minutes regenerated with `/recap-summarize` or `recap save-summary` are sent to bita again.
240
+
241
+ ## Pipeline
242
+
243
+ | Stage | Remote | In-person | Output |
244
+ |---|---|---|---|
245
+ | `audio` | mic and system tracks | mic track | `mic.wav`, `system.wav` (16 kHz mono) |
246
+ | `transcribe` | per channel, then merged | mic | `transcript.json`, `transcript.md` |
247
+ | `frames` | scene changes, at least 20 s apart, at most 40 | skipped | `frames/hh-mm-ss.jpg`, `frames.json` |
248
+ | `summarize` | `claude -p` with transcript and frames | `claude -p` with transcript | `summary.md` |
249
+ | `proposals` | only when linked to a bita entry and `live.proposals` is on | same | `proposals.json`, `proposals/<n>.md` and the docs branch `proposal/meeting-<entry>` |
250
+ | `wrapup` | only when linked to a bita entry | same | entry title and project, a bita page, backlog items and the `## Reunión` section of the entry document |
251
+
252
+ - Transcription runs locally with `whisper-cli`, `large-v3-turbo` and Silero VAD. Known whisper hallucinations on silence are dropped.
253
+ - In remote meetings the microphone is labelled **Sala** and the call audio **Remotos**. When the microphone picks up the speakers, segments that repeat the call audio within a few seconds are removed as echo; headphones avoid the problem entirely.
254
+ - The summary is written by Claude Code in headless mode, without user hooks, MCP servers or slash commands, and may open the key frames with the Read tool. It contains: Resumen, Temas, Acuerdos, Pendientes (owner, date, minute), Preguntas abiertas and Capturas. Customize it by copying `Resources/summary-prompt.md` to `~/.config/recap/summary-prompt.md`.
255
+
256
+ Every command accepts `--json` and prints an envelope:
257
+
258
+ ```json
259
+ { "schemaVersion": 1, "ok": true, "command": "stop", "generatedAt": "…", "data": { … } }
260
+ ```
261
+
262
+ Errors set `ok: false` and `error: { code, message }`, and exit with status 1.
263
+
264
+ ## Storage
265
+
266
+ Each meeting gets a folder under `~/Recap` (configurable):
267
+
268
+ ```
269
+ ~/Recap/2026-10-02-1530-sprint-planning/
270
+ ├── meeting.json metadata, status and pipeline stages
271
+ ├── recording.mov remote: video + mic track + system track
272
+ ├── recording.m4a in-person: mic track; remote after strip-video: mic + system tracks
273
+ ├── mic.wav, system.wav
274
+ ├── transcript.md merged transcript with timestamps
275
+ ├── frames/ remote only
276
+ ├── summary.md minutes
277
+ ├── live/ transcript.jsonl, answers.jsonl, asking/, asking.json, detector-state.json and chunks/ (live assistant)
278
+ ├── proposals.json proposed documentation changes, with proposals/<n>.md
279
+ ├── recorder.log
280
+ └── process.log
281
+ ```
282
+
283
+ Recordings are written as fragmented QuickTime (remote) or M4A (in-person), so a crash or a forced quit keeps everything up to the last few seconds.
284
+
285
+ ## Configuration
286
+
287
+ `~/.config/recap/config.json` (override with `RECAP_CONFIG_PATH`):
288
+
289
+ ```json
290
+ {
291
+ "root": "~/Recap",
292
+ "language": "es",
293
+ "vocabulary": ["CoDi", "webhook", "PostgreSQL"],
294
+ "summaryModel": "sonnet",
295
+ "whisperModel": "~/.local/share/recap/models/ggml-large-v3-turbo.bin",
296
+ "live": {
297
+ "enabled": true,
298
+ "openWindow": true,
299
+ "proposals": true,
300
+ "maxChunkSeconds": 10,
301
+ "assistModel": "sonnet",
302
+ "autoAsk": true,
303
+ "autoAskModel": "haiku",
304
+ "autoAskMinSeconds": 5,
305
+ "autoAskConcurrency": 3
306
+ },
307
+ "tools": {
308
+ "claude": "/Users/me/.nvm/versions/node/v24.19.0/bin/claude",
309
+ "ffmpeg": "/opt/homebrew/bin/ffmpeg"
310
+ }
311
+ }
312
+ ```
313
+
314
+ - `vocabulary` is passed to whisper as a glossary and fixes most misheard product names.
315
+ - `tools` holds absolute paths to the external commands. `recap setup` fills it in, so the pipeline also works when it is started with a minimal `PATH` (for example from a menu bar app).
316
+ - `recap setup` downloads the whisper and VAD models to `~/.local/share/recap/models`.
317
+ - `live` controls the live assistant; every key is optional. `enabled` turns the live transcription on, `openWindow` lets bita-desktop open its floating window while recording, `proposals` turns the `proposals` stage on, `maxChunkSeconds` (5–60) is the longest live chunk, `assistModel` is the model for `recap ask` (Claude Code's default when unset), `autoAsk` turns question detection on, `autoAskModel` is the model that detects them (`haiku` by default), `autoAskMinSeconds` (3–120) is the shortest time between two detections, and `autoAskConcurrency` (1–6) is how many detected questions are answered at once.
318
+ - `recap config get [<key>] --json` and `recap config set <key> <value> --json` read and change the `live.*` keys.
319
+
320
+ Environment overrides: `RECAP_ROOT`, `RECAP_STATE_DIR`, `RECAP_DATA_DIR`.
321
+
322
+ ## recap-capture
323
+
324
+ `recap-capture` is the capture half of recap as a standalone executable, for tools that want to record a meeting without the rest of recap. It ships inside `Recap.app` next to `recap` (`Recap.app/Contents/MacOS/recap-capture`), `make install` links it into `~/.local/bin`, and its code lives in the `RecapCapture` library target that `recap` itself uses, so both record exactly the same way.
325
+
326
+ ```sh
327
+ recap-capture record <meetingDir> [--live-worker <command>] # record until SIGINT or SIGTERM
328
+ recap-capture permissions [--request] [--json] # {microphone, screen}: granted, denied, not-determined, restricted
329
+ recap-capture capabilities --json # kit envelope: name, version, capabilities, emits
330
+ recap-capture --version
331
+ ```
332
+
333
+ `--json` prints one line with the same envelope as `recap` (`schemaVersion`, `ok`, `command`, `generatedAt`, `data` or `error {code, message}`). Capabilities: `capture.remote`, `capture.in-person` and `capture.live-chunks`. A usage error with `--json` prints an error envelope with code `USAGE` and exits with 64.
334
+
335
+ ### Launching it
336
+
337
+ macOS grants the microphone and screen permissions to the app that is running, so record through the bundle:
338
+
339
+ ```sh
340
+ open -g -n -a Recap.app --stdout <meetingDir>/recorder.log --stderr <meetingDir>/recorder.log \
341
+ --args capture record <meetingDir>
342
+ ```
343
+
344
+ `recap capture <command>` runs the same commands as `recap-capture <command>`, with the same envelopes; that is how the `open` call above, which always starts the bundle's main executable, reaches them. `permissions --request` only shows the system prompts this way; running `recap-capture` directly from a terminal attributes the permissions to the terminal. macOS applies a newly granted screen permission on the next launch, so the report that follows the request still says `denied` for it.
345
+
346
+ ### On-disk contract
347
+
348
+ The caller creates `<meetingDir>/meeting.json` and `recap-capture record` updates it in place (pretty-printed, sorted keys, ISO 8601 dates):
349
+
350
+ | Key | Who writes it | Meaning |
351
+ |---|---|---|
352
+ | `schemaVersion`, `id`, `title`, `createdAt`, `stages` | caller | required to read the file; `stages` can be `{}` |
353
+ | `mode` | caller | `remote` or `in-person` |
354
+ | `status` | caller (`starting`), recorder | `recording` once capture starts, `recorded` after a clean stop, `failed` when it cannot start |
355
+ | `display` | caller, optional | display ID to record in remote mode; the main display otherwise |
356
+ | `recorderPid` | recorder | the recorder's PID while it runs, removed when it stops |
357
+ | `startedAt`, `endedAt` | recorder | when the capture started and stopped |
358
+ | `error` | recorder | why it failed or stopped early |
359
+
360
+ Every other key, including ones recap does not know, is kept. The recording goes to `recording.mov` (remote: H.264 video at 1280 px and 2 fps, then the microphone and the system audio as separate AAC tracks) or `recording.m4a` (in-person: microphone only). Send SIGINT or SIGTERM to `recorderPid` to stop; the recorder closes the file, sets `status` to `recorded` and exits with 0, or with 1 after an error.
361
+
362
+ When `live.enabled` is on (the default, see Configuration), it also cuts the audio into 16 kHz mono 16-bit WAV chunks under `live/chunks/`, named `<channel>-<seq, 5 digits>.wav` with `channel` `mic` or `system`, and appends one line per chunk to `live/chunks/index.jsonl`:
363
+
364
+ ```json
365
+ {"channel":"mic","endMs":9870,"file":"mic-00001.wav","seq":1,"startMs":0}
366
+ {"channel":"system","endMs":12000,"seq":2,"startMs":9870}
367
+ ```
368
+
369
+ Offsets are milliseconds from the start of the recording; chunks without speech have no `file` and no WAV. Then it spawns the live worker, detached, with its output in `live/worker.log`. `--live-worker` (or `RECAP_LIVE_WORKER` when the option is absent) picks it: an executable runs as `<executable> live-worker <meetingDir>`, a JSON array such as `["node", "/path/recap.js", "live-worker"]` is the whole command with `<meetingDir>` appended, and `none` turns it off. Use absolute paths: an app started by `open` gets the minimal launchd `PATH`, and bare names are only searched there and in `/opt/homebrew/bin`, `/usr/local/bin` and `~/.local/bin`. `open` does not pass the caller's environment either; `recap start` forwards `RECAP_LIVE_WORKER` with `open --env`, and other callers should do the same or use `--live-worker`. By default `recap-capture` spawns the `recap` next to it and `recap` spawns itself, which is the behavior of `recap start`.
370
+
371
+ ## Development
372
+
373
+ ```sh
374
+ make build
375
+ make test
376
+ ```
377
+
378
+ ## Releases
379
+
380
+ ```sh
381
+ make release # dist/Recap-<version>-macos-arm64.zip and its .sha256
382
+ ./scripts/release.sh --publish # from main: create or update the v<version> GitHub release
383
+ ```
384
+
385
+ The version comes from `CaptureTool.version` in `Sources/RecapCapture/Commands/CaptureCommands.swift`, which `recap` and `recap-capture` share. Releases are built and signed locally with an Apple Development identity, so macOS keeps the permissions across updates; they are not notarized. `bita setup` downloads the `*-macos-arm64.zip` asset of the latest release.
386
+
387
+ ## License
388
+
389
+ MIT
@@ -0,0 +1,49 @@
1
+ Estás acompañando en vivo una reunión. Quien te consulta necesita una respuesta para darla ahí mismo, en voz alta, en pocos segundos.
2
+
3
+ - Reunión: «{{title}}»
4
+ - Proyecto: {{project}}
5
+ - Hora de la consulta: {{now}}
6
+
7
+ {{questionBlock}}
8
+
9
+ ## Dónde buscar
10
+
11
+ Responde solo con lo que encuentres en estas fuentes. Puedes leerlas con Read, Grep y Glob. Para el historial de un repositorio usa solo `git -C <ruta del repo> log`, `git -C <ruta del repo> show` o `git -C <ruta del repo> diff`, con la ruta exacta de la lista: un comando por llamada, sin `cd`, sin `&&` y sin tuberías; cualquier otra forma se rechaza.
12
+
13
+ - Raíz de la documentación de bita: {{docsRoot}}
14
+ - Páginas del proyecto (ruta relativa a la raíz):
15
+ {{pages}}
16
+ - Repositorios del proyecto:
17
+ {{repos}}
18
+
19
+ Empieza por las páginas cuyo título se relacione con la pregunta; si no alcanza, busca con Grep en la raíz de la documentación y después en los repositorios (README, scripts, Makefile, pipelines, código). Haz pocas lecturas: es una consulta en vivo.
20
+
21
+ ## Cómo responder
22
+
23
+ - No escribas nada mientras investigas: tu primera línea de texto ya es la respuesta.
24
+ - Si no te dieron la pregunta, la primera línea es `PREGUNTA: <la pregunta que vas a responder>`, tal como la diría quien preguntó, en una línea.
25
+ - Luego la respuesta, en español, breve: de una a cinco líneas, o una lista corta de pasos. Directo al punto, sin saludos ni repetir la pregunta.
26
+ - Si la pregunta es cómo se ejecuta, despliega o configura algo, da el comando exacto en un bloque de código, tal como aparece en la fuente.
27
+ - Cita de dónde sale cada dato en la misma línea, entre paréntesis: el título de la página, `archivo:línea` o el commit corto.
28
+ - Si no encuentras la respuesta en las fuentes, di exactamente «No está documentado.» y, en una línea, qué fuentes revisaste. Nunca inventes comandos, rutas, cifras ni nombres, ni completes con conocimiento general.
29
+ - Al final, siempre, un bloque con las fuentes que usaste, con esta forma exacta:
30
+
31
+ ```fuentes
32
+ {"question": "la pregunta respondida", "at": "HH:MM:SS", "found": true, "sources": [
33
+ {"kind": "page", "label": "Título de la página", "pageId": 12, "path": "ruta/relativa.md"},
34
+ {"kind": "file", "label": "README.md:42", "repo": "/ruta/absoluta/del/repo", "path": "/ruta/absoluta/README.md", "line": 42},
35
+ {"kind": "commit", "label": "abc1234 asunto del commit", "repo": "/ruta/absoluta/del/repo", "sha": "abc1234"}
36
+ ]}
37
+ ```
38
+
39
+ `found` es false cuando la respuesta es «No está documentado.», y entonces `sources` va vacío.
40
+
41
+ `at` es la hora, tal como aparece entre corchetes al inicio de la línea de la transcripción, de la línea donde se hizo la pregunta que respondiste; cópiala exacta con el formato `HH:MM:SS`. Si te dieron la pregunta, o no sale de ninguna línea de la transcripción, pon `"at": null`.
42
+
43
+ ## Transcripción reciente
44
+
45
+ {{speakers}}
46
+
47
+ <transcripcion>
48
+ {{transcript}}
49
+ </transcripcion>
@@ -0,0 +1,71 @@
1
+ Estás escuchando en vivo una reunión para encontrar las preguntas que se hacen sobre el proyecto, su tecnología o su trabajo, para que otro proceso las conteste con la documentación, el código y su historial. No respondes las preguntas: solo las encuentras. Perder una pregunta real es peor que proponer una de más: quien responde dirá «No está documentado» cuando no haya fuente.
2
+
3
+ - Reunión: «{{title}}»
4
+ - Proyecto: {{project}}
5
+ - Páginas de documentación del proyecto:
6
+ {{pages}}
7
+
8
+ ## Qué cuenta como pregunta
9
+
10
+ Cualquier pregunta real, dicha en la parte nueva de la transcripción, sobre el proyecto, sus sistemas, su tecnología, sus procesos, sus equipos o su historia. Por ejemplo:
11
+
12
+ - qué es algo o qué relación tiene con otra cosa: «¿Qué es DP Suites?», «¿Tiene HCL algo que ver con DP Suites?»;
13
+ - si existe algo: «¿Hay pruebas unitarias en los proyectos?»;
14
+ - cómo se ejecuta, despliega, instala, configura o prueba algo, y dónde o en qué máquina;
15
+ - qué cambió en algo, cuándo cambió o cómo se logró;
16
+ - cómo funciona algo o dónde está: un archivo, una variable, un servicio, una página;
17
+ - comparaciones y recomendaciones técnicas: «¿Cuál sería mejor, Playwright o PyWinAuto?», «¿Conviene migrar a X?»; la documentación puede tener comparativas, resultados o decisiones previas.
18
+
19
+ Solo dejas fuera:
20
+
21
+ - saludos, cortesías y pruebas de audio o pantalla: «¿me escuchan?», «¿se ve mi pantalla?»;
22
+ - logística pura: horarios, agenda, quién sigue, cuándo nos vemos;
23
+ - muletillas y preguntas retóricas que no piden información: «¿no?», «¿verdad?», «¿sale?», «¿va?»;
24
+ - chistes y charla personal sin relación con el trabajo;
25
+ - preguntas que se contestaron completas ahí mismo en la conversación.
26
+
27
+ Si el proyecto aparece como «sin proyecto», no descartes preguntas por su tema. Ante la duda, inclúyela.
28
+
29
+ {{speakers}}
30
+
31
+ ## Una entrada por pregunta distinta
32
+
33
+ - Devuelve **todas** las preguntas distintas que cumplan lo anterior, en el orden en que se dijeron, no solo la última.
34
+ - Preguntas sobre temas distintos van en entradas separadas, aunque se digan seguidas. Dos preguntas seguidas sobre el mismo tema, como «¿Qué es DP Suites? ¿Tiene HCL algo que ver con DP Suites?», pueden ir en una sola entrada que pregunte las dos cosas.
35
+ - Si una pregunta solo precisa o corrige la anterior, únelas en una sola pregunta independiente. Por ejemplo, «¿Qué necesitaría descargar para probar en local?» seguida de «¿O más bien en el ambiente de QA?» es una sola entrada: «¿Qué necesitaría descargar o configurar para probar el proyecto en local o en el ambiente de QA?».
36
+ - Si la parte nueva solo precisa una pregunta del contexto ya revisado y la precisión cambia lo que hay que responder (otro ambiente, otro componente), devuelve la pregunta completa ya precisada como una entrada; si no cambia nada, déjala fuera.
37
+ - Reformula cada pregunta como pregunta independiente y completa en español, que se entienda sin la transcripción: nombra el sistema, el componente o el ambiente del que se habla en lugar de «eso» o «ahí».
38
+
39
+ ## Preguntas ya atendidas
40
+
41
+ No repitas ninguna de estas, ni otra que pregunte lo mismo con otras palabras:
42
+
43
+ {{known}}
44
+
45
+ ## Cómo responder
46
+
47
+ Responde únicamente con un objeto JSON en una línea, sin texto antes ni después y sin bloque de código:
48
+
49
+ {"questions": [{"question": "la pregunta", "at": "HH:MM:SS"}]}
50
+
51
+ o, si en la parte nueva no hay ninguna pregunta que cuente:
52
+
53
+ {"questions": []}
54
+
55
+ `at` es la hora, tal como aparece entre corchetes al inicio de la línea de la transcripción, de la línea donde se dijo la pregunta (si se unieron dos, la de la primera). Cópiala exacta, con el formato `HH:MM:SS`; no la calcules ni la inventes.
56
+
57
+ ## Contexto ya revisado
58
+
59
+ Estas líneas ya se revisaron antes; sirven solo para entender la parte nueva. No devuelvas preguntas que estén aquí:
60
+
61
+ <revisado>
62
+ {{reviewed}}
63
+ </revisado>
64
+
65
+ ## Transcripción nueva
66
+
67
+ Busca las preguntas solo aquí:
68
+
69
+ <transcripcion>
70
+ {{transcript}}
71
+ </transcripcion>
@@ -0,0 +1,44 @@
1
+ Acabas de recibir la transcripción de una reunión. Tu tarea es detectar los cambios **explícitos y firmes** que se dijeron sobre documentación que ya existe, y redactarlos como propuestas de cambio a esas páginas. Nadie las aplica sin revisarlas antes.
2
+
3
+ - Reunión: «{{title}}» · Fecha: {{date}} · Modo: {{mode}}
4
+ - {{speakers}}
5
+ - Raíz de la documentación: {{docsRoot}}
6
+ - Páginas candidatas (solo puedes proponer cambios a estas; ábrelas con Read antes de proponer):
7
+ {{pages}}
8
+
9
+ ## Qué cuenta como cambio
10
+
11
+ Solo entra lo que en la conversación quedó afirmado como un hecho nuevo o una corrección de algo que la página dice hoy. Por ejemplo, «el despliegue ya no es con `make deploy`, ahora es `make release`», «el tope pasó a 10 000 pesos», «ese servicio ya no existe, lo reemplazó X».
12
+
13
+ Deja fuera:
14
+
15
+ - ideas, propuestas, opciones o cosas que «se podrían» hacer;
16
+ - dudas, preguntas y lo que quedó por confirmar;
17
+ - lo que alguien dijo y después se corrigió o se contradijo: en ese caso vale solo la versión final, y si no quedó clara, nada;
18
+ - pendientes y tareas (eso no va en las páginas);
19
+ - cambios a páginas que no están en la lista;
20
+ - lo que la página ya dice.
21
+
22
+ Si no hay ningún cambio que cumpla todo lo anterior, responde con la lista vacía. Es lo más común y es una respuesta correcta.
23
+
24
+ ## Cómo redactar cada propuesta
25
+
26
+ - `pageId`: el número de la página de la lista.
27
+ - `section`: el texto exacto del encabezado `##` de la sección que cambia, sin los `#`. Si el cambio no cabe en ninguna sección existente, un encabezado nuevo, corto. `null` solo si hay que reescribir la página completa.
28
+ - `markdown`: el contenido **completo** que debe quedar en esa sección (sin la línea del encabezado), o de la página entera si `section` es `null`. Conserva todo lo que la sección ya dice y siga siendo cierto, con su formato, y cambia solo lo necesario.
29
+ - Redacción del `markdown`: documentación técnica formal, en presente, que explica cómo es el sistema. **Nada que delate la conversación**: prohibido «se acordó», «acordamos», «decidimos», «se decidió», «por decisión de», «en la reunión», «según lo hablado», «como se comentó», «quedamos en», primera o segunda persona y referencias a la grabación. Se escribe el hecho, no quién lo dijo ni cuándo.
30
+ - `title`: una línea que describa el cambio, por ejemplo «Actualizar el comando de despliegue a make release».
31
+ - `rationale`: una o dos líneas para quien revisa, con qué se dijo y por qué cambia la página.
32
+ - `quotes`: de una a tres citas textuales de la transcripción que sostienen el cambio, cada una con `startMs` (los milisegundos de la marca `[hh:mm:ss]` del párrafo), `channel` (`mic` para «Sala», `system` para «Remotos»; en reuniones presenciales siempre `mic`) y `text`.
33
+
34
+ Una sola propuesta por sección: si varias cosas cambian la misma sección, júntalas.
35
+
36
+ ## Formato de la respuesta
37
+
38
+ Responde únicamente con un objeto JSON, sin texto antes ni después y sin bloque de código:
39
+
40
+ {"proposals": [{"pageId": 12, "section": "Despliegue", "markdown": "...", "title": "...", "rationale": "...", "quotes": [{"startMs": 754000, "channel": "mic", "text": "..."}]}]}
41
+
42
+ <transcripcion>
43
+ {{transcript}}
44
+ </transcripcion>
@@ -0,0 +1,37 @@
1
+ Eres el redactor de la minuta de una reunión. Abajo está la transcripción automática (whisper, sin corrección humana) de la reunión «{{title}}».
2
+
3
+ - Fecha: {{date}}
4
+ - Duración: {{duration}}
5
+ - Modo: {{mode}}
6
+ {{speakers}}
7
+ {{frames}}
8
+
9
+ Escribe la minuta en español, en Markdown, con exactamente esta estructura:
10
+
11
+ ## Resumen
12
+ Un párrafo de 3 a 6 oraciones: propósito de la reunión, qué se discutió y en qué quedó.
13
+
14
+ ## Temas
15
+ Lista con los temas tratados, en el orden en que aparecieron. Cada tema con 1 a 3 oraciones de lo que se dijo, citando el minuto entre corchetes, por ejemplo [00:12:30].
16
+
17
+ ## Acuerdos
18
+ Lista numerada de decisiones tomadas. Solo lo que se decidió de forma explícita; si algo quedó en duda, va en «Preguntas abiertas».
19
+
20
+ ## Pendientes
21
+ Tabla con las columnas `#`, `Pendiente`, `Responsable`, `Fecha` y `Minuto`. Responsable y fecha solo si se mencionaron; si no, «—». Cada pendiente redactado como acción concreta que empieza con verbo.
22
+
23
+ ## Preguntas abiertas
24
+ Lista de dudas, riesgos o temas que quedaron sin resolver. Si no hay, escribe «Ninguna».
25
+
26
+ ## Capturas
27
+ Lista de las capturas que aportan contexto (diapositivas, demos, documentos), con el nombre del archivo, el minuto y qué muestran. Omite la sección completa, encabezado incluido, si no hay capturas o si ninguna tiene relación con la reunión.
28
+
29
+ Reglas:
30
+ - No inventes nada que no esté en la transcripción o en las capturas. Si un nombre, cifra o fecha no se entiende, márcalo como «(inaudible)» o «(por confirmar)».
31
+ - La transcripción tiene errores de reconocimiento: corrige términos técnicos y nombres propios evidentes por contexto, sin cambiar el sentido.
32
+ - Redacta en tercera persona y en tono de documentación técnica. Nada de «el usuario dijo», «según la conversación» ni comentarios sobre la calidad del audio, salvo que impida entender un acuerdo.
33
+ - Responde únicamente con la minuta, empezando por «## Resumen». Sin preámbulo ni cierre.
34
+
35
+ <transcripcion>
36
+ {{transcript}}
37
+ </transcripcion>