@wji.ai/cli 0.4.0 → 0.5.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.
Files changed (58) hide show
  1. package/README.md +48 -2
  2. package/dist/commands/backreel.js +260 -0
  3. package/dist/commands/canvas.js +588 -88
  4. package/dist/commands/class.js +395 -0
  5. package/dist/commands/npc.js +413 -0
  6. package/dist/commands/workspace.js +109 -24
  7. package/dist/core/members.js +54 -0
  8. package/dist/core/npc.js +108 -0
  9. package/dist/core/output.js +4 -2
  10. package/dist/index.js +8 -0
  11. package/package.json +1 -1
  12. package/skill/short-drama/SKILL.md +86 -8
  13. package/skill/short-drama/reference/action-bullet-time.md +47 -0
  14. package/skill/short-drama/reference/action-car-chase.md +51 -0
  15. package/skill/short-drama/reference/action-gun-fu.md +52 -0
  16. package/skill/short-drama/reference/action-historical-realist.md +50 -0
  17. package/skill/short-drama/reference/action-hk-kungfu.md +54 -0
  18. package/skill/short-drama/reference/action-street-brawl.md +49 -0
  19. package/skill/short-drama/reference/action-superhero.md +50 -0
  20. package/skill/short-drama/reference/action-wuxia-fantasy.md +51 -0
  21. package/skill/short-drama/reference/action.md +94 -0
  22. package/skill/short-drama/reference/cinematography-costume-dark.md +117 -0
  23. package/skill/short-drama/reference/cinematography-costume-idol.md +126 -0
  24. package/skill/short-drama/reference/cinematography-low-key.md +115 -0
  25. package/skill/short-drama/reference/cinematography-period-nostalgia.md +106 -0
  26. package/skill/short-drama/reference/cinematography-scifi-hard.md +63 -0
  27. package/skill/short-drama/reference/cinematography-scifi-soft.md +66 -0
  28. package/skill/short-drama/reference/cinematography-sweet-romance.md +175 -0
  29. package/skill/short-drama/reference/cinematography.md +83 -0
  30. package/skill/short-drama/reference/composition-art-first.md +74 -0
  31. package/skill/short-drama/reference/craft-shots.md +3 -1
  32. package/skill/short-drama/reference/craft-story.md +4 -1
  33. package/skill/short-drama/reference/format.md +94 -0
  34. package/skill/short-drama/reference/grammar-comedy.md +105 -0
  35. package/skill/short-drama/reference/grammar-faceslap.md +67 -0
  36. package/skill/short-drama/reference/grammar.md +103 -0
  37. package/skill/short-drama/reference/operations-cli.md +1 -1
  38. package/skill/short-drama/reference/schema.md +34 -3
  39. package/skill/short-drama/reference/stage-art.md +1 -1
  40. package/skill/short-drama/reference/stage-outline.md +21 -0
  41. package/skill/short-drama/reference/stage-script.md +10 -0
  42. package/skill/short-drama/reference/stage-storyboard.md +15 -0
  43. package/skill/short-drama/reference/story-angst.md +117 -0
  44. package/skill/short-drama/reference/story-comedy.md +136 -0
  45. package/skill/short-drama/reference/story-cultivation.md +109 -0
  46. package/skill/short-drama/reference/story-family.md +119 -0
  47. package/skill/short-drama/reference/story-horror.md +124 -0
  48. package/skill/short-drama/reference/story-mystery.md +127 -0
  49. package/skill/short-drama/reference/story-palace.md +103 -0
  50. package/skill/short-drama/reference/story-revenge.md +124 -0
  51. package/skill/short-drama/reference/story-scifi.md +112 -0
  52. package/skill/short-drama/reference/story-sweet.md +128 -0
  53. package/skill/short-drama/reference/story.md +127 -0
  54. package/skill/short-drama/scripts/selftest.mjs +809 -1
  55. package/skill/short-drama/scripts/short-drama.mjs +1608 -16
  56. package/skill/wji/SKILL.md +66 -2
  57. package/skill/wji/reference/commands.md +76 -15
  58. package/skill/wji-classroom/SKILL.md +161 -0
@@ -0,0 +1,108 @@
1
+ import { isObjectId } from './client.js';
2
+ /**
3
+ * NPC City residents (NPCs). Search endpoints return raw entities (`owner` is
4
+ * an id); get / create / patch return the presented shape (`owner` is
5
+ * `{id,name,avatarUrl}`). Everything the CLI prints reads fields both share.
6
+ */
7
+ export const RESIDENTS = '/npc-city/residents';
8
+ export const NPC_STATUSES = ['active', 'debut', 'hiatus'];
9
+ export const NPC_VISIBILITIES = ['public', 'private'];
10
+ /** Work-history entry types; the server turns anything else into `event`. */
11
+ export const NPC_JOB_TYPES = ['concert', 'release', 'collaboration', 'brand', 'event', 'ad', 'appearance'];
12
+ /** How people refer to a resident: `NAME (@handle)`. */
13
+ export const npcLabel = (r) => (r?.handle ? `${r.name} (${r.handle})` : String(r?.name ?? r?.id ?? ''));
14
+ /** Every resident the caller manages in the current org context (`--org`). */
15
+ export async function listMyNpcs(client) {
16
+ const out = [];
17
+ for (let page = 1; page <= 50; page++) {
18
+ const res = await client.getFresh(`${RESIDENTS}/mine`, { page, limit: 100 });
19
+ out.push(...(res?.data || []));
20
+ if (!res?.pagination?.hasNext)
21
+ break;
22
+ }
23
+ return out;
24
+ }
25
+ /**
26
+ * A resident from what a person would type: id, `@handle` (or the handle
27
+ * without `@`), exact name, else a unique name/handle prefix among yours; an
28
+ * `@handle` that isn't yours falls back to the public directory. Ambiguity is
29
+ * an error, never a guess. Returns the presented resident.
30
+ */
31
+ export async function resolveNpc(client, token) {
32
+ const t = token.trim();
33
+ if (isObjectId(t))
34
+ return client.getFresh(`${RESIDENTS}/${t}`);
35
+ const mine = await listMyNpcs(client);
36
+ const lower = t.toLowerCase();
37
+ const handle = lower.startsWith('@') ? lower : `@${lower}`;
38
+ const pick = (r) => client.getFresh(`${RESIDENTS}/${r.id}`);
39
+ const byHandle = mine.filter((r) => String(r.handle || '').toLowerCase() === handle);
40
+ if (byHandle.length === 1)
41
+ return pick(byHandle[0]);
42
+ const byName = mine.filter((r) => String(r.name || '').toLowerCase() === lower);
43
+ if (byName.length === 1)
44
+ return pick(byName[0]);
45
+ if (byName.length > 1) {
46
+ throw new Error(`Several NPCs are called "${t}": ${byName.map(npcLabel).join(', ')} — use the @handle or id`);
47
+ }
48
+ const bare = lower.replace(/^@/, '');
49
+ const prefix = mine.filter((r) => String(r.name || '').toLowerCase().startsWith(bare) ||
50
+ String(r.handle || '').toLowerCase().replace(/^@/, '').startsWith(bare));
51
+ if (prefix.length === 1)
52
+ return pick(prefix[0]);
53
+ if (prefix.length > 1)
54
+ throw new Error(`"${t}" matches several NPCs: ${prefix.map(npcLabel).join(', ')}`);
55
+ // Someone else's public NPC (e.g. to credit it on a Backreel post).
56
+ if (t.startsWith('@')) {
57
+ const pub = await client.getFresh(`${RESIDENTS}/by-handle/${encodeURIComponent(t)}`).catch(() => undefined);
58
+ if (pub?.id)
59
+ return pub;
60
+ }
61
+ throw new Error(`No NPC "${t}" among yours. See \`wji npc list\`; org-owned NPCs need \`--org <orgId>\`.`);
62
+ }
63
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
64
+ /**
65
+ * A job from CLI flags, validated here because the server silently drops
66
+ * entries without a title or date and rewrites unknown types to `event`.
67
+ * With `base` the flags are merged onto an existing job (`job set`).
68
+ */
69
+ export function parseJob(opts, base) {
70
+ const job = {
71
+ type: opts.type ?? base?.type ?? '',
72
+ title: opts.title ?? base?.title ?? '',
73
+ detail: opts.detail ?? base?.detail ?? '',
74
+ date: opts.date ?? base?.date ?? '',
75
+ };
76
+ const location = opts.location ?? base?.location;
77
+ const url = opts.url ?? base?.url;
78
+ if (location)
79
+ job.location = location;
80
+ if (url)
81
+ job.url = url;
82
+ if (!NPC_JOB_TYPES.includes(job.type)) {
83
+ throw new Error(`Job type must be one of ${NPC_JOB_TYPES.join(', ')} (got "${job.type}")`);
84
+ }
85
+ if (!job.title.trim())
86
+ throw new Error('A job needs --title');
87
+ if (!DATE_RE.test(job.date))
88
+ throw new Error(`A job needs --date as YYYY-MM-DD (got "${job.date}")`);
89
+ return job;
90
+ }
91
+ /** Jobs have no ids: read the array fresh, change it, write it back whole. */
92
+ export async function updateJobs(client, id, fn) {
93
+ const current = await client.getFresh(`${RESIDENTS}/${id}`);
94
+ const jobs = fn([...(current.jobs || [])]);
95
+ return client.patch(`${RESIDENTS}/${id}`, { jobs });
96
+ }
97
+ /** 1-based positions from the command line, checked against the list length. */
98
+ export function parsePositions(values, length) {
99
+ const out = values.map((v) => {
100
+ const n = Number(v);
101
+ if (!Number.isInteger(n) || n < 1 || n > length) {
102
+ throw new Error(`No #${v} — there ${length === 1 ? 'is 1 entry' : `are ${length} entries`}`);
103
+ }
104
+ return n - 1;
105
+ });
106
+ return [...new Set(out)].sort((a, b) => a - b);
107
+ }
108
+ export const today = () => new Date().toISOString().slice(0, 10);
@@ -136,8 +136,10 @@ export function relativeTime(value) {
136
136
  [60000, 'm'],
137
137
  ];
138
138
  for (const [ms, label] of units) {
139
- if (Math.abs(diff) >= ms)
140
- return `${Math.round(diff / ms)}${label} ago`;
139
+ if (Math.abs(diff) >= ms) {
140
+ const n = Math.round(Math.abs(diff) / ms);
141
+ return diff >= 0 ? `${n}${label} ago` : `in ${n}${label}`;
142
+ }
141
143
  }
142
144
  return 'just now';
143
145
  }
package/dist/index.js CHANGED
@@ -17,6 +17,9 @@ import { presetCommand } from './commands/preset.js';
17
17
  import { skillCommand } from './commands/skill.js';
18
18
  import { canvasCommand } from './commands/canvas.js';
19
19
  import { crewCommand } from './commands/crew.js';
20
+ import { classCommand } from './commands/class.js';
21
+ import { npcCommand } from './commands/npc.js';
22
+ import { backreelCommand } from './commands/backreel.js';
20
23
  // Read from package.json so `wji --version` cannot drift from the published
21
24
  // version — the skill compares the two to detect a stale install.
22
25
  const VERSION = createRequire(import.meta.url)('../package.json').version;
@@ -69,6 +72,9 @@ program.addCommand(presetCommand(globals));
69
72
  program.addCommand(skillCommand(globals));
70
73
  program.addCommand(canvasCommand(globals));
71
74
  program.addCommand(crewCommand(globals));
75
+ program.addCommand(classCommand(globals));
76
+ program.addCommand(npcCommand(globals));
77
+ program.addCommand(backreelCommand(globals));
72
78
  program.addHelpText('after', `
73
79
  Examples:
74
80
  wji auth login --email you@example.com
@@ -79,6 +85,8 @@ Examples:
79
85
  wji fs tree $(wji ws get --json | jq -r .id) --depth 2
80
86
  wji asset upload ./reference.png --workspace <id>
81
87
  wji canvas status # after pressing the link button on a canvas
88
+ wji npc job add @mika -t concert --title "Neon Night" --date 2026-10-01
89
+ wji backreel credit add <postId> --npc @mika --role "飾 阿明" --job appearance
82
90
 
83
91
  Auth precedence: --token > $WJI_TOKEN / $WJI_API_KEY > profile apiKey > profile accessToken.
84
92
  Config lives in ~/.wji/config.json (chmod 600).`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wji.ai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Command line interface for the WJI AI platform",
5
5
  "license": "UNLICENSED",
6
6
  "author": "WJI AI",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: short-drama
3
- description: Produce an AI short drama (短劇) on the WJI platform end to end — write it the way a commercial 短剧 is written (创作简报, 创作指引, 人物圣经, 分集目录 with 爽点 and 钩子, the 15-second hook and 30-second turns, 谈话+行动, the right 语体), adapt a story or novel into an outline, character / scene / prop bibles, per-episode scripts and storyboards, then generate the sheets, keyframes and video clips through the `wji` CLI, with data stored as workspace datasets and the work laid out on canvases (characters, scenes, one per episode). Every stage is gated by `scripts/short-drama.mjs validate`. Use whenever a task mentions short drama, 短劇 / 短剧, 分集 / 分镜 / 剧本 for AI video, or producing a serialised video story on WJI. Works together with the `wji` skill.
3
+ description: Produce an AI short drama (短劇), a medium drama (中劇, 4–15-minute episodes) or a movie written in ~5-minute parts on the WJI platform end to end — write it the way a commercial 短剧 is written (创作简报, 创作指引, 人物圣经, 分集目录 with 爽点 and 钩子, the 15-second hook and 30-second turns, 谈话+行动, the right 语体), adapt a story or novel into an outline, character / scene / prop bibles, per-episode scripts and storyboards, then generate the sheets, keyframes and video clips through the `wji` CLI, with data stored as workspace datasets and the work laid out on canvases (characters, scenes, one per episode). Every stage is gated by `scripts/short-drama.mjs validate`. Use whenever a task mentions short drama, 短劇 / 短剧, 中剧, a series or a movie / 电影 / 微电影 made with AI video, 分集 / 分镜 / 剧本 for AI video, or producing a serialised or feature-length video story on WJI. Works together with the `wji` skill.
4
4
  ---
5
5
 
6
6
  # Short drama on WJI
@@ -51,10 +51,13 @@ ending), change the guideline first, then the episodes.
51
51
  `wji profile wallet` (a drama is many generations — know the balance).
52
52
  2. **Scan the message for `-auto`** (spending waiver, see the `wji` skill). Without it: one image
53
53
  runs, video and batches ask first.
54
- 3. **Ask the parameters once**, in one message: episodes × minutes per episode, genre, adapt
54
+ 3. **Ask the parameters once**, in one message: the **format** (`short` 短剧 1–3-minute episodes,
55
+ `medium` 中剧 4–15-minute episodes, `movie` 电影 written as ~5-minute parts — read
56
+ `{baseDir}/reference/format.md`; the pipeline and the story structure are the same, the rhythm
57
+ and the genre counts scale), episodes (or parts) × minutes each, genre, adapt
55
58
  mode (原创 / 忠实 / 抽核 / 借壳), the 语体 (`register`: 粤语 / 大陆普通话 / 台湾国语 / 港式普通话 /
56
59
  古装 / 年代 / 英文美式 / 英文英式 — it decides how the lines are written and the accent the video
57
- model speaks), ratio (default 9:16). Offer the defaults you will use unless
60
+ model speaks), ratio (default 9:16 for short, 16:9 for medium and movie). Offer the defaults you will use unless
58
61
  told otherwise: `video: h3` (MiniMax H3 ref), `style: realistic`, **`references: sheets`** (no
59
62
  storyboard images) and **`videoDuration: auto`**. Do not start Stage 1 without the first two.
60
63
 
@@ -89,9 +92,47 @@ ending), change the guideline first, then the episodes.
89
92
  `ARRI ALEXA Mini LF` / `Zeiss Supreme Prime` / `T1.5`. Every shot prompt names it verbatim, so
90
93
  the drama looks shot on one rig; a cut can override the focal and the stop. A look the user asks
91
94
  for on top of that (大光圈, 背景虛化, 前景模糊…) goes in `lookPhrase`.
95
+
96
+ **The cinematography** is `params.cinematography`, the drama's lighting and composition preset.
97
+ `init` picks it from the genre: `costume-idol` (古偶 / 仙侠: luminous, gauze and backlit silk) or
98
+ `costume-dark` (权谋 / 武侠 / 写实: candles, torches, lattice light, faces in shadow) for 古装 —
99
+ a bare 古装 gets `costume-idol`, so confirm it with the user; `period-nostalgia` for 年代 / 怀旧
100
+ (film grain, warm faded colour, window sun and bare bulbs); `scifi-hard` for 硬科幻 (instrument
101
+ panels, work lamps, one harsh sun, no magic or floating holograms) and `scifi-soft` for 软科幻 /
102
+ 科幻 / 赛博朋克 / 未来 (holograms, neon, dual-tone colour, haze) — a bare 科幻 gets `scifi-soft`,
103
+ confirm it; `sweet-romance` for 甜宠 / 恋爱 /
104
+ 韩风唯美 (bright soft light, a warm halo in the hair, pastel); `low-key` for everything else
105
+ (the scene's own practicals, people in shadow, side / back / rim light). The costume and period
106
+ presets refuse light sources their era did not have, and `scifi-hard` refuses magic and floating
107
+ holograms. The preset is gated from the scene lighting
108
+ states through every cut to the video prompt's Look line. Read
109
+ `{baseDir}/reference/cinematography.md`, then the chosen preset's guide. Pass
110
+ `--cinematography` when the genre mixes both (ask the user which way the picture leans), and
111
+ `none` only when the user wants a look that fits neither.
112
+
113
+ **Genre is composed, not picked.** Besides the look, a drama has an action style
114
+ (`params.action`: `gun-fu` · `hk-kungfu` · `wuxia-fantasy` · `historical-realist` ·
115
+ `bullet-time` · `superhero` · `street-brawl` · `car-chase`), a comedy style (`params.comedy`:
116
+ `slapstick` · `deadpan` · `romcom`), and a default **grammar** (`params.grammar`: `drama` ·
117
+ `action` · `faceslap` · `comedy`) for segments that do not name their own. `init` reads all of
118
+ them from the genre (古装喜剧 → `costume-idol` + `comedy` + `slapstick`; 未来动作 → `low-key` +
119
+ `bullet-time`). Confirm the composition with the user; when the story has fights or gags and
120
+ the style is `none`, ask. The recipes table is in `{baseDir}/reference/grammar.md`.
121
+ **The story genres** are `params.story`, a list that combines: `revenge` 复仇/逆袭 · `sweet` 甜宠 ·
122
+ `mystery` 悬疑 · `comedy` 喜剧 · `angst` 虐恋 · `family` 家庭伦理 · `palace` 宫斗/权谋 · `cultivation`
123
+ 玄幻/修仙/系统 · `scifi` 科幻 · `horror` 恐怖/灵异. `params.market` (`mainland` / `overseas`) decides
124
+ whether the supernatural may be real. They decide how the story is *written*: what the outline must
125
+ track (a payback ladder, romance milestones, a question and clue ledger, running gags) and the
126
+ genre rules it is held to. `init` reads them from the genre; confirm them with the user — read
127
+ `{baseDir}/reference/story.md`.
128
+ **Composition** is its own axis: `params.composition: art-first` (艺术优先) frames any genre for
129
+ negative space, symmetry and monumental scale — every cut names its devices
130
+ (`composition: [...]`), gated; read `{baseDir}/reference/composition-art-first.md`. A segment
131
+ may override it (`"composition": "standard"` for a chaotic fight).
132
+ `params.violence` is `blood` (default) or `implied`.
92
133
  4. **Locate or create the project directory** (the user's choice, else next to the source):
93
134
  ```bash
94
- node {baseDir}/scripts/short-drama.mjs init <dir> --title "<title>" --episodes N --minutes M --genre "<genre>" --register 台湾国语 [--adapt 抽核] [--ratio 9:16] [--video h3|seedance|seedance2|seedance2fast|wan] [--resolution 480P|…] [--accent "台湾口音国语"] [--style photoreal|realistic|ghibli] [--references sheets|keyframes] [--duration auto|fixed] [--look "<lens phrase>"] [--lang zh]
135
+ node {baseDir}/scripts/short-drama.mjs init <dir> --title "<title>" --episodes N --minutes M --genre "<genre>" --register 台湾国语 [--adapt 抽核] [--ratio 9:16] [--video h3|seedance|seedance2|seedance2fast|wan] [--resolution 480P|…] [--accent "台湾口音国语"] [--style photoreal|realistic|ghibli] [--references sheets|keyframes] [--duration auto|fixed] [--look "<lens phrase>"] [--cinematography low-key|sweet-romance|period-nostalgia|costume-idol|costume-dark|scifi-hard|scifi-soft|none] [--action gun-fu|hk-kungfu|wuxia-fantasy|historical-realist|bullet-time|superhero|street-brawl|car-chase|none] [--violence blood|implied] [--grammar drama|action|faceslap|comedy] [--comedy slapstick|deadpan|romcom|none] [--composition standard|art-first] [--story revenge,sweet,…] [--market mainland|overseas] [--format short|medium|movie] [--lang zh]
95
136
  # params.camera (body / lens / aperture) is edited in project.json
96
137
  ```
97
138
  An existing `project.json` means a resumed run: `validate` and `manifest` tell you where it stands.
@@ -129,10 +170,18 @@ Read `{baseDir}/reference/stage-outline.md`.
129
170
  `scenes[]` (id/name/primary/reusePlan), `props[]` (id/name/function/beatIds), `beats[]`,
130
171
  `outline.structure` (golden / escalation / climax with episode ranges) and `outline.guideline`
131
172
  (tone, intensity, dialogue style, visual style, limits, ≥ 3 forbidden, 3–5 priorities).
173
+ **Per story genre** (`params.story`), read its guide (`{baseDir}/reference/story-<genre>.md`)
174
+ and write its ledger with the skeleton: the antagonist tiers and the payback ladder
175
+ (`revenge`), the romance milestones (`sweet`), the central question, the culprit and the clue
176
+ ledger (`mystery`), the comic premise, comic flaws and running gags (`comedy`), the 虐/悔/追 beats
177
+ and the engineered misbelief (`angst`), the family web, secrets and reconciliations (`family`),
178
+ the rank ladder, schemes and factions (`palace`), the power system, 金手指 and upgrade log
179
+ (`cultivation`), the novum, world rules and countdown (`scifi`), the threat, its rules and the
180
+ scare log (`horror`).
132
181
  `validate --stage outline` will still fail on `episodes` — that is expected at this point; read
133
182
  the other gates.
134
183
  3. **Sign-off ⛔**: show the user the brief, what was cut, who was merged, where the major beats
135
- land (`cutNote` / `mergeNote`) and the guideline's priorities. Do not write episode synopses
184
+ land (`cutNote` / `mergeNote`), the guideline's priorities and the genre ledgers. Do not write episode synopses
136
185
  before they agree — a wrong skeleton wastes every downstream stage.
137
186
  4. **Episodes** in batches of ≤10 — design before prose: `function` / `task` / `change` /
138
187
  `satisfaction` / `hookType` (the 分集目录; episode 1 grabs, 2 proves the 爽点, 3 widens the line,
@@ -173,10 +222,11 @@ Read `{baseDir}/reference/stage-characters.md`.
173
222
 
174
223
  ## Stage 3 — Art (scenes + props)
175
224
 
176
- Read `{baseDir}/reference/stage-art.md`.
225
+ Read `{baseDir}/reference/stage-art.md`, and the cinematography preset's guide
226
+ (`{baseDir}/reference/cinematography-<preset>.md`, §1 and §2) before writing a lighting state.
177
227
 
178
228
  1. Scenes: `summary`, 3–5 `anchors`, `lighting` states derived from the episodes the scene appears
179
- in, empty-scene `image.*`; variants via `variantOf` + `changes`. Props (only narrative ones):
229
+ in (in the preset's look, lit by named sources), empty-scene `image.*`; variants via `variantOf` + `changes`. Props (only narrative ones):
180
230
  `scale`, `anchors`, `states`, white-background `image.*`. `validate --stage art`.
181
231
  2. Sync to `location/` and `prop/` datasets; `scenes` canvas: one sheet node per scene (16:9),
182
232
  lighting / variant nodes `--from` the parent sheet; `props` canvas (`wji canvas create props`):
@@ -194,7 +244,10 @@ Read `{baseDir}/reference/stage-script.md`.
194
244
  ids, previous cliff, budget). Fill `scenes[].flow` — action beats and line beats alternating,
195
245
  **common actions only**, hook image inside the first 3 beats and by 15 s, a `turn: true` beat
196
246
  at least every 30 s, the 爽点 on an action (`satisfactionBeat`), cliff as the last beat; lines
197
- in the register, from each speaker's `speech` (`dialogue-craft.md`). Delete `seedNote` when done.
247
+ in the register, from each speaker's `speech` (`dialogue-craft.md`). Genre markers on the beats:
248
+ `clue: "K1"` on the beat that plants a mystery clue in its plant episode; `gag: setup / build /
249
+ punchline` and `runningGag` in a comedy (a setup before every punchline, punchlines ≤ 20 字).
250
+ Delete `seedNote` when done.
198
251
  2. `validate --stage script --ep N` — duration math, line length, speakers, hook window, the
199
252
  rhythm, the design, filmability, upstream ids; the register gate only advises. Over budget: cut
200
253
  actions first; under: add conflict, not small talk. When dialogue is the weakness, run the
@@ -212,6 +265,31 @@ plus `light.note` saying what it picks out, what fills the shadow side and how i
212
265
  cut) and the **foreground** to throw out of focus. All three are gated and all three go into the
213
266
  prompt, alongside the camera package and each speaker's delivery.
214
267
 
268
+ **Every cut is lit and composed in the preset's look.** Read the preset's guide
269
+ (`{baseDir}/reference/cinematography-<preset>.md`) before the first storyboard. Name the source
270
+ in `light.source`, stage the person against it, and choose the camera side that gives the
271
+ preset's preferred direction (the table in `cinematography.md`). Compose as the guide says,
272
+ shoot past a foreground, and stay within the preset's stop (T2.8; T2 for `sweet-romance` and `costume-idol`, which
273
+ start on a T1.3 package and name their bokeh; T4 for `period-nostalgia`). Both costume presets (`costume-idol`, `costume-dark`) also need a 全景 / 大全景 with negative
274
+ space in every segment.
275
+ For `sweet-romance` and `costume-idol`, shoot the 甜宠 set pieces (心动对视, 回眸, 共伞…) as
276
+ `cinematography-sweet-romance.md` §6 lays out.
277
+
278
+ **Each segment is cut in its grammar.** Set `grammar` on a segment whose kind differs from the
279
+ drama's default (`"action"`, `"faceslap"`, `"comedy"`, `"drama"`, or several:
280
+ `["action", "comedy"]`) and read `{baseDir}/reference/grammar.md` plus the guide of each grammar
281
+ used before cutting it:
282
+ - `action` (`action.md`, `action-<style>.md`) — break the fight into moves (拆招): one move per
283
+ cut (`move: { kind, impact }`), open on a wide, a reaction or consequence after every impact.
284
+ - `faceslap` (`grammar-faceslap.md`) — pressure, the calm, the entrance, **the proof on screen**,
285
+ **a cascade of at least two reactions**. The 爽点 of a 打脸 / 身份揭露 / 智商碾压 / 复仇 episode
286
+ is cut this way.
287
+ - `comedy` (`grammar-comedy.md`) — **a setup before every punchline** (`gag`), **laugh room
288
+ after it**, in the comedy style's rhythm.
289
+ The bold rules are gated. Waive one for a segment (`exceptions: [{ rule, reason }]`) only when the
290
+ user asks for it, never to turn a gate green. `prompt segment` writes each grammar's line
291
+ (`Action:` / `Payoff:` / `Comedy:`). No film, franchise or performer names in any prompt (gated).
292
+
215
293
  **And every cut is a shot with a job** — read `{baseDir}/reference/craft-shots.md` and
216
294
  `{baseDir}/reference/craft-motion.md` before the first storyboard. In both reference modes a cut
217
295
  carries its `purpose` (kind + what changes), its three framing axes (`angle`, `height`, `axisSide`
@@ -0,0 +1,47 @@
1
+ # Action style `bullet-time` — 子弹时间
2
+
3
+ **The fight:** time itself is the effect. At the peak of a move, time slows to a near freeze; the
4
+ camera **orbits the frozen moment**; bullets crawl through the air leaving rippling trails;
5
+ debris, casings and raindrops hang suspended; then everything snaps back to full speed. Between
6
+ those moments, the fighting is sleek, acrobatic and precise. Shared rules and fields: `action.md`.
7
+
8
+ **Look:** `low-key`, graded cold (green-cyan or steel blue) with hard highlights; sleek black
9
+ clothing, sunglasses, long coats. Lobbies with pillars, rooftops, subway platforms, rain.
10
+
11
+ ## Rhythm and coverage
12
+
13
+ - **Cuts are medium to long** (3–5 s), because the frozen moment needs time to be circled.
14
+ - **Sizes**: medium and wide around the frozen moment (the whole body mid-air, the bullets in
15
+ frame); extreme-close for a bullet or a drop of water.
16
+ - **Camera**: the **`Arc Shot`** is the style — at least one per fight, circling a subject frozen
17
+ mid-move. `Push In` to a bullet; `Static Shot` for the snap back to speed.
18
+ - **Speed**: the freeze and the snap back are **two cuts** (one move per cut, gated): the frozen
19
+ cut says `time slows to a near freeze as …, in slow motion`, the next cut says `time snaps back
20
+ to full speed as …`. One freeze per sequence: too many and none of them lands.
21
+
22
+ ## Signature sequences
23
+
24
+ | sequence | cuts |
25
+ | --- | --- |
26
+ | **the dodge** | wide: the gunman fires across the rooftop (shot) → medium, Arc Shot: time freezes as she bends backwards, bullets crawling past above her with rippling trails (dodge, slow motion) → wide: time snaps back and she springs upright (consequence) |
27
+ | **the frozen kick** | medium: he leaps into a spinning kick (leap) → medium, Arc Shot: time freezes mid-air, the camera circling him, rain suspended (kick, slow motion) → close: the kick lands at full speed (kick, impact) → wide: the opponent crashes through a pillar (consequence) |
28
+ | **the lobby** | wide: the pillared lobby, guards turning (establish) → medium: she fires, casings hanging in the air in slow motion (shot, impact) → wide: pillar fragments exploding and hanging, then falling (consequence) |
29
+ | **the bullet** | extreme-close: a bullet crawling through the air, the air rippling around it (shot, slow motion) → close: his raised hand, the bullet stopping in front of his palm (power) |
30
+
31
+ ## Sound
32
+
33
+ The world slowing: sounds stretching and dropping in pitch, a deep boom, a single heartbeat, then
34
+ everything snapping back loud. Music: electronic, pulsing; `drop` into the freeze, `enter` hard on
35
+ the snap back.
36
+
37
+ ## Example `motion.action`
38
+
39
+ - `she bends backwards as the bullets pass above her, in slow motion`
40
+ - `time freezes as he spins into the kick mid-air, in slow motion` (the next cut: `time snaps back to full speed as the kick lands`)
41
+ - `a bullet crawls toward his palm and stops in the air`
42
+
43
+ ## Review questions
44
+
45
+ - Is there an Arc Shot around a frozen moment?
46
+ - Can you see the bullets, the casings, the rain hanging in the air?
47
+ - Does time snap back hard after the freeze?
@@ -0,0 +1,51 @@
1
+ # Action style `car-chase` — 飞车追逐
2
+
3
+ **The chase:** speed you can feel, cut between **inside** (the driver's hands, eyes, the mirror)
4
+ and **outside** (the cars racing low past the camera, drifting through turns, near misses with
5
+ traffic), building to a crash or an escape. A manoeuvre is a move: one swerve, drift, ram or
6
+ crash per cut. Shared rules and fields: `action.md`.
7
+
8
+ **Look:** `none` for daylight, `low-key` for night streets (tail lights and neon streaking in the
9
+ windows, wet asphalt).
10
+
11
+ ## Rhythm and coverage
12
+
13
+ - **Cuts are short**: 2–3 s, getting shorter toward the crash.
14
+ - **Sizes**: wide and extreme wide outside (the cars in the street, the traffic, the turn);
15
+ medium and close inside (the driver, the gear stick, the mirror with the pursuer in it).
16
+ - **Camera**: `Tracking Shot` low alongside the car (the style's shot), a static camera at road
17
+ level as a car roars past, `Pan Left` / `Pan Right` following a drift, `Shake Slightly` inside
18
+ the car.
19
+ - **Alternate inside and outside**: never three outside cuts in a row without the driver's face.
20
+
21
+ ## Signature sequences
22
+
23
+ | sequence | cuts |
24
+ | --- | --- |
25
+ | **the start** | close: the driver's hand on the gear stick (establish) → wide, road level: the car tears away, tyres smoking (chase) → close: the mirror, headlights appearing behind (pressure) |
26
+ | **the drift** | wide, high: the car slides sideways through the intersection (swerve) → medium inside: the driver fighting the wheel (performance) → wide: the pursuer misses the turn (consequence) |
27
+ | **the near miss** | low tracking: the car weaves between two trucks (swerve) → close inside: her eyes (reaction) → wide: the truck's mirror clipped off (consequence) |
28
+ | **the ram** | medium: the pursuer rams the rear bumper (crash, impact) → medium inside: the driver thrown forward (reaction) |
29
+ | **the crash** | wide: the pursuer's car hits the barrier and rolls (crash, impact) → wide: it comes to rest upside down, wheels spinning (consequence) → close inside: the driver breathing, watching it in the mirror (reaction) |
30
+
31
+ ## Staging
32
+
33
+ City streets at night, highways, tunnels, markets the car smashes through, mountain roads,
34
+ docks. Traffic is part of the choreography: trucks, buses, scooters, a crossing pedestrian.
35
+
36
+ ## Sound
37
+
38
+ Engines rising and falling through the gears, tyres screeching, horns doppler-shifting past,
39
+ metal crunching, glass, then the silence after the crash. Music: driving; `drop` at the crash.
40
+
41
+ ## Example `motion.action`
42
+
43
+ - `the car slides sideways through the intersection`
44
+ - `the black sedan rams the rear bumper`
45
+ - `she shifts down and floors the pedal`
46
+
47
+ ## Review questions
48
+
49
+ - Is there a low tracking shot alongside the car?
50
+ - Do inside and outside alternate, with the driver's face every few cuts?
51
+ - Does the chase build to a crash, an escape or a stop?
@@ -0,0 +1,52 @@
1
+ # Action style `gun-fu` — 近身枪斗
2
+
3
+ **The fight:** a professional working. Guns at arm's length, fused with throws, joint locks and
4
+ takedowns; every move efficient, nothing wasted. The camera stays back and **lets the whole body
5
+ be seen** in long takes, so the audience believes the skill. Violence is fast, clean and
6
+ procedural: reload, clear the room, move on. Shared rules and fields: `action.md`.
7
+
8
+ **Look:** `low-key` (neon, wet streets, a club, a hotel corridor, a concrete stairwell). Colour
9
+ from practicals: magenta and cyan neon, sodium orange, muzzle flash.
10
+
11
+ ## Rhythm and coverage
12
+
13
+ - **Cuts are long**: 4–5 s is normal (average 3.5–5 s). Fewer, longer cuts than any other style.
14
+ - **Sizes are wide and medium**: the body in the frame, head to shoes. Close-ups are for the
15
+ gun (a reload insert) and the eyes (the decision), not for the blows.
16
+ - **Camera**: `Tracking Shot` following the fighter down a corridor, `Truck Left` / `Truck Right`
17
+ alongside a room clear, `Static Shot` wides for an exchange. Almost no shake: the camera is as
18
+ controlled as the fighter.
19
+ - **Speed**: real time. No slow motion except, rarely, the last shot of a sequence.
20
+
21
+ ## Signature sequences
22
+
23
+ | sequence | cuts |
24
+ | --- | --- |
25
+ | **the room clear** | wide: he enters, three men turn (establish) → medium tracking: he fires at the first (shot, impact) → wide: the man drops (consequence) → medium: he throws the second over his hip (throw, impact) → close: the second hits the floor (reaction) → medium: he fires down at him (shot) |
26
+ | **the reload** | medium: slide locks back, empty → extreme-close insert: the magazine drops and the new one slams in (reload) → medium: he raises the gun as the next man rounds the corner |
27
+ | **gun and grapple** | medium: the enemy grabs his gun arm (grapple) → medium: he twists the arm into a lock (grapple) → close: the enemy's face hits the wall (reaction) |
28
+ | **the stairwell** | high wide down the stairwell: the fighters on two landings (establish) → medium: he fires up through the gap (shot) → wide: a body falls past the rail (consequence) |
29
+
30
+ ## Staging and props
31
+
32
+ Corridors, stairwells, clubs, glass rooms, car parks: spaces with lines and cover. Tactical
33
+ suits, holsters, suppressors. Spent casings on the floor are the style's detail, and each fight
34
+ ends in a room of stillness and smoke.
35
+
36
+ ## Sound
37
+
38
+ Suppressed and unsuppressed shots with real weight, casings tinkling, the slide racking, bodies
39
+ hitting floors, heavy breath. Music: a driving low synth or bass under the whole fight
40
+ (`enter` on the first shot, `drop` for the final one).
41
+
42
+ ## Example `motion.action` (one move each)
43
+
44
+ - `he fires twice into the chest of the man at the bar` (one move: the shot)
45
+ - `he hooks the man's arm and throws him over his hip onto the tiles`
46
+ - `the empty magazine drops and he slams in a new one`
47
+
48
+ ## Review questions
49
+
50
+ - Can you see the whole body in the blows? Is the cut long enough to believe it?
51
+ - Does each cut hold one move, and does the next cut show what it did?
52
+ - Is there a reload, a grapple and a moment of stillness after?
@@ -0,0 +1,50 @@
1
+ # Action style `historical-realist` — 写实冷兵器
2
+
3
+ **The fight:** heavy, clumsy, desperate and final. Swords and spears have real weight; fighters
4
+ tire, slip in the mud, gasp inside armour; exchanges are short because a blade ends them. No
5
+ flying, no flourishes: the drama is the standoff before and the exhaustion after. Shared rules
6
+ and fields: `action.md`.
7
+
8
+ **Look:** `costume-dark` (firelight, smoke, earthy texture). Rain, mud and grey sky suit it.
9
+
10
+ ## Rhythm and coverage
11
+
12
+ - **Cuts are medium**: 2.5–4 s. The standoff can hold longer; the clash itself is quick.
13
+ - **Sizes**: medium and close, inside the fight, with a wide to open (the geography, the scale of
14
+ the battle) and to reset.
15
+ - **Camera**: handheld — `Shake Slightly` inside the fight, `Shake Strongly` in the chaos of a
16
+ battle — close to the fighters, low. `Static Shot` for the standoff.
17
+ - **Speed**: real time. At most one slow cut: the aftermath, the fighter on his knees.
18
+
19
+ ## Signature sequences
20
+
21
+ | sequence | cuts |
22
+ | --- | --- |
23
+ | **the standoff** | wide: two fighters facing across the mud, rain falling (establish) → close: his hand tightening on the hilt (stance) → close: her eyes (pressure) |
24
+ | **the clash** | medium handheld: he charges and swings (slash) → close: the blades bind, grinding (block, impact) → medium: she shoves him back into the mud (reaction) |
25
+ | **the killing blow** | medium: the spear thrusts in (stab, impact) → close: his face as it lands (reaction) → wide: he sinks to his knees in the rain (consequence) |
26
+ | **the battlefield** | extreme wide: two lines of soldiers in the smoke (establish) → medium handheld: the hero in the press, shields crashing (grapple) → close: a man falls at his feet (consequence) → wide: the hero alone in the smoke, breathing (performance) |
27
+ | **after** | medium: the victor leaning on the sword, breath steaming, blood on the blade (performance) |
28
+
29
+ ## Staging and props
30
+
31
+ Muddy courtyards, rain, city walls, burning villages, forests, battlefields in smoke. Armour that
32
+ creaks, dented helmets, notched blades, torn cloth. Blood, if `params.violence: blood`, is real
33
+ and restrained: on the blade, on the hands, spattered on a face.
34
+
35
+ ## Sound
36
+
37
+ Metal on metal, armour creaking, feet in mud, grunts and ragged breath, rain, the silence after.
38
+ Music: low drums or nothing; the style often plays better with no score.
39
+
40
+ ## Example `motion.action`
41
+
42
+ - `he swings the heavy blade down at her shoulder`
43
+ - `she catches it on her own blade and the two edges grind together`
44
+ - `he sinks to his knees in the mud, the sword slipping from his hand`
45
+
46
+ ## Review questions
47
+
48
+ - Does the weapon feel heavy? Does anyone tire?
49
+ - Is there a standoff before and a silence after?
50
+ - Is the camera close and handheld inside the fight?
@@ -0,0 +1,54 @@
1
+ # Action style `hk-kungfu` — 港式功夫
2
+
3
+ **The fight:** crisp, fast, readable kung fu. Every strike and block is framed full-body so the
4
+ audience can follow the exchange; the hero uses **whatever is to hand** (a chair, a ladder, a
5
+ bench, a mop, a steamer basket); the key hit lands with a speed ramp and is **shown twice from
6
+ two angles**; and between blows there is physical comedy: the shaken hand after hitting a
7
+ pot, the look to camera. Shared rules and fields: `action.md`.
8
+
9
+ **Look:** `none` or `low-key`. The moves must read, so the light is clearer than a thriller's: a
10
+ market, a restaurant kitchen, a warehouse, a construction site, a tea house.
11
+
12
+ ## Rhythm and coverage
13
+
14
+ - **Cuts are short**: 2–3 s (average 2–3 s; the 2 s minimum is the floor). Speed comes from the
15
+ rhythm of short cuts and ramps, not from 1-second cuts the model cannot render cleanly.
16
+ - **Sizes**: a locked-off **wide or medium** for the exchange, full body, both fighters in frame;
17
+ inserts on the prop and the hit.
18
+ - **Camera**: mostly `Static Shot` so the choreography reads; a quick `Pan Left` / `Pan Right`
19
+ (whip-like) to follow a throw; `Push In` on the comic reaction.
20
+ - **Speed**: write the ramp into the cut (`in a speed ramp, the kick slows at the moment of
21
+ contact and snaps back`). **The repeat**: the decisive hit is cut twice, the second from a
22
+ new angle (the 3-hit repeat for the big one).
23
+
24
+ ## Signature sequences
25
+
26
+ | sequence | cuts |
27
+ | --- | --- |
28
+ | **the prop fight** | wide: he backs into a kitchen, cleavers and pans everywhere (establish) → medium: he snatches a pan from the stove (draw) → medium: he blocks a pole with the pan (block, impact) → close: the pan rings and he shakes his stinging hand (reaction, comedy) |
29
+ | **the repeat hit** | wide: the villain charges (establish) → medium: the hero's flying kick lands, speed ramp (kick, impact) → wide from the side: the same kick again, the villain flying back (consequence) → close: the villain's stunned face on the floor (reaction) |
30
+ | **the ladder** | wide: three thugs close in (establish) → medium: he swings the ladder round (strike, impact) → wide: the three go down in a row (consequence) → medium: he steps through the rungs, grinning (performance) |
31
+ | **the pose** | after the last blow, a medium on the hero settling into a stance, a beat of stillness (stance) — the 定格 |
32
+
33
+ ## Staging and props
34
+
35
+ Markets, dim sum kitchens, bamboo scaffolding, fishing docks, warehouses, rooftops. Everyday
36
+ props as weapons are the style: chairs, benches, ladders, brooms, umbrellas, woks, bicycles.
37
+
38
+ ## Sound
39
+
40
+ Exaggerated punch and kick sfx (whooshes and snaps), the clang of props, the crowd gasping, a
41
+ comic sting after a joke. Music: percussive, driving; `duck` for the comic beat.
42
+
43
+ ## Example `motion.action`
44
+
45
+ - `he swings the wooden bench round into the thug's ribs`
46
+ - `she ducks under the pole` (a dodge is one move)
47
+ - `he shakes his stinging hand and blows on his knuckles` (the comic beat)
48
+
49
+ ## Review questions
50
+
51
+ - Can you follow every exchange — both fighters in frame, full body?
52
+ - Is there a prop in the hero's hands at least once?
53
+ - Is the decisive hit shown twice, with a ramp?
54
+ - Is there one laugh inside the fight?
@@ -0,0 +1,49 @@
1
+ # Action style `street-brawl` — 写实街头格斗
2
+
3
+ **The fight:** ugly and desperate. Two or a few bodies in a space too small for them — a
4
+ corridor, a stairwell, a bathroom, an alley — slamming into walls and doors, grappling, falling,
5
+ getting up exhausted. No technique on display; survival. The camera is in there with them,
6
+ jostled. Shared rules and fields: `action.md`.
7
+
8
+ **Look:** `low-key` (a single bulb in a corridor, fluorescent tubes in a toilet, a sodium lamp in
9
+ an alley). Sweat, rain, grime.
10
+
11
+ ## Rhythm and coverage
12
+
13
+ - **Cuts are short to medium**: 2–3.5 s.
14
+ - **Sizes**: medium and close, cramped; a wide down the corridor to open and to reset (the long
15
+ corridor fight is the signature: one side of the frame to the other).
16
+ - **Camera**: handheld (`Shake Slightly`; `Shake Strongly` in the worst of it), close, at chest
17
+ height. A `Truck Left` / `Truck Right` along a corridor for the one-take feeling.
18
+ - **Speed**: real time. Nothing stylised.
19
+
20
+ ## Signature sequences
21
+
22
+ | sequence | cuts |
23
+ | --- | --- |
24
+ | **the corridor** | wide down the corridor: one man against six (establish) → wide, truck: he swings a hammer at the first (strike, impact) → medium: the man slams into the wall (reaction) → wide, truck: the crowd pushes him back along the corridor (grapple) |
25
+ | **the wall** | medium handheld: he drives the other man backwards into the door (grapple, impact) → close: the door frame splinters (consequence) |
26
+ | **the ground** | medium: both fall to the floor, grappling (grapple) → close: a hand reaching for a knife on the tiles (pressure) → close: the knife skids away under a kick (kick, impact) → medium: both lie gasping (consequence) |
27
+ | **after** | medium: he gets up slowly, one hand on the wall, bleeding from the brow (performance) |
28
+
29
+ ## Staging and props
30
+
31
+ Corridors, stairwells, toilets, kitchens, alleys, cramped apartments, a car's back seat. Whatever
32
+ is there: a hammer, a bottle, a chair, a fire extinguisher, a door.
33
+
34
+ ## Sound
35
+
36
+ Bodies on walls and doors, breaking glass, grunts, ragged breath, shoes on tiles, a phone
37
+ buzzing in the middle of it. Music: none or a low drone.
38
+
39
+ ## Example `motion.action`
40
+
41
+ - `he slams the man backwards into the door`
42
+ - `both men fall to the tiles, grappling`
43
+ - `he gets up slowly with one hand on the wall`
44
+
45
+ ## Review questions
46
+
47
+ - Is the space too small? Does the fight use its walls, doors and floor?
48
+ - Is the camera handheld and close?
49
+ - Does anyone look tired or hurt after?