Skip to main content

Python CLI for wx-ipad automation.

Project description

wxflywheel

Python CLI for wx-ipad automation.

Install

Requirement: Python 3.10+

Published package:

pip install wxflywheel

Local development:

cd cli
make install-dev

Authentication

export WXFLYWHEEL_KEY=your-api-key
export WXFLYWHEEL_HOST=http://host:8011

WXFLYWHEEL_KEY is required for API commands. Help output never requires a key.

Usage

wxflywheel --help
wxflywheel login --help
wxflywheel login status
wxflywheel search article --keyword "微信"
wxflywheel search article --keyword "微信" --sort hot
wxflywheel search article --cursor "<next_cursor>"
wxflywheel search article-detail --url "http://mp.weixin.qq.com/s?__biz=..."
wxflywheel search sticker --keyword "咖啡"
wxflywheel search sticker --keyword "咖啡" --tab newest
wxflywheel search sticker --cursor "<next_cursor>"
wxflywheel search sticker-detail --url "http://mp.weixin.qq.com/s?__biz=..."
wxflywheel search video --keyword "美食"
wxflywheel search video --keyword "美食" --tab newest
wxflywheel search video --cursor "<next_cursor>"
wxflywheel search video-detail --export-id "export/...." --hash-doc-id "..."
wxflywheel related aggregate --keyword "社群运营"
wxflywheel search related --seed "社群运营"
wxflywheel wxindex query --keyword "社群运营"
wxflywheel wxindex related --keyword "社群运营"
python -m wxflywheel login status

Current Command Surface

Curated commands are intentionally published one by one. The current public command surface is:

  • wxflywheel login status
  • wxflywheel search article --keyword "<关键词>" [--sort default|newest|hot]
  • wxflywheel search article --cursor "<next_cursor>"
  • wxflywheel search article-detail --url "<完整文章链接>"
  • wxflywheel search sticker --keyword "<关键词>" [--tab all|newest|hottest]
  • wxflywheel search sticker --cursor "<next_cursor>"
  • wxflywheel search sticker-detail --url "<完整贴图链接>"
  • wxflywheel search video --keyword "<关键词>" [--tab default|newest]
  • wxflywheel search video --cursor "<next_cursor>"
  • wxflywheel search video-detail --export-id "<EXPORT>" --hash-doc-id "<HASH>"
  • wxflywheel related aggregate --keyword "<关键词>"
  • wxflywheel search related --seed "<种子词>"
  • wxflywheel wxindex query --keyword "<关键词>"
  • wxflywheel wxindex related --keyword "<关键词>"
  • wxflywheel version / wxflywheel version show / wxflywheel version check
  • wxflywheel self update
  • wxflywheel mp article published-list|detail|content
  • wxflywheel mp data article-stats|article-list-stats|daily-reads|hourly-reads|daily-shares|by-channel|finish-reads|mass-send-report
  • wxflywheel mp comment articles-with-new|list|reply|delete-reply|elect
  • wxflywheel mp material upload-image|upload-video
  • wxflywheel mp draft create|update|delete|list|detail
  • wxflywheel mp publish mass-send-check|mass-send-commit|direct

WeChat Official Account Management

wxflywheel mp wraps all 26 /v1/mpoffice/* routes as six command groups. These commands keep backend field names as-is and return the raw backend data payload inside the standard {code, data, message} envelope. The two real publish commands default to --dry-run, which intentionally returns code=3132 until you opt in with --live and the server also allows real publish via MPOFFICE_ALLOW_REAL_MASSSEND=true.

  • mp article/v1/mpoffice/Article/*: published-list, detail, content
  • mp data/v1/mpoffice/Data/*: article-stats, article-list-stats, daily-reads, hourly-reads, daily-shares, by-channel, finish-reads, mass-send-report
  • mp comment/v1/mpoffice/Comment/*: articles-with-new, list, reply, delete-reply, elect
  • mp material/v1/mpoffice/Material/*: upload-image, upload-video
  • mp draft/v1/mpoffice/Draft/*: create, update, delete, list, detail
  • mp publish/v1/mpoffice/Publish/*: mass-send-check, mass-send-commit, direct

WeChat Article Search

wxflywheel search article is a read-only official-account article-list command for AI Agents.

  • First page: wxflywheel search article --keyword "微信" [--sort default|newest|hot]
  • Next page: wxflywheel search article --cursor "<next_cursor>"

The command intentionally returns only:

  • keyword
  • sort
  • has_more
  • next_cursor (only when another page exists)
  • items[]

Each article item keeps only:

  • article_id
  • title
  • summary
  • source_name
  • published_at_ts
  • url
  • cover_url
  • read_count_text (only when the live response includes reading-hotness text)
  • read_count (only when a numeric reading count can be parsed from the live response)

The command does not fetch article bodies, comments, like/share/comment counts, or raw backend fields such as report_extinfo_str.

WeChat Article Detail

wxflywheel search article-detail is the curated single-article detail command for AI Agents.

  • Input: one full official-account article URL
  • Output: one normalized payload with:
    • article
    • metrics
    • comments
    • optional comments_error

Important detail:

  • comments.scope is always selected
  • metrics.comment_count is the total comment count
  • comments.items[] are selected comments, not a fake "all comments" view
  • The CLI caller does not manually chain two backend calls; it wraps the backend aggregation endpoint directly

WeChat Sticker Search

wxflywheel search sticker is a read-only WeChat Search sticker-list command for AI Agents. Stickers (贴图) are image-centric official-account posts (item_show_type=8, similar to Xiaohongshu image notes), NOT emoji or GIF stickers — each sticker is an image gallery plus short body text published by an official account.

  • First page: wxflywheel search sticker --keyword "咖啡" [--tab all|newest|hottest]
  • Next page: wxflywheel search sticker --cursor "<next_cursor>"

--tab options:

  • all (default): WeChat's default relevance ranking, no server-side filter
  • newest: chronological newest first (docSortType=["2"])
  • hottest: highest engagement first (docSortType=["4"])

--keyword and --cursor are mutually exclusive: use --keyword to start a new search, --cursor to fetch the next page. --cursor cannot be combined with a non-default --tab (the cursor already encodes the original tab).

The command intentionally returns only:

  • keyword
  • tab
  • has_more
  • next_cursor (only when another page exists)
  • items[]

Each sticker item keeps 16 Agent-facing fields: sticker_id, title, source_name, source_icon_url, published_at_ts, published_relative, cover_url, sticker_url, content_type_code, mp_doc_id, rank, report_id, display_style, source_type, social_tags, hide_control.

The command does not fetch sticker detail pages, comments, image galleries, or engagement metrics — use wxflywheel search sticker-detail for the full 56-field sticker detail view.

WeChat Sticker Detail

wxflywheel search sticker-detail is the curated single-sticker detail command for AI Agents.

  • Input: one full WeChat sticker article URL (from search sticker result's sticker_url or search article result's url when item_show_type=8)
  • Output: one normalized payload with:
    • sticker_info (title, account, location, publish date, originality)
    • pictures[] (image gallery with width/height/dominant color/QR detection/attached products)
    • engagement_metrics (6-dim: read/like/watching/share/collect/comment counts)
    • text_content
    • hashtags[]
    • topics[] (with read counts and article counts)
    • monetization (tipping/ads/product window/paid subscription flags)
    • comments (selected comments with threaded replies)
    • optional comments_error

The returned fields are aligned 1:1 with the web admin console's sticker detail drawer. This wraps the backend's StickerDetail aggregation endpoint which internally chains BatchGetAppMsg(CGI 2594) + AppMsgCommentList(CGI 25246); the CLI caller does not manually chain these calls. The command strictly validates item_show_type==8; passing a long-form article URL returns an error asking the caller to use search article-detail instead.

WeChat Video Search

wxflywheel search video is a read-only WeChat Search video-list command.

  • First page: wxflywheel search video --keyword "小龙虾" [--tab default|newest]
  • Next page: wxflywheel search video --cursor "<next_cursor>"

The command intentionally returns only:

  • keyword
  • tab
  • has_more
  • next_cursor (only when another page exists)
  • items[]

Each video item keeps only:

  • video_id
  • export_id
  • hash_doc_id
  • title
  • cover_url
  • account_name
  • account_icon_url
  • duration_text
  • published_at_text
  • published_at_ts
  • show_type
  • interaction_metrics

interaction_metrics always contains four fields:

  • like_count
  • comment_count
  • forward_count
  • thumb_count

If a field is not present in backend report_extinfo_str, its value is null (not 0). This keeps null semantics for "not reported" and still preserves literal 0 when the backend explicitly reports it as 0.

The command does not fetch:

  • video playback streams
  • creator profiles
  • comments
  • detailed engagement analytics
  • other tabs beyond default and newest

WeChat Video Detail

wxflywheel search video-detail is the curated single Finder video detail command for AI Agents.

  • Input: one export_id and hash_doc_id from a prior wxflywheel search video response
  • Output: one normalized payload with:
    • video
    • author
    • metrics
    • media
    • comments
    • optional agent_download_instructions

video keeps:

  • video_id
  • export_id
  • hash_doc_id
  • title (from videoInfo.description, not search preview)
  • cover_url (from media.fullThumbUrl)
  • width
  • height
  • duration_seconds
  • published_at_ts (unix seconds, int; no relative-time text is generated — Agents should compute locale-appropriate display from the raw timestamp)

author keeps:

  • nickname
  • head_url
  • username
  • signature

metrics keeps:

  • like_count
  • comment_count
  • forward_count
  • friend_like_count
  • thumb_count(always null — the VideoDetailV2 backend does not return favorite/collect counts. If the Agent needs thumb_count, read it from the same video's search video result interaction_metrics.thumb_count

media keeps:

  • full_video_url
  • decode_key

comments keeps:

  • total_count
  • returned_count
  • items[], each with nickname, content, head_url, like_count, published_at_ts, published_at_text, reply_nickname, replies

The command does not fetch agent_download_instructions when full_video_url is missing or empty — this matches frontend conditional rendering.

For downloadable videos, agent_download_instructions is a byte-for-byte copy of the frontend Drawer command template, including:

  • exact newline count
  • decode_key fallback 0 behavior
  • no trailing newline

WeChat Index Query

wxflywheel wxindex query is the curated full-data WeChat Index command for AI Agents.

  • Input: one keyword via --keyword
  • Output: the backend's full keyword payload without CLI trimming or re-interpretation
  • Typical fields:
    • keyword
    • current_value
    • trend
    • multichannel
    • channel_trend
    • updated_at

Use this command when the Agent needs enough raw material to judge topic value, momentum, and channel mix. Use wxflywheel wxindex related only when the Agent wants short high-frequency expansion terms, not the full index dataset.

Output Contract

Command execution emits JSON. Help output remains plain text.

  • Success: stdout, exit code 0
  • Command/runtime errors: stderr, exit code 1
  • Click argument errors: stderr, exit code 2

Schema:

{
  "code": 200,
  "data": {},
  "message": "",
  "meta": {
    "cli": {
      "current_version": "0.3.0",
      "latest_version": "0.3.1",
      "update_available": true,
      "update_severity": "patch",
      "update_command": "pip install --upgrade wxflywheel",
      "self_update_command": "wxflywheel self update",
      "changelog_url": "https://pypi.org/project/wxflywheel/0.3.1/",
      "latest_release_date": "2026-04-10T12:00:00Z",
      "days_behind": 4,
      "cache_age_seconds": 120,
      "has_breaking": false,
      "python_version": "3.11.5"
    }
  }
}

Version Notifications for AI Agents

This CLI's primary users are AI Agents, not humans. Every successful command response carries a meta.cli field with 12 version-related signals so Agents can autonomously detect and decide on upgrades without any out-of-band monitoring.

How it works

  1. Every command emits meta.cli — no special flag needed, it's always there.
  2. 24-hour local cache — version info is cached at ~/.cache/wxflywheel/version_check.json to avoid hitting PyPI on every invocation.
  3. Background refresh — when the cache is stale, a fire-and-forget subprocess worker refreshes it without blocking the current command. If subprocess spawning fails (sandboxed environments), falls back to an inline 500ms-timeout sync refresh.
  4. Breaking-change detection — when a new version is found, the worker parses the CHANGELOG embedded in the PyPI info.description field (the release workflow splices CHANGELOG.md into README.md at build time so PyPI's long_description carries both). It marks update_severity = "breaking" if the new version's section contains ### Breaking, BREAKING CHANGE, or BREAKING: markers. Otherwise severity is SemVer-derived: patch / minor / major. This is a single-request design: version lookup and CHANGELOG parsing share the same PyPI JSON API call — no second network round-trip, and no dependency on GitHub raw (the upstream repository is private and would return 404 to anonymous fetches).
  5. Stable schema — even when offline or cache-empty, the meta.cli object has the same 12 keys with null for unknown values. Agents never need key-exists branches.

Agent upgrade workflow

# Pseudo-code for an Agent using wxflywheel
response = run_cli("wxflywheel login status")
cli_meta = response["meta"]["cli"]

if cli_meta["update_available"]:
    severity = cli_meta["update_severity"]
    if severity == "breaking":
        # Agent should read changelog first before auto-upgrading
        notify_user(
            f"wxflywheel has a BREAKING update to {cli_meta['latest_version']}. "
            f"See {cli_meta['changelog_url']}"
        )
    elif severity in ("patch", "minor"):
        # Safe to auto-upgrade
        run_cli(cli_meta["self_update_command"])  # == "wxflywheel self update"
        # NOTE: the new version only takes effect on the NEXT invocation;
        # the currently running Agent Python process keeps the old code in memory

Manual version check

# Read from cache (fast, offline-OK)
wxflywheel version
wxflywheel version show

# Force refresh from PyPI (may take 1-10s)
wxflywheel version check

Self-update

wxflywheel self update

Invokes python -m pip install --upgrade wxflywheel in a subprocess of the current Python interpreter. Protected by a filesystem lock (~/.cache/wxflywheel/update.lock) to prevent concurrent upgrades. On success, returns before/after version and a reminder that the new version only activates on the next invocation.

Opting out (tests / CI)

Set WXFLYWHEEL_NO_META=1 to make meta a deterministic minimal stub ({"cli": {"current_version": "X.Y.Z"}}). Useful for deterministic test snapshots. wxflywheel's own test suite uses this via an autouse pytest fixture in conftest.py.

Cache directory override

Set WXFLYWHEEL_CACHE_DIR=/custom/path to override the default ~/.cache/wxflywheel. Useful for containerized environments where the home directory is read-only.

Adding a Command Module

Curated commands are intentionally added one by one. Do not expose a backend route directly just because it exists.

Minimum standard for a new curated command:

  • One command maps to one stable user intent.
  • Read-only commands are preferred by default.
  • State-changing behavior must be explicit in the command name or behind an opt-in flag.
  • Output must stay on the standard code/data/message schema.
  • Command logic must use shared helpers from src/wxflywheel/commands/common.py.
  • Every new command must ship with tests before registration.

Workflow:

  1. Copy src/wxflywheel/commands/_template.py.
  2. Replace module/action names and API parameters.
  3. Add focused tests under tests.
  4. Register the command in src/wxflywheel/cli.py.
  5. Run the release checks before merging.

Release Checks

Repeatable release verification now lives in Makefile:

make release-check

This runs:

  • ruff
  • mypy
  • pytest
  • wheel/sdist build
  • twine check
  • editable-command help smoke
  • built-wheel install smoke
  • package rebuild from a clean dist/ / build/ state

The built package ships py.typed, so installed type checkers can treat wxflywheel as a typed package.

Optional live smoke against a real service:

export WXFLYWHEEL_HOST=http://host:8011
export WXFLYWHEEL_KEY=your-api-key
make smoke-live
make smoke-live-article KEYWORD=微信
make smoke-live-article-detail KEYWORD=微信

Development

make install-dev
make release-check

Release Management


[0.12.0] - 2026-04-24

Added

  • mp command group wrapping 26 routes under /v1/mpoffice/*:
    • mp article 3 cmds (published-list/detail/content)
    • mp data 8 cmds (article-stats/list-stats/daily/hourly/shares/by-channel/finish/mass-send-report)
    • mp comment 5 cmds (articles-with-new/list/reply/delete-reply/elect)
    • mp material 2 cmds (upload-image/upload-video with 6-layer local validation)
    • mp draft 5 cmds (create/update/delete/list/detail)
    • mp publish 3 cmds (mass-send-check/mass-send-commit/direct)
  • client.py: extended _request / _request_json / _request_json_once signatures with form= / files= kwargs (backward-compatible, all default None)
  • client.py: new public methods post_form(allow_recovery) (required kwarg) and post_multipart (strict once-only path, no recovery retry for large bodies)

Changed

  • client.py: query params merge order reversed so self.key cannot be overridden by user-supplied extra query (plan §3.2 V4-B)

Security

  • mp material upload-*: 6-layer local validation with path-stripping error messages to avoid leaking local filesystem paths to Agent logs
  • mp draft delete / mp comment delete-reply: --confirm flag required
  • mp publish mass-send-commit / direct: --live flag required for real send

[0.11.2] - 2026-04-15

Docs

  • cli/README.md 补齐 sticker 命令遗漏:多维度规范审计发现整个 README 完全没有 sticker / search sticker / search sticker-detail 相关文档 —— v0.7.0(sticker-detail)和 v0.8.0(sticker list)发版时仅更新了代码和 CHANGELOG,漏了 README。Agent 读 README 会完全不知道 CLI 具备贴图搜索能力,违反 Agent First 表达铁律五触点之"文档宁多勿模糊"。本次补齐:① Usage 代码块新增 4 条 sticker 使用示例(含 default / newest tab / cursor / detail)② Current Command Surface 列表新增 3 条 sticker 命令行 ③ 新增 WeChat Sticker Search 完整段落(位于 WeChat Article Detail 之后 / WeChat Video Search 之前),说明 sticker 的语义(item_show_type=8 图文而非 emoji)、tab 选项、互斥规则、16 字段归一化清单 ④ 新增 WeChat Sticker Detail 完整段落,说明 8 个顶层字段、StickerDetail 聚合接口、item_show_type==8 硬校验行为。
  • 纯文档修复,无代码行为改动;bump 为 0.11.2 以将更新的 README 同步到 PyPI long_description

Fixed

  • search video item schema 防御:真机发现 WeChat Search BT=7 返回的视频 item 在部分关键词下(如"美食")会把 duration / dateTime / image / title / exportId / hashDocID 字段置为 JSON null(livestream 场景或特定 schema 变种),0.11.0 及更早版本对这些字段用 _require_string(..., allow_empty=True) 不接受 None,会抛 "field is missing or not a string" 错误导致整个 search video 请求失败返回 items=[]。修复:对 6 个可选字符串字段用 item.get(key) or "" 统一 None → 空串兜底,对齐前端 item.duration || '' 防御模式。
  • search video item 的 pubTime / showType 改为 best-effort fallback 0:后端对 livestream / 非标准视频可能不返这两个字段,原先 _coerce_intlike 硬校验会抛错,改为 _try_coerce_intlike(...) or 0(对齐前端 ?? 0 防御和 video.width/height/duration_seconds 已有模式),不再阻断整条视频列表。published_at_ts == 0 的视频表示发布时间未知。
  • 新增 regression tests: test_video_normalize_item_with_null_optional_strings(6 字段 None + pubTime 缺失 + showType null 场景)和 test_video_normalize_missing_pub_time_falls_back_to_zero(非数字 pubTime fallback 验证)。

Added

  • search video-detail command:单视频详情命令,wrap 后端 GET /v1/Finder/VideoDetailV2 聚合接口,一次拿到视频元数据 + 创作者信息 + 完整评论(含递归子评论)+ agent_download_instructions 字段(当视频可下载时字节级对齐前端 VideoDetailDrawer.tsx 的下载指令模板,教 Agent 用 wxipad-video 包解密)。字段规则严格对齐前端 drawer 渲染语义:标题用后端 videoInfo.description(真实创作者文案)而非搜索 preview;收藏数在本接口不可得(后端不返),需从 search videointeraction_metrics.thumb_count 获取;发布时间只返 published_at_ts (int),不做相对文本格式化;评论时间格式化为 YYYY/MM/DD HH:MM 对齐前端 formatTime。视频已下架时 agent_download_instructions 字段不出现(对齐前端条件渲染)。

[0.10.1] - 2026-04-15

Fixed

  • search video command 协议漂移紧急修复:0.10.0 基于 2026-04-03 的陈旧 probe 数据实现,视频 group filter 写死 type=79 直挂 items。但 2026-04-15 真机验证发现 WebSearch BT=7 返回结构已变为 type=107 outer group + subBoxes[type=84] 嵌套,导致 0.10.0 所有 search video 请求返回 items=[]。本次修复按 live probe 更新 group filter 和 item 字段映射。
  • 字段 schema 更新cover_urlitem.imageUrl 改为 item.image(字段名后端已改)。删除不再存在的 watch_num_textitem.watchNum)和 tagsitem.tags)。新增 hash_doc_iditem.hashDocID,后端已 string 化无 uint64 精度问题)、duration_textitem.duration,如 "02:56")、published_at_textitem.dateTime,如 "31分钟前")、published_at_tsitem.pubTime,unix timestamp int)。每 item 归一化字段总数从 10 改为 12。
  • interaction_metrics 解析逻辑未变report_extinfo_str 4 维字段 like_cnt/comment_cnt/forward_cnt/thumb_cnt 后端结构稳定)。

Process lessons

  • probe 数据有保质期。Plan 基于 probe 开发时,必须在真机验证阶段用 live probe 再次对齐 —— 不能在 unit test 通过就宣布完成。本次 Plan 的三轮 code-reviewer 审查基于 probe 做事实核查,但 probe 本身已陈旧,所有链路下游都继承了这个陈旧事实。
  • 未来所有涉及外部协议的精品命令,merge 前最后一步必须跑一次 live API dump 对照 Plan §2 事实表的每一条常量值。

[0.10.0] - 2026-04-15

Added

  • search video command: WebSearch (BT=7, Scene=101) 视频列表搜索,支持 default (不限) / newest (最新) 两个 TAB 与 cursor 翻页。每个 item 归一化为 10 个字段 (video_id/export_id/title/cover_url/account_name/account_icon_url/watch_num_text/show_type/tags/interaction_metrics),含来自 report_extinfo_str 的 4 维互动数据(like/comment/forward/thumb,null 表示未上报区分"真 0")。严格对齐前端 web/ui/src/config/searchConfig.tsVideoCard.tsx 实现,无新协议常量。
  • search group docstring 同步从 "four search intents" 修正为 "six search intents",补齐现有的 search sticker 条目和新的 search video 条目(历史陈旧状态修复)。

[0.9.0] - 2026-04-10

Changed

  • Request timeout defaults raised to tolerate proxy tunnel jitter and align with the new server-side read-request retry. DEFAULT_REQUEST_TIMEOUT_SECONDS is now 60.0 (was 30.0); RECOVERY_WINDOW_SECONDS is now 65.0 (was 45.0). The 5-second buffer between window and single timeout prevents the clamp_request_timeout logic from being silently truncated by ε-scale clock drift between recovery_scope deadline capture and the first request's remaining read. Worst-case end-to-end budget for a single command is now ~125s (first attempt 60s + recovery wait 65s), well within the recommended Agent Bash tool timeout of 300000ms. No breaking API changes; callers that explicitly pass timeout= still override the default.
  • Recovery notice now says "最长65秒" instead of "最长45秒" to match the new window.

Server-side companion change (not in this package)

  • The Go backend (srv/wxlink/wxreqinvoker.go) now retries read-only search requests (SendWebSearchRequest, SendBatchGetAppMsgRequest, SendWebFinderSearchRequest) exactly once on network timeout (context.DeadlineExceeded / net.Error.Timeout() / "Client.Timeout exceeded" / "proxy dial timeout") using a fresh connection. Write-type requests (send message, post comment, etc.) are explicitly excluded to prevent duplicate side effects. This shifts the worst-case single-request duration on the server from ~35s to ~50s (pacer 20s + first attempt 15s + retry 15s), which is why the CLI single-request timeout had to rise from 30s to 60s.

[0.8.0] - 2026-04-09

Added

  • wxflywheel search sticker --keyword "<关键词>" [--tab all|newest|hottest] — a new curated sticker list search command that wraps POST /v1/Finder/WebSearch with BusinessType=33562628, Scene=101. Stickers (贴图) are photo-essay posts on WeChat official accounts (item_show_type=8), similar to Xiaohongshu image-text notes. The command parses the nested WebSearch response structure (groups[type=107] → subBoxes[type=84] → items[], a two-column layout) and normalizes each item into 16 Agent-readable fields: sticker_id, title, source_name, source_icon_url, published_at_ts, published_relative, cover_url, sticker_url, content_type_code, mp_doc_id, rank, report_id, display_style, source_type, social_tags, hide_control. Three sub-tabs are exposed: all (default relevance), newest (most recent), hottest (most engagement). Pagination uses the same opaque base64 cursor pattern as search article{keyword, tab, has_more, next_cursor?, items[]}. When the server returns data: null (e.g., no matching results), the command gracefully returns an empty list instead of raising an error.

[0.7.0] - 2026-04-09

Added

  • wxflywheel wxindex query --keyword "<关键词>" — a new curated full-data WeChat Index command that wraps GET /api/wxindex/{keyword} and returns the backend's full keyword payload without CLI trimming or opinionated summarization. The intended caller is an AI Agent that needs enough raw material to judge a topic's value, momentum, recent fluctuation, and channel mix. The typical payload includes keyword, current_value, trend, multichannel, channel_trend, and updated_at.
  • wxflywheel search sticker-detail --url "<贴图URL>" — a new curated single-sticker detail command that wraps GET /v1/Gh/StickerDetail. Stickers (贴图) are photo-essay posts on WeChat official accounts (item_show_type=8, page_type=2), similar to Xiaohongshu image-text notes — they carry an image gallery instead of a long-form HTML body. The command returns a normalized Agent-facing payload with seven stable blocks 100% aligned with the web management console's sticker detail drawer: sticker_info, pictures, engagement_metrics, text_content + hashtags, topics, monetization, and comments, plus optional comments_error. Use search article-detail for long-form articles; use this command for sticker/photo-essay posts.

Changed

  • Shared validation helpers (_require_dict, _require_string, _require_bool, _coerce_intlike) now use generic "payload" suffix in error messages instead of "article result list", since they are shared across article and sticker normalizers. The field_name parameter already carries the domain-specific context (e.g., StickerDetail sticker_info.title).

[0.6.0] - 2026-04-09

Added

  • wxflywheel search article-detail — a new curated single-article detail command that wraps GET /v1/Gh/ArticleDetailV2. The command takes one full official-account article URL and returns a normalized Agent-facing payload with three stable top-level blocks: article, metrics, and comments, plus optional comments_error when the backend's selected-comment补全失败 but the article body and counters still succeeded. The command does not expose backend raw fields such as comment_id, segment_comment_id, extra_comment_id, or the original BatchGetAppMsg content blob.
  • make smoke-live-article-detail — a dedicated live smoke target for the new command. It verifies login status, uses search article --sort hot to collect 3 real article URLs, then runs search article-detail against each and asserts the normalized article/metrics/comments shape plus meta.cli presence.

Changed

  • Article detail now has a formal aggregated backend contract. The CLI no longer needs to think in terms of "BatchGetAppMsg first, AppMsgCommentList second". search article-detail is bound to the new backend aggregation endpoint ArticleDetailV2, which internally still composes the raw steps but returns one stable response to the caller.
  • Comments are now explicitly labeled as selected comments everywhere in the CLI contract. comments.scope is fixed to "selected", metrics.comment_count is the total comment count, and comments.items[] is the selected-comment subset. This removes the previous ambiguity where the backend could return a preloaded batch while the UI phrasing looked like "all comments".
  • CLI docs and release docs now include the article-detail command and the new live smoke target, keeping the published command surface aligned with the actual curated strategy.

[0.5.0] - 2026-04-07

Added

  • wxflywheel search article — a new read-only curated command that wraps POST /v1/Finder/WebSearch for WeChat official-account article lists using the live-verified article search baseline (BusinessType=2, Scene=101). The command intentionally exposes only three server-verified orders (default, newest, hot) and returns a normalized Agent-facing payload: {keyword, sort, has_more, next_cursor?, items[]}. Each article item keeps only article_id, title, summary, source_name, published_at_ts, url, cover_url, plus read_count_text / read_count when the live response actually includes reading-hotness metadata. It does not fetch article bodies, comments, like/share/comment counts, or raw backend fields.
  • Opaque article paging cursor — article pagination no longer exposes raw offset, search_id, or cookies to the caller. The command now returns next_cursor, a base64url-encoded JSON blob carrying {v, keyword, sort, offset, search_id, cookies}. This design is driven by live paging verification: default uses server-returned offsets such as 23 -> 42 (not simple +20), and newest paging breaks if cookies are dropped.
  • make smoke-live-article — a dedicated live smoke target that validates login status, first-page article search for default/newest/hot, second-page paging for default/newest, forbidden-field absence, and meta.cli presence against a real backend.

Changed

  • CLI docs now treat search article as part of the intentional curated command surface. README.md, RELEASE.md, CLAUDE.md, and project memory are updated to describe the new command, its paging model, and the live-smoke entrypoint.
  • cli/RELEASE.md now matches the actual publish path: GitHub Actions OIDC Trusted Publisher, not a manually managed PYPI_API_TOKEN.

[0.4.3] - 2026-04-06

Fixed

  • parse_breaking_from_description no longer false-positives on CHANGELOG sections that describe the breaking-detection feature. Discovered during v0.4.2 end-to-end verification: when the parser finally ran against a real PyPI info.description (for the first time in 3 releases, after v0.4.2 broke the silent fallback), it flagged the v0.3.0 section as has_breaking=True. The reason was that v0.3.0's CHANGELOG entry documents the breaking-detection feature by naming all three marker tokens inline (`### Breaking` headers, `BREAKING CHANGE` phrases, or `BREAKING:` prefixes), and the token scanner treated these literal references as declarations. v0.4.3 adds a _strip_markdown_code_spans preprocessor that removes fenced code blocks (```...```) and inline code (`...`) from the section body before keyword scanning. Real ### Breaking headings and BREAKING CHANGE / BREAKING: tokens in regular prose are still detected; only tokens visually rendered as code literals are ignored.
  • ## [Unreleased] section no longer pollutes PyPI project page. Release workflow now uses awk '/^## \[[0-9]/{found=1} found' instead of cat CHANGELOG.md when splicing into README.md, emitting lines only from the first numeric version header onward. Empty [Unreleased] WIP placeholders stay out of the published long_description.

Added

  • 4 new regression tests covering markdown code-span awareness: backtick-wrapped marker reference (false positive guard), real heading coexisting with backtick reference (must still detect), fenced code block containing marker example (must ignore), and verbatim reproduction of v0.3.0's original prose as a pinned regression fixture.
  • 197 → 201 tests; 100% branch coverage preserved.

Notes

  • The v0.3.0 self-description trap existed from v0.3.0 and was silently masked by the GitHub-raw 404 fallback for 3 releases. This is a textbook "silent fallback hides real bugs" case — v0.4.2's fix to one problem (data source) immediately surfaced a dormant problem (parser not markdown-aware). Both fixes ship in consecutive patches rather than being bundled because v0.4.2 needed to exist on PyPI before end-to-end verification could run.
  • Memory rule reinforced: except X: return False is a reverse-debugging trap. Use Optional[T] to distinguish "unknown" from "confirmed-no"; if bool is mandatory, at least emit a debug log so failures are observable.

[0.4.2] - 2026-04-06

Fixed

  • Breaking-change detection is no longer silently broken. From v0.3.0 through v0.4.1 the fetch_changelog_breaking function fetched raw.githubusercontent.com/vinsew/weixin/main/cli/CHANGELOG.md to look for BREAKING markers, but the upstream repository is private and anonymous raw access returns 404 — meaning has_breaking has been permanently returning False regardless of real CHANGELOG contents since v0.3.0. Agents consuming meta.cli.has_breaking and meta.cli.update_severity == "breaking" have effectively been receiving incorrect (always-non-breaking) signals for every release. v0.4.2 replaces the GitHub raw fetch with local parsing of the PyPI info.description field, which the release workflow (wxflywheel-release.yml) now splices with README + CHANGELOG before twine upload. Zero new network requests — the PyPI JSON API call was already being made for version lookup, so the CHANGELOG arrives in the same response.
  • TD-015 closed. The original technical-debt entry described a pre-emptive concern about unbounded CHANGELOG file growth with stream=True / 100KB cap as the remediation. Investigation showed the real problem was not file size but permanent 404 fallback under private repositories — the "file too large" scenario could never occur because the fetch had never succeeded in the first place. TD-015 is closed as fixed (data source swap), not as implemented-as-described.

Changed

  • meta.cli.changelog_url now points to PyPI project page (https://pypi.org/project/wxflywheel/X.Y.Z/) instead of the GitHub release tag URL (https://github.com/vinsew/weixin/releases/tag/wxflywheel-vX.Y.Z). Agents can now actually follow this URL to read the full long_description including the embedded CHANGELOG. The GitHub release URL was previously unresolvable to anonymous callers because the repository is private.
  • fetch_pypi_latest renamed to fetch_pypi_meta and extended to return a 3-tuple (latest_version, upload_time_iso, description) instead of a 2-tuple. fetch_changelog_breaking deleted outright. New function parse_breaking_from_description(description, version) performs the BREAKING scan as a pure local operation (no network). refresh_cache_from_pypi now makes exactly one PyPI API call and parses everything from the single response.
  • Release workflow (.github/workflows/wxflywheel-release.yml) acquires a new step between tag validation and make release-check: printf '\n---\n\n' >> README.md && cat CHANGELOG.md >> README.md. This modifies only the CI working directory — local developers continue to see the original README.md.

Notes

  • This patch fixes a latent functional regression that existed since v0.3.0. The regression was silent because requests.get(...).raise_for_status() caught the 404 and the function returned False (no BREAKING detected), which is indistinguishable at the cache/meta layer from "CHANGELOG says no breaking changes". Agents relying on has_breaking to auto-upgrade safely were technically receiving a false-negative guarantee for every release since v0.3.0 — though in practice no v0.3.x/v0.4.x release contained real BREAKING markers, so the gap had no observable incident.
  • The v0.4.2 fix is discovered while investigating why TD-015 felt like "an optimization that would never trigger" — which led to recognizing the actual failure mode (private repo 404) vs. the theoretical one (file too large).

[0.4.1] - 2026-04-06

Fixed

  • Matrix CI coverage gap on Windows runners. v0.4.0's try_trigger_background_refresh contains a if sys.platform == "win32" ... else: start_new_session=True branch. On macOS / Linux runners the else branch is taken natively so its line is counted as covered; on Windows runners the if branch is taken natively so start_new_session=True is never executed, causing branch coverage to drop to 99.76% and the 100% coverage gate to fail. v0.4.0 had test_try_trigger_background_refresh_windows_creationflags which monkey-patches sys.platform = "win32" to force the Windows branch on POSIX runners, but lacked the symmetric counterpart. v0.4.1 adds test_try_trigger_background_refresh_posix_start_new_session which monkey-patches sys.platform = "linux" to force the POSIX branch on Windows runners. Both branches are now explicitly covered on every matrix cell; 3 OS × 4 Python = 12 jobs all reach 100% branch coverage.
  • No functional change on any platform. v0.4.0 was already functionally correct on Windows (all 190 tests passed); this patch only fixes the coverage measurement gap so the matrix CI release gate can be truly green.

Notes

  • This release was motivated by the matrix CI's FIRST Windows run revealing the gap — validating the entire v0.4.0 cross-platform strategy: we caught a Windows-specific issue without owning or testing on any Windows machine. The issue turned out to be a test-coverage methodology gap rather than a code bug, which is the best possible outcome for a first matrix run.
  • Release workflow (wxflywheel-release.yml) is still independent of matrix CI (wxflywheel-ci.yml) — v0.4.0 was successfully published to PyPI despite the matrix CI failure. A future v0.4.2 may wire matrix CI as a needs: dependency of the release workflow to make the gate truly blocking, but this requires either merging both workflows or using workflow_call; logged as a consideration for next iteration.

[0.4.0] - 2026-04-06

Added

  • wxflywheel doctor diagnostic command — runs a 6-subsystem self-check (Python runtime / platform identity / required dependencies / cache directory writability / PyPI+GitHub network reachability / subprocess spawn capability) and returns a single JSON verdict. Designed as the FIRST command an Agent should run after pip install wxflywheel on a new machine: surfaces missing deps, broken networks, read-only home directories, wrong Python versions, and sandbox restrictions in one shot rather than letting them emerge one-by-one during real business commands. Requires no API key and does not call any wx-ipad backend — purely a local probe safe in air-gapped environments.
  • Automatic environment fingerprint on all error responses — every error JSON (code/data/message envelope) now carries a compact 10-field platform snapshot under data._environment (platform_system / platform_release / platform_machine / platform_version / python_version / python_implementation / python_executable / locale_preferred_encoding / stdout_encoding / filesystem_encoding). This lets an Agent (or the upstream wx-ipad maintainer) diagnose cross-platform failures without having to round-trip "what OS are you on?" questions. The fingerprint is compact (<300 bytes), contains no PII (no username / hostname / MAC / IP / home path), and only appears on error responses — success responses remain unchanged to minimize bandwidth cost.
  • New module wxflywheel.environment — centralized environment fingerprint collection with defensive _safe wrapper (any attribute lookup that fails returns the string "unknown" instead of None, keeping the schema stable for Agent JSON parsers). Used by both doctor command and error payload injection.
  • GitHub Actions matrix CI (release gate) — on wxflywheel-v*.*.* tag push, the CI workflow now runs the full release-check on 3 operating systems × 4 Python versions = 12 combinations (ubuntu-latest, windows-latest, macos-latest × Python 3.10, 3.11, 3.12, 3.13). This acts as a hard gate before any PyPI publish. Regular main branch pushes continue to use a single fast job (ubuntu + Python 3.11, ~2 min) to preserve quick feedback and avoid burning CI minutes on every dev commit.

Changed

  • error_payload signature is unchanged but behavior changed: when data is None, the returned dict now contains {"_environment": {...}} instead of None. When data is a dict, _environment is merged in as an additional key. When data is a non-dict (list / str / primitive), it becomes {"details": original, "_environment": {...}}. This is technically a JSON envelope change for error responses, which is why v0.4.0 is a minor bump (not patch). Agents that only inspect code/message are unaffected; Agents that inspect data fields should see their existing keys preserved.
  • test_missing_key_exits_1_with_json_stderr (in test_entrypoint.py) updated to assert the new data._environment shape instead of data is None.

Fixed

  • (Layer 1 audit) Verified that no file I/O, subprocess call, JSON serialization, path manipulation, or string encoding in the wxflywheel source depends on the system locale, path separator, line ending convention, or timezone. All open() / Path.open() calls explicitly pass encoding="utf-8"; all subprocess.run / Popen calls use list-form args (no shell=True); all json.dumps uses ensure_ascii=False; all datetimes use timezone.utc. No fixes needed in this release — the audit confirmed v0.3.1 was already cross-platform clean at the source level.

Notes

  • This release completes the 5-layer cross-platform compatibility strategy for wxflywheel:
    1. Code layer: no implicit platform dependencies (verified by audit in v0.4.0)
    2. CI matrix: 3 OS × 4 Python on release gate
    3. Runtime diagnostic: wxflywheel doctor command
    4. Passive telemetry: automatic _environment on error responses
    5. (Optional) Alpine docker test — logged but not implemented
  • Agents can now rely on wxflywheel working on any combination of the 3 major OSes × 4 supported Python versions, and can use wxflywheel doctor to probe edge environments (sandboxes, containers, minimal Python distros) before committing to use the CLI.

[0.3.1] - 2026-04-06

Fixed

  • self update docstring now documents error code -3 (cache directory creation failure) which was previously undocumented, leaving Agents unable to interpret the error. Also corrected the docstring's claim that pip failures return success=false on stdout — in reality pip failures (code -7) are emitted on stderr with exit code 1, matching the standard error contract. The rewritten docstring tells Agents explicitly: "There is no success=false path on stdout — any failure uses stderr + exit 1 + code/data/message envelope; Agents parsing output must check exit code first and read stderr on non-zero."
  • self update subprocess calls (pip install --upgrade wxflywheel and pip show wxflywheel) now use encoding="utf-8", errors="replace" instead of text=True. The previous text=True relied on locale.getpreferredencoding(False), which on Windows CP1252 / CP936 / other non-UTF-8 locales could raise an uncaught UnicodeDecodeError when pip's output contained characters outside the local encoding — breaking the JSON output contract. With explicit UTF-8 + replace, non-decodable bytes become U+FFFD and the JSON envelope survives.
  • try_trigger_background_refresh now wraps the fire-and-forget subprocess.Popen call in a warnings.catch_warnings() block that locally suppresses ResourceWarning. CPython 3.12+ emits ResourceWarning: subprocess <pid> is still running when a Popen wrapper is garbage collected before wait() is called, even when the child process is detached (which is exactly our design). The suppression is local to this single call site; no other warnings are affected.
  • test_all_existing_commands_still_return_three_field_envelope no longer deletes the WXFLYWHEEL_NO_META environment variable. The previous test body had a reversed comment claiming "use stub meta via conftest default" while monkeypatch.delenv actually did the opposite — removing the stub flag set by the autouse conftest fixture and causing build_meta to hit the real code path, which in CI (where no cache file exists) would spawn an actual version-check worker subprocess and leave side effects. The test now keeps the autouse fixture's WXFLYWHEEL_NO_META=1 setting so build_meta returns a deterministic stub, preserving test isolation.

Notes

  • No behavioral changes visible to existing users on the success path. v0.3.0 and v0.3.1 are interchangeable for Agents already using the meta.cli field in the success case; v0.3.1 is strictly more robust on the error path (Windows locales, Python 3.12+, CI isolation).
  • All fixes originated from a post-release rigorous code review against the project-level rules (memory/ai-era-development-philosophy.md, memory/agent-first-expression.md). The review surfaced 2 Important issues and 3 Suggestions; 4 of the 5 are fixed in this patch; the 5th (CHANGELOG size unbounded fetch) is logged to docs/tech-debt.md as a pre-emptive concern with no current symptom.

[0.3.0] - 2026-04-06

Added

  • Agent-aware version notification system. Every command response now includes a meta.cli field with 12 version-related signals (current_version, latest_version, update_available, update_severity, update_command, self_update_command, changelog_url, latest_release_date, days_behind, cache_age_seconds, has_breaking, python_version). Agents using wxflywheel can read this field from any command's JSON response to autonomously detect update availability and decide whether to upgrade.
  • wxflywheel version command group. New version subcommand (alias: version show) displays the cli meta schema from the local 24-hour cache (fast, offline-tolerant). New version check subcommand forces a fresh synchronous PyPI query that bypasses the cache for Agents that need up-to-the-second data.
  • wxflywheel self update command. New self command group with self update subcommand that invokes python -m pip install --upgrade wxflywheel in a subprocess of the currently running Python interpreter. Protected by a cross-process file lock (~/.cache/wxflywheel/update.lock) with a 60-second acquisition timeout to prevent concurrent upgrades from corrupting the package. Returns before/after version, pip stdout/stderr tails, and a hint that the new version only takes effect on the next invocation (current process keeps old code in memory).
  • Background cache refresh worker. A fire-and-forget subprocess (python -m wxflywheel._version_check_worker) is spawned automatically when the 24-hour cache is expired or missing. On platforms where subprocess spawning fails (OSError), falls back to an inline synchronous refresh with a 500ms hard timeout. The main command is never blocked on network I/O by the version-check subsystem.
  • Breaking-change detection from CHANGELOG. When a new version is detected, the worker fetches the GitHub-hosted CHANGELOG and searches the new version's section for ### Breaking headers, BREAKING CHANGE phrases, or BREAKING: prefixes. If found, update_severity is set to "breaking" instead of the SemVer-derived patch/minor/major.

Changed

  • New runtime dependencies: packaging>=23.0,<26 (for PEP 440 version comparison) and filelock>=3.12,<4 (for cross-process update lock). Both are small, widely-used PyPA-maintained packages.
  • emit_payload now accepts an include_meta: bool = True keyword argument. Meta injection is wrapped in a broad except so any version-check failure (network, filesystem, parsing) is silently downgraded to an absent meta field — the primary code/data/message response contract is never broken by version-check internals.
  • Ruff per-file-ignores now also covers version_check.py and _version_check_worker.py for the same Agent First expression-rule reasons as cli.py and commands/.

Compatibility

  • The code/data/message three-field JSON envelope is unchanged and fully backwards compatible. meta is an additive optional field. Agents that parse only the original three fields continue to work without modification. Agents that parse the new meta field get a stable 12-key schema where unknown values are null (not missing keys) for deterministic parsing.

[0.2.1] - 2026-04-05

Changed

  • Rewrote all 8 command and group docstrings (4 groups + 4 actions) to comply with the project-level Agent First expression rule. Every docstring now includes: domain marker (WeChat), action verb, data source, concrete input/output example, and a contrasting qualifier that disambiguates from sibling commands. No behavioral changes — CLI surface, arguments, JSON output contract, and exit codes are identical to v0.2.0.
  • login status now discloses the full login state enum (online / reconnecting / offline / not_login / logout / unknown) and response fields (loginState, loginErrMsg, loginJournal, targetIp).
  • wxindex related vs search related now carry explicit cross-references telling Agents which to pick: WeChat Index returns short high-frequency terms (咖啡机 / 咖啡色), WeChat Search returns long-tail user queries (附近咖啡店 / 星巴克咖啡), and the two sources are non-overlapping in practice.
  • related aggregate docstring now documents all 8 response fields (related / from_search / from_wxindex / both_sources / search_only / wxindex_only / errors) and partial-failure resilience behavior.

[0.2.0] - 2026-04-05

Added

  • Added wxflywheel related aggregate --keyword "<关键词>", which merges Search and WxIndex related keywords with cross-source deduplication (both → search-only → wxindex-only order).
  • Added wxflywheel search related --seed "<种子词>", which calls the RelatedKeywords aggregation API and returns {seed, related[]} inside the standard JSON envelope.
  • Added wxflywheel wxindex related --keyword "<关键词>", which calls the wxindexsug endpoint and returns {keyword, related[]} inside the standard JSON envelope.

Fixed

  • main() now normalizes non-integer SystemExit values into standard JSON stderr output instead of silently returning exit code 1.
  • Click parser errors are now normalized through the CLI entrypoint and no longer bypass the code/data/message contract.
  • make release-check now rebuilds from a clean package artifact state, so stale wheels in dist/ no longer break wheel smoke tests.
  • The published wheel now includes wxflywheel/py.typed, matching the package's typed metadata.

[0.1.0] - 2026-04-04

Added

  • Standardized wxflywheel package foundation with src/ layout and installable console entry point.
  • Unified JSON output contract on code/data/message.
  • Shared command authoring helpers and repeatable release checks.
  • Buildable sdist/wheel release flow and repository CI coverage for the CLI package.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wxflywheel-0.12.0.tar.gz (168.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wxflywheel-0.12.0-py3-none-any.whl (86.1 kB view details)

Uploaded Python 3

File details

Details for the file wxflywheel-0.12.0.tar.gz.

File metadata

  • Download URL: wxflywheel-0.12.0.tar.gz
  • Upload date:
  • Size: 168.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wxflywheel-0.12.0.tar.gz
Algorithm Hash digest
SHA256 e3a59051cbad4c706e58ed148ba167a27505ba8546c9894c50e414145ac0864e
MD5 efa72285c78716bf76f19e2d385d7842
BLAKE2b-256 64227e7bf95c3c2feaf57ea2e0eb64393c6a5d102cc4c3539f171ba15041d9ce

See more details on using hashes here.

Provenance

The following attestation bundles were made for wxflywheel-0.12.0.tar.gz:

Publisher: wxflywheel-release.yml on vinsew/weixin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wxflywheel-0.12.0-py3-none-any.whl.

File metadata

  • Download URL: wxflywheel-0.12.0-py3-none-any.whl
  • Upload date:
  • Size: 86.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wxflywheel-0.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a5f1840b4a69481112b968032f61758a5fce67483ca3dc2371970f2bce1656fe
MD5 e8ae70113746be2cea74098a091f601c
BLAKE2b-256 3fdca06307dd40ec67230c10b730d8fcdc64992db5d8946a188df50fafa1d302

See more details on using hashes here.

Provenance

The following attestation bundles were made for wxflywheel-0.12.0-py3-none-any.whl:

Publisher: wxflywheel-release.yml on vinsew/weixin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page