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.
- package/HAMLETDEER.md +562 -0
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/README_CN.md +190 -0
- package/adapters/openclaw/index.ts +68 -0
- package/bin/autocrew.mjs +23 -0
- package/bin/autocrew.ts +13 -0
- package/openclaw.plugin.json +36 -0
- package/package.json +74 -0
- package/skills/_writing-style/SKILL.md +68 -0
- package/skills/audience-profiler/SKILL.md +241 -0
- package/skills/content-attribution/SKILL.md +128 -0
- package/skills/content-review/SKILL.md +257 -0
- package/skills/cover-generator/SKILL.md +93 -0
- package/skills/humanizer-zh/SKILL.md +75 -0
- package/skills/intel-digest/SKILL.md +57 -0
- package/skills/intel-pull/SKILL.md +74 -0
- package/skills/manage-pipeline/SKILL.md +63 -0
- package/skills/memory-distill/SKILL.md +89 -0
- package/skills/onboarding/SKILL.md +117 -0
- package/skills/pipeline-status/SKILL.md +51 -0
- package/skills/platform-rewrite/SKILL.md +125 -0
- package/skills/pre-publish/SKILL.md +142 -0
- package/skills/publish-content/SKILL.md +500 -0
- package/skills/remix-content/SKILL.md +77 -0
- package/skills/research/SKILL.md +127 -0
- package/skills/setup/SKILL.md +353 -0
- package/skills/spawn-batch-writer/SKILL.md +66 -0
- package/skills/spawn-planner/SKILL.md +72 -0
- package/skills/spawn-writer/SKILL.md +60 -0
- package/skills/teardown/SKILL.md +144 -0
- package/skills/title-craft/SKILL.md +234 -0
- package/skills/topic-ideas/SKILL.md +105 -0
- package/skills/video-timeline/SKILL.md +117 -0
- package/skills/write-script/SKILL.md +232 -0
- package/skills/xhs-cover-review/SKILL.md +48 -0
- package/src/adapters/browser/browser-cdp.ts +260 -0
- package/src/adapters/browser/browser-relay.ts +236 -0
- package/src/adapters/browser/gateway-client.ts +148 -0
- package/src/adapters/browser/types.ts +36 -0
- package/src/adapters/image/gemini.ts +219 -0
- package/src/adapters/research/tikhub.ts +19 -0
- package/src/cli/banner.ts +18 -0
- package/src/cli/bootstrap.ts +33 -0
- package/src/cli/commands/adapt.ts +28 -0
- package/src/cli/commands/advance.ts +28 -0
- package/src/cli/commands/assets.ts +24 -0
- package/src/cli/commands/audit.ts +18 -0
- package/src/cli/commands/contents.ts +18 -0
- package/src/cli/commands/cover.ts +58 -0
- package/src/cli/commands/events.ts +17 -0
- package/src/cli/commands/humanize.ts +27 -0
- package/src/cli/commands/index.ts +80 -0
- package/src/cli/commands/init.ts +28 -0
- package/src/cli/commands/intel.ts +55 -0
- package/src/cli/commands/learn.ts +34 -0
- package/src/cli/commands/memory.ts +18 -0
- package/src/cli/commands/migrate.ts +24 -0
- package/src/cli/commands/open.ts +21 -0
- package/src/cli/commands/pipelines.ts +18 -0
- package/src/cli/commands/pre-publish.ts +27 -0
- package/src/cli/commands/profile.ts +31 -0
- package/src/cli/commands/research.ts +36 -0
- package/src/cli/commands/restore.ts +28 -0
- package/src/cli/commands/review.ts +61 -0
- package/src/cli/commands/start.ts +28 -0
- package/src/cli/commands/status.ts +14 -0
- package/src/cli/commands/templates.ts +15 -0
- package/src/cli/commands/topics.ts +18 -0
- package/src/cli/commands/trash.ts +28 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/commands/versions.ts +24 -0
- package/src/cli/index.ts +40 -0
- package/src/data/sensitive-words-builtin.json +114 -0
- package/src/data/source-presets.yaml +54 -0
- package/src/e2e.test.ts +596 -0
- package/src/modules/auth/cookie-manager.ts +113 -0
- package/src/modules/cards/template-engine.ts +74 -0
- package/src/modules/cards/templates/comparison-table.ts +71 -0
- package/src/modules/cards/templates/data-chart.ts +76 -0
- package/src/modules/cards/templates/flow-chart.ts +49 -0
- package/src/modules/cards/templates/key-points.ts +59 -0
- package/src/modules/cover/prompt-builder.test.ts +157 -0
- package/src/modules/cover/prompt-builder.ts +212 -0
- package/src/modules/cover/ratio-adapter.test.ts +122 -0
- package/src/modules/cover/ratio-adapter.ts +104 -0
- package/src/modules/filter/sensitive-words.test.ts +72 -0
- package/src/modules/filter/sensitive-words.ts +212 -0
- package/src/modules/humanizer/zh.test.ts +75 -0
- package/src/modules/humanizer/zh.ts +175 -0
- package/src/modules/intel/collector.ts +19 -0
- package/src/modules/intel/collectors/competitor.test.ts +71 -0
- package/src/modules/intel/collectors/competitor.ts +65 -0
- package/src/modules/intel/collectors/rss.test.ts +56 -0
- package/src/modules/intel/collectors/rss.ts +70 -0
- package/src/modules/intel/collectors/trends.test.ts +80 -0
- package/src/modules/intel/collectors/trends.ts +107 -0
- package/src/modules/intel/collectors/web-search.test.ts +85 -0
- package/src/modules/intel/collectors/web-search.ts +81 -0
- package/src/modules/intel/integration.test.ts +203 -0
- package/src/modules/intel/intel-engine.test.ts +103 -0
- package/src/modules/intel/intel-engine.ts +96 -0
- package/src/modules/intel/source-config.test.ts +113 -0
- package/src/modules/intel/source-config.ts +131 -0
- package/src/modules/learnings/diff-tracker.test.ts +144 -0
- package/src/modules/learnings/diff-tracker.ts +189 -0
- package/src/modules/learnings/rule-distiller.ts +141 -0
- package/src/modules/memory/distill.ts +208 -0
- package/src/modules/migrate/legacy-migrate.test.ts +169 -0
- package/src/modules/migrate/legacy-migrate.ts +229 -0
- package/src/modules/pro/api-client.ts +192 -0
- package/src/modules/pro/gate.test.ts +110 -0
- package/src/modules/pro/gate.ts +104 -0
- package/src/modules/profile/creator-profile.test.ts +178 -0
- package/src/modules/profile/creator-profile.ts +248 -0
- package/src/modules/publish/douyin-api.ts +34 -0
- package/src/modules/publish/wechat-mp.ts +320 -0
- package/src/modules/publish/xiaohongshu-api.ts +127 -0
- package/src/modules/research/free-engine.ts +360 -0
- package/src/modules/timeline/markup-generator.ts +63 -0
- package/src/modules/timeline/parser.ts +275 -0
- package/src/modules/workflow/templates.ts +124 -0
- package/src/modules/writing/platform-rewrite.ts +190 -0
- package/src/modules/writing/title-hashtag.ts +385 -0
- package/src/runtime/context.test.ts +97 -0
- package/src/runtime/context.ts +129 -0
- package/src/runtime/events.test.ts +83 -0
- package/src/runtime/events.ts +104 -0
- package/src/runtime/hooks.ts +174 -0
- package/src/runtime/tool-runner.test.ts +204 -0
- package/src/runtime/tool-runner.ts +282 -0
- package/src/runtime/workflow-engine.test.ts +455 -0
- package/src/runtime/workflow-engine.ts +391 -0
- package/src/server/index.ts +409 -0
- package/src/server/start.ts +39 -0
- package/src/storage/local-store.test.ts +304 -0
- package/src/storage/local-store.ts +704 -0
- package/src/storage/pipeline-store.test.ts +363 -0
- package/src/storage/pipeline-store.ts +698 -0
- package/src/tools/asset.ts +96 -0
- package/src/tools/content-save.ts +276 -0
- package/src/tools/cover-review.ts +221 -0
- package/src/tools/humanize.ts +54 -0
- package/src/tools/init.ts +133 -0
- package/src/tools/intel.ts +92 -0
- package/src/tools/memory.ts +76 -0
- package/src/tools/pipeline-ops.ts +109 -0
- package/src/tools/pipeline.ts +168 -0
- package/src/tools/pre-publish.ts +232 -0
- package/src/tools/publish.ts +183 -0
- package/src/tools/registry.ts +198 -0
- package/src/tools/research.ts +304 -0
- package/src/tools/review.ts +305 -0
- package/src/tools/rewrite.ts +165 -0
- package/src/tools/status.ts +30 -0
- package/src/tools/timeline.ts +234 -0
- package/src/tools/topic-create.ts +50 -0
- package/src/types/providers.ts +69 -0
- package/src/types/timeline.test.ts +147 -0
- package/src/types/timeline.ts +83 -0
- package/src/utils/retry.test.ts +97 -0
- package/src/utils/retry.ts +85 -0
- package/templates/AGENTS.md +99 -0
- package/templates/SOUL.md +31 -0
- package/templates/TOOLS.md +76 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autocrew_pre_publish tool — Pre-publish checklist gate.
|
|
3
|
+
*
|
|
4
|
+
* Runs 6 checks before allowing content to be published:
|
|
5
|
+
* 1. Content review passed
|
|
6
|
+
* 2. Cover review passed (XHS/Douyin only)
|
|
7
|
+
* 3. Hashtags exist
|
|
8
|
+
* 4. Title within platform length range
|
|
9
|
+
* 5. Platform is set
|
|
10
|
+
* 6. Body length meets platform minimum
|
|
11
|
+
*/
|
|
12
|
+
import { Type } from "@sinclair/typebox";
|
|
13
|
+
import { getContent, getCoverReview, transitionStatus, normalizeLegacyStatus } from "../storage/local-store.js";
|
|
14
|
+
import { executeReview } from "./review.js";
|
|
15
|
+
import { getPlatformRules } from "../modules/writing/title-hashtag.js";
|
|
16
|
+
|
|
17
|
+
// --- Types ---
|
|
18
|
+
|
|
19
|
+
export interface CheckItem {
|
|
20
|
+
name: string;
|
|
21
|
+
status: "pass" | "fail" | "warn" | "skip";
|
|
22
|
+
detail: string;
|
|
23
|
+
fix?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PrePublishResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
contentId: string;
|
|
29
|
+
platform: string;
|
|
30
|
+
checks: CheckItem[];
|
|
31
|
+
allPassed: boolean;
|
|
32
|
+
passCount: number;
|
|
33
|
+
failCount: number;
|
|
34
|
+
summary: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// --- Platform body length minimums ---
|
|
38
|
+
|
|
39
|
+
const PLATFORM_MIN_BODY: Record<string, number> = {
|
|
40
|
+
xiaohongshu: 200,
|
|
41
|
+
xhs: 200,
|
|
42
|
+
douyin: 100,
|
|
43
|
+
wechat_mp: 800,
|
|
44
|
+
wechat_video: 100,
|
|
45
|
+
bilibili: 200,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// --- Platforms that require cover review ---
|
|
49
|
+
|
|
50
|
+
const COVER_REQUIRED_PLATFORMS = new Set(["xiaohongshu", "xhs", "douyin"]);
|
|
51
|
+
|
|
52
|
+
// --- Schema ---
|
|
53
|
+
|
|
54
|
+
export const prePublishSchema = Type.Object({
|
|
55
|
+
action: Type.Unsafe<"check">({
|
|
56
|
+
type: "string",
|
|
57
|
+
enum: ["check"],
|
|
58
|
+
description: "Action. 'check' runs the full pre-publish checklist.",
|
|
59
|
+
}),
|
|
60
|
+
content_id: Type.String({ description: "AutoCrew content id to check." }),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// --- Execute ---
|
|
64
|
+
|
|
65
|
+
export async function executePrePublish(params: Record<string, unknown>): Promise<PrePublishResult | { ok: false; error: string }> {
|
|
66
|
+
const contentId = params.content_id as string;
|
|
67
|
+
const dataDir = (params._dataDir as string) || undefined;
|
|
68
|
+
|
|
69
|
+
if (!contentId) return { ok: false, error: "content_id is required" };
|
|
70
|
+
|
|
71
|
+
const content = await getContent(contentId, dataDir);
|
|
72
|
+
if (!content) return { ok: false, error: `Content ${contentId} not found` };
|
|
73
|
+
|
|
74
|
+
const platform = content.platform || "";
|
|
75
|
+
const checks: CheckItem[] = [];
|
|
76
|
+
|
|
77
|
+
// --- Check 1: Content review ---
|
|
78
|
+
try {
|
|
79
|
+
const reviewResult = await executeReview({
|
|
80
|
+
action: "full_review",
|
|
81
|
+
content_id: contentId,
|
|
82
|
+
platform,
|
|
83
|
+
_dataDir: dataDir,
|
|
84
|
+
}) as any;
|
|
85
|
+
|
|
86
|
+
if (reviewResult.passed) {
|
|
87
|
+
const score = reviewResult.qualityScore?.total ?? "?";
|
|
88
|
+
checks.push({ name: "内容审核", status: "pass", detail: `通过 (质量 ${score}/100)` });
|
|
89
|
+
} else {
|
|
90
|
+
checks.push({
|
|
91
|
+
name: "内容审核",
|
|
92
|
+
status: "fail",
|
|
93
|
+
detail: reviewResult.summary || "未通过",
|
|
94
|
+
fix: "运行 autocrew_review action='auto_fix' 自动修复",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
checks.push({ name: "内容审核", status: "fail", detail: "审核执行出错", fix: "手动运行 autocrew_review" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- Check 2: Cover review (XHS/Douyin only) ---
|
|
102
|
+
if (COVER_REQUIRED_PLATFORMS.has(platform)) {
|
|
103
|
+
const coverReview = await getCoverReview(contentId, dataDir);
|
|
104
|
+
if (coverReview && coverReview.status === "approved" && coverReview.approvedLabel) {
|
|
105
|
+
checks.push({ name: "封面审核", status: "pass", detail: `已选定 ${coverReview.approvedLabel.toUpperCase()} 方案` });
|
|
106
|
+
} else if (coverReview && coverReview.status === "publish_ready" && coverReview.approvedLabel) {
|
|
107
|
+
checks.push({ name: "封面审核", status: "pass", detail: `已选定 ${coverReview.approvedLabel.toUpperCase()} 方案` });
|
|
108
|
+
} else {
|
|
109
|
+
checks.push({
|
|
110
|
+
name: "封面审核",
|
|
111
|
+
status: "fail",
|
|
112
|
+
detail: coverReview ? `状态: ${coverReview.status}` : "未完成",
|
|
113
|
+
fix: "运行 autocrew_cover_review action='create_candidates' 创建候选",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
checks.push({ name: "封面审核", status: "skip", detail: `${platform || "未知"} 平台无需封面审核` });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// --- Check 3: Hashtags ---
|
|
121
|
+
const hashtags = content.hashtags || [];
|
|
122
|
+
if (hashtags.length >= 1) {
|
|
123
|
+
checks.push({ name: "Hashtags", status: "pass", detail: `${hashtags.length} 个标签` });
|
|
124
|
+
} else {
|
|
125
|
+
checks.push({
|
|
126
|
+
name: "Hashtags",
|
|
127
|
+
status: "fail",
|
|
128
|
+
detail: "无标签",
|
|
129
|
+
fix: "通过 autocrew_rewrite 生成标题和标签,或手动 update hashtags",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Check 4: Title length ---
|
|
134
|
+
const title = content.title || "";
|
|
135
|
+
const rules = getPlatformRules(platform);
|
|
136
|
+
if (!title) {
|
|
137
|
+
checks.push({ name: "标题规范", status: "fail", detail: "无标题", fix: "设置标题" });
|
|
138
|
+
} else if (rules) {
|
|
139
|
+
const [minLen, maxLen] = rules.titleLengthRange;
|
|
140
|
+
const maxAbsolute = rules.maxTitleLength;
|
|
141
|
+
if (title.length > maxAbsolute) {
|
|
142
|
+
checks.push({
|
|
143
|
+
name: "标题规范",
|
|
144
|
+
status: "warn",
|
|
145
|
+
detail: `「${title}」(${title.length}字,超出 ${maxAbsolute} 上限)`,
|
|
146
|
+
fix: "通过 autocrew_rewrite 生成更短的标题变体",
|
|
147
|
+
});
|
|
148
|
+
} else if (title.length < minLen) {
|
|
149
|
+
checks.push({
|
|
150
|
+
name: "标题规范",
|
|
151
|
+
status: "warn",
|
|
152
|
+
detail: `「${title}」(${title.length}字,低于建议 ${minLen} 下限)`,
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
155
|
+
checks.push({
|
|
156
|
+
name: "标题规范",
|
|
157
|
+
status: "pass",
|
|
158
|
+
detail: `「${title}」(${title.length}字,符合 ${minLen}-${maxLen} 范围)`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// No rules for this platform, just check title exists
|
|
163
|
+
checks.push({ name: "标题规范", status: "pass", detail: `「${title}」(${title.length}字)` });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Check 5: Platform set ---
|
|
167
|
+
const supportedPlatforms = ["xiaohongshu", "xhs", "douyin", "wechat_mp", "wechat_video", "bilibili"];
|
|
168
|
+
if (platform && supportedPlatforms.includes(platform)) {
|
|
169
|
+
checks.push({ name: "平台设置", status: "pass", detail: platform });
|
|
170
|
+
} else if (platform) {
|
|
171
|
+
checks.push({ name: "平台设置", status: "warn", detail: `${platform} (非标准平台)` });
|
|
172
|
+
} else {
|
|
173
|
+
checks.push({ name: "平台设置", status: "fail", detail: "未指定平台", fix: "通过 autocrew_content update 设置 platform" });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Check 6: Body length ---
|
|
177
|
+
const bodyLen = (content.body || "").length;
|
|
178
|
+
const minBody = PLATFORM_MIN_BODY[platform] || 100;
|
|
179
|
+
if (bodyLen >= minBody) {
|
|
180
|
+
checks.push({ name: "正文字数", status: "pass", detail: `${bodyLen} 字 (≥${minBody})` });
|
|
181
|
+
} else {
|
|
182
|
+
checks.push({
|
|
183
|
+
name: "正文字数",
|
|
184
|
+
status: "fail",
|
|
185
|
+
detail: `${bodyLen} 字 (不足 ${minBody})`,
|
|
186
|
+
fix: "扩充正文,增加案例或数据",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Aggregate ---
|
|
191
|
+
const passCount = checks.filter((c) => c.status === "pass" || c.status === "skip").length;
|
|
192
|
+
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
193
|
+
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
194
|
+
const allPassed = failCount === 0;
|
|
195
|
+
|
|
196
|
+
// Build summary
|
|
197
|
+
const statusIcon: Record<string, string> = { pass: "✅", fail: "❌", warn: "⚠️", skip: "⏭️" };
|
|
198
|
+
const lines: string[] = [`📋 发布前检查 — ${contentId} (${platform || "未知平台"})`, ""];
|
|
199
|
+
for (const c of checks) {
|
|
200
|
+
lines.push(`${statusIcon[c.status]} ${c.name}:${c.detail}`);
|
|
201
|
+
if (c.fix) lines.push(` → ${c.fix}`);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
if (allPassed) {
|
|
205
|
+
lines.push("🟢 全部通过,可以发布!");
|
|
206
|
+
} else {
|
|
207
|
+
const issues = failCount + warnCount;
|
|
208
|
+
lines.push(`🔴 ${issues} 项需要关注${failCount > 0 ? `(${failCount} 项未通过)` : ""},请先修复再发布。`);
|
|
209
|
+
}
|
|
210
|
+
const summary = lines.join("\n");
|
|
211
|
+
|
|
212
|
+
// Auto-transition to publish_ready if all passed
|
|
213
|
+
if (allPassed) {
|
|
214
|
+
await transitionStatus(
|
|
215
|
+
contentId,
|
|
216
|
+
normalizeLegacyStatus("publish_ready"),
|
|
217
|
+
{},
|
|
218
|
+
dataDir,
|
|
219
|
+
).catch(() => { /* transition may fail if not in correct state — ok */ });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
ok: true,
|
|
224
|
+
contentId,
|
|
225
|
+
platform,
|
|
226
|
+
checks,
|
|
227
|
+
allPassed,
|
|
228
|
+
passCount,
|
|
229
|
+
failCount,
|
|
230
|
+
summary,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { getContent } from "../storage/local-store.js";
|
|
4
|
+
import { publishWechatMpDraft } from "../modules/publish/wechat-mp.js";
|
|
5
|
+
import { publishToXiaohongshu } from "../modules/publish/xiaohongshu-api.js";
|
|
6
|
+
import { publishToDouyin } from "../modules/publish/douyin-api.js";
|
|
7
|
+
import { loadCookie } from "../modules/auth/cookie-manager.js";
|
|
8
|
+
|
|
9
|
+
export const publishSchema = Type.Object({
|
|
10
|
+
action: Type.Unsafe<
|
|
11
|
+
"wechat_mp_draft" | "xiaohongshu_publish" | "douyin_publish" | "relay_publish"
|
|
12
|
+
>({
|
|
13
|
+
type: "string",
|
|
14
|
+
enum: ["wechat_mp_draft", "xiaohongshu_publish", "douyin_publish", "relay_publish"],
|
|
15
|
+
description:
|
|
16
|
+
"Publish action. Supported: 'wechat_mp_draft', 'xiaohongshu_publish', 'douyin_publish', 'relay_publish'.",
|
|
17
|
+
}),
|
|
18
|
+
article_path: Type.Optional(
|
|
19
|
+
Type.String({ description: "Absolute or relative path to the markdown article file." }),
|
|
20
|
+
),
|
|
21
|
+
content_id: Type.Optional(
|
|
22
|
+
Type.String({ description: "AutoCrew content id. If provided, draft.md will be used." }),
|
|
23
|
+
),
|
|
24
|
+
theme: Type.Optional(
|
|
25
|
+
Type.String({ description: "WeChat formatting theme. Default: newspaper." }),
|
|
26
|
+
),
|
|
27
|
+
dry_run: Type.Optional(
|
|
28
|
+
Type.Boolean({ description: "Generate assets and show the publish command without pushing." }),
|
|
29
|
+
),
|
|
30
|
+
skip_images: Type.Optional(
|
|
31
|
+
Type.Boolean({ description: "Skip image generation if images already exist." }),
|
|
32
|
+
),
|
|
33
|
+
author: Type.Optional(
|
|
34
|
+
Type.String({ description: "Displayed author name for the WeChat publish script." }),
|
|
35
|
+
),
|
|
36
|
+
image_size: Type.Optional(
|
|
37
|
+
Type.String({ description: "Image ratio for generated images. Default: 16:9." }),
|
|
38
|
+
),
|
|
39
|
+
image_generator_script: Type.Optional(
|
|
40
|
+
Type.String({ description: "Override path to the image generation script." }),
|
|
41
|
+
),
|
|
42
|
+
image_api_key: Type.Optional(
|
|
43
|
+
Type.String({ description: "Override image generation API key." }),
|
|
44
|
+
),
|
|
45
|
+
wechat_publish_script: Type.Optional(
|
|
46
|
+
Type.String({ description: "Override path to the WeChat publish.py script." }),
|
|
47
|
+
),
|
|
48
|
+
|
|
49
|
+
// XiaoHongShu-specific fields
|
|
50
|
+
title: Type.Optional(
|
|
51
|
+
Type.String({ description: "Title for XiaoHongShu / Douyin note." }),
|
|
52
|
+
),
|
|
53
|
+
description: Type.Optional(
|
|
54
|
+
Type.String({ description: "Description text for XiaoHongShu / Douyin note." }),
|
|
55
|
+
),
|
|
56
|
+
image_paths: Type.Optional(
|
|
57
|
+
Type.Array(Type.String(), { description: "Image file paths for XiaoHongShu note." }),
|
|
58
|
+
),
|
|
59
|
+
cookie: Type.Optional(
|
|
60
|
+
Type.String({ description: "Platform cookie string. Falls back to stored cookie or env." }),
|
|
61
|
+
),
|
|
62
|
+
is_private: Type.Optional(
|
|
63
|
+
Type.Boolean({ description: "Publish as private. Default: true (safety first)." }),
|
|
64
|
+
),
|
|
65
|
+
post_time: Type.Optional(
|
|
66
|
+
Type.String({ description: "Scheduled post time (e.g. '2026-04-05 10:00')." }),
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
// Douyin-specific fields
|
|
70
|
+
video_path: Type.Optional(
|
|
71
|
+
Type.String({ description: "Video file path for Douyin publish." }),
|
|
72
|
+
),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function resolveDataDir(customDir?: string): string {
|
|
76
|
+
if (customDir) return customDir;
|
|
77
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
78
|
+
return path.join(home, ".autocrew");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function resolveArticlePath(params: Record<string, unknown>): Promise<string | null> {
|
|
82
|
+
const articlePath = params.article_path as string | undefined;
|
|
83
|
+
if (articlePath) {
|
|
84
|
+
return path.resolve(articlePath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const contentId = params.content_id as string | undefined;
|
|
88
|
+
if (!contentId) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const dataDir = resolveDataDir((params._dataDir as string) || undefined);
|
|
93
|
+
const content = await getContent(contentId, dataDir);
|
|
94
|
+
if (!content) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return path.join(dataDir, "contents", content.id, "draft.md");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function executeWechatMpDraft(params: Record<string, unknown>) {
|
|
102
|
+
const articlePath = await resolveArticlePath(params);
|
|
103
|
+
if (!articlePath) {
|
|
104
|
+
return { ok: false, error: "article_path or content_id is required" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return publishWechatMpDraft({
|
|
108
|
+
articlePath,
|
|
109
|
+
theme: (params.theme as string) || "newspaper",
|
|
110
|
+
dryRun: Boolean(params.dry_run),
|
|
111
|
+
skipImages: Boolean(params.skip_images),
|
|
112
|
+
author: (params.author as string) || "Lawrence",
|
|
113
|
+
imageSize: (params.image_size as string) || "16:9",
|
|
114
|
+
imageGeneratorScript: (params.image_generator_script as string) || undefined,
|
|
115
|
+
imageApiKey: (params.image_api_key as string) || undefined,
|
|
116
|
+
wechatPublishScript: (params.wechat_publish_script as string) || undefined,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function executeXiaohongshuPublish(params: Record<string, unknown>) {
|
|
121
|
+
const title = params.title as string | undefined;
|
|
122
|
+
const description = params.description as string | undefined;
|
|
123
|
+
const imagePaths = params.image_paths as string[] | undefined;
|
|
124
|
+
|
|
125
|
+
if (!title || !description) {
|
|
126
|
+
return { ok: false, error: "title and description are required for xiaohongshu_publish" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!imagePaths || imagePaths.length === 0) {
|
|
130
|
+
return { ok: false, error: "image_paths (at least one image) is required for xiaohongshu_publish" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let cookie = params.cookie as string | undefined;
|
|
134
|
+
if (!cookie) {
|
|
135
|
+
cookie = (await loadCookie("xiaohongshu")) ?? undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return publishToXiaohongshu({
|
|
139
|
+
title,
|
|
140
|
+
description,
|
|
141
|
+
imagePaths,
|
|
142
|
+
cookie,
|
|
143
|
+
isPrivate: params.is_private !== false,
|
|
144
|
+
postTime: (params.post_time as string) || undefined,
|
|
145
|
+
dryRun: Boolean(params.dry_run),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function executeDouyinPublish(params: Record<string, unknown>) {
|
|
150
|
+
return publishToDouyin({
|
|
151
|
+
title: (params.title as string) || "",
|
|
152
|
+
description: (params.description as string) || "",
|
|
153
|
+
videoPath: (params.video_path as string) || undefined,
|
|
154
|
+
imagePaths: (params.image_paths as string[]) || undefined,
|
|
155
|
+
isPrivate: params.is_private !== false,
|
|
156
|
+
postTime: (params.post_time as string) || undefined,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function executePublish(params: Record<string, unknown>) {
|
|
161
|
+
const action = params.action as string;
|
|
162
|
+
|
|
163
|
+
switch (action) {
|
|
164
|
+
case "wechat_mp_draft":
|
|
165
|
+
return executeWechatMpDraft(params);
|
|
166
|
+
|
|
167
|
+
case "xiaohongshu_publish":
|
|
168
|
+
return executeXiaohongshuPublish(params);
|
|
169
|
+
|
|
170
|
+
case "douyin_publish":
|
|
171
|
+
return executeDouyinPublish(params);
|
|
172
|
+
|
|
173
|
+
case "relay_publish":
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error:
|
|
177
|
+
"relay_publish is experimental. Chrome Relay via OpenClaw Gateway is for research only, not publishing. Use platform-specific API publishers instead.",
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
default:
|
|
181
|
+
return { ok: false, error: `Unknown action: ${action}` };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry — single source of truth for all AutoCrew tool definitions.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the OpenClaw plugin (index.ts) and the standalone server (start.ts).
|
|
5
|
+
*/
|
|
6
|
+
import { topicCreateSchema, executeTopicCreate } from "./topic-create.js";
|
|
7
|
+
import { researchSchema, executeResearch } from "./research.js";
|
|
8
|
+
import { contentSaveSchema, executeContentSave } from "./content-save.js";
|
|
9
|
+
import { statusSchema, executeStatus } from "./status.js";
|
|
10
|
+
import { assetSchema, executeAsset } from "./asset.js";
|
|
11
|
+
import { pipelineSchema, createPipelineExecutor } from "./pipeline.js";
|
|
12
|
+
import { publishSchema, executePublish } from "./publish.js";
|
|
13
|
+
import { humanizeSchema, executeHumanize } from "./humanize.js";
|
|
14
|
+
import { rewriteSchema, executeRewrite } from "./rewrite.js";
|
|
15
|
+
import { coverReviewSchema, executeCoverReview } from "./cover-review.js";
|
|
16
|
+
import { memorySchema, executeMemory } from "./memory.js";
|
|
17
|
+
import { reviewSchema, executeReview } from "./review.js";
|
|
18
|
+
import { prePublishSchema, executePrePublish } from "./pre-publish.js";
|
|
19
|
+
import { intelSchema, executeIntel } from "./intel.js";
|
|
20
|
+
import { pipelineOpsSchema, executePipelineOps } from "./pipeline-ops.js";
|
|
21
|
+
import { timelineSchema, executeTimeline } from "./timeline.js";
|
|
22
|
+
import { executeInit } from "./init.js";
|
|
23
|
+
import { getProStatus } from "../modules/pro/gate.js";
|
|
24
|
+
import { loadProfile, detectMissingInfo } from "../modules/profile/creator-profile.js";
|
|
25
|
+
import type { ToolRunner } from "../runtime/tool-runner.js";
|
|
26
|
+
|
|
27
|
+
export function registerAllTools(runner: ToolRunner): void {
|
|
28
|
+
runner.register({
|
|
29
|
+
name: "autocrew_topic",
|
|
30
|
+
label: "AutoCrew Topic",
|
|
31
|
+
description: "Create or list content topics. Actions: create, list.",
|
|
32
|
+
parameters: topicCreateSchema,
|
|
33
|
+
execute: executeTopicCreate,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
runner.register({
|
|
37
|
+
name: "autocrew_research",
|
|
38
|
+
label: "AutoCrew Research",
|
|
39
|
+
description:
|
|
40
|
+
"Topic discovery with multiple modes: browser-first (Pro), API fallback, free (web search + viral scoring), or manual. " +
|
|
41
|
+
"Supports action='discover' to generate/save topics and action='session_status' to inspect browser login readiness.",
|
|
42
|
+
parameters: researchSchema,
|
|
43
|
+
execute: executeResearch,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
runner.register({
|
|
47
|
+
name: "autocrew_content",
|
|
48
|
+
label: "AutoCrew Content",
|
|
49
|
+
description:
|
|
50
|
+
"Manage content lifecycle: save drafts, list/get/update content, transition status, manage siblings and variants. " +
|
|
51
|
+
"Actions: save, list, get, update, transition, list_siblings, create_variant.",
|
|
52
|
+
parameters: contentSaveSchema,
|
|
53
|
+
execute: executeContentSave,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
runner.register({
|
|
57
|
+
name: "autocrew_status",
|
|
58
|
+
label: "AutoCrew Status",
|
|
59
|
+
description: "Show pipeline status: topic count, content count, status breakdown.",
|
|
60
|
+
parameters: statusSchema,
|
|
61
|
+
execute: executeStatus,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
runner.register({
|
|
65
|
+
name: "autocrew_asset",
|
|
66
|
+
label: "AutoCrew Asset",
|
|
67
|
+
description:
|
|
68
|
+
"Manage content project assets (covers, B-Roll, images, videos, subtitles) and version history. Actions: add, list, remove, versions, get_version, revert.",
|
|
69
|
+
parameters: assetSchema,
|
|
70
|
+
execute: executeAsset,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
runner.register({
|
|
74
|
+
name: "autocrew_pipeline",
|
|
75
|
+
label: "AutoCrew Pipeline",
|
|
76
|
+
description:
|
|
77
|
+
"Workflow orchestration for content pipelines. Actions: create (from template), start, status, approve (paused step), cancel, list, templates.",
|
|
78
|
+
parameters: pipelineSchema,
|
|
79
|
+
execute: createPipelineExecutor(runner),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
runner.register({
|
|
83
|
+
name: "autocrew_publish",
|
|
84
|
+
label: "AutoCrew Publish",
|
|
85
|
+
description:
|
|
86
|
+
"Run proven publishing flows. Actions: xiaohongshu_publish (API mode), douyin_publish, wechat_mp_draft, relay_publish (experimental).",
|
|
87
|
+
parameters: publishSchema,
|
|
88
|
+
execute: executePublish,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
runner.register({
|
|
92
|
+
name: "autocrew_humanize",
|
|
93
|
+
label: "AutoCrew Humanize",
|
|
94
|
+
description: "Run the Chinese de-AI pass on content text. Removes AI-sounding patterns and corporate buzzwords.",
|
|
95
|
+
parameters: humanizeSchema,
|
|
96
|
+
execute: executeHumanize,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
runner.register({
|
|
100
|
+
name: "autocrew_rewrite",
|
|
101
|
+
label: "AutoCrew Rewrite",
|
|
102
|
+
description:
|
|
103
|
+
"Create platform-native rewrites. Actions: adapt_platform (single platform), batch_adapt (multi-platform + auto title/hashtag + sibling linking).",
|
|
104
|
+
parameters: rewriteSchema,
|
|
105
|
+
execute: executeRewrite,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
runner.register({
|
|
109
|
+
name: "autocrew_cover_review",
|
|
110
|
+
label: "AutoCrew Cover Review",
|
|
111
|
+
description:
|
|
112
|
+
"Generate, review, and approve cover images via Gemini. Actions: create_candidates (generate 3 style variants), get (view review), approve (pick one), generate_ratios (Pro: 16:9 + 4:3).",
|
|
113
|
+
parameters: coverReviewSchema,
|
|
114
|
+
execute: executeCoverReview,
|
|
115
|
+
needsGemini: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
runner.register({
|
|
119
|
+
name: "autocrew_memory",
|
|
120
|
+
label: "AutoCrew Memory",
|
|
121
|
+
description:
|
|
122
|
+
"Capture user feedback into MEMORY.md or read current memory. Supports action='capture_feedback' and action='get_memory'.",
|
|
123
|
+
parameters: memorySchema,
|
|
124
|
+
execute: executeMemory,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
runner.register({
|
|
128
|
+
name: "autocrew_review",
|
|
129
|
+
label: "AutoCrew Review",
|
|
130
|
+
description:
|
|
131
|
+
"Content review: sensitive words scan + quality score + de-AI check. Actions: full_review, scan_only, quality_score, auto_fix.",
|
|
132
|
+
parameters: reviewSchema,
|
|
133
|
+
execute: executeReview,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
runner.register({
|
|
137
|
+
name: "autocrew_pre_publish",
|
|
138
|
+
label: "AutoCrew Pre-Publish",
|
|
139
|
+
description: "Pre-publish gate: 6 checks before allowing publish. Actions: check.",
|
|
140
|
+
parameters: prePublishSchema,
|
|
141
|
+
execute: executePrePublish,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
runner.register({
|
|
145
|
+
name: "autocrew_intel",
|
|
146
|
+
label: "AutoCrew 灵感源",
|
|
147
|
+
description:
|
|
148
|
+
"Inspiration source pipeline. Actions: pull (collect from web/RSS/trends), list (show saved inspiration), clean (archive expired).",
|
|
149
|
+
parameters: intelSchema,
|
|
150
|
+
execute: executeIntel,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
runner.register({
|
|
154
|
+
name: "autocrew_pipeline_ops",
|
|
155
|
+
label: "AutoCrew Pipeline Ops",
|
|
156
|
+
description:
|
|
157
|
+
"Content pipeline lifecycle management. Actions: status (stage counts), start (topic→project), advance (next stage), version (add draft), trash, restore.",
|
|
158
|
+
parameters: pipelineOpsSchema,
|
|
159
|
+
execute: executePipelineOps,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
runner.register({
|
|
163
|
+
name: "autocrew_timeline",
|
|
164
|
+
label: "AutoCrew Timeline",
|
|
165
|
+
description:
|
|
166
|
+
"Generate and manage video timelines. Actions: generate (parse marked script into timeline.json), get (retrieve timeline), update_segment (update segment status/asset), confirm_all (confirm all ready segments).",
|
|
167
|
+
parameters: timelineSchema,
|
|
168
|
+
execute: executeTimeline,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
runner.register({
|
|
172
|
+
name: "autocrew_init",
|
|
173
|
+
label: "AutoCrew Init",
|
|
174
|
+
description: "Initialize the AutoCrew data directory (~/.autocrew/) and creator profile. Safe to run multiple times.",
|
|
175
|
+
parameters: { type: "object" as const, properties: {} },
|
|
176
|
+
execute: async (params) => executeInit({ dataDir: params._dataDir as string }),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
runner.register({
|
|
180
|
+
name: "autocrew_pro_status",
|
|
181
|
+
label: "AutoCrew Pro Status",
|
|
182
|
+
description: "Check AutoCrew Pro status: whether Pro is active, profile completeness, and missing info.",
|
|
183
|
+
parameters: { type: "object" as const, properties: {} },
|
|
184
|
+
execute: async (params) => {
|
|
185
|
+
const dir = params._dataDir as string;
|
|
186
|
+
const proStatus = await getProStatus(dir);
|
|
187
|
+
const profile = await loadProfile(dir);
|
|
188
|
+
const missing = profile ? detectMissingInfo(profile) : ["profile_not_initialized"];
|
|
189
|
+
return {
|
|
190
|
+
ok: true,
|
|
191
|
+
isPro: proStatus.isPro,
|
|
192
|
+
profileExists: profile !== null,
|
|
193
|
+
missingInfo: missing,
|
|
194
|
+
styleCalibrated: profile?.styleCalibrated ?? false,
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|