@remixmate/cli 0.11.0 → 0.12.1
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 +12 -2
- package/dist/template/commands.js +16 -12
- package/dist/template/constants.js +1 -1
- package/dist/template/still-cases.js +1 -0
- package/dist/template/still-sheet.js +18 -0
- package/dist/template/still.js +4 -4
- package/package.json +1 -1
- package/skills/gen-script/SKILL.md +2 -0
- package/skills/gen-script/scripts/gen_script.py +3 -3
- package/skills/gen-script/skill.json +10 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/template-authoring/SKILL.md +10 -5
- package/skills/template-authoring/version.json +1 -1
package/dist/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.
|
|
4
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"generatedAt": "2026-10-12T11:13:40.014Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -590,6 +590,14 @@
|
|
|
590
590
|
"type": "string",
|
|
591
591
|
"description": "Scene background preset, one of the template's customPayloadSchema.background.preset values. Written to every scene's customPayload.background.preset. **Must** pass when the user picked a background: the replicate card shows `背景样式: 纯色(solid)` → pass `solid`. Without it the scene keeps the template default. Requires template_id; unsupported values are rejected. `image` / `video` also need customPayload.background.assetRef on each scene."
|
|
592
592
|
},
|
|
593
|
+
"font_id": {
|
|
594
|
+
"type": "string",
|
|
595
|
+
"description": "Font library key (font.uniq_id) for the on-screen text and subtitles. Written to global.font.fontId; match_template folds it into the binding's typography and ab-render resolves and installs the real font before rendering. **Must** pass when the user picked a font: the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)` → pass the id in the parentheses. Pass the opaque key, never a CSS family name or the display name — those fall back to the template font with no error. Without it the choice is silently dropped and the video renders in the template's own font. Omit to follow the template's font pairing."
|
|
596
|
+
},
|
|
597
|
+
"font_name": {
|
|
598
|
+
"type": "string",
|
|
599
|
+
"description": "Display name of the font picked in font_id (the text before the parentheses on the card). Stored at global.font.fontName for display only; never used for rendering. Pass it together with font_id."
|
|
600
|
+
},
|
|
593
601
|
"template_id": {
|
|
594
602
|
"type": "string",
|
|
595
603
|
"description": "Template id (from template_registry). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL."
|
|
@@ -681,6 +689,8 @@
|
|
|
681
689
|
"speed",
|
|
682
690
|
"variant",
|
|
683
691
|
"background_preset",
|
|
692
|
+
"font_id",
|
|
693
|
+
"font_name",
|
|
684
694
|
"template_id",
|
|
685
695
|
"template_source",
|
|
686
696
|
"template_candidates",
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
var
|
|
1
|
+
var A=Object.defineProperty;var p=(i,s)=>A(i,"name",{value:s,configurable:!0});import{cleanDiagnosticText as f}from"../diagnostics.js";import{EXIT as r,SkillError as g}from"../errors.js";import{flagNumber as c,flagString as o,parseTemplateArgs as R,usageError as h}from"./args.js";import{runCheck as b}from"./check.js";import{runUpgrade as T}from"./upgrade.js";import{STARTER_PACKAGE as w,STARTER_VERSION as u}from"./constants.js";import{runInit as P}from"./init.js";import{runList as y}from"./list.js";import{runPreview as S}from"./preview.js";import{isPublishConfirmation as j,runPublish as _}from"./publish.js";import{MAX_FRAMES_PER_CASE as N,runStill as O}from"./still.js";import{cachedUpdateInfo as U,freshUpdateInfo as C,updateHint as E}from"../update-check.js";const z="template",x=["init","check","still","preview","publish","list","upgrade"],d={init:{flags:{force:"bool","skip-install":"bool"},maxPositionals:1,usage:`remixmate template init <dir> [--force] [--skip-install] [--json]
|
|
2
2
|
从 ${w}@${u} 创建模板工程并安装依赖(无需登录)。
|
|
3
3
|
目标目录非空时拒绝,除非 --force。--skip-install 只解压不执行 npm install。
|
|
4
4
|
`},check:{flags:{"no-write":"bool"},maxPositionals:1,usage:`remixmate template check [name] [--json] [--no-write]
|
|
5
5
|
在模板工程内运行本地校验(类型、schema、安全策略、props 契约、core 导出)。
|
|
6
6
|
不传 name 校验 templates/ 下全部模板;默认刷新 coreVersion/coreExports,--no-write 只检查。
|
|
7
|
-
`},still:{flags:{frames:"string","composition-id":"string",scale:"string","out-dir":"string"},maxPositionals:1,usage:`remixmate template still <name> [--
|
|
8
|
-
用工程内的 Remotion 在本地渲染关键帧 PNG
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
`},still:{flags:{scenes:"string",frames:"string","composition-id":"string",scale:"string","out-dir":"string","no-sheet":"bool"},maxPositionals:1,usage:`remixmate template still <name> [--scenes 1,3,cover] [--frames settled,intro] [--composition-id ID] [--scale N] [--out-dir DIR] [--no-sheet] [--json]
|
|
8
|
+
用工程内的 Remotion 在本地渲染关键帧 PNG(无需登录、不占平台资源),并拼成带标注的对比图,供用户确认画面。
|
|
9
|
+
默认:dsl-example.json 的每个场景(外加没有场景用到的 composition,如封面),按场景数分配帧:
|
|
10
|
+
1 个场景 → intro / middle(超过 30 秒为 33% / 67%)/ settled / final;2–3 个 → intro / settled;≥4 个 → settled。
|
|
11
|
+
同一场景内完全相同的帧只保留一张。template.json 的 previewHints.keyframes 可为指定场景声明关键帧。
|
|
12
|
+
--scenes:场景序号(从 1 开始)、purpose 或场景 id,逗号分隔;改完代码后只重截改动的场景。
|
|
13
|
+
--frames:settled / intro / middle / final / start / end / 百分比(33%)/ 帧号 / 秒数(2.5s),作用于每个选中的场景,每场景最多 ${N} 帧。
|
|
14
|
+
默认输出到 output/stills/<name>/(拼图为 <name>-sheet-N.png,每页 12 格);--scale 0.5 可缩小单张图片。
|
|
11
15
|
`},preview:{flags:{"props-json":"string","duration-sec":"string","composition-id":"string","timeout-sec":"string"},maxPositionals:1,usage:`remixmate template preview <name> [--props-json JSON] [--duration-sec N] [--composition-id ID] [--timeout-sec N] [--json]
|
|
12
16
|
先 check,再把模板提交到远程渲染器出预览 mp4(需登录)。
|
|
13
17
|
样例数据取自模板的 dsl-example.json,可用 --props-json 覆盖(传数组 = 每个元素一页)。
|
|
@@ -24,7 +28,7 @@ var T=Object.defineProperty;var p=(r,s)=>T(r,"name",{value:s,configurable:!0});i
|
|
|
24
28
|
通常不用手动运行:check / still / preview / publish 发现规则过期会自动更新(REMIXMATE_NO_AUTO_UPGRADE=1 关闭)。
|
|
25
29
|
没改过的 AGENTS.md / CLAUDE.md 一起更新,改过的保留、新版写 *.new;--with-docs 强制覆盖(旧文件存为 *.orig)。
|
|
26
30
|
不改动 templates/、src/、examples/、package.json。
|
|
27
|
-
`}};function
|
|
31
|
+
`}};function I(){return`Usage: remixmate template <command> [options]
|
|
28
32
|
|
|
29
33
|
Commands:
|
|
30
34
|
init <dir> 创建模板工程(无需登录)
|
|
@@ -39,10 +43,10 @@ Common options: --json --token <PrivToken> --api-base-url <url>
|
|
|
39
43
|
环境变量:AB_RENDER_URL(渲染服务地址)、REMIXMATE_STARTER_TARBALL(init 使用本地 starter 包)、
|
|
40
44
|
REMIXMATE_NO_AUTO_UPGRADE=1(不自动更新工程规则)、REMIXMATE_NO_UPDATE_CHECK=1(不检查 cli 新版本)
|
|
41
45
|
运行 remixmate template <command> --help 查看子命令说明。
|
|
42
|
-
`}p(
|
|
46
|
+
`}p(I,"templateUsage");function v(i){const[s,...e]=i;if(!s||s==="--help"||s==="-h"||s==="help")return{sub:"help",args:{positionals:[],flags:{}}};if(!x.includes(s))throw h(`未知子命令: template ${s}(可选:${x.join(" | ")})`);const t=d[s];return{sub:s,args:R(e,t.flags,{maxPositionals:t.maxPositionals})}}p(v,"parseTemplateCommand");async function M(i,s){const{flags:e,positionals:t}=s,n=e.json===!0,a=o(e,"token"),l=o(e,"api-base-url");switch(i){case"init":{if(!t[0])throw h("缺少目标目录:remixmate template init <dir>");const m=await C();return m&&process.stderr.write(`${E(m)}
|
|
43
47
|
当前 cli 会用 starter ${u} 创建工程,可能不是最新的工作流与校验规则。建议先升级 cli 再 init。
|
|
44
|
-
`),await P({dir:t[0],force:e.force===!0,json:
|
|
45
|
-
${E(
|
|
46
|
-
`)}catch{}}p(k,"printCachedUpdateHint");async function Q(
|
|
47
|
-
`),
|
|
48
|
-
`),
|
|
48
|
+
`),await P({dir:t[0],force:e.force===!0,json:n,skipInstall:e["skip-install"]===!0,...m?{cliUpdate:m}:{}}),r.OK}case"check":return b({name:t[0],json:n,noWrite:e["no-write"]===!0});case"still":return await O({name:t[0],scenes:o(e,"scenes"),noSheet:e["no-sheet"]===!0,frames:o(e,"frames"),compositionId:o(e,"composition-id"),scale:c(e,"scale"),outDir:o(e,"out-dir"),json:n}),r.OK;case"preview":return await S({name:t[0],propsJson:o(e,"props-json"),durationSec:c(e,"duration-sec"),compositionId:o(e,"composition-id"),timeoutSec:c(e,"timeout-sec"),json:n,token:a,apiBaseUrl:l}),r.OK;case"publish":{const m=await _({name:t[0],displayName:o(e,"name"),dryRun:e["dry-run"]===!0,yes:e.yes===!0,skipPreviewCheck:e["skip-preview-check"]===!0,json:n,token:a,apiBaseUrl:l});return j(m)?r.NEEDS_CONFIRMATION:r.OK}case"list":return await y({json:n,token:a,apiBaseUrl:l}),r.OK;case"upgrade":return await T({dryRun:e["dry-run"]===!0,withDocs:e["with-docs"]===!0,force:e.force===!0,json:n}),r.OK}}p(M,"dispatch");function k(){try{const i=U();i&&process.stderr.write(`
|
|
49
|
+
${E(i)}
|
|
50
|
+
`)}catch{}}p(k,"printCachedUpdateHint");async function Q(i){const s=i.includes("--json");let e;try{const t=v(i);if(e=t.sub,e==="help")return process.stdout.write(I()),r.OK;if(t.args.flags.help)return process.stdout.write("Usage: "+d[e].usage),r.OK;const n=await M(e,t.args);return e!=="init"&&k(),n}catch(t){const n=t instanceof g?t.exitCode:r.ERROR,a=t instanceof g?t.message:`❌ unexpected error: ${t.stack??String(t)}`;return process.stderr.write(f(a,8192)+`
|
|
51
|
+
`),n===r.USAGE&&e&&e!=="help"&&process.stderr.write("Usage: "+d[e].usage),e&&e!=="help"&&e!=="init"&&k(),s&&process.stdout.write(JSON.stringify({ok:!1,exitCode:n,error:f(a.replace(/^❌\s*/,""),2048)})+`
|
|
52
|
+
`),n}}p(Q,"runTemplateCommand");export{d as SUB_SPECS,x as TEMPLATE_SUBCOMMANDS,z as TEMPLATE_VERB,v as parseTemplateCommand,Q as runTemplateCommand,I as templateUsage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const t="@remixmate/template-starter",e="0.4.
|
|
1
|
+
const t="@remixmate/template-starter",e="0.4.2",E=".remixmate/version.json",o=".remixmate",_=["AGENTS.md","CLAUDE.md"],r="REMIXMATE_STARTER_TARBALL",s=".remixmate/check/index.mjs",R=".remixmate.json",x=".remixmate-state",n="output/stills",p="https://api-render.remixmate.ai",T=1280,c=720,A=60,S=["__pycache__","node_modules","dist",".DS_Store","*.test.*","*.spec.*"];export{s as CHECK_ENTRY,p as DEFAULT_RENDER_BASE_URL,_ as DOC_FILES,R as IDENTITY_FILE,T as PREVIEW_MAX_LONG_EDGE,A as PREVIEW_MAX_SECONDS,c as PREVIEW_MAX_SHORT_EDGE,o as RULES_DIR,E as RULES_VERSION_FILE,t as STARTER_PACKAGE,r as STARTER_TARBALL_ENV,e as STARTER_VERSION,x as STATE_DIR,n as STILLS_DIR,S as TAR_EXCLUDES};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var S=Object.defineProperty;var u=(r,i)=>S(r,"name",{value:i,configurable:!0});import{usageError as m}from"./args.js";import{buildSubtitleSegments as b}from"./preview.js";const d=30,$=5,y=6,L=80;function g(r,i){const e=new Set;let t=r[i];for(;t?.$ref;){if(e.has(t.$ref))return{};e.add(t.$ref),t=r[t.$ref]}return t??{}}u(g,"resolveSlot");function A(r,i){return i.split(".").reduce((e,t)=>e&&typeof e=="object"?e[t]:void 0,r)}u(A,"getPath");function M(r,i){const e={};for(const[t,o]of Object.entries(i??{})){if(!o||typeof o.from!="string")continue;const n=o.from;if(n==="textLayers"){const f=(r.textLayers??[]).find(s=>s.role===o.role);f?.content!==void 0&&(e[t]=f.content)}else{const f=A(r,n);f!==void 0&&(e[t]=f)}}return e}u(M,"applyExtractors");function F(r,i){return typeof i?.duration=="number"&&i.duration>0?i.duration:r.supportedDurations?.min??$}u(F,"sceneSeconds");function x(r,i,e,t,o,n){const f=Math.max(1,Math.round(F(r,e)*d)),s=r.slotMapping??{},a=g(s,n),c=a.compositionId?a:g(s,t.slot),p=e?M(e,t.slot==="cover"?void 0:c.propExtractors):{};e?.customPayload&&(p.templateData=e.customPayload);const h={sceneId:e?.id??`case-${o}`,compositionId:t.compositionId,startFrame:0,endFrame:f,durationFrames:f,startTime:0,endTime:f/d,props:p,subtitleSegments:b(e,f),transition:{type:i?.transitions?.default??"none",durationFrames:Math.round((i?.transitions?.duration??0)*d)}};return{index:o,purpose:n,sceneId:typeof e?.id=="string"?e.id:null,compositionId:String(t.compositionId),durationFrames:f,props:{entry:h,resolvedAssets:Array.isArray(i?.assets)?i.assets:[],typography:r.defaultTypography}}}u(x,"buildCase");function T(r,i){const e=Array.isArray(r.compositions)?r.compositions:[];if(e.length===0)return[];const t=Array.isArray(i?.scenes)?i.scenes:[],o=r.slotMapping??{},n=u(s=>{const a=s?g(o,s).compositionId:void 0;return e.find(c=>c.compositionId===a)??e.find(c=>c.slot===s)??e[0]},"compFor"),f=t.map((s,a)=>x(r,i,s,n(s.purpose),a+1,String(s.purpose??"scene")));for(const s of e){if(f.some(c=>c.compositionId===s.compositionId))continue;const a=t.find(c=>c.purpose===s.slot)??t[0];f.push(x(r,i,a,s,f.length+1,String(s.slot??"composition")))}return f}u(T,"enumerateCases");function D(r,i,e){let t=r;if(e&&(t=t.filter(n=>n.compositionId===e),t.length===0))throw m(`composition "${e}" 不在该模板中。可选: ${[...new Set(r.map(n=>n.compositionId))].join(", ")}`);if(!i||i.trim()==="all")return t;const o=[];for(const n of i.split(",").map(f=>f.trim()).filter(Boolean)){const f=/^\d+$/.test(n)?t.filter(s=>s.index===Number(n)):t.filter(s=>s.purpose===n||s.sceneId===n);if(f.length===0)throw m(`--scenes 里的 '${n}' 不存在。可选:1–${r.length},或 ${[...new Set(r.map(s=>s.purpose))].join(" / ")}`);for(const s of f)o.includes(s)||o.push(s)}return o.sort((n,f)=>n.index-f.index)}u(D,"selectCases");function l(r,i){const e=i-1,t=u(n=>Math.min(Math.max(0,Math.round(n)),e),"clamp");if(typeof r=="number")return t(r*d);const o=r.trim();if(o==="start")return 0;if(o==="end")return e;if(o==="intro")return t(Math.min(1.5*d,i*.2));if(o==="middle"||o==="mid")return t(e/2);if(o==="settled")return t(Math.min(i*.85,e-d/2));if(o==="final")return t(e-d*.2);if(/^\d+(\.\d+)?%$/.test(o))return t(Number(o.slice(0,-1))/100*e);if(/^\d+(\.\d+)?s$/.test(o))return t(Number(o.slice(0,-1))*d);if(/^\d+$/.test(o))return t(Number(o));throw m(`帧取值不合法: '${o}'(可用 settled / intro / middle / final / start / end / 百分比如 33% / 帧号 / 秒数如 2.5s)`)}u(l,"anchorFrame");function I(r,i){const e=r.split(",").map(o=>o.trim()).filter(Boolean);if(e.length===0)throw m("--frames 不能为空");const t=[];for(const o of e){const n=l(o,i);t.some(f=>f.frame===n)||t.push({label:o,frame:n})}if(t.length>y)throw m(`--frames 每个场景最多 ${y} 帧,收到 ${t.length} 帧`);return t}u(I,"parseFrameSpec");function P(r,i,e=()=>{}){const t=r.previewHints?.keyframes;if(t===void 0)return[];if(!Array.isArray(t))return e(" ⚠ previewHints.keyframes 应为数组,已忽略"),[];const o=[];return t.forEach((n,f)=>{const s=n?.scene===void 0?1:n.scene,a=n?.at===void 0?"settled":n.at,c=typeof a=="number"?a>=0:typeof a=="string";if(!Number.isInteger(s)||s<1||s>i||!c){e(` ⚠ previewHints.keyframes[${f}] 无效(scene 应为 1–${i},at 为锚点或秒数),已忽略`);return}try{l(a,2)}catch{e(` ⚠ previewHints.keyframes[${f}].at='${a}' 无法识别,已忽略`);return}o.push({scene:s,at:a,...typeof n.label=="string"?{label:n.label}:{},...typeof n.expect=="string"?{expect:n.expect}:{}})}),o}u(P,"readDeclaredKeyframes");const w=30*d;function E(r,i){return r>=4?["settled"]:r>=2?["intro","settled"]:["intro",...i>w?["33%","67%"]:["middle"],"settled","final"]}u(E,"defaultAnchors");function j(r,i,e,t=r.length){const o=new Map;for(const n of r){if(i){o.set(n,I(i,n.durationFrames));continue}const f=e.filter(a=>a.scene===n.index);if(f.length===0){const a=[];for(const c of E(t,n.durationFrames)){const p=l(c,n.durationFrames);a.some(h=>h.frame===p)||a.push({label:c,frame:p})}o.set(n,a);continue}const s=[];for(const a of f){const c=l(a.at,n.durationFrames);s.some(p=>p.frame===c)||s.push({label:a.label??String(a.at),frame:c,...a.expect?{expect:a.expect}:{}})}o.set(n,s.slice(0,y))}return o}u(j,"planFrames");export{d as FPS,y as MAX_FRAMES_PER_CASE,L as MAX_TOTAL_STILLS,l as anchorFrame,E as defaultAnchors,T as enumerateCases,I as parseFrameSpec,j as planFrames,P as readDeclaredKeyframes,D as selectCases};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var T=Object.defineProperty;var m=(t,e)=>T(t,"name",{value:e,configurable:!0});import{mkdirSync as k,mkdtempSync as y,rmSync as S,writeFileSync as v}from"node:fs";import H from"node:os";import h from"node:path";import{STATE_DIR as R}from"./constants.js";import{runProcess as x}from"./proc.js";const $=12,f=32,d=24,C=56,E=92;function W(t,e,a){const o=e/a,c=o<.8?6:o<=1.25?4:3,i=o<.8?360:o<=1.25?520:720,r=Math.max(1,Math.min(t,c)),s=i,n=Math.round(s/o),l=Math.max(1,Math.ceil(t/r)),p=f*2+r*s+(r-1)*d,u=f*2+C+l*(n+E)+(l-1)*d;return{cols:r,thumbW:s,thumbH:n,width:F(p),height:F(u)}}m(W,"sheetLayout");const F=m(t=>Math.ceil(t/2)*2,"even"),j=`import React from 'react';
|
|
2
|
+
import { AbsoluteFill, Composition, Img, registerRoot, staticFile } from 'remotion';
|
|
3
|
+
const h = React.createElement;
|
|
4
|
+
const FONT = '-apple-system, "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", system-ui, sans-serif';
|
|
5
|
+
const clamp2 = { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' };
|
|
6
|
+
const Sheet = (p) => h(AbsoluteFill, { style: { backgroundColor: '#111318', padding: ${f}, fontFamily: FONT, color: '#e5e7eb' } },
|
|
7
|
+
h('div', { style: { height: ${C}, fontSize: 28, fontWeight: 600, color: '#f9fafb' } }, p.title),
|
|
8
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(' + p.cols + ', ' + p.thumbW + 'px)', columnGap: ${d}, rowGap: ${d} } },
|
|
9
|
+
p.items.map((it, i) => h('div', { key: i },
|
|
10
|
+
h(Img, { src: staticFile(it.file), style: { display: 'block', width: p.thumbW, height: p.thumbH, objectFit: 'contain', backgroundColor: '#000', borderRadius: 8, outline: '1px solid #2b2f36' } }),
|
|
11
|
+
h('div', { style: { height: ${E}, paddingTop: 10, overflow: 'hidden' } },
|
|
12
|
+
h('div', { style: { fontSize: 22, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, it.title),
|
|
13
|
+
it.sub ? h('div', { style: Object.assign({ marginTop: 4, fontSize: 18, lineHeight: 1.35, color: '#9ca3af' }, clamp2) }, it.sub) : null)))));
|
|
14
|
+
const Root = () => h(Composition, { id: 'Sheet', component: Sheet, durationInFrames: 1, fps: 30, width: 640, height: 360,
|
|
15
|
+
defaultProps: { items: [], cols: 1, thumbW: 100, thumbH: 100, width: 640, height: 360, title: '' },
|
|
16
|
+
calculateMetadata: ({ props }) => ({ width: props.width, height: props.height }) });
|
|
17
|
+
registerRoot(Root);
|
|
18
|
+
`;async function _(t){const e=[];for(let i=0;i<t.items.length;i+=$)e.push(t.items.slice(i,i+$));if(e.length===0)return[];const a=h.join(t.root,R);k(a,{recursive:!0});const o=y(h.join(a,"still-sheet-")),c=y(h.join(H.tmpdir(),"remixmate-sheet-"));try{const i=h.join(o,"entry.js");v(i,j,"utf8");const r=await x(process.execPath,[t.remotionCli,"bundle",i,"--out-dir",c,`--public-dir=${t.publicDir}`,"--log=error"],{cwd:t.root,stdio:"stderr"});if(r.code!==0)throw new Error(`拼图打包失败(remotion 退出码 ${r.code})`);const s=[];for(const[n,l]of e.entries()){const p=W(l.length,t.frameW,t.frameH),u=e.length>1?`${t.title}(${n+1}/${e.length})`:t.title,g=h.join(o,`props-${n+1}.json`);v(g,JSON.stringify({...p,items:l,title:u}),"utf8");const b=t.outFile(n+1),w=await x(process.execPath,[t.remotionCli,"still",c,"Sheet",b,`--props=${g}`,"--log=error"],{cwd:t.root,stdio:"stderr"});if(w.code!==0)throw new Error(`拼图第 ${n+1} 页渲染失败(remotion 退出码 ${w.code})`);s.push(b)}return s}finally{S(o,{recursive:!0,force:!0}),S(c,{recursive:!0,force:!0})}}m(_,"renderSheets");export{$ as SHEET_PAGE_SIZE,_ as renderSheets,W as sheetLayout};
|
package/dist/template/still.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
用 npm run dev 打开 Remotion Studio 定位问题,或先运行 remixmate template check。`),"fail"),
|
|
3
|
-
✅ 关键帧已生成:`);for(const
|
|
4
|
-
⏸
|
|
1
|
+
var K=Object.defineProperty;var p=(t,n)=>K(t,"name",{value:n,configurable:!0});import{createHash as W}from"node:crypto";import{existsSync as A,mkdirSync as Z,mkdtempSync as Q,readFileSync as V,rmSync as O,writeFileSync as Y}from"node:fs";import ee from"node:os";import r from"node:path";import{EXIT as C,SkillError as $}from"../errors.js";import{emitProgress as j}from"../progress.js";import{ensureRulesCurrent as te}from"./check.js";import{STILLS_DIR as oe}from"./constants.js";import{Output as ne,readJsonFile as M,requireProjectRoot as re,requireTemplateDir as se,validateTemplateName as ie}from"./project.js";import{runProcess as G}from"./proc.js";import{FPS as H,MAX_TOTAL_STILLS as U,enumerateCases as ae,planFrames as le,readDeclaredKeyframes as ce,selectCases as me}from"./still-cases.js";import{renderSheets as pe}from"./still-sheet.js";import{MAX_FRAMES_PER_CASE as Te}from"./still-cases.js";const X="node_modules/@remotion/cli/remotion-cli.js",q={"16:9":[1920,1080],"9:16":[1080,1920],"1:1":[1080,1080],"3:4":[1080,1440],"4:3":[1440,1080]};function de(t,n){return`${t}-${n}`.replace(/[^A-Za-z0-9-]/g,"-")}p(de,"studioCompositionId");const fe=p((t,n)=>`#${t.index} ${t.purpose}${t.sceneId?` · ${t.sceneId}`:""} · ${(n.frame/H).toFixed(1)}s`,"caseTitle");async function Ce(t){const n=new ne(!!t.json),i=ie(t.name),l=re(t.cwd),D=se(l,i);await te(l);const S=r.join(l,X);if(!A(S))throw new $(`❌ 找不到 ${X}:工程依赖未安装。在工程根目录运行 npm install 后重试。`,C.USAGE);const m=M(r.join(D,"template.json")),F=r.join(D,"dsl-example.json"),J=A(F)?M(F):null,d=ae(m,J);if(d.length===0)throw new $("❌ template.json 未声明 compositions",C.USAGE);const b=me(d,t.scenes,t.compositionId),T=t.frames||t.scenes||t.compositionId?[]:ce(m,d.length,o=>n.warn(o)),k=le(b,t.frames,T,d.length),g=[...k.values()].reduce((o,a)=>o+a.length,0);if(g>U)throw new $(`❌ 本次要渲染 ${g} 帧,超过上限 ${U}。用 --scenes 只截改动过的场景。`,C.USAGE);const f=r.resolve(l,t.outDir??r.join(oe,i));Z(f,{recursive:!0}),n.info(`模板 ${i}`),n.info(`场景 ${b.length} / ${d.length} 个,共 ${g} 帧${T.length?"(含 previewHints.keyframes 声明的关键帧)":""}`),n.info("首次运行会下载 headless Chrome,可能需要几分钟。");const z=p((o,a)=>new $(`❌ ${o}失败(remotion 退出码 ${a},错误详情见上方输出)。
|
|
2
|
+
用 npm run dev 打开 Remotion Studio 定位问题,或先运行 remixmate template check。`),"fail"),y=Q(r.join(ee.tmpdir(),"remixmate-still-")),P=r.join(y,"bundle"),c=[],w=[];let x=0;try{j({phase:"template-still:bundle",progress:0});const o=await G(process.execPath,[S,"bundle","src/index.ts","--out-dir",P,"--log=error"],{cwd:l,stdio:"stderr"});if(o.code!==0)throw z("打包工程",o.code);let a=0;for(const[e,E]of k){const I=r.join(y,`props-${e.index}.json`);Y(I,JSON.stringify(e.props),"utf8");const B=de(i,e.compositionId),_=new Set;for(const s of E){j({phase:"template-still:render",progress:(a+1)/(g+1),scene:e.index,frame:s.frame});const h=r.join(f,`${i}-s${String(e.index).padStart(2,"0")}-f${String(s.frame).padStart(5,"0")}.png`),v=[S,"still",P,B,h,`--frame=${s.frame}`,`--props=${I}`,"--log=error"];t.scale!==void 0&&v.push(`--scale=${t.scale}`);const L=await G(process.execPath,v,{cwd:l,stdio:"stderr"});if(a++,L.code!==0||!A(h)){w.push(`#${e.index} ${e.purpose}(第 ${s.frame} 帧,退出码 ${L.code})`);continue}const N=W("sha256").update(V(h)).digest("hex");if(_.has(N)){O(h,{force:!0}),x++;continue}_.add(N),c.push({scene:e.index,purpose:e.purpose,sceneId:e.sceneId,compositionId:e.compositionId,label:s.label,frame:s.frame,timeSec:Number((s.frame/H).toFixed(2)),path:h,...s.expect?{expect:s.expect}:{}})}}}finally{O(y,{recursive:!0,force:!0})}let u=[];if(!t.noSheet&&c.length>1){j({phase:"template-still:sheet",progress:.98});const[o,a]=q[(m.supportedAspectRatios??[])[0]]??q["16:9"];try{u=await pe({root:l,remotionCli:S,publicDir:f,outFile:p(e=>r.join(f,`${i}-sheet-${e}.png`),"outFile"),items:c.map(e=>{const E=b.find(I=>I.index===e.scene);return{file:r.basename(e.path),title:fe(E,{label:e.label,frame:e.frame}),sub:e.expect??(e.label!=="settled"?e.label:void 0)}}),frameW:o,frameH:a,title:`${typeof m.name=="string"&&m.name?m.name:i} · 关键帧`})}catch(e){n.warn(` ⚠ 拼图生成失败(${e.message}),单张图片不受影响。`)}}if(w.length>0)throw new $(`❌ ${w.length} 个关键帧渲染失败:${w.join(",")}。其余 ${c.length} 张已生成在 ${f}。用 npm run dev 打开 Remotion Studio 定位问题。`);j({phase:"template-still:done",progress:1,stills:c.map(o=>o.path),sheets:u,awaitingConfirmation:!0}),x>0&&n.info(` (${x} 帧与同场景的其它帧完全相同,已去掉)`),n.info(`
|
|
3
|
+
✅ 关键帧已生成:`);for(const o of c)n.info(` #${o.scene} ${o.purpose.padEnd(8)} ${String(o.timeSec).padStart(6)}s ${o.path}`);for(const o of u)n.info(` 拼图 ${o}`);n.info(`
|
|
4
|
+
⏸ 确认点:请把`+(u.length?"拼图":"这些图片")+"展示给用户确认画面效果(每格说明这一刻应该看到什么),按反馈修改后可只重截改动的场景(--scenes);用户确认后再做远程预览。");const R={ok:!0,name:i,stills:c,sheets:u,duplicatesDropped:x,awaitingConfirmation:!0};return n.result({...R}),R}p(Ce,"runStill");export{Te as MAX_FRAMES_PER_CASE,Ce as runStill,de as studioCompositionId};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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",
|
|
@@ -475,6 +475,8 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
475
475
|
| `--speed` | Narration speech rate (0.5–2.0), written to `global.narration.speed` and applied when `prepare_video_assets` calls gen-voice. Pass it whenever the user picked a speed (the replicate form's 语速 field sends one). Above ~1.3 subtitle alignment drifts and the delivery turns mechanical — shorten the script instead. | `1.0` |
|
|
476
476
|
| `--variant` | Template visual variant id (a key of the template's `variants`; `template-registry --list-templates` prints them as `variantIds`). Written to `meta.templateVariant`, which takes precedence over the fuzzy `--style` match. Pass it whenever the user picked a style — the replicate card shows it as `名字(id)`, e.g. `视觉风格: 紫色科技(tech-purple)`; pass the id in the parentheses. Requires `--template-id`; unknown ids are rejected. | template default |
|
|
477
477
|
| `--background-preset` | Scene background preset, one of the template's `customPayloadSchema.background.preset` values. Written to every scene's `customPayload.background.preset` (other keys of the template's default background are kept). Pass it whenever the user picked a background, e.g. `背景样式: 纯色(solid)` → `solid`. `image` / `video` also need `customPayload.background.assetRef`. Requires `--template-id`; unsupported values are rejected. | template default |
|
|
478
|
+
| `--font-id` | Font library key (`font.uniq_id`), written to `global.font.fontId`; ab-render resolves and installs the real font at render time. Pass it whenever the user picked a font — the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)`; pass the id in the parentheses. Never pass a CSS family or the display name: an unknown key falls back to the template font with no error. | template font |
|
|
479
|
+
| `--font-name` | Display name of the picked font → `global.font.fontName` (display only, never used for rendering). Pass it with `--font-id`. | — |
|
|
478
480
|
| `--template-source` | How `--template-id` was chosen: `user-explicit` / `user-confirmed` / `auto`, written to `meta.templateSelection.source`. `auto` is refused — confirm with the user first (see "Agent behavior: template selection comes before the script"). Omitted = not recorded, with a stderr notice. Requires `--template-id`. | — |
|
|
479
481
|
| `--template-candidates` | Repeatable. Template ids offered to the user, recommended first → `meta.templateSelection.candidates`. With `user-confirmed`, `--template-id` must be one of them. | — |
|
|
480
482
|
| `--allow-digital-human` | Whether to allow digital-human assets. | off |
|
|
@@ -2011,9 +2011,9 @@ def build_dsl(
|
|
|
2011
2011
|
f"is only a placeholder.\n"
|
|
2012
2012
|
f" Write the full DSL yourself from the user's material (scenes, assets, visuals, "
|
|
2013
2013
|
f"customPayload) and submit all of it to prepare_video_assets — it is used as-is, "
|
|
2014
|
-
f"not merged onto this skeleton. Keep meta.templateId, and copy meta.templateVariant
|
|
2015
|
-
f"each scene's customPayload.background from this skeleton when present
|
|
2016
|
-
f"user's --variant / --background-preset choice).",
|
|
2014
|
+
f"not merged onto this skeleton. Keep meta.templateId, and copy meta.templateVariant, "
|
|
2015
|
+
f"global.font and each scene's customPayload.background from this skeleton when present "
|
|
2016
|
+
f"(they carry the user's --variant / --font-id / --background-preset choice).",
|
|
2017
2017
|
file=sys.stderr,
|
|
2018
2018
|
)
|
|
2019
2019
|
|
|
@@ -52,6 +52,14 @@
|
|
|
52
52
|
"type": "string",
|
|
53
53
|
"description": "Scene background preset, one of the template's customPayloadSchema.background.preset values. Written to every scene's customPayload.background.preset. **Must** pass when the user picked a background: the replicate card shows `背景样式: 纯色(solid)` → pass `solid`. Without it the scene keeps the template default. Requires template_id; unsupported values are rejected. `image` / `video` also need customPayload.background.assetRef on each scene."
|
|
54
54
|
},
|
|
55
|
+
"font_id": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Font library key (font.uniq_id) for the on-screen text and subtitles. Written to global.font.fontId; match_template folds it into the binding's typography and ab-render resolves and installs the real font before rendering. **Must** pass when the user picked a font: the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)` → pass the id in the parentheses. Pass the opaque key, never a CSS family name or the display name — those fall back to the template font with no error. Without it the choice is silently dropped and the video renders in the template's own font. Omit to follow the template's font pairing."
|
|
58
|
+
},
|
|
59
|
+
"font_name": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "Display name of the font picked in font_id (the text before the parentheses on the card). Stored at global.font.fontName for display only; never used for rendering. Pass it together with font_id."
|
|
62
|
+
},
|
|
55
63
|
"template_id": {
|
|
56
64
|
"type": "string",
|
|
57
65
|
"description": "Template id (from template_registry). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL."
|
|
@@ -137,6 +145,8 @@
|
|
|
137
145
|
"speed",
|
|
138
146
|
"variant",
|
|
139
147
|
"background_preset",
|
|
148
|
+
"font_id",
|
|
149
|
+
"font_name",
|
|
140
150
|
"template_id",
|
|
141
151
|
"template_source",
|
|
142
152
|
"template_candidates",
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "gen-script",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "474",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V20",
|
|
6
6
|
"skillDescription": "Video-script generation skill. Turns a user-supplied topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.\n\nUse this skill as soon as the user mentions any of these intents:\n- Write a video script, generate a video script, plan a video, write storyboards\n- Create a short video, plan video content, generate a Video DSL\n- Turn a topic into a video structure / video content plan\n\nEven when the user does not say \"generate the DSL\", use this skill whenever they want to turn a topic into a structured video plan.\n\n⚠️ Stop-and-confirm gate: after this skill returns a DSL, show the full script and wait for the user's explicit confirmation. Never call `prepare_video_assets` in the same turn."
|
|
7
7
|
}
|
|
@@ -41,7 +41,7 @@ explicitly says so in this conversation (e.g. 「不用确认,直接发布」)
|
|
|
41
41
|
| ⏸ | When | Show the user | Proceed when |
|
|
42
42
|
|---|---|---|---|
|
|
43
43
|
| A | before writing any template code | short spec: aspect ratio, duration, scene structure, visual style, adjustable parameters (`customPayload` fields) | user approves / edits the spec |
|
|
44
|
-
| B | after `template still` | the
|
|
44
|
+
| B | after `template still` | the contact sheet (embed its path) + one line per cell on what it should show | user approves the look |
|
|
45
45
|
| C | after `template preview` | the preview URL + what changed since the last round | user approves motion and pacing |
|
|
46
46
|
| D | after `template publish` without `--yes` (exit code 3) | the printed summary: name, new vs. **update of an existing template**, parameters, preview state | user explicitly confirms publishing |
|
|
47
47
|
|
|
@@ -83,11 +83,16 @@ Exit code 3 from `publish` is the checkpoint, not a failure: nothing was uploade
|
|
|
83
83
|
template's core contract fields — never hand-edit those.
|
|
84
84
|
5. **Keyframe stills** (local, no login, no platform cost):
|
|
85
85
|
```bash
|
|
86
|
-
remixmate template still <name> [--
|
|
86
|
+
remixmate template still <name> [--scenes 3,5] [--frames intro,settled] [--json]
|
|
87
87
|
```
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
Default: every `dsl-example.json` scene plus uncovered compositions (e.g. the cover), frames
|
|
89
|
+
by budget — 1 scene: intro / middle / settled / final; 2–3: intro / settled; ≥ 4: settled
|
|
90
|
+
(identical frames within a scene are dropped) — and a captioned contact sheet
|
|
91
|
+
(`sheets[]`, 12 per page; single PNGs in `stills[].path`). Templates whose process matters
|
|
92
|
+
declare extra moments in `template.json` → `previewHints.keyframes` (anchors, not exact
|
|
93
|
+
times — see the project's `.remixmate/WORKFLOW.md`).
|
|
94
|
+
**⏸ B** — show the contact sheet (not every single PNG), say per cell what the user should
|
|
95
|
+
see, wait. After a fix re-render only the changed scenes with `--scenes`.
|
|
91
96
|
6. **Remote draft preview** (login required): renders on the real platform renderer.
|
|
92
97
|
```bash
|
|
93
98
|
remixmate template preview <name>
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "template-authoring",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "pending-template-authoring",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V6",
|
|
6
6
|
"skillDescription": "Local video-template authoring skill: scaffolds a RemixMate template starter project (`remixmate template init <dir>`), then guides the agent through the project's AGENTS.md, `npm run dev`, `remixmate template check`, `remixmate template still` (keyframe PNGs), `remixmate template preview`, `remixmate template publish` (summary first, `--yes` after the user confirms), with mandatory user confirmation checkpoints, and rendering with `remixmate render-video --template-id <templateKey>`.\n\nUse this skill whenever the user asks for any of:\n- 做视频模板 / 做视频模版 / 自定义视频模板 / 创建模板 / 创建一个新的视频模版 / 新建模版\n- template authoring, create a video template\n- preview or publish a template they develop locally"
|
|
7
7
|
}
|