dsh-speak 1.0.0 → 1.2.0
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 -21
- package/README.md +289 -186
- package/README.zh-CN.md +274 -177
- package/adapters/dsh/install.ps1 +81 -81
- package/adapters/dsh/speech-hook.js +142 -133
- package/docs/DESIGN.md +270 -245
- package/docs/DESIGN.zh-CN.md +253 -232
- package/engine/speak.ps1 +76 -76
- package/engine/speak.sh +89 -0
- package/engine/speech-prompt.ps1 +22 -22
- package/engine/speech-summary.ps1 +27 -27
- package/package.json +46 -43
package/docs/DESIGN.md
CHANGED
|
@@ -1,245 +1,270 @@
|
|
|
1
|
-
# DESIGN.md — dsh-speak: voice announcements for AI coding harnesses
|
|
2
|
-
|
|
3
|
-
English · [中文](DESIGN.zh-CN.md)
|
|
4
|
-
|
|
5
|
-
Status: **draft** — this document describes the current (proven) local implementation
|
|
6
|
-
and the target generic structure of this repository. It is the reference for the
|
|
7
|
-
README.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 1. Why
|
|
12
|
-
|
|
13
|
-
Agentic coding tools run long tasks (builds, tests, migrations, batch edits) while
|
|
14
|
-
you work on something else. When a reply finally lands you have to keep checking
|
|
15
|
-
the screen. **dsh-speak** reads the final reply aloud through
|
|
16
|
-
synthesis so you know *without looking* that a long
|
|
17
|
-
outcome was.
|
|
18
|
-
|
|
19
|
-
The original implementation was built and proven in a local DSH (DeepSeek Harness)
|
|
20
|
-
setup. This repository generalizes that working implementation into:
|
|
21
|
-
|
|
22
|
-
- a **harness-agnostic engine** (PowerShell + Windows SAPI5
|
|
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
|
-
1
|
|
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
|
-
file
|
|
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
|
-
by the
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
###
|
|
237
|
-
|
|
238
|
-
```powershell
|
|
239
|
-
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
1
|
+
# DESIGN.md — dsh-speak: voice announcements for AI coding harnesses
|
|
2
|
+
|
|
3
|
+
English · [中文](DESIGN.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Status: **draft** — this document describes the current (proven) local implementation
|
|
6
|
+
and the target generic structure of this repository. It is the reference for the
|
|
7
|
+
README.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Why
|
|
12
|
+
|
|
13
|
+
Agentic coding tools run long tasks (builds, tests, migrations, batch edits) while
|
|
14
|
+
you work on something else. When a reply finally lands you have to keep checking
|
|
15
|
+
the screen. **dsh-speak** reads the final reply aloud through system speech
|
|
16
|
+
synthesis (Windows SAPI5 / macOS `say`) so you know *without looking* that a long
|
|
17
|
+
task finished — and what its outcome was.
|
|
18
|
+
|
|
19
|
+
The original implementation was built and proven in a local DSH (DeepSeek Harness)
|
|
20
|
+
setup. This repository generalizes that working implementation into:
|
|
21
|
+
|
|
22
|
+
- a **harness-agnostic engine** (PowerShell + Windows SAPI5 / bash + macOS `say`)
|
|
23
|
+
that any process can call,
|
|
24
|
+
- **adapter layers** that turn harness-specific events into engine calls
|
|
25
|
+
(DSH session events, Claude Code Stop hooks, ...).
|
|
26
|
+
|
|
27
|
+
## 2. Goals / non-goals
|
|
28
|
+
|
|
29
|
+
Goals:
|
|
30
|
+
|
|
31
|
+
- One-command install for DSH users (engine + plugin + registration).
|
|
32
|
+
- Engine callable from any harness via a trivial command line.
|
|
33
|
+
- Best-effort speech: never throws, never blocks a harness, never breaks a session.
|
|
34
|
+
- Natural-sounding voices: Windows 11 built-in natural voice packs, or
|
|
35
|
+
NaturalVoiceSAPIAdapter on Windows 10; graceful fallback to stock voices.
|
|
36
|
+
|
|
37
|
+
Non-goals (for now):
|
|
38
|
+
|
|
39
|
+
- Both engines — Windows `speak.ps1` and macOS `speak.sh` (built-in `say`) — are
|
|
40
|
+
**officially supported** (macOS ships in the npm package since 1.2.0).
|
|
41
|
+
Linux/headless TTS is not supported.
|
|
42
|
+
- In-repo packaging of NaturalVoiceSAPIAdapter (Windows 10 only) or voice data —
|
|
43
|
+
they are prerequisites, not bundled.
|
|
44
|
+
- Streaming/queued playback, per-voice audio files, non-Chinese voice curation.
|
|
45
|
+
|
|
46
|
+
## 3. Architecture
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
+--------------------------------------------------------------+
|
|
50
|
+
| harness |
|
|
51
|
+
| (DSH web app | Claude Code | anything with a shell) |
|
|
52
|
+
+--------+-----------------------------+-----------------------+
|
|
53
|
+
| |
|
|
54
|
+
| session events | Stop hook JSON (stdin)
|
|
55
|
+
v v
|
|
56
|
+
+------------------+ +--------------------------+
|
|
57
|
+
| adapters/dsh/ | | adapters/claude-code/ |
|
|
58
|
+
| speech-hook.js | | stop-hook.ps1 |
|
|
59
|
+
| (event filter, | | (transcript extraction) |
|
|
60
|
+
| throttle, | +------------+-------------+
|
|
61
|
+
| cancel) | |
|
|
62
|
+
+--------+---------+ |
|
|
63
|
+
| text | text
|
|
64
|
+
v v
|
|
65
|
+
+---------------------------------------------------------------+
|
|
66
|
+
| engine/speak.ps1 / speak.sh (harness-agnostic) |
|
|
67
|
+
| text -> clean (markdown/emoji/length) -> system speech |
|
|
68
|
+
+--------------------+------------------------------------------+
|
|
69
|
+
| |
|
|
70
|
+
v v
|
|
71
|
+
Windows SAPI5 (System.Speech) — macOS say (system voice):
|
|
72
|
+
voices: * default follows the system
|
|
73
|
+
* preferred: a natural voice — voice (may be a Siri voice;
|
|
74
|
+
Windows 11 built-in pack, or not listed by `say -v '?'`,
|
|
75
|
+
one registered by not selectable by name)
|
|
76
|
+
NaturalVoiceSAPIAdapter on * or -v forces a classic voice
|
|
77
|
+
Windows 10 (e.g. "Microsoft (Eddy / Tingting / Flo ...)
|
|
78
|
+
Xiaoxiao") * no volume flag (follows
|
|
79
|
+
* fallback: any zh voice (e.g. the system output)
|
|
80
|
+
"Microsoft Huihui")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3.1 Engine — `engine/speak.ps1` (+ `engine/speak.sh` on macOS)
|
|
84
|
+
|
|
85
|
+
The only file a new adapter needs. Two input modes: `-Text "..."` inline, or
|
|
86
|
+
`-File C:\path\msg.txt` (UTF-8). Also `-Volume`, `-Rate`, `-MaxChars`,
|
|
87
|
+
`-LongTextMessage` (see §5). On macOS the plugin auto-picks `speak.sh` (the
|
|
88
|
+
`say` command; default voice follows the system — the Siri voices "声音 1-4"
|
|
89
|
+
are not exposed to `say`, use `-v` to force a name; no volume flag).
|
|
90
|
+
|
|
91
|
+
Processing pipeline (in order):
|
|
92
|
+
|
|
93
|
+
1. **Read** text (file read is always UTF-8).
|
|
94
|
+
2. **Strip markdown** — code blocks, inline code, links, bare URLs, emphasis chars.
|
|
95
|
+
3. **Strip emoji / non-printable** — keep CJK, CJK punctuation, full-width ranges,
|
|
96
|
+
ASCII printable (regex `[^一-龥 -〿- - -~]`).
|
|
97
|
+
4. **Collapse whitespace.**
|
|
98
|
+
5. **Length guard** — if cleaned text exceeds `MaxChars` (default 300), replace with
|
|
99
|
+
`LongTextMessage` (default: `本次播报内容较长,请自行阅读。`).
|
|
100
|
+
6. **Speak** — `System.Speech.Synthesis.SpeechSynthesizer`, volume/rate applied,
|
|
101
|
+
best zh natural voice selected, then `Speak()`.
|
|
102
|
+
|
|
103
|
+
Engine contract for adapters:
|
|
104
|
+
|
|
105
|
+
- exit 0 always; never writes to stdout/stderr on failure paths;
|
|
106
|
+
- synchronous (returns when the utterance finishes, or immediately on any failure);
|
|
107
|
+
- safe to call from a sandboxed process *provided* the caller does not need to nest
|
|
108
|
+
another `powershell.exe` inside a harness sandbox (see §6.3).
|
|
109
|
+
|
|
110
|
+
### 3.2 DSH adapter — `adapters/dsh/speech-hook.js`
|
|
111
|
+
|
|
112
|
+
A DSH web-profile plugin (Cordis plugin) registered via `cordis.patch.yml`. DSH has
|
|
113
|
+
no "reply finished" hook, so the plugin observes the session event stream:
|
|
114
|
+
|
|
115
|
+
- listens to `session/event`;
|
|
116
|
+
- filters `assistant/message` events with `surfaceOp == 'append'`;
|
|
117
|
+
- extracts only `text` content blocks (reasoning / tool_use blocks are skipped);
|
|
118
|
+
- buffers the text and starts a throttle timer (default 1500 ms) to merge
|
|
119
|
+
multi-step messages of one reply;
|
|
120
|
+
- a `tool/call` event **cancels** the pending announcement — that round's assistant
|
|
121
|
+
text is process narration, not the final reply;
|
|
122
|
+
- on fire: writes the text to a temp file and `spawn`s
|
|
123
|
+
`powershell.exe -File <engine> -File <tmp>` with `windowsHide` + `stdio: 'ignore'`
|
|
124
|
+
so the harness is never blocked; the temp file is deleted on exit.
|
|
125
|
+
|
|
126
|
+
Registration snippet (also automated by `install.ps1`):
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# ~/.dsh/profiles/web/cordis.patch.yml
|
|
130
|
+
- insert:
|
|
131
|
+
- id: speech-hook
|
|
132
|
+
# replace <your-username> with your Windows username
|
|
133
|
+
name: 'file:///C:/Users/<your-username>/.dsh/profiles/web/plugins/speech-hook.js'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
> Node's ESM loader does not accept Windows absolute paths as plugin names — the
|
|
137
|
+
> `file:///C:/...` URL form is required.
|
|
138
|
+
|
|
139
|
+
### 3.3 Claude Code adapter — `adapters/claude-code/stop-hook.ps1`
|
|
140
|
+
|
|
141
|
+
Claude Code *does* have a Stop hook. The hook JSON (with `transcript_path`) arrives
|
|
142
|
+
on stdin; the script scans the transcript backwards for the last assistant message
|
|
143
|
+
that contains text (the final entry is often a pure tool call), writes it to a temp
|
|
144
|
+
file and launches the engine in its own hidden powershell process, so the hook
|
|
145
|
+
returns immediately. (Async spawning is safe here — the nested-spawn restriction in
|
|
146
|
+
§6.3 is specific to DSH's sandbox.)
|
|
147
|
+
|
|
148
|
+
## 4. Event-flow truth table (DSH)
|
|
149
|
+
|
|
150
|
+
| assistant round contains | announced? |
|
|
151
|
+
| ------------------------------- | ----------- |
|
|
152
|
+
| final text reply, no tool call | ✅ after throttle |
|
|
153
|
+
| text + tool/call(s) | ❌ (cancelled — narration) |
|
|
154
|
+
| reasoning only, no text | ❌ (no text block) |
|
|
155
|
+
| streaming chunks | ❌ (filtered) |
|
|
156
|
+
|
|
157
|
+
## 5. Configuration reference
|
|
158
|
+
|
|
159
|
+
### Engine (`speak.ps1` parameters)
|
|
160
|
+
|
|
161
|
+
| param | default | meaning |
|
|
162
|
+
| ----------------- | --------------------------- | ---------------------------------------- |
|
|
163
|
+
| `-Text` | `''` | inline text (used when `-File` is empty) |
|
|
164
|
+
| `-File` | `''` | UTF-8 file to read |
|
|
165
|
+
| `-Volume` | `50` | 0–100 |
|
|
166
|
+
| `-Rate` | `1` | speech rate (SAPI scale) |
|
|
167
|
+
| `-MaxChars` | `300` | beyond this, replaced by `LongTextMessage` |
|
|
168
|
+
| `-LongTextMessage`| `本次播报内容较长,请自行阅读。` | spoken instead of over-long text |
|
|
169
|
+
|
|
170
|
+
### DSH plugin (environment variables)
|
|
171
|
+
|
|
172
|
+
| var | default | meaning |
|
|
173
|
+
| -------------------- | ---------------------------------------- | ------------------------------ |
|
|
174
|
+
| `DSH_SPEAK_ENGINE` | empty (auto-resolved) | engine path override; otherwise resolved as `<package>/engine/<platform script>` → `~/.dsh/hooks/<platform script>` (Windows: `speak.ps1`, macOS: `speak.sh`) |
|
|
175
|
+
| `DSH_SPEAK_THROTTLE_MS` | `1500` | merge delay before announcing |
|
|
176
|
+
|
|
177
|
+
## 6. Pitfalls (hard-won; do not "fix" casually)
|
|
178
|
+
|
|
179
|
+
| # | pitfall | symptom | fix / rule |
|
|
180
|
+
|---|---------|---------|------------|
|
|
181
|
+
| 6.1 | Emoji / surrogate pairs reach `Speak()` | **silent** — no audio, no error | strip non-CJK/ASCII before speaking (engine step 3) |
|
|
182
|
+
| 6.2 | Text longer than the adapter's per-`Speak` ceiling (~375–470 chars) | **silent** — the whole utterance is dropped, not truncated | length guard at 300 chars (engine step 5) |
|
|
183
|
+
| 6.3 | Nested `Start-Process powershell` inside a DSH-sandboxed process | silent failure, no exception | keep the DSH chain synchronous at the adapter boundary (spawn once from the plugin; `speech-summary.ps1` calls `speak.ps1` synchronously) |
|
|
184
|
+
| 6.4 | Plugin name with a raw Windows path in `cordis.patch.yml` | plugin fails to load | `file:///C:/...` URL form |
|
|
185
|
+
| 6.5 | Matching adapter voices by name only | falls back to robotic stock voice | match `Name + Description` against `Natural\|Online` |
|
|
186
|
+
| 6.6 | Reading/writing speech text as ANSI | mojibake or empty speech | always UTF-8 (`[System.IO.File]::ReadAllText(..., UTF8)`) |
|
|
187
|
+
| 6.7 | A repo `.sh` checked out as CRLF by `core.autocrlf=true`; `npm pack` bundles the **working-tree** file | the published `speak.sh` dies in bash on macOS (`command not found`, `syntax error near {`), silent failure | `.gitattributes` pins `*.sh text eol=lf` (check `file engine/speak.sh` for CRLF before publishing) |
|
|
188
|
+
| 6.8 | Log path hard-coded as `/tmp` | on macOS `os.tmpdir()` is `/var/folders/.../T`, the log is not at `/tmp` | look for the log at `os.tmpdir()` (= `$TMPDIR`) |
|
|
189
|
+
|
|
190
|
+
## 7. Extending
|
|
191
|
+
|
|
192
|
+
### New engine backend
|
|
193
|
+
The engine is the single seam for TTS backends. A future `speak-edge.ps1` could
|
|
194
|
+
wrap `edge-tts`, or a `speak-piper.ps1` a local offline model — same parameter
|
|
195
|
+
contract, same cleaning pipeline, swap the `Speak()` step. Adapters never change.
|
|
196
|
+
|
|
197
|
+
### New harness adapter
|
|
198
|
+
Implement: *capture the final reply text → call the engine*. DSH (event stream),
|
|
199
|
+
Claude Code (Stop hook), and any shell-based harness (`speech-summary.ps1` called
|
|
200
|
+
by the agent) are the three reference patterns.
|
|
201
|
+
|
|
202
|
+
## 8. Scope
|
|
203
|
+
|
|
204
|
+
This project is intentionally **not** a living product. It documents one proven way
|
|
205
|
+
to give a harness a voice: a small engine + the two adapter patterns (event-stream
|
|
206
|
+
and stop-hook) that worked. If you need more (voice management UI, more backends,
|
|
207
|
+
cross-platform), treat the engine as the seam and build on top — this repository
|
|
208
|
+
stays as a minimal, self-contained reference implementation.
|
|
209
|
+
|
|
210
|
+
## 9. Publishing as an npm plugin (appendix)
|
|
211
|
+
|
|
212
|
+
The DSH plugin mechanism is Cordis-based, and the official install path for
|
|
213
|
+
out-of-tree plugins is `dsh plugin --profile web add <package>` (pnpm-managed
|
|
214
|
+
dependencies in the profile). This repository is prepared for that path:
|
|
215
|
+
|
|
216
|
+
### Package layout
|
|
217
|
+
|
|
218
|
+
- `package.json` — `name: dsh-speak`, `main: adapters/dsh/speech-hook.js`,
|
|
219
|
+
`files` whitelists exactly what ships (plugin, `engine/*.ps1`, `install.ps1`,
|
|
220
|
+
docs, license). `prepublishOnly` runs `node --check` on the plugin.
|
|
221
|
+
- The plugin entry is the same CJS module (`module.exports = { apply(ctx) }`)
|
|
222
|
+
already used by the file install — no code change is needed to publish.
|
|
223
|
+
|
|
224
|
+
### Engine resolution (npm vs file install)
|
|
225
|
+
|
|
226
|
+
`speech-hook.js` locates `engine/speak.ps1` in this order:
|
|
227
|
+
|
|
228
|
+
1. `DSH_SPEAK_ENGINE` environment override;
|
|
229
|
+
2. `<package>/engine/speak.ps1` resolved relative to the plugin file — covers
|
|
230
|
+
both a repo checkout and `node_modules/dsh-speak/` after `npm install`;
|
|
231
|
+
3. legacy `%USERPROFILE%\.dsh\hooks\speak.ps1` (the file-install location).
|
|
232
|
+
|
|
233
|
+
Because the engine rides inside the npm package, `dsh plugin --profile web add
|
|
234
|
+
dsh-speak` alone is sufficient — no separate copying step.
|
|
235
|
+
|
|
236
|
+
### Publish steps (maintainer)
|
|
237
|
+
|
|
238
|
+
```powershell
|
|
239
|
+
npm login --registry=https://registry.npmjs.org # official registry, 2FA required
|
|
240
|
+
npm publish # publishConfig.registry pins the official registry
|
|
241
|
+
# bump "version" in package.json before every subsequent publish
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
> China note: if your global `.npmrc` points at a mirror (`registry.npmmirror.com`
|
|
245
|
+
> etc.), `npm login`/`npm publish` would target the mirror, which does **not**
|
|
246
|
+
> accept publishes. The package's `publishConfig.registry` pins publishing to the
|
|
247
|
+
> official registry; just make sure the login used the official registry too.
|
|
248
|
+
|
|
249
|
+
### Install steps (DSH user)
|
|
250
|
+
|
|
251
|
+
```powershell
|
|
252
|
+
dsh plugin --profile web add dsh-speak
|
|
253
|
+
# then register in ~/.dsh/profiles/web/cordis.patch.yml:
|
|
254
|
+
# - insert:
|
|
255
|
+
# - id: speech-hook
|
|
256
|
+
# name: 'dsh-speak'
|
|
257
|
+
# restart the DSH web app
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
> No pnpm installed? `dsh plugin` forwards to pnpm; the npm equivalent is
|
|
261
|
+
> (same result: package lands in the profile's `dependencies` + `node_modules`):
|
|
262
|
+
>
|
|
263
|
+
> - Windows (PowerShell):
|
|
264
|
+
> `npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak`
|
|
265
|
+
> - macOS (bash):
|
|
266
|
+
> `npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak`
|
|
267
|
+
>
|
|
268
|
+
> The patch watcher hot-reloads the plugin tree on `cordis.patch.yml` changes —
|
|
269
|
+
> verified: the plugin re-applies with the npm-bundled engine path, no restart
|
|
270
|
+
> needed for the registration switch itself (verified on macOS with 1.2.0).
|