@xdfnet/ispeak 1.6.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xdfnet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # iSpeak
2
+
3
+ ![Version](https://img.shields.io/badge/version-1.6.1-blue)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Go Version](https://img.shields.io/badge/Go-1.26-blue)](https://golang.org/dl/)
6
+ ![Platform](https://img.shields.io/badge/platform-macOS-green)
7
+
8
+ iSpeak 让 AI 编程助手开口说话。你写代码,它播结果——眼睛休息,耳朵来听。
9
+
10
+ 适合 Claude Code 或 Codex 常驻后台的开发者。AI 完成任务后自动播报;你发新消息时,旧播报立即中断,不花冤枉钱。
11
+
12
+ ## 效果示例
13
+
14
+ ```
15
+ # 默认音色:温柔女声
16
+ ispeak "Pull request 已合并,3 个测试通过"
17
+
18
+ # Claude 模式:专属音色
19
+ ispeak-claude "Code review 完成,发现 2 处可优化"
20
+
21
+ # Codex 模式:另一种音色
22
+ ispeak-codex "构建完成,耗时 12 秒"
23
+ ```
24
+
25
+ ## 为什么选 iSpeak
26
+
27
+ | 问题 | 方案 |
28
+ |------|------|
29
+ | AI 生成多条回复,TTS 账单飞涨 | 新消息只保留最新待合成任务,避免无效合成 |
30
+ | 回复快慢不一,音频播报乱序 | 单 speak worker,FIFO 顺序稳定 |
31
+ | 修改配置要重启服务 | 热更新:编辑 `config.json` 立即生效 |
32
+ | 默认音色太无聊 | 来源专属音色,Claude 和 Codex 声音不同 |
33
+
34
+ ## 快速上手
35
+
36
+ **npm 安装:**
37
+
38
+ ```bash
39
+ npm i -g @xdfnet/ispeak
40
+ ```
41
+
42
+ 当前 npm 安装会在本机编译 `ispeakd`,需要已安装 Go。没有 `ffplay` 时会自动回退 `afplay`;推荐安装 `ffmpeg` 获得流式播放:
43
+
44
+ ```bash
45
+ brew install ffmpeg
46
+ ```
47
+
48
+ **源码安装:**
49
+
50
+ ```bash
51
+ git clone https://github.com/xdfnet/iSpeak.git && cd iSpeak && make install
52
+ ```
53
+
54
+ 安装时手动输入 API Key,然后验证:
55
+
56
+ ```bash
57
+ ispeak status
58
+ ispeak test "iSpeak 准备好了"
59
+ ```
60
+
61
+ ## 工作原理
62
+
63
+ ```
64
+ 你:"重构 auth 模块"
65
+
66
+
67
+ ┌─────────────────────────────────────────────────────┐
68
+ │ ispeakd — Mac 上常驻的守护进程 │
69
+ │ │
70
+ │ 通过 Unix Socket 接收文本 │
71
+ │ │ │
72
+ │ ▼ │
73
+ │ 任务引擎 │
74
+ │ (pending_synth → speaking → delete) │
75
+ │ │ │
76
+ │ ▼ │
77
+ │ 单 Worker 流式链路 │
78
+ │ (SSE audio chunk → 播放器 stdin) │
79
+ │ │ │
80
+ │ ▼ │
81
+ │ 流式播放器 │
82
+ │ (优先 ffplay stdin,无 ffplay 回退 afplay) │
83
+ └─────────────────────────────────────────────────────┘
84
+ ```
85
+
86
+ **任务状态流转:**
87
+ ```
88
+ pending_synth → speaking → delete
89
+ ```
90
+
91
+ ## 全部命令
92
+
93
+ ```bash
94
+ ispeak "消息" # 播报
95
+ ispeak status # 服务状态
96
+ ispeak restart # 重启服务
97
+ ispeak version # 版本
98
+ ```
99
+
100
+ 语音专属快捷命令(指向 ispeak 的软链接):
101
+ ```bash
102
+ ispeak-claude "消息" # Claude 专属音色
103
+ ispeak-codex "消息" # Codex 专属音色
104
+ ```
105
+
106
+ ## 配置说明
107
+
108
+ `~/.config/iSpeak/config.json`:
109
+
110
+ ```json
111
+ {
112
+ "apiKey": "你的火山引擎 API Key",
113
+ "endpoint": "https://openspeech.bytedance.com/api/v3/tts/unidirectional",
114
+ "defaultVoice": {
115
+ "voice_type": "zh_female_mizai_uranus_bigtts",
116
+ "resourceId": "seed-tts-2.0"
117
+ },
118
+ "sourceVoices": {
119
+ "claude": {
120
+ "voice_type": "zh_female_tianmeitaozi_uranus_bigtts",
121
+ "resourceId": "seed-tts-2.0"
122
+ },
123
+ "codex": {
124
+ "voice_type": "zh_male_shaonianzixin_uranus_bigtts",
125
+ "resourceId": "seed-tts-2.0"
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ 音色库参考 [火山引擎 TTS 控制台](https://console.volcengine.com/tts),填写对应的 `voice_type` 和 `resourceId` 即可。
132
+
133
+ ## 集成说明
134
+
135
+ ### Claude Code
136
+
137
+ 在 `~/.claude/settings.json` 中添加 Stop Hook:
138
+
139
+ ```json
140
+ {
141
+ "hooks": {
142
+ "Stop": [{
143
+ "hooks": [{
144
+ "type": "command",
145
+ "command": "bash $HOME/.config/iSpeak/hook-speak.sh claude",
146
+ "timeout": 30
147
+ }]
148
+ }]
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### Codex
154
+
155
+ 在 `~/.codex/hooks.json` 中添加 Stop Hook:
156
+
157
+ ```json
158
+ {
159
+ "hooks": {
160
+ "Stop": [{
161
+ "hooks": [{
162
+ "type": "command",
163
+ "command": "bash $HOME/.config/iSpeak/hook-speak.sh codex",
164
+ "timeout": 30
165
+ }]
166
+ }]
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## 开发命令
172
+
173
+ ```bash
174
+ make build # 编译 ispeakd
175
+ make install # 安装并启动服务(自动自检)
176
+ make deploy # 安装 + 部署配置文件(不覆盖已有配置)
177
+ make uninstall # 卸载(停止服务 + 删除文件)
178
+ make clean # 清理编译产物
179
+ make help # 显示帮助
180
+ ```
181
+
182
+ ## 文件路径
183
+
184
+ | 文件 | 用途 |
185
+ |------|------|
186
+ | `~/Library/LaunchAgents/com.iSpeak.plist` | macOS 自动启动服务 |
187
+ | `~/.config/iSpeak/ispeak.sock` | Unix Socket |
188
+ | `~/.config/iSpeak/ispeak.log` | 日志(轮转) |
189
+ | `~/.config/iSpeak/config.json` | 你的 API Key 和音色配置 |
190
+ | `~/.config/iSpeak/hook-speak.sh` | Claude/Codex Hook 脚本 |
191
+
192
+ ## License
193
+
194
+ MIT — 随便用,随便改。
@@ -0,0 +1,20 @@
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>Label</key>
6
+ <string>com.iSpeak</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>BINARY_PATH_PLACEHOLDER</string>
10
+ </array>
11
+ <key>RunAtLoad</key>
12
+ <true/>
13
+ <key>KeepAlive</key>
14
+ <true/>
15
+ <key>StandardOutPath</key>
16
+ <string>/tmp/iSpeak.log</string>
17
+ <key>StandardErrorPath</key>
18
+ <string>/tmp/iSpeak.log</string>
19
+ </dict>
20
+ </plist>
@@ -0,0 +1,18 @@
1
+ {
2
+ "apiKey": "your-api-key",
3
+ "endpoint": "https://openspeech.bytedance.com/api/v3/tts/unidirectional",
4
+ "defaultVoice": {
5
+ "voice_type": "zh_female_mizai_uranus_bigtts",
6
+ "resourceId": "seed-tts-2.0"
7
+ },
8
+ "sourceVoices": {
9
+ "claude": {
10
+ "voice_type": "zh_female_tianmeitaozi_uranus_bigtts",
11
+ "resourceId": "seed-tts-2.0"
12
+ },
13
+ "codex": {
14
+ "voice_type": "zh_male_shaonianzixin_uranus_bigtts",
15
+ "resourceId": "seed-tts-2.0"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # Stop Hook: 从 transcript 文件中提取本次会话所有 Claude 回复文本
3
+ # iAgent 调用 Claude 时设 ISPEAK_SKIP=1,此时跳过(iAgent 自己播)
4
+ [[ "$ISPEAK_SKIP" == "1" ]] && exit 0
5
+
6
+ # 来源参数: claude 或 codex
7
+ SOURCE="${1:-claude}"
8
+
9
+ SOCK="$HOME/.config/iSpeak/ispeak.sock"
10
+ LOG="$HOME/.config/iSpeak/hook.log"
11
+
12
+ input=$(cat)
13
+
14
+ # 从 stdin JSON 提取 transcript 路径和最后一条消息
15
+ # 简单 JSON 解析(不依赖 python3)
16
+ transcript=$(echo "$input" | sed -n 's/.*"transcript_path"\s*:\s*"\([^"]*\)".*/\1/p')
17
+ last_msg=$(echo "$input" | sed -n 's/.*"last_assistant_message"\s*:\s*"\([^"]*\)".*/\1/p')
18
+
19
+ all_text="$last_msg"
20
+
21
+ # 如果有 transcript 文件,提取最近 30 秒内的所有 assistant 消息
22
+ if [[ -n "$transcript" && -f "$transcript" ]]; then
23
+ # 计算 30 秒前的时间戳(毫秒)
24
+ cutoff=$(($(date +%s) * 1000 - 30000))
25
+
26
+ # 用 awk 解析 JSON lines,提取 role=assistant 且 timestamp > cutoff 的 text
27
+ extra=$(awk -v cutoff="$cutoff" '
28
+ {
29
+ # 提取 timestamp
30
+ if (match($0, /"timestamp"\s*:\s*[0-9]+/)) {
31
+ ts = substr($0, RSTART, RLENGTH)
32
+ gsub(/[^0-9]/, "", ts)
33
+ ts = int(ts)
34
+ if (ts < cutoff) next
35
+ }
36
+
37
+ # 提取 role
38
+ if (match($0, /"role"\s*:\s*"assistant"/)) {
39
+ # 提取 content(可能是字符串或数组)
40
+ if (match($0, /"content"\s*:\s*\[/)) {
41
+ # 数组形式,提取所有 text 字段
42
+ gsub(/[^{]*\[/, "", $0)
43
+ gsub(/\].*/, "", $0)
44
+ while (match($0, /"text"\s*:\s*"[^"]*"/)) {
45
+ t = substr($0, RSTART, RLENGTH)
46
+ gsub(/"text"\s*:\s*"/, "", t)
47
+ gsub(/"$/, "", t)
48
+ if (t != "") print t
49
+ $0 = substr($0, RSTART + RLENGTH)
50
+ }
51
+ } else if (match($0, /"content"\s*:\s*"\([^"]*\)"/)) {
52
+ t = substr($0, RSTART, RLENGTH)
53
+ gsub(/"content"\s*:\s*"/, "", t)
54
+ gsub(/"$/, "", t)
55
+ if (t != "") print t
56
+ }
57
+ }
58
+ }
59
+ ' "$transcript" 2>/dev/null | sort -u | tr '\n' ' ')
60
+
61
+ if [[ -n "$extra" ]]; then
62
+ all_text="$extra"
63
+ fi
64
+ fi
65
+
66
+ echo "=== $(date) ===" >> "$LOG"
67
+ echo "SOURCE: $SOURCE" >> "$LOG"
68
+ echo "TEXT_LEN: ${#all_text}" >> "$LOG"
69
+ echo "PREVIEW: ${all_text:0:150}" >> "$LOG"
70
+
71
+ if [[ -n "$all_text" && -S "$SOCK" ]]; then
72
+ printf "{source:%s}%s" "$SOURCE" "$all_text" | nc -U -w5 "$SOCK" 2>> "$LOG"
73
+ echo "SPOKE: OK" >> "$LOG"
74
+ else
75
+ echo "SPOKE: SKIP" >> "$LOG"
76
+ fi
package/go.mod ADDED
@@ -0,0 +1,5 @@
1
+ module iSpeak
2
+
3
+ go 1.26.2
4
+
5
+ require gopkg.in/natefinch/lumberjack.v2 v2.2.1
package/go.sum ADDED
@@ -0,0 +1,4 @@
1
+ golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
2
+ golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
3
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
4
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=