@remixmate/cli 0.9.70 → 0.9.72
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 +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/handlers/gen-post.d.ts +22 -0
- package/dist/handlers/gen-post.js +192 -0
- package/dist/handlers/index.js +2 -0
- package/dist/manifest.json +119 -2
- package/package.json +1 -1
- package/skills/gen-post/SKILL.md +114 -0
- package/skills/gen-post/skill.json +112 -0
- package/skills/gen-post/version.json +7 -0
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ This project contains 13 AI media generation skills covering the full pipeline f
|
|
|
187
187
|
│ web-screenshot Web page screenshot (png / jpg) │
|
|
188
188
|
│ web-record Web page recording / storyboard video │
|
|
189
189
|
│ web-read Web page text extraction (markdown/json) │
|
|
190
|
+
│ gen-post Post copy: Douyin / XHS / TikTok │
|
|
190
191
|
└─────────────────────────────────────────────────────────────┘
|
|
191
192
|
```
|
|
192
193
|
|
|
@@ -207,6 +208,7 @@ This project contains 13 AI media generation skills covering the full pipeline f
|
|
|
207
208
|
│ ├── render-video/ # orchestration: Phase 3 Remotion render (canonical render_video.py)
|
|
208
209
|
│ ├── export-jianying/ # orchestration: export Jianying draft ZIP
|
|
209
210
|
│ ├── video-parser/ # tool: video deconstruction & analysis
|
|
211
|
+
│ ├── gen-post/ # tool: social post copy per platform (http handler)
|
|
210
212
|
│ ├── web-screenshot/ # tool: headless-browser screenshot (also hosts record.py)
|
|
211
213
|
│ ├── web-record/ # tool: headless-browser recording (entry -> web-screenshot/scripts/record.py)
|
|
212
214
|
│ └── web-read/ # tool: headless-browser text extraction (entry -> web-screenshot/scripts/read_page.py)
|
|
@@ -227,6 +229,7 @@ This project contains 13 AI media generation skills covering the full pipeline f
|
|
|
227
229
|
| render-video | orchestration | job_id → Remotion render → upload | python `scripts/render_video.py` |
|
|
228
230
|
| export-jianying | orchestration | Asset URLs → Jianying draft ZIP (auto-converts from RenderPlan) | python `scripts/gen_jianying_draft.py` |
|
|
229
231
|
| video-parser | tool | Video → audio + ASR + keyframes + scene segmentation | python `scripts/parse_via_render.py` |
|
|
232
|
+
| gen-post | tool | Post text for a finished video — title / description / hashtags for Douyin, Xiaohongshu, TikTok | http handler |
|
|
230
233
|
| web-screenshot | tool | Web page screenshot (png / jpg) | python `scripts/screenshot.py` |
|
|
231
234
|
| web-record | tool | Web page recording / scroll-through / storyboard video (webm -> mp4 -> VOD) | python `../web-screenshot/scripts/record.py` |
|
|
232
235
|
| web-read | tool | Web page → main text (markdown / plain text / structured JSON) | python `../web-screenshot/scripts/read_page.py` |
|
package/README.zh-CN.md
CHANGED
|
@@ -141,6 +141,7 @@ Python 技能(13 个中的 9 个)需要 `python3 >= 3.10`。`web-screenshot`
|
|
|
141
141
|
│ web-screenshot 网页截图(png / jpg) │
|
|
142
142
|
│ web-record 网页录屏 / 滚动录屏 / 分镜视频 │
|
|
143
143
|
│ web-read 网页正文抽取(markdown / 纯文本 / JSON) │
|
|
144
|
+
│ gen-post 发布文案(抖音 / 小红书 / TikTok) │
|
|
144
145
|
└─────────────────────────────────────────────────────────────┘
|
|
145
146
|
```
|
|
146
147
|
|
|
@@ -161,6 +162,7 @@ Python 技能(13 个中的 9 个)需要 `python3 >= 3.10`。`web-screenshot`
|
|
|
161
162
|
│ ├── render-video/ # 编排: Phase 3 Remotion 渲染(含 render_video.py 实现)
|
|
162
163
|
│ ├── export-jianying/ # 编排: 导出剪映草稿 ZIP
|
|
163
164
|
│ ├── video-parser/ # 工具: 视频解构与分析
|
|
165
|
+
│ ├── gen-post/ # 工具: 按平台生成发布文案(http handler)
|
|
164
166
|
│ ├── web-screenshot/ # 工具: 无头浏览器截图(record.py 也放在这里)
|
|
165
167
|
│ ├── web-record/ # 工具: 无头浏览器录屏(入口指向 web-screenshot/scripts/record.py)
|
|
166
168
|
│ └── web-read/ # 工具: 无头浏览器正文抽取(入口指向 web-screenshot/scripts/read_page.py)
|
|
@@ -181,6 +183,7 @@ Python 技能(13 个中的 9 个)需要 `python3 >= 3.10`。`web-screenshot`
|
|
|
181
183
|
| render-video | 编排 | job_id → Remotion 渲染 → 上传 | python `scripts/render_video.py` |
|
|
182
184
|
| export-jianying | 编排 | 素材 URL → 剪映草稿 ZIP(支持从 RenderPlan 自动转换) | python `scripts/gen_jianying_draft.py` |
|
|
183
185
|
| video-parser | 工具 | 视频 → 音频 + ASR + 关键帧 + 场景分段 | python `scripts/parse_via_render.py` |
|
|
186
|
+
| gen-post | 工具 | 成片的发布文案:抖音 / 小红书 / TikTok 的标题、描述、话题标签 | http handler |
|
|
184
187
|
| web-screenshot | 工具 | 网页截图(png / jpg) | python `scripts/screenshot.py` |
|
|
185
188
|
| web-record | 工具 | 网页录屏 / 滚动录屏 / 分镜视频(webm → mp4 → VOD) | python `../web-screenshot/scripts/record.py` |
|
|
186
189
|
| web-read | 工具 | 网页正文抽取(markdown / 纯文本 / 结构化 JSON) | python `../web-screenshot/scripts/read_page.py` |
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-post handler — POST ab-api /publish/caption/generate.
|
|
3
|
+
*
|
|
4
|
+
* Generates the text of a social post — per-platform title / description / tags —
|
|
5
|
+
* for a finished video (the video itself is not touched or uploaded): the same endpoint and prompts the web studio's 「发布文案」
|
|
6
|
+
* panel uses. Prompt wording, compliance rules and the platform roster all live
|
|
7
|
+
* in ab-api (publish_prompts.go + the user's saved prompt overrides); this
|
|
8
|
+
* handler only collects context and forwards it.
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - --platform is repeatable and/or comma-separated; `all` (the default) expands
|
|
12
|
+
* to the three platforms the web panel shows. Each platform is one backend
|
|
13
|
+
* call (one LLM run, billed separately), issued concurrently.
|
|
14
|
+
* - --render-id links the result to a studio take so it shows up in the web
|
|
15
|
+
* panel; without it the backend generates without persisting anything.
|
|
16
|
+
* - --instruction is the user's "how should it read" request. With a draft
|
|
17
|
+
* (--title / --description / --tag) it rewrites that caption — single platform
|
|
18
|
+
* only, since the draft belongs to one platform. Without a draft it is an extra
|
|
19
|
+
* requirement applied to a fresh generation on every requested platform.
|
|
20
|
+
*/
|
|
21
|
+
import type { HandlerContext, HandlerInput } from './index.js';
|
|
22
|
+
export declare function genPost(input: HandlerInput, ctxIn: HandlerContext): Promise<void>;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-post handler — POST ab-api /publish/caption/generate.
|
|
3
|
+
*
|
|
4
|
+
* Generates the text of a social post — per-platform title / description / tags —
|
|
5
|
+
* for a finished video (the video itself is not touched or uploaded): the same endpoint and prompts the web studio's 「发布文案」
|
|
6
|
+
* panel uses. Prompt wording, compliance rules and the platform roster all live
|
|
7
|
+
* in ab-api (publish_prompts.go + the user's saved prompt overrides); this
|
|
8
|
+
* handler only collects context and forwards it.
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - --platform is repeatable and/or comma-separated; `all` (the default) expands
|
|
12
|
+
* to the three platforms the web panel shows. Each platform is one backend
|
|
13
|
+
* call (one LLM run, billed separately), issued concurrently.
|
|
14
|
+
* - --render-id links the result to a studio take so it shows up in the web
|
|
15
|
+
* panel; without it the backend generates without persisting anything.
|
|
16
|
+
* - --instruction is the user's "how should it read" request. With a draft
|
|
17
|
+
* (--title / --description / --tag) it rewrites that caption — single platform
|
|
18
|
+
* only, since the draft belongs to one platform. Without a draft it is an extra
|
|
19
|
+
* requirement applied to a fresh generation on every requested platform.
|
|
20
|
+
*/
|
|
21
|
+
import { EXIT, mmPost, resolveHttpContext, SkillError } from '../http.js';
|
|
22
|
+
import { emitProgress } from '../progress.js';
|
|
23
|
+
import { isTrue, toNumber, toStringArray } from './shared.js';
|
|
24
|
+
/** Platforms the web panel enables by default; `all` expands to these. */
|
|
25
|
+
const DEFAULT_PLATFORMS = ['douyin', 'xiaohongshu', 'tiktok'];
|
|
26
|
+
/** Friendly aliases → backend platform codes. Unknown values pass through for ab-api to validate. */
|
|
27
|
+
const PLATFORM_ALIASES = {
|
|
28
|
+
dy: 'douyin',
|
|
29
|
+
抖音: 'douyin',
|
|
30
|
+
xhs: 'xiaohongshu',
|
|
31
|
+
rednote: 'xiaohongshu',
|
|
32
|
+
小红书: 'xiaohongshu',
|
|
33
|
+
tt: 'tiktok',
|
|
34
|
+
};
|
|
35
|
+
/** Display names for human-readable output only. */
|
|
36
|
+
const PLATFORM_LABELS = {
|
|
37
|
+
douyin: '抖音 Douyin',
|
|
38
|
+
xiaohongshu: '小红书 Xiaohongshu',
|
|
39
|
+
tiktok: 'TikTok',
|
|
40
|
+
};
|
|
41
|
+
/** Backend waits on a non-streaming LLM call (80s server-side cap); leave headroom. */
|
|
42
|
+
const GENERATE_TIMEOUT_MS = 90_000;
|
|
43
|
+
function resolvePlatforms(value) {
|
|
44
|
+
const raw = toStringArray(value)
|
|
45
|
+
.flatMap((v) => v.split(/[,,\s]+/))
|
|
46
|
+
.map((v) => v.trim())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
if (raw.length === 0)
|
|
49
|
+
return [...DEFAULT_PLATFORMS];
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const item of raw) {
|
|
52
|
+
const key = item.toLowerCase();
|
|
53
|
+
// 「全部」: the web param card writes option labels into the message, and the agent
|
|
54
|
+
// may forward the label verbatim.
|
|
55
|
+
const codes = key === 'all' || item === '全部' ? [...DEFAULT_PLATFORMS] : [PLATFORM_ALIASES[item] ?? PLATFORM_ALIASES[key] ?? key];
|
|
56
|
+
for (const code of codes)
|
|
57
|
+
if (!out.includes(code))
|
|
58
|
+
out.push(code);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function optString(value) {
|
|
63
|
+
if (typeof value !== 'string')
|
|
64
|
+
return undefined;
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
return trimmed || undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Tags may arrive as `--tag a --tag b`, `--tag "a,b"`, or with a leading `#`. */
|
|
69
|
+
function normalizeTags(value) {
|
|
70
|
+
return toStringArray(value)
|
|
71
|
+
.flatMap((v) => v.split(/[,,]+/))
|
|
72
|
+
.map((v) => v.trim().replace(/^#+/, ''))
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
async function generateOne(ctx, platform, base) {
|
|
76
|
+
emitProgress({ phase: 'gen-post:request', platform });
|
|
77
|
+
try {
|
|
78
|
+
const caption = await mmPost(ctx, '/publish/caption/generate', { ...base, platform }, { timeoutMs: GENERATE_TIMEOUT_MS });
|
|
79
|
+
if (!caption || (!caption.title && !caption.description)) {
|
|
80
|
+
return { platform, error: 'backend returned an empty caption' };
|
|
81
|
+
}
|
|
82
|
+
emitProgress({ phase: 'gen-post:done', platform });
|
|
83
|
+
return { platform, caption: { ...caption, platform, tags: caption.tags ?? [] } };
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
// Auth failures abort the whole run: every other platform would fail the same way.
|
|
87
|
+
if (err instanceof SkillError && err.exitCode === EXIT.NOT_AUTHENTICATED)
|
|
88
|
+
throw err;
|
|
89
|
+
return { platform, error: err instanceof Error ? err.message : String(err) };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function printHuman(results) {
|
|
93
|
+
for (const r of results) {
|
|
94
|
+
const label = PLATFORM_LABELS[r.platform] ?? r.platform;
|
|
95
|
+
process.stdout.write(`\n━━━━━━━━ ${label} ━━━━━━━━\n`);
|
|
96
|
+
if (!r.caption) {
|
|
97
|
+
process.stdout.write(`❌ ${r.error ?? 'generation failed'}\n`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const { title, description, tags } = r.caption;
|
|
101
|
+
process.stdout.write(`标题 / Title:\n${title}\n\n`);
|
|
102
|
+
process.stdout.write(`描述 / Description:\n${description}\n\n`);
|
|
103
|
+
process.stdout.write(`标签 / Tags:\n${tags.map((t) => `#${t}`).join(' ')}\n`);
|
|
104
|
+
}
|
|
105
|
+
const ok = results.filter((r) => r.caption).length;
|
|
106
|
+
process.stdout.write(`\n🎉 Generated publish copy for ${ok}/${results.length} platform(s)\n`);
|
|
107
|
+
}
|
|
108
|
+
export async function genPost(input, ctxIn) {
|
|
109
|
+
const topic = optString(input.topic);
|
|
110
|
+
const hook = optString(input.hook);
|
|
111
|
+
const instruction = optString(input.instruction);
|
|
112
|
+
const renderId = optString(input.render_id);
|
|
113
|
+
const platforms = resolvePlatforms(input.platform);
|
|
114
|
+
const current = {
|
|
115
|
+
title: optString(input.title) ?? '',
|
|
116
|
+
description: optString(input.description) ?? '',
|
|
117
|
+
tags: normalizeTags(input.tag),
|
|
118
|
+
};
|
|
119
|
+
const hasDraft = !!(current.title || current.description || current.tags.length);
|
|
120
|
+
if (instruction && instruction.length > 500) {
|
|
121
|
+
throw new SkillError('❌ --instruction must be at most 500 characters');
|
|
122
|
+
}
|
|
123
|
+
// With a draft, --instruction rewrites that one platform's caption. Without one it is
|
|
124
|
+
// an extra requirement for a fresh generation, which needs the video's context.
|
|
125
|
+
const rewriting = !!instruction && hasDraft;
|
|
126
|
+
if (hasDraft && !instruction) {
|
|
127
|
+
throw new SkillError('❌ --title / --description / --tag are the draft to rewrite; pass --instruction with them');
|
|
128
|
+
}
|
|
129
|
+
if (rewriting && platforms.length !== 1) {
|
|
130
|
+
throw new SkillError('❌ rewriting a caption (--instruction with --title / --description / --tag) needs exactly one --platform');
|
|
131
|
+
}
|
|
132
|
+
if (!rewriting && !topic && !hook) {
|
|
133
|
+
throw new SkillError('❌ Please provide --topic (what the video is about); --hook is optional extra context');
|
|
134
|
+
}
|
|
135
|
+
const durationSec = toNumber(input.duration, 'duration');
|
|
136
|
+
if (durationSec !== undefined && durationSec < 0) {
|
|
137
|
+
throw new SkillError('❌ --duration must be a non-negative number of seconds');
|
|
138
|
+
}
|
|
139
|
+
const base = {};
|
|
140
|
+
if (renderId)
|
|
141
|
+
base.renderId = renderId;
|
|
142
|
+
const videoProjectId = optString(input.video_project_id);
|
|
143
|
+
if (videoProjectId)
|
|
144
|
+
base.videoProjectId = videoProjectId;
|
|
145
|
+
if (topic)
|
|
146
|
+
base.topic = topic;
|
|
147
|
+
if (hook)
|
|
148
|
+
base.hook = hook;
|
|
149
|
+
const sourceUrl = optString(input.source_url);
|
|
150
|
+
if (sourceUrl)
|
|
151
|
+
base.sourceUrl = sourceUrl;
|
|
152
|
+
if (durationSec !== undefined)
|
|
153
|
+
base.durationSec = durationSec;
|
|
154
|
+
const aspectRatio = optString(input.aspect_ratio);
|
|
155
|
+
if (aspectRatio)
|
|
156
|
+
base.aspectRatio = aspectRatio;
|
|
157
|
+
const language = optString(input.language);
|
|
158
|
+
if (language)
|
|
159
|
+
base.language = language;
|
|
160
|
+
const promptOverride = optString(input.prompt);
|
|
161
|
+
if (promptOverride)
|
|
162
|
+
base.promptOverride = promptOverride;
|
|
163
|
+
if (instruction)
|
|
164
|
+
base.instruction = instruction;
|
|
165
|
+
if (rewriting)
|
|
166
|
+
base.current = current;
|
|
167
|
+
const ctx = await resolveHttpContext(ctxIn.skillName, {
|
|
168
|
+
apiBaseUrl: input.api_base_url,
|
|
169
|
+
privateToken: input.priv_token,
|
|
170
|
+
preflightToken: ctxIn.auth?.token,
|
|
171
|
+
});
|
|
172
|
+
const results = await Promise.all(platforms.map((p) => generateOne(ctx, p, base)));
|
|
173
|
+
const succeeded = results.filter((r) => r.caption);
|
|
174
|
+
if (isTrue(input.json_output)) {
|
|
175
|
+
process.stdout.write(JSON.stringify({
|
|
176
|
+
captions: succeeded.map((r) => ({
|
|
177
|
+
platform: r.platform,
|
|
178
|
+
title: r.caption.title,
|
|
179
|
+
description: r.caption.description,
|
|
180
|
+
tags: r.caption.tags,
|
|
181
|
+
})),
|
|
182
|
+
errors: results.filter((r) => !r.caption).map((r) => ({ platform: r.platform, error: r.error })),
|
|
183
|
+
persisted: !!renderId,
|
|
184
|
+
}) + '\n');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
printHuman(results);
|
|
188
|
+
}
|
|
189
|
+
if (succeeded.length === 0) {
|
|
190
|
+
throw new SkillError(`❌ publish copy generation failed for every platform: ${results.map((r) => `${r.platform}: ${r.error}`).join('; ')}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -9,10 +9,12 @@ import { genVoice } from './gen-voice.js';
|
|
|
9
9
|
import { genVideo } from './gen-video.js';
|
|
10
10
|
import { genDigitalHuman } from './gen-digital-human.js';
|
|
11
11
|
import { templateInit } from './template-init.js';
|
|
12
|
+
import { genPost } from './gen-post.js';
|
|
12
13
|
export const HANDLERS = {
|
|
13
14
|
'gen-image': genImage,
|
|
14
15
|
'gen-voice': genVoice,
|
|
15
16
|
'gen-video': genVideo,
|
|
16
17
|
'gen-digital-human': genDigitalHuman,
|
|
17
18
|
'template-init': templateInit,
|
|
19
|
+
'gen-post': genPost,
|
|
18
20
|
};
|
package/dist/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "0.9.72",
|
|
4
|
+
"generatedAt": "2026-10-08T04:30:08.225Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -411,6 +411,123 @@
|
|
|
411
411
|
]
|
|
412
412
|
}
|
|
413
413
|
},
|
|
414
|
+
{
|
|
415
|
+
"id": "gen-post",
|
|
416
|
+
"toolName": "gen_post",
|
|
417
|
+
"tier": "tool",
|
|
418
|
+
"category": "consuming",
|
|
419
|
+
"title": "Social Post Copy",
|
|
420
|
+
"summary": "Write the text of a social post for a finished video — title, description and hashtags — per platform (text only; it does not upload or publish the video): Douyin (抖音), Xiaohongshu (小红书) and TikTok. Same prompts as the web studio's publish panel, including the platform's length limits and ad-law / community-guideline compliance rules, and the user's saved prompt overrides. Generates all three platforms by default; pass `platform` to pick. Fill the video context yourself from the conversation — topic, hook, source URL, duration, aspect ratio — rather than asking the user; it only writes from what it is given. `instruction` carries the user's own request: alone it shapes a fresh generation, and with the current title/description/tags (from an earlier run in the conversation) it rewrites that caption instead. Each platform is one LLM call and costs credits.",
|
|
421
|
+
"triggers": [
|
|
422
|
+
"Publish copy / caption / title + description + hashtags for a video",
|
|
423
|
+
"发布文案、视频标题、话题标签、抖音文案、小红书笔记文案、TikTok caption",
|
|
424
|
+
"Rewrite or polish an existing video caption for a platform"
|
|
425
|
+
],
|
|
426
|
+
"entry": {
|
|
427
|
+
"type": "http",
|
|
428
|
+
"handler": "gen-post"
|
|
429
|
+
},
|
|
430
|
+
"runtime": "node",
|
|
431
|
+
"envVars": [
|
|
432
|
+
"PRIV_TOKEN",
|
|
433
|
+
"MM_API_BASE_URL",
|
|
434
|
+
"AGENT_NAME"
|
|
435
|
+
],
|
|
436
|
+
"parameters": {
|
|
437
|
+
"type": "object",
|
|
438
|
+
"properties": {
|
|
439
|
+
"topic": {
|
|
440
|
+
"type": "string",
|
|
441
|
+
"description": "What the video is about — the main input for the copy. Take it from the conversation (video title, script, source page). Required unless rewriting an existing caption."
|
|
442
|
+
},
|
|
443
|
+
"platform": {
|
|
444
|
+
"type": "array",
|
|
445
|
+
"items": {
|
|
446
|
+
"type": "string"
|
|
447
|
+
},
|
|
448
|
+
"description": "Target platforms: douyin / xiaohongshu / tiktok, or all (default: all three). Repeatable or comma-separated."
|
|
449
|
+
},
|
|
450
|
+
"hook": {
|
|
451
|
+
"type": "string",
|
|
452
|
+
"description": "The video's opening hook line, if known"
|
|
453
|
+
},
|
|
454
|
+
"source_url": {
|
|
455
|
+
"type": "string",
|
|
456
|
+
"description": "Project / source URL the video is about, if any (used as context; never written into the copy as a link)"
|
|
457
|
+
},
|
|
458
|
+
"duration": {
|
|
459
|
+
"type": "number",
|
|
460
|
+
"description": "Video duration in seconds"
|
|
461
|
+
},
|
|
462
|
+
"aspect_ratio": {
|
|
463
|
+
"type": "string",
|
|
464
|
+
"description": "Video aspect ratio, e.g. 9:16 (default 9:16)"
|
|
465
|
+
},
|
|
466
|
+
"language": {
|
|
467
|
+
"type": "string",
|
|
468
|
+
"description": "Output language. Default: English for tiktok, 简体中文 for the others."
|
|
469
|
+
},
|
|
470
|
+
"instruction": {
|
|
471
|
+
"type": "string",
|
|
472
|
+
"description": "The user's request for how the copy should read, e.g. 'shorter title, drop the emoji'. Alone: applied as an extra requirement to a fresh generation. With the current title / description / tag: rewrites that caption (exactly one platform). Max 500 characters."
|
|
473
|
+
},
|
|
474
|
+
"title": {
|
|
475
|
+
"type": "string",
|
|
476
|
+
"description": "Current title to rewrite (only with `instruction`)"
|
|
477
|
+
},
|
|
478
|
+
"description": {
|
|
479
|
+
"type": "string",
|
|
480
|
+
"description": "Current description to rewrite (only with `instruction`)"
|
|
481
|
+
},
|
|
482
|
+
"tag": {
|
|
483
|
+
"type": "array",
|
|
484
|
+
"items": {
|
|
485
|
+
"type": "string"
|
|
486
|
+
},
|
|
487
|
+
"description": "Current tags to rewrite (only with `instruction`). Repeatable or comma-separated; a leading # is stripped."
|
|
488
|
+
},
|
|
489
|
+
"prompt": {
|
|
490
|
+
"type": "string",
|
|
491
|
+
"description": "Replace the platform prompt for this run only. Placeholders: {{topic}} {{hook}} {{sourceUrl}} {{durationSec}} {{aspectRatio}} {{language}}. The output must still be a JSON object with title / description / tags."
|
|
492
|
+
},
|
|
493
|
+
"render_id": {
|
|
494
|
+
"type": "string",
|
|
495
|
+
"description": "Studio render (take) id. When given, the result is saved to that take and shows up in the web publish panel; otherwise nothing is saved."
|
|
496
|
+
},
|
|
497
|
+
"video_project_id": {
|
|
498
|
+
"type": "string",
|
|
499
|
+
"description": "Studio video project id of the take (only with `render_id`)"
|
|
500
|
+
},
|
|
501
|
+
"json_output": {
|
|
502
|
+
"type": "boolean",
|
|
503
|
+
"description": "Emit a JSON result ({ captions: [{ platform, title, description, tags }], errors, persisted }) instead of human-readable output"
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
"required": []
|
|
507
|
+
},
|
|
508
|
+
"ui": {
|
|
509
|
+
"primary": [
|
|
510
|
+
"platform",
|
|
511
|
+
"instruction"
|
|
512
|
+
],
|
|
513
|
+
"advanced": [],
|
|
514
|
+
"hidden": [
|
|
515
|
+
"topic",
|
|
516
|
+
"hook",
|
|
517
|
+
"source_url",
|
|
518
|
+
"duration",
|
|
519
|
+
"aspect_ratio",
|
|
520
|
+
"language",
|
|
521
|
+
"title",
|
|
522
|
+
"description",
|
|
523
|
+
"tag",
|
|
524
|
+
"prompt",
|
|
525
|
+
"render_id",
|
|
526
|
+
"video_project_id",
|
|
527
|
+
"json_output"
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
},
|
|
414
531
|
{
|
|
415
532
|
"id": "gen-script",
|
|
416
533
|
"toolName": "gen_script",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.72",
|
|
4
4
|
"description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gen-post
|
|
3
|
+
description: |
|
|
4
|
+
Social-post skill: write the post text — title, description and hashtags — for a finished video, per platform: Douyin (抖音), Xiaohongshu (小红书) and TikTok. Text only; it does not upload or publish the video. Backed by ab-api's `/publish/caption/generate`, the same prompts the web studio's publish panel uses.
|
|
5
|
+
|
|
6
|
+
Use this skill immediately whenever the user asks for any of:
|
|
7
|
+
- Publish copy / caption / title + description + hashtags for a video
|
|
8
|
+
- 发布文案、视频标题、话题标签、抖音文案、小红书笔记文案、TikTok caption
|
|
9
|
+
- Rewrite or polish an existing video caption for a platform
|
|
10
|
+
triggers:
|
|
11
|
+
- Publish copy / caption / title + description + hashtags for a video
|
|
12
|
+
- 发布文案、视频标题、话题标签、抖音文案、小红书笔记文案、TikTok caption
|
|
13
|
+
- Rewrite or polish an existing video caption for a platform
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Social Post Skill
|
|
17
|
+
|
|
18
|
+
Wraps ab-api's `POST /publish/caption/generate`. Platform prompts (length limits, tone, and
|
|
19
|
+
the ad-law / community-guideline compliance rules) live in the backend, and a user's saved
|
|
20
|
+
prompt overrides from the web panel apply here too.
|
|
21
|
+
|
|
22
|
+
| `--platform` | Default language | Shape |
|
|
23
|
+
|--------------|------------------|-------|
|
|
24
|
+
| `douyin` (`dy`) | 简体中文 | short hook title, 60-150 字 description, 3-5 tags |
|
|
25
|
+
| `xiaohongshu` (`xhs`) | 简体中文 | note-style title ≤ 20 字, 200-400 字 body, 5-8 tags |
|
|
26
|
+
| `tiktok` (`tt`) | English | first-line title, short caption, 3-5 hashtags |
|
|
27
|
+
|
|
28
|
+
Default is all three. Each platform is one LLM call, run concurrently, charged separately.
|
|
29
|
+
|
|
30
|
+
## Where the inputs come from
|
|
31
|
+
|
|
32
|
+
**You fill the video context; the user only says how they want it to read.** Take
|
|
33
|
+
`--topic` / `--hook` / `--source-url` / `--duration` / `--aspect-ratio` from the conversation —
|
|
34
|
+
the video's title, the script, the page it was made from, the render result. Don't ask the
|
|
35
|
+
user for them. The copy is written only from what you pass — anything missing is treated as
|
|
36
|
+
"not provided", never invented — so pass everything you know.
|
|
37
|
+
|
|
38
|
+
The user's own words ("标题去掉 emoji", "语气轻松一点", "只要小红书") go to `--instruction`
|
|
39
|
+
(and `--platform` when they name platforms). If they gave no request, just generate.
|
|
40
|
+
|
|
41
|
+
## Generate
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
remixmate gen-post \
|
|
45
|
+
--topic "<what the video is about>" \
|
|
46
|
+
--hook "<opening line>" \
|
|
47
|
+
--duration 45 --aspect-ratio 9:16
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# One platform, machine-readable
|
|
52
|
+
remixmate gen-post --topic "<topic>" --platform xhs --json-output
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# The user's request shapes a fresh generation on every platform
|
|
57
|
+
remixmate gen-post --topic "<topic>" --instruction "语气轻松一点,标题不要 emoji"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Rewrite
|
|
61
|
+
|
|
62
|
+
When a caption was already generated earlier in the conversation and the user asks to change
|
|
63
|
+
it, pass that caption back as the draft: `--instruction` plus `--title` / `--description` /
|
|
64
|
+
`--tag` rewrites it instead of starting over. One platform per call. Fields the instruction
|
|
65
|
+
doesn't mention are kept. Compliance rules still win either way: asking for superlatives,
|
|
66
|
+
guarantees or off-platform links gets a compliant rewording, not a literal one.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
remixmate gen-post --platform douyin \
|
|
70
|
+
--instruction "标题去掉 emoji,再短一点" \
|
|
71
|
+
--title "<current title>" --description "<current description>" \
|
|
72
|
+
--tag tagA --tag tagB
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Flags
|
|
76
|
+
|
|
77
|
+
| Flag | Description | Default |
|
|
78
|
+
|------|-------------|---------|
|
|
79
|
+
| `--topic` | What the video is about (required unless rewriting) | — |
|
|
80
|
+
| `--platform` | `douyin` / `xiaohongshu` / `tiktok` / `all`; repeatable or comma-separated | `all` |
|
|
81
|
+
| `--hook` | Opening hook line | none |
|
|
82
|
+
| `--source-url` | Project/source URL, context only | none |
|
|
83
|
+
| `--duration` | Seconds | none |
|
|
84
|
+
| `--aspect-ratio` | e.g. `9:16` | `9:16` |
|
|
85
|
+
| `--language` | Output language | per platform (see table) |
|
|
86
|
+
| `--instruction` | The user's request, ≤ 500 chars; rewrites when a draft is given | none |
|
|
87
|
+
| `--title` / `--description` / `--tag` | Current caption to rewrite | none |
|
|
88
|
+
| `--prompt` | Replace the platform prompt for this run; placeholders `{{topic}}` `{{hook}}` `{{sourceUrl}}` `{{durationSec}}` `{{aspectRatio}}` `{{language}}` | saved/default prompt |
|
|
89
|
+
| `--render-id` / `--video-project-id` | Save the result to a studio take (shows in the web publish panel) | not saved |
|
|
90
|
+
| `--json-output` | `{ captions: [{ platform, title, description, tags }], errors, persisted }` | off |
|
|
91
|
+
| `--api-base-url` / `--priv-token` | Override API root / token | env |
|
|
92
|
+
|
|
93
|
+
Tags come back without `#`; the human-readable output adds it.
|
|
94
|
+
|
|
95
|
+
## Output
|
|
96
|
+
|
|
97
|
+
Show the user each platform's title, description and tags as-is so they can copy them. If
|
|
98
|
+
some platforms failed, the rest still print; the run exits non-zero only when every platform
|
|
99
|
+
failed.
|
|
100
|
+
|
|
101
|
+
## Credits
|
|
102
|
+
|
|
103
|
+
Each platform charges credits. Relay the footer when it appears:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
💳 Charged <credits> credits · balance <balance>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Error handling
|
|
110
|
+
|
|
111
|
+
- **Token missing / 401**: set `PRIV_TOKEN` or run `remixmate login`.
|
|
112
|
+
- **`不支持的平台`**: the platform code is not one the backend knows.
|
|
113
|
+
- **`模型响应超时` / timeout**: retry; generating fewer platforms at once also helps.
|
|
114
|
+
- **Rewrite errors**: a draft (`--title` / `--description` / `--tag`) needs `--instruction` and exactly one `--platform`.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gen-post",
|
|
3
|
+
"toolName": "gen_post",
|
|
4
|
+
"tier": "tool",
|
|
5
|
+
"category": "consuming",
|
|
6
|
+
"title": "Social Post Copy",
|
|
7
|
+
"description": "Write the text of a social post for a finished video — title, description and hashtags — per platform (text only; it does not upload or publish the video): Douyin (抖音), Xiaohongshu (小红书) and TikTok. Same prompts as the web studio's publish panel, including the platform's length limits and ad-law / community-guideline compliance rules, and the user's saved prompt overrides. Generates all three platforms by default; pass `platform` to pick. Fill the video context yourself from the conversation — topic, hook, source URL, duration, aspect ratio — rather than asking the user; it only writes from what it is given. `instruction` carries the user's own request: alone it shapes a fresh generation, and with the current title/description/tags (from an earlier run in the conversation) it rewrites that caption instead. Each platform is one LLM call and costs credits.",
|
|
8
|
+
"auth": "required",
|
|
9
|
+
"envVars": [
|
|
10
|
+
"PRIV_TOKEN",
|
|
11
|
+
"MM_API_BASE_URL",
|
|
12
|
+
"AGENT_NAME"
|
|
13
|
+
],
|
|
14
|
+
"entry": {
|
|
15
|
+
"type": "http",
|
|
16
|
+
"handler": "gen-post"
|
|
17
|
+
},
|
|
18
|
+
"parameters": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"topic": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "What the video is about — the main input for the copy. Take it from the conversation (video title, script, source page). Required unless rewriting an existing caption."
|
|
24
|
+
},
|
|
25
|
+
"platform": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"description": "Target platforms: douyin / xiaohongshu / tiktok, or all (default: all three). Repeatable or comma-separated."
|
|
31
|
+
},
|
|
32
|
+
"hook": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "The video's opening hook line, if known"
|
|
35
|
+
},
|
|
36
|
+
"source_url": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Project / source URL the video is about, if any (used as context; never written into the copy as a link)"
|
|
39
|
+
},
|
|
40
|
+
"duration": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"description": "Video duration in seconds"
|
|
43
|
+
},
|
|
44
|
+
"aspect_ratio": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Video aspect ratio, e.g. 9:16 (default 9:16)"
|
|
47
|
+
},
|
|
48
|
+
"language": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Output language. Default: English for tiktok, 简体中文 for the others."
|
|
51
|
+
},
|
|
52
|
+
"instruction": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "The user's request for how the copy should read, e.g. 'shorter title, drop the emoji'. Alone: applied as an extra requirement to a fresh generation. With the current title / description / tag: rewrites that caption (exactly one platform). Max 500 characters."
|
|
55
|
+
},
|
|
56
|
+
"title": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Current title to rewrite (only with `instruction`)"
|
|
59
|
+
},
|
|
60
|
+
"description": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Current description to rewrite (only with `instruction`)"
|
|
63
|
+
},
|
|
64
|
+
"tag": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
"description": "Current tags to rewrite (only with `instruction`). Repeatable or comma-separated; a leading # is stripped."
|
|
70
|
+
},
|
|
71
|
+
"prompt": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Replace the platform prompt for this run only. Placeholders: {{topic}} {{hook}} {{sourceUrl}} {{durationSec}} {{aspectRatio}} {{language}}. The output must still be a JSON object with title / description / tags."
|
|
74
|
+
},
|
|
75
|
+
"render_id": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Studio render (take) id. When given, the result is saved to that take and shows up in the web publish panel; otherwise nothing is saved."
|
|
78
|
+
},
|
|
79
|
+
"video_project_id": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "Studio video project id of the take (only with `render_id`)"
|
|
82
|
+
},
|
|
83
|
+
"json_output": {
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"description": "Emit a JSON result ({ captions: [{ platform, title, description, tags }], errors, persisted }) instead of human-readable output"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": []
|
|
89
|
+
},
|
|
90
|
+
"ui": {
|
|
91
|
+
"primary": [
|
|
92
|
+
"platform",
|
|
93
|
+
"instruction"
|
|
94
|
+
],
|
|
95
|
+
"advanced": [],
|
|
96
|
+
"hidden": [
|
|
97
|
+
"topic",
|
|
98
|
+
"hook",
|
|
99
|
+
"source_url",
|
|
100
|
+
"duration",
|
|
101
|
+
"aspect_ratio",
|
|
102
|
+
"language",
|
|
103
|
+
"title",
|
|
104
|
+
"description",
|
|
105
|
+
"tag",
|
|
106
|
+
"prompt",
|
|
107
|
+
"render_id",
|
|
108
|
+
"video_project_id",
|
|
109
|
+
"json_output"
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skillName": "gen-post",
|
|
3
|
+
"repoName": "agent-skill-media-maker",
|
|
4
|
+
"skillId": "pending-gen-post",
|
|
5
|
+
"version": "V1",
|
|
6
|
+
"skillDescription": "Social-post skill: write the post text — title, description and hashtags — for a finished video, per platform: Douyin (抖音), Xiaohongshu (小红书) and TikTok. Text only; it does not upload or publish the video. Backed by ab-api's `/publish/caption/generate` (the same prompts as the web studio's publish panel).\n\nUse this skill immediately whenever the user asks for any of:\n- Publish copy / caption / title + description + hashtags for a video\n- 发布文案、视频标题、话题标签、抖音文案、小红书笔记文案、TikTok caption\n- Rewrite or polish an existing video caption for a platform"
|
|
7
|
+
}
|