reelkit-cli 0.10.2 → 0.10.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,93 @@
1
+ # Changelog
2
+
3
+ What changed in each published version of `reelkit-cli`, newest first. A push to `main` publishes the version in
4
+ `package.json` when it is not on npm yet, and it must have an entry here.
5
+
6
+ ## 0.10.4 - 2026-10-09
7
+
8
+ - The public library listing can be sorted by when items were added: `sort=new` or `sort=old`.
9
+ - This changelog ships with the package, and a push to `main` publishes a new version to npm.
10
+
11
+ ## 0.10.3 - 2026-10-07
12
+
13
+ - The library has a new kind, `prompt`: prompts people used to make motion videos, kept as references. Search and pull them with `--kind prompt`.
14
+
15
+ ## 0.10.2 - 2026-10-07
16
+
17
+ - The skill carries a studio pane for Claude Code: a live view of the project's timeline and player beside the conversation.
18
+
19
+ ## 0.10.0 - 2026-10-06
20
+
21
+ - Videos are built and rendered with HyperFrames. Remotion is gone; compositions import from `reelkit/frame`.
22
+ - Fifteen art styles, each with its own components and an example film, from flat vector and line art to synthwave, pixel and collage.
23
+ - 3D: `Assemble3D`, `Particles3D`, `Hero3D`, `Screen3D`, `Warp3D`, scene moods, and branded 3D transforms. 3D now fits the frame with true camera numbers.
24
+ - Pictures in layers: `reelkit assets layers` cuts the subject out of a picture, and `ImageLayers` and `TextOnImage` use the result.
25
+ - Product-surface components: `PromptBox`, `TerminalLog`, `NamedCursor`, `GlassPanel`, `ChapterFrame`, `Headline`, `CounterRoll`, `HudOverlay`, `BrowserFrame`.
26
+ - Five more transitions: `color-push`, `rgb-whip`, `flash`, `ring`, `flip`.
27
+ - Sound: `cuesFor` places cues on interface events, music holds steady under narration, and `reelkit sound` measures the voice against the music.
28
+ - Timing: word anchors (`wordFrame`, `onWord`, `onWordBeat`, `sceneById`), tighter gaps between narrated scenes, and a real length estimate in `plan check`.
29
+ - Preview writes a labelled contact sheet and shows the frame after each word.
30
+ - `reelkit init --private` keeps generated images, clips and graphics out of the shared library.
31
+ - Three narrator voices to choose from.
32
+
33
+ ## 0.8.0 - 2026-10-06
34
+
35
+ - Videos with no voice: `voice: none` in the plan, scenes timed in seconds on a beat grid, and `SoundCues` in the kit.
36
+ - A 3D layer on three.js, scene transitions, camera punches and `zoomTo`.
37
+ - Moving grounds, background assets and generated SVG graphics (`reelkit assets gen svg`).
38
+ - `reelkit sound` measures the sound of a finished film, and music can dip.
39
+ - `reelkit ref download` handles Instagram and other links whose length is not known in advance.
40
+ - A component that carries any of the video's own words, or a written-in sentence, is not shared.
41
+ - A second slash command for the voiceless launch film.
42
+
43
+ ## 0.6.0 - 2026-10-05
44
+
45
+ - New components the agent writes are shared with the library for review, by default. Say no with `reelkit init --private`, `reelkit render --no-share` or `REELKIT_NO_SHARE=1`.
46
+ - Music: a track, scene changes on its beat, a `Music` component and beat helpers.
47
+ - Captions are a choice: none, a word at a time, or phrases grouped as they are spoken.
48
+ - Every pulled sound is levelled and the render is mastered to -14 LUFS.
49
+
50
+ ## 0.5.0 - 2026-10-05
51
+
52
+ - `Carry` and `Camera` in the kit hold one element or one camera through several scenes.
53
+ - `reelkit ref analyze` measures how still a video is and how uneven its shots are.
54
+ - `plan check` says when the scenes are all about the same length, and preview reports which scene changes carry something across.
55
+ - The skill teaches continuity, rhythm and script writing, and offers three concepts before a plan.
56
+
57
+ ## 0.4.0 - 2026-10-05
58
+
59
+ - Reference videos: `reelkit ref download`, `audio`, `analyze` and `list`, with a transcript and a local beat and tempo analysis.
60
+ - A plan can name its reference, and `plan check` looks at it.
61
+ - A generated green-screen clip that is too dull to key is cut out on the server instead.
62
+
63
+ ## 0.3.1 - 2026-10-05
64
+
65
+ - The CLI talks to reelkit.cc by default.
66
+
67
+ ## 0.3.0 - 2026-10-05
68
+
69
+ - Cut the subject out of any video on the server: `reelkit assets upload --cutout` and `reelkit assets gen clip --cutout`.
70
+ - Key your own green-screen footage; the key colour is read from the frame.
71
+
72
+ ## 0.2.0 - 2026-10-05
73
+
74
+ - Generated clips: `reelkit assets gen clip`, with green-screen keying, clip scenes in the plan, and the `ClipLayer` and `KeyedClip` components.
75
+
76
+ ## 0.1.3 - 2026-10-05
77
+
78
+ - The looks catalogue is rebuilt around the kit and the shared library.
79
+ - The skill chooses a look, handles Hebrew on screen and applies what editing real footage teaches.
80
+
81
+ ## 0.1.2 - 2026-10-05
82
+
83
+ - `reelkit --version`.
84
+ - Preview shows two frames per scene, and `plan check` always reports the estimated length.
85
+ - The first preview or render says when the rendering browser is being downloaded.
86
+
87
+ ## 0.1.1 - 2026-10-05
88
+
89
+ - Node 22.12 or later is required.
90
+
91
+ ## 0.1.0 - 2026-10-05
92
+
93
+ - First release: `reelkit init`, `plan check`, asset upload, search and pull, voiceover and image generation, `check`, `preview` and `render`, device login, and the skill for coding agents.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelkit-cli",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "CLI and Claude skill for making short-form video with a shared asset library.",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Livshin",
@@ -39,7 +39,8 @@
39
39
  "files": [
40
40
  "bin",
41
41
  "src",
42
- "skill"
42
+ "skill",
43
+ "CHANGELOG.md"
43
44
  ],
44
45
  "engines": {
45
46
  "node": ">=22.12"
package/skill/SKILL.md CHANGED
@@ -15,7 +15,7 @@ For image or video cutouts, editable Blender product shots, custom 3D materials,
15
15
 
16
16
  To implement a chosen style, read its row in `reference/style-recipes.md` for action, timing, sound and proof. `reference/style-components.md` maps the shared components and built-in kit to shot roles; inspect the selected source before using its props.
17
17
 
18
- This skill targets `reelkit-cli` 0.10.2 and its HyperFrames kit. Check the installed version with `reelkit --version`; in a source checkout, invoke its `bin/reelkit.mjs` with Node. Use the matching CLI before authoring with `reelkit/frame`.
18
+ This skill targets `reelkit-cli` 0.10.4 and its HyperFrames kit. Check the installed version with `reelkit --version`; in a source checkout, invoke its `bin/reelkit.mjs` with Node. Use the matching CLI before authoring with `reelkit/frame`.
19
19
 
20
20
  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.
21
21
 
package/src/cli.ts CHANGED
@@ -76,7 +76,7 @@ assets.command("upload <file>").description("Add one of your own files to this p
76
76
  .option("--background", "use this video or image as the ground behind the whole film (it stays on this machine)").option("--share", "also send it to the shared library").option("--kind <kind>", "library kind when sharing").option("--tags <tags>", "comma-separated tags when sharing")
77
77
  // Called with the arguments named: commander adds its own command object last, which must not be taken for the function's test hooks.
78
78
  .action(run((ctx, file: string, opts: Parameters<typeof assetsUpload>[2]) => assetsUpload(ctx, file, opts)));
79
- assets.command("search <query>").description("Search the shared library by meaning; each result shows how well it fits").option("--kind <kind>", "image, overlay, sfx, music, component or clip").option("--limit <n>", "how many results")
79
+ assets.command("search <query>").description("Search the shared library by meaning; each result shows how well it fits").option("--kind <kind>", "image, overlay, sfx, music, component, clip or prompt (prompts other people used to make motion videos; found only with --kind prompt)").option("--limit <n>", "how many results")
80
80
  .action(run(assetsSearch));
81
81
  assets.command("pull <id>").description("Download a library item into this project").option("--scene <sceneId>", "use it as this scene's image or clip").option("--force", "replace a component file that already exists").option("--music", "make this music item the video's track (its tempo is measured and scene changes land on its beat)").option("--background", "make this clip, overlay or image the ground behind the film (with --scene: the ground of that scene, an image only)")
82
82
  .option("--layers", "with --scene and an image: right after it is pulled, cut the subject out as a layer (about 1 second of the cutout quota; the picture is sent to the Reelkit server)")
@@ -292,6 +292,15 @@ export async function assetsPull(ctx: Ctx, id: string, opts: { scene?: string; f
292
292
  if (treatment === "clip" && pulled.item.kind !== "clip") return { ok: false, summary: `${id} is ${pulled.item.kind}, not a clip, so it cannot be scene ${opts.scene}'s clip.` };
293
293
  if (treatment !== "clip" && pulled.item.kind !== "image") return { ok: false, summary: `${id} is ${pulled.item.kind}, not an image, so it cannot be a scene's image.` };
294
294
  }
295
+ // A prompt is something to read, not something the film shows: it goes to prompts/, outside what the composition can reference.
296
+ if (pulled.item.kind === "prompt") {
297
+ if (opts.scene || opts.music || opts.background) return { ok: false, summary: `${id} is a prompt: text to read for ideas, not a picture, clip or track. Pull it with no option.` };
298
+ const path = `prompts/${id}.md`;
299
+ await download(pulled.url, project.path(path));
300
+ const by = typeof pulled.item.meta.handle === "string" ? ` by @${pulled.item.meta.handle}` : "";
301
+ const source = typeof pulled.item.meta.source === "string" ? ` Source: ${pulled.item.meta.source}` : "";
302
+ return { ok: true, data: { path, kind: "prompt" }, summary: `Pulled ${id} to ${path}: "${pulled.item.title}"${by}. Read it for how the video was asked for; it is another person's prompt, so borrow the approach, not the wording.${source}` };
303
+ }
295
304
  // A component is code, not media: it goes beside Video.tsx, which may only import files in src/.
296
305
  if (pulled.item.kind === "component") {
297
306
  const path = `src/${pulled.filename}`;
@@ -38,6 +38,10 @@
38
38
  // that the item is good enough to reuse for the query. A server finds candidates by meaning and may judge them with a model, so `match` is not
39
39
  // a share of words; the order of results with the same `match` is not specified. The public listing is newest first; with a query it is best
40
40
  // match first, equal matches newest first. Paging continues the same order, with no item twice. `page` is from 1 to 10000.
41
+ // - A `prompt` is a text item: a prompt someone wrote to make a motion video, kept as a Markdown file with its author and source in `meta`
42
+ // (`author`, `handle`, `source`, and for a skill `install` and `repo`). It is reference material, not an asset, so search returns prompts
43
+ // only when asked with `kind: "prompt"`; a search with no kind never contains one (a client older than the kind cannot read it).
44
+ // A prompt cannot be uploaded: `libraryUpload` with `kind: "prompt"` is 400 `invalid_request`.
41
45
  // - A server may add tags (and a description when the uploader gave none) to an item that is shared, after upload or generation; the tags the
42
46
  // uploader gave stay in it, first.
43
47
  // - Upload URLs and file URLs must be unguessable (random or signed; never derived from an item id or a counter) and scoped to one object:
@@ -132,10 +136,11 @@ export type ErrorCode = z.infer<typeof ErrorCodeSchema>;
132
136
  // The code is any string here: a code this version does not know still carries a message worth showing (the client maps it to server_error).
133
137
  export const ApiErrorSchema = z.object({ error: z.object({ code: z.string(), message: z.string() }) });
134
138
 
135
- export const LibraryKindSchema = z.enum(["image", "overlay", "sfx", "music", "component", "clip"]);
139
+ export const LibraryKindSchema = z.enum(["image", "overlay", "sfx", "music", "component", "clip", "prompt"]);
136
140
  export type LibraryKind = z.infer<typeof LibraryKindSchema>;
137
- // What a user may upload: every kind. A component is code and is held for review (see the rules at the top).
138
- export const UploadKindSchema = LibraryKindSchema;
141
+ // What a user may upload: every kind but a prompt (those are published by the library's own import). A component is code and is held for
142
+ // review (see the rules at the top).
143
+ export const UploadKindSchema = LibraryKindSchema.exclude(["prompt"]);
139
144
  export type UploadKind = z.infer<typeof UploadKindSchema>;
140
145
 
141
146
  export const LibraryItemSchema = z.object({
@@ -254,7 +259,9 @@ export const routes = {
254
259
  z.object({ id: ItemId }),
255
260
  z.object({ id: z.string(), text: z.string(), language: z.string().optional(), segments: z.array(TranscriptSegmentSchema), durationSec: z.number().positive() })),
256
261
  publicLibrary: route("GET", "/public/library", false,
257
- z.object({ q: text(200).optional(), kind: LibraryKindSchema.optional(), page: z.coerce.number().int().min(1).max(10000).optional() }),
262
+ // `sort` orders a listing without words by when each item was added: "new" is newest first, "old" is oldest first. Without it the
263
+ // server chooses (newest first within a kind; the kinds take turns when none is named). It does not reorder a search by words.
264
+ z.object({ q: text(200).optional(), kind: LibraryKindSchema.optional(), page: z.coerce.number().int().min(1).max(10000).optional(), sort: z.enum(["new", "old"]).optional() }),
258
265
  // With `q` the items are the best matches by meaning, best first, each with `match` (0..1), in one page (`hasMore` false). Under heavy
259
266
  // use, or when ranking fails, the server answers the plain text listing instead (no `match`), so a client must accept both.
260
267
  z.object({ items: z.array(LibraryItemSchema.extend({ previewUrl: z.string().optional(), match: z.number().min(0).max(1).optional() })), page: z.number(), hasMore: z.boolean() })),
@@ -158,7 +158,7 @@ export async function startFakeApi(opts: { voiceoverCharLimit?: number; imageLim
158
158
  // Best match first; items that match equally well come newest first (the one added last).
159
159
  // match is the share of the query's words the item has, to two decimals.
160
160
  const items = [...store.values()]
161
- .filter((s) => s.item.visibility === "published" && (!kind || s.item.kind === kind))
161
+ .filter((s) => s.item.visibility === "published" && (kind ? s.item.kind === kind : s.item.kind !== "prompt"))
162
162
  .map((s, order) => ({ s, order, score: [...want].filter((w) => words(`${s.item.title} ${s.item.description} ${s.item.tags.join(" ")}`).has(w)).length }))
163
163
  .filter((x) => x.score > 0).sort((a, b) => b.score - a.score || b.order - a.order).slice(0, limit ?? 8)
164
164
  .map((x) => ({ ...x.s.item, match: Math.round((x.score / want.size) * 100) / 100 }));
@@ -323,14 +323,14 @@ export async function startFakeApi(opts: { voiceoverCharLimit?: number; imageLim
323
323
  };
324
324
  return job.result;
325
325
  },
326
- publicLibrary: ({ q, kind, page }) => {
327
- // Published items only. Newest first; with a query, best match first and equal matches newest first.
326
+ publicLibrary: ({ q, kind, page, sort }) => {
327
+ // Published items only. Newest first, or oldest first when asked; with a query, best match first and equal matches newest first.
328
328
  const want = q ? words(q) : undefined;
329
329
  const score = (i: LibraryItem) => (want ? [...want].filter((w) => words(`${i.title} ${i.description} ${i.tags.join(" ")}`).has(w)).length : 0);
330
330
  const all = [...store.values()]
331
331
  .map((s, order) => ({ s, order, score: score(s.item) }))
332
332
  .filter((x) => x.s.item.visibility === "published" && (!kind || x.s.item.kind === kind) && (!want || x.score > 0))
333
- .sort((a, b) => b.score - a.score || b.order - a.order).map((x) => x.s);
333
+ .sort((a, b) => b.score - a.score || (sort === "old" && !want ? a.order - b.order : b.order - a.order)).map((x) => x.s);
334
334
  const p = page ?? 1, size = 24;
335
335
  const items = all.slice((p - 1) * size, p * size).map((s) => (s.file && (PREVIEW_OF_ORIGINAL.includes(s.item.kind) || shortClip(s.item)) ? { ...s.item, previewUrl: fileUrl(s.file) } : s.item));
336
336
  return { items, page: p, hasMore: all.length > p * size };