chasqui-translate 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. chasqui_translate-0.2.0/LICENSE +21 -0
  2. chasqui_translate-0.2.0/PKG-INFO +263 -0
  3. chasqui_translate-0.2.0/README.md +229 -0
  4. chasqui_translate-0.2.0/chasqui/__init__.py +17 -0
  5. chasqui_translate-0.2.0/chasqui/__main__.py +6 -0
  6. chasqui_translate-0.2.0/chasqui/adapters/__init__.py +60 -0
  7. chasqui_translate-0.2.0/chasqui/adapters/base.py +155 -0
  8. chasqui_translate-0.2.0/chasqui/adapters/bridge.py +166 -0
  9. chasqui_translate-0.2.0/chasqui/adapters/device.py +47 -0
  10. chasqui_translate-0.2.0/chasqui/adapters/headset.py +171 -0
  11. chasqui_translate-0.2.0/chasqui/adapters/media.py +108 -0
  12. chasqui_translate-0.2.0/chasqui/adapters/meet.py +20 -0
  13. chasqui_translate-0.2.0/chasqui/adapters/text.py +67 -0
  14. chasqui_translate-0.2.0/chasqui/adapters/virtual_mic.py +80 -0
  15. chasqui_translate-0.2.0/chasqui/cli.py +392 -0
  16. chasqui_translate-0.2.0/chasqui/core/__init__.py +0 -0
  17. chasqui_translate-0.2.0/chasqui/core/audio.py +281 -0
  18. chasqui_translate-0.2.0/chasqui/core/config.py +169 -0
  19. chasqui_translate-0.2.0/chasqui/core/constants.py +25 -0
  20. chasqui_translate-0.2.0/chasqui/core/pipeline.py +404 -0
  21. chasqui_translate-0.2.0/chasqui/core/pricing.py +148 -0
  22. chasqui_translate-0.2.0/chasqui/core/selftest.py +131 -0
  23. chasqui_translate-0.2.0/chasqui/core/session.py +71 -0
  24. chasqui_translate-0.2.0/chasqui/core/windevice.py +68 -0
  25. chasqui_translate-0.2.0/chasqui/ui/__init__.py +9 -0
  26. chasqui_translate-0.2.0/chasqui/ui/app.py +1096 -0
  27. chasqui_translate-0.2.0/chasqui/ui/icon.py +233 -0
  28. chasqui_translate-0.2.0/chasqui/ui/runner.py +287 -0
  29. chasqui_translate-0.2.0/chasqui_translate.egg-info/PKG-INFO +263 -0
  30. chasqui_translate-0.2.0/chasqui_translate.egg-info/SOURCES.txt +47 -0
  31. chasqui_translate-0.2.0/chasqui_translate.egg-info/dependency_links.txt +1 -0
  32. chasqui_translate-0.2.0/chasqui_translate.egg-info/entry_points.txt +5 -0
  33. chasqui_translate-0.2.0/chasqui_translate.egg-info/requires.txt +13 -0
  34. chasqui_translate-0.2.0/chasqui_translate.egg-info/top_level.txt +1 -0
  35. chasqui_translate-0.2.0/pyproject.toml +66 -0
  36. chasqui_translate-0.2.0/setup.cfg +4 -0
  37. chasqui_translate-0.2.0/tests/test_adapter_control.py +181 -0
  38. chasqui_translate-0.2.0/tests/test_audio.py +69 -0
  39. chasqui_translate-0.2.0/tests/test_cli_streams.py +62 -0
  40. chasqui_translate-0.2.0/tests/test_config.py +50 -0
  41. chasqui_translate-0.2.0/tests/test_entry_points.py +78 -0
  42. chasqui_translate-0.2.0/tests/test_headset_wiring.py +210 -0
  43. chasqui_translate-0.2.0/tests/test_icon.py +258 -0
  44. chasqui_translate-0.2.0/tests/test_media_wiring.py +168 -0
  45. chasqui_translate-0.2.0/tests/test_pipeline.py +54 -0
  46. chasqui_translate-0.2.0/tests/test_pricing.py +109 -0
  47. chasqui_translate-0.2.0/tests/test_recv_flush.py +74 -0
  48. chasqui_translate-0.2.0/tests/test_session.py +22 -0
  49. chasqui_translate-0.2.0/tests/test_ui_runner.py +324 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kvothesson
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,263 @@
1
+ Metadata-Version: 2.4
2
+ Name: chasqui-translate
3
+ Version: 0.2.0
4
+ Summary: Real-time simultaneous translation. Speak your language. Be understood.
5
+ Author: kvothesson
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kvothesson/chasqui
8
+ Project-URL: Issues, https://github.com/kvothesson/chasqui/issues
9
+ Keywords: translation,real-time,speech,ai,interpreter,realtime-api
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Environment :: Win32 (MS Windows)
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: openai>=1.40.0
22
+ Requires-Dist: websockets>=12.0
23
+ Requires-Dist: sounddevice>=0.4.6
24
+ Requires-Dist: numpy>=1.24.0
25
+ Requires-Dist: click>=8.1.0
26
+ Requires-Dist: pyyaml>=6.0
27
+ Requires-Dist: python-dotenv>=1.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-asyncio; extra == "dev"
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: ruff; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # chasqui
36
+
37
+ > *"El chasqui corría cientos de kilómetros llevando un mensaje que no podía leer.*
38
+ > *Transmitía sin entender. Conectaba sin pertenecer."*
39
+
40
+ Real-time simultaneous translation. Speak your language. Be understood.
41
+
42
+ *[Leer en español →](https://github.com/kvothesson/chasqui/blob/master/README.es.md)*
43
+
44
+ <p align="center">
45
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-gadget.png" alt="The chasqui window: two language pickers, a mode, and Start" width="402">
46
+ </p>
47
+
48
+ ---
49
+
50
+ ## Install
51
+
52
+ **You need:** Python 3.11+, an OpenAI API key, and — for the modes that put
53
+ translated audio into another app — [VB-Audio Cable](https://vb-audio.com/Cable/)
54
+ on Windows.
55
+
56
+ The package is `chasqui-translate` (plain `chasqui` on PyPI belongs to an
57
+ unrelated project). It is not published yet, so install from source:
58
+
59
+ ```bash
60
+ git clone https://github.com/kvothesson/chasqui && cd chasqui
61
+ pip install -e .
62
+ ```
63
+
64
+ Give it a key, either as an environment variable:
65
+
66
+ ```bash
67
+ export OPENAI_API_KEY=sk-... # Windows: setx OPENAI_API_KEY sk-...
68
+ ```
69
+
70
+ ...or in a `chasqui.yaml` next to your project (`chasqui init` writes a starter
71
+ file). Then open the window:
72
+
73
+ ```bash
74
+ chasqui ui
75
+ ```
76
+
77
+ ### A desktop launcher
78
+
79
+ ```bash
80
+ chasqui icon --shortcut
81
+ ```
82
+
83
+ That writes the icon — drawn in code, there is no image file in the repo — and
84
+ puts a shortcut on your desktop. The shortcut runs `chasqui-ui`, a windowed
85
+ launcher. `chasqui ui` does the same thing but is a console script: fine from a
86
+ terminal, and from a double-click it parks a black console window behind the app
87
+ for the whole session.
88
+
89
+ ### Linux note
90
+
91
+ Tkinter ships with Python, so the UI adds no dependency. On Debian/Ubuntu it
92
+ lives in a separate package:
93
+
94
+ ```bash
95
+ sudo apt install python3-tk
96
+ ```
97
+
98
+ The audio adapters are Windows-first and honest about it: `headset` and `media`
99
+ switch your Windows default devices through PowerShell. The `text` adapter runs
100
+ anywhere.
101
+
102
+ ---
103
+
104
+ ## Using the interface
105
+
106
+ The window has two shapes. The one above is the default: small enough to park
107
+ beside a call, and it shows only what you need mid-conversation.
108
+
109
+ ### The two language pickers
110
+
111
+ **Your language is always the left box.** The captions above them change with
112
+ the mode and say whose language each box holds, so you never have to work it
113
+ out:
114
+
115
+ | Mode | Left box | Right box |
116
+ |---|---|---|
117
+ | `headset`, `meet`, `bridge` | I SPEAK | THEY SPEAK |
118
+ | `virtual-mic`, `device` | I SPEAK | THEY HEAR |
119
+ | `media` | I HEAR | THEY SPEAK |
120
+
121
+ The arrow between them tells you which way the translation travels: **⇄** in the
122
+ bidirectional modes, **→** when you are being broadcast one way, **←** in media
123
+ mode when audio is coming at you. Click it to swap the two languages.
124
+
125
+ Below them sits the mode picker. `Headset — bidirectional, any app` is the one
126
+ to start with.
127
+
128
+ ### Start, Ping, and the state light
129
+
130
+ **START** opens the session. **PING** runs the same round-trip smoke test as
131
+ `chasqui check`: no mic, no speakers, just "is the pipeline alive". The hexagon
132
+ in the header is the node's state:
133
+
134
+ | Light | What is happening |
135
+ |---|---|
136
+ | `LATENT` | Idle. Nothing is running. |
137
+ | `COMPILING` | Starting up: opening the socket, taking over your default devices. |
138
+ | `ON ROUTE` | Live. Audio is being translated in both directions if the mode is bidirectional. |
139
+ | `UNWINDING` | Stopping, and handing your Windows devices back. |
140
+ | `PING` | Running the round-trip check. |
141
+ | `GLITCH` | Something failed. Open BUFFER — the reason is in there. |
142
+
143
+ **Stopping matters more than it looks.** The `headset` and `media` modes take
144
+ over your Windows default mic and output while they run and hand them back on
145
+ exit, so use STOP, or just close the window — it waits up to 15 seconds for the
146
+ handover — instead of killing the process.
147
+
148
+ The hexagon at the top right (**⬢** / **⬡**) keeps the window above everything
149
+ else. Filled means held on top, which is what you want beside a call.
150
+
151
+ ### What it is costing you
152
+
153
+ The line under the buttons is a running cost, and it stays visible in the small
154
+ shape on purpose. `gpt-realtime-translate` bills by the minute, so chasqui
155
+ counts the audio it streams and prices it — and a bidirectional session pays for
156
+ two directions at once, which is worth seeing before a long call. Set
157
+ `pricing.budget` in `chasqui.yaml` to get "$X left" alongside it.
158
+
159
+ > OpenAI exposes no API for your remaining credit balance, so that budget is
160
+ > your own number, tracked locally — not a reading of your account. The `~`
161
+ > in front of the figure is deliberate: see `core/pricing.py` for what is
162
+ > measured and what is assumed.
163
+
164
+ ### BUFFER — the full panel
165
+
166
+ Click **▸ BUFFER** and the window grows into the second shape: the voice picker,
167
+ the audio devices, and the log.
168
+
169
+ <p align="center">
170
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-transcript.png" alt="The expanded window with a live transcript" width="402">
171
+ </p>
172
+
173
+ The log is where the conversation lands as it happens. Each turn shows the
174
+ source line in grey, the translation under it, and the round-trip latency:
175
+ **ice for what goes out, violet for what comes back**, so in a bidirectional
176
+ call you can see at a glance which way a line travelled. It also holds the
177
+ startup lines, the rate chasqui is pricing you at, and anything that went wrong.
178
+
179
+ Lines accumulate whether the panel is open or not, so opening it after a failure
180
+ shows you everything from the beginning. **FLUSH** clears it. The footer keeps
181
+ the model, the turn count and the average latency.
182
+
183
+ ### Audio devices
184
+
185
+ Devices stay on **Auto**, and Auto is right almost always. Open
186
+ **▸ AUDIO DEVICES** only when autodetection picks the wrong cable.
187
+
188
+ <p align="center">
189
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-devices.png" alt="The audio devices panel, four pickers on Auto" width="402">
190
+ </p>
191
+
192
+ Each field names both ends of the wire, and the pickers you do not need in the
193
+ current mode are greyed out:
194
+
195
+ | Field | What it is |
196
+ |---|---|
197
+ | **Your mic** | Where your voice goes in |
198
+ | **To the app** | The virtual cable Meet/Zoom/Discord listens to |
199
+ | **You hear** | Where the translation reaches you |
200
+ | **From the app** | Capture of what the app is saying |
201
+
202
+ **RESCAN** re-reads the device list — press it after plugging something in.
203
+ Pick devices by name, never by index: Windows renumbers indices whenever a
204
+ default changes, and chasqui's own adapters change defaults at startup.
205
+
206
+ Your languages, mode, voice, devices, window shape and running spend are all
207
+ remembered in `~/.chasqui/ui.json`.
208
+
209
+ ---
210
+
211
+ ## Adapters
212
+
213
+ | Adapter | Use case | Status |
214
+ |---|---|---|
215
+ | `text` | Keyboard in, terminal out — silent mode, accessibility, testing | ✅ Working |
216
+ | `virtual-mic` | Google Meet, Zoom, Teams, ChatGPT Voice (one-way: your voice, translated) | ✅ Working — validated end-to-end with ChatGPT Voice |
217
+ | `bridge` / `meet` | Bidirectional translation for any voice app (needs VB-Audio Cable) | OUT ✅ / IN ✅ (same plumbing as headset, validated there) |
218
+ | `headset` | Bidirectional for any app — Stereo Mix or Hi-Fi Cable for IN capture | ✅ **Validated end-to-end with ChatGPT Voice (2026-07-05), both directions.** Auto-switches default mic, restores on exit; separate mic/headphone via `--mic` / `--earphone` |
219
+ | `media` | YouTube, videos, any system audio → your language | ✅ Working — auto-switches Windows audio output to VB-Cable on start, restores on exit |
220
+ | `device` | Physical pendant (mic + speaker) — the **eco** hardware | Awaiting hardware |
221
+
222
+ ## Commands
223
+
224
+ ```bash
225
+ chasqui ui # desktop interface
226
+ chasqui icon --shortcut # desktop launcher + icon
227
+ chasqui translate "¿Dónde está la estación?" # one-shot text translation
228
+ chasqui listen --adapter device # live audio translation
229
+ chasqui listen --adapter headset --mic "USB Micro" --earphone "Auriculares"
230
+ # separate mic + headphones
231
+ chasqui devices # list audio devices, detect virtual mics
232
+ chasqui langs # supported language codes
233
+ chasqui init # generate chasqui.yaml starter config
234
+ ```
235
+
236
+ ## Configuration
237
+
238
+ Generate a starter config with `chasqui init`. Models are configurable per mode:
239
+
240
+ ```yaml
241
+ api:
242
+ key: ${OPENAI_API_KEY}
243
+ model: gpt-realtime-translate # realtime model for audio adapters
244
+ text_model: gpt-4o-mini # chat model for the text adapter
245
+ ```
246
+
247
+ > **403 `model_not_found`?** Your OpenAI project doesn't have access to the
248
+ > configured model. Check which models are enabled for your project at
249
+ > [platform.openai.com](https://platform.openai.com/settings) and set
250
+ > `api.model` / `api.text_model` accordingly.
251
+
252
+ ## Development
253
+
254
+ ```bash
255
+ pip install -e ".[dev]"
256
+ pytest
257
+ ```
258
+
259
+ See [SPEC.md](https://github.com/kvothesson/chasqui/blob/master/SPEC.md) for full architecture and [AGENTS.md](https://github.com/kvothesson/chasqui/blob/master/AGENTS.md) for contributor/agent notes.
260
+
261
+ ## License
262
+
263
+ MIT
@@ -0,0 +1,229 @@
1
+ # chasqui
2
+
3
+ > *"El chasqui corría cientos de kilómetros llevando un mensaje que no podía leer.*
4
+ > *Transmitía sin entender. Conectaba sin pertenecer."*
5
+
6
+ Real-time simultaneous translation. Speak your language. Be understood.
7
+
8
+ *[Leer en español →](https://github.com/kvothesson/chasqui/blob/master/README.es.md)*
9
+
10
+ <p align="center">
11
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-gadget.png" alt="The chasqui window: two language pickers, a mode, and Start" width="402">
12
+ </p>
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ **You need:** Python 3.11+, an OpenAI API key, and — for the modes that put
19
+ translated audio into another app — [VB-Audio Cable](https://vb-audio.com/Cable/)
20
+ on Windows.
21
+
22
+ The package is `chasqui-translate` (plain `chasqui` on PyPI belongs to an
23
+ unrelated project). It is not published yet, so install from source:
24
+
25
+ ```bash
26
+ git clone https://github.com/kvothesson/chasqui && cd chasqui
27
+ pip install -e .
28
+ ```
29
+
30
+ Give it a key, either as an environment variable:
31
+
32
+ ```bash
33
+ export OPENAI_API_KEY=sk-... # Windows: setx OPENAI_API_KEY sk-...
34
+ ```
35
+
36
+ ...or in a `chasqui.yaml` next to your project (`chasqui init` writes a starter
37
+ file). Then open the window:
38
+
39
+ ```bash
40
+ chasqui ui
41
+ ```
42
+
43
+ ### A desktop launcher
44
+
45
+ ```bash
46
+ chasqui icon --shortcut
47
+ ```
48
+
49
+ That writes the icon — drawn in code, there is no image file in the repo — and
50
+ puts a shortcut on your desktop. The shortcut runs `chasqui-ui`, a windowed
51
+ launcher. `chasqui ui` does the same thing but is a console script: fine from a
52
+ terminal, and from a double-click it parks a black console window behind the app
53
+ for the whole session.
54
+
55
+ ### Linux note
56
+
57
+ Tkinter ships with Python, so the UI adds no dependency. On Debian/Ubuntu it
58
+ lives in a separate package:
59
+
60
+ ```bash
61
+ sudo apt install python3-tk
62
+ ```
63
+
64
+ The audio adapters are Windows-first and honest about it: `headset` and `media`
65
+ switch your Windows default devices through PowerShell. The `text` adapter runs
66
+ anywhere.
67
+
68
+ ---
69
+
70
+ ## Using the interface
71
+
72
+ The window has two shapes. The one above is the default: small enough to park
73
+ beside a call, and it shows only what you need mid-conversation.
74
+
75
+ ### The two language pickers
76
+
77
+ **Your language is always the left box.** The captions above them change with
78
+ the mode and say whose language each box holds, so you never have to work it
79
+ out:
80
+
81
+ | Mode | Left box | Right box |
82
+ |---|---|---|
83
+ | `headset`, `meet`, `bridge` | I SPEAK | THEY SPEAK |
84
+ | `virtual-mic`, `device` | I SPEAK | THEY HEAR |
85
+ | `media` | I HEAR | THEY SPEAK |
86
+
87
+ The arrow between them tells you which way the translation travels: **⇄** in the
88
+ bidirectional modes, **→** when you are being broadcast one way, **←** in media
89
+ mode when audio is coming at you. Click it to swap the two languages.
90
+
91
+ Below them sits the mode picker. `Headset — bidirectional, any app` is the one
92
+ to start with.
93
+
94
+ ### Start, Ping, and the state light
95
+
96
+ **START** opens the session. **PING** runs the same round-trip smoke test as
97
+ `chasqui check`: no mic, no speakers, just "is the pipeline alive". The hexagon
98
+ in the header is the node's state:
99
+
100
+ | Light | What is happening |
101
+ |---|---|
102
+ | `LATENT` | Idle. Nothing is running. |
103
+ | `COMPILING` | Starting up: opening the socket, taking over your default devices. |
104
+ | `ON ROUTE` | Live. Audio is being translated in both directions if the mode is bidirectional. |
105
+ | `UNWINDING` | Stopping, and handing your Windows devices back. |
106
+ | `PING` | Running the round-trip check. |
107
+ | `GLITCH` | Something failed. Open BUFFER — the reason is in there. |
108
+
109
+ **Stopping matters more than it looks.** The `headset` and `media` modes take
110
+ over your Windows default mic and output while they run and hand them back on
111
+ exit, so use STOP, or just close the window — it waits up to 15 seconds for the
112
+ handover — instead of killing the process.
113
+
114
+ The hexagon at the top right (**⬢** / **⬡**) keeps the window above everything
115
+ else. Filled means held on top, which is what you want beside a call.
116
+
117
+ ### What it is costing you
118
+
119
+ The line under the buttons is a running cost, and it stays visible in the small
120
+ shape on purpose. `gpt-realtime-translate` bills by the minute, so chasqui
121
+ counts the audio it streams and prices it — and a bidirectional session pays for
122
+ two directions at once, which is worth seeing before a long call. Set
123
+ `pricing.budget` in `chasqui.yaml` to get "$X left" alongside it.
124
+
125
+ > OpenAI exposes no API for your remaining credit balance, so that budget is
126
+ > your own number, tracked locally — not a reading of your account. The `~`
127
+ > in front of the figure is deliberate: see `core/pricing.py` for what is
128
+ > measured and what is assumed.
129
+
130
+ ### BUFFER — the full panel
131
+
132
+ Click **▸ BUFFER** and the window grows into the second shape: the voice picker,
133
+ the audio devices, and the log.
134
+
135
+ <p align="center">
136
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-transcript.png" alt="The expanded window with a live transcript" width="402">
137
+ </p>
138
+
139
+ The log is where the conversation lands as it happens. Each turn shows the
140
+ source line in grey, the translation under it, and the round-trip latency:
141
+ **ice for what goes out, violet for what comes back**, so in a bidirectional
142
+ call you can see at a glance which way a line travelled. It also holds the
143
+ startup lines, the rate chasqui is pricing you at, and anything that went wrong.
144
+
145
+ Lines accumulate whether the panel is open or not, so opening it after a failure
146
+ shows you everything from the beginning. **FLUSH** clears it. The footer keeps
147
+ the model, the turn count and the average latency.
148
+
149
+ ### Audio devices
150
+
151
+ Devices stay on **Auto**, and Auto is right almost always. Open
152
+ **▸ AUDIO DEVICES** only when autodetection picks the wrong cable.
153
+
154
+ <p align="center">
155
+ <img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-devices.png" alt="The audio devices panel, four pickers on Auto" width="402">
156
+ </p>
157
+
158
+ Each field names both ends of the wire, and the pickers you do not need in the
159
+ current mode are greyed out:
160
+
161
+ | Field | What it is |
162
+ |---|---|
163
+ | **Your mic** | Where your voice goes in |
164
+ | **To the app** | The virtual cable Meet/Zoom/Discord listens to |
165
+ | **You hear** | Where the translation reaches you |
166
+ | **From the app** | Capture of what the app is saying |
167
+
168
+ **RESCAN** re-reads the device list — press it after plugging something in.
169
+ Pick devices by name, never by index: Windows renumbers indices whenever a
170
+ default changes, and chasqui's own adapters change defaults at startup.
171
+
172
+ Your languages, mode, voice, devices, window shape and running spend are all
173
+ remembered in `~/.chasqui/ui.json`.
174
+
175
+ ---
176
+
177
+ ## Adapters
178
+
179
+ | Adapter | Use case | Status |
180
+ |---|---|---|
181
+ | `text` | Keyboard in, terminal out — silent mode, accessibility, testing | ✅ Working |
182
+ | `virtual-mic` | Google Meet, Zoom, Teams, ChatGPT Voice (one-way: your voice, translated) | ✅ Working — validated end-to-end with ChatGPT Voice |
183
+ | `bridge` / `meet` | Bidirectional translation for any voice app (needs VB-Audio Cable) | OUT ✅ / IN ✅ (same plumbing as headset, validated there) |
184
+ | `headset` | Bidirectional for any app — Stereo Mix or Hi-Fi Cable for IN capture | ✅ **Validated end-to-end with ChatGPT Voice (2026-07-05), both directions.** Auto-switches default mic, restores on exit; separate mic/headphone via `--mic` / `--earphone` |
185
+ | `media` | YouTube, videos, any system audio → your language | ✅ Working — auto-switches Windows audio output to VB-Cable on start, restores on exit |
186
+ | `device` | Physical pendant (mic + speaker) — the **eco** hardware | Awaiting hardware |
187
+
188
+ ## Commands
189
+
190
+ ```bash
191
+ chasqui ui # desktop interface
192
+ chasqui icon --shortcut # desktop launcher + icon
193
+ chasqui translate "¿Dónde está la estación?" # one-shot text translation
194
+ chasqui listen --adapter device # live audio translation
195
+ chasqui listen --adapter headset --mic "USB Micro" --earphone "Auriculares"
196
+ # separate mic + headphones
197
+ chasqui devices # list audio devices, detect virtual mics
198
+ chasqui langs # supported language codes
199
+ chasqui init # generate chasqui.yaml starter config
200
+ ```
201
+
202
+ ## Configuration
203
+
204
+ Generate a starter config with `chasqui init`. Models are configurable per mode:
205
+
206
+ ```yaml
207
+ api:
208
+ key: ${OPENAI_API_KEY}
209
+ model: gpt-realtime-translate # realtime model for audio adapters
210
+ text_model: gpt-4o-mini # chat model for the text adapter
211
+ ```
212
+
213
+ > **403 `model_not_found`?** Your OpenAI project doesn't have access to the
214
+ > configured model. Check which models are enabled for your project at
215
+ > [platform.openai.com](https://platform.openai.com/settings) and set
216
+ > `api.model` / `api.text_model` accordingly.
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ pip install -e ".[dev]"
222
+ pytest
223
+ ```
224
+
225
+ See [SPEC.md](https://github.com/kvothesson/chasqui/blob/master/SPEC.md) for full architecture and [AGENTS.md](https://github.com/kvothesson/chasqui/blob/master/AGENTS.md) for contributor/agent notes.
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,17 @@
1
+ """Chasqui — Real-time simultaneous translation."""
2
+
3
+ __version__ = "0.2.0"
4
+
5
+ from .core.config import Config
6
+ from .core.pipeline import Pipeline, TranslationEvent
7
+ from .core.audio import AudioStream, MicStream, SpeakerStream, NullStream
8
+
9
+ __all__ = [
10
+ "Config",
11
+ "Pipeline",
12
+ "TranslationEvent",
13
+ "AudioStream",
14
+ "MicStream",
15
+ "SpeakerStream",
16
+ "NullStream",
17
+ ]
@@ -0,0 +1,6 @@
1
+ """Allow running as `python -m chasqui`."""
2
+
3
+ from .cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -0,0 +1,60 @@
1
+ """Adapter registry — the single place that maps adapter names to classes.
2
+
3
+ Imports stay lazy: `chasqui devices` and `chasqui langs` must not pay for
4
+ sounddevice/openai import time, and a missing optional dependency should only
5
+ blow up for the adapter that actually needs it.
6
+ """
7
+
8
+ import importlib
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING: # pragma: no cover - typing only
12
+ from ..core.config import Config
13
+ from .base import BaseAdapter
14
+
15
+ ADAPTER_PATHS: dict[str, str] = {
16
+ "text": "chasqui.adapters.text.TextAdapter",
17
+ "device": "chasqui.adapters.device.DeviceAdapter",
18
+ "virtual-mic": "chasqui.adapters.virtual_mic.VirtualMicAdapter",
19
+ "media": "chasqui.adapters.media.MediaAdapter",
20
+ "bridge": "chasqui.adapters.bridge.BridgeAdapter",
21
+ "meet": "chasqui.adapters.meet.MeetAdapter",
22
+ "headset": "chasqui.adapters.headset.HeadsetAdapter",
23
+ }
24
+
25
+ ADAPTER_NAMES: list[str] = list(ADAPTER_PATHS)
26
+
27
+ # One-line descriptions, shown in the UI's mode picker.
28
+ ADAPTER_LABELS: dict[str, str] = {
29
+ "headset": "Headset — bidirectional, any app (recommended)",
30
+ "meet": "Meet / Zoom / Teams — bidirectional, two cables",
31
+ "bridge": "Bridge — bidirectional, two cables",
32
+ "virtual-mic": "Virtual mic — one way, your voice into any app",
33
+ "media": "Media — translate what your system is playing",
34
+ "device": "Device — physical mic + speaker pendant",
35
+ "text": "Text — keyboard in, terminal out",
36
+ }
37
+
38
+ # Adapters the GUI can drive. The text adapter is a blocking stdin REPL, so it
39
+ # has nothing to read from in a windowed session.
40
+ UI_ADAPTER_NAMES: list[str] = [
41
+ "headset", "meet", "bridge", "virtual-mic", "media", "device",
42
+ ]
43
+
44
+
45
+ def get_adapter_class(name: str) -> type["BaseAdapter"]:
46
+ """Resolve an adapter name to its class. Raises KeyError if unknown."""
47
+ try:
48
+ path = ADAPTER_PATHS[name]
49
+ except KeyError:
50
+ raise KeyError(
51
+ f"Unknown adapter {name!r}. Available: {', '.join(ADAPTER_NAMES)}"
52
+ ) from None
53
+
54
+ module_path, class_name = path.rsplit(".", 1)
55
+ return getattr(importlib.import_module(module_path), class_name)
56
+
57
+
58
+ def build_adapter(config: "Config") -> "BaseAdapter":
59
+ """Instantiate the adapter named by `config.adapter`."""
60
+ return get_adapter_class(config.adapter)(config)