-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·222 lines (188 loc) · 7.73 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·222 lines (188 loc) · 7.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env bash
# Install voice-prompt: hold a hotkey to record a voice prompt, transcribe
# it locally with whisper, and drop straight into an opencode TUI session.
#
# Idempotent. Safe to re-run.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HAMMERSPOON_SRC="$REPO_DIR/hammerspoon"
SHARE_SRC="$REPO_DIR/share"
HAMMERSPOON_DIR="$HOME/.hammerspoon"
SHARE_DIR="$HOME/.local/share/voice-prompt"
WHISPER_DIR="$HOME/.local/share/whisper-cpp"
WORK_DIR="$HOME/voice-prompts"
WHISPER_MODEL_DEFAULT="ggml-large-v3-turbo-q5_0.bin"
WHISPER_MODEL_URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main/$WHISPER_MODEL_DEFAULT"
# --------------------------------------------------------------------
say() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; }
# --------------------------------------------------------------------
# 0. preflight: opencode and brew must be present
# --------------------------------------------------------------------
DEFAULT_OPENCODE_BIN="$HOME/.opencode/bin/opencode"
if ! command -v opencode >/dev/null && [[ ! -x "$DEFAULT_OPENCODE_BIN" ]]; then
cat <<EOF >&2
opencode is not installed. Install it first:
curl -fsSL https://opencode.ai/install | bash
Then authenticate:
opencode auth login
Then re-run this installer.
EOF
exit 1
fi
# --------------------------------------------------------------------
# 1. brew dependencies
# --------------------------------------------------------------------
say "Checking Homebrew dependencies"
command -v brew >/dev/null || fail "Homebrew is required. Install from https://brew.sh"
brew_install_if_missing() {
local kind="$1" name="$2"
if [[ "$kind" == "cask" ]]; then
if brew list --cask --versions "$name" >/dev/null 2>&1; then
echo " $name (cask) already installed"
else
say "Installing $name (cask)"
brew install --cask "$name"
fi
else
if brew list --versions "$name" >/dev/null 2>&1; then
echo " $name already installed"
else
say "Installing $name"
brew install "$name"
fi
fi
}
brew_install_if_missing cask hammerspoon
brew_install_if_missing formula whisper-cpp
brew_install_if_missing formula sox
brew_install_if_missing formula jq
# --------------------------------------------------------------------
# 2. whisper model
# --------------------------------------------------------------------
say "Checking whisper model"
mkdir -p "$WHISPER_DIR"
if [[ -f "$WHISPER_DIR/$WHISPER_MODEL_DEFAULT" ]]; then
echo " $WHISPER_MODEL_DEFAULT already downloaded"
else
say "Downloading $WHISPER_MODEL_DEFAULT (~570MB, one time)"
curl -L --progress-bar -o "$WHISPER_DIR/$WHISPER_MODEL_DEFAULT" "$WHISPER_MODEL_URL"
fi
# --------------------------------------------------------------------
# 3. symlink share/ files into ~/.local/share/voice-prompt/
# --------------------------------------------------------------------
say "Linking share files"
mkdir -p "$SHARE_DIR"
link_into_share() {
local src="$1" name="$2"
local dst="$SHARE_DIR/$name"
if [[ -L "$dst" && "$(readlink "$dst")" == "$src" ]]; then
echo " $name already linked"
return
fi
if [[ -e "$dst" ]]; then
local backup="$dst.backup.$(date +%Y%m%d_%H%M%S)"
warn "Existing $name found; backing up to $(basename "$backup")"
mv "$dst" "$backup"
fi
ln -s "$src" "$dst"
echo " linked $name"
}
link_into_share "$SHARE_SRC/pipeline.sh" pipeline.sh
link_into_share "$SHARE_SRC/run-in-terminal.sh" run-in-terminal.sh
link_into_share "$SHARE_SRC/stt-prompt.md" stt-prompt.md
chmod +x "$SHARE_SRC/pipeline.sh" "$SHARE_SRC/run-in-terminal.sh"
# --------------------------------------------------------------------
# 4. hammerspoon files
# --------------------------------------------------------------------
say "Linking Hammerspoon files"
mkdir -p "$HAMMERSPOON_DIR"
# voice-prompt.lua: symlink from repo (so updates flow automatically).
link_into_hs() {
local src="$1" name="$2"
local dst="$HAMMERSPOON_DIR/$name"
if [[ -L "$dst" && "$(readlink "$dst")" == "$src" ]]; then
echo " $name already linked"
return
fi
if [[ -e "$dst" ]]; then
local backup="$dst.backup.$(date +%Y%m%d_%H%M%S)"
warn "Existing $name found; backing up to $(basename "$backup")"
mv "$dst" "$backup"
fi
ln -s "$src" "$dst"
echo " linked $name"
}
link_into_hs "$HAMMERSPOON_SRC/voice-prompt.lua" voice-prompt.lua
# voice-prompt-env.lua: copy the example only if no env file exists. Users edit
# the live copy with their own paths/preferences; we never overwrite it.
ENV_DST="$HAMMERSPOON_DIR/voice-prompt-env.lua"
if [[ -f "$ENV_DST" ]]; then
echo " voice-prompt-env.lua already exists (keeping local edits)"
else
cp "$HAMMERSPOON_SRC/voice-prompt-env.lua.example" "$ENV_DST"
echo " seeded voice-prompt-env.lua from example"
fi
# --------------------------------------------------------------------
# 5. init.lua wiring
# --------------------------------------------------------------------
INIT="$HAMMERSPOON_DIR/init.lua"
INIT_MARKER="BEGIN opencode-voice block"
read -r -d '' INIT_BLOCK <<'LUA' || true
-- BEGIN opencode-voice block (managed by opencode-voice/install.sh)
require("hs.ipc") -- enables `hs -c "..."` from the shell
local vp = require("voice-prompt")
vp.setEnv(require("voice-prompt-env"))
vp.startHoldMode() -- hold right-Option to record. swap for vp.startToggleMode() for cmd+shift+space toggle
-- END opencode-voice block
LUA
if [[ ! -f "$INIT" ]]; then
say "Creating ~/.hammerspoon/init.lua"
printf '%s\n' "$INIT_BLOCK" > "$INIT"
elif grep -F -q -- "$INIT_MARKER" "$INIT"; then
echo " init.lua already wired (block present)"
elif grep -F -q -- 'require("voice-prompt")' "$INIT" || grep -F -q -- "require('voice-prompt')" "$INIT"; then
warn "init.lua already calls require(\"voice-prompt\") outside the managed block."
warn "Skipping append to avoid double-loading. Remove the manual block to let the installer manage it."
else
say "Appending voice-prompt block to existing init.lua"
printf '\n%s\n' "$INIT_BLOCK" >> "$INIT"
fi
# --------------------------------------------------------------------
# 6. work dir
# --------------------------------------------------------------------
mkdir -p "$WORK_DIR"
# --------------------------------------------------------------------
# 7. reload Hammerspoon if running
# --------------------------------------------------------------------
if pgrep -x Hammerspoon >/dev/null; then
say "Reloading Hammerspoon"
# `hs.reload()` invalidates the message port mid-call, so the exit code is
# always nonzero. Suppress and trust the reload happened (verified below).
hs -c "hs.reload()" >/dev/null 2>&1 || true
sleep 1
if hs -c 'print("ok")' >/dev/null 2>&1; then
echo " Hammerspoon reloaded"
else
warn "Hammerspoon CLI unreachable after reload. Reload manually from the menu bar if voice-prompt isn't responding."
fi
else
say "Launching Hammerspoon"
open -a Hammerspoon
fi
# --------------------------------------------------------------------
# 8. final notes
# --------------------------------------------------------------------
cat <<'EOF'
Done. One-time macOS permissions remain:
1. Accessibility (for the global hotkey)
System Settings → Privacy & Security → Accessibility → enable Hammerspoon
2. Microphone (for sox)
macOS will prompt the first time you record. Click Allow.
3. Automation (for the new-Terminal popup)
macOS will prompt the first time the pipeline runs. Click OK.
Usage: hold right-Option, speak, release. A new Terminal window opens straight
into the opencode TUI with your prompt as the first message.
Edit ~/.hammerspoon/voice-prompt-env.lua to change the working dir or pin a model.
EOF