@remixmate/cli 0.9.72 → 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 -192
- 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 +44 -3
- 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-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 -22
- 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,9 +1,9 @@
|
|
|
1
|
-
"""Validate template-required scene props before asset generation or rendering.
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -11,14 +11,14 @@ import math
|
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
17
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "template-registry" / "scripts"))
|
|
18
18
|
|
|
19
|
-
from countdown_spec import resolve_countdown_spec
|
|
19
|
+
from countdown_spec import resolve_countdown_spec
|
|
20
20
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "template-registry"))
|
|
21
|
-
from video_dsl.runtime.payload_contract import required_payload_errors
|
|
21
|
+
from video_dsl.runtime.payload_contract import required_payload_errors
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def _payload_prop(props: dict, key: str):
|
|
@@ -35,11 +35,11 @@ def _payload_prop(props: dict, key: str):
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _required_value_errors(value, schema: dict, path: str) -> list[str]:
|
|
38
|
-
"""Check required values only, using the template's declared shape and bounds.
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
43
|
if value is None or (isinstance(value, str) and not value.strip()):
|
|
44
44
|
return [path]
|
|
45
45
|
kind = schema.get("type")
|
|
@@ -67,10 +67,10 @@ def _required_value_errors(value, schema: dict, path: str) -> list[str]:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def validate_required_props(template: dict, rows: list[dict]) -> None:
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
required_by_composition: dict[str, set[str]] = {}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
|
|
73
|
+
|
|
74
74
|
for slot in (template.get("slotMapping") or {}).values():
|
|
75
75
|
if not isinstance(slot, dict) or not slot.get("compositionId"):
|
|
76
76
|
continue
|
|
@@ -102,29 +102,29 @@ def validate_required_props(template: dict, rows: list[dict]) -> None:
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
def _index_of(row: dict, spec: dict):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
113
|
item = _payload_prop(row.get("props") or {}, spec["item_prop"])
|
|
114
114
|
return item.get(spec["index_prop"]) if isinstance(item, dict) else None
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def _validate_countdown(template: dict, rows: list[dict]) -> None:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
128
|
spec = resolve_countdown_spec(template)
|
|
129
129
|
if spec is None:
|
|
130
130
|
return
|
|
@@ -134,7 +134,7 @@ def _validate_countdown(template: dict, rows: list[dict]) -> None:
|
|
|
134
134
|
ending_id = (slots.get("ending") or {}).get("compositionId")
|
|
135
135
|
points = [row for row in rows if row.get("compositionId") == point_id]
|
|
136
136
|
count_props = [spec["count_prop"], *spec["legacy_count_props"]]
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
declared = []
|
|
139
139
|
for row in rows:
|
|
140
140
|
props = row.get("props") or {}
|
|
@@ -163,7 +163,7 @@ def _validate_countdown(template: dict, rows: list[dict]) -> None:
|
|
|
163
163
|
)
|
|
164
164
|
if not rows or rows[0].get("compositionId") != opening_id or sum(row.get("compositionId") == opening_id for row in rows) != 1:
|
|
165
165
|
raise ValueError("Countdown requires exactly one opening before the beats")
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
endings = [row for row in rows if row.get("compositionId") == ending_id]
|
|
168
168
|
if len(endings) > 1 or (endings and (rows[-1] is not endings[0] or _index_of(endings[0], spec) != 1)):
|
|
169
169
|
raise ValueError("Countdown ending may only hold the #1 scene after the countdown")
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
视频上传工具 - 将本地 MP4 文件通过 multipart/form-data 上传到 ab-api,
|
|
4
|
-
获取可用 URL(直传阿里云 OSS,同步返回)。
|
|
5
2
|
|
|
6
|
-
默认行为:
|
|
7
|
-
读取本地文件,以 multipart/form-data 形式 POST 到
|
|
8
|
-
`<MM_API_BASE_URL>/file/uploadMediaFile`,解析响应 JSON,输出 fileUrl。
|
|
9
3
|
|
|
10
|
-
用法:
|
|
11
|
-
python3 upload_video.py --file output/video.mp4 --role final_video
|
|
12
|
-
python3 upload_video.py --file output/video.mp4 --role final_video --title "我的视频"
|
|
13
|
-
python3 upload_video.py --file output/video.mp4 --role final_video --json-output
|
|
14
4
|
|
|
15
|
-
环境变量 PRIV_TOKEN 必填(接口在 ab-api 私有组,产物落素材表并归到调用者名下)。
|
|
16
5
|
|
|
17
|
-
环境变量:
|
|
18
|
-
MM_API_BASE_URL - API 根地址(默认 MM_API_BASE_URL 环境变量或 https://api-agent.remixmate.ai/api)
|
|
19
6
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
import argparse
|
|
26
26
|
import http.client
|
|
@@ -42,11 +42,11 @@ def _guess_content_type(file_path: str) -> str:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def _build_multipart_body(file_path: str, fields: dict) -> (bytes, str):
|
|
45
|
-
"""
|
|
46
|
-
手动构建 multipart/form-data body。fields 里值为空的字段不发。
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
50
|
boundary = "----MediaMakerBoundary" + uuid.uuid4().hex
|
|
51
51
|
crlf = b"\r\n"
|
|
52
52
|
parts = []
|
|
@@ -59,7 +59,7 @@ def _build_multipart_body(file_path: str, fields: dict) -> (bytes, str):
|
|
|
59
59
|
parts.append(b"")
|
|
60
60
|
parts.append(str(value).encode("utf-8"))
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
file_name = os.path.basename(file_path)
|
|
64
64
|
content_type = _guess_content_type(file_path)
|
|
65
65
|
with open(file_path, "rb") as f:
|
|
@@ -76,7 +76,7 @@ def _build_multipart_body(file_path: str, fields: dict) -> (bytes, str):
|
|
|
76
76
|
parts.append(b"")
|
|
77
77
|
parts.append(file_data)
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
parts.append(("--" + boundary + "--").encode("utf-8"))
|
|
81
81
|
parts.append(b"")
|
|
82
82
|
|
|
@@ -94,15 +94,15 @@ def upload_media_file(
|
|
|
94
94
|
role: str,
|
|
95
95
|
retention: str = None,
|
|
96
96
|
) -> dict:
|
|
97
|
-
"""
|
|
98
|
-
将本地媒体文件以 multipart/form-data 形式上传到 ab-api,并落素材表。
|
|
99
97
|
|
|
100
|
-
role 必填:素材语义角色(final_video / dh_drive_audio / source_audio ...),由调用方
|
|
101
|
-
按「这是什么」声明——ab-api 不再按文件类型猜。retention 可选(intermediate / output / user)。
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
106
|
if not role:
|
|
107
107
|
raise ValueError("upload_media_file: role is required")
|
|
108
108
|
if not os.path.exists(file_path):
|
|
@@ -118,7 +118,7 @@ def upload_media_file(
|
|
|
118
118
|
body, content_type = _build_multipart_body(
|
|
119
119
|
file_path, {"title": title, "role": role, "retention": retention}
|
|
120
120
|
)
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
headers = {
|
|
123
123
|
"Content-Type": content_type,
|
|
124
124
|
"Content-Length": str(len(body)),
|
|
@@ -129,8 +129,8 @@ def upload_media_file(
|
|
|
129
129
|
if conversation_id:
|
|
130
130
|
headers["x-conversation-id"] = conversation_id
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
|
|
133
|
+
|
|
134
134
|
parsed = urllib.parse.urlparse(url)
|
|
135
135
|
host = parsed.hostname
|
|
136
136
|
port = parsed.port
|
|
@@ -164,7 +164,7 @@ def upload_media_file(
|
|
|
164
164
|
except json.JSONDecodeError as e:
|
|
165
165
|
raise RuntimeError(f"failed to parse response: {resp_body}") from e
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
if isinstance(result, dict) and result.get("code") not in (0, 200):
|
|
169
169
|
raise RuntimeError(f"server returned an error: code={result.get('code')}, msg={result.get('msg')}")
|
|
170
170
|
|
|
@@ -217,7 +217,7 @@ def main():
|
|
|
217
217
|
if args.json_output:
|
|
218
218
|
print(json.dumps(data, ensure_ascii=False))
|
|
219
219
|
else:
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
print(data["fileUrl"])
|
|
222
222
|
|
|
223
223
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "render-video",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "473",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V37",
|
|
6
6
|
"skillDescription": "Final-render skill: loads a persisted RenderPlan by `job_id` and drives the Remotion engine to produce the final video.\n\nUse this skill as soon as the user mentions any of these intents (after assets are already prepared):\n- Render the video, composite the video, export the video\n- Turn the prepared assets into the final clip\n- Render with Remotion\n\nPrerequisite: assets must already be generated via `prepare_video_assets`. This skill never resolves or regenerates assets — pass it a `job_id` from a previous `prepare_video_assets` call.\n\n⚠️ Stop-and-confirm gate: never call this skill until the user has explicitly confirmed the assets prepared by `prepare_video_assets`. If those assets were prepared in the current turn and the user has not replied since, stop and ask instead of rendering."
|
|
7
7
|
}
|
|
@@ -86,6 +86,19 @@ When the user asks to see / browse / pick templates, they decide by *watching* s
|
|
|
86
86
|
- No sample info (older backend, or a template with no samples yet) is not an error: show the template without it.
|
|
87
87
|
- The detail page's prompt is meant to be pasted back into this chat; treat it like any user request.
|
|
88
88
|
|
|
89
|
+
### Recommend templates when the user named none
|
|
90
|
+
|
|
91
|
+
A video request without a template is a template choice the user has not made yet — not one for the agent to make silently. Shortlist with the list verb's hard constraints — they read each template's declarations, so a template that cannot serve the request is dropped with a stated reason (JSON `excluded[{templateId, reasons}]`, or an `Excluded by the current filters` block in the table):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
remixmate template-registry --json-output --filter-aspect 9:16 --filter-language zh \
|
|
95
|
+
--user-media yes # the user handed over images/videos (no = they did not)
|
|
96
|
+
# --source-doc pptx # the user gave a .pptx (none = drop templates that need one)
|
|
97
|
+
# --narration no # the user wants no voice-over
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Set only the constraints the request actually pins down; an unset one does not filter. Then rank what is left against `llmHint` / `description` / `styleTags`, and use `notFor` (a template's own list of requests it is the wrong pick for) to separate close siblings. Use coarse levels (recommended / also fits) and show 2–3 in the format above with a one-line reason each. Then end the turn and let the user pick; `gen_script` takes the answer as `--template-source user-confirmed` plus `--template-candidates`. The full rule is in the `gen-script` skill, "Agent behavior: template selection comes before the script".
|
|
101
|
+
|
|
89
102
|
### Build a binding locally (debug only, not part of the render pipeline)
|
|
90
103
|
|
|
91
104
|
```python
|
|
@@ -110,6 +123,9 @@ print(json.dumps(binding, ensure_ascii=False, indent=2))
|
|
|
110
123
|
| `--filter-tag` | Keep only templates whose `styleTags` match this substring (case-insensitive). | none |
|
|
111
124
|
| `--filter-aspect` | Keep only templates declaring this aspect ratio (e.g. `9:16`). | none |
|
|
112
125
|
| `--filter-language` | Keep only templates whose `contentLanguage` includes this code (`zh`/`en`); language-agnostic templates always show. | none |
|
|
126
|
+
| `--user-media yes\|no` | Shortlist by the template's media shape (`capabilities.payloadStyle`, else derived from `assetRequirements`): `yes` drops templates with no place for the user's own images/videos, `no` drops templates that need them. | none |
|
|
127
|
+
| `--source-doc <kind>\|none` | Shortlist by `capabilities.payloadHydration.docKind`: a kind keeps only templates built from such a document; `none` drops them. | none |
|
|
128
|
+
| `--narration yes\|no` | Shortlist by `capabilities.needsNarration` (default true). | none |
|
|
113
129
|
| `--include-beta` | Also show `status: beta` templates (same effect as `ENABLE_BETA_TEMPLATES=1`). | off |
|
|
114
130
|
| `--json-output` | List mode: emit `{ "templates": [...] }` summaries (or full definitions with `--full`) instead of the table. Detail mode (`--template-id`) is always compact JSON. Summaries carry `pageUrl` (web detail page) and `showcases: { count, pageUrl, items }`; detail mode carries top-level `pageUrls` and `showcases` maps. Each is absent when the backend does not serve it. | off |
|
|
115
131
|
| `--open <id>` | Print the template's web detail page URL (samples, layouts) and open the browser when run from an interactive terminal. Exits 1 when the backend has no link for it. Cannot be combined with `--template-id`. | — |
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
"""Shared resolver for the ``countdown`` scene strategy.
|
|
2
|
-
|
|
3
|
-
A countdown is the "one opening, then N beats of the same kind, counting down"
|
|
4
|
-
shape: Top-N lists, "5 个技巧", "7 个常见错误". It sits on the same axis as
|
|
5
|
-
``arc`` / ``single`` / ``fixed`` because it is a *shape*, not a subject — which
|
|
6
|
-
is the whole reason it is not called "ranking" and holds no GitHub-specific
|
|
7
|
-
knowledge.
|
|
8
|
-
|
|
9
|
-
None of the existing strategies could express it: ``arc`` clamps to at least 3
|
|
10
|
-
scenes, always appends a ``cta``, and numbers its beats upward; ``fixed`` needs a
|
|
11
|
-
static scene list and so cannot take a count at all.
|
|
12
|
-
|
|
13
|
-
─── Why this module is shared rather than copied ──────────────────────────────
|
|
14
|
-
Two skills need the same answer from the same declaration:
|
|
15
|
-
|
|
16
|
-
* ``gen-script`` — to lay out the scenes and write the count into each payload
|
|
17
|
-
* ``render-video`` — to validate a plan (scene count, countdown completeness)
|
|
18
|
-
before any asset is paid for
|
|
19
|
-
|
|
20
|
-
Copying the resolution into both is how the previous iteration of this feature
|
|
21
|
-
went wrong in the other direction (a Python port of the template's copy rules
|
|
22
|
-
living next to the real ones). ``skills/template-registry/scripts/`` is the
|
|
23
|
-
sanctioned home for cross-skill Python — see AGENTS.md.
|
|
24
|
-
|
|
25
|
-
─── What is declared where ───────────────────────────────────────────────────
|
|
26
|
-
The strategy carries the shape; the template carries every concrete value:
|
|
27
|
-
|
|
28
|
-
"capabilities": {
|
|
29
|
-
"sceneStrategy": "countdown",
|
|
30
|
-
"countdown": {
|
|
31
|
-
"countProp": "topN", // where the count goes in customPayload
|
|
32
|
-
"itemProp": "item", // where one beat's item goes
|
|
33
|
-
"indexProp": "rank", // the index field inside that item
|
|
34
|
-
"legacyCountProps": ["totalItems"],
|
|
35
|
-
"openingSec": 2.5, // optional, default DEFAULT_OPENING_SEC
|
|
36
|
-
"countPatterns": ["..."] // optional: regexes that find a count stated
|
|
37
|
-
// in on-screen copy (group 1 = the number)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
1
|
|
|
41
|
-
Bounds are deliberately **not** repeated here: they are read from
|
|
42
|
-
``customPayloadSchema[countProp]``'s ``minimum`` / ``maximum`` / ``default``,
|
|
43
|
-
which every template already has to author for its own payload contract. Having
|
|
44
|
-
one number in two places is how they drift.
|
|
45
2
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
52
|
|
|
53
53
|
from __future__ import annotations
|
|
54
54
|
|
|
55
55
|
import re
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
61
|
DEFAULT_OPENING_SEC = 3
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class CountdownError(ValueError):
|
|
65
|
-
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
66
70
|
|
|
67
|
-
A subclass so callers can keep treating it as the ValueError they already
|
|
68
|
-
handle, while a reader can still see that an authoring mistake and a bad
|
|
69
|
-
``--item-count`` are two different things.
|
|
70
|
-
"""
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def resolve_countdown_spec(template: dict | None) -> dict | None:
|
|
74
|
-
"""Resolve a template's countdown declaration, or None if it is not one.
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
81
|
caps = (template or {}).get("capabilities")
|
|
82
82
|
caps = caps if isinstance(caps, dict) else {}
|
|
83
83
|
if caps.get("sceneStrategy") != "countdown":
|
|
@@ -96,8 +96,8 @@ def resolve_countdown_spec(template: dict | None) -> dict | None:
|
|
|
96
96
|
)
|
|
97
97
|
count_prop = count_prop.strip()
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
|
|
100
|
+
|
|
101
101
|
names: dict[str, str] = {}
|
|
102
102
|
for key in ("itemProp", "indexProp"):
|
|
103
103
|
value = cfg.get(key)
|
|
@@ -171,12 +171,12 @@ def resolve_countdown_spec(template: dict | None) -> dict | None:
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
def stated_counts(spec: dict, text: str) -> set[int]:
|
|
174
|
-
"""Counts that on-screen copy states, found with the template's ``countPatterns``.
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
180
|
found: set[int] = set()
|
|
181
181
|
for pattern in spec.get("count_patterns") or []:
|
|
182
182
|
for match in pattern.finditer(text or ""):
|
|
@@ -187,14 +187,14 @@ def stated_counts(spec: dict, text: str) -> set[int]:
|
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
def resolve_count(spec: dict, requested: int | None, scene_count: int | None) -> int:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
198
|
count = spec["default"] if requested is None else requested
|
|
199
199
|
if type(count) is not int or not spec["minimum"] <= count <= spec["maximum"]:
|
|
200
200
|
raise ValueError(
|
|
@@ -210,26 +210,26 @@ def resolve_count(spec: dict, requested: int | None, scene_count: int | None) ->
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
def opening_sec(spec: dict, count: int, duration: float) -> float:
|
|
213
|
-
"""Seconds for the single opening scene.
|
|
214
213
|
|
|
215
|
-
``openingSec`` is an upper bound, not a fixed value: on a short video it
|
|
216
|
-
yields to an even split so a long opening budget cannot starve the beats.
|
|
217
214
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
223
|
return min(spec["opening_sec"], duration / (count + 1))
|
|
224
224
|
|
|
225
225
|
|
|
226
226
|
def scene_payload(spec: dict, count: int, index: int | None) -> dict:
|
|
227
|
-
"""The countdown part of one scene's customPayload, under declared names.
|
|
228
227
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
233
|
payload: dict = {spec["count_prop"]: count}
|
|
234
234
|
for prop in spec["legacy_count_props"]:
|
|
235
235
|
payload[prop] = count
|