vocalize-cli 0.7.0__tar.gz → 0.8.0__tar.gz
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.
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/CHANGELOG.md +27 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/PKG-INFO +17 -7
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/README.md +16 -6
- vocalize_cli-0.8.0/hooks/install_quick_action.py +135 -0
- vocalize_cli-0.8.0/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Info.plist +26 -0
- vocalize_cli-0.8.0/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Resources/document.wflow +212 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Resources/document.wflow +6 -3
- vocalize_cli-0.8.0/hooks/speak_options.py +245 -0
- vocalize_cli-0.8.0/tests/test_install_quick_action.py +264 -0
- vocalize_cli-0.8.0/tests/test_speak_options.py +348 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/__init__.py +1 -1
- vocalize_cli-0.7.0/hooks/install_quick_action.py +0 -82
- vocalize_cli-0.7.0/tests/test_install_quick_action.py +0 -158
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/.env.example +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/.github/workflows/ci.yml +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/.gitignore +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/LICENSE +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/claude_stop_hook.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/install_hook.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Resources/document.wflow +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/hooks/speak_url_gate.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/pyproject.toml +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/conftest.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_audio.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_auth.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_claude_stop_hook.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_cli.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_clipboard.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_config.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_install_hook.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_preprocess.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_speak_url_gate.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_tts.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/tests/test_wizard.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/__main__.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/audio.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/auth.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/cli.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/clipboard.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/config.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/exceptions.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/preprocess.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/tts.py +0 -0
- {vocalize_cli-0.7.0 → vocalize_cli-0.8.0}/vocalize/wizard.py +0 -0
|
@@ -3,6 +3,33 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## 0.8.0 - 2026-08-31
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- The "Speak with Vocalize" and "Speak Latest Plan" Quick Actions now show
|
|
11
|
+
a picker when input is over the cap and overflow is `ask`: speak all,
|
|
12
|
+
three summary depths — light (~25s), medium (~1 min), detailed (~2.5 min)
|
|
13
|
+
— or truncate. Summaries are generated by piping the text through
|
|
14
|
+
`claude -p --model haiku` (tools denied); each is spoken with a hard
|
|
15
|
+
ceiling so an over-long summary can't reintroduce a long read. New
|
|
16
|
+
`hooks/speak_options.py` is the picker/summarizer front-end.
|
|
17
|
+
- `hooks/install_quick_action.py` now also resolves and bakes a `claude`
|
|
18
|
+
binary path (plus the PATH additions it needs under a bare Services
|
|
19
|
+
environment) and the helper's location. `claude` is optional — without
|
|
20
|
+
it, the picker just omits the three summary depths. Re-run the installer
|
|
21
|
+
to pick up a newly installed `claude`. Picking a summary has a few
|
|
22
|
+
seconds of silent cold-start before audio begins.
|
|
23
|
+
|
|
24
|
+
## 0.7.1 - 2026-08-31
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- "Speak Latest Plan" Quick Action — a no-input Service that speaks the
|
|
29
|
+
newest plan under `~/.claude/plans/` on demand (dialog-asks when over
|
|
30
|
+
the cap). For hearing a Claude Code plan proposal before accepting it;
|
|
31
|
+
installed by the same `hooks/install_quick_action.py`.
|
|
32
|
+
|
|
6
33
|
## 0.7.0 - 2026-08-31
|
|
7
34
|
|
|
8
35
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vocalize-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A CLI that turns text, markdown, or piped stdin into speech via the ElevenLabs API, with markdown-table-aware preprocessing.
|
|
5
5
|
Project-URL: Homepage, https://github.com/matthager12-collab/vocalize
|
|
6
6
|
Project-URL: Repository, https://github.com/matthager12-collab/vocalize
|
|
@@ -259,10 +259,18 @@ Two Services let you use vocalize from any app without a terminal:
|
|
|
259
259
|
- **Speak with Vocalize** — highlight text anywhere, right-click →
|
|
260
260
|
Services → Speak with Vocalize. It stops whatever was already playing
|
|
261
261
|
and reads the selection. If the selection is over your cap and your
|
|
262
|
-
overflow mode is `ask`, a
|
|
263
|
-
|
|
262
|
+
overflow mode is `ask`, a picker offers **Speak all**, three summary
|
|
263
|
+
depths (**light** ~25s, **medium** ~1 min, **detailed** ~2.5 min), or
|
|
264
|
+
**Truncate**. Summaries are produced by `claude -p --model haiku` (with
|
|
265
|
+
tools denied) — picking one has a few seconds of silent cold-start
|
|
266
|
+
before audio begins. If no `claude` binary was found when you ran the
|
|
267
|
+
installer, the picker simply omits the three summary depths.
|
|
264
268
|
- **Stop Vocalize** — appears in every app's application menu → Services;
|
|
265
269
|
silences playback from anywhere.
|
|
270
|
+
- **Speak Latest Plan** — reads the newest Claude Code plan file
|
|
271
|
+
(`~/.claude/plans/`) aloud on demand. Made for the plan-approval moment:
|
|
272
|
+
the proposal card is up, you press your shortcut, hear the plan, then
|
|
273
|
+
accept or reject. Nothing reads unless you trigger it.
|
|
266
274
|
|
|
267
275
|
Install both:
|
|
268
276
|
|
|
@@ -271,10 +279,12 @@ python3 hooks/install_quick_action.py
|
|
|
271
279
|
```
|
|
272
280
|
|
|
273
281
|
This copies the bundles from `hooks/quick_actions/` into
|
|
274
|
-
`~/Library/Services/` with this machine's absolute `vocalize`
|
|
275
|
-
in, then refreshes the Services registry.
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
`~/Library/Services/` with this machine's absolute `vocalize`, `claude`,
|
|
283
|
+
and helper paths baked in, then refreshes the Services registry. Run it
|
|
284
|
+
from a normal terminal — its PATH is what gets captured. To trigger the
|
|
285
|
+
actions from the keyboard, assign shortcuts under System Settings →
|
|
286
|
+
Keyboard → Keyboard Shortcuts → Services (Stop Vocalize is worth a
|
|
287
|
+
shortcut of its own so you can silence a read from anywhere).
|
|
278
288
|
|
|
279
289
|
Some Electron apps (Claude Code desktop among them) don't expose the
|
|
280
290
|
Services menu for text selected in their own window. There, copy the
|
|
@@ -225,10 +225,18 @@ Two Services let you use vocalize from any app without a terminal:
|
|
|
225
225
|
- **Speak with Vocalize** — highlight text anywhere, right-click →
|
|
226
226
|
Services → Speak with Vocalize. It stops whatever was already playing
|
|
227
227
|
and reads the selection. If the selection is over your cap and your
|
|
228
|
-
overflow mode is `ask`, a
|
|
229
|
-
|
|
228
|
+
overflow mode is `ask`, a picker offers **Speak all**, three summary
|
|
229
|
+
depths (**light** ~25s, **medium** ~1 min, **detailed** ~2.5 min), or
|
|
230
|
+
**Truncate**. Summaries are produced by `claude -p --model haiku` (with
|
|
231
|
+
tools denied) — picking one has a few seconds of silent cold-start
|
|
232
|
+
before audio begins. If no `claude` binary was found when you ran the
|
|
233
|
+
installer, the picker simply omits the three summary depths.
|
|
230
234
|
- **Stop Vocalize** — appears in every app's application menu → Services;
|
|
231
235
|
silences playback from anywhere.
|
|
236
|
+
- **Speak Latest Plan** — reads the newest Claude Code plan file
|
|
237
|
+
(`~/.claude/plans/`) aloud on demand. Made for the plan-approval moment:
|
|
238
|
+
the proposal card is up, you press your shortcut, hear the plan, then
|
|
239
|
+
accept or reject. Nothing reads unless you trigger it.
|
|
232
240
|
|
|
233
241
|
Install both:
|
|
234
242
|
|
|
@@ -237,10 +245,12 @@ python3 hooks/install_quick_action.py
|
|
|
237
245
|
```
|
|
238
246
|
|
|
239
247
|
This copies the bundles from `hooks/quick_actions/` into
|
|
240
|
-
`~/Library/Services/` with this machine's absolute `vocalize`
|
|
241
|
-
in, then refreshes the Services registry.
|
|
242
|
-
|
|
243
|
-
|
|
248
|
+
`~/Library/Services/` with this machine's absolute `vocalize`, `claude`,
|
|
249
|
+
and helper paths baked in, then refreshes the Services registry. Run it
|
|
250
|
+
from a normal terminal — its PATH is what gets captured. To trigger the
|
|
251
|
+
actions from the keyboard, assign shortcuts under System Settings →
|
|
252
|
+
Keyboard → Keyboard Shortcuts → Services (Stop Vocalize is worth a
|
|
253
|
+
shortcut of its own so you can silence a read from anywhere).
|
|
244
254
|
|
|
245
255
|
Some Electron apps (Claude Code desktop among them) don't expose the
|
|
246
256
|
Services menu for text selected in their own window. There, copy the
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Copy the vocalize Quick Actions into ~/Library/Services/.
|
|
3
|
+
|
|
4
|
+
Unlike install_hook.py there is no shared settings file to merge into —
|
|
5
|
+
each Service is a self-contained bundle, so "install" is: copy the bundle
|
|
6
|
+
with this machine's absolute vocalize path baked in. Re-running simply
|
|
7
|
+
overwrites the same two bundles.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from xml.sax.saxutils import escape as xml_escape
|
|
18
|
+
|
|
19
|
+
TEMPLATES_DIR = Path(__file__).resolve().parent / "quick_actions"
|
|
20
|
+
SERVICES_DIR = Path.home() / "Library" / "Services"
|
|
21
|
+
BUNDLE_NAMES = (
|
|
22
|
+
"Speak with Vocalize.workflow",
|
|
23
|
+
"Stop Vocalize.workflow",
|
|
24
|
+
"Speak Latest Plan.workflow",
|
|
25
|
+
)
|
|
26
|
+
PLACEHOLDER = "__VOCALIZE_BIN__"
|
|
27
|
+
CLAUDE_PLACEHOLDER = "__CLAUDE_BIN__"
|
|
28
|
+
CLAUDE_EXTRA_PATH_PLACEHOLDER = "__CLAUDE_EXTRA_PATH__"
|
|
29
|
+
HELPER_PLACEHOLDER = "__HELPER__"
|
|
30
|
+
PBS = "/System/Library/CoreServices/pbs"
|
|
31
|
+
|
|
32
|
+
# Baked values land inside "..." in the Quick Action's zsh script. These
|
|
33
|
+
# characters could escape those quotes, so refuse rather than try to quote.
|
|
34
|
+
_UNSAFE_PATH_CHARS = set('"\\`$')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_vocalize_bin() -> Path:
|
|
38
|
+
repo_venv = Path(__file__).resolve().parent.parent / ".venv" / "bin" / "vocalize"
|
|
39
|
+
if repo_venv.is_file():
|
|
40
|
+
return repo_venv
|
|
41
|
+
on_path = shutil.which("vocalize")
|
|
42
|
+
if on_path:
|
|
43
|
+
return Path(on_path).resolve()
|
|
44
|
+
print(
|
|
45
|
+
"Could not find a vocalize binary (checked .venv/bin/vocalize next to "
|
|
46
|
+
"this repo, then PATH). Install vocalize first.",
|
|
47
|
+
file=sys.stderr,
|
|
48
|
+
)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _resolve_helper() -> Path:
|
|
53
|
+
helper = Path(__file__).resolve().parent / "speak_options.py"
|
|
54
|
+
if not helper.is_file():
|
|
55
|
+
print(f"Could not find the picker helper at {helper}.", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
return helper
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _resolve_claude() -> tuple[str, str]:
|
|
61
|
+
"""Return (claude_path, extra_PATH). Empty strings when claude is absent.
|
|
62
|
+
|
|
63
|
+
claude.exe may need `node` resolved via PATH even when invoked by its
|
|
64
|
+
absolute path, and a bare Services environment has neither on PATH, so
|
|
65
|
+
bake claude's and node's directories for the helper to prepend.
|
|
66
|
+
"""
|
|
67
|
+
found = shutil.which("claude")
|
|
68
|
+
if not found:
|
|
69
|
+
return "", ""
|
|
70
|
+
claude_path = Path(found).resolve()
|
|
71
|
+
dirs = [str(claude_path.parent)]
|
|
72
|
+
node = shutil.which("node")
|
|
73
|
+
if node:
|
|
74
|
+
node_dir = str(Path(node).resolve().parent)
|
|
75
|
+
if node_dir not in dirs:
|
|
76
|
+
dirs.append(node_dir)
|
|
77
|
+
return str(claude_path), os.pathsep.join(dirs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _install_one(name: str, substitutions: dict) -> None:
|
|
81
|
+
src = TEMPLATES_DIR / name
|
|
82
|
+
dest = SERVICES_DIR / name
|
|
83
|
+
if dest.exists():
|
|
84
|
+
shutil.rmtree(dest)
|
|
85
|
+
shutil.copytree(src, dest)
|
|
86
|
+
|
|
87
|
+
wflow = dest / "Contents" / "Resources" / "document.wflow"
|
|
88
|
+
text = wflow.read_text(encoding="utf-8")
|
|
89
|
+
for placeholder, value in substitutions.items():
|
|
90
|
+
text = text.replace(placeholder, xml_escape(value))
|
|
91
|
+
wflow.write_text(text, encoding="utf-8")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main() -> int:
|
|
95
|
+
bin_path = str(_resolve_vocalize_bin())
|
|
96
|
+
helper_path = str(_resolve_helper())
|
|
97
|
+
claude_path, claude_extra_path = _resolve_claude()
|
|
98
|
+
|
|
99
|
+
substitutions = {
|
|
100
|
+
PLACEHOLDER: bin_path,
|
|
101
|
+
HELPER_PLACEHOLDER: helper_path,
|
|
102
|
+
CLAUDE_PLACEHOLDER: claude_path,
|
|
103
|
+
CLAUDE_EXTRA_PATH_PLACEHOLDER: claude_extra_path,
|
|
104
|
+
}
|
|
105
|
+
for value in substitutions.values():
|
|
106
|
+
if _UNSAFE_PATH_CHARS & set(value):
|
|
107
|
+
print(
|
|
108
|
+
f"Refusing to install: a resolved path contains characters that "
|
|
109
|
+
f"would break the Quick Action script: {value!r}",
|
|
110
|
+
file=sys.stderr,
|
|
111
|
+
)
|
|
112
|
+
return 1
|
|
113
|
+
|
|
114
|
+
SERVICES_DIR.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
for name in BUNDLE_NAMES:
|
|
116
|
+
_install_one(name, substitutions)
|
|
117
|
+
print(f"Installed {name} -> {SERVICES_DIR / name}")
|
|
118
|
+
|
|
119
|
+
# Nudge the Services registry so the new entries appear without a logout.
|
|
120
|
+
subprocess.run([PBS, "-update"], check=False)
|
|
121
|
+
|
|
122
|
+
print(f"\nUsing vocalize at: {bin_path}")
|
|
123
|
+
if claude_path:
|
|
124
|
+
print(f"Summaries via claude at: {claude_path}")
|
|
125
|
+
else:
|
|
126
|
+
print("claude not found; Quick Actions will offer speak-all / truncate only.")
|
|
127
|
+
print('Highlight text in any app, then right-click -> Services -> "Speak with Vocalize".')
|
|
128
|
+
print("Keyboard shortcuts: System Settings -> Keyboard -> Keyboard Shortcuts -> Services.")
|
|
129
|
+
print("If the actions don't appear, re-open the Services submenu once, or log out and in.")
|
|
130
|
+
print("Run this installer from a normal terminal (its PATH is what gets baked in).")
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
sys.exit(main())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSServices</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>NSMenuItem</key>
|
|
9
|
+
<dict>
|
|
10
|
+
<key>default</key>
|
|
11
|
+
<string>Speak Latest Plan</string>
|
|
12
|
+
</dict>
|
|
13
|
+
<key>NSMessage</key>
|
|
14
|
+
<string>runWorkflowAsService</string>
|
|
15
|
+
</dict>
|
|
16
|
+
</array>
|
|
17
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
18
|
+
<string>en_US</string>
|
|
19
|
+
<key>CFBundleIdentifier</key>
|
|
20
|
+
<string>cards.arda.vocalize.speakplan</string>
|
|
21
|
+
<key>CFBundleName</key>
|
|
22
|
+
<string>Speak Latest Plan</string>
|
|
23
|
+
<key>CFBundleShortVersionString</key>
|
|
24
|
+
<string>1.0</string>
|
|
25
|
+
</dict>
|
|
26
|
+
</plist>
|
vocalize_cli-0.8.0/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Resources/document.wflow
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AMApplicationBuild</key>
|
|
6
|
+
<string>346</string>
|
|
7
|
+
<key>AMApplicationVersion</key>
|
|
8
|
+
<string>2.3</string>
|
|
9
|
+
<key>AMDocumentVersion</key>
|
|
10
|
+
<string>2</string>
|
|
11
|
+
<key>actions</key>
|
|
12
|
+
<array>
|
|
13
|
+
<dict>
|
|
14
|
+
<key>action</key>
|
|
15
|
+
<dict>
|
|
16
|
+
<key>AMAccepts</key>
|
|
17
|
+
<dict>
|
|
18
|
+
<key>Container</key>
|
|
19
|
+
<string>List</string>
|
|
20
|
+
<key>Optional</key>
|
|
21
|
+
<true/>
|
|
22
|
+
<key>Types</key>
|
|
23
|
+
<array>
|
|
24
|
+
<string>com.apple.cocoa.string</string>
|
|
25
|
+
</array>
|
|
26
|
+
</dict>
|
|
27
|
+
<key>AMActionVersion</key>
|
|
28
|
+
<string>2.0.3</string>
|
|
29
|
+
<key>AMApplication</key>
|
|
30
|
+
<array>
|
|
31
|
+
<string>Automator</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>AMParameterProperties</key>
|
|
34
|
+
<dict>
|
|
35
|
+
<key>COMMAND_STRING</key>
|
|
36
|
+
<dict/>
|
|
37
|
+
<key>CheckedForUserDefaultShell</key>
|
|
38
|
+
<dict/>
|
|
39
|
+
<key>inputMethod</key>
|
|
40
|
+
<dict/>
|
|
41
|
+
<key>shell</key>
|
|
42
|
+
<dict/>
|
|
43
|
+
<key>source</key>
|
|
44
|
+
<dict/>
|
|
45
|
+
</dict>
|
|
46
|
+
<key>AMProvides</key>
|
|
47
|
+
<dict>
|
|
48
|
+
<key>Container</key>
|
|
49
|
+
<string>List</string>
|
|
50
|
+
<key>Types</key>
|
|
51
|
+
<array>
|
|
52
|
+
<string>com.apple.cocoa.string</string>
|
|
53
|
+
</array>
|
|
54
|
+
</dict>
|
|
55
|
+
<key>ActionBundlePath</key>
|
|
56
|
+
<string>/System/Library/Automator/Run Shell Script.action</string>
|
|
57
|
+
<key>ActionName</key>
|
|
58
|
+
<string>Run Shell Script</string>
|
|
59
|
+
<key>ActionParameters</key>
|
|
60
|
+
<dict>
|
|
61
|
+
<key>COMMAND_STRING</key>
|
|
62
|
+
<string># No input: speak the newest Claude Code plan file on demand.
|
|
63
|
+
BIN="__VOCALIZE_BIN__"
|
|
64
|
+
if [[ ! -x "$BIN" ]]; then
|
|
65
|
+
/usr/bin/osascript -e 'display notification "vocalize not found — re-run hooks/install_quick_action.py" with title "Vocalize"'
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
PLAN=$(ls -t "$HOME/.claude/plans"/*.md 2>/dev/null | head -1)
|
|
69
|
+
if [[ -z "$PLAN" ]]; then
|
|
70
|
+
/usr/bin/osascript -e 'display notification "No plan files found in ~/.claude/plans." with title "Vocalize"'
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
"$BIN" stop >/dev/null 2>&1
|
|
74
|
+
export VOCALIZE_BIN="$BIN"
|
|
75
|
+
export CLAUDE_BIN="__CLAUDE_BIN__"
|
|
76
|
+
export CLAUDE_EXTRA_PATH="__CLAUDE_EXTRA_PATH__"
|
|
77
|
+
exec /usr/bin/python3 "__HELPER__" < "$PLAN"</string>
|
|
78
|
+
<key>CheckedForUserDefaultShell</key>
|
|
79
|
+
<false/>
|
|
80
|
+
<key>inputMethod</key>
|
|
81
|
+
<integer>1</integer>
|
|
82
|
+
<key>shell</key>
|
|
83
|
+
<string>/bin/zsh</string>
|
|
84
|
+
<key>source</key>
|
|
85
|
+
<string></string>
|
|
86
|
+
</dict>
|
|
87
|
+
<key>BundleIdentifier</key>
|
|
88
|
+
<string>com.apple.RunShellScript</string>
|
|
89
|
+
<key>CFBundleVersion</key>
|
|
90
|
+
<string>2.0.3</string>
|
|
91
|
+
<key>CanShowSelectedItemsWhenRun</key>
|
|
92
|
+
<false/>
|
|
93
|
+
<key>CanShowWhenRun</key>
|
|
94
|
+
<true/>
|
|
95
|
+
<key>Category</key>
|
|
96
|
+
<array>
|
|
97
|
+
<string>AMCategoryUtilities</string>
|
|
98
|
+
</array>
|
|
99
|
+
<key>Class Name</key>
|
|
100
|
+
<string>RunShellScriptAction</string>
|
|
101
|
+
<key>InputUUID</key>
|
|
102
|
+
<string>2C7F9E10-55D2-4B31-9A6E-8E0D4C71F301</string>
|
|
103
|
+
<key>Keywords</key>
|
|
104
|
+
<array>
|
|
105
|
+
<string>Shell</string>
|
|
106
|
+
<string>Script</string>
|
|
107
|
+
<string>Command</string>
|
|
108
|
+
<string>Run</string>
|
|
109
|
+
<string>Unix</string>
|
|
110
|
+
</array>
|
|
111
|
+
<key>OutputUUID</key>
|
|
112
|
+
<string>2C7F9E10-55D2-4B31-9A6E-8E0D4C71F302</string>
|
|
113
|
+
<key>UUID</key>
|
|
114
|
+
<string>2C7F9E10-55D2-4B31-9A6E-8E0D4C71F303</string>
|
|
115
|
+
<key>UnlocalizedApplications</key>
|
|
116
|
+
<array>
|
|
117
|
+
<string>Automator</string>
|
|
118
|
+
</array>
|
|
119
|
+
<key>arguments</key>
|
|
120
|
+
<dict>
|
|
121
|
+
<key>0</key>
|
|
122
|
+
<dict>
|
|
123
|
+
<key>default value</key>
|
|
124
|
+
<integer>0</integer>
|
|
125
|
+
<key>name</key>
|
|
126
|
+
<string>inputMethod</string>
|
|
127
|
+
<key>required</key>
|
|
128
|
+
<string>0</string>
|
|
129
|
+
<key>type</key>
|
|
130
|
+
<string>0</string>
|
|
131
|
+
<key>uuid</key>
|
|
132
|
+
<string>0</string>
|
|
133
|
+
</dict>
|
|
134
|
+
<key>1</key>
|
|
135
|
+
<dict>
|
|
136
|
+
<key>default value</key>
|
|
137
|
+
<string></string>
|
|
138
|
+
<key>name</key>
|
|
139
|
+
<string>source</string>
|
|
140
|
+
<key>required</key>
|
|
141
|
+
<string>0</string>
|
|
142
|
+
<key>type</key>
|
|
143
|
+
<string>0</string>
|
|
144
|
+
<key>uuid</key>
|
|
145
|
+
<string>1</string>
|
|
146
|
+
</dict>
|
|
147
|
+
<key>2</key>
|
|
148
|
+
<dict>
|
|
149
|
+
<key>default value</key>
|
|
150
|
+
<false/>
|
|
151
|
+
<key>name</key>
|
|
152
|
+
<string>CheckedForUserDefaultShell</string>
|
|
153
|
+
<key>required</key>
|
|
154
|
+
<string>0</string>
|
|
155
|
+
<key>type</key>
|
|
156
|
+
<string>0</string>
|
|
157
|
+
<key>uuid</key>
|
|
158
|
+
<string>2</string>
|
|
159
|
+
</dict>
|
|
160
|
+
<key>3</key>
|
|
161
|
+
<dict>
|
|
162
|
+
<key>default value</key>
|
|
163
|
+
<string></string>
|
|
164
|
+
<key>name</key>
|
|
165
|
+
<string>COMMAND_STRING</string>
|
|
166
|
+
<key>required</key>
|
|
167
|
+
<string>0</string>
|
|
168
|
+
<key>type</key>
|
|
169
|
+
<string>0</string>
|
|
170
|
+
<key>uuid</key>
|
|
171
|
+
<string>3</string>
|
|
172
|
+
</dict>
|
|
173
|
+
<key>4</key>
|
|
174
|
+
<dict>
|
|
175
|
+
<key>default value</key>
|
|
176
|
+
<string>/bin/sh</string>
|
|
177
|
+
<key>name</key>
|
|
178
|
+
<string>shell</string>
|
|
179
|
+
<key>required</key>
|
|
180
|
+
<string>0</string>
|
|
181
|
+
<key>type</key>
|
|
182
|
+
<string>0</string>
|
|
183
|
+
<key>uuid</key>
|
|
184
|
+
<string>4</string>
|
|
185
|
+
</dict>
|
|
186
|
+
</dict>
|
|
187
|
+
<key>isViewVisible</key>
|
|
188
|
+
<true/>
|
|
189
|
+
</dict>
|
|
190
|
+
<key>isViewVisible</key>
|
|
191
|
+
<true/>
|
|
192
|
+
</dict>
|
|
193
|
+
</array>
|
|
194
|
+
<key>connectors</key>
|
|
195
|
+
<dict/>
|
|
196
|
+
<key>workflowMetaData</key>
|
|
197
|
+
<dict>
|
|
198
|
+
<key>serviceApplicationBundleID</key>
|
|
199
|
+
<string></string>
|
|
200
|
+
<key>serviceApplicationPath</key>
|
|
201
|
+
<string></string>
|
|
202
|
+
<key>serviceInputTypeIdentifier</key>
|
|
203
|
+
<string>com.apple.Automator.nothing</string>
|
|
204
|
+
<key>serviceOutputTypeIdentifier</key>
|
|
205
|
+
<string>com.apple.Automator.nothing</string>
|
|
206
|
+
<key>serviceProcessesInput</key>
|
|
207
|
+
<integer>0</integer>
|
|
208
|
+
<key>workflowTypeIdentifier</key>
|
|
209
|
+
<string>com.apple.Automator.servicesMenu</string>
|
|
210
|
+
</dict>
|
|
211
|
+
</dict>
|
|
212
|
+
</plist>
|
|
@@ -59,15 +59,18 @@
|
|
|
59
59
|
<key>ActionParameters</key>
|
|
60
60
|
<dict>
|
|
61
61
|
<key>COMMAND_STRING</key>
|
|
62
|
-
<string># Selected text arrives on stdin.
|
|
63
|
-
#
|
|
62
|
+
<string># Selected text arrives on stdin. Interpolated values (vocalize/claude
|
|
63
|
+
# paths, helper location) are baked in by hooks/install_quick_action.py.
|
|
64
64
|
BIN="__VOCALIZE_BIN__"
|
|
65
65
|
if [[ ! -x "$BIN" ]]; then
|
|
66
66
|
/usr/bin/osascript -e 'display notification "vocalize not found — re-run hooks/install_quick_action.py" with title "Vocalize"'
|
|
67
67
|
exit 1
|
|
68
68
|
fi
|
|
69
69
|
"$BIN" stop >/dev/null 2>&1
|
|
70
|
-
|
|
70
|
+
export VOCALIZE_BIN="$BIN"
|
|
71
|
+
export CLAUDE_BIN="__CLAUDE_BIN__"
|
|
72
|
+
export CLAUDE_EXTRA_PATH="__CLAUDE_EXTRA_PATH__"
|
|
73
|
+
exec /usr/bin/python3 "__HELPER__"</string>
|
|
71
74
|
<key>CheckedForUserDefaultShell</key>
|
|
72
75
|
<false/>
|
|
73
76
|
<key>inputMethod</key>
|