dsh-memory-eternal 0.4.27 → 0.5.0

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 CHANGED
@@ -146,7 +146,169 @@ dsh plugin --profile web add F:/MyApp/eternal/dsh-memory-eternal
146
146
  > - 要**官方最新**(避免 npmmirror 滞后):`npm_config_registry=https://registry.npmjs.org/ dsh plugin --profile web add dsh-memory-eternal`。
147
147
  > - **profile 是 pnpm workspace**:`cd ~/.dsh/profiles/web && pnpm add dsh-memory-eternal@latest`(`npm install` 会报 `EUNSUPPORTEDPROTOCOL`)。
148
148
 
149
- 装完**重启 dsh web**:设置 → 记忆 出现知识库;侧边栏底部出现「记忆」按钮。此后每轮对话自动沉淀。
149
+ 装完**重启 dsh web**:设置 → 记忆 出现知识库(iframe 加载 Web 端);侧边栏底部出现「记忆」按钮。此后每轮对话自动沉淀。
150
+
151
+ ## 🌐 多宿主:装完即用(Claude Code / Codex / Cursor / 浏览器)
152
+
153
+ 从 v0.5 起,本插件不再只属于 DSH——**一个记忆库,所有 Agent 共享**:
154
+
155
+ ```
156
+ ┌────────── 核心引擎(vault.js + capture.js,零耦合)──────────┐
157
+ │ ① DSH 原生插件 ② MCP server ③ Web 端 │
158
+ │ 自动沉淀+召回 stdio 零依赖 UI 唯一真源 │
159
+ │ UI=iframe 壳 Claude/Codex/ 浏览器直接访问 │
160
+ │ Cursor DSH 渲染也走它 │
161
+ └──────────────────────────────────────────────────────────────┘
162
+ ```
163
+
164
+ ### 自动挂载(零手动)
165
+
166
+ 插件激活时自动检测本机已装的 Agent,**幂等写入 MCP 配置**(改动前自动备份):
167
+
168
+ - **Claude Code** → `~/.claude.json` 的 `mcpServers` + `~/.claude/settings.json` 的 SessionEnd hook(会话结束自动沉淀)
169
+ - **Codex CLI** → `~/.codex/config.toml` 的 `[mcp_servers.memory]`
170
+ - **Cursor** → `~/.cursor/mcp.json`
171
+
172
+ 下次打开对应工具,`memory_recall` / `memory_capture` / `memory_stats` 三个工具直接可用。不想自动配置?设置里关 `autoMcpSetup`,或环境变量 `MEMORY_ETERNAL_SKIP_AUTO=1`。
173
+
174
+ ### Web 端(默认常驻)
175
+
176
+ 插件激活时自动拉起独立 Web server(detached 进程,不占 DSH 宿主),**UI 唯一真源**——DSH 内的设置页与浏览器访问的是同一份:
177
+
178
+ ```bash
179
+ dsh-memory open # 确保 Web 存活 + 浏览器打开(默认 http://127.0.0.1:7999)
180
+ dsh-memory serve --port 8080 # 前台跑 Web server
181
+ ```
182
+
183
+ ### CLI 速查
184
+
185
+ ```bash
186
+ dsh-memory recall "数据库选型" # 检索
187
+ dsh-memory capture "重要结论..." # 手动沉淀
188
+ dsh-memory sweep ~/.claude/projects # 挖掘已有会话记录
189
+ dsh-memory setup --dry-run # 重跑自动挂载(幂等)
190
+ dsh-memory mcp # MCP stdio(挂给任意 MCP 客户端)
191
+ ```
192
+
193
+ ### 蒸馏 LLM(可选)
194
+
195
+ MCP / hooks / CLI 的自动蒸馏需要一个 OpenAI 兼容端点(DSH 内免配):
196
+
197
+ ```bash
198
+ export MEMORY_LLM_BASE_URL=https://api.deepseek.com # 或 Ollama/vLLM/LM Studio
199
+ export MEMORY_LLM_KEY=sk-xxx
200
+ export MEMORY_LLM_MODEL=deepseek-chat
201
+ ```
202
+
203
+ 未配置时:recall 全功能;capture 降级为原文卡(不蒸馏)。
204
+
205
+ ### 单一副本原则(防版本漂移)
206
+
207
+ MCP/hooks/CLI 全部指向 DSH profile 内那一份代码(`~/.dsh/profiles/web/node_modules/dsh-memory-eternal`)。**更新只需一条命令**:`cd ~/.dsh/profiles/web && pnpm add dsh-memory-eternal@latest`,DSH + MCP + Web 齐步走。纯 Claude Code 用户(无 DSH)可 `npm i -g dsh-memory-eternal`,独立副本独立库。卡格式带 `formatVersion` 守卫,跨版本只向后兼容读,多副本共存不会写坏库。
208
+
209
+ ---
210
+
211
+ ## 📖 使用
212
+
213
+ > **核心心智模型:一个库(`~/.dsh/memory-vault`,纯 Markdown + 可 git),一群 Agent(DSH / Claude / Codex / Cursor)自动记、随时忆,一个网页(`dsh-memory open`)看全局。**
214
+
215
+ ### DSH 内使用(现状,0 步骤)
216
+
217
+ ```bash
218
+ # 重启 dsh web 后生效新版插件
219
+ ```
220
+
221
+ - **自动沉淀**:每轮对话结束自动把可复用内容蒸馏成知识卡,无需操作
222
+ - **`memory_recall` 工具**:Agent 在需要历史决策/项目背景时主动调用
223
+ - **可视化**:设置 → 记忆 或侧边栏底部「记忆」按钮 → 内嵌打开 Web 端(同一份 UI)
224
+ - **多 Vault**:设置里 `vaultProfiles` 命名分库 + `activeVault` 切换;跨库聚合检索 `?all=1`
225
+
226
+ ### Claude Code 使用
227
+
228
+ 装完即用——MCP 与 SessionEnd hook 都已自动挂载:
229
+
230
+ - 会话中说「recall 一下数据库选型」→ Claude 自动调 `memory_recall`
231
+ - 会话结束/上下文压缩前 → hook 自动蒸馏入库
232
+ - 想看记了什么:`dsh-memory open`
233
+
234
+ ### Codex CLI / Cursor 使用
235
+
236
+ 重启对应工具后,MCP 已在工具的 MCP 列表里,会话内直接:
237
+
238
+ ```
239
+ 用 memory_recall 查一下项目的历史决策
240
+ 用 memory_stats 看库内有多少张卡
241
+ ```
242
+
243
+ ### Web 端使用
244
+
245
+ ```bash
246
+ dsh-memory open # 确保 Web 存活 + 浏览器打开(默认 http://127.0.0.1:7999)
247
+ dsh-memory serve --port 8080 # 自定义端口前台运行
248
+ ```
249
+
250
+ 打开后所见即所得:统计概览 / 中文片段检索 / 知识卡网格(删除/编辑/合并/导入导出)/ 增强知识图谱(力导向+时间维着色+右键菜单+框选+PNG 导出)。与 DSH 内嵌「记忆」页面渲染同一份 UI(单一真源),数据天然同步。
251
+
252
+ ### CLI 速查
253
+
254
+ ```bash
255
+ dsh-memory recall "数据库选型" # 检索知识卡(输出 JSON)
256
+ dsh-memory capture "重要结论..." # 手动沉淀(- 读 stdin)
257
+ dsh-memory sweep ~/.claude/projects # 挖掘已有会话记录
258
+ dsh-memory setup --dry-run # 预览自动挂载(幂等)
259
+ dsh-memory mcp # MCP stdio(手动挂任意 MCP 客户端)
260
+ dsh-memory serve --port 7999 # 前台 Web server
261
+ dsh-memory open # 确保 Web 存活 + 浏览器打开
262
+ ```
263
+
264
+ ### 蒸馏 LLM(可选)
265
+
266
+ MCP / hooks / CLI 的自动蒸馏需要一个 OpenAI 兼容端点(DSH 内免配——复用 dsh 的 llm):
267
+
268
+ ```bash
269
+ export MEMORY_LLM_BASE_URL=https://api.deepseek.com # 或 Ollama/vLLM/LM Studio
270
+ export MEMORY_LLM_KEY=sk-xxx
271
+ export MEMORY_LLM_MODEL=deepseek-chat
272
+ ```
273
+
274
+ 未配置时:recall 全功能;capture 降级为原文卡(不蒸馏,仍可正常用)。
275
+
276
+ ### zcode(智谱)说明
277
+
278
+ zcode 暂无原生 MCP 支持,可经社区 [zcode-open-bridge](https://github.com/tizerluo/zcode-open-bridge) 转 MCP 后挂载,或直接用 CLI(`dsh-memory capture / recall / sweep`)。
279
+
280
+ ---
281
+
282
+ ## 🔄 更新
283
+
284
+ **单副本原则**是更新能「一条命令搞定」的根基——所有入口都指向同一份代码。
285
+
286
+ ```bash
287
+ # DSH 用户(最常见):profile 里更新 → DSH + MCP + Web 齐步走
288
+ cd ~/.dsh/profiles/web
289
+ pnpm add dsh-memory-eternal@latest
290
+
291
+ # 纯 Claude Code / Cursor 用户(无 DSH):全局包更新
292
+ npm i -g dsh-memory-eternal@latest
293
+ ```
294
+
295
+ 更新完后:
296
+ - **DSH 用户**:重启 dsh web(插件 reload)→ `autoMcpSetup` 自动幂等跳过(配置已是最新),`autoWeb` 进程已是新版
297
+ - **Claude/Codex/Cursor 用户**:重启对应工具,stdin MCP 进程由 agent 重 spawn → 加载新版
298
+
299
+ 不会出现的版本漂移:所有 agent 入口指向同一份代码,不存在「DSH 是新版、Claude Code 是旧版」导致的格式/行为不一致。卡文件 `formatVersion` 字段是最后一道防线,跨版本只向后兼容读。
300
+
301
+ 如果想从 GitHub 拉未发布版(dev/特性分支):
302
+
303
+ ```bash
304
+ # DSH 用户
305
+ cd ~/.dsh/profiles/web && pnpm add github:EternalNight996/dsh-memory-eternal
306
+
307
+ # 全局
308
+ npm i -g github:EternalNight996/dsh-memory-eternal
309
+ ```
310
+
311
+ ---
150
312
 
151
313
  ### 🧭 插件发现 / 收录标准
152
314
 
@@ -178,6 +340,8 @@ dsh plugin --profile web add F:/MyApp/eternal/dsh-memory-eternal
178
340
  | activeVault | '' | 当前激活的 Vault(对应 vaultProfiles 中的 name) |
179
341
  | sessionBudgetChars | 80000 | 会话级 token 预算(字符数),供 harness 触发压缩 |
180
342
  | recallEmbedding | '' | 语义召回 provider(空=零依赖 bigram + LLM 判定兜底) |
343
+ | autoMcpSetup | 开 | 激活时自动把 MCP 挂到本机已装的 Claude Code/Codex/Cursor(幂等+备份) |
344
+ | autoWeb | 开 | 激活时确保 Web server 常驻(UI 唯一真源,DSH 渲染走 iframe) |
181
345
 
182
346
  ---
183
347
 
@@ -218,6 +382,12 @@ dsh-memory-eternal/
218
382
  - **整理建议**(相似卡对合并/陈旧卡,非破坏预览)
219
383
  - **召回用户反馈**(`/feedback`,为相关性调优留数据)
220
384
  - **会话预算**(`sessionBudgetChars` + `/budget`)
385
+ - **用量趋势图 + 笔记模板(4种)**(v0.4.25)
386
+ - **一键清理陈旧卡片** + **优化面板增强**(v0.4.25)
387
+ - **新建卡片 kind 下拉可读背景色**(v0.4.27)
388
+ - **图谱合并/删除即时联动整理建议**(v0.4.27)
389
+ - **一键合并相似按钮从图谱工具栏移到整理建议面板**(v0.4.26)
390
+ - **多宿主**:MCP server + CLI `dsh-memory` + Web 端常驻 + 自动挂载 Claude/Codex/Cursor + SessionEnd hook + `formatVersion` 守卫(v0.5.0)
221
391
 
222
392
  **待办(核心功能优化)**:
223
393
  - [x] ~~多 Vault / 多 Profile~~、~~注入体积可配置~~、~~每日回顾+用量~~、~~自动归档整理~~、~~召回用户反馈~~、~~会话级 token 预算~~(v0.4.5)
@@ -226,11 +396,18 @@ dsh-memory-eternal/
226
396
  - [ ] 多 Vault 的**跨库合并/去重**(跨 profile 识别重复卡)
227
397
  - [ ] 语义召回**接入外部 embedding provider**(当前为本地零依赖加分)
228
398
  - [ ] 每日回顾**主动推送通道**(当前生成文件 + 手动查看,推送需 harness/通知渠道)
399
+ - [ ] **多宿主体验进一步收口**:zcode bridge 内置 / Codex sweep 增字段适配 / Web 端暗色主题微调
229
400
 
230
401
  ---
231
402
 
232
403
  ## 📦 发布记录
233
404
 
405
+ - **v0.5.0**:**多宿主化**——MCP server 零依赖 stdio(3 工具:recall/capture/stats),CLI 入口 `dsh-memory`(recall/capture/serve/open/mcp/setup/sweep),Web 端常驻(DSH 渲染走 iframe = 单一 UI 真源),自动挂载到 Claude Code/Codex/Cursor(幂等+备份),Claude Code SessionEnd hook 自动沉淀,卡格式 `formatVersion` 守卫,DSH 用户 / Claude Code 用户均「一条命令安装即用」。`api.js` 抽层 + `capture-run.js` 独立沉淀管线复用。
406
+ - **v0.4.28**:侧边栏底部「记忆」按钮**独占一行**——不再与「主题」等 footer 按钮挤在同一行(`flex-wrap` 同时命中直接包含与中间隔 wrapper 两种情况)。
407
+ - **v0.4.27**:新建卡片 kind 下拉可读背景色;图谱合并/删除节点后**即时联动整理建议**面板刷新。
408
+ - **v0.4.26**:一键合并相似按钮从图谱工具栏移到整理建议面板,操作路径更直观。
409
+ - **v0.4.25**:**用量趋势图**(近7天柱状图);**笔记模板**(4种:需求/决策/接口/复盘);**一键清理陈旧卡片**;优化面板增强 + `mergeCards` 修复。
410
+ - **v0.4.24**:preview 清理 + `.gitignore` 完善;README 设置文档补充;瘦身确认打包。
234
411
  - **v0.4.23**:项目瘦身——README 图片改 GitHub raw 链接,`files` 白名单移除 assets/docs/PUBLISH,npm 包从 ~10MB 降至 **50 KB**(99.5% 缩减);`/budget` 路由 500 防御降级 + fetchAll 容错;跨库聚合路由 try/catch 安全回退。
235
412
  - **v0.4.22**:每日回顾简报「查看/收起」toggle(并入用量面板)、去掉左侧栏第 5 项。
236
413
  - **v0.4.21**:筛选按钮移到搜索框右边;`mergeCards` 抽取到 vault.js + 单元测试覆盖。
@@ -246,6 +423,45 @@ dsh-memory-eternal/
246
423
 
247
424
  ---
248
425
 
426
+ ## 📜 Git 历史(近 30 条)
427
+
428
+ | Commit | 描述 |
429
+ |--------|------|
430
+ | `b6aee65` | fix(v0.4.27): 新建卡片kind下拉可读背景色/图谱合并删除即时联动整理建议 |
431
+ | `60c7271` | fix(v0.4.26): 一键合并相似按钮从图谱工具栏移到整理建议面板 |
432
+ | `93ce8f5` | v0.4.25: 用量趋势图/笔记模板(4种)/一键清理陈旧/优化面板增强/mergeCards修复 |
433
+ | `b5d326a` | v0.4.24: preview清理/.gitignore + README设置文档补充 + 瘦身确认打包 |
434
+ | `1c16e1b` | chore(v0.4.23): 项目瘦身——README图片改GitHub raw链接,files白名单移除assets/docs/PUBLISH |
435
+ | `a0d75f0` | refactor(v0.4.22): 每日回顾简报回收/并入用量统计(查看/收起)/去掉左栏第5项 |
436
+ | `4d8d91d` | feat(v0.4.21): 筛选按钮移到搜索框右边/mergeCards 抽取+测试覆盖 |
437
+ | `f88f4d3` | refactor(v0.4.20): 去重——每日回顾移除重复今日卡片列表仅保留简报;标签筛选并入左下角筛选下拉 |
438
+ | `d879ed4` | fix(v0.4.19): 知识图谱筛选下拉化+加载中持久化修复(/graph ok:false设error) |
439
+ | `c503e96` | fix(v0.4.18): /budget 500防御降级+fetchAll容错budget不再阻断用量/整理面板 |
440
+ | `904abd9` | feat(v0.4.17): 用量加载失败红色提示+重试/左侧栏加今日简报独立视图 |
441
+ | `e522979` | test(v0.4.16): 补API形状测试——stats/optimizeCandidates/cross-vault(graphAll+searchAll) |
442
+ | `f30762c` | test(v0.4.16): 补单元测试——stats/optimizeCandidates/语义检索+反馈/跨库聚合/每日简报/compressExcerpt |
443
+ | `e379a9d` | feat(v0.4.16): 召回反馈反哺排序+图谱标签过滤/标签云+卡片读者有用/无关反馈 |
444
+ | `43b0efb` | feat(v0.4.15): 图谱搜索节点定位——输入命中即居中放大并选中 |
445
+ | `54d0c88` | fix(v0.4.14): 用量/整理加载失败显性提示+图谱右键合并按钮 |
446
+ | `b28a07b` | feat(v0.4.13): 去掉统一管理/左侧栏改为独立功能项(卡片/图谱/用量/整理) |
447
+ | `783af40` | feat(v0.4.12): 管理改左侧栏第三视图内嵌渲染/收缩图标换/图谱搜索同卡片样式 |
448
+ | `4428b77` | feat(v0.4.11): 管理入左侧栏/卡片与图谱输入框独立分开切换 |
449
+ | `7db1e93` | feat(v0.4.10): 左侧栏美化——分栏分隔线/收缩按钮/标签按钮 |
450
+ | `51b39ec` | fix(v0.4.9): UI——卡片/图谱切换改固定左侧栏+选中色可读/图谱展示全部去滚动条 |
451
+ | `ac7b318` | feat(v0.4.8): UI增强——卡片/图谱切按钮移顶端栏凸显/去掉无效自选位置 |
452
+ | `887d1a5` | fix(v0.4.7): 管理面板TDZ崩溃——fetchAll提升为函数声明,修正useEffect依赖 |
453
+ | `faf1e7c` | fix(v0.4.6): 修复i18n对象闭合导致的esbuild构建失败 |
454
+ | `9d5ac70` | feat(v0.4.6): 记忆侧收官——批量智能合并/跨库聚合/语义召回加分/每日回顾定时生成 |
455
+ | `7951eee` | feat(v0.4.5): 记忆侧核心优化——注入可配置/多Vault/每日回顾统计/整理建议/召回反馈 |
456
+ | `0275444` | docs(v0.4.4): 重构README为热产品——痛点Before/After+内核设计+权威类比 |
457
+ | `f32a746` | feat(v0.4.3): 记忆侧省token/提质量——沉淀预筛+召回相关性minScore+片段截断 |
458
+ | `ee11948` | fix(v0.4.2): 压缩产品演示GIF至README 10MB阈值下(12MB→8MB) |
459
+ | `29d5c85` | feat(v0.4.1): Marketplace收录标准(dsh.marketplace元数据+自动扫描规则说明) |
460
+
461
+ > 完整历史:`git log --oneline` 或访问 [GitHub Commits](https://github.com/EternalNight996/dsh-memory-eternal/commits/main)
462
+
463
+ ---
464
+
249
465
  ## 📄 License
250
466
 
251
467
  MIT
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ // 记忆核心 · CLI(多宿主统一入口)
3
+ //
4
+ // 用法:
5
+ // dsh-memory recall <query> [--limit N] [--vault DIR] 检索知识卡
6
+ // dsh-memory capture <text | - > [--source TAG] 手动沉淀(- 读 stdin)
7
+ // dsh-memory serve [--port N] [--vault DIR] 启动 Web UI(前台常驻)
8
+ // dsh-memory open [--port N] 确保 Web 存活并用浏览器打开
9
+ // dsh-memory mcp MCP stdio server(各 agent 挂载)
10
+ // dsh-memory setup [--claude-only|--codex-only|--cursor-only] [--dry-run] [--no-hooks]
11
+ // 自动挂载 MCP 到已装的 agent
12
+ // dsh-memory sweep <dir> 挖掘 Claude Code 会话 JSONL
13
+ //
14
+ // 环境变量:
15
+ // MEMORY_VAULT_DIR vault 目录(默认 ~/.dsh/memory-vault)
16
+ // MEMORY_LLM_BASE_URL / MEMORY_LLM_KEY / MEMORY_LLM_MODEL 蒸馏 LLM(OpenAI 兼容)
17
+ // MEMORY_ETERNAL_SKIP_AUTO=1 setup 时不动外部配置
18
+
19
+ import path from 'node:path'
20
+ import process from 'node:process'
21
+
22
+ const argv = process.argv.slice(2)
23
+ const cmd = argv[0] || 'help'
24
+ const argOf = (name) => {
25
+ const i = argv.indexOf(name)
26
+ return i >= 0 && i + 1 < argv.length ? argv[i + 1] : null
27
+ }
28
+ const has = (name) => argv.includes(name)
29
+
30
+ /** 取位置参数(跳过子命令、flag 与 flag 的值) */
31
+ const positional = () => {
32
+ const valuesTaken = new Set(['--limit', '--vault', '--source', '--port'])
33
+ const out = []
34
+ for (let i = 1; i < argv.length; i++) {
35
+ const a = argv[i]
36
+ if (valuesTaken.has(a)) { i++; continue }
37
+ if (a.startsWith('--')) continue
38
+ out.push(a)
39
+ }
40
+ return out
41
+ }
42
+
43
+ async function main() {
44
+ switch (cmd) {
45
+ case 'recall': {
46
+ const query = positional().join(' ')
47
+ const { search } = await import('../lib/vault.js')
48
+ const { defaultVaultDir } = await import('../lib/capture-run.js')
49
+ const root = path.resolve(argOf('--vault') || defaultVaultDir())
50
+ const limit = Math.min(Math.max(Number(argOf('--limit')) || 5, 1), 20)
51
+ const hits = await search(root, query, { limit, minScore: 2 })
52
+ if (!hits.length) {
53
+ console.log(JSON.stringify({ ok: true, hits: [], note: `记忆库中没有与「${query}」相关的内容` }, null, 2))
54
+ return
55
+ }
56
+ console.log(JSON.stringify({ ok: true, vaultDir: root, hits }, null, 2))
57
+ return
58
+ }
59
+ case 'capture': {
60
+ const raw = positional().join(' ')
61
+ let text = raw
62
+ if (raw === '-' || (!raw && !process.stdin.isTTY)) {
63
+ text = await new Promise((resolve) => {
64
+ let buf = ''
65
+ process.stdin.setEncoding('utf8')
66
+ process.stdin.on('data', (c) => { buf += c })
67
+ process.stdin.on('end', () => resolve(buf))
68
+ })
69
+ }
70
+ const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
71
+ const root = path.resolve(defaultVaultDir())
72
+ const out = await runStandaloneCapture(root, text, { source: argOf('--source') || 'cli' })
73
+ console.log(JSON.stringify({ ...out, vaultDir: root }, null, 2))
74
+ process.exitCode = out.ok ? 0 : 1
75
+ return
76
+ }
77
+ case 'serve': {
78
+ const { startWebServer, DEFAULT_WEB_PORT } = await import('../lib/web.js')
79
+ const { defaultVaultDir } = await import('../lib/capture-run.js')
80
+ const port = Number(argOf('--port')) || DEFAULT_WEB_PORT
81
+ const vault = path.resolve(argOf('--vault') || defaultVaultDir())
82
+ await startWebServer({ port, vaultRoot: vault })
83
+ process.on('SIGINT', () => process.exit(0))
84
+ process.on('SIGTERM', () => process.exit(0))
85
+ return
86
+ }
87
+ case 'open': {
88
+ const { ensureWebServer, DEFAULT_WEB_PORT } = await import('../lib/web.js')
89
+ const { defaultVaultDir } = await import('../lib/capture-run.js')
90
+ const port = Number(argOf('--port')) || DEFAULT_WEB_PORT
91
+ const { url } = await ensureWebServer({ port, vaultRoot: path.resolve(defaultVaultDir()) })
92
+ console.log(url)
93
+ const plat = process.platform
94
+ const { exec } = await import('node:child_process')
95
+ if (plat === 'win32') exec(`start "" "${url}"`)
96
+ else if (plat === 'darwin') exec(`open "${url}"`)
97
+ else exec(`xdg-open "${url}"`)
98
+ return
99
+ }
100
+ case 'mcp': {
101
+ const { startMcpServer } = await import('../lib/mcp.js')
102
+ startMcpServer()
103
+ return
104
+ }
105
+ case 'setup': {
106
+ const { runSetup } = await import('../lib/setup.js')
107
+ const only = []
108
+ if (has('--claude-only')) only.push('claude')
109
+ if (has('--codex-only')) only.push('codex')
110
+ if (has('--cursor-only')) only.push('cursor')
111
+ const out = await runSetup({ only, withHooks: !has('--no-hooks'), dryRun: has('--dry-run') })
112
+ const done = out.results.filter((r) => r.ok).length
113
+ console.log(`\n记忆库目录:默认 ~/.dsh/memory-vault(MEMORY_VAULT_DIR 可改)`)
114
+ console.log(`完成:${done}/${out.results.length} 项。Web UI: dsh-memory open`)
115
+ return
116
+ }
117
+ case 'sweep': {
118
+ const dir = argv[1]
119
+ if (!dir || dir.startsWith('--')) {
120
+ console.error('用法:dsh-memory sweep <dir>(如 ~/.claude/projects)')
121
+ process.exitCode = 1
122
+ return
123
+ }
124
+ const { sweepSessions } = await import('../lib/sweep.js')
125
+ const out = await sweepSessions(dir)
126
+ console.log(JSON.stringify(out, null, 2))
127
+ return
128
+ }
129
+ default:
130
+ console.log(`dsh-memory — 记忆核心 CLI
131
+
132
+ 用法:
133
+ dsh-memory recall <query> [--limit N] 检索知识卡
134
+ dsh-memory capture <text | -> [--source T] 手动沉淀(- 读 stdin)
135
+ dsh-memory serve [--port N] Web UI(前台)
136
+ dsh-memory open 确保 Web 存活并打开浏览器
137
+ dsh-memory mcp MCP stdio server
138
+ dsh-memory setup [--dry-run] ... 自动挂载 MCP 到已装 agent
139
+ dsh-memory sweep <dir> 挖掘会话 JSONL
140
+
141
+ 环境变量:MEMORY_VAULT_DIR / MEMORY_LLM_BASE_URL / MEMORY_LLM_KEY / MEMORY_LLM_MODEL`)
142
+ }
143
+ }
144
+
145
+ main().catch((error) => {
146
+ console.error(String(error?.stack || error))
147
+ process.exit(1)
148
+ })
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ // 记忆核心 · Claude Code SessionEnd/PreCompact hook(Windows 友好,纯 node)。
3
+ //
4
+ // Claude Code 在会话结束/压缩前触发,stdin 传入 JSON:
5
+ // { session_id, transcript_path, hook_event_name, cwd, ... }
6
+ // 本脚本解析 transcript JSONL,提取最近若干轮 user/assistant 文本,蒸馏入库。
7
+ // 由 lib/setup.js 自动注册到 ~/.claude/settings.json 的 hooks.SessionEnd。
8
+ //
9
+ // 特性:静默失败(hook 崩溃不能影响 Claude Code);幂等(重复跑同一段只追加)。
10
+
11
+ import { promises as fs } from 'node:fs'
12
+ import path from 'node:path'
13
+
14
+ async function main() {
15
+ let input = {}
16
+ try {
17
+ const raw = await new Promise((resolve) => {
18
+ let buf = ''
19
+ process.stdin.setEncoding('utf8')
20
+ process.stdin.on('data', (c) => { buf += c })
21
+ process.stdin.on('end', () => resolve(buf))
22
+ setTimeout(() => resolve(buf), 2000)
23
+ })
24
+ input = JSON.parse(raw || '{}')
25
+ } catch { /* 静默 */ }
26
+
27
+ const transcriptPath = input.transcript_path
28
+ if (!transcriptPath) return
29
+ let lines
30
+ try { lines = (await fs.readFile(transcriptPath, 'utf8')).split('\n').filter(Boolean) } catch { return }
31
+
32
+ // 提取尾部对话文本(user 与 assistant 的 message.content 文本部分)
33
+ const texts = []
34
+ for (const line of lines.slice(-400)) {
35
+ let rec
36
+ try { rec = JSON.parse(line) } catch { continue }
37
+ const msg = rec?.message
38
+ if (!msg || !msg.role) continue
39
+ const content = msg.content
40
+ let text = ''
41
+ if (typeof content === 'string') text = content
42
+ else if (Array.isArray(content)) {
43
+ text = content.filter((c) => c?.type === 'text').map((c) => c.text).join('\n')
44
+ }
45
+ if (text && text.trim().length > 4) texts.push(`${msg.role}: ${text.trim()}`)
46
+ }
47
+ const conversation = texts.slice(-30).join('\n\n')
48
+ if (conversation.trim().length < 120) return
49
+
50
+ const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
51
+ await runStandaloneCapture(defaultVaultDir(), conversation, {
52
+ source: `claude-code:${input.session_id || path.basename(transcriptPath)}`,
53
+ })
54
+ }
55
+
56
+ main().catch(() => { /* hook 静默失败 */ })