@remixmate/cli 0.9.66 → 0.9.67
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/cli.js +7 -1
- package/dist/doctor.js +8 -2
- package/dist/install.d.ts +9 -0
- package/dist/install.js +19 -0
- package/dist/manifest.json +8 -6
- package/dist/template/check.js +6 -1
- package/dist/template/commands.d.ts +1 -1
- package/dist/template/commands.js +19 -2
- package/dist/template/constants.d.ts +15 -1
- package/dist/template/constants.js +15 -1
- package/dist/template/init.d.ts +6 -0
- package/dist/template/init.js +23 -5
- package/dist/template/project.d.ts +10 -0
- package/dist/template/project.js +52 -1
- package/dist/template/upgrade.d.ts +44 -0
- package/dist/template/upgrade.js +123 -0
- package/package.json +1 -1
- package/skills/template-authoring/SKILL.md +32 -13
- package/skills/template-authoring/version.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { parseArgv } from './argv.js';
|
|
|
16
16
|
import { runSkill } from './runner.js';
|
|
17
17
|
import { runAuthCommand } from './auth/commands.js';
|
|
18
18
|
import { HOST_VERB, PROJECT_VERB, runHostCommand, runProjectCommand } from './project/commands.js';
|
|
19
|
-
import { INSTALL_VERB, UNINSTALL_VERB, runInstall, runUninstall } from './install.js';
|
|
19
|
+
import { INSTALL_VERB, UNINSTALL_VERB, runInstall, runUninstall, skillInstallHint } from './install.js';
|
|
20
20
|
import { runExec } from './exec.js';
|
|
21
21
|
import { authStatusLine, runDoctor } from './doctor.js';
|
|
22
22
|
import { padDisplay } from './text.js';
|
|
@@ -71,6 +71,7 @@ function printUsage() {
|
|
|
71
71
|
process.stdout.write(' remixmate template preview <name> Render a draft preview mp4 remotely\n');
|
|
72
72
|
process.stdout.write(' remixmate template publish <name> Publish as your private template\n');
|
|
73
73
|
process.stdout.write(' remixmate template list List your private templates\n');
|
|
74
|
+
process.stdout.write(' remixmate template upgrade Update the project\'s local check rules (no login)\n');
|
|
74
75
|
process.stdout.write(' remixmate <name> [--flag value ...] Invoke a skill\n');
|
|
75
76
|
process.stdout.write(' remixmate <name> --help Show skill-specific help\n');
|
|
76
77
|
process.stdout.write('\nAvailable skills:\n');
|
|
@@ -119,6 +120,11 @@ function printList(json) {
|
|
|
119
120
|
async function printAuthFooter() {
|
|
120
121
|
try {
|
|
121
122
|
process.stderr.write(`\n${await authStatusLine()}\n`);
|
|
123
|
+
// `npm i -g` 之后第一条命令通常就是 --help / --list:这里提醒一次「还没 remixmate install」,
|
|
124
|
+
// 代替会被 npm 吞掉输出、且侵入家目录的 postinstall。
|
|
125
|
+
const hint = skillInstallHint();
|
|
126
|
+
if (hint)
|
|
127
|
+
process.stderr.write(`${hint}\n`);
|
|
122
128
|
}
|
|
123
129
|
catch {
|
|
124
130
|
// A diagnostic footer must never be the reason a listing command fails.
|
package/dist/doctor.js
CHANGED
|
@@ -20,7 +20,7 @@ import { listCredentials } from './auth/credential-store.js';
|
|
|
20
20
|
import { resolveTakeKey } from './project/host.js';
|
|
21
21
|
import { readBinding } from './project/store.js';
|
|
22
22
|
import { canAutoAuth, isHeadless } from './auth/environment.js';
|
|
23
|
-
import { installStates } from './install.js';
|
|
23
|
+
import { installStates, skillInstallHint } from './install.js';
|
|
24
24
|
import { padDisplay } from './text.js';
|
|
25
25
|
/**
|
|
26
26
|
* Short auth status suitable for a footer. Never performs network I/O — a
|
|
@@ -104,7 +104,8 @@ export async function runDoctor(opts = {}) {
|
|
|
104
104
|
// 宿主安装状态。这套方案唯一的静默失败模式是漂移:`npm update -g` 只动包,不动
|
|
105
105
|
// 已经铺进 ~/.claude/skills、~/.codex/skills 的副本,用户会拿着旧 SKILL.md 打新
|
|
106
106
|
// 后端,而且第一个症状往往是某个参数「莫名其妙不认」。
|
|
107
|
-
|
|
107
|
+
const hostStates = installStates();
|
|
108
|
+
for (const s of hostStates) {
|
|
108
109
|
checks.push({
|
|
109
110
|
// 只取宿主名的第一段(Claude Code → Claude)——检查名要留在 padDisplay 的
|
|
110
111
|
// 对齐宽度内,宿主目录已经写在 detail 里,不会有歧义。
|
|
@@ -170,6 +171,11 @@ export async function runDoctor(opts = {}) {
|
|
|
170
171
|
// detail 列顶出去,报告看起来像是漏了对齐。
|
|
171
172
|
process.stdout.write(`${ICON[c.status]} ${padDisplay(c.name, 16)}${c.detail}\n`);
|
|
172
173
|
}
|
|
174
|
+
// 宿主未装 skill 只算 warn(不影响退出码),但它是「agent 说不知道 RemixMate」的头号原因,
|
|
175
|
+
// 单独在报告末尾点名,避免淹没在一屏 ✅ 里。
|
|
176
|
+
const installHint = skillInstallHint(hostStates);
|
|
177
|
+
if (installHint)
|
|
178
|
+
process.stdout.write(`\n${installHint}\n`);
|
|
173
179
|
const failed = checks.filter((c) => c.status === 'fail');
|
|
174
180
|
if (failed.length === 0) {
|
|
175
181
|
process.stdout.write('\n一切正常。\n');
|
package/dist/install.d.ts
CHANGED
|
@@ -59,4 +59,13 @@ export interface InstallState {
|
|
|
59
59
|
* 用户会拿着旧 SKILL.md 打新后端。doctor 是「为什么跑不起来」的统一入口,检查放这里。
|
|
60
60
|
*/
|
|
61
61
|
export declare function installStates(): InstallState[];
|
|
62
|
+
/**
|
|
63
|
+
* 首次使用提示:检测到了宿主、但还没跑过 `remixmate install`(或装的是旧版)时返回一行提示,
|
|
64
|
+
* 否则 null。
|
|
65
|
+
*
|
|
66
|
+
* 不用 postinstall 做这件事:npm 7+ 默认吞掉生命周期脚本的输出,而在安装期往用户家目录
|
|
67
|
+
* 写文件太侵入。改为在用户/agent 一定会看到的地方(--list、--help 页脚、template init 的
|
|
68
|
+
* 下一步、doctor)提示。纯本地读文件,不发网络请求。
|
|
69
|
+
*/
|
|
70
|
+
export declare function skillInstallHint(states?: InstallState[]): string | null;
|
|
62
71
|
export {};
|
package/dist/install.js
CHANGED
|
@@ -427,3 +427,22 @@ export function installStates() {
|
|
|
427
427
|
};
|
|
428
428
|
});
|
|
429
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* 首次使用提示:检测到了宿主、但还没跑过 `remixmate install`(或装的是旧版)时返回一行提示,
|
|
432
|
+
* 否则 null。
|
|
433
|
+
*
|
|
434
|
+
* 不用 postinstall 做这件事:npm 7+ 默认吞掉生命周期脚本的输出,而在安装期往用户家目录
|
|
435
|
+
* 写文件太侵入。改为在用户/agent 一定会看到的地方(--list、--help 页脚、template init 的
|
|
436
|
+
* 下一步、doctor)提示。纯本地读文件,不发网络请求。
|
|
437
|
+
*/
|
|
438
|
+
export function skillInstallHint(states = installStates()) {
|
|
439
|
+
const missing = states.filter((s) => s.installedVersion == null).map((s) => s.label);
|
|
440
|
+
const drifted = states.filter((s) => s.drifted).map((s) => s.label);
|
|
441
|
+
if (missing.length > 0) {
|
|
442
|
+
return `💡 检测到 ${missing.join('、')},但还没装 skill → 运行 remixmate install,重启 agent 后它才能自动发现 RemixMate 的能力`;
|
|
443
|
+
}
|
|
444
|
+
if (drifted.length > 0) {
|
|
445
|
+
return `💡 ${drifted.join('、')} 里的 skill 是旧版本 → 运行 remixmate install 覆盖`;
|
|
446
|
+
}
|
|
447
|
+
return null;
|
|
448
|
+
}
|
package/dist/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "0.9.67",
|
|
4
|
+
"generatedAt": "2026-10-05T10:57:59.113Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -940,10 +940,12 @@
|
|
|
940
940
|
"title": "Video Template Authoring",
|
|
941
941
|
"summary": "Start authoring a custom video template locally: scaffolds a starter project (Remotion Studio preview, local validation, remote draft preview, publishing as the user's private template) into the given directory. After this, follow the project's AGENTS.md and use `remixmate template check|preview|publish`. Local-only; requires Node >= 18 and npm.",
|
|
942
942
|
"triggers": [
|
|
943
|
-
"做视频模板",
|
|
944
|
-
"自定义视频模板",
|
|
945
|
-
"创建模板 / 新建模板 /
|
|
946
|
-
"
|
|
943
|
+
"做视频模板 / 做视频模版",
|
|
944
|
+
"自定义视频模板 / 自定义视频模版",
|
|
945
|
+
"创建模板 / 新建模板 / 创建模版 / 新建模版",
|
|
946
|
+
"我想创建一个新的视频模版 / 创建一个视频模板",
|
|
947
|
+
"自己写一个视频模板 / 自己做一个模版",
|
|
948
|
+
"预览或发布我本地开发的模板 / 模版",
|
|
947
949
|
"template authoring",
|
|
948
950
|
"create video template / build a custom video template",
|
|
949
951
|
"publish my own Remotion template"
|
package/dist/template/check.js
CHANGED
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { CHECK_ENTRY } from './constants.js';
|
|
11
|
-
import { requireProjectRoot } from './project.js';
|
|
11
|
+
import { requireProjectRoot, staleRulesHint } from './project.js';
|
|
12
12
|
import { runProcess } from './proc.js';
|
|
13
13
|
/** Run the starter's check script; resolves to its exit code (0 ok / 1 issues / 2 usage). */
|
|
14
14
|
export async function runCheck(opts = {}) {
|
|
15
15
|
const root = requireProjectRoot(opts.cwd);
|
|
16
|
+
// 规则比 cli 对应的 starter 旧:提示 upgrade(stderr,不影响 --json 的最后一行)。
|
|
17
|
+
// 只提示不阻断 —— 服务端渲染前还会再扫一遍,旧规则最多让问题晚一点暴露。
|
|
18
|
+
const hint = staleRulesHint(root);
|
|
19
|
+
if (hint)
|
|
20
|
+
process.stderr.write(hint + '\n');
|
|
16
21
|
const args = [path.join(root, CHECK_ENTRY)];
|
|
17
22
|
if (opts.name)
|
|
18
23
|
args.push(opts.name);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type FlagSpec, type ParsedTemplateArgs } from './args.js';
|
|
10
10
|
export declare const TEMPLATE_VERB = "template";
|
|
11
|
-
export declare const TEMPLATE_SUBCOMMANDS: readonly ["init", "check", "preview", "publish", "list"];
|
|
11
|
+
export declare const TEMPLATE_SUBCOMMANDS: readonly ["init", "check", "preview", "publish", "list", "upgrade"];
|
|
12
12
|
export type TemplateSubcommand = (typeof TEMPLATE_SUBCOMMANDS)[number];
|
|
13
13
|
interface SubSpec {
|
|
14
14
|
flags: FlagSpec;
|
|
@@ -10,13 +10,14 @@ import { cleanDiagnosticText } from '../diagnostics.js';
|
|
|
10
10
|
import { EXIT, SkillError } from '../errors.js';
|
|
11
11
|
import { flagNumber, flagString, parseTemplateArgs, usageError } from './args.js';
|
|
12
12
|
import { runCheck } from './check.js';
|
|
13
|
+
import { runUpgrade } from './upgrade.js';
|
|
13
14
|
import { STARTER_PACKAGE, STARTER_VERSION } from './constants.js';
|
|
14
15
|
import { runInit } from './init.js';
|
|
15
16
|
import { runList } from './list.js';
|
|
16
17
|
import { runPreview } from './preview.js';
|
|
17
18
|
import { runPublish } from './publish.js';
|
|
18
19
|
export const TEMPLATE_VERB = 'template';
|
|
19
|
-
export const TEMPLATE_SUBCOMMANDS = ['init', 'check', 'preview', 'publish', 'list'];
|
|
20
|
+
export const TEMPLATE_SUBCOMMANDS = ['init', 'check', 'preview', 'publish', 'list', 'upgrade'];
|
|
20
21
|
export const SUB_SPECS = {
|
|
21
22
|
init: {
|
|
22
23
|
flags: { force: 'bool', 'skip-install': 'bool' },
|
|
@@ -52,6 +53,13 @@ export const SUB_SPECS = {
|
|
|
52
53
|
maxPositionals: 0,
|
|
53
54
|
usage: 'remixmate template list [--json]\n 列出你发布的私有模板(需登录)。\n',
|
|
54
55
|
},
|
|
56
|
+
upgrade: {
|
|
57
|
+
flags: { 'dry-run': 'bool', 'with-docs': 'bool', force: 'bool' },
|
|
58
|
+
maxPositionals: 0,
|
|
59
|
+
usage: 'remixmate template upgrade [--dry-run] [--with-docs] [--force] [--json]\n' +
|
|
60
|
+
` 把工程的本地校验规则(.remixmate/)更新到 ${STARTER_PACKAGE}@${STARTER_VERSION}(无需登录)。\n` +
|
|
61
|
+
' 不改动 templates/、src/、examples/、package.json;--with-docs 同时刷新 AGENTS.md / CLAUDE.md(旧文件存为 *.orig)。\n',
|
|
62
|
+
},
|
|
55
63
|
};
|
|
56
64
|
export function templateUsage() {
|
|
57
65
|
return ('Usage: remixmate template <command> [options]\n\n' +
|
|
@@ -60,7 +68,8 @@ export function templateUsage() {
|
|
|
60
68
|
' check [name] 本地校验(无需登录)\n' +
|
|
61
69
|
' preview <name> 远程渲染预览 mp4\n' +
|
|
62
70
|
' publish <name> 发布为私有模板\n' +
|
|
63
|
-
' list 列出我的私有模板\n
|
|
71
|
+
' list 列出我的私有模板\n' +
|
|
72
|
+
' upgrade 更新工程的本地校验规则(无需登录)\n\n' +
|
|
64
73
|
'Common options: --json --token <PrivToken> --api-base-url <url>\n' +
|
|
65
74
|
'环境变量:AB_RENDER_URL(渲染服务地址)、REMIXMATE_STARTER_TARBALL(init 使用本地 starter 包)\n' +
|
|
66
75
|
'运行 remixmate template <command> --help 查看子命令说明。\n');
|
|
@@ -114,6 +123,14 @@ async function dispatch(sub, args) {
|
|
|
114
123
|
case 'list':
|
|
115
124
|
await runList({ json, token, apiBaseUrl });
|
|
116
125
|
return EXIT.OK;
|
|
126
|
+
case 'upgrade':
|
|
127
|
+
await runUpgrade({
|
|
128
|
+
dryRun: flags['dry-run'] === true,
|
|
129
|
+
withDocs: flags['with-docs'] === true,
|
|
130
|
+
force: flags.force === true,
|
|
131
|
+
json,
|
|
132
|
+
});
|
|
133
|
+
return EXIT.OK;
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
136
|
export async function runTemplateCommand(argv) {
|
|
@@ -6,7 +6,21 @@
|
|
|
6
6
|
* the same project on every machine for a given cli version.
|
|
7
7
|
*/
|
|
8
8
|
export declare const STARTER_PACKAGE = "@remixmate/template-starter";
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 一个 starter 版本 = 一套本地校验规则版本(.remixmate/)。规则更新的发布顺序:
|
|
11
|
+
* template-library `pnpm publish:sdk --publish` 发新 starter → 改这里 → cli 发版。
|
|
12
|
+
* publish:sdk 会比对这个常量,对不上就拒发。
|
|
13
|
+
*/
|
|
14
|
+
export declare const STARTER_VERSION = "0.2.0";
|
|
15
|
+
/**
|
|
16
|
+
* 工程里记录规则版本的文件(starter ≥0.2.0 构建时写入;`template upgrade` 也会写)。
|
|
17
|
+
* 0.1.0 工程没有它,回落读 package.json 的 version(init 时整份复制自 starter)。
|
|
18
|
+
*/
|
|
19
|
+
export declare const RULES_VERSION_FILE = ".remixmate/version.json";
|
|
20
|
+
/** `template upgrade` 只替换这个目录;用户的 templates/、src/ 等一律不动。 */
|
|
21
|
+
export declare const RULES_DIR = ".remixmate";
|
|
22
|
+
/** `template upgrade --with-docs` 额外刷新的 agent 指南(旧文件备份为 *.orig)。 */
|
|
23
|
+
export declare const DOC_FILES: readonly string[];
|
|
10
24
|
/** Local tarball override (testing a starter before it is published). */
|
|
11
25
|
export declare const STARTER_TARBALL_ENV = "REMIXMATE_STARTER_TARBALL";
|
|
12
26
|
/** Marker file that identifies a starter project root (K3). */
|
|
@@ -6,7 +6,21 @@
|
|
|
6
6
|
* the same project on every machine for a given cli version.
|
|
7
7
|
*/
|
|
8
8
|
export const STARTER_PACKAGE = '@remixmate/template-starter';
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 一个 starter 版本 = 一套本地校验规则版本(.remixmate/)。规则更新的发布顺序:
|
|
11
|
+
* template-library `pnpm publish:sdk --publish` 发新 starter → 改这里 → cli 发版。
|
|
12
|
+
* publish:sdk 会比对这个常量,对不上就拒发。
|
|
13
|
+
*/
|
|
14
|
+
export const STARTER_VERSION = '0.2.0';
|
|
15
|
+
/**
|
|
16
|
+
* 工程里记录规则版本的文件(starter ≥0.2.0 构建时写入;`template upgrade` 也会写)。
|
|
17
|
+
* 0.1.0 工程没有它,回落读 package.json 的 version(init 时整份复制自 starter)。
|
|
18
|
+
*/
|
|
19
|
+
export const RULES_VERSION_FILE = '.remixmate/version.json';
|
|
20
|
+
/** `template upgrade` 只替换这个目录;用户的 templates/、src/ 等一律不动。 */
|
|
21
|
+
export const RULES_DIR = '.remixmate';
|
|
22
|
+
/** `template upgrade --with-docs` 额外刷新的 agent 指南(旧文件备份为 *.orig)。 */
|
|
23
|
+
export const DOC_FILES = ['AGENTS.md', 'CLAUDE.md'];
|
|
10
24
|
/** Local tarball override (testing a starter before it is published). */
|
|
11
25
|
export const STARTER_TARBALL_ENV = 'REMIXMATE_STARTER_TARBALL';
|
|
12
26
|
/** Marker file that identifies a starter project root (K3). */
|
package/dist/template/init.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* with the system `tar` (bsdtar ships with Windows 10+), then `npm install`ed.
|
|
7
7
|
* No login is needed (requirement 3.5).
|
|
8
8
|
*/
|
|
9
|
+
import { Output } from './project.js';
|
|
9
10
|
export interface InitOptions {
|
|
10
11
|
dir: string;
|
|
11
12
|
force?: boolean;
|
|
@@ -19,4 +20,9 @@ export interface InitResult {
|
|
|
19
20
|
starter: string;
|
|
20
21
|
installed: boolean;
|
|
21
22
|
}
|
|
23
|
+
/** Obtain the starter tarball: local override, else `npm pack` into `tmp`. Shared with `upgrade`. */
|
|
24
|
+
export declare function obtainStarterTarball(tmp: string, out: Output): Promise<{
|
|
25
|
+
file: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}>;
|
|
22
28
|
export declare function runInit(opts: InitOptions): Promise<InitResult>;
|
package/dist/template/init.js
CHANGED
|
@@ -14,6 +14,7 @@ import { emitProgress } from '../progress.js';
|
|
|
14
14
|
import { STARTER_PACKAGE, STARTER_TARBALL_ENV, STARTER_VERSION } from './constants.js';
|
|
15
15
|
import { Output } from './project.js';
|
|
16
16
|
import { runNpm, runProcess } from './proc.js';
|
|
17
|
+
import { skillInstallHint } from '../install.js';
|
|
17
18
|
function isNonEmptyDir(dir) {
|
|
18
19
|
if (!existsSync(dir))
|
|
19
20
|
return false;
|
|
@@ -22,8 +23,8 @@ function isNonEmptyDir(dir) {
|
|
|
22
23
|
}
|
|
23
24
|
return readdirSync(dir).length > 0;
|
|
24
25
|
}
|
|
25
|
-
/** Obtain the starter tarball: local override, else `npm pack` into `tmp`. */
|
|
26
|
-
async function
|
|
26
|
+
/** Obtain the starter tarball: local override, else `npm pack` into `tmp`. Shared with `upgrade`. */
|
|
27
|
+
export async function obtainStarterTarball(tmp, out) {
|
|
27
28
|
const override = (process.env[STARTER_TARBALL_ENV] ?? '').trim();
|
|
28
29
|
if (override) {
|
|
29
30
|
const file = path.resolve(override);
|
|
@@ -58,14 +59,17 @@ export async function runInit(opts) {
|
|
|
58
59
|
const out = new Output(Boolean(opts.json));
|
|
59
60
|
if (!opts.dir)
|
|
60
61
|
throw new SkillError('❌ 缺少目标目录:remixmate template init <dir>', EXIT.USAGE);
|
|
61
|
-
|
|
62
|
+
// agent 常把 `~/remixmate-templates/x` 加引号传进来,shell 不会展开 —— 这里补上,
|
|
63
|
+
// 否则会在 cwd 下建一个字面量 `~` 目录。
|
|
64
|
+
const raw = opts.dir === '~' || /^~[\\/]/.test(opts.dir) ? path.join(os.homedir(), opts.dir.slice(1)) : opts.dir;
|
|
65
|
+
const dir = path.resolve(raw);
|
|
62
66
|
if (isNonEmptyDir(dir) && !opts.force) {
|
|
63
67
|
throw new SkillError(`❌ 目标目录非空: ${dir}\n 换一个空目录,或确认可以覆盖后加 --force。`, EXIT.USAGE);
|
|
64
68
|
}
|
|
65
69
|
const tmp = mkdtempSync(path.join(os.tmpdir(), 'remixmate-starter-'));
|
|
66
70
|
try {
|
|
67
71
|
emitProgress({ phase: 'template-init:fetch', progress: 0.1 });
|
|
68
|
-
const { file, label } = await
|
|
72
|
+
const { file, label } = await obtainStarterTarball(tmp, out);
|
|
69
73
|
emitProgress({ phase: 'template-init:extract', progress: 0.4 });
|
|
70
74
|
mkdirSync(dir, { recursive: true });
|
|
71
75
|
// npm tarballs are rooted at `package/` — strip it so files land directly in <dir>.
|
|
@@ -94,7 +98,9 @@ export async function runInit(opts) {
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
function printNextSteps(out, dir, installed) {
|
|
97
|
-
|
|
101
|
+
// 相对路径要往上爬两层以上时(init 到了 /tmp 之类),直接给绝对路径更好读。
|
|
102
|
+
const relRaw = path.relative(process.cwd(), dir) || '.';
|
|
103
|
+
const rel = relRaw.startsWith(`..${path.sep}..`) ? dir : relRaw;
|
|
98
104
|
out.info('');
|
|
99
105
|
out.info(`✅ 模板工程已创建: ${dir}`);
|
|
100
106
|
out.info('');
|
|
@@ -112,4 +118,16 @@ function printNextSteps(out, dir, installed) {
|
|
|
112
118
|
out.info(' Claude Code 在工程目录打开,agent 会读取 CLAUDE.md(与 AGENTS.md 内容相同)');
|
|
113
119
|
out.info(' TraeWork 只认项目级 skill:在工程目录执行一次 remixmate install --project');
|
|
114
120
|
out.info(' 其它宿主若还没装 skill:remixmate install');
|
|
121
|
+
out.info(' 当前会话不在工程目录里时:先读 ' + path.join(dir, 'AGENTS.md') + ',或在该目录重开会话');
|
|
122
|
+
let hint = null;
|
|
123
|
+
try {
|
|
124
|
+
hint = skillInstallHint();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// 提示只是锦上添花,读回执失败不能让 init 失败。
|
|
128
|
+
}
|
|
129
|
+
if (hint) {
|
|
130
|
+
out.info('');
|
|
131
|
+
out.info(hint);
|
|
132
|
+
}
|
|
115
133
|
}
|
|
@@ -23,6 +23,16 @@ export declare function resolveTemplateKey(templateDir: string): {
|
|
|
23
23
|
existing: boolean;
|
|
24
24
|
};
|
|
25
25
|
export declare function writeIdentity(templateDir: string, identity: TemplateIdentity): void;
|
|
26
|
+
/** Numeric dotted compare (prerelease tags ignored): <0 / 0 / >0. */
|
|
27
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* The starter version whose rules this project's `.remixmate/` holds.
|
|
30
|
+
* `.remixmate/version.json` wins; 0.1.0 projects fall back to package.json
|
|
31
|
+
* (copied verbatim from the starter at init). null = unknown.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readRulesVersion(root: string): string | null;
|
|
34
|
+
/** Hint line when the project's rules are older than what this cli ships with, else null. */
|
|
35
|
+
export declare function staleRulesHint(root: string): string | null;
|
|
26
36
|
/**
|
|
27
37
|
* Human text goes to stdout by default. With --json it moves to stderr so the
|
|
28
38
|
* last stdout line is the JSON result (progress lines still go to stdout —
|
package/dist/template/project.js
CHANGED
|
@@ -5,7 +5,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { EXIT, SkillError } from '../errors.js';
|
|
8
|
-
import { CHECK_ENTRY, IDENTITY_FILE } from './constants.js';
|
|
8
|
+
import { CHECK_ENTRY, IDENTITY_FILE, RULES_VERSION_FILE, STARTER_PACKAGE, STARTER_VERSION } from './constants.js';
|
|
9
9
|
import { usageError } from './args.js';
|
|
10
10
|
// ── project root ─────────────────────────────────────────────────────────────
|
|
11
11
|
/** Walk up from `start` to the directory holding `.remixmate/check/index.mjs`. */
|
|
@@ -85,6 +85,57 @@ export function resolveTemplateKey(templateDir) {
|
|
|
85
85
|
export function writeIdentity(templateDir, identity) {
|
|
86
86
|
writeFileSync(path.join(templateDir, IDENTITY_FILE), JSON.stringify(identity, null, 2) + '\n', 'utf8');
|
|
87
87
|
}
|
|
88
|
+
// ── rules version (template upgrade) ─────────────────────────────────────────
|
|
89
|
+
/** Numeric dotted compare (prerelease tags ignored): <0 / 0 / >0. */
|
|
90
|
+
export function compareVersions(a, b) {
|
|
91
|
+
const parse = (v) => v.replace(/^v/, '').split(/[-+]/)[0].split('.').map((n) => Number(n) || 0);
|
|
92
|
+
const pa = parse(a);
|
|
93
|
+
const pb = parse(b);
|
|
94
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
95
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
96
|
+
if (d !== 0)
|
|
97
|
+
return d;
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The starter version whose rules this project's `.remixmate/` holds.
|
|
103
|
+
* `.remixmate/version.json` wins; 0.1.0 projects fall back to package.json
|
|
104
|
+
* (copied verbatim from the starter at init). null = unknown.
|
|
105
|
+
*/
|
|
106
|
+
export function readRulesVersion(root) {
|
|
107
|
+
const marker = path.join(root, RULES_VERSION_FILE);
|
|
108
|
+
if (existsSync(marker)) {
|
|
109
|
+
try {
|
|
110
|
+
const v = JSON.parse(readFileSync(marker, 'utf8')).starterVersion;
|
|
111
|
+
if (typeof v === 'string' && v)
|
|
112
|
+
return v;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* fall through */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const pkgFile = path.join(root, 'package.json');
|
|
119
|
+
if (existsSync(pkgFile)) {
|
|
120
|
+
try {
|
|
121
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
122
|
+
if (pkg.name === STARTER_PACKAGE && typeof pkg.version === 'string')
|
|
123
|
+
return pkg.version;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* unknown */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/** Hint line when the project's rules are older than what this cli ships with, else null. */
|
|
132
|
+
export function staleRulesHint(root) {
|
|
133
|
+
const have = readRulesVersion(root);
|
|
134
|
+
if (have && compareVersions(have, STARTER_VERSION) >= 0)
|
|
135
|
+
return null;
|
|
136
|
+
return (`⚠ 本地校验规则来自 starter ${have ?? '(未知版本)'},当前 cli 对应 ${STARTER_VERSION}。` +
|
|
137
|
+
'平台规则可能已更新,本地通过不代表发布能过。运行 remixmate template upgrade 更新规则(不会改动 templates/)。');
|
|
138
|
+
}
|
|
88
139
|
// ── output ───────────────────────────────────────────────────────────────────
|
|
89
140
|
/**
|
|
90
141
|
* Human text goes to stdout by default. With --json it moves to stderr so the
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `remixmate template upgrade [--dry-run] [--with-docs] [--force] [--json]`
|
|
3
|
+
*
|
|
4
|
+
* 只更新工程里的本地校验规则(`.remixmate/`:check 脚本、template.schema.json、
|
|
5
|
+
* template-security-policy.json),来源是本 cli 钉死的 starter 版本(STARTER_VERSION)。
|
|
6
|
+
*
|
|
7
|
+
* 为什么需要:starter 在 init 时整份复制进用户工程,之后平台收紧规则,老工程的
|
|
8
|
+
* 本地 check 还是旧规则 —— 本地通过、服务端拒绝。一个 starter 版本 = 一套规则版本,
|
|
9
|
+
* 所以升级 cli 后跑一次 upgrade 就能对齐。
|
|
10
|
+
*
|
|
11
|
+
* 刻意不动的东西:templates/(用户的模板)、src/、package.json 与依赖、examples/。
|
|
12
|
+
* core 版本变化只提示不自动改 —— 升 core 可能影响模板渲染,必须是用户的决定。
|
|
13
|
+
* `--with-docs` 额外刷新 AGENTS.md / CLAUDE.md(内容不同时旧文件备份为 *.orig)。
|
|
14
|
+
*/
|
|
15
|
+
export interface UpgradeOptions {
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
withDocs?: boolean;
|
|
18
|
+
/** Re-apply even when the project is already at (or above) this cli's starter version. */
|
|
19
|
+
force?: boolean;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
cwd?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface UpgradeResult {
|
|
24
|
+
ok: true;
|
|
25
|
+
from: string | null;
|
|
26
|
+
to: string;
|
|
27
|
+
upgraded: boolean;
|
|
28
|
+
dryRun: boolean;
|
|
29
|
+
docs: string[];
|
|
30
|
+
/** Dependency versions the new starter pins that differ from the project's (hint only). */
|
|
31
|
+
dependencyHints: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
project: string | null;
|
|
34
|
+
starter: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
type Pkg = {
|
|
38
|
+
dependencies?: Record<string, string>;
|
|
39
|
+
devDependencies?: Record<string, string>;
|
|
40
|
+
};
|
|
41
|
+
/** Dependencies whose pinned version the starter changed — reported, never auto-applied. */
|
|
42
|
+
export declare function diffDependencies(project: Pkg, starter: Pkg): UpgradeResult['dependencyHints'];
|
|
43
|
+
export declare function runUpgrade(opts?: UpgradeOptions): Promise<UpgradeResult>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `remixmate template upgrade [--dry-run] [--with-docs] [--force] [--json]`
|
|
3
|
+
*
|
|
4
|
+
* 只更新工程里的本地校验规则(`.remixmate/`:check 脚本、template.schema.json、
|
|
5
|
+
* template-security-policy.json),来源是本 cli 钉死的 starter 版本(STARTER_VERSION)。
|
|
6
|
+
*
|
|
7
|
+
* 为什么需要:starter 在 init 时整份复制进用户工程,之后平台收紧规则,老工程的
|
|
8
|
+
* 本地 check 还是旧规则 —— 本地通过、服务端拒绝。一个 starter 版本 = 一套规则版本,
|
|
9
|
+
* 所以升级 cli 后跑一次 upgrade 就能对齐。
|
|
10
|
+
*
|
|
11
|
+
* 刻意不动的东西:templates/(用户的模板)、src/、package.json 与依赖、examples/。
|
|
12
|
+
* core 版本变化只提示不自动改 —— 升 core 可能影响模板渲染,必须是用户的决定。
|
|
13
|
+
* `--with-docs` 额外刷新 AGENTS.md / CLAUDE.md(内容不同时旧文件备份为 *.orig)。
|
|
14
|
+
*/
|
|
15
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { EXIT, SkillError } from '../errors.js';
|
|
19
|
+
import { emitProgress } from '../progress.js';
|
|
20
|
+
import { DOC_FILES, RULES_DIR, RULES_VERSION_FILE, STARTER_VERSION } from './constants.js';
|
|
21
|
+
import { obtainStarterTarball } from './init.js';
|
|
22
|
+
import { Output, compareVersions, readRulesVersion, requireProjectRoot } from './project.js';
|
|
23
|
+
import { runProcess } from './proc.js';
|
|
24
|
+
/** Dependencies whose pinned version the starter changed — reported, never auto-applied. */
|
|
25
|
+
export function diffDependencies(project, starter) {
|
|
26
|
+
const mine = { ...project.devDependencies, ...project.dependencies };
|
|
27
|
+
const theirs = { ...starter.devDependencies, ...starter.dependencies };
|
|
28
|
+
return Object.entries(theirs)
|
|
29
|
+
.filter(([name, v]) => mine[name] !== v)
|
|
30
|
+
.map(([name, v]) => ({ name, project: mine[name] ?? null, starter: v }))
|
|
31
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
32
|
+
}
|
|
33
|
+
function readPkg(file) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function runUpgrade(opts = {}) {
|
|
42
|
+
const out = new Output(Boolean(opts.json));
|
|
43
|
+
const root = requireProjectRoot(opts.cwd);
|
|
44
|
+
const from = readRulesVersion(root);
|
|
45
|
+
const to = STARTER_VERSION;
|
|
46
|
+
if (from && compareVersions(from, to) >= 0 && !opts.force) {
|
|
47
|
+
out.info(`✅ 本地校验规则已是最新(starter ${from},当前 cli 对应 ${to})。`);
|
|
48
|
+
const result = { ok: true, from, to, upgraded: false, dryRun: Boolean(opts.dryRun), docs: [], dependencyHints: [] };
|
|
49
|
+
out.result({ ...result });
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const tmp = mkdtempSync(path.join(os.tmpdir(), 'remixmate-upgrade-'));
|
|
53
|
+
try {
|
|
54
|
+
emitProgress({ phase: 'template-upgrade:fetch', progress: 0.2 });
|
|
55
|
+
const { file, label } = await obtainStarterTarball(tmp, out);
|
|
56
|
+
const unpacked = path.join(tmp, 'starter');
|
|
57
|
+
mkdirSync(unpacked, { recursive: true });
|
|
58
|
+
const tar = await runProcess('tar', ['-xzf', file, '-C', unpacked, '--strip-components=1'], { stdio: 'pipe' });
|
|
59
|
+
if (tar.code !== 0) {
|
|
60
|
+
throw new SkillError(`❌ 解压 starter 失败(tar 退出码 ${tar.code}): ${tar.stderr.trim().slice(0, 300)}`);
|
|
61
|
+
}
|
|
62
|
+
const newRules = path.join(unpacked, RULES_DIR);
|
|
63
|
+
if (!existsSync(path.join(newRules, 'check', 'index.mjs'))) {
|
|
64
|
+
throw new SkillError(`❌ ${label} 里没有 ${RULES_DIR}/check/index.mjs,不是有效的 starter 包`);
|
|
65
|
+
}
|
|
66
|
+
const docs = opts.withDocs
|
|
67
|
+
? DOC_FILES.filter((f) => existsSync(path.join(unpacked, f)))
|
|
68
|
+
: [];
|
|
69
|
+
const dependencyHints = diffDependencies(readPkg(path.join(root, 'package.json')), readPkg(path.join(unpacked, 'package.json')));
|
|
70
|
+
out.info(`升级本地校验规则:starter ${from ?? '(未知版本)'} → ${to}(${label})`);
|
|
71
|
+
out.info(` 替换 ${RULES_DIR}/(check 脚本、schema、安全策略)`);
|
|
72
|
+
for (const d of docs)
|
|
73
|
+
out.info(` 刷新 ${d}(内容不同时旧文件备份为 ${d}.orig)`);
|
|
74
|
+
out.info(' 不改动 templates/、src/、examples/、package.json');
|
|
75
|
+
if (!opts.dryRun) {
|
|
76
|
+
emitProgress({ phase: 'template-upgrade:apply', progress: 0.6 });
|
|
77
|
+
// 先放到旁边再整体换名,避免复制到一半中断留下半新半旧的规则目录。
|
|
78
|
+
const target = path.join(root, RULES_DIR);
|
|
79
|
+
const staged = path.join(root, `${RULES_DIR}.upgrading`);
|
|
80
|
+
const old = path.join(root, `${RULES_DIR}.previous`);
|
|
81
|
+
rmSync(staged, { recursive: true, force: true });
|
|
82
|
+
rmSync(old, { recursive: true, force: true });
|
|
83
|
+
cpSync(newRules, staged, { recursive: true });
|
|
84
|
+
// 老 starter 包(0.1.0)没有 version.json:补写,之后 cli 才认得出版本。
|
|
85
|
+
const marker = path.join(staged, path.relative(RULES_DIR, RULES_VERSION_FILE));
|
|
86
|
+
if (!existsSync(marker)) {
|
|
87
|
+
writeFileSync(marker, JSON.stringify({ starterVersion: to }, null, 2) + '\n', 'utf8');
|
|
88
|
+
}
|
|
89
|
+
if (existsSync(target))
|
|
90
|
+
renameSync(target, old);
|
|
91
|
+
renameSync(staged, target);
|
|
92
|
+
rmSync(old, { recursive: true, force: true });
|
|
93
|
+
for (const d of docs) {
|
|
94
|
+
const dest = path.join(root, d);
|
|
95
|
+
const next = readFileSync(path.join(unpacked, d), 'utf8');
|
|
96
|
+
if (existsSync(dest)) {
|
|
97
|
+
const prev = readFileSync(dest, 'utf8');
|
|
98
|
+
if (prev === next)
|
|
99
|
+
continue;
|
|
100
|
+
writeFileSync(`${dest}.orig`, prev, 'utf8');
|
|
101
|
+
}
|
|
102
|
+
writeFileSync(dest, next, 'utf8');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const h of dependencyHints) {
|
|
106
|
+
out.warn(` ⚠ 新 starter 钉的 ${h.name}@${h.starter},你的工程是 ${h.project ?? '(未安装)'}。` +
|
|
107
|
+
`需要时手动执行 npm i ${h.name}@${h.starter}(升级 core 可能影响模板效果,先 check + preview 再发布)。`);
|
|
108
|
+
}
|
|
109
|
+
emitProgress({ phase: 'template-upgrade:done', progress: 1 });
|
|
110
|
+
out.info(opts.dryRun ? '\n[dry-run] 未写入任何文件。' : '\n✅ 已更新。接着运行 remixmate template check 看新规则下是否还有问题。');
|
|
111
|
+
const result = { ok: true, from, to, upgraded: !opts.dryRun, dryRun: Boolean(opts.dryRun), docs, dependencyHints };
|
|
112
|
+
out.result({ ...result });
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof SkillError)
|
|
117
|
+
throw err;
|
|
118
|
+
throw new SkillError(`❌ 升级失败: ${err.message}`, EXIT.ERROR);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.67",
|
|
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",
|
|
@@ -4,15 +4,17 @@ description: |
|
|
|
4
4
|
Create your own reusable video template locally and publish it as a private template on RemixMate.
|
|
5
5
|
Scaffolds a starter project (`remixmate template init <dir>`) with Remotion Studio preview, local validation,
|
|
6
6
|
remote draft preview and one-command publishing. Purely local until preview/publish; no login needed to start.
|
|
7
|
-
Use this skill whenever the user wants to:
|
|
8
|
-
- 做视频模板 / 自定义视频模板 /
|
|
7
|
+
Use this skill whenever the user wants to (「模板」and「模版」are the same word):
|
|
8
|
+
- 做视频模板 / 做视频模版 / 自定义视频模板 / 创建一个新的视频模版 / 新建模版 / 自己写一个模板 / 把这个画面做成模板
|
|
9
9
|
- template authoring, create a video template, build a custom Remotion template
|
|
10
10
|
- preview or publish a template they are developing locally
|
|
11
11
|
triggers:
|
|
12
|
-
- 做视频模板
|
|
13
|
-
- 自定义视频模板
|
|
14
|
-
- 创建模板 / 新建模板 /
|
|
15
|
-
-
|
|
12
|
+
- 做视频模板 / 做视频模版
|
|
13
|
+
- 自定义视频模板 / 自定义视频模版
|
|
14
|
+
- 创建模板 / 新建模板 / 创建模版 / 新建模版
|
|
15
|
+
- 我想创建一个新的视频模版 / 创建一个视频模板
|
|
16
|
+
- 自己写一个视频模板 / 自己做一个模版
|
|
17
|
+
- 预览或发布我本地开发的模板 / 模版
|
|
16
18
|
- template authoring
|
|
17
19
|
- create video template / build a custom video template
|
|
18
20
|
- publish my own Remotion template
|
|
@@ -30,15 +32,21 @@ fields; take them from that guide and from the bundled examples.
|
|
|
30
32
|
|
|
31
33
|
## Workflow
|
|
32
34
|
|
|
33
|
-
1. **Create the project** (no login)
|
|
35
|
+
1. **Create the project** (no login). Use an absolute path outside the current repo, e.g.
|
|
36
|
+
`~/remixmate-templates/<name>`, unless the user names a location:
|
|
34
37
|
```bash
|
|
35
|
-
remixmate template init
|
|
38
|
+
remixmate template init ~/remixmate-templates/<name>
|
|
36
39
|
```
|
|
37
40
|
Refuses a non-empty directory unless `--force`. Needs only Node ≥ 18 and npm.
|
|
38
41
|
As a skill call: `remixmate template-authoring --dir <dir> [--force]`.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
**Needs network**: it runs `npm pack` + `npm install` (a few hundred packages, about a minute).
|
|
43
|
+
In a sandboxed host (e.g. Codex) request network / escalated permission for this command up
|
|
44
|
+
front. Exit code 5 or an npm network error = no network — not a bug in the project.
|
|
45
|
+
2. **Open the project and read `<dir>/AGENTS.md`** before writing any code. If the session is not
|
|
46
|
+
rooted in `<dir>`, read that file by absolute path (or suggest reopening the session there).
|
|
47
|
+
Examples live in `examples/` (read-only — copy one into `templates/<name>/` and edit the copy).
|
|
48
|
+
3. **Develop with live preview**: `npm run dev` opens Remotion Studio. The first run (and the first
|
|
49
|
+
local render) downloads a headless Chrome — also needs network, may take a while.
|
|
42
50
|
4. **Validate locally** (no login):
|
|
43
51
|
```bash
|
|
44
52
|
remixmate template check <name> # add --json for a machine-readable issue list
|
|
@@ -56,12 +64,23 @@ fields; take them from that guide and from the bundled examples.
|
|
|
56
64
|
```
|
|
57
65
|
The first publish writes `templates/<name>/.remixmate.json` with the template key; publishing
|
|
58
66
|
again updates the same template. Keep that file.
|
|
59
|
-
7. **Render with it
|
|
67
|
+
7. **Render with it** — `render-video` needs a DSL source; `--template-id` alone is rejected.
|
|
68
|
+
Start from the template's own example (absolute path):
|
|
60
69
|
```bash
|
|
61
|
-
remixmate render-video --template-id <templateKey>
|
|
70
|
+
remixmate render-video --dsl "$PWD/templates/<name>/dsl-example.json" --template-id <templateKey>
|
|
62
71
|
```
|
|
63
72
|
`remixmate template list` shows the user's private templates.
|
|
64
73
|
|
|
74
|
+
## Keeping the local rules current
|
|
75
|
+
|
|
76
|
+
The project's check rules (`.remixmate/`) are copied in at init time. When `check` / `preview` /
|
|
77
|
+
`publish` print "本地校验规则来自 starter …" the platform rules have moved on — run:
|
|
78
|
+
```bash
|
|
79
|
+
remixmate template upgrade # replaces .remixmate/ only; templates/ untouched
|
|
80
|
+
remixmate template upgrade --with-docs # also refresh AGENTS.md / CLAUDE.md (old copies kept as *.orig)
|
|
81
|
+
```
|
|
82
|
+
Then re-run `check`. Dependency changes (e.g. a newer template-core) are reported, never applied.
|
|
83
|
+
|
|
65
84
|
## Notes
|
|
66
85
|
|
|
67
86
|
- Not logged in → exit code 4: run `remixmate login`. Backend unreachable → exit code 5.
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "template-authoring",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "pending-template-authoring",
|
|
5
|
-
"version": "
|
|
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 preview`, `remixmate template publish`, and rendering with `remixmate render-video --template-id <templateKey>`.\n\nUse this skill whenever the user asks for any of:\n- 做视频模板 / 自定义视频模板 /
|
|
5
|
+
"version": "V3",
|
|
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 preview`, `remixmate template publish`, 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
|
}
|