返回 html-video
bin.ts
根目录 / packages / cli / src / bin.ts
1 #!/usr/bin/env node
2 import cacModule from 'cac';
3 import { bootstrap } from './context.js';
4 import { setJsonMode, ok, fail } from './output.js';
5 import { runDoctor } from './commands/doctor.js';
6 import { listEngines } from './commands/list-engines.js';
7 import { searchTemplates, inspectTemplate } from './commands/templates.js';
8 import {
9 projectCreate,
10 projectList,
11 projectShow,
12 projectDelete,
13 projectAddAsset,
14 projectRemoveAsset,
15 projectSetTemplate,
16 projectSetVar,
17 projectSetVars,
18 projectPreview,
19 projectRender,
20 } from './commands/project.js';
21 import { startStudioServer } from './studio-server.js';
22
23 // cac is a CJS default export; ESM interop sometimes wraps it in `.default`
24 // biome-ignore lint/suspicious/noExplicitAny: cac's types don't expose this shape
25 const cacFn: (name: string) => any =
26 typeof cacModule === 'function' ? (cacModule as any) : (cacModule as any).default;
27 const cli = cacFn('html-video');
28
29 cli.option('--json', 'JSON output (default: on)', { default: true });
30 cli.option('--no-color', 'disable ANSI colors');
31 cli.option('--cwd <path>', 'project root');
32
33 cli.command('doctor', 'Diagnose environment').action(async (opts: any) => {
34 setJsonMode(!!opts.json);
35 const ctx = await bootstrap({ cwd: opts.cwd });
36 await runDoctor(ctx);
37 });
38
39 cli.command('list-engines', 'List installed engine adapters').action(async (opts: any) => {
40 setJsonMode(!!opts.json);
41 const ctx = await bootstrap({ cwd: opts.cwd });
42 await listEngines(ctx);
43 });
44
45 cli
46 .command('search-templates', 'Search templates by intent')
47 .option('--intent <text>', 'Free-text user intent')
48 .option('--aspect <ratio>', '16:9 / 9:16 / 1:1')
49 .option('--license-allow <list>', 'Comma-separated SPDX ids')
50 .option('--top <n>', 'Top N matches', { default: 5 })
51 .action(async (opts: any) => {
52 setJsonMode(!!opts.json);
53 const ctx = await bootstrap({ cwd: opts.cwd });
54 await searchTemplates(ctx, {
55 intent: opts.intent,
56 aspect: opts.aspect,
57 licenseAllow: opts.licenseAllow,
58 top: Number(opts.top),
59 });
60 });
61
62 cli
63 .command('inspect-template <id>', 'Show full metadata for a template')
64 .action(async (id: string, opts: any) => {
65 setJsonMode(!!opts.json);
66 const ctx = await bootstrap({ cwd: opts.cwd });
67 await inspectTemplate(ctx, id);
68 });
69
70 // ====== project-* commands (RFC-05) ======
71
72 cli
73 .command('project-create', 'Create a new project')
74 .option('--name <text>', 'Project name (required)')
75 .option('--intent <text>', 'One-sentence description')
76 .option('--aspect <ratio>', '16:9 / 9:16 / 1:1')
77 .option('--commercial', 'Mark as commercial-use')
78 .action(async (opts: any) => {
79 setJsonMode(!!opts.json);
80 const ctx = await bootstrap({ cwd: opts.cwd });
81 await projectCreate(ctx, {
82 name: opts.name,
83 intent: opts.intent,
84 aspect: opts.aspect,
85 commercial: !!opts.commercial,
86 });
87 });
88
89 cli.command('project-list', 'List all projects').action(async (opts: any) => {
90 setJsonMode(!!opts.json);
91 const ctx = await bootstrap({ cwd: opts.cwd });
92 await projectList(ctx);
93 });
94
95 cli
96 .command('project-show <id>', 'Show project details')
97 .action(async (id: string, opts: any) => {
98 setJsonMode(!!opts.json);
99 const ctx = await bootstrap({ cwd: opts.cwd });
100 await projectShow(ctx, id);
101 });
102
103 cli
104 .command('project-delete <id>', 'Delete a project')
105 .action(async (id: string, opts: any) => {
106 setJsonMode(!!opts.json);
107 const ctx = await bootstrap({ cwd: opts.cwd });
108 await projectDelete(ctx, id);
109 });
110
111 cli
112 .command('project-add-asset <id>', 'Add an asset to a project')
113 .option('--file <path>', 'Path to a file (image / video / audio / data)')
114 .option('--inline-text <text>', 'Inline text content')
115 .option('--inline-data-file <path>', 'Path to JSON / CSV file to embed as data asset')
116 .option('--caption <text>', 'Optional caption')
117 .action(async (id: string, opts: any) => {
118 setJsonMode(!!opts.json);
119 const ctx = await bootstrap({ cwd: opts.cwd });
120 await projectAddAsset(ctx, id, {
121 file: opts.file,
122 inlineText: opts.inlineText,
123 inlineDataFile: opts.inlineDataFile,
124 caption: opts.caption,
125 });
126 });
127
128 cli
129 .command('project-remove-asset <id>', 'Remove an asset from a project')
130 .option('--asset <assetId>', 'Asset id to remove (required)')
131 .action(async (id: string, opts: any) => {
132 setJsonMode(!!opts.json);
133 if (!opts.asset) fail('invalid-input', '--asset required');
134 const ctx = await bootstrap({ cwd: opts.cwd });
135 await projectRemoveAsset(ctx, id, opts.asset);
136 });
137
138 cli
139 .command('project-set-template <id>', 'Pick a template for the project')
140 .option('--template <templateId>', 'Template id (required)')
141 .action(async (id: string, opts: any) => {
142 setJsonMode(!!opts.json);
143 if (!opts.template) fail('invalid-input', '--template required');
144 const ctx = await bootstrap({ cwd: opts.cwd });
145 await projectSetTemplate(ctx, id, opts.template);
146 });
147
148 cli
149 .command('project-set-var <id>', 'Set a single variable')
150 .option('--key <name>', 'Variable name')
151 .option('--value <json>', 'Value (parsed as JSON if possible, else string)')
152 .action(async (id: string, opts: any) => {
153 setJsonMode(!!opts.json);
154 if (!opts.key) fail('invalid-input', '--key required');
155 const ctx = await bootstrap({ cwd: opts.cwd });
156 await projectSetVar(ctx, id, opts.key, opts.value);
157 });
158
159 cli
160 .command('project-set-vars <id>', 'Replace all variables from a JSON file')
161 .option('--vars-file <path>', 'Path to JSON file (required)')
162 .action(async (id: string, opts: any) => {
163 setJsonMode(!!opts.json);
164 if (!opts.varsFile) fail('invalid-input', '--vars-file required');
165 const ctx = await bootstrap({ cwd: opts.cwd });
166 await projectSetVars(ctx, id, opts.varsFile);
167 });
168
169 cli
170 .command('project-preview <id>', 'Render an HTML preview of the project')
171 .action(async (id: string, opts: any) => {
172 setJsonMode(!!opts.json);
173 const ctx = await bootstrap({ cwd: opts.cwd });
174 await projectPreview(ctx, id);
175 });
176
177 cli
178 .command('project-render <id>', 'Export the project to MP4')
179 .option('--output <path>', 'Output MP4 path')
180 .option('--stream-progress', 'Emit progress as NDJSON')
181 .action(async (id: string, opts: any) => {
182 setJsonMode(!!opts.json);
183 const ctx = await bootstrap({ cwd: opts.cwd });
184 await projectRender(ctx, id, {
185 output: opts.output,
186 streamProgress: !!opts.streamProgress,
187 });
188 });
189
190 // ====== Studio (HTML Anything-style three-pane UI) ======
191
192 cli
193 .command('studio', 'Launch the project studio in the browser')
194 .option('--port <n>', 'Port (default 3071)', { default: 3071 })
195 .action(async (opts: any) => {
196 setJsonMode(!!opts.json);
197 const ctx = await bootstrap({ cwd: opts.cwd });
198 const handle = await startStudioServer(ctx, Number(opts.port));
199 ok({
200 url: handle.url,
201 port: handle.port,
202 pid: process.pid,
203 project_count: (await ctx.orchestrator.list()).length,
204 template_count: ctx.templates.list().length,
205 note: 'Studio running. Press Ctrl+C to stop.',
206 });
207 process.on('SIGINT', () => {
208 handle.close();
209 process.exit(0);
210 });
211 });
212
213 cli.help();
214 cli.version('0.1.0');
215 cli.parse();
216
216 lines TYPESCRIPT