reelforge 0.8.0 → 0.10.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.
package/README.md CHANGED
@@ -1,240 +1,289 @@
1
- # reelforge
2
-
3
- > CLI for [ReelForge Studio](https://github.com/puke3615/ReelForge) — every REST API exposed as a command, with `--help` available at every level.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g reelforge
9
- ```
10
-
11
- Or use directly without install:
12
-
13
- ```bash
14
- npx reelforge <command>
15
- ```
16
-
17
- After install, two binaries are on your `PATH` — `reelforge` and the short alias `rf`. Both behave identically; the docs use `rf` from here on.
18
-
19
- ```bash
20
- rf --version # same as `reelforge --version`
21
- ```
22
-
23
- ## Quick start
24
-
25
- The CLI ships pointing at the hosted instance (`https://reelforge.timor419.com`). Log in once, then call:
26
-
27
- ```bash
28
- npm install -g reelforge
29
- rf login # opens browser; headless? rf login <api_key>
30
- rf whoami # balance + api_keys
31
- rf create "为什么我们还没找到外星文明?" # auto-saves to ./<title>-<id>.mp4 in cwd
32
- ```
33
-
34
- That's the whole story — no server to run.
35
-
36
- ### Output behavior
37
-
38
- | invocation | result |
39
- |---|---|
40
- | `rf create "..."` | Saves to `./<sanitized-title>-<task_id_short>.mp4`, prints the path |
41
- | `rf create "..." -o ./videos/space.mp4` | Saves to that exact path (must include filename, not just a directory) |
42
- | `rf create "..." --no-download` | Skips local save, prints JSON result with `video_url` |
43
- | `rf create "..." \| jq .video_url` | When stdout is piped, download is skipped automatically |
44
-
45
- ### Self-hosting
46
-
47
- If you want to run your own ReelForge Studio (own RelayX key, your own pricing) clone the upstream repo, `pnpm dev`, then point the CLI at it:
48
-
49
- ```bash
50
- rf --server http://localhost:8501 health
51
- # or persist:
52
- export REELFORGE_SERVER=http://localhost:8501
53
- # or via `rf login <key> --server http://localhost:8501`
54
- ```
55
-
56
- ## Global options
57
-
58
- | flag | description |
59
- |---|---|
60
- | `-s, --server <url>` | ReelForge server URL (overrides `$REELFORGE_SERVER`; default `https://reelforge.timor419.com`) |
61
- | `-k, --api-key <key>` | API key (overrides `$REELFORGE_API_KEY` and `reelforge login` saved key) |
62
- | `--json` | Output raw JSON instead of pretty text — pipe-friendly |
63
- | `--quiet` | Suppress informational messages on stderr |
64
- | `-v, --version` | Show CLI version |
65
- | `-h, --help` | Show help (works on every sub-command) |
66
-
67
- ## Command map
68
-
69
- Run `rf <command> --help` for full details on any of these.
70
-
71
- ### Core capabilities
72
-
73
- | command | what it does |
74
- |---|---|
75
- | `llm chat -p <text>` | Send one prompt to the configured LLM (RelayX gateway by default) |
76
- | `llm presets` | List built-in RelayX model presets |
77
- | `tts edge -t <text> -o out.mp3` | Local Edge TTS synthesis (free) |
78
- | `tts relayx -t <text> -o out.mp3` | RelayX TTS (vox/index-tts-2, 149 built-in voices) |
79
- | `tts voices [--locale zh]` | List supported Edge TTS voices |
80
- | `images generate -p <prompt> -m rx-image-flux` | Image generation via RelayX (rx-image-z / rx-image-flux / rx-image-qwen) |
81
-
82
- ### Content / audio / subtitle atomics
83
-
84
- | command | what it does |
85
- |---|---|
86
- | `content scene-plan -t <topic>` | Single LLM call: title + master script + per-scene image prompts (replaces the old narration / split / image-prompts / title trio) |
87
- | `content scene-plan --script <text-or-@file>` | Same, but the user supplies the script verbatim — LLM only segments and writes image prompts |
88
- | `audio transcribe -f <file>` / `--url <url>` | RelayX paraformer-v2 ASR with word + segment timestamps |
89
- | `subtitles split -t <text-or-@file>` | Deterministic tiered-punctuation subtitle line splitter (pure function, zero billing) |
90
-
91
- ### Composition
92
-
93
- | command | what it does |
94
- |---|---|
95
- | `templates list [--size 1080x1920] [--type image]` | List HTML frame templates |
96
- | `templates preview <keyOrPath> [-o out.png]` | Render a preview from a preset key **or your own local .html file** |
97
- | `templates show <key> [-o file.html]` | Print or save the source HTML of any preset — copy it as a starting point for a custom template |
98
- | `frames render -t <keyOrPath> --title ... --text ...` | Render a single composed frame to PNG. `-t` accepts a preset key **or a local .html path** |
99
- | `compositions concat <v1> <v2> -o out.mp4` | FFmpeg concat (+ optional BGM) |
100
- | `compositions bgm -i video.mp4 --bgm bgm.mp3 -o out.mp4` | Add background music |
101
- | `compositions image-to-video -i img.png -a aud.mp3 -o out.mp4` | Build video from image + audio |
102
- | `compositions overlay -v video.mp4 --overlay overlay.png -o out.mp4` | Overlay PNG on video |
103
-
104
- ### End-to-end pipelines
105
-
106
- All `pipelines *` commands submit an **async task** and (by default) poll until it finishes with a live progress indicator on stderr. Use `--no-wait` to return immediately with a `task_id`, then `rf tasks wait <id>` later.
107
-
108
- The standard pipeline is **audio-first**: scene-plan → one-shot TTS → ASR alignment → per-scene image generation → per-subtitle-line frame rendering → ffmpeg mux. One continuous master audio track; image cuts at scene boundaries; subtitle cuts at line boundaries.
109
-
110
- | command | what it does |
111
- |---|---|
112
- | `pipelines standard -t <topic>` (or `--script <text>`) | Audio-first pipeline; `-d/--duration` and `-p/--pace` are the two main knobs |
113
-
114
- ### Resources
115
-
116
- | command | what it does |
117
- |---|---|
118
- | `bgm list / upload <file> / delete <name>` | Manage background music |
119
- | `files list / upload <file> / download <path> / delete <path>` | Manage user assets |
120
-
121
- ### System
122
-
123
- | command | what it does |
124
- |---|---|
125
- | `config get` | Read server config (keys masked) |
126
- | `config set <key> <value>` | Update a dotted-path setting (e.g. `llm.api_key sk-xxx`) |
127
- | `config patch <file>` | Apply a JSON-merge patch |
128
- | `tasks list [--status running]` | List recent tasks |
129
- | `tasks get <id>` / `tasks wait <id>` / `tasks cancel <id>` | Task lifecycle |
130
- | `history list / get <id> / delete <id>` | Browse / delete completed runs |
131
- | `health` | Server health + capability check |
132
-
133
- ## Examples
134
-
135
- ```bash
136
- # 1. One-click out a video (45s default, AI writes the script)
137
- rf create "为什么我们还没找到外星文明?"
138
-
139
- # 2. Longer video with a slower visual rhythm
140
- rf create "深夜便利店的灯光" -d 90 -p slow
141
-
142
- # 3. Your own script — no narration-splitting on your side, the pipeline handles it
143
- rf create --script @./my-script.txt
144
- rf create --script "雨水缓缓滑落在玻璃窗上,像是无声的泪珠。"
145
-
146
- # 4. Pick a built-in visual style preset
147
- rf create "美食教程" --style photorealistic
148
-
149
- # 5. Pipeline form with explicit output path
150
- rf pipelines standard \
151
- --script @./script.txt \
152
- --frame-template 1080x1920/image_default.html \
153
- -p normal -o smoke.mp4
154
-
155
- # 6. Inspect existing tasks & redownload a finished video
156
- rf tasks list --limit 5
157
- rf history get <task-id> --download recovered.mp4
158
-
159
- # 7. Atomics for stand-alone use
160
- rf content scene-plan -t "雨天的玻璃窗" -d 45 --json | jq .scenes
161
- rf audio transcribe -f narration.mp3 --json | jq '.words[:5]'
162
- rf subtitles split -t @./narration.txt --min 10 --hard-max 24
163
-
164
- # 8. JSON pipe for automation
165
- rf llm presets --json | jq '.[].defaultModel'
166
-
167
- # 9. Configure & test LLM (self-hosted)
168
- rf config set llm.api_key rx-xxxxx # RelayX key (or your own provider key)
169
- rf config set llm.base_url https://relayx.timor419.com/v1
170
- rf config set llm.model anthropic/claude-4-7-sonnet
171
- rf llm chat -p 'one-sentence summary of antifragile'
172
-
173
- # 10. Use your own HTML template (no PR/release needed)
174
- # Any --frame-template that points to a local .html file is read and sent
175
- # inline. Declare size inside the file via
176
- # <meta name="template:width" content="1080">
177
- # <meta name="template:height" content="1920">
178
- # or pass --frame-template-size 1080x1920.
179
- rf templates show 1080x1920/image_default.html -o my-brand.html # copy a preset
180
- # ...edit my-brand.html to suit your style...
181
- rf templates preview ./my-brand.html --title "Hello" -o preview.png
182
- rf frames render -t ./my-brand.html --values '{"author":"Alice"}' -o frame.png
183
- rf pipelines standard -t "宠物" --frame-template ./my-brand.html -o final.mp4
184
- ```
185
-
186
- ### Custom HTML templates
187
-
188
- Easiest way to start: grab a preset as a reference.
189
-
190
- ```bash
191
- rf templates list # see all keys
192
- rf templates show 1080x1920/static_default.html # print to stdout
193
- rf templates show 1080x1920/image_default.html -o my-brand.html # save and edit
194
- ```
195
-
196
- `{{title}}`, `{{text}}`, `{{image}}`, `{{index}}`, `{{total}}` are reserved built-ins auto-injected by the pipeline; everything else uses the `{{name:type=default}}` DSL (`type` ∈ `text|number|color|bool`). Pass extras through `--values '{"author":"Alice"}'` (or `template_params` on the pipeline API).
197
-
198
- - `{{index}}` current scene number, 1-based
199
- - `{{total}}` scene count the LLM actually produced (use this for "scene N of M" badges; don't hardcode in `template_params`, the scene count is decided at runtime)
200
-
201
- #### Template type — does the pipeline generate an AI image per scene?
202
-
203
- When you ship an inline template through `rf create` / `rf pipelines standard`, ReelForge needs to know whether each scene should kick off RelayX image generation. Resolution priority (high → low):
204
-
205
- 1. Explicit flag `--frame-template-type image|static|asset` (or `frame_template_type` in the API body).
206
- 2. Inside the HTML `<meta name="template:type" content="image">` (or `static` / `asset`).
207
- 3. **Default: `image`** — best practice for zero-config users. If your template doesn't reference scene imagery (pure-text card, etc.), declare `static` explicitly to skip image generation and its cost.
208
-
209
- The placeholder `{{image}}` no longer doubles as a type signal — declare type explicitly.
210
-
211
- Limits and safety:
212
-
213
- - Max 2 MB per inline HTML.
214
- - The render sandbox blocks `file://`, loopback / private / link-local IPs, CGNAT range, cloud-metadata, and `*.local` / `*.internal` hostnames. So your template can only reference public `https`/`http` resources or `data:` URIs.
215
- - If the CLI is talking to a hosted server, local-path `--image` won't reach the server; either upload to `rf files upload` first or use an HTTPS URL / data: URI.
216
-
217
- #### API field reference
218
-
219
- | endpoint | inline HTML field | size field | type field |
220
- |---|---|---|---|
221
- | `POST /api/v1/frames/render` | `template_html` | `size` | — (n/a, no image generation) |
222
- | `POST /api/v1/templates/preview` | `template_html` | `size` | |
223
- | `POST /api/v1/pipelines/standard` | `frame_template_inline` | `frame_template_size` | `frame_template_type` |
224
-
225
- The pipeline endpoint uses the `frame_template_*` prefix because it already has a `frame_template` field (preset key). The single-frame endpoints use the shorter `template_html` because they don't.
226
-
227
- ## Tip getting unstuck
228
-
229
- Every level has `--help`:
230
-
231
- ```bash
232
- rf --help # top-level overview
233
- rf pipelines --help # list of pipelines
234
- rf pipelines standard --help # full option reference
235
- rf tts edge --help # one specific command
236
- ```
237
-
238
- ## License
239
-
240
- Apache-2.0
1
+ # reelforge
2
+
3
+ > CLI for [ReelForge Studio](https://github.com/puke3615/ReelForge) — every REST API exposed as a command, with `--help` available at every level.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g reelforge
9
+ ```
10
+
11
+ Or use directly without install:
12
+
13
+ ```bash
14
+ npx reelforge <command>
15
+ ```
16
+
17
+ After install, two binaries are on your `PATH` — `reelforge` and the short alias `rf`. Both behave identically; the docs use `rf` from here on.
18
+
19
+ ```bash
20
+ rf --version # same as `reelforge --version`
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ The CLI ships pointing at the hosted instance (`https://reelforge.timor419.com`). Log in once, then call:
26
+
27
+ ```bash
28
+ npm install -g reelforge
29
+ rf login # opens browser; headless? rf login <api_key>
30
+ rf whoami # balance + api_keys
31
+ rf create "为什么我们还没找到外星文明?" # auto-saves to ./<title>-<id>.mp4 in cwd
32
+ ```
33
+
34
+ That's the whole story — no server to run.
35
+
36
+ ### Output behavior
37
+
38
+ | invocation | result |
39
+ |---|---|
40
+ | `rf create "..."` | Saves to `./<sanitized-title>-<task_id_short>.mp4`, prints the path |
41
+ | `rf create "..." -o ./videos/space.mp4` | Saves to that exact path (must include filename, not just a directory) |
42
+ | `rf create "..." --no-download` | Skips local save, prints JSON result with `video_url` |
43
+ | `rf create "..." \| jq .video_url` | When stdout is piped, download is skipped automatically |
44
+
45
+ ### Self-hosting
46
+
47
+ If you want to run your own ReelForge Studio (own RelayX key, your own pricing) clone the upstream repo, `pnpm dev`, then point the CLI at it:
48
+
49
+ ```bash
50
+ rf --server http://localhost:8501 health
51
+ # or persist:
52
+ export REELFORGE_SERVER=http://localhost:8501
53
+ # or via `rf login <key> --server http://localhost:8501`
54
+ ```
55
+
56
+ ## Global options
57
+
58
+ | flag | description |
59
+ |---|---|
60
+ | `-s, --server <url>` | ReelForge server URL (overrides `$REELFORGE_SERVER`; default `https://reelforge.timor419.com`) |
61
+ | `-k, --api-key <key>` | API key (overrides `$REELFORGE_API_KEY` and `reelforge login` saved key) |
62
+ | `--json` | Output raw JSON instead of pretty text — pipe-friendly |
63
+ | `--quiet` | Suppress informational messages on stderr |
64
+ | `-v, --version` | Show CLI version |
65
+ | `-h, --help` | Show help (works on every sub-command) |
66
+
67
+ ## Command map
68
+
69
+ Run `rf <command> --help` for full details on any of these.
70
+
71
+ ### Core capabilities
72
+
73
+ | command | what it does |
74
+ |---|---|
75
+ | `llm chat -p <text>` | Send one prompt to the configured LLM (RelayX gateway by default) |
76
+ | `llm presets` | List built-in RelayX model presets |
77
+ | `tts edge -t <text> -o out.mp3` | Local Edge TTS synthesis (free) |
78
+ | `tts relayx -t <text> -o out.mp3` | RelayX TTS (vox/index-tts-2, 149 built-in voices) |
79
+ | `tts voices [--locale zh]` | List supported Edge TTS voices |
80
+ | `images generate -p <prompt> -m rx-image-flux` | Image generation via RelayX (rx-image-z / rx-image-flux / rx-image-qwen) |
81
+
82
+ ### Content / audio / subtitle atomics
83
+
84
+ | command | what it does |
85
+ |---|---|
86
+ | `content scene-plan -t <topic>` | Single LLM call: title + master script + per-scene image prompts (replaces the old narration / split / image-prompts / title trio) |
87
+ | `content scene-plan --script <text-or-@file>` | Same, but the user supplies the script verbatim — LLM only segments and writes image prompts |
88
+ | `audio transcribe -f <file>` / `--url <url>` | RelayX paraformer-v2 ASR with word + segment timestamps |
89
+ | `subtitles split -t <text-or-@file>` | Deterministic tiered-punctuation subtitle line splitter (pure function, zero billing) |
90
+
91
+ ### Composition
92
+
93
+ | command | what it does |
94
+ |---|---|
95
+ | `templates list [--size 1080x1920] [--type image]` | List HTML frame templates |
96
+ | `templates preview <keyOrPath> [-o out.png]` | Render a preview from a preset key **or your own local .html file** |
97
+ | `templates show <key> [-o file.html]` | Print or save the source HTML of any preset — copy it as a starting point for a custom template |
98
+ | `frames render -t <keyOrPath> --title ... --text ...` | Render a single composed frame to PNG. `-t` accepts a preset key **or a local .html path** |
99
+ | `compositions concat <v1> <v2> -o out.mp4` | FFmpeg concat (+ optional BGM) |
100
+ | `compositions bgm -i video.mp4 --bgm bgm.mp3 -o out.mp4` | Add background music |
101
+ | `compositions image-to-video -i img.png -a aud.mp3 -o out.mp4` | Build video from image + audio |
102
+ | `compositions overlay -v video.mp4 --overlay overlay.png -o out.mp4` | Overlay PNG on video |
103
+
104
+ ### End-to-end pipelines
105
+
106
+ All `pipelines *` commands submit an **async task** and (by default) poll until it finishes with a live progress indicator on stderr. Use `--no-wait` to return immediately with a `task_id`, then `rf tasks wait <id>` later.
107
+
108
+ The standard pipeline is **audio-first**: scene-plan → one-shot TTS → ASR alignment → per-scene image generation → per-subtitle-line frame rendering → ffmpeg mux. One continuous master audio track; image cuts at scene boundaries; subtitle cuts at line boundaries.
109
+
110
+ | command | what it does |
111
+ |---|---|
112
+ | `pipelines standard -t <topic>` (or `--script <text>`) | Audio-first pipeline; `-d/--duration` and `-p/--pace` are the two main knobs |
113
+
114
+ #### Composition knobs
115
+
116
+ Three independent axes mix and match as you like:
117
+
118
+ | flag | values | default | what changes |
119
+ |---|---|---|---|
120
+ | `--motion` | `off` / `lite` / `max` | `lite` | per-scene zoompan + crossfade intensity |
121
+ | `--layout` | `full` / `blur-bg` / `letterbox` | `full` | how the image sits in the canvas |
122
+ | `--subtitle-style` | `plate` / `stroke` / `cinema` | `plate` | subtitle look |
123
+
124
+ **Layout presets**:
125
+ - `full` image fills the whole 1080×1920 canvas. Generates a 1080×1920 image. High-impact; best for human portraits, landscapes, 9:16-native content.
126
+ - `blur-bg` image at 1080×1080 centered, top/bottom is a gaussian-blurred copy of the **same image** moving in sync with the foreground. Generates a 1080×1080 image (cheaper + no wasted pixels). Best for charts, screenshots, non-9:16 source content (小红书 / 抖音 style).
127
+ - `letterbox` image at 1080×1080 centered, top/bottom is a solid matte (CSS color). Generates a 1080×1080 image. Cinematic / calm. Customize the matte with `--layout-matte-color "#1a1a1a"` (default `black`).
128
+
129
+ Examples:
130
+ ```bash
131
+ rf create "财经日报" --layout blur-bg # 小红书 / 抖音
132
+ rf create "纪录片片段" --layout letterbox --motion max # 电影感
133
+ rf create "..." --layout letterbox --layout-matte-color "#1a1a1a" # 柔和黑
134
+ ```
135
+
136
+ ### Resources
137
+
138
+ | command | what it does |
139
+ |---|---|
140
+ | `bgm list / upload <file> / delete <name>` | Manage background music |
141
+ | `files list / upload <file> / download <path> / delete <path>` | Manage user assets |
142
+
143
+ ### System
144
+
145
+ | command | what it does |
146
+ |---|---|
147
+ | `config get` | Read server config (keys masked) |
148
+ | `config set <key> <value>` | Update a dotted-path setting (e.g. `llm.api_key sk-xxx`) |
149
+ | `config patch <file>` | Apply a JSON-merge patch |
150
+ | `tasks list [--status running]` | List recent tasks |
151
+ | `tasks get <id>` / `tasks wait <id>` / `tasks cancel <id>` | Task lifecycle |
152
+ | `history list / get <id> / delete <id>` | Browse / delete completed runs |
153
+ | `health` | Server health + capability check |
154
+
155
+ ### Heavy brand customization (custom overlay templates)
156
+
157
+ `--motion` / `--layout` / `--subtitle-*` / `--brand-*` cover the common cases. For full visual identity ownership (custom path bar, accent decorations, light theme, footer block, etc.) pass a custom overlay HTML via `--frame-template <local.html | preset_key>`:
158
+
159
+ ```bash
160
+ rf templates show 1080x1920/default.html -o ./my-brand.html
161
+ # ...edit my-brand.html (change colors, add structure, etc.)...
162
+ rf create "我的视频" --frame-template ./my-brand.html
163
+ ```
164
+
165
+ **Contract for custom HTML**:
166
+
167
+ - Canvas is **1080×1920** (pipeline-fixed; don't declare a different size in `<meta>`).
168
+ - Background must be **transparent** the AI image is composited by ffmpeg OUTSIDE the HTML layer.
169
+ - **`{{image}}` no longer exists.** Using `<img src="{{image}}">` is a hard error at submit time. Image is composited by ffmpeg.
170
+ - The pipeline injects these placeholders for you:
171
+
172
+ | Placeholder | What it is |
173
+ |---|---|
174
+ | `{{title}}` `{{text}}` | per-frame content |
175
+ | `{{index}}` `{{total}}` | "scene N of M" — `{{total}}` is the LLM-decided scene count, don't hardcode |
176
+ | `{{layout}}` | `"full"` / `"blur-bg"` / `"letterbox"` — react via `body[data-layout="..."]` CSS to put title/subtitle in the matte zones when layout ≠ full |
177
+ | `{{subtitle_style}}` `{{subtitle_color}}` `{{subtitle_background}}` | subtitle preset + overrides |
178
+ | `{{brand_position}}` `{{brand_handle}}` `{{brand_slogan}}` `{{brand_logo}}` `{{brand_color}}` | brand-chrome inputs (use or ignore as you like) |
179
+
180
+ Inline HTML is hard-capped at 2 MB. The audio + motion + character-ref + scene-plan stages all keep working identically — only the overlay layer is yours.
181
+
182
+ ## Examples
183
+
184
+ ```bash
185
+ # 1. One-click out a video (45s default, AI writes the script)
186
+ rf create "为什么我们还没找到外星文明?"
187
+
188
+ # 2. Longer video with a slower visual rhythm
189
+ rf create "深夜便利店的灯光" -d 90 -p slow
190
+
191
+ # 3. Your own script — no narration-splitting on your side, the pipeline handles it
192
+ rf create --script @./my-script.txt
193
+ rf create --script "雨水缓缓滑落在玻璃窗上,像是无声的泪珠。"
194
+
195
+ # 4. Pick a built-in visual style preset
196
+ rf create "美食教程" --style photorealistic
197
+
198
+ # 5. Pipeline form with explicit output path
199
+ rf pipelines standard \
200
+ --script @./script.txt \
201
+ --frame-template 1080x1920/image_default.html \
202
+ -p normal -o smoke.mp4
203
+
204
+ # 6. Inspect existing tasks & redownload a finished video
205
+ rf tasks list --limit 5
206
+ rf history get <task-id> --download recovered.mp4
207
+
208
+ # 7. Atomics for stand-alone use
209
+ rf content scene-plan -t "雨天的玻璃窗" -d 45 --json | jq .scenes
210
+ rf audio transcribe -f narration.mp3 --json | jq '.words[:5]'
211
+ rf subtitles split -t @./narration.txt --min 10 --hard-max 24
212
+
213
+ # 8. JSON pipe for automation
214
+ rf llm presets --json | jq '.[].defaultModel'
215
+
216
+ # 9. Configure & test LLM (self-hosted)
217
+ rf config set llm.api_key rx-xxxxx # RelayX key (or your own provider key)
218
+ rf config set llm.base_url https://relayx.timor419.com/v1
219
+ rf config set llm.model anthropic/claude-4-7-sonnet
220
+ rf llm chat -p 'one-sentence summary of antifragile'
221
+
222
+ # 10. Use your own HTML template (no PR/release needed)
223
+ # Any --frame-template that points to a local .html file is read and sent
224
+ # inline. Declare size inside the file via
225
+ # <meta name="template:width" content="1080">
226
+ # <meta name="template:height" content="1920">
227
+ # or pass --frame-template-size 1080x1920.
228
+ rf templates show 1080x1920/image_default.html -o my-brand.html # copy a preset
229
+ # ...edit my-brand.html to suit your style...
230
+ rf templates preview ./my-brand.html --title "Hello" -o preview.png
231
+ rf frames render -t ./my-brand.html --values '{"author":"Alice"}' -o frame.png
232
+ rf pipelines standard -t "宠物" --frame-template ./my-brand.html -o final.mp4
233
+ ```
234
+
235
+ ### Custom HTML templates
236
+
237
+ Easiest way to start: grab a preset as a reference.
238
+
239
+ ```bash
240
+ rf templates list # see all keys
241
+ rf templates show 1080x1920/static_default.html # print to stdout
242
+ rf templates show 1080x1920/image_default.html -o my-brand.html # save and edit
243
+ ```
244
+
245
+ `{{title}}`, `{{text}}`, `{{image}}`, `{{index}}`, `{{total}}` are reserved built-ins auto-injected by the pipeline; everything else uses the `{{name:type=default}}` DSL (`type` ∈ `text|number|color|bool`). Pass extras through `--values '{"author":"Alice"}'` (or `template_params` on the pipeline API).
246
+
247
+ - `{{index}}` — current scene number, 1-based
248
+ - `{{total}}` — scene count the LLM actually produced (use this for "scene N of M" badges; don't hardcode in `template_params`, the scene count is decided at runtime)
249
+
250
+ #### Template type — does the pipeline generate an AI image per scene?
251
+
252
+ When you ship an inline template through `rf create` / `rf pipelines standard`, ReelForge needs to know whether each scene should kick off RelayX image generation. Resolution priority (high → low):
253
+
254
+ 1. Explicit flag — `--frame-template-type image|static|asset` (or `frame_template_type` in the API body).
255
+ 2. Inside the HTML — `<meta name="template:type" content="image">` (or `static` / `asset`).
256
+ 3. **Default: `image`** — best practice for zero-config users. If your template doesn't reference scene imagery (pure-text card, etc.), declare `static` explicitly to skip image generation and its cost.
257
+
258
+ The placeholder `{{image}}` no longer doubles as a type signal — declare type explicitly.
259
+
260
+ Limits and safety:
261
+
262
+ - Max 2 MB per inline HTML.
263
+ - The render sandbox blocks `file://`, loopback / private / link-local IPs, CGNAT range, cloud-metadata, and `*.local` / `*.internal` hostnames. So your template can only reference public `https`/`http` resources or `data:` URIs.
264
+ - If the CLI is talking to a hosted server, local-path `--image` won't reach the server; either upload to `rf files upload` first or use an HTTPS URL / data: URI.
265
+
266
+ #### API field reference
267
+
268
+ | endpoint | inline HTML field | size field | type field |
269
+ |---|---|---|---|
270
+ | `POST /api/v1/frames/render` | `template_html` | `size` | — (n/a, no image generation) |
271
+ | `POST /api/v1/templates/preview` | `template_html` | `size` | — |
272
+ | `POST /api/v1/pipelines/standard` | `frame_template_inline` | `frame_template_size` | `frame_template_type` |
273
+
274
+ The pipeline endpoint uses the `frame_template_*` prefix because it already has a `frame_template` field (preset key). The single-frame endpoints use the shorter `template_html` because they don't.
275
+
276
+ ## Tip — getting unstuck
277
+
278
+ Every level has `--help`:
279
+
280
+ ```bash
281
+ rf --help # top-level overview
282
+ rf pipelines --help # list of pipelines
283
+ rf pipelines standard --help # full option reference
284
+ rf tts edge --help # one specific command
285
+ ```
286
+
287
+ ## License
288
+
289
+ Apache-2.0
@@ -45,6 +45,22 @@ async function resolveTextOrFile(input) {
45
45
  * Returns undefined when input is missing/blank so the caller can branch on
46
46
  * "user actually provided this knob".
47
47
  */
48
+ /**
49
+ * Tell apart a preset template key (e.g. "1080x1920/default.html") from a
50
+ * local file path. Heuristic: anything that starts with ./ ~ /, contains a
51
+ * backslash, or whose .html extension corresponds to an existing file on
52
+ * disk is treated as a local path. Otherwise it's a key for the server's
53
+ * preset registry. Mirrors the 0.7.x detection so muscle memory carries over.
54
+ */
55
+ function looksLikeLocalHtmlPath(value) {
56
+ if (/^[.~]|^\//.test(value))
57
+ return true;
58
+ if (value.includes("\\"))
59
+ return true;
60
+ if (value.endsWith(".html") && fsSync.existsSync(value))
61
+ return true;
62
+ return false;
63
+ }
48
64
  async function resolveRefImage(input, flagName) {
49
65
  if (input === undefined)
50
66
  return undefined;
@@ -163,8 +179,48 @@ function optsToBody(opts) {
163
179
  out.video_fps = opts.videoFps;
164
180
  if (opts.motion !== undefined)
165
181
  out.motion = opts.motion;
182
+ if (opts.layout !== undefined)
183
+ out.layout = opts.layout;
184
+ if (opts.layoutMatteColor !== undefined)
185
+ out.layout_matte_color = opts.layoutMatteColor;
186
+ if (opts.frameTemplate !== undefined) {
187
+ // Local .html path? Read inline and send as frame_template_html. Otherwise
188
+ // treat as a preset key. Same heuristic as 0.7.x had for --frame-template.
189
+ if (looksLikeLocalHtmlPath(opts.frameTemplate)) {
190
+ const abs = path.resolve(opts.frameTemplate);
191
+ if (!fsSync.existsSync(abs)) {
192
+ throw new Error(`--frame-template: local file not found: ${abs}`);
193
+ }
194
+ out.frame_template_html = fsSync.readFileSync(abs, "utf-8");
195
+ }
196
+ else {
197
+ out.frame_template = opts.frameTemplate;
198
+ }
199
+ }
200
+ if (opts.frameTemplateHtml !== undefined)
201
+ out.frame_template_html = opts.frameTemplateHtml;
166
202
  if (opts.subtitleStyle !== undefined)
167
203
  out.subtitle_style = opts.subtitleStyle;
204
+ if (opts.subtitleColor !== undefined)
205
+ out.subtitle_color = opts.subtitleColor;
206
+ if (opts.subtitleBackground !== undefined)
207
+ out.subtitle_background = opts.subtitleBackground;
208
+ // Brand: collect flat --brand-* flags into a nested brand object only if
209
+ // at least one is set. The pipeline-side field-level merge with
210
+ // config.video.default_brand will fill in unset fields.
211
+ const brand = {};
212
+ if (opts.brandHandle !== undefined)
213
+ brand.handle = opts.brandHandle;
214
+ if (opts.brandSlogan !== undefined)
215
+ brand.slogan = opts.brandSlogan;
216
+ if (opts.brandLogo !== undefined)
217
+ brand.logo_url = opts.brandLogo;
218
+ if (opts.brandPosition !== undefined)
219
+ brand.position = opts.brandPosition;
220
+ if (opts.brandColor !== undefined)
221
+ brand.color = opts.brandColor;
222
+ if (Object.keys(brand).length > 0)
223
+ out.brand = brand;
168
224
  if (opts.subtitleMinChars !== undefined)
169
225
  out.subtitle_min_chars = opts.subtitleMinChars;
170
226
  if (opts.subtitleHardMax !== undefined)
@@ -296,7 +352,18 @@ export function registerCreate(program) {
296
352
  .option("--style <preset>", "image style preset — shortcut for --prompt-prefix; see 'Style presets' below")
297
353
  .option("--character-ref <urlOrPath>", "reference image of the main character — locks identity across scenes. URL, data: URI, or local png/jpg/webp path (auto-encoded). Auto-enables rx-image-qwen-edit.")
298
354
  .option("--motion <preset>", "per-scene image animation intensity. See 'Motion presets' below. Default: lite.")
355
+ .option("--layout <preset>", "image layout within canvas: full (default) | blur-bg | letterbox. See 'Layout presets' below.")
356
+ .option("--layout-matte-color <css>", "letterbox matte color (CSS string, e.g. 'black', '#1a1a1a', '#2d3748'). Ignored unless --layout letterbox. Default: black.")
357
+ .option("--frame-template <keyOrPath>", "custom overlay template — preset key (e.g. 1080x1920/default.html) OR path to a local .html (auto-sent inline). See 'Custom templates' below.")
299
358
  .option("--subtitle-style <preset>", "subtitle visual style. See 'Subtitle styles' below. Default: plate.")
359
+ .option("--subtitle-color <css>", "override subtitle text color, e.g. '#ffeb3b'. Omit for preset default.")
360
+ .option("--subtitle-background <css>", "override plate-preset background, e.g. 'rgba(20,30,80,0.75)'. Other presets ignore.")
361
+ // --- Brand chrome (corner @handle / slogan / logo) ---
362
+ .option("--brand-handle <text>", "creator handle shown in corner, e.g. '@大灰狼'")
363
+ .option("--brand-slogan <text>", "creator slogan/tagline shown under handle")
364
+ .option("--brand-logo <urlOrPath>", "logo/avatar URL or local file path (PNG/JPG/WebP)")
365
+ .option("--brand-position <pos>", "where to render brand chrome: top-left | top-right | bottom-left | bottom-right")
366
+ .option("--brand-color <css>", "brand text color, e.g. '#ffffff' (default)")
300
367
  // --- Audio (TTS) ---
301
368
  .option("--voice-id <id>", "RelayX TTS voice id (default 专业解说); see `rf tts voices`")
302
369
  .option("--tts-speed <n>", "speech speed 0.5..2 (default 1.0)", parseFloat)
@@ -330,7 +397,7 @@ export function registerCreate(program) {
330
397
  " fast split long semantic chunks into multiple shots for variety",
331
398
  "",
332
399
  "Defaults:",
333
- " duration=45s · pace=normal · motion=lite · subtitle-style=plate · tts-speed=1.0",
400
+ " duration=45s · pace=normal · motion=lite · layout=full · subtitle-style=plate · tts-speed=1.0",
334
401
  "",
335
402
  "Motion presets (--motion <preset>) — per-scene image animation intensity:",
336
403
  " off no motion, hard cuts between scenes — PPT / slideshow mode",
@@ -339,10 +406,52 @@ export function registerCreate(program) {
339
406
  " · sub-animations are Fisher-Yates shuffled per task_id, so every video",
340
407
  " cycles a different order — no two videos feel identical.",
341
408
  "",
409
+ "Layout presets (--layout <preset>) — how the AI image sits in the 1080×1920 canvas:",
410
+ " full image fills the whole canvas (default; high-impact, attention-grabbing).",
411
+ " best for human portraits, landscapes, 9:16-native content.",
412
+ " blur-bg image at 1080×1080 centered, top/bottom is a gaussian-blurred copy",
413
+ " of the same image — moves in sync with the foreground (小红书/抖音 style).",
414
+ " best for charts, screenshots, infographics, non-9:16 source content.",
415
+ " letterbox image at 1080×1080 centered, top/bottom is a solid matte (cinematic).",
416
+ " use --layout-matte-color to change (default 'black'; try '#1a1a1a' for less harsh).",
417
+ " · The image generator is asked for the actual on-screen size (1080×1920 for full,",
418
+ " 1080×1080 for blur-bg/letterbox), so you don't pay for pixels that get cropped.",
419
+ "",
342
420
  "Subtitle styles (--subtitle-style <preset>):",
343
421
  " plate semi-transparent black plate + white text (CapCut default; safest readability)",
344
422
  " stroke bold white text with black stroke + shadow, no plate (抖音网红风)",
345
423
  " cinema bottom black gradient backdrop + lighter text (film / documentary look)",
424
+ " · use --subtitle-color / --subtitle-background to override the preset's colors",
425
+ " e.g. --subtitle-color '#ffeb3b' --subtitle-background 'rgba(20,30,80,0.75)'",
426
+ "",
427
+ "Brand chrome (--brand-* flags or config.video.default_brand):",
428
+ " Adds a constant @handle / slogan / logo block in a frame corner.",
429
+ " --brand-position bottom-right --brand-handle '@大灰狼' --brand-slogan '财经科普'",
430
+ " --brand-logo accepts URL / data: URI / local png/jpg/webp path.",
431
+ " All fields optional; missing handle/slogan/logo are individually hidden.",
432
+ " Per-request flags merge over config defaults field by field.",
433
+ "",
434
+ "Custom templates (--frame-template) — heavy brand customization:",
435
+ " Override the entire overlay HTML when --brand-* and --subtitle-color aren't",
436
+ " enough (custom top bar / footer / decoration / theme color etc.).",
437
+ " --frame-template ./my-overlay.html # local .html, auto sent inline",
438
+ " --frame-template 1080x1920/default.html # preset key (the built-in default)",
439
+ "",
440
+ " Contract for custom HTML:",
441
+ " · Canvas is 1080×1920 (pipeline-fixed; don't declare a different size meta).",
442
+ " · Background must be transparent — the AI image is composited by ffmpeg",
443
+ " OUTSIDE the HTML layer. {{image}} placeholder no longer exists; using",
444
+ " <img src=\"{{image}}\"> in your template is a hard error at submit time.",
445
+ " · Inject points the pipeline writes for you:",
446
+ " {{title}} {{text}} per-frame content",
447
+ " {{index}} {{total}} \"scene N of M\"",
448
+ " {{layout}} \"full\" | \"blur-bg\" | \"letterbox\" — react via",
449
+ " body[data-layout=\"...\"] CSS to move title /",
450
+ " subtitle to the matte zones when layout != full.",
451
+ " {{subtitle_style}} / {{subtitle_color}} / {{subtitle_background}}",
452
+ " {{brand_*}} handle / slogan / logo / position / color",
453
+ " · Bootstrap from the built-in: rf templates show 1080x1920/default.html -o my-overlay.html",
454
+ " · Inline HTML hard-capped at 2 MB.",
346
455
  "",
347
456
  "Image style presets (--style <preset>) — quick shortcut for --prompt-prefix:",
348
457
  formatStylePresetsList(),
@@ -378,6 +487,16 @@ export function registerCreate(program) {
378
487
  ' rf create "..." --motion lite --subtitle-style cinema # 文艺纪录片',
379
488
  ' rf create "..." --motion off # PPT 模式(旧行为)',
380
489
  "",
490
+ " # Layout combos",
491
+ ' rf create "财经日报" --layout blur-bg # 小红书 / 抖音 风(图表内容首选)',
492
+ ' rf create "纪录片片段" --layout letterbox --motion max # 电影感',
493
+ ' rf create "..." --layout letterbox --layout-matte-color "#1a1a1a" # 柔和黑',
494
+ "",
495
+ " # Custom overlay HTML (heavy brand customization)",
496
+ " rf templates show 1080x1920/default.html -o ./brand.html # copy as starting point",
497
+ " # ...edit brand.html: change colors, add top path bar / footer, etc.",
498
+ ' rf create "..." --frame-template ./brand.html # apply your custom overlay',
499
+ "",
381
500
  " # Recipe + replay last",
382
501
  " rf create --recipe ./space.recipe.json",
383
502
  " rf create --redo # replay last successful create",
@@ -409,9 +528,15 @@ export function registerCreate(program) {
409
528
  if (opts.motion && !["off", "lite", "max"].includes(opts.motion)) {
410
529
  throw new Error(`--motion must be one of off|lite|max (got: ${opts.motion})`);
411
530
  }
531
+ if (opts.layout && !["full", "blur-bg", "letterbox"].includes(opts.layout)) {
532
+ throw new Error(`--layout must be one of full|blur-bg|letterbox (got: ${opts.layout})`);
533
+ }
412
534
  if (opts.subtitleStyle && !["plate", "stroke", "cinema"].includes(opts.subtitleStyle)) {
413
535
  throw new Error(`--subtitle-style must be one of plate|stroke|cinema (got: ${opts.subtitleStyle})`);
414
536
  }
537
+ if (opts.brandPosition && !["top-left", "top-right", "bottom-left", "bottom-right"].includes(opts.brandPosition)) {
538
+ throw new Error(`--brand-position must be one of top-left|top-right|bottom-left|bottom-right (got: ${opts.brandPosition})`);
539
+ }
415
540
  // 1. Layer defaults: --redo → --recipe → CLI opts → positional topic
416
541
  let body = {};
417
542
  if (opts.redo) {
@@ -452,6 +577,14 @@ export function registerCreate(program) {
452
577
  body.character_ref = resolvedChar;
453
578
  else
454
579
  delete body.character_ref;
580
+ // Brand logo: same URL / data: / local-file rules as character ref.
581
+ if (body.brand?.logo_url) {
582
+ const resolvedLogo = await resolveRefImage(body.brand.logo_url, "--brand-logo");
583
+ if (resolvedLogo !== undefined)
584
+ body.brand.logo_url = resolvedLogo;
585
+ else
586
+ delete body.brand.logo_url;
587
+ }
455
588
  // Validate content mode
456
589
  const hasTopic = typeof body.topic === "string" && body.topic.trim().length > 0;
457
590
  const hasScript = typeof body.script === "string" && body.script.trim().length > 0;
@@ -1,4 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
+ import fsSync from "node:fs";
3
+ import path from "node:path";
2
4
  import { post } from "../client.js";
3
5
  import { waitForTask } from "../utils/task-waiter.js";
4
6
  import { downloadTo } from "../utils/download.js";
@@ -46,6 +48,9 @@ export function registerPipelines(program) {
46
48
  .option("-d, --duration <sec>", "target video duration in seconds (generate mode; default 45)", (v) => parseInt(v, 10))
47
49
  .option("-p, --pace <pace>", "visual rhythm hint: slow | normal | fast (default normal)")
48
50
  .option("--motion <preset>", "per-scene image animation: off | lite (default) | max")
51
+ .option("--layout <preset>", "image layout: full (default) | blur-bg | letterbox. See below.")
52
+ .option("--layout-matte-color <css>", "letterbox matte color (CSS). Ignored unless --layout letterbox. Default: black.")
53
+ .option("--frame-template <keyOrPath>", "custom overlay: preset key OR local .html (auto sent inline). Canvas 1080×1920, transparent bg, no {{image}}.")
49
54
  .option("--subtitle-style <preset>", "subtitle visual style: plate (default) | stroke | cinema")
50
55
  .option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen | rx-image-qwen-edit)")
51
56
  .option("--prompt-prefix <text>", "style prefix prepended to every image prompt")
@@ -65,10 +70,19 @@ export function registerPipelines(program) {
65
70
  "Motion (per-scene animation): off | lite | max",
66
71
  "Subtitle style: plate | stroke | cinema",
67
72
  "",
73
+ "Layout — how the AI image sits in the 1080×1920 canvas:",
74
+ " full image fills the whole canvas (default; punchy, 9:16-native content).",
75
+ " blur-bg image at 1080×1080 centered + same image scaled/blurred as background",
76
+ " (小红书 / 抖音 style; best for charts, screenshots, non-9:16 source).",
77
+ " letterbox image at 1080×1080 centered + solid matte top/bottom (cinematic).",
78
+ " tweak with --layout-matte-color (default 'black').",
79
+ " · Image is generated at the actual on-screen size, so you don't pay for cropped pixels.",
80
+ "",
68
81
  "Examples:",
69
82
  " rf pipelines standard -t 'why we explore space' -d 60 -o space.mp4",
70
83
  " rf pipelines standard --script @script.txt -p slow --motion max -o out.mp4",
71
- " rf pipelines standard -t '宠物' --motion lite --subtitle-style cinema -o final.mp4",
84
+ " rf pipelines standard -t '财经日报' --layout blur-bg --subtitle-style plate -o out.mp4",
85
+ " rf pipelines standard -t '纪录片' --layout letterbox --motion max -o film.mp4",
72
86
  "",
73
87
  "Tip: `rf create` is a more ergonomic wrapper around the same endpoint.",
74
88
  ].join("\n"))).action(async (opts) => {
@@ -86,6 +100,9 @@ export function registerPipelines(program) {
86
100
  if (opts.motion && !["off", "lite", "max"].includes(opts.motion)) {
87
101
  throw new Error(`--motion must be one of off|lite|max (got: ${opts.motion})`);
88
102
  }
103
+ if (opts.layout && !["full", "blur-bg", "letterbox"].includes(opts.layout)) {
104
+ throw new Error(`--layout must be one of full|blur-bg|letterbox (got: ${opts.layout})`);
105
+ }
89
106
  if (opts.subtitleStyle && !["plate", "stroke", "cinema"].includes(opts.subtitleStyle)) {
90
107
  throw new Error(`--subtitle-style must be one of plate|stroke|cinema (got: ${opts.subtitleStyle})`);
91
108
  }
@@ -95,12 +112,35 @@ export function registerPipelines(program) {
95
112
  topic = await fs.readFile(topic.slice(1), "utf-8");
96
113
  if (script?.startsWith("@"))
97
114
  script = await fs.readFile(script.slice(1), "utf-8");
115
+ // --frame-template can be a preset key OR a local .html — same heuristic
116
+ // as 0.7.x. Local path is read and sent as frame_template_html inline.
117
+ let frame_template;
118
+ let frame_template_html;
119
+ if (opts.frameTemplate) {
120
+ const v = opts.frameTemplate;
121
+ const isLocal = /^[.~]|^\//.test(v) || v.includes("\\") ||
122
+ (v.endsWith(".html") && fsSync.existsSync(v));
123
+ if (isLocal) {
124
+ const abs = path.resolve(v);
125
+ if (!fsSync.existsSync(abs)) {
126
+ throw new Error(`--frame-template: local file not found: ${abs}`);
127
+ }
128
+ frame_template_html = await fs.readFile(abs, "utf-8");
129
+ }
130
+ else {
131
+ frame_template = v;
132
+ }
133
+ }
98
134
  await submitAndMaybeWait("/api/v1/pipelines/standard", {
99
135
  topic,
100
136
  script,
101
137
  duration: opts.duration,
102
138
  pace: opts.pace,
103
139
  motion: opts.motion,
140
+ layout: opts.layout,
141
+ layout_matte_color: opts.layoutMatteColor,
142
+ frame_template,
143
+ frame_template_html,
104
144
  subtitle_style: opts.subtitleStyle,
105
145
  image_model: opts.imageModel,
106
146
  prompt_prefix: opts.promptPrefix,
@@ -70,14 +70,15 @@ export function registerTemplates(program) {
70
70
  .addHelpText("after", [
71
71
  "",
72
72
  "Examples:",
73
- " # see what the default static template looks like",
74
- " rf templates show 1080x1920/static_default.html",
73
+ " # print the standard pipeline's default overlay HTML",
74
+ " rf templates show 1080x1920/default.html",
75
75
  "",
76
- " # copy a preset and tweak it as your own brand template",
77
- " rf templates show 1080x1920/image_default.html -o my-brand.html",
78
- " # ...edit my-brand.html...",
79
- " rf frames render -t ./my-brand.html --title 'X' -o frame.png",
76
+ " # copy as a starting point for a custom brand template",
77
+ " rf templates show 1080x1920/default.html -o my-brand.html",
78
+ " # ...edit my-brand.html (change colors, add path bar / footer, etc.)...",
79
+ " rf create '...' --frame-template ./my-brand.html",
80
80
  "",
81
+ " Contract for custom HTML: see `rf create --help` → 'Custom templates'.",
81
82
  " Get the list of keys via `rf templates list`.",
82
83
  ].join("\n"))
83
84
  .action(async (key, opts) => {
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "reelforge",
3
- "version": "0.8.0",
4
- "description": "CLI for ReelForge Studio — AI video engine. Installs as both `reelforge` and the short alias `rf`. Every REST API exposed as a command, with --help on every level.",
5
- "license": "Apache-2.0",
6
- "type": "module",
7
- "bin": {
8
- "reelforge": "./bin/reelforge.js",
9
- "rf": "./bin/reelforge.js"
10
- },
11
- "files": [
12
- "bin",
13
- "dist",
14
- "README.md"
15
- ],
16
- "engines": {
17
- "node": ">=18.17"
18
- },
19
- "scripts": {
20
- "build": "tsc -p tsconfig.json",
21
- "dev": "tsc -p tsconfig.json --watch",
22
- "typecheck": "tsc -p tsconfig.json --noEmit",
23
- "clean": "rimraf dist",
24
- "prepublishOnly": "npm run clean && npm run build"
25
- },
26
- "dependencies": {
27
- "commander": "^12.1.0",
28
- "kleur": "^4.1.5"
29
- },
30
- "devDependencies": {
31
- "@types/node": "^20.14.0",
32
- "rimraf": "^6.0.1",
33
- "typescript": "^5.5.0"
34
- },
35
- "keywords": [
36
- "reelforge",
37
- "ai-video",
38
- "relayx",
39
- "tts",
40
- "edge-tts",
41
- "ffmpeg",
42
- "playwright",
43
- "cli"
44
- ],
45
- "repository": {
46
- "type": "git",
47
- "url": "https://github.com/puke3615/ReelForge.git",
48
- "directory": "cli"
49
- },
50
- "homepage": "https://github.com/puke3615/ReelForge"
51
- }
1
+ {
2
+ "name": "reelforge",
3
+ "version": "0.10.0",
4
+ "description": "CLI for ReelForge Studio — AI video engine. Installs as both `reelforge` and the short alias `rf`. Every REST API exposed as a command, with --help on every level.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "reelforge": "./bin/reelforge.js",
9
+ "rf": "./bin/reelforge.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18.17"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "dev": "tsc -p tsconfig.json --watch",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "clean": "rimraf dist",
24
+ "prepublishOnly": "npm run clean && npm run build"
25
+ },
26
+ "dependencies": {
27
+ "commander": "^12.1.0",
28
+ "kleur": "^4.1.5"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.14.0",
32
+ "rimraf": "^6.0.1",
33
+ "typescript": "^5.5.0"
34
+ },
35
+ "keywords": [
36
+ "reelforge",
37
+ "ai-video",
38
+ "relayx",
39
+ "tts",
40
+ "edge-tts",
41
+ "ffmpeg",
42
+ "playwright",
43
+ "cli"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/puke3615/ReelForge.git",
48
+ "directory": "cli"
49
+ },
50
+ "homepage": "https://github.com/puke3615/ReelForge"
51
+ }