@remixmate/cli 0.12.1 → 0.12.2

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
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.12.1",
4
- "generatedAt": "2026-10-12T11:13:40.014Z",
3
+ "version": "0.12.2",
4
+ "generatedAt": "2026-10-12T13:04:48.879Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
@@ -556,7 +556,11 @@
556
556
  "properties": {
557
557
  "topic": {
558
558
  "type": "string",
559
- "description": "Video topic (required)"
559
+ "description": "Video topic (required, except with list_fonts=true)"
560
+ },
561
+ "list_fonts": {
562
+ "type": "boolean",
563
+ "description": "List the font library and exit — no script is generated and no other parameter is needed. Returns each font's id, display name, language and category from the live library; this is the complete set of valid font_id values. Call it when the user asks which fonts are available, or wants a font but has not given its id."
560
564
  },
561
565
  "duration": {
562
566
  "type": "number",
@@ -592,7 +596,7 @@
592
596
  },
593
597
  "font_id": {
594
598
  "type": "string",
595
- "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. 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."
599
+ "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."
596
600
  },
597
601
  "font_name": {
598
602
  "type": "string",
@@ -691,6 +695,7 @@
691
695
  "background_preset",
692
696
  "font_id",
693
697
  "font_name",
698
+ "list_fonts",
694
699
  "template_id",
695
700
  "template_source",
696
701
  "template_candidates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
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",
@@ -477,6 +477,7 @@ python3 <SkillDir>/scripts/gen_script.py \
477
477
  | `--background-preset` | Scene background preset, one of the template's `customPayloadSchema.background.preset` values. Written to every scene's `customPayload.background.preset` (other keys of the template's default background are kept). Pass it whenever the user picked a background, e.g. `背景样式: 纯色(solid)` → `solid`. `image` / `video` also need `customPayload.background.assetRef`. Requires `--template-id`; unsupported values are rejected. | template default |
478
478
  | `--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 |
479
479
  | `--font-name` | Display name of the picked font → `global.font.fontName` (display only, never used for rendering). Pass it with `--font-id`. | — |
480
+ | `--list-fonts` | Print the live font library (`/font/page`: id, name, lang, category, license) and exit; no script is generated and `--topic` is not needed. The ids it prints are the only valid `--font-id` values — use it when the user asks which fonts exist or names a font without its id. | — |
480
481
  | `--template-source` | How `--template-id` was chosen: `user-explicit` / `user-confirmed` / `auto`, written to `meta.templateSelection.source`. `auto` is refused — confirm with the user first (see "Agent behavior: template selection comes before the script"). Omitted = not recorded, with a stderr notice. Requires `--template-id`. | — |
481
482
  | `--template-candidates` | Repeatable. Template ids offered to the user, recommended first → `meta.templateSelection.candidates`. With `user-confirmed`, `--template-id` must be one of them. | — |
482
483
  | `--allow-digital-human` | Whether to allow digital-human assets. | off |
@@ -0,0 +1,133 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import sys
23
+ import urllib.error
24
+ import urllib.request
25
+
26
+ _DEFAULT_API_BASE_URL = "https://api.remixmate.ai/api"
27
+ _PAGE_SIZE = 100
28
+ _MAX_PAGES = 10
29
+ _TIMEOUT_SEC = 30
30
+
31
+
32
+ class FontCatalogError(RuntimeError):
33
+ pass
34
+
35
+
36
+ def _api_base_url() -> str:
37
+
38
+ return (
39
+ os.environ.get("MM_API_BASE_URL", "").strip()
40
+ or os.environ.get("MM_BACKEND_API_URL", "").strip()
41
+ or _DEFAULT_API_BASE_URL
42
+ ).rstrip("/")
43
+
44
+
45
+ def _post(path: str, payload: dict) -> dict:
46
+ url = f"{_api_base_url()}{path}"
47
+ headers = {"Content-Type": "application/json", "x-invoke-skill": "gen-script"}
48
+ token = os.environ.get("PRIV_TOKEN", "").strip()
49
+ if token:
50
+ headers["X-Priv-Token"] = token
51
+ req = urllib.request.Request(
52
+ url, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST",
53
+ )
54
+ try:
55
+ with urllib.request.urlopen(req, timeout=_TIMEOUT_SEC) as resp:
56
+ result = json.loads(resp.read().decode("utf-8"))
57
+ except urllib.error.HTTPError as exc:
58
+ raise FontCatalogError(f"HTTP {exc.code} from {path}") from exc
59
+ except urllib.error.URLError as exc:
60
+ raise FontCatalogError(f"network request failed ({path}): {exc.reason}") from exc
61
+ except ValueError as exc:
62
+ raise FontCatalogError(f"invalid JSON from {path}") from exc
63
+
64
+
65
+ if result.get("code") != 0:
66
+ msg = result.get("msg") or result.get("message") or "unknown error"
67
+ raise FontCatalogError(f"API returned an error [{path}]: {msg}")
68
+ return result.get("data") or {}
69
+
70
+
71
+ def fetch_fonts() -> list[dict]:
72
+
73
+ fonts: list[dict] = []
74
+ for current in range(1, _MAX_PAGES + 1):
75
+ data = _post("/font/page", {"current": current, "pageSize": _PAGE_SIZE})
76
+ page = data.get("list") or []
77
+ fonts.extend(page)
78
+ total = (data.get("pager") or {}).get("total")
79
+ if not page or len(page) < _PAGE_SIZE:
80
+ break
81
+ if isinstance(total, int) and len(fonts) >= total:
82
+ break
83
+ return fonts
84
+
85
+
86
+ def _row(font: dict) -> dict:
87
+ return {
88
+ "fontId": font.get("uniqId") or "",
89
+ "fontName": font.get("name") or "",
90
+ "lang": font.get("lang") or "",
91
+ "category": font.get("category") or "",
92
+ "license": font.get("license") or "",
93
+ }
94
+
95
+
96
+ def format_fonts(fonts: list[dict]) -> str:
97
+
98
+ if not fonts:
99
+ return (
100
+ "⚠️ No fonts available in the font library. "
101
+ "Omit font_id to keep the template's own font.\n"
102
+ )
103
+ rows = [_row(f) for f in fonts]
104
+ id_w = max(len("Font ID"), *(len(r["fontId"]) for r in rows))
105
+ name_w = max(len("Name"), *(len(r["fontName"]) for r in rows))
106
+ lines = [
107
+ "",
108
+ f"{'Font ID'.ljust(id_w)} {'Name'.ljust(name_w)} Lang Category License",
109
+ f"{'-' * id_w} {'-' * name_w} ---- -------- -------",
110
+ ]
111
+ for r in rows:
112
+ lines.append(
113
+ f"{r['fontId'].ljust(id_w)} {r['fontName'].ljust(name_w)} "
114
+ f"{r['lang'].ljust(4)} {r['category'].ljust(8)} {r['license']}"
115
+ )
116
+ lines += [
117
+ "",
118
+ f"{len(rows)} font(s) available. This is the live library and the complete set.",
119
+ "💡 Pass --font-id <Font ID> --font-name <Name> to gen_script.",
120
+ "",
121
+ ]
122
+ return "\n".join(lines)
123
+
124
+
125
+ def print_font_catalog() -> int:
126
+
127
+ try:
128
+ fonts = fetch_fonts()
129
+ except FontCatalogError as exc:
130
+ print(f"❌ failed to list fonts: {exc}", file=sys.stderr)
131
+ return 1
132
+ sys.stdout.write(format_fonts(fonts))
133
+ return 0
@@ -2063,14 +2063,21 @@ Examples:
2063
2063
  "--font-id",
2064
2064
  default=None,
2065
2065
  help=(
2066
- "Font family key from the font library (font.uniq_id, e.g. zzgf-xi-mai). "
2067
- "Omit to follow the template's own font pairing. "
2066
+ "Font family key from the font library (font.uniq_id); run --list-fonts to see "
2067
+ "the valid keys. Omit to follow the template's own font pairing. "
2068
2068
  "Pass the opaque key, NOT a CSS family name: the family name is a rendering "
2069
2069
  "detail resolved by ab-render; a mistyped family name silently falls back to "
2070
2070
  "the default font with no error."
2071
2071
  ),
2072
2072
  )
2073
2073
  parser.add_argument("--font-name", default=None, help="Human-readable font name. Logged and stored for display only; never used for rendering.")
2074
+ parser.add_argument(
2075
+ "--list-fonts",
2076
+ action="store_true",
2077
+ help="List the font library (font id, name, lang, category) from ab-api and exit. "
2078
+ "No script is generated and --topic is not needed. The ids printed here are "
2079
+ "the only valid values for --font-id.",
2080
+ )
2074
2081
  parser.add_argument(
2075
2082
  "--variant", default=None,
2076
2083
  help="Template visual variant id (a key of the template's `variants`, listed as variantIds by "
@@ -2167,6 +2174,12 @@ Examples:
2167
2174
 
2168
2175
  args = parser.parse_args()
2169
2176
 
2177
+
2178
+
2179
+ if args.list_fonts:
2180
+ from font_catalog import print_font_catalog
2181
+ sys.exit(print_font_catalog())
2182
+
2170
2183
  if args.validate:
2171
2184
  if not args.input:
2172
2185
  print("❌ validate mode requires --input", file=sys.stderr)
@@ -18,7 +18,11 @@
18
18
  "properties": {
19
19
  "topic": {
20
20
  "type": "string",
21
- "description": "Video topic (required)"
21
+ "description": "Video topic (required, except with list_fonts=true)"
22
+ },
23
+ "list_fonts": {
24
+ "type": "boolean",
25
+ "description": "List the font library and exit — no script is generated and no other parameter is needed. Returns each font's id, display name, language and category from the live library; this is the complete set of valid font_id values. Call it when the user asks which fonts are available, or wants a font but has not given its id."
22
26
  },
23
27
  "duration": {
24
28
  "type": "number",
@@ -54,7 +58,7 @@
54
58
  },
55
59
  "font_id": {
56
60
  "type": "string",
57
- "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. 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."
61
+ "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."
58
62
  },
59
63
  "font_name": {
60
64
  "type": "string",
@@ -147,6 +151,7 @@
147
151
  "background_preset",
148
152
  "font_id",
149
153
  "font_name",
154
+ "list_fonts",
150
155
  "template_id",
151
156
  "template_source",
152
157
  "template_candidates",
@@ -2,6 +2,6 @@
2
2
  "skillName": "gen-script",
3
3
  "repoName": "agent-skill-media-maker",
4
4
  "skillId": "474",
5
- "version": "V20",
5
+ "version": "V21",
6
6
  "skillDescription": "Video-script generation skill. Turns a user-supplied topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.\n\nUse this skill as soon as the user mentions any of these intents:\n- Write a video script, generate a video script, plan a video, write storyboards\n- Create a short video, plan video content, generate a Video DSL\n- Turn a topic into a video structure / video content plan\n\nEven when the user does not say \"generate the DSL\", use this skill whenever they want to turn a topic into a structured video plan.\n\n⚠️ Stop-and-confirm gate: after this skill returns a DSL, show the full script and wait for the user's explicit confirmation. Never call `prepare_video_assets` in the same turn."
7
7
  }