@remixmate/cli 0.12.8 → 0.12.9
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/dist/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.12.
|
|
4
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "0.12.9",
|
|
4
|
+
"generatedAt": "2026-10-21T03:02:36.247Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -625,6 +625,17 @@
|
|
|
625
625
|
],
|
|
626
626
|
"description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`."
|
|
627
627
|
},
|
|
628
|
+
"ending_payload": {
|
|
629
|
+
"type": "array",
|
|
630
|
+
"items": {
|
|
631
|
+
"type": "string"
|
|
632
|
+
},
|
|
633
|
+
"description": "Like payload, but `KEY=VALUE` is written only to the ending scene and locked there (e.g. `recap.cta=关注我,每周更新开源热榜` for github-repo-rank's 片尾引导). On countdown templates (Top-N lists) it also appends the ending scene after #1 — fill that scene's narration (a closing line) and its customPayload.item with the #1 entry. Without it a countdown ends on the #1 card. Requires template_id."
|
|
634
|
+
},
|
|
635
|
+
"avatar_id": {
|
|
636
|
+
"type": "string",
|
|
637
|
+
"description": "Digital-human avatar id (`dh_xxx`, the replicate card's 数字人主讲 / 数字人讲师 line) for templates declaring capabilities.digitalHuman (image-slide, ppt-to-video, html-slide, …). Adds one gen-digital-human asset referenced by every scene's visuals.avatar.assetRef: one continuous presenter whose lips follow the narration. Placement stays the template default; adjust with payload `digitalHuman.<field>=…`. Copy the id verbatim, never invent one. Costs digital-human credits, so pass it only when the user picked an avatar. Requires template_id."
|
|
638
|
+
},
|
|
628
639
|
"font_id": {
|
|
629
640
|
"type": "string",
|
|
630
641
|
"description": "Font library key (font.uniq_id) for the on-screen text and subtitles. Written to global.font.fontId; match_template folds it into the binding's typography and ab-render resolves and installs the real font before rendering. **Must** pass when the user picked a font: the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)` → pass the id in the parentheses. Pass the opaque key, never a CSS family name or the display name — those fall back to the template font with no error. When the user names a font without its id, call gen_script with list_fonts=true first and take the id from that list; do not invent ids. Without it the choice is silently dropped and the video renders in the template's own font. Omit to follow the template's font pairing."
|
|
@@ -728,6 +739,8 @@
|
|
|
728
739
|
"sequence_fit",
|
|
729
740
|
"background_asset",
|
|
730
741
|
"payload",
|
|
742
|
+
"ending_payload",
|
|
743
|
+
"avatar_id",
|
|
731
744
|
"motion",
|
|
732
745
|
"font_id",
|
|
733
746
|
"font_name",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
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",
|
|
@@ -486,6 +486,8 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
486
486
|
| `--sequence-fit` | Image-sequence fill mode, one of `customPayloadSchema.sequence.fit` (`cover` fill and crop / `contain` whole picture over a blurred backdrop), written to `customPayload.sequence.fit`; beats the preset's fit. `auto` = template / preset default. | template / preset default |
|
|
487
487
|
| `--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 |
|
|
488
488
|
| `--payload KEY=VALUE` | Template-specific card choice, repeatable. `KEY` is a dotted path in `customPayloadSchema` (or `capabilities.frameLevelKeys`), e.g. `grade=四年级`, `watermarkText=…`, `preset=course`, `carousel.fit=cover`, `caption.charsPerSec=20`, `templateData.revealMode=progressive`. 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 |
|
|
489
|
+
| `--ending-payload KEY=VALUE` | Same as `--payload`, but written only to the ending scene, e.g. `recap.cta=关注我,每周更新开源热榜`. On countdown templates it also appends an ending scene after #1 (it carries `item.rank=1`; fill its narration and `item`). Errors when the script has no ending scene. | none |
|
|
490
|
+
| `--avatar-id` | Digital-human avatar id (`dh_xxx`) for templates declaring `capabilities.digitalHuman`. Adds asset `asset-avatar` (`type: avatar`, `source: gen-digital-human`, `payload.avatarId`) and sets `visuals.avatar.assetRef` on every scene whose slot reads it — one continuous presenter driven by the narration. Placement: template default, or `--payload digitalHuman.<field>=…` (a photo-only presenter is `--payload digitalHuman.posterUrl=<url>` without this flag). | none |
|
|
489
491
|
| `--motion` | Camera motion for every scene (`animationHints.motion`): `static` / `kenburns-in` / `kenburns-out` / `pan-left` / `pan-right` / `float`. | skeleton alternates in / out |
|
|
490
492
|
| `--font-id` | Font library key (`font.uniq_id`), written to `global.font.fontId`; ab-render resolves and installs the real font at render time. Pass it whenever the user picked a font — the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)`; pass the id in the parentheses. Never pass a CSS family or the display name: an unknown key falls back to the template font with no error. | template font |
|
|
491
493
|
| `--font-name` | Display name of the picked font → `global.font.fontName` (display only, never used for rendering). Pass it with `--font-id`. | — |
|
|
@@ -503,7 +505,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
503
505
|
| `--scene-images` | Repeatable. One scene's visual for a **`visual-overlay`** template, mapped **positionally**. Each entry is a URL (that existing asset) or `ai` / `ai:<prompt>` (that scene goes to gen-image, optionally with the user's own prompt) — so an AI scene can sit anywhere in the order. Scenes past the last entry still get AI images. Scene count is raised (never lowered) to fit the entries when `--scenes` is absent. **Required** whenever the user supplies images for such a template — omitting it silently ignores every image they gave. | — |
|
|
504
506
|
| `--caption-lines` | Repeatable. On-screen caption line for `carousel-caption` templates → `customPayload.caption.lines`. Line limits and `**emphasis**` support are declared per template (see its `llmHint`). **Required** for `durationStrategy: fit-caption` templates — write the lines yourself if the user did not supply them. Optional only for `fit-images` templates. | — |
|
|
505
507
|
|
|
506
|
-
Values written by `--background-preset`, `--background-asset`, `--payload` and `--motion` are listed in `meta.lockedScenePaths` (scene-relative paths such as `customPayload.carousel.fit`, `animationHints.motion`). When the agent later saves its own `dsl_json` in merge mode, ab-agent restores those paths from this skeleton after merging, so a model-written `carousel` / `templateData` / `background` object cannot wipe a card choice. Do not hand-write these keys in `dsl_json`; pass the flag.
|
|
508
|
+
Values written by `--background-preset`, `--background-asset`, `--payload`, `--ending-payload`, `--avatar-id` and `--motion` are listed in `meta.lockedScenePaths` (scene-relative paths such as `customPayload.carousel.fit`, `animationHints.motion`). When the agent later saves its own `dsl_json` in merge mode, ab-agent restores those paths from this skeleton after merging, so a model-written `carousel` / `templateData` / `background` object cannot wipe a card choice. Do not hand-write these keys in `dsl_json`; pass the flag. Templates that author their own scenes (`meta.authoredScenes`, e.g. screen-walkthrough) are not merged: there ab-agent writes each locked value that the skeleton holds on every scene (with one value) onto every submitted scene, and adds the skeleton assets those values reference (`asset-background`, `asset-avatar`) when the submitted `assets` lacks them.
|
|
507
509
|
|
|
508
510
|
## DSL generation principles
|
|
509
511
|
|
|
@@ -1430,7 +1430,12 @@ def _build_custom_payload(template_config: dict | None) -> dict:
|
|
|
1430
1430
|
return {}
|
|
1431
1431
|
|
|
1432
1432
|
|
|
1433
|
-
def _plan_countdown_scenes(
|
|
1433
|
+
def _plan_countdown_scenes(
|
|
1434
|
+
spec: dict, count: int, duration: float, headline: str, ending: bool = False
|
|
1435
|
+
) -> list:
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
|
|
1434
1439
|
|
|
1435
1440
|
|
|
1436
1441
|
|
|
@@ -1440,11 +1445,14 @@ def _plan_countdown_scenes(spec: dict, count: int, duration: float, headline: st
|
|
|
1440
1445
|
|
|
1441
1446
|
|
|
1442
1447
|
head_sec = countdown_opening_sec(spec, count, duration)
|
|
1443
|
-
beat_sec = (duration - head_sec) / count
|
|
1444
|
-
|
|
1448
|
+
beat_sec = (duration - head_sec) / (count + (1 if ending else 0))
|
|
1449
|
+
plans = [{"purpose": "opening", "duration": head_sec, "label": headline}] + [
|
|
1445
1450
|
{"purpose": "point", "duration": beat_sec, "label": f"#{index}", "countdownIndex": index}
|
|
1446
1451
|
for index in range(count, 0, -1)
|
|
1447
1452
|
]
|
|
1453
|
+
if ending:
|
|
1454
|
+
plans.append({"purpose": "ending", "duration": beat_sec, "label": "#1", "countdownIndex": 1})
|
|
1455
|
+
return plans
|
|
1448
1456
|
|
|
1449
1457
|
|
|
1450
1458
|
def _warn_on_count_mismatch(spec: dict, headline: str, subheadline: str, count: int) -> None:
|
|
@@ -1797,6 +1805,102 @@ def _write_payload(custom_payload: dict, writes: PayloadWrites) -> None:
|
|
|
1797
1805
|
node[parts[-1]] = value
|
|
1798
1806
|
|
|
1799
1807
|
|
|
1808
|
+
def _template_has_ending_slot(template_config: dict | None) -> bool:
|
|
1809
|
+
return isinstance(((template_config or {}).get("slotMapping") or {}).get("ending"), dict)
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
def _apply_ending_payload(
|
|
1813
|
+
dsl: dict,
|
|
1814
|
+
template_config: dict | None,
|
|
1815
|
+
template_id: str | None,
|
|
1816
|
+
payload_specs: list[str] | None,
|
|
1817
|
+
) -> None:
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
|
|
1825
|
+
if not payload_specs:
|
|
1826
|
+
return
|
|
1827
|
+
if not template_id or template_config is None:
|
|
1828
|
+
raise ValueError("--ending-payload needs --template-id: the allowed keys come from the template.")
|
|
1829
|
+
try:
|
|
1830
|
+
writes = _parse_payload_specs(template_config, payload_specs)
|
|
1831
|
+
except ValueError as exc:
|
|
1832
|
+
raise ValueError(str(exc).replace("--payload ", "--ending-payload ", 1)) from None
|
|
1833
|
+
endings = [s for s in dsl.get("scenes", []) if s.get("purpose") == "ending"]
|
|
1834
|
+
if not endings:
|
|
1835
|
+
raise ValueError(
|
|
1836
|
+
"--ending-payload: this script has no ending scene to write to "
|
|
1837
|
+
f"(scene purposes: {', '.join(str(s.get('purpose')) for s in dsl.get('scenes', []))})."
|
|
1838
|
+
)
|
|
1839
|
+
for scene in endings:
|
|
1840
|
+
_write_payload(scene.setdefault("customPayload", {}), writes)
|
|
1841
|
+
for parts, _ in writes:
|
|
1842
|
+
_lock_scene_path(dsl, "customPayload." + ".".join(parts))
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
AVATAR_ASSET_ID = "asset-avatar"
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
def _slot_extracts(template_config: dict | None, purpose: str, source: str) -> bool:
|
|
1849
|
+
|
|
1850
|
+
mapping = (template_config or {}).get("slotMapping")
|
|
1851
|
+
if not isinstance(mapping, dict):
|
|
1852
|
+
return False
|
|
1853
|
+
slot = mapping.get(purpose) or mapping.get("default") or {}
|
|
1854
|
+
seen: set = set()
|
|
1855
|
+
while isinstance(slot, dict) and "$ref" in slot and slot["$ref"] not in seen:
|
|
1856
|
+
seen.add(slot["$ref"])
|
|
1857
|
+
slot = mapping.get(slot["$ref"]) or {}
|
|
1858
|
+
extractors = slot.get("propExtractors") if isinstance(slot, dict) else None
|
|
1859
|
+
return isinstance(extractors, dict) and any(
|
|
1860
|
+
isinstance(ex, dict) and ex.get("from") == source for ex in extractors.values()
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
def _apply_avatar(
|
|
1865
|
+
dsl: dict,
|
|
1866
|
+
template_config: dict | None,
|
|
1867
|
+
template_id: str | None,
|
|
1868
|
+
avatar_id: str | None,
|
|
1869
|
+
) -> None:
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
if not avatar_id:
|
|
1877
|
+
return
|
|
1878
|
+
if not template_id or template_config is None:
|
|
1879
|
+
raise ValueError("--avatar-id needs --template-id: the template decides where the presenter shows.")
|
|
1880
|
+
support = ((template_config.get("capabilities") or {}).get("digitalHuman") or {}).get("support")
|
|
1881
|
+
if support not in ("optional", "required"):
|
|
1882
|
+
raise ValueError(f"--avatar-id: template {template_id!r} does not declare capabilities.digitalHuman")
|
|
1883
|
+
scenes = [
|
|
1884
|
+
s for s in dsl.get("scenes", [])
|
|
1885
|
+
if _slot_extracts(template_config, str(s.get("purpose")), "visuals.avatar.assetRef")
|
|
1886
|
+
]
|
|
1887
|
+
if not scenes:
|
|
1888
|
+
raise ValueError(f"--avatar-id: no scene of template {template_id!r} reads visuals.avatar.assetRef")
|
|
1889
|
+
if not any((s.get("audio") or {}).get("narration") for s in scenes):
|
|
1890
|
+
raise ValueError("--avatar-id: the presenter is driven by the narration, and this script has none")
|
|
1891
|
+
for scene in scenes:
|
|
1892
|
+
visuals = scene.get("visuals") if isinstance(scene.get("visuals"), dict) else {}
|
|
1893
|
+
scene["visuals"] = {**visuals, "avatar": {"assetRef": AVATAR_ASSET_ID}}
|
|
1894
|
+
dsl.setdefault("assets", []).append({
|
|
1895
|
+
"assetId": AVATAR_ASSET_ID,
|
|
1896
|
+
"type": "avatar",
|
|
1897
|
+
"source": "gen-digital-human",
|
|
1898
|
+
"status": "planned",
|
|
1899
|
+
"payload": {"avatarId": avatar_id},
|
|
1900
|
+
})
|
|
1901
|
+
_lock_scene_path(dsl, "visuals.avatar.assetRef")
|
|
1902
|
+
|
|
1903
|
+
|
|
1800
1904
|
def build_dsl(
|
|
1801
1905
|
topic: str,
|
|
1802
1906
|
duration: int,
|
|
@@ -1824,6 +1928,7 @@ def build_dsl(
|
|
|
1824
1928
|
sequence_preset: str | None = None,
|
|
1825
1929
|
sequence_fit: str | None = None,
|
|
1826
1930
|
payload_writes: PayloadWrites | None = None,
|
|
1931
|
+
countdown_ending: bool = False,
|
|
1827
1932
|
) -> dict:
|
|
1828
1933
|
|
|
1829
1934
|
|
|
@@ -1862,6 +1967,10 @@ def build_dsl(
|
|
|
1862
1967
|
)
|
|
1863
1968
|
beat_count = resolve_countdown_count(countdown, item_count, scene_count) if countdown else None
|
|
1864
1969
|
|
|
1970
|
+
countdown_ending = bool(countdown_ending and countdown)
|
|
1971
|
+
if countdown_ending and not _template_has_ending_slot(template_config):
|
|
1972
|
+
raise ValueError(f"--ending-payload: template {template_id!r} has no ending slot")
|
|
1973
|
+
|
|
1865
1974
|
primary_visual_type = contract["primary_visual_type"]
|
|
1866
1975
|
payload_style = contract["payload_style"]
|
|
1867
1976
|
|
|
@@ -1980,7 +2089,9 @@ def build_dsl(
|
|
|
1980
2089
|
provided_slots = [s.strip() for s in (scene_images or []) if s and s.strip()]
|
|
1981
2090
|
|
|
1982
2091
|
if beat_count is not None:
|
|
1983
|
-
scene_plans = _plan_countdown_scenes(
|
|
2092
|
+
scene_plans = _plan_countdown_scenes(
|
|
2093
|
+
countdown, beat_count, duration, effective_headline, ending=countdown_ending
|
|
2094
|
+
)
|
|
1984
2095
|
else:
|
|
1985
2096
|
scene_plans = _plan_contract_scenes(contract, topic, duration, scene_count, output_language)
|
|
1986
2097
|
|
|
@@ -2465,6 +2576,8 @@ Examples:
|
|
|
2465
2576
|
"pan-left / pan-right / float. Replaces the skeleton's alternating default.",
|
|
2466
2577
|
)
|
|
2467
2578
|
parser.add_argument("--allow-digital-human", action="store_true", help="Allow digital-human assets")
|
|
2579
|
+
parser.add_argument("--avatar-id", default=None, help="Digital-human avatar id (dh_xxx) for templates declaring capabilities.digitalHuman: adds one gen-digital-human asset referenced by every scene's visuals.avatar.assetRef (one continuous presenter driven by the narration). Placement stays the template default unless set with --payload digitalHuman.<field>=...")
|
|
2580
|
+
parser.add_argument("--ending-payload", action="append", default=None, metavar="KEY=VALUE", help="Like --payload, but written only to the ending scene (e.g. recap.cta=关注我,每周更新开源热榜). On countdown templates it also appends the ending scene after #1. Repeatable.")
|
|
2468
2581
|
parser.add_argument("--allow-ai-video", action="store_true", help="Allow AI-generated video assets")
|
|
2469
2582
|
parser.add_argument("--template-id", default=None, help="Template id. The template owns outputLanguage and may declare a defaultVoiceId.")
|
|
2470
2583
|
parser.add_argument(
|
|
@@ -2705,6 +2818,7 @@ Examples:
|
|
|
2705
2818
|
sequence_preset=args.sequence_preset,
|
|
2706
2819
|
sequence_fit=args.sequence_fit,
|
|
2707
2820
|
payload_writes=payload_writes,
|
|
2821
|
+
countdown_ending=bool(args.ending_payload),
|
|
2708
2822
|
)
|
|
2709
2823
|
except ValueError as exc:
|
|
2710
2824
|
|
|
@@ -2723,6 +2837,8 @@ Examples:
|
|
|
2723
2837
|
args.background_asset,
|
|
2724
2838
|
)
|
|
2725
2839
|
_apply_scene_payload(dsl, look_config, args.template_id, args.payload, args.motion)
|
|
2840
|
+
_apply_ending_payload(dsl, look_config, args.template_id, args.ending_payload)
|
|
2841
|
+
_apply_avatar(dsl, look_config, args.template_id, args.avatar_id)
|
|
2726
2842
|
except ValueError as exc:
|
|
2727
2843
|
parser.error(str(exc))
|
|
2728
2844
|
|
|
@@ -87,6 +87,17 @@
|
|
|
87
87
|
],
|
|
88
88
|
"description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`."
|
|
89
89
|
},
|
|
90
|
+
"ending_payload": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"description": "Like payload, but `KEY=VALUE` is written only to the ending scene and locked there (e.g. `recap.cta=关注我,每周更新开源热榜` for github-repo-rank's 片尾引导). On countdown templates (Top-N lists) it also appends the ending scene after #1 — fill that scene's narration (a closing line) and its customPayload.item with the #1 entry. Without it a countdown ends on the #1 card. Requires template_id."
|
|
96
|
+
},
|
|
97
|
+
"avatar_id": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Digital-human avatar id (`dh_xxx`, the replicate card's 数字人主讲 / 数字人讲师 line) for templates declaring capabilities.digitalHuman (image-slide, ppt-to-video, html-slide, …). Adds one gen-digital-human asset referenced by every scene's visuals.avatar.assetRef: one continuous presenter whose lips follow the narration. Placement stays the template default; adjust with payload `digitalHuman.<field>=…`. Copy the id verbatim, never invent one. Costs digital-human credits, so pass it only when the user picked an avatar. Requires template_id."
|
|
100
|
+
},
|
|
90
101
|
"font_id": {
|
|
91
102
|
"type": "string",
|
|
92
103
|
"description": "Font library key (font.uniq_id) for the on-screen text and subtitles. Written to global.font.fontId; match_template folds it into the binding's typography and ab-render resolves and installs the real font before rendering. **Must** pass when the user picked a font: the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)` → pass the id in the parentheses. Pass the opaque key, never a CSS family name or the display name — those fall back to the template font with no error. When the user names a font without its id, call gen_script with list_fonts=true first and take the id from that list; do not invent ids. Without it the choice is silently dropped and the video renders in the template's own font. Omit to follow the template's font pairing."
|
|
@@ -190,6 +201,8 @@
|
|
|
190
201
|
"sequence_fit",
|
|
191
202
|
"background_asset",
|
|
192
203
|
"payload",
|
|
204
|
+
"ending_payload",
|
|
205
|
+
"avatar_id",
|
|
193
206
|
"motion",
|
|
194
207
|
"font_id",
|
|
195
208
|
"font_name",
|