wechat-pub-skills 0.1.0

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/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # wechat-pub-skills
2
+
3
+ Agent-friendly `npx` tools for WeChat public account articles.
4
+
5
+ This package is intentionally separate from the VS Code extension. It provides command-line skills that an agent can call with explicit configuration to:
6
+
7
+ - render Markdown into WeChat-ready HTML
8
+ - upload local images to the WeChat image CDN
9
+ - replace Markdown image paths with uploaded WeChat URLs
10
+ - publish an article to the draft box
11
+ - update an existing draft
12
+
13
+ ## Install / Run
14
+
15
+ Run directly with `npx`:
16
+
17
+ ```bash
18
+ npx wechat-pub-skills help
19
+ ```
20
+
21
+ For local development from a packed tarball:
22
+
23
+ ```bash
24
+ npm pack
25
+ npx ./wechat-pub-skills-0.1.0.tgz help
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ Pass credentials as environment variables:
31
+
32
+ ```bash
33
+ export WECHAT_PUB_APP_ID="your-app-id"
34
+ export WECHAT_PUB_APP_SECRET="your-app-secret"
35
+ export WECHAT_PUB_PROXY_ORIGIN="https://your-wechat-proxy.example.com"
36
+ ```
37
+
38
+ Or pass them per command:
39
+
40
+ ```bash
41
+ --app-id APP_ID --app-secret APP_SECRET --proxy-origin https://your-wechat-proxy.example.com
42
+ ```
43
+
44
+ Or pass a JSON config:
45
+
46
+ ```json
47
+ {
48
+ "wechat": {
49
+ "appId": "your-app-id",
50
+ "appSecret": "your-app-secret",
51
+ "proxyOrigin": "https://your-wechat-proxy.example.com"
52
+ },
53
+ "style": {
54
+ "theme": "grace",
55
+ "primaryColor": "#35b378",
56
+ "fontSize": "16px",
57
+ "indent": false,
58
+ "justify": false
59
+ }
60
+ }
61
+ ```
62
+
63
+ ```bash
64
+ npx wechat-pub-skills publish article.md --config wechat-pub.config.json --json
65
+ ```
66
+
67
+ ## Commands
68
+
69
+ Render WeChat HTML:
70
+
71
+ ```bash
72
+ npx wechat-pub-skills render article.md --out article.html --theme grace --json
73
+ ```
74
+
75
+ Upload an image and replace it in Markdown:
76
+
77
+ ```bash
78
+ npx wechat-pub-skills upload-image images/cover.png --replace-in article.md --json
79
+ ```
80
+
81
+ Create or update a draft:
82
+
83
+ ```bash
84
+ npx wechat-pub-skills publish article.md --cover images/cover.png --json
85
+ ```
86
+
87
+ Update an explicit draft:
88
+
89
+ ```bash
90
+ npx wechat-pub-skills update article.md --media-id MEDIA_ID --json
91
+ ```
92
+
93
+ ## Agent Contract
94
+
95
+ Use `--json` for machine-readable output. The package stores local state under `.wechat-pub/` in the workspace:
96
+
97
+ - `.wechat-pub/images.json`: local image path to WeChat image URL mapping
98
+ - `.wechat-pub/drafts.json`: local article path to WeChat draft `media_id` mapping
99
+
100
+ Common style options:
101
+
102
+ ```bash
103
+ --theme default|grace|simple
104
+ --primary-color "#35b378"
105
+ --font-family "PingFang SC"
106
+ --font-size 16px
107
+ --indent
108
+ --justify
109
+ --legend alt|title|filename
110
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: wechat-pub-skills
3
+ description: Use npx commands to render, upload images, publish, and update WeChat public account Markdown articles.
4
+ ---
5
+
6
+ # WeChat Pub Skills
7
+
8
+ Use this package when an agent needs to operate on WeChat public account articles from the command line.
9
+
10
+ ## Configuration
11
+
12
+ Prefer environment variables:
13
+
14
+ ```bash
15
+ WECHAT_PUB_APP_ID=...
16
+ WECHAT_PUB_APP_SECRET=...
17
+ WECHAT_PUB_PROXY_ORIGIN=https://your-wechat-proxy.example.com
18
+ ```
19
+
20
+ Per-command flags are also supported:
21
+
22
+ ```bash
23
+ --app-id APP_ID --app-secret APP_SECRET --proxy-origin https://your-wechat-proxy.example.com
24
+ ```
25
+
26
+ Or pass `--config wechat-pub.config.json`:
27
+
28
+ ```json
29
+ {
30
+ "wechat": {
31
+ "appId": "...",
32
+ "appSecret": "...",
33
+ "proxyOrigin": "https://your-wechat-proxy.example.com"
34
+ },
35
+ "style": {
36
+ "theme": "grace",
37
+ "primaryColor": "#35b378"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Commands
43
+
44
+ Render Markdown:
45
+
46
+ ```bash
47
+ npx wechat-pub-skills render article.md --out article.html --json
48
+ ```
49
+
50
+ Upload and replace an image:
51
+
52
+ ```bash
53
+ npx wechat-pub-skills upload-image images/cover.png --replace-in article.md --json
54
+ ```
55
+
56
+ Publish or update a draft based on local mapping:
57
+
58
+ ```bash
59
+ npx wechat-pub-skills publish article.md --cover images/cover.png --json
60
+ ```
61
+
62
+ Update a known draft:
63
+
64
+ ```bash
65
+ npx wechat-pub-skills update article.md --media-id MEDIA_ID --json
66
+ ```
67
+
68
+ Always pass `--json` when another agent will parse the output.
@@ -0,0 +1,415 @@
1
+ /* Omitting the below because of https://github.com/jsdom/cssstyle/issues/193.
2
+ And we don't implement namespace constraints anyway.
3
+
4
+ @namespace "http://www.w3.org/1999/xhtml";
5
+ */
6
+
7
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-page */
8
+
9
+ html, body { display: block; }
10
+
11
+ /* https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3
12
+ - Omits presentational hints
13
+ - Omits quirks mode
14
+ */
15
+
16
+ address, blockquote, center, dialog, div, figure, figcaption, footer, form,
17
+ header, hr, legend, listing, main, p, plaintext, pre, search, xmp {
18
+ display: block;
19
+ }
20
+
21
+ blockquote, figure, listing, p, plaintext, pre, xmp {
22
+ margin-block: 1em;
23
+ }
24
+
25
+ blockquote, figure { margin-inline: 40px; }
26
+
27
+ address { font-style: italic; }
28
+ listing, plaintext, pre, xmp {
29
+ font-family: monospace; white-space: pre;
30
+ }
31
+
32
+ dialog:not([open]) { display: none; }
33
+ dialog {
34
+ position: absolute;
35
+ inset-inline-start: 0; inset-inline-end: 0;
36
+ width: fit-content;
37
+ height: fit-content;
38
+ margin: auto;
39
+ border: solid;
40
+ padding: 1em;
41
+ background-color: Canvas;
42
+ color: CanvasText;
43
+ }
44
+ dialog:modal {
45
+ position: fixed;
46
+ overflow: auto;
47
+ inset-block: 0;
48
+ max-width: calc(100% - 6px - 2em);
49
+ max-height: calc(100% - 6px - 2em);
50
+ }
51
+ dialog::backdrop {
52
+ background: rgba(0,0,0,0.1);
53
+ }
54
+
55
+ [popover]:not(:popover-open):not(dialog[open]) {
56
+ display:none;
57
+ }
58
+
59
+ dialog:popover-open {
60
+ display:block;
61
+ }
62
+
63
+ [popover] {
64
+ position: fixed;
65
+ inset: 0;
66
+ width: fit-content;
67
+ height: fit-content;
68
+ margin: auto;
69
+ border: solid;
70
+ padding: 0.25em;
71
+ overflow: auto;
72
+ color: CanvasText;
73
+ background-color: Canvas;
74
+ }
75
+
76
+ :popover-open::backdrop {
77
+ position: fixed;
78
+ inset: 0;
79
+ pointer-events: none !important;
80
+ background-color: transparent;
81
+ }
82
+
83
+ slot {
84
+ display: contents;
85
+ }
86
+
87
+ /* https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3
88
+ - Omits presentational hints
89
+ */
90
+
91
+
92
+ cite, dfn, em, i, var { font-style: italic; }
93
+ b, strong { font-weight: bolder; }
94
+ code, kbd, samp, tt { font-family: monospace; }
95
+ big { font-size: larger; }
96
+ small { font-size: smaller; }
97
+
98
+ sub { vertical-align: sub; }
99
+ sup { vertical-align: super; }
100
+ sub, sup { line-height: normal; font-size: smaller; }
101
+
102
+ ruby { display: ruby; }
103
+ rt { display: ruby-text; }
104
+
105
+ :link { color: #0000EE; }
106
+ :visited { color: #551A8B; }
107
+ :link:active, :visited:active { color: #FF0000; }
108
+ :link, :visited { text-decoration: underline; cursor: pointer; }
109
+
110
+ :focus-visible { outline: auto; }
111
+
112
+ mark { background: yellow; color: black; } /* this color is just a suggestion and can be changed based on implementation feedback */
113
+
114
+ abbr[title], acronym[title] { text-decoration: dotted underline; }
115
+ ins, u { text-decoration: underline; }
116
+ del, s, strike { text-decoration: line-through; }
117
+
118
+ q::before { content: open-quote; }
119
+ q::after { content: close-quote; }
120
+
121
+ br { display-outside: newline; } /* this also has bidi implications */
122
+ nobr { white-space: nowrap; }
123
+ wbr { display-outside: break-opportunity; } /* this also has bidi implications */
124
+ nobr wbr { white-space: normal; }
125
+
126
+ /* https://html.spec.whatwg.org/multipage/rendering.html#bidi-rendering
127
+ - Omits ISO-8859-8
128
+ */
129
+
130
+ [dir]:dir(ltr), bdi:dir(ltr), input[type=tel i]:dir(ltr) { direction: ltr; }
131
+ [dir]:dir(rtl), bdi:dir(rtl) { direction: rtl; }
132
+
133
+ address, blockquote, center, div, figure, figcaption, footer, form, header, hr,
134
+ legend, listing, main, p, plaintext, pre, summary, xmp, article, aside, h1, h2,
135
+ h3, h4, h5, h6, hgroup, nav, section, search, table, caption, colgroup, col,
136
+ thead, tbody, tfoot, tr, td, th, dir, dd, dl, dt, menu, ol, ul, li, bdi, output,
137
+ [dir=ltr i], [dir=rtl i], [dir=auto i] {
138
+ unicode-bidi: isolate;
139
+ }
140
+
141
+ bdo, bdo[dir] { unicode-bidi: isolate-override; }
142
+
143
+ input[dir=auto i]:is([type=search i], [type=tel i], [type=url i],
144
+ [type=email i]), textarea[dir=auto i], pre[dir=auto i] {
145
+ unicode-bidi: plaintext;
146
+ }
147
+ /* see prose for input elements whose type attribute is in the Text state */
148
+
149
+ /* the rules setting the 'content' property on br and wbr elements also has bidi implications */
150
+
151
+ /* https://html.spec.whatwg.org/multipage/rendering.html#sections-and-headings
152
+ - Special h1 styles removed per upcoming change: https://github.com/whatwg/html/pull/11102
153
+ */
154
+
155
+ article, aside, h1, h2, h3, h4, h5, h6, hgroup, nav, section {
156
+ display: block;
157
+ }
158
+
159
+ h1 { margin-block: 0.67em; font-size: 2.00em; font-weight: bold; }
160
+ h2 { margin-block: 0.83em; font-size: 1.50em; font-weight: bold; }
161
+ h3 { margin-block: 1.00em; font-size: 1.17em; font-weight: bold; }
162
+ h4 { margin-block: 1.33em; font-size: 1.00em; font-weight: bold; }
163
+ h5 { margin-block: 1.67em; font-size: 0.83em; font-weight: bold; }
164
+ h6 { margin-block: 2.33em; font-size: 0.67em; font-weight: bold; }
165
+
166
+ /* https://html.spec.whatwg.org/multipage/rendering.html#lists
167
+ - Omit presentational hints
168
+ - Omit quirks mode
169
+ */
170
+
171
+ dir, dd, dl, dt, menu, ol, ul { display: block; }
172
+ li { display: list-item; text-align: match-parent; }
173
+
174
+ dir, dl, menu, ol, ul { margin-block: 1em; }
175
+
176
+ :is(dir, dl, menu, ol, ul) :is(dir, dl, menu, ol, ul) {
177
+ margin-block: 0;
178
+ }
179
+
180
+ dd { margin-inline-start: 40px; }
181
+ dir, menu, ol, ul { padding-inline-start: 40px; }
182
+
183
+ ol, ul, menu { counter-reset: list-item; }
184
+ ol { list-style-type: decimal; }
185
+
186
+ dir, menu, ul {
187
+ list-style-type: disc;
188
+ }
189
+ :is(dir, menu, ol, ul) :is(dir, menu, ul) {
190
+ list-style-type: circle;
191
+ }
192
+ :is(dir, menu, ol, ul) :is(dir, menu, ol, ul) :is(dir, menu, ul) {
193
+ list-style-type: square;
194
+ }
195
+
196
+ /* https://html.spec.whatwg.org/multipage/rendering.html#tables-2
197
+ - Omit presentational hints
198
+ - Omit quirks mode
199
+ - Omit HTML documents
200
+ */
201
+
202
+ table { display: table; }
203
+ caption { display: table-caption; }
204
+ colgroup, colgroup[hidden] { display: table-column-group; }
205
+ col, col[hidden] { display: table-column; }
206
+ thead, thead[hidden] { display: table-header-group; }
207
+ tbody, tbody[hidden] { display: table-row-group; }
208
+ tfoot, tfoot[hidden] { display: table-footer-group; }
209
+ tr, tr[hidden] { display: table-row; }
210
+ td, th { display: table-cell; }
211
+
212
+ colgroup[hidden], col[hidden], thead[hidden], tbody[hidden],
213
+ tfoot[hidden], tr[hidden] {
214
+ visibility: collapse;
215
+ }
216
+
217
+ table {
218
+ box-sizing: border-box;
219
+ border-spacing: 2px;
220
+ border-collapse: separate;
221
+ text-indent: initial;
222
+ }
223
+ td, th { padding: 1px; }
224
+ th { font-weight: bold; }
225
+
226
+ caption { text-align: center; }
227
+ thead, tbody, tfoot, table > tr { vertical-align: middle; }
228
+ tr, td, th { vertical-align: inherit; }
229
+
230
+ thead, tbody, tfoot, tr { border-color: inherit; }
231
+ table[rules=none i], table[rules=groups i], table[rules=rows i],
232
+ table[rules=cols i], table[rules=all i], table[frame=void i],
233
+ table[frame=above i], table[frame=below i], table[frame=hsides i],
234
+ table[frame=lhs i], table[frame=rhs i], table[frame=vsides i],
235
+ table[frame=box i], table[frame=border i],
236
+ table[rules=none i] > tr > td, table[rules=none i] > tr > th,
237
+ table[rules=groups i] > tr > td, table[rules=groups i] > tr > th,
238
+ table[rules=rows i] > tr > td, table[rules=rows i] > tr > th,
239
+ table[rules=cols i] > tr > td, table[rules=cols i] > tr > th,
240
+ table[rules=all i] > tr > td, table[rules=all i] > tr > th,
241
+ table[rules=none i] > thead > tr > td, table[rules=none i] > thead > tr > th,
242
+ table[rules=groups i] > thead > tr > td, table[rules=groups i] > thead > tr > th,
243
+ table[rules=rows i] > thead > tr > td, table[rules=rows i] > thead > tr > th,
244
+ table[rules=cols i] > thead > tr > td, table[rules=cols i] > thead > tr > th,
245
+ table[rules=all i] > thead > tr > td, table[rules=all i] > thead > tr > th,
246
+ table[rules=none i] > tbody > tr > td, table[rules=none i] > tbody > tr > th,
247
+ table[rules=groups i] > tbody > tr > td, table[rules=groups i] > tbody > tr > th,
248
+ table[rules=rows i] > tbody > tr > td, table[rules=rows i] > tbody > tr > th,
249
+ table[rules=cols i] > tbody > tr > td, table[rules=cols i] > tbody > tr > th,
250
+ table[rules=all i] > tbody > tr > td, table[rules=all i] > tbody > tr > th,
251
+ table[rules=none i] > tfoot > tr > td, table[rules=none i] > tfoot > tr > th,
252
+ table[rules=groups i] > tfoot > tr > td, table[rules=groups i] > tfoot > tr > th,
253
+ table[rules=rows i] > tfoot > tr > td, table[rules=rows i] > tfoot > tr > th,
254
+ table[rules=cols i] > tfoot > tr > td, table[rules=cols i] > tfoot > tr > th,
255
+ table[rules=all i] > tfoot > tr > td, table[rules=all i] > tfoot > tr > th {
256
+ border-color: black;
257
+ }
258
+
259
+ /* https://html.spec.whatwg.org/multipage/rendering.html#form-controls
260
+ - Omit quirks mode
261
+ */
262
+
263
+ input, select, button, textarea {
264
+ letter-spacing: initial;
265
+ word-spacing: initial;
266
+ line-height: initial;
267
+ text-transform: initial;
268
+ text-indent: initial;
269
+ text-shadow: initial;
270
+ appearance: auto;
271
+ }
272
+
273
+ input:not([type=image i], [type=range i], [type=checkbox i], [type=radio i]) {
274
+ overflow: clip !important;
275
+ overflow-clip-margin: 0 !important;
276
+ }
277
+
278
+ input, select, textarea {
279
+ text-align: initial;
280
+ }
281
+
282
+ :autofill {
283
+ field-sizing: fixed !important;
284
+ }
285
+
286
+ input:is([type=reset i], [type=button i], [type=submit i]), button {
287
+ text-align: center;
288
+ }
289
+
290
+ input, button {
291
+ display: inline-block;
292
+ }
293
+
294
+ input[type=hidden i], input[type=file i], input[type=image i] {
295
+ appearance: none;
296
+ }
297
+
298
+ input:is([type=radio i], [type=checkbox i], [type=reset i], [type=button i],
299
+ [type=submit i], [type=color i], [type=search i]), select, button {
300
+ box-sizing: border-box;
301
+ }
302
+
303
+ textarea { white-space: pre-wrap; }
304
+
305
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-hr-element-2
306
+ - Omit presentational hints
307
+ */
308
+
309
+ hr {
310
+ color: gray;
311
+ border-style: inset;
312
+ border-width: 1px;
313
+ margin-block: 0.5em;
314
+ margin-inline: auto;
315
+ overflow: hidden;
316
+ }
317
+
318
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-fieldset-and-legend-elements */
319
+
320
+ fieldset {
321
+ display: block;
322
+ margin-inline: 2px;
323
+ border: groove 2px ThreeDFace;
324
+ padding-block: 0.35em 0.625em;
325
+ padding-inline: 0.75em;
326
+ min-inline-size: min-content;
327
+ }
328
+
329
+ legend {
330
+ padding-inline: 2px;
331
+ }
332
+
333
+ legend[align=left i] {
334
+ justify-self: left;
335
+ }
336
+
337
+ legend[align=center i] {
338
+ justify-self: center;
339
+ }
340
+
341
+ legend[align=right i] {
342
+ justify-self: right;
343
+ }
344
+
345
+ /* https://html.spec.whatwg.org/multipage/rendering.html#embedded-content-rendering-rules */
346
+
347
+ iframe { border: 2px inset; }
348
+ video { object-fit: contain; }
349
+
350
+ /* https://html.spec.whatwg.org/multipage/rendering.html#images-3
351
+ - Omit quirks mode
352
+ */
353
+
354
+ img:is([sizes="auto" i], [sizes^="auto," i]) {
355
+ contain: size !important;
356
+ contain-intrinsic-size: 300px 150px;
357
+ }
358
+
359
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements
360
+ - Omit internal shadow tree styles
361
+ */
362
+
363
+ details, summary {
364
+ display: block;
365
+ }
366
+ details > summary:first-of-type {
367
+ display: list-item;
368
+ counter-increment: list-item 0;
369
+ list-style: disclosure-closed inside;
370
+ }
371
+ details[open] > summary:first-of-type {
372
+ list-style-type: disclosure-open;
373
+ }
374
+
375
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-marquee-element-2 */
376
+
377
+ marquee {
378
+ display: inline-block;
379
+ text-align: initial;
380
+ overflow: hidden !important;
381
+ }
382
+
383
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-meter-element-2 */
384
+
385
+ meter { appearance: auto; }
386
+
387
+ /* https://html.spec.whatwg.org/multipage/rendering.html#the-progress-element-2 */
388
+
389
+ progress { appearance: auto; }
390
+
391
+ /* https://html.spec.whatwg.org/multipage/rendering.html#hidden-elements
392
+ - Moved to the bottom because our lack of specificity support causes tag name `display: block` and the below [hidden]
393
+ `display: none` to be last-one-wins.
394
+ */
395
+
396
+ area, base, basefont, datalist, head, link, meta, noembed,
397
+ noframes, param, rp, script, style, template, title {
398
+ display: none;
399
+ }
400
+
401
+ [hidden]:not([hidden=until-found i]):not(embed) {
402
+ display: none;
403
+ }
404
+
405
+ [hidden=until-found i]:not(embed) {
406
+ content-visibility: hidden;
407
+ }
408
+
409
+ embed[hidden] { display: inline; height: 0; width: 0; }
410
+
411
+ input[type=hidden i] { display: none !important; }
412
+
413
+ @media (scripting) {
414
+ noscript { display: none !important; }
415
+ }