@arcforgelabs/dictate 2026.6.20 → 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.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.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,7 +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
- DESKTOP_PATH="$DESKTOP_DIR/Dictate.desktop"
14
+ APP_ICON_SOURCE="$SCRIPT_DIR/ui-shell/src-tauri/icons/icon.png"
15
+ DESKTOP_PATH="$DESKTOP_DIR/dictate.desktop"
15
16
  CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/dictate"
16
17
  CONFIG_PATH="$CONFIG_DIR/config.yaml"
17
18
  DEFAULT_CONFIG_SOURCE="$SCRIPT_DIR/config/default-config.yaml"
@@ -20,6 +21,9 @@ PREPARE_TURBO=1
20
21
  SEED_DEFAULT_CONFIG=1
21
22
  STARTUP=1
22
23
  INSTALL_UI=1
24
+ INSTALL_GPU="${DICTATE_INSTALL_GPU:-0}"
25
+ INSTALL_MEETING="${DICTATE_INSTALL_MEETING:-0}"
26
+ INSTALL_SCOPE="user"
23
27
  PYTHON_BIN="${PYTHON_BIN:-python3}"
24
28
 
25
29
  # Prefer the distro Python so --system-site-packages can see modules such as
@@ -30,17 +34,58 @@ fi
30
34
 
31
35
  usage() {
32
36
  cat <<EOF
33
- 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]
34
38
 
35
- 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
36
42
  ~/.local/bin/dictate-ui-server, seeds the default config on first install,
37
- creates app launcher/autostart entries, prepares the faster-whisper turbo model,
43
+ creates app launcher/autostart entries, prepares the Parakeet local model,
38
44
  and installs the desktop "Quiet Console" UI shell when a build toolchain is
39
45
  present (unless disabled). All steps degrade gracefully when prerequisites are
40
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.
41
57
  EOF
42
58
  }
43
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
+
44
89
  detect_session_backend() {
45
90
  if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
46
91
  printf 'wayland\n'
@@ -80,6 +125,24 @@ while [ "$#" -gt 0 ]; do
80
125
  --no-ui)
81
126
  INSTALL_UI=0
82
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
+ ;;
83
146
  --session-backend)
84
147
  shift
85
148
  SESSION_BACKEND="${1:-}"
@@ -99,17 +162,33 @@ while [ "$#" -gt 0 ]; do
99
162
  shift
100
163
  done
101
164
 
165
+ if [ "$INSTALL_SCOPE" = "system" ]; then
166
+ install_system_package
167
+ exit 0
168
+ fi
169
+
102
170
  if [ "$SESSION_BACKEND" = "auto" ]; then
103
171
  SESSION_BACKEND="$(detect_session_backend)"
104
172
  fi
105
173
 
106
- PIP_TARGET="$SCRIPT_DIR"
174
+ EXTRAS=()
107
175
  if [ "$SESSION_BACKEND" = "x11" ]; then
108
- PIP_TARGET="${SCRIPT_DIR}[x11]"
176
+ EXTRAS+=("x11")
109
177
  elif [ "$SESSION_BACKEND" = "wayland" ]; then
110
- PIP_TARGET="${SCRIPT_DIR}[wayland]"
178
+ EXTRAS+=("wayland")
111
179
  elif [ "$SESSION_BACKEND" = "unknown" ]; then
112
- 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}]"
113
192
  fi
114
193
 
115
194
  echo "Detected install session backend: $SESSION_BACKEND"
@@ -125,6 +204,34 @@ elif command -v rpm >/dev/null 2>&1 && rpm -q dictate >/dev/null 2>&1; then
125
204
  echo " Use one install method. To remove the package first: sudo dnf remove dictate"
126
205
  fi
127
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
+
128
235
  echo "Creating venv at $INSTALL_DIR ..."
129
236
  uv venv "$INSTALL_DIR/venv" --python "$PYTHON_BIN" --system-site-packages --quiet
130
237
 
@@ -140,11 +247,15 @@ ln -sf "$INSTALL_DIR/venv/bin/dictate-ui-server" "$BIN_DIR/dictate-ui-server"
140
247
  echo "Installing icon ..."
141
248
  mkdir -p "$ICON_DIR"
142
249
  rm -f "$ICON_DIR/dictate-controls.png" "$ICON_DIR/dictate.png"
143
- 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
144
255
 
145
256
  echo "Installing desktop entry ..."
146
257
  mkdir -p "$DESKTOP_DIR"
147
- rm -f "$DESKTOP_DIR/dictate-settings.desktop" "$DESKTOP_DIR/dictate.desktop"
258
+ rm -f "$DESKTOP_DIR/dictate-settings.desktop" "$DESKTOP_DIR/Dictate.desktop" "$DESKTOP_DIR/dictate.desktop"
148
259
  cat > "$DESKTOP_PATH" <<EOF
149
260
  [Desktop Entry]
150
261
  Name=Dictate
@@ -155,6 +266,10 @@ Type=Application
155
266
  Categories=AudioVideo;Audio;
156
267
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
157
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
158
273
  EOF
159
274
 
160
275
  update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
@@ -172,6 +287,7 @@ Type=Application
172
287
  Categories=AudioVideo;Audio;
173
288
  Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
174
289
  Terminal=false
290
+ StartupWMClass=dictate-ui-shell
175
291
  X-GNOME-Autostart-enabled=true
176
292
  EOF
177
293
  fi
@@ -206,22 +322,58 @@ install_desktop_ui() {
206
322
  npm --prefix "$SCRIPT_DIR/ui" ci >/dev/null 2>&1 \
207
323
  || npm --prefix "$SCRIPT_DIR/ui" install >/dev/null 2>&1 \
208
324
  || { 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
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
213
345
  echo "Desktop UI shell build failed; the engine + tray remain fully functional."
214
346
  print_ui_hint
215
347
  return 0
216
348
  fi
217
349
  install -m 755 "$shell_src/src-tauri/target/release/dictate-ui-shell" "$target"
218
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"
219
363
  }
220
364
 
221
365
  if [ "$INSTALL_UI" -eq 1 ]; then
222
366
  install_desktop_ui
223
367
  fi
224
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
+
225
377
  if [ "$SEED_DEFAULT_CONFIG" -eq 1 ]; then
226
378
  if [ ! -f "$CONFIG_PATH" ]; then
227
379
  if [ ! -f "$DEFAULT_CONFIG_SOURCE" ]; then
@@ -266,14 +418,15 @@ run_logged_check() {
266
418
 
267
419
  if [ "$PREPARE_TURBO" -eq 1 ]; then
268
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.
269
423
  run_logged_check \
270
- "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" \
271
425
  "$PREPARE_LOG" \
272
426
  1800 \
273
427
  "$DICTATE_BIN" \
274
428
  prepare-model \
275
- --stt-backend faster-whisper \
276
- --model turbo \
429
+ --stt-backend parakeet \
277
430
  --device auto \
278
431
  --compute-type int8
279
432
  fi
@@ -283,14 +436,12 @@ if [ "$VERIFY" -eq 1 ]; then
283
436
  run_logged_check "dictate --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" --help
284
437
  run_logged_check "dictate benchmark --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" benchmark --help
285
438
  run_logged_check \
286
- "dictate doctor --quick --stt-backend faster-whisper --model turbo" \
439
+ "dictate doctor --quick" \
287
440
  "$VERIFY_LOG" \
288
441
  20 \
289
442
  "$DICTATE_BIN" \
290
443
  doctor \
291
- --quick \
292
- --stt-backend faster-whisper \
293
- --model turbo
444
+ --quick
294
445
  fi
295
446
 
296
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.20",
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.20"
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