@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
package/install.sh
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install/update dictate into a standalone venv at ~/.local/share/dictate.
|
|
3
|
+
# Uses --system-site-packages so GTK/gi bindings are available.
|
|
4
|
+
# Re-run this script after pulling changes to update the installation.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
INSTALL_DIR="$HOME/.local/share/dictate"
|
|
9
|
+
BIN_DIR="$HOME/.local/bin"
|
|
10
|
+
DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
|
11
|
+
AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
|
|
12
|
+
ICON_DIR="$INSTALL_DIR/share/icons"
|
|
13
|
+
ICON_PATH="$ICON_DIR/dictate-simple.png"
|
|
14
|
+
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/dictate"
|
|
15
|
+
CONFIG_PATH="$CONFIG_DIR/config.yaml"
|
|
16
|
+
DEFAULT_CONFIG_SOURCE="$SCRIPT_DIR/config/default-config.yaml"
|
|
17
|
+
VERIFY=1
|
|
18
|
+
PREPARE_TURBO=1
|
|
19
|
+
SEED_DEFAULT_CONFIG=1
|
|
20
|
+
STARTUP=1
|
|
21
|
+
INSTALL_UI=1
|
|
22
|
+
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
|
23
|
+
|
|
24
|
+
# Prefer the distro Python so --system-site-packages can see modules such as
|
|
25
|
+
# python3-gi from /usr/lib/python3/dist-packages.
|
|
26
|
+
if [ -x /usr/bin/python3 ]; then
|
|
27
|
+
PYTHON_BIN="/usr/bin/python3"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat <<EOF
|
|
32
|
+
Usage: $0 [--no-verify] [--no-prepare-turbo] [--no-seed-default-config] [--no-startup] [--no-ui] [--session-backend auto|x11|wayland]
|
|
33
|
+
|
|
34
|
+
Installs dictate into ~/.local/share/dictate, links ~/.local/bin/dictate and
|
|
35
|
+
~/.local/bin/dictate-ui-server, seeds the default config on first install,
|
|
36
|
+
creates app launcher/autostart entries, prepares the faster-whisper turbo model,
|
|
37
|
+
and installs the desktop "Quiet Console" UI shell when a build toolchain is
|
|
38
|
+
present (unless disabled). All steps degrade gracefully when prerequisites are
|
|
39
|
+
missing.
|
|
40
|
+
EOF
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
detect_session_backend() {
|
|
44
|
+
if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
|
|
45
|
+
printf 'wayland\n'
|
|
46
|
+
return
|
|
47
|
+
fi
|
|
48
|
+
if [ -n "${DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "x11" ]; then
|
|
49
|
+
printf 'x11\n'
|
|
50
|
+
return
|
|
51
|
+
fi
|
|
52
|
+
if [ -n "${XDG_SESSION_ID:-}" ] && command -v loginctl >/dev/null 2>&1; then
|
|
53
|
+
local detected
|
|
54
|
+
detected="$(loginctl show-session "$XDG_SESSION_ID" -p Type --value 2>/dev/null || true)"
|
|
55
|
+
if [ "$detected" = "wayland" ] || [ "$detected" = "x11" ]; then
|
|
56
|
+
printf '%s\n' "$detected"
|
|
57
|
+
return
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
printf 'unknown\n'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
SESSION_BACKEND="auto"
|
|
64
|
+
|
|
65
|
+
while [ "$#" -gt 0 ]; do
|
|
66
|
+
case "$1" in
|
|
67
|
+
--no-verify)
|
|
68
|
+
VERIFY=0
|
|
69
|
+
;;
|
|
70
|
+
--no-prepare-turbo)
|
|
71
|
+
PREPARE_TURBO=0
|
|
72
|
+
;;
|
|
73
|
+
--no-seed-default-config)
|
|
74
|
+
SEED_DEFAULT_CONFIG=0
|
|
75
|
+
;;
|
|
76
|
+
--no-startup)
|
|
77
|
+
STARTUP=0
|
|
78
|
+
;;
|
|
79
|
+
--no-ui)
|
|
80
|
+
INSTALL_UI=0
|
|
81
|
+
;;
|
|
82
|
+
--session-backend)
|
|
83
|
+
shift
|
|
84
|
+
SESSION_BACKEND="${1:-}"
|
|
85
|
+
;;
|
|
86
|
+
--session-backend=*)
|
|
87
|
+
SESSION_BACKEND="${1#*=}"
|
|
88
|
+
;;
|
|
89
|
+
-h|--help)
|
|
90
|
+
usage
|
|
91
|
+
exit 0
|
|
92
|
+
;;
|
|
93
|
+
*)
|
|
94
|
+
usage
|
|
95
|
+
exit 1
|
|
96
|
+
;;
|
|
97
|
+
esac
|
|
98
|
+
shift
|
|
99
|
+
done
|
|
100
|
+
|
|
101
|
+
if [ "$SESSION_BACKEND" = "auto" ]; then
|
|
102
|
+
SESSION_BACKEND="$(detect_session_backend)"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
PIP_TARGET="$SCRIPT_DIR"
|
|
106
|
+
if [ "$SESSION_BACKEND" = "x11" ]; then
|
|
107
|
+
PIP_TARGET="${SCRIPT_DIR}[x11]"
|
|
108
|
+
elif [ "$SESSION_BACKEND" = "wayland" ]; then
|
|
109
|
+
PIP_TARGET="${SCRIPT_DIR}[wayland]"
|
|
110
|
+
elif [ "$SESSION_BACKEND" = "unknown" ]; then
|
|
111
|
+
PIP_TARGET="${SCRIPT_DIR}[x11,wayland]"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
echo "Detected install session backend: $SESSION_BACKEND"
|
|
115
|
+
|
|
116
|
+
# The packaged build (.deb/.rpm) and this source install both ship a tray/daemon
|
|
117
|
+
# and would fight over the push-to-talk key. Warn rather than silently double up.
|
|
118
|
+
if command -v dpkg-query >/dev/null 2>&1 \
|
|
119
|
+
&& dpkg-query -W -f='${Status}' dictate 2>/dev/null | grep -q "install ok installed"; then
|
|
120
|
+
echo "WARNING: a packaged Dictate (.deb) is already installed and would conflict."
|
|
121
|
+
echo " Use one install method. To remove the package first: sudo apt remove dictate"
|
|
122
|
+
elif command -v rpm >/dev/null 2>&1 && rpm -q dictate >/dev/null 2>&1; then
|
|
123
|
+
echo "WARNING: a packaged Dictate (.rpm) is already installed and would conflict."
|
|
124
|
+
echo " Use one install method. To remove the package first: sudo dnf remove dictate"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
echo "Creating venv at $INSTALL_DIR ..."
|
|
128
|
+
uv venv "$INSTALL_DIR/venv" --python "$PYTHON_BIN" --system-site-packages --quiet
|
|
129
|
+
|
|
130
|
+
echo "Installing dictate from $PIP_TARGET ..."
|
|
131
|
+
uv pip install "$PIP_TARGET" --python "$INSTALL_DIR/venv/bin/python" --quiet
|
|
132
|
+
|
|
133
|
+
echo "Linking binaries ..."
|
|
134
|
+
mkdir -p "$BIN_DIR"
|
|
135
|
+
ln -sf "$INSTALL_DIR/venv/bin/dictate" "$BIN_DIR/dictate"
|
|
136
|
+
# Expose the control server so the desktop shell can launch it on PATH.
|
|
137
|
+
ln -sf "$INSTALL_DIR/venv/bin/dictate-ui-server" "$BIN_DIR/dictate-ui-server"
|
|
138
|
+
|
|
139
|
+
echo "Installing icon ..."
|
|
140
|
+
mkdir -p "$ICON_DIR"
|
|
141
|
+
rm -f "$ICON_DIR/dictate-controls.png" "$ICON_DIR/dictate.png"
|
|
142
|
+
install -m 644 "$SCRIPT_DIR/assets/dictate.png" "$ICON_PATH"
|
|
143
|
+
|
|
144
|
+
echo "Installing desktop entry ..."
|
|
145
|
+
mkdir -p "$DESKTOP_DIR"
|
|
146
|
+
rm -f "$DESKTOP_DIR/dictate-settings.desktop"
|
|
147
|
+
cat > "$DESKTOP_DIR/dictate.desktop" <<EOF
|
|
148
|
+
[Desktop Entry]
|
|
149
|
+
Name=Dictate
|
|
150
|
+
Comment=Dictate into the focused app
|
|
151
|
+
Exec=$HOME/.local/bin/dictate
|
|
152
|
+
Icon=$ICON_PATH
|
|
153
|
+
Type=Application
|
|
154
|
+
Categories=AudioVideo;Audio;
|
|
155
|
+
Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
|
|
156
|
+
Terminal=false
|
|
157
|
+
EOF
|
|
158
|
+
|
|
159
|
+
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
|
|
160
|
+
|
|
161
|
+
if [ "$STARTUP" -eq 1 ]; then
|
|
162
|
+
echo "Installing autostart entry ..."
|
|
163
|
+
mkdir -p "$AUTOSTART_DIR"
|
|
164
|
+
cat > "$AUTOSTART_DIR/dictate.desktop" <<EOF
|
|
165
|
+
[Desktop Entry]
|
|
166
|
+
Name=Dictate
|
|
167
|
+
Comment=Dictate into the focused app
|
|
168
|
+
Exec=$HOME/.local/bin/dictate
|
|
169
|
+
Icon=$ICON_PATH
|
|
170
|
+
Type=Application
|
|
171
|
+
Categories=AudioVideo;Audio;
|
|
172
|
+
Keywords=voice;speech;transcription;dictation;asr;whisper;canary;
|
|
173
|
+
Terminal=false
|
|
174
|
+
X-GNOME-Autostart-enabled=true
|
|
175
|
+
EOF
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
print_ui_hint() {
|
|
179
|
+
cat <<EOF
|
|
180
|
+
The Quiet Console desktop UI is optional — the tray app works without it
|
|
181
|
+
(native dialogs as fallback). To add it later:
|
|
182
|
+
- download a .deb / AppImage from the GitHub release, or
|
|
183
|
+
- build it from this checkout: scripts/build-linux-desktop.sh
|
|
184
|
+
See ui-shell/README.md for details.
|
|
185
|
+
EOF
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
install_desktop_ui() {
|
|
189
|
+
local shell_src="$SCRIPT_DIR/ui-shell"
|
|
190
|
+
local target="$BIN_DIR/dictate-ui-shell"
|
|
191
|
+
|
|
192
|
+
if command -v dictate-ui-shell >/dev/null 2>&1; then
|
|
193
|
+
echo "Desktop UI shell already installed: $(command -v dictate-ui-shell)"
|
|
194
|
+
return 0
|
|
195
|
+
fi
|
|
196
|
+
if [ ! -d "$shell_src" ]; then
|
|
197
|
+
echo "Desktop UI shell sources not present; skipping the optional UI."
|
|
198
|
+
print_ui_hint
|
|
199
|
+
return 0
|
|
200
|
+
fi
|
|
201
|
+
if ! command -v cargo >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1 \
|
|
202
|
+
|| ! pkg-config --exists webkit2gtk-4.1 2>/dev/null; then
|
|
203
|
+
echo "Desktop UI build toolchain not found (needs cargo, npm, webkit2gtk-4.1-dev)."
|
|
204
|
+
print_ui_hint
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
echo "Building the desktop UI shell (this can take a few minutes) ..."
|
|
209
|
+
npm --prefix "$SCRIPT_DIR/ui" ci >/dev/null 2>&1 \
|
|
210
|
+
|| npm --prefix "$SCRIPT_DIR/ui" install >/dev/null 2>&1 \
|
|
211
|
+
|| { echo "UI front-end install failed; skipping the optional shell."; print_ui_hint; return 0; }
|
|
212
|
+
if ! npm --prefix "$SCRIPT_DIR/ui" run build >/dev/null 2>&1; then
|
|
213
|
+
echo "UI front-end build failed; skipping the optional shell."; print_ui_hint; return 0
|
|
214
|
+
fi
|
|
215
|
+
if ! ( cd "$shell_src" && cargo build --release --manifest-path src-tauri/Cargo.toml ); then
|
|
216
|
+
echo "Desktop UI shell build failed; the engine + tray remain fully functional."
|
|
217
|
+
print_ui_hint
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
install -m 755 "$shell_src/src-tauri/target/release/dictate-ui-shell" "$target"
|
|
221
|
+
echo "Installed desktop UI shell: $target"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if [ "$INSTALL_UI" -eq 1 ]; then
|
|
225
|
+
install_desktop_ui
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if [ "$SEED_DEFAULT_CONFIG" -eq 1 ]; then
|
|
229
|
+
if [ ! -f "$CONFIG_PATH" ]; then
|
|
230
|
+
if [ ! -f "$DEFAULT_CONFIG_SOURCE" ]; then
|
|
231
|
+
echo "Default config template not found: $DEFAULT_CONFIG_SOURCE"
|
|
232
|
+
exit 1
|
|
233
|
+
fi
|
|
234
|
+
echo "Seeding default config at $CONFIG_PATH ..."
|
|
235
|
+
mkdir -p "$CONFIG_DIR"
|
|
236
|
+
install -m 600 "$DEFAULT_CONFIG_SOURCE" "$CONFIG_PATH"
|
|
237
|
+
if [ "$SESSION_BACKEND" = "wayland" ]; then
|
|
238
|
+
sed -i 's/^push_to_talk_combo: .*/push_to_talk_combo: ctrl+space/' "$CONFIG_PATH"
|
|
239
|
+
fi
|
|
240
|
+
else
|
|
241
|
+
echo "Existing config found at $CONFIG_PATH; leaving it unchanged."
|
|
242
|
+
fi
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
DICTATE_BIN="$INSTALL_DIR/venv/bin/dictate"
|
|
246
|
+
|
|
247
|
+
run_logged_check() {
|
|
248
|
+
local label="$1"
|
|
249
|
+
local log_path="$2"
|
|
250
|
+
local timeout_seconds="$3"
|
|
251
|
+
shift 3
|
|
252
|
+
echo "Running: $label ..."
|
|
253
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
254
|
+
if ! timeout "${timeout_seconds}s" "$@" >"$log_path" 2>&1; then
|
|
255
|
+
echo "Command failed: $label"
|
|
256
|
+
echo "See: $log_path"
|
|
257
|
+
tail -n 120 "$log_path" || true
|
|
258
|
+
exit 1
|
|
259
|
+
fi
|
|
260
|
+
else
|
|
261
|
+
if ! "$@" >"$log_path" 2>&1; then
|
|
262
|
+
echo "Command failed: $label"
|
|
263
|
+
echo "See: $log_path"
|
|
264
|
+
tail -n 120 "$log_path" || true
|
|
265
|
+
exit 1
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if [ "$PREPARE_TURBO" -eq 1 ]; then
|
|
271
|
+
PREPARE_LOG="/tmp/dictate-install-prepare.log"
|
|
272
|
+
run_logged_check \
|
|
273
|
+
"dictate prepare-model --stt-backend faster-whisper --model turbo --device auto --compute-type int8" \
|
|
274
|
+
"$PREPARE_LOG" \
|
|
275
|
+
1800 \
|
|
276
|
+
"$DICTATE_BIN" \
|
|
277
|
+
prepare-model \
|
|
278
|
+
--stt-backend faster-whisper \
|
|
279
|
+
--model turbo \
|
|
280
|
+
--device auto \
|
|
281
|
+
--compute-type int8
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
if [ "$VERIFY" -eq 1 ]; then
|
|
285
|
+
VERIFY_LOG="/tmp/dictate-install-verify.log"
|
|
286
|
+
run_logged_check "dictate --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" --help
|
|
287
|
+
run_logged_check "dictate benchmark --help" "$VERIFY_LOG" 20 "$DICTATE_BIN" benchmark --help
|
|
288
|
+
run_logged_check \
|
|
289
|
+
"dictate doctor --quick --stt-backend faster-whisper --model turbo" \
|
|
290
|
+
"$VERIFY_LOG" \
|
|
291
|
+
20 \
|
|
292
|
+
"$DICTATE_BIN" \
|
|
293
|
+
doctor \
|
|
294
|
+
--quick \
|
|
295
|
+
--stt-backend faster-whisper \
|
|
296
|
+
--model turbo
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if [ "$STARTUP" -eq 1 ]; then
|
|
300
|
+
echo "Done. 'dictate' is now available on your PATH, in the app launcher, and starts when you sign in."
|
|
301
|
+
else
|
|
302
|
+
echo "Done. 'dictate' is now available on your PATH and in the app launcher."
|
|
303
|
+
fi
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageRoot = dirname(scriptDir);
|
|
8
|
+
const invokedAs = basename(process.argv[1] || "dictate-install");
|
|
9
|
+
const firstArg = process.argv[2] || "";
|
|
10
|
+
|
|
11
|
+
let command = "install";
|
|
12
|
+
let passthrough = process.argv.slice(2);
|
|
13
|
+
if (invokedAs.includes("update")) {
|
|
14
|
+
command = "update";
|
|
15
|
+
} else if (invokedAs.includes("uninstall")) {
|
|
16
|
+
command = "uninstall";
|
|
17
|
+
} else if (["install", "update", "uninstall", "wizard"].includes(firstArg)) {
|
|
18
|
+
command = firstArg;
|
|
19
|
+
passthrough = process.argv.slice(3);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isWindows = process.platform === "win32";
|
|
23
|
+
const scripts = isWindows
|
|
24
|
+
? {
|
|
25
|
+
install: ["powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(packageRoot, "install.ps1")]],
|
|
26
|
+
update: ["powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(packageRoot, "update.ps1")]],
|
|
27
|
+
uninstall: ["powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(packageRoot, "uninstall.ps1")]],
|
|
28
|
+
wizard: ["powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(packageRoot, "install.ps1"), "-Wizard"]],
|
|
29
|
+
}
|
|
30
|
+
: {
|
|
31
|
+
install: ["bash", [join(packageRoot, "install.sh")]],
|
|
32
|
+
update: ["bash", [join(packageRoot, "update.sh")]],
|
|
33
|
+
uninstall: ["bash", [join(packageRoot, "uninstall.sh")]],
|
|
34
|
+
wizard: null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const selected = scripts[command];
|
|
38
|
+
if (!selected) {
|
|
39
|
+
console.error(`dictate ${command} is not supported on ${process.platform}`);
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const [exe, args] = selected;
|
|
44
|
+
const result = spawnSync(exe, [...args, ...passthrough], { stdio: "inherit" });
|
|
45
|
+
if (result.error) {
|
|
46
|
+
console.error(result.error.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
process.exit(result.status ?? 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arcforgelabs/dictate",
|
|
3
|
+
"version": "2026.6.3",
|
|
4
|
+
"description": "Installer shim for Dictate desktop dictation.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://github.com/arcforgelabs/dictate#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/arcforgelabs/dictate.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/arcforgelabs/dictate/issues"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"dictate-install": "npm/dictate-lifecycle.mjs",
|
|
17
|
+
"dictate-update": "npm/dictate-lifecycle.mjs",
|
|
18
|
+
"dictate-uninstall": "npm/dictate-lifecycle.mjs"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"install.ps1",
|
|
22
|
+
"update.ps1",
|
|
23
|
+
"install-windows.ps1",
|
|
24
|
+
"install-windows-wizard.ps1",
|
|
25
|
+
"update-windows.ps1",
|
|
26
|
+
"uninstall.ps1",
|
|
27
|
+
"uninstall-windows.ps1",
|
|
28
|
+
"install.sh",
|
|
29
|
+
"update.sh",
|
|
30
|
+
"uninstall.sh",
|
|
31
|
+
"config/default-config.yaml",
|
|
32
|
+
"assets/dictate.ico",
|
|
33
|
+
"assets/dictate.png",
|
|
34
|
+
"assets/dictate-listening.ico",
|
|
35
|
+
"assets/dictate-listening.png",
|
|
36
|
+
"npm/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"pack:check": "npm pack --dry-run"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$Quiet,
|
|
3
|
+
[switch]$RemoveUserData
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = "Stop"
|
|
7
|
+
|
|
8
|
+
function Remove-IfExists {
|
|
9
|
+
param([string]$Path)
|
|
10
|
+
if (Test-Path $Path) {
|
|
11
|
+
Remove-Item -Force -Path $Path
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function Stop-DictateProcesses {
|
|
16
|
+
$currentPid = $PID
|
|
17
|
+
$matches = Get-CimInstance Win32_Process |
|
|
18
|
+
Where-Object {
|
|
19
|
+
$_.ProcessId -ne $currentPid -and (
|
|
20
|
+
$_.Name -in @("dictate.exe", "dictate-controls.exe") -or
|
|
21
|
+
$_.CommandLine -like '*dictate.exe* --type-backend pynput*' -or
|
|
22
|
+
$_.CommandLine -like '*pythonw.exe* -m dictate --type-backend pynput*' -or
|
|
23
|
+
$_.CommandLine -like '*dictate-daemon.cmd*' -or
|
|
24
|
+
$_.CommandLine -like '*dictate-controls*'
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
foreach ($match in $matches) {
|
|
28
|
+
Stop-Process -Id $match.ProcessId -Force -ErrorAction SilentlyContinue
|
|
29
|
+
}
|
|
30
|
+
Start-Sleep -Milliseconds 500
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$programsDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
|
|
34
|
+
if (-not $env:APPDATA) {
|
|
35
|
+
$programsDir = Join-Path $HOME "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
|
|
36
|
+
}
|
|
37
|
+
$startupDir = Join-Path $programsDir "Startup"
|
|
38
|
+
|
|
39
|
+
Remove-IfExists -Path (Join-Path $programsDir "Dictate.lnk")
|
|
40
|
+
Remove-IfExists -Path (Join-Path $programsDir "Dictate Controls.lnk")
|
|
41
|
+
Remove-IfExists -Path (Join-Path $startupDir "Dictate.lnk")
|
|
42
|
+
|
|
43
|
+
$uninstallKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Dictate"
|
|
44
|
+
if (Test-Path $uninstallKey) {
|
|
45
|
+
Remove-Item -Recurse -Force -Path $uninstallKey
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Stop-DictateProcesses
|
|
49
|
+
|
|
50
|
+
$venvDir = Join-Path $PSScriptRoot ".venv"
|
|
51
|
+
if (Test-Path $venvDir) {
|
|
52
|
+
Remove-Item -Recurse -Force -Path $venvDir
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$localAppData = $env:LOCALAPPDATA
|
|
56
|
+
if (-not $localAppData) {
|
|
57
|
+
$localAppData = Join-Path $HOME "AppData\Local"
|
|
58
|
+
}
|
|
59
|
+
$appData = $env:APPDATA
|
|
60
|
+
if (-not $appData) {
|
|
61
|
+
$appData = Join-Path $HOME "AppData\Roaming"
|
|
62
|
+
}
|
|
63
|
+
$dataDir = Join-Path $localAppData "dictate"
|
|
64
|
+
$configDir = Join-Path $appData "dictate"
|
|
65
|
+
|
|
66
|
+
if ($RemoveUserData) {
|
|
67
|
+
if (Test-Path $dataDir) {
|
|
68
|
+
Remove-Item -Recurse -Force -Path $dataDir
|
|
69
|
+
}
|
|
70
|
+
if (Test-Path $configDir) {
|
|
71
|
+
Remove-Item -Recurse -Force -Path $configDir
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (-not $Quiet) {
|
|
76
|
+
Write-Host "Dictate shortcuts, startup entry, app registration, and runtime venv removed."
|
|
77
|
+
if ($RemoveUserData) {
|
|
78
|
+
Write-Host "User config/data removed."
|
|
79
|
+
} else {
|
|
80
|
+
Write-Host "User config/data preserved. Re-run with -RemoveUserData to remove it."
|
|
81
|
+
}
|
|
82
|
+
Write-Host "Install source files were left in place: $PSScriptRoot"
|
|
83
|
+
}
|
package/uninstall.ps1
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$InstallRoot,
|
|
3
|
+
[switch]$Quiet,
|
|
4
|
+
[switch]$RemoveUserData
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
|
|
9
|
+
if (-not $InstallRoot) {
|
|
10
|
+
$base = $env:LOCALAPPDATA
|
|
11
|
+
if (-not $base) {
|
|
12
|
+
$base = Join-Path $HOME "AppData\Local"
|
|
13
|
+
}
|
|
14
|
+
$InstallRoot = Join-Path $base "Dictate"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$installRootPath = [System.IO.Path]::GetFullPath($InstallRoot)
|
|
18
|
+
$sourceDir = Join-Path $installRootPath "source"
|
|
19
|
+
$uninstaller = Join-Path $sourceDir "uninstall-windows.ps1"
|
|
20
|
+
|
|
21
|
+
if (Test-Path $uninstaller) {
|
|
22
|
+
$args = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $uninstaller)
|
|
23
|
+
if ($Quiet) { $args += "-Quiet" }
|
|
24
|
+
if ($RemoveUserData) { $args += "-RemoveUserData" }
|
|
25
|
+
& powershell @args
|
|
26
|
+
if ($LASTEXITCODE -ne 0) {
|
|
27
|
+
throw "Dictate Windows uninstaller failed with exit code $LASTEXITCODE."
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
if (-not $Quiet) {
|
|
31
|
+
Write-Host "Dictate source uninstaller not found: $uninstaller"
|
|
32
|
+
Write-Host "Removing known Start Menu, startup, and Installed Apps entries."
|
|
33
|
+
}
|
|
34
|
+
$programsDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
|
|
35
|
+
if (-not $env:APPDATA) {
|
|
36
|
+
$programsDir = Join-Path $HOME "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
|
|
37
|
+
}
|
|
38
|
+
Remove-Item -Force -ErrorAction SilentlyContinue -Path `
|
|
39
|
+
(Join-Path $programsDir "Dictate.lnk"), `
|
|
40
|
+
(Join-Path $programsDir "Dictate Controls.lnk"), `
|
|
41
|
+
(Join-Path $programsDir "Startup\Dictate.lnk")
|
|
42
|
+
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Dictate"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Test-Path $installRootPath) {
|
|
46
|
+
Remove-Item -Recurse -Force -Path $installRootPath
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (-not $Quiet) {
|
|
50
|
+
Write-Host "Dictate removed from: $installRootPath"
|
|
51
|
+
}
|
package/uninstall.sh
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Remove Dictate launchers/autostart and installed runtime files.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
INSTALL_DIR="$HOME/.local/share/dictate"
|
|
6
|
+
BIN_PATH="$HOME/.local/bin/dictate"
|
|
7
|
+
UI_SERVER_BIN_PATH="$HOME/.local/bin/dictate-ui-server"
|
|
8
|
+
DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
|
9
|
+
AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
|
|
10
|
+
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/dictate"
|
|
11
|
+
REMOVE_USER_DATA=0
|
|
12
|
+
QUIET=0
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<EOF
|
|
16
|
+
Usage: $0 [--remove-user-data] [--quiet]
|
|
17
|
+
|
|
18
|
+
Removes Dictate launcher/autostart entries, ~/.local/bin/dictate, and the
|
|
19
|
+
installed runtime venv/icons. User config, logs, history, and downloaded models
|
|
20
|
+
are preserved unless --remove-user-data is passed.
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
while [ "$#" -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--remove-user-data)
|
|
27
|
+
REMOVE_USER_DATA=1
|
|
28
|
+
;;
|
|
29
|
+
--quiet)
|
|
30
|
+
QUIET=1
|
|
31
|
+
;;
|
|
32
|
+
-h|--help)
|
|
33
|
+
usage
|
|
34
|
+
exit 0
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
usage
|
|
38
|
+
exit 1
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
shift
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
say() {
|
|
45
|
+
if [ "$QUIET" -eq 0 ]; then
|
|
46
|
+
printf '%s\n' "$1"
|
|
47
|
+
fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
remove_file() {
|
|
51
|
+
local path="$1"
|
|
52
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
53
|
+
rm -f "$path"
|
|
54
|
+
say "Removed $path"
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
remove_dir_if_empty() {
|
|
59
|
+
local path="$1"
|
|
60
|
+
if [ -d "$path" ]; then
|
|
61
|
+
rmdir "$path" 2>/dev/null || true
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Stop a running source-install daemon/server before pulling its venv out from
|
|
66
|
+
# under it (only our venv path is matched, never a packaged /usr install).
|
|
67
|
+
stop_source_processes() {
|
|
68
|
+
local pattern="$INSTALL_DIR/venv"
|
|
69
|
+
if command -v pkill >/dev/null 2>&1 && pgrep -f "$pattern" >/dev/null 2>&1; then
|
|
70
|
+
say "Stopping running source-install Dictate processes ..."
|
|
71
|
+
pkill -TERM -f "$pattern" 2>/dev/null || true
|
|
72
|
+
for _ in 1 2 3 4 5; do
|
|
73
|
+
pgrep -f "$pattern" >/dev/null 2>&1 || break
|
|
74
|
+
sleep 0.3
|
|
75
|
+
done
|
|
76
|
+
pkill -KILL -f "$pattern" 2>/dev/null || true
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
remove_managed_symlink() {
|
|
81
|
+
local link="$1" want="$2"
|
|
82
|
+
if [ -L "$link" ]; then
|
|
83
|
+
local target
|
|
84
|
+
target="$(readlink "$link" || true)"
|
|
85
|
+
if [ "$target" = "$want" ]; then
|
|
86
|
+
remove_file "$link"
|
|
87
|
+
else
|
|
88
|
+
say "Leaving $link because it points to $target"
|
|
89
|
+
fi
|
|
90
|
+
elif [ -e "$link" ]; then
|
|
91
|
+
say "Leaving $link because it is not a Dictate-managed symlink"
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
stop_source_processes
|
|
96
|
+
|
|
97
|
+
remove_file "$DESKTOP_DIR/dictate.desktop"
|
|
98
|
+
remove_file "$DESKTOP_DIR/dictate-settings.desktop"
|
|
99
|
+
remove_file "$AUTOSTART_DIR/dictate.desktop"
|
|
100
|
+
|
|
101
|
+
remove_managed_symlink "$BIN_PATH" "$INSTALL_DIR/venv/bin/dictate"
|
|
102
|
+
remove_managed_symlink "$UI_SERVER_BIN_PATH" "$INSTALL_DIR/venv/bin/dictate-ui-server"
|
|
103
|
+
|
|
104
|
+
rm -rf "$INSTALL_DIR/venv" "$INSTALL_DIR/share/icons" "$INSTALL_DIR/logs"
|
|
105
|
+
remove_dir_if_empty "$INSTALL_DIR/share"
|
|
106
|
+
say "Removed installed runtime files from $INSTALL_DIR"
|
|
107
|
+
|
|
108
|
+
if [ "$REMOVE_USER_DATA" -eq 1 ]; then
|
|
109
|
+
rm -rf "$CONFIG_DIR" "$INSTALL_DIR"
|
|
110
|
+
say "Removed user config/data: $CONFIG_DIR and $INSTALL_DIR"
|
|
111
|
+
else
|
|
112
|
+
say "Preserved user config/data: $CONFIG_DIR and $INSTALL_DIR"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
|
|
116
|
+
|
|
117
|
+
say "Dictate uninstall complete."
|