@iamsamuelrodda/dictate 2026.5.18-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Samuel Rodda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # 🎙️ Dictate
2
+
3
+ Desktop dictation that types into the focused app.
4
+
5
+ `dictate` runs as a small tray app. Press your configured push-to-talk shortcut,
6
+ speak, and it transcribes into whatever app you are already using.
7
+
8
+ Current status: early desktop app. Linux and Windows 11 installs, tray controls,
9
+ startup integration, recent history, model selection, API key storage, update,
10
+ and uninstall paths are implemented. Signed Windows installer packaging is still
11
+ future work.
12
+
13
+ ## Install
14
+
15
+ Windows 11 normal install:
16
+
17
+ ```powershell
18
+ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/install.ps1 | iex"
19
+ ```
20
+
21
+ Open **Dictate** from the Start Menu after install.
22
+
23
+ Ubuntu/Debian source install:
24
+
25
+ ```bash
26
+ ./install-ubuntu.sh
27
+ ```
28
+
29
+ Generic Linux source install:
30
+
31
+ ```bash
32
+ ./install.sh
33
+ ```
34
+
35
+ Open **Dictate** from the app launcher after install.
36
+
37
+ Windows source install, from the repo/source directory:
38
+
39
+ ```powershell
40
+ powershell -ExecutionPolicy Bypass -File .\install-windows-wizard.ps1
41
+ ```
42
+
43
+ The local `.ps1` installer scripts must be run from a checkout or extracted
44
+ source directory. They will not work from `C:\Windows\System32`.
45
+
46
+ Node/npm users can also run:
47
+
48
+ ```powershell
49
+ npx @iamsamuelrodda/dictate install
50
+ ```
51
+
52
+ ## Workflow
53
+
54
+ ```text
55
+ Open Dictate
56
+ Select Model
57
+ Set API key if using a hosted model
58
+ Set push-to-talk shortcut if desired
59
+ Hold shortcut, speak, release
60
+ Review Recent History when needed
61
+ ```
62
+
63
+ By default, Dictate installs a normal app launcher entry and starts on sign-in.
64
+ Startup can be changed from Settings.
65
+
66
+ ## Update And Uninstall
67
+
68
+ Windows installed from the hosted installer:
69
+
70
+ ```powershell
71
+ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/update.ps1 | iex"
72
+ powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/uninstall.ps1 | iex"
73
+ ```
74
+
75
+ Windows from source:
76
+
77
+ ```powershell
78
+ powershell -ExecutionPolicy Bypass -File .\update-windows.ps1
79
+ powershell -ExecutionPolicy Bypass -File .\uninstall-windows.ps1
80
+ ```
81
+
82
+ Linux:
83
+
84
+ ```bash
85
+ ./update.sh
86
+ ./uninstall.sh
87
+ ```
88
+
89
+ Use `-RemoveUserData` on Windows or `--remove-user-data` on Linux only when you
90
+ also want to remove config, logs, history, and downloaded model data.
91
+
92
+ ## What It Does Today
93
+
94
+ - Starts from the Windows Start Menu or Linux app launcher
95
+ - Runs as a tray app
96
+ - Types dictated text into the focused app
97
+ - Supports configurable push-to-talk
98
+ - Shows selected model/status in the app UI
99
+ - Supports launch on startup
100
+ - Stores hosted-provider API keys in the OS secret store
101
+ - Keeps a small Recent History for copy/paste recovery
102
+ - Provides installer, updater, uninstaller, and doctor paths
103
+
104
+ ## Models
105
+
106
+ Supported provider defaults:
107
+
108
+ - `faster-whisper/turbo` for local transcription
109
+ - `openai/gpt-4o-mini-transcribe`
110
+ - `xai/grok-speech-to-text`
111
+ - `gemini/gemini-3-flash-preview`
112
+
113
+ Local transcription can use CPU or GPU where supported. Hosted providers require
114
+ an API key before they can be selected.
115
+
116
+ ## Commands
117
+
118
+ ```bash
119
+ dictate
120
+ dictate --no-tray
121
+ dictate --once
122
+ dictate --once --copy
123
+ dictate doctor --quick
124
+ dictate doctor --quick --fix
125
+ dictate doctor --check-model-load
126
+ ```
127
+
128
+ Hotword and model options are available from Settings. CLI flags still exist for
129
+ automation and testing:
130
+
131
+ ```bash
132
+ dictate --stt-backend faster-whisper --model turbo
133
+ dictate --stt-backend openai --model gpt-4o-mini-transcribe
134
+ dictate --stt-backend xai --model grok-speech-to-text
135
+ dictate --stt-backend gemini --model gemini-3-flash-preview
136
+ dictate --add-hotword AcmeWidget
137
+ dictate --list-hotwords
138
+ ```
139
+
140
+ ## State
141
+
142
+ User state is local:
143
+
144
+ ```text
145
+ Linux:
146
+ ~/.config/dictate/config.yaml
147
+ ~/.local/share/dictate/
148
+
149
+ Windows:
150
+ %APPDATA%\dictate\config.yaml
151
+ %LOCALAPPDATA%\dictate\
152
+ ```
153
+
154
+ Repo defaults intentionally ship with `hotwords: []`. Hotwords are user-specific
155
+ and should not be packaged into the public repo default config.
156
+
157
+ ## Safety
158
+
159
+ - Dictate does not intentionally write raw API keys to `config.yaml`.
160
+ - API keys configured in the app use the OS secret store.
161
+ - Dictation text can be sensitive; check logs and issue reports before sharing.
162
+ - Important transcriptions should be verified before relying on them.
163
+ - Support and maintenance are best-effort.
164
+
165
+ ## Docs
166
+
167
+ - [Windows 11 support](docs/windows-11.md)
168
+ - [Recent History spec](docs/recent-dictation-history-spec.md)
169
+ - [Release/versioning](docs/release-versioning.md)
170
+ - [Development streams](docs/development-streams.md)
171
+ - [Security policy](SECURITY.md)
172
+
173
+ ## License
174
+
175
+ MIT. See [LICENSE](LICENSE).
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ hotwords: []
2
+ push_to_talk_combo: ctrl_r
3
+ stt_backend: faster-whisper
4
+ stt_compute_type: int8
5
+ stt_device: auto
6
+ # Local Whisper uses the single supported local model:
7
+ # stt_model: turbo
8
+ # To avoid local compute, set one hosted backend:
9
+ # stt_backend: openai
10
+ # stt_model: gpt-4o-mini-transcribe
11
+ # openai_api_key_command: /path/to/command/that/prints/the/key
12
+ # stt_backend: xai
13
+ # stt_model: grok-speech-to-text
14
+ # xai_api_key_command: /path/to/command/that/prints/the/key
15
+ # stt_backend: gemini
16
+ # stt_model: gemini-3-flash-preview
17
+ # gemini_api_key_command: /path/to/command/that/prints/the/key
@@ -0,0 +1,392 @@
1
+ param(
2
+ [string]$InitialAction = "Install"
3
+ )
4
+
5
+ $ErrorActionPreference = "Stop"
6
+ $TermsUrl = "https://arcforge.au/terms"
7
+ $DocumentationUrl = "https://github.com/arcforgelabs/dictate#readme"
8
+ $HostedWindowsUpdateUrl = "https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/update.ps1"
9
+
10
+ Add-Type -AssemblyName System.Windows.Forms
11
+ Add-Type -AssemblyName System.Drawing
12
+
13
+ function New-ActionRadio {
14
+ param(
15
+ [string]$Text,
16
+ [string]$Tag,
17
+ [int]$Top,
18
+ [bool]$Checked = $false
19
+ )
20
+ $radio = New-Object System.Windows.Forms.RadioButton
21
+ $radio.Text = $Text
22
+ $radio.Tag = $Tag
23
+ $radio.Left = 18
24
+ $radio.Top = $Top
25
+ $radio.Width = 430
26
+ $radio.Checked = $Checked
27
+ return $radio
28
+ }
29
+
30
+ function Get-SelectedAction {
31
+ foreach ($control in $actionsGroup.Controls) {
32
+ if ($control.Checked) {
33
+ return [string]$control.Tag
34
+ }
35
+ }
36
+ return "Install"
37
+ }
38
+
39
+ function Append-Log {
40
+ param([string]$Message)
41
+ $logBox.AppendText($Message + [Environment]::NewLine)
42
+ $logBox.SelectionStart = $logBox.TextLength
43
+ $logBox.ScrollToCaret()
44
+ }
45
+
46
+ function Open-ExternalUrl {
47
+ param([string]$Url)
48
+ Start-Process $Url
49
+ }
50
+
51
+ function Sync-RunButton {
52
+ if ($null -ne $runButton) {
53
+ $runButton.Enabled = $termsCheck.Checked
54
+ }
55
+ }
56
+
57
+ function Sync-ShortcutOptions {
58
+ if ($null -ne $startupCheck) {
59
+ if ($shortcutCheck.Checked) {
60
+ $startupCheck.Enabled = $true
61
+ } else {
62
+ $startupCheck.Checked = $false
63
+ $startupCheck.Enabled = $false
64
+ }
65
+ }
66
+ }
67
+
68
+ function Get-StartMenuProgramsDir {
69
+ $programsDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
70
+ if (-not $env:APPDATA) {
71
+ $programsDir = Join-Path $HOME "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
72
+ }
73
+ return $programsDir
74
+ }
75
+
76
+ function Get-StartupShortcutPath {
77
+ return (Join-Path (Join-Path (Get-StartMenuProgramsDir) "Startup") "Dictate.lnk")
78
+ }
79
+
80
+ function Drain-LogQueue {
81
+ param($Queue)
82
+ $line = $null
83
+ while ($Queue.TryDequeue([ref]$line)) {
84
+ Append-Log $line
85
+ $line = $null
86
+ }
87
+ }
88
+
89
+ function Invoke-LoggedProcess {
90
+ param(
91
+ [string]$Label,
92
+ [string]$FileName,
93
+ [string]$ArgumentString,
94
+ [string]$WorkingDirectory = $PSScriptRoot
95
+ )
96
+
97
+ Append-Log "==> $Label"
98
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
99
+ $psi.FileName = $FileName
100
+ $psi.Arguments = $ArgumentString
101
+ $psi.WorkingDirectory = $WorkingDirectory
102
+ $psi.UseShellExecute = $false
103
+ $psi.RedirectStandardOutput = $true
104
+ $psi.RedirectStandardError = $true
105
+ $psi.CreateNoWindow = $true
106
+
107
+ $stdoutQueue = New-Object "System.Collections.Concurrent.ConcurrentQueue[string]"
108
+ $stderrQueue = New-Object "System.Collections.Concurrent.ConcurrentQueue[string]"
109
+ $stdoutHandler = [System.Diagnostics.DataReceivedEventHandler]{
110
+ param($sender, $eventArgs)
111
+ if ($null -ne $eventArgs.Data) {
112
+ $stdoutQueue.Enqueue($eventArgs.Data)
113
+ }
114
+ }
115
+ $stderrHandler = [System.Diagnostics.DataReceivedEventHandler]{
116
+ param($sender, $eventArgs)
117
+ if ($null -ne $eventArgs.Data) {
118
+ $stderrQueue.Enqueue($eventArgs.Data)
119
+ }
120
+ }
121
+
122
+ $process = New-Object System.Diagnostics.Process
123
+ $process.StartInfo = $psi
124
+ $process.add_OutputDataReceived($stdoutHandler)
125
+ $process.add_ErrorDataReceived($stderrHandler)
126
+ try {
127
+ [void]$process.Start()
128
+ $process.BeginOutputReadLine()
129
+ $process.BeginErrorReadLine()
130
+ while (-not $process.WaitForExit(100)) {
131
+ Drain-LogQueue $stdoutQueue
132
+ Drain-LogQueue $stderrQueue
133
+ [System.Windows.Forms.Application]::DoEvents()
134
+ }
135
+ $process.WaitForExit()
136
+ Drain-LogQueue $stdoutQueue
137
+ Drain-LogQueue $stderrQueue
138
+ if ($process.ExitCode -ne 0) {
139
+ throw "$Label failed with exit code $($process.ExitCode)."
140
+ }
141
+ } finally {
142
+ $process.remove_OutputDataReceived($stdoutHandler)
143
+ $process.remove_ErrorDataReceived($stderrHandler)
144
+ $process.Dispose()
145
+ }
146
+ }
147
+
148
+ function Invoke-Step {
149
+ param(
150
+ [string]$Label,
151
+ [string]$FilePath,
152
+ [string[]]$Arguments,
153
+ [string]$WorkingDirectory = $PSScriptRoot
154
+ )
155
+
156
+ $argumentString = (
157
+ @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$FilePath`"") + $Arguments
158
+ ) -join " "
159
+ Invoke-LoggedProcess -Label $Label -FileName "powershell" -ArgumentString $argumentString -WorkingDirectory $WorkingDirectory
160
+ }
161
+
162
+ function Invoke-DictateUpdate {
163
+ param([string[]]$Arguments)
164
+
165
+ if (Test-Path (Join-Path $PSScriptRoot ".git")) {
166
+ Invoke-Step "Updating Dictate" (Join-Path $PSScriptRoot "update-windows.ps1") $Arguments
167
+ return
168
+ }
169
+
170
+ $tempUpdater = Join-Path ([System.IO.Path]::GetTempPath()) ("dictate-update-" + [guid]::NewGuid().ToString("N") + ".ps1")
171
+ try {
172
+ Append-Log "==> Fetching current hosted updater"
173
+ Invoke-WebRequest -UseBasicParsing -Uri $HostedWindowsUpdateUrl -OutFile $tempUpdater
174
+ $workingDirectory = Split-Path -Parent $PSScriptRoot
175
+ Invoke-Step -Label "Updating Dictate from hosted source" -FilePath $tempUpdater -Arguments $Arguments -WorkingDirectory $workingDirectory
176
+ } finally {
177
+ Remove-Item -Force -ErrorAction SilentlyContinue -Path $tempUpdater
178
+ }
179
+ }
180
+
181
+ function Invoke-DictateDoctorFix {
182
+ $dictateExe = Join-Path $PSScriptRoot ".venv\Scripts\dictate.exe"
183
+ if (-not (Test-Path $dictateExe)) {
184
+ throw "Dictate is not installed in this source folder. Run Install first."
185
+ }
186
+
187
+ Invoke-LoggedProcess "Repairing Dictate with doctor --fix" $dictateExe "doctor --quick --fix --type-backend pynput"
188
+ }
189
+
190
+ function Start-SelectedAction {
191
+ $runButton.Enabled = $false
192
+ $closeButton.Enabled = $false
193
+ $logBox.Clear()
194
+ try {
195
+ if (-not $termsCheck.Checked) {
196
+ throw "Please acknowledge the Dictate terms before continuing."
197
+ }
198
+ $action = Get-SelectedAction
199
+ $commonArgs = @()
200
+ if (-not $verifyCheck.Checked) { $commonArgs += "-NoVerify" }
201
+ if (-not $prepareCheck.Checked) { $commonArgs += "-NoPrepareTurbo" }
202
+ if (-not $shortcutCheck.Checked) { $commonArgs += "-NoShortcut" }
203
+ if (-not $startupCheck.Checked) { $commonArgs += "-NoStartup" }
204
+
205
+ if ($action -eq "Install") {
206
+ Invoke-Step "Installing Dictate" (Join-Path $PSScriptRoot "install-windows.ps1") $commonArgs
207
+ } elseif ($action -eq "Update") {
208
+ $updateArgs = @($commonArgs)
209
+ if ($startupCheck.Checked) { $updateArgs += "-ForceStartup" }
210
+ Invoke-DictateUpdate $updateArgs
211
+ } elseif ($action -eq "Repair") {
212
+ Invoke-DictateDoctorFix
213
+ } elseif ($action -eq "Uninstall") {
214
+ $uninstallArgs = @()
215
+ if ($removeUserDataCheck.Checked) { $uninstallArgs += "-RemoveUserData" }
216
+ Invoke-Step "Uninstalling Dictate" (Join-Path $PSScriptRoot "uninstall-windows.ps1") $uninstallArgs
217
+ }
218
+ Append-Log ""
219
+ Append-Log "Done."
220
+ [System.Windows.Forms.MessageBox]::Show(
221
+ "Dictate $($action.ToLowerInvariant()) completed.",
222
+ "Dictate Setup",
223
+ [System.Windows.Forms.MessageBoxButtons]::OK,
224
+ [System.Windows.Forms.MessageBoxIcon]::Information
225
+ ) | Out-Null
226
+ } catch {
227
+ Append-Log ""
228
+ Append-Log "ERROR: $($_.Exception.Message)"
229
+ [System.Windows.Forms.MessageBox]::Show(
230
+ $_.Exception.Message,
231
+ "Dictate Setup",
232
+ [System.Windows.Forms.MessageBoxButtons]::OK,
233
+ [System.Windows.Forms.MessageBoxIcon]::Error
234
+ ) | Out-Null
235
+ } finally {
236
+ Sync-RunButton
237
+ $closeButton.Enabled = $true
238
+ }
239
+ }
240
+
241
+ $form = New-Object System.Windows.Forms.Form
242
+ $form.Text = "Dictate Setup"
243
+ $form.StartPosition = "CenterScreen"
244
+ $form.Width = 720
245
+ $form.Height = 600
246
+ $form.FormBorderStyle = "FixedDialog"
247
+ $form.MaximizeBox = $false
248
+
249
+ $title = New-Object System.Windows.Forms.Label
250
+ $title.Text = "Dictate Setup"
251
+ $title.Font = New-Object System.Drawing.Font("Segoe UI", 16, [System.Drawing.FontStyle]::Bold)
252
+ $title.Left = 18
253
+ $title.Top = 14
254
+ $title.Width = 660
255
+ $title.Height = 34
256
+ $form.Controls.Add($title)
257
+
258
+ $subtitle = New-Object System.Windows.Forms.Label
259
+ $subtitle.Text = "Install, update, repair, or remove Dictate for this Windows user."
260
+ $subtitle.Left = 20
261
+ $subtitle.Top = 52
262
+ $subtitle.Width = 660
263
+ $subtitle.Height = 24
264
+ $form.Controls.Add($subtitle)
265
+
266
+ $actionsGroup = New-Object System.Windows.Forms.GroupBox
267
+ $actionsGroup.Text = "Action"
268
+ $actionsGroup.Left = 18
269
+ $actionsGroup.Top = 88
270
+ $actionsGroup.Width = 660
271
+ $actionsGroup.Height = 140
272
+ $form.Controls.Add($actionsGroup)
273
+
274
+ $actionsGroup.Controls.Add((New-ActionRadio "Install Dictate" "Install" 24 ($InitialAction -eq "Install")))
275
+ $actionsGroup.Controls.Add((New-ActionRadio "Update Dictate" "Update" 52 ($InitialAction -eq "Update")))
276
+ $actionsGroup.Controls.Add((New-ActionRadio "Repair launchers and runtime checks (doctor --fix)" "Repair" 80 ($InitialAction -eq "Repair")))
277
+ $actionsGroup.Controls.Add((New-ActionRadio "Uninstall Dictate" "Uninstall" 108 ($InitialAction -eq "Uninstall")))
278
+
279
+ $optionsGroup = New-Object System.Windows.Forms.GroupBox
280
+ $optionsGroup.Text = "Options"
281
+ $optionsGroup.Left = 18
282
+ $optionsGroup.Top = 238
283
+ $optionsGroup.Width = 660
284
+ $optionsGroup.Height = 142
285
+ $form.Controls.Add($optionsGroup)
286
+
287
+ $startupCheck = New-Object System.Windows.Forms.CheckBox
288
+ $startupCheck.Text = "Launch on startup"
289
+ $startupCheck.Left = 18
290
+ $startupCheck.Top = 24
291
+ $startupCheck.Width = 180
292
+ $startupCheck.Checked = $true
293
+ $optionsGroup.Controls.Add($startupCheck)
294
+ if ($InitialAction -eq "Update") {
295
+ $startupCheck.Checked = Test-Path (Get-StartupShortcutPath)
296
+ }
297
+
298
+ $shortcutCheck = New-Object System.Windows.Forms.CheckBox
299
+ $shortcutCheck.Text = "Create Start Menu entry"
300
+ $shortcutCheck.Left = 220
301
+ $shortcutCheck.Top = 24
302
+ $shortcutCheck.Width = 200
303
+ $shortcutCheck.Checked = $true
304
+ $shortcutCheck.Add_CheckedChanged({ Sync-ShortcutOptions })
305
+ $optionsGroup.Controls.Add($shortcutCheck)
306
+
307
+ $prepareCheck = New-Object System.Windows.Forms.CheckBox
308
+ $prepareCheck.Text = "Prepare default local model"
309
+ $prepareCheck.Left = 18
310
+ $prepareCheck.Top = 54
311
+ $prepareCheck.Width = 220
312
+ $prepareCheck.Checked = $true
313
+ $optionsGroup.Controls.Add($prepareCheck)
314
+
315
+ $verifyCheck = New-Object System.Windows.Forms.CheckBox
316
+ $verifyCheck.Text = "Run verification"
317
+ $verifyCheck.Left = 260
318
+ $verifyCheck.Top = 54
319
+ $verifyCheck.Width = 160
320
+ $verifyCheck.Checked = $true
321
+ $optionsGroup.Controls.Add($verifyCheck)
322
+
323
+ $removeUserDataCheck = New-Object System.Windows.Forms.CheckBox
324
+ $removeUserDataCheck.Text = "Also remove user config, logs, history, and models"
325
+ $removeUserDataCheck.Left = 18
326
+ $removeUserDataCheck.Top = 78
327
+ $removeUserDataCheck.Width = 360
328
+ $removeUserDataCheck.Checked = $false
329
+ $optionsGroup.Controls.Add($removeUserDataCheck)
330
+
331
+ $termsCheck = New-Object System.Windows.Forms.CheckBox
332
+ $termsCheck.Text = "I understand Dictate has real-world risks and agree to the Arc Forge terms"
333
+ $termsCheck.Left = 18
334
+ $termsCheck.Top = 104
335
+ $termsCheck.Width = 500
336
+ $termsCheck.Checked = $false
337
+ $termsCheck.Add_CheckedChanged({ Sync-RunButton })
338
+ $optionsGroup.Controls.Add($termsCheck)
339
+
340
+ $expectationLabel = New-Object System.Windows.Forms.Label
341
+ $expectationLabel.Text = "Support and maintenance are best-effort. Check important output and report issues."
342
+ $expectationLabel.Left = 36
343
+ $expectationLabel.Top = 124
344
+ $expectationLabel.Width = 500
345
+ $expectationLabel.Height = 18
346
+ $optionsGroup.Controls.Add($expectationLabel)
347
+
348
+ $termsLink = New-Object System.Windows.Forms.LinkLabel
349
+ $termsLink.Text = "Terms"
350
+ $termsLink.Left = 548
351
+ $termsLink.Top = 105
352
+ $termsLink.Width = 52
353
+ $termsLink.Add_Click({ Open-ExternalUrl $TermsUrl })
354
+ $optionsGroup.Controls.Add($termsLink)
355
+
356
+ $docsLink = New-Object System.Windows.Forms.LinkLabel
357
+ $docsLink.Text = "Documentation"
358
+ $docsLink.Left = 548
359
+ $docsLink.Top = 124
360
+ $docsLink.Width = 120
361
+ $docsLink.Add_Click({ Open-ExternalUrl $DocumentationUrl })
362
+ $optionsGroup.Controls.Add($docsLink)
363
+
364
+ $logBox = New-Object System.Windows.Forms.TextBox
365
+ $logBox.Left = 18
366
+ $logBox.Top = 394
367
+ $logBox.Width = 660
368
+ $logBox.Height = 116
369
+ $logBox.Multiline = $true
370
+ $logBox.ScrollBars = "Vertical"
371
+ $logBox.ReadOnly = $true
372
+ $logBox.Font = New-Object System.Drawing.Font("Consolas", 9)
373
+ $form.Controls.Add($logBox)
374
+
375
+ $runButton = New-Object System.Windows.Forms.Button
376
+ $runButton.Text = "Run"
377
+ $runButton.Left = 496
378
+ $runButton.Top = 524
379
+ $runButton.Width = 86
380
+ $runButton.Enabled = $false
381
+ $runButton.Add_Click({ Start-SelectedAction })
382
+ $form.Controls.Add($runButton)
383
+
384
+ $closeButton = New-Object System.Windows.Forms.Button
385
+ $closeButton.Text = "Close"
386
+ $closeButton.Left = 592
387
+ $closeButton.Top = 524
388
+ $closeButton.Width = 86
389
+ $closeButton.Add_Click({ $form.Close() })
390
+ $form.Controls.Add($closeButton)
391
+
392
+ [void]$form.ShowDialog()