@remixmate/cli 0.13.9 → 0.13.10

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.
@@ -1,7 +1,7 @@
1
- var U=Object.defineProperty;var w=(t,e)=>U(t,"name",{value:e,configurable:!0});import{mmPost as h,pollUntil as y,resolveHttpContext as b,SkillError as i}from"../http.js";import{getCapabilities as N}from"../capabilities.js";import{emitProgress as v}from"../progress.js";import{isTrue as g,toNumber as I}from"./shared.js";const E=["video","image"],k=["hifly","heygen"],S="hifly";function c(t){return t==null?"":String(t).trim()}w(c,"toId");async function M(t,e){const o=g(e.mine),n=!o&&g(e.all),a=!o&&!n,r={current:1,pageSize:100};a?r.featured=!0:n&&(r.source=c(e.source)||S),o&&(r.mine=!0),e.gender&&(r.gender=String(e.gender)),e.name&&(r.name=String(e.name));const u=await h(t,"/digital-human/avatar/page",r,{timeoutMs:3e4}),l=u.list??[];if(g(e.json_output)){process.stdout.write(JSON.stringify({avatars:l})+`
2
- `);return}if(l.length===0){process.stdout.write(a?`⚠️ No curated public avatars match. Try --all for the full inventory, or --mine for your own.
1
+ var b=Object.defineProperty;var u=(t,e)=>b(t,"name",{value:e,configurable:!0});import{mmPost as p,pollUntil as S,resolveHttpContext as M,SkillError as i}from"../http.js";import{getCapabilities as N}from"../capabilities.js";import{emitProgress as v}from"../progress.js";import{isTrue as g,toNumber as I}from"./shared.js";const E=["video","image"],k=["hifly","heygen"],y="hifly";function c(t){return t==null?"":String(t).trim()}u(c,"toId");async function O(t,e){const r=g(e.mine),a=!r&&g(e.all),n=!r&&!a,o={current:1,pageSize:100};n?o.featured=!0:a&&(o.source=c(e.source)||y),r&&(o.mine=!0),e.gender&&(o.gender=String(e.gender)),e.name&&(o.name=String(e.name));const f=await p(t,"/digital-human/avatar/page",o,{timeoutMs:3e4}),l=f.list??[];if(g(e.json_output)){process.stdout.write(JSON.stringify({avatars:l})+`
2
+ `);return}if(l.length===0){process.stdout.write(n?`⚠️ No curated public avatars match. Try --all for the full inventory, or --mine for your own.
3
3
  `:`⚠️ No avatars available
4
- `);return}if(a){process.stdout.write(`
4
+ `);return}if(n){process.stdout.write(`
5
5
  ${"ID".padEnd(24)} ${"Name".padEnd(16)} ${"Gender".padEnd(8)} ${"Ratio".padEnd(6)} ${"Scenes".padEnd(22)} Voice
6
6
  `),process.stdout.write(`${"-".repeat(24)} ${"-".repeat(16)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(22)} ${"-".repeat(12)}
7
7
  `);for(const d of l)process.stdout.write(`${c(d.id).padEnd(24)} ${String(d.name??"").padEnd(16)} ${String(d.gender??"").padEnd(8)} ${String(d.aspectRatio??"").padEnd(6)} ${(d.tags??[]).join(",").padEnd(22)} ${d.defaultVoiceId||"-"}
@@ -9,31 +9,31 @@ ${"ID".padEnd(24)} ${"Name".padEnd(16)} ${"Gender".padEnd(8)} ${"Ratio".padEnd(6
9
9
  ${"ID".padEnd(24)} ${"Name".padEnd(16)} ${"Source".padEnd(14)} ${"Gender".padEnd(8)} ${"Ratio".padEnd(6)} Clone
10
10
  `),process.stdout.write(`${"-".repeat(24)} ${"-".repeat(16)} ${"-".repeat(14)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(8)}
11
11
  `);for(const d of l)process.stdout.write(`${c(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||"-"}
12
- `)}const s=u.pager?.total??l.length;process.stdout.write(`
12
+ `)}const s=f.pager?.total??l.length;process.stdout.write(`
13
13
  ${l.length} avatar(s) shown${s>l.length?` of ${s}; narrow with --name`:""}
14
- `),process.stdout.write(a?`💡 Use --avatar-id <ID> to pick an avatar; pass its Voice as --voice-id for a matching voice
14
+ `),process.stdout.write(n?`💡 Use --avatar-id <ID> to pick an avatar; pass its Voice as --voice-id for a matching voice
15
15
  `:`💡 Use --avatar-id <ID> to pick an avatar for generation
16
- `)}w(M,"listAvatars");async function O(t,e){for(const o of[{mine:!0},{featured:!0}]){const a=((await h(t,"/digital-human/avatar/page",{current:1,pageSize:100,...o},{timeoutMs:3e4})).list??[]).find(r=>c(r.id)===e);if(a)return a}return{id:e,source:S}}w(O,"fetchAvatarInfo");function R(t,e){const o=(t.source??"").trim()||S;if(!e.includes(o))throw new i(`❌ avatar uses provider "${o}", which is not available (supported: ${e.join("/")}). Pick another avatar from --list-avatars.`);return o}w(R,"resolveSource");async function T(t,e){const o=c(e.clone_url);if(!/^https?:\/\//i.test(o))throw new i("❌ --clone needs --clone-url <public http(s) URL of a video or portrait image> (local files are not uploaded)");const n=c(e.name);if(!n)throw new i("❌ --clone needs --name <avatar name>");const a=c(e.clone_type)||"video";if(!E.includes(a))throw new i(`❌ --clone-type must be one of ${E.join(" / ")}, received: ${a}`);const r=c(e.clone_provider)||S;if(!k.includes(r))throw new i(`❌ --clone-provider must be one of ${k.join(" / ")}, received: ${r}`);if(r==="heygen"&&a!=="image")throw new i("❌ HeyGen avatars are created from a photo only; add --clone-type image");const u={name:n,sourceType:a,fileUrl:o};r!==S&&(u.provider=r),e.thumbnail_url&&(u.thumbnailUrl=c(e.thumbnail_url)),e.gender&&(u.gender=c(e.gender)),v({phase:"gen-digital-human:clone-request",sourceType:a,provider:r});const l=await h(t,"/digital-human/avatar/clone",u,{timeoutMs:6e4}),s=c(l.id);if(!s)throw new i(`❌ could not parse the new avatar id; API returned: ${JSON.stringify(l)}`);v({phase:"gen-digital-human:clone-async",avatarId:s});const d=g(e.json_output);if(e.wait!==void 0&&!g(e.wait)){if(d){process.stdout.write(JSON.stringify({avatarId:s,cloneStatus:l.cloneStatus??"cloning"})+`
16
+ `)}u(O,"listAvatars");async function R(t,e){for(const r of[{mine:!0},{featured:!0}]){const n=((await p(t,"/digital-human/avatar/page",{current:1,pageSize:100,...r},{timeoutMs:3e4})).list??[]).find(o=>c(o.id)===e);if(n)return n}return{id:e,source:y}}u(R,"fetchAvatarInfo");function L(t,e){const r=(t.source??"").trim()||y;if(!e.includes(r))throw new i(`❌ avatar uses provider "${r}", which is not available (supported: ${e.join("/")}). Pick another avatar from --list-avatars.`);return r}u(L,"resolveSource");async function T(t,e){const r=c(e.clone_url);if(!/^https?:\/\//i.test(r))throw new i("❌ --clone needs --clone-url <public http(s) URL of a video or portrait image> (local files are not uploaded)");const a=c(e.name);if(!a)throw new i("❌ --clone needs --name <avatar name>");const n=c(e.clone_type)||"video";if(!E.includes(n))throw new i(`❌ --clone-type must be one of ${E.join(" / ")}, received: ${n}`);const o=c(e.clone_provider)||y;if(!k.includes(o))throw new i(`❌ --clone-provider must be one of ${k.join(" / ")}, received: ${o}`);if(o==="heygen"&&n!=="image")throw new i("❌ HeyGen avatars are created from a photo only; add --clone-type image");const f={name:a,sourceType:n,fileUrl:r};o!==y&&(f.provider=o),e.thumbnail_url&&(f.thumbnailUrl=c(e.thumbnail_url)),e.gender&&(f.gender=c(e.gender)),v({phase:"gen-digital-human:clone-request",sourceType:n,provider:o});const l=await p(t,"/digital-human/avatar/clone",f,{timeoutMs:6e4}),s=c(l.id);if(!s)throw new i(`❌ could not parse the new avatar id; API returned: ${JSON.stringify(l)}`);v({phase:"gen-digital-human:clone-async",avatarId:s});const d=g(e.json_output);if(e.wait!==void 0&&!g(e.wait)){if(d){process.stdout.write(JSON.stringify({avatarId:s,cloneStatus:l.cloneStatus??"cloning"})+`
17
17
  `);return}process.stdout.write(`
18
18
  🧬 Clone submitted, avatar id: ${s}
19
19
  `),process.stdout.write(`💡 Check progress with --clone-status --avatar-id ${s}
20
- `);return}let m;try{m=await y(async()=>{const f=await h(t,"/digital-human/avatar/clone-status",{id:s},{timeoutMs:3e4}),p=f.cloneStatus??"unknown";if(v({phase:"gen-digital-human:clone-poll",avatarId:s,status:p}),p==="done")return f;if(p==="failed")throw new i(`❌ avatar clone failed: ${f.cloneError||"unknown error"}`);return null},{intervalMs:1e4,timeoutMs:20*6e4})}catch(f){throw f instanceof i&&/polling timed out/.test(f.message)?new i(`❌ avatar ${s} is still cloning; rerun --clone-status --avatar-id ${s} later`):f}if(d){process.stdout.write(JSON.stringify({avatarId:s,cloneStatus:m.cloneStatus})+`
20
+ `);return}let m;try{m=await S(async()=>{const w=await p(t,"/digital-human/avatar/clone-status",{id:s},{timeoutMs:3e4}),$=w.cloneStatus??"unknown";if(v({phase:"gen-digital-human:clone-poll",avatarId:s,status:$}),$==="done")return w;if($==="failed")throw new i(`❌ avatar clone failed: ${w.cloneError||"unknown error"}`);return null},{intervalMs:1e4,timeoutMs:20*6e4})}catch(w){throw w instanceof i&&/polling timed out/.test(w.message)?new i(`❌ avatar ${s} is still cloning; rerun --clone-status --avatar-id ${s} later`):w}if(d){process.stdout.write(JSON.stringify({avatarId:s,cloneStatus:m.cloneStatus})+`
21
21
  `);return}process.stdout.write(`
22
22
  ✅ Avatar cloned, id: ${s}
23
23
  `),process.stdout.write(`💡 Generate with --avatar-id ${s} --text "..."
24
- `)}w(T,"cloneAvatar");async function L(t,e,o){const n=await h(t,"/digital-human/avatar/clone-status",{id:e},{timeoutMs:3e4}),a=n.cloneStatus||"unknown";if(o){process.stdout.write(JSON.stringify({avatarId:e,cloneStatus:a,error:n.cloneError||null})+`
24
+ `)}u(T,"cloneAvatar");async function j(t,e,r){const a=await p(t,"/digital-human/avatar/clone-status",{id:e},{timeoutMs:3e4}),n=a.cloneStatus||"unknown";if(r){process.stdout.write(JSON.stringify({avatarId:e,cloneStatus:n,error:a.cloneError||null})+`
25
25
  `);return}process.stdout.write(`
26
- clone status: ${a}
27
- `),a==="failed"&&process.stdout.write(` failure reason: ${n.cloneError||"unknown error"}
28
- `)}w(L,"checkCloneStatus");async function j(t,e,o){const n=await h(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),a=n.status??"unknown";if(o){process.stdout.write(JSON.stringify({status:a,url:n.videoUrl??null,generationId:e})+`
26
+ clone status: ${n}
27
+ `),n==="failed"&&process.stdout.write(` failure reason: ${a.cloneError||"unknown error"}
28
+ `)}u(j,"checkCloneStatus");async function C(t,e,r){const a=await p(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),n=a.status??"unknown";if(r){process.stdout.write(JSON.stringify({status:n,url:a.videoUrl??null,generationId:e})+`
29
29
  `);return}if(process.stdout.write(`
30
- task status: ${a}
31
- `),n.videoUrl&&process.stdout.write(` video URL: ${n.videoUrl}
32
- `),a==="failed"){const r=n.errorMessage??n.error??n.message??"unknown error";process.stdout.write(` failure reason: ${r}
33
- `)}}w(j,"checkStatus");const C=30*6e4;async function D(t,e){return await y(async()=>{const n=await h(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),a=n.status??"unknown";if(v({phase:"gen-digital-human:poll",generationId:e,status:a}),a==="completed")return n.videoUrl??"";if(a==="failed"){const r=n.errorMessage??n.error??n.message??"unknown error";throw new i(`❌ video generation failed: ${r}`)}return null},{intervalMs:5e3,timeoutMs:C}).catch(n=>{throw n instanceof i&&/polling timed out/.test(n.message)?new i(`${n.message}; still generating on the server — resume with --check-status --generation-id ${e}`):n})}w(D,"pollVideoStatus");async function q(t,e){const o=await b(e.skillName,{apiBaseUrl:t.api_base_url,privateToken:t.priv_token,preflightToken:e.auth?.token}),n=g(t.json_output);if(g(t.list_avatars))return M(o,t);if(g(t.clone))return T(o,t);if(g(t.clone_status)){const $=c(t.avatar_id);if(!$)throw new i("❌ clone-status mode requires --avatar-id");return L(o,$,n)}if(g(t.check_status)){const $=I(t.generation_id,"generation_id");if($===void 0)throw new i("❌ check-status mode requires --generation-id");return j(o,$,n)}const a=c(t.avatar_id);if(!a)throw new i("❌ provide --avatar-id to pick the avatar (use --list-avatars to see available avatars)");const r=(t.audio_url??"").trim(),u=t.text??"";if(r&&!/^https?:\/\//i.test(r))throw new i(`❌ --audio-url needs a public http(s) URL (local files are not uploaded), received: ${r}`);if(!r&&!u.trim())throw new i("❌ in TTS mode provide --text, or use --audio-url for audio-driven mode");const l=(await N(t.api_base_url)).digitalHuman;v({phase:"gen-digital-human:resolve-avatar",avatarId:a});const s=await O(o,a);if(s.cloneStatus==="cloning")throw new i(`❌ avatar ${a} is still cloning; check with --clone-status --avatar-id ${a}`);if(s.cloneStatus==="failed")throw new i(`❌ avatar ${a} failed to clone (${s.cloneError||"unknown error"}); clone it again`);const d=R(s,l.sources),m={avatarId:a,source:d};r?m.audioUrl=r:(m.text=u,m.voiceId=c(t.voice_id)||c(s.defaultVoiceId)||l.defaultVoiceId,t.voice_name&&(m.voiceName=String(t.voice_name))),v({phase:"gen-digital-human:request",avatarId:a,source:d,mode:r?"audio":"tts"});const f=await h(o,"/digital-human/video/generate",m,{timeoutMs:6e4}),p=f.generationId;if(!p)throw new i(`❌ could not parse generationId; API returned: ${JSON.stringify(f)}`);v({phase:"gen-digital-human:async",generationId:p});const _=await D(o,p);if(!_)throw new i(`❌ video generated but no URL was returned; rerun --check-status --generation-id ${p} later`);if(n){process.stdout.write(JSON.stringify({url:_,generationId:p})+`
30
+ task status: ${n}
31
+ `),a.videoUrl&&process.stdout.write(` video URL: ${a.videoUrl}
32
+ `),n==="failed"){const o=a.errorMessage??a.error??a.message??"unknown error";process.stdout.write(` failure reason: ${o}
33
+ `)}}u(C,"checkStatus");const D=30*6e4;async function J(t,e){return await S(async()=>{const a=await p(t,"/digital-human/video/status",{generationId:e},{timeoutMs:3e4}),n=a.status??"unknown";if(v({phase:"gen-digital-human:poll",generationId:e,status:n}),n==="completed")return a.videoUrl??"";if(n==="failed"){const o=a.errorMessage??a.error??a.message??"unknown error";throw new i(`❌ video generation failed: ${o}`)}return null},{intervalMs:5e3,timeoutMs:D}).catch(a=>{throw a instanceof i&&/polling timed out/.test(a.message)?new i(`${a.message}; still generating on the server — resume with --check-status --generation-id ${e}`):a})}u(J,"pollVideoStatus");function P(t){const e=String(t.video_url??"").trim(),r=String(t.audio_url??"").trim(),a=String(t.lipsync_mode??"speed");for(const[n,o]of[["--video-url",e],["--audio-url",r]])if(!/^https?:\/\//i.test(o))throw new i(`❌ lip-sync needs a public http(s) URL for ${n}, received: ${o}`);if(a!=="speed"&&a!=="precision")throw new i(`❌ --lipsync-mode must be speed or precision, received: ${a}`);return{videoUrl:e,audioUrl:r,mode:a}}u(P,"buildLipsyncRequest");async function H(t,e){const r=await M(e.skillName,{apiBaseUrl:t.api_base_url,privateToken:t.priv_token,preflightToken:e.auth?.token}),a=g(t.json_output);if(g(t.list_avatars))return O(r,t);if(g(t.clone))return T(r,t);if(g(t.clone_status)){const h=c(t.avatar_id);if(!h)throw new i("❌ clone-status mode requires --avatar-id");return j(r,h,a)}if(g(t.check_status)){const h=I(t.generation_id,"generation_id");if(h===void 0)throw new i("❌ check-status mode requires --generation-id");return C(r,h,a)}if(t.video_url){const h=P(t);v({phase:"gen-digital-human:request",mode:"lipsync",lipsyncMode:h.mode});const _=await p(r,"/digital-human/lipsync/generate",h,{timeoutMs:6e4});if(!_.generationId)throw new i(`❌ could not parse generationId; API returned: ${JSON.stringify(_)}`);return U(r,_.generationId,a)}const n=c(t.avatar_id);if(!n)throw new i("❌ provide --avatar-id to pick the avatar (use --list-avatars to see available avatars)");const o=(t.audio_url??"").trim(),f=t.text??"";if(o&&!/^https?:\/\//i.test(o))throw new i(`❌ --audio-url needs a public http(s) URL (local files are not uploaded), received: ${o}`);if(!o&&!f.trim())throw new i("❌ in TTS mode provide --text, or use --audio-url for audio-driven mode");const l=(await N(t.api_base_url)).digitalHuman;v({phase:"gen-digital-human:resolve-avatar",avatarId:n});const s=await R(r,n);if(s.cloneStatus==="cloning")throw new i(`❌ avatar ${n} is still cloning; check with --clone-status --avatar-id ${n}`);if(s.cloneStatus==="failed")throw new i(`❌ avatar ${n} failed to clone (${s.cloneError||"unknown error"}); clone it again`);const d=L(s,l.sources),m={avatarId:n,source:d};o?m.audioUrl=o:(m.text=f,m.voiceId=c(t.voice_id)||c(s.defaultVoiceId)||l.defaultVoiceId,t.voice_name&&(m.voiceName=String(t.voice_name))),v({phase:"gen-digital-human:request",avatarId:n,source:d,mode:o?"audio":"tts"});const w=await p(r,"/digital-human/video/generate",m,{timeoutMs:6e4}),$=w.generationId;if(!$)throw new i(`❌ could not parse generationId; API returned: ${JSON.stringify(w)}`);return U(r,$,a)}u(H,"genDigitalHuman");async function U(t,e,r){v({phase:"gen-digital-human:async",generationId:e});const a=await J(t,e);if(!a)throw new i(`❌ video generated but no URL was returned; rerun --check-status --generation-id ${e} later`);if(r){process.stdout.write(JSON.stringify({url:a,generationId:e})+`
34
34
  `);return}process.stdout.write(`
35
35
  🔗 Video URL (renders directly, no download needed):
36
- ${_}
36
+ ${a}
37
37
  `),process.stdout.write(`
38
38
  🎉 Digital-human video generation done!
39
- `)}w(q,"genDigitalHuman");export{q as genDigitalHuman};
39
+ `)}u(U,"finishGeneration");export{P as buildLipsyncRequest,H as genDigitalHuman};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.13.9",
4
- "generatedAt": "2026-11-03T15:23:12.126Z",
3
+ "version": "0.13.10",
4
+ "generatedAt": "2026-11-05T14:34:43.632Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
@@ -269,6 +269,18 @@
269
269
  "type": "string",
270
270
  "description": "Narration text (TTS mode, ≤ 10000 chars). Synthesized with voice_id, then drives the avatar; the speech must last at least 5 s. Ignored when audio_url is given."
271
271
  },
272
+ "video_url": {
273
+ "type": "string",
274
+ "description": "Lip-sync mode: public http(s) URL of the video to re-voice. Needs audio_url. Replaces avatar_id."
275
+ },
276
+ "lipsync_mode": {
277
+ "type": "string",
278
+ "enum": [
279
+ "speed",
280
+ "precision"
281
+ ],
282
+ "description": "Lip-sync quality, default speed. precision costs about 4x as much."
283
+ },
272
284
  "audio_url": {
273
285
  "type": "string",
274
286
  "description": "Audio-driven mode: public http(s) URL of mp3 / m4a / wav, 5 s – 30 min, ≤ 100 MB. Takes precedence over text / voice_id."
@@ -301,7 +313,9 @@
301
313
  "avatar_id",
302
314
  "text",
303
315
  "voice_id",
304
- "audio_url"
316
+ "audio_url",
317
+ "video_url",
318
+ "lipsync_mode"
305
319
  ],
306
320
  "advanced": [],
307
321
  "hidden": [
@@ -625,6 +639,43 @@
625
639
  "type": "string",
626
640
  "description": "Media fill mode for templates declaring capabilities.mediaBinding.options.fit; one of that option's enum in the template's customPayloadSchema (its descriptions explain each value). Beats the preset's own fit. **Must** pass when the user picked it: `显示方式: <名称>(<id>)` → pass `<id>`; `auto` keeps the template / preset default."
627
641
  },
642
+ "source": {
643
+ "type": "string",
644
+ "description": "Presenter source, for templates that declare capabilities.sources: digital-human, lip-sync or real-footage. Omit to use the template's default."
645
+ },
646
+ "template_style": {
647
+ "type": "string",
648
+ "description": "Visual style for templates that ship several, e.g. talking-head-studio: douyin-bold, news-anchor, live-ecommerce."
649
+ },
650
+ "presenter_video": {
651
+ "type": "string",
652
+ "description": "URL of the user's talking-head video. Required with source real-footage or lip-sync."
653
+ },
654
+ "lipsync_mode": {
655
+ "type": "string",
656
+ "enum": [
657
+ "speed",
658
+ "precision"
659
+ ],
660
+ "description": "Lip-sync quality for source lip-sync, default speed. precision costs about 4x as much."
661
+ },
662
+ "broll_asset": {
663
+ "type": "array",
664
+ "items": {
665
+ "type": "string"
666
+ },
667
+ "description": "URLs of images or videos the user picked as b-roll from their asset library (the replicate card's 空镜素材 asset lines), on any source. Not scene_images: scene_images treats every entry as an image."
668
+ },
669
+ "broll_mode": {
670
+ "type": "string",
671
+ "enum": [
672
+ "auto",
673
+ "ai-image",
674
+ "ai-video",
675
+ "none"
676
+ ],
677
+ "description": "Scenes without a broll_asset (the replicate card's 其余画面): auto (the visual style's default), ai-image, ai-video (at most 3 scenes, the rest stay images) or none."
678
+ },
628
679
  "narration": {
629
680
  "type": "string",
630
681
  "enum": [
@@ -770,6 +821,10 @@
770
821
  "background_preset",
771
822
  "sequence_preset",
772
823
  "sequence_fit",
824
+ "source",
825
+ "template_style",
826
+ "presenter_video",
827
+ "lipsync_mode",
773
828
  "narration",
774
829
  "background_asset",
775
830
  "payload",
@@ -787,6 +842,8 @@
787
842
  "subheadline",
788
843
  "carousel_items",
789
844
  "scene_images",
845
+ "broll_asset",
846
+ "broll_mode",
790
847
  "caption_lines",
791
848
  "stub_image_url",
792
849
  "stub_video_url",
@@ -1171,6 +1228,10 @@
1171
1228
  "minimum": 1,
1172
1229
  "description": "Render job id (positive integer, e.g. 8). This is the integer N parsed from prepare_video_assets's stdout line '📦 render job jobId: N' (also tolerated: legacy zh '📦 渲染任务 jobId: N'). Never pass 0, a placeholder string, or descriptive text."
1173
1230
  },
1231
+ "restyle": {
1232
+ "type": "string",
1233
+ "description": "Render the same video again in another visual style (a template_style id). Reuses the script, assets and timing, so nothing is regenerated. Only for templates with visual styles such as talking-head-studio."
1234
+ },
1174
1235
  "save_job": {
1175
1236
  "type": "boolean",
1176
1237
  "description": "Persist the render Manifest back to the database under the same jobId (default true). Auto-degrades to false when PRIV_TOKEN is missing. Pass false to opt out explicitly."
@@ -1205,6 +1266,7 @@
1205
1266
  "job_id"
1206
1267
  ],
1207
1268
  "advanced": [
1269
+ "restyle",
1208
1270
  "save_job",
1209
1271
  "upload_title",
1210
1272
  "no_upload",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.13.9",
3
+ "version": "0.13.10",
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",
@@ -116,6 +116,8 @@ remixmate gen-digital-human --clone-status --avatar-id dh_xxx
116
116
  | `--voice-id` | generate | Voice id (text-driven) | the avatar's own voice (curated suggestion / HeyGen twin's cloned voice), else the service default |
117
117
  | `--voice-name` | generate | Voice display name (recordkeeping only) | none |
118
118
  | `--audio-url` | generate | Audio URL (audio-driven) | none |
119
+ | `--video-url` | generate | Lip-sync an existing video instead of an avatar; needs `--audio-url` | none |
120
+ | `--lipsync-mode` | generate | `speed` or `precision` (about 4x the price) | `speed` |
119
121
  | `--check-status` / `--generation-id` | status | Poll a generation job | — |
120
122
  | `--clone` / `--clone-url` | clone | Clone mode and source URL (required) | — |
121
123
  | `--clone-type` | clone | `video` / `image` | `video` |
@@ -95,6 +95,15 @@
95
95
  "type": "string",
96
96
  "description": "Narration text (TTS mode, ≤ 10000 chars). Synthesized with voice_id, then drives the avatar; the speech must last at least 5 s. Ignored when audio_url is given."
97
97
  },
98
+ "video_url": {
99
+ "type": "string",
100
+ "description": "Lip-sync mode: public http(s) URL of the video to re-voice. Needs audio_url. Replaces avatar_id."
101
+ },
102
+ "lipsync_mode": {
103
+ "type": "string",
104
+ "enum": ["speed", "precision"],
105
+ "description": "Lip-sync quality, default speed. precision costs about 4x as much."
106
+ },
98
107
  "audio_url": {
99
108
  "type": "string",
100
109
  "description": "Audio-driven mode: public http(s) URL of mp3 / m4a / wav, 5 s – 30 min, ≤ 100 MB. Takes precedence over text / voice_id."
@@ -127,7 +136,9 @@
127
136
  "avatar_id",
128
137
  "text",
129
138
  "voice_id",
130
- "audio_url"
139
+ "audio_url",
140
+ "video_url",
141
+ "lipsync_mode"
131
142
  ],
132
143
  "advanced": [],
133
144
  "hidden": [
@@ -487,6 +487,12 @@ python3 <SkillDir>/scripts/gen_script.py \
487
487
  | `--sequence-preset` | Style preset for templates declaring `capabilities.mediaBinding.options.preset`; one of that option's enum in the template's `customPayloadSchema` (`none` keeps the template defaults). Written next to the media list; the preset sets transitions, motion, text layout and pacing, and the scene duration follows. **Must** pass when the user picked a style preset on the replicate card (pass the id in parentheses, e.g. `风格预设: <名称>(<id>)` → `<id>`); otherwise the choice is silently dropped. Rejected on templates without that option. |
488
488
  | `--sequence-fit` | Fill mode for templates declaring `capabilities.mediaBinding.options.fit`; one of that option's enum (`auto` keeps the template / preset default). Beats the preset's own fit. **Must** pass when the user picked it on the replicate card (`显示方式: <名称>(<id>)` → `<id>`). |
489
489
  | `--narration` | `auto` (the template's `capabilities.needsNarration` decides), `on` or `off`. Templates where narration is optional default to none; **must** pass `on` when the user picked narration on the replicate card (`旁白: <名称>(narrated)`), otherwise the narration written later is dropped without an error. `on` adds the narration slot, a `gen-voice` asset and subtitles; on `fit-images` templates that declare `customPayload.minDurationSec` it also writes the media's natural length there, so a short narration does not compress the media (a longer one stretches the scene). |
490
+ | `--template-style` | Visual style for templates that ship several (`talking-head-studio`: `douyin-bold`, `news-anchor`, `live-ecommerce`, `minimal-talk`, `interview-serif`, `gold-quote`, `lecture`, `soft-glow`, `cinema-talk`). |
491
+ | `--presenter-video` | URL of the user's talking-head video, with `--source real-footage` (used as is, no cutting) or `--source lip-sync` (re-voiced with the script). |
492
+ | `--broll-asset` | Repeatable. URL of an image or video the user picked as b-roll from their asset library (replicate card field `空镜素材`). Works on every `--source`. Use the description on the asset line to anchor it to the sentence it matches with `atText`; if one fits nowhere, remove it and tell the user which. |
493
+ | `--broll-mode` | Scenes with no `--broll-asset`: `auto` (the visual style's default), `ai-image`, `ai-video` (at most 3 scenes, the rest stay images) or `none`. Pass the replicate card field `其余画面`. |
494
+ | `--lipsync-mode` | `speed` (default) or `precision` (about 4x the price), with `--source lip-sync`. |
495
+ | `--source` | Presenter source for templates that declare `capabilities.sources`: `digital-human`, `lip-sync` or `real-footage`. Omit to use the template's default. Writes `global.presenter.source` and `global.configLock`; capabilities, `llmHint` and the payload schema then follow that source. |
490
496
  | `--background-asset` | URL of the user's background image or video (`.mp4/.mov/.webm/.m4v` → video). Added as existing asset `asset-background`; every scene's `customPayload.background` gets `preset: image` + `assetRef`, or `preset: video` + `videoAssetRef`. Conflicts with a different `--background-preset`; rejected when the template's background enum lacks that kind. | none |
491
497
  | `--payload KEY=VALUE` | Template-specific card choice, repeatable. `KEY` is a dotted path in `customPayloadSchema` (or `capabilities.frameLevelKeys`), e.g. `<field>=<value>`, `<object>.<field>=<value>`. Value is converted to the declared type (true/false, numbers) and checked against enum / range; undeclared keys are rejected. Written to every scene with sibling keys kept, applied before fit-caption duration estimation. | template default |
492
498
  | `--ending-payload KEY=VALUE` | Same as `--payload`, but written only to the ending scene, e.g. `<object>.<field>=<closing line>`. On countdown templates it also appends an ending scene after #1 (it carries the #1 index; fill its narration and the item field `capabilities.countdown.itemProp` names). Errors when the script has no ending scene. | none |