@arcforgelabs/dictate 2026.6.18 → 2026.7.4-unstable.2.1

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
@@ -6,18 +6,19 @@ Desktop dictation that types into the focused app.
6
6
  speak, and it transcribes into whatever app you are already using.
7
7
 
8
8
  Current status: early desktop app. Linux installs, Windows 11 source installs,
9
- tray controls, startup integration, recent history, model selection, API key
10
- storage, update, and uninstall paths are implemented. Windows Store/signed
11
- installer packaging is being prepared; see `docs/goal.md`.
9
+ tray controls, startup integration, local dictation history, update, and
10
+ uninstall paths are implemented. Microsoft Store packaging and submission
11
+ automation are maintained separately from GitHub releases; see
12
+ `docs/msstore-automation.md`. The current transcription/model deployment plan is
13
+ `docs/TRANSCRIPTION_PLAN.md`.
12
14
 
13
15
  ## Install
14
16
 
15
17
  Windows 11 normal install:
16
18
 
17
- The target public channel is Microsoft Store distribution. Until that listing is
18
- ready, use the GitHub release installer artifacts for internal validation only.
19
- The PowerShell bootstrap installer below is a developer/source path, not the
20
- normal public install route.
19
+ The target public channel is Microsoft Store distribution. GitHub release
20
+ artifacts and the PowerShell bootstrap installer below are developer/source or
21
+ internal validation paths, not the normal public Windows install route.
21
22
 
22
23
  Windows developer/source install from the hosted bootstrap:
23
24
 
@@ -25,15 +26,21 @@ Windows developer/source install from the hosted bootstrap:
25
26
  powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@latest/install.ps1 | iex"
26
27
  ```
27
28
 
29
+ Unstable developer/source bootstrap for pre-stable feature testing:
30
+
31
+ ```powershell
32
+ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@unstable/install.ps1 | iex"
33
+ ```
34
+
28
35
  Open **Dictate** from the Start Menu after install.
29
36
 
30
- Ubuntu/Debian source install:
37
+ Linux default install (per-user, no sudo for app updates):
31
38
 
32
39
  ```bash
33
40
  ./install-ubuntu.sh
34
41
  ```
35
42
 
36
- Generic Linux source install:
43
+ Generic Linux user install:
37
44
 
38
45
  ```bash
39
46
  ./install.sh
@@ -41,6 +48,14 @@ Generic Linux source install:
41
48
 
42
49
  Open **Dictate** from the app launcher after install.
43
50
 
51
+ Linux system package install is also supported when you explicitly want a
52
+ machine-wide `.deb` install:
53
+
54
+ ```bash
55
+ DICTATE_BUNDLES=deb scripts/build-linux-desktop.sh
56
+ ./install.sh --system
57
+ ```
58
+
44
59
  Windows developer/source install, from the repo/source directory:
45
60
 
46
61
  ```powershell
@@ -60,15 +75,13 @@ npx @arcforgelabs/dictate install
60
75
 
61
76
  ```text
62
77
  Open Dictate
63
- Select Model
64
- Set API key if using a hosted model
65
- Set push-to-talk shortcut if desired
78
+ Use the mic button or configured push-to-talk shortcut
66
79
  Hold shortcut, speak, release
67
- Review Recent History when needed
80
+ Review Dictations when needed
68
81
  ```
69
82
 
70
83
  By default, Dictate installs a normal app launcher entry and starts on sign-in.
71
- Startup can be changed from Settings.
84
+ Advanced configuration is available through the `dictate config` CLI.
72
85
 
73
86
  ## Update And Uninstall
74
87
 
@@ -79,6 +92,20 @@ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/
79
92
  powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@latest/uninstall.ps1 | iex"
80
93
  ```
81
94
 
95
+ To test updates before they are promoted to stable:
96
+
97
+ ```powershell
98
+ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@unstable/update.ps1 | iex"
99
+ ```
100
+
101
+ Users on the app's npm-backed update path can opt into or out of unstable
102
+ updates from the CLI:
103
+
104
+ ```bash
105
+ dictate config set-update-channel unstable
106
+ dictate config set-update-channel stable
107
+ ```
108
+
82
109
  Windows from source:
83
110
 
84
111
  ```powershell
@@ -86,13 +113,19 @@ powershell -ExecutionPolicy Bypass -File .\update-windows.ps1
86
113
  powershell -ExecutionPolicy Bypass -File .\uninstall-windows.ps1
87
114
  ```
88
115
 
89
- Linux:
116
+ Linux user install:
90
117
 
91
118
  ```bash
92
119
  ./update.sh
93
120
  ./uninstall.sh
94
121
  ```
95
122
 
123
+ Linux system package update:
124
+
125
+ ```bash
126
+ ./update.sh --system
127
+ ```
128
+
96
129
  Use `-RemoveUserData` on Windows or `--remove-user-data` on Linux only when you
97
130
  also want to remove config, logs, history, and downloaded model data.
98
131
 
@@ -102,23 +135,37 @@ also want to remove config, logs, history, and downloaded model data.
102
135
  - Runs as a tray app
103
136
  - Types dictated text into the focused app
104
137
  - Supports configurable push-to-talk
105
- - Shows selected model/status in the app UI
138
+ - Presents a simple capture-first desktop UI
106
139
  - Supports launch on startup
107
- - Stores hosted-provider API keys in the OS secret store
108
- - Keeps a small Recent History for copy/paste recovery
140
+ - Stores CLI-configured hosted-provider API keys in the OS secret store
141
+ - Keeps a small local dictations history for copy/paste recovery
109
142
  - Provides installer, updater, uninstaller, and doctor paths
110
143
 
111
144
  ## Models
112
145
 
113
- Supported provider defaults:
114
-
115
- - `faster-whisper/turbo` for local transcription
116
- - `openai/gpt-4o-mini-transcribe`
117
- - `xai/grok-speech-to-text`
118
- - `gemini/gemini-3-flash-preview`
119
-
120
- Local transcription can use CPU or GPU where supported. Hosted providers require
121
- an API key before they can be selected.
146
+ The default local English path is Parakeet where the runtime is available. The
147
+ desktop UI keeps engine names out of the primary workflow; advanced users and
148
+ tests can still configure explicit local or hosted providers through CLI options
149
+ and `dictate config`.
150
+
151
+ GPU lanes are explicit:
152
+
153
+ - NVIDIA CUDA: install with the `gpu` extra and verify with
154
+ `dictate doctor --stt-backend parakeet --device cuda --quick`.
155
+ - Windows AMD GPU: install with the `amd` extra for ONNX Runtime DirectML and
156
+ verify with `dictate doctor --stt-backend parakeet --device amd --quick`.
157
+ - Linux AMD GPU: install a ROCm/MIGraphX-capable ONNX Runtime build, then verify
158
+ with `dictate doctor --stt-backend parakeet --device amd --quick`.
159
+ - Meeting uses a dedicated speaker-attribution lane. Inspect it with
160
+ `dictate config show`; set it with
161
+ `dictate config set-meeting-model parakeet-pyannote/parakeet-tdt-0.6b-v2`.
162
+ Source installs can add pyannote support with `./install.sh --meeting` or
163
+ `.\install-windows.ps1 -Meeting`, then verify with
164
+ `dictate doctor --stt-backend parakeet-pyannote --device cuda --quick`.
165
+ Experimental preflight targets also exist for
166
+ `parakeet-diarizen/parakeet-tdt-0.6b-v2` and
167
+ `parakeet-sortformer/parakeet-tdt-0.6b-v2`; these still require their
168
+ runtime-specific DiariZen or NeMo setup before selection.
122
169
 
123
170
  ## Commands
124
171
 
@@ -132,11 +179,13 @@ dictate doctor --quick --fix
132
179
  dictate doctor --check-model-load
133
180
  ```
134
181
 
135
- Hotword and model options are available from Settings. CLI flags still exist for
136
- automation and testing:
182
+ Advanced configuration remains available for automation and testing:
137
183
 
138
184
  ```bash
139
185
  dictate --stt-backend faster-whisper --model turbo
186
+ dictate config show
187
+ dictate config set-provider online
188
+ dictate config set-key xai xai-YOUR_KEY_HERE
140
189
  dictate --stt-backend openai --model gpt-4o-mini-transcribe
141
190
  dictate --stt-backend xai --model grok-speech-to-text
142
191
  dictate --stt-backend gemini --model gemini-3-flash-preview
@@ -164,33 +213,45 @@ and should not be packaged into the public repo default config.
164
213
  ## Safety
165
214
 
166
215
  - Dictate does not intentionally write raw API keys to `config.yaml`.
167
- - API keys configured in the app use the OS secret store.
216
+ - API keys configured through the CLI use the OS secret store.
168
217
  - Dictation text can be sensitive; check logs and issue reports before sharing.
169
218
  - Important transcriptions should be verified before relying on them.
170
219
  - Support and maintenance are best-effort.
171
220
 
172
221
  ## Desktop UI — the Quiet Console (preview)
173
222
 
174
- A design-system desktop Settings window is being built alongside the tray:
223
+ A quiet desktop window sits alongside the tray — a capture home (the mic is the
224
+ record button), the notes list, and the ⌘K palette; no settings menu (config via
225
+ the `dictate config` CLI).
175
226
 
176
- - [`ui/`](ui/README.md) — React/Vite front-end (the Quiet Console: seven views,
177
- ⌘K palette, listening HUD, light/dark, GNOME/KDE chrome).
227
+ - [`ui/`](ui/README.md) — React/Vite front-end. For UI work, **`cd ui && npm run
228
+ dev`** opens the whole app at `http://localhost:5173` against a built-in mock
229
+ (no engine, no Tauri, no STT) — the fast UI loop in a browser. Details, and
230
+ when to use the Tauri shell instead, are in that README's *Develop* section.
178
231
  - [`ui-shell/`](ui-shell/README.md) — Tauri 2 shell that hosts it on Linux.
179
232
  - `src/dictate/ui_server.py` — the loopback control server the UI talks to; the
180
- tray's **Open Settings…** launches the shell (falling back to native dialogs).
233
+ tray can launch the shell for the desktop capture surface.
181
234
  - See [`design/PLAN.md`](design/PLAN.md) for the cross-platform plan.
182
235
 
183
- **Install it like a normal app:** tagged releases attach a **self-contained**
184
- Linux **`.deb`** and **`.AppImage`** they bundle the frozen Python engine
185
- inside (PyInstaller sidecar), so there's no separate Python/pip step. Download,
186
- install, launch; speech models download on first use.
236
+ **Install it like a normal app:** the default Linux channel is a per-user install
237
+ under `~/.local/share/dictate` with launchers in `~/.local/bin` and
238
+ `~/.local/share/applications`. App updates do not need sudo.
239
+
240
+ ```bash
241
+ ./install.sh
242
+ ./update.sh
243
+ ```
244
+
245
+ Tagged releases can also attach a **self-contained** Linux **`.deb`** for users
246
+ who explicitly want a system package. It bundles the frozen Python engine inside
247
+ (PyInstaller sidecar), so there's no separate Python/pip step:
187
248
 
188
249
  ```bash
189
- sudo apt install ./dictate_*_amd64.deb # or: chmod +x Dictate_*.AppImage && ./Dictate_*.AppImage
250
+ sudo apt install ./Dictate_*_amd64.deb
190
251
  ```
191
252
 
192
- The app lives in the tray (Open Settings / Quit) and does push-to-talk straight
193
- away. Build the package yourself in one step with
253
+ The app lives in the tray and desktop shell and does push-to-talk straight away.
254
+ Build the package yourself in one step with
194
255
  [`scripts/build-linux-desktop.sh`](scripts/build-linux-desktop.sh) — see
195
256
  [`ui-shell/README.md`](ui-shell/README.md). The `pip`/`install.sh` route remains
196
257
  for source/dev installs.
@@ -198,13 +259,14 @@ for source/dev installs.
198
259
  ## Docs
199
260
 
200
261
  - [Windows 11 support](docs/windows-11.md)
201
- - [Recent History spec](docs/recent-dictation-history-spec.md)
262
+ - [Transcription deployment plan](docs/TRANSCRIPTION_PLAN.md)
202
263
  - [Release/versioning](docs/release-versioning.md)
203
264
  - [Desktop packaging & CI runbook](docs/desktop-packaging.md)
204
265
  - [Microsoft Store automation](docs/msstore-automation.md)
205
266
  - [Microsoft Store listing draft](docs/msstore-listing.md)
206
- - [Windows release goal](docs/goal.md)
267
+ - [Deployment security](docs/deployment-security.md)
207
268
  - [Development streams](docs/development-streams.md)
269
+ - [Archived docs index](docs/archive/README.md)
208
270
  - [Security policy](SECURITY.md)
209
271
 
210
272
  ## License
Binary file
@@ -1,10 +1,20 @@
1
1
  hotwords: []
2
2
  push_to_talk_combo: ctrl_r
3
- stt_backend: faster-whisper
4
3
  stt_compute_type: int8
5
4
  stt_device: auto
6
- # Local Whisper uses the single supported local model:
5
+ # Leave stt_backend/stt_model unset for the hardware-aware local default:
6
+ # English Parakeet v2 on CPU, CUDA, and AMD when the Parakeet runtime is
7
+ # available. Accelerator readiness is checked by doctor/preflight.
8
+ # Set stt_backend/stt_model explicitly to override:
9
+ # stt_backend: parakeet
10
+ # stt_model: parakeet-tdt-0.6b-v2
11
+ # Meeting mode uses a separate speaker-attribution lane. Leave these unset for
12
+ # the default local Parakeet+pyannote Meeting backend, or set them explicitly.
13
+ # meeting_stt_backend: parakeet-pyannote
14
+ # meeting_stt_model: parakeet-tdt-0.6b-v2
15
+ # stt_backend: faster-whisper
7
16
  # stt_model: turbo
17
+ # stt_model: large-v3
8
18
  # To avoid local compute, set one hosted backend:
9
19
  # stt_backend: openai
10
20
  # stt_model: gpt-4o-mini-transcribe
@@ -3,6 +3,7 @@ param(
3
3
  [switch]$NoPrepareTurbo,
4
4
  [switch]$NoShortcut,
5
5
  [switch]$NoStartup,
6
+ [switch]$Meeting,
6
7
  [switch]$RecreateVenv
7
8
  )
8
9
 
@@ -257,7 +258,7 @@ function Register-InstalledApp {
257
258
  $keyPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Dictate"
258
259
  New-Item -Force -Path $keyPath | Out-Null
259
260
  New-ItemProperty -Force -Path $keyPath -Name "DisplayName" -Value "Dictate" -PropertyType String | Out-Null
260
- New-ItemProperty -Force -Path $keyPath -Name "DisplayVersion" -Value "2026.6.18" -PropertyType String | Out-Null
261
+ New-ItemProperty -Force -Path $keyPath -Name "DisplayVersion" -Value "2026.7.4" -PropertyType String | Out-Null
261
262
  New-ItemProperty -Force -Path $keyPath -Name "Publisher" -Value "Arc Forge Labs" -PropertyType String | Out-Null
262
263
  New-ItemProperty -Force -Path $keyPath -Name "InstallLocation" -Value $InstallLocation -PropertyType String | Out-Null
263
264
  if (Test-Path $DisplayIcon) {
@@ -383,7 +384,12 @@ if (-not (Test-Path $venvPython)) {
383
384
  }
384
385
 
385
386
  Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "pip", "install", "--upgrade", "pip") -Description "Upgrading pip"
386
- Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "pip", "install", "-e", "${PSScriptRoot}[windows]") -Description "Installing Dictate Windows package"
387
+ $installExtras = @("windows")
388
+ if ($Meeting) {
389
+ $installExtras += "meeting"
390
+ }
391
+ $installTarget = "${PSScriptRoot}[$($installExtras -join ',')]"
392
+ Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "pip", "install", "-e", $installTarget) -Description "Installing Dictate Windows package"
387
393
 
388
394
  Seed-Config
389
395
  Write-LauncherScripts -ScriptsDir $scriptsDir
@@ -395,7 +401,10 @@ Install-StartupShortcut -TargetPath $wscript -Arguments $trayArgs -WorkingDirect
395
401
  Register-InstalledApp -InstallLocation $PSScriptRoot -DisplayIcon (Join-Path $PSScriptRoot "assets\dictate.ico")
396
402
 
397
403
  if (-not $NoPrepareTurbo) {
398
- Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "dictate", "prepare-model", "--stt-backend", "faster-whisper", "--model", "turbo", "--device", "auto", "--compute-type", "int8") -Description "Preparing faster-whisper turbo model"
404
+ # Keep the legacy switch name for installer compatibility, but prepare the
405
+ # fresh local default: Parakeet v2. Explicit faster-whisper users can still
406
+ # prepare that backend manually.
407
+ Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "dictate", "prepare-model", "--stt-backend", "parakeet", "--device", "auto", "--compute-type", "int8") -Description "Preparing Parakeet model"
399
408
  }
400
409
 
401
410
  if (-not $NoVerify) {
package/install.ps1 CHANGED
@@ -10,10 +10,10 @@ param(
10
10
  )
11
11
 
12
12
  $ErrorActionPreference = "Stop"
13
- $DictateVersion = "2026.6.18"
13
+ $DictateVersion = "2026.7.4-unstable.2.1"
14
14
 
15
15
  if (-not $ArchiveUrl) {
16
- $ArchiveUrl = "https://github.com/arcforgelabs/dictate/archive/refs/tags/v$DictateVersion.zip"
16
+ $ArchiveUrl = "https://github.com/arcforgelabs/dictate/archive/69118505102a569ed2787e6457b425e205ff8b5c.zip"
17
17
  }
18
18
 
19
19
  if (-not $InstallRoot) {
package/install.sh CHANGED
@@ -11,6 +11,8 @@ DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
11
11
  AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
12
12
  ICON_DIR="$INSTALL_DIR/share/icons"
13
13
  ICON_PATH="$ICON_DIR/dictate-simple.png"
14
+ APP_ICON_SOURCE="$SCRIPT_DIR/ui-shell/src-tauri/icons/icon.png"
15
+ DESKTOP_PATH="$DESKTOP_DIR/dictate.desktop"
14
16
  CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/dictate"
15
17
  CONFIG_PATH="$CONFIG_DIR/config.yaml"
16
18
  DEFAULT_CONFIG_SOURCE="$SCRIPT_DIR/config/default-config.yaml"
@@ -19,6 +21,9 @@ PREPARE_TURBO=1
19
21
  SEED_DEFAULT_CONFIG=1
20
22
  STARTUP=1
21
23
  INSTALL_UI=1
24
+ INSTALL_GPU="${DICTATE_INSTALL_GPU:-0}"
25
+ INSTALL_MEETING="${DICTATE_INSTALL_MEETING:-0}"
26
+ INSTALL_SCOPE="user"
22
27
  PYTHON_BIN="${PYTHON_BIN:-python3}"
23
28
 
24
29
  # Prefer the distro Python so --system-site-packages can see modules such as
@@ -29,17 +34,58 @@ fi
29
34
 
30
35
  usage() {
31
36
  cat <<EOF
32
- Usage: $0 [--no-verify] [--no-prepare-turbo] [--no-seed-default-config] [--no-startup] [--no-ui] [--session-backend auto|x11|wayland]
37
+ Usage: $0 [--user|--system] [--gpu] [--meeting] [--no-verify] [--no-prepare-turbo] [--no-seed-default-config] [--no-startup] [--no-ui] [--session-backend auto|x11|wayland]
33
38
 
34
- Installs dictate into ~/.local/share/dictate, links ~/.local/bin/dictate and
39
+ Default: --user.
40
+
41
+ --user installs dictate into ~/.local/share/dictate, links ~/.local/bin/dictate and
35
42
  ~/.local/bin/dictate-ui-server, seeds the default config on first install,
36
- creates app launcher/autostart entries, prepares the faster-whisper turbo model,
43
+ creates app launcher/autostart entries, prepares the Parakeet local model,
37
44
  and installs the desktop "Quiet Console" UI shell when a build toolchain is
38
45
  present (unless disabled). All steps degrade gracefully when prerequisites are
39
46
  missing.
47
+
48
+ --system installs a Linux desktop package into system paths using apt/pkexec or
49
+ sudo. It is intentionally explicit because it requires administrator approval.
50
+
51
+ --gpu installs Dictate's GPU optional dependencies for local CUDA/ONNX Runtime
52
+ testing on capable machines.
53
+
54
+ --meeting installs Dictate's pyannote/torch optional dependencies for the
55
+ parakeet-pyannote Meeting lane. DiariZen and Sortformer still require their
56
+ runtime-specific setup before selecting those experimental lanes.
40
57
  EOF
41
58
  }
42
59
 
60
+ install_system_package() {
61
+ if [ "$(uname -s)" != "Linux" ]; then
62
+ echo "--system is only supported by this installer on Linux."
63
+ exit 2
64
+ fi
65
+
66
+ local deb_path="${DICTATE_DEB:-}"
67
+ if [ -z "$deb_path" ]; then
68
+ deb_path="$(find "$SCRIPT_DIR/ui-shell/src-tauri/target/release/bundle/deb" -maxdepth 1 -name 'Dictate_*_amd64.deb' 2>/dev/null | sort | tail -n 1 || true)"
69
+ fi
70
+ if [ -z "$deb_path" ] || [ ! -f "$deb_path" ]; then
71
+ echo "No local .deb found. Build one first:"
72
+ echo " DICTATE_BUNDLES=deb scripts/build-linux-desktop.sh"
73
+ echo "Or pass one explicitly:"
74
+ echo " DICTATE_DEB=/path/to/Dictate_..._amd64.deb $0 --system"
75
+ exit 1
76
+ fi
77
+
78
+ echo "Installing system package: $deb_path"
79
+ if command -v pkexec >/dev/null 2>&1; then
80
+ pkexec apt-get install -y "$deb_path"
81
+ elif command -v sudo >/dev/null 2>&1; then
82
+ sudo apt-get install -y "$deb_path"
83
+ else
84
+ echo "Need pkexec or sudo to install a system package."
85
+ exit 1
86
+ fi
87
+ }
88
+
43
89
  detect_session_backend() {
44
90
  if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
45
91
  printf 'wayland\n'
@@ -79,6 +125,24 @@ while [ "$#" -gt 0 ]; do
79
125
  --no-ui)
80
126
  INSTALL_UI=0
81
127
  ;;
128
+ --gpu)
129
+ INSTALL_GPU=1
130
+ ;;
131
+ --no-gpu)
132
+ INSTALL_GPU=0
133
+ ;;
134
+ --meeting)
135
+ INSTALL_MEETING=1
136
+ ;;
137
+ --no-meeting)
138
+ INSTALL_MEETING=0
139
+ ;;
140
+ --user)
141
+ INSTALL_SCOPE="user"
142
+ ;;
143
+ --system)
144
+ INSTALL_SCOPE="system"
145
+ ;;
82
146
  --session-backend)
83
147
  shift
84
148
  SESSION_BACKEND="${1:-}"
@@ -98,17 +162,33 @@ while [ "$#" -gt 0 ]; do
98
162
  shift
99
163
  done
100
164
 
165
+ if [ "$INSTALL_SCOPE" = "system" ]; then
166
+ install_system_package
167
+ exit 0
168
+ fi
169
+
101
170
  if [ "$SESSION_BACKEND" = "auto" ]; then
102
171
  SESSION_BACKEND="$(detect_session_backend)"
103
172
  fi
104
173
 
105
- PIP_TARGET="$SCRIPT_DIR"
174
+ EXTRAS=()
106
175
  if [ "$SESSION_BACKEND" = "x11" ]; then
107
- PIP_TARGET="${SCRIPT_DIR}[x11]"
176
+ EXTRAS+=("x11")
108
177
  elif [ "$SESSION_BACKEND" = "wayland" ]; then
109
- PIP_TARGET="${SCRIPT_DIR}[wayland]"
178
+ EXTRAS+=("wayland")
110
179
  elif [ "$SESSION_BACKEND" = "unknown" ]; then
111
- PIP_TARGET="${SCRIPT_DIR}[x11,wayland]"
180
+ EXTRAS+=("x11" "wayland")
181
+ fi
182
+ if [ "$INSTALL_GPU" -eq 1 ]; then
183
+ EXTRAS+=("gpu")
184
+ fi
185
+ if [ "$INSTALL_MEETING" -eq 1 ]; then
186
+ EXTRAS+=("meeting")
187
+ fi
188
+ PIP_TARGET="$SCRIPT_DIR"
189
+ if [ "${#EXTRAS[@]}" -gt 0 ]; then
190
+ extras_csv="$(IFS=,; printf '%s' "${EXTRAS[*]}")"
191
+ PIP_TARGET="${SCRIPT_DIR}[${extras_csv}]"
112
192
  fi
113
193
 
114
194
  echo "Detected install session backend: $SESSION_BACKEND"
@@ -124,6 +204,34 @@ elif command -v rpm >/dev/null 2>&1 && rpm -q dictate >/dev/null 2>&1; then
124
204
  echo " Use one install method. To remove the package first: sudo dnf remove dictate"
125
205
  fi
126
206
 
207
+ # Stop any running Dictate engine before we overwrite it, so the new install can
208
+ # claim the single-instance lock cleanly instead of colliding with a stale daemon.
209
+ stop_running_dictate() {
210
+ # Prefer the installed CLI's own clean stop; it knows the lock location.
211
+ if command -v dictate >/dev/null 2>&1 && dictate stop --quiet 2>/dev/null; then
212
+ return 0
213
+ fi
214
+ # Fallback (older builds without `dictate stop`): kill via the lock PID file.
215
+ local lockdir
216
+ if [ -n "${XDG_RUNTIME_DIR:-}" ]; then
217
+ lockdir="$XDG_RUNTIME_DIR/dictate-daemon.lockdir"
218
+ else
219
+ lockdir="/tmp/dictate-daemon-$(id -u).lockdir"
220
+ fi
221
+ local pidfile="$lockdir/pid"
222
+ [ -f "$pidfile" ] || return 0
223
+ local pid
224
+ pid="$(cat "$pidfile" 2>/dev/null || true)"
225
+ [ -n "$pid" ] || return 0
226
+ if kill -0 "$pid" 2>/dev/null; then
227
+ kill "$pid" 2>/dev/null || true
228
+ for _ in 1 2 3 4 5 6 7 8 9 10; do kill -0 "$pid" 2>/dev/null || break; sleep 0.3; done
229
+ kill -9 "$pid" 2>/dev/null || true
230
+ fi
231
+ }
232
+ echo "Stopping any running Dictate engine ..."
233
+ stop_running_dictate
234
+
127
235
  echo "Creating venv at $INSTALL_DIR ..."
128
236
  uv venv "$INSTALL_DIR/venv" --python "$PYTHON_BIN" --system-site-packages --quiet
129
237
 
@@ -139,12 +247,16 @@ ln -sf "$INSTALL_DIR/venv/bin/dictate-ui-server" "$BIN_DIR/dictate-ui-server"
139
247
  echo "Installing icon ..."
140
248
  mkdir -p "$ICON_DIR"
141
249
  rm -f "$ICON_DIR/dictate-controls.png" "$ICON_DIR/dictate.png"
142
- install -m 644 "$SCRIPT_DIR/assets/dictate.png" "$ICON_PATH"
250
+ if [ -f "$APP_ICON_SOURCE" ]; then
251
+ install -m 644 "$APP_ICON_SOURCE" "$ICON_PATH"
252
+ else
253
+ install -m 644 "$SCRIPT_DIR/assets/dictate.png" "$ICON_PATH"
254
+ fi
143
255
 
144
256
  echo "Installing desktop entry ..."
145
257
  mkdir -p "$DESKTOP_DIR"
146
- rm -f "$DESKTOP_DIR/dictate-settings.desktop"
147
- cat > "$DESKTOP_DIR/dictate.desktop" <<EOF
258
+ rm -f "$DESKTOP_DIR/dictate-settings.desktop" "$DESKTOP_DIR/Dictate.desktop" "$DESKTOP_DIR/dictate.desktop"
259
+ cat > "$DESKTOP_PATH" <<EOF
148
260
  [Desktop Entry]
149
261
  Name=Dictate
150
262
  Comment=Dictate into the focused app
@@ -154,6 +266,10 @@ Type=Application
154
266
  Categories=AudioVideo;Audio;
155
267
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
156
268
  Terminal=false
269
+ # The Tauri shell window reports app_id "dictate-ui-shell" (GTK prgname). Without
270
+ # this, GNOME can't match the running window to this launcher and shows a second,
271
+ # icon-less "dictate-ui-shell" entry with a generic fallback icon.
272
+ StartupWMClass=dictate-ui-shell
157
273
  EOF
158
274
 
159
275
  update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
@@ -171,6 +287,7 @@ Type=Application
171
287
  Categories=AudioVideo;Audio;
172
288
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
173
289
  Terminal=false
290
+ StartupWMClass=dictate-ui-shell
174
291
  X-GNOME-Autostart-enabled=true
175
292
  EOF
176
293
  fi
@@ -189,10 +306,6 @@ install_desktop_ui() {
189
306
  local shell_src="$SCRIPT_DIR/ui-shell"
190
307
  local target="$BIN_DIR/dictate-ui-shell"
191
308
 
192
- if command -v dictate-ui-shell >/dev/null 2>&1; then
193
- echo "Desktop UI shell already installed: $(command -v dictate-ui-shell)"
194
- return 0
195
- fi
196
309
  if [ ! -d "$shell_src" ]; then
197
310
  echo "Desktop UI shell sources not present; skipping the optional UI."
198
311
  print_ui_hint
@@ -209,22 +322,58 @@ install_desktop_ui() {
209
322
  npm --prefix "$SCRIPT_DIR/ui" ci >/dev/null 2>&1 \
210
323
  || npm --prefix "$SCRIPT_DIR/ui" install >/dev/null 2>&1 \
211
324
  || { echo "UI front-end install failed; skipping the optional shell."; print_ui_hint; return 0; }
212
- if ! npm --prefix "$SCRIPT_DIR/ui" run build >/dev/null 2>&1; then
213
- echo "UI front-end build failed; skipping the optional shell."; print_ui_hint; return 0
214
- fi
215
- if ! ( cd "$shell_src" && cargo build --release --manifest-path src-tauri/Cargo.toml ); then
325
+ npm --prefix "$shell_src" ci >/dev/null 2>&1 \
326
+ || npm --prefix "$shell_src" install >/dev/null 2>&1 \
327
+ || { echo "UI shell tooling install failed; skipping the optional shell."; print_ui_hint; return 0; }
328
+ # Tauri validates configured bundle resources even for --no-bundle builds.
329
+ # Stage a per-user engine wrapper before the build so the resource glob exists,
330
+ # then install the same wrapper next to the shell binary below.
331
+ local bundled_engine_dir="$shell_src/src-tauri/engine"
332
+ mkdir -p "$bundled_engine_dir"
333
+ cat > "$bundled_engine_dir/dictate-engine" <<WRAP
334
+ #!/bin/sh
335
+ exec "$INSTALL_DIR/venv/bin/dictate" "\$@"
336
+ WRAP
337
+ chmod 755 "$bundled_engine_dir/dictate-engine"
338
+ # Build through the Tauri CLI, NOT a raw `cargo build`. The CLI enables the
339
+ # `custom-protocol` feature that embeds the frontend and serves it over the
340
+ # app protocol. A plain cargo build omits that feature, so the shell falls
341
+ # back to loading the dev server (localhost:5173) and shows "Could not connect
342
+ # to localhost: Connection refused" at runtime. --no-bundle: we only need the
343
+ # binary here, not a .deb/AppImage. (beforeBuildCommand rebuilds ui/dist.)
344
+ if ! ( cd "$shell_src" && npm run tauri -- build --no-bundle ); then
216
345
  echo "Desktop UI shell build failed; the engine + tray remain fully functional."
217
346
  print_ui_hint
218
347
  return 0
219
348
  fi
220
349
  install -m 755 "$shell_src/src-tauri/target/release/dictate-ui-shell" "$target"
221
350
  echo "Installed desktop UI shell: $target"
351
+
352
+ # Give the shell an absolute-path engine right next to it. The shell's
353
+ # bundled-engine lookup checks <exe_dir>/engine first — before any system
354
+ # path (e.g. a leftover /usr/lib/Dictate from an old .deb) and independent of
355
+ # the minimal PATH a desktop launcher provides. Pointing it at the per-user
356
+ # venv means one process both dictates and serves, and keeps sys.executable
357
+ # inside ~/.local so the install reads as "linux-user" and the in-app updater
358
+ # never needs sudo/pkexec.
359
+ local engine_dir="$BIN_DIR/engine"
360
+ mkdir -p "$engine_dir"
361
+ install -m 755 "$bundled_engine_dir/dictate-engine" "$engine_dir/dictate-engine"
362
+ echo "Installed per-user engine launcher: $engine_dir/dictate-engine"
222
363
  }
223
364
 
224
365
  if [ "$INSTALL_UI" -eq 1 ]; then
225
366
  install_desktop_ui
226
367
  fi
227
368
 
369
+ if [ -x "$BIN_DIR/dictate-ui-shell" ]; then
370
+ echo "Pointing launcher entries at the desktop UI shell ..."
371
+ sed -i "s|^Exec=.*|Exec=$BIN_DIR/dictate-ui-shell|" "$DESKTOP_PATH"
372
+ if [ "$STARTUP" -eq 1 ] && [ -f "$AUTOSTART_DIR/dictate.desktop" ]; then
373
+ sed -i "s|^Exec=.*|Exec=$BIN_DIR/dictate-ui-shell|" "$AUTOSTART_DIR/dictate.desktop"
374
+ fi
375
+ fi
376
+
228
377
  if [ "$SEED_DEFAULT_CONFIG" -eq 1 ]; then
229
378
  if [ ! -f "$CONFIG_PATH" ]; then
230
379
  if [ ! -f "$DEFAULT_CONFIG_SOURCE" ]; then
@@ -269,14 +418,15 @@ run_logged_check() {
269
418
 
270
419
  if [ "$PREPARE_TURBO" -eq 1 ]; then
271
420
  PREPARE_LOG="/tmp/dictate-install-prepare.log"
421
+ # Keep the legacy flag name for installer compatibility, but prepare the
422
+ # fresh local default: Parakeet v2.
272
423
  run_logged_check \
273
- "dictate prepare-model --stt-backend faster-whisper --model turbo --device auto --compute-type int8" \
424
+ "dictate prepare-model --stt-backend parakeet --device auto --compute-type int8" \
274
425
  "$PREPARE_LOG" \
275
426
  1800 \
276
427
  "$DICTATE_BIN" \
277
428
  prepare-model \
278
- --stt-backend faster-whisper \
279
- --model turbo \
429
+ --stt-backend parakeet \
280
430
  --device auto \
281
431
  --compute-type int8
282
432
  fi
@@ -286,14 +436,12 @@ if [ "$VERIFY" -eq 1 ]; then
286
436
  run_logged_check "dictate --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" --help
287
437
  run_logged_check "dictate benchmark --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" benchmark --help
288
438
  run_logged_check \
289
- "dictate doctor --quick --stt-backend faster-whisper --model turbo" \
439
+ "dictate doctor --quick" \
290
440
  "$VERIFY_LOG" \
291
441
  20 \
292
442
  "$DICTATE_BIN" \
293
443
  doctor \
294
- --quick \
295
- --stt-backend faster-whisper \
296
- --model turbo
444
+ --quick
297
445
  fi
298
446
 
299
447
  if [ "$STARTUP" -eq 1 ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcforgelabs/dictate",
3
- "version": "2026.6.18",
3
+ "version": "2026.7.4-unstable.2.1",
4
4
  "description": "Installer shim for Dictate desktop dictation.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/uninstall.sh CHANGED
@@ -4,6 +4,7 @@ set -euo pipefail
4
4
 
5
5
  INSTALL_DIR="$HOME/.local/share/dictate"
6
6
  BIN_PATH="$HOME/.local/bin/dictate"
7
+ UI_SHELL_BIN_PATH="$HOME/.local/bin/dictate-ui-shell"
7
8
  UI_SERVER_BIN_PATH="$HOME/.local/bin/dictate-ui-server"
8
9
  DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
9
10
  AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
@@ -95,10 +96,12 @@ remove_managed_symlink() {
95
96
  stop_source_processes
96
97
 
97
98
  remove_file "$DESKTOP_DIR/dictate.desktop"
99
+ remove_file "$DESKTOP_DIR/Dictate.desktop"
98
100
  remove_file "$DESKTOP_DIR/dictate-settings.desktop"
99
101
  remove_file "$AUTOSTART_DIR/dictate.desktop"
100
102
 
101
103
  remove_managed_symlink "$BIN_PATH" "$INSTALL_DIR/venv/bin/dictate"
104
+ remove_file "$UI_SHELL_BIN_PATH"
102
105
  remove_managed_symlink "$UI_SERVER_BIN_PATH" "$INSTALL_DIR/venv/bin/dictate-ui-server"
103
106
 
104
107
  rm -rf "$INSTALL_DIR/venv" "$INSTALL_DIR/share/icons" "$INSTALL_DIR/logs"
package/update.ps1 CHANGED
@@ -10,10 +10,10 @@ param(
10
10
  )
11
11
 
12
12
  $ErrorActionPreference = "Stop"
13
- $DictateVersion = "2026.6.18"
13
+ $DictateVersion = "2026.7.4-unstable.2.1"
14
14
 
15
15
  if (-not $ArchiveUrl) {
16
- $ArchiveUrl = "https://github.com/arcforgelabs/dictate/archive/refs/tags/v$DictateVersion.zip"
16
+ $ArchiveUrl = "https://github.com/arcforgelabs/dictate/archive/69118505102a569ed2787e6457b425e205ff8b5c.zip"
17
17
  }
18
18
 
19
19
  if (-not $InstallRoot) {
package/update.sh CHANGED
@@ -8,19 +8,55 @@ DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
8
8
  AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
9
9
  ICON_DIR="$INSTALL_DIR/share/icons"
10
10
  AUTOSTART_PATH="$AUTOSTART_DIR/dictate.desktop"
11
+ UPDATE_SCOPE="user"
12
+
13
+ usage() {
14
+ cat <<EOF
15
+ Usage: $0 [--user|--system] [install.sh options]
16
+
17
+ Default: --user.
18
+
19
+ --user updates the per-user install in ~/.local/share/dictate without sudo.
20
+ --system updates a Linux desktop package in system paths using apt/pkexec or sudo.
21
+ EOF
22
+ }
11
23
 
12
24
  cleanup_legacy_entries() {
13
25
  rm -f "$DESKTOP_DIR/dictate-settings.desktop"
14
26
  rm -f "$ICON_DIR/dictate-controls.png" "$ICON_DIR/dictate.png"
15
27
  }
16
28
 
29
+ args=()
30
+ while [ "$#" -gt 0 ]; do
31
+ case "$1" in
32
+ --user)
33
+ UPDATE_SCOPE="user"
34
+ ;;
35
+ --system)
36
+ UPDATE_SCOPE="system"
37
+ ;;
38
+ -h|--help)
39
+ usage
40
+ exit 0
41
+ ;;
42
+ *)
43
+ args+=("$1")
44
+ ;;
45
+ esac
46
+ shift
47
+ done
48
+
49
+ if [ "$UPDATE_SCOPE" = "system" ]; then
50
+ "$SCRIPT_DIR/install.sh" --system "${args[@]}"
51
+ exit 0
52
+ fi
53
+
17
54
  if [ -d "$SCRIPT_DIR/.git" ]; then
18
55
  git -C "$SCRIPT_DIR" pull --ff-only
19
56
  fi
20
57
 
21
58
  cleanup_legacy_entries
22
59
 
23
- args=("$@")
24
60
  startup_option_seen=0
25
61
  for arg in "${args[@]}"; do
26
62
  if [ "$arg" = "--no-startup" ]; then