@milenyumai/film-kit 2.3.2 → 2.3.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/README.md CHANGED
@@ -558,10 +558,7 @@ These subpackages are marked `private: true` and are bundled as internal sources
558
558
  Maintainer release checklist:
559
559
 
560
560
  ```bash
561
- npm run typecheck
562
- npm test
563
- npm run build
564
- npm pack --json --dry-run
561
+ npm run release:check
565
562
  ```
566
563
 
567
564
  Publish the single public package:
@@ -572,6 +569,12 @@ npm whoami
572
569
  npm publish --access public
573
570
  ```
574
571
 
572
+ If the account has npm two-factor authentication enabled for publish, include the current one-time password from the authenticator app:
573
+
574
+ ```bash
575
+ npm publish --access public --otp=123456
576
+ ```
577
+
575
578
  Optional validation before publish:
576
579
 
577
580
  ```bash
package/build/lib/cli.js CHANGED
@@ -414,7 +414,7 @@ async function parseGenerateStoryboardCommand(args, rootDir) {
414
414
  break;
415
415
  }
416
416
  }
417
- if (!brief) {
417
+ if (!brief?.trim()) {
418
418
  throw new Error("generate-storyboard requires --brief.");
419
419
  }
420
420
  if (characterRefs.length === 0) {
@@ -41,6 +41,37 @@ function formatProviderTokens(bundle) {
41
41
  })
42
42
  .join("\n");
43
43
  }
44
+ function getProviderToken(bundle, role, fallback) {
45
+ return Object.values(bundle.providerAssetMapping)
46
+ .find(entry => entry.role === role)?.token ?? fallback;
47
+ }
48
+ function formatCoverageReferences(bundle) {
49
+ const interpretation = bundle.storyboardInterpretation;
50
+ const characterTokens = Object.values(bundle.providerAssetMapping)
51
+ .filter(entry => entry.role === "character_identity")
52
+ .map(entry => entry.token)
53
+ .join(", ") || "@Image1";
54
+ const storyboardToken = getProviderToken(bundle, "storyboard_plan", "@Image2");
55
+ const visualWorld = interpretation.visualWorld;
56
+ const camera = interpretation.cameraPlan;
57
+ const firstPhase = interpretation.phases[0];
58
+ const lastPhase = interpretation.phases.at(-1) ?? firstPhase;
59
+ return `## Coverage References In Same File
60
+
61
+ Coverage is standalone editorial material for this shot only. Do not chain coverage into the next main shot. Keep the same generated character identity, wardrobe, props, lighting direction, environment, screen direction, and visual_world.
62
+
63
+ ### ${bundle.shotId}A - ECU Reaction | 3s
64
+
65
+ \`\`\`text
66
+ Use ${characterTokens} as the exact identity source and ${storyboardToken} only for scene continuity. Extreme close-up reaction coverage during ${firstPhase?.timeRange ?? "the opening beat"}: ${firstPhase?.subjectAction ?? "the character registers the story beat"}. Keep ${visualWorld.lighting}, ${visualWorld.atmosphere}, and ${camera.screenDirection}. Static or barely drifting camera, shallow depth of field, natural skin texture, visible breath or eye tension, no new action beat, no new identity. Audio: close breath, subtle clothing sound, matched ambience, Music: NONE. Avoid: identity drift, face drift, wardrobe drift, beauty filter, plastic skin, distorted eyes, flicker, warping, on-screen text, watermark, logo.
67
+ \`\`\`
68
+
69
+ ### ${bundle.shotId}B - Insert / Environmental Detail | 3s
70
+
71
+ \`\`\`text
72
+ Use ${storyboardToken} only for composition and environment continuity; do not introduce a new character identity. Tight insert coverage tied to ${lastPhase?.timeRange ?? "the final beat"}: ${lastPhase?.subjectAction ?? "the action resolves into a clear visual detail"}. Show a physically plausible detail from the same visual world: foreground texture, prop contact, reflection, light spill, hand tension, fabric motion, or environment response. Keep ${visualWorld.environment}; ${visualWorld.foreground}; ${visualWorld.background}; ${visualWorld.lighting}. Audio: matched practical SFX and ambience, no dialogue unless explicitly present in the main Audio Plan, Music: NONE. Avoid: new location, new character, clear logo, readable text, impossible physics, wrong reflection angle, identity override, flicker, warping, cartoon style, CGI look.
73
+ \`\`\``;
74
+ }
44
75
  export function renderShotMarkdown(bundle) {
45
76
  const interpretation = bundle.storyboardInterpretation;
46
77
  const audioPlan = getAudioPlan(bundle);
@@ -104,6 +135,8 @@ ${JSON.stringify(audioPlan ?? null, null, 2)}
104
135
 
105
136
  ${modelPrompts}
106
137
 
138
+ ${formatCoverageReferences(bundle)}
139
+
107
140
  ## QA
108
141
  ${formatQa(bundle)}
109
142
  `;
@@ -99,8 +99,12 @@ export function validateModelPromptOutput(output) {
99
99
  }
100
100
  export function validatePromptBundle(bundle, assets, voiceCast = []) {
101
101
  const roleIssues = validateResolvedAssetRoles(assets);
102
+ const modelValidationResults = Object.values(bundle.modelPrompts)
103
+ .filter((output) => Boolean(output))
104
+ .map(validateModelPromptOutput);
102
105
  const modelIssues = Object.values(bundle.modelPrompts)
103
- .flatMap(output => output?.qa.issues ?? []);
106
+ .flatMap(output => output?.qa.issues ?? [])
107
+ .concat(modelValidationResults.flatMap(result => result.issues));
104
108
  const voiceCastKeys = new Set(voiceCast.map(entry => entry.speakerKey));
105
109
  const audioPlans = Object.values(bundle.modelPrompts)
106
110
  .map(output => output?.audioPlan)
@@ -110,9 +114,10 @@ export function validatePromptBundle(bundle, assets, voiceCast = []) {
110
114
  hasStoryboardReference: assets.storyboards.length >= 1 || Boolean(bundle.storyboardImagePrompt),
111
115
  hasStoryboardImagePrompt: Boolean(bundle.storyboardImagePrompt?.promptText.trim()),
112
116
  storyboardPromptHasReferenceLockStructure: hasGptImageStillPromptContract(bundle.storyboardImagePrompt?.promptText ?? ""),
113
- hasAvoidLineEverywhere: Object.values(bundle.modelPrompts).every(output => Boolean(output?.qa.checks.hasAvoidLine ?? output?.qa.checks.hasAvoidOrNegative)),
117
+ hasAvoidLineEverywhere: Object.values(bundle.modelPrompts).every(output => Boolean(output) && hasAvoidOrNegativePrompt(output.promptText)),
114
118
  storyboardPromptHasAvoidLine: hasAvoidOrNegativePrompt(bundle.storyboardImagePrompt?.promptText ?? ""),
115
- modelGrammarPasses: Object.values(bundle.modelPrompts).every(output => output?.qa.verdict === "pass"),
119
+ modelGrammarPasses: Object.values(bundle.modelPrompts).every(output => output?.qa.verdict === "pass")
120
+ && modelValidationResults.every(result => result.verdict === "pass"),
116
121
  storyboardDoesNotOverrideIdentity: Object.values(bundle.modelPrompts).every(output => /storyboard.*staging|storyboard.*planning|storyboard.*composition|storyboard.*only/i.test(output?.promptText ?? "")),
117
122
  audioPlanPresent: Object.values(bundle.modelPrompts).every(output => Boolean(output?.audioPlan)),
118
123
  speakingAudioPlansHaveActiveSpeaker: audioPlans.every(plan => plan.dialogueTranscript === "NONE" || Boolean(plan.activeSpeakerKey)),
@@ -131,10 +136,20 @@ export function validatePromptBundle(bundle, assets, voiceCast = []) {
131
136
  };
132
137
  }
133
138
  export function assertStoryboardReferenceBuildPass(result) {
134
- const failedBundles = result.bundles.filter(bundle => bundle.qa.verdict !== "pass");
135
- const issues = failedBundles.flatMap(bundle => (bundle.qa.issues.length > 0
136
- ? bundle.qa.issues.map(issue => `${bundle.shotId}: ${issue}`)
137
- : [`${bundle.shotId}: bundle QA failed without a detailed issue.`]));
139
+ const issues = validateCharacterReferenceSheetPrompts(result.characterReferenceSheetPrompts)
140
+ .map(issue => `character-sheet: ${issue}`);
141
+ for (const bundle of result.bundles) {
142
+ const currentQa = validatePromptBundle(bundle, result.assets, result.plan.voiceCast);
143
+ if (bundle.qa.verdict !== "pass" || currentQa.verdict !== "pass") {
144
+ const bundleIssues = [
145
+ ...bundle.qa.issues,
146
+ ...currentQa.issues
147
+ ];
148
+ issues.push(...(bundleIssues.length > 0
149
+ ? bundleIssues.map(issue => `${bundle.shotId}: ${issue}`)
150
+ : [`${bundle.shotId}: bundle QA failed without a detailed issue.`]));
151
+ }
152
+ }
138
153
  if (result.bundles.length === 0) {
139
154
  issues.push("No storyboard-reference prompt bundles were generated.");
140
155
  }
@@ -11,7 +11,7 @@ export function buildCameraPlan(request) {
11
11
  lens: wideShot ? "35mm lens feel" : closeShot ? "85mm lens feel" : "50mm lens feel",
12
12
  framing: wideShot ? "layered wide composition" : closeShot ? "eye-level close framing" : "stable mid-shot framing",
13
13
  stabilization: "tripod-stable with minimal organic drift",
14
- screenDirection: "preserve the storyboard's screen direction and eyeline relationships"
14
+ screenDirection: "storyboard screen direction and eyeline relationships"
15
15
  };
16
16
  }
17
17
  export function buildVisualWorld(request) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milenyumai/film-kit",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "Single-package Film-Kit distribution with preset-driven cinematic runtime setup for OpenAI Codex App, Claude Code, Cursor, Copilot, and Antigravity.",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",
@@ -44,6 +44,7 @@
44
44
  "build:presets": "npm --prefix packages/multi run build && npm --prefix packages/hybrid run build && npm --prefix packages/hybrid-smart run build && npm --prefix packages/gpt-image-smart run build && npm --prefix packages/studio run build",
45
45
  "dev:setup": "tsx src/postinstall.ts",
46
46
  "postinstall": "node -e \"import('./build/postinstall.js').then(m=>m.runPostinstall()).catch(()=>{})\"",
47
+ "release:check": "npm run typecheck && npm test && npm run build && npm publish --access public --dry-run",
47
48
  "test": "vitest run",
48
49
  "test:watch": "vitest",
49
50
  "typecheck": "tsc -p tsconfig.json --noEmit"