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 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 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

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 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.11.1] - 2026-04-15

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.11.1.tar.gz (137.9 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.11.1-py3-none-any.whl (67.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for wxflywheel-0.11.1.tar.gz
Algorithm Hash digest
SHA256 08b0f3ccca46bad4c74045fdaea8304d78ef8796391b8daa9c5e87d96094efe7
MD5 b0b20446b15e926ca04ad5d7ef7ea857
BLAKE2b-256 6967d5d7b6415ff4f10b2a82dacd1130ba808b2bf5be5e8f97724005facb55db

See more details on using hashes here.

Provenance

The following attestation bundles were made for wxflywheel-0.11.1.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.11.1-py3-none-any.whl.

File metadata

  • Download URL: wxflywheel-0.11.1-py3-none-any.whl
  • Upload date:
  • Size: 67.9 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.11.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4793ded7481ac3b6c2a1a602b8db17956a2dcc3f823582578eff846810b11f92
MD5 957e2530ca6520806a23b5e1b4f50671
BLAKE2b-256 4ee1b9716b2532246b1ec0d6e51b3f6f25139663f9ea417dec7a89299168aa1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for wxflywheel-0.11.1-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