
Wx Cli
- 2.1k installs
- 4k repo stars
- Updated June 11, 2026
- jackwener/wx-cli
wx-cli is an agent skill that wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。.
About
查微信聊天记录 微信消息历史 微信联系人 微信群成员 微信群昵称 群名片 微信收藏 wechat history messages contacts wx cli 帮我看看微信里 搜索微信消息 macOS Apple Silicon Intel 或 Linux 微信桌面版 4 x 已安装并登录 Node js 14 npm 安装方式 或 curl shell 安装方式 首次 wx init 需要 sudo 内存扫描提取密钥 bash npm install g jackwener wx cli bash curl fsSL https raw githubusercontent com jackwener wx cli main install sh bash The wx cli skill documents workflows prerequisites and usage patterns grounded in its repository SKILL md Agents should follow the documented steps respect safety and permission notes and cite only capabilities described in the source It triggers on phrases matching the skill description and integrates with the agent toolchain for the tasks outlined in the documentation
- description: "wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。"
- - wechat history / messages / contacts
- - macOS(Apple Silicon / Intel)或 Linux
- See SKILL.md for wx-cli operational details.
- See SKILL.md for wx-cli operational details.
Wx Cli by the numbers
- 2,135 all-time installs (skills.sh)
- Ranked #497 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
wx-cli capabilities & compatibility
- Capabilities
- description: "wx cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等 · wechat history / messages / contacts · macos(apple silicon / intel)或 linux · see skill.md for wx cli operational details.
- Use cases
- orchestration
What wx-cli says it does
description: "wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。"
- wechat history / messages / contacts
- macOS(Apple Silicon / Intel)或 Linux
npx skills add https://github.com/jackwener/wx-cli --skill wx-cliAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 4k |
| Security audit | 0 / 3 scanners passed |
| Last updated | June 11, 2026 |
| Repository | jackwener/wx-cli ↗ |
What does wx-cli help with and when should an agent load it?
wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。
Who is it for?
Developers using wx-cli as documented in the skill repository.
Skip if: Skip when the task falls outside the wx-cli documented scope.
When should I use this skill?
wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。
What you get
Agent actions aligned with the wx-cli SKILL.md workflow and documented deliverables.
- WeChat message search results
- Contact and group member listings
- Favorites query output
By the numbers
- Requires Node.js 14 or newer for npm installation
- Targets WeChat desktop 4.x on macOS and Linux
Files
wx-cli
Triggers
- 查微信聊天记录
- 微信消息历史
- 微信联系人
- 微信群成员
- 微信群昵称 / 群名片
- 微信收藏
- wechat history / messages / contacts
- wx-cli
- 帮我看看微信里
- 搜索微信消息
Prerequisites
- macOS(Apple Silicon / Intel)或 Linux
- 微信桌面版 4.x 已安装并登录
- Node.js >= 14(npm 安装方式)或 curl(shell 安装方式)
- 首次
wx init需要sudo(内存扫描提取密钥)
---
安装
方式一:npm(推荐)
npm install -g @jackwener/wx-cli方式二:curl
curl -fsSL https://raw.githubusercontent.com/jackwener/wx-cli/main/install.sh | bash安装后验证:
wx --version---
初始化(首次使用,只需一次)
macOS(必须按顺序执行)
第一步:对 WeChat 重新签名(只需做一次,WeChat 更新后需重做)
codesign --force --deep --sign - /Applications/WeChat.app如果报错 signature in use 或某个 dylib 签名损坏,先修复再签名:
codesign --remove-signature "/Applications/WeChat.app/Contents/Frameworks/vlc_plugins/librtp_mpeg4_plugin.dylib"
codesign --force --deep --sign - /Applications/WeChat.app第二步:清理 WeChat 在 macOS TCC 隐私数据库里的旧授权记录(重签名后必做)
macOS TCC 按 bundle id + csreq 联合校验权限;csreq 编码自代码签名。重签名后旧 csreq 和新签名不再匹配,旧授权记录会 silent 失效(System Settings 仍把开关画成"已允许",运行时实际拒绝)。把 WeChat 在 TCC 里的旧记录抹掉,让 macOS 在下次微信请求权限时按新签名重新生成 csreq:
tccutil reset ScreenCapture com.tencent.xinWeChat # 截图 / 屏幕共享
tccutil reset Camera com.tencent.xinWeChat # 视频通话 / 扫码
tccutil reset Microphone com.tencent.xinWeChat # 语音消息 / 通话
tccutil reset AppleEvents com.tencent.xinWeChat # 自动化 / 输入法
tccutil reset AddressBook com.tencent.xinWeChat # 通讯录
tccutil reset SystemPolicyDocumentsFolder com.tencent.xinWeChat
tccutil reset SystemPolicyDownloadsFolder com.tencent.xinWeChat
tccutil reset SystemPolicyDesktopFolder com.tencent.xinWeChattccutil 对没有授权过的 service 会报 "No such bundle identifier",是 no-op,不影响其他 service 的 reset。
第三步:重启 WeChat
killall WeChat && open /Applications/WeChat.app
# 等待微信完全登录后再继续之后微信触发权限请求时按 GUI 提示重新允许即可。在 macOS 26 上,把 WeChat 加进 隐私与安全 → 录屏与系统录音 的上半区,不要只勾下半区的"仅系统录音"——后者不能授予截图权限。
第四步:初始化
sudo wx initLinux
sudo wx initwx init 会自动: 1. 检测微信数据目录 2. 扫描进程内存,提取所有数据库密钥 3. 写入 ~/.wx-cli/config.json
初始化完成后,后续所有命令无需 sudo,daemon 在首次调用时自动启动。
---
命令速查
所有命令默认输出 YAML,更省 token & 易读;--json 可切换为 JSON(方便 jq 处理等)。
会话与消息
# 最近 20 个会话
wx sessions
# 有未读消息的会话
wx unread
# 只看真人(私聊 + 群聊)的未读,过滤公众号与折叠入口
wx unread --filter private,group
# 上次检查后的新消息(增量)
wx new-messages
wx new-messages --json # JSON 输出,适合 agent 解析
# 聊天记录(支持昵称/备注名)
wx history "张三"
wx history "张三" -n 2000
wx history "AI群" --since 2026-04-01 --until 2026-04-15 -n 100
# 全库搜索
wx search "关键词"
wx search "关键词" -n 500
wx search "会议" --in "工作群" --since 2026-01-01history / search / export 都支持 -n / --limit 指定返回条数。默认值只是为了避免一次输出过多,不是硬上限。
sessions / unread / history / new-messages / stats 的输出都带 chat_type 字段,agent 可据此分流:
| 取值 | 含义 | username 特征 |
|---|---|---|
private | 真人私聊 | wxid_* 或自定义短号 |
group | 群聊 | *@chatroom |
official_account | 公众号 / 订阅号 / 服务号 / 系统通知 | gh_*、biz_*、mphelper、qqsafe、@opencustomerservicemsg |
folded | 折叠入口(订阅号折叠、折叠群聊的聚合条目) | brandsessionholder、@placeholder_foldgroup |
wx unread --filter 支持 private / group / official / folded / all,逗号分隔多选。默认 all。
群聊消息里的 last_sender、sender 和 stats.top_senders 会优先显示群昵称(群名片)。如果本地数据库没有群昵称,再回退到联系人备注、微信昵称或 username。
history / search / new-messages / attachments 和 stats.top_senders 在群上下文里同时输出稳定身份三件套:sender_username(稳定 wxid,用来区分同名成员)/ sender_contact_display(备注 > 昵称 > wxid 兜底)/ sender_group_nickname(群名片,等价于 sender 的来源,免去再做字符串解析)。当 wxid 解析不到时,这三字段不会输出,避免空字符串污染下游过滤。
sessions / unread / history / search / new-messages / stats / attachments 的 stdout 现在统一是 wrapper:
{
"messages": [...],
"meta": {
"status": "ok",
"unknown_shards": [],
"chat_latest_timestamp": 1715750400,
"chat_latest_db": "message/message_2.db",
"session_last_timestamp": 1715760000
}
}其中:
status = possibly_stale_unknown_shards:磁盘上出现 daemon 不认识的新message_N.db,先跑wx init --forcestatus = possibly_stale:session.db记录的最新时间明显领先于本次查到的最新消息,结果可能漏消息status = windowed:这次查询本来就是窗口化/过滤后的局部视图,不应把它当作"全量最新状态"--with-meta:额外返回per_shard_latest/cache_mode_per_shard--debug-source:在--with-meta基础上再暴露真实shard_paths
引用消息(appmsg type=57)在 history / search / new-messages 输出里会展开为两行:第一行是当前回复,第二行以 ↳ 开头显示被引用原文,例如:
[引用] 当前回复
↳ 发送者: 被引用内容--type link / --type file 会覆盖微信 appmsg 的链接、文件、合并聊天记录和引用消息等变体;search --type link 也会匹配解压并格式化后的引用原文。
联系人与群组
# 联系人列表 / 搜索
wx contacts
wx contacts --query "李"
# 群成员列表
wx members "AI交流群"wx members --json 每个成员包含:
username:微信内部 usernamedisplay:推荐展示名,优先使用群昵称contact_display:联系人备注或微信昵称group_nickname:群昵称;没有记录时为空字符串is_owner:是否群主
Agent 展示群成员时优先用 display。需要区分群昵称和联系人名时,再读取 group_nickname 与 contact_display。
朋友圈(SNS)
三个命令,作用各不同:
# 1) 互动通知(点赞 / 评论,默认仅未读)
wx sns-notifications
wx sns-notifications --include-read --since 2026-04-01 -n 100
# 2) 时间线:浏览本地缓存的朋友圈帖子
wx sns-feed # 近 20 条
wx sns-feed --user "张三" # 只看某人
wx sns-feed --since 2026-04-01 --until 2026-04-18 -n 100
# 3) 全文搜索:在正文里找关键词
wx sns-search "关键词"
wx sns-search "婚礼" --user "李四" --since 2023-01-01 -n 50字段区分:
sns-notifications返回"通知"条目:type(like/comment)、from_nickname、content(评论正文,点赞为空)、feed_preview+feed_author(对应的原帖)sns-feed/sns-search返回"帖子"条目:author、content(朋友圈正文)、media、media_count(图片/视频数)、location、timestamp;media字段含每张图的 url/thumb/key/token/md5/enc_idx/size,供下游做图片代理或离线渲染。media_count = media.len(),按 DOM 解析的合法<media>子节点计数(malformed XML 返回 0)
只保存你本地刷到过的朋友圈(微信 app 按需下载)。没刷到过的帖子不在本地,任何命令都拿不到。
公众号文章
公众号的文章推送存在独立的 biz_message_*.db 分片,与普通 message_0.db 分开:
# 最近 50 篇(默认)
wx biz-articles
# 更多
wx biz-articles -n 200
# 限定公众号(名称模糊匹配 display name / username)
wx biz-articles --account "返朴"
# 时间范围(YYYY-MM-DD,发布时间,非接收时间)
wx biz-articles --since 2026-05-01 --until 2026-05-10
# 仅有未读消息的公众号,每号取最新 1 篇(适合"今天有什么新推送"扫描)
wx biz-articles --unread
wx biz-articles --unread --account "Datawhale" # 与 --account 取交集
# 下游消费:拿 URL 做内容抓取
wx biz-articles --since 2026-05-10 --json | jq '.[].url'每条返回的字段:account / account_username(gh_*)/ title / url(mp.weixin.qq.com 链接)/ digest / cover_url / time + timestamp(文章发布时间)/ recv_time_str + recv_time(微信接收推送的时间)。多图文推送会展开为多行。
附件提取(图片)
聊天里的图片本体在 xwechat_files/<wxid>/msg/attach/... 下加密存储(.dat),需要按消息所在 message_resource.db 的 md5 + 平台相关 image key 才能解码。两步走:
# 1) 先列出图片附件,拿到不透明的 attachment_id
wx attachments "张三"
wx attachments "AI群" --kind image -n 100
wx attachments "AI群" --since 2026-04-01 --until 2026-04-15
# 2) 用 attachment_id 把单个资源解密写到指定路径
wx extract <attachment_id> -o ~/Desktop/photo.jpg
wx extract <attachment_id> -o /tmp/x.jpg --overwriteattachments 输出每条带:attachment_id / kind(当前固定 image)/ type / local_id / timestamp / time,群聊里另带 sender 和稳定身份三件套(同上文)。命令名保留成 attachments 是为了后续扩到其他附件类型时不 break CLI。
extract 报告里带:md5 / dat_path / dat_size / output / output_size / format(实际识别出的图片格式:jpg / png / gif / webp / hevc 等)/ decoder(实际选用的解码器:legacy_xor / v1_aes / v2)。
支持的解码档位:
- legacy XOR:早期单字节 XOR,无 magic(按文件首字节探测格式自动反推)
- V1 fixed-AES(
07 08 V1 08 07):AES-128-ECB + 固定 keycfcd208495d565ef - V2 AES + XOR(
07 08 V2 08 07):AES-128-ECB + raw + XOR;AES key 平台派生
V2 image key 提取(macOS / Windows 自动;Linux 暂不支持):
- macOS:
kvcommcache(key_<uin>_*.statistic文件名取 uin →md5(str(uin) + wxid)[:16])+ brute-force fallback;xor_key = uin & 0xff - Windows:扫
Weixin.exe内存匹配[A-Za-z0-9]{32|16}候选,按 V2 template ciphertext-block 反验
收藏与统计
# 全部收藏
wx favorites
# 按类型筛选:text / image / article / card / video
wx favorites --type image
# 搜索收藏内容
wx favorites --query "关键词"
# 聊天统计(发言人、消息类型、活跃时段)
wx stats "AI群"
wx stats "AI群" --since 2026-01-01导出
# 导出为 Markdown(默认)
wx export "张三" --format markdown -o chat.md
wx export "张三" -n 2000 --format markdown -o chat.md
# 导出为 JSON
wx export "AI群" --since 2026-01-01 --format json -o chat.jsonDaemon 管理
wx daemon status
wx daemon stop
wx daemon logs --follow---
Agent 使用建议
查询结果需要程序处理时,统一加 --json:
wx sessions --json
wx new-messages --json
wx search "关键词" --json | jq '.results[0]'
wx history "张三" --json -n 50 | jq '.messages[0]'
wx history "张三" --json | jq '.meta'
wx history "张三" --json --with-meta | jq '.meta.cache_mode_per_shard'CHAT 参数支持昵称、备注名、微信 ID,模糊匹配。不确定准确名称时,先用 wx contacts --query 搜索。
---
数据文件位置
~/.wx-cli/
├── config.json # 配置
├── all_keys.json # 数据库密钥(敏感,勿分享)
├── daemon.sock # Unix socket
├── daemon.pid / .log
└── cache/ # 解密后的数据库缓存---
常见问题
微信重启后密钥失效:重新运行 sudo wx init --force(微信必须正在运行)。
daemon 无响应:wx daemon stop 后重新调用任意命令自动重启。
找不到聊天:用 wx contacts --query 确认昵称/备注名,或用微信 ID 直接查询。
为什么只能获取 500 条消息?:这是默认输出条数,不是硬限制。显式传 -n 即可,例如 wx history "张三" -n 2000 或 wx export "张三" -n 2000 -o chat.md。
{
"permissions": {
"allow": [
"Bash(grep -E \"\\\\.py$|\\\\.md$\")",
"Bash(git checkout:*)",
"Bash(python3 -c \"import ast; ast.parse\\(open\\(''wx_daemon.py''\\).read\\(\\)\\); print\\(''wx_daemon.py OK''\\)\")",
"Bash(python3 -c \"import ast; ast.parse\\(open\\(''wx.py''\\).read\\(\\)\\); print\\(''wx.py OK''\\)\")",
"Bash(pip install:*)",
"Bash(pip show:*)",
"Bash(pip3 install:*)",
"Bash(python3 -c \"import click; print\\(''click'', click.__version__\\)\")",
"Bash(python3 wx.py --help)",
"Bash(python3 wx.py sessions --help)",
"Bash(python3 -c \"import sys; print\\(sys.executable\\)\")",
"Bash(uv pip:*)",
"Bash(uv venv:*)"
]
}
}
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: write
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: cargo check linux target
run: cargo check --target x86_64-unknown-linux-gnu
build:
needs: check
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
asset: wx-macos-arm64
npm_dir: darwin-arm64
bin: wx
- os: macos-latest
target: x86_64-apple-darwin
asset: wx-macos-x86_64
npm_dir: darwin-x64
bin: wx
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset: wx-linux-x86_64
npm_dir: linux-x64
bin: wx
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
asset: wx-linux-arm64
npm_dir: linux-arm64
bin: wx
- os: windows-latest
target: x86_64-pc-windows-msvc
asset: wx-windows-x86_64.exe
npm_dir: win32-x64
bin: wx.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross-compile tools (Linux arm64)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update -q
sudo apt-get install -y gcc-aarch64-linux-gnu
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.target }}-cargo-
- name: Build release
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Copy binary (Unix)
if: matrix.os != 'windows-latest'
run: |
cp target/${{ matrix.target }}/release/wx ${{ matrix.asset }}
mkdir -p npm/platforms/${{ matrix.npm_dir }}/bin
cp target/${{ matrix.target }}/release/wx npm/platforms/${{ matrix.npm_dir }}/bin/wx
- name: Copy binary (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
Copy-Item "target\${{ matrix.target }}\release\wx.exe" "${{ matrix.asset }}"
New-Item -ItemType Directory -Force -Path "npm\platforms\${{ matrix.npm_dir }}\bin" | Out-Null
Copy-Item "target\${{ matrix.target }}\release\wx.exe" "npm\platforms\${{ matrix.npm_dir }}\bin\wx.exe"
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.asset }}
path: ${{ matrix.asset }}
- uses: actions/upload-artifact@v4
with:
name: npm-${{ matrix.npm_dir }}
path: npm/platforms/${{ matrix.npm_dir }}/bin/
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: ${{ matrix.asset }}
publish-npm:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Download all platform binaries
uses: actions/download-artifact@v4
with:
pattern: npm-*
path: npm-bins/
- name: Place binaries into platform packages
run: |
for dir in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
mkdir -p npm/platforms/$dir/bin
cp npm-bins/npm-$dir/wx npm/platforms/$dir/bin/wx
chmod +x npm/platforms/$dir/bin/wx
done
mkdir -p npm/platforms/win32-x64/bin
cp npm-bins/npm-win32-x64/wx.exe npm/platforms/win32-x64/bin/wx.exe
- name: Publish platform packages
run: |
for dir in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64; do
cd npm/platforms/$dir
npm publish 2>&1 | tee /tmp/npm-out.txt || grep -q "previously published" /tmp/npm-out.txt || exit 1
cd ../../..
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish main package
run: |
cd npm/wx-cli
npm publish 2>&1 | tee /tmp/npm-out.txt || grep -q "previously published" /tmp/npm-out.txt || exit 1
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Decrypted databases and keys - NEVER upload
all_keys.json
wechat_key.txt
config.json
decrypted/
decoded_images/
*.db
*.db-shm
*.db-wal
*.db.tmp_monitor
# Hook outputs
hook_output.txt
hook_start_output.txt
hook_stderr.txt
run_hook.bat
# Rust
target/
# Python
__pycache__/
*.py[cod]
*.egg-info/
# OS
.DS_Store
Thumbs.db
find_all_keys_macos
.claude/worktrees/
wx-cli Agent Rules
每次改完代码后必须做的事
1. `cargo check` — 改任何 .rs 文件后立刻运行,不通过不提交 2. 改了跨平台代码时加运行跨平台 check:
cargo check --target x86_64-unknown-linux-gnu
cargo check --target x86_64-pc-windows-msvc3. 改了 `Cargo.toml` 版本号时: cargo update --workspace
禁止行为
- 不能在
cargo check失败的情况下 commit - 不能只在 macOS 本地 check 就认为跨平台没问题
- 不能改完
Cargo.toml不更新Cargo.lock就打 tag
常见陷阱
| 陷阱 | 正确做法 |
|---|---|
libc::__error() 在 #[cfg(unix)] 里 | 用 std::io::Error::last_os_error() |
把通用 dep 放到 [target.cfg(windows).dependencies] 后面 | TOML section 是贪婪的,通用 dep 必须在 target section 之前 |
| 改版本号忘更新 Cargo.lock | cargo update --workspace |
| Windows 代码用 trait method 忘 import trait | use std::os::windows::process::CommandExt 等 |
#[cfg(windows)] 里引用了未定义的函数 | 跨平台 check 会发现 |
Push 规则
- remote 名称:
wx-cli,使用 SSH - 每次 commit 后立刻 push
- 打 tag 用
git tag vX.Y.Z && git push wx-cli vX.Y.Z
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]]
name = "cc"
version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "doctest-file"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "interprocess"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69"
dependencies = [
"doctest-file",
"futures-core",
"libc",
"recvmsg",
"tokio",
"widestring",
"windows-sys 0.52.0",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.185"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
[[package]]
name = "libredox"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [
"libc",
]
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "recvmsg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.17",
"libredox",
"thiserror",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rusqlite"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
dependencies = [
"unicode-ident",
]
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core 0.58.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "wx-cli"
version = "0.3.0"
dependencies = [
"aes",
"anyhow",
"base64",
"cbc",
"chrono",
"clap",
"dirs",
"hmac",
"interprocess",
"libc",
"md5",
"pbkdf2",
"regex",
"roxmltree",
"rusqlite",
"serde",
"serde_json",
"serde_yaml",
"sha2",
"tokio",
"windows",
"zstd",
]
[[package]]
name = "zerocopy"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [
"cc",
"pkg-config",
]
[package]
name = "wx-cli"
version = "0.3.0"
edition = "2021"
description = "WeChat 4.x (macOS/Linux) local data CLI — decrypt SQLCipher DBs, query chat history, watch new messages"
license = "Apache-2.0"
repository = "https://github.com/jackwener/wx-cli"
keywords = ["wechat", "sqlcipher", "decrypt", "cli"]
categories = ["command-line-utilities"]
readme = "README.md"
[[bin]]
name = "wx"
path = "src/main.rs"
[dependencies]
# CLI
clap = { version = "4", features = ["derive"] }
# 异步
tokio = { version = "1", features = ["full"] }
# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "=1.0.140"
serde_yaml = "0.9"
# SQLite
rusqlite = { version = "0.31", features = ["bundled"] }
# 加密
aes = "0.8"
cbc = { version = "0.1", features = ["alloc"] }
hmac = "0.12"
sha2 = "0.10"
pbkdf2 = "0.12"
# 解压
zstd = "0.13"
# 错误处理
anyhow = "1"
# 时间
chrono = { version = "0.4", features = ["serde"] }
# 跨平台路径
dirs = "5"
# MD5 (联系人表名 Msg_<md5>)
md5 = "0.7"
# 附件 ID 编码(base64url)
base64 = "0.22"
# 正则表达式
regex = "1"
roxmltree = "0.20"
# IPC Windows named pipe(Unix 直接用 tokio::net::UnixListener)
[target.'cfg(windows)'.dependencies]
interprocess = { version = "2", features = ["tokio"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.58", features = [
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Threading",
"Win32_Foundation",
"Win32_System_Memory",
"Win32_System_Com",
"Win32_UI_Shell",
] }
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
wx-cli Project Rules
After Every Code Change
Rust 代码改动后,必须立刻运行:
cargo check不允许在 cargo check 通过之前提交或推送。
改动涉及跨平台代码(`#[cfg(...)]` / `Cargo.toml` dependencies)时,额外运行:
cargo check --target x86_64-unknown-linux-gnu
cargo check --target x86_64-pc-windows-gnu # 在 macOS 上用这个,msvc 需要 MSVC 工具链macOS 上需要一次性安装 target 和交叉编译器:
rustup target add x86_64-pc-windows-gnu
brew install mingw-w64 # 提供 x86_64-w64-mingw32-gcc,zstd-sys 等 C 依赖需要这两条 check 命令用于提前暴露 Linux/Windows 特有的编译错误,只做类型检查(不 link)。
IPC / 跨平台同库约定
动任何 IPC / 网络代码时:两端必须用同一个库、同一套 API。例如 server 用 interprocess::local_socket::tokio::Listener,client 就必须用 interprocess::local_socket::Stream::connect,不能用 std::fs::OpenOptions 打开同名路径——即使 kernel 名字对上了,底层的 framing / overlapped 模式也不兼容。
Cargo.toml 修改规则
- 修改版本号后,必须运行
cargo update --workspace更新 Cargo.lock - 添加/移动
[target.'cfg(...)'.dependencies]section 时,确认后续依赖没有被意外归入该 section(TOML section 持续到下一个 header) - 改完后运行
cargo check验证
Git 规则
- 每次 commit 后必须 push(
git push wx-cli main) - 打 tag 前确认
cargo check和cargo update --workspace都已完成 - remote 使用
wx-cli(SSH),不用origin
平台兼容性检查清单
改动以下内容时必须做跨平台 check:
- [ ]
libc::调用 → 确认函数在 Linux 和 macOS 都存在(__error是 macOS 专属,用std::io::Error::last_os_error()代替) - [ ]
#[cfg(unix)]块 → unix 包括 macOS 和 Linux,不能用 macOS 专属 API - [ ]
Cargo.tomldependency section 顺序 → 检查是否有 dep 意外落入 target section - [ ] Windows named pipe 代码 → 确认函数都已定义,trait import 齐全
CI 结构
check job(ubuntu)
└── cargo check --target linux-x86, linux-arm64, windows-x86
↓ 通过后
build jobs(5平台并行)
↓ 全部通过后
publish-npm job{
"db_dir": "D:\\xwechat_files\\your_wxid\\db_storage",
"keys_file": "all_keys.json",
"decrypted_dir": "decrypted",
"wechat_process": "Weixin.exe"
}
WeChat macOS 数据库解密指南:3.x vs 4.x 完整对比
一、背景
微信 macOS 版使用 SQLCipher 加密本地数据库。不同大版本的加密参数完全不同,解密方法不能混用。
| 项目 | WeChat 3.x (≤3.8.x) | WeChat 4.x (≥4.0.x) |
|---|---|---|
| SQLCipher 版本 | 3 | 4 |
| 默认 page_size | 1024 | 4096 |
| HMAC 算法 | HMAC-SHA1 (20 bytes) | HMAC-SHA512 (64 bytes) |
| Reserve 区大小 | 48 bytes (IV16 + HMAC20 + pad12) | 80 bytes (IV16 + HMAC64) |
| KDF 迭代次数 | 64,000 | 256,000 |
| KDF 算法 | PBKDF2-SHA1 | PBKDF2-SHA512 |
| 密钥使用方式 | 32字节 raw key 直接使用 | 32字节 raw key 直接使用 |
---
二、数据存放位置
WeChat 3.x
~/Library/Containers/com.tencent.xinWeChat/Data/
Library/Application Support/com.tencent.xinWeChat/
2.0b4.0.9/<account_md5_hash>/
Message/msg_0.db ~ msg_9.db ← 聊天消息 (按hash分片)
Contact/wccontact_new2.db ← 联系人
Session/session_new.db ← 会话列表
Group/group_new.db ← 群信息
Favorites/favorites.db ← 收藏
...共约 34 个 DBWeChat 4.x
~/Library/Containers/com.tencent.xinWeChat/Data/
Documents/xwechat_files/<account_id>/
db_storage/
message/message_0.db ~ message_5.db ← 聊天消息
contact/contact.db ← 联系人
session/session.db ← 会话列表
...关键区别: 3.x 用 MD5 hash 做账号目录名(看不出是谁),4.x 用微信ID做目录名。
---
三、密钥提取(核心步骤)
两个版本的密钥提取方式完全一样:从微信进程内存中读取 32 字节 raw key。
前提条件
1. 微信已登录且正在运行 2. 安装 Frida:pip3 install frida-tools 或 brew install frida 3. 管理员密码(sudo 权限)
macOS 权限要求
密钥提取需要调用 task_for_pid(),能否成功取决于微信 App 的代码签名状态:
- Ad-hoc 签名(如安装了防撤回补丁):
sudo即可,SSH 也行 - Apple 官方签名(有 Hardened Runtime):需要本机 Terminal + sudo,SSH 不可行
# 检查微信签名状态
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
# Ad-hoc: flags=0x2(adhoc) → sudo 直接可用
# Apple: flags=0x10000(runtime) → 需本机 Terminal 或先重签名如果需要 SSH 远程操作,可以重签名微信去掉 Hardened Runtime:
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 重启微信后 SSH sudo 即可提取密钥📖 完整的权限模型、SSH 配置、常见误区详见 macOS 权限完全指南
新手操作步骤
根据你的微信签名状态,选择对应方案:
# 首先检查你的微信签名状态
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
# 如果显示 Signature=adhoc, flags=0x2(adhoc)
# → 恭喜!直接 sudo 即可,SSH 也行
sudo ./find_all_keys_macos
# 如果显示 Authority=..Apple.., flags 包含 runtime
# → 需要本机 Terminal 操作,或者先重签名:
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 然后重启微信,再用 sudo 提取密钥SSH 远程提取方案(需 ad-hoc 签名)
以下方法全部在 Apple 官方签名的微信上失败(经多台机器穷举验证):
sudo frida -p <pid>→ "unable to access process"lldb -p <pid>→ "non-interactive debug session"sudo gcore <pid>→ "insufficient privilege"- 自编译带
com.apple.security.cs.debuggerentitlement 的 C 程序 → KERN_FAILURE=5 vmmap/heap→ 只能看元数据,无法读内存内容- LaunchDaemon (root) / LaunchAgent (Aqua) /
launchctl asuser→ 全部失败 - 修改 TCC.db → SIP 保护,
restricted标志,只读
实际操作步骤
方法 A: 使用 C 版扫描器(推荐,4.x)
# 编译
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
# 运行(自动查找微信进程、扫描内存、匹配 DB salt)
sudo ./find_all_keys_macos扫描器会在内存中搜索 x'<64hex_key><32hex_salt>' 格式的密钥,自动匹配 DB 文件的 salt,输出 all_keys.json。
方法 B: 使用 Frida(3.x / 通用)
# 附加到微信进程,手动 dump 内存搜索 32 字节密钥
sudo frida -p $(pgrep -x WeChat) -l scan_keys.js输出示例(3.x 实际结果):
600000d8d930 72 8e 8e dd 26 68 48 37 92 89 2c 7b 24 10 58 9d r...&hH7..,{$.X.
600000d8d940 3e 64 1e e7 ef b3 47 c9 9f 17 3d 58 bf 9d 38 05 >d....G...=X..8.这 32 字节就是密钥:728e8edd2668483792892c7b2410589d3e641ee7efb347c99f173d58bf9d3805
---
四、解密实现
核心原理
SQLCipher 加密的每一页(page)结构:
┌─────────────────────────────────────────────────────┐
│ 第 1 页 (特殊) │
├──────────┬──────────────────────┬───────────────────┤
│ Salt │ 加密的数据 │ Reserve区 │
│ 16 bytes │ (page_size-16-rsv) │ IV+HMAC+padding │
├──────────┴──────────────────────┴───────────────────┤
│ │
│ 第 2~N 页 (普通页) │
├────────────────────────────────┬────────────────────┤
│ 加密的数据 │ Reserve区 │
│ (page_size - reserve) │ IV + HMAC + padding │
└────────────────────────────────┴────────────────────┘第 1 页特殊处理:前 16 字节是明文 salt(不加密),解密后需要拼回 SQLite format 3\0 头。
WeChat 3.x 解密参数
# SQLCipher 3 参数
PAGE_SIZE = 1024
RESERVE = 48 # 16(IV) + 20(HMAC-SHA1) + 12(padding)
KDF_ITER = 64000
HMAC_ALGO = 'sha1'
HMAC_LEN = 20WeChat 4.x 解密参数
# SQLCipher 4 参数
PAGE_SIZE = 4096
RESERVE = 80 # 16(IV) + 64(HMAC-SHA512)
KDF_ITER = 256000
HMAC_ALGO = 'sha512'
HMAC_LEN = 643.x 的特殊陷阱:同一账号的 DB 使用不同参数!
这是 3.x 最坑的地方。我们实测发现同一个账号的 34 个 DB 居然用了 4 种不同的 SQLCipher 配置:
| DB 类别 | page_size | key 模式 |
|---|---|---|
| 大部分 DB (msg, contact, session...) | 1024 | raw key 直接使用 |
| WebTemplate/webtemplate.db | 4096 | raw key 直接使用 |
| FTS 索引 (ftsmessage, ftsfilemessage) | 1024 | PBKDF2(raw_key, salt, 64000) |
| mediaData.db | 4096 | PBKDF2(raw_key, salt, 64000) |
还有 3 个 DB 根本没加密(kv_config, solitaire_chat, multiTalk),直接复制即可。
所以解密脚本必须自动判断并尝试多种组合。
完整解密代码(Python, 3.x)
#!/usr/bin/env python3
"""WeChat 3.x macOS 数据库解密器"""
import hashlib, hmac, struct, shutil
from Crypto.Cipher import AES
def decrypt_page(page_data, enc_key, page_no, page_size, reserve):
"""解密单个 page"""
if page_no == 1:
# 第1页: 前16字节是salt(明文), 后面才是加密数据
salt = page_data[:16]
encrypted = page_data[16:page_size - reserve]
iv = page_data[page_size - reserve:page_size - reserve + 16]
else:
encrypted = page_data[:page_size - reserve]
iv = page_data[page_size - reserve:page_size - reserve + 16]
cipher = AES.new(enc_key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(encrypted)
if page_no == 1:
# 拼回 SQLite 头: "SQLite format 3\0" + 解密内容 + reserve填零
page = bytearray(b'SQLite format 3\x00' + decrypted + b'\x00' * reserve)
# 清除 header offset 20 的 reserved-space 字段
# 加密时该字段 = reserve size,解密后需要归零,否则 SQLite 误判 usable page size
page[20] = 0
return bytes(page)
else:
# Reserve 区填零(SQLite 不读取该区域,清零保持输出干净)
return decrypted + b'\x00' * reserve
def verify_hmac_page1(page_data, enc_key, page_size, reserve):
"""验证第1页的 HMAC-SHA1 (SQLCipher 3)"""
salt = page_data[:16]
mac_salt = bytes([b ^ 0x3a for b in salt])
mac_key = hashlib.pbkdf2_hmac('sha1', enc_key, mac_salt, 2, dklen=32)
content = page_data[16:page_size - reserve]
iv = page_data[page_size - reserve:page_size - reserve + 16]
stored_hmac = page_data[page_size - reserve + 16:page_size - reserve + 36]
msg = content + iv + struct.pack('<I', 1)
calc_hmac = hmac.new(mac_key, msg, hashlib.sha1).digest()
return calc_hmac == stored_hmac
def decrypt_db(db_path, raw_key_hex, output_path):
"""
解密单个数据库文件
自动尝试多种 SQLCipher 参数组合
"""
raw_key = bytes.fromhex(raw_key_hex)
with open(db_path, 'rb') as f:
data = f.read()
# 检查是否已经是 SQLite (未加密)
if data[:15] == b'SQLite format 3':
shutil.copy2(db_path, output_path)
return 'unencrypted'
salt = data[:16]
# 尝试的参数组合: (page_size, use_pbkdf2, reserve)
# SQLCipher 3 reserve = 48: IV(16) + HMAC-SHA1(20) + padding(12)
configs = [
(1024, False, 48), # 大部分 DB
(4096, False, 48), # WebTemplate
(1024, True, 48), # FTS 索引
(4096, True, 48), # mediaData
]
for page_size, use_pbkdf2, reserve in configs:
if use_pbkdf2:
enc_key = hashlib.pbkdf2_hmac('sha1', raw_key, salt, 64000, dklen=32)
else:
enc_key = raw_key
if verify_hmac_page1(data, enc_key, page_size, reserve):
# HMAC 验证通过,开始解密
# 注意: 生产代码应对每一页都验证 HMAC,防止单页损坏/篡改
# 后续页的 HMAC 计算方式相同,只是 content 从 offset 0 开始(无 salt),
# 且 page_no 使用对应的页码(从 1 开始)
num_pages = len(data) // page_size
output = b''
for i in range(num_pages):
page = data[i * page_size:(i + 1) * page_size]
output += decrypt_page(page, enc_key, i + 1, page_size, reserve)
with open(output_path, 'wb') as f:
f.write(output)
mode = 'pbkdf2' if use_pbkdf2 else 'direct'
return f'ok (page={page_size}, {mode})'
return 'failed'依赖安装: pip3 install pycryptodome
4.x 的解密差异
4.x 的代码逻辑相同,只需改参数:
reserve = 80, HMAC 用 SHA512,mac_key的 PBKDF2 也用 SHA512verify_hmac中stored_hmac长度为 64 字节- 4.x 中所有 DB 使用统一的参数(不像 3.x 那样混用多种配置)
---
五、新手操作清单
你需要准备什么
- [x] macOS 电脑,微信已登录
- [x] Python 3 + pycryptodome (
pip3 install pycryptodome) - [x] Frida (
pip3 install frida-tools) - [x] 管理员密码(sudo 权限)
一步步操作
# 1. 确认微信版本
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/
# 如果看到 2.0b4.0.9 → 3.x 版本
# 如果看到其他 / Documents/xwechat_files → 4.x 版本
# 2. 找到你的账号目录
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/2.0b4.0.9/
# 最大的那个目录就是你的主账号
# 3. 确认数据库是加密的
file ~/.../<account>/Message/msg_0.db
# 应该显示 "data" 而不是 "SQLite 3.x database"
# 4. 提取密钥 (必须在本机 Terminal!)
# 方法 A: 使用 C 工具(推荐,见本 repo 的 find_all_keys_macos.c)
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
sudo ./find_all_keys_macos
# 输出 all_keys.json,可直接用于解密
# 方法 B: 使用 Frida(需自行编写扫描脚本)
# sudo frida -p $(pgrep -x WeChat) -l your_scan_script.js
# 5. 运行解密(需配置 config.json 指向 db_storage 目录)
python3 decrypt_db.py
# 6. 验证
file decrypted/Message/msg_0.db
# 应该显示 "SQLite 3.x database"
sqlite3 decrypted/Message/msg_0.db "SELECT COUNT(*) FROM (SELECT name FROM sqlite_master WHERE type='table')"常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| Frida 报 "unable to access process" | SSH 下运行 / TCC 未授权 | 必须在本机 Terminal 运行 |
| 解密后文件打不开 | 参数不匹配 | 脚本会自动尝试4种配置 |
| 部分 DB 用不同密钥 | ChatSync.db 等特殊 DB | 非关键数据,可跳过 |
| "No module named Crypto" | 未安装 pycryptodome | pip3 install pycryptodome |
| 3.x 和 4.x 混用参数 | 版本判断错误 | 先确认微信版本号 |
---
六、总结对比
WeChat 3.x WeChat 4.x
────────── ──────────
SQLCipher 3 SQLCipher 4
page 1024 (混用4096) page 4096 (统一)
HMAC-SHA1, reserve 48 HMAC-SHA512, reserve 80
KDF 64000 迭代 KDF 256000 迭代
4种参数组合混用 (坑!) 统一参数 (简单)
msg_0~msg_9.db message_0~message_5.db
Chat_<hash> 表名 不同表结构
密钥提取方式相同: Frida dump 32字节 密钥提取方式相同核心经验: 密钥提取是最难的一步(受 macOS TCC 限制),解密算法本身是确定的。3.x 比 4.x 更复杂,因为同一账号内的数据库使用了不同的加密参数组合。
macOS WeChat 密钥提取:权限与签名完全指南
基于多台机器 (macOS 10.15 ~ 15.x, Intel + Apple Silicon) 的实测经验总结。
核心结论
能否从微信进程提取加密密钥,取决于 两个独立问题:
| 问题 | 控制什么 | 关键因素 |
|---|---|---|
task_for_pid() 能否成功 | 读取进程内存 | 目标 App 的代码签名 |
codesign 能否重签名 | 修改 App 文件 | 调用者的完全磁盘访问 |
---
一、task_for_pid 权限(读取微信内存)
决定因素:微信 App 的 Hardened Runtime
# 检查微信签名状态
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"情况 A:Ad-hoc 签名(无 Hardened Runtime)
flags=0x2(adhoc)
Signature=adhoc
TeamIdentifier=not set原因: 安装过防撤回补丁等第三方修改工具,App 被重新签名。
权限要求: 只需 sudo,任何上下文(Terminal、SSH、cron)都能成功。
# SSH 远程直接可用
sudo ./find_all_keys_macos情况 B:Apple 官方签名(有 Hardened Runtime)
flags=0x10000(runtime)
Signature size=9092
Authority=...Apple...原因: App Store 下载或官方 DMG 安装,未经修改。
权限要求: sudo + 本机 GUI 终端 + TCC "开发者工具"授权。SSH 不可行。
taskgated 检查流程:
目标有 hardened runtime?
YES → 检查调用者的"负责应用"是否有 TCC DeveloperTool 授权
SSH 的负责应用是 sshd → 无法获得 TCC 授权 → 拒绝
Terminal.app 可以弹窗获得授权 → 允许
NO → root (sudo) 即可 → 允许实测数据
| 机器 | macOS | WeChat 签名 | 本机 Terminal sudo | SSH sudo |
|---|---|---|---|---|
| MacBook (macOS 15.x) | 15.x | ad-hoc (防撤回补丁) | ✅ | ✅ |
| Mac mini (Catalina) | 10.15.8 | Apple 官方 runtime | ✅ | ❌ |
| MacBook Pro (Big Sur) | 11.1 | Apple 官方 runtime | ✅ | ❌ |
SSH 下穷举过的所有方法(Apple 签名时全部失败)
| 方法 | 结果 | 错误信息 |
|---|---|---|
sudo frida -p <pid> | ❌ | unable to access process |
lldb -p <pid> | ❌ | non-interactive debug session |
sudo gcore <pid> | ❌ | insufficient privilege |
| 带 debugger entitlement 的 C 程序 | ❌ | KERN_FAILURE=5 |
launchctl asuser (用户会话) | ❌ | task_for_pid=5 |
| LaunchAgent (Aqua GUI 会话) | ❌ | 非 root,需要 sudo |
| LaunchDaemon (root) | ❌ | 系统域无 GUI 上下文 |
launchctl submit (root) | ❌ | 同上 |
osascript 操控 Terminal.app | ❌ | 需要辅助功能权限/挂起 |
| 修改 TCC.db 给 sshd 授权 | ❌ | SIP 保护,restricted 只读 |
vmmap / heap | ⚠️ | 只能看元数据,无法读内存 |
---
二、codesign 权限(重签名微信 App)
如果微信是 Apple 官方签名,需要重签名为 ad-hoc 来解锁 SSH 提取。
问题:SSH 下 codesign 可能失败
$ sudo codesign --force --deep --sign - /Applications/WeChat.app
/Applications/WeChat.app: Operation not permitted
In subcomponent: /Applications/WeChat.app/Contents/MacOS/WeChatAppEx.app原因: SSH 进程没有「完全磁盘访问」(Full Disk Access, FDA) 权限,无法修改 /Applications 下的 App bundle 文件。
给 SSH 授予完全磁盘访问
在目标机器的 GUI 上操作:
系统偏好设置 → 安全性与隐私 → 隐私 → 完全磁盘访问
点击 🔒 解锁 → 点 + 号 → Cmd+Shift+G 输入路径必须添加这两个(缺一不可):
| 路径 | 说明 |
|---|---|
/usr/sbin/sshd | SSH 守护进程 |
/usr/libexec/sshd-keygen-wrapper | SSH 的实际执行进程(负责应用) |
⚠️ 添加后必须断开 SSH 重新连接!TCC 权限在进程启动时检查,不会热更新。
验证 FDA 是否生效
# 重连 SSH 后执行
cat ~/Library/Application\ Support/com.apple.TCC/TCC.db > /dev/null 2>&1 && echo "FDA: YES" || echo "FDA: NO"TCC.db 是受保护文件,只有 FDA 进程能读取。
完整流程:SSH 远程重签名微信
# 0. 前提:SSH 已有 FDA(上面的步骤)
# 1. 确认微信已退出
kill $(pgrep -x WeChat) 2>/dev/null
sleep 2
pgrep -x WeChat && echo "还在运行!" || echo "已退出"
# 2. 清除扩展属性(可选,防止干扰)
sudo xattr -cr /Applications/WeChat.app
# 3. Ad-hoc 重签名
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 4. 验证签名
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
# 期望: flags=0x2(adhoc), Signature=adhoc
# 5. 用户需在 GUI 上重新打开微信并登录
# (或者 SSH 执行 open,但用户仍需在 GUI 上完成登录)
open /Applications/WeChat.app注意事项
| 事项 | 说明 |
|---|---|
| 微信必须先退出 | 运行中的 App,其 dylib/binary 被占用,codesign 会报 internal error |
| 重签名后必须重启微信 | 已运行的进程仍使用旧签名的内存映像,task_for_pid 仍会失败。必须 kill 后重新启动 |
| 重签名后需重新登录微信 | 签名变更会使登录态失效 |
| 自动更新可能覆盖签名 | 微信更新后变回 Apple 签名,需要再次重签 |
| 小程序可能受影响 | 部分小程序校验签名,ad-hoc 可能报安全错误 |
---
三、权限矩阵总结
| 操作 | 需要的权限 | SSH 需要额外配置 |
|---|---|---|
| 读取微信数据库文件 | 文件系统权限(通常有) | 无 |
task_for_pid (ad-hoc App) | sudo | 无 |
task_for_pid (Apple 签名 App) | sudo + TCC DeveloperTool | 不可行,必须本机 Terminal |
codesign 重签名 App | sudo + FDA | SSH 需添加 sshd + sshd-keygen-wrapper 到 FDA |
| 修改 TCC.db | sudo + 关闭 SIP | 不推荐 |
完全远程操作清单(一次性 GUI 配置)
只需在目标机器 GUI 上做一次,之后 SSH 永久可用:
1. 完全磁盘访问 → 添加 /usr/sbin/sshd 和 /usr/libexec/sshd-keygen-wrapper 2. SSH 连入 → sudo codesign --force --deep --sign - /Applications/WeChat.app 3. 用户在 GUI 重开微信并登录 4. 之后 SSH 永久可以 sudo 提取密钥,微信重启也不影响(除非更新覆盖签名)
---
四、常见误区
| 误区 | 真相 |
|---|---|
| "需要给终端完全磁盘访问才能调试" | ❌ FDA 控制文件访问,不控制进程调试 |
| "需要给终端开发者工具权限" | ⚠️ 仅当目标 App 有 hardened runtime 时才需要 |
| "SSH 下永远无法提取密钥" | ❌ 目标 App 是 ad-hoc 签名时,SSH sudo 可以 |
| "macOS 版本决定了能否 SSH 调试" | ❌ 主要取决于目标 App 的签名状态 |
| "SIP 阻止了调试微信" | ❌ SIP 只保护系统进程,微信不受 SIP 保护 |
| "加了 sshd 到 FDA 就行" | ❌ 还需要加 sshd-keygen-wrapper,且要重连 SSH |
| "微信开着也能重签名" | ❌ 运行中的 binary/dylib 被占用,codesign 会失败 |
---
五、重签名后微信权限 silent 失效
现象
完成 ad-hoc 重签名后,微信任意以下功能都可能"看起来已授权但实际被拒绝":
- 截图 / 屏幕共享(
ScreenCapture) - 视频通话 / 扫码(
Camera) - 语音消息 / 通话(
Microphone) - 自动化、第三方输入法(
AppleEvents) - 同步通讯录(
AddressBook) - 文件发送 / 接收(
SystemPolicyDocumentsFolder/Downloads/Desktop)
System Settings 里通常仍看到"微信.app"开关是 ON,但运行时权限校验失败。微信会反复弹"需要开启 X 权限"。
根因(第一性原理)
macOS TCC(Transparency, Consent, and Control)按 bundle id + csreq 联合校验权限。csreq(code requirement)是从 app 的 code signature 推导出的二进制 blob,存在 /Library/Application Support/com.apple.TCC/TCC.db 的 access 表里,每条 ~160 字节。
codesign --force --deep --sign - 把 WeChat 从官方签名换成 ad-hoc 签名(甚至 ad-hoc → ad-hoc 重签也会变),新进程的 csreq 跟旧记录里那条对不上 —— tccd 拒绝。
System Settings UI 只按 client 显示开关、不重算 csreq,所以视觉上是"已授权",运行时实际拒绝。这是 silent drift。
修复步骤
把 WeChat 在 TCC 里的旧记录全部抹掉,让 macOS 在下次微信请求权限时按新签名重新生成 csreq:
for s in ScreenCapture Camera Microphone AppleEvents AddressBook \
SystemPolicyDocumentsFolder SystemPolicyDownloadsFolder SystemPolicyDesktopFolder; do
tccutil reset "$s" com.tencent.xinWeChat
donetccutil 对没有授权过的 service 会报 "No such bundle identifier",这是 no-op,不影响其他 service 的 reset。
之后退出并重新打开微信,按 GUI 提示重新允许:
killall WeChat
open /Applications/WeChat.app这一步应当由用户/agent 手动执行,不在 wx init 里自动跑——TCC 重置会让用户的现有授权失效,需要由人决定时机。macOS 26 的 UI 拆分
在 macOS 26 上,隐私与安全 → 录屏与系统录音 显示为两块,容易踩坑:
| 区域 | 作用 |
|---|---|
| 录屏与系统录音(上半区) | 录制屏幕内容 + 系统音频;微信截图、屏幕共享需要这一项 |
| 仅系统录音(下半区) | 只录系统音频;只打开这一项不能修复微信截图 |
把 WeChat 加进上半区;只勾下半区的"仅系统录音"无效。
验证
确认 WeChat 当前是 ad-hoc 签名(这是修复前提):
codesign -dv --verbose=4 /Applications/WeChat.app 2>&1 | grep -E "Signature|flags|TeamIdentifier"期望看到:
flags=0x2(adhoc)
Signature=adhoc
TeamIdentifier=not set最直接的功能验证:在微信里使用截图、视频通话、麦克风等功能,按 GUI 弹窗的"允许"重新授权一次,之后正常工作。
---
六、"微信" 想访问其他 App 的数据 弹窗
现象
执行过 wx init、对 /Applications/WeChat.app 做过 ad-hoc 重签名之后,再使用微信时会比较频繁地看到 macOS 弹出:
"微信" 想访问其他 App 的数据。
单独存放 App 数据可让你更容易管理隐私和安全。
[ 不允许 ] [ 允许 ]最常见的触发面是在微信里打开公众号文章,但这只是高频触发面,不是根因。
根因(第一性原理)
这弹窗是 macOS Ventura+ / 14 / 15 对 app data container 跨身份访问 的保护:当前进程("微信")正在读取另一个 code identity 的 app 留下的数据。
我们当前 macOS 方案为了让 task_for_pid 能拿到 WeChat 的 task port、读取进程内存里的 raw key,要求用户执行:
codesign --force --deep --sign - /Applications/WeChat.app这一步把 WeChat 从 Apple 官方签名换成 ad-hoc 身份。对用户来说它仍然是"微信";对 macOS 安全模型来说,重签前的 WeChat 和 重签后的 WeChat 已经不是同一个 app identity。
之后当(重签后的)微信访问它原本的 ~/Library/Containers/com.tencent.xinWeChat/...、缓存、app group 等数据时,系统看到的是"一个新身份在读旧身份留下的 container 数据",于是按隐私保护策略弹这个对话框。公众号文章里的 webview / cookie / 缓存路径刚好踩到了这条访问路径,所以"打开公众号就弹"会非常容易复现,但本质不是公众号页面的问题,而是 code identity + container access。
注意:这不是 "wx-cli 在偷偷读别的 App 的数据",wx-cli 进程本身对 WeChat container 是只读访问;但要求用户重签 WeChat 这一步本身就是这类弹窗的直接诱因。所以这是当前 macOS invasive init 路径的已知副作用,不是与 wx-cli 无关的系统行为。
应对
短期缓解:
- 点"允许"通常只是放行当前这次 WeChat 进程;下一次 WeChat 启动权限会 reset,可能还会再弹
- 该授权一般不会在 System Settings 里留下显式开关,因为它绑定的是动态的 code identity
彻底不弹:
- 把
/Applications/WeChat.app恢复成官方签名(重装官方 WeChat 包),不再执行codesign --force --deep --sign - - 这一步只是放弃当前依赖 ad-hoc 重签的默认路径,并不等于放弃 macOS memory-scan:在本机 GUI Terminal 下、对 Terminal.app 授予「开发者工具」TCC 权限后,
task_for_pid对 Apple 官方签名(hardened runtime)的 WeChat 应当仍能走通——参考 §一 实测表里的"Apple 签名 + 本机 Terminal sudo = ✅" - ⚠️ 实测覆盖范围说明:§一 实测表里 "Apple 签名 + 本机 Terminal sudo ✅" 的两条实证只覆盖 macOS 10.15 (Catalina) 与 11.1 (Big Sur);macOS 14 (Sonoma) / 15 (Sequoia) 上是否仍走通未在本项目内实测。如果你按这条路恢复官方签名后发现 init 走不通,请回到重签路径并接受本节描述的弹窗副作用
- 真正受限的场景是 SSH 远程 + Apple 签名 WeChat:
sshd拿不到 TCC 开发者工具授权,这时才必须走重签路径
长期方向:
- 这条副作用的真正修复是把
wx init重新设计成safe → assisted → invasive fallback三层:默认不动 WeChat,只有在前两条都不可行时才走 ad-hoc 重签,并先打出完整副作用清单让用户显式确认。在那之前,这是已知 trade-off。
# wx-cli Windows installer
# Run with: irm https://raw.githubusercontent.com/jackwener/wx-cli/main/install.ps1 | iex
$ErrorActionPreference = "Stop"
$Repo = "jackwener/wx-cli"
$BinName = "wx.exe"
$Asset = "wx-windows-x86_64.exe"
$InstallDir = "$env:LOCALAPPDATA\wx-cli"
# ── 获取最新版本 ────────────────────────────────────────────
Write-Host "正在获取最新版本..."
$Release = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest"
$Tag = $Release.tag_name
if (-not $Tag) {
Write-Error "获取版本失败,请检查网络或访问 https://github.com/$Repo/releases"
exit 1
}
Write-Host "版本: $Tag"
# ── 下载 ────────────────────────────────────────────────────
$Url = "https://github.com/$Repo/releases/download/$Tag/$Asset"
$TmpFile = Join-Path $env:TEMP "wx-cli-download.exe"
Write-Host "下载中: $Url"
Invoke-WebRequest -Uri $Url -OutFile $TmpFile -UseBasicParsing
# ── 安装 ────────────────────────────────────────────────────
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir | Out-Null
}
Move-Item -Force $TmpFile (Join-Path $InstallDir $BinName)
# ── 加入 PATH(当前用户) ────────────────────────────────────
$UserPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($UserPath -notlike "*$InstallDir*") {
[Environment]::SetEnvironmentVariable("PATH", "$UserPath;$InstallDir", "User")
Write-Host "已将 $InstallDir 加入用户 PATH(重新打开终端生效)"
}
Write-Host ""
Write-Host "✓ wx 已安装到 $InstallDir\$BinName"
Write-Host ""
Write-Host "快速开始(以管理员身份运行):"
Write-Host " wx init # 首次初始化(需要微信正在运行)"
Write-Host " wx sessions # 查看最近会话"
Write-Host " wx --help # 查看所有命令"
#!/usr/bin/env bash
set -euo pipefail
REPO="jackwener/wx-cli"
BIN_NAME="wx"
INSTALL_DIR="/usr/local/bin"
# ── 检测平台 ────────────────────────────────────────────────
OS=$(uname -s)
ARCH=$(uname -m)
case "${OS}-${ARCH}" in
Darwin-arm64) ASSET="wx-macos-arm64" ;;
Darwin-x86_64) ASSET="wx-macos-x86_64" ;;
Linux-x86_64) ASSET="wx-linux-x86_64" ;;
Linux-aarch64) ASSET="wx-linux-aarch64" ;;
*)
echo "不支持的平台: ${OS}-${ARCH}"
echo "请从 https://github.com/${REPO}/releases 手动下载"
exit 1
;;
esac
# ── 获取最新版本号 ──────────────────────────────────────────
echo "正在获取最新版本..."
TAG=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
if [ -z "$TAG" ]; then
echo "获取版本失败,请检查网络或访问 https://github.com/${REPO}/releases"
exit 1
fi
echo "版本: ${TAG} 平台: ${ASSET}"
# ── 下载 ────────────────────────────────────────────────────
URL="https://github.com/${REPO}/releases/download/${TAG}/${ASSET}"
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT
echo "下载中: ${URL}"
curl -fsSL --progress-bar -o "$TMP" "$URL"
chmod +x "$TMP"
# ── 安装 ────────────────────────────────────────────────────
if [ -w "$INSTALL_DIR" ]; then
mv "$TMP" "${INSTALL_DIR}/${BIN_NAME}"
else
echo "需要 sudo 权限安装到 ${INSTALL_DIR}"
sudo mv "$TMP" "${INSTALL_DIR}/${BIN_NAME}"
fi
echo ""
echo "✓ wx 已安装到 ${INSTALL_DIR}/${BIN_NAME}"
echo ""
echo "快速开始:"
echo " sudo wx init # 首次初始化(需要微信正在运行)"
echo " wx sessions # 查看最近会话"
echo " wx --help # 查看所有命令"
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
{
"name": "@jackwener/wx-cli-darwin-arm64",
"version": "0.3.0",
"description": "wx-cli binary for macOS arm64",
"os": ["darwin"],
"cpu": ["arm64"],
"files": ["bin/"],
"license": "Apache-2.0",
"publishConfig": { "access": "public" }
}
{
"name": "@jackwener/wx-cli-darwin-x64",
"version": "0.3.0",
"description": "wx-cli binary for macOS x64",
"os": ["darwin"],
"cpu": ["x64"],
"files": ["bin/"],
"license": "Apache-2.0",
"publishConfig": { "access": "public" }
}
{
"name": "@jackwener/wx-cli-linux-arm64",
"version": "0.3.0",
"description": "wx-cli binary for Linux arm64",
"os": ["linux"],
"cpu": ["arm64"],
"files": ["bin/"],
"license": "Apache-2.0",
"publishConfig": { "access": "public" }
}
{
"name": "@jackwener/wx-cli-linux-x64",
"version": "0.3.0",
"description": "wx-cli binary for Linux x64",
"os": ["linux"],
"cpu": ["x64"],
"files": ["bin/"],
"license": "Apache-2.0",
"publishConfig": { "access": "public" }
}
{
"name": "@jackwener/wx-cli-win32-x64",
"version": "0.3.0",
"description": "wx-cli binary for Windows x64",
"os": ["win32"],
"cpu": ["x64"],
"files": ["bin/"],
"license": "Apache-2.0",
"publishConfig": { "access": "public" }
}
#!/usr/bin/env node
'use strict';
const { execFileSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const PLATFORM_PACKAGES = {
'darwin-arm64': '@jackwener/wx-cli-darwin-arm64',
'darwin-x64': '@jackwener/wx-cli-darwin-x64',
'linux-x64': '@jackwener/wx-cli-linux-x64',
'linux-arm64': '@jackwener/wx-cli-linux-arm64',
'win32-x64': '@jackwener/wx-cli-win32-x64',
};
const platformKey = `${process.platform}-${process.arch}`;
const ext = process.platform === 'win32' ? '.exe' : '';
function getBinaryPath() {
if (process.env.WX_CLI_BINARY) {
return process.env.WX_CLI_BINARY;
}
const pkg = PLATFORM_PACKAGES[platformKey];
if (!pkg) {
console.error(`wx-cli: unsupported platform ${platformKey}`);
process.exit(1);
}
try {
return require.resolve(`${pkg}/bin/wx${ext}`);
} catch {
const modPath = path.join(
path.dirname(require.resolve(`${pkg}/package.json`)),
`bin/wx${ext}`
);
if (fs.existsSync(modPath)) return modPath;
}
console.error(`wx-cli: binary not found for ${platformKey}`);
console.error('Try: npm install -g @jackwener/wx-cli');
process.exit(1);
}
try {
execFileSync(getBinaryPath(), process.argv.slice(2), {
stdio: 'inherit',
env: { ...process.env },
});
} catch (e) {
if (e && e.status != null) process.exit(e.status);
throw e;
}
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const PLATFORM_PACKAGES = {
'darwin-arm64': '@jackwener/wx-cli-darwin-arm64',
'darwin-x64': '@jackwener/wx-cli-darwin-x64',
'linux-x64': '@jackwener/wx-cli-linux-x64',
'linux-arm64': '@jackwener/wx-cli-linux-arm64',
'win32-x64': '@jackwener/wx-cli-win32-x64',
};
const platformKey = `${process.platform}-${process.arch}`;
const pkg = PLATFORM_PACKAGES[platformKey];
if (!pkg) {
console.log(`wx-cli: no binary for ${platformKey}, skipping`);
process.exit(0);
}
const ext = process.platform === 'win32' ? '.exe' : '';
try {
const binaryPath = require.resolve(`${pkg}/bin/wx${ext}`);
if (process.platform !== 'win32') {
fs.chmodSync(binaryPath, 0o755);
}
} catch {
console.log(`wx-cli: platform package ${pkg} not installed`);
}
{
"name": "@jackwener/wx-cli",
"version": "0.3.0",
"description": "Query your local WeChat data from the command line. Designed for LLM agent tool calls.",
"bin": {
"wx": "bin/wx.js"
},
"scripts": {
"postinstall": "node install.js"
},
"files": [
"bin/",
"install.js"
],
"optionalDependencies": {
"@jackwener/wx-cli-darwin-arm64": "0.3.0",
"@jackwener/wx-cli-darwin-x64": "0.3.0",
"@jackwener/wx-cli-linux-x64": "0.3.0",
"@jackwener/wx-cli-linux-arm64": "0.3.0",
"@jackwener/wx-cli-win32-x64": "0.3.0"
},
"engines": { "node": ">=14" },
"keywords": ["wechat", "cli", "wx", "llm", "ai", "sqlite", "sqlcipher"],
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/jackwener/wx-cli"
},
"publishConfig": { "access": "public" }
}
<div align="center">
wx-cli
从命令行查询本地微信数据
  
会话 · 聊天记录 · 搜索 · 联系人 · 群成员 · 群昵称 · 收藏 · 统计 · 导出
</div>
---
AI Agent Skill
通过 skills CLI 一键安装到 Claude Code、Cursor、Codex 等 agent:
npx skills add jackwener/wx-cli或全局安装:
npx skills add jackwener/wx-cli -g安装后 agent 会自动读取 SKILL.md,了解如何安装和调用 wx-cli。
---
特性
- 零依赖安装 — 单一 Rust 二进制,一行命令装完
- 毫秒级响应 — 后台 daemon 持久缓存解密数据库,mtime 不变则复用
- AI 友好 —
history/search/sessions/new-messages/stats/attachments默认返回{..., meta}wrapper,agent 能直接消费 freshness / source 信息 - 完全本地 — 数据不出本机,实时解密,无需全量预解密
---
安装
npm(推荐,全平台)
npm install -g @jackwener/wx-climacOS / Linux(curl)
curl -fsSL https://raw.githubusercontent.com/jackwener/wx-cli/main/install.sh | bashWindows(PowerShell,以管理员身份运行)
irm https://raw.githubusercontent.com/jackwener/wx-cli/main/install.ps1 | iex<details> <summary>其他安装方式</summary>
手动下载
从 Releases 下载对应平台文件:
| 平台 | 文件 |
|---|---|
| macOS Apple Silicon | wx-macos-arm64 |
| macOS Intel | wx-macos-x86_64 |
| Linux x86_64 | wx-linux-x86_64 |
| Linux arm64 | wx-linux-arm64 |
| Windows x86_64 | wx-windows-x86_64.exe |
macOS / Linux:chmod +x wx && sudo mv wx /usr/local/bin/
从源码构建
git clone git@github.com:jackwener/wx-cli.git && cd wx-cli
cargo build --release
# 产物:target/release/wx(Windows: wx.exe)</details>
---
快速开始
保持微信运行,然后初始化(只需一次):
macOS(需要先对微信做 ad-hoc 签名,才能扫描其内存)
# 1. 签名(只需做一次,WeChat 更新后重做)
codesign --force --deep --sign - /Applications/WeChat.app
# 2. 清理旧 TCC 授权记录(重签名后必做,否则微信截图/通话权限可能 silent 失效)
for s in ScreenCapture Camera Microphone AppleEvents AddressBook \
SystemPolicyDocumentsFolder SystemPolicyDownloadsFolder SystemPolicyDesktopFolder; do
tccutil reset "$s" com.tencent.xinWeChat
done
# 3. 重启微信,等待完全登录
killall WeChat && open /Applications/WeChat.app
# 4. 初始化
sudo wx init如果codesign报signature in use,先执行:
```bash
codesign --remove-signature "/Applications/WeChat.app/Contents/Frameworks/vlc_plugins/librtp_mpeg4_plugin.dylib"
codesign --force --deep --sign - /Applications/WeChat.app
```
>
重签名后 macOS 的 TCC 隐私授权按新 code signature 重新校验,旧记录会失效。如果跳过 tccutil reset,微信截图/视频通话/麦克风等权限可能"看起来已开启但实际拒绝"。详见 macOS 权限与签名指南。副作用提示:完成上面的 ad-hoc 重签后,macOS 会比较频繁地弹 "微信" 想访问其他 App 的数据(在微信里打开公众号文章时尤其容易触发)。这是当前 macOS invasive init 路径的已知副作用:重签后 WeChat 的 code identity 变了,它再访问自己原来的 container / 缓存数据会被系统识别为"跨 App 访问"。点"允许"通常只是放行当前 WeChat 进程;想彻底不弹得恢复官方 WeChat——这只放弃当前依赖重签的默认路径,不等于放弃 memory-scan:在本机 GUI Terminal 下、Terminal.app 拿到「开发者工具」TCC 授权后,对 Apple 官方签名的 WeChat 应当仍可以走通(实证覆盖只有 Catalina / Big Sur,macOS 14+ 未在本项目内实测);只有 SSH 远程 + Apple 签名 WeChat 这种组合才必须重签。详见 macOS 权限与签名指南 §六。Linux
sudo wx initWindows(以管理员身份运行 PowerShell)
wx init验证安装:
wx sessions能看到最近会话即表示一切正常。daemon 在首次调用时自动启动。
---
命令
消息
wx sessions # 最近 20 个会话
wx unread # 有未读消息的会话
wx unread --filter private,group # 只看真人未读(过滤公众号/折叠入口)
wx new-messages # 上次检查后的新消息(增量)
wx history "张三" # 最近 50 条记录
wx history "张三" -n 2000 # 拉更多历史消息
wx history "AI群" --since 2026-04-01 --until 2026-04-15
wx search "关键词" # 全库搜索
wx search "关键词" -n 500 # 放宽搜索结果条数
wx search "会议" --in "工作群" --since 2026-01-01history / search / export 都支持 -n / --limit 指定条数。默认值只是为了避免一次性输出过多消息,不是硬上限。
会话/消息输出里都带 chat_type 字段,取值为 private / group / official_account / folded。official_account 涵盖公众号、订阅号、服务号及 mphelper / qqsafe 等系统通知;folded 对应微信里的"订阅号折叠"和"折叠群聊"两个聚合入口。
群聊里的 last_sender、sender 和 stats 的 top_senders 会优先使用群昵称(群名片)。如果本地数据库里没有对应群昵称,则回退到联系人备注、微信昵称或 username。
history / search / new-messages / attachments 以及 stats.top_senders,在群聊上下文里还会附带稳定身份三件套:
sender_username:稳定 wxid,用来区分两个昵称同名的成员sender_contact_display:通讯录里的显示名(备注 > 昵称 > wxid 兜底)sender_group_nickname:群名片本身(同sender的来源,方便机器读取时不必再解析)
解析不到 wxid 时(id2u 没命中且老格式 wxid_xxx:\n... 前缀也不存在)这三字段不会输出,避免伪造空字段污染下游过滤。
history / search / sessions / unread / new-messages / stats / attachments 现在都会附带 meta:
status:ok/possibly_stale/possibly_stale_unknown_shards/windowedunknown_shards: 磁盘上存在、但 daemon 当前没有 key 的message_N.db分片;非空时应先跑wx init --forcechat_latest_timestamp/chat_latest_db: 当前命中数据里最新一条消息的时间和分片来源session_last_timestamp:session.db里 WeChat 自己记录的最新时间;如果明显领先于chat_latest_timestamp,说明结果可能漏了消息
默认情况下,人类用户会在 stderr 看到可执行的 warning;agent / 脚本可直接读 stdout 里的 meta。传 --with-meta 会额外返回 per_shard_latest / cache_mode_per_shard,传隐藏 flag --debug-source 还会带真实 shard_paths。
引用消息会在 history / search / new-messages 输出中显示当前回复和被引用原文:
[引用] 当前回复
↳ 发送者: 被引用内容--type link / --type file 会包含微信 appmsg 里的链接、文件、合并聊天记录和引用消息等变体;搜索时也会匹配解压后可见的引用原文。
朋友圈(SNS)
三个独立命令,区分"通知"和"帖子":
wx sns-notifications # 点赞/评论通知(默认仅未读)
wx sns-notifications --include-read -n 100 # 含已读
wx sns-feed # 近 20 条朋友圈(时间线)
wx sns-feed --user "张三" # 限定作者
wx sns-feed --since 2026-04-01 -n 100 # 按时间
wx sns-search "关键词" # 全文搜索朋友圈正文
wx sns-search "婚礼" --user "李四" --since 2023-01-01- sns-notifications 返回互动通知:
type(like/comment)、from_nickname、content(评论正文)、feed_preview+feed_author(对应原帖) - sns-feed / sns-search 返回朋友圈帖子:
author、content(正文)、media、media_count、location、timestamp;media字段含每张图的 url/thumb/key/token/md5/enc_idx/size,供下游做图片代理或离线渲染。media_count = media.len(),按 DOM 解析的合法<media>子节点计数(malformed XML 返回 0)
朋友圈数据只覆盖你本地刷到过的帖子(微信 app 按需下载)。
公众号文章
公众号文章推送存在独立的 biz_message_*.db 分片,用 biz-articles 单独查:
wx biz-articles # 最近 50 篇
wx biz-articles -n 200 # 更多
wx biz-articles --account "返朴" # 限定公众号(名称模糊匹配)
wx biz-articles --since 2026-05-01 --until 2026-05-10
wx biz-articles --unread # 仅有未读的公众号,每号取最新 1 篇
wx biz-articles --json | jq '.[].url' # 下游消费 URL每条返回:account / account_username / title / url / digest / cover_url / time / timestamp / recv_time_str。多图文推送会展开成多行。
附件提取(图片)
聊天里的附件本体存在 xwechat_files/<wxid>/msg/attach/... 下的 .dat 文件,需要按消息所在 message_resource.db 的 md5 + 平台相关 image key 解码才能拿到原图。
# 1) 列出会话里的图片附件,先拿到不透明的 attachment_id
wx attachments "张三"
wx attachments "AI群" --kind image -n 100
wx attachments "AI群" --since 2026-04-01 --until 2026-04-15
# 2) 把单个 attachment_id 解密写出去(扩展名建议保留 .jpg / .mp4 等)
wx extract <attachment_id> -o ~/Desktop/photo.jpg
wx extract <attachment_id> -o /tmp/x.jpg --overwriteattachments 输出每条带:attachment_id / kind / type / local_id / timestamp / time,群聊里还有 sender 以及稳定身份三件套 sender_username / sender_contact_display / sender_group_nickname(语义同 history / search / new-messages:sender_username 是 wxid,用于两个同名成员之间的稳定区分;解析不到 wxid 时这三字段不输出)。当前 kind 固定为 image;命令名保留成 attachments 是为了后续扩到其他附件类型时不 break CLI。
extract 输出报告里带:md5 / dat_path / dat_size / output / output_size / format(实际识别出的图片格式:jpg / png / gif / webp / hevc 等)/ decoder(实际选用的解码器:legacy_xor / v1_aes / v2)。
支持的解码档位:
- legacy XOR:早期单字节 XOR,无 magic(按文件首字节探测格式自动反推)
- V1 fixed-AES(
07 08 V1 08 07):AES-128-ECB + 固定 keycfcd208495d565ef - V2 AES + XOR(
07 08 V2 08 07):AES-128-ECB + raw + XOR;AES key 平台派生
V2 image key 提取:
- macOS:
kvcommcache(key_<uin>_*.statistic文件名取 uin →md5(str(uin) + wxid)[:16])+ brute-force fallback(md5(str(uin))[:4] == wxid_suffix枚举 2^24);xor_key =uin & 0xff,不是硬编码 0x88 - Windows:扫
Weixin.exe内存匹配[A-Za-z0-9]{32|16}候选,按 V2 template ciphertext-block 反验 - Linux:上游空白,遇到 V2 .dat 会报 unsupported
联系人 & 群组
wx contacts # 联系人列表
wx contacts --query "李" # 按名字搜索
wx members "AI交流群" # 群成员列表wx members --json 返回的成员字段包括:
username:微信内部 usernamedisplay:用于展示的名称,优先使用群昵称contact_display:联系人备注或微信昵称group_nickname:群昵称;本地没有记录时为空字符串is_owner:是否群主
收藏 & 统计
wx favorites # 全部收藏
wx favorites --type image # 按类型筛选(text/image/article/card/video)
wx favorites --query "关键词" # 搜索收藏内容
wx stats "AI群" # 聊天统计
wx stats "AI群" --since 2026-01-01 # 指定时间范围导出
wx export "张三" --format markdown -o chat.md
wx export "张三" -n 2000 --format markdown -o chat.md
wx export "AI群" --since 2026-01-01 --format json输出格式
默认输出 YAML;--json 可切换为 JSON。对 agent 而言,history / search / sessions / new-messages / stats / attachments 的 stdout 现在是 wrapper,而不是裸数组:
wx sessions --json
wx search "关键词" --json | jq '.results[0].content'
wx new-messages --json
wx history "张三" --json | jq '.meta'
wx history "张三" --json --with-meta | jq '.meta.cache_mode_per_shard'Daemon 管理
wx daemon status
wx daemon stop
wx daemon logs --follow---
架构
wx (CLI) ──Unix socket──▶ wx-daemon (后台进程)
│
┌─────────┴──────────┐
DBCache 联系人缓存
(mtime 感知复用)daemon 首次解密后将数据库和 mtime 持久化到 ~/.wx-cli/cache/。重启后 mtime 未变则直接复用,无需重解密。
~/.wx-cli/
├── config.json # 配置
├── all_keys.json # 数据库密钥
├── daemon.sock # Unix socket
├── daemon.pid / .log
└── cache/
├── _mtimes.json # mtime 索引
└── *.db # 解密后的数据库---
原理
微信 4.x 使用 SQLCipher 4 加密本地数据库(AES-256-CBC + HMAC-SHA512,PBKDF2 256,000 次迭代)。WCDB 在进程内存中缓存派生后的 raw key,格式为 x'<64hex_key><32hex_salt>'。
wx-cli 通过 macOS Mach VM API(mach_vm_region + mach_vm_read)、Linux /proc/<pid>/mem 或 Windows VirtualQueryEx + ReadProcessMemory(需要 PROCESS_VM_READ | PROCESS_QUERY_INFORMATION 权限)扫描微信进程内存,匹配该模式提取密钥,daemon 按需解密并缓存。
---
致谢
本项目受 ylytdeng/wechat-decrypt 启发,在其基础上进行了重新设计与实现。感谢原作者的研究与探索。
---
免责声明
本工具仅用于学习和研究目的,用于解密自己的微信数据。请遵守相关法律法规,不得用于未经授权的数据访问。
//! 不透明附件 ID — 跨 CLI / IPC 的圆 trip 句柄。
//!
//! 编码:`base64url_no_pad(serde_json(payload))`。
//! 选择 base64url(json) 而不是紧凑 bit-pack:
//! - phase 1 求稳,不发明二进制协议
//! - 后面加字段(`resource_md5` / `decoder_hint` 之类)老 CLI 不 break
//! - debug 直接 base64 -d | jq 看字段
//!
//! ⚠️ `local_id` 在同一 chat 内会被 WeChat 复用(实测同 chat 最多 7 条同 local_id),
//! 所以 `(chat, local_id, create_time)` 三元组才是定位资源行的最小集。
use anyhow::{anyhow, Context, Result};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AttachmentKind {
Image,
Video,
File,
Voice,
}
impl AttachmentKind {
/// 从 message.local_type 推 attachment kind(只覆盖 phase 1 关心的几种)。
/// 高 32 bit 是版本/会话 flag,要先 mask 到低 32 bit。
pub fn from_local_type(local_type: i64) -> Option<Self> {
let lo = (local_type as u64) & 0xFFFF_FFFF;
match lo {
3 => Some(AttachmentKind::Image),
34 => Some(AttachmentKind::Voice),
43 => Some(AttachmentKind::Video),
// type=49 是 appmsg,里面 subtype=6 才是文件;这里偏宽松返回 File,
// 由 resolver 进一步根据 appmsg subtype 决定是否真的能 extract
49 => Some(AttachmentKind::File),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
AttachmentKind::Image => "image",
AttachmentKind::Video => "video",
AttachmentKind::File => "file",
AttachmentKind::Voice => "voice",
}
}
}
/// 附件 ID payload(序列化后 base64url 编码)。
///
/// `v` 是版本字段,将来 schema 变了可以走分支兼容。当前 v=1。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttachmentId {
/// payload schema version
pub v: u32,
/// 会话 username(同时用于 ChatName2Id 查 chat_id 和拼 attach 路径)
pub chat: String,
/// 消息行的 local_id
pub local_id: i64,
/// 消息行的 create_time(unix 秒)— 用于 disambiguate 同 chat 内 local_id 复用
pub create_time: i64,
/// 附件类别
pub kind: AttachmentKind,
/// 可选 hint:消息所在 message_N.db 的 N。给定时 resolver 可跳过 shard 扫描;
/// 缺省时 resolver 会按 `find_msg_tables` 逻辑全量扫
#[serde(default, skip_serializing_if = "Option::is_none")]
pub db: Option<u8>,
}
impl AttachmentId {
pub fn encode(&self) -> Result<String> {
let json = serde_json::to_vec(self).context("序列化 AttachmentId")?;
Ok(URL_SAFE_NO_PAD.encode(json))
}
pub fn decode(s: &str) -> Result<Self> {
let bytes = URL_SAFE_NO_PAD
.decode(s.trim())
.map_err(|e| anyhow!("attachment_id 不是合法 base64url: {}", e))?;
let id: AttachmentId =
serde_json::from_slice(&bytes).context("attachment_id payload 非合法 JSON")?;
if id.v != 1 {
return Err(anyhow!("不支持的 attachment_id 版本 v={}", id.v));
}
Ok(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_minimal() {
let id = AttachmentId {
v: 1,
chat: "wxid_abc".to_string(),
local_id: 12345,
create_time: 1_715_678_901,
kind: AttachmentKind::Image,
db: None,
};
let s = id.encode().unwrap();
let back = AttachmentId::decode(&s).unwrap();
assert_eq!(back.chat, id.chat);
assert_eq!(back.local_id, id.local_id);
assert_eq!(back.create_time, id.create_time);
assert_eq!(back.kind, id.kind);
assert_eq!(back.db, id.db);
}
#[test]
fn round_trip_with_db_hint() {
let id = AttachmentId {
v: 1,
chat: "1234@chatroom".to_string(),
local_id: 42,
create_time: 1,
kind: AttachmentKind::Image,
db: Some(2),
};
let s = id.encode().unwrap();
assert!(!s.contains('=')); // base64url no-pad
let back = AttachmentId::decode(&s).unwrap();
assert_eq!(back.db, Some(2));
}
#[test]
fn local_type_mask_high_bits() {
// monitor_web.py 里 image push 路径:高位带 flag,低 32 bit 是 3
let high_flag = (0xDEAD_BEEFu64 << 32) as i64 | 3;
assert_eq!(
AttachmentKind::from_local_type(high_flag),
Some(AttachmentKind::Image)
);
}
#[test]
fn rejects_unknown_version() {
let id = AttachmentId {
v: 99,
chat: "x".to_string(),
local_id: 0,
create_time: 0,
kind: AttachmentKind::Image,
db: None,
};
let s = id.encode().unwrap();
assert!(AttachmentId::decode(&s).is_err());
}
}
use anyhow::{bail, Result};
use super::{ImageKeyMaterial, ImageKeyProvider};
pub struct LinuxImageKeyProvider;
impl ImageKeyProvider for LinuxImageKeyProvider {
fn get_key(&self, _wxid: &str) -> Result<ImageKeyMaterial> {
bail!("Linux V2 图片 key 当前未实现;请先用 legacy/V1 图片或在 README 中标注 unsupported")
}
}
mod config;
mod ipc;
mod crypto;
mod scanner;
mod daemon;
mod cli;
mod attachment;
fn main() {
if std::env::var("WX_DAEMON_MODE").is_ok() {
daemon::run();
} else {
cli::run();
}
}
Related skills
How it compares
Use wx-cli when agents need searchable local WeChat desktop data; avoid it when cloud WeChat API integration or non-desktop platforms are required.
FAQ
What is wx-cli?
wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。
When should I use wx-cli?
wx-cli — 从本地微信数据库查询聊天记录、联系人、会话、收藏等。用户提到微信聊天记录、联系人、消息历史、群成员、收藏内容时,使用此 skill 安装并调用 wx-cli。
Is wx-cli safe to install?
Review the Security Audits panel on this page before installing in production.