* docs(macos): document codesign side-effect popup ("微信" 想访问其他 App 的数据)
After `codesign --force --deep --sign - /Applications/WeChat.app`, macOS
treats the re-signed WeChat as a different code identity from the
original. When WeChat then accesses its own container / cache / app-group
data (notably triggered when opening 公众号 articles), macOS fires the
"'微信' 想访问其他 App 的数据" popup.
This is a known side-effect of the current macOS invasive init path,
not a "wx-cli is reading other apps' data" issue and not a 公众号-only
problem — 公众号 is just a high-frequency trigger surface because of
WebView / cache access.
Document this in 3 places per agreed scope:
- README.md macOS init: add "副作用提示" callout linking to the guide
- docs/macos-permission-guide.md: new §六 with first-principles
explanation, mitigation options, and long-term direction
- src/cli/init.rs: print a short macOS-only warning at the end of
`wx init` so users see it right when they finish the invasive setup
* review: stop overstating the trade-off and condition the init warning
Per codex review on PR #64:
1. src/cli/init.rs warning was unconditional but the wording presumed
the user had taken the ad-hoc re-sign path. If init goes through the
tier 2 path (Apple-signed WeChat + GUI Terminal + Developer Tools TCC
authorization), the warning would mis-fire. Reword conditionally and
point to the GitHub URL of the doc instead of a relative path that
release-binary / npm-installed users won't have on disk.
2. docs/macos-permission-guide.md §六 and the matching README callout
said "restoring official WeChat = giving up macOS memory-scan". This
contradicts the same guide's §一 实测表 which shows
"Apple 签名 + 本机 Terminal sudo = ✅". Restoring the official
signature only gives up the default re-sign path; the local-Terminal
+ Developer-Tools route still works on Apple-signed WeChat. Only
SSH + Apple-signed WeChat actually requires re-signing.
* review (round 2): caveat empirical gap + drop emoji
Self-review found two issues both LGTMs missed:
1. The "tier 2 仍走通" claim (README + §六) leans on §一 实测表 row
"Apple 签名 + 本机 Terminal sudo = ✅". But that data only covers
macOS 10.15 (Catalina) and 11.1 (Big Sur). macOS 14/15 — the exact
versions where the popup behavior originates — were never tested
for that path in this project. Add an explicit caveat instead of
silently extrapolating across major macOS versions.
2. `init.rs` warning used a ⚠️ emoji prefix, which violates the
project + global "no emojis in files unless requested" rule. README
and the rest of init.rs have no emoji. Replace with `[macOS]`.
- Move init_logging() into daemon-only branch — CLI path doesn't need
tracing and WARN-level output could corrupt JSON/YAML stdout
- Add TeeWriter that duplicates tracing output to both stderr and
~/.wx-cli/daemon.log, ensuring direct invocation (WX_DAEMON_MODE=1
without 'wx daemon start') also writes to the log file
- Keep run_start() stderr redirect as primary path; file writer is
a safety net for edge-case direct launches
Lays down the skeleton for聊天附件 (chat attachment) extraction. This commit
introduces the `attachment` module with:
- `attachment_id`: opaque base64url(json) round-trip handle for CLI/IPC. Carries
`(chat, local_id, create_time, kind)` — `local_id` alone is not unique
(实测同 chat 内最多 7 条同 local_id 的记录), so create_time is required for
disambiguation.
- `decoder/`: dispatch by 6B header magic. Three branches:
- `V2_MAGIC` → AES-128-ECB + raw + XOR (need image AES key)
- `V1_MAGIC` → AES-128-ECB with fixed key `cfcd208495d565ef` (= md5("0")[:16])
- else → legacy single-byte XOR with magic auto-detect
Manual ECB + PKCS7 unpad to avoid pulling in another crate.
- `resolver`: `message_resource.db` lookup chain
`username → ChatName2Id.rowid → MessageResourceInfo.packed_info → md5`
+ on-disk `.dat` selection (full > _h > _t) under
`<wxchat_base>/msg/attach/<md5(chat)>/<YYYY-MM>/Img/<md5>[_t|_h].dat`.
Honors `message_local_type % 2^32` to strip the high flag bits, and orders by
`message_create_time DESC` to handle local_id reuse.
- `image_key/`: stub trait + macOS / Windows placeholders. To be filled by
codex with the V2 image key extraction (kvcomm + brute-force on macOS, memory
scan on Windows).
V1 decoder ships with 6 unit tests covering every supported magic + the BMP
extra validation; resolver ships with packed_info parser + dat-file selection
tests; v2 decoder ships with header validation tests. 21 tests pass.
`cargo check` and `cargo check --target x86_64-pc-windows-gnu` both clean.
The 原理 section previously listed only macOS Mach VM API and Linux /proc/<pid>/mem,
omitting the Windows scanner path that has existed in src/scanner/windows.rs since
the Rust rewrite. Add the Windows API pair and the required process access rights
so the section accurately reflects all three platforms supported in CI/builds.
- daemon: write pid file only after IPC bound; clean sock+pid on normal return
- transport: PidFile JSON metadata + identity verification (ps/QueryFullProcessImageNameW); SIGTERM with poll-timeout; backward-compat read for plain-text pid
- daemon_cmd: status/stop work with both new JSON and legacy plain-text pid file
- config: cwd → exe_dir → ~/.wx-cli config precedence matches `wx init` write order; Windows DB auto-detect picks newest by latest mtime
- crypto: full_decrypt uses read_exact for intermediate pages, zero-pads only the final partial page; tests cover short-chunk reads and early EOF
- scanner/windows: page protect check covers PAGE_READWRITE / PAGE_WRITECOPY / PAGE_EXECUTE_*WRITE* with modifier-bit stripping
Cross-reviewed by @wx-cli-coder. Windows verified via `cargo check --target x86_64-pc-windows-gnu` (no Windows runtime test).
- q_contacts: replaced ad-hoc `gh_*`/`biz_*` prefix filter with
`chat_type_of == "private"`. The old filter leaked groups
(`@chatroom`), folded entries (`brandsessionholder` /
`@placeholder_foldgroup`), verified service accounts
(`verify_flag != 0`), and internal `@xxx` system accounts into
`wx contacts` output.
- q_search: parallelized the per-message-DB blocking phase via
`JoinSet::spawn_blocking`. Previously the `for (db_path, ...) in
by_path { ... .await }` loop ran one DB at a time; users with N
message_*.db shards paid N× latency. Each DB now runs concurrently
on the blocking pool; total latency collapses to a single slow DB.
- q_new_messages: fixed `new_state` reset path so first-run + truncated
sessions don't lock `since_ts` at `fallback_ts` forever. Old code
always wrote `state[uname] = old_since_ts || fallback_ts` for changed
sessions, then advanced only those that appeared in `all_msgs`. On
first run (state=None) truncated sessions ended up with
`state[uname] = now-86400` and stayed there across calls — every
subsequent call re-scanned a window that grew with elapsed time.
New logic separates three cases:
* in_results → advance to returned_max (incremental fetch)
* truncated + state → keep prev since_ts (retry next call)
* truncated + none → advance to session_ts (avoid lock-in; old
messages remain reachable via `wx history`).
macOS TCC binds permissions to (bundle id, csreq) where csreq encodes
the app's code signature. `codesign --force --deep --sign -` on
WeChat changes the csreq, silently invalidating every existing TCC
grant for com.tencent.xinWeChat — yet System Settings still paints
each toggle as ON because the UI only checks bundle id, hiding the
drift. WeChat then reprompts for screen recording / camera /
microphone / file access despite "looking allowed".
Three doc-only updates, no code changes:
- README.md quick start: add the `tccutil reset` loop right after the
codesign step, plus a one-line callout pointing at the deep-dive
section.
- SKILL.md macOS init flow: same loop in the agent-readable order, so
agents executing the steps don't skip it.
- docs/macos-permission-guide.md: new section 五 with first-principles
root cause, the reset loop, the macOS 26 "录屏与系统录音 / 仅系统
录音" UI split footgun, and ad-hoc signature verification.
Builds on the BobbyCat PR #29 — keeps the symptom description and the
macOS 26 UI split note, expands scope from ScreenCapture-only to all
TCC services that re-signing actually breaks (Camera / Microphone /
AppleEvents / AddressBook / Documents / Downloads / Desktop), drops
the misleading TCC.db sqlite query (path varies by macOS version, can
need FDA, and is no more useful than just trying WeChat's screenshot
again), and explicitly leaves the reset as a manual step rather than
auto-running it from `wx init` because it would wipe currently-working
grants.
Co-authored-by: BobbyCat <114374951+BobbyCats@users.noreply.github.com>
* feat: expose url field for link/appmsg messages
Extract <url> from appmsg XML in type-49 messages and append it as
a 'url' field in history/search output. The field is omitted when
the message has no valid URL (non-link types, empty, non-http).
* fix: normalize appmsg urls across query outputs
---------
Co-authored-by: tsinghu <tsinghu@tencent.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: support group nicknames
* fix(group): keep duplicate nickname senders separate in stats
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
DaemonCommands::Start had its own --tcp subcommand flag, so
'wx --tcp=ADDR daemon start' ignored the global --tcp and
started the daemon with no TCP listener. Now the global --tcp
is used as fallback when the subcommand flag is absent.
--tcp consumed the following subcommand as its value (e.g.
'wx --tcp daemon start' parsed --tcp=daemon). Adding
require_equals=true forces --tcp=ADDR syntax so subcommands
are parsed correctly.
- src/cli/transport.rs
GSD context:
- Milestone: M001 - TCP Transport
- Slice: S04
- Task: T02 - Added TCP vs local transport data comparison test that queries sessions via both transports and asserts deep equality
GSD-Task: S04/T02
- (none)
GSD context:
- Milestone: M001 - TCP Transport
- Slice: S02
- Task: T03 - All changes compile on native and Windows targets; 32 unit tests pass including new TCP transport tests
GSD-Task: S02/T03