@xdfnet/ispeak 1.6.7 → 1.6.9
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/README.md +5 -57
- package/configs/{com.iSpeak.plist → com.ispeak.plist} +1 -1
- package/configs/hook-speak.sh +45 -6
- package/{Docs/ARCHITECTURE.md → docs/architecture.md} +1 -1
- package/{Docs/HOOK_TEXT_EXTRACTION.md → docs/hook-text-extraction.md} +60 -27
- package/npm/postinstall.js +9 -4
- package/package.json +3 -5
- package/scripts/ispeak +5 -10
- /package/{Docs/HTTP Chunked:SSE/345/215/225/345/220/221/346/265/201/345/274/217-V3.md" → docs/http-chunked-sse-unidirectional-v3.md} +0 -0
- /package/{Docs//345/243/260/351/237/263/345/244/215/345/210/273API-V3.md" → docs/tts-voice-clone-api-v3.md} +0 -0
- /package/{Docs//351/237/263/350/211/262/345/210/227/350/241/250.md" → docs/voice-list.md} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# iSpeak
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://golang.org/dl/)
|
|
6
6
|

|
|
@@ -14,12 +14,6 @@ iSpeak 让 AI 编程助手开口说话。你写代码,它播结果——眼睛
|
|
|
14
14
|
```
|
|
15
15
|
# 默认音色:温柔女声
|
|
16
16
|
ispeak "Pull request 已合并,3 个测试通过"
|
|
17
|
-
|
|
18
|
-
# Claude 模式:专属音色
|
|
19
|
-
ispeak-claude "Code review 完成,发现 2 处可优化"
|
|
20
|
-
|
|
21
|
-
# Codex 模式:另一种音色
|
|
22
|
-
ispeak-codex "构建完成,耗时 12 秒"
|
|
23
17
|
```
|
|
24
18
|
|
|
25
19
|
## 为什么选 iSpeak
|
|
@@ -29,7 +23,7 @@ ispeak-codex "构建完成,耗时 12 秒"
|
|
|
29
23
|
| AI 生成多条回复,TTS 账单飞涨 | 新消息只保留最新待执行任务,避免无效合成 |
|
|
30
24
|
| 回复快慢不一,音频播报乱序 | 单 transaction worker,FIFO 顺序稳定 |
|
|
31
25
|
| 修改配置要重启服务 | 热更新:编辑 `config.json` 立即生效 |
|
|
32
|
-
| 默认音色太无聊 |
|
|
26
|
+
| 默认音色太无聊 | hook 按来源前缀选择音色 |
|
|
33
27
|
|
|
34
28
|
## 快速上手
|
|
35
29
|
|
|
@@ -113,12 +107,6 @@ ispeak restart # 重启服务
|
|
|
113
107
|
ispeak version # 版本
|
|
114
108
|
```
|
|
115
109
|
|
|
116
|
-
语音专属快捷命令(指向 ispeak 的软链接):
|
|
117
|
-
```bash
|
|
118
|
-
ispeak-claude "消息" # Claude 专属音色
|
|
119
|
-
ispeak-codex "消息" # Codex 专属音色
|
|
120
|
-
```
|
|
121
|
-
|
|
122
110
|
## 配置说明
|
|
123
111
|
|
|
124
112
|
`~/.config/iSpeak/config.json`:
|
|
@@ -148,49 +136,9 @@ ispeak-codex "消息" # Codex 专属音色
|
|
|
148
136
|
|
|
149
137
|
## 集成说明
|
|
150
138
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
在 `~/.claude/settings.json` 中添加 Stop Hook:
|
|
154
|
-
|
|
155
|
-
```json
|
|
156
|
-
{
|
|
157
|
-
"hooks": {
|
|
158
|
-
"Stop": [{
|
|
159
|
-
"hooks": [{
|
|
160
|
-
"type": "command",
|
|
161
|
-
"command": "bash $HOME/.config/iSpeak/hook-speak.sh claude",
|
|
162
|
-
"timeout": 30
|
|
163
|
-
}]
|
|
164
|
-
}]
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Codex
|
|
170
|
-
|
|
171
|
-
推荐在 `~/.codex/config.toml` 中添加回合结束通知:
|
|
172
|
-
|
|
173
|
-
```toml
|
|
174
|
-
notify = ["bash", "/Users/你的用户名/.config/iSpeak/hook-speak.sh", "codex"]
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
如果你启用了 Codex hooks,也可以在 `~/.codex/hooks.json` 中添加 Stop Hook:
|
|
178
|
-
|
|
179
|
-
```json
|
|
180
|
-
{
|
|
181
|
-
"hooks": {
|
|
182
|
-
"Stop": [{
|
|
183
|
-
"hooks": [{
|
|
184
|
-
"type": "command",
|
|
185
|
-
"command": "bash $HOME/.config/iSpeak/hook-speak.sh codex",
|
|
186
|
-
"timeout": 30
|
|
187
|
-
}]
|
|
188
|
-
}]
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
```
|
|
139
|
+
Claude Code 和 Codex 的详细 hook 配置见 [docs/hook-text-extraction.md](/Users/admin/iCode/iSpeak/docs/hook-text-extraction.md)。
|
|
192
140
|
|
|
193
|
-
`hook-speak.sh` 会按 `turn_id`
|
|
141
|
+
`hook-speak.sh` 会按 `turn_id` 做一次去重,所以同一回合不会播两次。
|
|
194
142
|
|
|
195
143
|
## 开发命令
|
|
196
144
|
|
|
@@ -207,7 +155,7 @@ make help # 显示帮助
|
|
|
207
155
|
|
|
208
156
|
| 文件 | 用途 |
|
|
209
157
|
|------|------|
|
|
210
|
-
| `~/Library/LaunchAgents/com.
|
|
158
|
+
| `~/Library/LaunchAgents/com.ispeak.plist` | macOS 自动启动服务 |
|
|
211
159
|
| `~/.config/iSpeak/ispeak.sock` | Unix Socket |
|
|
212
160
|
| `~/.config/iSpeak/ispeak.log` | 日志(轮转) |
|
|
213
161
|
| `~/.config/iSpeak/config.json` | 你的 API Key 和音色配置 |
|
package/configs/hook-speak.sh
CHANGED
|
@@ -52,7 +52,7 @@ function lastClaudeAssistant(payload) {
|
|
|
52
52
|
if (direct) return { text: direct, turnId: extractTurnId(payload) };
|
|
53
53
|
|
|
54
54
|
const transcript = firstString(payload.transcript_path, payload.transcriptPath);
|
|
55
|
-
return transcript ?
|
|
55
|
+
return transcript ? lastClaudeTranscript(transcript, payload) : { text: "", turnId: extractTurnId(payload) };
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function lastCodexAssistant(payload) {
|
|
@@ -98,11 +98,29 @@ function firstString(...values) {
|
|
|
98
98
|
|
|
99
99
|
function collectText(content) {
|
|
100
100
|
if (typeof content === "string") return content;
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
if (Array.isArray(content)) {
|
|
102
|
+
return content
|
|
103
|
+
.map(item => collectText(item))
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.join(" ");
|
|
106
|
+
}
|
|
107
|
+
if (!content || typeof content !== "object") return "";
|
|
108
|
+
if (typeof content.text === "string") return content.text;
|
|
109
|
+
if (content.content) return collectText(content.content);
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function lastClaudeTranscript(file, payload) {
|
|
114
|
+
const deadline = Date.now() + 1200;
|
|
115
|
+
let result = { text: "", turnId: extractTurnId(payload) };
|
|
116
|
+
|
|
117
|
+
while (Date.now() <= deadline) {
|
|
118
|
+
result = lastAssistantFromTranscript(file, "claude");
|
|
119
|
+
if (result.text) return result;
|
|
120
|
+
sleepMs(120);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
function lastAssistantFromTranscript(file, source) {
|
|
@@ -179,6 +197,10 @@ function textHash(text) {
|
|
|
179
197
|
return crypto.createHash("sha1").update(text, "utf8").digest("hex");
|
|
180
198
|
}
|
|
181
199
|
|
|
200
|
+
function sleepMs(ms) {
|
|
201
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
202
|
+
}
|
|
203
|
+
|
|
182
204
|
NODE
|
|
183
205
|
)
|
|
184
206
|
|
|
@@ -192,6 +214,23 @@ echo "SOURCE: $SOURCE" >> "$LOG"
|
|
|
192
214
|
echo "TEXT_LEN: ${#result}" >> "$LOG"
|
|
193
215
|
echo "PREVIEW: ${result:0:150}" >> "$LOG"
|
|
194
216
|
|
|
217
|
+
# Claude Code Stop Hook 调试
|
|
218
|
+
if [[ "$SOURCE" == "claude" && -n "$input" ]]; then
|
|
219
|
+
# 用 grep 提取 transcript_path
|
|
220
|
+
tp=$(echo "$input" | grep -o '"transcript_path":"[^"]*"' | head -1 | sed 's/"transcript_path":"//;s/"$//')
|
|
221
|
+
if [[ -n "$tp" ]]; then
|
|
222
|
+
echo "CLAUDE_TRANSCRIPT_PATH: $tp" >> "$LOG"
|
|
223
|
+
if [[ -f "$tp" ]]; then
|
|
224
|
+
echo "CLAUDE_TRANSCRIPT_EXISTS: yes" >> "$LOG"
|
|
225
|
+
else
|
|
226
|
+
echo "CLAUDE_TRANSCRIPT_EXISTS: no" >> "$LOG"
|
|
227
|
+
fi
|
|
228
|
+
else
|
|
229
|
+
echo "CLAUDE_TRANSCRIPT_PATH: none" >> "$LOG"
|
|
230
|
+
echo "CLAUDE_RAW: ${input:0:300}" >> "$LOG"
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
|
|
195
234
|
if [[ -n "$result" && -S "$SOCK" ]]; then
|
|
196
235
|
printf "{source:%s}%s" "$SOURCE" "$result" | nc -U -w5 "$SOCK" 2>> "$LOG"
|
|
197
236
|
echo "SPOKE: OK" >> "$LOG"
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
推荐优先级:
|
|
8
8
|
|
|
9
|
-
1. **Codex `notify`**:从脚本第二个参数 `$2` 读取 JSON,取 `last-assistant-message
|
|
10
|
-
2. **
|
|
11
|
-
3.
|
|
9
|
+
1. **Codex `notify`**:从脚本第二个参数 `$2` 读取 JSON,取 `last-assistant-message`(kebab-case)。
|
|
10
|
+
2. **Codex Stop Hook**:从 stdin 读取 JSON,取 `last_assistant_message`(snake_case)。
|
|
11
|
+
3. **Claude Code Stop Hook**:从 stdin 读取 JSON,只读 `transcript_path`(官方无 direct 字段)。
|
|
12
12
|
|
|
13
13
|
不扫描 `~/.codex/sessions`。没有 direct 字段也没有 `transcript_path` 时,本次不播报。
|
|
14
14
|
|
|
@@ -61,7 +61,7 @@ input="${2:-}"
|
|
|
61
61
|
payload["last-assistant-message"]
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
源码依据:`codex-rs/hooks/src/legacy_notify.rs
|
|
64
|
+
源码依据:`codex-rs/hooks/src/legacy_notify.rs`(https://github.com/openai/codex,2026-05-11)。该文件把 `last_assistant_message` 序列化为 kebab-case 的 `last-assistant-message`,并在执行命令前 `command.arg(notify_payload)`。
|
|
65
65
|
|
|
66
66
|
## Codex CLI:Stop Hook
|
|
67
67
|
|
|
@@ -95,7 +95,23 @@ $2 = empty
|
|
|
95
95
|
stdin = '{"hook_event_name":"Stop",...,"last_assistant_message":"..."}'
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
核心字段(源码 `StopCommandInput` struct):
|
|
99
|
+
|
|
100
|
+
```rust
|
|
101
|
+
struct StopCommandInput {
|
|
102
|
+
session_id: String,
|
|
103
|
+
turn_id: String,
|
|
104
|
+
transcript_path: NullableString,
|
|
105
|
+
cwd: String,
|
|
106
|
+
hook_event_name: String,
|
|
107
|
+
model: String,
|
|
108
|
+
permission_mode: String,
|
|
109
|
+
stop_hook_active: bool,
|
|
110
|
+
last_assistant_message: NullableString, // ← Codex 有此字段
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
对应 JSON:
|
|
99
115
|
|
|
100
116
|
```json
|
|
101
117
|
{
|
|
@@ -113,9 +129,8 @@ stdin = '{"hook_event_name":"Stop",...,"last_assistant_message":"..."}'
|
|
|
113
129
|
|
|
114
130
|
源码依据:
|
|
115
131
|
|
|
116
|
-
- `codex-rs/hooks/src/events/stop.rs
|
|
117
|
-
- `codex-rs/hooks/
|
|
118
|
-
- `codex-rs/hooks/src/engine/command_runner.rs`:Hook 命令通过 stdin 接收 `input_json`。
|
|
132
|
+
- `codex-rs/hooks/src/events/stop.rs`(https://github.com/openai/codex,2026-05-11):构造 `StopCommandInput`,包含 `last_assistant_message` 和 `transcript_path`。
|
|
133
|
+
- `codex-rs/hooks/src/engine/command_runner.rs`(同上):Hook 命令通过 stdin 接收 `input_json`。
|
|
119
134
|
|
|
120
135
|
## Codex Transcript
|
|
121
136
|
|
|
@@ -153,30 +168,35 @@ event.payload.content[].text
|
|
|
153
168
|
|
|
154
169
|
## Claude Code:Stop Hook
|
|
155
170
|
|
|
156
|
-
Claude Code
|
|
171
|
+
> **来源**:[Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks.md),更新时间:2026-05-11
|
|
172
|
+
|
|
173
|
+
Claude Code 官方 Stop Hook **没有 `last_assistant_message` 字段**。
|
|
174
|
+
|
|
175
|
+
根据官方文档,Stop Hook 的 Common Input Fields 为:
|
|
157
176
|
|
|
158
177
|
```json
|
|
159
178
|
{
|
|
160
|
-
"session_id": "
|
|
161
|
-
"transcript_path": "
|
|
179
|
+
"session_id": "abc123",
|
|
180
|
+
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
|
|
181
|
+
"cwd": "/home/user/my-project",
|
|
182
|
+
"permission_mode": "default",
|
|
162
183
|
"hook_event_name": "Stop",
|
|
163
|
-
"
|
|
184
|
+
"effort": {
|
|
185
|
+
"level": "medium"
|
|
186
|
+
}
|
|
164
187
|
}
|
|
165
188
|
```
|
|
166
189
|
|
|
167
|
-
|
|
190
|
+
子 agent 上下文中额外字段:
|
|
168
191
|
|
|
169
192
|
```json
|
|
170
193
|
{
|
|
171
|
-
"
|
|
194
|
+
"agent_id": "subagent_xyz",
|
|
195
|
+
"agent_type": "Explore"
|
|
172
196
|
}
|
|
173
197
|
```
|
|
174
198
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
1. `last_assistant_message`
|
|
178
|
-
2. `message`
|
|
179
|
-
3. `transcript_path`
|
|
199
|
+
**结论**:Claude Code Stop Hook 官方设计只提供 `transcript_path`,没有直接内嵌 `last_assistant_message`。旧版本脚本的 `last_assistant_message` / `message` fallback 实际上**从未被官方文档支持**。
|
|
180
200
|
|
|
181
201
|
Claude transcript 常见 assistant 形态:
|
|
182
202
|
|
|
@@ -207,11 +227,11 @@ fi
|
|
|
207
227
|
- Claude / Codex Stop Hook:读 stdin
|
|
208
228
|
- 如果 Codex 的 `notify` 和 `Stop` 同时启用,脚本会按 `turn_id` 去重,避免同一回合播两次
|
|
209
229
|
|
|
210
|
-
Codex
|
|
230
|
+
Codex 文本字段优先级(源码确认):
|
|
211
231
|
|
|
212
232
|
```js
|
|
213
|
-
payload["last-assistant-message"]
|
|
214
|
-
payload.last_assistant_message
|
|
233
|
+
payload["last-assistant-message"] // notify: kebab-case
|
|
234
|
+
payload.last_assistant_message // Stop Hook: snake_case
|
|
215
235
|
payload.lastAssistantMessage
|
|
216
236
|
payload.message
|
|
217
237
|
payload.lastMessage
|
|
@@ -220,15 +240,14 @@ payload.transcriptPath
|
|
|
220
240
|
payload["transcript-path"]
|
|
221
241
|
```
|
|
222
242
|
|
|
223
|
-
Claude
|
|
243
|
+
Claude Code 文本字段优先级(官方文档):
|
|
224
244
|
|
|
225
245
|
```js
|
|
226
|
-
payload.
|
|
227
|
-
payload.message
|
|
228
|
-
payload.transcript_path
|
|
229
|
-
payload.transcriptPath
|
|
246
|
+
payload.transcript_path // 官方支持的唯一方式
|
|
230
247
|
```
|
|
231
248
|
|
|
249
|
+
> **注**:Claude Code Stop Hook 官方 payload 中**没有 `last_assistant_message` 字段**,这是与 Codex 的本质区别。
|
|
250
|
+
|
|
232
251
|
## 为什么不能只读 stdin
|
|
233
252
|
|
|
234
253
|
因为 Codex `notify` 不走 stdin。只读 stdin 会导致:
|
|
@@ -239,3 +258,17 @@ SPOKE: SKIP
|
|
|
239
258
|
```
|
|
240
259
|
|
|
241
260
|
正确做法是先读 `$2`,再读 stdin;不扫历史 session。
|
|
261
|
+
|
|
262
|
+
## Claude Code TEXT_LEN: 0 的根因
|
|
263
|
+
|
|
264
|
+
当 Claude Code Stop Hook 触发但 `TEXT_LEN: 0` 时:
|
|
265
|
+
|
|
266
|
+
1. **官方字段不存在**:Claude Code Stop Hook 官方 payload 中**没有 `last_assistant_message` 字段**,只有 `transcript_path`
|
|
267
|
+
2. **transcript 文件可能晚一点才写完**:Hook 触发时文件虽已存在,但最后一条 assistant 文本还没落盘
|
|
268
|
+
3. **结果**:如果只读一次,`hook-speak.sh` 可能拿到空串,本次不播报
|
|
269
|
+
|
|
270
|
+
当前脚本对 Claude transcript 做了很短的轮询,等最后一条 assistant 文本真正出现再播,避免这个时序窗。
|
|
271
|
+
|
|
272
|
+
这是 **Claude Code 与 Codex 的设计差异**,非 bug。Codex CLI(无论 notify 还是 Stop Hook)都提供 `last_assistant_message`,而 Claude Code 官方只提供 `transcript_path`。
|
|
273
|
+
|
|
274
|
+
解决方案:从 `transcript_path` 读取并解析为最终一条 assistant 回复,并在 Claude 路径上补一个短轮询。
|
package/npm/postinstall.js
CHANGED
|
@@ -10,7 +10,8 @@ const root = path.resolve(__dirname, "..");
|
|
|
10
10
|
const home = os.homedir();
|
|
11
11
|
const binDir = path.join(home, ".local", "bin");
|
|
12
12
|
const configDir = path.join(home, ".config", "iSpeak");
|
|
13
|
-
const plistPath = path.join(home, "Library", "LaunchAgents", "com.
|
|
13
|
+
const plistPath = path.join(home, "Library", "LaunchAgents", "com.ispeak.plist");
|
|
14
|
+
const legacyPlistPath = path.join(home, "Library", "LaunchAgents", "com.iSpeak.plist");
|
|
14
15
|
const socketPath = path.join(configDir, "ispeak.sock");
|
|
15
16
|
const binaryPath = path.join(binDir, "ispeakd");
|
|
16
17
|
const cliPath = path.join(binDir, "ispeak");
|
|
@@ -136,12 +137,16 @@ function main() {
|
|
|
136
137
|
run("go", ["build", "-ldflags=-s -w", "-o", buildPath, "."]);
|
|
137
138
|
|
|
138
139
|
console.log("停止旧服务...");
|
|
140
|
+
run("launchctl", ["unload", legacyPlistPath], { allowFailure: true, stdio: "ignore" });
|
|
139
141
|
run("launchctl", ["unload", plistPath], { allowFailure: true, stdio: "ignore" });
|
|
142
|
+
try {
|
|
143
|
+
fs.rmSync(legacyPlistPath, { force: true });
|
|
144
|
+
} catch (_) {
|
|
145
|
+
// Ignore migration cleanup failures.
|
|
146
|
+
}
|
|
140
147
|
|
|
141
148
|
copyExecutable(buildPath, binaryPath);
|
|
142
149
|
copyExecutable(path.join(root, "scripts", "ispeak"), cliPath);
|
|
143
|
-
symlinkForce(cliPath, path.join(binDir, "ispeak-claude"));
|
|
144
|
-
symlinkForce(cliPath, path.join(binDir, "ispeak-codex"));
|
|
145
150
|
|
|
146
151
|
const configPath = path.join(configDir, "config.json");
|
|
147
152
|
copyIfMissing(path.join(root, "configs", "config.example.json"), configPath);
|
|
@@ -149,7 +154,7 @@ function main() {
|
|
|
149
154
|
installHook(path.join(root, "configs", "hook-speak.sh"), path.join(configDir, "hook-speak.sh"));
|
|
150
155
|
|
|
151
156
|
const plist = fs
|
|
152
|
-
.readFileSync(path.join(root, "configs", "com.
|
|
157
|
+
.readFileSync(path.join(root, "configs", "com.ispeak.plist"), "utf8")
|
|
153
158
|
.replaceAll("BINARY_PATH_PLACEHOLDER", binaryPath);
|
|
154
159
|
fs.writeFileSync(plistPath, plist);
|
|
155
160
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdfnet/ispeak",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.9",
|
|
4
4
|
"description": "Local macOS TTS daemon for AI coding assistants, powered by Volcengine streaming TTS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/xdfnet/iSpeak#readme",
|
|
@@ -19,9 +19,7 @@
|
|
|
19
19
|
"x64"
|
|
20
20
|
],
|
|
21
21
|
"bin": {
|
|
22
|
-
"ispeak": "scripts/ispeak"
|
|
23
|
-
"ispeak-claude": "scripts/ispeak",
|
|
24
|
-
"ispeak-codex": "scripts/ispeak"
|
|
22
|
+
"ispeak": "scripts/ispeak"
|
|
25
23
|
},
|
|
26
24
|
"scripts": {
|
|
27
25
|
"build": "go build -ldflags=\"-s -w\" -o build/ispeakd .",
|
|
@@ -38,7 +36,7 @@
|
|
|
38
36
|
"scripts/ispeak",
|
|
39
37
|
"configs/",
|
|
40
38
|
"npm/",
|
|
41
|
-
"
|
|
39
|
+
"docs/",
|
|
42
40
|
"README.md",
|
|
43
41
|
"LICENSE"
|
|
44
42
|
],
|
package/scripts/ispeak
CHANGED
|
@@ -2,16 +2,10 @@
|
|
|
2
2
|
# ispeak — iSpeak 控制命令
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
-
VERSION="1.6.
|
|
5
|
+
VERSION="1.6.9"
|
|
6
6
|
SOCK="$HOME/.config/iSpeak/ispeak.sock"
|
|
7
|
-
PLIST="$HOME/Library/LaunchAgents/com.
|
|
8
|
-
|
|
9
|
-
SOURCE_PREFIX=""
|
|
10
|
-
|
|
11
|
-
case "$CMD_NAME" in
|
|
12
|
-
ispeak-claude) SOURCE_PREFIX="{source:claude}" ;;
|
|
13
|
-
ispeak-codex) SOURCE_PREFIX="{source:codex}" ;;
|
|
14
|
-
esac
|
|
7
|
+
PLIST="$HOME/Library/LaunchAgents/com.ispeak.plist"
|
|
8
|
+
LEGACY_PLIST="$HOME/Library/LaunchAgents/com.iSpeak.plist"
|
|
15
9
|
|
|
16
10
|
cmd_status() {
|
|
17
11
|
echo "== iSpeak =="
|
|
@@ -28,6 +22,7 @@ cmd_status() {
|
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
cmd_restart() {
|
|
25
|
+
launchctl unload "$LEGACY_PLIST" 2>/dev/null || true
|
|
31
26
|
launchctl unload "$PLIST" 2>/dev/null || true
|
|
32
27
|
launchctl load "$PLIST"
|
|
33
28
|
sleep 0.5
|
|
@@ -53,6 +48,6 @@ case "${1:-}" in
|
|
|
53
48
|
echo " ispeak version 版本"
|
|
54
49
|
;;
|
|
55
50
|
*)
|
|
56
|
-
printf "%s
|
|
51
|
+
printf "%s" "$*" | nc -U -w5 "$SOCK" 2>/dev/null || echo "ispeak: socket 不可用" >&2
|
|
57
52
|
;;
|
|
58
53
|
esac
|
|
File without changes
|
|
File without changes
|
|
File without changes
|