reelkit-cli 0.10.2 → 0.10.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelkit-cli",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
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",
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.3 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({
@@ -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 }));