pairvoice 0.1.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 (207) hide show
  1. pairvoice-0.1.0/.claude-plugin/marketplace.json +14 -0
  2. pairvoice-0.1.0/.github/workflows/release.yml +41 -0
  3. pairvoice-0.1.0/.gitignore +10 -0
  4. pairvoice-0.1.0/CLAUDE.md +21 -0
  5. pairvoice-0.1.0/DEVELOPMENT.md +119 -0
  6. pairvoice-0.1.0/LICENSE +21 -0
  7. pairvoice-0.1.0/PKG-INFO +221 -0
  8. pairvoice-0.1.0/README.md +194 -0
  9. pairvoice-0.1.0/examples/dict.example.tsv +3 -0
  10. pairvoice-0.1.0/examples/prompt.example.txt +19 -0
  11. pairvoice-0.1.0/plugin/.claude-plugin/plugin.json +9 -0
  12. pairvoice-0.1.0/plugin/README.md +67 -0
  13. pairvoice-0.1.0/plugin/hooks/hooks.json +14 -0
  14. pairvoice-0.1.0/plugin/hooks/speak-summary.sh +277 -0
  15. pairvoice-0.1.0/plugin/skills/tune/SKILL.md +45 -0
  16. pairvoice-0.1.0/pyproject.toml +95 -0
  17. pairvoice-0.1.0/src/pairvoice/__init__.py +0 -0
  18. pairvoice-0.1.0/src/pairvoice/__main__.py +14 -0
  19. pairvoice-0.1.0/src/pairvoice/audio_state.py +147 -0
  20. pairvoice-0.1.0/src/pairvoice/bundle.py +18 -0
  21. pairvoice-0.1.0/src/pairvoice/checker.py +199 -0
  22. pairvoice-0.1.0/src/pairvoice/cli.py +361 -0
  23. pairvoice-0.1.0/src/pairvoice/client.py +26 -0
  24. pairvoice-0.1.0/src/pairvoice/config.py +258 -0
  25. pairvoice-0.1.0/src/pairvoice/eval_cases.tsv +26 -0
  26. pairvoice-0.1.0/src/pairvoice/evaluation.py +184 -0
  27. pairvoice-0.1.0/src/pairvoice/generations.py +47 -0
  28. pairvoice-0.1.0/src/pairvoice/install.py +171 -0
  29. pairvoice-0.1.0/src/pairvoice/launchd.py +25 -0
  30. pairvoice-0.1.0/src/pairvoice/lifecycle.py +475 -0
  31. pairvoice-0.1.0/src/pairvoice/llm.py +98 -0
  32. pairvoice-0.1.0/src/pairvoice/logs.py +86 -0
  33. pairvoice-0.1.0/src/pairvoice/menubar.py +357 -0
  34. pairvoice-0.1.0/src/pairvoice/mute.py +163 -0
  35. pairvoice-0.1.0/src/pairvoice/player.py +175 -0
  36. pairvoice-0.1.0/src/pairvoice/postprocess.py +117 -0
  37. pairvoice-0.1.0/src/pairvoice/profiles.py +114 -0
  38. pairvoice-0.1.0/src/pairvoice/server.py +219 -0
  39. pairvoice-0.1.0/src/pairvoice/studio_process.py +128 -0
  40. pairvoice-0.1.0/src/pairvoice/tts.py +274 -0
  41. pairvoice-0.1.0/studio/.oxfmtrc.json +8 -0
  42. pairvoice-0.1.0/studio/.oxlintrc.json +64 -0
  43. pairvoice-0.1.0/studio/components.json +25 -0
  44. pairvoice-0.1.0/studio/package.json +53 -0
  45. pairvoice-0.1.0/studio/pnpm-lock.yaml +5199 -0
  46. pairvoice-0.1.0/studio/pnpm-workspace.yaml +3 -0
  47. pairvoice-0.1.0/studio/server/app.ts +68 -0
  48. pairvoice-0.1.0/studio/server/history.ts +122 -0
  49. pairvoice-0.1.0/studio/server/http.ts +124 -0
  50. pairvoice-0.1.0/studio/server/paths.ts +30 -0
  51. pairvoice-0.1.0/studio/server/router.ts +80 -0
  52. pairvoice-0.1.0/studio/server/routes/corpus.ts +189 -0
  53. pairvoice-0.1.0/studio/server/routes/dict.ts +96 -0
  54. pairvoice-0.1.0/studio/server/routes/pairvoice.ts +153 -0
  55. pairvoice-0.1.0/studio/server/routes/profiles.ts +299 -0
  56. pairvoice-0.1.0/studio/server/routes/prompt.ts +45 -0
  57. pairvoice-0.1.0/studio/server/static.ts +51 -0
  58. pairvoice-0.1.0/studio/server/storage.ts +89 -0
  59. pairvoice-0.1.0/studio/server.test.ts +1844 -0
  60. pairvoice-0.1.0/studio/server.ts +49 -0
  61. pairvoice-0.1.0/studio/shared/api-types.ts +125 -0
  62. pairvoice-0.1.0/studio/tsconfig.json +22 -0
  63. pairvoice-0.1.0/studio/web/dist/assets/index-CpU73UpB.css +2 -0
  64. pairvoice-0.1.0/studio/web/dist/assets/index-Cyj45oOs.js +11 -0
  65. pairvoice-0.1.0/studio/web/dist/index.html +13 -0
  66. pairvoice-0.1.0/studio/web/index.html +12 -0
  67. pairvoice-0.1.0/studio/web/src/components/AppSidebar.test.tsx +106 -0
  68. pairvoice-0.1.0/studio/web/src/components/AppSidebar.tsx +59 -0
  69. pairvoice-0.1.0/studio/web/src/components/CommandMenu.test.tsx +144 -0
  70. pairvoice-0.1.0/studio/web/src/components/CommandMenu.tsx +153 -0
  71. pairvoice-0.1.0/studio/web/src/components/ConfirmButton.test.tsx +40 -0
  72. pairvoice-0.1.0/studio/web/src/components/ConfirmButton.tsx +64 -0
  73. pairvoice-0.1.0/studio/web/src/components/EmptyState.tsx +9 -0
  74. pairvoice-0.1.0/studio/web/src/components/PageHeader.tsx +19 -0
  75. pairvoice-0.1.0/studio/web/src/components/StatusText.tsx +20 -0
  76. pairvoice-0.1.0/studio/web/src/components/commands.test.ts +23 -0
  77. pairvoice-0.1.0/studio/web/src/components/commands.ts +14 -0
  78. pairvoice-0.1.0/studio/web/src/components/nav.ts +14 -0
  79. pairvoice-0.1.0/studio/web/src/components/ui/badge.tsx +48 -0
  80. pairvoice-0.1.0/studio/web/src/components/ui/button.tsx +57 -0
  81. pairvoice-0.1.0/studio/web/src/components/ui/dialog.tsx +138 -0
  82. pairvoice-0.1.0/studio/web/src/components/ui/dropdown-menu.tsx +257 -0
  83. pairvoice-0.1.0/studio/web/src/components/ui/input.tsx +19 -0
  84. pairvoice-0.1.0/studio/web/src/components/ui/kbd.tsx +26 -0
  85. pairvoice-0.1.0/studio/web/src/components/ui/popover.tsx +74 -0
  86. pairvoice-0.1.0/studio/web/src/components/ui/separator.tsx +18 -0
  87. pairvoice-0.1.0/studio/web/src/components/ui/sheet.tsx +123 -0
  88. pairvoice-0.1.0/studio/web/src/components/ui/tabs.tsx +72 -0
  89. pairvoice-0.1.0/studio/web/src/components/ui/textarea.tsx +17 -0
  90. pairvoice-0.1.0/studio/web/src/components/ui/tooltip.tsx +53 -0
  91. pairvoice-0.1.0/studio/web/src/features/dict/DictRow.tsx +68 -0
  92. pairvoice-0.1.0/studio/web/src/features/dict/DictTable.test.tsx +266 -0
  93. pairvoice-0.1.0/studio/web/src/features/dict/DictTable.tsx +203 -0
  94. pairvoice-0.1.0/studio/web/src/features/dict/queries.ts +12 -0
  95. pairvoice-0.1.0/studio/web/src/features/dict/upsertDictRow.test.ts +23 -0
  96. pairvoice-0.1.0/studio/web/src/features/dict/upsertDictRow.ts +13 -0
  97. pairvoice-0.1.0/studio/web/src/features/health/MuteMenu.tsx +52 -0
  98. pairvoice-0.1.0/studio/web/src/features/health/StatusFooter.tsx +27 -0
  99. pairvoice-0.1.0/studio/web/src/features/health/pairvoiceStatus.test.ts +132 -0
  100. pairvoice-0.1.0/studio/web/src/features/health/pairvoiceStatus.ts +38 -0
  101. pairvoice-0.1.0/studio/web/src/features/health/queries.ts +14 -0
  102. pairvoice-0.1.0/studio/web/src/features/health/useMute.test.tsx +58 -0
  103. pairvoice-0.1.0/studio/web/src/features/health/useMute.ts +34 -0
  104. pairvoice-0.1.0/studio/web/src/features/history/HistoryList.test.tsx +81 -0
  105. pairvoice-0.1.0/studio/web/src/features/history/HistoryList.tsx +67 -0
  106. pairvoice-0.1.0/studio/web/src/features/history/queries.ts +19 -0
  107. pairvoice-0.1.0/studio/web/src/features/profiles/CaptionHistorySheet.tsx +48 -0
  108. pairvoice-0.1.0/studio/web/src/features/profiles/ConditionsPanel.tsx +139 -0
  109. pairvoice-0.1.0/studio/web/src/features/profiles/DesignProfile.test.tsx +78 -0
  110. pairvoice-0.1.0/studio/web/src/features/profiles/DesignProfile.tsx +220 -0
  111. pairvoice-0.1.0/studio/web/src/features/profiles/EmojiPalette.test.tsx +81 -0
  112. pairvoice-0.1.0/studio/web/src/features/profiles/EmojiPalette.tsx +24 -0
  113. pairvoice-0.1.0/studio/web/src/features/profiles/Field.tsx +16 -0
  114. pairvoice-0.1.0/studio/web/src/features/profiles/NewProfile.tsx +34 -0
  115. pairvoice-0.1.0/studio/web/src/features/profiles/ProfileCard.test.tsx +127 -0
  116. pairvoice-0.1.0/studio/web/src/features/profiles/ProfileCard.tsx +143 -0
  117. pairvoice-0.1.0/studio/web/src/features/profiles/ProfileList.test.tsx +46 -0
  118. pairvoice-0.1.0/studio/web/src/features/profiles/ProfileList.tsx +63 -0
  119. pairvoice-0.1.0/studio/web/src/features/profiles/ProfilesPage.tsx +107 -0
  120. pairvoice-0.1.0/studio/web/src/features/profiles/SamplerFields.test.tsx +76 -0
  121. pairvoice-0.1.0/studio/web/src/features/profiles/SamplerFields.tsx +114 -0
  122. pairvoice-0.1.0/studio/web/src/features/profiles/TakeCard.test.tsx +115 -0
  123. pairvoice-0.1.0/studio/web/src/features/profiles/TakeCard.tsx +136 -0
  124. pairvoice-0.1.0/studio/web/src/features/profiles/UploadProfile.tsx +102 -0
  125. pairvoice-0.1.0/studio/web/src/features/profiles/VoiceWorkbench.test.tsx +210 -0
  126. pairvoice-0.1.0/studio/web/src/features/profiles/VoiceWorkbench.tsx +166 -0
  127. pairvoice-0.1.0/studio/web/src/features/profiles/emojiAnnotations.ts +69 -0
  128. pairvoice-0.1.0/studio/web/src/features/profiles/pairvoiceOutdated.test.ts +30 -0
  129. pairvoice-0.1.0/studio/web/src/features/profiles/pairvoiceOutdated.ts +13 -0
  130. pairvoice-0.1.0/studio/web/src/features/profiles/queries.ts +32 -0
  131. pairvoice-0.1.0/studio/web/src/features/profiles/samplerKnobs.test.ts +103 -0
  132. pairvoice-0.1.0/studio/web/src/features/profiles/samplerKnobs.ts +256 -0
  133. pairvoice-0.1.0/studio/web/src/features/profiles/tomlSnippet.test.ts +40 -0
  134. pairvoice-0.1.0/studio/web/src/features/profiles/tomlSnippet.ts +32 -0
  135. pairvoice-0.1.0/studio/web/src/features/profiles/useEmojiInsert.test.tsx +80 -0
  136. pairvoice-0.1.0/studio/web/src/features/profiles/useEmojiInsert.ts +38 -0
  137. pairvoice-0.1.0/studio/web/src/features/profiles/useTakes.test.tsx +212 -0
  138. pairvoice-0.1.0/studio/web/src/features/profiles/useTakes.ts +184 -0
  139. pairvoice-0.1.0/studio/web/src/features/prompt/PromptEditor.test.tsx +81 -0
  140. pairvoice-0.1.0/studio/web/src/features/prompt/PromptEditor.tsx +105 -0
  141. pairvoice-0.1.0/studio/web/src/features/prompt/queries.ts +12 -0
  142. pairvoice-0.1.0/studio/web/src/features/review/AddToDict.test.tsx +111 -0
  143. pairvoice-0.1.0/studio/web/src/features/review/AddToDict.tsx +158 -0
  144. pairvoice-0.1.0/studio/web/src/features/review/ReviewRow.test.tsx +195 -0
  145. pairvoice-0.1.0/studio/web/src/features/review/ReviewRow.tsx +223 -0
  146. pairvoice-0.1.0/studio/web/src/features/review/formatWhen.test.ts +15 -0
  147. pairvoice-0.1.0/studio/web/src/features/review/formatWhen.ts +11 -0
  148. pairvoice-0.1.0/studio/web/src/features/review/queries.test.ts +97 -0
  149. pairvoice-0.1.0/studio/web/src/features/review/queries.ts +58 -0
  150. pairvoice-0.1.0/studio/web/src/features/review/reviewCounts.test.ts +128 -0
  151. pairvoice-0.1.0/studio/web/src/features/review/reviewCounts.ts +56 -0
  152. pairvoice-0.1.0/studio/web/src/features/review/searchState.test.ts +19 -0
  153. pairvoice-0.1.0/studio/web/src/features/review/searchState.ts +28 -0
  154. pairvoice-0.1.0/studio/web/src/features/review/selection.test.ts +48 -0
  155. pairvoice-0.1.0/studio/web/src/features/review/selection.ts +30 -0
  156. pairvoice-0.1.0/studio/web/src/features/review/useAddDictEntry.test.tsx +52 -0
  157. pairvoice-0.1.0/studio/web/src/features/review/useAddDictEntry.ts +21 -0
  158. pairvoice-0.1.0/studio/web/src/features/review/usePlayer.test.tsx +106 -0
  159. pairvoice-0.1.0/studio/web/src/features/review/usePlayer.ts +60 -0
  160. pairvoice-0.1.0/studio/web/src/features/review/useReviewActions.test.tsx +230 -0
  161. pairvoice-0.1.0/studio/web/src/features/review/useReviewActions.ts +127 -0
  162. pairvoice-0.1.0/studio/web/src/index.css +130 -0
  163. pairvoice-0.1.0/studio/web/src/lib/api.test.ts +157 -0
  164. pairvoice-0.1.0/studio/web/src/lib/api.ts +165 -0
  165. pairvoice-0.1.0/studio/web/src/lib/hotkeys.test.tsx +131 -0
  166. pairvoice-0.1.0/studio/web/src/lib/hotkeys.ts +83 -0
  167. pairvoice-0.1.0/studio/web/src/lib/ids.test.ts +9 -0
  168. pairvoice-0.1.0/studio/web/src/lib/ids.ts +5 -0
  169. pairvoice-0.1.0/studio/web/src/lib/speak.ts +36 -0
  170. pairvoice-0.1.0/studio/web/src/lib/useDraft.test.tsx +22 -0
  171. pairvoice-0.1.0/studio/web/src/lib/useDraft.ts +17 -0
  172. pairvoice-0.1.0/studio/web/src/lib/useTransientStatus.ts +19 -0
  173. pairvoice-0.1.0/studio/web/src/main.tsx +24 -0
  174. pairvoice-0.1.0/studio/web/src/router.tsx +54 -0
  175. pairvoice-0.1.0/studio/web/src/routes/__root.tsx +49 -0
  176. pairvoice-0.1.0/studio/web/src/routes/dict.tsx +11 -0
  177. pairvoice-0.1.0/studio/web/src/routes/profiles.tsx +14 -0
  178. pairvoice-0.1.0/studio/web/src/routes/prompt.tsx +11 -0
  179. pairvoice-0.1.0/studio/web/src/routes/redirects.test.tsx +63 -0
  180. pairvoice-0.1.0/studio/web/src/routes/review.test.tsx +251 -0
  181. pairvoice-0.1.0/studio/web/src/routes/review.tsx +270 -0
  182. pairvoice-0.1.0/studio/web/tsconfig.json +19 -0
  183. pairvoice-0.1.0/studio/web/vite.config.ts +37 -0
  184. pairvoice-0.1.0/tests/fakes.py +74 -0
  185. pairvoice-0.1.0/tests/test_audio_state.py +52 -0
  186. pairvoice-0.1.0/tests/test_bundle.py +19 -0
  187. pairvoice-0.1.0/tests/test_checker.py +131 -0
  188. pairvoice-0.1.0/tests/test_cli.py +512 -0
  189. pairvoice-0.1.0/tests/test_config.py +337 -0
  190. pairvoice-0.1.0/tests/test_engine.py +519 -0
  191. pairvoice-0.1.0/tests/test_evaluation.py +210 -0
  192. pairvoice-0.1.0/tests/test_generations.py +76 -0
  193. pairvoice-0.1.0/tests/test_install.py +244 -0
  194. pairvoice-0.1.0/tests/test_launchd.py +16 -0
  195. pairvoice-0.1.0/tests/test_lifecycle.py +429 -0
  196. pairvoice-0.1.0/tests/test_llm.py +145 -0
  197. pairvoice-0.1.0/tests/test_logs.py +96 -0
  198. pairvoice-0.1.0/tests/test_menubar.py +415 -0
  199. pairvoice-0.1.0/tests/test_mute.py +278 -0
  200. pairvoice-0.1.0/tests/test_packaging.py +33 -0
  201. pairvoice-0.1.0/tests/test_player.py +207 -0
  202. pairvoice-0.1.0/tests/test_postprocess.py +114 -0
  203. pairvoice-0.1.0/tests/test_profiles.py +108 -0
  204. pairvoice-0.1.0/tests/test_server.py +405 -0
  205. pairvoice-0.1.0/tests/test_studio_process.py +210 -0
  206. pairvoice-0.1.0/tests/test_tts.py +517 -0
  207. pairvoice-0.1.0/uv.lock +1500 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "pairvoice",
3
+ "description": "pairvoice(ローカル要約・音声合成サーバー)と組み合わせて使う Claude Code プラグイン",
4
+ "owner": {
5
+ "name": "matzkoh"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "pairvoice",
10
+ "source": "./plugin",
11
+ "description": "Claude Code の応答をローカルの pairvoice で要約・音声化して読み上げる"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,41 @@
1
+ # v* のタグを push すると、studio をビルドしてから wheel と sdist を作り、PyPI に公開する。
2
+ # PyPI 側には Trusted Publishing(matzkoh/pairvoice・release.yml・environment pypi)を登録してあり、
3
+ # トークンは置かない
4
+ name: release
5
+
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ permissions:
15
+ contents: read
16
+ id-token: write
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
20
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
21
+ with:
22
+ package_json_file: studio/package.json
23
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
24
+ with:
25
+ node-version: "24"
26
+ cache: pnpm
27
+ cache-dependency-path: studio/pnpm-lock.yaml
28
+ # タグと pyproject の版がずれたまま公開すると、PyPI の版は取り消せない
29
+ - name: タグと版がそろっているか確かめる
30
+ run: |
31
+ version=$(uv version --short)
32
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
33
+ echo "タグ $GITHUB_REF_NAME と pyproject の版 $version が違う" >&2
34
+ exit 1
35
+ fi
36
+ - run: pnpm install --frozen-lockfile
37
+ working-directory: studio
38
+ - run: pnpm build
39
+ working-directory: studio
40
+ - run: uv build
41
+ - run: uv publish
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .claude/worktrees/
6
+ studio/node_modules/
7
+ studio/web/node_modules/
8
+ studio/web/dist/
9
+ studio/web/.vitest/
10
+ .superpowers/
@@ -0,0 +1,21 @@
1
+ # CLAUDE.md
2
+
3
+ 常駐サーバー(`src/pairvoice/`)・studio(`studio/`)・読み上げフック(`plugin/`)・メニューバー(常駐サーバーの子)の4つは、データの置き場所と HTTP だけを共有する。
4
+
5
+ ## 横断する規則
6
+
7
+ - 実行して生まれるもの(プロンプト・辞書・コーパス・声のプロファイル・レビュー)は `PAIRVOICE_DATA_ROOT` に置き、作業ツリーや同梱物には置かない。フックは実行時に `prompt.txt` を読むのでブランチの切り替えで読み上げが変わり、wheel の同梱物は更新で消える
8
+ - フックは `plugin/` の中だけで完結させる。プラグインはインストール時に `plugin/` だけがキャッシュへ写される
9
+ - MLX の処理は `Runner` のキューを通し、専用の1本のスレッドで実行する。別のスレッドで生成すると `There is no Stream(gpu, N)` で落ちる
10
+ - Host / Origin のループバック判定は `studio/server/app.ts` と `server.is_local_request()` の2か所にあり、そろえておく
11
+ - 試聴は保存済みの caption を書き換えず、`/speak` の引数で渡す(書き換えると、その最中のフックの読み上げが試行中の声で鳴る)。複数候補は seed だけ変えて1件ずつ叩く(まとめると `Runner` を占有し、フックの読み上げが待たされる)
12
+ - 要約の改善はプラグインの `tune` スキルが利用者のエージェントで行う。pairvoice は測る道具とデータだけを持つ
13
+
14
+ ## studio
15
+
16
+ - studio は常駐サーバーから切り離して起動するので、コードを変えたら studio も再起動する(常駐サーバーの再起動では入れ替わらない)
17
+ - `server.ts` は Node の標準ライブラリだけで動かし、フロントの依存はすべて `devDependencies` に入れる(`shadcn add` は `dependencies` に入れるので移す)
18
+ - 公開用の wheel はビルド済みの `web/dist` を同梱するので、`(cd studio && pnpm build) && uv build` の順で作る
19
+ - テストで `PAIRVOICE_DATA_ROOT` を差し替えるときは、`server.ts` の import 前か別プロセスで行う(読み込み時に定数を確定する。`PAIRVOICE_URL` は呼ぶたびに読む)
20
+ - 画面の項目は `web/src/components/nav.ts`、キーボード操作は `useHotkeys`(`web/src/lib/hotkeys.ts`)を通す
21
+ - `web/src/components/ui/` は shadcn/ui の生成物で、`studio/` で `pnpm exec shadcn add <name>` して足す
@@ -0,0 +1,119 @@
1
+ # pairvoice の開発
2
+
3
+ 使う側の説明は [README](README.md)。開発の決まりごと(不変条件と落とし穴)は [CLAUDE.md](CLAUDE.md) にある。
4
+
5
+ ## 構成
6
+
7
+ | 部分 | 場所 | 中身 |
8
+ | --- | --- | --- |
9
+ | 常駐サーバー | `src/pairvoice/` | Python / FastAPI。`127.0.0.1:17495`。要約(mlx-lm)と音声合成(mlx-audio / Irodori-TTS) |
10
+ | studio | `studio/` | `server.ts`(Node の標準ライブラリだけ、`127.0.0.1:17494`)と React 19 + Vite のフロント(`web/`) |
11
+ | 読み上げフック | `plugin/` | Claude Code プラグイン(フックと、要約を改善する `tune` スキル)。マーケットプレイスはリポジトリ直下の `.claude-plugin/marketplace.json` |
12
+ | メニューバー | `src/pairvoice/menubar.py` | 常駐サーバーの子として起きる |
13
+
14
+ ## 手元で動かす
15
+
16
+ 常駐は、利用者と同じ形(uv tool)で入れた wheel で動かす。
17
+ wheel でしか起きない壊れ方(同梱物の漏れ、依存の抜け)に、ふだん使いの中で気づけるようにするためである。
18
+
19
+ ```bash
20
+ uv sync
21
+ (cd studio && pnpm install)
22
+ rm -rf dist && (cd studio && pnpm build) && uv build --wheel
23
+ uv tool install --reinstall dist/pairvoice-*.whl
24
+ pairvoice install # 初回だけ。LaunchAgent を tool 側の Python に向ける
25
+ ```
26
+
27
+ LaunchAgent は tool 側の Python を指すので、リポジトリのコードを変えても常駐サーバーには効かない。
28
+ 反映するときは、wheel のビルドと `uv tool install --reinstall` をやり直す。
29
+ 入れ直したあとの立て直し方は、変えた場所で選ぶ。
30
+ サーバーを立て直すとモデルを読み込み直すので、要らないときは避ける。
31
+
32
+ | 変えた場所 | 立て直すもの |
33
+ | --- | --- |
34
+ | `studio/` だけ | `pairvoice studio --restart`(studio だけ。モデルは載ったまま) |
35
+ | `src/pairvoice/` | メニューの「再起動」(studio とサーバーの両方。`pairvoice restart` はサーバーだけで、studio は古いコードのまま残る) |
36
+ | `prompt.txt`、辞書 | 要らない(フックが読み上げのたびに読む) |
37
+
38
+ 作業ツリーのコードをその場で動かしたいときは、メニューの「pairvoice を終了」で常駐を止めてから手で起動する(ポート `:17495` を取り合うので同時には動かせない)。
39
+
40
+ ```bash
41
+ uv run pairvoice serve # Ctrl-C でメニューバーごと降りる
42
+ launchctl kickstart gui/$(id -u)/local.pairvoice # 常駐に戻す
43
+
44
+ # studio のフロントを開発する(:17493。/api は :17494 へ proxy する)
45
+ node studio/server.ts --port 17494 &
46
+ (cd studio && pnpm dev)
47
+ ```
48
+
49
+ プラグインは、clone したディレクトリをそのままマーケットプレイスとして入れられる
50
+ (`claude plugin marketplace add <clone のパス>`)。この場合キャッシュに写されず作業ツリーから読まれるので、
51
+ ブランチを切り替えるとふだんの読み上げのフックも変わる。
52
+
53
+ ## テスト
54
+
55
+ ```bash
56
+ uv run pytest # 実モデルは載らない
57
+ uv run ruff check && uv run ruff format --check && uv run ty check
58
+ cd studio && node --test server.test.ts
59
+ cd studio && pnpm test:web && pnpm typecheck && pnpm lint && pnpm format:check
60
+ ```
61
+
62
+ studio のテストのうち1本は、`plugin/hooks/speak-summary.sh` とデータの置き場所の `dict.tsv` を読んで、
63
+ 辞書置換の bash 実装と JS 実装が一致することを確かめる(`dict.tsv` が無ければ skip)。
64
+
65
+ ## 公開用のビルド
66
+
67
+ ```bash
68
+ (cd studio && pnpm build) && uv build
69
+ ```
70
+
71
+ wheel には studio(ビルド済みの `web/dist` を含む)・`examples/` を同梱する(`pyproject.toml` の
72
+ `force-include`)。`web/dist` は git で追跡しないので、ビルドを忘れると wheel のビルドが失敗する。
73
+
74
+ ## 要約の評価(`pairvoice eval`)
75
+
76
+ ```bash
77
+ pairvoice eval # 使用中の prompt.txt を同梱のケースで評価
78
+ pairvoice eval --prompt 候補.txt --reviews --out 結果.jsonl
79
+ pairvoice eval --model mlx-community/<モデル> # 別のモデルをこのプロセスに読み込んで評価
80
+ ```
81
+
82
+ `src/pairvoice/checker.py` が次の7つの規則で判定し、`evaluation.py` が集計する。同梱のケースは
83
+ `src/pairvoice/eval_cases.tsv`。`--reviews` は studio のレビュー(アーカイブしたものを除く)をケースに加える。
84
+
85
+ 1. 句点・感嘆符・疑問符が1〜2個(二文まで)
86
+ 2. コード識別子(ファイル名・関数名・パスなど)を含まない
87
+ 3. 命令・急かし口調を含まない
88
+ 4. 名詞・形容詞に直接「ね」を付けて終わらない
89
+ 5. 10〜45文字程度で、絵文字を含まない
90
+ 6. 一人称(俺・僕など)を含まない
91
+ 7. です・ます調の文末を含まない(`config.toml` の `[eval] style = "casual"` のときだけ判定する)
92
+
93
+ 改善の作業は利用者のエージェントがプラグインの `tune` スキルに沿って行い、pairvoice は測る道具だけを持つ。
94
+
95
+ ## データの置き場所を変える
96
+
97
+ `PAIRVOICE_DATA_ROOT` で置き場を変えられる。シェルに設定してから `pairvoice install` を実行し直す
98
+ (launchd から起きる常駐サーバーにはシェルの環境変数が届かないので、LaunchAgent に焼き込む)。
99
+ `config.toml` で `tts.output_dir` を明示している場合は、その親もデータの置き場所に揃える(ずれていると
100
+ `install` が警告する。片方だけだと studio の再生が黙って失敗する)。
101
+
102
+ ## 詳しい状態の見方
103
+
104
+ `pairvoice status` は常駐サーバーの `/health` をそのまま表示する。
105
+
106
+ | 項目 | 意味 |
107
+ | --- | --- |
108
+ | `llm.state` / `tts.state` | `unloaded` / `downloading` / `loading` / `loaded` / `failed` / `misconfigured` |
109
+ | `tts.profile` | 使用中の声のプロファイル(まだ一度も読み上げていなければ `null`) |
110
+ | `mute` | ミュート中か、その理由(`manual` / `microphone` / `audio_output`)と期限 |
111
+ | `playback` | 鳴っているか(`playing`)と、順番待ちの数(`waiting`) |
112
+ | `dropped_recent` | 直近で後続に追い越されて捨てた要約の数。3件以上ならキューが詰まっている |
113
+ | `config_stale` | 設定ファイルが起動後に変わった(`pairvoice restart` が要る) |
114
+
115
+ ログは、常駐サーバーが `~/Library/Logs/pairvoice.log`、フックが `~/Library/Logs/speak-summary.log`。
116
+ 常駐サーバーのログは `serve --log-file` が 10MB × 3世代で回しながら書き、成功した `/health` は書かない。
117
+ メニューバーの警告は `~/Library/Logs/pairvoice-menubar.log` に同じく回しながら書く。
118
+ logging を通らない出力(落ちたときの Traceback、ネイティブ層の出力)は、launchd が `~/Library/Logs/pairvoice.stderr.log` に書く。
119
+ フックのログの `SKIP (...)` の理由は [plugin/README.md](plugin/README.md) にまとめてある。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 matzkoh
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,221 @@
1
+ Metadata-Version: 2.5
2
+ Name: pairvoice
3
+ Version: 0.1.0
4
+ Summary: Claude Code の読み上げ用に、要約と音声合成を担うローカル常駐サーバー
5
+ Project-URL: Repository, https://github.com/matzkoh/pairvoice
6
+ Project-URL: Issues, https://github.com/matzkoh/pairvoice/issues
7
+ Author: matzkoh
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude-code,japanese,mlx,tts
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: MacOS X
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Natural Language :: Japanese
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: fastapi<1,>=0.141
21
+ Requires-Dist: mlx-audio==0.5.7
22
+ Requires-Dist: mlx-lm<0.33,>=0.32.0
23
+ Requires-Dist: pyobjc-framework-avfoundation<13,>=12
24
+ Requires-Dist: pyobjc-framework-cocoa<13,>=12
25
+ Requires-Dist: uvicorn<1,>=0.54
26
+ Description-Content-Type: text/markdown
27
+
28
+ # pairvoice
29
+
30
+ **Claude Code の返事を、ひと言の日本語にして読み上げる。**
31
+
32
+ Claude Code に長い作業を任せて別の画面を見ていると、とうに終わって入力を待っていたことに、あとから気づく。
33
+ 返事をそのまま読み上げれば済みそうに思える。
34
+ ところが返事にはコミットのハッシュやテストの件数が混ざっていて、耳で聞いても何が起きたのか分からない。
35
+
36
+ > 入力: Task 2 実装完了(`45f2238`、135 passed)。レビュアーが走っている。<br>
37
+ > 読み上げ: 「実装が終わり、テストはすべて通っています。いまレビューを待っています。」
38
+
39
+ pairvoice は、返事をこのように2文ほどの日本語に縮めてから読む。
40
+ 画面から目を離していても、いま何が終わって次に何をするのかが耳で分かる。
41
+
42
+ ## できること
43
+
44
+ - **識別子を読まない。** ファイル名や関数名はそのまま読まず、何をしたのかに言い換える
45
+ - **手元だけで動く。** 要約(mlx-lm)と音声合成(Irodori-TTS)を Apple Silicon の上で動かし、返事の中身を外に送らない。その分メモリを使う(下の「動作要件」を参照)
46
+ - **会議中や動画・音楽の再生中は黙る。** マイクが使われている間と、ほかのアプリが音を出している間は自動でミュートする。メニューバーから時間を区切ってミュートもできる
47
+ - **声を選べる。** 声の描写(「落ち着いた低い声。ゆっくり話す。」など)から声を作るか、手持ちの音声を取り込んで使う
48
+ - **読み方を育てられる。** ブラウザの画面(studio)で、読み上げの履歴に 👍👎 を付け、要約の指示や読み替えの辞書を直せる
49
+
50
+ 日本語話者向けのツールで、要約の指示も読み辞書も音声合成のモデルも、日本語を前提にしている。
51
+
52
+ ## しくみ
53
+
54
+ ```
55
+ Claude Code ──(プラグインのフック)──▶ 常駐サーバー ──▶ 要約 ──▶ 音声合成 ──▶ 再生
56
+ │
57
+ └─ studio(ブラウザ)で指示と辞書と声を育てる
58
+ ```
59
+
60
+ Claude Code に入れるプラグインが返事を受け取り、手元で常駐するサーバーに要約と音声合成を頼んで再生する。
61
+ サーバーは `127.0.0.1` にだけ待ち受ける。
62
+
63
+ ## はじめかた
64
+
65
+ **1. 必要なものをそろえる**
66
+
67
+ - Apple Silicon の Mac(ユニファイドメモリ 32GB 以上を推奨。下の「動作要件」を参照)
68
+ - [uv](https://docs.astral.sh/uv/)、Node 24.13 以降、`jq`(`brew install jq`)
69
+
70
+ **2. 入れる**
71
+
72
+ ```bash
73
+ uv tool install pairvoice
74
+ pairvoice install
75
+ ```
76
+
77
+ `pairvoice install` は、常駐サーバーをログイン時に起きるよう登録し、要約の指示と読み辞書の雛形を置く。
78
+ メニューバーにアイコンが出る。
79
+
80
+ **3. Claude Code にプラグインを入れて、Claude Code を起動し直す**
81
+
82
+ ```
83
+ /plugin marketplace add matzkoh/pairvoice
84
+ /plugin install pairvoice@pairvoice
85
+ ```
86
+
87
+ 最初の読み上げは、モデルのダウンロード(約 15GB)と読み込みで時間がかかる。
88
+ 進み具合は `pairvoice status` で見られる。
89
+
90
+ ## 動作要件
91
+
92
+ - macOS(Apple Silicon)
93
+ - メモリ: 既定のモデル(要約 `gemma-4-e4b-it-8bit`、音声合成 `Irodori-TTS-v4.1-Small-8bit`)を両方載せると、常駐で約 9.5GB、生成中のピークで約 12GB を使う(48GB の機種で実測)。
94
+ どちらも10分使わなければ自動で解放する。
95
+ メモリが少ないときは、下の「設定」で要約のモデルを小さくできる
96
+ - モデルは同梱しない。初回に Hugging Face から手元へダウンロードする。各モデルのライセンスはそれぞれの配布元に従う
97
+ - 要約: [Gemma 4](https://huggingface.co/google/gemma-4-E4B-it)([Gemma Terms of Use](https://ai.google.dev/gemma/terms))
98
+ - 音声合成: [Irodori-TTS](https://huggingface.co/Aratako/Irodori-TTS-v4.1-Small)(MIT)
99
+
100
+ ## 読み上げた内容の扱い
101
+
102
+ 返事の中身は外に出ないが、手元には残る。
103
+
104
+ - 返事の全文と要約は、手元のデータの置き場所(`~/Library/Application Support/pairvoice/`)の `corpus.jsonl` に書きためる。
105
+ studio のレビューと要約の指示の改善に使う。
106
+ 作業の中身がそのまま入るので、要らなくなったら消す
107
+ - 外と通信するのは、モデルの初回ダウンロードだけ
108
+
109
+ ## 声を変える
110
+
111
+ 読み上げの声は**プロファイル**(参照音声と声の描写の組)で決まる。
112
+ studio の「プロファイル」画面で、
113
+
114
+ - 声の描写から候補をいくつか合成し、気に入ったものを選んで保存する
115
+ - 手持ちの wav(数秒〜十数秒)を取り込む
116
+
117
+ のどちらかで作り、「使う」で切り替える。
118
+ 何も作らなくても、最初の読み上げのときに既定の声が1つ作られる。
119
+ 切り替えは次の読み上げから効き、再起動は要らない。
120
+
121
+ ## 読み方を育てる
122
+
123
+ 要約は、いつも思いどおりに縮まるとは限らない。
124
+ 識別子を読み上げてしまったり、知りたかった結果を落としたりすることがある。
125
+ 読み上げの履歴に印を付けておくと、それを材料に要約の指示と辞書を直せる。
126
+
127
+ studio はメニューの「studio を開く」か `pairvoice studio` で起動する(`127.0.0.1:17494` だけで待ち受ける)。
128
+
129
+ | 画面 | できること |
130
+ | --- | --- |
131
+ | レビュー | 読み上げの履歴を聞き直し、👍👎 と「こう読んでほしかった」を残す |
132
+ | プロンプト | 要約の指示(`prompt.txt`)を直す。前の版に戻せる |
133
+ | 辞書 | 読み間違える言葉を、読み方に置き換える |
134
+ | 声 | 声の描写とサンプラーを変えて試聴し、気に入った描写を使用中の声に当てる |
135
+ | プロファイル | 声を作る、取り込む、切り替える |
136
+
137
+ 要約の指示の改善は、Claude Code で `/pairvoice:tune` を実行して頼む。
138
+ レビューを材料に改善案を作り、`pairvoice eval` で今の指示と比べてから提案する。当てるかは人が決める。
139
+
140
+ ## 使い方
141
+
142
+ メニューバーのアイコンから、読み上げを止める、ミュート(15分、30分、60分)、studio を開く、再起動(サーバーと studio)、終了ができる。
143
+ 同じことはコマンドでもできる。
144
+
145
+ | コマンド | すること |
146
+ | --- | --- |
147
+ | `pairvoice status` | 状態(モデル、ミュート、声)を表示する |
148
+ | `pairvoice mute 30m` / `unmute` | 30分ミュートする(1〜480分)/解除する |
149
+ | `pairvoice say "テスト"` | 読み上げてみる(ミュート中でも鳴る) |
150
+ | `pairvoice stop` | 鳴っている読み上げと、順番待ちの読み上げを止める |
151
+ | `pairvoice warmup` | モデルを先に読み込んでおく |
152
+ | `pairvoice restart` | 常駐サーバーを再起動する(設定を変えたら要る) |
153
+ | `pairvoice studio` | studio を開く(`--restart` で studio だけを立て直す) |
154
+ | `pairvoice eval` | 要約の指示やモデルを規則で採点する(`--reviews` でレビューも使う、`--model` で別のモデルを試す) |
155
+
156
+ 「終了」で止めたサーバーは、ログインし直すか `launchctl kickstart gui/$(id -u)/local.pairvoice` で戻る。
157
+
158
+ ## ミュート
159
+
160
+ 手動のミュート(期限付き)と、自動の条件のどちらかが当てはまれば黙る。
161
+ 自動の条件は、マイクの使用中(会議アプリを問わない)と、ほかのアプリが音を出している間(YouTube や Spotify の再生中など)。
162
+ ほかのアプリの音は鳴らす直前まで確かめ、通知音のように5秒以内に止めば、止んだ後に読み上げる。
163
+ 読み上げている最中も見張る。ミュートやマイクが入ったらその場で止め、ほかのアプリが音を出したら音量を下げ、5秒続いたら止める。
164
+
165
+ ## 設定
166
+
167
+ `~/.config/pairvoice/config.toml`。
168
+ 無くても既定値で動く。
169
+ 変えたら `pairvoice restart` する。
170
+
171
+ | 表 | よく触る項目 |
172
+ | --- | --- |
173
+ | `[llm]` | `model`(要約のモデル) |
174
+ | `[tts]` | `model`(音声合成のモデル)、`output_max_age_days`(合成した wav を消すまでの日数。既定 7、0 で消さない) |
175
+ | `[tts.profile]` | 最初の声を作るときだけ使う。`ref_audio`(取り込む wav)、`caption`(声の描写) |
176
+ | `[tts.sampler]` | 音声合成の細かい調整。studio の「声」画面で試して、表示される設定を写す |
177
+ | `[mute]` | `auto_microphone`(既定 true)、`auto_audio_output`(既定 true)、`audio_output_wait_seconds`(既定 5) |
178
+ | `[playback]` | `volume`(既定 1.0)、`duck_volume`(ほかのアプリの音が鳴っている間の音量。既定 0.3)、`max_wait_seconds`(順番待ちがこれを超えたら捨てる。既定 60) |
179
+ | `[eval]` | `style = "casual"` で、`pairvoice eval` がです/ます調を違反にする(タメ口に育てるとき) |
180
+
181
+ メモリが少ない機種では、要約のモデルを小さくすると要約側のピークが約 8GB から約 4.7GB に下がる。
182
+ ただしコード識別子をそのまま読み上げやすく、既定のモデルより崩れやすい。
183
+
184
+ ```toml
185
+ [llm]
186
+ model = "mlx-community/gemma-4-e4b-it-4bit"
187
+ ```
188
+
189
+ ## うまく鳴らないとき
190
+
191
+ - **何も鳴らない**: `pairvoice status` を見る
192
+ - `mute.active` が `true` ならミュート中(`mute.reason` が `microphone` なら会議中の自動ミュート)
193
+ - `state` が `downloading` / `loading` ならモデルの準備中。初回は時間がかかる
194
+ - `state` が `failed` / `misconfigured` なら、`~/Library/Logs/pairvoice.log` を見る
195
+ - サーバーが起動しては落ちるなら、落ちたときの出力が `~/Library/Logs/pairvoice.stderr.log` に残る
196
+ - サーバーに届かなければ、メニューの「再起動」かログインし直しで戻す
197
+ - **サーバーは元気なのに鳴らない**: `~/Library/Logs/speak-summary.log` を見る。
198
+ 何も書かれていなければプラグインが読まれていないので、Claude Code を起動し直す。
199
+ 書かれていれば理由が `SKIP (...)` に出る(一覧は [plugin/README.md](https://github.com/matzkoh/pairvoice/blob/main/plugin/README.md))
200
+ - **設定を変えたのに効かない**: `pairvoice restart`
201
+ - **辞書を直したのに効かない**: studio の「保存」を押すまでは書き換わらない
202
+
203
+ ## やめるとき
204
+
205
+ Claude Code で `/plugin uninstall pairvoice@pairvoice`、続けて次を実行する。
206
+
207
+ ```bash
208
+ pairvoice uninstall # 常駐の登録を外す
209
+ uv tool uninstall pairvoice
210
+ ```
211
+
212
+ 要約の指示、辞書、声、読み上げの記録は `~/Library/Application Support/pairvoice/` に残る。
213
+ 要らなければ消す。
214
+
215
+ ## 開発に参加する
216
+
217
+ 手元での動かし方、テスト、評価ハーネスは [DEVELOPMENT.md](https://github.com/matzkoh/pairvoice/blob/main/DEVELOPMENT.md) にある。
218
+
219
+ ## ライセンス
220
+
221
+ [MIT](https://github.com/matzkoh/pairvoice/blob/main/LICENSE)
@@ -0,0 +1,194 @@
1
+ # pairvoice
2
+
3
+ **Claude Code の返事を、ひと言の日本語にして読み上げる。**
4
+
5
+ Claude Code に長い作業を任せて別の画面を見ていると、とうに終わって入力を待っていたことに、あとから気づく。
6
+ 返事をそのまま読み上げれば済みそうに思える。
7
+ ところが返事にはコミットのハッシュやテストの件数が混ざっていて、耳で聞いても何が起きたのか分からない。
8
+
9
+ > 入力: Task 2 実装完了(`45f2238`、135 passed)。レビュアーが走っている。<br>
10
+ > 読み上げ: 「実装が終わり、テストはすべて通っています。いまレビューを待っています。」
11
+
12
+ pairvoice は、返事をこのように2文ほどの日本語に縮めてから読む。
13
+ 画面から目を離していても、いま何が終わって次に何をするのかが耳で分かる。
14
+
15
+ ## できること
16
+
17
+ - **識別子を読まない。** ファイル名や関数名はそのまま読まず、何をしたのかに言い換える
18
+ - **手元だけで動く。** 要約(mlx-lm)と音声合成(Irodori-TTS)を Apple Silicon の上で動かし、返事の中身を外に送らない。その分メモリを使う(下の「動作要件」を参照)
19
+ - **会議中や動画・音楽の再生中は黙る。** マイクが使われている間と、ほかのアプリが音を出している間は自動でミュートする。メニューバーから時間を区切ってミュートもできる
20
+ - **声を選べる。** 声の描写(「落ち着いた低い声。ゆっくり話す。」など)から声を作るか、手持ちの音声を取り込んで使う
21
+ - **読み方を育てられる。** ブラウザの画面(studio)で、読み上げの履歴に 👍👎 を付け、要約の指示や読み替えの辞書を直せる
22
+
23
+ 日本語話者向けのツールで、要約の指示も読み辞書も音声合成のモデルも、日本語を前提にしている。
24
+
25
+ ## しくみ
26
+
27
+ ```
28
+ Claude Code ──(プラグインのフック)──▶ 常駐サーバー ──▶ 要約 ──▶ 音声合成 ──▶ 再生
29
+ │
30
+ └─ studio(ブラウザ)で指示と辞書と声を育てる
31
+ ```
32
+
33
+ Claude Code に入れるプラグインが返事を受け取り、手元で常駐するサーバーに要約と音声合成を頼んで再生する。
34
+ サーバーは `127.0.0.1` にだけ待ち受ける。
35
+
36
+ ## はじめかた
37
+
38
+ **1. 必要なものをそろえる**
39
+
40
+ - Apple Silicon の Mac(ユニファイドメモリ 32GB 以上を推奨。下の「動作要件」を参照)
41
+ - [uv](https://docs.astral.sh/uv/)、Node 24.13 以降、`jq`(`brew install jq`)
42
+
43
+ **2. 入れる**
44
+
45
+ ```bash
46
+ uv tool install pairvoice
47
+ pairvoice install
48
+ ```
49
+
50
+ `pairvoice install` は、常駐サーバーをログイン時に起きるよう登録し、要約の指示と読み辞書の雛形を置く。
51
+ メニューバーにアイコンが出る。
52
+
53
+ **3. Claude Code にプラグインを入れて、Claude Code を起動し直す**
54
+
55
+ ```
56
+ /plugin marketplace add matzkoh/pairvoice
57
+ /plugin install pairvoice@pairvoice
58
+ ```
59
+
60
+ 最初の読み上げは、モデルのダウンロード(約 15GB)と読み込みで時間がかかる。
61
+ 進み具合は `pairvoice status` で見られる。
62
+
63
+ ## 動作要件
64
+
65
+ - macOS(Apple Silicon)
66
+ - メモリ: 既定のモデル(要約 `gemma-4-e4b-it-8bit`、音声合成 `Irodori-TTS-v4.1-Small-8bit`)を両方載せると、常駐で約 9.5GB、生成中のピークで約 12GB を使う(48GB の機種で実測)。
67
+ どちらも10分使わなければ自動で解放する。
68
+ メモリが少ないときは、下の「設定」で要約のモデルを小さくできる
69
+ - モデルは同梱しない。初回に Hugging Face から手元へダウンロードする。各モデルのライセンスはそれぞれの配布元に従う
70
+ - 要約: [Gemma 4](https://huggingface.co/google/gemma-4-E4B-it)([Gemma Terms of Use](https://ai.google.dev/gemma/terms))
71
+ - 音声合成: [Irodori-TTS](https://huggingface.co/Aratako/Irodori-TTS-v4.1-Small)(MIT)
72
+
73
+ ## 読み上げた内容の扱い
74
+
75
+ 返事の中身は外に出ないが、手元には残る。
76
+
77
+ - 返事の全文と要約は、手元のデータの置き場所(`~/Library/Application Support/pairvoice/`)の `corpus.jsonl` に書きためる。
78
+ studio のレビューと要約の指示の改善に使う。
79
+ 作業の中身がそのまま入るので、要らなくなったら消す
80
+ - 外と通信するのは、モデルの初回ダウンロードだけ
81
+
82
+ ## 声を変える
83
+
84
+ 読み上げの声は**プロファイル**(参照音声と声の描写の組)で決まる。
85
+ studio の「プロファイル」画面で、
86
+
87
+ - 声の描写から候補をいくつか合成し、気に入ったものを選んで保存する
88
+ - 手持ちの wav(数秒〜十数秒)を取り込む
89
+
90
+ のどちらかで作り、「使う」で切り替える。
91
+ 何も作らなくても、最初の読み上げのときに既定の声が1つ作られる。
92
+ 切り替えは次の読み上げから効き、再起動は要らない。
93
+
94
+ ## 読み方を育てる
95
+
96
+ 要約は、いつも思いどおりに縮まるとは限らない。
97
+ 識別子を読み上げてしまったり、知りたかった結果を落としたりすることがある。
98
+ 読み上げの履歴に印を付けておくと、それを材料に要約の指示と辞書を直せる。
99
+
100
+ studio はメニューの「studio を開く」か `pairvoice studio` で起動する(`127.0.0.1:17494` だけで待ち受ける)。
101
+
102
+ | 画面 | できること |
103
+ | --- | --- |
104
+ | レビュー | 読み上げの履歴を聞き直し、👍👎 と「こう読んでほしかった」を残す |
105
+ | プロンプト | 要約の指示(`prompt.txt`)を直す。前の版に戻せる |
106
+ | 辞書 | 読み間違える言葉を、読み方に置き換える |
107
+ | 声 | 声の描写とサンプラーを変えて試聴し、気に入った描写を使用中の声に当てる |
108
+ | プロファイル | 声を作る、取り込む、切り替える |
109
+
110
+ 要約の指示の改善は、Claude Code で `/pairvoice:tune` を実行して頼む。
111
+ レビューを材料に改善案を作り、`pairvoice eval` で今の指示と比べてから提案する。当てるかは人が決める。
112
+
113
+ ## 使い方
114
+
115
+ メニューバーのアイコンから、読み上げを止める、ミュート(15分、30分、60分)、studio を開く、再起動(サーバーと studio)、終了ができる。
116
+ 同じことはコマンドでもできる。
117
+
118
+ | コマンド | すること |
119
+ | --- | --- |
120
+ | `pairvoice status` | 状態(モデル、ミュート、声)を表示する |
121
+ | `pairvoice mute 30m` / `unmute` | 30分ミュートする(1〜480分)/解除する |
122
+ | `pairvoice say "テスト"` | 読み上げてみる(ミュート中でも鳴る) |
123
+ | `pairvoice stop` | 鳴っている読み上げと、順番待ちの読み上げを止める |
124
+ | `pairvoice warmup` | モデルを先に読み込んでおく |
125
+ | `pairvoice restart` | 常駐サーバーを再起動する(設定を変えたら要る) |
126
+ | `pairvoice studio` | studio を開く(`--restart` で studio だけを立て直す) |
127
+ | `pairvoice eval` | 要約の指示やモデルを規則で採点する(`--reviews` でレビューも使う、`--model` で別のモデルを試す) |
128
+
129
+ 「終了」で止めたサーバーは、ログインし直すか `launchctl kickstart gui/$(id -u)/local.pairvoice` で戻る。
130
+
131
+ ## ミュート
132
+
133
+ 手動のミュート(期限付き)と、自動の条件のどちらかが当てはまれば黙る。
134
+ 自動の条件は、マイクの使用中(会議アプリを問わない)と、ほかのアプリが音を出している間(YouTube や Spotify の再生中など)。
135
+ ほかのアプリの音は鳴らす直前まで確かめ、通知音のように5秒以内に止めば、止んだ後に読み上げる。
136
+ 読み上げている最中も見張る。ミュートやマイクが入ったらその場で止め、ほかのアプリが音を出したら音量を下げ、5秒続いたら止める。
137
+
138
+ ## 設定
139
+
140
+ `~/.config/pairvoice/config.toml`。
141
+ 無くても既定値で動く。
142
+ 変えたら `pairvoice restart` する。
143
+
144
+ | 表 | よく触る項目 |
145
+ | --- | --- |
146
+ | `[llm]` | `model`(要約のモデル) |
147
+ | `[tts]` | `model`(音声合成のモデル)、`output_max_age_days`(合成した wav を消すまでの日数。既定 7、0 で消さない) |
148
+ | `[tts.profile]` | 最初の声を作るときだけ使う。`ref_audio`(取り込む wav)、`caption`(声の描写) |
149
+ | `[tts.sampler]` | 音声合成の細かい調整。studio の「声」画面で試して、表示される設定を写す |
150
+ | `[mute]` | `auto_microphone`(既定 true)、`auto_audio_output`(既定 true)、`audio_output_wait_seconds`(既定 5) |
151
+ | `[playback]` | `volume`(既定 1.0)、`duck_volume`(ほかのアプリの音が鳴っている間の音量。既定 0.3)、`max_wait_seconds`(順番待ちがこれを超えたら捨てる。既定 60) |
152
+ | `[eval]` | `style = "casual"` で、`pairvoice eval` がです/ます調を違反にする(タメ口に育てるとき) |
153
+
154
+ メモリが少ない機種では、要約のモデルを小さくすると要約側のピークが約 8GB から約 4.7GB に下がる。
155
+ ただしコード識別子をそのまま読み上げやすく、既定のモデルより崩れやすい。
156
+
157
+ ```toml
158
+ [llm]
159
+ model = "mlx-community/gemma-4-e4b-it-4bit"
160
+ ```
161
+
162
+ ## うまく鳴らないとき
163
+
164
+ - **何も鳴らない**: `pairvoice status` を見る
165
+ - `mute.active` が `true` ならミュート中(`mute.reason` が `microphone` なら会議中の自動ミュート)
166
+ - `state` が `downloading` / `loading` ならモデルの準備中。初回は時間がかかる
167
+ - `state` が `failed` / `misconfigured` なら、`~/Library/Logs/pairvoice.log` を見る
168
+ - サーバーが起動しては落ちるなら、落ちたときの出力が `~/Library/Logs/pairvoice.stderr.log` に残る
169
+ - サーバーに届かなければ、メニューの「再起動」かログインし直しで戻す
170
+ - **サーバーは元気なのに鳴らない**: `~/Library/Logs/speak-summary.log` を見る。
171
+ 何も書かれていなければプラグインが読まれていないので、Claude Code を起動し直す。
172
+ 書かれていれば理由が `SKIP (...)` に出る(一覧は [plugin/README.md](https://github.com/matzkoh/pairvoice/blob/main/plugin/README.md))
173
+ - **設定を変えたのに効かない**: `pairvoice restart`
174
+ - **辞書を直したのに効かない**: studio の「保存」を押すまでは書き換わらない
175
+
176
+ ## やめるとき
177
+
178
+ Claude Code で `/plugin uninstall pairvoice@pairvoice`、続けて次を実行する。
179
+
180
+ ```bash
181
+ pairvoice uninstall # 常駐の登録を外す
182
+ uv tool uninstall pairvoice
183
+ ```
184
+
185
+ 要約の指示、辞書、声、読み上げの記録は `~/Library/Application Support/pairvoice/` に残る。
186
+ 要らなければ消す。
187
+
188
+ ## 開発に参加する
189
+
190
+ 手元での動かし方、テスト、評価ハーネスは [DEVELOPMENT.md](https://github.com/matzkoh/pairvoice/blob/main/DEVELOPMENT.md) にある。
191
+
192
+ ## ライセンス
193
+
194
+ [MIT](https://github.com/matzkoh/pairvoice/blob/main/LICENSE)
@@ -0,0 +1,3 @@
1
+ 通っ とおっ 「通った」を「かよった」と読まれるのを直す
2
+ CI シーアイ
3
+ PR プルリクエスト