hyper-animator-codex 0.2.0 → 0.4.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.
@@ -0,0 +1,113 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ export const DEFAULT_MINIMAX_MODEL = "music-2.6-free";
5
+ export const TEXT_MUSIC_MODELS = new Set(["music-2.6", "music-2.6-free"]);
6
+
7
+ async function pathExists(path) {
8
+ try {
9
+ await stat(path);
10
+ return true;
11
+ } catch (error) {
12
+ if (error && error.code === "ENOENT") {
13
+ return false;
14
+ }
15
+ throw error;
16
+ }
17
+ }
18
+
19
+ function cleanString(value) {
20
+ if (typeof value !== "string") {
21
+ return undefined;
22
+ }
23
+
24
+ const trimmed = value.trim();
25
+ return trimmed.length > 0 ? trimmed : undefined;
26
+ }
27
+
28
+ function normalize(input = {}) {
29
+ return {
30
+ api_key: cleanString(input.api_key),
31
+ group_id: cleanString(input.group_id),
32
+ model: cleanString(input.model),
33
+ };
34
+ }
35
+
36
+ function hasValues(input = {}) {
37
+ return Boolean(cleanString(input.api_key) || cleanString(input.group_id) || cleanString(input.model));
38
+ }
39
+
40
+ export function redactMinimaxConfig(config = {}) {
41
+ const normalized = normalize(config);
42
+
43
+ return {
44
+ api_key: normalized.api_key ? "[redacted]" : undefined,
45
+ group_id: normalized.group_id,
46
+ model: normalized.model || DEFAULT_MINIMAX_MODEL,
47
+ };
48
+ }
49
+
50
+ export function validateMinimaxConfig(config = {}) {
51
+ const normalized = normalize(config);
52
+
53
+ if (!normalized.api_key) {
54
+ throw new Error("MiniMax api_key is required");
55
+ }
56
+
57
+ if (!normalized.group_id) {
58
+ throw new Error("MiniMax group_id is required");
59
+ }
60
+
61
+ const model = normalized.model || DEFAULT_MINIMAX_MODEL;
62
+
63
+ if (!TEXT_MUSIC_MODELS.has(model)) {
64
+ throw new Error(`Unsupported MiniMax text music model: ${model}. Use music-2.6 or music-2.6-free.`);
65
+ }
66
+
67
+ return {
68
+ api_key: normalized.api_key,
69
+ group_id: normalized.group_id,
70
+ model,
71
+ };
72
+ }
73
+
74
+ async function readJson(path) {
75
+ const raw = await readFile(path, "utf8");
76
+ const parsed = JSON.parse(raw);
77
+
78
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
79
+ throw new Error(`MiniMax config file must be a JSON object: ${path}`);
80
+ }
81
+
82
+ return parsed;
83
+ }
84
+
85
+ export async function readMinimaxRuntimeConfig({ skillRoot, env = process.env, configPath } = {}) {
86
+ const explicitConfigPath = cleanString(configPath);
87
+ const defaultConfigPath = skillRoot ? join(skillRoot, "config", "minimax.json") : undefined;
88
+ const filePath = explicitConfigPath || defaultConfigPath;
89
+
90
+ if (filePath && await pathExists(filePath)) {
91
+ return {
92
+ source: "file",
93
+ configPath: filePath,
94
+ config: validateMinimaxConfig(await readJson(filePath)),
95
+ };
96
+ }
97
+
98
+ const envConfig = normalize({
99
+ api_key: env.MINIMAX_API_KEY,
100
+ group_id: env.MINIMAX_GROUP_ID,
101
+ model: env.MINIMAX_MODEL,
102
+ });
103
+
104
+ if (!hasValues(envConfig)) {
105
+ return null;
106
+ }
107
+
108
+ return {
109
+ source: "environment",
110
+ configPath: null,
111
+ config: validateMinimaxConfig(envConfig),
112
+ };
113
+ }
@@ -13,7 +13,12 @@ def has_attr(html: str, name: str) -> bool:
13
13
  return re.search(rf"\b{name}\s*=\s*['\"]?[^'\"\s>]+", html, re.IGNORECASE) is not None
14
14
 
15
15
 
16
- def check_html(html: str, *, component: bool = False) -> tuple[list[str], list[str]]:
16
+ def check_html(
17
+ html: str,
18
+ *,
19
+ component: bool = False,
20
+ preview_controls: bool = False,
21
+ ) -> tuple[list[str], list[str]]:
17
22
  failures: list[str] = []
18
23
  warnings: list[str] = []
19
24
 
@@ -51,6 +56,24 @@ def check_html(html: str, *, component: bool = False) -> tuple[list[str], list[s
51
56
  if len(re.findall(r"data-duration\s*=\s*['\"]?(\d+(?:\.\d+)?)", html)) > 1:
52
57
  warnings.append("multiple data-duration values found; verify the intended render duration")
53
58
 
59
+ if preview_controls:
60
+ if "data-hyper-preview-ui" not in html:
61
+ failures.append("missing preview controls: data-hyper-preview-ui")
62
+ if "data-hyper-preview-page" not in html:
63
+ failures.append("missing preview controls: page indicator")
64
+ if "data-hyper-preview-progress" not in html:
65
+ failures.append("missing preview controls: progress input")
66
+ if not re.search(r"<input[^>]+type=[\"']range[\"']", html, re.IGNORECASE | re.DOTALL):
67
+ failures.append("missing preview controls: range input")
68
+ if "ArrowLeft" not in html or "ArrowRight" not in html:
69
+ failures.append("missing preview controls: left/right keyboard handlers")
70
+ if 'params.get("render") === "1"' not in html:
71
+ failures.append("missing preview controls: ?render=1 hidden-mode check")
72
+ if 'params.get("preview") === "0"' not in html:
73
+ failures.append("missing preview controls: ?preview=0 hidden-mode check")
74
+ if 'dataset.renderMode === "video"' not in html:
75
+ failures.append("missing preview controls: data-render-mode video hidden-mode check")
76
+
54
77
  return failures, warnings
55
78
 
56
79
 
@@ -62,6 +85,11 @@ def main() -> int:
62
85
  action="store_true",
63
86
  help="Validate a component snippet instead of a full block composition",
64
87
  )
88
+ parser.add_argument(
89
+ "--preview-controls",
90
+ action="store_true",
91
+ help="Require injected Hyper Animator preview controls",
92
+ )
65
93
  args = parser.parse_args()
66
94
 
67
95
  path = Path(args.html_file)
@@ -70,7 +98,11 @@ def main() -> int:
70
98
  return 2
71
99
 
72
100
  html = path.read_text(encoding="utf-8")
73
- failures, warnings = check_html(html, component=args.component)
101
+ failures, warnings = check_html(
102
+ html,
103
+ component=args.component,
104
+ preview_controls=args.preview_controls,
105
+ )
74
106
 
75
107
  for warning in warnings:
76
108
  print(f"WARN: {warning}", file=sys.stderr)