vocalize-cli 0.6.0__tar.gz → 0.7.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.
Files changed (40) hide show
  1. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/CHANGELOG.md +18 -0
  2. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/PKG-INFO +41 -1
  3. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/README.md +40 -0
  4. vocalize_cli-0.7.0/hooks/install_quick_action.py +82 -0
  5. vocalize_cli-0.7.0/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Info.plist +30 -0
  6. vocalize_cli-0.7.0/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Resources/document.wflow +205 -0
  7. vocalize_cli-0.7.0/hooks/quick_actions/Stop Vocalize.workflow/Contents/Info.plist +26 -0
  8. vocalize_cli-0.7.0/hooks/quick_actions/Stop Vocalize.workflow/Contents/Resources/document.wflow +202 -0
  9. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_cli.py +239 -0
  10. vocalize_cli-0.7.0/tests/test_clipboard.py +38 -0
  11. vocalize_cli-0.7.0/tests/test_install_quick_action.py +158 -0
  12. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/__init__.py +1 -1
  13. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/cli.py +130 -5
  14. vocalize_cli-0.7.0/vocalize/clipboard.py +30 -0
  15. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/exceptions.py +4 -0
  16. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/.env.example +0 -0
  17. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/.github/workflows/ci.yml +0 -0
  18. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/.gitignore +0 -0
  19. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/LICENSE +0 -0
  20. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/hooks/claude_stop_hook.py +0 -0
  21. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/hooks/install_hook.py +0 -0
  22. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/hooks/speak_url_gate.py +0 -0
  23. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/pyproject.toml +0 -0
  24. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/conftest.py +0 -0
  25. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_audio.py +0 -0
  26. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_auth.py +0 -0
  27. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_claude_stop_hook.py +0 -0
  28. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_config.py +0 -0
  29. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_install_hook.py +0 -0
  30. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_preprocess.py +0 -0
  31. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_speak_url_gate.py +0 -0
  32. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_tts.py +0 -0
  33. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/tests/test_wizard.py +0 -0
  34. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/__main__.py +0 -0
  35. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/audio.py +0 -0
  36. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/auth.py +0 -0
  37. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/config.py +0 -0
  38. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/preprocess.py +0 -0
  39. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/tts.py +0 -0
  40. {vocalize_cli-0.6.0 → vocalize_cli-0.7.0}/vocalize/wizard.py +0 -0
@@ -3,6 +3,24 @@
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.7.0 - 2026-08-31
7
+
8
+ ### Added
9
+
10
+ - `vocalize clip` — speaks the macOS clipboard (pbpaste). Stops any
11
+ current playback first, refuses an empty clipboard, and refuses
12
+ credential-shaped content: a single high-entropy token, or one starting
13
+ with a known secret prefix (sk-, pypi-, ghp\_, op://, eyJ, …), is never
14
+ echoed or sent to ElevenLabs. `--allow-secret` bypasses the guard.
15
+ - `--ask-dialog` on speak/speak-file/clip — when overflow is `ask` and no
16
+ terminal is attached, ask via a native macOS dialog (Truncate / Speak
17
+ all / Cancel, 30 s timeout defaulting to Truncate) instead of silently
18
+ truncating. Off by default; the Claude Code Stop hook never uses it.
19
+ - macOS Quick Actions: "Speak with Vocalize" (highlight text in any app →
20
+ right-click → Services, or a keyboard shortcut) and "Stop Vocalize".
21
+ Install with `python3 hooks/install_quick_action.py`; the checked-in
22
+ bundles live in `hooks/quick_actions/`.
23
+
6
24
  ## 0.6.0 - 2026-08-31
7
25
 
8
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vocalize-cli
3
- Version: 0.6.0
3
+ Version: 0.7.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
@@ -136,6 +136,9 @@ vocalize speak-file report.md
136
136
  # Pipe anything in
137
137
  cat notes.md | vocalize speak-file -
138
138
 
139
+ # Speak whatever is on the macOS clipboard
140
+ vocalize clip
141
+
139
142
  # List available voices and grab an ID
140
143
  vocalize voices
141
144
 
@@ -171,6 +174,14 @@ possible, then sentences, then words — into requests no bigger than
171
174
  `--chunk-chars` (default 9500), so a long read no longer fails the API's
172
175
  own per-request cap.
173
176
 
177
+ `vocalize clip` stops any current playback first, then speaks the
178
+ clipboard. It refuses content shaped like a secret — a single
179
+ high-entropy token, or one starting with a known credential prefix
180
+ (`sk-`, `pypi-`, `ghp_`, `op://`, `eyJ`, …) — without echoing it anywhere;
181
+ `--allow-secret` bypasses the guard when you're sure. This is a
182
+ habit-breaking guard for text copied out of a password manager, not a
183
+ full secret scanner.
184
+
174
185
  ## Configuration
175
186
 
176
187
  Each setting is resolved on its own, taking the first source that supplies
@@ -241,6 +252,35 @@ in the current directory, then the OS keychain. `vocalize auth login` sets
241
252
  up the keychain entry; `vocalize auth status` shows which of those sources
242
253
  is currently supplying the key.
243
254
 
255
+ ## macOS Quick Actions (highlight → speak)
256
+
257
+ Two Services let you use vocalize from any app without a terminal:
258
+
259
+ - **Speak with Vocalize** — highlight text anywhere, right-click →
260
+ Services → Speak with Vocalize. It stops whatever was already playing
261
+ and reads the selection. If the selection is over your cap and your
262
+ overflow mode is `ask`, a native dialog asks Truncate / Speak all /
263
+ Cancel (that's the `--ask-dialog` flag at work).
264
+ - **Stop Vocalize** — appears in every app's application menu → Services;
265
+ silences playback from anywhere.
266
+
267
+ Install both:
268
+
269
+ ```bash
270
+ python3 hooks/install_quick_action.py
271
+ ```
272
+
273
+ This copies the bundles from `hooks/quick_actions/` into
274
+ `~/Library/Services/` with this machine's absolute `vocalize` path baked
275
+ in, then refreshes the Services registry. To trigger them from the
276
+ keyboard, assign shortcuts under System Settings → Keyboard → Keyboard
277
+ Shortcuts → Services → Text.
278
+
279
+ Some Electron apps (Claude Code desktop among them) don't expose the
280
+ Services menu for text selected in their own window. There, copy the
281
+ selection and use `/speak clip` (or `vocalize clip` in a terminal)
282
+ instead.
283
+
244
284
  ## Claude Code integration
245
285
 
246
286
  The hook scripts ship in the git repository, not the PyPI package — clone
@@ -102,6 +102,9 @@ vocalize speak-file report.md
102
102
  # Pipe anything in
103
103
  cat notes.md | vocalize speak-file -
104
104
 
105
+ # Speak whatever is on the macOS clipboard
106
+ vocalize clip
107
+
105
108
  # List available voices and grab an ID
106
109
  vocalize voices
107
110
 
@@ -137,6 +140,14 @@ possible, then sentences, then words — into requests no bigger than
137
140
  `--chunk-chars` (default 9500), so a long read no longer fails the API's
138
141
  own per-request cap.
139
142
 
143
+ `vocalize clip` stops any current playback first, then speaks the
144
+ clipboard. It refuses content shaped like a secret — a single
145
+ high-entropy token, or one starting with a known credential prefix
146
+ (`sk-`, `pypi-`, `ghp_`, `op://`, `eyJ`, …) — without echoing it anywhere;
147
+ `--allow-secret` bypasses the guard when you're sure. This is a
148
+ habit-breaking guard for text copied out of a password manager, not a
149
+ full secret scanner.
150
+
140
151
  ## Configuration
141
152
 
142
153
  Each setting is resolved on its own, taking the first source that supplies
@@ -207,6 +218,35 @@ in the current directory, then the OS keychain. `vocalize auth login` sets
207
218
  up the keychain entry; `vocalize auth status` shows which of those sources
208
219
  is currently supplying the key.
209
220
 
221
+ ## macOS Quick Actions (highlight → speak)
222
+
223
+ Two Services let you use vocalize from any app without a terminal:
224
+
225
+ - **Speak with Vocalize** — highlight text anywhere, right-click →
226
+ Services → Speak with Vocalize. It stops whatever was already playing
227
+ and reads the selection. If the selection is over your cap and your
228
+ overflow mode is `ask`, a native dialog asks Truncate / Speak all /
229
+ Cancel (that's the `--ask-dialog` flag at work).
230
+ - **Stop Vocalize** — appears in every app's application menu → Services;
231
+ silences playback from anywhere.
232
+
233
+ Install both:
234
+
235
+ ```bash
236
+ python3 hooks/install_quick_action.py
237
+ ```
238
+
239
+ This copies the bundles from `hooks/quick_actions/` into
240
+ `~/Library/Services/` with this machine's absolute `vocalize` path baked
241
+ in, then refreshes the Services registry. To trigger them from the
242
+ keyboard, assign shortcuts under System Settings → Keyboard → Keyboard
243
+ Shortcuts → Services → Text.
244
+
245
+ Some Electron apps (Claude Code desktop among them) don't expose the
246
+ Services menu for text selected in their own window. There, copy the
247
+ selection and use `/speak clip` (or `vocalize clip` in a terminal)
248
+ instead.
249
+
210
250
  ## Claude Code integration
211
251
 
212
252
  The hook scripts ship in the git repository, not the PyPI package — clone
@@ -0,0 +1,82 @@
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 shutil
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+ from xml.sax.saxutils import escape as xml_escape
17
+
18
+ TEMPLATES_DIR = Path(__file__).resolve().parent / "quick_actions"
19
+ SERVICES_DIR = Path.home() / "Library" / "Services"
20
+ BUNDLE_NAMES = ("Speak with Vocalize.workflow", "Stop Vocalize.workflow")
21
+ PLACEHOLDER = "__VOCALIZE_BIN__"
22
+ PBS = "/System/Library/CoreServices/pbs"
23
+
24
+ # The path lands inside BIN="..." in the Quick Action's zsh script. These
25
+ # characters could escape those quotes, so refuse rather than try to quote.
26
+ _UNSAFE_PATH_CHARS = set('"\\`$')
27
+
28
+
29
+ def _resolve_vocalize_bin() -> Path:
30
+ repo_venv = Path(__file__).resolve().parent.parent / ".venv" / "bin" / "vocalize"
31
+ if repo_venv.is_file():
32
+ return repo_venv
33
+ on_path = shutil.which("vocalize")
34
+ if on_path:
35
+ return Path(on_path).resolve()
36
+ print(
37
+ "Could not find a vocalize binary (checked .venv/bin/vocalize next to "
38
+ "this repo, then PATH). Install vocalize first.",
39
+ file=sys.stderr,
40
+ )
41
+ sys.exit(1)
42
+
43
+
44
+ def _install_one(name: str, bin_path: str) -> None:
45
+ src = TEMPLATES_DIR / name
46
+ dest = SERVICES_DIR / name
47
+ if dest.exists():
48
+ shutil.rmtree(dest)
49
+ shutil.copytree(src, dest)
50
+
51
+ wflow = dest / "Contents" / "Resources" / "document.wflow"
52
+ text = wflow.read_text(encoding="utf-8")
53
+ wflow.write_text(text.replace(PLACEHOLDER, xml_escape(bin_path)), encoding="utf-8")
54
+
55
+
56
+ def main() -> int:
57
+ bin_path = str(_resolve_vocalize_bin())
58
+ if _UNSAFE_PATH_CHARS & set(bin_path):
59
+ print(
60
+ f"Refusing to install: the resolved vocalize path contains characters "
61
+ f"that would break the Quick Action script: {bin_path!r}",
62
+ file=sys.stderr,
63
+ )
64
+ return 1
65
+
66
+ SERVICES_DIR.mkdir(parents=True, exist_ok=True)
67
+ for name in BUNDLE_NAMES:
68
+ _install_one(name, bin_path)
69
+ print(f"Installed {name} -> {SERVICES_DIR / name}")
70
+
71
+ # Nudge the Services registry so the new entries appear without a logout.
72
+ subprocess.run([PBS, "-update"], check=False)
73
+
74
+ print(f"\nUsing vocalize at: {bin_path}")
75
+ print('Highlight text in any app, then right-click -> Services -> "Speak with Vocalize".')
76
+ print("Keyboard shortcuts: System Settings -> Keyboard -> Keyboard Shortcuts -> Services.")
77
+ print("If the actions don't appear, re-open the Services submenu once, or log out and in.")
78
+ return 0
79
+
80
+
81
+ if __name__ == "__main__":
82
+ sys.exit(main())
@@ -0,0 +1,30 @@
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 with Vocalize</string>
12
+ </dict>
13
+ <key>NSMessage</key>
14
+ <string>runWorkflowAsService</string>
15
+ <key>NSSendTypes</key>
16
+ <array>
17
+ <string>public.utf8-plain-text</string>
18
+ </array>
19
+ </dict>
20
+ </array>
21
+ <key>CFBundleDevelopmentRegion</key>
22
+ <string>en_US</string>
23
+ <key>CFBundleIdentifier</key>
24
+ <string>cards.arda.vocalize.speak</string>
25
+ <key>CFBundleName</key>
26
+ <string>Speak with Vocalize</string>
27
+ <key>CFBundleShortVersionString</key>
28
+ <string>1.0</string>
29
+ </dict>
30
+ </plist>
@@ -0,0 +1,205 @@
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># Selected text arrives on stdin. The only interpolated value is the
63
+ # vocalize path, baked in by hooks/install_quick_action.py.
64
+ BIN="__VOCALIZE_BIN__"
65
+ if [[ ! -x "$BIN" ]]; then
66
+ /usr/bin/osascript -e 'display notification "vocalize not found — re-run hooks/install_quick_action.py" with title "Vocalize"'
67
+ exit 1
68
+ fi
69
+ "$BIN" stop &gt;/dev/null 2&gt;&amp;1
70
+ exec "$BIN" speak-file - --play --ask-dialog</string>
71
+ <key>CheckedForUserDefaultShell</key>
72
+ <false/>
73
+ <key>inputMethod</key>
74
+ <integer>0</integer>
75
+ <key>shell</key>
76
+ <string>/bin/zsh</string>
77
+ <key>source</key>
78
+ <string></string>
79
+ </dict>
80
+ <key>BundleIdentifier</key>
81
+ <string>com.apple.RunShellScript</string>
82
+ <key>CFBundleVersion</key>
83
+ <string>2.0.3</string>
84
+ <key>CanShowSelectedItemsWhenRun</key>
85
+ <false/>
86
+ <key>CanShowWhenRun</key>
87
+ <true/>
88
+ <key>Category</key>
89
+ <array>
90
+ <string>AMCategoryUtilities</string>
91
+ </array>
92
+ <key>Class Name</key>
93
+ <string>RunShellScriptAction</string>
94
+ <key>InputUUID</key>
95
+ <string>8A4A21F5-3C6E-4F58-9C1D-2B7E6A90D101</string>
96
+ <key>Keywords</key>
97
+ <array>
98
+ <string>Shell</string>
99
+ <string>Script</string>
100
+ <string>Command</string>
101
+ <string>Run</string>
102
+ <string>Unix</string>
103
+ </array>
104
+ <key>OutputUUID</key>
105
+ <string>8A4A21F5-3C6E-4F58-9C1D-2B7E6A90D102</string>
106
+ <key>UUID</key>
107
+ <string>8A4A21F5-3C6E-4F58-9C1D-2B7E6A90D103</string>
108
+ <key>UnlocalizedApplications</key>
109
+ <array>
110
+ <string>Automator</string>
111
+ </array>
112
+ <key>arguments</key>
113
+ <dict>
114
+ <key>0</key>
115
+ <dict>
116
+ <key>default value</key>
117
+ <integer>0</integer>
118
+ <key>name</key>
119
+ <string>inputMethod</string>
120
+ <key>required</key>
121
+ <string>0</string>
122
+ <key>type</key>
123
+ <string>0</string>
124
+ <key>uuid</key>
125
+ <string>0</string>
126
+ </dict>
127
+ <key>1</key>
128
+ <dict>
129
+ <key>default value</key>
130
+ <string></string>
131
+ <key>name</key>
132
+ <string>source</string>
133
+ <key>required</key>
134
+ <string>0</string>
135
+ <key>type</key>
136
+ <string>0</string>
137
+ <key>uuid</key>
138
+ <string>1</string>
139
+ </dict>
140
+ <key>2</key>
141
+ <dict>
142
+ <key>default value</key>
143
+ <false/>
144
+ <key>name</key>
145
+ <string>CheckedForUserDefaultShell</string>
146
+ <key>required</key>
147
+ <string>0</string>
148
+ <key>type</key>
149
+ <string>0</string>
150
+ <key>uuid</key>
151
+ <string>2</string>
152
+ </dict>
153
+ <key>3</key>
154
+ <dict>
155
+ <key>default value</key>
156
+ <string></string>
157
+ <key>name</key>
158
+ <string>COMMAND_STRING</string>
159
+ <key>required</key>
160
+ <string>0</string>
161
+ <key>type</key>
162
+ <string>0</string>
163
+ <key>uuid</key>
164
+ <string>3</string>
165
+ </dict>
166
+ <key>4</key>
167
+ <dict>
168
+ <key>default value</key>
169
+ <string>/bin/sh</string>
170
+ <key>name</key>
171
+ <string>shell</string>
172
+ <key>required</key>
173
+ <string>0</string>
174
+ <key>type</key>
175
+ <string>0</string>
176
+ <key>uuid</key>
177
+ <string>4</string>
178
+ </dict>
179
+ </dict>
180
+ <key>isViewVisible</key>
181
+ <true/>
182
+ </dict>
183
+ <key>isViewVisible</key>
184
+ <true/>
185
+ </dict>
186
+ </array>
187
+ <key>connectors</key>
188
+ <dict/>
189
+ <key>workflowMetaData</key>
190
+ <dict>
191
+ <key>serviceApplicationBundleID</key>
192
+ <string></string>
193
+ <key>serviceApplicationPath</key>
194
+ <string></string>
195
+ <key>serviceInputTypeIdentifier</key>
196
+ <string>com.apple.Automator.text</string>
197
+ <key>serviceOutputTypeIdentifier</key>
198
+ <string>com.apple.Automator.nothing</string>
199
+ <key>serviceProcessesInput</key>
200
+ <integer>1</integer>
201
+ <key>workflowTypeIdentifier</key>
202
+ <string>com.apple.Automator.servicesMenu</string>
203
+ </dict>
204
+ </dict>
205
+ </plist>
@@ -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>Stop Vocalize</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.stop</string>
21
+ <key>CFBundleName</key>
22
+ <string>Stop Vocalize</string>
23
+ <key>CFBundleShortVersionString</key>
24
+ <string>1.0</string>
25
+ </dict>
26
+ </plist>