dsh-fishkit 0.1.0 → 0.1.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/client.js CHANGED
@@ -341,13 +341,27 @@ window.__ModuleLoader__.load({
341
341
  }
342
342
 
343
343
  // ── 更新历史页 ─────────────────────────────────────────
344
- const 更新页 = ({ 备份信息 }) => e('div', null,
345
- e('div', { className: 'fk-repo' }, REPO),
346
- e('div', { className: 'fk-win', style: { maxHeight: 260, overflow: 'auto' } },
347
- e('div', { className: 'fk-card', style: { marginBottom: 8 } },
348
- e('span', { className: 'fk-doc-title' }, 'v0.1'), e('span', { className: 'fk-sub' }, ' 2026-10-31'))),
349
- e(Note, null, '图纸放 Git 仓库,插件里只留目录。'),
350
- e('div', { className: 'fk-say' }, 备份信息 || ''))
344
+ const 更新页 = ({ 备份信息 }) => {
345
+ const [记录, set记录] = useState('')
346
+ useEffect(() => {
347
+ void fetch('/dsh-fishsoul/changelog').then((r) => r.json()).then((d) => { if (d && d.ok) set记录(String(d.文 || '')) }).catch(() => {})
348
+ }, [])
349
+ const 正文 = 记录.replace(/^# .*\n+/, '')
350
+ const 段 = 正文.split(/\n## /).filter(Boolean).map((x) => x.trim())
351
+ return e('div', null,
352
+ e('div', { className: 'fk-doc-title', style: { margin: '2px 0 8px' } }, '更新历史'),
353
+ e('div', { className: 'fk-repo' }, REPO),
354
+ e('div', { className: 'fk-win', style: { maxHeight: 300, overflow: 'auto' } },
355
+ 段.length === 0
356
+ ? e('div', { className: 'fk-note' }, '还没有更新记录')
357
+ : 段.map((x, i) => e('div', { className: 'fk-card', key: i, style: { marginBottom: 8 } },
358
+ e('div', { className: 'fk-doc-title' }, 'v' + x.split(' · ')[0].replace(/^v/, '')),
359
+ e('span', { className: 'fk-sub' }, ' ' + String(x.split(' · ')[1] || '').split('\n')[0]),
360
+ e('div', { className: 'fk-note', style: { textAlign: 'left', margin: '6px 0 0' } },
361
+ x.split('\n').slice(1).filter((l) => l.startsWith('- ')).map((l) => l.slice(2)).join(';'))))),
362
+ e(Note, null, '图纸放 Git 仓库,插件里只留目录。'),
363
+ e('div', { className: 'fk-say' }, 备份信息 || ''))
364
+ }
351
365
 
352
366
  // ── 设置页 ─────────────────────────────────────────────
353
367
  function FishSoulSettings() {
@@ -399,7 +413,14 @@ window.__ModuleLoader__.load({
399
413
  if (d.有新版本) return set更新信息('有新版本:' + d.最新 + '(' + d.仓库 + ')')
400
414
  set更新信息(d.错误 ? ('暂时查不到新版本:' + d.错误) : ('已经是最新的 v' + d.当前))
401
415
  }).catch((error) => set更新信息('检查失败:' + String(error)))
402
- } }, '检查更新')),
416
+ } }, '检查更新'),
417
+ e('button', { className: 'fk-btn', onClick: () => {
418
+ set更新信息('正在更新…')
419
+ void fetch('/dsh-fishsoul/update/apply', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
420
+ .then((r) => r.json())
421
+ .then((d) => set更新信息(d.ok ? '✅ ' + (d.说 || '更新完成') : '⚠ ' + (d.说 || '更新失败')))
422
+ .catch((error) => set更新信息('更新失败:' + String(error)))
423
+ } }, '更新')),
403
424
  (更新信息 || 错 || !状态) ? e('div', { className: 'fk-badge' }, e('span', { className: 'fk-note' }, 更新信息 || (错 ? '档案没连上:' + 错 : '正在读档案…'))) : null,
404
425
  e('div', { className: 'fk-tabs' },
405
426
  PAGES.map((p, i) => e('button', { key: p, className: 'fk-tab' + (i === page ? ' active' : ''), onClick: () => setPage(i) }, p))),
package/lib/index.js CHANGED
@@ -7,13 +7,14 @@
7
7
  // ⚠ 这里不碰记忆、不碰会话、不碰别的插件 —— 它只是「总控」,坏了就卸掉,什么都不影响。
8
8
  // 新功能验证过之后,往 modules/ 里放一个 .js 就挂上了,不用改这个文件。
9
9
 
10
- import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
10
+ import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync, cpSync } from 'node:fs'
11
11
  import { join, dirname } from 'node:path'
12
12
  import { fileURLToPath, pathToFileURL } from 'node:url'
13
13
  import { 建档案核心 } from './fishsoul.js'
14
14
  import { 挂人格 } from './soul.js'
15
15
  import { 建整理器 } from './digest.js'
16
16
  import { 建夜巡 } from './nightly.js'
17
+ import { 建更新器 } from './updater.js'
17
18
 
18
19
  export const name = 'fishkit'
19
20
  export const inject = ['webServer', 'timer', 'agents', 'systemPrompt']
@@ -148,6 +149,11 @@ export function apply(ctx) {
148
149
  const 来 = await 读体(req)
149
150
  const 新 = String((来 && 来.mode) || '') === 'shared' ? 'shared' : 'independent'
150
151
  mkdirSync(DSH家, { recursive: true })
152
+ // 第一次切共享:把当前工作区档案带过去,别让新档案从空白开始
153
+ const 共享根 = join(DSH家, 'fishsoul-shared')
154
+ if (新 === 'shared' && !existsSync(join(共享根, 'manifest.json'))) {
155
+ try { cpSync(join(工作区, '.fishsoul'), 共享根, { recursive: true }) } catch (error) { 记账('共享档案初始化失败:' + String((error && error.message) || error)) }
156
+ }
151
157
  writeFileSync(模式配置, JSON.stringify({ mode: 新 }, null, 2) + '\n', 'utf8')
152
158
  回(res, { ok: true, mode: 新, 说: '已切换成' + (新 === 'shared' ? '共享' : '独立') + ',重启 DSH 后生效' })
153
159
  })
@@ -158,6 +164,15 @@ export function apply(ctx) {
158
164
  回(res, 整理器.整理(来 || {}))
159
165
  })
160
166
 
167
+ const 更新器 = 建更新器(工作区, 备份根, 记账)
168
+ 路线('/dsh-fishsoul/update/apply', async (req, res) => {
169
+ try {
170
+ 回(res, await 更新器.应用())
171
+ } catch (error) {
172
+ 回(res, { ok: false, 说: '更新失败:' + String((error && error.message) || error) })
173
+ }
174
+ })
175
+
161
176
  const 夜巡 = 建夜巡(档案, 整理器, 记账)
162
177
  ctx.effect(() => {
163
178
  ctx.interval(() => { try { 夜巡.跑() } catch (error) {} }, 30 * 60 * 1000)
@@ -193,25 +208,35 @@ export function apply(ctx) {
193
208
  路线('/dsh-fishsoul/update', async (req, res) => {
194
209
  const 当前 = '0.1.0'
195
210
  const 仓库 = 'https://github.com/dangaoyu21-netizen/fishkit'
211
+ const 包名 = 'dsh-fishkit'
196
212
  let 最新 = ''
197
213
  let 错误 = ''
214
+ let tarball = ''
198
215
  try {
199
- const r = await fetch('https://api.github.com/repos/dangaoyu21-netizen/fishkit/releases/latest', {
200
- headers: { 'user-agent': 'dsh-fishkit', accept: 'application/vnd.github+json' },
201
- signal: AbortSignal.timeout(8000),
216
+ const r = await fetch('https://registry.npmjs.org/' + 包名 + '/latest', {
217
+ headers: { 'user-agent': 'dsh-fishkit', accept: 'application/json' },
218
+ signal: AbortSignal.timeout(10000),
202
219
  })
203
220
  if (r.ok) {
204
221
  const j = await r.json()
205
- 最新 = String(j.tag_name || j.name || '')
222
+ 最新 = String(j.version || '')
223
+ tarball = String((j.dist && j.dist.tarball) || '')
206
224
  } else if (r.status === 404) {
207
- 错误 = '仓库还没有发布版本'
225
+ 错误 = 'npm 上还没有这个包'
208
226
  } else {
209
227
  错误 = 'HTTP ' + r.status
210
228
  }
211
229
  } catch (error) {
212
230
  错误 = String((error && error.message) || error)
213
231
  }
214
- 回(res, { ok: true, 当前, 最新, 仓库, 错误, 有新版本: Boolean(最新 && 最新 !== 当前 && 最新 !== 'v' + 当前) })
232
+ 回(res, { ok: true, 当前, 最新, tarball, 仓库, 错误, 有新版本: Boolean(最新 && 最新 !== 当前) })
233
+ })
234
+
235
+ // 更新历史:读插件根目录的 CHANGELOG.md
236
+ 路线('/dsh-fishsoul/changelog', async (req, res) => {
237
+ let 文 = ''
238
+ try { 文 = readFileSync(join(本目录, '..', 'CHANGELOG.md'), 'utf8') } catch (error) {}
239
+ 回(res, { ok: true, 文 })
215
240
  })
216
241
 
217
242
  void 加载模块们()
package/lib/soul.js CHANGED
@@ -66,6 +66,13 @@ export function 挂人格(ctx, 档案, 记账) {
66
66
  if (开关.mood !== false) {
67
67
  行.push('当前心情:' + (m.emotion || '平静') + '(抵触↔接受 ' + Number(m.valence ?? 0).toFixed(2) + ',平静↔激动 ' + Number(m.arousal ?? 0).toFixed(2) + ')')
68
68
  }
69
+ if (开关.attitude !== false) {
70
+ const 人 = (状态.attitude && 状态.attitude.people && 状态.attitude.people[0]) || null
71
+ if (人 && 人.name) 行.push('对「' + 人.name + '」的好感:' + Number(人.value || 0) + '%')
72
+ }
73
+ if (开关.behavior !== false) {
74
+ 行.push('行为:群里不谈键政、不过度亲密,别人聊这些就无视;被同一句话反复叫,前三次正常回,第四次起可以哈气;五秒内的群消息随机挑两条回;外部渠道一句话就够了。')
75
+ }
69
76
  if (开关.memory !== false) {
70
77
  const 全部 = 状态.events || []
71
78
  const 选 = []
package/lib/updater.js ADDED
@@ -0,0 +1,95 @@
1
+ // 小肥鱼人格 · 一键更新
2
+ //
3
+ // 检查 → 下载 npm 包 → 解压到临时目录 → 语法校验 → 备份现有插件 → 覆盖。
4
+ // 任何一步不过就停,不动现有文件。
5
+
6
+ import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readFileSync } from 'node:fs'
7
+ import { join, dirname } from 'node:path'
8
+ import { spawnSync } from 'node:child_process'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ const 本文件 = fileURLToPath(import.meta.url)
12
+ const 插件目录 = dirname(dirname(本文件))
13
+
14
+ export function 建更新器(工作区, 备份根, 记账) {
15
+ const 临时根 = join(工作区, '.fishsoul-update')
16
+ const 包名 = 'dsh-fishkit'
17
+
18
+ const 当前版本 = () => {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(join(插件目录, 'package.json'), 'utf8'))
21
+ return String(pkg.version || '0.0.0')
22
+ } catch (error) {
23
+ return '0.0.0'
24
+ }
25
+ }
26
+
27
+ const 查最新 = async () => {
28
+ const r = await fetch('https://registry.npmjs.org/' + 包名 + '/latest', {
29
+ headers: { 'user-agent': 'dsh-fishkit', accept: 'application/json' },
30
+ signal: AbortSignal.timeout(10000),
31
+ })
32
+ if (!r.ok) throw new Error('npm 查询失败:HTTP ' + r.status)
33
+ const j = await r.json()
34
+ return { 版本: String(j.version || ''), tarball: String((j.dist && j.dist.tarball) || '') }
35
+ }
36
+
37
+ const 应用 = async () => {
38
+ const 当前 = 当前版本()
39
+ const { 版本, tarball } = await 查最新()
40
+ if (!版本 || !tarball) return { ok: false, 说: 'npm 上没有拿到版本信息' }
41
+ if (版本 === 当前) return { ok: true, 说: '已经是最新的 v' + 当前, 当前 }
42
+
43
+ // 1) 备份现有插件目录
44
+ const 补 = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
45
+ const 插件备份 = join(备份根, 'plugin-' + 补)
46
+ try {
47
+ cpSync(插件目录, 插件备份, { recursive: true })
48
+ } catch (error) {
49
+ return { ok: false, 说: '备份现有插件失败:' + String((error && error.message) || error) }
50
+ }
51
+
52
+ // 2) 下载 + 解压到临时目录
53
+ rmSync(临时根, { recursive: true, force: true })
54
+ mkdirSync(临时根, { recursive: true })
55
+ const tgz = join(临时根, 'pkg.tgz')
56
+ const 下载 = await fetch(tarball, { signal: AbortSignal.timeout(60000) })
57
+ if (!下载.ok) return { ok: false, 说: '下载失败:HTTP ' + 下载.status }
58
+ writeFileSync(tgz, Buffer.from(await 下载.arrayBuffer()))
59
+ const 解 = spawnSync('tar', ['-xzf', tgz, '-C', 临时根], { windowsHide: true, encoding: 'utf8' })
60
+ if (解.status !== 0) return { ok: false, 说: '解压失败:' + String((解.stderr || '').slice(0, 200)) }
61
+ const 新目录 = join(临时根, 'package')
62
+ if (!existsSync(join(新目录, 'package.json'))) return { ok: false, 说: '解压后没找到 package.json' }
63
+
64
+ // 3) 语法校验(隔离测试)
65
+ const 要校 = ['lib/index.js', 'lib/fishsoul.js', 'lib/soul.js', 'lib/digest.js', 'lib/nightly.js', 'lib/updater.js', 'client.js']
66
+ for (const 相对 of 要校) {
67
+ const 文件 = join(新目录, 相对)
68
+ if (!existsSync(文件)) return { ok: false, 说: '新版缺少文件:' + 相对 }
69
+ const 查 = spawnSync(process.execPath, ['--check', 文件], { windowsHide: true, encoding: 'utf8', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } })
70
+ if (查.status !== 0) return { ok: false, 说: 相对 + ' 语法校验没过:' + String((查.stderr || '').slice(0, 200)) }
71
+ }
72
+ 记账('更新:v' + 当前 + ' → v' + 版本 + ',隔离校验通过,开始覆盖')
73
+
74
+ // 4) 覆盖(保留 README 等)
75
+ const 覆盖 = ['lib', 'client.js', 'package.json', 'cordis.patch.yml']
76
+ for (const 相对 of 覆盖) {
77
+ const 源 = join(新目录, 相对)
78
+ const 目标 = join(插件目录, 相对)
79
+ if (!existsSync(源)) continue
80
+ if (存在目录(目标)) rmSync(目标, { recursive: true, force: true })
81
+ cpSync(源, 目标, { recursive: true })
82
+ }
83
+
84
+ // 5) 清理临时目录
85
+ rmSync(临时根, { recursive: true, force: true })
86
+ 记账('更新完成:v' + 版本 + '(插件备份在 ' + 插件备份 + ')')
87
+ return { ok: true, 说: '更新到 v' + 版本 + ' 了,重启 DSH 生效', 当前, 版本, 备份到: 插件备份 }
88
+ }
89
+
90
+ return { 查最新, 应用, 当前版本 }
91
+ }
92
+
93
+ function 存在目录(p) {
94
+ try { return existsSync(p) } catch (error) { return false }
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fishkit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "用经历去塑造小肥鱼的插件:聊过的事记得住,心情会变还能自己记,会自己长出性格,对谁都有好恶,闲着时还会自己找点事做。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",