reelkit-cli 0.1.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/LICENSE +21 -0
- package/README.md +91 -0
- package/bin/reelkit.mjs +5 -0
- package/package.json +89 -0
- package/skill/SKILL.md +101 -0
- package/skill/THIRD_PARTY.md +20 -0
- package/skill/command.md +5 -0
- package/skill/reference/asset-reuse.md +31 -0
- package/skill/reference/captions.md +57 -0
- package/skill/reference/component-authoring.md +26 -0
- package/skill/reference/kit.md +135 -0
- package/skill/reference/motion-design.md +62 -0
- package/skill/reference/remotion-composition.md +58 -0
- package/skill/reference/scene-treatments.md +35 -0
- package/skill/reference/scriptwriting.md +44 -0
- package/skill/reference/sound-design.md +43 -0
- package/src/agents.ts +103 -0
- package/src/api/client.ts +58 -0
- package/src/cli.ts +91 -0
- package/src/commands/assets.ts +196 -0
- package/src/commands/auth.ts +118 -0
- package/src/commands/build.ts +109 -0
- package/src/commands/init.ts +32 -0
- package/src/commands/install.ts +24 -0
- package/src/commands/plan.ts +45 -0
- package/src/context.ts +21 -0
- package/src/contract/index.ts +157 -0
- package/src/credentials.ts +55 -0
- package/src/open.ts +48 -0
- package/src/pipeline/review.ts +30 -0
- package/src/pipeline/schema.ts +110 -0
- package/src/pipeline/timing.ts +30 -0
- package/src/project/manifest.ts +71 -0
- package/src/project/probe.ts +54 -0
- package/src/project/project.ts +58 -0
- package/src/project/serve.ts +81 -0
- package/src/remotion/Root.tsx +29 -0
- package/src/remotion/kit/Captions.tsx +77 -0
- package/src/remotion/kit/Counter.tsx +18 -0
- package/src/remotion/kit/Entrance.tsx +53 -0
- package/src/remotion/kit/FootageLayer.tsx +10 -0
- package/src/remotion/kit/Icon.tsx +35 -0
- package/src/remotion/kit/KenBurnsImage.tsx +18 -0
- package/src/remotion/kit/Layers.tsx +46 -0
- package/src/remotion/kit/LowerThird.tsx +17 -0
- package/src/remotion/kit/SceneFrame.tsx +19 -0
- package/src/remotion/kit/ScreenOverlay.tsx +15 -0
- package/src/remotion/kit/Sfx.tsx +11 -0
- package/src/remotion/kit/TitleCard.tsx +23 -0
- package/src/remotion/kit/Voiceover.tsx +5 -0
- package/src/remotion/kit/brand-icons.ts +37 -0
- package/src/remotion/kit/docs.ts +98 -0
- package/src/remotion/kit/index.ts +20 -0
- package/src/remotion/kit/media.ts +10 -0
- package/src/remotion/kit/theme.ts +131 -0
- package/src/remotion/types.ts +2 -0
- package/src/render/component-preview.ts +65 -0
- package/src/render/deps.ts +8 -0
- package/src/render/render.ts +67 -0
- package/src/render/validate.ts +191 -0
- package/src/testing/conformance.ts +396 -0
- package/src/testing/fake-api.ts +228 -0
- package/src/testing/fixtures.ts +44 -0
- package/src/ui/CommandView.tsx +18 -0
- package/src/ui/run.tsx +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Livshin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Reelkit
|
|
2
|
+
|
|
3
|
+
A CLI and a Claude skill for making short-form video. Claude plans the video and writes the motion design; `reelkit` searches a shared asset library, records voiceover, generates what is missing, checks the composition and renders it on your machine with Remotion.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node 20 or newer
|
|
8
|
+
- ffmpeg
|
|
9
|
+
- A Reelkit account (accounts open when the service launches)
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g reelkit-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This puts the `reelkit` command on your path. Then put the skill, and a `/reelkit-video` command where the agent supports one, into your coding agents (Claude Code, Codex, Cursor, Gemini CLI and the shared `.agents` folder):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
reelkit install
|
|
21
|
+
reelkit auth login
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
It installs for every agent it finds on your machine. Name one with `--agent claude`, or use `--agent all`. `reelkit init` and `reelkit auth login` keep the installed skill up to date on their own; set `REELKIT_NO_AUTO_INSTALL=1` to turn that off.
|
|
25
|
+
|
|
26
|
+
## Use
|
|
27
|
+
|
|
28
|
+
Ask Claude to make a video. The skill takes it from there. To drive it by hand:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
reelkit auth login # a person: opens the login page in your browser and waits while you approve it
|
|
32
|
+
reelkit init my-video --aspect 9:16
|
|
33
|
+
cd my-video
|
|
34
|
+
reelkit assets upload ./logo.png --describe "Acme logo"
|
|
35
|
+
# write plan.json
|
|
36
|
+
reelkit plan check
|
|
37
|
+
reelkit assets voiceover --all
|
|
38
|
+
reelkit assets search "city skyline at night" --kind image
|
|
39
|
+
reelkit assets gen image --scene intro
|
|
40
|
+
# write src/Video.tsx
|
|
41
|
+
reelkit check
|
|
42
|
+
reelkit preview
|
|
43
|
+
reelkit render
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`reelkit auth login` prints the login link and the code, and opens the link in your browser when you run it in a terminal. It does not open a browser with `--no-browser`, with `REELKIT_NO_BROWSER=1`, with `--json`, or when its output is not a terminal; it also does not open a login page that is on a different site than the API (it says so and leaves the link for you to check). The link is printed either way. Logging in from an agent that cannot wait: `reelkit auth login --start` prints the link (the code is already filled in) and returns at once without opening a browser; after you approve it in the browser, `reelkit auth login --finish` completes the login (it waits up to a minute, and can be run again if you were slow).
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `reelkit auth login` / `logout` | Log in or out. `login` opens the login page in your browser (`--no-browser` to only print the link) and waits while you approve it; an agent uses `login --start`, shows you the link, then `login --finish` |
|
|
53
|
+
| `reelkit whoami` | Account, quota, contributions |
|
|
54
|
+
| `reelkit install` | Install the skill into your coding agents (`--agent <id>`, `--force`) |
|
|
55
|
+
| `reelkit init [name]` | Set up a video project, in a new folder named after it |
|
|
56
|
+
| `reelkit assets upload <file>` | Add one of your own files (private unless `--share`) |
|
|
57
|
+
| `reelkit assets search "<query>"` | Search the shared library by meaning; each result shows a match percentage |
|
|
58
|
+
| `reelkit assets pull <id>` | Download a library item (a component lands in `src/`) |
|
|
59
|
+
| `reelkit assets voices` | List narration voices |
|
|
60
|
+
| `reelkit assets voiceover` | Record narration from `plan.json` |
|
|
61
|
+
| `reelkit assets gen image` | Generate a scene's illustration |
|
|
62
|
+
| `reelkit plan check` | Validate `plan.json` |
|
|
63
|
+
| `reelkit check` | Check the composition without rendering |
|
|
64
|
+
| `reelkit preview` | One test frame per scene |
|
|
65
|
+
| `reelkit render` | Render `out/video.mp4` |
|
|
66
|
+
|
|
67
|
+
## What is shared
|
|
68
|
+
|
|
69
|
+
Your own files, your plan and your video stay on your machine. Illustrations that the plan marks as generic, and anything you add with `--share`, go to the shared library for review, where other people can reuse them.
|
|
70
|
+
|
|
71
|
+
A component you pull from the library is code, and it runs on your machine when you preview or render. The library only serves components published by Reelkit.
|
|
72
|
+
|
|
73
|
+
The CLI talks to `https://reelkit-kohl.vercel.app/api/v1` by default. Set `REELKIT_API_URL` to point it at a different API. To use a local API while developing, set `REELKIT_API_URL=http://localhost:3000/api/v1`.
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
To run from source instead of the npm package:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone https://github.com/Livshind15/reelkit.git
|
|
81
|
+
cd reelkit
|
|
82
|
+
pnpm install
|
|
83
|
+
node bin/reelkit.mjs --help # or `npm link` to get the `reelkit` command
|
|
84
|
+
pnpm test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Tests run against an in-process fake of the API (`src/testing/fake-api.ts`), so they need no account and no network.
|
|
88
|
+
|
|
89
|
+
## Licence
|
|
90
|
+
|
|
91
|
+
MIT. Parts of the skill and starter kit are adapted from other open-source work; see `skill/THIRD_PARTY.md`.
|
package/bin/reelkit.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reelkit-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI and Claude skill for making short-form video with a shared asset library.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Daniel Livshin",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Livshind15/reelkit.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Livshind15/reelkit#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Livshind15/reelkit/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"video",
|
|
17
|
+
"short-form",
|
|
18
|
+
"reels",
|
|
19
|
+
"tiktok",
|
|
20
|
+
"remotion",
|
|
21
|
+
"cli",
|
|
22
|
+
"ai-agents",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"skill",
|
|
25
|
+
"voiceover"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"bin": {
|
|
29
|
+
"reelkit": "./bin/reelkit.mjs"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/contract/index.ts",
|
|
33
|
+
"./kit": "./src/remotion/kit/index.ts",
|
|
34
|
+
"./root": "./src/remotion/Root.tsx",
|
|
35
|
+
"./validate": "./src/render/validate.ts",
|
|
36
|
+
"./component-preview": "./src/render/component-preview.ts",
|
|
37
|
+
"./testing": "./src/testing/fake-api.ts",
|
|
38
|
+
"./schema": "./src/pipeline/schema.ts",
|
|
39
|
+
"./client": "./src/api/client.ts",
|
|
40
|
+
"./context": "./src/context.ts",
|
|
41
|
+
"./commands/*": "./src/commands/*.ts",
|
|
42
|
+
"./testing/conformance": "./src/testing/conformance.ts"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"bin",
|
|
46
|
+
"src",
|
|
47
|
+
"skill"
|
|
48
|
+
],
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"reelkit": "node bin/reelkit.mjs",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"typecheck": "tsc --noEmit"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@remotion/bundler": "^4.0.532",
|
|
62
|
+
"@remotion/google-fonts": "4.0.532",
|
|
63
|
+
"@remotion/renderer": "^4.0.532",
|
|
64
|
+
"@types/react": "^19.3.0",
|
|
65
|
+
"@types/react-dom": "^19.3.0",
|
|
66
|
+
"commander": "^15.0.0",
|
|
67
|
+
"ink": "^8.0.0",
|
|
68
|
+
"ink-spinner": "^5.0.0",
|
|
69
|
+
"react": "^19.3.0",
|
|
70
|
+
"react-dom": "^19.3.0",
|
|
71
|
+
"remotion": "^4.0.532",
|
|
72
|
+
"tsx": "^4.23.15",
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"zod": "^4.6.5"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@types/node": "^26.6.4",
|
|
78
|
+
"ink-testing-library": "^4.0.0",
|
|
79
|
+
"vitest": "^5.0.3"
|
|
80
|
+
},
|
|
81
|
+
"peerDependencies": {
|
|
82
|
+
"vitest": ">=5"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"vitest": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reelkit
|
|
3
|
+
description: Produce short-form and explainer videos from an idea plus the user's own screenshots, logo, clips or footage. Searches a shared asset library first, generates only what is missing, and renders locally with Remotion. Use when the user wants to create, edit or assemble a video, turn an app or idea into a demo, or make a promo.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reelkit
|
|
7
|
+
|
|
8
|
+
You make the video. The `reelkit` CLI gives you the tools: it holds no model and makes no creative decisions. You write the plan and the motion code yourself, check them with the CLI, and show the user your work at two checkpoints before anything is rendered.
|
|
9
|
+
|
|
10
|
+
Every command takes `--json` for machine-readable output and exits non-zero with a one-line reason when something is wrong. Read the reason and act on it.
|
|
11
|
+
|
|
12
|
+
## Setup (once per machine, then once per video)
|
|
13
|
+
|
|
14
|
+
1. `reelkit whoami`. If it says you are not logged in, run `reelkit auth login --start` (it returns at once and never opens a browser; plain `reelkit auth login` would wait for a person and block you). Show the user the link it prints (the code is already in it) and wait for them to say they have approved it, then run `reelkit auth login --finish`. If that says it is not approved yet, ask the user again and re-run it. (Plain `reelkit auth login` opens a browser on the user's machine when run in a terminal; `--no-browser` stops that. Still prefer `--start`.)
|
|
15
|
+
2. Run `reelkit init <project-name> --aspect 9:16` (`16:9` or `1:1` for other shapes), then work in the folder it prints.
|
|
16
|
+
|
|
17
|
+
The folder holds everything: `plan.json`, `assets/`, `src/` (your composition and any components you pull), `out/`.
|
|
18
|
+
|
|
19
|
+
## The loop
|
|
20
|
+
|
|
21
|
+
### 1. Inputs
|
|
22
|
+
Find out what the video is about and collect what the user has. Ask for nothing you can infer.
|
|
23
|
+
|
|
24
|
+
For each file the user gives, look at it, then register it with a description of what it shows:
|
|
25
|
+
`reelkit assets upload ./logo.png --describe "Acme logo, white wordmark on blue"`
|
|
26
|
+
Add `--footage` for a video the motion design should be laid over. User files stay on this machine.
|
|
27
|
+
|
|
28
|
+
### 2. Plan
|
|
29
|
+
Read `reference/scriptwriting.md` and `reference/scene-treatments.md`. Run `reelkit assets voices` and choose a voice that fits the idea, audience and language.
|
|
30
|
+
|
|
31
|
+
Write `plan.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"title": "string",
|
|
36
|
+
"aspect": "9:16",
|
|
37
|
+
"mode": "motion",
|
|
38
|
+
"voiceId": "an id from reelkit assets voices",
|
|
39
|
+
"pace": "normal",
|
|
40
|
+
"scenes": [
|
|
41
|
+
{
|
|
42
|
+
"id": "hook",
|
|
43
|
+
"narration": "the words spoken in this scene",
|
|
44
|
+
"treatment": "motion-graphic",
|
|
45
|
+
"onScreenText": ["up to three short items"],
|
|
46
|
+
"imagePrompt": null,
|
|
47
|
+
"shareable": false,
|
|
48
|
+
"imageTags": [],
|
|
49
|
+
"userAssetIds": [],
|
|
50
|
+
"notes": "visual direction for yourself when you write the code"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- 3 to 8 scenes. Scene ids are short, unique, lowercase with dashes.
|
|
57
|
+
- `treatment` is `motion-graphic`, `illustration` or `footage-overlay`. With footage, `mode` is `"footage"` and every scene is `footage-overlay`; otherwise `mode` is `"motion"` and no scene is.
|
|
58
|
+
- `imagePrompt` is set only for `illustration` scenes, with 3 to 6 `imageTags`. Set `shareable` to true only when the prompt is fully generic: no brand, product, person or detail specific to this user.
|
|
59
|
+
- `userAssetIds` lists the ids of the user's files shown in that scene.
|
|
60
|
+
- `pace` is `slow`, `normal` or `fast`.
|
|
61
|
+
|
|
62
|
+
Run `reelkit plan check`. Fix everything under "Fix these". Act on "Worth improving" unless you have a good reason not to.
|
|
63
|
+
|
|
64
|
+
**Checkpoint.** Show the user the title, the estimated length, and each scene's narration, on-screen text and one line on the visual. Wait for a clear yes. A question or a comment is not approval: answer it and ask again.
|
|
65
|
+
|
|
66
|
+
### 3. Voice
|
|
67
|
+
`reelkit assets voiceover --all`. It records each scene and prints the real length of the video. If the user wants a different voice, change `voiceId` in `plan.json` and run it again with `--redo`.
|
|
68
|
+
|
|
69
|
+
### 4. Images
|
|
70
|
+
Read `reference/asset-reuse.md`. For each illustration scene, search first:
|
|
71
|
+
`reelkit assets search "<what the scene needs>" --kind image`
|
|
72
|
+
Each result starts with a match percentage: how likely it is good enough to reuse. Pull the best result (`reelkit assets pull <id> --scene <sceneId>`) when its match is 60% or more and, reading its description, it fits the scene. Otherwise generate: `reelkit assets gen image --scene <sceneId>`. Never give two scenes the same image.
|
|
73
|
+
|
|
74
|
+
### 5. Composition
|
|
75
|
+
Read `reference/kit.md`, `reference/remotion-composition.md`, `reference/motion-design.md` and `reference/captions.md`.
|
|
76
|
+
|
|
77
|
+
- Search the library before writing a component: `reelkit assets search "<what it shows>" --kind component`. To use one, `reelkit assets pull <id>`: it lands in `src/` and the command prints the import line and an example.
|
|
78
|
+
- Read `reference/sound-design.md`, then find the few sounds the video needs: `reelkit assets search "whoosh" --kind sfx`, `reelkit assets pull <id>`. The pull prints how to reference the file.
|
|
79
|
+
- Before writing a new component, read `reference/component-authoring.md`.
|
|
80
|
+
- Write `src/Video.tsx` and any component files beside it. `Video.tsx` may import only `react`, `remotion`, `reelkit/kit` and sibling components (`./Name`).
|
|
81
|
+
- `manifest.json` is the exact object passed to `Video` as the `manifest` prop. Media is referenced as `urls[path]`, where `path` is the file's path in the project, such as `urls[scene.voiceoverKey]` or `urls["assets/lib/<id>/clip.mp3"]`.
|
|
82
|
+
|
|
83
|
+
Run `reelkit check` and fix every error until it passes. Then `reelkit preview` and look at each frame in `out/preview/`. Look for: text cut off or overflowing, text overlapping other text or the captions, text too small or too low-contrast for a phone, an empty or broken frame, content hidden behind another layer, and any number, price or quote on screen that is not in the plan. A frame is one moment: an element mid-animation is not a problem. Fix real problems and preview again.
|
|
84
|
+
|
|
85
|
+
**Checkpoint.** Show the user the preview frames. Wait for a clear yes. For changes, edit the code, `reelkit check`, `reelkit preview`, and show them again.
|
|
86
|
+
|
|
87
|
+
### 6. Render
|
|
88
|
+
`reelkit render`. Give the user the path it prints.
|
|
89
|
+
|
|
90
|
+
If the render fails, read the error, fix the composition, and run `reelkit check` before rendering again.
|
|
91
|
+
|
|
92
|
+
## Rules
|
|
93
|
+
|
|
94
|
+
- Search before generating. Reuse beats regenerate.
|
|
95
|
+
- Never generate app UI. Use the user's real screenshots.
|
|
96
|
+
- The user's own files are private. Do not pass `--share` unless they ask you to contribute a file.
|
|
97
|
+
- Pass the user's facts through unchanged. Never invent a number, statistic, price or quote.
|
|
98
|
+
- Keep components driven by props, not hardcoded, so they can be reused.
|
|
99
|
+
- One stage at a time. Do not render before the user has approved the preview.
|
|
100
|
+
- If a command reports that a quota is used up, tell the user what ran out and when it resets. Do not work around it. A message that says to wait a minute or an hour ("Too many searches", "Too many uploads started") is a throttle, not a used-up quota: wait and retry once instead of stopping.
|
|
101
|
+
- Reply in the user's language.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Third-party sources
|
|
2
|
+
|
|
3
|
+
Some guidance in these skills, and some starter-kit components, are adapted from three open-source motion-design skills. The first two target a different renderer (HTML pages captured with Playwright), so their engines, scripts and workflows are not used here; only renderer-independent design principles were taken and rewritten for Remotion.
|
|
4
|
+
|
|
5
|
+
- **Barty-Bart/motion-graphics** (`skills/motion-broll`), MIT License, Copyright (c) 2026 Bart.
|
|
6
|
+
https://github.com/Barty-Bart/motion-graphics
|
|
7
|
+
Adapted: pacing changes to spoken words, one idea per clip, never inventing data, and the list of banned effects.
|
|
8
|
+
|
|
9
|
+
- **howseen-ai/claude-motion-design** (`skill/motion-design`), MIT License.
|
|
10
|
+
https://github.com/howseen-ai/claude-motion-design
|
|
11
|
+
Adapted: spring presets, one accent colour, one thing moving at a time, no frozen frames, masked text reveals, determinism, truthfulness rules, and the idea of checking frames before calling a video done.
|
|
12
|
+
|
|
13
|
+
- **haidrrrry/claude-remotion-skill** (`remotion-motion-graphics`), MIT License.
|
|
14
|
+
https://github.com/haidrrrry/claude-remotion-skill
|
|
15
|
+
This one targets Remotion, so more carried over. Adapted into the starter kit (`src/remotion/kit`): the layer stack (background mesh, grade, grain, vignette), the combined entrance, the word-by-word reveal, Ken Burns with pan, spring and easing presets, and the starting palettes. Adapted into the skills: the animation rules, hero-colour rule, hit-hold-build rhythm, safe zones and common layout bugs. Its setup, render and sound-effect steps are not used; this project has its own pipeline.
|
|
16
|
+
|
|
17
|
+
- **A "CapCut-style animated captions" skill** supplied by the project owner as pasted text, source repository not stated.
|
|
18
|
+
It burns captions in with FFmpeg subtitle files, which this project does not do. Adapted into the kit's `Captions` component (the pop, highlight and karaoke modes) and the `captions` skill (words per screen, timing, safe zones, contrast, shake and flash limits). Its engagement statistics were left out because they are unsourced.
|
|
19
|
+
|
|
20
|
+
Used in: `motion-design`, `remotion-composition`, `scriptwriting`, `scene-treatments`, the starter kit, and the preview checklist in `SKILL.md`.
|
package/skill/command.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: asset-reuse
|
|
3
|
+
description: Use when deciding whether to reuse a shared library image or generate a new one, and whether a new image may be shared.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reusing and generating images
|
|
7
|
+
|
|
8
|
+
## Order of work
|
|
9
|
+
For each scene that needs an image:
|
|
10
|
+
1. `reelkit assets search "<description>" --kind image` with the key subject and style words. Try a second, broader query if the first returns nothing.
|
|
11
|
+
2. Each result starts with a match percentage, the chance it is good enough to reuse. Pull the best one (`reelkit assets pull <id> --scene <sceneId>`) when its match is 60% or more and its description fits the scene. Otherwise `reelkit assets gen image --scene <sceneId>`.
|
|
12
|
+
3. When every scene is resolved, run `reelkit check` (it lists scenes with no image).
|
|
13
|
+
|
|
14
|
+
## When a candidate is close enough to reuse
|
|
15
|
+
The match is a guide, not the decision: read the description too. Reuse when the match is 60% or more and all of these hold:
|
|
16
|
+
- Same main subject (a glass of water is a glass of water).
|
|
17
|
+
- A compatible mood and style with the rest of this video.
|
|
18
|
+
- Nothing in its prompt contradicts the narration.
|
|
19
|
+
|
|
20
|
+
A slightly imperfect existing image is usually fine, and it is free, but below 60% generate. When every result is under 60%, generate a new image rather than lowering your bar to reuse the best of a weak set. Do not reuse when the subject differs, or when the same image is already used by another scene in this video (never give two scenes the same image).
|
|
21
|
+
|
|
22
|
+
## Generating
|
|
23
|
+
- Start from the scene's wanted prompt. Keep one consistent style across the video's images.
|
|
24
|
+
- Never ask for text, letters, logos or UI in the picture.
|
|
25
|
+
- Generate one image per scene. Do not regenerate to chase small improvements.
|
|
26
|
+
|
|
27
|
+
## What may be shared
|
|
28
|
+
- shareable: true only for generic imagery with nothing specific to this user. Write those prompts in generic terms.
|
|
29
|
+
- shareable: false if the prompt mentions or depends on a brand, product, person, place or anything the user uploaded.
|
|
30
|
+
- If the user says a scene may not be shared, it stays private whatever you pass.
|
|
31
|
+
- Give 3 to 6 short lowercase tags (subject, style, mood); they are how later videos find the image.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: captions
|
|
3
|
+
description: Use when choosing and placing the spoken-word captions for a video - which caption style fits, how many words per screen, timing, position, contrast and accessibility limits.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Captions
|
|
7
|
+
|
|
8
|
+
Most short video is watched with the sound off, so the captions carry the message. Captions are drawn by the kit's `<Captions>` component from the scene's word timings (`s.words`); nothing is burned in afterwards.
|
|
9
|
+
|
|
10
|
+
## Pick one style for the whole video
|
|
11
|
+
|
|
12
|
+
| mode | What it does | Use for |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `highlight` (default) | A line of words; the spoken one takes the highlight colour | Calm, educational, professional |
|
|
15
|
+
| `pop` | One to three words at a time, each popping in as it is spoken | High energy, hooks, punchy lists |
|
|
16
|
+
| `karaoke` | A line that fills with colour as it is spoken | Voiceover-led, music, storytelling |
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<Captions words={s.words} mode="pop" highlight={palette.hero} uppercase />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do not mix modes between scenes. Set `highlight` to the palette's hero colour (captions are the one exception to the one-hero-element rule) or to a high-contrast yellow on busy footage.
|
|
23
|
+
|
|
24
|
+
## Words per screen
|
|
25
|
+
- `pop`: 1 to 3 words (`perLine` 1 to 3). Short words can share a screen.
|
|
26
|
+
- `highlight` and `karaoke`: 3 to 5 words for vertical video, up to 6 for landscape.
|
|
27
|
+
- Never show a full sentence at once. Word-level timing reads better than sentence captions.
|
|
28
|
+
|
|
29
|
+
## Timing
|
|
30
|
+
- A word lights on the frame it starts, never before. The component does this from the word timings; do not offset it.
|
|
31
|
+
- Entrance of a popped word: roughly 80 to 250 ms. A small overshoot is fine for captions; keep it under about 110 percent.
|
|
32
|
+
- Reading speed: no more than about 3.3 words per second on screen. If the narration is faster than that, use fewer words per screen rather than smaller text.
|
|
33
|
+
- No caption group should flash by in under about 0.25 seconds.
|
|
34
|
+
|
|
35
|
+
## Position and safe zone
|
|
36
|
+
- Vertical video (1080 by 1920): keep captions at least 200 to 300 px above the bottom edge and 40 to 60 px from the sides; platform buttons and descriptions cover the rest. The component's `bottom` prop (a fraction of the height, default 0.16) controls this.
|
|
37
|
+
- Keep the top 150 to 200 px clear as well.
|
|
38
|
+
- Captions must never overlap other on-screen text. If a scene has a low headline or a lower-third, raise `bottom` or move the graphic.
|
|
39
|
+
- In footage mode, do not cover the subject's face.
|
|
40
|
+
|
|
41
|
+
## Legibility
|
|
42
|
+
- Heavy sans-serif weight, large: about 5.5 percent of the width for line captions, 7.5 percent for pop. The component sets these.
|
|
43
|
+
- Contrast of at least 4.5 to 1 against whatever is behind. The component adds a dark outline and shadow; on very bright footage also dim the footage.
|
|
44
|
+
- `uppercase` suits `pop` and short hooks; avoid it for long lines.
|
|
45
|
+
|
|
46
|
+
## Emphasis effects, with limits
|
|
47
|
+
- Shake: only on a single impact word, amplitude under about 10 px and under 15 percent of the font size, decaying within 0.3 seconds.
|
|
48
|
+
- Pulse: scale between 100 and at most 108 percent.
|
|
49
|
+
- Never flash anything more than three times a second, and no rapid colour strobing. This is an accessibility limit, not a style choice.
|
|
50
|
+
|
|
51
|
+
## Hebrew
|
|
52
|
+
For Hebrew narration pass `face={font("heebo")}` (or `assistant`, `rubik`) and `rtl` to `<Captions>`. Do not use `uppercase`; Hebrew has no capitals.
|
|
53
|
+
|
|
54
|
+
## Do not
|
|
55
|
+
- Do not add emoji to captions; they ignore the palette and render differently per platform.
|
|
56
|
+
- Do not write your own caption component unless the kit one cannot do what the scene needs.
|
|
57
|
+
- Do not re-time or paraphrase the words: captions show exactly what is spoken.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-authoring
|
|
3
|
+
description: Use when writing a new reusable component file for a video's src/ folder.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authoring reusable components
|
|
7
|
+
|
|
8
|
+
## First, do not write one
|
|
9
|
+
Run `reelkit assets search "<description>" --kind component`. If a library component does the job with different props, `reelkit assets pull <id>` it (it lands in `src/`) and use it. Write a new component only when nothing fits.
|
|
10
|
+
|
|
11
|
+
## Rules for a component file
|
|
12
|
+
- One component per file. File `NumberBadge.tsx` exports `export const NumberBadge: React.FC<NumberBadgeProps>`.
|
|
13
|
+
- Imports only from `react`, `remotion` and `reelkit/kit`. A component never imports another component file.
|
|
14
|
+
- Everything specific comes in through props: text, numbers, colours, timing. No baked-in copy, brand names, user details or asset paths.
|
|
15
|
+
- Give every visual prop a sensible default so `<NumberBadge value={1} />` works on its own.
|
|
16
|
+
- Size relative to `useVideoConfig()`, never fixed pixels.
|
|
17
|
+
- Animate from the local `useCurrentFrame()`, and accept an optional `delay` prop in frames.
|
|
18
|
+
- Declare the props type in the same file and export it.
|
|
19
|
+
|
|
20
|
+
## Keep it reusable
|
|
21
|
+
Write each component so it would suit a video on an unrelated topic:
|
|
22
|
+
- **Generic** - nothing in it is specific to this video.
|
|
23
|
+
- **Structural** - a badge, checklist, chart, progress bar, callout, quote card, icon animation, transition.
|
|
24
|
+
- **Passing** - `reelkit check` passes with it in use.
|
|
25
|
+
|
|
26
|
+
Avoid one-off layouts, anything with content baked in, or a near-duplicate of a library component. Sharing components back to the library is not available yet.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# The starter kit
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
Import from "reelkit/kit". Media props (src) take urls[key]; the kit components also accept the bare project path. For your own <Img>, <Audio> or <OffthreadVideo>, always pass urls[key]. Inside a SceneFrame, useCurrentFrame() starts at 0 for that scene.
|
|
5
|
+
|
|
6
|
+
SceneFrame { from: number; durationInFrames: number; children }
|
|
7
|
+
Wraps one scene. Places children on the timeline and fades them in and out.
|
|
8
|
+
<SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>...</SceneFrame>
|
|
9
|
+
|
|
10
|
+
TitleCard { text: string; subtitle?: string; color?: string; background?: string }
|
|
11
|
+
Large centred title that springs in.
|
|
12
|
+
<TitleCard text="Ship faster" subtitle="in three steps" background="#101020" />
|
|
13
|
+
|
|
14
|
+
LowerThird { title: string; subtitle?: string; accent?: string }
|
|
15
|
+
Name/label strip that slides in from the left, low on the screen.
|
|
16
|
+
<LowerThird title="Step 1" subtitle="Connect your account" />
|
|
17
|
+
|
|
18
|
+
Captions { words: WordTiming[]; mode?: "highlight" | "pop" | "karaoke"; highlight?: string; color?: string; perLine?: number; uppercase?: boolean; bottom?: number; face?: string; rtl?: boolean }
|
|
19
|
+
Word-timed captions near the bottom. Pass the scene's words from the manifest. Pick one mode for the whole video:
|
|
20
|
+
"highlight" (a line, spoken word coloured), "pop" (1-3 words popping in as spoken), "karaoke" (a line filling with colour).
|
|
21
|
+
bottom is the distance from the bottom edge as a fraction of the height (default 0.16).
|
|
22
|
+
For Hebrew narration pass face={font("heebo")} and rtl.
|
|
23
|
+
<Captions words={s.words} mode="pop" highlight={palette.hero} uppercase />
|
|
24
|
+
|
|
25
|
+
Counter { from?: number; to: number; durationFrames?: number; prefix?: string; suffix?: string; color?: string }
|
|
26
|
+
Centred number that counts up.
|
|
27
|
+
<Counter to={10000} suffix="+" durationFrames={45} />
|
|
28
|
+
|
|
29
|
+
KenBurnsImage { src: string; zoom?: number; direction?: "in" | "out" }
|
|
30
|
+
Full-frame image with a slow zoom and pan. src must come from urls[...]. Alternate direction between consecutive image scenes.
|
|
31
|
+
<KenBurnsImage src={urls[s.imageKey]} />
|
|
32
|
+
|
|
33
|
+
FootageLayer { src: string; muted?: boolean; dim?: number }
|
|
34
|
+
Full-frame user footage. Place it once, outside the scenes, as the bottom layer. dim (0-1) darkens it for legibility.
|
|
35
|
+
<FootageLayer src={urls[manifest.footageKey]} dim={0.25} />
|
|
36
|
+
|
|
37
|
+
BgMesh { bg: string; hero: string; accent?: string }
|
|
38
|
+
The bottom layer of the video: the base colour with two slow-drifting soft colour fields. Use it instead of a flat background.
|
|
39
|
+
<BgMesh bg={palette.bg} hero={palette.hero} accent={palette.accent} />
|
|
40
|
+
|
|
41
|
+
Grade { color: string; strength?: number } Grain { opacity?: number; blend?: "multiply" | "overlay" } Vignette { strength?: number }
|
|
42
|
+
Finishing layers, placed once at the very top of the video in this order: Grade, Grain, Vignette.
|
|
43
|
+
Grade tints everything toward the hero colour so images and graphics read as one look (strength 0.10-0.15 on light themes, 0.18-0.25 on dark).
|
|
44
|
+
Grain uses blend "multiply" on light themes and "overlay" on dark ones.
|
|
45
|
+
<Grade color={palette.hero} /><Grain blend="overlay" /><Vignette />
|
|
46
|
+
|
|
47
|
+
Entrance { delay?: number; exitAt?: number; rise?: number; breathe?: boolean; style?; children }
|
|
48
|
+
The standard way to bring anything in: fade + rise + scale on a spring, then a slow idle breathe. delay and exitAt are frames from the scene start; exitAt adds a faster exit.
|
|
49
|
+
<Entrance delay={8} exitAt={s.durationFrames - 14}><Card /></Entrance>
|
|
50
|
+
|
|
51
|
+
WordReveal { text: string; delay?: number; per?: number; highlight?: string; highlightColor?: string; style? }
|
|
52
|
+
Headline that rises in word by word from behind a mask. highlight colours one word.
|
|
53
|
+
<WordReveal text="Stretch first, phone second" highlight="first" highlightColor={palette.hero} style={{ fontFamily: fonts.display, fontWeight: 800, fontSize: width * 0.09, color: palette.ink }} />
|
|
54
|
+
|
|
55
|
+
fonts { display: string; body: string }
|
|
56
|
+
Loaded font families. Use fonts.display (weights 600-800) for headlines and numbers, fonts.body for supporting text. Never leave hero text on a default font.
|
|
57
|
+
|
|
58
|
+
Icon { name: IconName; size: number; color?: string } brandColor(name): string
|
|
59
|
+
A platform's real logo mark, or a plain interface glyph. size is in pixels. color "brand" uses the platform's own colour.
|
|
60
|
+
Brands: instagram, tiktok, youtube, x, facebook, whatsapp, telegram, twitch, discord, spotify, snapchat, pinterest, messenger, threads,
|
|
61
|
+
reddit, github, gmail, soundcloud, behance, vimeo, dribbble, imessage, apple, googlemessages, signal, googlecalendar, stripe, paypal,
|
|
62
|
+
applepay, shopify, notion, zoom. Glyphs: message, mail, bell, phone, calendar, cart, heart, star, user, play.
|
|
63
|
+
Use Icon for any platform logo. Never draw a logo yourself, never use a letter or an emoji in its place, and only show a
|
|
64
|
+
platform's mark when the video really refers to that platform.
|
|
65
|
+
<Icon name="instagram" size={width * 0.06} color="brand" />
|
|
66
|
+
|
|
67
|
+
font(name): string
|
|
68
|
+
Loads one extra typeface and returns its family name. Call it once at the top of a file: const heavy = font("montserrat").
|
|
69
|
+
montserrat (300, 800, 900), montserratItalic (800, 900), kanit (600), roboto (300, 500, 700, 900), robotoSlab (700),
|
|
70
|
+
bebasNeue (tall condensed caps), archivoBlack (very heavy), unbounded (wide heavy display, 800, 900), modak (chunky rounded),
|
|
71
|
+
greatVibes (formal script), sacramento (monoline script), outfit (geometric sans 100, 300, 500, 900), syne (wide heavy 800),
|
|
72
|
+
lilitaOne (heavy rounded), kaushanScript (bold brush script), yellowtail (flowing brush script), mrsSaintDelafield (signature script),
|
|
73
|
+
permanentMarker (marker lettering), bungeeShade (outlined sign capitals), cinzel (Roman capitals 700, 900: epic),
|
|
74
|
+
playfairDisplay (elegant serif 700, 900), orbitron (sci-fi 700, 900), rye (western), creepster (horror).
|
|
75
|
+
Hebrew text must use a Hebrew face; every face above is Latin only and Hebrew would fall back to a default font. Hebrew faces
|
|
76
|
+
(all also cover Latin): heebo, rubik, notoSansHebrew (400, 700, 900: neutral sans), assistant (400, 700, 800), alef (400, 700),
|
|
77
|
+
secularOne (strong headline), varelaRound (soft rounded), fredoka (playful rounded 500, 700), karantina (tall condensed 400, 700),
|
|
78
|
+
suezOne (heavy serif headline), frankRuhlLibre (book serif 400, 700, 900), davidLibre (traditional serif 400, 700),
|
|
79
|
+
amaticSC (hand-lettered 400, 700).
|
|
80
|
+
Use at most two typefaces in one video.
|
|
81
|
+
|
|
82
|
+
springs { snappy, smooth, heavy } ease { out, in, inOut }
|
|
83
|
+
Spring configs for spring({ frame, fps, config: springs.smooth }) and easings for interpolate(..., { easing: ease.out, extrapolateLeft: "clamp", extrapolateRight: "clamp" }).
|
|
84
|
+
|
|
85
|
+
palettes { darkTech, warmEditorial, warmPremium, cleanLight } each is a Palette { bg, ink, hero, accent, dim }
|
|
86
|
+
Proven starting palettes. Pick one that suits the topic, or define your own Palette object. Declare it once at the top of Video.tsx and take every colour from it.
|
|
87
|
+
|
|
88
|
+
ScreenOverlay { src: string; durationSec?: number; opacity?: number }
|
|
89
|
+
Lays a shared overlay clip (film marks, HUD frames) over the video with a screen blend. Pull one with: reelkit assets search "<description>" --kind overlay, then reelkit assets pull <id>; the clip's durationSec is in its entry in assets/library.json.
|
|
90
|
+
Place it once, above the scenes and below Grade. Works best on dark themes.
|
|
91
|
+
<ScreenOverlay src={urls["assets/lib/<id>/clip.mp4"]} durationSec={5} opacity={0.5} />
|
|
92
|
+
|
|
93
|
+
Sfx { src: string; at?: number; volume?: number }
|
|
94
|
+
Plays one sound effect from the shared library, starting at the frame given by "at", counted from the start of the enclosing scene. Pull one with: reelkit assets search "<description>" --kind sfx, then reelkit assets pull <id>.
|
|
95
|
+
<Sfx src={urls["assets/lib/<id>/clip.mp3"]} at={10} volume={0.35} />
|
|
96
|
+
|
|
97
|
+
Voiceover { src: string; volume?: number }
|
|
98
|
+
The scene's narration audio. One per narrated scene, inside its SceneFrame. Guard it with s.voiceoverKey so a scene without a recording still renders.
|
|
99
|
+
{s.voiceoverKey ? <Voiceover src={urls[s.voiceoverKey]} /> : null}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## A minimal working Video.tsx
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import React from "react";
|
|
106
|
+
import { AbsoluteFill, useVideoConfig } from "remotion";
|
|
107
|
+
import { BgMesh, Captions, FootageLayer, fonts, Grade, Grain, KenBurnsImage, palettes, SceneFrame, Vignette, Voiceover, WordReveal } from "reelkit/kit";
|
|
108
|
+
import type { VideoProps } from "reelkit/kit";
|
|
109
|
+
|
|
110
|
+
const palette = palettes.darkTech;
|
|
111
|
+
|
|
112
|
+
export const Video: React.FC<VideoProps> = ({ manifest, urls }) => {
|
|
113
|
+
const { width } = useVideoConfig();
|
|
114
|
+
return (
|
|
115
|
+
<AbsoluteFill>
|
|
116
|
+
{manifest.footageKey ? <FootageLayer src={urls[manifest.footageKey]} dim={0.2} /> : <BgMesh bg={palette.bg} hero={palette.hero} accent={palette.accent} />}
|
|
117
|
+
{manifest.scenes.map((s, i) => (
|
|
118
|
+
<SceneFrame key={s.id} from={s.startFrame} durationInFrames={s.durationFrames}>
|
|
119
|
+
{s.imageKey ? <KenBurnsImage src={urls[s.imageKey]} direction={i % 2 ? "out" : "in"} /> : null}
|
|
120
|
+
{i === 0 && !manifest.footageKey ? (
|
|
121
|
+
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", padding: width * 0.08 }}>
|
|
122
|
+
<WordReveal text="Demo video" highlight="Demo" highlightColor={palette.hero} style={{ fontFamily: fonts.display, fontWeight: 800, fontSize: width * 0.1, color: palette.ink }} />
|
|
123
|
+
</AbsoluteFill>
|
|
124
|
+
) : null}
|
|
125
|
+
<Captions words={s.words} />
|
|
126
|
+
{s.voiceoverKey ? <Voiceover src={urls[s.voiceoverKey]} /> : null}
|
|
127
|
+
</SceneFrame>
|
|
128
|
+
))}
|
|
129
|
+
{manifest.footageKey ? null : <Grade color={palette.hero} />}
|
|
130
|
+
<Grain blend="overlay" />
|
|
131
|
+
<Vignette />
|
|
132
|
+
</AbsoluteFill>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
```
|