plotlink-ows 1.0.32 → 1.2.94
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 +4 -0
- package/app/lib/agent-command.ts +85 -0
- package/app/lib/agent-readiness.ts +133 -0
- package/app/lib/apply-schema.ts +55 -0
- package/app/lib/bubble-text.ts +160 -0
- package/app/lib/cartoon-coach.ts +198 -0
- package/app/lib/cartoon-markdown.ts +83 -0
- package/app/lib/cartoon-prompt.ts +122 -0
- package/app/lib/cartoon-readiness.ts +811 -0
- package/app/lib/clean-image-sync.ts +245 -0
- package/app/lib/codex-images.ts +152 -0
- package/app/lib/cut-asset-diagnostics.ts +120 -0
- package/app/lib/cuts.ts +302 -0
- package/app/lib/fonts.ts +109 -0
- package/app/lib/generate-claude-md.ts +10 -3
- package/app/lib/generate-story-instructions.ts +731 -0
- package/app/lib/image-asset-validate.ts +123 -0
- package/app/lib/lettering-status.ts +133 -0
- package/app/lib/overlays.ts +637 -0
- package/app/lib/paths.ts +10 -0
- package/app/lib/public-title.ts +65 -0
- package/app/lib/publish.ts +16 -2
- package/app/lib/story-progress.ts +243 -0
- package/app/lib/terminal-protocol.ts +16 -0
- package/app/lib/terminal-redact.ts +50 -0
- package/app/prisma/schema.sql +25 -0
- package/app/routes/agent.ts +42 -0
- package/app/routes/codex-images.ts +67 -0
- package/app/routes/publish.ts +209 -28
- package/app/routes/stories.ts +961 -5
- package/app/routes/terminal.ts +383 -31
- package/app/server.ts +47 -12
- package/app/vite.config.ts +6 -0
- package/app/web/components/CartoonPreview.tsx +267 -0
- package/app/web/components/CartoonPublishPage.tsx +407 -0
- package/app/web/components/CartoonPublishPreview.tsx +121 -0
- package/app/web/components/CartoonStepGuide.tsx +90 -0
- package/app/web/components/CartoonWorkflowNav.tsx +68 -0
- package/app/web/components/CodexImportPicker.tsx +230 -0
- package/app/web/components/CutListPanel.tsx +1299 -0
- package/app/web/components/EpisodesPage.tsx +80 -0
- package/app/web/components/FinishEpisodePanel.tsx +151 -0
- package/app/web/components/Layout.tsx +7 -4
- package/app/web/components/LetteringEditor.tsx +1141 -0
- package/app/web/components/PreviewPanel.tsx +1017 -144
- package/app/web/components/Settings.tsx +63 -0
- package/app/web/components/StoriesPage.tsx +710 -33
- package/app/web/components/StoryBrowser.tsx +22 -14
- package/app/web/components/StoryInfoPage.tsx +266 -0
- package/app/web/components/StoryProgressPanel.tsx +516 -0
- package/app/web/components/TerminalPanel.tsx +233 -11
- package/app/web/components/WorkflowCoach.tsx +128 -0
- package/app/web/components/asset-image.tsx +114 -0
- package/app/web/components/asset-test-utils.ts +44 -0
- package/app/web/components/export-cut.ts +320 -0
- package/app/web/dist/assets/export-cut-nKQ_n2-J.js +1 -0
- package/app/web/dist/assets/index-BAZGwVwj.js +143 -0
- package/app/web/dist/assets/index-DoXH2OlP.css +32 -0
- package/app/web/dist/index.html +2 -2
- package/app/web/lib/cartoon-publish-summary.ts +43 -0
- package/app/web/lib/codex-import.ts +94 -0
- package/app/web/lib/image-compress.ts +53 -0
- package/app/web/lib/import-image.ts +58 -0
- package/app/web/lib/publish-helpers.ts +385 -0
- package/app/web/lib/upload-retry.ts +130 -0
- package/app/web/lib/verify-public-title.ts +105 -0
- package/app/web/styles.css +9 -0
- package/bin/plotlink-ows.js +53 -16
- package/bin/startup-plan.cjs +58 -0
- package/lib/genres.ts +92 -0
- package/package.json +60 -20
- package/scripts/gen-schema-sql.mjs +49 -0
- package/scripts/package-hygiene.mjs +116 -0
- package/scripts/preflight.mjs +173 -0
- package/scripts/start-smoke.mjs +128 -0
- package/app/node_modules/.prisma/local-client/client.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/client.js +0 -5
- package/app/node_modules/.prisma/local-client/default.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/default.js +0 -5
- package/app/node_modules/.prisma/local-client/edge.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/edge.js +0 -184
- package/app/node_modules/.prisma/local-client/index-browser.js +0 -173
- package/app/node_modules/.prisma/local-client/index.d.ts +0 -3304
- package/app/node_modules/.prisma/local-client/index.js +0 -207
- package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/app/node_modules/.prisma/local-client/package.json +0 -183
- package/app/node_modules/.prisma/local-client/query_engine_bg.js +0 -2
- package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
- package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +0 -35
- package/app/node_modules/.prisma/local-client/runtime/edge.js +0 -35
- package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +0 -370
- package/app/node_modules/.prisma/local-client/runtime/index-browser.js +0 -17
- package/app/node_modules/.prisma/local-client/runtime/library.d.ts +0 -3982
- package/app/node_modules/.prisma/local-client/runtime/library.js +0 -147
- package/app/node_modules/.prisma/local-client/runtime/react-native.js +0 -84
- package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +0 -85
- package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +0 -38
- package/app/node_modules/.prisma/local-client/schema.prisma +0 -21
- package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +0 -5
- package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +0 -5
- package/app/node_modules/.prisma/local-client/wasm.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/wasm.js +0 -191
- package/app/web/dist/assets/index-B-2Ft7Yv.css +0 -32
- package/app/web/dist/assets/index-BFw-v-OZ.js +0 -134
- package/packages/cli/node_modules/commander/LICENSE +0 -22
- package/packages/cli/node_modules/commander/Readme.md +0 -1149
- package/packages/cli/node_modules/commander/esm.mjs +0 -16
- package/packages/cli/node_modules/commander/index.js +0 -24
- package/packages/cli/node_modules/commander/lib/argument.js +0 -149
- package/packages/cli/node_modules/commander/lib/command.js +0 -2662
- package/packages/cli/node_modules/commander/lib/error.js +0 -39
- package/packages/cli/node_modules/commander/lib/help.js +0 -709
- package/packages/cli/node_modules/commander/lib/option.js +0 -367
- package/packages/cli/node_modules/commander/lib/suggestSimilar.js +0 -101
- package/packages/cli/node_modules/commander/package-support.json +0 -16
- package/packages/cli/node_modules/commander/package.json +0 -82
- package/packages/cli/node_modules/commander/typings/esm.d.mts +0 -3
- package/packages/cli/node_modules/commander/typings/index.d.ts +0 -1045
- package/packages/cli/node_modules/resolve-from/index.d.ts +0 -31
- package/packages/cli/node_modules/resolve-from/index.js +0 -47
- package/packages/cli/node_modules/resolve-from/license +0 -9
- package/packages/cli/node_modules/resolve-from/package.json +0 -36
- package/packages/cli/node_modules/resolve-from/readme.md +0 -72
- package/packages/cli/node_modules/tsup/LICENSE +0 -21
- package/packages/cli/node_modules/tsup/README.md +0 -75
- package/packages/cli/node_modules/tsup/assets/cjs_shims.js +0 -13
- package/packages/cli/node_modules/tsup/assets/esm_shims.js +0 -9
- package/packages/cli/node_modules/tsup/assets/package.json +0 -3
- package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +0 -153
- package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +0 -42
- package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +0 -6
- package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +0 -352
- package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +0 -203
- package/packages/cli/node_modules/tsup/dist/cli-default.js +0 -12
- package/packages/cli/node_modules/tsup/dist/cli-main.js +0 -8
- package/packages/cli/node_modules/tsup/dist/cli-node.js +0 -14
- package/packages/cli/node_modules/tsup/dist/index.d.ts +0 -511
- package/packages/cli/node_modules/tsup/dist/index.js +0 -1711
- package/packages/cli/node_modules/tsup/dist/rollup.js +0 -6949
- package/packages/cli/node_modules/tsup/package.json +0 -99
- package/packages/cli/node_modules/tsup/schema.json +0 -362
- package/public/screenshot-1.png +0 -0
- package/public/screenshot-2.png +0 -0
- package/public/screenshot-3.png +0 -0
- package/scripts/e2e-verify.ts +0 -1100
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Cut } from "./cuts";
|
|
2
|
+
|
|
3
|
+
const MARKER_START = (id: string) => `<!-- ows:cartoon-cut ${id} start -->`;
|
|
4
|
+
const MARKER_END = (id: string) => `<!-- ows:cartoon-cut ${id} end -->`;
|
|
5
|
+
// Matches each existing cut-block START marker (capturing its id), used only to
|
|
6
|
+
// report stale blocks — blocks that existed but no longer map to a cut.
|
|
7
|
+
const START_MARKER_REGEX = /<!-- ows:cartoon-cut (cut-\d+) start -->/g;
|
|
8
|
+
|
|
9
|
+
function cutId(index: number): string {
|
|
10
|
+
return `cut-${String(index).padStart(3, "0")}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function generateCutBlock(cut: Cut, index: number): string {
|
|
14
|
+
const id = cutId(index);
|
|
15
|
+
const desc = cut.description || `Cut ${index}`;
|
|
16
|
+
|
|
17
|
+
// Every cut is a planned image cut. The publish-facing markdown only carries
|
|
18
|
+
// the uploaded image once it exists; before that we emit a safe awaiting-upload
|
|
19
|
+
// marker comment. We never copy dialogue/narration prose from cuts.json into
|
|
20
|
+
// the skeleton — those texts are lettered onto the image, not published as text.
|
|
21
|
+
const content = cut.uploadedUrl
|
|
22
|
+
? ``
|
|
23
|
+
: `<!-- Cut ${index}: awaiting upload -->`;
|
|
24
|
+
|
|
25
|
+
return `${MARKER_START(id)}\n${content}\n${MARKER_END(id)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function generateCartoonMarkdown(cuts: Cut[]): string {
|
|
29
|
+
return cuts.map((cut, i) => generateCutBlock(cut, i + 1)).join("\n\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate the publish-facing cartoon markdown for a plot from its cut plan.
|
|
34
|
+
*
|
|
35
|
+
* Publish-facing cartoon markdown is a PURE `ows:cartoon-cut` image sequence, so
|
|
36
|
+
* the output is rebuilt entirely from `cuts` rather than edited in place: no
|
|
37
|
+
* surrounding prose from `existingMd` — scaffold instructions, stale placeholders,
|
|
38
|
+
* headings, manual commentary — can survive into it (#319). Rebuilding (rather
|
|
39
|
+
* than the earlier strip-and-replace) also closes the case @re1 flagged where
|
|
40
|
+
* prose sat in the same blank-line paragraph as a marker block (e.g.
|
|
41
|
+
* `Intro\n<!-- ...start -->\n…\n<!-- ...end -->\nOutro`) and leaked through a
|
|
42
|
+
* block-only regex replace.
|
|
43
|
+
*
|
|
44
|
+
* `existingMd` is consulted only to (a) leave a non-cartoon document — markerless
|
|
45
|
+
* and with no cuts, i.e. fiction — untouched, and (b) report cut blocks that
|
|
46
|
+
* existed before but no longer map to a cut ("stale" blocks).
|
|
47
|
+
*/
|
|
48
|
+
export function mergeCartoonMarkdown(
|
|
49
|
+
existingMd: string,
|
|
50
|
+
cuts: Cut[],
|
|
51
|
+
): { markdown: string; warnings: string[] } {
|
|
52
|
+
const warnings: string[] = [];
|
|
53
|
+
|
|
54
|
+
// A markerless doc with no cuts is fiction — leave it untouched. (The route
|
|
55
|
+
// already guards on cuts.json, but keep the function safe in isolation.)
|
|
56
|
+
const isCartoonDoc = cuts.length > 0 || /ows:cartoon-cut/.test(existingMd);
|
|
57
|
+
if (!isCartoonDoc) return { markdown: existingMd, warnings };
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < cuts.length; i++) {
|
|
60
|
+
if (!cuts[i].uploadedUrl) {
|
|
61
|
+
warnings.push(`Cut ${i + 1}: missing upload URL`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Warn about cut marker blocks present in the old markdown that no longer map
|
|
66
|
+
// to a cut, so a removed/renumbered cut is not silently dropped.
|
|
67
|
+
const newIds = new Set<string>(cuts.map((_, i) => cutId(i + 1)));
|
|
68
|
+
for (const m of existingMd.matchAll(START_MARKER_REGEX)) {
|
|
69
|
+
if (!newIds.has(m[1])) warnings.push(`Removed stale block: ${m[1]}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { markdown: generateCartoonMarkdown(cuts), warnings };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getReadinessWarnings(cuts: Cut[]): string[] {
|
|
76
|
+
const warnings: string[] = [];
|
|
77
|
+
for (let i = 0; i < cuts.length; i++) {
|
|
78
|
+
if (!cuts[i].uploadedUrl) {
|
|
79
|
+
warnings.push(`Cut ${i + 1}: not uploaded`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return warnings;
|
|
83
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Cut } from "./cuts";
|
|
2
|
+
import { cutScriptLines } from "./lettering-status";
|
|
3
|
+
|
|
4
|
+
const SHOT_TYPE_LABELS: Record<string, string> = {
|
|
5
|
+
wide: "Wide",
|
|
6
|
+
medium: "Medium",
|
|
7
|
+
"close-up": "Close-up",
|
|
8
|
+
"extreme-close-up": "Extreme close-up",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const NO_TEXT_CONSTRAINT =
|
|
12
|
+
"No speech bubbles, captions, sound effects, narration, or any text or lettering in the image.";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Baseline visual style lock for every clean cut image (#404).
|
|
16
|
+
*
|
|
17
|
+
* Image generation drifts toward polished photoreal / painterly concept art unless
|
|
18
|
+
* the prompt fights it explicitly — "semi-realistic webtoon" alone is too weak (it
|
|
19
|
+
* was the #211 Sci-fi pilot's failure mode). This block pins the requested
|
|
20
|
+
* illustrated-panel look with strong positive descriptors AND hard negative
|
|
21
|
+
* constraints (no photorealism, no painterly concept art, no 3D render), so it is
|
|
22
|
+
* reusable across every cut and the agent-facing "Copy Codex task" prompt without
|
|
23
|
+
* each cut re-stating it. Story-specific style (palette, line weight, the exact
|
|
24
|
+
* webtoon reference) is layered on top via structure.md's Visual Style Guide.
|
|
25
|
+
*/
|
|
26
|
+
export const CLEAN_IMAGE_STYLE_LOCK =
|
|
27
|
+
"Style lock — illustrated comic/webtoon panel art: clean black contour/ink lines, " +
|
|
28
|
+
"flat or cel shading, simplified but realistic (semi-realistic) anatomy and faces, " +
|
|
29
|
+
"backgrounds drawn as illustrated comic panels. Hold this same style on every cut for " +
|
|
30
|
+
"character and panel consistency. " +
|
|
31
|
+
"Hard negatives — NOT photorealistic, NOT a photograph, NOT a glossy or painterly digital " +
|
|
32
|
+
"painting, NOT concept art, NOT a 3D/CGI render, NOT airbrushed, no photoreal textures.";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build a deterministic clean-image generation prompt from a cut's fields only.
|
|
36
|
+
*
|
|
37
|
+
* Pure function — no side effects. Dialogue, narration, and SFX text are
|
|
38
|
+
* intentionally excluded: those are lettered onto the image later, not drawn.
|
|
39
|
+
*/
|
|
40
|
+
export function buildCleanImagePrompt(cut: Cut): string {
|
|
41
|
+
const shotLabel = SHOT_TYPE_LABELS[cut.shotType] ?? cut.shotType;
|
|
42
|
+
const description = cut.description?.trim() || `Cut ${cut.id}`;
|
|
43
|
+
|
|
44
|
+
const lines: string[] = [`${shotLabel} shot. ${description}`];
|
|
45
|
+
|
|
46
|
+
if (cut.characters.length > 0) {
|
|
47
|
+
lines.push(`Characters: ${cut.characters.join(", ")}.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
lines.push(CLEAN_IMAGE_STYLE_LOCK);
|
|
51
|
+
lines.push(NO_TEXT_CONSTRAINT);
|
|
52
|
+
|
|
53
|
+
return lines.join("\n").trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Canonical clean-image output path for a cut (webp, matching the sync/import contract). */
|
|
57
|
+
export function cleanImageOutputPath(plotFile: string, cutId: number): string {
|
|
58
|
+
return `assets/${plotFile}/cut-${String(cutId).padStart(2, "0")}-clean.webp`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build an actionable Codex *task* prompt for generating a cut's clean image.
|
|
63
|
+
*
|
|
64
|
+
* Unlike `buildCleanImagePrompt` (a pure visual description), this instructs the
|
|
65
|
+
* agent to PRODUCE THE ACTUAL IMAGE and hand it off to the cut. A generated PNG in
|
|
66
|
+
* the image cache is an accepted outcome: the agent must NOT convert it (no
|
|
67
|
+
* agent-side image tools) — the writer imports it via the OWS "Import from Codex"
|
|
68
|
+
* button, which converts it. A tool that already emits WebP/JPEG <1MB can write the
|
|
69
|
+
* asset path directly. The visual prompt is embedded so no scene detail is lost.
|
|
70
|
+
* Pure function — no side effects.
|
|
71
|
+
*/
|
|
72
|
+
export function buildCodexTaskPrompt(cut: Cut, plotFile: string): string {
|
|
73
|
+
const outputPath = cleanImageOutputPath(plotFile, cut.id);
|
|
74
|
+
return [
|
|
75
|
+
`Generate the clean image for cut ${cut.id}.`,
|
|
76
|
+
"",
|
|
77
|
+
"Image description:",
|
|
78
|
+
buildCleanImagePrompt(cut),
|
|
79
|
+
"",
|
|
80
|
+
"How to hand it off:",
|
|
81
|
+
"- Produce the actual image — do not just describe it or return a prompt.",
|
|
82
|
+
`- If your image tool can write a WebP or JPEG under 1MB, save it at ${outputPath} and run "Sync clean images".`,
|
|
83
|
+
"- If it only produces a PNG (e.g. built-in image generation saves to ~/.codex/generated_images), that is fine — do NOT convert or rename it yourself. Leave it there and import it into this cut with the OWS \"Import from Codex\" button, which converts the PNG automatically.",
|
|
84
|
+
"- Clean image only: no text, speech bubbles, captions, sound effects, signage, watermark, or signature.",
|
|
85
|
+
"- Hold the style lock above — an illustrated comic/webtoon panel, NOT a photoreal photo, painterly concept art, or 3D render. If a result reads photorealistic, regenerate it as illustrated panel art.",
|
|
86
|
+
"- Do not letter or upload anything — final lettering and upload happen later in OWS.",
|
|
87
|
+
].join("\n");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build the "Ask AI to draft lettering" prompt for a cut (#442). The agent writes
|
|
92
|
+
* DRAFT speech bubbles/captions into the cut's `overlays` array in cuts.json from
|
|
93
|
+
* the recorded script; the writer then reviews/adjusts them in the OWS lettering
|
|
94
|
+
* editor and exports there. Intentionally a copy-paste prompt — no auto-apply, no
|
|
95
|
+
* export/upload — so the human stays in control of the final lettering. Pure.
|
|
96
|
+
*/
|
|
97
|
+
export function buildLetteringPrompt(cut: Cut, plotFile: string): string {
|
|
98
|
+
const cutsFile = `${plotFile}.cuts.json`;
|
|
99
|
+
const lines = cutScriptLines(cut);
|
|
100
|
+
const script = lines.length > 0
|
|
101
|
+
? lines
|
|
102
|
+
.map((l) =>
|
|
103
|
+
l.type === "speech"
|
|
104
|
+
? `- speech — ${l.speaker || "Speaker"}: "${l.text}"`
|
|
105
|
+
: l.type === "narration"
|
|
106
|
+
? `- narration: ${l.text}`
|
|
107
|
+
: `- sfx: ${l.text}`,
|
|
108
|
+
)
|
|
109
|
+
.join("\n")
|
|
110
|
+
: "- (no dialogue/narration/SFX recorded for this cut — add a caption only if the scene needs one)";
|
|
111
|
+
return [
|
|
112
|
+
`Draft the speech bubbles and captions for cut ${cut.id} of ${plotFile}.`,
|
|
113
|
+
"",
|
|
114
|
+
"Script to letter:",
|
|
115
|
+
script,
|
|
116
|
+
"",
|
|
117
|
+
"How to draft it:",
|
|
118
|
+
`- Edit cut ${cut.id}'s "overlays" array in ${cutsFile}: add one overlay per line above — "type":"speech" for dialogue (also set "speaker"), "narration" for captions, "sfx" for sound effects, with the line's text.`,
|
|
119
|
+
"- Position each overlay with x, y, width, height as 0–1 fractions of the panel, roughly where it belongs over the art, and keep bubbles clear of faces.",
|
|
120
|
+
"- These are DRAFT positions only: do NOT export or upload. The writer reviews and adjusts them in the OWS lettering editor, then exports the final image there.",
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|