@remixmate/cli 0.9.71 → 0.9.73
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/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -188
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +53 -13
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-post/SKILL.md +23 -13
- package/skills/gen-post/skill.json +9 -10
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -21
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
|
@@ -1,264 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* still
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const payload = { current: 1, pageSize: 100, source: ACTIVE_SOURCE };
|
|
35
|
-
if (input.gender)
|
|
36
|
-
payload.gender = String(input.gender);
|
|
37
|
-
if (isTrue(input.mine))
|
|
38
|
-
payload.mine = true;
|
|
39
|
-
if (input.name)
|
|
40
|
-
payload.name = String(input.name);
|
|
41
|
-
const data = await mmPost(ctx, '/digital-human/avatar/page', payload, { timeoutMs: 30_000 });
|
|
42
|
-
const avatars = data.list ?? [];
|
|
43
|
-
if (isTrue(input.json_output)) {
|
|
44
|
-
process.stdout.write(JSON.stringify({ avatars }) + '\n');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (avatars.length === 0) {
|
|
48
|
-
process.stdout.write('⚠️ No avatars available\n');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
process.stdout.write(`\n${'ID'.padEnd(24)} ${'Name'.padEnd(16)} ${'Source'.padEnd(14)} ${'Gender'.padEnd(8)} ${'Ratio'.padEnd(6)} Clone\n`);
|
|
52
|
-
process.stdout.write(`${'-'.repeat(24)} ${'-'.repeat(16)} ${'-'.repeat(14)} ${'-'.repeat(8)} ${'-'.repeat(6)} ${'-'.repeat(8)}\n`);
|
|
53
|
-
for (const a of avatars) {
|
|
54
|
-
process.stdout.write(`${toId(a.id).padEnd(24)} ${String(a.name ?? '').padEnd(16)} ${String(a.source ?? '').padEnd(14)} ${String(a.gender ?? '').padEnd(8)} ${String(a.aspectRatio ?? '').padEnd(6)} ${a.cloneStatus || '-'}\n`);
|
|
55
|
-
}
|
|
56
|
-
const total = data.pager?.total ?? avatars.length;
|
|
57
|
-
process.stdout.write(`\n${avatars.length} avatar(s) shown${total > avatars.length ? ` of ${total}; narrow with --name` : ''}\n`);
|
|
58
|
-
process.stdout.write('💡 Use --avatar-id <ID> to pick an avatar for generation\n');
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Look up one avatar's metadata. Only the caller's own avatars are searched: they
|
|
62
|
-
* are few, and they are the only ones that can be mid-clone or failed. The public
|
|
63
|
-
* HiFly catalog has ~1000 entries, far beyond one page, so a public id that is not
|
|
64
|
-
* among "mine" is passed through as-is and the backend validates it (unknown ids
|
|
65
|
-
* come back as a business error).
|
|
66
|
-
*/
|
|
67
|
-
async function fetchAvatarInfo(ctx, avatarId) {
|
|
68
|
-
const data = await mmPost(ctx, '/digital-human/avatar/page', { current: 1, pageSize: 100, mine: true, source: ACTIVE_SOURCE }, { timeoutMs: 30_000 });
|
|
69
|
-
const found = (data.list ?? []).find((a) => toId(a.id) === avatarId);
|
|
70
|
-
return found ?? { id: avatarId, source: ACTIVE_SOURCE };
|
|
71
|
-
}
|
|
72
|
-
function resolveSource(avatar, validSources) {
|
|
73
|
-
const src = (avatar.source ?? '').trim() || ACTIVE_SOURCE;
|
|
74
|
-
if (!validSources.includes(src)) {
|
|
75
|
-
throw new SkillError(`❌ avatar uses provider "${src}", which is not available (supported: ${validSources.join('/')}). Pick another avatar from --list-avatars.`);
|
|
76
|
-
}
|
|
77
|
-
return src;
|
|
78
|
-
}
|
|
79
|
-
// ── Avatar cloning (HiFly) ─────────────────────────────────────────────────
|
|
80
|
-
async function cloneAvatar(ctx, input) {
|
|
81
|
-
const fileUrl = toId(input.clone_url);
|
|
82
|
-
if (!/^https?:\/\//i.test(fileUrl)) {
|
|
83
|
-
throw new SkillError('❌ --clone needs --clone-url <public http(s) URL of a video or portrait image> (local files are not uploaded)');
|
|
84
|
-
}
|
|
85
|
-
const name = toId(input.name);
|
|
86
|
-
if (!name)
|
|
87
|
-
throw new SkillError('❌ --clone needs --name <avatar name>');
|
|
88
|
-
const sourceType = toId(input.clone_type) || 'video';
|
|
89
|
-
if (!CLONE_TYPES.includes(sourceType)) {
|
|
90
|
-
throw new SkillError(`❌ --clone-type must be one of ${CLONE_TYPES.join(' / ')}, received: ${sourceType}`);
|
|
91
|
-
}
|
|
92
|
-
const payload = { name, sourceType, fileUrl };
|
|
93
|
-
if (input.thumbnail_url)
|
|
94
|
-
payload.thumbnailUrl = toId(input.thumbnail_url);
|
|
95
|
-
if (input.gender)
|
|
96
|
-
payload.gender = toId(input.gender);
|
|
97
|
-
emitProgress({ phase: 'gen-digital-human:clone-request', sourceType });
|
|
98
|
-
const data = await mmPost(ctx, '/digital-human/avatar/clone', payload, { timeoutMs: 60_000 });
|
|
99
|
-
const avatarId = toId(data.id);
|
|
100
|
-
if (!avatarId) {
|
|
101
|
-
throw new SkillError(`❌ could not parse the new avatar id; API returned: ${JSON.stringify(data)}`);
|
|
102
|
-
}
|
|
103
|
-
emitProgress({ phase: 'gen-digital-human:clone-async', avatarId });
|
|
104
|
-
const json = isTrue(input.json_output);
|
|
105
|
-
if (input.wait !== undefined && !isTrue(input.wait)) {
|
|
106
|
-
if (json) {
|
|
107
|
-
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: data.cloneStatus ?? 'cloning' }) + '\n');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
process.stdout.write(`\n🧬 Clone submitted, avatar id: ${avatarId}\n`);
|
|
111
|
-
process.stdout.write(`💡 Check progress with --clone-status --avatar-id ${avatarId}\n`);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
let final;
|
|
115
|
-
try {
|
|
116
|
-
final = await pollUntil(async () => {
|
|
117
|
-
const s = await mmPost(ctx, '/digital-human/avatar/clone-status', { id: avatarId }, { timeoutMs: 30_000 });
|
|
118
|
-
const status = s.cloneStatus ?? 'unknown';
|
|
119
|
-
emitProgress({ phase: 'gen-digital-human:clone-poll', avatarId, status });
|
|
120
|
-
if (status === 'done')
|
|
121
|
-
return s;
|
|
122
|
-
if (status === 'failed')
|
|
123
|
-
throw new SkillError(`❌ avatar clone failed: ${s.cloneError || 'unknown error'}`);
|
|
124
|
-
return null;
|
|
125
|
-
}, { intervalMs: 10_000, timeoutMs: 20 * 60_000 });
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
if (err instanceof SkillError && /polling timed out/.test(err.message)) {
|
|
129
|
-
throw new SkillError(`❌ avatar ${avatarId} is still cloning; rerun --clone-status --avatar-id ${avatarId} later`);
|
|
130
|
-
}
|
|
131
|
-
throw err;
|
|
132
|
-
}
|
|
133
|
-
if (json) {
|
|
134
|
-
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: final.cloneStatus }) + '\n');
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
process.stdout.write(`\n✅ Avatar cloned, id: ${avatarId}\n`);
|
|
138
|
-
process.stdout.write(`💡 Generate with --avatar-id ${avatarId} --text "..."\n`);
|
|
139
|
-
}
|
|
140
|
-
async function checkCloneStatus(ctx, avatarId, json) {
|
|
141
|
-
const s = await mmPost(ctx, '/digital-human/avatar/clone-status', { id: avatarId }, { timeoutMs: 30_000 });
|
|
142
|
-
const status = s.cloneStatus || 'unknown';
|
|
143
|
-
if (json) {
|
|
144
|
-
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: status, error: s.cloneError || null }) + '\n');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
process.stdout.write(`\nclone status: ${status}\n`);
|
|
148
|
-
if (status === 'failed')
|
|
149
|
-
process.stdout.write(` failure reason: ${s.cloneError || 'unknown error'}\n`);
|
|
150
|
-
}
|
|
151
|
-
// ── Video generation ───────────────────────────────────────────────────────
|
|
152
|
-
async function checkStatus(ctx, generationId, json) {
|
|
153
|
-
const data = await mmPost(ctx, '/digital-human/video/status', { generationId }, { timeoutMs: 30_000 });
|
|
154
|
-
const status = data.status ?? 'unknown';
|
|
155
|
-
if (json) {
|
|
156
|
-
process.stdout.write(JSON.stringify({ status, url: data.videoUrl ?? null, generationId }) + '\n');
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
process.stdout.write(`\ntask status: ${status}\n`);
|
|
160
|
-
if (data.videoUrl)
|
|
161
|
-
process.stdout.write(` video URL: ${data.videoUrl}\n`);
|
|
162
|
-
if (status === 'failed') {
|
|
163
|
-
const err = data.errorMessage ?? data.error ?? data.message ?? 'unknown error';
|
|
164
|
-
process.stdout.write(` failure reason: ${err}\n`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function pollVideoStatus(ctx, generationId) {
|
|
168
|
-
const url = await pollUntil(async () => {
|
|
169
|
-
const data = await mmPost(ctx, '/digital-human/video/status', { generationId }, { timeoutMs: 30_000 });
|
|
170
|
-
const status = data.status ?? 'unknown';
|
|
171
|
-
emitProgress({ phase: 'gen-digital-human:poll', generationId, status });
|
|
172
|
-
if (status === 'completed')
|
|
173
|
-
return data.videoUrl ?? '';
|
|
174
|
-
if (status === 'failed') {
|
|
175
|
-
const err = data.errorMessage ?? data.error ?? data.message ?? 'unknown error';
|
|
176
|
-
throw new SkillError(`❌ video generation failed: ${err}`);
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}, { intervalMs: 5_000, timeoutMs: 600_000 });
|
|
180
|
-
return url;
|
|
181
|
-
}
|
|
182
|
-
export async function genDigitalHuman(input, ctxIn) {
|
|
183
|
-
const ctx = await resolveHttpContext(ctxIn.skillName, {
|
|
184
|
-
apiBaseUrl: input.api_base_url,
|
|
185
|
-
privateToken: input.priv_token,
|
|
186
|
-
preflightToken: ctxIn.auth?.token,
|
|
187
|
-
});
|
|
188
|
-
const json = isTrue(input.json_output);
|
|
189
|
-
// ── Mode 1: list avatars ────────────────────────────────────────────────
|
|
190
|
-
if (isTrue(input.list_avatars)) {
|
|
191
|
-
return listAvatars(ctx, input);
|
|
192
|
-
}
|
|
193
|
-
// ── Mode 2: clone a custom avatar (HiFly) ───────────────────────────────
|
|
194
|
-
if (isTrue(input.clone)) {
|
|
195
|
-
return cloneAvatar(ctx, input);
|
|
196
|
-
}
|
|
197
|
-
// ── Mode 3: clone status ────────────────────────────────────────────────
|
|
198
|
-
if (isTrue(input.clone_status)) {
|
|
199
|
-
const avatarId = toId(input.avatar_id);
|
|
200
|
-
if (!avatarId)
|
|
201
|
-
throw new SkillError('❌ clone-status mode requires --avatar-id');
|
|
202
|
-
return checkCloneStatus(ctx, avatarId, json);
|
|
203
|
-
}
|
|
204
|
-
// ── Mode 4: check generation status ─────────────────────────────────────
|
|
205
|
-
if (isTrue(input.check_status)) {
|
|
206
|
-
const generationId = toNumber(input.generation_id, 'generation_id');
|
|
207
|
-
if (generationId === undefined) {
|
|
208
|
-
throw new SkillError('❌ check-status mode requires --generation-id');
|
|
209
|
-
}
|
|
210
|
-
return checkStatus(ctx, generationId, json);
|
|
211
|
-
}
|
|
212
|
-
// ── Mode 5: generate (default) ──────────────────────────────────────────
|
|
213
|
-
const avatarId = toId(input.avatar_id);
|
|
214
|
-
if (!avatarId) {
|
|
215
|
-
throw new SkillError('❌ provide --avatar-id to pick the avatar (use --list-avatars to see available avatars)');
|
|
216
|
-
}
|
|
217
|
-
const audioUrl = (input.audio_url ?? '').trim();
|
|
218
|
-
const text = input.text ?? '';
|
|
219
|
-
if (audioUrl && !/^https?:\/\//i.test(audioUrl)) {
|
|
220
|
-
throw new SkillError(`❌ --audio-url needs a public http(s) URL (local files are not uploaded), received: ${audioUrl}`);
|
|
221
|
-
}
|
|
222
|
-
if (!audioUrl) {
|
|
223
|
-
if (!text.trim()) {
|
|
224
|
-
throw new SkillError('❌ in TTS mode provide --text, or use --audio-url for audio-driven mode');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
const dh = (await getCapabilities(input.api_base_url)).digitalHuman;
|
|
228
|
-
emitProgress({ phase: 'gen-digital-human:resolve-avatar', avatarId });
|
|
229
|
-
const avatar = await fetchAvatarInfo(ctx, avatarId);
|
|
230
|
-
if (avatar.cloneStatus === 'cloning') {
|
|
231
|
-
throw new SkillError(`❌ avatar ${avatarId} is still cloning; check with --clone-status --avatar-id ${avatarId}`);
|
|
232
|
-
}
|
|
233
|
-
if (avatar.cloneStatus === 'failed') {
|
|
234
|
-
throw new SkillError(`❌ avatar ${avatarId} failed to clone (${avatar.cloneError || 'unknown error'}); clone it again`);
|
|
235
|
-
}
|
|
236
|
-
const source = resolveSource(avatar, dh.sources);
|
|
237
|
-
const payload = { avatarId, source };
|
|
238
|
-
if (audioUrl) {
|
|
239
|
-
payload.audioUrl = audioUrl;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
payload.text = text;
|
|
243
|
-
payload.voiceId = input.voice_id ?? dh.defaultVoiceId;
|
|
244
|
-
if (input.voice_name)
|
|
245
|
-
payload.voiceName = String(input.voice_name);
|
|
246
|
-
}
|
|
247
|
-
emitProgress({ phase: 'gen-digital-human:request', avatarId, source, mode: audioUrl ? 'audio' : 'tts' });
|
|
248
|
-
const data = await mmPost(ctx, '/digital-human/video/generate', payload, { timeoutMs: 60_000 });
|
|
249
|
-
const generationId = data.generationId;
|
|
250
|
-
if (!generationId) {
|
|
251
|
-
throw new SkillError(`❌ could not parse generationId; API returned: ${JSON.stringify(data)}`);
|
|
252
|
-
}
|
|
253
|
-
emitProgress({ phase: 'gen-digital-human:async', generationId });
|
|
254
|
-
const videoUrl = await pollVideoStatus(ctx, generationId);
|
|
255
|
-
if (!videoUrl) {
|
|
256
|
-
throw new SkillError(`❌ video generated but no URL was returned; rerun --check-status --generation-id ${generationId} later`);
|
|
257
|
-
}
|
|
258
|
-
if (json) {
|
|
259
|
-
process.stdout.write(JSON.stringify({ url: videoUrl, generationId }) + '\n');
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
process.stdout.write(`\n🔗 Video URL (renders directly, no download needed):\n${videoUrl}\n`);
|
|
263
|
-
process.stdout.write('\n🎉 Digital-human video generation done!\n');
|
|
264
|
-
}
|
|
1
|
+
var E=Object.defineProperty;var l=(t,e)=>E(t,"name",{value:e,configurable:!0});import{mmPost as w,pollUntil as y,resolveHttpContext as U,SkillError as s}from"../http.js";import{getCapabilities as b}from"../capabilities.js";import{emitProgress as m}from"../progress.js";import{isTrue as f,toNumber as M}from"./shared.js";const k=["video","image"],_="hifly";function u(t){return t==null?"":String(t).trim()}l(u,"toId");async function N(t,e){const r={current:1,pageSize:100,source:_};e.gender&&(r.gender=String(e.gender)),f(e.mine)&&(r.mine=!0),e.name&&(r.name=String(e.name));const n=await w(t,"/digital-human/avatar/page",r,{timeoutMs:3e4}),a=n.list??[];if(f(e.json_output)){process.stdout.write(JSON.stringify({avatars:a})+`
|
|
2
|
+
`);return}if(a.length===0){process.stdout.write(`⚠️ No avatars available
|
|
3
|
+
`);return}process.stdout.write(`
|
|
4
|
+
${"ID".padEnd(24)} ${"Name".padEnd(16)} ${"Source".padEnd(14)} ${"Gender".padEnd(8)} ${"Ratio".padEnd(6)} Clone
|
|
5
|
+
`),process.stdout.write(`${"-".repeat(24)} ${"-".repeat(16)} ${"-".repeat(14)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(8)}
|
|
6
|
+
`);for(const d of a)process.stdout.write(`${u(d.id).padEnd(24)} ${String(d.name??"").padEnd(16)} ${String(d.source??"").padEnd(14)} ${String(d.gender??"").padEnd(8)} ${String(d.aspectRatio??"").padEnd(6)} ${d.cloneStatus||"-"}
|
|
7
|
+
`);const o=n.pager?.total??a.length;process.stdout.write(`
|
|
8
|
+
${a.length} avatar(s) shown${o>a.length?` of ${o}; narrow with --name`:""}
|
|
9
|
+
`),process.stdout.write(`💡 Use --avatar-id <ID> to pick an avatar for generation
|
|
10
|
+
`)}l(N,"listAvatars");async function I(t,e){return((await w(t,"/digital-human/avatar/page",{current:1,pageSize:100,mine:!0,source:_},{timeoutMs:3e4})).list??[]).find(a=>u(a.id)===e)??{id:e,source:_}}l(I,"fetchAvatarInfo");function O(t,e){const r=(t.source??"").trim()||_;if(!e.includes(r))throw new s(`❌ avatar uses provider "${r}", which is not available (supported: ${e.join("/")}). Pick another avatar from --list-avatars.`);return r}l(O,"resolveSource");async function C(t,e){const r=u(e.clone_url);if(!/^https?:\/\//i.test(r))throw new s("❌ --clone needs --clone-url <public http(s) URL of a video or portrait image> (local files are not uploaded)");const n=u(e.name);if(!n)throw new s("❌ --clone needs --name <avatar name>");const a=u(e.clone_type)||"video";if(!k.includes(a))throw new s(`❌ --clone-type must be one of ${k.join(" / ")}, received: ${a}`);const o={name:n,sourceType:a,fileUrl:r};e.thumbnail_url&&(o.thumbnailUrl=u(e.thumbnail_url)),e.gender&&(o.gender=u(e.gender)),m({phase:"gen-digital-human:clone-request",sourceType:a});const d=await w(t,"/digital-human/avatar/clone",o,{timeoutMs:6e4}),i=u(d.id);if(!i)throw new s(`❌ could not parse the new avatar id; API returned: ${JSON.stringify(d)}`);m({phase:"gen-digital-human:clone-async",avatarId:i});const g=f(e.json_output);if(e.wait!==void 0&&!f(e.wait)){if(g){process.stdout.write(JSON.stringify({avatarId:i,cloneStatus:d.cloneStatus??"cloning"})+`
|
|
11
|
+
`);return}process.stdout.write(`
|
|
12
|
+
🧬 Clone submitted, avatar id: ${i}
|
|
13
|
+
`),process.stdout.write(`💡 Check progress with --clone-status --avatar-id ${i}
|
|
14
|
+
`);return}let v;try{v=await y(async()=>{const c=await w(t,"/digital-human/avatar/clone-status",{id:i},{timeoutMs:3e4}),h=c.cloneStatus??"unknown";if(m({phase:"gen-digital-human:clone-poll",avatarId:i,status:h}),h==="done")return c;if(h==="failed")throw new s(`❌ avatar clone failed: ${c.cloneError||"unknown error"}`);return null},{intervalMs:1e4,timeoutMs:20*6e4})}catch(c){throw c instanceof s&&/polling timed out/.test(c.message)?new s(`❌ avatar ${i} is still cloning; rerun --clone-status --avatar-id ${i} later`):c}if(g){process.stdout.write(JSON.stringify({avatarId:i,cloneStatus:v.cloneStatus})+`
|
|
15
|
+
`);return}process.stdout.write(`
|
|
16
|
+
✅ Avatar cloned, id: ${i}
|
|
17
|
+
`),process.stdout.write(`💡 Generate with --avatar-id ${i} --text "..."
|
|
18
|
+
`)}l(C,"cloneAvatar");async function J(t,e,r){const n=await w(t,"/digital-human/avatar/clone-status",{id:e},{timeoutMs:3e4}),a=n.cloneStatus||"unknown";if(r){process.stdout.write(JSON.stringify({avatarId:e,cloneStatus:a,error:n.cloneError||null})+`
|
|
19
|
+
`);return}process.stdout.write(`
|
|
20
|
+
clone status: ${a}
|
|
21
|
+
`),a==="failed"&&process.stdout.write(` failure reason: ${n.cloneError||"unknown error"}
|
|
22
|
+
`)}l(J,"checkCloneStatus");async function R(t,e,r){const n=await w(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),a=n.status??"unknown";if(r){process.stdout.write(JSON.stringify({status:a,url:n.videoUrl??null,generationId:e})+`
|
|
23
|
+
`);return}if(process.stdout.write(`
|
|
24
|
+
task status: ${a}
|
|
25
|
+
`),n.videoUrl&&process.stdout.write(` video URL: ${n.videoUrl}
|
|
26
|
+
`),a==="failed"){const o=n.errorMessage??n.error??n.message??"unknown error";process.stdout.write(` failure reason: ${o}
|
|
27
|
+
`)}}l(R,"checkStatus");async function T(t,e){return await y(async()=>{const n=await w(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),a=n.status??"unknown";if(m({phase:"gen-digital-human:poll",generationId:e,status:a}),a==="completed")return n.videoUrl??"";if(a==="failed"){const o=n.errorMessage??n.error??n.message??"unknown error";throw new s(`❌ video generation failed: ${o}`)}return null},{intervalMs:5e3,timeoutMs:6e5})}l(T,"pollVideoStatus");async function q(t,e){const r=await U(e.skillName,{apiBaseUrl:t.api_base_url,privateToken:t.priv_token,preflightToken:e.auth?.token}),n=f(t.json_output);if(f(t.list_avatars))return N(r,t);if(f(t.clone))return C(r,t);if(f(t.clone_status)){const $=u(t.avatar_id);if(!$)throw new s("❌ clone-status mode requires --avatar-id");return J(r,$,n)}if(f(t.check_status)){const $=M(t.generation_id,"generation_id");if($===void 0)throw new s("❌ check-status mode requires --generation-id");return R(r,$,n)}const a=u(t.avatar_id);if(!a)throw new s("❌ provide --avatar-id to pick the avatar (use --list-avatars to see available avatars)");const o=(t.audio_url??"").trim(),d=t.text??"";if(o&&!/^https?:\/\//i.test(o))throw new s(`❌ --audio-url needs a public http(s) URL (local files are not uploaded), received: ${o}`);if(!o&&!d.trim())throw new s("❌ in TTS mode provide --text, or use --audio-url for audio-driven mode");const i=(await b(t.api_base_url)).digitalHuman;m({phase:"gen-digital-human:resolve-avatar",avatarId:a});const g=await I(r,a);if(g.cloneStatus==="cloning")throw new s(`❌ avatar ${a} is still cloning; check with --clone-status --avatar-id ${a}`);if(g.cloneStatus==="failed")throw new s(`❌ avatar ${a} failed to clone (${g.cloneError||"unknown error"}); clone it again`);const v=O(g,i.sources),c={avatarId:a,source:v};o?c.audioUrl=o:(c.text=d,c.voiceId=t.voice_id??i.defaultVoiceId,t.voice_name&&(c.voiceName=String(t.voice_name))),m({phase:"gen-digital-human:request",avatarId:a,source:v,mode:o?"audio":"tts"});const h=await w(r,"/digital-human/video/generate",c,{timeoutMs:6e4}),p=h.generationId;if(!p)throw new s(`❌ could not parse generationId; API returned: ${JSON.stringify(h)}`);m({phase:"gen-digital-human:async",generationId:p});const S=await T(r,p);if(!S)throw new s(`❌ video generated but no URL was returned; rerun --check-status --generation-id ${p} later`);if(n){process.stdout.write(JSON.stringify({url:S,generationId:p})+`
|
|
28
|
+
`);return}process.stdout.write(`
|
|
29
|
+
🔗 Video URL (renders directly, no download needed):
|
|
30
|
+
${S}
|
|
31
|
+
`),process.stdout.write(`
|
|
32
|
+
🎉 Digital-human video generation done!
|
|
33
|
+
`)}l(q,"genDigitalHuman");export{q as genDigitalHuman};
|
|
@@ -1,113 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* - --size accepts a WxH string (→ width/height) or an aspect-ratio preset key
|
|
11
|
-
* (→ aspectRatio; for Seedream models ab-api maps the preset to pixels).
|
|
12
|
-
* - --resolution is accepted but inert: it only ever fed the Gemini `size` tier,
|
|
13
|
-
* and Gemini is not wired up on the gateway, so we no longer send it.
|
|
14
|
-
* - Reference images: HTTPS URLs and data: URIs pass through; local paths are
|
|
15
|
-
* read and base64-encoded into a data: URI.
|
|
16
|
-
* - Async response (generationId + status:'generating'/'pending') is polled
|
|
17
|
-
* against /model/getImgStatus until completed/failed/timeout.
|
|
18
|
-
*/
|
|
19
|
-
import { mmPost, pollUntil, resolveHttpContext, SkillError } from '../http.js';
|
|
20
|
-
import { getCapabilities, matchModel, defaultModel } from '../capabilities.js';
|
|
21
|
-
import { emitProgress } from '../progress.js';
|
|
22
|
-
import { isTrue, resolveImageInput, toNumber, toStringArray } from './shared.js';
|
|
23
|
-
async function pollImageStatus(ctx, generationId) {
|
|
24
|
-
return pollUntil(async () => {
|
|
25
|
-
const data = await mmPost(ctx, '/model/getImgStatus', { generationId }, { timeoutMs: 30_000 });
|
|
26
|
-
const status = data.status ?? 'unknown';
|
|
27
|
-
emitProgress({ phase: 'gen-image:poll', generationId, status });
|
|
28
|
-
if (status === 'completed' || status === 'succeeded') {
|
|
29
|
-
return (data.imageUrls ?? []).filter((u) => !!u);
|
|
30
|
-
}
|
|
31
|
-
if (status === 'failed') {
|
|
32
|
-
const err = data.errorMsg ?? data.error ?? 'unknown error';
|
|
33
|
-
throw new SkillError(`❌ image generation failed: ${err}`);
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
}, { intervalMs: 5_000, timeoutMs: 120_000 });
|
|
37
|
-
}
|
|
38
|
-
export async function genImage(input, ctxIn) {
|
|
39
|
-
const prompt = input.prompt;
|
|
40
|
-
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
41
|
-
throw new SkillError('❌ Please provide --prompt to describe the image');
|
|
42
|
-
}
|
|
43
|
-
const apiBaseUrl = input.api_base_url;
|
|
44
|
-
const caps = await getCapabilities(apiBaseUrl);
|
|
45
|
-
// Resolve the model against the catalog: explicit alias/id, env override, or default.
|
|
46
|
-
const requested = input.model ?? process.env.MM_IMAGE_MODEL;
|
|
47
|
-
const descriptor = requested ? matchModel(caps.image, requested) : defaultModel(caps.image);
|
|
48
|
-
// Send the resolved id when known; otherwise forward the raw value for the backend to handle.
|
|
49
|
-
const model = descriptor?.id ?? requested ?? defaultModel(caps.image)?.id ?? '';
|
|
50
|
-
const size = input.size ?? '1:1';
|
|
51
|
-
const n = toNumber(input.n, 'n') ?? 1;
|
|
52
|
-
const seed = toNumber(input.seed, 'seed');
|
|
53
|
-
const imageStrength = toNumber(input.image_strength, 'image_strength');
|
|
54
|
-
const guidanceScale = toNumber(input.guidance_scale, 'guidance_scale');
|
|
55
|
-
const negativePrompt = input.negative_prompt;
|
|
56
|
-
// --watermark / --no-watermark — parseArgv only sees `--watermark true` style;
|
|
57
|
-
// when ab-agent's buildCliArgs passes booleans, only the truthy flag appears.
|
|
58
|
-
// Treat presence as true; explicit `--no-watermark` isn't supported here.
|
|
59
|
-
const watermark = input.watermark === true ? true : undefined;
|
|
60
|
-
const references = toStringArray(input.reference).map(resolveImageInput).filter(Boolean);
|
|
61
|
-
const refLimit = descriptor?.constraints.refImageMax;
|
|
62
|
-
if (refLimit !== undefined && references.length > refLimit) {
|
|
63
|
-
throw new SkillError(`❌ ${descriptor?.label ?? model} accepts at most ${refLimit} reference images, got ${references.length}`);
|
|
64
|
-
}
|
|
65
|
-
const payload = { model, prompt, imageCount: n };
|
|
66
|
-
if (guidanceScale !== undefined)
|
|
67
|
-
payload.guidanceScale = guidanceScale;
|
|
68
|
-
if (references.length > 0)
|
|
69
|
-
payload.referenceImages = references;
|
|
70
|
-
if (imageStrength !== undefined)
|
|
71
|
-
payload.imageStrength = imageStrength;
|
|
72
|
-
if (negativePrompt)
|
|
73
|
-
payload.negativePrompt = negativePrompt;
|
|
74
|
-
if (seed !== undefined)
|
|
75
|
-
payload.seed = seed;
|
|
76
|
-
if (watermark !== undefined)
|
|
77
|
-
payload.watermark = watermark;
|
|
78
|
-
// --size as WxH → explicit pixels; otherwise treat as an aspect-ratio preset.
|
|
79
|
-
// ab-api maps an aspect-ratio preset to that model's pixel table, so the CLI
|
|
80
|
-
// carries no per-model size table.
|
|
81
|
-
if (/^\d+x\d+$/i.test(size)) {
|
|
82
|
-
const [w, h] = size.toLowerCase().split('x').map((v) => parseInt(v, 10));
|
|
83
|
-
payload.width = w;
|
|
84
|
-
payload.height = h;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
payload.aspectRatio = size;
|
|
88
|
-
}
|
|
89
|
-
const ctx = await resolveHttpContext(ctxIn.skillName, {
|
|
90
|
-
apiBaseUrl,
|
|
91
|
-
privateToken: input.priv_token,
|
|
92
|
-
preflightToken: ctxIn.auth?.token,
|
|
93
|
-
});
|
|
94
|
-
emitProgress({ phase: 'gen-image:request', model, size, n });
|
|
95
|
-
const data = await mmPost(ctx, '/model/genImg', payload, { timeoutMs: 120_000 });
|
|
96
|
-
let imageUrls = (data.imageUrls ?? []).filter((u) => !!u);
|
|
97
|
-
if (imageUrls.length === 0 && data.generationId && (data.status === 'generating' || data.status === 'pending')) {
|
|
98
|
-
emitProgress({ phase: 'gen-image:async', generationId: data.generationId });
|
|
99
|
-
imageUrls = await pollImageStatus(ctx, data.generationId);
|
|
100
|
-
}
|
|
101
|
-
if (imageUrls.length === 0) {
|
|
102
|
-
throw new SkillError(`❌ API response did not contain any image URL: ${JSON.stringify(data)}`);
|
|
103
|
-
}
|
|
104
|
-
if (isTrue(input.json_output)) {
|
|
105
|
-
process.stdout.write(JSON.stringify({ urls: imageUrls }) + '\n');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
process.stdout.write(`\n📸 Generated ${imageUrls.length} image(s)\n`);
|
|
109
|
-
process.stdout.write(`\n🔗 Image URLs (renders directly, no download needed):\n`);
|
|
110
|
-
for (const url of imageUrls)
|
|
111
|
-
process.stdout.write(`${url}\n`);
|
|
112
|
-
process.stdout.write(`\n🎉 Image generation done!\n`);
|
|
113
|
-
}
|
|
1
|
+
var L=Object.defineProperty;var h=(e,r)=>L(e,"name",{value:r,configurable:!0});import{mmPost as U,pollUntil as C,resolveHttpContext as N,SkillError as c}from"../http.js";import{getCapabilities as A,matchModel as E,defaultModel as P}from"../capabilities.js";import{emitProgress as I}from"../progress.js";import{isTrue as O,resolveImageInput as R,toNumber as l,toStringArray as T}from"./shared.js";async function q(e,r){return C(async()=>{const n=await U(e,"/model/getImgStatus",{generationId:r},{timeoutMs:3e4}),o=n.status??"unknown";if(I({phase:"gen-image:poll",generationId:r,status:o}),o==="completed"||o==="succeeded")return(n.imageUrls??[]).filter(s=>!!s);if(o==="failed"){const s=n.errorMsg??n.error??"unknown error";throw new c(`❌ image generation failed: ${s}`)}return null},{intervalMs:5e3,timeoutMs:12e4})}h(q,"pollImageStatus");async function D(e,r){const n=e.prompt;if(!n||typeof n!="string"||!n.trim())throw new c("❌ Please provide --prompt to describe the image");const o=e.api_base_url,s=await A(o),f=e.model??process.env.MM_IMAGE_MODEL,p=f?E(s.image,f):P(s.image),u=p?.id??f??P(s.image)?.id??"",m=e.size??"1:1",_=l(e.n,"n")??1,M=l(e.seed,"seed"),k=l(e.image_strength,"image_strength"),y=l(e.guidance_scale,"guidance_scale"),v=e.negative_prompt,S=e.watermark===!0?!0:void 0,d=T(e.reference).map(R).filter(Boolean),w=p?.constraints.refImageMax;if(w!==void 0&&d.length>w)throw new c(`❌ ${p?.label??u} accepts at most ${w} reference images, got ${d.length}`);const t={model:u,prompt:n,imageCount:_};if(y!==void 0&&(t.guidanceScale=y),d.length>0&&(t.referenceImages=d),k!==void 0&&(t.imageStrength=k),v&&(t.negativePrompt=v),M!==void 0&&(t.seed=M),S!==void 0&&(t.watermark=S),/^\d+x\d+$/i.test(m)){const[g,b]=m.toLowerCase().split("x").map(x=>parseInt(x,10));t.width=g,t.height=b}else t.aspectRatio=m;const $=await N(r.skillName,{apiBaseUrl:o,privateToken:e.priv_token,preflightToken:r.auth?.token});I({phase:"gen-image:request",model:u,size:m,n:_});const a=await U($,"/model/genImg",t,{timeoutMs:12e4});let i=(a.imageUrls??[]).filter(g=>!!g);if(i.length===0&&a.generationId&&(a.status==="generating"||a.status==="pending")&&(I({phase:"gen-image:async",generationId:a.generationId}),i=await q($,a.generationId)),i.length===0)throw new c(`❌ API response did not contain any image URL: ${JSON.stringify(a)}`);if(O(e.json_output)){process.stdout.write(JSON.stringify({urls:i})+`
|
|
2
|
+
`);return}process.stdout.write(`
|
|
3
|
+
📸 Generated ${i.length} image(s)
|
|
4
|
+
`),process.stdout.write(`
|
|
5
|
+
🔗 Image URLs (renders directly, no download needed):
|
|
6
|
+
`);for(const g of i)process.stdout.write(`${g}
|
|
7
|
+
`);process.stdout.write(`
|
|
8
|
+
🎉 Image generation done!
|
|
9
|
+
`)}h(D,"genImage");export{D as genImage};
|
|
@@ -1,188 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* - --instruction rewrites an existing caption (--title / --description / --tag
|
|
17
|
-
* are the draft) instead of generating from scratch; single platform only,
|
|
18
|
-
* since the draft belongs to one platform.
|
|
19
|
-
*/
|
|
20
|
-
import { EXIT, mmPost, resolveHttpContext, SkillError } from '../http.js';
|
|
21
|
-
import { emitProgress } from '../progress.js';
|
|
22
|
-
import { isTrue, toNumber, toStringArray } from './shared.js';
|
|
23
|
-
/** Platforms the web panel enables by default; `all` expands to these. */
|
|
24
|
-
const DEFAULT_PLATFORMS = ['douyin', 'xiaohongshu', 'tiktok'];
|
|
25
|
-
/** Friendly aliases → backend platform codes. Unknown values pass through for ab-api to validate. */
|
|
26
|
-
const PLATFORM_ALIASES = {
|
|
27
|
-
dy: 'douyin',
|
|
28
|
-
抖音: 'douyin',
|
|
29
|
-
xhs: 'xiaohongshu',
|
|
30
|
-
rednote: 'xiaohongshu',
|
|
31
|
-
小红书: 'xiaohongshu',
|
|
32
|
-
tt: 'tiktok',
|
|
33
|
-
};
|
|
34
|
-
/** Display names for human-readable output only. */
|
|
35
|
-
const PLATFORM_LABELS = {
|
|
36
|
-
douyin: '抖音 Douyin',
|
|
37
|
-
xiaohongshu: '小红书 Xiaohongshu',
|
|
38
|
-
tiktok: 'TikTok',
|
|
39
|
-
};
|
|
40
|
-
/** Backend waits on a non-streaming LLM call (80s server-side cap); leave headroom. */
|
|
41
|
-
const GENERATE_TIMEOUT_MS = 90_000;
|
|
42
|
-
function resolvePlatforms(value) {
|
|
43
|
-
const raw = toStringArray(value)
|
|
44
|
-
.flatMap((v) => v.split(/[,,\s]+/))
|
|
45
|
-
.map((v) => v.trim())
|
|
46
|
-
.filter(Boolean);
|
|
47
|
-
if (raw.length === 0)
|
|
48
|
-
return [...DEFAULT_PLATFORMS];
|
|
49
|
-
const out = [];
|
|
50
|
-
for (const item of raw) {
|
|
51
|
-
const key = item.toLowerCase();
|
|
52
|
-
const codes = key === 'all' ? [...DEFAULT_PLATFORMS] : [PLATFORM_ALIASES[item] ?? PLATFORM_ALIASES[key] ?? key];
|
|
53
|
-
for (const code of codes)
|
|
54
|
-
if (!out.includes(code))
|
|
55
|
-
out.push(code);
|
|
56
|
-
}
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
function optString(value) {
|
|
60
|
-
if (typeof value !== 'string')
|
|
61
|
-
return undefined;
|
|
62
|
-
const trimmed = value.trim();
|
|
63
|
-
return trimmed || undefined;
|
|
64
|
-
}
|
|
65
|
-
/** Tags may arrive as `--tag a --tag b`, `--tag "a,b"`, or with a leading `#`. */
|
|
66
|
-
function normalizeTags(value) {
|
|
67
|
-
return toStringArray(value)
|
|
68
|
-
.flatMap((v) => v.split(/[,,]+/))
|
|
69
|
-
.map((v) => v.trim().replace(/^#+/, ''))
|
|
70
|
-
.filter(Boolean);
|
|
71
|
-
}
|
|
72
|
-
async function generateOne(ctx, platform, base) {
|
|
73
|
-
emitProgress({ phase: 'gen-post:request', platform });
|
|
74
|
-
try {
|
|
75
|
-
const caption = await mmPost(ctx, '/publish/caption/generate', { ...base, platform }, { timeoutMs: GENERATE_TIMEOUT_MS });
|
|
76
|
-
if (!caption || (!caption.title && !caption.description)) {
|
|
77
|
-
return { platform, error: 'backend returned an empty caption' };
|
|
78
|
-
}
|
|
79
|
-
emitProgress({ phase: 'gen-post:done', platform });
|
|
80
|
-
return { platform, caption: { ...caption, platform, tags: caption.tags ?? [] } };
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
// Auth failures abort the whole run: every other platform would fail the same way.
|
|
84
|
-
if (err instanceof SkillError && err.exitCode === EXIT.NOT_AUTHENTICATED)
|
|
85
|
-
throw err;
|
|
86
|
-
return { platform, error: err instanceof Error ? err.message : String(err) };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function printHuman(results) {
|
|
90
|
-
for (const r of results) {
|
|
91
|
-
const label = PLATFORM_LABELS[r.platform] ?? r.platform;
|
|
92
|
-
process.stdout.write(`\n━━━━━━━━ ${label} ━━━━━━━━\n`);
|
|
93
|
-
if (!r.caption) {
|
|
94
|
-
process.stdout.write(`❌ ${r.error ?? 'generation failed'}\n`);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
const { title, description, tags } = r.caption;
|
|
98
|
-
process.stdout.write(`标题 / Title:\n${title}\n\n`);
|
|
99
|
-
process.stdout.write(`描述 / Description:\n${description}\n\n`);
|
|
100
|
-
process.stdout.write(`标签 / Tags:\n${tags.map((t) => `#${t}`).join(' ')}\n`);
|
|
101
|
-
}
|
|
102
|
-
const ok = results.filter((r) => r.caption).length;
|
|
103
|
-
process.stdout.write(`\n🎉 Generated publish copy for ${ok}/${results.length} platform(s)\n`);
|
|
104
|
-
}
|
|
105
|
-
export async function genPost(input, ctxIn) {
|
|
106
|
-
const topic = optString(input.topic);
|
|
107
|
-
const hook = optString(input.hook);
|
|
108
|
-
const instruction = optString(input.instruction);
|
|
109
|
-
const renderId = optString(input.render_id);
|
|
110
|
-
const platforms = resolvePlatforms(input.platform);
|
|
111
|
-
const current = {
|
|
112
|
-
title: optString(input.title) ?? '',
|
|
113
|
-
description: optString(input.description) ?? '',
|
|
114
|
-
tags: normalizeTags(input.tag),
|
|
115
|
-
};
|
|
116
|
-
const hasDraft = !!(current.title || current.description || current.tags.length);
|
|
117
|
-
if (instruction) {
|
|
118
|
-
if (platforms.length !== 1) {
|
|
119
|
-
throw new SkillError('❌ --instruction rewrites one platform\'s caption; pass exactly one --platform');
|
|
120
|
-
}
|
|
121
|
-
if (!hasDraft) {
|
|
122
|
-
throw new SkillError('❌ --instruction needs the caption to rewrite: pass --title / --description / --tag');
|
|
123
|
-
}
|
|
124
|
-
if (instruction.length > 500) {
|
|
125
|
-
throw new SkillError('❌ --instruction must be at most 500 characters');
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
else if (!topic && !hook) {
|
|
129
|
-
throw new SkillError('❌ Please provide --topic (what the video is about); --hook is optional extra context');
|
|
130
|
-
}
|
|
131
|
-
const durationSec = toNumber(input.duration, 'duration');
|
|
132
|
-
if (durationSec !== undefined && durationSec < 0) {
|
|
133
|
-
throw new SkillError('❌ --duration must be a non-negative number of seconds');
|
|
134
|
-
}
|
|
135
|
-
const base = {};
|
|
136
|
-
if (renderId)
|
|
137
|
-
base.renderId = renderId;
|
|
138
|
-
const videoProjectId = optString(input.video_project_id);
|
|
139
|
-
if (videoProjectId)
|
|
140
|
-
base.videoProjectId = videoProjectId;
|
|
141
|
-
if (topic)
|
|
142
|
-
base.topic = topic;
|
|
143
|
-
if (hook)
|
|
144
|
-
base.hook = hook;
|
|
145
|
-
const sourceUrl = optString(input.source_url);
|
|
146
|
-
if (sourceUrl)
|
|
147
|
-
base.sourceUrl = sourceUrl;
|
|
148
|
-
if (durationSec !== undefined)
|
|
149
|
-
base.durationSec = durationSec;
|
|
150
|
-
const aspectRatio = optString(input.aspect_ratio);
|
|
151
|
-
if (aspectRatio)
|
|
152
|
-
base.aspectRatio = aspectRatio;
|
|
153
|
-
const language = optString(input.language);
|
|
154
|
-
if (language)
|
|
155
|
-
base.language = language;
|
|
156
|
-
const promptOverride = optString(input.prompt);
|
|
157
|
-
if (promptOverride)
|
|
158
|
-
base.promptOverride = promptOverride;
|
|
159
|
-
if (instruction) {
|
|
160
|
-
base.instruction = instruction;
|
|
161
|
-
base.current = current;
|
|
162
|
-
}
|
|
163
|
-
const ctx = await resolveHttpContext(ctxIn.skillName, {
|
|
164
|
-
apiBaseUrl: input.api_base_url,
|
|
165
|
-
privateToken: input.priv_token,
|
|
166
|
-
preflightToken: ctxIn.auth?.token,
|
|
167
|
-
});
|
|
168
|
-
const results = await Promise.all(platforms.map((p) => generateOne(ctx, p, base)));
|
|
169
|
-
const succeeded = results.filter((r) => r.caption);
|
|
170
|
-
if (isTrue(input.json_output)) {
|
|
171
|
-
process.stdout.write(JSON.stringify({
|
|
172
|
-
captions: succeeded.map((r) => ({
|
|
173
|
-
platform: r.platform,
|
|
174
|
-
title: r.caption.title,
|
|
175
|
-
description: r.caption.description,
|
|
176
|
-
tags: r.caption.tags,
|
|
177
|
-
})),
|
|
178
|
-
errors: results.filter((r) => !r.caption).map((r) => ({ platform: r.platform, error: r.error })),
|
|
179
|
-
persisted: !!renderId,
|
|
180
|
-
}) + '\n');
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
printHuman(results);
|
|
184
|
-
}
|
|
185
|
-
if (succeeded.length === 0) {
|
|
186
|
-
throw new SkillError(`❌ publish copy generation failed for every platform: ${results.map((r) => `${r.platform}: ${r.error}`).join('; ')}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
1
|
+
var P=Object.defineProperty;var d=(t,e)=>P(t,"name",{value:e,configurable:!0});import{EXIT as $,mmPost as O,resolveHttpContext as L,SkillError as f}from"../http.js";import{emitProgress as v}from"../progress.js";import{isTrue as M,toNumber as I,toStringArray as x}from"./shared.js";const E=["douyin","xiaohongshu","tiktok"],S={dy:"douyin",抖音:"douyin",xhs:"xiaohongshu",rednote:"xiaohongshu",小红书:"xiaohongshu",tt:"tiktok"},j={douyin:"抖音 Douyin",xiaohongshu:"小红书 Xiaohongshu",tiktok:"TikTok"},N=9e4;function R(t){const e=x(t).flatMap(o=>o.split(/[,,\s]+/)).map(o=>o.trim()).filter(Boolean);if(e.length===0)return[...E];const r=[];for(const o of e){const i=o.toLowerCase(),c=i==="all"||o==="全部"?[...E]:[S[o]??S[i]??i];for(const p of c)r.includes(p)||r.push(p)}return r}d(R,"resolvePlatforms");function a(t){return typeof t!="string"?void 0:t.trim()||void 0}d(a,"optString");function U(t){return x(t).flatMap(e=>e.split(/[,,]+/)).map(e=>e.trim().replace(/^#+/,"")).filter(Boolean)}d(U,"normalizeTags");async function D(t,e,r){v({phase:"gen-post:request",platform:e});try{const o=await O(t,"/publish/caption/generate",{...r,platform:e},{timeoutMs:N});return!o||!o.title&&!o.description?{platform:e,error:"backend returned an empty caption"}:(v({phase:"gen-post:done",platform:e}),{platform:e,caption:{...o,platform:e,tags:o.tags??[]}})}catch(o){if(o instanceof f&&o.exitCode===$.NOT_AUTHENTICATED)throw o;return{platform:e,error:o instanceof Error?o.message:String(o)}}}d(D,"generateOne");function B(t){for(const r of t){const o=j[r.platform]??r.platform;if(process.stdout.write(`
|
|
2
|
+
━━━━━━━━ ${o} ━━━━━━━━
|
|
3
|
+
`),!r.caption){process.stdout.write(`❌ ${r.error??"generation failed"}
|
|
4
|
+
`);continue}const{title:i,description:c,tags:p}=r.caption;process.stdout.write(`标题 / Title:
|
|
5
|
+
${i}
|
|
6
|
+
|
|
7
|
+
`),process.stdout.write(`描述 / Description:
|
|
8
|
+
${c}
|
|
9
|
+
|
|
10
|
+
`),process.stdout.write(`标签 / Tags:
|
|
11
|
+
${p.map(l=>`#${l}`).join(" ")}
|
|
12
|
+
`)}const e=t.filter(r=>r.caption).length;process.stdout.write(`
|
|
13
|
+
🎉 Generated publish copy for ${e}/${t.length} platform(s)
|
|
14
|
+
`)}d(B,"printHuman");async function X(t,e){const r=a(t.topic),o=a(t.hook),i=a(t.instruction),c=a(t.render_id),p=R(t.platform),l={title:a(t.title)??"",description:a(t.description)??"",tags:U(t.tag)},m=!!(l.title||l.description||l.tags.length);if(i&&i.length>500)throw new f("❌ --instruction must be at most 500 characters");const h=!!i&&m;if(m&&!i)throw new f("❌ --title / --description / --tag are the draft to rewrite; pass --instruction with them");if(h&&p.length!==1)throw new f("❌ rewriting a caption (--instruction with --title / --description / --tag) needs exactly one --platform");if(!h&&!r&&!o)throw new f("❌ Please provide --topic (what the video is about); --hook is optional extra context");const u=I(t.duration,"duration");if(u!==void 0&&u<0)throw new f("❌ --duration must be a non-negative number of seconds");const s={};c&&(s.renderId=c);const w=a(t.video_project_id);w&&(s.videoProjectId=w),r&&(s.topic=r),o&&(s.hook=o);const k=a(t.source_url);k&&(s.sourceUrl=k),u!==void 0&&(s.durationSec=u);const T=a(t.aspect_ratio);T&&(s.aspectRatio=T);const y=a(t.language);y&&(s.language=y);const _=a(t.prompt);_&&(s.promptOverride=_),i&&(s.instruction=i),h&&(s.current=l);const A=await L(e.skillName,{apiBaseUrl:t.api_base_url,privateToken:t.priv_token,preflightToken:e.auth?.token}),g=await Promise.all(p.map(n=>D(A,n,s))),b=g.filter(n=>n.caption);if(M(t.json_output)?process.stdout.write(JSON.stringify({captions:b.map(n=>({platform:n.platform,title:n.caption.title,description:n.caption.description,tags:n.caption.tags})),errors:g.filter(n=>!n.caption).map(n=>({platform:n.platform,error:n.error})),persisted:!!c})+`
|
|
15
|
+
`):B(g),b.length===0)throw new f(`❌ publish copy generation failed for every platform: ${g.map(n=>`${n.platform}: ${n.error}`).join("; ")}`)}d(X,"genPost");export{X as genPost};
|