@arcforgelabs/dictate 2026.6.20 → 2026.7.4-unstable.3.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.20" -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.20"
13
+ $DictateVersion = "2026.7.4-unstable.3.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/3f6f050451d0d0e140193534f102c6b5f2b58b19.zip"
17
17
  }
18
18
 
19
19
  if (-not $InstallRoot) {
@@ -76,6 +76,10 @@ try {
76
76
  if ($LASTEXITCODE -ne 0) {
77
77
  throw "Dictate Windows installer failed with exit code $LASTEXITCODE."
78
78
  }
79
+ $dictateExe = Join-Path $installRootPath ".venv\Scripts\dictate.exe"
80
+ if (Test-Path $dictateExe) {
81
+ & $dictateExe set-installed-package-version $DictateVersion | Out-Null
82
+ }
79
83
  } finally {
80
84
  if (Test-Path $stagingRoot) {
81
85
  Remove-Item -Recurse -Force $stagingRoot
package/install.sh CHANGED
@@ -5,13 +5,15 @@
5
5
  set -euo pipefail
6
6
 
7
7
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ PACKAGE_VERSION="$(sed -n 's/^ "version": "\([^"]*\)",/\1/p' "$SCRIPT_DIR/package.json" | head -n 1 || true)"
8
9
  INSTALL_DIR="$HOME/.local/share/dictate"
9
10
  BIN_DIR="$HOME/.local/bin"
10
11
  DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
11
12
  AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
12
13
  ICON_DIR="$INSTALL_DIR/share/icons"
13
14
  ICON_PATH="$ICON_DIR/dictate-simple.png"
14
- DESKTOP_PATH="$DESKTOP_DIR/Dictate.desktop"
15
+ APP_ICON_SOURCE="$SCRIPT_DIR/ui-shell/src-tauri/icons/icon.png"
16
+ DESKTOP_PATH="$DESKTOP_DIR/dictate.desktop"
15
17
  CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/dictate"
16
18
  CONFIG_PATH="$CONFIG_DIR/config.yaml"
17
19
  DEFAULT_CONFIG_SOURCE="$SCRIPT_DIR/config/default-config.yaml"
@@ -20,6 +22,9 @@ PREPARE_TURBO=1
20
22
  SEED_DEFAULT_CONFIG=1
21
23
  STARTUP=1
22
24
  INSTALL_UI=1
25
+ INSTALL_GPU="${DICTATE_INSTALL_GPU:-0}"
26
+ INSTALL_MEETING="${DICTATE_INSTALL_MEETING:-0}"
27
+ INSTALL_SCOPE="user"
23
28
  PYTHON_BIN="${PYTHON_BIN:-python3}"
24
29
 
25
30
  # Prefer the distro Python so --system-site-packages can see modules such as
@@ -30,17 +35,58 @@ fi
30
35
 
31
36
  usage() {
32
37
  cat <<EOF
33
- Usage: $0 [--no-verify] [--no-prepare-turbo] [--no-seed-default-config] [--no-startup] [--no-ui] [--session-backend auto|x11|wayland]
38
+ Usage: $0 [--user|--system] [--gpu] [--meeting] [--no-verify] [--no-prepare-turbo] [--no-seed-default-config] [--no-startup] [--no-ui] [--session-backend auto|x11|wayland]
34
39
 
35
- Installs dictate into ~/.local/share/dictate, links ~/.local/bin/dictate and
40
+ Default: --user.
41
+
42
+ --user installs dictate into ~/.local/share/dictate, links ~/.local/bin/dictate and
36
43
  ~/.local/bin/dictate-ui-server, seeds the default config on first install,
37
- creates app launcher/autostart entries, prepares the faster-whisper turbo model,
44
+ creates app launcher/autostart entries, prepares the Parakeet local model,
38
45
  and installs the desktop "Quiet Console" UI shell when a build toolchain is
39
46
  present (unless disabled). All steps degrade gracefully when prerequisites are
40
47
  missing.
48
+
49
+ --system installs a Linux desktop package into system paths using apt/pkexec or
50
+ sudo. It is intentionally explicit because it requires administrator approval.
51
+
52
+ --gpu installs Dictate's GPU optional dependencies for local CUDA/ONNX Runtime
53
+ testing on capable machines.
54
+
55
+ --meeting installs Dictate's pyannote/torch optional dependencies for the
56
+ parakeet-pyannote Meeting lane. DiariZen and Sortformer still require their
57
+ runtime-specific setup before selecting those experimental lanes.
41
58
  EOF
42
59
  }
43
60
 
61
+ install_system_package() {
62
+ if [ "$(uname -s)" != "Linux" ]; then
63
+ echo "--system is only supported by this installer on Linux."
64
+ exit 2
65
+ fi
66
+
67
+ local deb_path="${DICTATE_DEB:-}"
68
+ if [ -z "$deb_path" ]; then
69
+ 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)"
70
+ fi
71
+ if [ -z "$deb_path" ] || [ ! -f "$deb_path" ]; then
72
+ echo "No local .deb found. Build one first:"
73
+ echo " DICTATE_BUNDLES=deb scripts/build-linux-desktop.sh"
74
+ echo "Or pass one explicitly:"
75
+ echo " DICTATE_DEB=/path/to/Dictate_..._amd64.deb $0 --system"
76
+ exit 1
77
+ fi
78
+
79
+ echo "Installing system package: $deb_path"
80
+ if command -v pkexec >/dev/null 2>&1; then
81
+ pkexec apt-get install -y "$deb_path"
82
+ elif command -v sudo >/dev/null 2>&1; then
83
+ sudo apt-get install -y "$deb_path"
84
+ else
85
+ echo "Need pkexec or sudo to install a system package."
86
+ exit 1
87
+ fi
88
+ }
89
+
44
90
  detect_session_backend() {
45
91
  if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
46
92
  printf 'wayland\n'
@@ -80,6 +126,24 @@ while [ "$#" -gt 0 ]; do
80
126
  --no-ui)
81
127
  INSTALL_UI=0
82
128
  ;;
129
+ --gpu)
130
+ INSTALL_GPU=1
131
+ ;;
132
+ --no-gpu)
133
+ INSTALL_GPU=0
134
+ ;;
135
+ --meeting)
136
+ INSTALL_MEETING=1
137
+ ;;
138
+ --no-meeting)
139
+ INSTALL_MEETING=0
140
+ ;;
141
+ --user)
142
+ INSTALL_SCOPE="user"
143
+ ;;
144
+ --system)
145
+ INSTALL_SCOPE="system"
146
+ ;;
83
147
  --session-backend)
84
148
  shift
85
149
  SESSION_BACKEND="${1:-}"
@@ -99,17 +163,33 @@ while [ "$#" -gt 0 ]; do
99
163
  shift
100
164
  done
101
165
 
166
+ if [ "$INSTALL_SCOPE" = "system" ]; then
167
+ install_system_package
168
+ exit 0
169
+ fi
170
+
102
171
  if [ "$SESSION_BACKEND" = "auto" ]; then
103
172
  SESSION_BACKEND="$(detect_session_backend)"
104
173
  fi
105
174
 
106
- PIP_TARGET="$SCRIPT_DIR"
175
+ EXTRAS=()
107
176
  if [ "$SESSION_BACKEND" = "x11" ]; then
108
- PIP_TARGET="${SCRIPT_DIR}[x11]"
177
+ EXTRAS+=("x11")
109
178
  elif [ "$SESSION_BACKEND" = "wayland" ]; then
110
- PIP_TARGET="${SCRIPT_DIR}[wayland]"
179
+ EXTRAS+=("wayland")
111
180
  elif [ "$SESSION_BACKEND" = "unknown" ]; then
112
- PIP_TARGET="${SCRIPT_DIR}[x11,wayland]"
181
+ EXTRAS+=("x11" "wayland")
182
+ fi
183
+ if [ "$INSTALL_GPU" -eq 1 ]; then
184
+ EXTRAS+=("gpu")
185
+ fi
186
+ if [ "$INSTALL_MEETING" -eq 1 ]; then
187
+ EXTRAS+=("meeting")
188
+ fi
189
+ PIP_TARGET="$SCRIPT_DIR"
190
+ if [ "${#EXTRAS[@]}" -gt 0 ]; then
191
+ extras_csv="$(IFS=,; printf '%s' "${EXTRAS[*]}")"
192
+ PIP_TARGET="${SCRIPT_DIR}[${extras_csv}]"
113
193
  fi
114
194
 
115
195
  echo "Detected install session backend: $SESSION_BACKEND"
@@ -125,6 +205,34 @@ elif command -v rpm >/dev/null 2>&1 && rpm -q dictate >/dev/null 2>&1; then
125
205
  echo " Use one install method. To remove the package first: sudo dnf remove dictate"
126
206
  fi
127
207
 
208
+ # Stop any running Dictate engine before we overwrite it, so the new install can
209
+ # claim the single-instance lock cleanly instead of colliding with a stale daemon.
210
+ stop_running_dictate() {
211
+ # Prefer the installed CLI's own clean stop; it knows the lock location.
212
+ if command -v dictate >/dev/null 2>&1 && dictate stop --quiet 2>/dev/null; then
213
+ return 0
214
+ fi
215
+ # Fallback (older builds without `dictate stop`): kill via the lock PID file.
216
+ local lockdir
217
+ if [ -n "${XDG_RUNTIME_DIR:-}" ]; then
218
+ lockdir="$XDG_RUNTIME_DIR/dictate-daemon.lockdir"
219
+ else
220
+ lockdir="/tmp/dictate-daemon-$(id -u).lockdir"
221
+ fi
222
+ local pidfile="$lockdir/pid"
223
+ [ -f "$pidfile" ] || return 0
224
+ local pid
225
+ pid="$(cat "$pidfile" 2>/dev/null || true)"
226
+ [ -n "$pid" ] || return 0
227
+ if kill -0 "$pid" 2>/dev/null; then
228
+ kill "$pid" 2>/dev/null || true
229
+ for _ in 1 2 3 4 5 6 7 8 9 10; do kill -0 "$pid" 2>/dev/null || break; sleep 0.3; done
230
+ kill -9 "$pid" 2>/dev/null || true
231
+ fi
232
+ }
233
+ echo "Stopping any running Dictate engine ..."
234
+ stop_running_dictate
235
+
128
236
  echo "Creating venv at $INSTALL_DIR ..."
129
237
  uv venv "$INSTALL_DIR/venv" --python "$PYTHON_BIN" --system-site-packages --quiet
130
238
 
@@ -140,11 +248,15 @@ ln -sf "$INSTALL_DIR/venv/bin/dictate-ui-server" "$BIN_DIR/dictate-ui-server"
140
248
  echo "Installing icon ..."
141
249
  mkdir -p "$ICON_DIR"
142
250
  rm -f "$ICON_DIR/dictate-controls.png" "$ICON_DIR/dictate.png"
143
- install -m 644 "$SCRIPT_DIR/assets/dictate.png" "$ICON_PATH"
251
+ if [ -f "$APP_ICON_SOURCE" ]; then
252
+ install -m 644 "$APP_ICON_SOURCE" "$ICON_PATH"
253
+ else
254
+ install -m 644 "$SCRIPT_DIR/assets/dictate.png" "$ICON_PATH"
255
+ fi
144
256
 
145
257
  echo "Installing desktop entry ..."
146
258
  mkdir -p "$DESKTOP_DIR"
147
- rm -f "$DESKTOP_DIR/dictate-settings.desktop" "$DESKTOP_DIR/dictate.desktop"
259
+ rm -f "$DESKTOP_DIR/dictate-settings.desktop" "$DESKTOP_DIR/Dictate.desktop" "$DESKTOP_DIR/dictate.desktop"
148
260
  cat > "$DESKTOP_PATH" <<EOF
149
261
  [Desktop Entry]
150
262
  Name=Dictate
@@ -155,6 +267,10 @@ Type=Application
155
267
  Categories=AudioVideo;Audio;
156
268
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
157
269
  Terminal=false
270
+ # The Tauri shell window reports app_id "dictate-ui-shell" (GTK prgname). Without
271
+ # this, GNOME can't match the running window to this launcher and shows a second,
272
+ # icon-less "dictate-ui-shell" entry with a generic fallback icon.
273
+ StartupWMClass=dictate-ui-shell
158
274
  EOF
159
275
 
160
276
  update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
@@ -172,6 +288,7 @@ Type=Application
172
288
  Categories=AudioVideo;Audio;
173
289
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
174
290
  Terminal=false
291
+ StartupWMClass=dictate-ui-shell
175
292
  X-GNOME-Autostart-enabled=true
176
293
  EOF
177
294
  fi
@@ -206,22 +323,58 @@ install_desktop_ui() {
206
323
  npm --prefix "$SCRIPT_DIR/ui" ci >/dev/null 2>&1 \
207
324
  || npm --prefix "$SCRIPT_DIR/ui" install >/dev/null 2>&1 \
208
325
  || { echo "UI front-end install failed; skipping the optional shell."; print_ui_hint; return 0; }
209
- if ! npm --prefix "$SCRIPT_DIR/ui" run build >/dev/null 2>&1; then
210
- echo "UI front-end build failed; skipping the optional shell."; print_ui_hint; return 0
211
- fi
212
- if ! ( cd "$shell_src" && cargo build --release --manifest-path src-tauri/Cargo.toml ); then
326
+ npm --prefix "$shell_src" ci >/dev/null 2>&1 \
327
+ || npm --prefix "$shell_src" install >/dev/null 2>&1 \
328
+ || { echo "UI shell tooling install failed; skipping the optional shell."; print_ui_hint; return 0; }
329
+ # Tauri validates configured bundle resources even for --no-bundle builds.
330
+ # Stage a per-user engine wrapper before the build so the resource glob exists,
331
+ # then install the same wrapper next to the shell binary below.
332
+ local bundled_engine_dir="$shell_src/src-tauri/engine"
333
+ mkdir -p "$bundled_engine_dir"
334
+ cat > "$bundled_engine_dir/dictate-engine" <<WRAP
335
+ #!/bin/sh
336
+ exec "$INSTALL_DIR/venv/bin/dictate" "\$@"
337
+ WRAP
338
+ chmod 755 "$bundled_engine_dir/dictate-engine"
339
+ # Build through the Tauri CLI, NOT a raw `cargo build`. The CLI enables the
340
+ # `custom-protocol` feature that embeds the frontend and serves it over the
341
+ # app protocol. A plain cargo build omits that feature, so the shell falls
342
+ # back to loading the dev server (localhost:5173) and shows "Could not connect
343
+ # to localhost: Connection refused" at runtime. --no-bundle: we only need the
344
+ # binary here, not a .deb/AppImage. (beforeBuildCommand rebuilds ui/dist.)
345
+ if ! ( cd "$shell_src" && npm run tauri -- build --no-bundle ); then
213
346
  echo "Desktop UI shell build failed; the engine + tray remain fully functional."
214
347
  print_ui_hint
215
348
  return 0
216
349
  fi
217
350
  install -m 755 "$shell_src/src-tauri/target/release/dictate-ui-shell" "$target"
218
351
  echo "Installed desktop UI shell: $target"
352
+
353
+ # Give the shell an absolute-path engine right next to it. The shell's
354
+ # bundled-engine lookup checks <exe_dir>/engine first — before any system
355
+ # path (e.g. a leftover /usr/lib/Dictate from an old .deb) and independent of
356
+ # the minimal PATH a desktop launcher provides. Pointing it at the per-user
357
+ # venv means one process both dictates and serves, and keeps sys.executable
358
+ # inside ~/.local so the install reads as "linux-user" and the in-app updater
359
+ # never needs sudo/pkexec.
360
+ local engine_dir="$BIN_DIR/engine"
361
+ mkdir -p "$engine_dir"
362
+ install -m 755 "$bundled_engine_dir/dictate-engine" "$engine_dir/dictate-engine"
363
+ echo "Installed per-user engine launcher: $engine_dir/dictate-engine"
219
364
  }
220
365
 
221
366
  if [ "$INSTALL_UI" -eq 1 ]; then
222
367
  install_desktop_ui
223
368
  fi
224
369
 
370
+ if [ -x "$BIN_DIR/dictate-ui-shell" ]; then
371
+ echo "Pointing launcher entries at the desktop UI shell ..."
372
+ sed -i "s|^Exec=.*|Exec=$BIN_DIR/dictate-ui-shell|" "$DESKTOP_PATH"
373
+ if [ "$STARTUP" -eq 1 ] && [ -f "$AUTOSTART_DIR/dictate.desktop" ]; then
374
+ sed -i "s|^Exec=.*|Exec=$BIN_DIR/dictate-ui-shell|" "$AUTOSTART_DIR/dictate.desktop"
375
+ fi
376
+ fi
377
+
225
378
  if [ "$SEED_DEFAULT_CONFIG" -eq 1 ]; then
226
379
  if [ ! -f "$CONFIG_PATH" ]; then
227
380
  if [ ! -f "$DEFAULT_CONFIG_SOURCE" ]; then
@@ -240,6 +393,9 @@ if [ "$SEED_DEFAULT_CONFIG" -eq 1 ]; then
240
393
  fi
241
394
 
242
395
  DICTATE_BIN="$INSTALL_DIR/venv/bin/dictate"
396
+ if [ -n "$PACKAGE_VERSION" ]; then
397
+ "$DICTATE_BIN" set-installed-package-version "$PACKAGE_VERSION" >/dev/null 2>&1 || true
398
+ fi
243
399
 
244
400
  run_logged_check() {
245
401
  local label="$1"
@@ -266,14 +422,15 @@ run_logged_check() {
266
422
 
267
423
  if [ "$PREPARE_TURBO" -eq 1 ]; then
268
424
  PREPARE_LOG="/tmp/dictate-install-prepare.log"
425
+ # Keep the legacy flag name for installer compatibility, but prepare the
426
+ # fresh local default: Parakeet v2.
269
427
  run_logged_check \
270
- "dictate prepare-model --stt-backend faster-whisper --model turbo --device auto --compute-type int8" \
428
+ "dictate prepare-model --stt-backend parakeet --device auto --compute-type int8" \
271
429
  "$PREPARE_LOG" \
272
430
  1800 \
273
431
  "$DICTATE_BIN" \
274
432
  prepare-model \
275
- --stt-backend faster-whisper \
276
- --model turbo \
433
+ --stt-backend parakeet \
277
434
  --device auto \
278
435
  --compute-type int8
279
436
  fi
@@ -283,14 +440,12 @@ if [ "$VERIFY" -eq 1 ]; then
283
440
  run_logged_check "dictate --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" --help
284
441
  run_logged_check "dictate benchmark --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" benchmark --help
285
442
  run_logged_check \
286
- "dictate doctor --quick --stt-backend faster-whisper --model turbo" \
443
+ "dictate doctor --quick" \
287
444
  "$VERIFY_LOG" \
288
445
  20 \
289
446
  "$DICTATE_BIN" \
290
447
  doctor \
291
- --quick \
292
- --stt-backend faster-whisper \
293
- --model turbo
448
+ --quick
294
449
  fi
295
450
 
296
451
  if [ "$STARTUP" -eq 1 ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcforgelabs/dictate",
3
- "version": "2026.6.20",
3
+ "version": "2026.7.4-unstable.3.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.20"
13
+ $DictateVersion = "2026.7.4-unstable.3.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/3f6f050451d0d0e140193534f102c6b5f2b58b19.zip"
17
17
  }
18
18
 
19
19
  if (-not $InstallRoot) {
@@ -105,6 +105,10 @@ try {
105
105
  if ($LASTEXITCODE -ne 0) {
106
106
  throw "Dictate Windows updater failed with exit code $LASTEXITCODE."
107
107
  }
108
+ $dictateExe = Join-Path $installRootPath ".venv\Scripts\dictate.exe"
109
+ if (Test-Path $dictateExe) {
110
+ & $dictateExe set-installed-package-version $DictateVersion | Out-Null
111
+ }
108
112
  } finally {
109
113
  if (Test-Path $stagingRoot) {
110
114
  Remove-Item -Recurse -Force $stagingRoot
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