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 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 are implemented, but no visual workflow builder exists yet.
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 available for AppImage, but no signed rollback-capable updater pipeline yet.
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
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twokey",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Linux-first desktop AI assistant built with Tauri, React, and TypeScript.",
5
5
  "license": "MIT",
6
6
  "repository": {