@remixmate/cli 0.9.18 → 0.9.20
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/README.md +6 -6
- package/README.zh-CN.md +6 -6
- package/dist/handlers/gen-image.d.ts +2 -1
- package/dist/handlers/gen-image.js +4 -5
- package/dist/manifest.json +953 -18
- package/dist/project/host.d.ts +15 -1
- package/dist/project/host.js +47 -2
- package/dist/skill-schema.d.ts +31 -0
- package/dist/skill-schema.js +92 -0
- package/package.json +1 -1
- package/skills/export-jianying/skill.json +62 -10
- package/skills/gen-digital-human/skill.json +23 -0
- package/skills/gen-image/SKILL.md +9 -20
- package/skills/gen-image/skill.json +24 -4
- package/skills/gen-image/version.json +2 -2
- package/skills/gen-script/SKILL.md +25 -6
- package/skills/gen-script/scripts/gen_script.py +103 -5
- package/skills/gen-script/skill.json +95 -18
- package/skills/gen-video/SKILL.md +47 -50
- package/skills/gen-video/skill.json +26 -4
- package/skills/gen-video/version.json +2 -2
- package/skills/gen-voice/skill.json +14 -0
- package/skills/template-registry/scripts/match_template.py +14 -0
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +11 -0
- package/skills/video-parser/skill.json +13 -0
- package/skills/web-record/skill.json +38 -0
- package/skills/web-screenshot/skill.json +21 -0
package/dist/project/host.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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. */
|
package/dist/project/host.js
CHANGED
|
@@ -62,8 +62,51 @@ export function detectHostSession(env = process.env) {
|
|
|
62
62
|
}
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
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
|
-
|
|
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)
|
package/dist/skill-schema.d.ts
CHANGED
|
@@ -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.
|
package/dist/skill-schema.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.9.20",
|
|
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": [
|
|
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": {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
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
|
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gen-image
|
|
3
3
|
description: |
|
|
4
|
-
AI image generation skill: produce an image from a text prompt, or do image-to-image with reference images. Backed by ab-api's `/model/genImg` (Seedream
|
|
4
|
+
AI image generation skill: produce an image from a text prompt, or do image-to-image with reference images. Backed by ab-api's `/model/genImg` (the Seedream 5.0 family).
|
|
5
5
|
|
|
6
6
|
Use this skill immediately whenever the user asks for any of:
|
|
7
7
|
- AI image generation, text-to-image, "draw me ...", "generate an image of ..."
|
|
8
8
|
- Image-to-image, reference image, style transfer, image variation
|
|
9
|
-
- Generate an image with Doubao / Seedream
|
|
9
|
+
- Generate an image with Doubao / Seedream
|
|
10
10
|
- Provide a prompt and ask for an image
|
|
11
11
|
|
|
12
12
|
Even without an explicit "use AI", any request that turns a description into an image should route here.
|
|
13
13
|
triggers:
|
|
14
14
|
- AI image generation, text-to-image, "draw me ...", "generate an image of ..."
|
|
15
15
|
- Image-to-image, reference image, style transfer, image variation
|
|
16
|
-
- Generate an image with Doubao / Seedream
|
|
16
|
+
- Generate an image with Doubao / Seedream
|
|
17
17
|
- Provide a prompt and ask for an image
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
# AI Image Generation Skill
|
|
21
21
|
|
|
22
|
-
Wraps ab-api's `POST /model/genImg` (the same endpoint the web studio uses), authenticated with the **Tianyan privateToken**, routed through LiteLLM to **Seedream**
|
|
22
|
+
Wraps ab-api's `POST /model/genImg` (the same endpoint the web studio uses), authenticated with the **Tianyan privateToken**, routed through LiteLLM to the **Seedream 5.0** family.
|
|
23
23
|
|
|
24
24
|
## Models and sizes
|
|
25
25
|
|
|
@@ -31,7 +31,6 @@ the names below are the stable aliases to use.
|
|
|
31
31
|
|-----------|------------|------------------|
|
|
32
32
|
| `seedream` | Default. General-purpose, highest output resolution. | up to 14 |
|
|
33
33
|
| `seedream-pro` | High-fidelity variant: better placement/element control, more faithful text rendering. Costs more per image. | up to 10 |
|
|
34
|
-
| `gemini` | Gemini 3 Pro. | up to 4 |
|
|
35
34
|
|
|
36
35
|
- **Seedream**: `--size` is an aspect ratio (e.g. `1:1`, `9:16`) or `WxH`. The backend maps the
|
|
37
36
|
ratio to that model's own pixel preset and rescales out-of-range sizes, so prefer a ratio
|
|
@@ -39,8 +38,8 @@ the names below are the stable aliases to use.
|
|
|
39
38
|
- **`seedream-pro`** additionally supports `3:2` / `2:3` / `21:9`, and caps output at ~2K
|
|
40
39
|
(about 4.6 MP). Asking it for 4K pixels gets scaled down, not rejected — use `seedream`
|
|
41
40
|
when you need a genuinely larger image.
|
|
42
|
-
-
|
|
43
|
-
|
|
41
|
+
- **`--resolution`** is inert today: it only ever applied to Gemini, which is not wired up on
|
|
42
|
+
the gateway. Control image dimensions with `--size`.
|
|
44
43
|
|
|
45
44
|
## Auth & environment
|
|
46
45
|
|
|
@@ -71,14 +70,6 @@ remixmate gen-image \
|
|
|
71
70
|
--size "9:16"
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
```bash
|
|
75
|
-
remixmate gen-image \
|
|
76
|
-
--prompt "<image description>" \
|
|
77
|
-
--model gemini \
|
|
78
|
-
--size "16:9" \
|
|
79
|
-
--resolution "2K"
|
|
80
|
-
```
|
|
81
|
-
|
|
82
73
|
```bash
|
|
83
74
|
# High-fidelity: precise placement, legible on-image text
|
|
84
75
|
remixmate gen-image \
|
|
@@ -95,7 +86,6 @@ Reference images accept local file paths, HTTPS URLs, or data URIs. Pass `--refe
|
|
|
95
86
|
- **`seedream-pro`**: up to **10** reference images. Best choice when the edit has to land in a
|
|
96
87
|
specific spot — describe the target region in the prompt (e.g. "in the marked area at the
|
|
97
88
|
bottom left") and it holds position far better than `seedream`.
|
|
98
|
-
- **`gemini`**: up to **4** reference images.
|
|
99
89
|
|
|
100
90
|
Over-the-limit runs fail fast in the CLI, before spending credits.
|
|
101
91
|
|
|
@@ -129,13 +119,12 @@ remixmate gen-image \
|
|
|
129
119
|
| Flag | Description | Default |
|
|
130
120
|
|------|-------------|---------|
|
|
131
121
|
| `-p` / `--prompt` | Description (required) | — |
|
|
132
|
-
| `-m` / `--model` | `seedream` / `seedream-pro`
|
|
133
|
-
| `-s` / `--size` |
|
|
134
|
-
| `--resolution` | Gemini only: `1K` / `2K` / `4K` | `1K` |
|
|
122
|
+
| `-m` / `--model` | `seedream` / `seedream-pro` | see `MM_IMAGE_MODEL` |
|
|
123
|
+
| `-s` / `--size` | Aspect ratio or WxH | `1:1` |
|
|
135
124
|
| `-n` | Number of images, 1–4 | `1` |
|
|
136
125
|
| `-g` / `--guidance-scale` | Guidance scale (when supported) | backend default |
|
|
137
126
|
| `--reference` | Reference image (repeatable; local path / URL / data URI) | none |
|
|
138
|
-
| `--image-strength` | Reference strength 0–1
|
|
127
|
+
| `--image-strength` | Reference strength 0–1 | backend default |
|
|
139
128
|
| `--negative-prompt` | Things to avoid | none |
|
|
140
129
|
| `--seed` | Random seed (reproducibility) | none |
|
|
141
130
|
| `--watermark` | Add a watermark (no `--no-watermark` opt-out) | backend default |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"tier": "atomic",
|
|
5
5
|
"category": "asset",
|
|
6
6
|
"title": "AI Image Generation",
|
|
7
|
-
"description": "AI image generation: produce an image from a text prompt. Supports the Seedream family (
|
|
7
|
+
"description": "AI image generation: produce an image from a text prompt. Supports the Seedream 5.0 family (Lite and a high-fidelity 'pro' variant), plus image-to-image with reference images.",
|
|
8
8
|
"auth": "required",
|
|
9
9
|
"joinsTake": true,
|
|
10
10
|
"envVars": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"model": {
|
|
28
28
|
"type": "string",
|
|
29
|
-
"description": "Model: 'seedream' (default
|
|
29
|
+
"description": "Model: 'seedream' (default, Seedream 5.0 Lite) or 'seedream-pro' (high fidelity, precise placement and on-image text; costs more per image)"
|
|
30
30
|
},
|
|
31
31
|
"size": {
|
|
32
32
|
"type": "string",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"2K",
|
|
40
40
|
"4K"
|
|
41
41
|
],
|
|
42
|
-
"description": "Output resolution (Gemini
|
|
42
|
+
"description": "Output resolution tier. Currently inert: the only backend model that read it (Gemini) is not wired up, so this is ignored — use `size` for image dimensions."
|
|
43
43
|
},
|
|
44
44
|
"n": {
|
|
45
45
|
"type": "number",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"items": {
|
|
51
51
|
"type": "string"
|
|
52
52
|
},
|
|
53
|
-
"description": "Reference images for image-to-image: local file path, https URL, or data URI. Pass multiple to blend several references (seedream: max 14, seedream-pro: max 10
|
|
53
|
+
"description": "Reference images for image-to-image: local file path, https URL, or data URI. Pass multiple to blend several references (seedream: max 14, seedream-pro: max 10 — over the limit fails before spending credits)."
|
|
54
54
|
},
|
|
55
55
|
"image_strength": {
|
|
56
56
|
"type": "number",
|
|
@@ -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
|
+
"image_strength"
|
|
95
|
+
],
|
|
96
|
+
"hidden": [
|
|
97
|
+
"json_output",
|
|
98
|
+
"seed",
|
|
99
|
+
"guidance_scale",
|
|
100
|
+
"watermark",
|
|
101
|
+
"resolution"
|
|
102
|
+
]
|
|
83
103
|
}
|
|
84
104
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "gen-image",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "337",
|
|
5
|
-
"version": "
|
|
6
|
-
"skillDescription": "AI image generation skill: produce an image from a text prompt, or do image-to-image with reference images. Backed by ab-api's `/model/genImg` (Seedream
|
|
5
|
+
"version": "V10",
|
|
6
|
+
"skillDescription": "AI image generation skill: produce an image from a text prompt, or do image-to-image with reference images. Backed by ab-api's `/model/genImg` (the Seedream 5.0 family).\n\nUse this skill immediately whenever the user asks for any of:\n- AI image generation, text-to-image, \"draw me ...\", \"generate an image of ...\"\n- Image-to-image, reference image, style transfer, image variation\n- Generate an image with Doubao / Seedream\n- Provide a prompt and ask for an image\n\nEven without an explicit \"use AI\", any request that turns a description into an image should route here."
|
|
7
7
|
}
|
|
@@ -106,6 +106,18 @@ This skill does not hit any external API; no token required. The script only doe
|
|
|
106
106
|
Show **every scene in full** — do not collapse them with phrases like "scenes 2–6 same as above".
|
|
107
107
|
The user is reviewing the narration word by word; a summary they cannot proofread defeats the gate.
|
|
108
108
|
|
|
109
|
+
> ⚠️ **Read every value back from the returned DSL, never from what you meant to pass.**
|
|
110
|
+
> The summary exists so the user can catch a wrong tool call; a summary written from intent hides
|
|
111
|
+
> exactly the bug it should surface.
|
|
112
|
+
> - Durations ← `scenes[].duration`, **not** your `--duration` argument (`fit-caption` / `fit-narration`
|
|
113
|
+
> templates recompute it and ignore the target you passed).
|
|
114
|
+
> - On-screen text ← `textLayers[]`, `customPayload.caption.lines`, `customPayload.carousel.items` —
|
|
115
|
+
> quoted verbatim, with the counts you actually see.
|
|
116
|
+
> - If something you intended to set is missing or empty in the returned DSL, that is a failed call:
|
|
117
|
+
> say so and re-run `gen_script.py` with the right flags. Listing caption lines that are not in
|
|
118
|
+
> `customPayload.caption.lines` means the user confirms a script that does not exist and pays to
|
|
119
|
+
> render something else.
|
|
120
|
+
|
|
109
121
|
### Summary content
|
|
110
122
|
|
|
111
123
|
The agent should show the following in clear Markdown:
|
|
@@ -156,9 +168,12 @@ For these templates the picture comes from `customPayload.carousel.items`, which
|
|
|
156
168
|
When the user selects a `carousel-caption` template (or any template whose `assetRequirements` is image/video-only and whose `payloadStyle` is `carousel-caption`):
|
|
157
169
|
|
|
158
170
|
1. **Extract every media URL the user provided** (image or video links in the prompt) and pass each one as a separate `--carousel-items <url>` flag — preserve the user's order, and pass the URLs **verbatim** (do not rewrite host/path/query).
|
|
159
|
-
2.
|
|
171
|
+
2. Pass each on-screen caption line as `--caption-lines '<text>'`. Whether this is optional depends on the template's `capabilities.durationStrategy`, **not** on `needsNarration` (every `carousel-caption` template has `needsNarration: false`):
|
|
172
|
+
- `durationStrategy: fit-caption` (e.g. `spotlight-card`) → **caption lines are mandatory.** The template has no narration and the typewriter copy is both the content and the clock: it is what the video says *and* what decides how long it runs. **If the user did not supply the copy, write it yourself** from the material you researched (repo README, page screenshots, the topic) and pass it. `gen_script.py` hard-fails on an empty caption for these templates.
|
|
173
|
+
- `durationStrategy: fit-images` (e.g. `adaptive-image-video` / `image-to-video`) → purely visual, captions genuinely optional; duration comes from the image count.
|
|
160
174
|
3. **Never call gen_script for a `carousel-caption` template without `--carousel-items`.** If the user picked such a template but provided no media, ask them for the image/video URLs first — do not generate an empty carousel.
|
|
161
175
|
4. Do **not** route these user-provided images through `gen-image`; they are existing assets and go straight into the carousel.
|
|
176
|
+
5. Nothing downstream fills these in for you. `gen_script.py` routes on `capabilities.payloadStyle` alone — omitting the flags does **not** fall back to a generic path that generates images or writes copy; it assembles an empty carousel / empty caption. There is no auto-generation of caption text anywhere in the pipeline.
|
|
162
177
|
|
|
163
178
|
### Command example
|
|
164
179
|
|
|
@@ -266,7 +281,9 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
266
281
|
|------|-------------|---------|
|
|
267
282
|
| `--topic` | Video topic (required unless `--validate`). | — |
|
|
268
283
|
| `--platform` | Target platform: `douyin` / `xiaohongshu` / `bilibili` / `wechat` / `youtube` / `generic`. | `generic` |
|
|
269
|
-
| `--
|
|
284
|
+
| `--headline` | On-screen main title (4–12 chars / ~3 words). Written to `meta.headline` + `textLayers[role=headline]`. **Pass it whenever the user gave a title** — otherwise headline falls back to the long-form topic and overflows the top text layer. | falls back to `--topic` |
|
|
285
|
+
| `--subheadline` | On-screen subtitle / project name / slogan. Written to `meta.subheadline` + `textLayers[role=subheadline]`. Not the same thing as CC subtitles (`global.subtitle`). | `""` |
|
|
286
|
+
| `--duration` | Target duration (seconds). Templates whose `durationStrategy` is `fit-caption` / `fit-narration` recompute the real duration and ignore this value. | `30` |
|
|
270
287
|
| `--style` | Style tag. | — |
|
|
271
288
|
| `--ratio` | Aspect ratio. | `16:9` |
|
|
272
289
|
| `--scenes` | Scene count. | auto-planned |
|
|
@@ -279,7 +296,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
279
296
|
| `--stub-image-url` | Test mode: every image AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_IMAGE_URL`). | — |
|
|
280
297
|
| `--stub-video-url` | Test mode: every video AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_VIDEO_URL`). | — |
|
|
281
298
|
| `--carousel-items` | Repeatable. Media URL placed directly into `customPayload.carousel.items` for `carousel-caption` templates (e.g. `adaptive-image-video`, `spotlight-card`). Bypasses gen-image. **Required** for `carousel-caption` templates when the user supplies images. | — |
|
|
282
|
-
| `--caption-lines` | Repeatable. On-screen typewriter caption line for `carousel-caption` templates → `customPayload.caption.lines
|
|
299
|
+
| `--caption-lines` | Repeatable. On-screen typewriter caption line for `carousel-caption` templates → `customPayload.caption.lines` (max 10 lines). Supports `**emphasis**`. **Required** for `durationStrategy: fit-caption` templates (`spotlight-card`) — write the lines yourself if the user did not supply them. Optional only for `fit-images` templates. | — |
|
|
283
300
|
|
|
284
301
|
## DSL generation principles
|
|
285
302
|
|
|
@@ -295,7 +312,7 @@ Apply the following principles when producing the DSL:
|
|
|
295
312
|
instead of one continuous paragraph.
|
|
296
313
|
5. **Moderate scene count**: 30-second videos work well with 4–6 scenes, 60-second videos with 6–10.
|
|
297
314
|
6. **Leave room for templates**: pick generic layouts; do not assume a specific template implementation.
|
|
298
|
-
7. **Image model allowlist**: every `type: image` + `source: gen-image` `AssetRef`'s `payload.model` **must** be one of the values in the table below. **Never** use display names, short forms, or made-up ids (e.g. `seedream`, `
|
|
315
|
+
7. **Image model allowlist**: every `type: image` + `source: gen-image` `AssetRef`'s `payload.model` **must** be one of the values in the table below. **Never** use display names, short forms, or made-up ids (e.g. `seedream`, `seedream-5`, etc.).
|
|
299
316
|
|
|
300
317
|
### Allowlist `model` values aligned with gen-image
|
|
301
318
|
|
|
@@ -307,12 +324,11 @@ prefix and version — not a short alias:
|
|
|
307
324
|
|-----------------|--------------|----------|-------|
|
|
308
325
|
| `doubao/doubao-seedream-5-0-260128` | Seedream 5.0 Lite | Volcano | Default. Highest output resolution, up to 14 reference images. |
|
|
309
326
|
| `doubao/doubao-seedream-5-0-pro-260628` | Seedream 5.0 Pro | Volcano | High fidelity: precise element placement, faithful on-image text. Up to 10 reference images, caps out around 2K. Costs noticeably more per image. |
|
|
310
|
-
| `gemini-3-pro` | Gemini 3 Pro | Google | Up to 4 reference images. |
|
|
311
327
|
|
|
312
328
|
**Agent behavior (avoid accidentally rewriting `model`)**:
|
|
313
329
|
|
|
314
330
|
- `gen_script.py` already writes a valid `payload.model` (`doubao/doubao-seedream-5-0-260128` unless `DEFAULT_IMAGE_MODEL` overrides it). When the user only asks to refine narration, change `payload.prompt`, add or remove scenes, etc. and does **not** ask to change the image model, the agent **must keep** each image asset's original `payload.model` — do not replace it under the guise of "polishing the script".
|
|
315
|
-
- **Only when the user explicitly asks to change the image model** (e.g. switches to the Pro variant
|
|
331
|
+
- **Only when the user explicitly asks to change the image model** (e.g. switches to the Pro variant), update the corresponding image `AssetRef`'s `payload.model` to the matching row id from the table. Writing a display name or an alias into JSON is wrong.
|
|
316
332
|
- When creating a new image `AssetRef`, pick one of the values above for `payload.model`; default to `doubao/doubao-seedream-5-0-260128` to match the script, or to whichever value the user specified.
|
|
317
333
|
- If a run fails with an unknown-model error, the catalog has moved on from this table — check `/model/capabilities` rather than guessing a version string.
|
|
318
334
|
|
|
@@ -321,6 +337,9 @@ prefix and version — not a short alias:
|
|
|
321
337
|
- **Schema validation failed**: check the DSL JSON shape and required fields against the schema.
|
|
322
338
|
- **Scene duration mismatch**: adjust the narration length or the scene duration.
|
|
323
339
|
- **Invalid platform**: look at the supported-platform list.
|
|
340
|
+
- **`carousel-caption template ... needs visual or text content`**: you passed neither `--carousel-items` nor `--caption-lines`. Pass the user's media URLs (and caption lines where the template requires them).
|
|
341
|
+
- **`template ... is typewriter-driven ... but --caption-lines is empty`**: a `fit-caption` template (e.g. `spotlight-card`) got no caption. Write the copy yourself if the user did not supply it, then pass one `--caption-lines` per line.
|
|
342
|
+
- **`duration Ns is below/above template ... supportedDurations`**: the assembled DSL falls outside the range the template declares it was designed for. Below the minimum usually means the content is too thin (add caption lines / narration / scenes); above the maximum means trimming content or lowering `--duration`. This is enforced at generation time on purpose — a degenerate video still costs full render credits.
|
|
324
343
|
|
|
325
344
|
## scripts/ contents
|
|
326
345
|
|