@fateforge/wechat-mp-cli 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/AGENT.md +59 -0
- package/.agent/AGENT_zh.md +59 -0
- package/.agent/CLI-SPEC.md +760 -0
- package/.agent/CLI-SPEC_zh.md +700 -0
- package/.agent/SEC-SPEC.md +142 -0
- package/.agent/SEC-SPEC_zh.md +126 -0
- package/.agent/SKILL-SPEC.md +199 -0
- package/.agent/SKILL-SPEC_zh.md +195 -0
- package/AGENTS.md +30 -0
- package/AGENTS_zh.md +30 -0
- package/CHANGELOG.md +119 -0
- package/CODE_OF_CONDUCT.md +35 -0
- package/CODE_OF_CONDUCT_zh.md +35 -0
- package/CONTRIBUTING.md +144 -0
- package/CONTRIBUTING_zh.md +144 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/NOTICE_zh.md +16 -0
- package/README.md +148 -0
- package/README_zh.md +148 -0
- package/SECURITY.md +71 -0
- package/SECURITY_zh.md +71 -0
- package/docs/LIVE-SMOKE-EVIDENCE.md +195 -0
- package/docs/OFFICIAL_ENDPOINT_COVERAGE.md +42 -0
- package/docs/OFFICIAL_ENDPOINT_COVERAGE_zh.md +42 -0
- package/docs/OPEN_SOURCE_CHECKLIST.md +60 -0
- package/docs/OPEN_SOURCE_CHECKLIST_zh.md +60 -0
- package/package.json +57 -0
- package/scripts/run.js +46 -0
- package/skills/wechat-mp-cli/SKILL.md +143 -0
- package/skills/wechat-mp-cli/test-prompts.json +57 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Live Smoke Evidence
|
|
2
|
+
|
|
3
|
+
Recorded live smoke for `release_readiness.required_evidence:
|
|
4
|
+
recorded_live_smoke_for_stable`, run against the **real WeChat Official
|
|
5
|
+
Account API** (`https://api.weixin.qq.com`).
|
|
6
|
+
|
|
7
|
+
- **Date:** 2026-06-14
|
|
8
|
+
- **Target:** a WeChat Official Account **sandbox/test account** (测试号).
|
|
9
|
+
The AppID and AppSecret are intentionally **not** recorded here; only
|
|
10
|
+
aggregate pass/fail. Credentials were supplied via the
|
|
11
|
+
`WECHAT_MP_CLI_APP_ID` / `WECHAT_MP_CLI_APP_SECRET` environment variables.
|
|
12
|
+
- **Method:** each command invoked with `--format json`; envelope `ok`/`error`
|
|
13
|
+
asserted. The one real write was a temporary material upload, which WeChat
|
|
14
|
+
auto-expires in 3 days — no cleanup required.
|
|
15
|
+
|
|
16
|
+
## 2026-06-14 — v1.0.2 new commands (live)
|
|
17
|
+
|
|
18
|
+
Verified against the same test account (aggregate only):
|
|
19
|
+
|
|
20
|
+
| Command | Result | Notes |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `qrcode create --scene-str … --expire-seconds 60` (dry-run → confirm) | PASS | returned ticket + `showqrcode_url` |
|
|
23
|
+
| `tag create` / `tag get` / `tag update` / `tag delete` (dry-run → confirm) | PASS | full CRUD; created tag id 100, updated, then deleted (cleaned up) |
|
|
24
|
+
| `user list` | PASS | `total: 0`, `next_openid: ""` — correct empty result (test account has no followers) |
|
|
25
|
+
| `menu addconditional --file … --dangerous` (dry-run) | PASS | preview generated; not confirmed to avoid leaving persistent conditional-menu state |
|
|
26
|
+
| `user info <openid>` · `tag members` · `tag tagging`/`untagging` | implemented, mock-verified | **not live-exercisable on the test account** — these need real followers, which the 测试号 has none of. The commands authenticate and call the real endpoints correctly; only follower data is unavailable in the sandbox. |
|
|
27
|
+
|
|
28
|
+
All v1.0.2 new commands are implemented + mock-verified; the follower-dependent ones are honestly noted as not exercisable on an empty test account.
|
|
29
|
+
|
|
30
|
+
## 2026-06-15 — batch commands (dry-run / contract only)
|
|
31
|
+
|
|
32
|
+
New batch surface: `message mass sendall` / `mass send --openids` / `mass preview`
|
|
33
|
+
/ `mass get` / `mass delete`, and `user info-batch`.
|
|
34
|
+
|
|
35
|
+
**Environment for this round: no live WeChat credentials were available**
|
|
36
|
+
(`doctor` → `account_config: warn (no account configured)`, no
|
|
37
|
+
`WECHAT_MP_CLI_APP_ID`/`_APP_SECRET`), and per the safety policy
|
|
38
|
+
broadcast/irreversible batches are **dry-run only at night** regardless. So no
|
|
39
|
+
real broadcast, preview-send, recall, or follower read was executed against the
|
|
40
|
+
live API. Verification was done **offline against the built binary** (validation
|
|
41
|
+
/ gate / envelope / single-use confirm) plus the **mock-upstream e2e suite**
|
|
42
|
+
(per-item aggregation, replay-conflict through a real send to the mock).
|
|
43
|
+
Aggregate pass/fail only — no openids, msg_ids, tokens, or secrets recorded.
|
|
44
|
+
|
|
45
|
+
| Command | Result | Method |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `message mass sendall` | dry-run only | binary: critical `--dangerous` double-gate enforced; `--dry-run` issues a confirm token (not executed); audience guard (`--to-all` xor positive `--tag-id`) and body guard (exactly one of `--text`/`--mpnews-media-id`) both return `E_VALIDATION`. **Not real-machine executed** (no creds + night policy). |
|
|
48
|
+
| `message mass send --openids` | dry-run only | binary: critical gate enforced; plural `--openids` (comma + repeated) de-duped, input order preserved, blast-radius `total`/`targets` shown in preview; empty list and dual-body → `E_VALIDATION`. **Not real-machine executed.** |
|
|
49
|
+
| `message mass preview` | dry-run only | binary: high-risk gate enforced; `--dry-run` confirm token issued; missing recipient (`--openid`/`--towxname`) → `E_VALIDATION`. Sends one real message when confirmed, so **not executed** (no creds). |
|
|
50
|
+
| `message mass delete` | dry-run only | binary: critical gate enforced; `--dry-run` token issued (not executed); `--msg-id ≤ 0` → `E_VALIDATION`. Irreversible recall → **dry-run only, never real-machine executed.** |
|
|
51
|
+
| `message mass get` | not tested (live) | binary: `--msg-id ≤ 0` → `E_VALIDATION` before any call; valid id with no creds → `E_CONFIG` (validation precedence confirmed). Live read **not tested** — needs a real `msg_id` + credentials. Read path mock-verified. |
|
|
52
|
+
| `user info-batch` | dry/structure only | binary: empty `--openids` → `E_VALIDATION`; populated list with no creds → `E_CONFIG` (validation + plural parse precede the call). Per-item aggregation (`items[]` keyed by openid, de-dupe, `_untrusted`, `summary` total/succeeded/failed) **mock-verified** in e2e. Live read **not tested** — needs real followers. |
|
|
53
|
+
|
|
54
|
+
### Contract points exercised this round
|
|
55
|
+
|
|
56
|
+
| Contract point | Result | Method |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Single-use confirm token (replay rejected) | PASS | binary: dry-run → first `--confirm` consumes token (then `E_CONFIG`, no creds) → replay returns `E_CONFLICT` "token already used". Also mock e2e `TestBatch_MassSendConfirmThenReplayConflict`. |
|
|
59
|
+
| `--dangerous` second gate (high/critical) | PASS | binary: every critical (`sendall`/`send`/`delete`) and high (`preview`) command blocks with `E_CONFIRMATION_REQUIRED` when `--dangerous` is absent. |
|
|
60
|
+
| Partial-failure aggregation shape | PASS | mock e2e `TestBatch_InfoBatchAggregatesPerItem`: 3 inputs incl. one non-follower → `succeeded:2 / failed:1`, item order preserved, ghost item carries `E_NOT_FOUND`. |
|
|
61
|
+
|
|
62
|
+
No real broadcast, recall, preview-send, or follower read was performed. The
|
|
63
|
+
irreversible/broadcast batches (`mass sendall`, `mass send`, `mass delete`) were
|
|
64
|
+
**dry-run verified, never real-machine executed**, per the safety policy.
|
|
65
|
+
|
|
66
|
+
## 2026-06-15 — batch commands (real credentials, live probe)
|
|
67
|
+
|
|
68
|
+
Re-run of the batch surface **with real test-account credentials present** this
|
|
69
|
+
time (supplied via `WECHAT_MP_CLI_APP_ID` / `WECHAT_MP_CLI_APP_SECRET` env vars
|
|
70
|
+
only — never written to any file, commit, or this document). Built from branch
|
|
71
|
+
`smoke/batch-2026-06-15` source (`go build`); the globally installed v1.0.2 has
|
|
72
|
+
no batch commands.
|
|
73
|
+
|
|
74
|
+
- **Account state:** `user list` → `total: 0`, `next_openid: ""`. The test
|
|
75
|
+
account still has **zero followers**, so follower-dependent reads have no live
|
|
76
|
+
data and broadcasts have no recipients (non-polluting by construction).
|
|
77
|
+
- **AppID/AppSecret are intentionally not recorded.** Only aggregate pass/fail,
|
|
78
|
+
WeChat return codes, and exit codes — no openids, msg_ids, tokens, or secrets.
|
|
79
|
+
|
|
80
|
+
### Live API probe results (real round-trips)
|
|
81
|
+
|
|
82
|
+
Each command was driven to a **real** `api.weixin.qq.com` call to observe the
|
|
83
|
+
genuine upstream return code per the test account's permission grant.
|
|
84
|
+
|
|
85
|
+
| Command | Classification | Live result |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `user info-batch --openids` | **live** | Real batchget call; invalid openids → upstream `40003` mapped per-item to `E_VALIDATION`; `items[]`/`summary` aggregated correctly (see below). |
|
|
88
|
+
| `message mass sendall --to-all` | **live, permission OK** | Real call returned `errcode:0` "send job submission success" + a sandbox `msg_id`. Test account **has** sendall permission; 0 followers → no recipients reached. |
|
|
89
|
+
| `message mass send --openids` | **live, permission restricted (48001)** | Real call returned WeChat `48001 api unauthorized` → `E_FORBIDDEN`, exit 4. **Openid-list mass send is not authorized on the test account.** |
|
|
90
|
+
| `message mass preview` | **live, permission OK** | Real call returned `40003 invalid openid` (not 48001) → `E_VALIDATION`. The API accepted the request and only rejected the synthetic openid; preview permission is present. |
|
|
91
|
+
| `message mass get --msg-id` | **live** | Real call with a bogus id returned `40059 invalid msg id` → `E_VALIDATION`. Read permission present. |
|
|
92
|
+
| `message mass delete --msg-id` | **live** | Real call with a bogus id returned `40059 invalid msg id` → `E_VALIDATION`. Delete reached real API; only the bogus id was rejected. No real message recalled. |
|
|
93
|
+
|
|
94
|
+
**Permission summary:** on this test account only the **openid-list `mass send`**
|
|
95
|
+
returned `48001 api unauthorized`. `sendall`, `preview`, `get`, and `delete` all
|
|
96
|
+
reached genuine API behavior (`errcode:0` or content-level errors), so they are
|
|
97
|
+
**not** permission-restricted here.
|
|
98
|
+
|
|
99
|
+
### user info-batch aggregation (live)
|
|
100
|
+
|
|
101
|
+
Input `--openids o_fake_BBB,o_fake_AAA,o_fake_BBB --openids o_fake_AAA,o_fake_CCC`
|
|
102
|
+
resolved to exactly `[BBB, AAA, CCC]` — **de-duplicated, input order preserved**.
|
|
103
|
+
All three failed at the live batchget call (`40003`); the chunk-level error
|
|
104
|
+
mapped onto **every item** as `E_VALIDATION`, `summary{total:3,succeeded:0,failed:3}`
|
|
105
|
+
(counts equal the item tally), and the envelope carried `_untrusted:["items"]`.
|
|
106
|
+
**Success path (live, 2026-06-15 — owner scanned the test-account QR to follow):**
|
|
107
|
+
one real follower openid → `summary{total:1,succeeded:1,failed:0}`, `item.ok=true`
|
|
108
|
+
with the full subscriber profile (nickname / openid / subscribe_time / tagid_list /
|
|
109
|
+
remark / sex / … 17 fields) and `_untrusted` present. De-dupe + partial-failure in one
|
|
110
|
+
call: real openid ×2 + one bogus → `total:2` (folded), `succeeded:1/failed:1`, per-item
|
|
111
|
+
`[true,false]`. (No real openid/nickname values are recorded in this document.) The
|
|
112
|
+
`E_NOT_FOUND` ghost-item path remains mock-verified.
|
|
113
|
+
|
|
114
|
+
### Guards & gating (real creds present, so gates run for real)
|
|
115
|
+
|
|
116
|
+
| Contract point | Result | Method |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `--dangerous` second gate (critical `sendall`) | PASS | Without `--dangerous`: `E_CONFIRMATION_REQUIRED`, exit 5. With `--dangerous --dry-run`: confirm token + `expires_at` issued. |
|
|
119
|
+
| Single-use confirm replay | PASS | `send` confirm token consumed by first call (token burned **before** the write, so even the 48001 failure consumed it); replay → `E_CONFLICT`, exit 6. |
|
|
120
|
+
| Audience guard — neither `--to-all` nor `--tag-id` | PASS | `E_VALIDATION` "set --to-all or a positive --tag-id", exit 2. |
|
|
121
|
+
| Audience guard — `--to-all` **and** `--tag-id` together | **PASS (fixed 2026-06-15, commit f2dcbef)** | Now rejected: both set → `E_VALIDATION` "mutually exclusive: set exactly one", exit 2. Originally an observed gap (`--to-all` silently won); fixed via XOR validation + unit test `TestResolveSendAllFilterXOR`. |
|
|
122
|
+
| Body guard — no body | PASS | `E_VALIDATION` "a message body is required", exit 2. |
|
|
123
|
+
| Body guard — both `--text` and `--mpnews-media-id` | PASS | `E_VALIDATION` "set only one of …", exit 2. |
|
|
124
|
+
| `mass get` / `info-batch` validation precedence | PASS | `--msg-id 0` and empty `--openids` → `E_VALIDATION` before any API call. |
|
|
125
|
+
| Dry-run envelopes (all batch cmds) | PASS | `send`/`preview`/`delete`/`sendall` dry-run all emit `operation` + `preview` + `confirm_token` + `expires_at`; `send` preview shows de-duped `targets[]` + `total`. |
|
|
126
|
+
|
|
127
|
+
### Honest classification per command
|
|
128
|
+
|
|
129
|
+
| Command | Grade |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `user info-batch` | **live** (validation + per-item aggregation + **success path** against a real follower, 2026-06-15) |
|
|
132
|
+
| `message mass sendall` | **live**, permission OK (0 followers → no recipients) |
|
|
133
|
+
| `message mass send` | **live**, **permission restricted (48001)** |
|
|
134
|
+
| `message mass preview` | **live**, permission OK (only synthetic openid rejected) |
|
|
135
|
+
| `message mass get` | **live** |
|
|
136
|
+
| `message mass delete` | **live** (real API reached; only bogus id rejected) |
|
|
137
|
+
|
|
138
|
+
One gap found this round: `mass sendall` does not reject `--to-all` + `--tag-id`
|
|
139
|
+
supplied together (see guards table). No test account pollution occurred.
|
|
140
|
+
|
|
141
|
+
## Result by class
|
|
142
|
+
|
|
143
|
+
### Auth + token — PASS
|
|
144
|
+
| Command | Result |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `doctor` | PASS (config + account credentials OK) |
|
|
147
|
+
| `token refresh` (stable_token) | PASS — real access token, `expires_in: 7200` |
|
|
148
|
+
| `token status` | PASS |
|
|
149
|
+
|
|
150
|
+
### Reads — PASS
|
|
151
|
+
| Command | Result |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `asset count` | PASS |
|
|
154
|
+
| `asset list --type image` | PASS |
|
|
155
|
+
| `draft count` | PASS |
|
|
156
|
+
| `draft list` | PASS |
|
|
157
|
+
| `menu get` | PASS |
|
|
158
|
+
| `draft switch status` | PASS |
|
|
159
|
+
|
|
160
|
+
`asset list` output carries `_untrusted` markers for WeChat-controlled fields.
|
|
161
|
+
|
|
162
|
+
### Write safety + real execution — PASS
|
|
163
|
+
| Step | Result |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `menu delete` without `--confirm` | blocked (`E_CONFIRMATION_REQUIRED`) |
|
|
166
|
+
| `menu delete --dry-run` without `--dangerous` | blocked: "high risk and requires --dangerous in both steps" (T2 double gate) |
|
|
167
|
+
| `menu delete --dangerous --dry-run` | confirm token issued, **not executed** |
|
|
168
|
+
| `asset temp upload --type image --dangerous` (dry-run → confirm) | **real temporary material created** (media_id returned); auto-expires in 3 days |
|
|
169
|
+
|
|
170
|
+
### Error taxonomy — PASS
|
|
171
|
+
| Path | Result |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `asset list --type <invalid>` | `E_VALIDATION` |
|
|
174
|
+
| any write without `--confirm` | `E_CONFIRMATION_REQUIRED` |
|
|
175
|
+
|
|
176
|
+
## Notes
|
|
177
|
+
|
|
178
|
+
- This run used the WeChat sandbox/test account; its token, material, draft,
|
|
179
|
+
and menu endpoints are all live. A few advanced endpoints are restricted on
|
|
180
|
+
test accounts, but the core token / asset / draft / menu surface — including
|
|
181
|
+
the stable_token lifecycle and the T2 `--dangerous` write gate — is exercised
|
|
182
|
+
end-to-end here.
|
|
183
|
+
- No defects were found in this run.
|
|
184
|
+
|
|
185
|
+
## Reproduce
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
export WECHAT_MP_CLI_APP_ID=<appid>
|
|
189
|
+
export WECHAT_MP_CLI_APP_SECRET=<secret>
|
|
190
|
+
wechat-mp-cli doctor --compact
|
|
191
|
+
wechat-mp-cli token refresh --compact
|
|
192
|
+
wechat-mp-cli asset count --compact
|
|
193
|
+
wechat-mp-cli asset temp upload --type image --dangerous cover.png --dry-run --compact
|
|
194
|
+
wechat-mp-cli asset temp upload --type image --dangerous cover.png --confirm <token> --compact
|
|
195
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Official WeChat Endpoint Coverage
|
|
2
|
+
|
|
3
|
+
This document maps `wechat-mp-cli` commands to the official WeChat Official Account documentation checked on 2026-06-12.
|
|
4
|
+
|
|
5
|
+
## Sources
|
|
6
|
+
|
|
7
|
+
- Draft box: <https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Add_draft.html>
|
|
8
|
+
- Publish: <https://developers.weixin.qq.com/doc/offiaccount/Publish/Publish.html>
|
|
9
|
+
- Comments: <https://developers.weixin.qq.com/doc/service/guide/product/comments.html>
|
|
10
|
+
- Analytics: <https://developers.weixin.qq.com/doc/offiaccount/Analytics/Graphic_Analysis_Data_Interface.html>
|
|
11
|
+
- Materials: <https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html>
|
|
12
|
+
|
|
13
|
+
## Covered Article Workflow
|
|
14
|
+
|
|
15
|
+
| Official area | Endpoints | CLI commands |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Drafts | `/cgi-bin/draft/switch`, `/add`, `/update`, `/count`, `/batchget`, `/get`, `/delete` | `draft switch status/enable`, `draft create/update/count/list/get/delete` |
|
|
18
|
+
| Publish | `/cgi-bin/freepublish/submit`, `/get`, `/batchget`, `/getarticle`, `/delete` | `publish submit/status/list/get-article/delete` |
|
|
19
|
+
| Comments | `/cgi-bin/comment/open`, `/close`, `/list`, `/markelect`, `/unmarkelect`, `/delete`, `/reply/add`, `/reply/delete` | `comment open/close/list/mark/unmark/delete/reply-add/reply-delete` |
|
|
20
|
+
| Article analytics | `/datacube/getarticlesummary`, `/getarticletotal`, `/getuserread`, `/getuserreadhour`, `/getusershare`, `/getusersharehour`, `/getarticleread`, `/getarticleshare`, `/getbizsummary`, `/getarticletotaldetail` | `analytics article ...` |
|
|
21
|
+
| User analytics | `/datacube/getusersummary`, `/getusercumulate` | `analytics user summary/cumulate` |
|
|
22
|
+
| Materials used by articles | `/cgi-bin/media/uploadimg`, `/cgi-bin/material/add_material`, `/get_material`, `/get_materialcount`, `/batchget_material`, `/del_material` | `image upload`, `asset count/list/get/delete` |
|
|
23
|
+
| Temporary media | `/cgi-bin/media/upload`, `/get`, `/get/jssdk` | `asset temp upload/get/get-hd-voice` |
|
|
24
|
+
| Menu | `/cgi-bin/menu/create`, `/delete`, `/get_current_selfmenu_info`, `/addconditional` | `menu set/delete/get/addconditional` |
|
|
25
|
+
| Account QR codes | `/cgi-bin/qrcode/create` | `qrcode create` |
|
|
26
|
+
| Followers | `/cgi-bin/user/info`, `/cgi-bin/user/get` | `user info/list` |
|
|
27
|
+
| Follower tags | `/cgi-bin/tags/create`, `/get`, `/update`, `/delete`, `/cgi-bin/user/tag/get`, `/cgi-bin/tags/members/batchtagging`, `/batchuntagging` | `tag create/get/update/delete/members/tagging/untagging` |
|
|
28
|
+
|
|
29
|
+
## Intentional Boundaries
|
|
30
|
+
|
|
31
|
+
The CLI does not claim to cover every Official Account API. Current scope is the article production and publication workflow plus adjacent diagnostics.
|
|
32
|
+
|
|
33
|
+
Not yet implemented from the checked analytics page:
|
|
34
|
+
|
|
35
|
+
- Message analytics: `/datacube/getupstreammsg*`.
|
|
36
|
+
- Passive reply/interface analytics: `/datacube/getinterfacesummary`, `/datacube/getinterfacesummaryhour`.
|
|
37
|
+
|
|
38
|
+
Not yet implemented from the checked draft page:
|
|
39
|
+
|
|
40
|
+
- Product card DOM endpoint: `/channels/ec/service/product/getcardinfo`.
|
|
41
|
+
|
|
42
|
+
These are useful future extensions, but they are not required for the core article draft, publish, comment, material, and article analytics path.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 微信官方端点覆盖说明
|
|
2
|
+
|
|
3
|
+
本文档记录 `wechat-mp-cli` 与微信官方公众号文档的端点对照。检查日期:2026-06-12。
|
|
4
|
+
|
|
5
|
+
## 来源
|
|
6
|
+
|
|
7
|
+
- 草稿箱:<https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Add_draft.html>
|
|
8
|
+
- 发布:<https://developers.weixin.qq.com/doc/offiaccount/Publish/Publish.html>
|
|
9
|
+
- 留言管理:<https://developers.weixin.qq.com/doc/service/guide/product/comments.html>
|
|
10
|
+
- 数据统计:<https://developers.weixin.qq.com/doc/offiaccount/Analytics/Graphic_Analysis_Data_Interface.html>
|
|
11
|
+
- 素材管理:<https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html>
|
|
12
|
+
|
|
13
|
+
## 已覆盖的文章主链路
|
|
14
|
+
|
|
15
|
+
| 官方领域 | 端点 | CLI 命令 |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| 草稿 | `/cgi-bin/draft/switch`, `/add`, `/update`, `/count`, `/batchget`, `/get`, `/delete` | `draft switch status/enable`, `draft create/update/count/list/get/delete` |
|
|
18
|
+
| 发布 | `/cgi-bin/freepublish/submit`, `/get`, `/batchget`, `/getarticle`, `/delete` | `publish submit/status/list/get-article/delete` |
|
|
19
|
+
| 留言 | `/cgi-bin/comment/open`, `/close`, `/list`, `/markelect`, `/unmarkelect`, `/delete`, `/reply/add`, `/reply/delete` | `comment open/close/list/mark/unmark/delete/reply-add/reply-delete` |
|
|
20
|
+
| 图文/发表内容数据 | `/datacube/getarticlesummary`, `/getarticletotal`, `/getuserread`, `/getuserreadhour`, `/getusershare`, `/getusersharehour`, `/getarticleread`, `/getarticleshare`, `/getbizsummary`, `/getarticletotaldetail` | `analytics article ...` |
|
|
21
|
+
| 用户数据 | `/datacube/getusersummary`, `/getusercumulate` | `analytics user summary/cumulate` |
|
|
22
|
+
| 文章相关素材 | `/cgi-bin/media/uploadimg`, `/cgi-bin/material/add_material`, `/get_material`, `/get_materialcount`, `/batchget_material`, `/del_material` | `image upload`, `asset count/list/get/delete` |
|
|
23
|
+
| 临时素材 | `/cgi-bin/media/upload`, `/get`, `/get/jssdk` | `asset temp upload/get/get-hd-voice` |
|
|
24
|
+
| 自定义菜单 | `/cgi-bin/menu/create`, `/delete`, `/get_current_selfmenu_info`, `/addconditional` | `menu set/delete/get/addconditional` |
|
|
25
|
+
| 账号二维码 | `/cgi-bin/qrcode/create` | `qrcode create` |
|
|
26
|
+
| 粉丝 | `/cgi-bin/user/info`, `/cgi-bin/user/get` | `user info/list` |
|
|
27
|
+
| 粉丝标签 | `/cgi-bin/tags/create`, `/get`, `/update`, `/delete`, `/cgi-bin/user/tag/get`, `/cgi-bin/tags/members/batchtagging`, `/batchuntagging` | `tag create/get/update/delete/members/tagging/untagging` |
|
|
28
|
+
|
|
29
|
+
## 有意保留的边界
|
|
30
|
+
|
|
31
|
+
本 CLI 不声明覆盖公众号全部 OpenAPI。当前范围是文章生产、发布、评论、素材和文章数据统计主链路,以及相邻的诊断能力。
|
|
32
|
+
|
|
33
|
+
本轮检查到但暂未实现的数据统计端点:
|
|
34
|
+
|
|
35
|
+
- 消息数据:`/datacube/getupstreammsg*`。
|
|
36
|
+
- 被动回复/接口数据:`/datacube/getinterfacesummary`, `/datacube/getinterfacesummaryhour`。
|
|
37
|
+
|
|
38
|
+
本轮检查到但暂未实现的草稿相关端点:
|
|
39
|
+
|
|
40
|
+
- 商品卡片 DOM:`/channels/ec/service/product/getcardinfo`。
|
|
41
|
+
|
|
42
|
+
这些可以作为后续扩展,但不影响当前草稿、发布、留言、素材和文章统计主链路的完整性。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Open-Source Checklist
|
|
2
|
+
|
|
3
|
+
[English](OPEN_SOURCE_CHECKLIST.md) | [中文](OPEN_SOURCE_CHECKLIST_zh.md)
|
|
4
|
+
|
|
5
|
+
Run through this gate **before the first public push** of `wechat-mp-cli`. It is a security and quality checkpoint, not documentation — every box must be ticked (or consciously waived with a note) before the repo goes public. Once public, secrets in history cannot be un-leaked.
|
|
6
|
+
|
|
7
|
+
## Secrets
|
|
8
|
+
|
|
9
|
+
- [ ] No credentials, tokens, API keys, or passwords anywhere in the working tree.
|
|
10
|
+
- [ ] No secrets in **git history** — checked with a scanner (e.g. `gitleaks detect`, `git log -p | grep`); if found, the history is rewritten or the repo re-created, not just deleted from `HEAD`.
|
|
11
|
+
- [ ] No internal hostnames, private IPs, internal URLs, or employer-internal identifiers in code, config, or comments.
|
|
12
|
+
- [ ] Test fixtures and recorded responses contain only synthetic / redacted data — no real account data, no real tokens.
|
|
13
|
+
- [ ] `.env`, `*.local`, credential files, and `~/.wechat-mp-cli/` artifacts are listed in `.gitignore` and confirmed untracked (`git status --ignored`).
|
|
14
|
+
- [ ] Credentials are stored encrypted at rest (OS keyring or encrypted envelope, `0600`), never plaintext in the config file.
|
|
15
|
+
|
|
16
|
+
## Docs
|
|
17
|
+
|
|
18
|
+
- [ ] `README.md` follows the REPO-SPEC §2 skeleton (Title/badges → Agent Install → What It Does → Capabilities → Agent Workflow → Machine Contract → Configuration → Project Structure → Development → Links).
|
|
19
|
+
- [ ] `README.md` and `README_zh.md` are **content-synced** — same sections, same commands, same placeholders resolved to the same real values.
|
|
20
|
+
- [ ] `CHANGELOG.md` exists, uses Keep a Changelog format, and has an `## [Unreleased]` section on top.
|
|
21
|
+
- [ ] `LICENSE` is present, the license is chosen deliberately (default MIT), and `2026` / `Sean Guo` are filled in.
|
|
22
|
+
- [ ] Install blocks are copy-paste-runnable and use the real published `@fateforge/wechat-mp-cli` / `fatecannotbealtered/wechat-mp-cli`.
|
|
23
|
+
|
|
24
|
+
## Governance
|
|
25
|
+
|
|
26
|
+
- [ ] `SECURITY.md` is present with a working disclosure channel (`security@example.com`) and a supported-versions table.
|
|
27
|
+
- [ ] `CONTRIBUTING.md` is present (env setup, branch/commit, test, PR flow).
|
|
28
|
+
- [ ] `CODE_OF_CONDUCT.md` is present (Contributor Covenant) if the project accepts external contributions.
|
|
29
|
+
- [ ] If `wechat-mp-cli` wraps a third-party product (WeChat Official Account), `NOTICE.md` carries the trademark / non-affiliation notice and `docs/COMPATIBILITY.md` lists the verified backend version matrix.
|
|
30
|
+
|
|
31
|
+
## Build / CI
|
|
32
|
+
|
|
33
|
+
- [ ] CI (`.github/workflows/ci.yml`) is **green** on the commit being pushed.
|
|
34
|
+
- [ ] CI **enforces** lint and tests — a red lint or failing test blocks merge (not advisory-only).
|
|
35
|
+
- [ ] Functional Contract Coverage is 100%: every public behavior documented in README, Skill, `reference`, `--help`, `context`, `doctor`, `changelog`, or `update` has automated command-level tests.
|
|
36
|
+
- [ ] `reference.release_readiness.level` is accurate: `stable` has FCC 100%, mock upstream/contract tests, and recorded live smoke/E2E evidence; missing live evidence is `beta`; missing command-level coverage is `unpublishable`.
|
|
37
|
+
- [ ] `doctor` includes a `release_readiness` check whose status matches the declared release level.
|
|
38
|
+
- [ ] The formatter config is committed (ruff / golangci-lint / prettier, by language) and CI runs the format check.
|
|
39
|
+
- [ ] No build artifacts, caches, venvs, or IDE config are committed (covered by `.gitignore`).
|
|
40
|
+
|
|
41
|
+
## Distribution
|
|
42
|
+
|
|
43
|
+
- [ ] `package.json` `version` matches the git tag being released (`vX.Y.Z` ↔ `X.Y.Z`); `release.yml` guards this and fails on mismatch.
|
|
44
|
+
- [ ] The binary itself (`bin/`, `*.exe`, `dist/`) is **not committed** — it is produced by CI and gitignored.
|
|
45
|
+
- [ ] GitHub Release artifacts ship a `checksums.txt`; standalone binary install/update paths verify the checksum and **fail closed** on mismatch or missing entries.
|
|
46
|
+
- [ ] Release pipeline signs `checksums.txt` with Sigstore/Cosign keyless signing, publishes the bundle, and standalone install/update paths report signature verification status separately from checksum verification.
|
|
47
|
+
- [ ] npm distribution publishes the main wrapper package plus every supported OS/CPU platform package from CI-built artifacts, with `npm publish --provenance`.
|
|
48
|
+
- [ ] The version number has a single source of truth; the runtime `changelog` command and the GitHub Release body are derived from `CHANGELOG.md`, not hand-copied.
|
|
49
|
+
|
|
50
|
+
## AI-native
|
|
51
|
+
|
|
52
|
+
- [ ] Root `AGENTS.md` is present and points to `.agent/AGENT.md`.
|
|
53
|
+
- [ ] `.agent/{AGENT,CLI-SPEC,SKILL-SPEC,SEC-SPEC}.md` specs are present; the shared repo skeleton standard is referenced from `ai-native-cli-spec/REPO-SPEC.md`.
|
|
54
|
+
- [ ] `skills/wechat-mp-cli/SKILL.md` is present; frontmatter includes `version`, `license: MIT`, `user-invocable: true`, and `metadata.requires.min_version` matching the CLI version.
|
|
55
|
+
- [ ] `SKILL.md` includes `When to use`, `Do not use`, `First Step`, agent defaults, JSON contract, write recipe or explicit read-only boundary, `STOP CHECKPOINT`, error decision tree, security boundary, self-update, and eval scenarios.
|
|
56
|
+
- [ ] `skills/wechat-mp-cli/test-prompts.json` is present, valid JSON, and covers fresh-agent read, write safety or read-only boundary, permission boundary, `_untrusted` handling, and self-update.
|
|
57
|
+
- [ ] `update --confirm` syncs the whole `skills/wechat-mp-cli/` directory or returns a `skill_sync_command` equivalent to `npx skills add fatecannotbealtered/wechat-mp-cli -y -g`.
|
|
58
|
+
- [ ] `wechat-mp-cli reference`, `wechat-mp-cli context`, and `wechat-mp-cli doctor` run and emit valid JSON envelopes — an agent can self-onboard from a clean checkout.
|
|
59
|
+
- [ ] `wechat-mp-cli reference` exposes `release_readiness`, and `wechat-mp-cli doctor` reports the matching check.
|
|
60
|
+
- [ ] The risk tier in `SECURITY.md` matches the tier declared in `.agent/SEC-SPEC.md` (`T2`).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# 开源检查清单
|
|
2
|
+
|
|
3
|
+
[English](OPEN_SOURCE_CHECKLIST.md) | [中文](OPEN_SOURCE_CHECKLIST_zh.md)
|
|
4
|
+
|
|
5
|
+
在 `wechat-mp-cli` **首次公开推送之前**逐项走查。这是一道安全与质量关卡,不是文档 —— 仓库公开前每一项都必须勾选(或明确写明理由后豁免)。一旦公开,历史中泄露的密钥就无法收回。
|
|
6
|
+
|
|
7
|
+
## 密钥
|
|
8
|
+
|
|
9
|
+
- [ ] 工作区任何位置都没有凭据、token、API key 或密码。
|
|
10
|
+
- [ ] **git 历史**中没有密钥 —— 已用扫描工具检查(如 `gitleaks detect`、`git log -p | grep`);若发现,需重写历史或重建仓库,而不是只从 `HEAD` 删除。
|
|
11
|
+
- [ ] 代码、配置、注释中没有内部主机名、内网 IP、内部 URL 或公司内部标识符。
|
|
12
|
+
- [ ] 测试夹具和录制的响应只含合成 / 脱敏数据 —— 没有真实账户数据,没有真实 token。
|
|
13
|
+
- [ ] `.env`、`*.local`、凭据文件以及 `~/.wechat-mp-cli/` 产物已列入 `.gitignore` 并确认未被跟踪(`git status --ignored`)。
|
|
14
|
+
- [ ] 凭据静态加密存储(操作系统钥匙串或加密信封,`0600`),绝不以明文写入配置文件。
|
|
15
|
+
|
|
16
|
+
## 文档
|
|
17
|
+
|
|
18
|
+
- [ ] `README.md` 遵循 REPO-SPEC §2 骨架(标题/徽章 → Agent 安装 → 它做什么 → 能力 → Agent 工作流 → 机器契约 → 配置 → 项目结构 → 开发 → 链接)。
|
|
19
|
+
- [ ] `README.md` 与 `README_zh.md` **内容同步** —— 章节一致、命令一致、占位符解析为相同的真实值。
|
|
20
|
+
- [ ] `CHANGELOG.md` 存在,使用 Keep a Changelog 格式,顶部有 `## [Unreleased]` 小节。
|
|
21
|
+
- [ ] `LICENSE` 存在,许可证经过有意选择(默认 MIT),`2026` / `Sean Guo` 已填写。
|
|
22
|
+
- [ ] 安装块可直接复制运行,使用真实已发布的 `@fateforge/wechat-mp-cli` / `fatecannotbealtered/wechat-mp-cli`。
|
|
23
|
+
|
|
24
|
+
## 治理
|
|
25
|
+
|
|
26
|
+
- [ ] `SECURITY.md` 存在,含可用的披露渠道(`security@example.com`)和受支持版本表。
|
|
27
|
+
- [ ] `CONTRIBUTING.md` 存在(环境搭建、分支/提交、测试、PR 流程)。
|
|
28
|
+
- [ ] 若项目接受外部贡献,`CODE_OF_CONDUCT.md` 存在(Contributor Covenant)。
|
|
29
|
+
- [ ] 若 `wechat-mp-cli` 包装第三方产品(WeChat Official Account),`NOTICE.md` 载明商标 / 非隶属声明,且 `docs/COMPATIBILITY.md` 列出已验证的后端版本矩阵。
|
|
30
|
+
|
|
31
|
+
## 构建 / CI
|
|
32
|
+
|
|
33
|
+
- [ ] 待推送的提交上 CI(`.github/workflows/ci.yml`)为**绿色**。
|
|
34
|
+
- [ ] CI **强制**执行 lint 和测试 —— lint 失败或测试失败会阻断合并(不仅仅是提示性的)。
|
|
35
|
+
- [ ] 功能契约覆盖率为 100%:README、Skill、`reference`、`--help`、`context`、`doctor`、`changelog` 或 `update` 中记录的每个公开行为,都有自动化命令级测试。
|
|
36
|
+
- [ ] `reference.release_readiness.level` 准确:`stable` 具备 FCC 100%、mock upstream / contract tests 和真实环境 smoke/E2E 记录;缺真实证据为 `beta`;缺命令级覆盖为 `unpublishable`。
|
|
37
|
+
- [ ] `doctor` 包含 `release_readiness` 检查,且状态与声明的发布等级一致。
|
|
38
|
+
- [ ] 格式化工具配置已提交(按语言:ruff / golangci-lint / prettier),且 CI 运行格式校验。
|
|
39
|
+
- [ ] 没有提交构建产物、缓存、虚拟环境或 IDE 配置(已由 `.gitignore` 覆盖)。
|
|
40
|
+
|
|
41
|
+
## 分发
|
|
42
|
+
|
|
43
|
+
- [ ] `package.json` 的 `version` 与待发布的 git tag 一致(`vX.Y.Z` ↔ `X.Y.Z`);`release.yml` 对此做守卫,不一致即失败。
|
|
44
|
+
- [ ] 二进制本身(`bin/`、`*.exe`、`dist/`)**不提交** —— 由 CI 产出并被 gitignore。
|
|
45
|
+
- [ ] GitHub Release 发布产物附带 `checksums.txt`;standalone 二进制安装/更新路径校验 checksum,且在不匹配或缺少条目时**失败关闭**。
|
|
46
|
+
- [ ] release pipeline 使用 Sigstore/Cosign keyless 签署 `checksums.txt`,发布 bundle,standalone 安装/更新路径把签名验证状态与 checksum 校验分开报告。
|
|
47
|
+
- [ ] npm 分发从 CI 构建产物发布主 wrapper 包和每个受支持 OS/CPU 的平台包,并使用 `npm publish --provenance`。
|
|
48
|
+
- [ ] 版本号有唯一真相来源;运行时 `changelog` 命令和 GitHub Release 正文均派生自 `CHANGELOG.md`,而非手工复制。
|
|
49
|
+
|
|
50
|
+
## AI 原生
|
|
51
|
+
|
|
52
|
+
- [ ] 根目录 `AGENTS.md` 存在并指向 `.agent/AGENT.md`。
|
|
53
|
+
- [ ] `.agent/{AGENT,CLI-SPEC,SKILL-SPEC,SEC-SPEC}.md` 规格文件齐全;共享仓库骨架标准引用 `ai-native-cli-spec/REPO-SPEC.md`。
|
|
54
|
+
- [ ] `skills/wechat-mp-cli/SKILL.md` 存在;frontmatter 包含 `version`、`license: MIT`、`user-invocable: true`,且 `metadata.requires.min_version` 匹配 CLI 版本。
|
|
55
|
+
- [ ] `SKILL.md` 包含 `When to use`、`Do not use`、`First Step`、Agent 默认规则、JSON contract、写操作配方或明确只读边界、`STOP CHECKPOINT`、错误决策树、安全边界、自更新和评估场景。
|
|
56
|
+
- [ ] `skills/wechat-mp-cli/test-prompts.json` 存在、JSON 合法,并覆盖 fresh-agent read、写操作安全或只读边界、权限边界、`_untrusted` 处理和自更新。
|
|
57
|
+
- [ ] `update --confirm` 同步整个 `skills/wechat-mp-cli/` 目录,或返回等价于 `npx skills add fatecannotbealtered/wechat-mp-cli -y -g` 的 `skill_sync_command`。
|
|
58
|
+
- [ ] `wechat-mp-cli reference`、`wechat-mp-cli context`、`wechat-mp-cli doctor` 可运行并输出合法的 JSON 信封 —— 代理能从干净的检出自助上手。
|
|
59
|
+
- [ ] `wechat-mp-cli reference` 暴露 `release_readiness`,`wechat-mp-cli doctor` 报告匹配的检查项。
|
|
60
|
+
- [ ] `SECURITY.md` 中的风险等级与 `.agent/SEC-SPEC.md` 声明的等级一致(`T2`)。
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fateforge/wechat-mp-cli",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "AI-native CLI for WeChat Official Account drafting, publishing, assets, comments, analytics, menus, users, and webhooks",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wechat",
|
|
7
|
+
"weixin",
|
|
8
|
+
"official-account",
|
|
9
|
+
"cli",
|
|
10
|
+
"ai-agent",
|
|
11
|
+
"publishing",
|
|
12
|
+
"content-workflow"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Sean Guo",
|
|
16
|
+
"homepage": "https://github.com/fatecannotbealtered/wechat-mp-cli#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/fatecannotbealtered/wechat-mp-cli/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/fatecannotbealtered/wechat-mp-cli.git"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"wechat-mp-cli": "scripts/run.js"
|
|
29
|
+
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"@fateforge/wechat-mp-cli-darwin-arm64": "1.0.3",
|
|
32
|
+
"@fateforge/wechat-mp-cli-darwin-x64": "1.0.3",
|
|
33
|
+
"@fateforge/wechat-mp-cli-linux-arm64": "1.0.3",
|
|
34
|
+
"@fateforge/wechat-mp-cli-linux-x64": "1.0.3",
|
|
35
|
+
"@fateforge/wechat-mp-cli-win32-arm64": "1.0.3",
|
|
36
|
+
"@fateforge/wechat-mp-cli-win32-x64": "1.0.3"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"scripts/run.js",
|
|
40
|
+
"skills/",
|
|
41
|
+
"README.md",
|
|
42
|
+
"README_zh.md",
|
|
43
|
+
"*_zh.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"NOTICE.md",
|
|
46
|
+
"CHANGELOG.md",
|
|
47
|
+
"CONTRIBUTING.md",
|
|
48
|
+
"SECURITY.md",
|
|
49
|
+
"CODE_OF_CONDUCT.md",
|
|
50
|
+
"AGENTS.md",
|
|
51
|
+
".agent/",
|
|
52
|
+
"docs/"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=16"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/scripts/run.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Thin forwarder: exec the binary shipped by the npm platform package.
|
|
5
|
+
const { execFileSync } = require("child_process");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const rootPackage = require("../package.json");
|
|
9
|
+
const toolName = Object.keys(rootPackage.bin || {})[0] || "wechat-mp-cli";
|
|
10
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
11
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
12
|
+
const platformPackage = `${rootPackage.name}-${platformKey}`;
|
|
13
|
+
const optionalDependencies = rootPackage.optionalDependencies || {};
|
|
14
|
+
|
|
15
|
+
if (!Object.prototype.hasOwnProperty.call(optionalDependencies, platformPackage)) {
|
|
16
|
+
console.error(
|
|
17
|
+
`${toolName} does not ship an npm platform package for ${platformKey}.\n` +
|
|
18
|
+
"Install a supported platform package or use the GitHub standalone binary."
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let bin;
|
|
24
|
+
try {
|
|
25
|
+
const platformPackageJson = require.resolve(`${platformPackage}/package.json`);
|
|
26
|
+
bin = path.join(path.dirname(platformPackageJson), "bin", toolName + ext);
|
|
27
|
+
} catch {
|
|
28
|
+
console.error(
|
|
29
|
+
`${toolName} platform package ${platformPackage} is not installed.\n` +
|
|
30
|
+
"This usually means npm optional dependencies were omitted.\n" +
|
|
31
|
+
`Reinstall with: npm install -g ${rootPackage.name} --include=optional`
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e.code === "ENOENT") {
|
|
40
|
+
console.error(
|
|
41
|
+
`${toolName} binary not found inside ${platformPackage}.\n` +
|
|
42
|
+
`Reinstall with: npm install -g ${rootPackage.name} --include=optional`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
process.exit(e.status || 1);
|
|
46
|
+
}
|