tacheles 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.
- tacheles-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +19 -0
- tacheles-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
- tacheles-0.1.0/.github/workflows/ci.yml +29 -0
- tacheles-0.1.0/.gitignore +16 -0
- tacheles-0.1.0/CHANGELOG.md +42 -0
- tacheles-0.1.0/CONTRIBUTING.md +38 -0
- tacheles-0.1.0/LICENSE +21 -0
- tacheles-0.1.0/PKG-INFO +350 -0
- tacheles-0.1.0/README.md +303 -0
- tacheles-0.1.0/SECURITY.md +48 -0
- tacheles-0.1.0/data/tacheles.desktop +11 -0
- tacheles-0.1.0/docs/CREDENTIALS.md +180 -0
- tacheles-0.1.0/docs/FAQ.md +103 -0
- tacheles-0.1.0/docs/HOTKEYS.md +74 -0
- tacheles-0.1.0/docs/LAUNCH.md +75 -0
- tacheles-0.1.0/docs/LOCAL.md +71 -0
- tacheles-0.1.0/docs/media/README.md +14 -0
- tacheles-0.1.0/docs/media/multilingual.gif +0 -0
- tacheles-0.1.0/docs/media/multilingual.tape +22 -0
- tacheles-0.1.0/docs/media/vocabulary.gif +0 -0
- tacheles-0.1.0/docs/media/vocabulary.tape +38 -0
- tacheles-0.1.0/gnome-extension/tacheles@erikeremenko.github.io/extension.js +484 -0
- tacheles-0.1.0/gnome-extension/tacheles@erikeremenko.github.io/metadata.json +8 -0
- tacheles-0.1.0/gnome-extension/tacheles@erikeremenko.github.io/stylesheet.css +32 -0
- tacheles-0.1.0/install.sh +75 -0
- tacheles-0.1.0/llms.txt +57 -0
- tacheles-0.1.0/pyproject.toml +56 -0
- tacheles-0.1.0/scripts/gh-metadata.sh +16 -0
- tacheles-0.1.0/tacheles/__init__.py +4 -0
- tacheles-0.1.0/tacheles/__main__.py +3 -0
- tacheles-0.1.0/tacheles/audio.py +371 -0
- tacheles-0.1.0/tacheles/cli.py +922 -0
- tacheles-0.1.0/tacheles/config.py +392 -0
- tacheles-0.1.0/tacheles/correct.py +822 -0
- tacheles-0.1.0/tacheles/data/default_aliases.toml +62 -0
- tacheles-0.1.0/tacheles/doctor.py +215 -0
- tacheles-0.1.0/tacheles/history.py +106 -0
- tacheles-0.1.0/tacheles/keys.py +256 -0
- tacheles-0.1.0/tacheles/notify.py +171 -0
- tacheles-0.1.0/tacheles/output.py +477 -0
- tacheles-0.1.0/tacheles/profiles/coding.toml +42 -0
- tacheles-0.1.0/tacheles/profiles/general.toml +13 -0
- tacheles-0.1.0/tacheles/session.py +333 -0
- tacheles-0.1.0/tacheles/transcribe.py +456 -0
- tacheles-0.1.0/tests/conftest.py +51 -0
- tacheles-0.1.0/tests/test_audio.py +74 -0
- tacheles-0.1.0/tests/test_config.py +129 -0
- tacheles-0.1.0/tests/test_correct.py +459 -0
- tacheles-0.1.0/tests/test_keys.py +29 -0
- tacheles-0.1.0/tests/test_output.py +159 -0
- tacheles-0.1.0/tests/test_session_history.py +123 -0
- tacheles-0.1.0/tests/test_transcribe.py +245 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something does not work
|
|
3
|
+
body:
|
|
4
|
+
- type: textarea
|
|
5
|
+
id: doctor
|
|
6
|
+
attributes:
|
|
7
|
+
label: Output of `tacheles doctor`
|
|
8
|
+
description: This reports your compositor, audio backend and injection method. Redact nothing except your API key.
|
|
9
|
+
render: shell
|
|
10
|
+
validations: { required: true }
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: what
|
|
13
|
+
attributes:
|
|
14
|
+
label: What happened, and what did you expect?
|
|
15
|
+
validations: { required: true }
|
|
16
|
+
- type: input
|
|
17
|
+
id: distro
|
|
18
|
+
attributes: { label: Distro + desktop + session (e.g. Fedora 44, GNOME 50, Wayland) }
|
|
19
|
+
validations: { required: true }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push: { branches: [main] }
|
|
4
|
+
pull_request:
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
# The correction layer asks hunspell whether a word is real. Without a dictionary it
|
|
14
|
+
# cannot tell "Supervisor" from a mistranscription, so the tiering is untested.
|
|
15
|
+
# German and Russian too: the corrector refuses to auto-apply anything when a declared
|
|
16
|
+
# language cannot be spell-checked, so English alone leaves the multilingual path untested.
|
|
17
|
+
- name: Install spell-check dictionaries
|
|
18
|
+
run: sudo apt-get update -qq && sudo apt-get install -y -qq hunspell hunspell-en-us hunspell-de-de hunspell-ru wamerican
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
run: uv python install ${{ matrix.python-version }}
|
|
22
|
+
- name: Install
|
|
23
|
+
run: uv sync --extra dev
|
|
24
|
+
- name: Lint
|
|
25
|
+
run: uv run ruff check tacheles tests
|
|
26
|
+
- name: Format check
|
|
27
|
+
run: uv run ruff format --check tacheles tests
|
|
28
|
+
- name: Tests
|
|
29
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First release. Everything below was tested on Fedora 44 with GNOME 50 on Wayland; other
|
|
6
|
+
combinations are supported but less exercised, and the README says which.
|
|
7
|
+
|
|
8
|
+
### Dictation
|
|
9
|
+
|
|
10
|
+
- Push-to-talk recording via `pw-record`, `parecord` or `arecord`, whichever is present. Capture
|
|
11
|
+
starts about 65 ms after the hotkey, so the first word is not clipped.
|
|
12
|
+
- Transcription with OpenAI `gpt-transcribe`, falling back through `gpt-4o-transcribe`,
|
|
13
|
+
`gpt-4o-mini-transcribe` and `whisper-1` depending on what the key can reach. Request parameters
|
|
14
|
+
are built per model, because `keywords` and `languages` exist only on some of them.
|
|
15
|
+
- A local backend for whisper.cpp, faster-whisper-server, Speaches or LocalAI. No audio leaves the
|
|
16
|
+
machine and no API key is needed.
|
|
17
|
+
- Delivery to the clipboard, with optional automatic pasting. The paste chord is chosen from the
|
|
18
|
+
window that had focus when recording began, so terminals get Ctrl+Shift+V.
|
|
19
|
+
|
|
20
|
+
### Getting your words right
|
|
21
|
+
|
|
22
|
+
- Vocabulary profiles sent to the API as `keywords`, with inheritance between profiles.
|
|
23
|
+
- Post-transcription correction against that same vocabulary: casing, near misses, and compound
|
|
24
|
+
terms the model split in two ("fast API" to FastAPI, "red is" to Redis).
|
|
25
|
+
- Learned aliases via `tacheles fix`, applied deterministically thereafter.
|
|
26
|
+
- A shipped set of corrections for manglings no vocabulary hint fixes ("P and PM" for
|
|
27
|
+
pnpm, "Griffana" for Grafana, "cloud code" for Claude Code). Your own override them.
|
|
28
|
+
- Corrections are tiered. A word hunspell recognises in any of your declared languages is never
|
|
29
|
+
rewritten unattended; it is suggested for `tacheles review` instead.
|
|
30
|
+
- Multilingual dictation, mixed within a sentence, each language kept in its own script.
|
|
31
|
+
|
|
32
|
+
### Around the edges
|
|
33
|
+
|
|
34
|
+
- A GNOME Shell extension showing recording, transcribing and ready in the top bar, with a
|
|
35
|
+
transcript history you can copy from and a profile picker.
|
|
36
|
+
- Idempotent GNOME hotkey installation that appends to your existing shortcuts and moves
|
|
37
|
+
`switch-input-source` to Shift+Alt, restoring it on uninstall.
|
|
38
|
+
- An auto-stop watchdog, five minutes by default, so a forgotten recording cannot run on.
|
|
39
|
+
- A silence guard, calibrated to your room by `tacheles calibrate`, so a muted microphone costs
|
|
40
|
+
nothing.
|
|
41
|
+
- `tacheles doctor`, which explains anything that will not work and why.
|
|
42
|
+
- API key from the GNOME keyring, any secret manager via `api_key_command`, or a `0600` file.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Contributing to tacheles
|
|
2
|
+
|
|
3
|
+
Thanks for helping out. tacheles is a small, dependency-light tool; please keep it that way.
|
|
4
|
+
|
|
5
|
+
## Ground rules
|
|
6
|
+
|
|
7
|
+
- **Standard library first.** The only hard runtime dependency is `openai`. Anything else must be
|
|
8
|
+
optional and guarded behind a try/except import.
|
|
9
|
+
- **Shell out, don't bind.** We call `pw-record`, `wl-copy`, `ydotool` and friends as subprocesses.
|
|
10
|
+
This keeps the install trivial and avoids compiled dependencies.
|
|
11
|
+
- **Every external binary is optional.** If a tool is missing, degrade gracefully and say so in
|
|
12
|
+
`tacheles doctor`, and never crash.
|
|
13
|
+
- **No network in tests.** The OpenAI client is mocked. `pytest` must pass on a headless runner
|
|
14
|
+
with no audio device.
|
|
15
|
+
|
|
16
|
+
## Development setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/ErikEremenko/tacheles && cd tacheles
|
|
20
|
+
uv sync --extra dev # or: python -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
21
|
+
uv run pytest -q
|
|
22
|
+
uv run ruff check tacheles tests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Adding support for another desktop
|
|
26
|
+
|
|
27
|
+
Text injection and hotkey binding are the two desktop-specific parts.
|
|
28
|
+
|
|
29
|
+
- Injection: add a class to `tacheles/output.py` implementing the `Injector` protocol
|
|
30
|
+
(`name`, `available()`, `type_text()`, `key_chord()`) and register it in `INJECTORS`.
|
|
31
|
+
- Hotkeys: add a backend to `tacheles/keys.py` implementing `install()` / `uninstall()` /
|
|
32
|
+
`available()`.
|
|
33
|
+
|
|
34
|
+
Please include what you tested it on, and add a row to the support table in the README.
|
|
35
|
+
|
|
36
|
+
## Commit style
|
|
37
|
+
|
|
38
|
+
Short imperative subject lines (`add opus encoding`, `fix wav header repair on empty capture`).
|
tacheles-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Erik Eremenko
|
|
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.
|
tacheles-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tacheles
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Push-to-talk dictation for Linux that knows your technical vocabulary and learns the words it gets wrong. OpenAI or fully local.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ErikEremenko/tacheles
|
|
6
|
+
Project-URL: Issues, https://github.com/ErikEremenko/tacheles/issues
|
|
7
|
+
Author: Erik Eremenko
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Erik Eremenko
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: accessibility,dictation,gnome,linux,openai,speech-to-text,wayland
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Environment :: X11 Applications :: GTK
|
|
33
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
38
|
+
Classifier: Topic :: Utilities
|
|
39
|
+
Requires-Python: >=3.11
|
|
40
|
+
Requires-Dist: openai<4,>=3.6
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
44
|
+
Provides-Extra: indicator
|
|
45
|
+
Requires-Dist: pygobject>=3.46; extra == 'indicator'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# tacheles
|
|
49
|
+
|
|
50
|
+
**Dictation that speaks your stack.** Push a key, talk, push again. The text lands where you were
|
|
51
|
+
typing. For developers who would explain the whole thing to their coding agent if writing it out
|
|
52
|
+
were not such an effort, and who are tired of transcription that mangles every identifier.
|
|
53
|
+
|
|
54
|
+
*Tacheles reden* is German, from the Yiddish **תכלית** *takhlis*, "purpose". It means talking
|
|
55
|
+
plainly and getting to the point. Pronounced **TAH-khe-les**.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### It knows your vocabulary
|
|
60
|
+
|
|
61
|
+
General speech models have never heard of your stack. tacheles has.
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
`fast API` → `FastAPI`. `red is` → `Redis`. `post gres SQL` → `PostgreSQL`. None of that is
|
|
66
|
+
guesswork. It sends your vocabulary with every request, then corrects what comes back against that
|
|
67
|
+
same list.
|
|
68
|
+
|
|
69
|
+
### It learns the rest
|
|
70
|
+
|
|
71
|
+
The jargon no model could guess, you teach once. It sticks, permanently, and it is applied
|
|
72
|
+
deterministically, not re-guessed each time.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
tacheles fix 'P and PM' pnpm # said once, corrected forever
|
|
76
|
+
tacheles review # confirm what it was unsure about
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
It also ships with corrections for manglings that no vocabulary hint fixes, because what the model
|
|
80
|
+
heard is nothing like the right word: `P and PM` for `pnpm`, `Griffana` for `Grafana`, `item
|
|
81
|
+
potent` for `idempotent`, `cloud code` for `Claude Code`. Those work on a fresh install, and your
|
|
82
|
+
own always take precedence.
|
|
83
|
+
|
|
84
|
+
Every automatic correction is inspectable, and anything it is not certain about is *suggested*
|
|
85
|
+
rather than silently applied. It never rewrites a real word behind your back.
|
|
86
|
+
|
|
87
|
+
### It speaks more than one language, mid-sentence
|
|
88
|
+
|
|
89
|
+

|
|
90
|
+
|
|
91
|
+
English, German and Russian in a single breath, each kept in its own script, with no setting
|
|
92
|
+
required. Declaring the languages you mix is supported and harmless, but in testing here it made
|
|
93
|
+
no measurable difference: Russian came back in correct Cyrillic even when only English was
|
|
94
|
+
declared.
|
|
95
|
+
|
|
96
|
+
### It can run entirely on your machine
|
|
97
|
+
|
|
98
|
+
Point it at a local whisper.cpp, faster-whisper or Speaches server and **no audio leaves your
|
|
99
|
+
computer at all**. The vocabulary and learning layers work exactly the same, and they matter
|
|
100
|
+
more, because small local models are worse at rare terms rather than better.
|
|
101
|
+
|
|
102
|
+
```toml
|
|
103
|
+
[transcription]
|
|
104
|
+
backend = "local"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
See [docs/LOCAL.md](docs/LOCAL.md) for setup, and for an honest account of what you trade away.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Why not just use the dictation built into your editor?
|
|
112
|
+
|
|
113
|
+
Because it was not built for this. Dictation bundled into AI tools tends to transcribe
|
|
114
|
+
*everything*: every "um", every false start, every repeated word. You get a wall of text and then
|
|
115
|
+
have to edit it. And none of it knows what `pnpm` is.
|
|
116
|
+
|
|
117
|
+
tacheles takes the opposite position on both counts:
|
|
118
|
+
|
|
119
|
+
| | Built-in dictation | tacheles |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| Filler words | Transcribed verbatim | Dropped, while keeping how you actually talk |
|
|
122
|
+
| Your jargon | Guessed phonetically | Sent as vocabulary, then corrected against it |
|
|
123
|
+
| Getting it wrong twice | Every time | Teach it once, never again |
|
|
124
|
+
| Mixed languages | Usually one language wins | Each kept in its own script |
|
|
125
|
+
| Where your audio goes | Their servers | OpenAI, or nowhere but your own machine |
|
|
126
|
+
|
|
127
|
+
## Why this exists
|
|
128
|
+
|
|
129
|
+
The bottleneck in working with coding agents is not typing speed. It is the small, constant
|
|
130
|
+
reluctance to explain something properly.
|
|
131
|
+
|
|
132
|
+
You know the shape of what you want. Writing it out means four paragraphs of context: what the
|
|
133
|
+
code does now, what it should do instead, which edge case bit you last time, what not to touch.
|
|
134
|
+
Every one of those paragraphs has a cost, and when you are tired or in the middle of something
|
|
135
|
+
else you pay it by writing less. You send three lines instead of thirty, the agent guesses at the
|
|
136
|
+
rest, and you spend the afternoon correcting work that would have been right if you had just said
|
|
137
|
+
the whole thing.
|
|
138
|
+
|
|
139
|
+
Speaking it costs almost nothing. So you say the whole thing, first time.
|
|
140
|
+
|
|
141
|
+
That is the actual win, and it is not about words per minute. Building
|
|
142
|
+
[PolySimulator](https://polysimulator.com), a paper-trading platform for Polymarket prediction
|
|
143
|
+
markets, we run a lot of agents at once. The scarce resource is not keystrokes, it is attention:
|
|
144
|
+
holding the architecture in your head, reading what came back, judging whether it is right,
|
|
145
|
+
deciding what happens next. Composing precise English is a tax on exactly that attention. Paying
|
|
146
|
+
it ten times a day leaves less of you for the parts that actually need a person.
|
|
147
|
+
|
|
148
|
+
Which is what the name is about. *Tacheles reden* means saying the thing plainly and getting on
|
|
149
|
+
with it, with no throat-clearing and no working up to it. Say what you want; let the work start.
|
|
150
|
+
|
|
151
|
+
There was one thing standing in the way on Linux. Dictation here is bad. Browser microphone
|
|
152
|
+
buttons are flaky, desktop apps cannot type into other apps on Wayland, and local models either
|
|
153
|
+
want a GPU or mangle every technical term. Every tool that got this right turned out to be
|
|
154
|
+
macOS-only, which I discovered the hard way while looking for a name: the good ones were already
|
|
155
|
+
taken by macOS dictation apps.
|
|
156
|
+
|
|
157
|
+
And general transcription mangles developer speech specifically. It writes "P and PM" for `pnpm`,
|
|
158
|
+
"fast API" for `FastAPI`, "red is" for `Redis`, and turns a colleague's name into something
|
|
159
|
+
unrecognisable. When half your sentences carry identifiers, that is not a small annoyance, because
|
|
160
|
+
fixing the transcript costs more than typing it would have. The friction comes straight back, just
|
|
161
|
+
somewhere else.
|
|
162
|
+
|
|
163
|
+
So tacheles sends your vocabulary with every request, corrects what comes back against that same
|
|
164
|
+
vocabulary, and permanently learns whatever you tell it once. The point is that dictating stays
|
|
165
|
+
cheaper than typing all the way through, including the proofreading.
|
|
166
|
+
|
|
167
|
+
It is ~3,700 lines of Python that shells out to tools you already have (`pw-record`, `wl-copy`,
|
|
168
|
+
`notify-send`) and uses OpenAI, or a local Whisper server, for the hard part.
|
|
169
|
+
|
|
170
|
+
*Tacheles*, from the Yiddish **תכלית** *takhlis*, "purpose", is the German idiom for talking
|
|
171
|
+
plainly and getting to the point. *Tacheles reden.* Pronounced **TAH-khe-les**.
|
|
172
|
+
|
|
173
|
+
## Install
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/ErikEremenko/tacheles && cd tacheles
|
|
177
|
+
./install.sh
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`install.sh` creates a private virtualenv, installs tacheles into it, and registers the GNOME
|
|
181
|
+
shortcuts. Then give it a key and check the setup:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
tacheles auth login # stored in the GNOME keyring, or a 0600 file
|
|
185
|
+
tacheles doctor # explains anything that will not work, and why
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Your key never leaves your machine except in requests to OpenAI. Nobody involved in this project
|
|
189
|
+
receives it, and [docs/CREDENTIALS.md](docs/CREDENTIALS.md) shows you how to verify that rather
|
|
190
|
+
than take it on trust. It also covers using a secret manager (Bitwarden Secrets Manager, pass,
|
|
191
|
+
1Password, gopass) instead of storing the key at all.
|
|
192
|
+
|
|
193
|
+
`tacheles doctor` is the first thing to run whenever something misbehaves. It reports your
|
|
194
|
+
recorder, credentials, model, delivery method, keyboard layout and hotkeys in one screen.
|
|
195
|
+
|
|
196
|
+
## How the text reaches your app
|
|
197
|
+
|
|
198
|
+
This is the genuinely hard part on Wayland, which deliberately stops one app synthesising input
|
|
199
|
+
into another. tacheles has three modes, and **always writes the clipboard first** so a failure
|
|
200
|
+
downstream can never lose your transcript.
|
|
201
|
+
|
|
202
|
+
| Mode | What it does | Setup | Notes |
|
|
203
|
+
|---|---|---|---|
|
|
204
|
+
| `clipboard` *(default)* | Copies the text; you press Ctrl+V | none | Always works |
|
|
205
|
+
| `paste` | Copies, then synthesises Ctrl+V | `tacheles setup-paste` | Needs ydotool on GNOME |
|
|
206
|
+
| `type` | Types the text keystroke by keystroke | `tacheles setup-paste` | **US layout only**, see below |
|
|
207
|
+
|
|
208
|
+
**A warning about `type` mode.** ydotool emits raw US keycodes and ignores your active XKB
|
|
209
|
+
layout. If you have a non-US layout configured, typed text comes out mangled. tacheles detects this
|
|
210
|
+
and refuses rather than producing garbage. `paste` mode is unaffected, because only the Ctrl+V
|
|
211
|
+
chord travels through the keyboard, and the text itself goes via the clipboard.
|
|
212
|
+
|
|
213
|
+
## Seeing what it is doing
|
|
214
|
+
|
|
215
|
+
GNOME hides notification banners after a few seconds whatever expire-timeout you ask for, so a
|
|
216
|
+
notification cannot tell you a recording is still running. The optional top-bar indicator can:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
tacheles install-indicator # then log out and back in
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
It shows the state in the panel: microphone when idle, a red dot and a running timer while
|
|
223
|
+
recording, an amber icon while transcribing, a green tick when the text is ready. Clicking it
|
|
224
|
+
opens your recent transcripts; clicking one copies it again, and a middle click on the icon
|
|
225
|
+
copies the newest without opening anything.
|
|
226
|
+
|
|
227
|
+
That list matters more than it sounds. `wl-copy` holds the clipboard in a child process, so a
|
|
228
|
+
session restart empties the clipboard even though the transcript was produced correctly. Nothing
|
|
229
|
+
is ever lost, because it is in the history either way:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
tacheles last --copy # put the most recent transcript back on the clipboard
|
|
233
|
+
tacheles last -n 5 # or look at the last five
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Audio cues are on by default. `tacheles sounds` plays each one, and they are configurable
|
|
237
|
+
under `[notify]`.
|
|
238
|
+
|
|
239
|
+
## Vocabulary profiles
|
|
240
|
+
|
|
241
|
+
Profiles live in `~/.config/tacheles/profiles/*.toml` and shadow the bundled ones.
|
|
242
|
+
|
|
243
|
+
```toml
|
|
244
|
+
name = "coding"
|
|
245
|
+
language = "en"
|
|
246
|
+
prompt = """
|
|
247
|
+
Technical software-development dictation. Keep technical terms in canonical casing.
|
|
248
|
+
"""
|
|
249
|
+
keywords = ["pnpm", "FastAPI", "PostgreSQL", "Grafana", "idempotent"]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`inherits = "coding"` merges another profile's keywords, so a profile of your own project's names
|
|
253
|
+
can extend the general technical vocabulary rather than repeat it.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
tacheles profiles # list them
|
|
257
|
+
tacheles toggle -p coding # use one for a single dictation
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Safety and cost
|
|
261
|
+
|
|
262
|
+
- **Auto-stop.** A forgotten recording stops itself after `audio.max_seconds` (default 5 minutes)
|
|
263
|
+
and transcribes what it captured. Set `max_seconds_action = "discard"` to spend nothing
|
|
264
|
+
instead, or `max_seconds = 0` to disable.
|
|
265
|
+
- **Silence guard.** An all-silent recording is never uploaded, so a muted mic costs nothing.
|
|
266
|
+
- **Spend tracking.** `tacheles history --totals` shows minutes dictated and estimated cost.
|
|
267
|
+
|
|
268
|
+
## Commands
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
tacheles toggle [-p PROFILE] start, or stop and transcribe
|
|
272
|
+
tacheles cancel discard the current recording
|
|
273
|
+
tacheles status is it recording?
|
|
274
|
+
tacheles transcribe FILE transcribe an existing audio file
|
|
275
|
+
tacheles doctor check everything
|
|
276
|
+
tacheles models transcription models your key can use
|
|
277
|
+
tacheles history [--totals] recent transcripts and spend
|
|
278
|
+
tacheles config edit edit the config
|
|
279
|
+
tacheles install-hotkeys register GNOME shortcuts
|
|
280
|
+
tacheles uninstall-hotkeys remove them and restore GNOME's originals
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Hotkeys and your input-source switcher
|
|
284
|
+
|
|
285
|
+
GNOME ships `Super+Space` bound to *switch input source*. Rather than leaving you without a
|
|
286
|
+
layout switcher, `tacheles install-hotkeys` moves that binding to the Windows-style **Shift+Alt**
|
|
287
|
+
and records the original. `tacheles uninstall-hotkeys` puts it back exactly as it was.
|
|
288
|
+
|
|
289
|
+
Shortcuts are appended to your existing custom shortcuts, never overwritten.
|
|
290
|
+
|
|
291
|
+
## Support
|
|
292
|
+
|
|
293
|
+
The part that does the work is plain Linux. Recording goes through `pw-record`, `parecord` or
|
|
294
|
+
`arecord`, so PipeWire, PulseAudio and ALSA are all covered; the clipboard through `wl-copy`,
|
|
295
|
+
`xclip` or `xsel`, so Wayland and X11 both are; notifications through `notify-send`, which is a
|
|
296
|
+
freedesktop standard. Nothing in the recording, transcription or correction path needs GNOME, and
|
|
297
|
+
that is checked rather than assumed: the core runs in an environment stripped of D-Bus, `DISPLAY`
|
|
298
|
+
and `gsettings`.
|
|
299
|
+
|
|
300
|
+
What GNOME adds is convenience, and each piece degrades on its own when it is absent.
|
|
301
|
+
|
|
302
|
+
| | Status |
|
|
303
|
+
|---|---|
|
|
304
|
+
| Recording, transcription, vocabulary, corrections, clipboard | Works anywhere. Verified with no GNOME present |
|
|
305
|
+
| GNOME / Wayland | Everything, including hotkey installation and the top-bar indicator. Used daily by the author |
|
|
306
|
+
| GNOME / X11 | Should work; `xclip` and `xdotool` cover clipboard and pasting. Not tested |
|
|
307
|
+
| KDE, sway, Hyprland, others | Core works; bind the commands yourself, see [docs/HOTKEYS.md](docs/HOTKEYS.md). Not tested |
|
|
308
|
+
| Automatic paste-chord selection | GNOME only, because the extension is what can see the focused window. Falls back to the configured chord elsewhere |
|
|
309
|
+
|
|
310
|
+
"Not tested" means exactly that. It is expected to work and nobody has confirmed it. If you run
|
|
311
|
+
one of those, an issue saying so either way is genuinely useful.
|
|
312
|
+
|
|
313
|
+
## Configuration
|
|
314
|
+
|
|
315
|
+
`tacheles config init` writes a fully commented file with every default. Highlights:
|
|
316
|
+
|
|
317
|
+
```toml
|
|
318
|
+
[general]
|
|
319
|
+
profile = "coding" # default vocabulary profile
|
|
320
|
+
output_mode = "auto" # auto | clipboard | paste | type
|
|
321
|
+
|
|
322
|
+
[openai]
|
|
323
|
+
model = "gpt-transcribe" # $0.0045/min; falls back if unavailable to your key
|
|
324
|
+
|
|
325
|
+
[audio]
|
|
326
|
+
max_seconds = 300 # auto-stop safety limit
|
|
327
|
+
silence_rms = 30 # below this, do not bother uploading
|
|
328
|
+
|
|
329
|
+
[postprocess]
|
|
330
|
+
enabled = false # optional second pass to tidy punctuation
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## How this was built
|
|
334
|
+
|
|
335
|
+
Written by [Erik Eremenko](https://github.com/ErikEremenko), largely with
|
|
336
|
+
[Claude Code](https://claude.com/claude-code) driving the keyboard. I am stating that plainly
|
|
337
|
+
because it is true and because you can see it in the commit history anyway. A project about
|
|
338
|
+
dictating to AI agents that hid its own use of them would be a bit ridiculous.
|
|
339
|
+
|
|
340
|
+
What that means for you as a user or contributor: every design decision here was tested against
|
|
341
|
+
this machine rather than assumed, and the awkward findings are documented rather than smoothed
|
|
342
|
+
over: the ~100 ms clipboard wait, the refusal to type on non-US layouts, the German compound word
|
|
343
|
+
that silently inverted meaning. Where something is unverified, it says so.
|
|
344
|
+
|
|
345
|
+
If you find something that was clearly never exercised, open an issue. That is the failure mode
|
|
346
|
+
worth watching for, and I would rather hear about it.
|
|
347
|
+
|
|
348
|
+
## License
|
|
349
|
+
|
|
350
|
+
MIT. See [LICENSE](LICENSE). Contributions welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
|