autocrew 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.
Files changed (165) hide show
  1. package/HAMLETDEER.md +562 -0
  2. package/LICENSE +21 -0
  3. package/README.md +190 -0
  4. package/README_CN.md +190 -0
  5. package/adapters/openclaw/index.ts +68 -0
  6. package/bin/autocrew.mjs +23 -0
  7. package/bin/autocrew.ts +13 -0
  8. package/openclaw.plugin.json +36 -0
  9. package/package.json +74 -0
  10. package/skills/_writing-style/SKILL.md +68 -0
  11. package/skills/audience-profiler/SKILL.md +241 -0
  12. package/skills/content-attribution/SKILL.md +128 -0
  13. package/skills/content-review/SKILL.md +257 -0
  14. package/skills/cover-generator/SKILL.md +93 -0
  15. package/skills/humanizer-zh/SKILL.md +75 -0
  16. package/skills/intel-digest/SKILL.md +57 -0
  17. package/skills/intel-pull/SKILL.md +74 -0
  18. package/skills/manage-pipeline/SKILL.md +63 -0
  19. package/skills/memory-distill/SKILL.md +89 -0
  20. package/skills/onboarding/SKILL.md +117 -0
  21. package/skills/pipeline-status/SKILL.md +51 -0
  22. package/skills/platform-rewrite/SKILL.md +125 -0
  23. package/skills/pre-publish/SKILL.md +142 -0
  24. package/skills/publish-content/SKILL.md +500 -0
  25. package/skills/remix-content/SKILL.md +77 -0
  26. package/skills/research/SKILL.md +127 -0
  27. package/skills/setup/SKILL.md +353 -0
  28. package/skills/spawn-batch-writer/SKILL.md +66 -0
  29. package/skills/spawn-planner/SKILL.md +72 -0
  30. package/skills/spawn-writer/SKILL.md +60 -0
  31. package/skills/teardown/SKILL.md +144 -0
  32. package/skills/title-craft/SKILL.md +234 -0
  33. package/skills/topic-ideas/SKILL.md +105 -0
  34. package/skills/video-timeline/SKILL.md +117 -0
  35. package/skills/write-script/SKILL.md +232 -0
  36. package/skills/xhs-cover-review/SKILL.md +48 -0
  37. package/src/adapters/browser/browser-cdp.ts +260 -0
  38. package/src/adapters/browser/browser-relay.ts +236 -0
  39. package/src/adapters/browser/gateway-client.ts +148 -0
  40. package/src/adapters/browser/types.ts +36 -0
  41. package/src/adapters/image/gemini.ts +219 -0
  42. package/src/adapters/research/tikhub.ts +19 -0
  43. package/src/cli/banner.ts +18 -0
  44. package/src/cli/bootstrap.ts +33 -0
  45. package/src/cli/commands/adapt.ts +28 -0
  46. package/src/cli/commands/advance.ts +28 -0
  47. package/src/cli/commands/assets.ts +24 -0
  48. package/src/cli/commands/audit.ts +18 -0
  49. package/src/cli/commands/contents.ts +18 -0
  50. package/src/cli/commands/cover.ts +58 -0
  51. package/src/cli/commands/events.ts +17 -0
  52. package/src/cli/commands/humanize.ts +27 -0
  53. package/src/cli/commands/index.ts +80 -0
  54. package/src/cli/commands/init.ts +28 -0
  55. package/src/cli/commands/intel.ts +55 -0
  56. package/src/cli/commands/learn.ts +34 -0
  57. package/src/cli/commands/memory.ts +18 -0
  58. package/src/cli/commands/migrate.ts +24 -0
  59. package/src/cli/commands/open.ts +21 -0
  60. package/src/cli/commands/pipelines.ts +18 -0
  61. package/src/cli/commands/pre-publish.ts +27 -0
  62. package/src/cli/commands/profile.ts +31 -0
  63. package/src/cli/commands/research.ts +36 -0
  64. package/src/cli/commands/restore.ts +28 -0
  65. package/src/cli/commands/review.ts +61 -0
  66. package/src/cli/commands/start.ts +28 -0
  67. package/src/cli/commands/status.ts +14 -0
  68. package/src/cli/commands/templates.ts +15 -0
  69. package/src/cli/commands/topics.ts +18 -0
  70. package/src/cli/commands/trash.ts +28 -0
  71. package/src/cli/commands/upgrade.ts +48 -0
  72. package/src/cli/commands/versions.ts +24 -0
  73. package/src/cli/index.ts +40 -0
  74. package/src/data/sensitive-words-builtin.json +114 -0
  75. package/src/data/source-presets.yaml +54 -0
  76. package/src/e2e.test.ts +596 -0
  77. package/src/modules/auth/cookie-manager.ts +113 -0
  78. package/src/modules/cards/template-engine.ts +74 -0
  79. package/src/modules/cards/templates/comparison-table.ts +71 -0
  80. package/src/modules/cards/templates/data-chart.ts +76 -0
  81. package/src/modules/cards/templates/flow-chart.ts +49 -0
  82. package/src/modules/cards/templates/key-points.ts +59 -0
  83. package/src/modules/cover/prompt-builder.test.ts +157 -0
  84. package/src/modules/cover/prompt-builder.ts +212 -0
  85. package/src/modules/cover/ratio-adapter.test.ts +122 -0
  86. package/src/modules/cover/ratio-adapter.ts +104 -0
  87. package/src/modules/filter/sensitive-words.test.ts +72 -0
  88. package/src/modules/filter/sensitive-words.ts +212 -0
  89. package/src/modules/humanizer/zh.test.ts +75 -0
  90. package/src/modules/humanizer/zh.ts +175 -0
  91. package/src/modules/intel/collector.ts +19 -0
  92. package/src/modules/intel/collectors/competitor.test.ts +71 -0
  93. package/src/modules/intel/collectors/competitor.ts +65 -0
  94. package/src/modules/intel/collectors/rss.test.ts +56 -0
  95. package/src/modules/intel/collectors/rss.ts +70 -0
  96. package/src/modules/intel/collectors/trends.test.ts +80 -0
  97. package/src/modules/intel/collectors/trends.ts +107 -0
  98. package/src/modules/intel/collectors/web-search.test.ts +85 -0
  99. package/src/modules/intel/collectors/web-search.ts +81 -0
  100. package/src/modules/intel/integration.test.ts +203 -0
  101. package/src/modules/intel/intel-engine.test.ts +103 -0
  102. package/src/modules/intel/intel-engine.ts +96 -0
  103. package/src/modules/intel/source-config.test.ts +113 -0
  104. package/src/modules/intel/source-config.ts +131 -0
  105. package/src/modules/learnings/diff-tracker.test.ts +144 -0
  106. package/src/modules/learnings/diff-tracker.ts +189 -0
  107. package/src/modules/learnings/rule-distiller.ts +141 -0
  108. package/src/modules/memory/distill.ts +208 -0
  109. package/src/modules/migrate/legacy-migrate.test.ts +169 -0
  110. package/src/modules/migrate/legacy-migrate.ts +229 -0
  111. package/src/modules/pro/api-client.ts +192 -0
  112. package/src/modules/pro/gate.test.ts +110 -0
  113. package/src/modules/pro/gate.ts +104 -0
  114. package/src/modules/profile/creator-profile.test.ts +178 -0
  115. package/src/modules/profile/creator-profile.ts +248 -0
  116. package/src/modules/publish/douyin-api.ts +34 -0
  117. package/src/modules/publish/wechat-mp.ts +320 -0
  118. package/src/modules/publish/xiaohongshu-api.ts +127 -0
  119. package/src/modules/research/free-engine.ts +360 -0
  120. package/src/modules/timeline/markup-generator.ts +63 -0
  121. package/src/modules/timeline/parser.ts +275 -0
  122. package/src/modules/workflow/templates.ts +124 -0
  123. package/src/modules/writing/platform-rewrite.ts +190 -0
  124. package/src/modules/writing/title-hashtag.ts +385 -0
  125. package/src/runtime/context.test.ts +97 -0
  126. package/src/runtime/context.ts +129 -0
  127. package/src/runtime/events.test.ts +83 -0
  128. package/src/runtime/events.ts +104 -0
  129. package/src/runtime/hooks.ts +174 -0
  130. package/src/runtime/tool-runner.test.ts +204 -0
  131. package/src/runtime/tool-runner.ts +282 -0
  132. package/src/runtime/workflow-engine.test.ts +455 -0
  133. package/src/runtime/workflow-engine.ts +391 -0
  134. package/src/server/index.ts +409 -0
  135. package/src/server/start.ts +39 -0
  136. package/src/storage/local-store.test.ts +304 -0
  137. package/src/storage/local-store.ts +704 -0
  138. package/src/storage/pipeline-store.test.ts +363 -0
  139. package/src/storage/pipeline-store.ts +698 -0
  140. package/src/tools/asset.ts +96 -0
  141. package/src/tools/content-save.ts +276 -0
  142. package/src/tools/cover-review.ts +221 -0
  143. package/src/tools/humanize.ts +54 -0
  144. package/src/tools/init.ts +133 -0
  145. package/src/tools/intel.ts +92 -0
  146. package/src/tools/memory.ts +76 -0
  147. package/src/tools/pipeline-ops.ts +109 -0
  148. package/src/tools/pipeline.ts +168 -0
  149. package/src/tools/pre-publish.ts +232 -0
  150. package/src/tools/publish.ts +183 -0
  151. package/src/tools/registry.ts +198 -0
  152. package/src/tools/research.ts +304 -0
  153. package/src/tools/review.ts +305 -0
  154. package/src/tools/rewrite.ts +165 -0
  155. package/src/tools/status.ts +30 -0
  156. package/src/tools/timeline.ts +234 -0
  157. package/src/tools/topic-create.ts +50 -0
  158. package/src/types/providers.ts +69 -0
  159. package/src/types/timeline.test.ts +147 -0
  160. package/src/types/timeline.ts +83 -0
  161. package/src/utils/retry.test.ts +97 -0
  162. package/src/utils/retry.ts +85 -0
  163. package/templates/AGENTS.md +99 -0
  164. package/templates/SOUL.md +31 -0
  165. package/templates/TOOLS.md +76 -0
@@ -0,0 +1,500 @@
1
+ ---
2
+ name: publish-content
3
+ description: |
4
+ Publish approved content to social media platforms. Activate when user asks to publish, post, or distribute content. Trigger: "发布" / "发到小红书" / "帮我发" / "发布这篇". Supports Xiaohongshu, Douyin, WeChat Video, WeChat MP (公众号).
5
+ ---
6
+
7
+ # 发布内容
8
+
9
+ > 工具型技能。双模式:复制粘贴格式化(通用)+ 浏览器自动化(OpenClaw MCP Browser)。
10
+
11
+ ## 功能矩阵
12
+
13
+ | Feature | OpenClaw | Claude Code |
14
+ |---------|----------|-------------|
15
+ | Pre-publish validation | ✅ | ✅ |
16
+ | Format for copy-paste | ✅ | ✅ |
17
+ | Browser automation publish | ✅ (MCP Browser) | ❌ (manual) |
18
+ | Status tracking | ✅ | ✅ |
19
+
20
+ ## 关键规则(浏览器自动化)
21
+
22
+ 1. **Navigate by URL**, not by clicking menus (menus are unreliable in accessibility tree).
23
+ 2. **Wait 5 seconds** after each navigation for page to load.
24
+ 3. **Video upload uses `input[type=file]`** — find the hidden file input via `evaluate`, then use `setInputFiles`. NEVER try drag-and-drop.
25
+ 4. **Cover image upload** — same approach: find `input[type=file]` for cover, use `setInputFiles`.
26
+ 5. **Truncate content** to platform limits silently. Add "…" if truncated.
27
+ 6. **Respond in Simplified Chinese** for all user-facing messages.
28
+
29
+ ## 平台路由
30
+
31
+ | Platform | Upload/Create URL | Login Detection |
32
+ |----------|------------------|-----------------|
33
+ | xiaohongshu | `https://creator.xiaohongshu.com/publish/publish` | URL contains `/login` |
34
+ | douyin | `https://creator.douyin.com/creator-micro/content/upload` | URL redirects to `sso.douyin.com` |
35
+ | wechat_video | `https://channels.weixin.qq.com/platform/post/create` | URL redirects to `/login.html` |
36
+ | wechat_mp | `https://mp.weixin.qq.com/` | URL contains `/login` or no cookie |
37
+
38
+ ## 平台限制
39
+
40
+ | Platform | Title limit | Body limit | Tags |
41
+ |----------|------------|-----------|------|
42
+ | xiaohongshu | 20 chars | 1000 chars | 5-15 |
43
+ | douyin | 30 chars | 1000 chars | 5 |
44
+ | wechat_video | — (描述 only) | 1000 chars | 5 |
45
+ | wechat_mp | 64 chars | 20000 chars | — |
46
+
47
+ ---
48
+
49
+ ## 第零步:确定要发布的内容
50
+
51
+ 1. List content: `autocrew_content` action="list"
52
+ 2. Filter for status="approved" (or "draft" if user explicitly asks)
53
+ 3. IF no content → tell user to write some first.
54
+ 4. Load the content project: `autocrew_asset` action="list" content_id=xxx to check for cover/video assets.
55
+
56
+ ## 第一步:发布前校验
57
+
58
+ Check content against platform limits. Auto-truncate with "…" if over limit.
59
+
60
+ Check required assets:
61
+ - xiaohongshu image note: needs at least 1 image
62
+ - douyin: needs video
63
+ - wechat_video: needs video
64
+ - wechat_mp: text only is fine
65
+
66
+ IF missing required assets → ask user to provide them, or use `autocrew_asset` action="add" to register.
67
+
68
+ ## 第二步:路由到模式
69
+
70
+ - IF browser automation available (OpenClaw MCP Browser) AND user wants auto-publish → go to **Browser Automation Steps**
71
+ - ELSE → go to **Copy-Paste Mode**
72
+
73
+ ---
74
+
75
+ ## 复制粘贴模式(通用)
76
+
77
+ 格式化内容供用户手动发布,在聊天中输出。
78
+
79
+ ### 小红书格式:
80
+ <output_template lang="zh-CN">
81
+ ```
82
+ 📋 小红书发布内容:
83
+
84
+ 标题:{title}
85
+
86
+ 正文:
87
+ {body}
88
+
89
+ 标签:#tag1 #tag2 #tag3 #tag4 #tag5
90
+
91
+ 📎 封面:{cover asset path or "需要手动上传"}
92
+ 🔗 发布地址:https://creator.xiaohongshu.com/publish/publish
93
+ ```
94
+ </output_template>
95
+
96
+ ### 抖音格式:
97
+ <output_template lang="zh-CN">
98
+ ```
99
+ 📋 抖音发布内容:
100
+
101
+ 作品描述:{title}
102
+ 简介:{body_truncated_1000}
103
+ 标签:#tag1 #tag2 #tag3
104
+
105
+ 📎 视频:{video asset path}
106
+ 📎 封面:{cover asset path or "使用自动封面"}
107
+ 🔗 发布地址:https://creator.douyin.com/creator-micro/content/upload
108
+ ```
109
+ </output_template>
110
+
111
+ ### 视频号格式:
112
+ <output_template lang="zh-CN">
113
+ ```
114
+ 📋 视频号发布内容:
115
+
116
+ 创作描述:
117
+ {title}
118
+ {body_truncated_900}
119
+
120
+ #tag1 #tag2 #tag3
121
+
122
+ 📎 视频:{video asset path}
123
+ 🔗 发布地址:https://channels.weixin.qq.com/platform/post/create
124
+ ```
125
+ </output_template>
126
+
127
+ ### 公众号格式:
128
+ <output_template lang="zh-CN">
129
+ ```
130
+ 📋 公众号发布内容:
131
+
132
+ 标题:{title}
133
+ 正文:
134
+ {body_with_formatting}
135
+
136
+ 📎 封面:{cover asset path or "需要手动上传"}
137
+ 🔗 发布地址:https://mp.weixin.qq.com/
138
+ ```
139
+ </output_template>
140
+
141
+ After outputting, update status:
142
+ ```json
143
+ { "action": "update", "id": "content-xxx", "status": "published" }
144
+ ```
145
+
146
+ ---
147
+
148
+ ## 浏览器自动化流程(仅 OpenClaw)
149
+
150
+ ### 通用:导航并检查登录
151
+
152
+ ```json
153
+ {"action": "navigate", "targetUrl": "<platform create URL>"}
154
+ ```
155
+
156
+ Wait 5 seconds. Take snapshot.
157
+
158
+ - IF URL matches login detection pattern → tell user: "平台登录已过期,请手动登录后重试。"
159
+ - ELSE → continue to platform-specific steps.
160
+
161
+ ---
162
+
163
+ ### 抖音步骤
164
+
165
+ > URL: `https://creator.douyin.com/creator-micro/content/upload`
166
+ > After video upload, auto-redirects to `/content/publish`
167
+
168
+ **D1: Upload video**
169
+
170
+ Find file input and upload:
171
+ ```json
172
+ {
173
+ "action": "act",
174
+ "request": {
175
+ "kind": "evaluate",
176
+ "fn": "(() => { const input = document.querySelector('input[type=file][accept*=video], input[type=file][accept*=mp4]'); if (input) { input.style.display = 'block'; return 'found'; } return 'not_found'; })()"
177
+ }
178
+ }
179
+ ```
180
+
181
+ Get video path from project assets, then:
182
+ ```json
183
+ {
184
+ "action": "act",
185
+ "request": {
186
+ "kind": "setInputFiles",
187
+ "selector": "input[type=file][accept*=video], input[type=file][accept*=mp4]",
188
+ "files": ["<video_path>"]
189
+ }
190
+ }
191
+ ```
192
+
193
+ Wait for upload (poll every 5s, up to 120s). Check if URL changes to `/content/publish`.
194
+
195
+ **D2: Fill form**
196
+
197
+ Title (作品描述, max 30 chars):
198
+ ```json
199
+ {
200
+ "action": "act",
201
+ "request": {
202
+ "kind": "evaluate",
203
+ "fn": "(() => { const input = document.querySelector('input[placeholder*=标题], input[placeholder*=作品]'); if (input) { const nativeSet = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set; nativeSet.call(input, '<title_30>'); input.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } return 'not_found'; })()"
204
+ }
205
+ }
206
+ ```
207
+
208
+ Description (简介, max 1000 chars) — contenteditable div:
209
+ ```json
210
+ {
211
+ "action": "act",
212
+ "request": {
213
+ "kind": "evaluate",
214
+ "fn": "(() => { const editors = document.querySelectorAll('[contenteditable=true]'); for (const ed of editors) { if (ed.offsetHeight > 50) { ed.focus(); ed.textContent = '<description_1000>'; ed.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } } return 'not_found'; })()"
215
+ }
216
+ }
217
+ ```
218
+
219
+ Tags — append to description or find tag input:
220
+ ```json
221
+ {
222
+ "action": "act",
223
+ "request": {
224
+ "kind": "evaluate",
225
+ "fn": "(() => { const editors = document.querySelectorAll('[contenteditable=true]'); for (const ed of editors) { if (ed.textContent.includes('添加话题') || ed.closest('[class*=topic]')) { ed.focus(); const tags = ['tag1','tag2','tag3']; tags.slice(0,5).forEach(t => { ed.textContent += ' #' + t; }); ed.dispatchEvent(new Event('input', {bubbles: true})); return 'added'; } } return 'not_found'; })()"
226
+ }
227
+ }
228
+ ```
229
+
230
+ **D3: Set cover** (if cover asset exists)
231
+
232
+ Find cover file input:
233
+ ```json
234
+ {
235
+ "action": "act",
236
+ "request": {
237
+ "kind": "evaluate",
238
+ "fn": "(() => { const inputs = document.querySelectorAll('input[type=file][accept*=image]'); for (const inp of inputs) { const parent = inp.closest('[class*=cover], [class*=poster]'); if (parent) { inp.style.display = 'block'; return 'found'; } } return 'not_found'; })()"
239
+ }
240
+ }
241
+ ```
242
+
243
+ Then `setInputFiles` with cover path. Wait 3s.
244
+
245
+ **D4: Publish**
246
+ ```json
247
+ {
248
+ "action": "act",
249
+ "request": {
250
+ "kind": "evaluate",
251
+ "fn": "(() => { const btns = document.querySelectorAll('button'); for (const b of btns) { if (b.textContent.trim() === '发布' && !b.disabled) { b.click(); return 'clicked'; } } return 'not_found'; })()"
252
+ }
253
+ }
254
+ ```
255
+
256
+ Wait 5s. Verify success (redirect to content management or success message).
257
+
258
+ ---
259
+
260
+ ### 小红书步骤
261
+
262
+ > URL: `https://creator.xiaohongshu.com/publish/publish`
263
+ > Tabs: 上传视频 / 上传图文 / 写长文 (tabs NOT in accessibility tree, use JS click)
264
+
265
+ **X1: Switch tab if needed**
266
+
267
+ IF no video (image note) → switch to "上传图文":
268
+ ```json
269
+ {
270
+ "action": "act",
271
+ "request": {
272
+ "kind": "evaluate",
273
+ "fn": "(() => { const tabs = document.querySelectorAll('[class*=tab] span, [role=tab]'); for (const t of tabs) { if (t.textContent.includes('上传图文')) { t.click(); return 'switched'; } } return 'not_found'; })()"
274
+ }
275
+ }
276
+ ```
277
+
278
+ **X2: Upload media**
279
+
280
+ Video or images via `input[type=file]` + `setInputFiles`. Same pattern as Douyin.
281
+
282
+ **X3: Fill form**
283
+
284
+ Title (max 20 chars):
285
+ ```json
286
+ {
287
+ "action": "act",
288
+ "request": {
289
+ "kind": "evaluate",
290
+ "fn": "(() => { const input = document.querySelector('input[placeholder*=标题], input[class*=title]'); if (input) { const nativeSet = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set; nativeSet.call(input, '<title_20>'); input.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } return 'not_found'; })()"
291
+ }
292
+ }
293
+ ```
294
+
295
+ Body (max 1000 chars) — contenteditable:
296
+ ```json
297
+ {
298
+ "action": "act",
299
+ "request": {
300
+ "kind": "evaluate",
301
+ "fn": "(() => { const editors = document.querySelectorAll('[contenteditable=true], textarea[placeholder*=正文]'); for (const ed of editors) { if (ed.offsetHeight > 80 || ed.tagName === 'TEXTAREA') { ed.focus(); if (ed.tagName === 'TEXTAREA') { ed.value = '<body_1000>'; } else { ed.textContent = '<body_1000>'; } ed.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } } return 'not_found'; })()"
302
+ }
303
+ }
304
+ ```
305
+
306
+ Tags — append `#tag1 #tag2` to body, or find tag input and Enter each tag.
307
+
308
+ **X4: Publish**
309
+ ```json
310
+ {
311
+ "action": "act",
312
+ "request": {
313
+ "kind": "evaluate",
314
+ "fn": "(() => { const btns = document.querySelectorAll('button'); for (const b of btns) { const t = b.textContent.trim(); if ((t === '发布' || t === '发布笔记') && !b.disabled) { b.click(); return 'clicked'; } } return 'not_found'; })()"
315
+ }
316
+ }
317
+ ```
318
+
319
+ ---
320
+
321
+ ### 视频号步骤
322
+
323
+ > URL: `https://channels.weixin.qq.com/platform/post/create`
324
+
325
+ **W1: Upload video** — same `input[type=file]` + `setInputFiles` pattern.
326
+
327
+ **W2: Fill 创作描述** — combine title + body + tags into one field (no separate title):
328
+ ```json
329
+ {
330
+ "action": "act",
331
+ "request": {
332
+ "kind": "evaluate",
333
+ "fn": "(() => { const editors = document.querySelectorAll('[contenteditable=true], textarea'); for (const ed of editors) { if (ed.offsetHeight > 50) { ed.focus(); const text = '<title>\\n<body_900>\\n\\n#tag1 #tag2 #tag3'; if (ed.tagName === 'TEXTAREA') { ed.value = text; } else { ed.textContent = text; } ed.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } } return 'not_found'; })()"
334
+ }
335
+ }
336
+ ```
337
+
338
+ **W3: Set cover** — find cover file input if custom cover exists.
339
+
340
+ **W4: Toggle 原创声明** (if original content):
341
+ ```json
342
+ {
343
+ "action": "act",
344
+ "request": {
345
+ "kind": "evaluate",
346
+ "fn": "(() => { const switches = document.querySelectorAll('[role=switch], input[type=checkbox]'); for (const s of switches) { if (s.closest('label')?.textContent?.includes('原创')) { if (!s.checked) { s.click(); return 'toggled'; } return 'already_on'; } } return 'not_found'; })()"
347
+ }
348
+ }
349
+ ```
350
+
351
+ **W5: Publish** — button text is "发表":
352
+ ```json
353
+ {
354
+ "action": "act",
355
+ "request": {
356
+ "kind": "evaluate",
357
+ "fn": "(() => { const btns = document.querySelectorAll('button'); for (const b of btns) { if ((b.textContent.trim() === '发表' || b.textContent.trim() === '发布') && !b.disabled) { b.click(); return 'clicked'; } } return 'not_found'; })()"
358
+ }
359
+ }
360
+ ```
361
+
362
+ ---
363
+
364
+ ### 公众号步骤
365
+
366
+ > URL: `https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&type=77`
367
+ > This is the "图文消息" editor. Navigate via: 内容与互动 → 图文消息 → 写新图文
368
+
369
+ **M1: Navigate to new article editor**
370
+
371
+ ```json
372
+ {"action": "navigate", "targetUrl": "https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&type=77"}
373
+ ```
374
+
375
+ Wait 5s. If redirected to login → tell user to log in manually.
376
+
377
+ **M2: Fill title**
378
+
379
+ ```json
380
+ {
381
+ "action": "act",
382
+ "request": {
383
+ "kind": "evaluate",
384
+ "fn": "(() => { const input = document.querySelector('#title, input[placeholder*=标题], input[name=title]'); if (input) { const nativeSet = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set; nativeSet.call(input, '<title_64>'); input.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } return 'not_found'; })()"
385
+ }
386
+ }
387
+ ```
388
+
389
+ **M3: Fill body**
390
+
391
+ The MP editor uses a rich text iframe or contenteditable. Fill with formatted content:
392
+
393
+ ```json
394
+ {
395
+ "action": "act",
396
+ "request": {
397
+ "kind": "evaluate",
398
+ "fn": "(() => { const editor = document.querySelector('#edui1_body, [contenteditable=true][class*=rich], .edui-body-container'); if (editor) { editor.innerHTML = '<formatted_html_body>'; editor.dispatchEvent(new Event('input', {bubbles: true})); return 'filled'; } const iframe = document.querySelector('#ueditor_0, iframe[class*=edui]'); if (iframe) { const doc = iframe.contentDocument || iframe.contentWindow.document; const body = doc.querySelector('body[contenteditable=true], body'); if (body) { body.innerHTML = '<formatted_html_body>'; return 'filled_iframe'; } } return 'not_found'; })()"
399
+ }
400
+ }
401
+ ```
402
+
403
+ Note: MP body supports HTML formatting. Convert markdown to HTML:
404
+ - `## heading` → `<h2>heading</h2>`
405
+ - `**bold**` → `<strong>bold</strong>`
406
+ - Paragraphs → `<p>text</p>`
407
+ - Blank lines → `<p><br></p>`
408
+
409
+ **M4: Set cover image**
410
+
411
+ Find the cover upload area (usually at the top of the editor):
412
+ ```json
413
+ {
414
+ "action": "act",
415
+ "request": {
416
+ "kind": "evaluate",
417
+ "fn": "(() => { const inputs = document.querySelectorAll('input[type=file][accept*=image]'); for (const inp of inputs) { inp.style.display = 'block'; return 'found'; } return 'not_found'; })()"
418
+ }
419
+ }
420
+ ```
421
+
422
+ Then `setInputFiles` with cover path.
423
+
424
+ **M5: Preview and publish**
425
+
426
+ MP has a two-step flow: "预览" then "群发" or "发布".
427
+
428
+ For draft save (safer):
429
+ ```json
430
+ {
431
+ "action": "act",
432
+ "request": {
433
+ "kind": "evaluate",
434
+ "fn": "(() => { const btns = document.querySelectorAll('button, a'); for (const b of btns) { const t = b.textContent.trim(); if (t === '保存' || t === '保存草稿') { b.click(); return 'saved_draft'; } } return 'not_found'; })()"
435
+ }
436
+ }
437
+ ```
438
+
439
+ For direct publish:
440
+ ```json
441
+ {
442
+ "action": "act",
443
+ "request": {
444
+ "kind": "evaluate",
445
+ "fn": "(() => { const btns = document.querySelectorAll('button, a'); for (const b of btns) { const t = b.textContent.trim(); if (t === '群发' || t === '发布') { b.click(); return 'clicked'; } } return 'not_found'; })()"
446
+ }
447
+ }
448
+ ```
449
+
450
+ ---
451
+
452
+ ## 通用:更新状态
453
+
454
+ After publish (success or failure), update content status:
455
+
456
+ ```json
457
+ { "action": "update", "id": "content-xxx", "status": "published" }
458
+ ```
459
+
460
+ Output:
461
+ ```
462
+ ✅ 已发布到{platform_label}
463
+ ```
464
+ or
465
+ ```
466
+ ❌ 发布失败:{error_message}。请检查账号状态后重试。
467
+ ```
468
+
469
+ ## 选择器适配策略
470
+
471
+ DOM 选择器基于 2026 年初观察到的页面结构。**页面会变。**
472
+
473
+ 当选择器返回 `not_found` 时:
474
+ 1. 用 `browser snapshot` 查看当前页面结构
475
+ 2. 通过 role、placeholder 或相邻标签定位正确元素
476
+ 3. 适配选择器
477
+ 4. 核心模式不变:evaluate → 找元素 → 填写/点击
478
+
479
+ 关键提示:
480
+ - **Douyin**: Title placeholder "填写作品标题". Publish button "发布".
481
+ - **Xiaohongshu**: Title placeholder contains "标题". Publish button "发布" or "发布笔记".
482
+ - **WeChat Video**: One main text area (创作描述). Publish button "发表".
483
+ - **WeChat MP**: Title input `#title`. Body in iframe or contenteditable. Save "保存", publish "群发".
484
+
485
+ ## 错误处理
486
+
487
+ | 故障 | 处理 |
488
+ |------|------|
489
+ | 登录过期(重定向) | 告诉用户手动登录 |
490
+ | 上传超时(>120s) | 报告失败,建议重试 |
491
+ | 验证码弹窗 | 报告失败,建议手动登录 |
492
+ | 表单校验错误 | 从 DOM 读取错误信息,报告给用户 |
493
+ | 内容过长 | 截断到限制 + "…",继续 |
494
+ | `setInputFiles` 失败 | 尝试页面上所有 `input[type=file]` |
495
+ | 找不到发布按钮 | 用 snapshot 找到正确按钮 |
496
+ | 浏览器不可用 | 回退到复制粘贴模式 |
497
+
498
+ ## 变更日志
499
+
500
+ - 2026-03-31: v1 — Adapted from Qingmo publish-content.md v2. Added WeChat MP (公众号) support. Dual-mode: copy-paste + browser automation. Removed backend API dependency. Assets loaded from local project directory.
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: remix-content
3
+ description: |
4
+ 内容二创。用不同角度重写已有内容,确保原创度>70%。
5
+ Trigger: 二创, 改写, remix, 换个角度写
6
+ ---
7
+
8
+ # 内容二创
9
+
10
+ 用不同角度重写已有内容。确保原创度 >70%,不是换词洗稿,而是重新思考。
11
+
12
+ ## 写作规范
13
+
14
+ 加载 `_writing-style` skill 作为格式规范。所有输出遵循其 hook、正文结构、CTA、标题规则。
15
+
16
+ ## 流程
17
+
18
+ ### 1. 分析源内容
19
+
20
+ 拿到源内容后,先拆解:
21
+
22
+ - **话题**:这篇在说什么?一句话概括。
23
+ - **核心论点**:列出所有信息点(通常 3-8 个)。
24
+ - **结构**:hook 类型、论证方式、CTA 方向。
25
+ - **语气**:正式/口语/搞笑/专业?
26
+
27
+ ### 2. 生成二创版本
28
+
29
+ 基于分析,重写。以下是硬性要求:
30
+
31
+ **Hook**:
32
+ - 必须使用与原文不同的角度。
33
+ - 如果原文用痛点开头,换成反差或悬念。
34
+ - 参考 `_writing-style` 的 hook 类型表选择。
35
+
36
+ **正文**:
37
+ - 5-8 个信息点。
38
+ - 其中**至少 3 个是原创的**(原文中没有的新论点、新例子、新数据)。
39
+ - 可以保留原文的核心洞察,但论证路径必须不同。
40
+ - 800-1500 字。
41
+
42
+ **CTA**:
43
+ - 品牌专属,与内容价值挂钩。
44
+ - 从 `~/.autocrew/STYLE.md` 获取品牌语气。
45
+
46
+ **标题**:
47
+ - 用 `web_search` 查 2-3 个热搜关键词。
48
+ - 嵌入 1 个热搜词 + 制造好奇缺口。
49
+ - 15-25 字。
50
+
51
+ ### 3. 自检清单
52
+
53
+ 写完后逐条检查,全部通过才算完成:
54
+
55
+ - [ ] **重合度 <30%**:没有复用原文的短语、比喻、句式?
56
+ - [ ] **至少 3 个原创信息点**:不是换词,是新的论据?
57
+ - [ ] **字数 800-1500 字**:在范围内?
58
+ - [ ] **语气匹配**:与 `_writing-style` 和 STYLE.md 的要求一致?
59
+ - [ ] **Hook 角度不同**:和原文用了不同的 hook 类型?
60
+
61
+ 如果任一项没通过,改到通过为止。
62
+
63
+ ## 保存
64
+
65
+ 通过 `autocrew_content` 工具保存:
66
+
67
+ ```json
68
+ {
69
+ "action": "create",
70
+ "title": "...",
71
+ "body": "...",
72
+ "source": "remix",
73
+ "source_ref": "原文链接或ID"
74
+ }
75
+ ```
76
+
77
+ 也可以直接在聊天中输出,由用户决定是否保存。