dsh-feini 0.1.2 → 0.1.4

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/INSTALL.md CHANGED
@@ -1,4 +1,4 @@
1
- # 安装 dsh-feini
1
+ # 安装 dsh-feini
2
2
 
3
3
  两条路:自己动手,或者把下面那段提示词丢给一个 AI 让它替你装。
4
4
 
@@ -103,7 +103,24 @@ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和
103
103
  2. 从 QQ 发一句,能收到回复
104
104
  3. `memory.json` 里多了你俩那一轮
105
105
 
106
- ## 四、排错
106
+ ## 四、识图和联网(可选:一个智谱 key)
107
+
108
+ 不配也能用,只是两件事干不了:**看不懂你发的图**、**查不了现在的事**(天气、价格、赛果、新版本)。
109
+
110
+ 要这两样,去 [open.bigmodel.cn](https://open.bigmodel.cn/) 注册,实名后建一个 API Key(`<一串>.<一串>` 的格式),然后二选一:
111
+
112
+ ```bash
113
+ # 装机的时候填过就算了;没填就用这条:
114
+ dsh-feini key vision 你的智谱key
115
+ ```
116
+
117
+ 或者直接把 key 粘进 `%USERPROFILE%\.dsh\dsh-feini\vision-key.txt`。
118
+
119
+ 认图和联网都是调一次扣一点,按量计费;正常聊天一天几分钱到几毛钱,具体看你在那边充了多少。
120
+
121
+ 写完不用重启——下一句话就生效。想关掉就把 `vision-key.txt` 清空。
122
+
123
+ ## 五、排错
107
124
 
108
125
  | 现象 | 多半是 | 怎么办 |
109
126
  | --- | --- | --- |
@@ -113,7 +130,7 @@ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和
113
130
  | 它重复说同一句 | 发送超时后补发 | 看日志里的「核对」;已经修过,还犯就把日志发我 |
114
131
  | 它老忘事 | 笔记本没写进去 | 直接说「记住:…」;改 `notebook.md` 立刻生效 |
115
132
 
116
- ## 五、想跟桌宠共用一个身份
133
+ ## 六、想跟桌宠共用一个身份
117
134
 
118
135
  想在桌面上有一只、QQ 里这只又跟它是同一个人的话,装两个东西:
119
136
 
@@ -141,7 +158,7 @@ dsh-feini start
141
158
  - **想断开**:把 `config.json` 里的 `systemPromptPath` 改回 `%USERPROFILE%\.dsh\dsh-feini\persona.md`,重启
142
159
  - **桌宠没装也别慌**:`link-pet` 会提示找不到配置,正常用 `persona.md` 就行
143
160
 
144
- ## 六、更新与卸载
161
+ ## 七、更新与卸载
145
162
 
146
163
  ```bash
147
164
  npm i -g dsh-feini # 更新
package/bin/cli.mjs CHANGED
@@ -107,6 +107,8 @@ const help = `dsh-feini —— 在 QQ 里养一个自己的伙伴
107
107
  dsh-feini status 看状态 + 最近日志
108
108
  dsh-feini sea 手动重建记忆海(桥在跑时每两分钟自动建)
109
109
  dsh-feini link-pet 和桌面宠物共用同一份人设(读 dsh-pet 的配置)
110
+ dsh-feini key vision <k> 补写智谱 key(识图 + 联网用)
111
+ dsh-feini key model <k> 补写 DeepSeek key
110
112
  dsh-feini path 打印数据目录
111
113
 
112
114
  参数(init 可用,也可以不交互直接给):
@@ -164,7 +166,11 @@ async function cmdInit(argv) {
164
166
  const owner = String(a.owner || (await ask('你怎么称呼自己', '主人'))).trim() || '主人';
165
167
 
166
168
  const key = String(a.key || (await ask('DeepSeek API key(可留空,事后写进 key.txt)', '')) || '').trim();
167
- const vkey = String(a['vision-key'] || (await ask('智谱 key(识图/联网用,可留空)', '')) || '').trim();
169
+ const vkey = String(
170
+ a['vision-key'] ||
171
+ (await ask('智谱 key(识图+联网用,open.bigmodel.cn 建,没有就留空)', '')) ||
172
+ '',
173
+ ).trim();
168
174
  rl?.close();
169
175
 
170
176
  if (key) fs.writeFileSync(path.join(home, 'key.txt'), key, 'utf8');
@@ -187,7 +193,8 @@ async function cmdInit(argv) {
187
193
  },
188
194
  modelVision: {
189
195
  baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
190
- model: 'glm-4v-flash',
196
+ // 智谱现在的视觉模型;旧名 glm-4v-flash 已经不太稳,识图会 400
197
+ model: 'glm-5.3-flash',
191
198
  apiKeyEnv: 'FEINI_VISION_KEY',
192
199
  keyFile: path.join(home, 'vision-key.txt'),
193
200
  },
@@ -345,6 +352,32 @@ function cmdSea() {
345
352
  console.log('文件:' + (cfg.seaFile || path.join(home, 'me.json')));
346
353
  }
347
354
 
355
+ /**
356
+ * 补写 key:dsh-feini key vision <智谱key> / key model <DeepSeek key>
357
+ * 装的时候没填、或者后来换了 key,用它,不用重新 init。
358
+ */
359
+ function cmdKey(argv) {
360
+ const kind = String(argv[0] || '').toLowerCase();
361
+ const value = String(argv[1] || '').trim();
362
+ if (!kind || !value) {
363
+ console.error('用法:dsh-feini key vision <智谱key> 或者 dsh-feini key model <DeepSeek key>');
364
+ process.exit(1);
365
+ }
366
+ ensureDir(home);
367
+ if (kind === 'vision' || kind === '智谱') {
368
+ fs.writeFileSync(path.join(home, 'vision-key.txt'), value, 'utf8');
369
+ console.log('智谱 key 写好了:' + path.join(home, 'vision-key.txt'));
370
+ console.log('识图和联网下一句就生效,不用重启');
371
+ } else if (kind === 'model' || kind === 'deepseek') {
372
+ fs.writeFileSync(path.join(home, 'key.txt'), value, 'utf8');
373
+ console.log('DeepSeek key 写好了:' + path.join(home, 'key.txt'));
374
+ console.log('要重启桥才生效:dsh-feini stop && dsh-feini start');
375
+ } else {
376
+ console.error(`不认识的 key 类型:${kind}(只认 vision / model)`);
377
+ process.exit(1);
378
+ }
379
+ }
380
+
348
381
  /* ---------- 入口 ---------- */
349
382
 
350
383
  const cmd = process.argv[2];
@@ -357,6 +390,7 @@ try {
357
390
  else if (cmd === 'status') cmdStatus();
358
391
  else if (cmd === 'sea') cmdSea();
359
392
  else if (cmd === 'link-pet') cmdLinkPet();
393
+ else if (cmd === 'key') cmdKey(rest);
360
394
  else if (cmd === 'path') cmdPath();
361
395
  else {
362
396
  console.error(`不认识的命令:${cmd}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-feini",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "菲妮做的 QQ 陪伴插件:一张空白人设、一本笔记本、一套会攒记忆的规矩,让你自己养自己的伙伴。",
5
5
  "author": "Feini",
6
6
  "icon": "assets/logo.svg",
package/src/proactive.mjs CHANGED
@@ -159,6 +159,13 @@ export function startProactive({ config, getSystemPrompt, apiKey, bot, history,
159
159
  if (sleptLate && now - state.lastUserAt < quietMs) return;
160
160
  }
161
161
 
162
+ // 他说了「晚安」:就当他睡了,7 小时内不主动、也不闹脾气。
163
+ // 2026-10-31 02:33 那次就是因为没有这条——他 01:5x 说晚安,之后还被追着喊了三回。
164
+ if (state.goodnightAt && now - state.goodnightAt < quietMs) return;
165
+
166
+ // 不在活跃时段(默认 07:00–02:00)就别开口
167
+ if (!inWindow(window.from, window.to)) return;
168
+
162
169
  try {
163
170
  // 他刚说过话:别插嘴,等安静够 5 分钟再说
164
171
  if (state.lastUserAt && now - state.lastUserAt < afterReplyMin * 60000) return;
@@ -201,11 +208,28 @@ export function startProactive({ config, getSystemPrompt, apiKey, bot, history,
201
208
  },
202
209
  /** 他回话了:不是立刻消气——傲娇要哄到位 */
203
210
  noteUserReply(text) {
211
+ const t = String(text || '');
204
212
  state.lastUserAt = Date.now();
205
213
  state.pendingSince = 0;
206
214
  // 他刚回话,下一轮主动重新计时:不然对话一结束、安静满 5 分钟就立刻开口
207
215
  state.nextAt = Date.now() + randBetween(gapMin, gapMax) * 60000;
208
216
 
217
+ // 他说「晚安 / 去睡了」:记下来,接下来 7 小时不主动也不闹。
218
+ // 醒来说第一句就清掉(下面 else 分支),不用他专门说「我醒了」。
219
+ if (/(晚安|我睡了|先去睡|去睡了|睡觉去了|睡啦|睡咯|晚安啦)/.test(t)) {
220
+ state.goodnightAt = Date.now();
221
+ state.sulking = false;
222
+ state.sulkCount = 0;
223
+ state.coaxNeed = 0;
224
+ state.coaxCount = 0;
225
+ writeSulk(sulkFile, null);
226
+ log('他道晚安了,接下来不主动找他');
227
+ } else if (state.goodnightAt && Date.now() - state.goodnightAt > 30 * 60000) {
228
+ // 隔了半小时以上又开口,说明醒了:解除静默
229
+ state.goodnightAt = 0;
230
+ log('他醒了,恢复主动');
231
+ }
232
+
209
233
  if (state.sulking) {
210
234
  const t = String(text || '');
211
235
  const coax = /(对不起|我错|别生气|不要气|哄你|抱抱|亲亲|爱你|想你|乖|抱歉|么么|摸摸|宝宝|我别)/;