@remixmate/cli 0.9.17 → 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.
- package/dist/manifest.json +949 -14
- package/dist/project/host.d.ts +15 -1
- package/dist/project/host.js +47 -2
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +1 -0
- package/dist/runner.d.ts +18 -0
- package/dist/runner.js +28 -2
- package/dist/skill-schema.d.ts +40 -0
- package/dist/skill-schema.js +108 -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.json +20 -0
- package/skills/gen-script/skill.json +94 -17
- package/skills/gen-video/skill.json +22 -0
- package/skills/gen-voice/skill.json +14 -0
- package/skills/render-video/SKILL.md +11 -1
- package/skills/render-video/scripts/render_video.py +20 -0
- package/skills/render-video/skill.json +1 -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/registry.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface SkillDef {
|
|
|
20
20
|
createsTake: boolean;
|
|
21
21
|
/** Attach to an existing take, never create one — see skill-schema.ts. */
|
|
22
22
|
joinsTake: boolean;
|
|
23
|
+
/** Argv keys that downgrade createsTake to joinsTake — see skill-schema.ts. */
|
|
24
|
+
createsTakeUnless: string[];
|
|
23
25
|
skillDir: string;
|
|
24
26
|
/** Absolute path to the python entry script (only when entry.type === 'python'). */
|
|
25
27
|
scriptAbsolutePath?: string;
|
package/dist/registry.js
CHANGED
|
@@ -40,6 +40,7 @@ function loadOne(skillDir) {
|
|
|
40
40
|
auth: resolveAuthMode(raw),
|
|
41
41
|
createsTake: raw.createsTake === true,
|
|
42
42
|
joinsTake: raw.joinsTake === true,
|
|
43
|
+
createsTakeUnless: Array.isArray(raw.createsTakeUnless) ? raw.createsTakeUnless : [],
|
|
43
44
|
skillDir,
|
|
44
45
|
};
|
|
45
46
|
if (entry.type === 'python') {
|
package/dist/runner.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Resolves to the child process's exit code so cli.ts can propagate it.
|
|
11
11
|
*/
|
|
12
|
+
import { type SkillDef } from './registry.js';
|
|
12
13
|
import type { ParsedArgs } from './argv.js';
|
|
13
14
|
export interface RunOptions {
|
|
14
15
|
/** Override the skills directory (defaults to the package's bundled skills/). */
|
|
@@ -19,3 +20,20 @@ export interface RunOptions {
|
|
|
19
20
|
parsedArgs: ParsedArgs;
|
|
20
21
|
}
|
|
21
22
|
export declare function runSkill(skillName: string, opts: RunOptions): Promise<number>;
|
|
23
|
+
/** What this invocation is allowed to do about attribution. */
|
|
24
|
+
export type TakeMode = 'create' | 'join' | 'none';
|
|
25
|
+
/**
|
|
26
|
+
* Decide the take mode from the skill's declaration *and* what this particular
|
|
27
|
+
* invocation was asked to do.
|
|
28
|
+
*
|
|
29
|
+
* The manifest flags alone are not enough, because they describe the skill, not
|
|
30
|
+
* the call: `render-video --help` prints usage and exits, and
|
|
31
|
+
* `render-video --resolve-only` stops after asset resolution. Both used to run
|
|
32
|
+
* the full createsTake path, so a help query was enough to mint an empty take
|
|
33
|
+
* and lazily create 「未分类」 — the exact clutter project/take.ts sets out to
|
|
34
|
+
* avoid ("an empty VideoProject is worse clutter than an unattributed image").
|
|
35
|
+
*
|
|
36
|
+
* Note that a downgraded run still *joins*: when its own pipeline already
|
|
37
|
+
* started a take under the same grouping key, assets it generates stay attached.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveTakeMode(skill: Pick<SkillDef, 'createsTake' | 'joinsTake' | 'createsTakeUnless'>, args: ParsedArgs): TakeMode;
|
package/dist/runner.js
CHANGED
|
@@ -61,6 +61,31 @@ export async function runSkill(skillName, opts) {
|
|
|
61
61
|
emitBillingFooter();
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Decide the take mode from the skill's declaration *and* what this particular
|
|
66
|
+
* invocation was asked to do.
|
|
67
|
+
*
|
|
68
|
+
* The manifest flags alone are not enough, because they describe the skill, not
|
|
69
|
+
* the call: `render-video --help` prints usage and exits, and
|
|
70
|
+
* `render-video --resolve-only` stops after asset resolution. Both used to run
|
|
71
|
+
* the full createsTake path, so a help query was enough to mint an empty take
|
|
72
|
+
* and lazily create 「未分类」 — the exact clutter project/take.ts sets out to
|
|
73
|
+
* avoid ("an empty VideoProject is worse clutter than an unattributed image").
|
|
74
|
+
*
|
|
75
|
+
* Note that a downgraded run still *joins*: when its own pipeline already
|
|
76
|
+
* started a take under the same grouping key, assets it generates stay attached.
|
|
77
|
+
*/
|
|
78
|
+
export function resolveTakeMode(skill, args) {
|
|
79
|
+
// Introspection — the skill prints its usage and exits without producing
|
|
80
|
+
// anything, so it must not touch the backend at all.
|
|
81
|
+
if (args.help === true || args.h === true)
|
|
82
|
+
return 'none';
|
|
83
|
+
if (skill.createsTake) {
|
|
84
|
+
const downgraded = (skill.createsTakeUnless ?? []).some((key) => args[key] === true);
|
|
85
|
+
return downgraded ? 'join' : 'create';
|
|
86
|
+
}
|
|
87
|
+
return skill.joinsTake ? 'join' : 'none';
|
|
88
|
+
}
|
|
64
89
|
/**
|
|
65
90
|
* Attach this invocation to a take, publishing the result as `CONVERSATION_ID`.
|
|
66
91
|
* That variable is the only thing that makes an uploaded asset reachable from
|
|
@@ -83,7 +108,8 @@ export async function runSkill(skillName, opts) {
|
|
|
83
108
|
* asset still uploads, just unattributed — exactly the old behaviour.
|
|
84
109
|
*/
|
|
85
110
|
async function applyTakeContext(skill, opts, auth) {
|
|
86
|
-
|
|
111
|
+
const mode = resolveTakeMode(skill, opts.parsedArgs);
|
|
112
|
+
if (mode === 'none')
|
|
87
113
|
return;
|
|
88
114
|
if ((process.env.CONVERSATION_ID ?? '').trim())
|
|
89
115
|
return;
|
|
@@ -95,7 +121,7 @@ async function applyTakeContext(skill, opts, auth) {
|
|
|
95
121
|
preflightToken: auth.token,
|
|
96
122
|
});
|
|
97
123
|
let take;
|
|
98
|
-
if (
|
|
124
|
+
if (mode === 'create') {
|
|
99
125
|
const jobIdArg = opts.parsedArgs.job_id;
|
|
100
126
|
const jobId = typeof jobIdArg === 'number' ? jobIdArg : Number(jobIdArg);
|
|
101
127
|
const flagProject = opts.parsedArgs.project;
|
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[];
|
|
@@ -72,6 +96,15 @@ export interface RawSkillJson {
|
|
|
72
96
|
* worse clutter than an unattributed asset.
|
|
73
97
|
*/
|
|
74
98
|
joinsTake?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Parsed-argv keys (snake_case, as produced by argv.ts) whose presence means
|
|
101
|
+
* *this* invocation won't finish a video — `render-video --resolve-only`
|
|
102
|
+
* resolves assets and stops. Such a run degrades from createsTake to
|
|
103
|
+
* joinsTake: it still attaches to a take its own pipeline already started,
|
|
104
|
+
* but never brings one — nor the lazily created 「未分类」 — into being for a
|
|
105
|
+
* pass that may end in a contract error and no video at all.
|
|
106
|
+
*/
|
|
107
|
+
createsTakeUnless?: string[];
|
|
75
108
|
}
|
|
76
109
|
/**
|
|
77
110
|
* Resolve a skill's auth mode, defaulting for records that predate the field.
|
|
@@ -91,6 +124,13 @@ export declare const REQUIRED_SKILL_JSON_FIELDS: readonly ["name", "tier", "titl
|
|
|
91
124
|
* skills keep working untouched. Returns null when neither is present.
|
|
92
125
|
*/
|
|
93
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;
|
|
94
134
|
/**
|
|
95
135
|
* Validate a parsed skill.json against the directory it lives in. Returns an
|
|
96
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.
|
|
@@ -67,6 +158,22 @@ export function validateSkillJson(raw, skillId) {
|
|
|
67
158
|
if (raw.joinsTake != null && typeof raw.joinsTake !== 'boolean') {
|
|
68
159
|
errors.push(`skill.json.joinsTake must be a boolean, got '${String(raw.joinsTake)}'`);
|
|
69
160
|
}
|
|
161
|
+
if (raw.createsTakeUnless != null) {
|
|
162
|
+
if (!Array.isArray(raw.createsTakeUnless) ||
|
|
163
|
+
raw.createsTakeUnless.some((f) => typeof f !== 'string' || f === '')) {
|
|
164
|
+
errors.push("skill.json.createsTakeUnless must be an array of non-empty argv keys");
|
|
165
|
+
}
|
|
166
|
+
else if (raw.createsTake !== true) {
|
|
167
|
+
// It only ever downgrades createsTake; on any other skill it would read as
|
|
168
|
+
// working while doing nothing.
|
|
169
|
+
errors.push("skill.json.createsTakeUnless requires createsTake=true");
|
|
170
|
+
}
|
|
171
|
+
else if (raw.createsTakeUnless.some((f) => f.includes('-'))) {
|
|
172
|
+
// argv.ts hands the dispatcher snake_case keys, so `--resolve-only`
|
|
173
|
+
// arrives as `resolve_only`. A kebab entry would silently never match.
|
|
174
|
+
errors.push("skill.json.createsTakeUnless entries must be snake_case argv keys (e.g. 'resolve_only', not 'resolve-only')");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
70
177
|
if (raw.createsTake === true && raw.joinsTake === true) {
|
|
71
178
|
errors.push("skill.json cannot set both 'createsTake' and 'joinsTake' — createsTake already implies joining");
|
|
72
179
|
}
|
|
@@ -76,6 +183,7 @@ export function validateSkillJson(raw, skillId) {
|
|
|
76
183
|
if ((raw.createsTake === true || raw.joinsTake === true) && resolveAuthMode(raw) === 'none') {
|
|
77
184
|
errors.push("skill.json declares createsTake/joinsTake but auth='none' — a purely local skill has nothing to attribute");
|
|
78
185
|
}
|
|
186
|
+
errors.push(...validateUiHints(raw));
|
|
79
187
|
if (raw.name && raw.name !== skillId) {
|
|
80
188
|
errors.push(`skill.json.name='${raw.name}' does not match directory name '${skillId}'`);
|
|
81
189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
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": [
|
|
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
|
}
|
|
@@ -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
|
}
|