@remixmate/cli 0.12.9 → 0.13.0

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.
@@ -2,6 +2,6 @@
2
2
  "skillName": "render-video",
3
3
  "repoName": "agent-skill-media-maker",
4
4
  "skillId": "473",
5
- "version": "V38",
5
+ "version": "V39",
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
  }
@@ -140,7 +140,7 @@ remixmate template-registry --template-id <templateId> # then re
140
140
  remixmate template-registry --template-id <templateId> --slide-id key-points --slide-id timeline # exact schemas of the chosen layouts
141
141
  ```
142
142
 
143
- Detail mode used to pretty-print, which nearly doubled the size (html-slide: 37 K → 65 K characters) and pushed the plain call past the agent's tool-result cap — the result was spilled to a file the agent cannot open, so it re-called with other flags, and re-read the whole contract on the next turn too (session review 2026-09-27). Output is now always compact, and `slideSchemas` — two thirds of a multi-layout template — is a menu until you name the layouts.
143
+ Detail mode used to pretty-print, which nearly doubled the size (a multi-layout template: 37 K → 65 K characters) and pushed the plain call past the agent's tool-result cap — the result was spilled to a file the agent cannot open, so it re-called with other flags, and re-read the whole contract on the next turn too (session review 2026-09-27). Output is now always compact, and `slideSchemas` — two thirds of a multi-layout template — is a menu until you name the layouts.
144
144
 
145
145
  This mirrors the list/detail split described in `scripts/registry_loader.py` (P1.2), realized CLI-side so it holds even while the backend still serves one merged registry payload.
146
146
 
@@ -102,8 +102,6 @@ def build_binding(template: dict, dsl: dict) -> dict:
102
102
 
103
103
 
104
104
 
105
-
106
-
107
105
  custom = scene.get("customPayload") or {}
108
106
  template_data = custom.get("templateData") or scene.get("templateData") or {}
109
107
 
@@ -131,6 +129,8 @@ def build_binding(template: dict, dsl: dict) -> dict:
131
129
  dual_channel = sorted(dual_channel)
132
130
 
133
131
 
132
+
133
+
134
134
  for k, v in template_data.items():
135
135
  if k not in props:
136
136
  props[k] = v
@@ -129,11 +129,19 @@ def hydration_spec(template_config: Optional[dict]) -> Optional[dict]:
129
129
  "Upgrade @remixmate/cli or fix the template."
130
130
  )
131
131
 
132
+
133
+
134
+ doc_kind = spec.get("docKind")
135
+ if not isinstance(doc_kind, str) or not doc_kind:
136
+ raise HydrationError(
137
+ "template declares capabilities.payloadHydration without docKind (e.g. the file "
138
+ "extension the extractor should parse). Fix the template's payloadHydration."
139
+ )
132
140
  index_field = spec.get("indexField")
133
141
  return {
134
142
  "payload_field": payload_field,
135
143
  "source": source,
136
- "doc_kind": spec.get("docKind") or "pptx",
144
+ "doc_kind": doc_kind,
137
145
  "fields": spec.get("fields") or "content,layout",
138
146
  "index_field": index_field if isinstance(index_field, str) and index_field else DEFAULT_INDEX_FIELD,
139
147
  }
@@ -77,6 +77,10 @@ def _post(path: str, payload: dict, priv_token: str, timeout: int = 30) -> dict:
77
77
  except urllib.error.URLError as e:
78
78
  raise RuntimeError(f"network request failed ({path}): {e.reason}") from e
79
79
 
80
+
81
+ except (OSError, ValueError) as e:
82
+ raise RuntimeError(f"request failed ({path}): {type(e).__name__}: {e}") from e
83
+
80
84
  _record_billing(result)
81
85
 
82
86
  if result.get("code") != 0:
@@ -2,6 +2,6 @@
2
2
  "skillName": "template-registry",
3
3
  "repoName": "agent-skill-media-maker",
4
4
  "skillId": "475",
5
- "version": "V29",
5
+ "version": "V30",
6
6
  "skillDescription": "Video-template registry skill. Stores every video-template definition and lists the available templates (templateId / name / aspect ratio / style tags).\n\nUse this skill as soon as the user mentions any of these intents:\n- View available templates / list every template\n\nNote: DSL→TemplateBinding is no longer a separate exposed step — once prepare_video_assets receives a template_id it builds the binding internally."
7
7
  }
@@ -573,6 +573,30 @@ def _check_slide_id_chosen(dsl: dict) -> list[ValidationError]:
573
573
  return errors
574
574
 
575
575
 
576
+ def _cross_field_marks(slide_schema: dict) -> tuple[list, list]:
577
+
578
+
579
+
580
+
581
+
582
+
583
+ line_marks: list = []
584
+ substring_marks: list = []
585
+ props = (slide_schema or {}).get("properties") or {}
586
+ for key, spec in props.items():
587
+ if not isinstance(spec, dict):
588
+ continue
589
+ if isinstance(spec.get("x-lineIndexOf"), str):
590
+ line_marks.append((None, key, spec["x-lineIndexOf"]))
591
+ if isinstance(spec.get("x-substringOf"), str):
592
+ substring_marks.append((key, spec["x-substringOf"]))
593
+ item_props = ((spec.get("items") or {}).get("properties") or {}) if spec.get("type") == "array" else {}
594
+ for sub_key, sub_spec in item_props.items():
595
+ if isinstance(sub_spec, dict) and isinstance(sub_spec.get("x-lineIndexOf"), str):
596
+ line_marks.append((key, sub_key, sub_spec["x-lineIndexOf"]))
597
+ return line_marks, substring_marks
598
+
599
+
576
600
  def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
577
601
 
578
602
 
@@ -611,23 +635,31 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
611
635
  if not tdata:
612
636
  continue
613
637
  base = f"scenes[{idx}].customPayload.templateData"
638
+ line_marks, substring_marks = _cross_field_marks(schemas.get(slide_id) or {})
614
639
 
615
640
 
616
- code = tdata.get("code")
617
- annotations = tdata.get("annotations")
618
- if isinstance(code, str) and isinstance(annotations, list):
619
- total = len(code.split("\n"))
620
- for i, note in enumerate(annotations):
621
- if not isinstance(note, dict):
622
- continue
623
- line = note.get("line")
641
+ for array_key, key, ref in line_marks:
642
+ text = tdata.get(ref)
643
+ if not isinstance(text, str):
644
+ continue
645
+ total = len(text.split("\n"))
646
+ if array_key is None:
647
+ targets = [(f"{base}.{key}", tdata.get(key))]
648
+ else:
649
+ rows = tdata.get(array_key)
650
+ targets = [
651
+ (f"{base}.{array_key}[{i}].{key}", row.get(key))
652
+ for i, row in enumerate(rows if isinstance(rows, list) else [])
653
+ if isinstance(row, dict)
654
+ ]
655
+ for path, line in targets:
624
656
  if not isinstance(line, int) or isinstance(line, bool):
625
657
  continue
626
658
  if line < 1 or line > total:
627
659
  errors.append(ValidationError(
628
- f"{base}.annotations[{i}].line",
629
- f"scene '{sid}': annotation points at line {line} but the code block has "
630
- f"{total} line(s) — while that narration line plays, no code line lights "
660
+ path,
661
+ f"scene '{sid}': {key} points at line {line} of '{ref}', which has "
662
+ f"{total} line(s) — while that narration line plays, nothing lights "
631
663
  "up at all. Line numbers are 1-based.",
632
664
  ))
633
665
 
@@ -648,20 +680,14 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
648
680
  ))
649
681
 
650
682
 
651
-
652
- if "highlight" in ((schemas.get(slide_id) or {}).get("properties") or {}):
653
- highlight = tdata.get("highlight")
654
- title = tdata.get("title")
655
- if (
656
- isinstance(highlight, str)
657
- and highlight.strip()
658
- and isinstance(title, str)
659
- and highlight not in title
660
- ):
683
+ for key, ref in substring_marks:
684
+ part, whole = tdata.get(key), tdata.get(ref)
685
+ if isinstance(part, str) and part.strip() and isinstance(whole, str) and part not in whole:
661
686
  errors.append(ValidationError(
662
- f"{base}.highlight",
663
- f"scene '{sid}': highlight '{highlight}' is not a substring of title "
664
- f"'{title}' — the two-colour split silently falls back to a single colour.",
687
+ f"{base}.{key}",
688
+ f"scene '{sid}': {key} '{part}' is not a substring of {ref} "
689
+ f"'{whole}' — the template declares it must be, and silently drops "
690
+ "the emphasis otherwise.",
665
691
  severity="warning",
666
692
  ))
667
693
 
@@ -122,7 +122,13 @@
122
122
  },
123
123
  "layers": {
124
124
  "type": "array",
125
- "items": { "$ref": "#/definitions/Layer" }
125
+ "items": { "$ref": "#/definitions/Layer" },
126
+ "description": "已废弃:render_video 不再写入,读到旧 RenderPlan 里的也会丢掉。场景用到的素材以 props 里的 assetId 引用为准"
127
+ },
128
+ "textLayers": {
129
+ "type": "array",
130
+ "items": { "type": "object" },
131
+ "description": "DSL 场景 textLayers 的原样拷贝(渲染不读,供剪映导出等只拿到 RenderPlan 的下游还原文本图层)"
126
132
  },
127
133
  "subtitleSegments": {
128
134
  "type": "array",
@@ -808,11 +808,13 @@ def build_assets_summary(decon: dict) -> dict:
808
808
  "url": audio.get("url", ""),
809
809
  }
810
810
 
811
+
811
812
  kf_list = []
812
813
  for kf in keyframes:
813
814
  kf_list.append({
814
815
  "index": kf["index"],
815
816
  "localPath": kf.get("localPath", ""),
817
+ "url": kf.get("url", ""),
816
818
  "timestampSec": kf.get("timestampSec", 0.0),
817
819
  })
818
820
 
@@ -2,6 +2,6 @@
2
2
  "skillName": "video-parser",
3
3
  "repoName": "agent-skill-media-maker",
4
4
  "skillId": "553",
5
- "version": "V3",
5
+ "version": "V4",
6
6
  "skillDescription": "Video deconstruction skill. Splits a video into reusable content assets — audio, ASR transcript, scene segments, keyframe images.\n\nUse this skill as soon as the user mentions any of these intents:\n- Deconstruct / split / analyze / parse a video\n- Extract keyframes, extract the script from a video, transcribe a video\n- Pull content assets or raw material out of a video\n\nEven when the user does not say \"deconstruct\" explicitly, use this skill whenever they want to extract script text, keyframes, or structured information from a video."
7
7
  }