dsh-qiyu-stack 0.0.0-stage → 0.15.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -2
  3. package/bin/qiyu.mjs +165 -0
  4. package/docs/conventions.md +111 -0
  5. package/docs/deployment.md +216 -0
  6. package/docs/settings-tabs.md +114 -0
  7. package/examples/README.md +18 -0
  8. package/package.json +45 -6
  9. package/qiyu.config.example.json +14 -0
  10. package/runtime/README.md +84 -0
  11. package/runtime/bridge/CHANGELOG.md +70 -0
  12. package/runtime/bridge/LICENSE +21 -0
  13. package/runtime/bridge/README.md +116 -0
  14. package/runtime/bridge/cordis.patch.yml +4 -0
  15. package/runtime/bridge/lib/client.js +289 -0
  16. package/runtime/bridge/lib/host.js +1823 -0
  17. package/runtime/bridge/lib/hostdeps.mjs +141 -0
  18. package/runtime/bridge/lib/selfupdate.mjs +151 -0
  19. package/runtime/bridge/lib/voice.mjs +102 -0
  20. package/runtime/bridge/package.json +48 -0
  21. package/runtime/bridge/tools//350/257/255/351/237/263/README.md +111 -0
  22. package/runtime/bridge/tools//350/257/255/351/237/263//345/220/257/345/212/250-/345/275/225/350/257/255/351/237/263.bat +40 -0
  23. package/runtime/bridge/tools//350/257/255/351/237/263//345/275/225/350/257/255/351/237/263.mjs +295 -0
  24. package/runtime/bridge/tools//350/257/255/351/237/263//345/275/225/351/237/263/345/233/236/347/216/257.ps1 +399 -0
  25. package/runtime/bridge/tools//350/257/255/351/237/263//346/234/227/350/257/273/344/277/241/345/217/267/345/223/250/345/205/265/background.js +6 -0
  26. package/runtime/bridge/tools//350/257/255/351/237/263//346/234/227/350/257/273/344/277/241/345/217/267/345/223/250/345/205/265/content.js +29 -0
  27. package/runtime/bridge/tools//350/257/255/351/237/263//346/234/227/350/257/273/344/277/241/345/217/267/345/223/250/345/205/265/manifest.json +25 -0
  28. package/runtime/memory/LICENSE +21 -0
  29. package/runtime/memory/README.md +190 -0
  30. package/runtime/memory/cordis.patch.yml +3 -0
  31. package/runtime/memory/lib/client.js +3938 -0
  32. package/runtime/memory/lib/hostdeps.mjs +157 -0
  33. package/runtime/memory/lib/index.js +5898 -0
  34. package/runtime/memory/lib/selfupdate.mjs +151 -0
  35. package/runtime/memory/lib//347/224/237/346/210/220/346/241/243/346/241/210.js +565 -0
  36. package/runtime/memory/lib//350/257/215/345/205/270-/346/235/245/346/272/220.txt +53 -0
  37. package/runtime/memory/lib//350/257/215/345/205/270.txt +109750 -0
  38. package/runtime/memory/lib//350/257/215/351/242/221.txt +20000 -0
  39. package/runtime/memory/package.json +56 -0
  40. package/runtime/mood/CHANGELOG.md +271 -0
  41. package/runtime/mood/LICENSE +21 -0
  42. package/runtime/mood/README.md +174 -0
  43. package/runtime/mood/assets/face-good.png +0 -0
  44. package/runtime/mood/assets/face-great.png +0 -0
  45. package/runtime/mood/assets/face-ok.png +0 -0
  46. package/runtime/mood/assets/face-sad.png +0 -0
  47. package/runtime/mood/assets/face-worry.png +0 -0
  48. package/runtime/mood/assets/feiyu.png +0 -0
  49. package/runtime/mood/assets/widget.js +306 -0
  50. package/runtime/mood/client.js +389 -0
  51. package/runtime/mood/cordis.patch.yml +3 -0
  52. package/runtime/mood/lib/index.js +1090 -0
  53. package/runtime/mood/lib/selfupdate.mjs +151 -0
  54. package/runtime/mood/package.json +55 -0
  55. package/runtime/rhythm/CHANGELOG.md +85 -0
  56. package/runtime/rhythm/README.md +224 -0
  57. package/runtime/rhythm/cordis.patch.yml +7 -0
  58. package/runtime/rhythm/lib/client.js +656 -0
  59. package/runtime/rhythm/lib/host.js +1290 -0
  60. package/runtime/rhythm/lib/selfupdate.mjs +151 -0
  61. package/runtime/rhythm/package.json +46 -0
  62. package/runtime/rhythm/tests//345/256/277/344/270/273/345/215/212/350/276/271.mjs +499 -0
  63. package/runtime/rhythm/tests//347/275/221/351/241/265/345/215/212/350/276/271.mjs +354 -0
  64. package/runtime/voice//350/257/255/351/237/263/350/275/254/345/206/231.py +104 -0
  65. package/src/config.mjs +61 -0
  66. package/src/deploy.mjs +510 -0
  67. package/src/docker.mjs +95 -0
  68. package/src/doctor.mjs +323 -0
  69. package/src/init.mjs +228 -0
  70. package/src/install.mjs +414 -0
  71. package/src/preflight.mjs +412 -0
  72. package/src/qr.mjs +99 -0
  73. package/src/wizard.mjs +112 -0
  74. package/templates/blank/AGENTS.md.tpl +76 -0
  75. package/templates/blank/character.json +44 -0
  76. package/templates/blank/schedule.json +18 -0
  77. package/tools/merge-settings-tabs.mjs +104 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qiyu-dsh-stack contributors
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 CHANGED
@@ -1,3 +1,172 @@
1
- # Temporary Holding Version
1
+ # 栖语栈 · dsh-qiyu-stack
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 让一个智能体**住进 QQ**:能听会说、记得住人、有自己的作息,还不会在群里刷屏。
4
+
5
+ 名字来自「栖」——模型不是被调用一次就消失的 API,它得有地方住:有人格、有记忆、有作息的日常。
6
+
7
+ ---
8
+
9
+ ## 它解决什么问题
10
+
11
+ 自己从头搭一个「住在 QQ 里的智能体」,最难的往往不是模型,是这几件脏活:
12
+
13
+ 1. **怎么稳定收发 QQ 消息** —— 这块的坑深到离谱(见 [docs/deployment.md](docs/deployment.md))
14
+ 2. **怎么让她像人** —— 什么时候说话、什么时候闭嘴、被 @ 要应、不刷屏
15
+ 3. **怎么记住人** —— 记忆不是把聊天记录全塞回去
16
+ 4. **怎么有日常** —— 作息、主动搭话、别半夜冒出来
17
+
18
+ 栖语栈把这四件事做成**能直接跑的东西**,而不是一份教程。
19
+
20
+ ---
21
+
22
+ ## 一个设计决定:行为给默认,性格留白
23
+
24
+ | 层 | 默认给不给 | 内容 |
25
+ |---|---|---|
26
+ | **行为层** | ✅ 给 | 怎么说话、群里何时闭嘴、被 @ 要应、不刷屏、不编记忆、边界 |
27
+ | **性格层** | ⬜ 留白 | 名字、年龄、口吻、喜欢/讨厌、口头禅、写给她的信、表情包 |
28
+
29
+ 原因很实在:**纯白纸的智能体在群里会翻车**——它不知道「群消息可以不理」,会每条都接;
30
+ 不知道「不要一次发五条」,会刷屏。这些是**工程问题**,不该让每个使用者重踩一遍。
31
+
32
+ 而「她叫什么、几岁、说话冲不冲」才是该留白的部分。
33
+
34
+ 所以默认起来是:**行为层齐全 + 性格层空白 + 一个空表情包目录**(她不会自己发自拍)。
35
+
36
+ ---
37
+
38
+ ## 第二条红线:默认值里不许有「别人的东西」
39
+
40
+ 这条是踩了一脚写下来的。某个插件把作者的称呼 `小鱿鱼` 写成了默认值,
41
+ 还落进了用户的配置 —— 用户打开设置页,看到一个自己从没设过的名字。
42
+
43
+ 所以栖语栈里:
44
+
45
+ - **代码默认值**只能是空的或中性的(`port: 0`、`container: 'napcat-qq'`、`音色: ''`)
46
+ - **模板**里全是空字段,一个真实 QQ 号、真实路径、作者名字都不许出现
47
+ - **你自己的值**放在本机配置里,且不进版本库:`qiyu.config.json`、`characters/<名字>/`
48
+ - `qiyu doctor` 会**自动扫模板**,发现本机路径 / 作者角色名 / 像 QQ 号的数字 / 令牌就报警
49
+
50
+ 判断标准只有一句:**换一个陌生人来装,这个值还对吗?**
51
+
52
+ 细节和反面例子:[docs/conventions.md](docs/conventions.md)
53
+
54
+ ---
55
+
56
+ ## 快速开始
57
+
58
+ ```bash
59
+ # 0. 先看这台机器行不行(Node / Windows / WSL / 路径 / 空间 / 网络 / 杀软 / DSH / 模型 / Python)
60
+ npx dsh-qiyu-stack preflight
61
+
62
+ # 1. 建一个角色 —— 会问你几个问题(名字 / QQ / 称呼 / 作息 / 性格 / 能不能自己改性格)
63
+ npx dsh-qiyu-stack init
64
+
65
+ # 不想被问就带名字,全用默认值:npx dsh-qiyu-stack init 小满
66
+
67
+ # 2. 填她自己的东西
68
+ # characters/小满/AGENTS.md 「性格」那几行
69
+ # characters/小满/character.json QQ 号 / 称呼 / 语音 / 路径(模板里全是空的)
70
+
71
+ # 3. 起 NapCat —— 一条命令:建目录、写 OneBot 配置、跑容器、出码
72
+ npx dsh-qiyu-stack deploy --qq 你的QQ号
73
+
74
+ # 4. 把运行时(QQ 桥/记忆/心情/作息)装进 DSH,并让她成为「活动角色」
75
+ npx dsh-qiyu-stack install --character 小满 --yes # 装完重启 DSH(关掉窗口再开,不用重启电脑)
76
+
77
+ # 5. 扫码(图会自动打开),然后自查
78
+ npx dsh-qiyu-stack doctor
79
+ ```
80
+
81
+ > **用 npx 也没关系。** `install` 会先把运行时装一份到 `~/.dsh/qiyu/runtime/`,
82
+ > profile 里的 `file:` 依赖指向那份**稳定副本**,不是 qiyu 包自己的位置。
83
+ > npx 的临时缓存被清掉、或者你把 qiyu 删了,DSH 那边照样能跑。
84
+
85
+ `doctor` 会逐项告诉你:Docker 在不在、容器起没起、三个端口通不通、OneBot 认不认得出人、
86
+ 事件流连不连得上、DSH 桥接没接上。**每条失败都带一句怎么修。**
87
+
88
+ ---
89
+
90
+ ## 目录结构
91
+
92
+ ```
93
+ dsh-qiyu-stack/ 引擎(角色无关,可升级)
94
+ ├── bin/qiyu.mjs 命令行入口
95
+ ├── src/
96
+ │ ├── config.mjs 配置(文件 + 环境变量)
97
+ │ ├── init.mjs 建角色包
98
+ │ ├── deploy.mjs 起 NapCat(建目录/写配置/跑容器/出码)
99
+ │ ├── doctor.mjs 自检
100
+ │ └── qr.mjs 取二维码并直接打开
101
+ ├── tools/
102
+ │ └── merge-settings-tabs.mjs 把插件设置合并成「一个页面 + 页签」
103
+ ├── runtime/ 运行时五块(从真跑着的机器上搬来、洗过私人值)
104
+ │ ├── bridge/ QQ ↔ DSH 双向(全配置驱动)
105
+ │ ├── memory/ 长期记忆(含 1MB 中文词典)
106
+ │ ├── mood/ 心情
107
+ │ ├── rhythm/ 作息 / 主动搭话
108
+ │ └── voice/ 语音转写脚本(TTS 还在 bridge 里,待拆)
109
+ ├── templates/
110
+ │ └── blank/ 默认模板:行为齐全、性格空白、character.json 全空
111
+ ├── docs/
112
+ │ ├── deployment.md 部署(含 Windows 版为什么一定失败)
113
+ │ ├── settings-tabs.md 插件设置合并成页签(含回滚)
114
+ │ └── conventions.md 默认值里不许有别人的东西(含反面例子)
115
+ └── examples/ 样板角色(以后放去隐私后的例子)
116
+
117
+ characters/小满/ 你的角色包(不进版本库)
118
+ ├── AGENTS.md 人格
119
+ ├── character.json 她的号 / 称呼 / 语音 / 路径(模板里全空)
120
+ ├── schedule.json 作息
121
+ ├── letters/ 写给她的信(默认空)
122
+ ├── stickers/ 表情包(默认空)
123
+ ├── inbox/ 收到的图(默认空)
124
+ ├── diary/ 日记本(默认空)
125
+ ├── voice/ 她说的话(默认空)
126
+ └── memory/ 长期记忆(默认空)
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 现在的进度(v0.9)
132
+
133
+ 已经能用:
134
+
135
+ - [x] **`qiyu init` 向导**:问名字、QQ、称呼、作息、性格、「能不能自己改性格」;
136
+ 每个问题都有默认值,回车就过;带名字调用则全用默认值(给脚本用)
137
+ - [x] **`qiyu deploy`** 一条命令起 NapCat:查 Docker → 查端口 → 建目录 → 写 OneBot/WebUI 配置
138
+ → 拉镜像 → 跑容器 → 等 WebUI → **直接打开二维码**;令牌自动生成并写回 `qiyu.config.json`
139
+ - [x] `qiyu doctor` 全链路自检 + **模板体检**(扫出别人的路径/名字/QQ号/令牌)
140
+ - [x] `qiyu qr` 取登录二维码并**自动打开**(不是甩个路径让你自己找)
141
+ - [x] `tools/merge-settings-tabs.mjs` 一条命令把五六个插件的设置收进「插件」页做页签,
142
+ 带 `--dry-run` / `--restore`(见 [docs/settings-tabs.md](docs/settings-tabs.md))
143
+ - [x] 部署文档:Docker + NapCat + 一个必勾的「下次登录无需确认」
144
+ - [x] 行为层人格模板
145
+ - [x] 默认值红线:中性默认 + 本机配置分离(见 [docs/conventions.md](docs/conventions.md))
146
+ - [x] **runtime/ 五块已收进仓库**:bridge 全配置驱动、memory/mood/rhythm 原样搬、voice 只搬了脚本
147
+ (私人路径/角色名/QQ号/令牌已清干净,见 [runtime/README.md](runtime/README.md))
148
+ - [x] **`qiyu install`** 把 runtime 装进 DSH profile:默认只列清单,`--yes` 才动手;
149
+ 目标已存在就跳过(`--force` 覆盖且先备份),`profile/package.json` 改动前必留 `.bak`
150
+ - [x] **语音拆干净了**:`bridge/lib/voice.mjs` 管「说 + 听」,桥只管「向 NapCat 要 wav」;
151
+ `install` 会把转写脚本放到 `~/.dsh/qiyu/`
152
+ - [x] **配置收敛到活动角色**:`install --character 小满` 把角色包拷到 `~/.dsh/qiyu/`,
153
+ 桥读身份的优先级变成 **`character.json` > `qq-bridge-config.json` > 代码默认值**
154
+ (改人只改角色包,不碰插件配置)
155
+
156
+ - [x] **端到端真跑过一次**:空白 DSH profile → `qiyu install` → 起独立实例 → 桥接上备号
157
+ → 发消息进群 ✅ → 群里 @ 她,话被转进会话 ✅(最后「她回话」那步因测试 profile 没配模型没验)
158
+
159
+ 还差的:
160
+
161
+ - [x] ~~`memory` / `mood` / `rhythm` 也改读活动角色~~ —— 都接上了:
162
+ rhythm 读 `schedule.json` 的时段、mood 读 `名字`/`称呼.主人`、memory 的名字兜底读 `名字`
163
+
164
+ > 五块现在**都从活动角色读配置**了:改 `characters/<名字>/` 就能换人,不用碰插件配置。
165
+
166
+ > 这一段是**从已验证能跑的实现里抽出来的**,不是照文档重写。
167
+
168
+ ---
169
+
170
+ ## 许可
171
+
172
+ MIT。你养出来的角色,是你自己的。
package/bin/qiyu.mjs ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ // 栖语栈 CLI —— 只有一个入口,命令都在这里分发。
3
+ //
4
+ // qiyu init <名字> [--preset blank] 生成一个角色包
5
+ // qiyu deploy 一条命令起 NapCat:建目录、写配置、跑容器、出码
6
+ // qiyu doctor 自检:Docker / 容器 / 端口 / OneBot / 桥
7
+ // qiyu qr 取登录二维码并直接打开
8
+ // qiyu config 看一眼当前配置从哪来
9
+ // qiyu help
10
+
11
+ import { init } from '../src/init.mjs'
12
+ import { doctor } from '../src/doctor.mjs'
13
+ import { qr } from '../src/qr.mjs'
14
+ import { deploy } from '../src/deploy.mjs'
15
+ import { install } from '../src/install.mjs'
16
+ import { preflight } from '../src/preflight.mjs'
17
+ import { 读配置, 配置文件路径 } from '../src/config.mjs'
18
+
19
+ const 参数 = process.argv.slice(2)
20
+ const 命令 = 参数[0] || 'help'
21
+
22
+ function 取值(名, 兜底 = null) {
23
+ const i = 参数.indexOf(名)
24
+ if (i === -1) return 兜底
25
+ const 下一个 = 参数[i + 1]
26
+ if (!下一个 || 下一个.startsWith('--')) return 兜底
27
+ return 下一个
28
+ }
29
+
30
+ function 有开关(名) {
31
+ return 参数.includes(名)
32
+ }
33
+
34
+ const 帮助 = `
35
+ 栖语栈 qiyu —— 让一个智能体住进 QQ
36
+
37
+ 用法:
38
+ qiyu init 进向导:问名字、QQ、称呼、作息、性格、能不能自己改
39
+ qiyu init <名字> 跳过向导,全部用默认值直接建
40
+ --preset <模板> blank(以后再加)
41
+ --qq / --as / --rhythm / --personality / --self-edit 非交互时直接给值
42
+ --force 目录已存在时覆盖
43
+
44
+ qiyu preflight 装之前先查这台机器:Node / Docker / Windows / WSL / 路径 /
45
+ 空间 / 网络 / 杀软 / DSH / 模型凭证 / Python
46
+ (不假设 Docker 已经装了)
47
+ --fix 能修的帮你修:启动 Docker 引擎、停掉占端口的容器、装 edge-tts
48
+ (每一项都会先问一句,非交互环境不动手)
49
+
50
+ qiyu deploy 一条命令起 NapCat:建目录、写 OneBot 配置、跑容器、出码
51
+ --dir <目录> 数据放哪(默认挑非系统盘里空间最大的;会检查空间和中文路径)
52
+ --move 换目录时,把旧目录的数据整体搬过去
53
+ --qq <号> 她的 QQ 号(给了就开快速登录,重启容器免扫码)
54
+ --name <容器名> 默认 napcat-qq
55
+ --token/--ws-token/--webui-token 自己指定令牌(不给就随机生成)
56
+ --dry-run 只把 docker 命令打出来,不动手
57
+
58
+ qiyu doctor 自检:Docker / 容器 / 端口 / OneBot / 桥
59
+ --json 只输出 JSON(给脚本用)
60
+ --skip-ws 跳过 WebSocket 检查
61
+
62
+ qiyu qr 把登录二维码取出来并**直接打开**给你扫
63
+ (不会只给一个路径让你自己找文件)
64
+
65
+ qiyu install 把 runtime 五块装进 DSH profile
66
+ --profile <目录> 默认 ~/.dsh/profiles/desktop
67
+ --character <名字> 顺便把这个角色设为「活动角色」(拷到 ~/.dsh/qiyu/)
68
+ --only bridge,memory 只装其中几块
69
+ --dry-run 只列清单,不动手
70
+ --yes 真装(不加就只提示)
71
+ --force 已存在也覆盖(覆盖前自动备份)
72
+
73
+ qiyu config 显示配置来源(文件 / 环境变量 / 默认值)
74
+
75
+ 配置文件:qiyu.config.json(当前目录),也可以用环境变量覆盖:
76
+ QIYU_ONEBOT_TOKEN OneBot HTTP 令牌
77
+ QIYU_WS_TOKEN OneBot WS 令牌
78
+ QIYU_DSH_PORT DSH 的端口(不配就跳过 DSH 桥检查)
79
+ `
80
+
81
+ async function 主() {
82
+ if (命令 === 'help' || 命令 === '--help' || 命令 === '-h') {
83
+ console.log(帮助)
84
+ return 0
85
+ }
86
+
87
+ if (命令 === 'init') {
88
+ const 名字 = 参数[1]
89
+ // 不带名字 = 进向导(问名字、作息、性格…);带名字 = 老样子,用默认值直接建
90
+ if (!名字 || 名字.startsWith('--')) {
91
+ if (有开关('--yes')) {
92
+ console.error('--yes 需要一个名字:qiyu init 小明 --yes')
93
+ return 1
94
+ }
95
+ const { wizard } = await import('../src/wizard.mjs')
96
+ return await wizard()
97
+ }
98
+ return await init(名字, {
99
+ 模板: 取值('--preset', 'blank'),
100
+ 覆盖: 有开关('--force'),
101
+ 答案: {
102
+ qq: 取值('--qq', ''),
103
+ 称呼: 取值('--as', '你'),
104
+ 作息: 取值('--rhythm', 'regular'),
105
+ 性格: 取值('--personality', 'blank'),
106
+ 自改: 取值('--self-edit', 'ask'),
107
+ },
108
+ })
109
+ }
110
+
111
+ if (命令 === 'doctor' || 命令 === 'status') {
112
+ return await doctor({ 只输出JSON: 有开关('--json'), 跳过WebSocket: 有开关('--skip-ws') })
113
+ }
114
+
115
+ if (命令 === 'preflight' || 命令 === 'check') {
116
+ return await preflight({ 修复: 有开关('--fix') })
117
+ }
118
+
119
+ if (命令 === 'qr' || 命令 === 'login') {
120
+ return await qr()
121
+ }
122
+
123
+ if (命令 === 'deploy') {
124
+ return await deploy({
125
+ 名字: 取值('--name'),
126
+ 镜像: 取值('--image'),
127
+ 目录: 取值('--dir'),
128
+ qq: 取值('--qq'),
129
+ token: 取值('--token'),
130
+ ws令牌: 取值('--ws-token'),
131
+ webui令牌: 取值('--webui-token'),
132
+ 搬: 有开关('--move'),
133
+ 干跑: 有开关('--dry-run'),
134
+ })
135
+ }
136
+
137
+ if (命令 === 'install') {
138
+ return await install({
139
+ profile: 取值('--profile'),
140
+ 只: 取值('--only'),
141
+ 角色: 取值('--character'),
142
+ 干跑: 有开关('--dry-run'),
143
+ 确认: 有开关('--yes'),
144
+ 覆盖: 有开关('--force'),
145
+ })
146
+ }
147
+
148
+ if (命令 === 'config') {
149
+ const 配置 = 读配置()
150
+ console.log('配置文件:' + 配置文件路径())
151
+ console.log(JSON.stringify(配置, null, 2))
152
+ return 0
153
+ }
154
+
155
+ console.error('不认识的命令:' + 命令)
156
+ console.log(帮助)
157
+ return 1
158
+ }
159
+
160
+ 主()
161
+ .then((码) => process.exit(码 || 0))
162
+ .catch((错) => {
163
+ console.error('出错了:' + ((错 && 错.message) || 错))
164
+ process.exit(1)
165
+ })
@@ -0,0 +1,111 @@
1
+ # 默认值里不许有「别人的东西」
2
+
3
+ 这条是踩了一脚才写下来的。
4
+
5
+ ## 教训
6
+
7
+ `dsh-bili-subagent` 里有个字段 `ownerName`(设置页叫「称呼」),代码里写着:
8
+
9
+ ```js
10
+ ownerName: z.string().default('小鱿鱼')
11
+ ```
12
+
13
+ `小鱿鱼` 是插件作者自己的称呼。它不只是个默认值 —— 它被写进了用户的
14
+ `cordis.patch.yml`,而配置优先级是「设置页 > 插件 config.json > 代码默认值」,
15
+ 于是用户打开设置页,看到的是**一个自己从没设过的名字**,还以为是自己填错了。
16
+
17
+ **一个默认值,能让用户对自己的配置产生怀疑。**
18
+
19
+ ## 规矩
20
+
21
+ | 层 | 放什么 | 例子 |
22
+ |---|---|---|
23
+ | **代码默认值** | 空的,或对任何陌生人都成立的中性值 | `port: 0`、`container: 'napcat-qq'`、`音色: ''` |
24
+ | **模板** | 空字段 + 说明「这里填你自己的」 | `templates/blank/character.json` |
25
+ | **本机配置** | 用户自己的真实值,且**不进版本库** | `qiyu.config.json`、`characters/<名字>/` |
26
+
27
+ ## 对用户说的话,还有两条硬规矩
28
+
29
+ ### ① 「重启」必须带宾语
30
+
31
+ 用户看到「重启一下」会愣住:重启电脑?重启 DSH?重启 Docker?
32
+
33
+ 所以每一处都写清**重启的对象和范围**:
34
+
35
+ | 场景 | 该怎么说 |
36
+ |---|---|
37
+ | 装完 Docker Desktop | 「重启**这台电脑**(不是重启 DSH)」 |
38
+ | 装完插件 / bundle | 「重启 **DSH**:把窗口关掉再打开(不用重启电脑)」 |
39
+ | 改了容器配置 | 「重启**容器**:`docker restart <名>`(不用重启电脑)」 |
40
+ | 改了 client.js 样式 | 「**刷新页面**(Ctrl+R)就行,不用重启」 |
41
+
42
+ ### ② 会自动弹窗的动作,必须提前说清「弹的是什么」
43
+
44
+ 用户最怕的是:莫名其妙跳出来一个页面,上面问要 token。
45
+
46
+ 所以凡是会自己打开东西的地方,**先一句预告,再动手**——而且要说到「这是什么、为什么要它」:
47
+
48
+ - **二维码**:「会弹出 QQ 的登录码(图片窗口)。用你要登的那个号拿手机 QQ 扫它,扫完那个号就登进容器了,之后别人发消息才会走到你这边。」
49
+ (只说「弹出一张二维码」不够——用户不知道扫码是在干什么。)
50
+ - **启动 DSH**:「DSH 启动时可能自己弹一个浏览器窗口,那是它自己的界面,不是报错;里面的 QQ 设置已经填好了,不用改。」
51
+ - **NapCat 的 WebUI**:能不提就不提。真要提,一句话说清「日常用不到、只监听本机、token 在哪」。
52
+ 实测:NapCat 容器里没有浏览器,它自己**不会**开弹窗;用户看到的窗口多半是 DSH 开的。
53
+
54
+ 反面例子(真实发生过):用户第一次部署,浏览器弹出 NapCat 的 token 输入页,他不知道那是什么、也不知道 token 在哪,只能来问。
55
+
56
+ ## 一个值能不能当默认值?问一句:
57
+
58
+ > **换一个陌生人来装,这个值还对吗?**
59
+
60
+ 对 → 可以。不对 → 不能。
61
+
62
+ | 值 | 能不能当默认 | 为什么 |
63
+ |---|---|---|
64
+ | OneBot HTTP 端口 `3000` | ✅ | 是 OneBot 的惯例,人人都用 |
65
+ | DSH 端口 `55861` | ❌ | 每台机器随机分配,这里是作者的 |
66
+ | 容器名 `napcat-qq` | ✅ | 中性,谁装都叫这个 |
67
+ | 容器名 `my-bot-container` | ❌ | 作者自己起的名字 |
68
+ | 音色 `zh-CN-XiaoxiaoNeural` | ✅ | edge 的通用女声 |
69
+ | 音色 `zh-CN-XiaoyiNeural` | ❌ | 作者给某个角色选的 |
70
+ | 转写脚本 `D:\某人\语音转写.py` | ❌ | 作者的盘符和目录 |
71
+ | 称呼 `小鱿鱼` | ❌ | 作者本人 |
72
+
73
+ ## 实测踩到的「漏出来」清单
74
+
75
+ 这些是真的从别人机器上漏进过配置的:
76
+
77
+ - `ownerName: 小鱿鱼` —— 作者称呼,出现在设置页
78
+ - `转写脚本: D:\某人\语音转写.py` —— 作者路径,写死在代码默认值里
79
+ - `if (uid === 10001)` —— 作者角色的 QQ 号,写死在逻辑里
80
+ - `表情目录: D:\某人\表情包` —— 作者路径
81
+
82
+ 前两条已经改成读配置;后两条由使用者在 `character.json` 里填自己的。
83
+
84
+ ## 那「示例」怎么办?
85
+
86
+ 想让人看懂格式,又不想漏私人值:
87
+
88
+ 1. **用明显假的**:`123456789`、`C:\path\to\file`、`某甲 / 某乙`
89
+ 2. **放 `examples/`,默认不加载**,并在 README 里说明「这是样板,不是默认值」
90
+ 3. **说明文字里提一句**「比如:小鱿鱼」——只要它只是文案、不是值
91
+
92
+ ## 自检
93
+
94
+ `qiyu doctor` 会扫一遍 `templates/`,看到这些就报警:
95
+
96
+ | 规则 | 抓什么 |
97
+ |---|---|
98
+ | `[A-Z]:\\(Users|AI)\\` | 别人的本机路径 |
99
+ | `云千语\|小鱿鱼` | 作者的角色名 |
100
+ | 连续 8–11 位数字 | 像 QQ 号的数字 |
101
+ | `SESSDATA` / `mQ-…` | 令牌 |
102
+
103
+ 扫不到东西会显示:**模板体检通过:没有别人的私人值**。
104
+
105
+ ## 给插件作者的一条建议
106
+
107
+ 写 `default` 之前,把「我自己的值」从手指底下挪开:
108
+
109
+ - `default('')` 永远比 `default('我的名字')` 安全
110
+ - 想给用户看格式 → 写在 `description` 里,不是写在 `default` 里
111
+ - 配置项一旦落盘,用户很难分清「这是我设的」还是「插件给我的」