shadow-clerk 0.2.0__tar.gz

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 (49) hide show
  1. shadow_clerk-0.2.0/LICENSE +21 -0
  2. shadow_clerk-0.2.0/PKG-INFO +650 -0
  3. shadow_clerk-0.2.0/README.md +601 -0
  4. shadow_clerk-0.2.0/pyproject.toml +113 -0
  5. shadow_clerk-0.2.0/setup.cfg +4 -0
  6. shadow_clerk-0.2.0/src/shadow_clerk/__init__.py +46 -0
  7. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_audio.py +477 -0
  8. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_config.py +58 -0
  9. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_constants.py +187 -0
  10. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard.py +5 -0
  11. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_base.py +358 -0
  12. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_css.py +253 -0
  13. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_handler.py +8 -0
  14. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_html.py +298 -0
  15. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_js.py +6 -0
  16. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_js_core.py +558 -0
  17. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_js_panels.py +589 -0
  18. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_dashboard_ops.py +1001 -0
  19. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_log_buffer.py +195 -0
  20. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_main.py +206 -0
  21. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_recorder.py +9 -0
  22. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_recorder_capture.py +335 -0
  23. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_recorder_command.py +581 -0
  24. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_recorder_transcribe.py +725 -0
  25. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_transcriber.py +208 -0
  26. shadow_clerk-0.2.0/src/shadow_clerk/_daemon_vad.py +99 -0
  27. shadow_clerk-0.2.0/src/shadow_clerk/_llm_config.py +151 -0
  28. shadow_clerk-0.2.0/src/shadow_clerk/_llm_glossary.py +260 -0
  29. shadow_clerk-0.2.0/src/shadow_clerk/_llm_summarize.py +359 -0
  30. shadow_clerk-0.2.0/src/shadow_clerk/_llm_translate.py +362 -0
  31. shadow_clerk-0.2.0/src/shadow_clerk/_transcript_name.py +198 -0
  32. shadow_clerk-0.2.0/src/shadow_clerk/clerk_daemon.py +25 -0
  33. shadow_clerk-0.2.0/src/shadow_clerk/clerk_util.py +510 -0
  34. shadow_clerk-0.2.0/src/shadow_clerk/domain/__init__.py +18 -0
  35. shadow_clerk-0.2.0/src/shadow_clerk/domain/language.py +34 -0
  36. shadow_clerk-0.2.0/src/shadow_clerk/domain/meeting_session.py +48 -0
  37. shadow_clerk-0.2.0/src/shadow_clerk/domain/speaker.py +24 -0
  38. shadow_clerk-0.2.0/src/shadow_clerk/domain/summary.py +24 -0
  39. shadow_clerk-0.2.0/src/shadow_clerk/domain/transcript_line.py +55 -0
  40. shadow_clerk-0.2.0/src/shadow_clerk/domain/translation.py +24 -0
  41. shadow_clerk-0.2.0/src/shadow_clerk/gcal_monitor.py +318 -0
  42. shadow_clerk-0.2.0/src/shadow_clerk/i18n.py +928 -0
  43. shadow_clerk-0.2.0/src/shadow_clerk/llm_client.py +234 -0
  44. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/PKG-INFO +650 -0
  45. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/SOURCES.txt +47 -0
  46. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/dependency_links.txt +1 -0
  47. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/entry_points.txt +3 -0
  48. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/requires.txt +26 -0
  49. shadow_clerk-0.2.0/src/shadow_clerk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atsushi Kato
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.
@@ -0,0 +1,650 @@
1
+ Metadata-Version: 2.4
2
+ Name: shadow-clerk
3
+ Version: 0.2.0
4
+ Summary: Real-time audio recording and transcription tool for web meetings
5
+ Author-email: Atsushi Kato <ktat.is@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/edocode/shadow-clerk
8
+ Project-URL: Repository, https://github.com/edocode/shadow-clerk
9
+ Project-URL: Issues, https://github.com/edocode/shadow-clerk/issues
10
+ Keywords: meeting,transcription,speech-to-text,whisper,real-time,audio,japanese,asr,translation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Capture/Recording
22
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
23
+ Classifier: Topic :: Office/Business
24
+ Classifier: Natural Language :: English
25
+ Classifier: Natural Language :: Japanese
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: faster-whisper>=1.0.0
30
+ Requires-Dist: sounddevice>=0.4.6
31
+ Requires-Dist: webrtcvad-wheels>=2.0.11
32
+ Requires-Dist: numpy>=1.24.0
33
+ Requires-Dist: pyyaml>=6.0
34
+ Requires-Dist: openai>=1.0.0
35
+ Requires-Dist: pynput>=1.7.6
36
+ Requires-Dist: evdev>=1.6.0; sys_platform == "linux"
37
+ Requires-Dist: PyAudioWPatch>=0.2.12; sys_platform == "win32"
38
+ Requires-Dist: langdetect>=1.0.9
39
+ Provides-Extra: spell-check
40
+ Requires-Dist: transformers>=4.30.0; extra == "spell-check"
41
+ Requires-Dist: torch>=2.0.0; extra == "spell-check"
42
+ Requires-Dist: sentencepiece>=0.1.99; extra == "spell-check"
43
+ Provides-Extra: reazonspeech
44
+ Requires-Dist: sherpa-onnx>=1.12.28; extra == "reazonspeech"
45
+ Provides-Extra: gcal
46
+ Requires-Dist: google-auth-oauthlib>=1.0.0; extra == "gcal"
47
+ Requires-Dist: google-api-python-client>=2.0.0; extra == "gcal"
48
+ Dynamic: license-file
49
+
50
+ # Shadow-clerk
51
+
52
+ A tool that records web meeting audio in real-time and transcribes it. Also supports translation and meeting minutes generation.
53
+
54
+ ## Platform support
55
+
56
+ | OS | Status | Notes |
57
+ |----|--------|-------|
58
+ | Linux (PipeWire/PulseAudio) | Supported | Primary development target |
59
+ | Windows 10/11 | Supported | Monitor capture via WASAPI loopback (default playback device) |
60
+ | macOS | Not supported yet | Requires a virtual audio driver (e.g. BlackHole) — not implemented |
61
+
62
+ ### Windows-specific notes
63
+
64
+ Recommended install (explicit Windows deps):
65
+
66
+ ```powershell
67
+ uv python install 3.13
68
+ uv tool install --python 3.13 --with PyAudioWPatch -e ".[spell-check,gcal]"
69
+ # +ReazonSpeech k2 (Japanese ASR, optional):
70
+ uv tool install --python 3.13 --with PyAudioWPatch --with sherpa-onnx --with "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr" -e ".[spell-check,gcal,reazonspeech]"
71
+ ```
72
+
73
+ Why explicit `--python` and `--with`:
74
+
75
+ - **`--python 3.13` (uv-managed Python)**: Microsoft Store Python runs in an AppContainer sandbox that redirects `%APPDATA%\shadow-clerk` to `%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.X.YY_<id>\LocalCache\Roaming\shadow-clerk\`. The package id changes when the Python minor version is upgraded, silently moving the data directory and orphaning existing transcripts/config. uv-managed Python avoids the sandbox. Daemon startup also logs a WARNING when Store Python is detected.
76
+ - **`--with PyAudioWPatch`**: WASAPI loopback monitor capture uses [PyAudioWPatch](https://github.com/s0d3s/PyAudioWPatch). It's declared as a Windows-only dep in `pyproject.toml` but some uv versions don't reliably resolve PEP 508 markers from local-editable installs, so passing it explicitly is safer.
77
+ - **`--with sherpa-onnx`** (ReazonSpeech only): Same reason — ensures uv picks the Windows wheel (with `onnxruntime.dll`) rather than a stale resolution to the Linux wheel.
78
+
79
+ Other notes:
80
+
81
+ - **Microphone permission**: Allow mic access for the terminal you launch from (Windows Settings → Privacy → Microphone).
82
+ - **Monitor capture**: Uses WASAPI loopback on the system default playback device. Switching the default device in Windows sound settings switches what gets captured.
83
+ - **Data directory**: `%APPDATA%\shadow-clerk` (the `~/.local/share/shadow-clerk` paths in the rest of this README map to that on Windows). Override with `SHADOW_CLERK_DATA_DIR` if needed.
84
+ - **Remote Desktop (RDP)**: When running inside an RDP session, the host's "Remote Audio" virtual device is auto-skipped (it would either segfault or capture nothing useful). The daemon falls back to a non-RDP loopback device if available; otherwise monitor capture is disabled and only the mic is recorded.
85
+ - **`voice_command_key`**: The default `f23` is a Linux/xremap convention. On Windows set it to `null` (disable PTT) or to one of `menu`/`ctrl_r`/`ctrl_l`/`alt_r`/`alt_l`/`shift_r`/`shift_l` in `config.yaml`.
86
+ - **Stopping the daemon**: `clerk-util stop` works (Windows path uses `taskkill`). `clerk-util start` runs the daemon in the foreground with Ctrl+C handling, mirroring Linux.
87
+
88
+ ## Features and requirements
89
+
90
+ | Feature | Requires | Quality | Speed | Related settings |
91
+ |---|---|:---:|:---:|---|
92
+ | Transcription (default) | faster-whisper (included) | 3 | 4 | `default_model`, `default_language` |
93
+ | Transcription (Kotoba-Whisper) | Same (auto-downloaded on first use) | 5 | 3 | `japanese_asr_model: kotoba-whisper` |
94
+ | Transcription (ReazonSpeech) | `uv sync --extra reazonspeech` | 5 | 4 | `japanese_asr_model: reazonspeech-k2` |
95
+ | Interim transcription | Same | 2 | 5 | `interim_transcription: true`, `interim_model` |
96
+ | Translation (LibreTranslate) | LibreTranslate server | 2 | 4 | `translation_provider: libretranslate` |
97
+ | Translation (OpenAI compatible API) | OpenAI compatible API | 3-5 | 2-5 | `translation_provider: api`, `api_endpoint`, `api_model` |
98
+ | Translation (Claude) | Claude Code | 5 | 2 | `translation_provider: claude` |
99
+ | Language detection (pre-translation) | langdetect (included) | — | — | Automatically detects source language to select correct prompt |
100
+ | Summary (Claude) | Claude Code | 5 | 3 | `llm_provider: claude` |
101
+ | Summary (OpenAI compatible API) | OpenAI compatible API | 3-5 | 2-5 | `llm_provider: api`, `api_endpoint`, `api_model` |
102
+ | Voice commands (PTT) | None (built-in) | — | — | `voice_command_key` |
103
+ | Voice commands (LLM matching) | OpenAI compatible API | — | — | `api_endpoint`, `api_model` |
104
+ | Spell check (pre-translation) | transformers (auto-downloaded on first use) | — | — | `libretranslate_spell_check: true` |
105
+
106
+ **Minimal setup without LLM:** Transcription + LibreTranslate translation requires no external API or Claude Code. Everything runs locally.
107
+
108
+ See the [Feature Tour](docs/feature-tour.en.md) for a visual walkthrough with screenshots.
109
+
110
+ ## Setup
111
+
112
+ ### 1. System packages
113
+
114
+ ```bash
115
+ sudo apt install libportaudio2 portaudio19-dev
116
+ ```
117
+
118
+ ### 2. Install
119
+
120
+ ```bash
121
+ git clone https://gitlab.edocode.co.jp/common/shadow-clerk.git
122
+ cd shadow-clerk
123
+ ```
124
+
125
+ | | Command |
126
+ |---|---|
127
+ | Basic | `uv tool install -e .` |
128
+ | + ReazonSpeech | `uv tool install -e ".[reazonspeech]" --with "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr"` |
129
+ | + Spell check | `uv tool install -e ".[spell-check]"` |
130
+ | + Both (ReazonSpeech + Spell check) | `uv tool install -e ".[spell-check,reazonspeech]" --with "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr"` |
131
+ | + Google Calendar | `uv tool install -e ".[gcal]"` |
132
+ | All | `uv tool install -e ".[spell-check,gcal,reazonspeech]" --with "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr"` |
133
+
134
+ > **Note:** `uv tool install` maintains a single environment per tool. When reinstalling with different extras, use `--force` — without it, `uv tool install` reports "already installed" and does not add the extra. Only the extras specified in the command are included; previously installed extras are removed.
135
+
136
+ ### 2a. For development
137
+
138
+ | | Command |
139
+ |---|---|
140
+ | Basic | `uv sync` |
141
+ | + ReazonSpeech | `uv sync --extra reazonspeech` |
142
+ | + Spell check | `uv sync --extra spell-check` |
143
+ | + Both (ReazonSpeech + Spell check) | `uv sync --extra spell-check --extra reazonspeech` |
144
+ | + Google Calendar | `uv sync --extra gcal` |
145
+ | All | `uv sync --extra spell-check --extra gcal --extra reazonspeech` |
146
+
147
+ This is all you need for transcription. The following optional extras are available:
148
+
149
+ ### Optional: Japanese ASR models
150
+
151
+ **Kotoba-Whisper** — No extra install required. The model is auto-downloaded on first use. Just set:
152
+
153
+ ```yaml
154
+ # config.yaml
155
+ japanese_asr_model: kotoba-whisper
156
+ ```
157
+
158
+ **ReazonSpeech k2** — Requires the `reazonspeech` extra plus the `reazonspeech-k2-asr` package, which is only distributed via Git (not on PyPI), so it must be installed separately:
159
+
160
+ ```bash
161
+ uv tool install -e ".[reazonspeech]" \
162
+ --with "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr"
163
+ # or for development:
164
+ uv sync --extra reazonspeech
165
+ uv pip install "reazonspeech-k2-asr @ git+https://github.com/reazon-research/ReazonSpeech.git#subdirectory=pkg/k2-asr"
166
+ ```
167
+
168
+ ```yaml
169
+ # config.yaml
170
+ japanese_asr_model: reazonspeech-k2
171
+ ```
172
+
173
+ ### Optional: Spell check (pre-translation correction)
174
+
175
+ Requires the `spell-check` extra (installs `transformers`, `torch`, `sentencepiece`):
176
+
177
+ ```bash
178
+ uv tool install "shadow-clerk[spell-check]"
179
+ # or for development:
180
+ uv sync --extra spell-check
181
+ ```
182
+
183
+ ```yaml
184
+ # config.yaml
185
+ libretranslate_spell_check: true
186
+ spell_check_model: mbyhphat/t5-japanese-typo-correction # default
187
+ ```
188
+
189
+ The spell check model is auto-downloaded on first use. It corrects Japanese speech recognition typos before sending text to LibreTranslate.
190
+
191
+ ### Optional: Google Calendar integration
192
+
193
+ Automatically starts and ends meeting sessions based on your Google Calendar schedule. Requires the `gcal` extra:
194
+
195
+ ```bash
196
+ uv tool install -e ".[gcal]"
197
+ # or for development:
198
+ uv sync --extra gcal
199
+ ```
200
+
201
+ Then authenticate and configure:
202
+
203
+ ```bash
204
+ # One-time OAuth setup (opens browser)
205
+ clerk-util gcal-auth ~/credentials.json
206
+
207
+ # Enable in config
208
+ clerk-util write-config-value gcal_integration true
209
+ clerk-util write-config-value gcal_credentials_file ~/credentials.json
210
+ ```
211
+
212
+ When enabled, clerk-daemon polls Google Calendar every 60 seconds. Events automatically trigger `start_meeting` / `end_meeting`, creating transcript files named `transcript-YYYYMMDDHHMM@EventTitle.txt`.
213
+
214
+ See [docs/google-calendar-setup.md](docs/google-calendar-setup.md) for full setup instructions including how to obtain `credentials.json` from Google Cloud Console.
215
+
216
+ Add the following options if you need translation or summarization.
217
+
218
+ ### 3. (Optional) LibreTranslate setup
219
+
220
+ Local translation without LLM. Install via Docker or pip:
221
+
222
+ ```bash
223
+ # Docker (recommended)
224
+ docker run -d -p 5000:5000 libretranslate/libretranslate
225
+
226
+ # Or pip
227
+ pip install libretranslate
228
+ libretranslate --host 0.0.0.0 --port 5000
229
+ ```
230
+
231
+ Configuration:
232
+
233
+ ```yaml
234
+ # config.yaml
235
+ translation_provider: libretranslate
236
+ libretranslate_endpoint: http://localhost:5000
237
+ ```
238
+
239
+ ### 4. (Optional) OpenAI compatible API setup
240
+
241
+ Used for translation, summarization, and LLM voice command matching:
242
+
243
+ ```yaml
244
+ # config.yaml — OpenAI
245
+ llm_provider: api
246
+ api_endpoint: https://api.openai.com/v1
247
+ api_model: gpt-4o
248
+ # Add SHADOW_CLERK_API_KEY=sk-... to ~/.local/share/shadow-clerk/.env
249
+ ```
250
+
251
+ ```yaml
252
+ # config.yaml — Ollama (local)
253
+ llm_provider: api
254
+ api_endpoint: http://localhost:11434/v1
255
+ api_model: llama3
256
+ ```
257
+
258
+ ### 5. (Optional) Use Claude CLI as the LLM provider
259
+
260
+ If you have Claude Code installed (`claude` on your `$PATH`), shadow-clerk can shell out to `claude -p` for translation and summarization. Set in `config.yaml`:
261
+
262
+ ```yaml
263
+ llm_provider: claude
264
+ claude_cli_model: haiku # or sonnet / opus / a full model id
265
+ # claude_cli_path: claude # full path if not on $PATH
266
+ ```
267
+
268
+ This uses your existing Claude Code OAuth login. No extra setup needed — translation and summarization run inside the daemon as background threads, no Claude Code session required.
269
+
270
+ ## Usage
271
+
272
+ ### Starting the daemon
273
+
274
+ If you installed via `uv tool install`:
275
+
276
+ ```bash
277
+ clerk-daemon
278
+ ```
279
+
280
+ For development (`uv sync`):
281
+
282
+ ```bash
283
+ uv run clerk-daemon
284
+ ```
285
+
286
+ > **Note:** `uv run` uses the project `.venv`, while `uv tool install` uses its own isolated environment. Make sure extras (e.g. `spell-check`, `reazonspeech`) are installed in the matching environment.
287
+
288
+ ### Recording & transcription
289
+
290
+ ```bash
291
+ # Basic (record mic + system audio, auto-transcribe)
292
+ clerk-daemon
293
+
294
+ # List available devices
295
+ clerk-daemon --list-devices
296
+
297
+ # With options
298
+ clerk-daemon \
299
+ --language ja \
300
+ --model small \
301
+ --output ~/my-transcript.txt \
302
+ --verbose
303
+ ```
304
+
305
+ Press `Ctrl+C` to stop recording.
306
+
307
+ ### Voice commands
308
+
309
+ #### Push-to-Talk (recommended)
310
+
311
+ Hold down the Menu key (next to Right Alt) while speaking a command — no wake word needed:
312
+
313
+ ```
314
+ [Hold Menu key] "start translation" → Translation starts
315
+ [Hold Menu key] "start meeting" → Meeting session starts
316
+ ```
317
+
318
+ The trigger key can be changed via `voice_command_key` in `config.yaml` (`ctrl_r`, `ctrl_l`, `alt_r`, `alt_l`, `shift_r`, `shift_l`). Set to `null` to disable.
319
+
320
+ #### Prefix mode (fallback)
321
+
322
+ During recording, say the wake word (default: "sheruku" / "シェルク") followed by a command for hands-free control:
323
+
324
+ | Voice command | Action |
325
+ |---|---|
326
+ | "sheruku, start meeting" | Start a new meeting session |
327
+ | "sheruku, end meeting" | End the meeting session |
328
+ | "sheruku, language ja" | Switch transcription language to Japanese |
329
+ | "sheruku, language en" | Switch transcription language to English |
330
+ | "sheruku, unset language" | Reset to auto-detect |
331
+ | "sheruku, start translation" | Start the translation loop |
332
+ | "sheruku, stop translation" | Stop the translation loop |
333
+
334
+ The separator (comma, space) between the wake word and command is optional. The wake word can be changed via `wake_word` in `config.yaml`.
335
+
336
+ #### Custom voice commands
337
+
338
+ You can register custom voice commands in `config.yaml` under `custom_commands`. They are evaluated after built-in commands:
339
+
340
+ ```yaml
341
+ custom_commands:
342
+ - pattern: "youtube"
343
+ action: "xdg-open https://www.youtube.com"
344
+ - pattern: "gmail|mail"
345
+ action: "xdg-open https://mail.google.com"
346
+ ```
347
+
348
+ - `pattern`: Regular expression (case-insensitive)
349
+ - `action`: Shell command to execute
350
+
351
+ #### LLM fallback
352
+
353
+ If a voice command doesn't match any built-in or custom command and `api_endpoint` is configured, the utterance is sent to the LLM as a query. The response is printed to stdout and saved to `.clerk_response`.
354
+
355
+ ```
356
+ "sheruku, what is 1+1?" → LLM returns the answer
357
+ ```
358
+
359
+ ### CLI options
360
+
361
+ | Option | Description | Default |
362
+ |---|---|---|
363
+ | `--output`, `-o` | Output file path | `~/.local/share/shadow-clerk/transcript-YYYYMMDD.txt` |
364
+ | `--model`, `-m` | Whisper model size (`tiny`, `base`, `small`, `medium`, `large-v3`) | `small` |
365
+ | `--language`, `-l` | Language code (`ja`, `en`, etc.). Auto-detect if omitted | Auto |
366
+ | `--mic` | Microphone device number | Auto-detect |
367
+ | `--monitor` | Monitor device number (sounddevice) | Auto-detect |
368
+ | `--backend` | Audio backend (`auto`, `pipewire`, `pulseaudio`, `sounddevice`) | `auto` |
369
+ | `--list-devices` | List devices and exit | - |
370
+ | `--verbose`, `-v` | Verbose logging | - |
371
+ | `--dashboard` / `--no-dashboard` | Enable/disable dashboard | Enabled |
372
+ | `--dashboard-port` | Dashboard port number | `8765` |
373
+ | `--beam-size` | Whisper beam size (`1`=fast, `5`=accurate) | `5` |
374
+ | `--compute-type` | Whisper compute precision (`int8`, `float16`, `float32`) | `int8` |
375
+ | `--device` | Whisper device (`cpu`, `cuda`) | `cpu` |
376
+
377
+ ### Translation & Summary Providers
378
+
379
+ Translation and summary each support multiple providers with different operation modes:
380
+
381
+ #### Claude mode (`translation_provider: claude` / `llm_provider: claude`)
382
+
383
+ clerk-daemon shells out to `claude -p` per request, reusing your existing Claude Code OAuth login.
384
+
385
+ - **Highest quality** — especially for Japanese homophone correction (ja→ja)
386
+ - **Requires `claude` on PATH** — found automatically if Claude Code is installed
387
+ - **No Claude Code session required** — the daemon spawns `claude -p` per job, so you don't need to keep a Claude Code terminal open
388
+ - **Translation and summary both run inside daemon threads** — same plumbing as api / libretranslate
389
+ - **Cost tracking**: `claude -p --output-format json` returns `total_cost_usd`, which is logged by the daemon
390
+
391
+ ```yaml
392
+ # config.yaml
393
+ translation_provider: claude # Translation by Claude
394
+ llm_provider: claude # Summary by Claude (default)
395
+ claude_cli_path: claude # full path if not on PATH
396
+ claude_cli_model: haiku # haiku / sonnet / opus or a full model id
397
+ ```
398
+
399
+ #### API mode (`translation_provider: api` / `llm_provider: api`)
400
+
401
+ clerk-daemon calls an external API (OpenAI-compatible) internally. Claude Code is not required.
402
+
403
+ - **Works without Claude Code** — clerk-daemon handles translation and summary on its own
404
+ - **Quality depends on model** — high-end models (GPT-4o) produce good results; smaller models may struggle with Japanese correction
405
+ - **How translation works**: An internal thread in clerk-daemon processes translation. Started/stopped via voice commands or dashboard
406
+ - **Summary works similarly**: `clerk-util summarize` generates minutes via the external API
407
+
408
+ ```yaml
409
+ # config.yaml
410
+ translation_provider: api # Translation via external API
411
+ llm_provider: api # Summary via external API
412
+ api_endpoint: https://api.openai.com/v1
413
+ api_model: gpt-4o
414
+ ```
415
+
416
+ #### LibreTranslate mode (`translation_provider: libretranslate`)
417
+
418
+ Translation only. Runs locally without any external API or Claude Code (summary still needs `llm_provider`).
419
+
420
+ #### Recommended configurations
421
+
422
+ | Use case | Translation | Summary | Notes |
423
+ |---|---|---|---|
424
+ | Best quality (Claude CLI) | `translation_provider: claude` | `llm_provider: claude` | Highest quality, needs `claude` CLI |
425
+ | Autonomous (external API) | `translation_provider: api` | `llm_provider: api` | OpenAI-compatible, quality varies by model |
426
+ | Fully local | `translation_provider: libretranslate` | — | No LLM needed, lower quality |
427
+ | Hybrid | `translation_provider: api` | `llm_provider: claude` | Auto translation + high-quality summary |
428
+
429
+ ### Meeting minutes
430
+
431
+ Three ways to generate minutes: automatically at meeting end, on demand from the dashboard, or via `clerk-util` from the command line:
432
+
433
+ ```
434
+ clerk-util start # Start daemon (background)
435
+ clerk-util stop # Stop daemon
436
+ clerk-util recorder-status # Show running state
437
+ clerk-util summarize # Update minutes from transcript diff
438
+ clerk-util summarize --mode full # Regenerate from full transcript
439
+ clerk-util summarize 20260425 --mode full # Specify date
440
+ clerk-util command start_meeting # Start meeting session
441
+ clerk-util command end_meeting # End meeting session (auto_summary linked)
442
+ clerk-util command translate_start # Start translation loop
443
+ clerk-util command translate_stop # Stop translation loop
444
+ ```
445
+
446
+ Meeting start/end is also available via **voice commands** ("clerk, start meeting" / "clerk, end meeting") or **dashboard buttons**. The dashboard's "Generate Summary" button can trigger minutes generation at any time.
447
+
448
+ Generated meeting minutes are saved to `~/.local/share/shadow-clerk/summary-YYYYMMDD.md`.
449
+
450
+ ### Configuration file
451
+
452
+ Customize defaults and auto-features in `~/.local/share/shadow-clerk/config.yaml`:
453
+
454
+ ```yaml
455
+ # shadow-clerk config
456
+ translate_language: en # Translation target language (ja/en/etc)
457
+ auto_translate: false # Auto-start translation on start meeting
458
+ auto_summary: false # Auto-generate summary on end meeting
459
+ default_language: null # Default language for clerk-daemon (null=auto-detect)
460
+ default_model: small # Default Whisper model for clerk-daemon
461
+ output_directory: null # Transcript output directory (null=data directory)
462
+ llm_provider: claude # LLM for summary ("claude" or "api")
463
+ translation_provider: null # Translation provider (null=use llm_provider, "claude", "api", "libretranslate")
464
+ api_endpoint: null # OpenAI Compatible API base URL
465
+ api_model: null # API model name (gpt-4o, etc.)
466
+ api_key_env: SHADOW_CLERK_API_KEY # Environment variable name for API key
467
+ summary_source: null # Summary source (null=auto: prefer translation if exists / "transcript" / "translate")
468
+ summary_language: null # Summary output language (null=fallback to ui_language / ja, en, zh, ...)
469
+ libretranslate_endpoint: null # LibreTranslate API URL (e.g. http://localhost:5000)
470
+ libretranslate_api_key: null # LibreTranslate API key (null if not required)
471
+ libretranslate_spell_check: false # Spell check before LibreTranslate translation
472
+ spell_check_model: mbyhphat/t5-japanese-typo-correction # Spell check model
473
+ custom_commands: [] # Custom voice commands (list of pattern + action)
474
+ initial_prompt: null # Whisper initial_prompt (vocabulary hints for recognition)
475
+ voice_command_key: f23 # Push-to-Talk key (null=disabled)
476
+ wake_word: シェルク # Wake word (trigger word for voice commands)
477
+ whisper_beam_size: 5 # Whisper beam size (1=fast, 5=accurate)
478
+ whisper_compute_type: int8 # Compute precision (int8/float16/float32)
479
+ whisper_device: cpu # Device (cpu/cuda)
480
+ interim_transcription: false # Interim transcription (real-time display while speaking)
481
+ interim_model: base # Model for interim transcription
482
+ interim_translation: true # Translate interim transcription to dashboard's interim panel
483
+ interim_translation_provider: null # null=auto, "api", "libretranslate", or "claude"
484
+ japanese_asr_model: default # Japanese ASR model (default/kotoba-whisper/reazonspeech-k2)
485
+ kotoba_whisper_model: kotoba-tech/kotoba-whisper-v2.0-faster # Kotoba-Whisper model
486
+ interim_japanese_asr_model: default # Japanese ASR for interim transcription
487
+ reazonspeech_precision: fp32 # ReazonSpeech k2: fp32 / int8 / int8-fp32 (fp16 is invalid)
488
+ ui_language: ja # UI language (ja/en) — dashboard, terminal output, LLM prompts
489
+ ```
490
+
491
+ Manage configuration from Claude Code:
492
+
493
+ ```
494
+ clerk-util read-config # Show current config
495
+ clerk-util write-config-value default_model tiny # Change a setting
496
+ clerk-util write-config-value auto_translate true # Enable auto-translation
497
+ ```
498
+
499
+ With `auto_translate: true`, translation starts automatically when a meeting session begins.
500
+ With `auto_summary: true`, meeting minutes are generated automatically when a meeting session ends.
501
+
502
+ ### Summary source selection
503
+
504
+ When `summary_source` is unset (null/auto), the summary is generated from the translation file if one exists (falling back to the transcript if not). To pin the behavior explicitly:
505
+
506
+ ```
507
+ clerk-util write-config-value summary_source transcript # always use transcript
508
+ clerk-util write-config-value summary_source translate # always use translation (fallback to transcript if missing)
509
+ ```
510
+
511
+ ### Summary language
512
+
513
+ `summary_language` controls the output language of the summary. When unset (null), it falls back to `ui_language`:
514
+
515
+ ```
516
+ clerk-util write-config-value summary_language en # summarize in English
517
+ clerk-util write-config-value summary_language ja # summarize in Japanese
518
+ ```
519
+
520
+ ## File structure
521
+
522
+ ```
523
+ shadow-clerk/ # Repository
524
+ pyproject.toml # Project definition & dependencies
525
+ src/shadow_clerk/ # Main package
526
+ __init__.py # Data directory configuration
527
+ clerk_daemon.py # Recording, VAD, transcription & dashboard
528
+ llm_client.py # External API translation & summary
529
+ i18n.py # Internationalization (ja/en)
530
+ clerk_util.py # Data directory operations & process management
531
+
532
+ ~/.local/share/shadow-clerk/ # Runtime data
533
+ transcript-YYYYMMDD.txt # Transcription output (date-based)
534
+ transcript-YYYYMMDDHHMM.txt # Meeting session transcript
535
+ transcript-YYYYMMDDHHMM@Title.txt # Meeting session transcript (with event title)
536
+ transcript-YYYYMMDD-<lang>.txt # Translation output
537
+ summary-YYYYMMDD.md # Meeting minutes (corresponds to transcript)
538
+ summary-YYYYMMDDHHMM@Title.md # Meeting minutes (named session)
539
+ glossary.txt # Glossary (TSV: translation terms & reading-based text replacement)
540
+ config.yaml # Configuration file
541
+ gcal_token.json # Google Calendar OAuth token (created by gcal-auth)
542
+ ```
543
+
544
+ ## Troubleshooting
545
+
546
+ ### Device not found
547
+
548
+ ```bash
549
+ # List available devices
550
+ clerk-daemon --list-devices
551
+
552
+ # PipeWire: check status
553
+ wpctl status
554
+
555
+ # PulseAudio: list sources
556
+ pactl list short sources
557
+ ```
558
+
559
+ ### Monitor source (system audio) not detected
560
+
561
+ On PipeWire, check sink (output) devices with `wpctl status`.
562
+ On PulseAudio, look for sources containing `.monitor` with `pactl list short sources`.
563
+
564
+ You can also specify the device number manually:
565
+
566
+ ```bash
567
+ clerk-daemon --monitor 5
568
+ ```
569
+
570
+ ### PortAudio error
571
+
572
+ Make sure `libportaudio2` is installed:
573
+
574
+ ```bash
575
+ dpkg -l | grep portaudio
576
+ ```
577
+
578
+ If you see `PortAudioError: Error initializing PortAudio: ... PulseAudio_Initialize: Can't connect to server`, the PulseAudio-compatible service may have crashed. On PipeWire systems, restart `pipewire-pulse`:
579
+
580
+ ```bash
581
+ systemctl --user restart pipewire-pulse
582
+ ```
583
+
584
+ ### Slow transcription
585
+
586
+ Use a lighter model with `--model tiny`:
587
+
588
+ ```bash
589
+ clerk-daemon --model tiny
590
+ ```
591
+
592
+ ### Japanese ASR models
593
+
594
+ The `japanese_asr_model` setting selects the ASR backend used when `language=ja`. When the language changes to something other than `ja`, it automatically reverts to standard Whisper.
595
+
596
+ | Value | Model | Requires | Japanese accuracy | CPU speed |
597
+ |---|---|---|---|---|
598
+ | `default` | Standard Whisper | — | Depends on model size | Depends on model size |
599
+ | `kotoba-whisper` | [Kotoba-Whisper](https://huggingface.co/kotoba-tech/kotoba-whisper-v2.0) | Auto-downloaded on first use | High (rivals large-v3) | ~medium |
600
+ | `reazonspeech-k2` | [ReazonSpeech k2](https://github.com/reazon-research/ReazonSpeech) | `uv sync --extra reazonspeech` | High | Fast |
601
+
602
+ **Kotoba-Whisper** retains the full large-v3 encoder (32 layers) while distilling the decoder down to just 2 layers. Since it has only 2 decoder layers, **beam=5 has almost no speed penalty**.
603
+
604
+ **ReazonSpeech k2** uses sherpa-onnx for inference. When selected, Whisper-specific settings (`default_model`, `whisper_beam_size`, `whisper_compute_type`, `initial_prompt`) are not used.
605
+
606
+ **Selection guide:**
607
+
608
+ | Use case | Settings |
609
+ |---|---|
610
+ | Japanese-focused, accuracy priority | `japanese_asr_model: kotoba-whisper`, `whisper_beam_size: 5` |
611
+ | Japanese-focused, fast & accurate | `japanese_asr_model: reazonspeech-k2` |
612
+ | Japanese-focused, speed priority (CPU) | `japanese_asr_model: default`, `default_model: small`, `whisper_beam_size: 3` |
613
+ | Multilingual | `japanese_asr_model: kotoba-whisper`, `default_model: small` (Kotoba for ja, small for others) |
614
+
615
+ **Interim transcription:**
616
+
617
+ `interim_japanese_asr_model` controls which Japanese ASR model is used for interim transcription (real-time display while speaking). On CPU, keeping the default (`default` with a lightweight model like tiny/base) is recommended.
618
+
619
+ ```yaml
620
+ # Japanese accuracy priority (GPU recommended)
621
+ japanese_asr_model: kotoba-whisper
622
+ interim_japanese_asr_model: kotoba-whisper
623
+ whisper_beam_size: 5
624
+
625
+ # Japanese accuracy + fast interim (CPU recommended)
626
+ japanese_asr_model: kotoba-whisper
627
+ interim_japanese_asr_model: default
628
+ interim_model: base
629
+ whisper_beam_size: 5 # Kotoba has only 2 decoder layers, beam=5 is fine
630
+
631
+ # ReazonSpeech (fast & accurate, CPU friendly)
632
+ japanese_asr_model: reazonspeech-k2
633
+ interim_japanese_asr_model: default
634
+ interim_model: base
635
+
636
+ # Maximum speed (CPU)
637
+ japanese_asr_model: default
638
+ default_model: small
639
+ interim_model: base
640
+ whisper_beam_size: 1
641
+ ```
642
+
643
+ **Interim translation:**
644
+
645
+ When `interim_transcription` is on, the daemon also emits a translation of each pre-confirmed line to the dashboard's interim panel. Two knobs control this:
646
+
647
+ - `interim_translation: true` — toggle the translation panel without disabling interim ASR.
648
+ - `interim_translation_provider: null | "api" | "libretranslate" | "claude"` — pick the backend explicitly. `null` falls back to `translation_provider`; if that is `claude` it is auto-routed to `api` then `libretranslate` (claude is too slow for interim, ~5-10s per call). Set to `claude` only if you accept the latency.
649
+
650
+ The interim panel needs sub-second responses to be useful, so `libretranslate` (local) is recommended; `api` is OK with a fast model. Confirmed-transcript translation is unaffected — it always uses `translation_provider`.