@arcforgelabs/dictate 2026.6.3
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/LICENSE +21 -0
- package/README.md +212 -0
- package/assets/dictate-listening.ico +0 -0
- package/assets/dictate-listening.png +0 -0
- package/assets/dictate.ico +0 -0
- package/assets/dictate.png +0 -0
- package/config/default-config.yaml +17 -0
- package/install-windows-wizard.ps1 +547 -0
- package/install-windows.ps1 +422 -0
- package/install.ps1 +87 -0
- package/install.sh +303 -0
- package/npm/dictate-lifecycle.mjs +49 -0
- package/package.json +50 -0
- package/uninstall-windows.ps1 +83 -0
- package/uninstall.ps1 +51 -0
- package/uninstall.sh +117 -0
- package/update-windows.ps1 +70 -0
- package/update.ps1 +115 -0
- package/update.sh +34 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$NoVerify,
|
|
3
|
+
[switch]$NoPrepareTurbo,
|
|
4
|
+
[switch]$NoShortcut,
|
|
5
|
+
[switch]$NoStartup,
|
|
6
|
+
[switch]$RecreateVenv
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = "Stop"
|
|
10
|
+
|
|
11
|
+
function Test-PythonVersion {
|
|
12
|
+
param(
|
|
13
|
+
[string]$Exe,
|
|
14
|
+
[string[]]$ArgumentList
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
& $Exe @ArgumentList -c "import sys; raise SystemExit(0 if (3, 11) <= sys.version_info < (3, 13) else 1)" *> $null
|
|
18
|
+
return ($LASTEXITCODE -eq 0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Resolve-Python {
|
|
22
|
+
$candidates = @()
|
|
23
|
+
|
|
24
|
+
if ($env:PYTHON) {
|
|
25
|
+
$candidates += [pscustomobject]@{ Exe = $env:PYTHON; ArgumentList = @() }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$python = Get-Command python -ErrorAction SilentlyContinue
|
|
29
|
+
if ($python) {
|
|
30
|
+
$candidates += [pscustomobject]@{ Exe = $python.Source; ArgumentList = @() }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$python3 = Get-Command python3 -ErrorAction SilentlyContinue
|
|
34
|
+
if ($python3) {
|
|
35
|
+
$candidates += [pscustomobject]@{ Exe = $python3.Source; ArgumentList = @() }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$py = Get-Command py -ErrorAction SilentlyContinue
|
|
39
|
+
if ($py) {
|
|
40
|
+
$candidates += [pscustomobject]@{ Exe = $py.Source; ArgumentList = @("-3.12") }
|
|
41
|
+
$candidates += [pscustomobject]@{ Exe = $py.Source; ArgumentList = @("-3.11") }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
foreach ($candidate in $candidates) {
|
|
45
|
+
if (Test-PythonVersion -Exe $candidate.Exe -ArgumentList $candidate.ArgumentList) {
|
|
46
|
+
return $candidate
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw "Python 3.11 or 3.12 was not found. Install Python from python.org or winget, then rerun this script."
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Invoke-Checked {
|
|
54
|
+
param(
|
|
55
|
+
[string]$Exe,
|
|
56
|
+
[string[]]$ArgumentList,
|
|
57
|
+
[string]$Description
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
Write-Host "==> $Description"
|
|
61
|
+
& $Exe @ArgumentList
|
|
62
|
+
if ($LASTEXITCODE -ne 0) {
|
|
63
|
+
throw "$Description failed with exit code $LASTEXITCODE."
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Test-VcRuntime {
|
|
68
|
+
$system32 = Join-Path $env:WINDIR "System32"
|
|
69
|
+
$required = @("vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll")
|
|
70
|
+
foreach ($dll in $required) {
|
|
71
|
+
if (-not (Test-Path (Join-Path $system32 $dll))) {
|
|
72
|
+
return $false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return $true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Ensure-VcRuntime {
|
|
79
|
+
if (Test-VcRuntime) {
|
|
80
|
+
Write-Host "==> Microsoft Visual C++ runtime already installed"
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$installer = Join-Path $env:TEMP "vc_redist.x64.exe"
|
|
85
|
+
$url = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
|
86
|
+
Write-Host "==> Installing Microsoft Visual C++ runtime"
|
|
87
|
+
Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $installer
|
|
88
|
+
$process = Start-Process -FilePath $installer -ArgumentList "/install", "/quiet", "/norestart" -Wait -PassThru
|
|
89
|
+
if (($process.ExitCode -ne 0) -and ($process.ExitCode -ne 3010)) {
|
|
90
|
+
throw "Microsoft Visual C++ runtime install failed with exit code $($process.ExitCode)."
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Get-AppDataConfigPath {
|
|
95
|
+
$base = $env:APPDATA
|
|
96
|
+
if (-not $base) {
|
|
97
|
+
$base = Join-Path $HOME "AppData\Roaming"
|
|
98
|
+
}
|
|
99
|
+
return Join-Path $base "dictate\config.yaml"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Seed-Config {
|
|
103
|
+
$configPath = Get-AppDataConfigPath
|
|
104
|
+
if (Test-Path $configPath) {
|
|
105
|
+
Write-Host "==> Config already exists: $configPath"
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
$defaultConfig = Join-Path $PSScriptRoot "config\default-config.yaml"
|
|
110
|
+
$configDir = Split-Path -Parent $configPath
|
|
111
|
+
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
|
|
112
|
+
Copy-Item -Path $defaultConfig -Destination $configPath
|
|
113
|
+
Write-Host "==> Seeded config: $configPath"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Write-LauncherScripts {
|
|
117
|
+
param([string]$ScriptsDir)
|
|
118
|
+
|
|
119
|
+
$daemonPath = Join-Path $ScriptsDir "dictate-daemon.cmd"
|
|
120
|
+
$oncePath = Join-Path $ScriptsDir "dictate-once.cmd"
|
|
121
|
+
$controlsPath = Join-Path $ScriptsDir "dictate-controls.cmd"
|
|
122
|
+
$trayPath = Join-Path $ScriptsDir "dictate-tray.cmd"
|
|
123
|
+
$trayVbsPath = Join-Path $ScriptsDir "dictate-tray.vbs"
|
|
124
|
+
|
|
125
|
+
Set-Content -Path $trayPath -Encoding ASCII -Value @(
|
|
126
|
+
"@echo off",
|
|
127
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
128
|
+
'"%SCRIPT_DIR%dictate.exe" --type-backend pynput %*'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
Set-Content -Path $daemonPath -Encoding ASCII -Value @(
|
|
132
|
+
"@echo off",
|
|
133
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
134
|
+
'"%SCRIPT_DIR%dictate.exe" --no-tray --type-backend pynput %*'
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
Set-Content -Path $oncePath -Encoding ASCII -Value @(
|
|
138
|
+
"@echo off",
|
|
139
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
140
|
+
'"%SCRIPT_DIR%dictate.exe" --once %*'
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
Set-Content -Path $controlsPath -Encoding ASCII -Value @(
|
|
144
|
+
"@echo off",
|
|
145
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
146
|
+
'start "" "%SCRIPT_DIR%dictate-controls.exe" %*'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
Set-Content -Path $trayVbsPath -Encoding ASCII -Value @(
|
|
150
|
+
'Set shell = CreateObject("WScript.Shell")',
|
|
151
|
+
'Set fso = CreateObject("Scripting.FileSystemObject")',
|
|
152
|
+
'scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)',
|
|
153
|
+
'venvDir = fso.GetParentFolderName(scriptDir)',
|
|
154
|
+
'shell.CurrentDirectory = fso.GetParentFolderName(venvDir)',
|
|
155
|
+
'shell.Run """" & scriptDir & "\pythonw.exe"" -m dictate --type-backend pynput", 0, False'
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
Write-Host "==> Wrote launchers:"
|
|
159
|
+
Write-Host " $trayPath"
|
|
160
|
+
Write-Host " $trayVbsPath"
|
|
161
|
+
Write-Host " $daemonPath"
|
|
162
|
+
Write-Host " $oncePath"
|
|
163
|
+
Write-Host " $controlsPath"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-StartMenuProgramsDir {
|
|
167
|
+
$programsDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
|
|
168
|
+
if (-not $env:APPDATA) {
|
|
169
|
+
$programsDir = Join-Path $HOME "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
|
|
170
|
+
}
|
|
171
|
+
return $programsDir
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function Get-StartupDir {
|
|
175
|
+
return (Join-Path (Get-StartMenuProgramsDir) "Startup")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function New-DictateShortcut {
|
|
179
|
+
param(
|
|
180
|
+
[string]$ShortcutPath,
|
|
181
|
+
[string]$TargetPath,
|
|
182
|
+
[string]$Arguments = "",
|
|
183
|
+
[string]$WorkingDirectory,
|
|
184
|
+
[string]$Description
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
$iconPath = Join-Path $PSScriptRoot "assets\dictate.ico"
|
|
188
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
189
|
+
$shortcut = $shell.CreateShortcut($ShortcutPath)
|
|
190
|
+
$shortcut.TargetPath = $TargetPath
|
|
191
|
+
$shortcut.Arguments = $Arguments
|
|
192
|
+
$shortcut.WorkingDirectory = $WorkingDirectory
|
|
193
|
+
$shortcut.Description = $Description
|
|
194
|
+
if (Test-Path $iconPath) {
|
|
195
|
+
$shortcut.IconLocation = $iconPath
|
|
196
|
+
}
|
|
197
|
+
$shortcut.Save()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function Install-StartMenuShortcut {
|
|
201
|
+
param(
|
|
202
|
+
[string]$TargetPath,
|
|
203
|
+
[string]$Arguments = "",
|
|
204
|
+
[string]$WorkingDirectory
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
$programsDir = Get-StartMenuProgramsDir
|
|
208
|
+
New-Item -ItemType Directory -Force -Path $programsDir | Out-Null
|
|
209
|
+
|
|
210
|
+
$shortcutPath = Join-Path $programsDir "Dictate.lnk"
|
|
211
|
+
$legacyShortcutPath = Join-Path $programsDir "Dictate Controls.lnk"
|
|
212
|
+
|
|
213
|
+
if ($NoShortcut) {
|
|
214
|
+
Remove-Item -Force -ErrorAction SilentlyContinue -Path $shortcutPath, $legacyShortcutPath
|
|
215
|
+
Write-Host "==> Removed Start Menu shortcuts"
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Remove-Item -Force -ErrorAction SilentlyContinue -Path $legacyShortcutPath
|
|
220
|
+
New-DictateShortcut -ShortcutPath $shortcutPath -TargetPath $TargetPath -Arguments $Arguments -WorkingDirectory $WorkingDirectory -Description "Start Dictate push-to-talk tray"
|
|
221
|
+
|
|
222
|
+
Write-Host "==> Installed Start Menu shortcut: $shortcutPath"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function Install-StartupShortcut {
|
|
226
|
+
param(
|
|
227
|
+
[string]$TargetPath,
|
|
228
|
+
[string]$Arguments = "",
|
|
229
|
+
[string]$WorkingDirectory
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if ($NoShortcut -or $NoStartup) {
|
|
233
|
+
$startupDir = Get-StartupDir
|
|
234
|
+
$shortcutPath = Join-Path $startupDir "Dictate.lnk"
|
|
235
|
+
if (Test-Path $shortcutPath) {
|
|
236
|
+
Remove-Item -Force $shortcutPath
|
|
237
|
+
Write-Host "==> Removed startup shortcut: $shortcutPath"
|
|
238
|
+
}
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
$startupDir = Get-StartupDir
|
|
243
|
+
New-Item -ItemType Directory -Force -Path $startupDir | Out-Null
|
|
244
|
+
|
|
245
|
+
$shortcutPath = Join-Path $startupDir "Dictate.lnk"
|
|
246
|
+
New-DictateShortcut -ShortcutPath $shortcutPath -TargetPath $TargetPath -Arguments $Arguments -WorkingDirectory $WorkingDirectory -Description "Start Dictate automatically at sign-in"
|
|
247
|
+
|
|
248
|
+
Write-Host "==> Installed startup shortcut: $shortcutPath"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function Register-InstalledApp {
|
|
252
|
+
param(
|
|
253
|
+
[string]$InstallLocation,
|
|
254
|
+
[string]$DisplayIcon
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
$keyPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Dictate"
|
|
258
|
+
New-Item -Force -Path $keyPath | Out-Null
|
|
259
|
+
New-ItemProperty -Force -Path $keyPath -Name "DisplayName" -Value "Dictate" -PropertyType String | Out-Null
|
|
260
|
+
New-ItemProperty -Force -Path $keyPath -Name "DisplayVersion" -Value "2026.6.3" -PropertyType String | Out-Null
|
|
261
|
+
New-ItemProperty -Force -Path $keyPath -Name "Publisher" -Value "Arc Forge Labs" -PropertyType String | Out-Null
|
|
262
|
+
New-ItemProperty -Force -Path $keyPath -Name "InstallLocation" -Value $InstallLocation -PropertyType String | Out-Null
|
|
263
|
+
if (Test-Path $DisplayIcon) {
|
|
264
|
+
New-ItemProperty -Force -Path $keyPath -Name "DisplayIcon" -Value $DisplayIcon -PropertyType String | Out-Null
|
|
265
|
+
}
|
|
266
|
+
$uninstallScript = Join-Path $InstallLocation "uninstall-windows.ps1"
|
|
267
|
+
if (Test-Path $uninstallScript) {
|
|
268
|
+
$uninstallCommand = "powershell -NoProfile -ExecutionPolicy Bypass -File `"$uninstallScript`""
|
|
269
|
+
New-ItemProperty -Force -Path $keyPath -Name "UninstallString" -Value $uninstallCommand -PropertyType String | Out-Null
|
|
270
|
+
New-ItemProperty -Force -Path $keyPath -Name "QuietUninstallString" -Value "$uninstallCommand -Quiet" -PropertyType String | Out-Null
|
|
271
|
+
}
|
|
272
|
+
New-ItemProperty -Force -Path $keyPath -Name "NoModify" -Value 1 -PropertyType DWord | Out-Null
|
|
273
|
+
New-ItemProperty -Force -Path $keyPath -Name "NoRepair" -Value 1 -PropertyType DWord | Out-Null
|
|
274
|
+
Write-Host "==> Registered Dictate in Windows Installed Apps"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function Normalize-PathForCompare {
|
|
278
|
+
param([string]$Path)
|
|
279
|
+
if (-not $Path) {
|
|
280
|
+
return ""
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
return [System.IO.Path]::GetFullPath($Path).TrimEnd("\").ToLowerInvariant()
|
|
284
|
+
} catch {
|
|
285
|
+
return $Path.TrimEnd("\").ToLowerInvariant()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function Remove-StaleUserInstallSurface {
|
|
290
|
+
param([string]$CurrentInstallLocation)
|
|
291
|
+
|
|
292
|
+
$currentInstall = Normalize-PathForCompare -Path $CurrentInstallLocation
|
|
293
|
+
$shell = $null
|
|
294
|
+
try {
|
|
295
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
296
|
+
} catch {
|
|
297
|
+
Write-Host "==> Could not inspect Windows shortcuts for stale Dictate entries"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
$profilesRoot = Join-Path $env:SystemDrive "Users"
|
|
301
|
+
if (Test-Path $profilesRoot) {
|
|
302
|
+
Get-ChildItem -Path $profilesRoot -Directory -ErrorAction SilentlyContinue |
|
|
303
|
+
Where-Object { $_.Name -notin @("Public", "Default", "Default User", "All Users") } |
|
|
304
|
+
ForEach-Object {
|
|
305
|
+
$profile = $_.FullName
|
|
306
|
+
$programsDir = Join-Path $profile "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
|
|
307
|
+
$startupDir = Join-Path $programsDir "Startup"
|
|
308
|
+
$legacyShortcut = Join-Path $programsDir "Dictate Controls.lnk"
|
|
309
|
+
if (Test-Path -LiteralPath $legacyShortcut) {
|
|
310
|
+
Remove-Item -Force -LiteralPath $legacyShortcut -ErrorAction SilentlyContinue
|
|
311
|
+
Write-Host "==> Removed stale Dictate shortcut: $legacyShortcut"
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
foreach ($shortcutPath in @(
|
|
315
|
+
(Join-Path $programsDir "Dictate.lnk"),
|
|
316
|
+
(Join-Path $startupDir "Dictate.lnk")
|
|
317
|
+
)) {
|
|
318
|
+
if (-not (Test-Path -LiteralPath $shortcutPath)) {
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
$removeShortcut = $false
|
|
322
|
+
if ($shell) {
|
|
323
|
+
$shortcut = $shell.CreateShortcut($shortcutPath)
|
|
324
|
+
$workingDirectory = Normalize-PathForCompare -Path $shortcut.WorkingDirectory
|
|
325
|
+
$argumentTarget = ""
|
|
326
|
+
if ($shortcut.Arguments -match '"([^"]+dictate-tray\.vbs)"') {
|
|
327
|
+
$argumentTarget = $matches[1]
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
($workingDirectory -and ($workingDirectory -ne $currentInstall)) -or
|
|
331
|
+
($argumentTarget -and (-not (Test-Path -LiteralPath $argumentTarget)))
|
|
332
|
+
) {
|
|
333
|
+
$removeShortcut = $true
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
$removeShortcut = $true
|
|
337
|
+
}
|
|
338
|
+
if ($removeShortcut) {
|
|
339
|
+
Remove-Item -Force -LiteralPath $shortcutPath -ErrorAction SilentlyContinue
|
|
340
|
+
Write-Host "==> Removed stale Dictate shortcut: $shortcutPath"
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
$hostedSource = Join-Path $profile "AppData\Local\Dictate\source"
|
|
345
|
+
$hostedRoot = Join-Path $profile "AppData\Local\Dictate"
|
|
346
|
+
if (
|
|
347
|
+
(Test-Path -LiteralPath (Join-Path $hostedSource "install-windows.ps1")) -and
|
|
348
|
+
((Normalize-PathForCompare -Path $hostedSource) -ne $currentInstall)
|
|
349
|
+
) {
|
|
350
|
+
Remove-Item -Recurse -Force -LiteralPath $hostedRoot -ErrorAction SilentlyContinue
|
|
351
|
+
Write-Host "==> Removed stale Dictate managed source: $hostedRoot"
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
Get-ChildItem Registry::HKEY_USERS -ErrorAction SilentlyContinue | ForEach-Object {
|
|
357
|
+
$keyPath = Join-Path $_.PSPath "Software\Microsoft\Windows\CurrentVersion\Uninstall\Dictate"
|
|
358
|
+
if (Test-Path $keyPath) {
|
|
359
|
+
$entry = Get-ItemProperty -Path $keyPath
|
|
360
|
+
if ((Normalize-PathForCompare -Path $entry.InstallLocation) -ne $currentInstall) {
|
|
361
|
+
Remove-Item -Recurse -Force -Path $keyPath -ErrorAction SilentlyContinue
|
|
362
|
+
Write-Host "==> Removed stale Dictate Installed Apps entry"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
$pythonCommand = Resolve-Python
|
|
369
|
+
$venvDir = Join-Path $PSScriptRoot ".venv"
|
|
370
|
+
$scriptsDir = Join-Path $venvDir "Scripts"
|
|
371
|
+
$venvPython = Join-Path $scriptsDir "python.exe"
|
|
372
|
+
|
|
373
|
+
Remove-StaleUserInstallSurface -CurrentInstallLocation $PSScriptRoot
|
|
374
|
+
Ensure-VcRuntime
|
|
375
|
+
|
|
376
|
+
if ($RecreateVenv -and (Test-Path $venvDir)) {
|
|
377
|
+
Write-Host "==> Removing existing virtual environment: $venvDir"
|
|
378
|
+
Remove-Item -Recurse -Force $venvDir
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (-not (Test-Path $venvPython)) {
|
|
382
|
+
Invoke-Checked -Exe $pythonCommand.Exe -ArgumentList @($pythonCommand.ArgumentList + @("-m", "venv", $venvDir)) -Description "Creating virtual environment"
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
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
|
+
|
|
388
|
+
Seed-Config
|
|
389
|
+
Write-LauncherScripts -ScriptsDir $scriptsDir
|
|
390
|
+
$trayVbs = Join-Path $scriptsDir "dictate-tray.vbs"
|
|
391
|
+
$wscript = Join-Path $env:WINDIR "System32\wscript.exe"
|
|
392
|
+
$trayArgs = "`"$trayVbs`""
|
|
393
|
+
Install-StartMenuShortcut -TargetPath $wscript -Arguments $trayArgs -WorkingDirectory $PSScriptRoot
|
|
394
|
+
Install-StartupShortcut -TargetPath $wscript -Arguments $trayArgs -WorkingDirectory $PSScriptRoot
|
|
395
|
+
Register-InstalledApp -InstallLocation $PSScriptRoot -DisplayIcon (Join-Path $PSScriptRoot "assets\dictate.ico")
|
|
396
|
+
|
|
397
|
+
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"
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (-not $NoVerify) {
|
|
402
|
+
Invoke-Checked -Exe $venvPython -ArgumentList @("-m", "dictate", "doctor", "--quick", "--type-backend", "pynput") -Description "Running Dictate doctor"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
Write-Host ""
|
|
406
|
+
Write-Host "Dictate is installed."
|
|
407
|
+
if ($NoShortcut -or $NoStartup) {
|
|
408
|
+
Write-Host "Dictate startup shortcut was not installed."
|
|
409
|
+
} else {
|
|
410
|
+
Write-Host "Dictate starts automatically when you sign in."
|
|
411
|
+
}
|
|
412
|
+
Write-Host "Start push-to-talk with a Windows tray icon from the Start Menu shortcut named 'Dictate', or run:"
|
|
413
|
+
Write-Host " .\.venv\Scripts\dictate-tray.cmd"
|
|
414
|
+
Write-Host ""
|
|
415
|
+
Write-Host "Headless push-to-talk daemon:"
|
|
416
|
+
Write-Host " .\.venv\Scripts\dictate-daemon.cmd"
|
|
417
|
+
Write-Host ""
|
|
418
|
+
Write-Host "Control panel:"
|
|
419
|
+
Write-Host " .\.venv\Scripts\dictate-controls.cmd"
|
|
420
|
+
Write-Host ""
|
|
421
|
+
Write-Host "One-shot mode:"
|
|
422
|
+
Write-Host " .\.venv\Scripts\dictate-once.cmd"
|
package/install.ps1
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$InstallRoot,
|
|
3
|
+
[string]$ArchiveUrl,
|
|
4
|
+
[switch]$NoVerify,
|
|
5
|
+
[switch]$NoPrepareTurbo,
|
|
6
|
+
[switch]$NoShortcut,
|
|
7
|
+
[switch]$NoStartup,
|
|
8
|
+
[switch]$Wizard,
|
|
9
|
+
[switch]$RecreateVenv
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = "Stop"
|
|
13
|
+
$DictateVersion = "2026.6.3"
|
|
14
|
+
|
|
15
|
+
if (-not $ArchiveUrl) {
|
|
16
|
+
$ArchiveUrl = "https://github.com/arcforgelabs/dictate/archive/refs/tags/v$DictateVersion.zip"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (-not $InstallRoot) {
|
|
20
|
+
$base = $env:LOCALAPPDATA
|
|
21
|
+
if (-not $base) {
|
|
22
|
+
$base = Join-Path $HOME "AppData\Local"
|
|
23
|
+
}
|
|
24
|
+
$InstallRoot = Join-Path $base "Dictate"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$installRootPath = [System.IO.Path]::GetFullPath($InstallRoot)
|
|
28
|
+
$sourceDir = Join-Path $installRootPath "source"
|
|
29
|
+
$stagingRoot = Join-Path $env:TEMP ("dictate-install-" + [guid]::NewGuid().ToString("N"))
|
|
30
|
+
$archivePath = Join-Path $stagingRoot "dictate.zip"
|
|
31
|
+
|
|
32
|
+
Write-Host "==> Installing Dictate to $installRootPath"
|
|
33
|
+
New-Item -ItemType Directory -Force -Path $stagingRoot | Out-Null
|
|
34
|
+
New-Item -ItemType Directory -Force -Path $installRootPath | Out-Null
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
if (Test-Path -LiteralPath $ArchiveUrl) {
|
|
38
|
+
Write-Host "==> Copying local archive $ArchiveUrl"
|
|
39
|
+
Copy-Item -Force -LiteralPath $ArchiveUrl -Destination $archivePath
|
|
40
|
+
} else {
|
|
41
|
+
Write-Host "==> Downloading $ArchiveUrl"
|
|
42
|
+
Invoke-WebRequest -UseBasicParsing -Uri $ArchiveUrl -OutFile $archivePath
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Write-Host "==> Expanding source archive"
|
|
46
|
+
Expand-Archive -Force -Path $archivePath -DestinationPath $stagingRoot
|
|
47
|
+
$expanded = Get-ChildItem -Path $stagingRoot -Directory |
|
|
48
|
+
Where-Object { Test-Path (Join-Path $_.FullName "install-windows.ps1") } |
|
|
49
|
+
Select-Object -First 1
|
|
50
|
+
if (-not $expanded) {
|
|
51
|
+
throw "Downloaded archive did not contain install-windows.ps1."
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (Test-Path $sourceDir) {
|
|
55
|
+
Write-Host "==> Replacing existing managed source: $sourceDir"
|
|
56
|
+
Remove-Item -Recurse -Force $sourceDir
|
|
57
|
+
}
|
|
58
|
+
Move-Item -Path $expanded.FullName -Destination $sourceDir
|
|
59
|
+
|
|
60
|
+
$installerScript = if ($Wizard) {
|
|
61
|
+
Join-Path $sourceDir "install-windows-wizard.ps1"
|
|
62
|
+
} else {
|
|
63
|
+
Join-Path $sourceDir "install-windows.ps1"
|
|
64
|
+
}
|
|
65
|
+
$installerArgs = @("-ExecutionPolicy", "Bypass", "-File", $installerScript)
|
|
66
|
+
if (-not $Wizard) {
|
|
67
|
+
if ($NoVerify) { $installerArgs += "-NoVerify" }
|
|
68
|
+
if ($NoPrepareTurbo) { $installerArgs += "-NoPrepareTurbo" }
|
|
69
|
+
if ($NoShortcut) { $installerArgs += "-NoShortcut" }
|
|
70
|
+
if ($NoStartup) { $installerArgs += "-NoStartup" }
|
|
71
|
+
if ($RecreateVenv) { $installerArgs += "-RecreateVenv" }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Write-Host "==> Running Dictate Windows installer"
|
|
75
|
+
& powershell @installerArgs
|
|
76
|
+
if ($LASTEXITCODE -ne 0) {
|
|
77
|
+
throw "Dictate Windows installer failed with exit code $LASTEXITCODE."
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
if (Test-Path $stagingRoot) {
|
|
81
|
+
Remove-Item -Recurse -Force $stagingRoot
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Write-Host ""
|
|
86
|
+
Write-Host "Dictate install source: $sourceDir"
|
|
87
|
+
Write-Host "Start Dictate from the Start Menu shortcut named 'Dictate'."
|