@remixmate/cli 0.9.18 → 0.9.19

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.
@@ -46,7 +46,21 @@ export interface HostSession {
46
46
  }
47
47
  /** Identify the host and its root session id, or null when unrecognised. */
48
48
  export declare function detectHostSession(env?: NodeJS.ProcessEnv): HostSession | null;
49
- /** Host label for provenance, even when no session id could be read. */
49
+ /**
50
+ * Normalise a declared host label.
51
+ *
52
+ * `video_project.client_host` is `varchar(32)`, and the value is rendered as a
53
+ * badge, so free-form input gets slugified rather than trusted: lowercase,
54
+ * non-alphanumerics collapsed to `-`, capped at 32. Anything that normalises to
55
+ * empty is treated as absent — an unusable label should degrade to "no badge",
56
+ * never to a garbage one.
57
+ */
58
+ export declare function normaliseHostLabel(raw: string | undefined): string;
59
+ /**
60
+ * Host label for provenance, even when no session id could be read.
61
+ *
62
+ * Declaration wins over detection: the declaring party knows, the table guesses.
63
+ */
50
64
  export declare function detectHostLabel(env?: NodeJS.ProcessEnv): string;
51
65
  export interface TakeKey {
52
66
  /** `<host>:<sessionId>`, or '' when nothing groups this invocation. */
@@ -62,8 +62,51 @@ export function detectHostSession(env = process.env) {
62
62
  }
63
63
  return null;
64
64
  }
65
- /** Host label for provenance, even when no session id could be read. */
65
+ /**
66
+ * Explicit host declaration, checked *before* the detection table.
67
+ *
68
+ * Two callers, both of which detection cannot serve:
69
+ *
70
+ * - **WorkBuddy (ab-agent)** spawns this CLI itself. Sniffing our own process
71
+ * would turn a fact we already know into a guess — and a fragile one, since
72
+ * the generic variables it injects (CONVERSATION_ID …) are exactly the kind
73
+ * another host might also set. It declares `REMIXMATE_HOST=workbuddy`.
74
+ * - **Codex** does not expose anything: per the appendix-A survey it
75
+ * "官方明确不通过环境变量暴露". No `detect()` we could write would be honest.
76
+ * A user or the host itself can export `REMIXMATE_HOST=codex` and get the
77
+ * provenance label without waiting for a CLI release.
78
+ *
79
+ * Symmetric with `REMIXMATE_TAKE_KEY`: that one overrides *grouping*, this one
80
+ * overrides *identity*. They are independent — declaring a host does not group,
81
+ * and grouping does not imply a host.
82
+ */
83
+ const HOST_OVERRIDE_VAR = 'REMIXMATE_HOST';
84
+ /**
85
+ * Normalise a declared host label.
86
+ *
87
+ * `video_project.client_host` is `varchar(32)`, and the value is rendered as a
88
+ * badge, so free-form input gets slugified rather than trusted: lowercase,
89
+ * non-alphanumerics collapsed to `-`, capped at 32. Anything that normalises to
90
+ * empty is treated as absent — an unusable label should degrade to "no badge",
91
+ * never to a garbage one.
92
+ */
93
+ export function normaliseHostLabel(raw) {
94
+ return (raw ?? '')
95
+ .trim()
96
+ .toLowerCase()
97
+ .replace(/[^a-z0-9]+/g, '-')
98
+ .replace(/^-+|-+$/g, '')
99
+ .slice(0, 32);
100
+ }
101
+ /**
102
+ * Host label for provenance, even when no session id could be read.
103
+ *
104
+ * Declaration wins over detection: the declaring party knows, the table guesses.
105
+ */
66
106
  export function detectHostLabel(env = process.env) {
107
+ const declared = normaliseHostLabel(env[HOST_OVERRIDE_VAR]);
108
+ if (declared)
109
+ return declared;
67
110
  return HOST_PROVIDERS.find((p) => p.detect(env))?.host ?? '';
68
111
  }
69
112
  /**
@@ -83,7 +126,9 @@ export function resolveTakeKey(jobId, env = process.env) {
83
126
  const host = detectHostLabel(env);
84
127
  const detected = detectHostSession(env);
85
128
  if (detected) {
86
- return { key: `${detected.host}:${detected.sessionId}`, host: detected.host, source: 'host' };
129
+ // key 的命名空间用**探测到的**宿主,不用声明值:它是既有分组的命名空间,
130
+ // 跟着声明变会把同一会话的 take 拆成两组。而 host(溯源标签)以声明为准。
131
+ return { key: `${detected.host}:${detected.sessionId}`, host, source: 'host' };
87
132
  }
88
133
  const override = (env.REMIXMATE_TAKE_KEY ?? '').trim();
89
134
  if (override)
@@ -46,6 +46,28 @@ export declare const CATEGORY_VALUES: readonly ["authoring", "consuming", "asset
46
46
  */
47
47
  export type SkillAuthMode = 'required' | 'optional' | 'none';
48
48
  export declare const AUTH_VALUES: readonly ["required", "optional", "none"];
49
+ /**
50
+ * 参数的**展示分级** —— 给人填表用的,不影响调度。
51
+ *
52
+ * `parameters` 是给 agent 的完整契约(十几个字段很正常);把它整份渲染成表单
53
+ * 就是把门槛从"不知道说什么"换成"不知道填什么"。所以每个 skill 自己声明哪些
54
+ * 字段该直出、哪些折叠、哪些**根本不该给人看见**(`json_output` / `seed` 这类
55
+ * 是给调用方的开关,不是给人的选项)。
56
+ *
57
+ * 它和 schema 同住 skill.json,是刻意的:跨仓库的第二份名单必然漂移——
58
+ * ab-web 曾经用硬编码的 `ARCHETYPES` 常量对着后端字典表,最后两边对不上。
59
+ *
60
+ * 未声明的字段自动落进 `advanced`(见 build-manifest 的 resolveUiHints):
61
+ * 新加一个参数时,最坏情况是它多显示了一层,而不是静默消失。
62
+ */
63
+ export interface SkillUiHints {
64
+ /** 表单展开即可见 */
65
+ primary?: string[];
66
+ /** 折叠在「高级」里 */
67
+ advanced?: string[];
68
+ /** 不渲染给人类 */
69
+ hidden?: string[];
70
+ }
49
71
  export interface RawSkillJson {
50
72
  name: string;
51
73
  toolName: string;
@@ -55,6 +77,8 @@ export interface RawSkillJson {
55
77
  category?: SkillCategory;
56
78
  auth?: SkillAuthMode;
57
79
  parameters?: Record<string, unknown>;
80
+ /** 参数的展示分级;缺省时按 required → primary、其余 → advanced 推导。 */
81
+ ui?: SkillUiHints;
58
82
  scriptPath?: string;
59
83
  entry?: SkillEntry;
60
84
  envVars?: string[];
@@ -100,6 +124,13 @@ export declare const REQUIRED_SKILL_JSON_FIELDS: readonly ["name", "tier", "titl
100
124
  * skills keep working untouched. Returns null when neither is present.
101
125
  */
102
126
  export declare function normalizeEntry(raw: Pick<RawSkillJson, 'entry' | 'scriptPath'>): SkillEntry | null;
127
+ /**
128
+ * 把 `ui` 声明补全成三个互斥的完整分桶。没声明 `ui` 时按 required 推导。
129
+ *
130
+ * **未提及的字段一律落进 advanced**:新增参数时最坏是多折一层,而不是消失。
131
+ * 返回 null 表示这个 skill 没有可填的参数(不该弹表单)。
132
+ */
133
+ export declare function resolveUiHints(raw: Pick<RawSkillJson, 'parameters' | 'ui'>): Required<SkillUiHints> | null;
103
134
  /**
104
135
  * Validate a parsed skill.json against the directory it lives in. Returns an
105
136
  * array of problem strings; an empty array means the record is well-formed.
@@ -41,6 +41,97 @@ export function normalizeEntry(raw) {
41
41
  return { type: 'python', scriptPath: raw.scriptPath };
42
42
  return null;
43
43
  }
44
+ /** `parameters.properties` 的键;非对象 / 无 properties 时返回空数组。 */
45
+ function parameterKeys(parameters) {
46
+ if (!parameters || typeof parameters !== 'object')
47
+ return [];
48
+ const props = parameters.properties;
49
+ if (!props || typeof props !== 'object')
50
+ return [];
51
+ return Object.keys(props);
52
+ }
53
+ /** `parameters.required`;缺失或格式不对时返回空数组。 */
54
+ function requiredKeys(parameters) {
55
+ if (!parameters || typeof parameters !== 'object')
56
+ return [];
57
+ const req = parameters.required;
58
+ return Array.isArray(req) ? req.filter((k) => typeof k === 'string') : [];
59
+ }
60
+ /**
61
+ * 把 `ui` 声明补全成三个互斥的完整分桶。没声明 `ui` 时按 required 推导。
62
+ *
63
+ * **未提及的字段一律落进 advanced**:新增参数时最坏是多折一层,而不是消失。
64
+ * 返回 null 表示这个 skill 没有可填的参数(不该弹表单)。
65
+ */
66
+ export function resolveUiHints(raw) {
67
+ const keys = parameterKeys(raw.parameters);
68
+ if (keys.length === 0)
69
+ return null;
70
+ if (!raw.ui) {
71
+ const required = new Set(requiredKeys(raw.parameters));
72
+ return {
73
+ primary: keys.filter((k) => required.has(k)),
74
+ advanced: keys.filter((k) => !required.has(k)),
75
+ hidden: [],
76
+ };
77
+ }
78
+ const declared = raw.ui;
79
+ const keySet = new Set(keys);
80
+ // 只保留真实存在的字段:schema 删了某个参数但 ui 忘了同步时,
81
+ // 不至于让下游渲染出一个没有定义的表单项。
82
+ const keep = (list) => (list ?? []).filter((k) => keySet.has(k));
83
+ const primary = keep(declared.primary);
84
+ const advanced = keep(declared.advanced);
85
+ const hidden = keep(declared.hidden);
86
+ const mentioned = new Set([...primary, ...advanced, ...hidden]);
87
+ return {
88
+ primary,
89
+ advanced: [...advanced, ...keys.filter((k) => !mentioned.has(k))],
90
+ hidden,
91
+ };
92
+ }
93
+ /**
94
+ * `ui` 分级的自检。三条规则,每条都对应一种"表单看起来正常但填不出东西"的坏状态。
95
+ */
96
+ function validateUiHints(raw) {
97
+ if (raw.ui == null)
98
+ return [];
99
+ const errors = [];
100
+ if (typeof raw.ui !== 'object' || Array.isArray(raw.ui)) {
101
+ return ['skill.json.ui must be an object with primary / advanced / hidden arrays'];
102
+ }
103
+ const keys = new Set(parameterKeys(raw.parameters));
104
+ const seen = new Map(); // 字段 → 首次出现的桶
105
+ for (const bucket of ['primary', 'advanced', 'hidden']) {
106
+ const list = raw.ui[bucket];
107
+ if (list == null)
108
+ continue;
109
+ if (!Array.isArray(list) || list.some((k) => typeof k !== 'string' || k === '')) {
110
+ errors.push(`skill.json.ui.${bucket} must be an array of non-empty parameter names`);
111
+ continue;
112
+ }
113
+ for (const key of list) {
114
+ // 引用了不存在的参数:多半是 schema 改名后 ui 没跟着改。
115
+ if (!keys.has(key)) {
116
+ errors.push(`skill.json.ui.${bucket} references unknown parameter '${key}'`);
117
+ }
118
+ const first = seen.get(key);
119
+ if (first) {
120
+ errors.push(`skill.json.ui lists '${key}' in both '${first}' and '${bucket}'`);
121
+ }
122
+ else {
123
+ seen.set(key, bucket);
124
+ }
125
+ }
126
+ }
127
+ // 必填字段被藏起来 = 表单永远填不完整,且没有任何提示。
128
+ for (const key of requiredKeys(raw.parameters)) {
129
+ if (raw.ui.hidden?.includes(key)) {
130
+ errors.push(`skill.json.ui.hidden contains required parameter '${key}' — it could never be filled`);
131
+ }
132
+ }
133
+ return errors;
134
+ }
44
135
  /**
45
136
  * Validate a parsed skill.json against the directory it lives in. Returns an
46
137
  * array of problem strings; an empty array means the record is well-formed.
@@ -92,6 +183,7 @@ export function validateSkillJson(raw, skillId) {
92
183
  if ((raw.createsTake === true || raw.joinsTake === true) && resolveAuthMode(raw) === 'none') {
93
184
  errors.push("skill.json declares createsTake/joinsTake but auth='none' — a purely local skill has nothing to attribute");
94
185
  }
186
+ errors.push(...validateUiHints(raw));
95
187
  if (raw.name && raw.name !== skillId) {
96
188
  errors.push(`skill.json.name='${raw.name}' does not match directory name '${skillId}'`);
97
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.9.18",
3
+ "version": "0.9.19",
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",
@@ -6,21 +6,73 @@
6
6
  "title": "Jianying (CapCut) Draft Export",
7
7
  "description": "Jianying (CapCut) draft export: package asset URLs into a draft ZIP that Jianying can import. Supports automatic conversion from a RenderPlan.",
8
8
  "auth": "required",
9
- "envVars": ["PRIV_TOKEN", "MM_API_BASE_URL", "AGENT_NAME"],
9
+ "envVars": [
10
+ "PRIV_TOKEN",
11
+ "MM_API_BASE_URL",
12
+ "AGENT_NAME"
13
+ ],
10
14
  "scriptPath": "scripts/gen_jianying_draft.py",
11
15
  "parameters": {
12
16
  "type": "object",
13
17
  "properties": {
14
- "from_render_plan": { "type": "string", "description": "Build the draft from a RenderPlan JSON (replaces --scenes)" },
15
- "scenes": { "type": "string", "description": "Scene array as JSON (inline string or file path)" },
16
- "title": { "type": "string", "description": "Draft title" },
17
- "width": { "type": "number", "description": "Canvas width in pixels (default 1080)" },
18
- "height": { "type": "number", "description": "Canvas height in pixels (default 1920)" },
19
- "system": { "type": "string", "enum": ["mac", "windows"], "description": "Draft-root preset" },
20
- "draft_root_path": { "type": "string", "description": "Explicit Jianying draft root path (overrides --system)" },
21
- "no_download": { "type": "boolean", "description": "Do not download the ZIP; print the URL only" },
22
- "output": { "type": "string", "description": "Local download path" }
18
+ "from_render_plan": {
19
+ "type": "string",
20
+ "description": "Build the draft from a RenderPlan JSON (replaces --scenes)"
21
+ },
22
+ "scenes": {
23
+ "type": "string",
24
+ "description": "Scene array as JSON (inline string or file path)"
25
+ },
26
+ "title": {
27
+ "type": "string",
28
+ "description": "Draft title"
29
+ },
30
+ "width": {
31
+ "type": "number",
32
+ "description": "Canvas width in pixels (default 1080)"
33
+ },
34
+ "height": {
35
+ "type": "number",
36
+ "description": "Canvas height in pixels (default 1920)"
37
+ },
38
+ "system": {
39
+ "type": "string",
40
+ "enum": [
41
+ "mac",
42
+ "windows"
43
+ ],
44
+ "description": "Draft-root preset"
45
+ },
46
+ "draft_root_path": {
47
+ "type": "string",
48
+ "description": "Explicit Jianying draft root path (overrides --system)"
49
+ },
50
+ "no_download": {
51
+ "type": "boolean",
52
+ "description": "Do not download the ZIP; print the URL only"
53
+ },
54
+ "output": {
55
+ "type": "string",
56
+ "description": "Local download path"
57
+ }
23
58
  },
24
59
  "required": []
60
+ },
61
+ "ui": {
62
+ "primary": [
63
+ "title"
64
+ ],
65
+ "advanced": [
66
+ "system",
67
+ "width",
68
+ "height",
69
+ "output"
70
+ ],
71
+ "hidden": [
72
+ "from_render_plan",
73
+ "scenes",
74
+ "draft_root_path",
75
+ "no_download"
76
+ ]
25
77
  }
26
78
  }
@@ -89,5 +89,28 @@
89
89
  }
90
90
  },
91
91
  "required": []
92
+ },
93
+ "ui": {
94
+ "primary": [
95
+ "avatar_id",
96
+ "text"
97
+ ],
98
+ "advanced": [
99
+ "source",
100
+ "voice_id",
101
+ "aspect_ratio",
102
+ "prompt",
103
+ "audio_url"
104
+ ],
105
+ "hidden": [
106
+ "json_output",
107
+ "list_avatars",
108
+ "mine",
109
+ "name",
110
+ "gender",
111
+ "check_status",
112
+ "generation_id",
113
+ "voice_name"
114
+ ]
92
115
  }
93
116
  }
@@ -80,5 +80,25 @@
80
80
  "required": [
81
81
  "prompt"
82
82
  ]
83
+ },
84
+ "ui": {
85
+ "primary": [
86
+ "prompt",
87
+ "model",
88
+ "size"
89
+ ],
90
+ "advanced": [
91
+ "n",
92
+ "reference",
93
+ "negative_prompt",
94
+ "resolution",
95
+ "image_strength"
96
+ ],
97
+ "hidden": [
98
+ "json_output",
99
+ "seed",
100
+ "guidance_scale",
101
+ "watermark"
102
+ ]
83
103
  }
84
104
  }
@@ -6,39 +6,116 @@
6
6
  "title": "Video Script Generation",
7
7
  "description": "Video script generation: turn a topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.",
8
8
  "auth": "none",
9
- "envVars": ["DEFAULT_IMAGE_MODEL", "DEFAULT_VIDEO_MODEL", "STUB_IMAGE_URL", "STUB_VIDEO_URL"],
9
+ "envVars": [
10
+ "DEFAULT_IMAGE_MODEL",
11
+ "DEFAULT_VIDEO_MODEL",
12
+ "STUB_IMAGE_URL",
13
+ "STUB_VIDEO_URL"
14
+ ],
10
15
  "scriptPath": "scripts/gen_script.py",
11
16
  "parameters": {
12
17
  "type": "object",
13
18
  "properties": {
14
- "topic": { "type": "string", "description": "Video topic (required)" },
19
+ "topic": {
20
+ "type": "string",
21
+ "description": "Video topic (required)"
22
+ },
15
23
  "platform": {
16
24
  "type": "string",
17
- "enum": ["douyin", "xiaohongshu", "bilibili", "wechat", "youtube", "generic"],
25
+ "enum": [
26
+ "douyin",
27
+ "xiaohongshu",
28
+ "bilibili",
29
+ "wechat",
30
+ "youtube",
31
+ "generic"
32
+ ],
18
33
  "description": "Target platform"
19
34
  },
20
- "duration": { "type": "number", "description": "Target duration in seconds" },
21
- "style": { "type": "string", "description": "Style tag" },
22
- "ratio": { "type": "string", "description": "Aspect ratio, e.g. 16:9 or 9:16" },
23
- "scenes": { "type": "number", "description": "Scene count" },
24
- "voice_id": { "type": "string", "description": "Narration voice id. Default depends on the bound template's outputLanguage; query gen_voice with list_voices=true to see available ids." },
25
- "template_id": { "type": "string", "description": "Template id (e.g. html-slide). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL." },
26
- "headline": { "type": "string", "description": "On-screen headline (recommended 4-12 chars / ~3 words). Stored at meta.headline and pushed into every scene's textLayers[role=headline] so the template can render it as the top big-text. **Must** be set when the user explicitly provided a headline / main title; without it, headline falls back to the long-form topic and overflows the top text layer." },
27
- "subheadline": { "type": "string", "description": "On-screen subheadline (project name / slogan / source, e.g. 'Pixelle-Video'). Stored at meta.subheadline and pushed into every scene's textLayers[role=subheadline] so the template can render it as the top small-text. **Must** be set when the user explicitly provided a subtitle / project name. Note: this is the on-screen subheadline, not the CC subtitle (global.subtitle) — they are independent." },
35
+ "duration": {
36
+ "type": "number",
37
+ "description": "Target duration in seconds"
38
+ },
39
+ "style": {
40
+ "type": "string",
41
+ "description": "Style tag"
42
+ },
43
+ "ratio": {
44
+ "type": "string",
45
+ "description": "Aspect ratio, e.g. 16:9 or 9:16"
46
+ },
47
+ "scenes": {
48
+ "type": "number",
49
+ "description": "Scene count"
50
+ },
51
+ "voice_id": {
52
+ "type": "string",
53
+ "description": "Narration voice id. Default depends on the bound template's outputLanguage; query gen_voice with list_voices=true to see available ids."
54
+ },
55
+ "template_id": {
56
+ "type": "string",
57
+ "description": "Template id (e.g. html-slide). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL."
58
+ },
59
+ "headline": {
60
+ "type": "string",
61
+ "description": "On-screen headline (recommended 4-12 chars / ~3 words). Stored at meta.headline and pushed into every scene's textLayers[role=headline] so the template can render it as the top big-text. **Must** be set when the user explicitly provided a headline / main title; without it, headline falls back to the long-form topic and overflows the top text layer."
62
+ },
63
+ "subheadline": {
64
+ "type": "string",
65
+ "description": "On-screen subheadline (project name / slogan / source, e.g. 'Pixelle-Video'). Stored at meta.subheadline and pushed into every scene's textLayers[role=subheadline] so the template can render it as the top small-text. **Must** be set when the user explicitly provided a subtitle / project name. Note: this is the on-screen subheadline, not the CC subtitle (global.subtitle) — they are independent."
66
+ },
28
67
  "carousel_items": {
29
68
  "type": "array",
30
- "items": { "type": "string" },
69
+ "items": {
70
+ "type": "string"
71
+ },
31
72
  "description": "Media URLs for the template's image/video carousel (e.g. spotlight-card's middle carousel). When provided together with a template_id whose capabilities.payloadStyle=carousel-caption, these URLs are placed directly into customPayload.carousel.items as existing assets — NO AI image generation is triggered. **Must** pass when the user explicitly provides image/video URLs for carousel-style templates (spotlight-card, etc.). Each element is a full URL string."
32
73
  },
33
74
  "caption_lines": {
34
75
  "type": "array",
35
- "items": { "type": "string" },
76
+ "items": {
77
+ "type": "string"
78
+ },
36
79
  "description": "Bottom typewriter text lines for templates that support a caption/typewriter area (e.g. spotlight-card). Each element is one line of text. Supports **emphasis** syntax (rendered with accent color). **Must** pass when the user explicitly provides bullet-point text / bottom copy for the video."
37
80
  },
38
- "stub_image_url": { "type": "string", "description": "Test-mode image stub URL. Only pass when the user explicitly says things like 'just testing / don't actually generate / use a placeholder image / stub URL / save credits' AND provides a concrete URL. With this set, every image AssetRef in the produced DSL is written as source=existing, status=generated, url=<this URL> — no gen-image call. Do not pass otherwise; if the user expressed the intent without a URL, ask for one — do not invent one." },
39
- "stub_video_url": { "type": "string", "description": "Test-mode video stub URL. Only pass when the user explicitly says things like 'just testing / don't actually generate the video / placeholder clip / save credits' AND provides a concrete URL. With this set, every video AssetRef in the produced DSL is written as source=existing, status=generated, url=<this URL> — no gen-video call. Do not pass otherwise; if the user expressed the intent without a URL, ask for one — do not invent one." },
40
- "skip_asset_generation": { "type": "boolean", "description": "All-in-one switch for downstream agents (e.g. template-creator) that only want the DSL shape: every produced AssetRef is marked as already generated with placeholder URLs (image: https://placeholder.local/stub.png, video: stub.mp4, audio: stub.mp3). Implies the equivalent of --stub-image-url + --stub-video-url with sentinel defaults plus the same rewrite for gen-voice / gen-digital-human assets. Useful when the agent only needs to inspect DSL structure or feed it into try_render_local with all assets pre-stubbed." }
81
+ "stub_image_url": {
82
+ "type": "string",
83
+ "description": "Test-mode image stub URL. Only pass when the user explicitly says things like 'just testing / don't actually generate / use a placeholder image / stub URL / save credits' AND provides a concrete URL. With this set, every image AssetRef in the produced DSL is written as source=existing, status=generated, url=<this URL> — no gen-image call. Do not pass otherwise; if the user expressed the intent without a URL, ask for one do not invent one."
84
+ },
85
+ "stub_video_url": {
86
+ "type": "string",
87
+ "description": "Test-mode video stub URL. Only pass when the user explicitly says things like 'just testing / don't actually generate the video / placeholder clip / save credits' AND provides a concrete URL. With this set, every video AssetRef in the produced DSL is written as source=existing, status=generated, url=<this URL> — no gen-video call. Do not pass otherwise; if the user expressed the intent without a URL, ask for one — do not invent one."
88
+ },
89
+ "skip_asset_generation": {
90
+ "type": "boolean",
91
+ "description": "All-in-one switch for downstream agents (e.g. template-creator) that only want the DSL shape: every produced AssetRef is marked as already generated with placeholder URLs (image: https://placeholder.local/stub.png, video: stub.mp4, audio: stub.mp3). Implies the equivalent of --stub-image-url + --stub-video-url with sentinel defaults plus the same rewrite for gen-voice / gen-digital-human assets. Useful when the agent only needs to inspect DSL structure or feed it into try_render_local with all assets pre-stubbed."
92
+ }
41
93
  },
42
- "required": ["topic"]
94
+ "required": [
95
+ "topic"
96
+ ]
97
+ },
98
+ "ui": {
99
+ "primary": [
100
+ "topic",
101
+ "platform",
102
+ "duration"
103
+ ],
104
+ "advanced": [
105
+ "style",
106
+ "ratio",
107
+ "scenes",
108
+ "voice_id",
109
+ "template_id",
110
+ "headline",
111
+ "subheadline",
112
+ "carousel_items",
113
+ "caption_lines"
114
+ ],
115
+ "hidden": [
116
+ "stub_image_url",
117
+ "stub_video_url",
118
+ "skip_asset_generation"
119
+ ]
43
120
  }
44
121
  }
@@ -87,5 +87,27 @@
87
87
  "required": [
88
88
  "prompt"
89
89
  ]
90
+ },
91
+ "ui": {
92
+ "primary": [
93
+ "prompt",
94
+ "model",
95
+ "duration",
96
+ "ratio"
97
+ ],
98
+ "advanced": [
99
+ "resolution",
100
+ "first_frame",
101
+ "last_frame",
102
+ "reference",
103
+ "generate_audio",
104
+ "camera_fixed",
105
+ "negative_prompt"
106
+ ],
107
+ "hidden": [
108
+ "json_output",
109
+ "seed",
110
+ "person_generation"
111
+ ]
90
112
  }
91
113
  }
@@ -45,5 +45,19 @@
45
45
  }
46
46
  },
47
47
  "required": []
48
+ },
49
+ "ui": {
50
+ "primary": [
51
+ "text",
52
+ "voice_id"
53
+ ],
54
+ "advanced": [
55
+ "speed"
56
+ ],
57
+ "hidden": [
58
+ "json_output",
59
+ "list_voices",
60
+ "local"
61
+ ]
48
62
  }
49
63
  }
@@ -40,5 +40,18 @@
40
40
  "required": [
41
41
  "url"
42
42
  ]
43
+ },
44
+ "ui": {
45
+ "primary": [
46
+ "url"
47
+ ],
48
+ "advanced": [
49
+ "scene_threshold",
50
+ "skip_asr",
51
+ "skip_keyframes"
52
+ ],
53
+ "hidden": [
54
+ "json_output"
55
+ ]
43
56
  }
44
57
  }
@@ -147,5 +147,43 @@
147
147
  "required": [
148
148
  "url"
149
149
  ]
150
+ },
151
+ "ui": {
152
+ "primary": [
153
+ "url",
154
+ "duration",
155
+ "scroll_through"
156
+ ],
157
+ "advanced": [
158
+ "device",
159
+ "viewport",
160
+ "max_duration",
161
+ "stop_when_selector",
162
+ "stop_when_hidden",
163
+ "scroll_step",
164
+ "scroll_interval",
165
+ "scroll_pause_top",
166
+ "scroll_pause_bottom",
167
+ "color_scheme",
168
+ "wait_for_selector",
169
+ "wait_for_timeout",
170
+ "vod_title",
171
+ "cover_at_sec",
172
+ "browser"
173
+ ],
174
+ "hidden": [
175
+ "output",
176
+ "storyboard",
177
+ "template",
178
+ "param",
179
+ "list_templates",
180
+ "user_agent",
181
+ "timeout",
182
+ "ignore_https_errors",
183
+ "storage_state",
184
+ "cookies",
185
+ "no_upload",
186
+ "keep_webm"
187
+ ]
150
188
  }
151
189
  }