@remixmate/cli 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.11.0",
4
- "generatedAt": "2026-10-09T01:32:09.848Z",
3
+ "version": "0.12.0",
4
+ "generatedAt": "2026-10-09T05:51:49.567Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
@@ -1,13 +1,17 @@
1
- var T=Object.defineProperty;var p=(r,s)=>T(r,"name",{value:s,configurable:!0});import{cleanDiagnosticText as f}from"../diagnostics.js";import{EXIT as n,SkillError as g}from"../errors.js";import{flagNumber as l,flagString as o,parseTemplateArgs as A,usageError as h}from"./args.js";import{runCheck as R}from"./check.js";import{runUpgrade as b}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 N}from"./publish.js";import{MAX_STILLS as O,runStill as U}from"./still.js";import{cachedUpdateInfo as _,freshUpdateInfo as I,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]
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> [--frames start,mid,end] [--composition-id ID] [--scale N] [--out-dir DIR] [--json]
8
- 用工程内的 Remotion 在本地渲染关键帧 PNG(无需登录、不占平台资源),供用户确认画面。
9
- --frames 取值:start / mid / end / 帧号 / 秒数(如 2.5s),逗号分隔,最多 ${O} 帧;默认 start,mid,end。
10
- 默认输出到 output/stills/<name>/;--scale 0.5 可缩小图片。
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 C(){return`Usage: remixmate template <command> [options]
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(C,"templateUsage");function M(r){const[s,...e]=r;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:A(e,t.flags,{maxPositionals:t.maxPositionals})}}p(M,"parseTemplateCommand");async function v(r,s){const{flags:e,positionals:t}=s,i=e.json===!0,a=o(e,"token"),c=o(e,"api-base-url");switch(r){case"init":{if(!t[0])throw h("缺少目标目录:remixmate template init <dir>");const m=await I();return m&&process.stderr.write(`${E(m)}
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:i,skipInstall:e["skip-install"]===!0,...m?{cliUpdate:m}:{}}),n.OK}case"check":return R({name:t[0],json:i,noWrite:e["no-write"]===!0});case"still":return await U({name:t[0],frames:o(e,"frames"),compositionId:o(e,"composition-id"),scale:l(e,"scale"),outDir:o(e,"out-dir"),json:i}),n.OK;case"preview":return await S({name:t[0],propsJson:o(e,"props-json"),durationSec:l(e,"duration-sec"),compositionId:o(e,"composition-id"),timeoutSec:l(e,"timeout-sec"),json:i,token:a,apiBaseUrl:c}),n.OK;case"publish":{const m=await N({name:t[0],displayName:o(e,"name"),dryRun:e["dry-run"]===!0,yes:e.yes===!0,skipPreviewCheck:e["skip-preview-check"]===!0,json:i,token:a,apiBaseUrl:c});return j(m)?n.NEEDS_CONFIRMATION:n.OK}case"list":return await y({json:i,token:a,apiBaseUrl:c}),n.OK;case"upgrade":return await b({dryRun:e["dry-run"]===!0,withDocs:e["with-docs"]===!0,force:e.force===!0,json:i}),n.OK}}p(v,"dispatch");function k(){try{const r=_();r&&process.stderr.write(`
45
- ${E(r)}
46
- `)}catch{}}p(k,"printCachedUpdateHint");async function Q(r){const s=r.includes("--json");let e;try{const t=M(r);if(e=t.sub,e==="help")return process.stdout.write(C()),n.OK;if(t.args.flags.help)return process.stdout.write("Usage: "+d[e].usage),n.OK;const i=await v(e,t.args);return e!=="init"&&k(),i}catch(t){const i=t instanceof g?t.exitCode:n.ERROR,a=t instanceof g?t.message:`❌ unexpected error: ${t.stack??String(t)}`;return process.stderr.write(f(a,8192)+`
47
- `),i===n.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:i,error:f(a.replace(/^❌\s*/,""),2048)})+`
48
- `),i}}p(Q,"runTemplateCommand");export{d as SUB_SPECS,x as TEMPLATE_SUBCOMMANDS,z as TEMPLATE_VERB,M as parseTemplateCommand,Q as runTemplateCommand,C as templateUsage};
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",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};
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};
@@ -1,4 +1,4 @@
1
- var R=Object.defineProperty;var l=(o,t)=>R(o,"name",{value:t,configurable:!0});import{existsSync as x,mkdirSync as _,mkdtempSync as O,rmSync as U}from"node:fs";import q from"node:os";import d from"node:path";import{EXIT as P,SkillError as j}from"../errors.js";import{emitProgress as b}from"../progress.js";import{usageError as y}from"./args.js";import{ensureRulesCurrent as B}from"./check.js";import{STILLS_DIR as G}from"./constants.js";import{Output as X,readJsonFile as T,requireProjectRoot as z,requireTemplateDir as J,validateTemplateName as Z}from"./project.js";import{runProcess as k}from"./proc.js";const h=30,H=5,v=6,L="node_modules/@remotion/cli/remotion-cli.js";function K(o,t){return`${o}-${t}`.replace(/[^A-Za-z0-9-]/g,"-")}l(K,"studioCompositionId");function Q(o,t){const i=new Set;let e=o[t];for(;e?.$ref;){if(i.has(e.$ref))return{};i.add(e.$ref),e=o[e.$ref]}return e??{}}l(Q,"resolveSlot");function V(o,t,i){const e=Array.isArray(t?.scenes)?t.scenes:[],a=o.slotMapping??{},r=e.find(m=>m.purpose&&Q(a,m.purpose).compositionId===i.compositionId)??e.find(m=>m.purpose===i.slot)??e[0],s=typeof r?.duration=="number"&&r.duration>0?r.duration:o.supportedDurations?.min??H;return Math.max(1,Math.round(s*h))}l(V,"sampleDurationFrames");function W(o,t){const i=t-1,e=o.split(",").map(r=>r.trim()).filter(Boolean);if(e.length===0)throw y("--frames 不能为空");const a=[];for(const r of e){let s;if(r==="start")s=0;else if(r==="mid")s=Math.floor(i/2);else if(r==="end")s=i;else if(/^\d+(\.\d+)?s$/.test(r))s=Math.round(Number(r.slice(0,-1))*h);else if(/^\d+$/.test(r))s=Number(r);else throw y(`--frames 取值不合法: '${r}'(可用 start / mid / end / 帧号 / 秒数如 2.5s)`);s=Math.min(Math.max(0,s),i),a.some(m=>m.frame===s)||a.push({label:r,frame:s})}if(a.length>v)throw y(`--frames 最多 ${v} 帧,收到 ${a.length} 帧`);return a}l(W,"parseFrameSpec");async function lo(o){const t=new X(!!o.json),i=Z(o.name),e=z(o.cwd),a=J(e,i);await B(e);const r=d.join(e,L);if(!x(r))throw new j(`❌ 找不到 ${L}:工程依赖未安装。在工程根目录运行 npm install 后重试。`,P.USAGE);const s=T(d.join(a,"template.json")),m=Array.isArray(s.compositions)?s.compositions:[],f=o.compositionId?m.find(n=>n.compositionId===o.compositionId):m[0];if(!f)throw new j(o.compositionId?`❌ composition "${o.compositionId}" 不在该模板中。可选: ${m.map(n=>n.compositionId).join(", ")}`:"❌ template.json 未声明 compositions",P.USAGE);const D=d.join(a,"dsl-example.json"),N=x(D)?T(D):null,p=V(s,N,f),$=W(o.frames??"start,mid,end",p),S=K(i,String(f.compositionId)),A=d.resolve(e,o.outDir??d.join(G,i));_(A,{recursive:!0}),t.info(`模板 ${i}`),t.info(`composition ${S}(${(p/h).toFixed(1)}s,${p} 帧)`),t.info(`截帧 ${$.map(n=>`${n.label}=${n.frame}`).join(", ")}`),t.info("首次运行会下载 headless Chrome,可能需要几分钟。");const E=l((n,g)=>new j(`❌ ${n}失败(remotion 退出码 ${g},错误详情见上方输出)。
2
- 用 npm run dev 打开 Remotion Studio 定位问题,或先运行 remixmate template check。`),"fail"),w=O(d.join(q.tmpdir(),"remixmate-still-")),u=[];try{b({phase:"template-still:bundle",progress:0});const n=await k(process.execPath,[r,"bundle","src/index.ts","--out-dir",w,"--log=error"],{cwd:e,stdio:"stderr"});if(n.code!==0)throw E("打包工程",n.code);for(const[g,c]of $.entries()){b({phase:"template-still:render",progress:(g+1)/($.length+1),frame:c.frame});const I=d.join(A,`${i}-f${String(c.frame).padStart(5,"0")}.png`),C=[r,"still",w,S,I,`--frame=${c.frame}`,"--log=error"];o.scale!==void 0&&C.push(`--scale=${o.scale}`);const F=await k(process.execPath,C,{cwd:e,stdio:"stderr"});if(F.code!==0||!x(I))throw E(`第 ${c.frame} 帧渲染`,F.code);u.push({label:c.label,frame:c.frame,timeSec:Number((c.frame/h).toFixed(2)),path:I})}}finally{U(w,{recursive:!0,force:!0})}b({phase:"template-still:done",progress:1,stills:u.map(n=>n.path),awaitingConfirmation:!0}),t.info(`
3
- ✅ 关键帧已生成:`);for(const n of u)t.info(` ${n.label.padEnd(6)} ${n.timeSec}s ${n.path}`);t.info(`
4
- ⏸ 确认点:请把这些图片展示给用户确认画面效果,按反馈修改后可重新截帧;用户确认后再做远程预览。`);const M={ok:!0,name:i,compositionId:String(f.compositionId),studioCompositionId:S,durationFrames:p,stills:u,awaitingConfirmation:!0};return t.result({...M}),M}l(lo,"runStill");export{v as MAX_STILLS,W as parseFrameSpec,lo as runStill,V as sampleDurationFrames,K as studioCompositionId};
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.11.0",
3
+ "version": "0.12.0",
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",
@@ -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 PNG keyframes (attach or embed the image paths) + one-line description of each | user approves the look |
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> [--frames start,mid,end] [--scale 0.5] [--json]
86
+ remixmate template still <name> [--scenes 3,5] [--frames intro,settled] [--json]
87
87
  ```
88
- `--frames` takes `start` / `mid` / `end` / a frame number / seconds like `2.5s` (max 6).
89
- PNGs land in `output/stills/<name>/`; `--json` returns their absolute paths in `stills[].path`.
90
- **⏸ B** — show the images, wait. Cheap to repeat: re-run after every visual fix.
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": "V5",
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
  }