mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
feat: 完成 TraceMemo v2.2.0 品牌升级并保留旧数据兼容
- 将用户可见品牌升级为 TraceMemo(迹忆) - 增加最早期 userData/sessionData 兼容路径选择 - 保留 WechatExplorer runtime identity 以兼容 safeStorage - 继续使用旧 Knowledge、Settings、API Token 和 Provider 配置 - 新日志写入 TraceMemo 目录并保留历史日志 - 保留旧 API、Skill、环境变量和导出目录兼容标识 - 更新相关文档、界面文案与自动化测试
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# WechatExplorer
|
||||
# TraceMemo(迹忆)
|
||||
|
||||
<p align="center">
|
||||
<img src="./build/icon.png" width="120" alt="WechatExplorer Logo" />
|
||||
<img src="./build/icon.png" width="120" alt="TraceMemo Logo" />
|
||||
</p>
|
||||
|
||||
<h2 align="center">把微信聊过的事,找回来、问清楚、留下来</h2>
|
||||
@@ -18,7 +18,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Wxw-Gu/WechatExplorer/releases"><b>下载 WechatExplorer</b></a>
|
||||
<a href="https://github.com/Wxw-Gu/WechatExplorer/releases"><b>下载 TraceMemo</b></a>
|
||||
·
|
||||
<a href="./docs/user-guide/getting-started.md"><b>第一次使用</b></a>
|
||||
·
|
||||
@@ -26,16 +26,30 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./public/software-1.png" alt="WechatExplorer 主界面" />
|
||||
<img src="./public/software-1.png" alt="TraceMemo 主界面" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./public/机器人.png" alt="WechatExplorer 主界面" />
|
||||
<img src="./public/机器人.png" alt="TraceMemo 主界面" />
|
||||
</p>
|
||||
|
||||
## WechatExplorer 是什么
|
||||
## TraceMemo(迹忆)是什么
|
||||
|
||||
WechatExplorer 是一个本地优先的微信聊天记录搜索与 AI 工作台。
|
||||
TraceMemo(迹忆)是一款本地优先、可追溯的 AI 微信知识与分析工作台。
|
||||
|
||||
TraceMemo(迹忆)原名 WechatExplorer,是一次从“微信聊天记录探索工具”向“可追溯的本地 AI 知识工作台”演进后的正式品牌升级。
|
||||
|
||||
## 为什么叫 TraceMemo(迹忆)
|
||||
|
||||
`Trace` 代表聊天记录留下的痕迹、可以追溯的信息来源、AI 搜索过程,以及从结果回到原始聊天上下文并核对证据的能力。
|
||||
|
||||
`Memo` 代表记忆、知识沉淀和长期保存:让聊天中产生的信息逐渐形成个人知识。
|
||||
|
||||
“迹忆”可以理解为“留下痕迹的记忆”。TraceMemo 不是单纯查看微信聊天记录的工具,而是希望让聊天中产生的信息留下痕迹,并能够被再次找到、理解、验证和沉淀。
|
||||
|
||||
> **品牌说明**
|
||||
>
|
||||
> TraceMemo(迹忆)原名 WechatExplorer。WechatExplorer 最初是一个用于查看和探索微信聊天记录的工具。随着本地搜索、AI 问答、来源追溯、知识库、日报、语音转写和 Agent 能力逐渐形成,项目已经从单纯的聊天记录查看器发展为本地 AI 知识与分析工作台,因此在 v2.2.0 正式更名为 TraceMemo(迹忆)。
|
||||
|
||||
它可以帮你浏览、搜索和整理微信历史,也可以让 AI 帮你找回聊过的内容,并回到原始消息核对答案。
|
||||
|
||||
@@ -50,7 +64,7 @@ WechatExplorer 是一个本地优先的微信聊天记录搜索与 AI 工作台
|
||||
## 💬 交流与反馈
|
||||
|
||||
<p align="center">
|
||||
<img src="./public/二维码.jpg" alt="WechatExplorer 交流与售后群二维码" width="280" />
|
||||
<img src="./public/二维码.jpg" alt="TraceMemo 交流与售后群二维码" width="280" />
|
||||
</p>
|
||||
|
||||
|
||||
@@ -65,7 +79,7 @@ WechatExplorer 是一个本地优先的微信聊天记录搜索与 AI 工作台
|
||||
| 把微信语音变成可搜索的文字 | [设置 → 语音转文字](./docs/user-guide/voice.md) | 准备本地语音模型 |
|
||||
| 把聊天保存成 HTML、Markdown、CSV 或 JSON | [导出](./docs/user-guide/export.md) | 选择聊天、时间和格式,不需要 AI |
|
||||
| 尽量保留之后捕获到的撤回消息 | [设置 → 防撤回](./docs/user-guide/recall-protection.md) | 默认关闭;开启前先了解写入和性能边界 |
|
||||
| 直接在微信里向 WechatExplorer 提问 | [微信机器人](./docs/agent/agent-hub.md) | 扫码连接机器人;总结类任务需要 AI |
|
||||
| 直接在微信里向 TraceMemo 提问 | [微信机器人](./docs/agent/agent-hub.md) | 扫码连接机器人;总结类任务需要 AI |
|
||||
| 让 Codex 等外部 Agent 查询微信历史 | [外部 Agent](./docs/agent/overview.md) | 安装 Reader Skill 并配置本机 Token |
|
||||
|
||||
## 最核心的三个能力
|
||||
@@ -83,7 +97,7 @@ WechatExplorer 是一个本地优先的微信聊天记录搜索与 AI 工作台
|
||||
|
||||
打开“问问微信”,选择搜索范围和时间,然后像提问一样描述你想找的内容。
|
||||
|
||||
WechatExplorer 会先在本机查找候选消息,再把整理后的少量来源交给你配置的 AI 模型生成回答。你可以查看答案参考了哪些聊天、来自哪个人和时间,并从来源标记跳回原始消息核对;“查看检索详情”还会展示本次查找经历了哪些阶段。
|
||||
TraceMemo 会先在本机查找候选消息,再把整理后的少量来源交给你配置的 AI 模型生成回答。你可以查看答案参考了哪些聊天、来自哪个人和时间,并从来源标记跳回原始消息核对;“查看检索详情”还会展示本次查找经历了哪些阶段。
|
||||
|
||||
<p align="center">
|
||||
<img src="./public/问一问.png" alt="问问微信与聊天来源" />
|
||||
@@ -93,11 +107,11 @@ WechatExplorer 会先在本机查找候选消息,再把整理后的少量来
|
||||
|
||||
### 直接在微信里问你的历史聊天
|
||||
|
||||
打开应用中的“Agent”入口(页面标题为“Agent Hub”,对应微信机器人功能),扫码连接一个微信机器人账号。例如,你可以直接给机器人发送“最近 5 个会话”“张三最近和我聊了什么”,或者让它生成指定群聊的总结图片。WechatExplorer 会在本机读取已连接的聊天数据并把结果回复到微信。
|
||||
打开应用中的“Agent”入口(页面标题为“Agent Hub”,对应微信机器人功能),扫码连接一个微信机器人账号。例如,你可以直接给机器人发送“最近 5 个会话”“张三最近和我聊了什么”,或者让它生成指定群聊的总结图片。TraceMemo 会在本机读取已连接的聊天数据并把结果回复到微信。
|
||||
|
||||
这个入口不要求另外安装 Codex、Claude Code 等外部 Agent。当前主要处理文字消息,不支持群发、定时任务或通用自主操作微信;总结和自然语言理解需要先配置 AI 服务。
|
||||
|
||||
详细步骤和能力边界见[在微信里向 WechatExplorer 提问](./docs/agent/agent-hub.md)。
|
||||
详细步骤和能力边界见[在微信里向 TraceMemo 提问](./docs/agent/agent-hub.md)。
|
||||
|
||||
## 其他能力
|
||||
|
||||
@@ -126,13 +140,13 @@ WechatExplorer 会先在本机查找候选消息,再把整理后的少量来
|
||||
|
||||
### 转写微信语音
|
||||
|
||||
WechatExplorer 支持在本机转写单条或批量微信语音,结果可以参与本地知识库检索和 HTML 导出。转写本身不要求把语音文件发送给在线 AI;随后用于 AI 问答或日报时,文字会按对应功能的规则处理。
|
||||
TraceMemo 支持在本机转写单条或批量微信语音,结果可以参与本地知识库检索和 HTML 导出。转写本身不要求把语音文件发送给在线 AI;随后用于 AI 问答或日报时,文字会按对应功能的规则处理。
|
||||
|
||||
详细说明:[语音转文字](./docs/user-guide/voice.md)
|
||||
|
||||
### 防撤回
|
||||
|
||||
可选开启后,WechatExplorer 会尽量保留开启期间捕获到的撤回消息。该能力受微信版本和应用运行状态影响,不保证找回所有内容,也不能恢复开启前已经撤回的消息。
|
||||
可选开启后,TraceMemo 会尽量保留开启期间捕获到的撤回消息。该能力受微信版本和应用运行状态影响,不保证找回所有内容,也不能恢复开启前已经撤回的消息。
|
||||
|
||||
详细说明:[防撤回](./docs/user-guide/recall-protection.md)
|
||||
|
||||
@@ -152,7 +166,7 @@ WechatExplorer 支持在本机转写单条或批量微信语音,结果可以
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[本机微信数据] --> B[WechatExplorer 读取与解析]
|
||||
A[本机微信数据] --> B[TraceMemo 读取与解析]
|
||||
B --> C[聊天档案]
|
||||
B --> D[本地知识库与搜索]
|
||||
D --> E[筛选相关聊天来源]
|
||||
@@ -187,7 +201,7 @@ flowchart LR
|
||||
## 快速开始
|
||||
|
||||
1. 从 [GitHub Releases](https://github.com/Wxw-Gu/WechatExplorer/releases) 下载安装包。
|
||||
2. 启动 WechatExplorer,按照“第一次使用”页面选择微信数据目录。
|
||||
2. 启动 TraceMemo,按照“第一次使用”页面选择微信数据目录。
|
||||
3. 第一次使用请先点击“开始连接”,按页面提示准备连接组件并获取数据库密钥;只有已经有密钥的高级用户才需要“手动连接”。
|
||||
4. 连接成功后打开“档案”,确认联系人和聊天消息已经出现。
|
||||
5. 先在“档案”里搜索一句你记得的原话;这一步不需要 AI。
|
||||
@@ -198,7 +212,7 @@ Windows 安装后无法启动时,请先安装 [Microsoft Visual C++ x64 运行
|
||||
|
||||
如果 macOS 页面提示处理 SIP,请先阅读对应说明。具体步骤和限制见[第一次使用](./docs/user-guide/getting-started.md)。
|
||||
|
||||
完整步骤:[第一次使用 WechatExplorer](./docs/user-guide/getting-started.md)
|
||||
完整步骤:[第一次使用 TraceMemo](./docs/user-guide/getting-started.md)
|
||||
|
||||
## 配置 AI
|
||||
|
||||
@@ -248,20 +262,20 @@ pnpm test:e2e:build
|
||||
|
||||
## 支持与反馈
|
||||
|
||||
遇到问题时,先查看[常见问题与排查](./docs/user-guide/troubleshooting.md)。提交 Issue 时请提供操作系统、微信版本、WechatExplorer 版本、复现步骤和已遮挡敏感信息的截图。
|
||||
遇到问题时,先查看[常见问题与排查](./docs/user-guide/troubleshooting.md)。提交 Issue 时请提供操作系统、微信版本、TraceMemo 版本、复现步骤和已遮挡敏感信息的截图。
|
||||
|
||||
请仅处理你有权访问的数据,并遵守适用的法律法规、组织政策和微信使用规则。数据库读取、解密、自动化和机器人能力都可能受平台版本与账号环境影响。
|
||||
|
||||
## 许可说明
|
||||
|
||||
仓库中的第三方组件、模型和连接器遵循各自的许可证。当前仓库根目录未提供独立的项目 `LICENSE` 文件;贡献、复制或再分发前,请先向维护者确认 WechatExplorer 本身的许可范围。
|
||||
仓库中的第三方组件、模型和连接器遵循各自的许可证。当前仓库根目录未提供独立的项目 `LICENSE` 文件;贡献、复制或再分发前,请先向维护者确认 TraceMemo 本身的许可范围。
|
||||
|
||||
## 致谢
|
||||
|
||||
<details>
|
||||
<summary>展开致谢与参考项目</summary>
|
||||
|
||||
WechatExplorer 在开发过程中参考了多个优秀的开源项目,感谢这些项目作者的工作与分享。
|
||||
TraceMemo 在开发过程中参考了多个优秀的开源项目,感谢这些项目作者的工作与分享。
|
||||
|
||||
特别感谢:
|
||||
|
||||
@@ -272,7 +286,7 @@ WechatExplorer 在开发过程中参考了多个优秀的开源项目,感谢
|
||||
- **[chatlog](https://github.com/sjzar/chatlog)**
|
||||
- 提供了聊天记录导出与数据处理方面的参考。
|
||||
|
||||
在此基础上,WechatExplorer 进行了重新设计与实现,包括:
|
||||
在此基础上,TraceMemo 进行了重新设计与实现,包括:
|
||||
|
||||
- AI 问问微信
|
||||
- AI 群聊日报
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
# WechatExplorer 产品经理交接
|
||||
|
||||
> 面向下一任产品经理的产品事实、边界和文档维护说明。
|
||||
>
|
||||
> - 最后核验:2026-08-07
|
||||
> - 仓库版本:`2.1.9`
|
||||
> - 基准提交:`cd2c3cf`(`docs: 更新文档`)
|
||||
|
||||
## 1. 接手时先记住什么
|
||||
|
||||
WechatExplorer 当前可以定位为:
|
||||
|
||||
> **一个本地优先的微信聊天记录查看、搜索、整理与 AI 分析工具。**
|
||||
|
||||
它把几类原本分散的任务放在一起:
|
||||
|
||||
- 浏览和搜索本机微信聊天;
|
||||
- 用 AI 查找历史信息,并回到来源消息核对;
|
||||
- 建立本地知识库,辅助跨会话、跨时间查找;
|
||||
- 生成群聊日报,转写语音,导出聊天档案;
|
||||
- 让外部 Agent 或应用内微信机器人按边界使用本机数据能力。
|
||||
|
||||
产品表达应先回答用户能完成什么,再解释 Knowledge、Evidence、Citation、Search Trace、FTS 等内部术语。README 负责定位、主要价值和最短上手路径;完整步骤、限制和安全说明放在 `docs/`。
|
||||
|
||||
### 事实来源优先级
|
||||
|
||||
描述“当前支持”前,按以下顺序核验:
|
||||
|
||||
1. 当前源码和 UI;
|
||||
2. 当前测试;
|
||||
3. `package.json`、构建和发布配置;
|
||||
4. 正式 `docs/`;
|
||||
5. README;
|
||||
6. 历史说明和产品设想。
|
||||
|
||||
历史文档、旧版本描述和聊天记录不能单独证明当前能力。新增事实陈述时,最好同时写清限制并链接到正式文档或实现位置。
|
||||
|
||||
## 2. 当前已验证的产品能力
|
||||
|
||||
本节是能力地图,不替代正式使用手册。用户步骤和异常处理以链接的正式文档为准。
|
||||
|
||||
### 2.1 连接与查看微信数据
|
||||
|
||||
当前代码面向微信 4.x 数据结构,支持在 macOS 和 Windows 上连接本机微信数据。连接结果会受到微信版本、账号数据、系统权限和数据迁移状态影响。
|
||||
|
||||
连接成功后,“档案”可以浏览已读取到的联系人、群聊、折叠群聊和公众号会话,并显示文本、图片、视频、语音、文件、链接、引用、小程序、表情和系统消息等类型。
|
||||
|
||||
边界:消息类型可被读取,不等于对应媒体一定可以解码或显示。来源文件缺失、权限不足或微信存储方式变化都可能导致媒体不可用。
|
||||
|
||||
来源:[第一次使用](./docs/user-guide/getting-started.md)、[查看和搜索聊天](./docs/user-guide/chat-archive.md)
|
||||
|
||||
### 2.2 普通搜索与 AI Search
|
||||
|
||||
档案内关键词搜索适合已知原话、文件名、人名或大致会话的任务。AI Search 适合“记得含义但不记得关键词或位置”的问题。
|
||||
|
||||
AI Search 会先在本机查找候选聊天,再把完成任务所需的受控上下文交给用户配置的 AI Provider。回答可以展示来源会话、发送者、时间、来源标记和检索过程,并允许用户回到档案检查上下文。
|
||||
|
||||
边界:Evidence、Citation 和 Search Trace 提供核对路径,不保证模型结论正确,也不保证选定范围之外没有遗漏。关键决定仍应回到原始消息确认。
|
||||
|
||||
来源:[AI Search](./docs/user-guide/ai-search.md)、[如何核对 AI 回答来源](./docs/concepts/answer-sources.md)
|
||||
|
||||
### 2.3 本地知识库
|
||||
|
||||
用户可以主动为当前微信账号建立本地索引,并在之后同步新增或变化的记录。不同微信账号使用独立索引;界面会显示索引规模和磁盘占用。
|
||||
|
||||
清理知识库不会删除微信原始数据库。知识库同步期间新的 AI 分析会暂停,但已有索引在部分异常情况下仍可能可用。
|
||||
|
||||
边界:知识库是本地检索资料,不是新的微信数据库,也不是所有问题都必须先建立。只查一条已知原话时,普通搜索通常更直接。
|
||||
|
||||
来源:[本地知识库](./docs/user-guide/knowledge.md)、`src/main/knowledge/`、`tests/unit/knowledge-*.test.ts`
|
||||
|
||||
### 2.4 群聊日报
|
||||
|
||||
用户可以选择一个群聊以及今天、昨天或近 7 天的范围,生成可能包含主题、重要消息、问答、资源、待办、未解决事项、关键词、活跃统计和可用媒体精选的报告。成功结果会保存为本地 HTML 和 PNG。
|
||||
|
||||
边界:当前日报入口只支持群聊。具体栏目取决于所选消息、媒体可用性和模型能力;图片不可读或模型未通过图片理解验证时,图片精选会跳过。语音数量统计不代表语音内容已经转写或理解。
|
||||
|
||||
来源:[群聊日报](./docs/user-guide/report.md)、`src/renderer/src/utils/group-report-facts.ts`
|
||||
|
||||
### 2.5 本地语音转写
|
||||
|
||||
应用使用本地 SenseVoice/sherpa-onnx 运行时转写微信语音,支持单条转写和按联系人或群聊批量处理。批量任务支持进度、取消、缓存复用和部分失败提示。
|
||||
|
||||
成功转写的文本可以用于查看、本地知识库检索和导出。离线转写本身在本机完成;如果用户随后把转写文本用于 AI Search 或日报,文本会按对应 AI 功能的规则处理。
|
||||
|
||||
边界:首次使用可能需要下载模型。失败或尚未转写的语音不会被自动当作已理解内容。
|
||||
|
||||
来源:[语音转文字](./docs/user-guide/voice.md)、`src/main/voice-pipeline/`、`tests/unit/voice-*.test.ts`
|
||||
|
||||
### 2.6 聊天导出
|
||||
|
||||
当前支持 HTML、Markdown、CSV 和 JSON:
|
||||
|
||||
| 能力 | 当前边界 |
|
||||
| ------------------- | -------------------------------------------------- |
|
||||
| HTML | 可包含媒体和头像;支持最多五个会话合并 |
|
||||
| Markdown、CSV、JSON | 主要保留文本内容,不包含 HTML 资源文件 |
|
||||
| ZIP | 是 HTML 资源包的压缩选项,不是独立内容格式 |
|
||||
| 增量合并 | 仅适用于同名 HTML 档案 |
|
||||
| 语音转写 | 只写入已经成功取得的转写文本,不会自动补齐失败内容 |
|
||||
|
||||
导出不会修改微信原始数据库;删除导出文件也不会删除应用中的聊天或知识库。
|
||||
|
||||
来源:[导出聊天](./docs/user-guide/export.md)、`src/main/export-service.ts`、`src/renderer/src/components/export/ExportWorkspace.tsx`、`tests/integration/export-media-flow.test.ts`
|
||||
|
||||
### 2.7 外部 Agent 与 Local HTTP API
|
||||
|
||||
外部 Agent 可以安装随应用提供的 Reader Skill,通过本机 Local HTTP API 按需读取联系人、会话、指定时间范围的聊天和群成员信息,也可以请求生成群聊总结图片。
|
||||
|
||||
当前 API 默认地址为 `http://127.0.0.1:6131`:
|
||||
|
||||
- `GET /api/v1/health` 不需要 Token;
|
||||
- 其他端点需要 `Authorization: Bearer <TOKEN>`;
|
||||
- Token 在 API Center 中显示、复制和重新生成;
|
||||
- 重新生成后旧 Token 立即失效;
|
||||
- API 没有细粒度用户 Scope,不应转发到公网。
|
||||
|
||||
`6131` 是普通 Local HTTP API,当前不是 MCP Server。Reader Skill 不会自动监听微信实时消息。
|
||||
|
||||
来源:[Agent 接入概览](./docs/agent/overview.md)、[Local HTTP API](./docs/agent/api.md)、[API 安全](./docs/agent/api-security.md)、[Reader Skill](./docs/agent/reader-skill.md)
|
||||
|
||||
### 2.8 Agent Hub 与微信机器人
|
||||
|
||||
Agent Hub 是应用内的实时微信入口。用户扫码连接一个微信机器人账号后,机器人收到文字消息,Agent Hub 可以查询本机数据、按需调用已配置的 AI,并把结果回复给触发请求的微信用户。
|
||||
|
||||
当前明确支持的实时任务包括:
|
||||
|
||||
- 查看最近会话,数量限制为 1 至 20;
|
||||
- 查询与某位联系人的近期聊天;
|
||||
- 总结与某位联系人近 7 天的聊天;
|
||||
- 生成今天、昨天或近 7 天的群聊总结图片;
|
||||
- 总结指定群成员的近期发言;
|
||||
- 对不需要读取聊天的普通文字请求给出有限的 AI 回复。
|
||||
|
||||
边界:实时自然语言入口主要处理文字。底层连接器可以接收其他媒体,但 Agent Hub 尚未提供同等的图片、语音、文件和视频意图处理。它也没有群发、广播、定时任务或通用自主操作微信的能力。
|
||||
|
||||
来源:[Agent Hub](./docs/agent/agent-hub.md)、`src/main/agent/`、Agent Hub 相关测试
|
||||
|
||||
## 3. 隐私与安全边界
|
||||
|
||||
“本地优先”不能表达成“所有数据永远不会离开电脑”。
|
||||
|
||||
默认在本机完成的处理包括:微信数据库读取和解析、档案浏览、普通关键词搜索、本地知识库索引、离线语音转写、导出文件生成和本地日报历史。
|
||||
|
||||
当用户主动使用 AI Search、群聊日报或图片理解,并配置远程 Provider 时,用户问题、受控检索上下文和最终用于总结的来源内容可能发送给该 Provider。Provider 的日志、保留、计费和地区规则不由 WechatExplorer 控制。
|
||||
|
||||
外部 Agent 是否把 API 读取结果继续发送给云端模型,取决于 Agent 自己的配置。Agent Hub 的机器人账号、个人微信数据库连接和外部 Agent/API Token 是三条不同的安全边界。
|
||||
|
||||
来源:[数据、隐私与安全](./docs/user-guide/privacy.md)
|
||||
|
||||
## 4. 尚未实现或不能宣称的能力
|
||||
|
||||
以下内容不是当前能力。未来讨论这些方向时,必须明确写成“未来场景 / 尚未实现”,且不能据此承诺路线图或发布时间:
|
||||
|
||||
- **未来场景 / 尚未实现:**按固定时间自动生成或发送每日群聊总结;
|
||||
- **未来场景 / 尚未实现:**群发、广播或通用微信自动化;
|
||||
- **未来场景 / 尚未实现:**Agent Hub 对图片、语音、文件和视频提供与文字相同的实时理解能力;
|
||||
- **未来场景 / 尚未实现:**将 `127.0.0.1:6131` 作为 MCP Server 使用;
|
||||
- **未来场景 / 尚未实现:**对外提供实时入站 webhook 或由 Reader Skill 订阅实时微信消息;
|
||||
- **不能宣称:**所有微信 4.x 版本、所有账号和所有系统组合都能稳定连接;
|
||||
- **不能宣称:**所有图片、视频、文件或语音都一定能读取、解码或理解;
|
||||
- **不能宣称:**AI 回答或日报一定完整、准确,或者 Evidence 本身能保证结论正确;
|
||||
- **不能宣称:**启用远程 AI 后所有数据仍只停留在本机。
|
||||
|
||||
## 5. 仅凭当前仓库仍无法确认的事项
|
||||
|
||||
以下问题需要真实发布环境、用户研究或外部平台信息,不能仅凭当前源码和测试得出结论:
|
||||
|
||||
1. GitHub Releases 中各平台安装包当前是否齐全、可下载,以及在不同系统安全策略下的实际安装成功率;
|
||||
2. 不同微信 4.x 小版本、历史迁移状态和真实账号规模下的连接成功率与兼容矩阵;
|
||||
3. 超大聊天历史下,索引、AI Search、日报、语音批处理和导出的真实耗时、容量上限与失败率;
|
||||
4. 微信机器人账号在长期运行中的登录稳定性、平台规则风险和账号限制;
|
||||
5. 用户是否真正理解并使用“来源核对”、Knowledge、Reader Skill 和 Agent Hub,以及这些功能是否改善了实际任务结果。
|
||||
|
||||
这些事项在得到真实证据前,应写成“待验证”,不能转写成产品优势。
|
||||
|
||||
## 6. 文档职责和维护方法
|
||||
|
||||
### README
|
||||
|
||||
README 只负责:
|
||||
|
||||
- 一句话说明产品是什么;
|
||||
- 展示最重要的用户任务和差异;
|
||||
- 给出最短上手路径;
|
||||
- 引导到正式 docs。
|
||||
|
||||
不要把完整 API、安全实现、数据库结构、检索原理或所有边缘情况塞进 README。
|
||||
|
||||
### 正式 docs
|
||||
|
||||
- `docs/user-guide/`:第一次使用、档案、AI Search、Knowledge、日报、语音、导出、隐私和排障;
|
||||
- `docs/concepts/`:来源核对和工作原理;
|
||||
- `docs/agent/`:Reader Skill、Local HTTP API、安全和 Agent Hub;
|
||||
- `docs/platform/`:平台权限和限制;
|
||||
- `docs/development/`:开发、测试、构建及代码与文档的对应关系。
|
||||
|
||||
文档入口见:[docs 首页](./docs/README.md)。
|
||||
|
||||
### 每次产品变更后的检查
|
||||
|
||||
1. 用户是否能直接感知变化?如果能,检查 README 和对应 User Guide;
|
||||
2. 第一次使用路径是否变化?如果变化,检查 Getting Started;
|
||||
3. AI 的输入、来源或完整性提示是否变化?如果变化,检查 AI Search、Answer Sources 和 Privacy;
|
||||
4. API、Token、Reader Skill 或 Agent Hub 是否变化?如果变化,同步检查全部 Agent 文档;
|
||||
5. 兼容性、构建或发布范围是否变化?如果变化,检查平台和开发文档;
|
||||
6. 文案是否把“可能”“计划”“测试样例”误写成“当前支持”?
|
||||
|
||||
推荐的能力陈述格式是:
|
||||
|
||||
> 用户可以完成什么 + 当前限制是什么 + 事实来源在哪里。
|
||||
|
||||
## 7. 下一任产品经理最应该关注的 5 个产品问题
|
||||
|
||||
### 1. 新用户能否在几分钟内完成第一次连接
|
||||
|
||||
连接微信数据是所有能力的前置条件。需要建立真实平台和微信版本的成功率、失败原因和耗时数据,而不只依赖开发环境与测试样例。
|
||||
|
||||
### 2. 用户能否理解 AI 回答的可信边界
|
||||
|
||||
“可回到来源核对”是重要差异,但目前仍需要验证用户是否会打开来源、是否看得懂覆盖提示,以及这些信息能否减少错误决策。
|
||||
|
||||
### 3. Knowledge 是否解决了用户可感知的问题
|
||||
|
||||
需要验证建立和同步索引的成本、等待时间与搜索收益是否匹配,并明确哪些任务适合普通搜索、哪些任务真正需要 Knowledge。
|
||||
|
||||
### 4. Reader Skill 和 Agent Hub 的定位是否足够清楚且安全
|
||||
|
||||
两条路径服务不同用户,也有不同的 Token、机器人账号和数据外发边界。需要验证入口命名、配置流程、权限提示和失败恢复是否让用户理解。
|
||||
|
||||
### 5. 兼容性和分发是否足以支撑产品承诺
|
||||
|
||||
需要维护真实的系统、处理器、微信版本和账号数据兼容矩阵,同时确认安装包、系统授权、连接器和升级路径在发布环境中可用。
|
||||
|
||||
## 8. 接手原则
|
||||
|
||||
- 先核实能力,再决定怎么宣传;
|
||||
- 用用户任务描述价值,用正式 docs 承担细节;
|
||||
- 明确区分当前能力、限制、待验证事项和未来场景;
|
||||
- 不因文案完整性补充不存在的能力;
|
||||
- 不把测试通过等同于真实用户环境已经得到验证。
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
# WechatExplorer 文档
|
||||
# TraceMemo 文档
|
||||
|
||||
WechatExplorer 的文档按“你想完成什么”组织,而不是按源码模块组织。
|
||||
TraceMemo 的文档按“你想完成什么”组织,而不是按源码模块组织。
|
||||
|
||||
## 从这里开始
|
||||
|
||||
@@ -15,7 +15,7 @@ WechatExplorer 的文档按“你想完成什么”组织,而不是按源码
|
||||
- [语音转文字](./user-guide/voice.md)
|
||||
- [导出聊天档案](./user-guide/export.md)
|
||||
- [防撤回](./user-guide/recall-protection.md)
|
||||
- [在微信里向 WechatExplorer 提问](./agent/agent-hub.md)
|
||||
- [在微信里向 TraceMemo 提问](./agent/agent-hub.md)
|
||||
- [数据、隐私与安全](./user-guide/privacy.md)
|
||||
- [常见问题与排查](./user-guide/troubleshooting.md)
|
||||
|
||||
@@ -26,7 +26,7 @@ WechatExplorer 的文档按“你想完成什么”组织,而不是按源码
|
||||
|
||||
## 微信机器人和外部 Agent
|
||||
|
||||
WechatExplorer 有两种不同的接入方式。微信机器人是普通用户可以直接使用的产品能力;Reader Skill 和 Local HTTP API 面向已经在使用 Codex、Claude Code、OpenClaw 等外部 Agent 的用户。
|
||||
TraceMemo 有两种不同的接入方式。微信机器人是普通用户可以直接使用的产品能力;Reader Skill 和 Local HTTP API 面向已经在使用 Codex、Claude Code、OpenClaw 等外部 Agent 的用户。
|
||||
|
||||
| 你想做什么 | 应该看哪里 |
|
||||
| --------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
@@ -57,4 +57,4 @@ WechatExplorer 有两种不同的接入方式。微信机器人是普通用户
|
||||
- [开发、测试与构建](./development/overview.md)
|
||||
- [v2.1.9 API 鉴权迁移说明](./agent/release-notes-v2.1.9.md)
|
||||
|
||||
当前工作区版本:**2.1.9**。文档只描述当前代码已经实现的能力;版本兼容性、AI Provider 行为和媒体读取结果可能随系统、微信客户端和服务商变化。
|
||||
当前工作区版本:**2.2.0**。文档只描述当前代码已经实现的能力;版本兼容性、AI Provider 行为和媒体读取结果可能随系统、微信客户端和服务商变化。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 在微信里向 WechatExplorer 提问(Agent Hub)
|
||||
# 在微信里向 TraceMemo 提问(Agent Hub)
|
||||
|
||||
Agent Hub 是 WechatExplorer 内置的微信机器人入口,也是应用一级导航中的“Agent”页面。你先扫码登录一个微信机器人账号,再用微信账号向机器人发送文字;本机 Agent Hub 会接收消息、读取已经连接的微信数据,必要时调用已配置的 AI,再把结果回复给发送者。
|
||||
Agent Hub 是 TraceMemo 内置的微信机器人入口,也是应用一级导航中的“Agent”页面。你先扫码登录一个微信机器人账号,再用微信账号向机器人发送文字;本机 Agent Hub 会接收消息、读取已经连接的微信数据,必要时调用已配置的 AI,再把结果回复给发送者。
|
||||
|
||||
普通用户不需要安装 Reader Skill,也不需要配置 API Token。先连接微信数据库,再扫码登录机器人即可开始;需要总结或自然语言理解的任务还要配置 AI Provider。
|
||||
|
||||
@@ -11,7 +11,7 @@ Agent Hub 是 WechatExplorer 内置的微信机器人入口,也是应用一级
|
||||
|
||||
## 连接器和 Agent Hub 是什么关系
|
||||
|
||||
你不需要单独部署这些组件。扫码后,后台的微信连接器负责登录机器人、保持连接、接收微信消息和发送回复;Agent Hub 负责判断消息要做什么、查询 WechatExplorer 本地数据、调用 AI 并组织结果。可以把它理解为:连接器负责“和微信通信”,Hub 负责“处理任务”。
|
||||
你不需要单独部署这些组件。扫码后,后台的微信连接器负责登录机器人、保持连接、接收微信消息和发送回复;Agent Hub 负责判断消息要做什么、查询 TraceMemo 本地数据、调用 AI 并组织结果。可以把它理解为:连接器负责“和微信通信”,Hub 负责“处理任务”。
|
||||
|
||||
## 你能做什么
|
||||
|
||||
@@ -50,9 +50,9 @@ Agent Hub 页面会记录系统、Agent Hub 和微信连接器日志。日志支
|
||||
|
||||
## 需要满足的条件
|
||||
|
||||
- WechatExplorer 的微信数据库已经连接,并且数据 API 可以查询;
|
||||
- TraceMemo 的微信数据库已经连接,并且数据 API 可以查询;
|
||||
- 依赖总结或自然语言理解的任务,需要在“设置 → AI 模型”配置可用的 AI 服务;
|
||||
- WechatExplorer 和 Agent Hub 需要保持运行,机器人才能接收和回复消息。
|
||||
- TraceMemo 和 Agent Hub 需要保持运行,机器人才能接收和回复消息。
|
||||
|
||||
## 安全与边界
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
## 当前安全边界
|
||||
|
||||
WechatExplorer 的本地 API 默认监听 `127.0.0.1:6131`。它面向同一台电脑上的 API Center、Reader Skill、CLI 和 Agent,不是公网网关,也不是带用户账户和细粒度权限 Scope 的服务。
|
||||
TraceMemo 的本地 API 默认监听 `127.0.0.1:6131`。它面向同一台电脑上的 API Center、Reader Skill、CLI 和 Agent,不是公网网关,也不是带用户账户和细粒度权限 Scope 的服务。
|
||||
|
||||
## Bearer Token
|
||||
|
||||
文档中的 `WECHATEXPLORER_API_TOKEN` 是历史兼容环境变量名;TraceMemo v2.2.0 继续支持这一名称,以免已安装的 Reader Skill 和 Agent 配置失效。
|
||||
|
||||
- `/api/v1/health` 是公开健康检查;
|
||||
- 其他所有端点都要求 `Authorization: Bearer <TOKEN>`;
|
||||
- Token 由应用生成,使用 32 个随机字节编码;
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
# WechatExplorer Local HTTP API
|
||||
# TraceMemo Local HTTP API
|
||||
|
||||
本文面向需要自己写集成的开发者。普通用户请先阅读[Agent 接入概览](./overview.md)。
|
||||
|
||||
@@ -24,6 +24,8 @@ curl -H "Authorization: Bearer $WECHATEXPLORER_API_TOKEN" \
|
||||
|
||||
不要把 Token 放入 URL、Skill 文件、仓库或命令历史可被共享的脚本中。
|
||||
|
||||
`WECHATEXPLORER_API_TOKEN` 是历史兼容环境变量名,TraceMemo v2.2.0 继续沿用它以保持 Reader Skill 和外部 Agent 兼容。
|
||||
|
||||
## 端点
|
||||
|
||||
| 方法 | 路径 | 作用 | 参数/请求体 |
|
||||
@@ -57,7 +59,7 @@ curl -H "Authorization: Bearer $WECHATEXPLORER_API_TOKEN" \
|
||||
- `YYYY-MM-DD/HH:mm`:从该分钟开始的 60 秒;
|
||||
- 也可以使用 Unix 秒级 `startTime` 和 `endTime`。
|
||||
|
||||
时间按运行 WechatExplorer 的本机时区解析。用户说“今天”“昨天”时,先调用 `current_time`,再根据返回的 `localDate` 计算日期,避免使用 Agent 自己的时区。
|
||||
时间按运行 TraceMemo 的本机时区解析。用户说“今天”“昨天”时,先调用 `current_time`,再根据返回的 `localDate` 计算日期,避免使用 Agent 自己的时区。
|
||||
|
||||
## 常用工作流
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 在微信机器人或外部 Agent 中使用 WechatExplorer
|
||||
# 在微信机器人或外部 Agent 中使用 TraceMemo
|
||||
|
||||
WechatExplorer 提供两条不同路径。先按你实际想做的事选择,不需要先理解 Agent、Skill 或 API 等术语。
|
||||
TraceMemo 提供两条不同路径。先按你实际想做的事选择,不需要先理解 Agent、Skill 或 API 等术语。
|
||||
|
||||
| 你想做什么 | 使用方式 | 需要什么 |
|
||||
| ---------------------------------------------------- | ----------------------------- | --------------------------------------------------------- |
|
||||
@@ -9,7 +9,7 @@ WechatExplorer 提供两条不同路径。先按你实际想做的事选择,
|
||||
|
||||
## 直接在微信里提问
|
||||
|
||||
打开应用一级导航中的“Agent”,进入“Agent Hub”,扫码登录一个微信机器人账号。之后用另一个微信账号向机器人发送文字,它会调用 WechatExplorer 的本机数据,必要时使用已配置的 AI,再把结果回复给发送者。
|
||||
打开应用一级导航中的“Agent”,进入“Agent Hub”,扫码登录一个微信机器人账号。之后用另一个微信账号向机器人发送文字,它会调用 TraceMemo 的本机数据,必要时使用已配置的 AI,再把结果回复给发送者。
|
||||
|
||||
可以先尝试:
|
||||
|
||||
@@ -23,7 +23,7 @@ WechatExplorer 提供两条不同路径。先按你实际想做的事选择,
|
||||
|
||||
## 在外部 Agent 中查询历史微信
|
||||
|
||||
Reader Skill 是给外部 Agent 的操作说明。安装后,Codex、Claude Code、OpenClaw 或其他本地 Agent 可以通过 WechatExplorer Local HTTP API 按需读取联系人、群聊、最近会话、指定时间范围的聊天和群成员信息。
|
||||
Reader Skill 是给外部 Agent 的操作说明。安装后,Codex、Claude Code、OpenClaw 或其他本地 Agent 可以通过 TraceMemo Local HTTP API 按需读取联系人、群聊、最近会话、指定时间范围的聊天和群成员信息。
|
||||
|
||||
典型问题包括:
|
||||
|
||||
@@ -35,7 +35,7 @@ Reader Skill 是给外部 Agent 的操作说明。安装后,Codex、Claude Cod
|
||||
|
||||
## 外部 Agent 的安装步骤
|
||||
|
||||
1. 启动 WechatExplorer 并完成微信数据库连接。
|
||||
1. 启动 TraceMemo 并完成微信数据库连接。
|
||||
2. 打开一级导航“API”(页面为“API Center”),确认本地 API、数据库和 Reader Skill 都可用。
|
||||
3. 选择目标 Agent,点击“复制安装指令”。
|
||||
4. 在 Agent 自己的 Skill/配置目录执行或粘贴指令。
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
## 先理解它能做什么
|
||||
|
||||
Reader Skill 是一份给 Agent 的操作说明。安装后,Codex、Claude Code、OpenClaw 或其他本地 Agent 可以按需调用 WechatExplorer,读取联系人、群聊、最近会话、指定时间的聊天和群成员信息。
|
||||
Reader Skill 是一份给 Agent 的操作说明。安装后,Codex、Claude Code、OpenClaw 或其他本地 Agent 可以按需调用 TraceMemo,读取联系人、群聊、最近会话、指定时间的聊天和群成员信息。
|
||||
|
||||
它使用的是 WechatExplorer Local HTTP API,不是 MCP Server。
|
||||
它使用的是 TraceMemo Local HTTP API,不是 MCP Server。
|
||||
|
||||
Reader Skill 只负责“外部 Agent 主动查询历史微信数据”。它不负责二维码登录、监听微信实时消息、接收机器人消息或管理 Agent Hub。想让机器人收到微信消息后处理并回复,请阅读[Agent Hub](./agent-hub.md)。
|
||||
|
||||
Reader Skill 的展示名称已迁移为 TraceMemo Reader;安装目录和 `WECHATEXPLORER_API_TOKEN` 环境变量仍保留历史兼容标识,以便旧 Agent 配置继续工作。
|
||||
|
||||
## 推荐安装流程
|
||||
|
||||
1. 启动 WechatExplorer 并完成数据库连接。
|
||||
1. 启动 TraceMemo 并完成数据库连接。
|
||||
2. 打开“API Center”,确认 API 服务和数据库状态正常。
|
||||
3. 在 Reader Skill 区域选择目标 Agent,点击“复制安装指令”。
|
||||
4. 把指令粘贴到对应 Agent 的 Skill/配置目录;应用会根据本机路径生成适合 Codex、Claude Code、OpenClaw 或通用 Agent 的说明。
|
||||
@@ -22,7 +24,7 @@ Reader Skill 只负责“外部 Agent 主动查询历史微信数据”。它不
|
||||
|
||||
6. 先执行 health 检查,再读取数据端点。
|
||||
|
||||
WechatExplorer 不会自动把 Token 写进 Agent 配置。重新生成 Token 后,必须同步更新 Agent 环境。
|
||||
TraceMemo 不会自动把 Token 写进 Agent 配置。重新生成 Token 后,必须同步更新 Agent 环境。
|
||||
|
||||
## Agent 的读取顺序
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# WechatExplorer 2.1.9:Local HTTP API 鉴权迁移
|
||||
# TraceMemo 2.1.9:Local HTTP API 鉴权迁移
|
||||
|
||||
2.1.9 为 Local HTTP API 增加 Bearer Token 鉴权。这是一次有意的兼容性变化:除健康检查外,数据接口不再接受裸请求。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- 2.1.9 中,相同请求必须携带 `Authorization: Bearer <TOKEN>`,否则返回 `401`;
|
||||
- `GET /api/v1/health` 保持公开;
|
||||
- 升级后应用会生成并安全保存 Token,原有 API 启用状态、监听地址和端口设置保持不变;
|
||||
- Token 可在 WechatExplorer → API Center 中显示、复制和重新生成;
|
||||
- Token 可在 TraceMemo → API Center 中显示、复制和重新生成;
|
||||
- Reader Skill、Codex、Claude Code、OpenClaw 和其他本地 Agent 需要在自己的环境中设置 `WECHATEXPLORER_API_TOKEN`。
|
||||
|
||||
如果旧 Agent 无法访问,请先从 API Center 复制当前 Token,再确认每个非 health 请求都带有 Bearer header。完整规则见[API 安全](./api-security.md)。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
AI 回答后,你可以继续查看它参考了哪些聊天内容、这些内容来自哪个会话和时间,并跳回原始消息检查上下文。
|
||||
|
||||
这让 WechatExplorer 和只给一段摘要的聊天机器人不同:答案不是终点,来源也应该能被你检查。
|
||||
这让 TraceMemo 和只给一段摘要的聊天机器人不同:答案不是终点,来源也应该能被你检查。
|
||||
|
||||
## 三类来源信息
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# WechatExplorer 如何把聊天变成可用的信息
|
||||
# TraceMemo 如何把聊天变成可用的信息
|
||||
|
||||
你可以把一次任务想成下面这条路径:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 开发、测试与构建
|
||||
|
||||
本文面向希望参与 WechatExplorer 开发、验证文档或维护集成的贡献者。普通用户请从[第一次使用](../user-guide/getting-started.md)开始。
|
||||
本文面向希望参与 TraceMemo 开发、验证文档或维护集成的贡献者。普通用户请从[第一次使用](../user-guide/getting-started.md)开始。
|
||||
|
||||
## 技术基线
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## 你什么时候会看到这些提示
|
||||
|
||||
WechatExplorer 需要读取微信本地数据。macOS 会根据系统版本、微信状态和安全设置,要求应用完成授权;自动获取数据库密钥时,页面可能提示暂时调整系统安全设置。
|
||||
TraceMemo 需要读取微信本地数据。macOS 会根据系统版本、微信状态和安全设置,要求应用完成授权;自动获取数据库密钥时,页面可能提示暂时调整系统安全设置。
|
||||
|
||||
## 推荐步骤
|
||||
|
||||
1. 先启动 WechatExplorer,阅读连接页面显示的当前前置条件。
|
||||
1. 先启动 TraceMemo,阅读连接页面显示的当前前置条件。
|
||||
2. 确认微信数据目录指向当前账号。
|
||||
3. 只在页面明确要求时处理系统授权或 SIP;按页面提示完成密钥获取后,恢复你平时使用的安全设置。
|
||||
4. 返回应用重新检测账号、数据库和图片资源状态。
|
||||
@@ -15,7 +15,7 @@ WechatExplorer 需要读取微信本地数据。macOS 会根据系统版本、
|
||||
|
||||
## SIP 风险
|
||||
|
||||
关闭 System Integrity Protection 会降低 macOS 对系统文件和进程的保护。它不是日常使用 WechatExplorer 的功能开关,也不应长期保持关闭。只有在你理解风险、确认页面要求且完成必要操作时才处理;完成后按 Apple 官方方式重新启用。
|
||||
关闭 System Integrity Protection 会降低 macOS 对系统文件和进程的保护。它不是日常使用 TraceMemo 的功能开关,也不应长期保持关闭。只有在你理解风险、确认页面要求且完成必要操作时才处理;完成后按 Apple 官方方式重新启用。
|
||||
|
||||
## 应用无法打开
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
---
|
||||
name: wechatexplorer-reader
|
||||
description: 通过 WechatExplorer 本地 HTTP API 按需读取用户有权访问的微信聊天数据。当用户要求查看微信消息、查找联系人或群聊、总结聊天、生成群聊总结时使用。此 Skill 由本机 WechatExplorer 提供数据,不是 MCP Server。
|
||||
description: 通过 TraceMemo 本地 HTTP API 按需读取用户有权访问的微信聊天数据。当用户要求查看微信消息、查找联系人或群聊、总结聊天、生成群聊总结时使用。此 Skill 由本机 TraceMemo 提供数据,不是 MCP Server。
|
||||
---
|
||||
|
||||
# WechatExplorer Reader
|
||||
# TraceMemo Reader
|
||||
|
||||
你是一个通过本机 WechatExplorer 读取微信历史的 Agent。先确认用户已经在 WechatExplorer 中完成数据库连接,再按需调用 API;不要假设数据库已就绪,也不要声称读取了没有调用过的消息。
|
||||
你是一个通过本机 TraceMemo 读取微信历史的 Agent。先确认用户已经在 TraceMemo 中完成数据库连接,再按需调用 API;不要假设数据库已就绪,也不要声称读取了没有调用过的消息。
|
||||
|
||||
## 连接信息
|
||||
|
||||
- Base URL 默认是 `http://127.0.0.1:6131/api/v1`。
|
||||
- `GET /health` 不需要 Token。
|
||||
- 其他端点必须带 `Authorization: Bearer $WECHATEXPLORER_API_TOKEN`。
|
||||
- Token 由用户在 WechatExplorer → API Center 显示/复制,并放在 Agent 自己的本地环境中。
|
||||
- Token 由用户在 TraceMemo → API Center 显示/复制,并放在 Agent 自己的本地环境中。
|
||||
- 不要把 Token 放到 URL、回答、日志、Skill 文件或仓库。
|
||||
- 6131 是普通 Local HTTP API,不是 MCP Server;不要生成 `mcpServers` 配置。
|
||||
|
||||
`wechatexplorer-reader` 是该 Skill 为兼容既有安装而保留的目录和标识名;对用户展示时称为 TraceMemo Reader。
|
||||
|
||||
## 每次任务前
|
||||
|
||||
1. 调用 `/health`,确认服务和数据库状态。
|
||||
@@ -43,7 +45,7 @@ description: 通过 WechatExplorer 本地 HTTP API 按需读取用户有权访
|
||||
|
||||
## 时间与上下文规则
|
||||
|
||||
`/chatlog` 的 `time` 支持 `YYYY-MM-DD`、日期闭区间和分钟范围;也可以使用 Unix 秒级 `startTime`/`endTime`。时间按 WechatExplorer 所在机器的本机时区解释。
|
||||
`/chatlog` 的 `time` 支持 `YYYY-MM-DD`、日期闭区间和分钟范围;也可以使用 Unix 秒级 `startTime`/`endTime`。时间按 TraceMemo 所在机器的本机时区解释。
|
||||
|
||||
当用户问“某个话题是谁说的、后来结论是什么”时,先定位会话和时间,再读取关键消息前后文。回答时区分:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 第一次使用 WechatExplorer
|
||||
# 第一次使用 TraceMemo
|
||||
|
||||
如果你刚下载 WechatExplorer,只需要完成一条主线:
|
||||
如果你刚下载 TraceMemo,只需要完成一条主线:
|
||||
|
||||
> 安装应用 → 连接微信数据 → 确认聊天已加载 → 搜索或提问。
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
| Windows | [微信 Windows `4.1.9.57`](https://github.com/iibob/wechat-win-archive/releases#release-v4.1.9.57) | 首次使用时请确认微信数据目录;Windows 不需要关闭 SIP |
|
||||
|
||||
- 上表是当前实际测试过的客户端版本,不代表只有这些版本可以使用。其他微信 4.x 版本可能可以连接,但尚未逐一验证。
|
||||
- WechatExplorer 必须取得当前微信账号对应的数据库密钥,才能读取聊天记录。
|
||||
- TraceMemo 必须取得当前微信账号对应的数据库密钥,才能读取聊天记录。
|
||||
- 你需要有权访问要读取的微信账号和聊天数据。
|
||||
- 如果要使用 AI 问答、群聊日报或图片理解,还需要在应用中配置一个 AI 服务。
|
||||
|
||||
@@ -26,23 +26,23 @@
|
||||
|
||||
### Windows
|
||||
|
||||
1. 从 Releases 下载 Windows x64 的 `WechatExplorer-<版本号>-setup.exe` 安装包。
|
||||
1. 从 Releases 下载 Windows x64 的 `TraceMemo-<版本号>-setup.exe` 安装包。
|
||||
2. 双击安装包,按向导完成安装。
|
||||
3. 启动 WechatExplorer。
|
||||
4. 如果安装完成后软件无法启动,请安装 Microsoft Visual C++ x64 运行库:[vc_redist.x64.exe](https://aka.ms/vc14/vc_redist.x64.exe),安装完成后重新启动 WechatExplorer。
|
||||
3. 启动 TraceMemo。
|
||||
4. 如果安装完成后软件无法启动,请安装 Microsoft Visual C++ x64 运行库:[vc_redist.x64.exe](https://aka.ms/vc14/vc_redist.x64.exe),安装完成后重新启动 TraceMemo。
|
||||
|
||||
### macOS
|
||||
|
||||
1. 根据处理器下载对应的 `.dmg`:Apple Silicon(M 系列)选择 `arm64`,Intel Mac 选择 `x64`。
|
||||
2. 打开 DMG,将 WechatExplorer 拖入“应用程序”文件夹。
|
||||
2. 打开 DMG,将 TraceMemo 拖入“应用程序”文件夹。
|
||||
3. 如果系统提示“无法打开,因为开发者无法验证”,前往“系统设置 → 隐私与安全性”,点击“仍要打开”。
|
||||
4. 如果系统提示应用已损坏,可在终端执行:
|
||||
|
||||
```bash
|
||||
xattr -cr "/Applications/WechatExplorer.app"
|
||||
xattr -cr "/Applications/TraceMemo.app"
|
||||
```
|
||||
|
||||
5. 启动 WechatExplorer。首次自动获取数据库密钥时,按连接页面显示的授权要求操作;只有页面明确提示时才按[关闭 SIP 教程](../mac-disable-sip.md)处理。关闭 SIP 会降低系统安全性,完成密钥配置后应重新开启。
|
||||
5. 启动 TraceMemo。首次自动获取数据库密钥时,按连接页面显示的授权要求操作;只有页面明确提示时才按[关闭 SIP 教程](../mac-disable-sip.md)处理。关闭 SIP 会降低系统安全性,完成密钥配置后应重新开启。
|
||||
|
||||
更完整的权限和安全边界见 [macOS 数据访问说明](../platform/macos.md)。
|
||||
|
||||
@@ -107,12 +107,12 @@
|
||||
- [转写微信语音](./voice.md)
|
||||
- [导出聊天档案](./export.md)
|
||||
- [可选开启防撤回](./recall-protection.md)
|
||||
- [在微信里向 WechatExplorer 提问](../agent/agent-hub.md)
|
||||
- [在微信里向 TraceMemo 提问](../agent/agent-hub.md)
|
||||
- [让外部 Agent 查询微信历史](../agent/overview.md)
|
||||
|
||||
## 7. 想直接在微信里提问
|
||||
|
||||
如果你希望直接在微信里向 WechatExplorer 提问,而不是另外配置 Codex 等外部 Agent,请使用 Agent Hub:
|
||||
如果你希望直接在微信里向 TraceMemo 提问,而不是另外配置 Codex 等外部 Agent,请使用 Agent Hub:
|
||||
|
||||
1. 先完成上面的微信数据库连接,并确认“档案”里能看到聊天。
|
||||
2. 打开应用主导航中的“Agent”;页面标题为“Agent Hub”。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
如果你经常查同一批工作群、项目讨论或长期联系人,只靠每次临时翻聊天会越来越慢。Knowledge 会在本机建立一份可重复查找的索引,让“以前聊过什么”这类问题更容易跨会话、跨时间找到相关内容。
|
||||
|
||||
它不是另一个聊天窗口,也不会替你修改微信原始数据库;它是 WechatExplorer 为当前账号维护的本地加速资料。
|
||||
它不是另一个聊天窗口,也不会替你修改微信原始数据库;它是 TraceMemo 为当前账号维护的本地加速资料。
|
||||
|
||||
## 建立和同步
|
||||
|
||||
@@ -34,5 +34,5 @@ Knowledge 不会在第一次连接后自动悄悄建立。进入“问问微信
|
||||
|
||||
## 产品术语(可选)
|
||||
|
||||
源码和日志中可能出现 SQLite、FTS、Chunk、索引等词。它们描述的是本地存储和检索实现,不是你开始使用 WechatExplorer 的前置知识。
|
||||
源码和日志中可能出现 SQLite、FTS、Chunk、索引等词。它们描述的是本地存储和检索实现,不是你开始使用 TraceMemo 的前置知识。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 数据、隐私与安全
|
||||
|
||||
WechatExplorer 的核心路径是本地优先,但“本地优先”不等于所有功能都完全离线。是否有数据离开电脑,取决于你是否启用了对应的 AI、Agent 或机器人能力。
|
||||
TraceMemo 的核心路径是本地优先,但“本地优先”不等于所有功能都完全离线。是否有数据离开电脑,取决于你是否启用了对应的 AI、Agent 或机器人能力。
|
||||
|
||||
## 默认留在本机的内容
|
||||
|
||||
@@ -12,9 +12,9 @@ WechatExplorer 的核心路径是本地优先,但“本地优先”不等于
|
||||
- 离线语音转写;
|
||||
- 导出文件生成和本地日报历史。
|
||||
|
||||
应用不会因为你打开 WechatExplorer 就自动把整份微信数据库上传。
|
||||
应用不会因为你打开 TraceMemo 就自动把整份微信数据库上传。
|
||||
|
||||
防撤回默认关闭,并且和上面的普通读取路径不同。用户第一次明确开启时,当前实现会在微信消息数据库中安装本地撤回日志/监听结构,同时在 WechatExplorer 用户数据目录保存必要的恢复记录。关闭开关不等于移除已经安装的结构或清空既有记录;当前 UI 没有对应的清理入口。详见[防撤回](./recall-protection.md)。
|
||||
防撤回默认关闭,并且和上面的普通读取路径不同。用户第一次明确开启时,当前实现会在微信消息数据库中安装本地撤回日志/监听结构,同时在 TraceMemo 用户数据目录保存必要的恢复记录。为兼容旧版本,磁盘上的用户数据目录可能仍沿用历史名称 `WechatExplorer`。关闭开关不等于移除已经安装的结构或清空既有记录;当前 UI 没有对应的清理入口。详见[防撤回](./recall-protection.md)。
|
||||
|
||||
## 什么时候会请求外部服务
|
||||
|
||||
@@ -24,7 +24,7 @@ WechatExplorer 的核心路径是本地优先,但“本地优先”不等于
|
||||
- 受控检索所需的有限上下文;
|
||||
- 最终用于总结的 Evidence。
|
||||
|
||||
不会发送完整微信数据库、全量聊天记录、未选中的聊天范围、数据库密钥、内部索引结构或内部会话/消息引用 ID。Provider 的日志、保留、计费和跨境规则不由 WechatExplorer 控制,请查看你所选服务商的政策。
|
||||
不会发送完整微信数据库、全量聊天记录、未选中的聊天范围、数据库密钥、内部索引结构或内部会话/消息引用 ID。Provider 的日志、保留、计费和跨境规则不由 TraceMemo 控制,请查看你所选服务商的政策。
|
||||
|
||||
Ollama 等本机 Provider 可以把模型请求留在本机,但本机服务的日志和配置仍由你负责。
|
||||
|
||||
@@ -47,7 +47,7 @@ Token 由应用生成,使用 Electron `safeStorage` 加密保存在本机 `loc
|
||||
|
||||
外部 Agent 通过 Reader Skill 调用本机 API,按需读取联系人、会话或时间范围内的聊天;它不会因此获得数据库文件路径或任意文件系统权限。Agent 是否把读取结果再次发送给模型,取决于 Agent 本身及其配置。
|
||||
|
||||
应用内 Agent Hub 是另一条路径:微信机器人通过本机 Hub 调用 WechatExplorer,并且可能使用已配置的 AI 来理解问题。请把机器人账号、发送权限和日志视为独立的安全边界。
|
||||
应用内 Agent Hub 是另一条路径:微信机器人通过本机 Hub 调用 TraceMemo,并且可能使用已配置的 AI 来理解问题。请把机器人账号、发送权限和日志视为独立的安全边界。
|
||||
|
||||
机器人收到的文字会先进入本机 Agent Hub;如果任务需要总结或自然语言理解,受控上下文可能发送给你配置的 AI Provider。机器人账号扫码登录、个人微信数据库连接和外部 Agent/API Token 是不同的边界,使用前请分别确认账号与权限。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 防撤回
|
||||
|
||||
防撤回是一个默认关闭的可选功能。开启后,WechatExplorer 会尽量保留它能够捕获到的撤回消息,并在聊天气泡旁标记“消息已撤回”。
|
||||
防撤回是一个默认关闭的可选功能。开启后,TraceMemo 会尽量保留它能够捕获到的撤回消息,并在聊天气泡旁标记“消息已撤回”。
|
||||
|
||||
它适合希望在本机档案中保留后续聊天上下文的用户,但不能保证找回每一条撤回消息。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
1. 先连接微信数据库,并确认“档案”可以正常读取聊天。
|
||||
2. 打开“设置 → 防撤回”。
|
||||
3. 阅读性能和数据提示后,开启“防撤回”。
|
||||
4. 保持 WechatExplorer 与当前微信数据连接;之后捕获到的撤回消息会尽量保留并标记。
|
||||
4. 保持 TraceMemo 与当前微信数据连接;之后捕获到的撤回消息会尽量保留并标记。
|
||||
|
||||
防撤回不是第一次使用的必要步骤。只想浏览、搜索、提问或导出时,可以保持关闭。
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
- 监听应用能够识别到的后续撤回变化;
|
||||
- 在本地保留必要的消息和撤回关系;
|
||||
- 将已识别的原消息与撤回状态一起显示在档案中;
|
||||
- 按微信账号隔离 WechatExplorer 保存的恢复记录。
|
||||
- 按微信账号隔离 TraceMemo 保存的恢复记录。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 不能恢复开启前已经撤回、且应用从未保存到的消息;
|
||||
- WechatExplorer 未运行、数据库未连接或没有捕获到撤回变化时,消息可能无法保留;
|
||||
- TraceMemo 未运行、数据库未连接或没有捕获到撤回变化时,消息可能无法保留;
|
||||
- 微信版本、消息表结构和数据库事件变化都可能让部分消息无法恢复或正确匹配;
|
||||
- 开启后需要为消息表增加监听,聊天很多或磁盘较慢时可能影响加载性能;
|
||||
- “消息已撤回”只说明应用识别到了撤回关系,不保证恢复内容完整。
|
||||
|
||||
## 数据写入与关闭边界
|
||||
|
||||
普通浏览、搜索和 Knowledge 不会修改微信原始聊天数据库;防撤回是一个例外。用户第一次明确开启时,当前实现会在微信消息数据库中安装用于记录撤回的本地日志/监听结构,并在 WechatExplorer 的用户数据目录保存必要的本地恢复记录。
|
||||
普通浏览、搜索和 Knowledge 不会修改微信原始聊天数据库;防撤回是一个例外。用户第一次明确开启时,当前实现会在微信消息数据库中安装用于记录撤回的本地日志/监听结构,并在 TraceMemo 的用户数据目录保存必要的本地恢复记录。为兼容旧版本,该目录在磁盘上可能继续显示为历史名称 `WechatExplorer`。
|
||||
|
||||
关闭设置中的开关,不等同于删除已经安装的日志结构或清空此前保存的恢复记录。当前版本没有在 UI 中提供“移除防撤回日志结构”或“清空防撤回记录”的独立操作。对数据库写入、磁盘占用或完全回滚有要求时,应在开启前先确认这一边界,并使用微信官方方式备份重要数据。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 生成群聊日报和总结
|
||||
|
||||
如果你每天在多个群里聊天,晚上不想重新翻几十个群,可以让 WechatExplorer 根据一个群的聊天内容整理出一份可阅读、可保存的报告。
|
||||
如果你每天在多个群里聊天,晚上不想重新翻几十个群,可以让 TraceMemo 根据一个群的聊天内容整理出一份可阅读、可保存的报告。
|
||||
|
||||
## 报告适合做什么
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
1. 确认下载的是 GitHub Releases 中的 Windows x64 `-setup.exe`,并已完成安装。
|
||||
2. 安装 [Microsoft Visual C++ x64 运行库](https://aka.ms/vc14/vc_redist.x64.exe)。
|
||||
3. 安装完成后重新启动 WechatExplorer;如果仍无响应,再重新运行安装包进行覆盖安装。
|
||||
3. 安装完成后重新启动 TraceMemo;如果仍无响应,再重新运行安装包进行覆盖安装。
|
||||
|
||||
### macOS
|
||||
|
||||
- 提示“无法打开,因为开发者无法验证”时,前往“系统设置 → 隐私与安全性”并点击“仍要打开”。
|
||||
- 提示应用已损坏时,确认应用位于“应用程序”目录,再执行 `xattr -cr "/Applications/WechatExplorer.app"`。
|
||||
- 提示应用已损坏时,确认应用位于“应用程序”目录,再执行 `xattr -cr "/Applications/TraceMemo.app"`。
|
||||
|
||||
完整安装步骤见[第一次使用 WechatExplorer](./getting-started.md#2-安装并启动)。
|
||||
完整安装步骤见[第一次使用 TraceMemo](./getting-started.md#2-安装并启动)。
|
||||
|
||||
## 连接微信失败
|
||||
|
||||
@@ -69,7 +69,7 @@ AI Search 失败时可能仍保留部分来源;不要把部分结果当成完
|
||||
|
||||
确认:
|
||||
|
||||
1. WechatExplorer 正在运行且 API Center 显示本地服务在线;
|
||||
1. TraceMemo 正在运行且 API Center 显示本地服务在线;
|
||||
2. Agent 使用的是当前 Reader Skill,而不是旧的 MCP 配置;
|
||||
3. 请求地址为 `http://127.0.0.1:6131`;
|
||||
4. 非 health 请求带有最新 `Authorization: Bearer <TOKEN>`;
|
||||
@@ -91,4 +91,4 @@ Agent Hub 和外部 Agent 是两条路径。机器人异常时依次确认:
|
||||
|
||||
## 防撤回没有保留消息
|
||||
|
||||
防撤回只能尽量保留开启后且应用成功捕获到的撤回变化。确认开启时数据库已经连接、WechatExplorer 在撤回发生时保持运行,并检查聊天加载是否明显变慢。开启前已经消失、应用未捕获或微信结构无法识别的消息不能保证恢复;详见[防撤回](./recall-protection.md)。
|
||||
防撤回只能尽量保留开启后且应用成功捕获到的撤回变化。确认开启时数据库已经连接、TraceMemo 在撤回发生时保持运行,并检查聊天加载是否明显变慢。开启前已经消失、应用未捕获或微信结构无法识别的消息不能保证恢复;详见[防撤回](./recall-protection.md)。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 语音转文字
|
||||
|
||||
WechatExplorer 可以把微信语音转换成可搜索的文字,适合你不想逐条播放、希望把语音内容带入后续查找或导出的场景。
|
||||
TraceMemo 可以把微信语音转换成可搜索的文字,适合你不想逐条播放、希望把语音内容带入后续查找或导出的场景。
|
||||
|
||||
## 使用前准备
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Keep the legacy appId so existing userData directories and upgrades remain readable.
|
||||
appId: com.wechatexplorer.app
|
||||
productName: WechatExplorer
|
||||
productName: TraceMemo
|
||||
afterPack: scripts/after-pack.cjs
|
||||
directories:
|
||||
buildResources: build
|
||||
@@ -32,8 +33,8 @@ extraResources:
|
||||
win:
|
||||
icon: icon.ico
|
||||
# WCDB's Windows runtime checks the host executable name. The dev runtime is
|
||||
# electron.exe, so keep the packaged executable compatible while preserving
|
||||
# WechatExplorer as the product/shortcut name.
|
||||
# electron.exe, so keep the packaged executable compatible while using
|
||||
# TraceMemo as the product/shortcut name.
|
||||
executableName: electron
|
||||
nsis:
|
||||
oneClick: false
|
||||
@@ -47,9 +48,10 @@ mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
extendInfo:
|
||||
# The bundled WCDB bridge accepts Electron as its internal host name. The
|
||||
# public app name and bundle identifier remain WechatExplorer-specific.
|
||||
# legacy bundle identifier remains unchanged for upgrade compatibility;
|
||||
# the public display name is migrated to TraceMemo.
|
||||
CFBundleName: Electron
|
||||
CFBundleDisplayName: WechatExplorer
|
||||
CFBundleDisplayName: TraceMemo
|
||||
NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "wechatexplorer",
|
||||
"version": "2.1.9",
|
||||
"name": "tracememo",
|
||||
"version": "2.2.0",
|
||||
"packageManager": "pnpm@7.33.7",
|
||||
"description": "macOS / Windows 本地优先的微信聊天记录搜索与 AI 工作台",
|
||||
"description": "macOS / Windows 本地优先、可追溯的 AI 微信知识与分析工作台",
|
||||
"keywords": [
|
||||
"wechat",
|
||||
"wechat chat",
|
||||
|
||||
@@ -594,7 +594,7 @@
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
数据来源:WechatExplorer · 微信群聊记录<br />
|
||||
数据来源:TraceMemo · 微信群聊记录<br />
|
||||
生成时间:{{GENERATED_AT}}<br />
|
||||
{{FOOTER_NOTE}}
|
||||
</footer>
|
||||
|
||||
@@ -831,7 +831,7 @@
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
数据来源:WechatExplorer · 微信群聊记录<br />
|
||||
数据来源:TraceMemo · 微信群聊记录<br />
|
||||
生成时间:{{GENERATED_AT}}<br />
|
||||
{{FOOTER_NOTE}}
|
||||
</footer>
|
||||
|
||||
@@ -108,7 +108,7 @@ function setPlistValue(plistPath, key, value) {
|
||||
function validateReaderSkillRuntime(runtimeResources) {
|
||||
const skillPath = path.join(runtimeResources, 'skill', 'wechatexplorer-reader', 'SKILL.md')
|
||||
if (!existsSync(skillPath)) {
|
||||
throw new Error(`Missing bundled WechatExplorer Reader Skill: ${skillPath}`)
|
||||
throw new Error(`Missing bundled TraceMemo Reader Skill: ${skillPath}`)
|
||||
}
|
||||
return skillPath
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# WechatExplorer v2.1.9 Local HTTP API 手动验收脚本
|
||||
# TraceMemo v2.2.0 Local HTTP API 手动验收脚本
|
||||
# 仅用于 macOS Terminal;不会写入或输出真实 API Token。
|
||||
|
||||
set -u
|
||||
|
||||
API_BASE_URL="${API_BASE_URL:-http://127.0.0.1:6131}"
|
||||
API_BASE_URL="${API_BASE_URL%/}"
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/wechatexplorer-api-test.XXXXXX")"
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/tracememo-api-test.XXXXXX")"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
PASS_COUNT=0
|
||||
@@ -18,7 +18,7 @@ pass() { PASS_COUNT=$((PASS_COUNT + 1)); printf 'PASS %s\n' "$1"; }
|
||||
fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); printf 'FAIL %s%s\n' "$1" "${2:+ ($2)}"; }
|
||||
skip() { SKIP_COUNT=$((SKIP_COUNT + 1)); printf 'SKIP %s\n' "$1"; }
|
||||
|
||||
printf 'WechatExplorer Local HTTP API 手动测试\n'
|
||||
printf 'TraceMemo Local HTTP API 手动测试\n'
|
||||
printf 'API 地址: %s\n\n' "$API_BASE_URL"
|
||||
read -r -s -p '请输入 API Token(不会显示): ' API_TOKEN
|
||||
printf '\n'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const templatePath = path.join(root, 'resources', 'mobile_daily_report.html')
|
||||
const outputDir = path.join(os.tmpdir(), 'wechatexplorer-report-fixtures')
|
||||
const outputDir = path.join(os.tmpdir(), 'tracememo-report-fixtures')
|
||||
|
||||
const escapeHtml = (value) =>
|
||||
String(value ?? '')
|
||||
|
||||
@@ -34,7 +34,7 @@ const local = {
|
||||
for (const [target, expected] of [
|
||||
['codex', 'Codex 项目或用户 Skill 目录'],
|
||||
['claude-code', '按照 SKILL\.md 调用本地 HTTP API'],
|
||||
['openclaw', '作为 WechatExplorer Reader Skill 安装'],
|
||||
['openclaw', '作为 TraceMemo Reader Skill 安装'],
|
||||
['generic', '读取并安装']
|
||||
]) {
|
||||
const text = buildSkillInstallInstruction({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WechatExplorer WeChat Connector
|
||||
# TraceMemo WeChat Connector
|
||||
|
||||
This repository-local service provides the minimal WeChat bridge required by WechatExplorer:
|
||||
This repository-local service provides the minimal WeChat bridge required by TraceMemo:
|
||||
|
||||
- QR-code login with a single persisted credential
|
||||
- account discovery
|
||||
@@ -18,8 +18,8 @@ go run . accounts --json
|
||||
go run . start --foreground --api-addr 127.0.0.1:18011 --account-id <account-id>
|
||||
```
|
||||
|
||||
Credential and synchronization state is stored under `~/.wechatexplorer/wechat-connector/accounts`. A successful login is written before the older credential and synchronization state are removed, so an incomplete login cannot destroy the last working credential.
|
||||
Credential and synchronization state is stored under `~/.wechatexplorer/wechat-connector/accounts`. This legacy directory name is intentionally retained so upgrades can reuse existing accounts. A successful login is written before the older credential and synchronization state are removed, so an incomplete login cannot destroy the last working credential.
|
||||
|
||||
## Attribution
|
||||
|
||||
Low-level protocol and media transport portions are distributed under the MIT license in [LICENSE](LICENSE). WechatExplorer-specific process management, webhook contract, product UI, and Agent Hub behavior live in the surrounding WechatExplorer project.
|
||||
Low-level protocol and media transport portions are distributed under the MIT license in [LICENSE](LICENSE). TraceMemo-specific process management, webhook contract, product UI, and Agent Hub behavior live in the surrounding TraceMemo project.
|
||||
|
||||
@@ -73,7 +73,7 @@ func (m *Monitor) Run(ctx context.Context) error {
|
||||
log.Printf("[monitor] GetUpdates error (%d/%d, backoff=%s): %v",
|
||||
m.failures, maxConsecutiveFailures, backoff, err)
|
||||
if m.failures == maxConsecutiveFailures {
|
||||
log.Printf("[monitor] WARNING: %d consecutive failures; reconnect from WechatExplorer if this persists.", maxConsecutiveFailures)
|
||||
log.Printf("[monitor] WARNING: %d consecutive failures; reconnect from TraceMemo if this persists.", maxConsecutiveFailures)
|
||||
}
|
||||
select {
|
||||
case <-time.After(backoff):
|
||||
@@ -96,7 +96,7 @@ func (m *Monitor) Run(ctx context.Context) error {
|
||||
} else {
|
||||
// Sync buf already empty but still getting session expired:
|
||||
// the bot token itself has expired. The user needs to re-login.
|
||||
log.Printf("[monitor] WARNING: WeChat session expired and cannot be auto-recovered; reconnect from WechatExplorer.")
|
||||
log.Printf("[monitor] WARNING: WeChat session expired and cannot be auto-recovered; reconnect from TraceMemo.")
|
||||
}
|
||||
select {
|
||||
case <-time.After(sessionExpiredBackoff):
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { app } from 'electron'
|
||||
import path from 'path'
|
||||
import {
|
||||
chooseUserDataRoot,
|
||||
getUserDataRoots,
|
||||
LEGACY_USER_DATA_NAME
|
||||
} from './app-data-paths'
|
||||
|
||||
// This module must remain the first main-process import. Static imports in
|
||||
// settings/cache services can otherwise resolve Electron paths before the
|
||||
// legacy runtime identity and selected userData are installed.
|
||||
app.setName(process.platform === 'win32' ? 'WeFlow' : LEGACY_USER_DATA_NAME)
|
||||
|
||||
const isolatedUserData = process.env['WXE_USER_DATA']
|
||||
const roots = getUserDataRoots(app.getPath('appData'))
|
||||
const selectedUserData = chooseUserDataRoot({
|
||||
...roots,
|
||||
isolated: isolatedUserData
|
||||
})
|
||||
|
||||
app.setPath('userData', selectedUserData)
|
||||
app.setPath('sessionData', selectedUserData)
|
||||
|
||||
// Logs are intentionally independent from userData. New TraceMemo logs go to
|
||||
// the new visible directory while historical WechatExplorer logs remain in
|
||||
// place and are never moved or renamed.
|
||||
if (process.platform === 'darwin') {
|
||||
app.setPath('logs', path.join(app.getPath('home'), 'Library', 'Logs', 'TraceMemo'))
|
||||
}
|
||||
|
||||
export { roots, selectedUserData }
|
||||
@@ -0,0 +1,104 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export const LEGACY_USER_DATA_NAME = 'WechatExplorer'
|
||||
export const CURRENT_USER_DATA_NAME = 'tracememo'
|
||||
|
||||
export interface UserDataRoots {
|
||||
legacy: string
|
||||
current: string
|
||||
}
|
||||
|
||||
export interface UserDataSelectionInput extends UserDataRoots {
|
||||
isolated?: string
|
||||
}
|
||||
|
||||
function isNonEmptyFile(filePath: string): boolean {
|
||||
try {
|
||||
const stat = fs.statSync(filePath)
|
||||
return stat.isFile() && stat.size > 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function hasPersistentEntries(directoryPath: string): boolean {
|
||||
try {
|
||||
return fs.readdirSync(directoryPath, { withFileTypes: true }).some((entry) => {
|
||||
if (entry.name === '.DS_Store') return false
|
||||
if (entry.name === 'LOCK' || entry.name === 'LOG' || entry.name === 'LOG.old') return false
|
||||
return entry.isFile() || entry.isDirectory()
|
||||
})
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function hasDatabaseKey(directoryPath: string): boolean {
|
||||
try {
|
||||
return fs.readdirSync(directoryPath, { withFileTypes: true }).some((entry) => {
|
||||
return entry.isFile() && entry.name.endsWith('.bin') && isNonEmptyFile(path.join(directoryPath, entry.name))
|
||||
})
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function hasKnowledgeDatabase(root: string): boolean {
|
||||
const knowledgeRoot = path.join(root, 'knowledge')
|
||||
try {
|
||||
return fs.readdirSync(knowledgeRoot, { withFileTypes: true }).some((entry) => {
|
||||
if (!entry.isDirectory()) return false
|
||||
return isNonEmptyFile(path.join(knowledgeRoot, entry.name, 'knowledge.sqlite'))
|
||||
})
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime-only Chromium files are deliberately excluded. A directory is a
|
||||
* valid data root only when it contains at least one user-owned marker.
|
||||
*/
|
||||
export function hasValidUserAssets(root: string): boolean {
|
||||
const markers = [
|
||||
'settings.json',
|
||||
'ai-providers.json',
|
||||
'ai-provider-keys.bin',
|
||||
'local-api-token.bin',
|
||||
'wechat-db-key.bin',
|
||||
'wechat-image-keys.bin',
|
||||
'image-insights.json',
|
||||
'wechat-share-service.bin'
|
||||
]
|
||||
if (markers.some((marker) => isNonEmptyFile(path.join(root, marker)))) return true
|
||||
if (hasKnowledgeDatabase(root)) return true
|
||||
if (hasDatabaseKey(path.join(root, 'database-keys'))) return true
|
||||
if (hasPersistentEntries(path.join(root, 'reports'))) return true
|
||||
if (hasPersistentEntries(path.join(root, 'recall-archive'))) return true
|
||||
if (hasPersistentEntries(path.join(root, 'digital-twin'))) return true
|
||||
if (hasPersistentEntries(path.join(root, 'group-exit-monitor'))) return true
|
||||
if (hasPersistentEntries(path.join(root, 'Local Storage', 'leveldb'))) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function getUserDataRoots(appDataPath: string): UserDataRoots {
|
||||
return {
|
||||
legacy: path.join(appDataPath, LEGACY_USER_DATA_NAME),
|
||||
current: path.join(appDataPath, CURRENT_USER_DATA_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select exactly one root. This intentionally does not copy, merge, delete or
|
||||
* modify either directory. Legacy wins when both roots contain user assets so
|
||||
* a v2.1.9 upgrade remains deterministic and lossless.
|
||||
*/
|
||||
export function chooseUserDataRoot(input: UserDataSelectionInput): string {
|
||||
const isolated = input.isolated?.trim()
|
||||
if (isolated) return path.resolve(isolated)
|
||||
|
||||
if (hasValidUserAssets(input.legacy)) return input.legacy
|
||||
if (hasValidUserAssets(input.current)) return input.current
|
||||
return input.current
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export class AppLogger {
|
||||
}
|
||||
|
||||
get logPath(): string {
|
||||
return path.join(this.logDir, 'wechatexplorer.log')
|
||||
return path.join(this.logDir, 'tracememo.log')
|
||||
}
|
||||
|
||||
private rotateIfNeeded(): void {
|
||||
|
||||
@@ -44,6 +44,18 @@ const exportStamp = (): string => {
|
||||
const pad = (value: number): string => String(value).padStart(2, '0')
|
||||
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}_${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`
|
||||
}
|
||||
const defaultExportRoot = (): string => join(app.getPath('documents'), 'TraceMemo', '导出')
|
||||
const legacyExportRoot = (): string => join(app.getPath('documents'), 'WechatExplorer', '导出')
|
||||
const resolveDefaultExportRoot = async (outputFolder?: string): Promise<string> => {
|
||||
if (!outputFolder) return defaultExportRoot()
|
||||
try {
|
||||
await fs.access(join(legacyExportRoot(), outputFolder))
|
||||
// Continue incremental exports in the legacy folder when it already exists.
|
||||
return legacyExportRoot()
|
||||
} catch {
|
||||
return defaultExportRoot()
|
||||
}
|
||||
}
|
||||
const imageKeys = new ImageKeyConfigService()
|
||||
|
||||
export interface HtmlExportConversation {
|
||||
@@ -908,13 +920,13 @@ async function runSingleExport(
|
||||
total: messages.length,
|
||||
percent: request.format === 'html' ? 18 : 20
|
||||
})
|
||||
const root = options.outputRoot || join(app.getPath('documents'), 'WechatExplorer', '导出')
|
||||
await fs.mkdir(root, { recursive: true })
|
||||
const ext = request.format === 'markdown' ? 'md' : request.format
|
||||
const outputFolder =
|
||||
request.format === 'html'
|
||||
? options.outputFolderName || safeFilePart(request.outputName)
|
||||
: `${safeFilePart(request.outputName)}_${exportStamp()}`
|
||||
const root = options.outputRoot || (await resolveDefaultExportRoot(outputFolder))
|
||||
await fs.mkdir(root, { recursive: true })
|
||||
const outputDir = join(root, outputFolder)
|
||||
const outputPath =
|
||||
request.format === 'html'
|
||||
@@ -1506,8 +1518,8 @@ async function runAllExport(
|
||||
throw new Error('导出聊天不能重复')
|
||||
}
|
||||
|
||||
const exportRoot = join(app.getPath('documents'), 'WechatExplorer', '导出')
|
||||
const outputFolder = safeFilePart(request.outputName)
|
||||
const exportRoot = await resolveDefaultExportRoot(outputFolder)
|
||||
outputDir = join(exportRoot, outputFolder)
|
||||
const folderNames = conversationFolderNames(targets)
|
||||
let lastProgressAt = 0
|
||||
|
||||
@@ -75,7 +75,7 @@ const embedAvatar = async (source: string | undefined, name: string): Promise<st
|
||||
if (/^https?:\/\//i.test(source)) {
|
||||
const response = await fetch(source, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 WechatExplorer',
|
||||
'User-Agent': 'Mozilla/5.0 TraceMemo',
|
||||
Referer: 'https://weixin.qq.com/'
|
||||
},
|
||||
signal: AbortSignal.timeout(8000)
|
||||
|
||||
@@ -167,7 +167,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
sendJson(res, 200, {
|
||||
ok: true,
|
||||
ready: isReady(),
|
||||
service: 'WechatExplorer Reader',
|
||||
service: 'TraceMemo Reader',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
@@ -186,7 +186,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
},
|
||||
|
||||
'/api/v1/contact': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const filter = url.searchParams.get('filter') || undefined
|
||||
const type = url.searchParams.get('type') || undefined
|
||||
let contacts = listContacts(filter)
|
||||
@@ -197,7 +197,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
},
|
||||
|
||||
'/api/v1/chatroom': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const keyword = url.searchParams.get('keyword') || ''
|
||||
let groups = listContacts().filter((c) => c.type === 'group')
|
||||
if (keyword) {
|
||||
@@ -212,14 +212,14 @@ const routes: Record<string, RouteHandler> = {
|
||||
},
|
||||
|
||||
'/api/v1/recent_chat': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const limit = parseNumeric(url.searchParams.get('limit'), 50)
|
||||
const items = listRecentChat(limit)
|
||||
sendJson(res, 200, { count: items.length, items })
|
||||
},
|
||||
|
||||
'/api/v1/chatlog': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const talker = url.searchParams.get('talker')
|
||||
if (!talker) return sendError(res, 400, '缺少必要参数 talker')
|
||||
|
||||
@@ -257,7 +257,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
},
|
||||
|
||||
'/api/v1/group_snapshot': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const md5 = url.searchParams.get('md5')
|
||||
if (!md5) return sendError(res, 400, '缺少必要参数 md5')
|
||||
const snapshot = getGroupSnapshot(md5)
|
||||
@@ -266,7 +266,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
},
|
||||
|
||||
'/api/v1/resolve': ({ res, url }) => {
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
const q = url.searchParams.get('q')
|
||||
if (!q) return sendError(res, 400, '缺少必要参数 q')
|
||||
const contact = resolveMd5(q)
|
||||
@@ -276,7 +276,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
|
||||
'/api/v1/report': async ({ req, res, body }) => {
|
||||
if (req.method !== 'POST') return sendError(res, 405, '需要 POST 请求')
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
if (typeof body !== 'string' || !body.trim()) {
|
||||
return sendError(res, 400, '请求体为空,需 POST GroupReportExportRequest JSON')
|
||||
}
|
||||
@@ -300,7 +300,7 @@ const routes: Record<string, RouteHandler> = {
|
||||
|
||||
'/api/v1/agent/group-report': async ({ req, res, body }) => {
|
||||
if (req.method !== 'POST') return sendError(res, 405, '需要 POST 请求')
|
||||
if (!isReady()) return sendError(res, 503, 'WechatExplorer 数据库未初始化')
|
||||
if (!isReady()) return sendError(res, 503, 'TraceMemo 数据库未初始化')
|
||||
let request: { group?: string; range?: 'today' | 'yesterday' | '7days' }
|
||||
try {
|
||||
request = JSON.parse(typeof body === 'string' ? body : '{}')
|
||||
|
||||
@@ -562,7 +562,7 @@ export class ImageDecryptService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 md5 查找图片文件 (WechatExplorer 风格)
|
||||
* 根据 md5 查找图片文件
|
||||
*/
|
||||
findImageFile(md5?: string, imageDatName?: string, options?: ImageFindOptions): string | null {
|
||||
const allowThumbnail = options?.allowThumbnail !== false
|
||||
@@ -648,7 +648,7 @@ export class ImageDecryptService {
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试 WechatExplorer 的目录结构: msg/attach/{hash}/{YYYY-MM}/Img/
|
||||
// 尝试 TraceMemo 兼容的微信目录结构: msg/attach/{hash}/{YYYY-MM}/Img/
|
||||
if (!existsSync(attachDir)) {
|
||||
imageDecryptLog('[ImageDecrypt] attach dir not found:', attachDir)
|
||||
return rememberPath(
|
||||
|
||||
+6
-16
@@ -1,3 +1,4 @@
|
||||
import './app-data-bootstrap'
|
||||
import './preload-env'
|
||||
import {
|
||||
app,
|
||||
@@ -272,17 +273,6 @@ protocol.registerSchemesAsPrivileged([
|
||||
}
|
||||
])
|
||||
|
||||
// WCDB's Windows runtime checks the host application name during wcdb_init.
|
||||
// Mirroring WeFlow's name unblocks the -1006 init failure on Windows.
|
||||
app.setName(
|
||||
process.platform === 'win32'
|
||||
? 'WeFlow'
|
||||
: process.env['WXE_USER_DATA']
|
||||
? 'WechatExplorer Dev'
|
||||
: 'WechatExplorer'
|
||||
)
|
||||
const isolatedUserData = process.env['WXE_USER_DATA']
|
||||
if (isolatedUserData) app.setPath('userData', isolatedUserData)
|
||||
let dbInitInFlight: Promise<{ success: boolean; monitoring?: boolean; error?: string }> | null =
|
||||
null
|
||||
let appShutdownRequested = false
|
||||
@@ -435,7 +425,7 @@ function createWindow(): void {
|
||||
void dialog
|
||||
.showMessageBox(mainWindow, {
|
||||
type: 'question',
|
||||
title: '关闭 WechatExplorer',
|
||||
title: '关闭 TraceMemo',
|
||||
message: '请选择关闭方式',
|
||||
detail: '你可以将窗口隐藏到系统托盘,或退出整个应用进程。',
|
||||
buttons: ['最小化到系统托盘', '关闭进程', '取消'],
|
||||
@@ -516,11 +506,11 @@ app.whenReady().then(async () => {
|
||||
return new Response('Media unavailable', { status: 500 })
|
||||
}
|
||||
})
|
||||
console.log(`WechatExplorer main build: ${BUILD_MARK}`)
|
||||
console.log(`TraceMemo main build: ${BUILD_MARK}`)
|
||||
appLogger.write({
|
||||
level: 'info',
|
||||
scope: 'lifecycle',
|
||||
message: 'WechatExplorer 启动',
|
||||
message: 'TraceMemo 启动',
|
||||
details: { build: BUILD_MARK, platform: process.platform, version: app.getVersion() }
|
||||
})
|
||||
process.on('uncaughtException', (error) => {
|
||||
@@ -1695,7 +1685,7 @@ function buildTrayMenu(): Menu {
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出 WechatExplorer',
|
||||
label: '退出 TraceMemo',
|
||||
click: () => {
|
||||
tray?.destroy()
|
||||
tray = null
|
||||
@@ -1714,7 +1704,7 @@ function setupTray(): void {
|
||||
? nativeImage.createEmpty()
|
||||
: image.resize({ width: traySize, height: traySize, quality: 'best' })
|
||||
tray = new Tray(trayImage)
|
||||
tray.setToolTip('WechatExplorer')
|
||||
tray.setToolTip('TraceMemo')
|
||||
// macOS may show a Tray context menu on a primary click when it is set
|
||||
// directly on the Tray. Keep the menu for an explicit secondary click so
|
||||
// the primary click only restores the main window.
|
||||
|
||||
@@ -33,5 +33,5 @@ try {
|
||||
process.env.WEFLOW_PROJECT_NAME = process.env.WEFLOW_PROJECT_NAME || 'WeFlow'
|
||||
prependPath(dllDirs.filter((dir) => fs.existsSync(dir)))
|
||||
} catch (error) {
|
||||
console.error('[WechatExplorer] failed to enforce local DLL priority:', error)
|
||||
console.error('[TraceMemo] failed to enforce local DLL priority:', error)
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ class AgentHubService {
|
||||
|
||||
private async replyRecentChats(inbound: InboundMessage, limit: number): Promise<void> {
|
||||
if (!isReady()) {
|
||||
await this.sendConnector(inbound, 'WechatExplorer 本地数据库尚未连接,请连接后再试。')
|
||||
await this.sendConnector(inbound, 'TraceMemo 本地数据库尚未连接,请连接后再试。')
|
||||
return
|
||||
}
|
||||
const items = listRecentChat(limit)
|
||||
@@ -474,7 +474,7 @@ class AgentHubService {
|
||||
const result = await agentAIProvider.chat([
|
||||
{
|
||||
role: 'system',
|
||||
content: `你是 WechatExplorer 微信机器人的意图理解器。只能输出一行 JSON,不要 Markdown。
|
||||
content: `你是 TraceMemo 微信机器人的意图理解器。只能输出一行 JSON,不要 Markdown。
|
||||
支持的工具:
|
||||
1. recent:查看最近会话,参数 limit 为 1-20。
|
||||
2. contact:查看我与某个联系人的最近聊天,参数 contact 和 limit。
|
||||
@@ -544,7 +544,7 @@ class AgentHubService {
|
||||
intent: ContactChatIntent
|
||||
): Promise<void> {
|
||||
if (!isReady()) {
|
||||
await this.sendConnector(inbound, 'WechatExplorer 本地数据库尚未连接,请连接后再试。')
|
||||
await this.sendConnector(inbound, 'TraceMemo 本地数据库尚未连接,请连接后再试。')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@ class AgentHubService {
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!isReady()) {
|
||||
await this.sendConnector(inbound, 'WechatExplorer 本地数据库尚未连接,请连接后再试。')
|
||||
await this.sendConnector(inbound, 'TraceMemo 本地数据库尚未连接,请连接后再试。')
|
||||
return
|
||||
}
|
||||
const contact = resolveMd5(intent.contact)
|
||||
@@ -642,7 +642,7 @@ class AgentHubService {
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!isReady()) {
|
||||
await this.sendConnector(inbound, 'WechatExplorer 本地数据库尚未连接,请连接后再试。')
|
||||
await this.sendConnector(inbound, 'TraceMemo 本地数据库尚未连接,请连接后再试。')
|
||||
return
|
||||
}
|
||||
const group = this.resolveGroup(intent.group)
|
||||
|
||||
@@ -80,7 +80,7 @@ const agentSystemPrompt = (
|
||||
question: string,
|
||||
scopeLabel: string,
|
||||
rangeLabel: string
|
||||
): string => `你是 WechatExplorer 的受控本地聊天搜索代理,只负责决定下一步检索,不回答用户问题。
|
||||
): string => `你是 TraceMemo 的受控本地聊天搜索代理,只负责决定下一步检索,不回答用户问题。
|
||||
用户问题:${question}
|
||||
允许范围:${scopeLabel};时间范围:${rangeLabel}。
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export async function inspectImageDecryptionStatus(
|
||||
const imageDirectoryFound = hasImageDirectory(accountRoot)
|
||||
const stickerCacheFound =
|
||||
fs.existsSync(path.join(accountRoot, 'cache')) ||
|
||||
fs.existsSync(path.join(os.homedir(), 'Documents', 'TraceMemo', 'Emojis')) ||
|
||||
fs.existsSync(path.join(os.homedir(), 'Documents', 'WechatExplorer', 'Emojis'))
|
||||
const dbConnected = chat.isReady()
|
||||
const [wechatRunning, decoder] = await Promise.all([
|
||||
@@ -287,7 +288,7 @@ export function buildImageTestDiagnosticLog(input: {
|
||||
const rootIsDirectory = rootExists ? safeIsDirectory(root) : false
|
||||
const resultCode = input.result.success ? 'SUCCESS' : input.result.code || 'UNKNOWN'
|
||||
return [
|
||||
'WechatExplorer 图片解析测试日志(已脱敏)',
|
||||
'TraceMemo 图片解析测试日志(已脱敏)',
|
||||
`时间:${new Date().toISOString()}`,
|
||||
`应用版本:${safeAppVersion()}`,
|
||||
`运行环境:${process.platform} ${process.arch}`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/main/services/image-insight-service.ts
|
||||
// WechatExplorer AI 图片理解基础设施
|
||||
// TraceMemo AI 图片理解基础设施
|
||||
//
|
||||
// 设计原则:
|
||||
// 1. base64 不走 IPC,只在 main 内部流转(renderer 只看到 ImageInsight 结构化结果)
|
||||
|
||||
@@ -90,7 +90,7 @@ export function resolveSkillResourceStatus(
|
||||
available: false,
|
||||
source,
|
||||
githubUrl: GITHUB_URL,
|
||||
error: `未找到 WechatExplorer Reader Skill 文件(已检查:${candidates.map((item) => item.path).join(';')})`
|
||||
error: `未找到 TraceMemo Reader Skill 文件(已检查:${candidates.map((item) => item.path).join(';')})`
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -19,9 +19,12 @@ const downloadCache = new Map<string, Promise<StickerResult>>()
|
||||
|
||||
export class StickerService {
|
||||
private readonly cacheDir: string
|
||||
private readonly legacyCacheDir: string
|
||||
|
||||
constructor(private readonly wcdb4Client?: Wcdb4Client | null) {
|
||||
this.cacheDir = path.join(os.homedir(), 'Documents', 'WechatExplorer', 'Emojis')
|
||||
this.cacheDir = path.join(os.homedir(), 'Documents', 'TraceMemo', 'Emojis')
|
||||
// Keep reading the former directory so existing sticker caches remain usable.
|
||||
this.legacyCacheDir = path.join(os.homedir(), 'Documents', 'WechatExplorer', 'Emojis')
|
||||
}
|
||||
|
||||
async resolveSticker(cdnUrl?: string, md5?: string): Promise<StickerResult> {
|
||||
@@ -69,7 +72,7 @@ export class StickerService {
|
||||
const extensions = ['.gif', '.png', '.webp', '.jpg', '.jpeg']
|
||||
const cacheDirs = [
|
||||
this.cacheDir,
|
||||
path.join(os.homedir(), 'Documents', 'WechatExplorer', 'Emojis')
|
||||
this.legacyCacheDir
|
||||
]
|
||||
for (const cacheDir of cacheDirs) {
|
||||
for (const ext of extensions) {
|
||||
@@ -128,7 +131,7 @@ export class StickerService {
|
||||
url,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 MicroMessenger WechatExplorer',
|
||||
'User-Agent': 'Mozilla/5.0 MicroMessenger TraceMemo',
|
||||
Referer: 'https://weixin.qq.com/'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -261,6 +261,7 @@ export function resolveWindowsNativeAccountRoot(
|
||||
.find((candidate) => candidate && isAsciiPath(candidate))
|
||||
if (!publicRoot || !isAsciiPath(publicRoot)) return accountRoot
|
||||
|
||||
// Preserve the legacy ASCII bridge path so existing junctions remain reusable.
|
||||
const bridgeRoot = path.join(publicRoot, 'WechatExplorer', 'path-bridges')
|
||||
const bridgePath = path.join(
|
||||
bridgeRoot,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>WechatExplorer</title>
|
||||
<title>TraceMemo(迹忆)</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/brand-icon.svg" />
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
|
||||
@@ -805,7 +805,7 @@ function App(): React.ReactElement {
|
||||
})
|
||||
setStartupProgress({
|
||||
title: '正在加载账号信息...',
|
||||
subtitle: '即将进入 WechatExplorer',
|
||||
subtitle: '即将进入 TraceMemo',
|
||||
detail: '正在读取联系人和当前账号',
|
||||
percent: 70
|
||||
})
|
||||
@@ -1813,7 +1813,7 @@ function App(): React.ReactElement {
|
||||
? autoConnectSource === 'env'
|
||||
? '检测到环境变量中的密钥'
|
||||
: '使用上次安全保存的密钥'
|
||||
: 'WechatExplorer')
|
||||
: 'TraceMemo')
|
||||
return (
|
||||
<div className={`boot-splash ${appearanceSettings.showStartupProgress ? '' : 'is-quiet'}`}>
|
||||
<div className="boot-splash-spinner" aria-hidden />
|
||||
|
||||
@@ -141,12 +141,12 @@ export function DatabaseConnectionPage({
|
||||
|
||||
return (
|
||||
<main className="database-login-page">
|
||||
<section className="database-login-brand" aria-label="WechatExplorer 产品说明">
|
||||
<section className="database-login-brand" aria-label="TraceMemo(迹忆)产品说明">
|
||||
<div className="database-login-brand-content">
|
||||
<div className="database-login-logo" aria-hidden="true">
|
||||
<LineIcon name="database" />
|
||||
</div>
|
||||
<h1>WechatExplorer</h1>
|
||||
<h1>TraceMemo(迹忆)</h1>
|
||||
<p className="database-login-tagline">让 AI 读懂你的微信</p>
|
||||
<p className="database-login-description">
|
||||
连接成功后,你可以搜索聊天记录、生成群聊日报,并按需使用 AI 分析。
|
||||
@@ -261,7 +261,7 @@ export function DatabaseConnectionPage({
|
||||
'确认下方检测结果;没有找到目录时可以手动选择。',
|
||||
'请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。',
|
||||
'开始后请按页面提示完成系统授权。',
|
||||
'正在准备连接组件,请不要关闭微信或 WechatExplorer。',
|
||||
'正在准备连接组件,请不要关闭微信或 TraceMemo。',
|
||||
'请回到微信完成登录,登录成功后再回来验证。',
|
||||
'正在验证密钥和本地数据库,请稍候。'
|
||||
][guideStep - 1]}
|
||||
@@ -567,7 +567,7 @@ export function DatabaseConnectionPage({
|
||||
<button type="button" onClick={onClearKey}>
|
||||
清除已保存密钥
|
||||
</button>
|
||||
<span>WechatExplorer</span>
|
||||
<span>TraceMemo</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -478,7 +478,7 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
|
||||
API 默认仅监听本机,并通过 Bearer Token 保护数据接口。Token 请在 API Center
|
||||
中显示或复制。关闭后 Claude / Codex 等客户端无法读取聊天数据。
|
||||
<br />
|
||||
配置文档:<code>docs/skill/wechatexplorer-reader/SKILL.md</code>
|
||||
配置文档(兼容路径):<code>docs/skill/wechatexplorer-reader/SKILL.md</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export function ExportTaskCenter({
|
||||
|
||||
const copyTaskLog = async (task: ExportTaskRecord): Promise<void> => {
|
||||
const log = [
|
||||
'WechatExplorer 导出任务日志',
|
||||
'TraceMemo 导出任务日志',
|
||||
`时间:${new Date(task.createdAt).toLocaleString('zh-CN')}`,
|
||||
`会话:${task.targetLabel}`,
|
||||
`格式:${task.format.toUpperCase()}`,
|
||||
|
||||
@@ -431,13 +431,13 @@ export function ExportWorkspace({
|
||||
|
||||
const targetPath = exportAll
|
||||
? format === 'html' && zip
|
||||
? `文稿/WechatExplorer/导出/${outputName}.zip`
|
||||
: `文稿/WechatExplorer/导出/${outputName}/`
|
||||
? `文稿/TraceMemo/导出/${outputName}.zip`
|
||||
: `文稿/TraceMemo/导出/${outputName}/`
|
||||
: format === 'html'
|
||||
? zip
|
||||
? `文稿/WechatExplorer/导出/${outputName}.zip`
|
||||
: `文稿/WechatExplorer/导出/${outputName}/`
|
||||
: `文稿/WechatExplorer/导出/${outputName}.${format === 'markdown' ? 'md' : format}`
|
||||
? `文稿/TraceMemo/导出/${outputName}.zip`
|
||||
: `文稿/TraceMemo/导出/${outputName}/`
|
||||
: `文稿/TraceMemo/导出/${outputName}.${format === 'markdown' ? 'md' : format}`
|
||||
|
||||
return (
|
||||
<div className="export-workspace">
|
||||
|
||||
@@ -26,7 +26,7 @@ interface AppShellProps {
|
||||
|
||||
function BrandLogo(): React.ReactElement {
|
||||
return (
|
||||
<div className="app-brand" title="WechatExplorer" aria-label="WechatExplorer">
|
||||
<div className="app-brand" title="TraceMemo(迹忆)" aria-label="TraceMemo(迹忆)">
|
||||
<img src={brandIcon} alt="" aria-hidden="true" />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -194,7 +194,7 @@ export function AiReportWorkspace({
|
||||
<h3>隐私说明</h3>
|
||||
<p>微信数据库和聊天记录默认从本机读取。</p>
|
||||
<p>所选内容将发送至你配置的模型服务进行处理。</p>
|
||||
<p>WechatExplorer 本身不额外保存或转发内容。</p>
|
||||
<p>TraceMemo 本身不额外保存或转发内容。</p>
|
||||
</section>
|
||||
|
||||
{generatedImage && (
|
||||
|
||||
@@ -1094,7 +1094,7 @@ export function AISearchWorkspace({
|
||||
<div className="ai-search-workspace">
|
||||
<header className="ai-search-header">
|
||||
<div>
|
||||
<span className="ai-search-kicker">WechatExplorer · LOCAL INTELLIGENCE</span>
|
||||
<span className="ai-search-kicker">TraceMemo · LOCAL INTELLIGENCE</span>
|
||||
<h1>问问你的微信</h1>
|
||||
<p>在本地聊天记录中提炼主题、结论和可追溯证据</p>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ export function AgentHubWorkspace(): React.ReactElement {
|
||||
<div className="agent-hub-workspace">
|
||||
<header className="agent-hub-header">
|
||||
<div>
|
||||
<div className="agent-hub-eyebrow">WechatExplorer</div>
|
||||
<div className="agent-hub-eyebrow">TraceMemo</div>
|
||||
<h1>Agent Hub</h1>
|
||||
<p>让微信机器人安全调用聊天数据与 AI 能力。</p>
|
||||
</div>
|
||||
@@ -193,7 +193,7 @@ export function AgentHubWorkspace(): React.ReactElement {
|
||||
<aside className="agent-hub-card agent-hub-capability-card">
|
||||
<span className="agent-hub-card-kicker">已启用能力</span>
|
||||
<h2>微信数据助手</h2>
|
||||
<p>机器人通过本机 Agent Hub 调用 WechatExplorer,不向公网暴露数据库。</p>
|
||||
<p>机器人通过本机 Agent Hub 调用 TraceMemo,不向公网暴露数据库。</p>
|
||||
<div className="agent-hub-example">
|
||||
<span>支持自然语言,可以这样问</span>
|
||||
<strong>“最近 5 条消息是谁?”</strong>
|
||||
|
||||
@@ -195,7 +195,7 @@ export function ApiRuntimePanel({
|
||||
<h3>隐私说明</h3>
|
||||
<p>
|
||||
{localOnly
|
||||
? '本地 API 默认监听 127.0.0.1。WechatExplorer 不会通过该接口自动把聊天内容发送到云端。外部 Agent 是否调用第三方模型,取决于其自身配置。'
|
||||
? '本地 API 默认监听 127.0.0.1。TraceMemo 不会通过该接口自动把聊天内容发送到云端。外部 Agent 是否调用第三方模型,取决于其自身配置。'
|
||||
: '当前服务并非仅本机访问。请确认局域网环境可信;API Token 不等同于公网安全防护。'}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -37,13 +37,13 @@ export function ReaderSkillOverview({
|
||||
<div className="api-workspace-heading">
|
||||
<div>
|
||||
<div className="api-title-line">
|
||||
<h1>WechatExplorer Reader</h1>
|
||||
<h1>TraceMemo Reader</h1>
|
||||
<span className={`api-skill-status ${skill?.available ? 'ready' : 'error'}`}>
|
||||
{skill?.available ? '已安装' : '文件不可用'}
|
||||
</span>
|
||||
<span className="api-version">{skill?.version || 'v1.0'}</span>
|
||||
</div>
|
||||
<p>通过本地 HTTP API 读取 WechatExplorer 已解锁的微信聊天数据</p>
|
||||
<p>通过本地 HTTP API 读取 TraceMemo 已解锁的微信聊天数据</p>
|
||||
</div>
|
||||
<div className="api-header-actions">
|
||||
<button type="button" onClick={onPreview} disabled={!skill?.available}>
|
||||
@@ -79,9 +79,9 @@ export function ReaderSkillOverview({
|
||||
<div className="api-introduction">
|
||||
<h2>能力简介</h2>
|
||||
<p>
|
||||
WechatExplorer Reader 让本地 AI Agent
|
||||
TraceMemo Reader 让本地 AI Agent
|
||||
在用户授权和本地服务运行时,读取联系人、群聊、聊天记录和群成员信息,并调用内置模板导出群聊日报。聊天数据由
|
||||
WechatExplorer 本地服务提供,不会由该 API 自动上传到其他服务器。
|
||||
TraceMemo 本地服务提供,不会由该 API 自动上传到其他服务器。
|
||||
</p>
|
||||
<div className="api-flow">
|
||||
<span>AI Agent</span>
|
||||
|
||||
@@ -18,6 +18,10 @@ export function SkillDetails({
|
||||
<dl>
|
||||
<div>
|
||||
<dt>名称</dt>
|
||||
<dd>TraceMemo Reader</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>兼容标识</dt>
|
||||
<dd>wechatexplorer-reader</dd>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export function SkillInstallFlow({
|
||||
<section className={service?.running && dbReady ? 'done' : 'active'}>
|
||||
<b>1</b>
|
||||
<div>
|
||||
<h3>确认 WechatExplorer 已就绪</h3>
|
||||
<h3>确认 TraceMemo 已就绪</h3>
|
||||
<p>
|
||||
本地 API:{service?.running ? '运行中' : '已停止'} · {address}
|
||||
</p>
|
||||
|
||||
@@ -16,12 +16,12 @@ export function SkillPreviewDialog({
|
||||
className="api-markdown-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="WechatExplorer Reader Skill 预览"
|
||||
aria-label="TraceMemo Reader Skill 预览"
|
||||
>
|
||||
<div>
|
||||
<header>
|
||||
<div>
|
||||
<strong>WechatExplorer Reader</strong>
|
||||
<strong>TraceMemo Reader</strong>
|
||||
<span>{version || 'v1.0'}</span>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const REPORT_REQUEST_PRESET = JSON.stringify(
|
||||
activeUsers: 0,
|
||||
timeSpan: '00:00-23:59',
|
||||
generatedAt: '2026-07-13 22:00',
|
||||
recordNote: '本日报由 WechatExplorer 自动生成',
|
||||
recordNote: '本日报由 TraceMemo 自动生成',
|
||||
footerNote: '',
|
||||
heroParticipants: [],
|
||||
avatars: {},
|
||||
@@ -37,7 +37,7 @@ export const AGENT_GROUP_REPORT_PRESET = JSON.stringify(
|
||||
)
|
||||
|
||||
export const AGENT_SEND_PRESET = JSON.stringify(
|
||||
{ to: '', text: 'WechatExplorer Agent Hub 发送测试' },
|
||||
{ to: '', text: 'TraceMemo Agent Hub 发送测试' },
|
||||
null,
|
||||
2
|
||||
)
|
||||
|
||||
@@ -7,13 +7,13 @@ function requestHost(host: string): string {
|
||||
function opening(target: AgentInstallTarget): string {
|
||||
switch (target) {
|
||||
case 'codex':
|
||||
return '请将本地目录中的 WechatExplorer Reader Skill 安装到当前 Codex 项目或用户 Skill 目录:'
|
||||
return '请将本地目录中的 TraceMemo Reader Skill 安装到当前 Codex 项目或用户 Skill 目录:'
|
||||
case 'claude-code':
|
||||
return '请安装以下本地 WechatExplorer Reader Skill,并按照 SKILL.md 调用本地 HTTP API:'
|
||||
return '请安装以下本地 TraceMemo Reader Skill,并按照 SKILL.md 调用本地 HTTP API:'
|
||||
case 'openclaw':
|
||||
return '请将以下本地目录作为 WechatExplorer Reader Skill 安装,并阅读其中的 SKILL.md:'
|
||||
return '请将以下本地目录作为 TraceMemo Reader Skill 安装,并阅读其中的 SKILL.md:'
|
||||
default:
|
||||
return '请读取并安装以下 WechatExplorer Reader Skill:'
|
||||
return '请读取并安装以下 TraceMemo Reader Skill:'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ export function buildSkillInstallInstruction({
|
||||
const sourceText =
|
||||
source.type === 'local'
|
||||
? `${opening(target)}\n\n${source.directoryPath}\n\n请先阅读该目录中的 SKILL.md,然后调用:`
|
||||
: `请从以下地址安装 WechatExplorer Reader Skill:\n\n${source.installUrl}\n\n阅读 SKILL.md 后,调用:`
|
||||
return `${sourceText}\n\n${healthUrl}\n\n先调用公开的 health 接口验证服务。然后请用户在 WechatExplorer → API Center → API Token 中点击“复制 Token”,并把 Token 配置为 Agent 本机环境变量 WECHATEXPLORER_API_TOKEN。读取联系人、会话或聊天记录时,必须发送 Authorization: Bearer $WECHATEXPLORER_API_TOKEN。此服务是 Local HTTP API,不是 MCP Server。安装完成后告诉我验证结果。`
|
||||
: `请从以下地址安装 TraceMemo Reader Skill:\n\n${source.installUrl}\n\n阅读 SKILL.md 后,调用:`
|
||||
return `${sourceText}\n\n${healthUrl}\n\n先调用公开的 health 接口验证服务。然后请用户在 TraceMemo → API Center → API Token 中点击“复制 Token”,并把 Token 配置为 Agent 本机环境变量 WECHATEXPLORER_API_TOKEN。读取联系人、会话或聊天记录时,必须发送 Authorization: Bearer $WECHATEXPLORER_API_TOKEN。此服务是 Local HTTP API,不是 MCP Server。安装完成后告诉我验证结果。`
|
||||
}
|
||||
|
||||
export function buildSkillVerificationPrompt(): string {
|
||||
return '请检查 WechatExplorer Reader 是否已连接,然后列出最近 5 个微信会话。'
|
||||
return '请检查 TraceMemo Reader 是否已连接,然后列出最近 5 个微信会话。'
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function AIImageUnderstandingTest({
|
||||
</label>
|
||||
|
||||
<p className="ai-vision-privacy">
|
||||
图片只会发送给你配置的 AI 服务,不会上传到 WechatExplorer 的其他服务器,也不会写入本地缓存。
|
||||
图片只会发送给你配置的 AI 服务,不会上传到 TraceMemo 的其他服务器,也不会写入本地缓存。
|
||||
</p>
|
||||
|
||||
{state.error ? <p className="ai-vision-error">{state.error}</p> : null}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function DatabaseKeyAutoDetect({
|
||||
<div className="database-key-auto-heading">
|
||||
<div>
|
||||
<strong>Windows 自动获取</strong>
|
||||
<p>WechatExplorer 可在微信桌面端正在运行时,通过本机内存扫描尝试获取数据库密钥。</p>
|
||||
<p>TraceMemo 可在微信桌面端正在运行时,通过本机内存扫描尝试获取数据库密钥。</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -63,7 +63,7 @@ export function DatabaseKeyDangerZone({
|
||||
>
|
||||
<h2 id="database-key-confirm-title">确认清除数据库密钥?</h2>
|
||||
<p>
|
||||
清除后 WechatExplorer
|
||||
清除后 TraceMemo
|
||||
将暂时无法读取聊天记录,需要重新输入或获取密钥。该操作不会删除微信原始数据。
|
||||
</p>
|
||||
<div>
|
||||
@@ -99,7 +99,7 @@ export function DatabaseKeyDangerZone({
|
||||
>
|
||||
<h2 id="database-key-return-confirm-title">返回登录界面?</h2>
|
||||
<p>
|
||||
WechatExplorer
|
||||
TraceMemo
|
||||
将断开当前数据库连接并回到密钥输入界面。已保存的数据库密钥和微信原始数据不会被删除。
|
||||
</p>
|
||||
<div>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function buildDatabaseKeyDiagnostics(
|
||||
): string {
|
||||
const validation = state.validation
|
||||
return [
|
||||
'WechatExplorer 数据库密钥诊断',
|
||||
'TraceMemo 数据库密钥诊断',
|
||||
`已保存: ${state.saved ? '是' : '否'}`,
|
||||
`已验证: ${validation?.success ? '是' : '否'}`,
|
||||
`密钥长度合法: ${isDatabaseKeyFormatValid(input) ? '是' : '否'}`,
|
||||
|
||||
@@ -54,13 +54,13 @@ export function AboutPage({ onNotice }: { onNotice: (message: string) => void })
|
||||
<header className="settings-page-header">
|
||||
<div>
|
||||
<h1>关于</h1>
|
||||
<p>WechatExplorer 本地微信聊天记录工作台。</p>
|
||||
<p>TraceMemo(迹忆)本地优先、可追溯的 AI 微信知识与分析工作台。</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="settings-page-scroll">
|
||||
<div className="settings-page-content">
|
||||
<section className="settings-card about-identity-card">
|
||||
<div><span className="settings-card-kicker">当前版本</span><strong>WechatExplorer</strong><small>v{update.currentVersion}</small></div>
|
||||
<div><span className="settings-card-kicker">当前版本</span><strong>TraceMemo(迹忆)</strong><small>v{update.currentVersion}</small></div>
|
||||
<a href={REPOSITORY_URL} target="_blank" rel="noreferrer">GitHub 仓库</a>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export function DatabaseKeyPage({
|
||||
<div>
|
||||
<strong>密钥仅保存在本机</strong>
|
||||
<p>
|
||||
WechatExplorer
|
||||
TraceMemo
|
||||
使用数据库密钥读取本机微信数据库。密钥通过系统安全存储加密保存,不会写入普通日志,也不会上传到服务器。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@ export function ImageDecryptionPage({
|
||||
</svg>
|
||||
<div>
|
||||
<strong>图片仅在本机解析</strong>
|
||||
<p>WechatExplorer 不会上传您的微信图片。所有图片解析和缓存处理均在本地完成。</p>
|
||||
<p>TraceMemo 不会上传您的微信图片。所有图片解析和缓存处理均在本地完成。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -658,7 +658,7 @@ export const buildGroupReportFacts = async (
|
||||
mediaMessageCount: imageCount + voiceCount + stickerCount,
|
||||
timeSpan,
|
||||
generatedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
|
||||
recordNote: `基于 WechatExplorer 已加载的 ${transcriptRows.length} 条记录`,
|
||||
recordNote: `基于 TraceMemo 已加载的 ${transcriptRows.length} 条记录`,
|
||||
footerNote: '基于已读取聊天记录生成;图片、表情等未解析内容默认只按类型与上下文参与日报。',
|
||||
heroParticipants: topSpeakers.slice(0, 4).map((speaker) => speaker.name),
|
||||
avatars,
|
||||
|
||||
@@ -4,7 +4,7 @@ export function buildSafeDiagnosticSummary(
|
||||
environment: Omit<DatabaseKeyEnvironment, 'diagnosticSummary'>
|
||||
): string {
|
||||
return [
|
||||
`WechatExplorer: ${environment.appVersion}`,
|
||||
`TraceMemo: ${environment.appVersion}`,
|
||||
`操作系统: ${environment.osVersion}`,
|
||||
`微信客户端: ${environment.wechatVersion}`,
|
||||
`数据结构: ${environment.dataStructureVersion}`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/shared/image-insight.ts
|
||||
// ImageInsight:微信图片的 AI 理解结果持久化数据结构
|
||||
// 与 WechatExplorer 整体 AI 知识平台定位一致 — 图片理解结果可索引、可缓存、可复用。
|
||||
// 与 TraceMemo 整体 AI 知识平台定位一致 — 图片理解结果可索引、可缓存、可复用。
|
||||
|
||||
export type ImageCategory =
|
||||
| 'screenshot' // 截图
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const WINDOWS_VC_RUNTIME_DOWNLOAD_URL = 'https://aka.ms/vc14/vc_redist.x64.exe'
|
||||
|
||||
export const WINDOWS_VC_RUNTIME_ERROR_MESSAGE = `当前 Windows 缺少 Microsoft Visual C++ 2015-2022 x64 运行库,无法加载微信数据库组件。请下载安装后重新启动 WechatExplorer:${WINDOWS_VC_RUNTIME_DOWNLOAD_URL}`
|
||||
export const WINDOWS_VC_RUNTIME_ERROR_MESSAGE = `当前 Windows 缺少 Microsoft Visual C++ 2015-2022 x64 运行库,无法加载微信数据库组件。请下载安装后重新启动 TraceMemo:${WINDOWS_VC_RUNTIME_DOWNLOAD_URL}`
|
||||
|
||||
const VC_RUNTIME_LIBRARY_PATTERN =
|
||||
/(?:vcruntime140(?:_1)?\.dll|msvcp140(?:_[12])?\.dll|concrt140\.dll|ucrtbase\.dll|api-ms-win-crt)/i
|
||||
|
||||
@@ -23,7 +23,7 @@ function renderPage(
|
||||
wechatVersion: '4.1.9.57',
|
||||
dataStructureVersion: '微信 4.x(WCDB)',
|
||||
dataDirectoryDetected: true,
|
||||
diagnosticSummary: 'WechatExplorer: v2.1.6',
|
||||
diagnosticSummary: 'TraceMemo: v2.1.6',
|
||||
autoDetectSupported: true,
|
||||
wechatRunning: true,
|
||||
accountIdentified: false,
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('ImageTestSection', () => {
|
||||
fileFound: true,
|
||||
decrypted: false,
|
||||
readable: false,
|
||||
diagnosticLog: 'WechatExplorer 图片解析测试日志(已脱敏)'
|
||||
diagnosticLog: 'TraceMemo 图片解析测试日志(已脱敏)'
|
||||
}
|
||||
}}
|
||||
batchTest={emptyBatchTest}
|
||||
|
||||
@@ -9,7 +9,7 @@ test('APP-01 first launch renders a usable connection screen without uncaught er
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await expect(fixture.page.getByRole('heading', { name: 'WechatExplorer' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('main')).not.toBeEmpty()
|
||||
expect(pageErrors).toEqual([])
|
||||
} finally {
|
||||
|
||||
@@ -117,7 +117,7 @@ handle('key:getEnvironment', () => ({
|
||||
wechatVersion: '4.1.9.57',
|
||||
dataStructureVersion: settings.dbRoot === 'fixture-account' ? '微信 4.x(WCDB)' : '未检测到',
|
||||
dataDirectoryDetected: settings.dbRoot === 'fixture-account',
|
||||
diagnosticSummary: 'WechatExplorer: v2.1.6\n数据目录: 已检测到',
|
||||
diagnosticSummary: 'TraceMemo: v2.1.6\n数据目录: 已检测到',
|
||||
autoDetectSupported: true,
|
||||
wechatRunning: true,
|
||||
accountIdentified: connected,
|
||||
@@ -371,7 +371,7 @@ handle('image:testConfig', () => ({
|
||||
fileFound: true,
|
||||
decrypted: true,
|
||||
readable: true,
|
||||
diagnosticLog: 'WechatExplorer 图片解析测试日志(已脱敏)\n测试结果:成功(SUCCESS)'
|
||||
diagnosticLog: 'TraceMemo 图片解析测试日志(已脱敏)\n测试结果:成功(SUCCESS)'
|
||||
}))
|
||||
handle('image:clearConfig', () => ({ success: true }))
|
||||
handle('image:getDecoderStatus', () => ({
|
||||
|
||||
@@ -14,7 +14,7 @@ test('NAV-01 login page visual @visual', async () => {
|
||||
const fixture = await launchTestApp({ mode: 'disconnected' })
|
||||
try {
|
||||
await fixture.page.setViewportSize(visualViewport)
|
||||
await expect(fixture.page.getByRole('heading', { name: 'WechatExplorer' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible()
|
||||
await expect(fixture.page).toHaveScreenshot('login-page.png', {
|
||||
animations: 'disabled',
|
||||
caret: 'hide'
|
||||
|
||||
@@ -1138,7 +1138,7 @@ describe('media export flow', () => {
|
||||
|
||||
expect(result).toEqual({ success: false, error: '已取消' })
|
||||
expect(state.exportReads).toEqual(['cancel-1'])
|
||||
const outputDir = join(state.documents, 'WechatExplorer', '导出', 'cancelled-all-conversations')
|
||||
const outputDir = join(state.documents, 'TraceMemo', '导出', 'cancelled-all-conversations')
|
||||
expect(existsSync(join(outputDir, '联系人', '联系人一', 'index.html'))).toBe(true)
|
||||
expect(existsSync(join(outputDir, '联系人', '联系人二', 'index.html'))).toBe(false)
|
||||
const partialManifest = JSON.parse(readFileSync(join(outputDir, '导出清单.json'), 'utf8')) as {
|
||||
@@ -1195,7 +1195,7 @@ describe('media export flow', () => {
|
||||
expect(firstSize).toBeGreaterThan(0)
|
||||
expect(readFileSync(second.outputPath!).subarray(0, 2).toString()).toBe('PK')
|
||||
const entries = execFileSync('unzip', ['-Z1', second.outputPath!], { encoding: 'utf8' })
|
||||
const htmlPath = join(state.documents, 'WechatExplorer', '导出', 'zip-fixture', 'index.html')
|
||||
const htmlPath = join(state.documents, 'TraceMemo', '导出', 'zip-fixture', 'index.html')
|
||||
const archive = readArchive(htmlPath)
|
||||
expect(entries).toContain('zip-fixture/index.html')
|
||||
expect(entries).toContain('zip-fixture/data/messages.js')
|
||||
@@ -1209,7 +1209,7 @@ describe('media export flow', () => {
|
||||
true
|
||||
)
|
||||
expect(
|
||||
readdirSync(join(state.documents, 'WechatExplorer', '导出')).some((name) =>
|
||||
readdirSync(join(state.documents, 'TraceMemo', '导出')).some((name) =>
|
||||
name.startsWith('zip-fixture.zip.tmp-')
|
||||
)
|
||||
).toBe(false)
|
||||
@@ -1250,7 +1250,7 @@ describe('media export flow', () => {
|
||||
expect(cancelled).toEqual({ success: false, error: '已取消' })
|
||||
expect(readFileSync(first.outputPath!)).toEqual(completeZip)
|
||||
expect(
|
||||
readdirSync(join(state.documents, 'WechatExplorer', '导出')).some((name) =>
|
||||
readdirSync(join(state.documents, 'TraceMemo', '导出')).some((name) =>
|
||||
name.startsWith('zip-cancel-fixture.zip.tmp-')
|
||||
)
|
||||
).toBe(false)
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('Local API authentication', () => {
|
||||
const health = await fetch(`${baseUrl(handle)}/api/v1/health`)
|
||||
expect(health.status).toBe(200)
|
||||
const healthBody = await health.json()
|
||||
expect(healthBody).toMatchObject({ ok: true, service: 'WechatExplorer Reader' })
|
||||
expect(healthBody).toMatchObject({ ok: true, service: 'TraceMemo Reader' })
|
||||
expect(JSON.stringify(healthBody)).not.toMatch(
|
||||
/token|authorization|wxid|databasePath|provider/i
|
||||
)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { chooseUserDataRoot, getUserDataRoots, hasValidUserAssets } from '../../src/main/app-data-paths'
|
||||
|
||||
let root = ''
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(path.join(os.tmpdir(), 'tracememo-app-data-paths-'))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function fixtureRoots(): ReturnType<typeof getUserDataRoots> {
|
||||
return getUserDataRoots(path.join(root, 'Application Support'))
|
||||
}
|
||||
|
||||
function writeMarker(filePath: string): void {
|
||||
mkdirSync(path.dirname(filePath), { recursive: true })
|
||||
writeFileSync(filePath, 'fixture')
|
||||
}
|
||||
|
||||
describe('app data compatibility root selection', () => {
|
||||
it('chooses the new root for a clean install', () => {
|
||||
const roots = fixtureRoots()
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(false)
|
||||
expect(hasValidUserAssets(roots.current)).toBe(false)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('ignores runtime-only Chromium files', () => {
|
||||
const roots = fixtureRoots()
|
||||
mkdirSync(roots.current, { recursive: true })
|
||||
writeMarker(path.join(roots.current, 'Local State'))
|
||||
mkdirSync(path.join(roots.current, 'Cache'), { recursive: true })
|
||||
expect(hasValidUserAssets(roots.current)).toBe(false)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('prefers a legacy root containing user settings', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'settings.json'))
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
})
|
||||
|
||||
it('uses a current root when only it contains valid user assets', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.current, 'ai-providers.json'))
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||
})
|
||||
|
||||
it('prefers legacy without modifying a valid current root', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'reports', '2026', '08', 'report.json'))
|
||||
writeMarker(path.join(roots.current, 'local-api-token.bin'))
|
||||
const currentToken = path.join(roots.current, 'local-api-token.bin')
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
expect(hasValidUserAssets(roots.current)).toBe(true)
|
||||
expect(readFileSync(currentToken, 'utf8')).toBe('fixture')
|
||||
})
|
||||
|
||||
it('recognizes Knowledge only when a non-empty knowledge.sqlite exists', () => {
|
||||
const roots = fixtureRoots()
|
||||
mkdirSync(path.join(roots.legacy, 'knowledge', 'account-hash'), { recursive: true })
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(false)
|
||||
writeMarker(path.join(roots.legacy, 'knowledge', 'account-hash', 'knowledge.sqlite'))
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(true)
|
||||
})
|
||||
|
||||
it('honors WXE_USER_DATA isolation before normal root selection', () => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, 'settings.json'))
|
||||
const isolated = path.join(root, 'isolated-user-data')
|
||||
expect(chooseUserDataRoot({ ...roots, isolated })).toBe(isolated)
|
||||
})
|
||||
|
||||
it.each([
|
||||
'ai-provider-keys.bin',
|
||||
'local-api-token.bin',
|
||||
'wechat-image-keys.bin',
|
||||
path.join('database-keys', 'account.bin'),
|
||||
path.join('recall-archive', 'entry.json'),
|
||||
path.join('Local Storage', 'leveldb', '000001.ldb')
|
||||
])('recognizes %s as a persistent user asset', (relativePath) => {
|
||||
const roots = fixtureRoots()
|
||||
writeMarker(path.join(roots.legacy, relativePath))
|
||||
expect(hasValidUserAssets(roots.legacy)).toBe(true)
|
||||
expect(chooseUserDataRoot(roots)).toBe(roots.legacy)
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,7 @@ describe('connection diagnostics', () => {
|
||||
encryptionAvailable: true
|
||||
})
|
||||
|
||||
expect(summary).toContain('WechatExplorer: v2.1.6')
|
||||
expect(summary).toContain('TraceMemo: v2.1.6')
|
||||
expect(summary).toContain('微信客户端: 4.1.9.57')
|
||||
expect(summary).not.toContain('0123456789abcdef')
|
||||
expect(summary).not.toContain('C:\\Users\\fixture\\xwechat_files\\wxid_secret')
|
||||
|
||||
@@ -45,11 +45,11 @@ describe('production runtime packaging', () => {
|
||||
expect(config).toContain('docs/skill/wechatexplorer-reader')
|
||||
expect(config).toContain('to: skill/wechatexplorer-reader')
|
||||
expect(() => validateReaderSkillRuntime(resources)).toThrow(
|
||||
/Missing bundled WechatExplorer Reader Skill/
|
||||
/Missing bundled TraceMemo Reader Skill/
|
||||
)
|
||||
|
||||
mkdirSync(dirname(skillPath), { recursive: true })
|
||||
writeFileSync(skillPath, '# WechatExplorer Reader\n')
|
||||
writeFileSync(skillPath, '# TraceMemo Reader\n')
|
||||
expect(validateReaderSkillRuntime(resources)).toBe(skillPath)
|
||||
})
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function environment(root: string, packaged: boolean) {
|
||||
|
||||
function writeSkill(filePath: string): void {
|
||||
mkdirSync(dirname(filePath), { recursive: true })
|
||||
writeFileSync(filePath, '# WechatExplorer Reader\n', 'utf8')
|
||||
writeFileSync(filePath, '# TraceMemo Reader\n', 'utf8')
|
||||
}
|
||||
|
||||
describe('Reader Skill resource resolution', () => {
|
||||
|
||||
Reference in New Issue
Block a user