twokey 1.0.12 → 1.0.14
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.
- package/README.md +55 -3
- package/bin/postinstall.js +35 -0
- package/bin/twokey.js +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,9 +12,15 @@ The core idea matches the video workflow:
|
|
|
12
12
|
## What Works Now
|
|
13
13
|
|
|
14
14
|
- Global hold hotkey on X11 for voice capture.
|
|
15
|
+
- Wayland compositor detection (GNOME/KDE/Sway/Hyprland) with compositor-specific runtime capability messaging.
|
|
16
|
+
- Wayland automation integration paths:
|
|
17
|
+
- text insertion via `wtype` or `ydotool`
|
|
18
|
+
- selected-text read via `wtype` + `wl-paste` when available
|
|
15
19
|
- Double-tap hotkey to cycle modes.
|
|
16
20
|
- Single-tap hotkey to open file-context picker.
|
|
17
21
|
- Voice-triggered toolchains for multi-step desktop actions.
|
|
22
|
+
- Visual toolchain editor in settings with dry-run and manual execution controls.
|
|
23
|
+
- Toolchain safety guardrails (dangerous shell pattern blocking).
|
|
18
24
|
- Four modes:
|
|
19
25
|
- Conversation
|
|
20
26
|
- Edit Text
|
|
@@ -43,6 +49,14 @@ The core idea matches the video workflow:
|
|
|
43
49
|
- Tray icon and settings window.
|
|
44
50
|
- GitHub release check from settings.
|
|
45
51
|
- In-app AppImage update download and launch.
|
|
52
|
+
- Optional in-app background update download to staged AppImage file.
|
|
53
|
+
- Local Whisper auto-setup on first use:
|
|
54
|
+
- managed `ffmpeg` in `~/.local/share/twokey/bin/`
|
|
55
|
+
- managed Whisper venv in `~/.local/share/twokey/whisper-venv/`
|
|
56
|
+
- Local Whisper tuning in settings:
|
|
57
|
+
- model selection (`tiny` .. `large-v3`)
|
|
58
|
+
- beam size tuning (speed vs quality)
|
|
59
|
+
- Visible install/save status in settings (including install spinner).
|
|
46
60
|
|
|
47
61
|
## Current Video Parity
|
|
48
62
|
|
|
@@ -58,14 +72,22 @@ Implemented from video behavior:
|
|
|
58
72
|
|
|
59
73
|
Still not fully equivalent to the video vision:
|
|
60
74
|
|
|
61
|
-
- Toolchains
|
|
75
|
+
- Toolchains have a visual settings editor; richer action templates and safety controls are still pending.
|
|
62
76
|
- Wayland still has compositor-specific limits for global hold hotkeys and full automation.
|
|
63
|
-
- Update install is
|
|
77
|
+
- Update install is AppImage-first with channel-aware checks (`stable`/`beta`/`dev`), checksum validation and local rollback fallback.
|
|
78
|
+
|
|
79
|
+
Current workflow action catalog:
|
|
80
|
+
|
|
81
|
+
- `open_url`
|
|
82
|
+
- `open_app`
|
|
83
|
+
- `shell` (with safety blocking for known dangerous patterns)
|
|
84
|
+
- `wait_ms`
|
|
85
|
+
- `check_command`
|
|
64
86
|
|
|
65
87
|
## Install
|
|
66
88
|
|
|
67
89
|
```bash
|
|
68
|
-
npm install twokey
|
|
90
|
+
npm install -g twokey
|
|
69
91
|
```
|
|
70
92
|
|
|
71
93
|
Run:
|
|
@@ -78,6 +100,7 @@ Default behavior:
|
|
|
78
100
|
|
|
79
101
|
- starts native desktop app in background
|
|
80
102
|
- if no native binary is installed, tries to download latest AppImage release
|
|
103
|
+
- prepares runtime dependencies in user space (best effort, no system package mutation)
|
|
81
104
|
|
|
82
105
|
Useful options:
|
|
83
106
|
|
|
@@ -86,8 +109,16 @@ twokey --help
|
|
|
86
109
|
twokey --cli
|
|
87
110
|
twokey --once "Erklaere X11 vs Wayland kurz"
|
|
88
111
|
twokey --desktop
|
|
112
|
+
twokey --prepare-runtime
|
|
113
|
+
twokey --prepare-runtime-only
|
|
89
114
|
```
|
|
90
115
|
|
|
116
|
+
Updater behavior:
|
|
117
|
+
|
|
118
|
+
- Uses update channel from settings (`stable`, `beta`, `dev`).
|
|
119
|
+
- Verifies AppImage checksum when a `.sha256` asset is available.
|
|
120
|
+
- Restores previous local AppImage if updated binary cannot be started.
|
|
121
|
+
|
|
91
122
|
## Development
|
|
92
123
|
|
|
93
124
|
Install dependencies:
|
|
@@ -114,6 +145,14 @@ npm run build
|
|
|
114
145
|
cd src-tauri && cargo check
|
|
115
146
|
```
|
|
116
147
|
|
|
148
|
+
Runtime smoke tests:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cargo test --manifest-path src-tauri/Cargo.toml runtime_e2e -- --nocapture
|
|
152
|
+
TWOKEY_E2E_SESSION=x11 cargo test --manifest-path src-tauri/Cargo.toml runtime_e2e -- --nocapture
|
|
153
|
+
TWOKEY_E2E_SESSION=wayland cargo test --manifest-path src-tauri/Cargo.toml runtime_e2e -- --nocapture
|
|
154
|
+
```
|
|
155
|
+
|
|
117
156
|
## Hotkey Behavior
|
|
118
157
|
|
|
119
158
|
Default hotkey can be changed in settings. Examples:
|
|
@@ -146,6 +185,8 @@ Configured in settings (`sttProvider`):
|
|
|
146
185
|
|
|
147
186
|
`external-command` needs `TWOKEY_STT_COMMAND` with `{audio}` placeholder.
|
|
148
187
|
|
|
188
|
+
For `local-whisper`, TwoKey attempts runtime setup in user space and checks required binaries before transcription.
|
|
189
|
+
|
|
149
190
|
Example:
|
|
150
191
|
|
|
151
192
|
```bash
|
|
@@ -171,6 +212,11 @@ Optional runtime tools:
|
|
|
171
212
|
- PDF extraction: `poppler-utils` (`pdftotext`)
|
|
172
213
|
- TTS backends: `spd-say` or `espeak-ng`/`espeak`
|
|
173
214
|
|
|
215
|
+
Notes:
|
|
216
|
+
|
|
217
|
+
- `ffmpeg` is auto-provisioned to `~/.local/share/twokey/bin/ffmpeg` if missing.
|
|
218
|
+
- Local Whisper CLI is auto-provisioned to `~/.local/share/twokey/whisper-venv/bin/whisper` if missing.
|
|
219
|
+
|
|
174
220
|
## Data Paths
|
|
175
221
|
|
|
176
222
|
- Config: `~/.config/twokey-ai/`
|
|
@@ -178,11 +224,17 @@ Optional runtime tools:
|
|
|
178
224
|
- Cache: `~/.cache/twokey-ai/`
|
|
179
225
|
- History DB: `~/.local/share/twokey-ai/history.db`
|
|
180
226
|
- Toolchains: `~/.config/twokey-ai/toolchains.json`
|
|
227
|
+
- Managed runtime bin: `~/.local/share/twokey/bin/`
|
|
228
|
+
- Managed Whisper venv: `~/.local/share/twokey/whisper-venv/`
|
|
181
229
|
|
|
182
230
|
## Repo
|
|
183
231
|
|
|
184
232
|
https://github.com/meinzeug/twokey
|
|
185
233
|
|
|
234
|
+
## Additional Docs
|
|
235
|
+
|
|
236
|
+
- Wayland matrix: `docs/WAYLAND_COMPATIBILITY_MATRIX.md`
|
|
237
|
+
|
|
186
238
|
## License
|
|
187
239
|
|
|
188
240
|
MIT
|
package/bin/postinstall.js
CHANGED
|
@@ -56,6 +56,10 @@ if (isRoot && sudoUser && sudoUser !== "root") {
|
|
|
56
56
|
delegated.on("error", () => process.exit(0));
|
|
57
57
|
delegated.on("close", () => process.exit(0));
|
|
58
58
|
} else {
|
|
59
|
+
if (isRoot) {
|
|
60
|
+
ensureSystemTtsBackend();
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
const runtimePrep = spawn(process.execPath, [cliPath, "--prepare-runtime-only", "--quiet"], {
|
|
60
64
|
stdio: "ignore",
|
|
61
65
|
shell: false,
|
|
@@ -103,3 +107,34 @@ function isDesktopRunningForUser(username) {
|
|
|
103
107
|
const out = spawnSync("pgrep", ["-u", username, "-f", "twokey-ai(\\.AppImage)?"], { encoding: "utf8" });
|
|
104
108
|
return out.status === 0;
|
|
105
109
|
}
|
|
110
|
+
|
|
111
|
+
function ensureSystemTtsBackend() {
|
|
112
|
+
if (hasAnyTtsBackend()) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!commandExists("apt-get")) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = spawnSync("apt-get", ["install", "-y", "espeak-ng"], {
|
|
122
|
+
stdio: "ignore",
|
|
123
|
+
shell: false,
|
|
124
|
+
});
|
|
125
|
+
if (result.status !== 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// best effort
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function hasAnyTtsBackend() {
|
|
134
|
+
return commandExists("spd-say") || commandExists("espeak-ng") || commandExists("espeak");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function commandExists(name) {
|
|
138
|
+
const out = spawnSync("sh", ["-c", `command -v ${name} >/dev/null 2>&1`], { encoding: "utf8" });
|
|
139
|
+
return out.status === 0;
|
|
140
|
+
}
|
package/bin/twokey.js
CHANGED
|
@@ -375,6 +375,7 @@ async function ensureRuntimeDependencies() {
|
|
|
375
375
|
await fs.promises.mkdir(APPIMAGE_DIR, { recursive: true });
|
|
376
376
|
await ensureManagedFfmpeg();
|
|
377
377
|
await ensureManagedWhisperCli();
|
|
378
|
+
await ensureManagedTtsBackend();
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
async function ensureManagedFfmpeg() {
|
|
@@ -426,6 +427,24 @@ async function ensureManagedWhisperCli() {
|
|
|
426
427
|
}
|
|
427
428
|
}
|
|
428
429
|
|
|
430
|
+
async function ensureManagedTtsBackend() {
|
|
431
|
+
if (await commandExists("spd-say") || await commandExists("espeak-ng") || await commandExists("espeak")) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!isRoot()) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (await commandExists("apt-get")) {
|
|
440
|
+
try {
|
|
441
|
+
await runCommand("apt-get", ["install", "-y", "espeak-ng"]);
|
|
442
|
+
} catch {
|
|
443
|
+
// best effort only
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
429
448
|
async function commandExists(name) {
|
|
430
449
|
return new Promise((resolve) => {
|
|
431
450
|
const child = spawn("sh", ["-c", `command -v ${name} >/dev/null 2>&1`], {
|
|
@@ -437,6 +456,10 @@ async function commandExists(name) {
|
|
|
437
456
|
});
|
|
438
457
|
}
|
|
439
458
|
|
|
459
|
+
function isRoot() {
|
|
460
|
+
return typeof process.getuid === "function" && process.getuid() === 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
440
463
|
async function runCommand(program, commandArgs) {
|
|
441
464
|
return new Promise((resolve, reject) => {
|
|
442
465
|
const child = spawn(program, commandArgs, {
|
|
@@ -471,6 +494,11 @@ function withManagedBinPath(baseEnv) {
|
|
|
471
494
|
parts.unshift(APPIMAGE_DIR);
|
|
472
495
|
}
|
|
473
496
|
env.PATH = parts.join(":");
|
|
497
|
+
// Remove variables that interfere with Python virtual environments.
|
|
498
|
+
// An inherited PYTHONHOME or PYTHONPATH causes the venv interpreter to look
|
|
499
|
+
// for the standard library (including "encodings") in the wrong location.
|
|
500
|
+
delete env.PYTHONHOME;
|
|
501
|
+
delete env.PYTHONPATH;
|
|
474
502
|
return env;
|
|
475
503
|
}
|
|
476
504
|
|