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 +217 -1
- package/bin/dsh-memory.mjs +148 -0
- package/hooks/claude-session.js +56 -0
- package/index.js +35 -169
- package/lib/api.js +187 -0
- package/lib/capture-run.js +69 -0
- package/lib/capture.js +23 -2
- package/lib/client.js +10 -7
- package/lib/llm-openai.js +84 -0
- package/lib/mcp.js +162 -0
- package/lib/setup.js +202 -0
- package/lib/sweep.js +56 -0
- package/lib/vault.js +8 -2
- package/lib/web.js +145 -0
- package/package.json +15 -5
- package/web/app.js +223 -0
- package/web/index.html +46 -0
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 静默失败 */ })
|