plotlink-ows 1.0.33 → 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 +8 -1
- 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 +203 -22
- 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 +951 -78
- 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-DxATSk7X.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,731 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import type { AgentProvider } from "../routes/stories";
|
|
4
|
+
|
|
5
|
+
function fictionInstructions(): string {
|
|
6
|
+
return `# Writing Instructions — Fiction
|
|
7
|
+
|
|
8
|
+
> Auto-generated by PlotLink OWS. Do not edit manually.
|
|
9
|
+
> For API endpoints and publish details, see ~/.plotlink-ows/CLAUDE.md.
|
|
10
|
+
|
|
11
|
+
## Story File Structure
|
|
12
|
+
|
|
13
|
+
\`\`\`
|
|
14
|
+
structure.md — Story outline, characters, and arc
|
|
15
|
+
genesis.md — Synopsis hook (~1000 chars)
|
|
16
|
+
plot-01.md — Chapter 1 (max 10K chars)
|
|
17
|
+
plot-02.md — Chapter 2
|
|
18
|
+
...
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## structure.md Format
|
|
22
|
+
|
|
23
|
+
### Required Sections
|
|
24
|
+
|
|
25
|
+
1. **Core Concept** — One paragraph describing the premise
|
|
26
|
+
2. **Main Characters** — For each character:
|
|
27
|
+
- Age, Personality, Flaw, Arc
|
|
28
|
+
3. **Story Arc** — Beginning, Middle, End
|
|
29
|
+
4. **Chapter Plan** — Numbered list of planned chapters with one-line descriptions
|
|
30
|
+
5. **Progress Log** — Track what has been written
|
|
31
|
+
|
|
32
|
+
## genesis.md Format
|
|
33
|
+
|
|
34
|
+
The genesis is the story's hook — the first thing readers see on-chain.
|
|
35
|
+
|
|
36
|
+
- ~1000 characters (hard limit for on-chain genesis)
|
|
37
|
+
- Create immediate intrigue or emotional hook
|
|
38
|
+
- Introduce the core premise without spoilers
|
|
39
|
+
- End with a question or tension that pulls readers forward
|
|
40
|
+
|
|
41
|
+
## plot-NN.md Format
|
|
42
|
+
|
|
43
|
+
Each chapter is a self-contained prose section:
|
|
44
|
+
|
|
45
|
+
- Maximum 10,000 characters per file
|
|
46
|
+
- Number sequentially: plot-01.md, plot-02.md, etc.
|
|
47
|
+
- Each chapter should advance the story meaningfully
|
|
48
|
+
- Illustrations: upload via API, embed as \`\` in markdown
|
|
49
|
+
|
|
50
|
+
## Publishing Rules
|
|
51
|
+
|
|
52
|
+
- Content is **immutable after publish** — verify everything in Preview first
|
|
53
|
+
- Genesis publishes via \`createStoryline\` (one-time per story)
|
|
54
|
+
- Plots publish via \`chainPlot\` (one per chapter)
|
|
55
|
+
- Always check character count before publishing (500–10,000 chars)
|
|
56
|
+
|
|
57
|
+
## Security — never print secrets into the terminal
|
|
58
|
+
|
|
59
|
+
- Do NOT print, \`echo\`, \`cat\`, or log auth material in this terminal: no
|
|
60
|
+
\`Authorization: Bearer\` headers or session tokens, no \`OWS_PASSPHRASE\`, and no
|
|
61
|
+
login passphrase or any login command that contains it.
|
|
62
|
+
- The app stores the passphrase in \`~/.plotlink-ows/.env\` and authenticates for
|
|
63
|
+
you — you never need to read or echo it. Use the documented API; keep secrets
|
|
64
|
+
out of command output.
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Provider-branched clean-image workflow section for cartoon instructions.
|
|
70
|
+
*
|
|
71
|
+
* The clean-image-first rules (no dialogue/SFX/bubbles/watermark/signature baked
|
|
72
|
+
* into art) are shared; only the "who creates the file" contract differs:
|
|
73
|
+
*
|
|
74
|
+
* - Codex: creating the real `assets/plot-NN/cut-XX-clean.webp` file is the
|
|
75
|
+
* PRIMARY instruction, but it is gated behind a capability/checkpoint guardrail
|
|
76
|
+
* (#307) so the session never sits in an indefinite `Working` state: Codex
|
|
77
|
+
* makes one bounded attempt, and if image generation is unavailable it reports
|
|
78
|
+
* the blocker and falls back to the manual prompt + import path instead of
|
|
79
|
+
* hanging. Critically, Codex is never told it cannot create image files (#274)
|
|
80
|
+
* — only to verify-and-report rather than stall.
|
|
81
|
+
* - Claude / legacy (absent provider): Claude does not generate image files in the
|
|
82
|
+
* terminal, so the manual prompt + import handoff stays primary (unchanged
|
|
83
|
+
* behavior).
|
|
84
|
+
*/
|
|
85
|
+
function cleanImageWorkflowSection(provider: AgentProvider): string {
|
|
86
|
+
if (provider === "codex") {
|
|
87
|
+
return `### Write the planning files FIRST — never leave a story shell (#362)
|
|
88
|
+
|
|
89
|
+
Before you generate (or attempt to generate) ANY image, write the episode's text
|
|
90
|
+
artifacts to the story folder so the story is never left as just \`.story.json\` +
|
|
91
|
+
\`CLAUDE.md\` if image generation stalls or is unavailable:
|
|
92
|
+
|
|
93
|
+
1. \`structure.md\` (outline, Character Bible, Visual Style Guide)
|
|
94
|
+
2. \`genesis.md\` (the reader-facing prologue/opening)
|
|
95
|
+
3. \`plot-NN.cuts.json\` (the shot-by-shot cut plan, with its real episode \`title\`)
|
|
96
|
+
|
|
97
|
+
Image generation is the LAST step, not the first. A pilot once spent >10 minutes
|
|
98
|
+
attempting image generation and left the folder with no \`structure.md\`,
|
|
99
|
+
\`genesis.md\`, or \`plot-NN.cuts.json\` at all — that is the failure this rule
|
|
100
|
+
prevents. Save the planning files, confirm they exist in the story folder, and
|
|
101
|
+
only then move on to images. (\`plot-NN.md\` is the exception — OWS generates it
|
|
102
|
+
from cuts.json after final images upload; never hand-write it.)
|
|
103
|
+
|
|
104
|
+
### Before generating images: confirm capability, checkpoint, and never hang (#307)
|
|
105
|
+
|
|
106
|
+
Image generation in this session is NOT guaranteed to be available. Before you
|
|
107
|
+
rely on it, follow this guardrail so the terminal never sits in an indefinite
|
|
108
|
+
\`Working\` state with no files and no blocker report:
|
|
109
|
+
|
|
110
|
+
1. **Checkpoint first, then make ONE bounded attempt.** Before generating, post a
|
|
111
|
+
one-line status naming the file you are about to create (e.g. "generating
|
|
112
|
+
cut-01 clean image"). Make exactly ONE attempt per file — never retry image
|
|
113
|
+
generation in a loop or wait indefinitely for it to finish.
|
|
114
|
+
2. **Confirm the capability on that attempt.** If there is no working in-session
|
|
115
|
+
image-generation capability — the attempt errors, returns no usable image
|
|
116
|
+
bytes, or you cannot confirm it quickly — treat image generation as
|
|
117
|
+
UNAVAILABLE for this session. (Per Asset Tooling, never shell out to
|
|
118
|
+
\`magick\`/\`sharp\`/Playwright to fake it.)
|
|
119
|
+
3. **Fail visibly, never silently.** The moment image generation is
|
|
120
|
+
unavailable/blocked, report it in one clear line — "Image generation is
|
|
121
|
+
unavailable in this Codex session; switching to the prompt-and-import
|
|
122
|
+
handoff." — then use the Fallback below and stop cleanly. An unreported hang
|
|
123
|
+
is a bug; a clear blocker report is the correct, expected outcome.
|
|
124
|
+
4. **Report progress per file.** After each saved image or each blocker, post a
|
|
125
|
+
one-line checkpoint ("cut 2/6 saved" / "cover saved" / "blocked: <reason>") so
|
|
126
|
+
the writer always sees progress, never a bare spinner.
|
|
127
|
+
|
|
128
|
+
This guardrail applies to BOTH the cover and every clean cut image. Creating the
|
|
129
|
+
files is still your primary job when generation works (below); the rule is only
|
|
130
|
+
that you verify-and-report instead of stalling.
|
|
131
|
+
|
|
132
|
+
### Create the clean image file directly — your primary job
|
|
133
|
+
|
|
134
|
+
When image generation is available (confirm it per the guardrail above), for each
|
|
135
|
+
cut you CREATE THE REAL CLEAN-IMAGE FILE — you do not just return a prompt or
|
|
136
|
+
description. How that image reaches the cut depends on the format your image tool
|
|
137
|
+
produces — there are TWO acceptable outcomes, and a generated PNG is one of them:
|
|
138
|
+
|
|
139
|
+
1. Generate the clean image for the requested cut from its shot type + description
|
|
140
|
+
+ characters (the OWS UI's per-cut "Copy Codex task" / "Copy prompt" gives you
|
|
141
|
+
the exact visual prompt). Built-in image generation usually writes a **PNG** into
|
|
142
|
+
a generation cache such as \`~/.codex/generated_images/…\`, NOT your story folder —
|
|
143
|
+
that is an EXPECTED, acceptable result, not a failure. Do NOT try to convert it
|
|
144
|
+
yourself (the Asset Tooling ban forbids \`magick\`/\`sharp\`).
|
|
145
|
+
2. **Get the image onto the cut — pick the path that matches the file you have:**
|
|
146
|
+
- **Already WebP or JPEG, under 1MB:** finalize it into the OWS asset path. Copy
|
|
147
|
+
or move the file to \`assets/plot-NN/cut-XX-clean.webp\` — use the episode's
|
|
148
|
+
\`plot-NN\` folder and the cut's zero-padded id (\`cut-01\`, \`cut-02\`, …). A plain
|
|
149
|
+
file copy/move (\`cp\`/\`mv\`) is fine — the Asset Tooling ban is only on using
|
|
150
|
+
\`magick\`/\`sharp\`/Playwright to resize/convert/composite/measure. Then run the
|
|
151
|
+
OWS "Sync clean images" action so OWS records \`cleanImagePath\`.
|
|
152
|
+
- **A PNG (the common case), or any file OWS will not take directly:** do NOT
|
|
153
|
+
convert it and do NOT rename a \`.png\` to \`.webp\` — a PNG saved under a \`.webp\`
|
|
154
|
+
name is not a valid clean asset, and OWS rejects it on its content check. Leave
|
|
155
|
+
the file where it is and report it on an \`IMPORT NEEDED\` line (below); the
|
|
156
|
+
writer imports it into that exact cut with the OWS per-cut **"Import from
|
|
157
|
+
Codex"** button, which converts the generated PNG to a compliant WebP
|
|
158
|
+
automatically and records \`cleanImagePath\`. Importing a generated PNG this way
|
|
159
|
+
is a normal, supported finish — not a fallback, and not something you should
|
|
160
|
+
keep retrying to avoid.
|
|
161
|
+
3. The image must contain NO text, captions, speech bubbles, SFX lettering,
|
|
162
|
+
readable signage, watermark, or signature.
|
|
163
|
+
4. **Validate at the OWS path before reporting success.** A cut counts as SAVED only
|
|
164
|
+
when \`assets/plot-NN/cut-XX-clean.webp\` really exists — e.g.
|
|
165
|
+
\`find assets/plot-NN -name 'cut-XX-clean.*'\`, with \`file\` reporting WebP or JPEG
|
|
166
|
+
under 1MB (existence/type/size checks like \`find\`/\`file\` are allowed — they don't
|
|
167
|
+
manipulate the image). A cut whose image is still a PNG in
|
|
168
|
+
\`~/.codex/generated_images/…\` is NOT "saved" — list it as \`IMPORT NEEDED\` so the
|
|
169
|
+
writer imports it via "Import from Codex", and never hand-write a \`cleanImagePath\`
|
|
170
|
+
for a file that is not really at the OWS path.
|
|
171
|
+
|
|
172
|
+
**Only exception:** Include text in an image when it is part of the physical scene
|
|
173
|
+
(a sign on a building, text on a screen, a letter being read) AND the writer has
|
|
174
|
+
explicitly requested it.
|
|
175
|
+
|
|
176
|
+
### Fallback: hand the prompt to the writer (only if image generation is unavailable)
|
|
177
|
+
|
|
178
|
+
If image generation is not available in this session, do not block — fall back to
|
|
179
|
+
the manual handoff for each cut:
|
|
180
|
+
1. PREPARE THE EXACT CLEAN-IMAGE PROMPT (shot type + description + characters +
|
|
181
|
+
the no-text constraint). This is the same prompt the OWS UI exposes per cut.
|
|
182
|
+
2. Tell the writer to generate it externally (any provider/tool) and then
|
|
183
|
+
upload/import the resulting WebP/JPEG into OWS using the per-cut "Copy prompt" +
|
|
184
|
+
"Upload clean image" controls.
|
|
185
|
+
3. **Do NOT claim that \`assets/plot-NN/cut-XX-clean.webp\` was created unless the
|
|
186
|
+
file actually exists.** Never report an image as generated when you only
|
|
187
|
+
produced a prompt.
|
|
188
|
+
|
|
189
|
+
### Cover fallback (only if cover generation is unavailable)
|
|
190
|
+
|
|
191
|
+
The cover follows the same guardrail. If you cannot generate \`assets/cover.webp\`
|
|
192
|
+
in this session, do NOT hang on the cover: say so in one line and tell the writer
|
|
193
|
+
to import a cover via the OWS "Import generated image" control on the genesis
|
|
194
|
+
publish panel (it accepts a PNG and converts it). The same one-attempt-then-report
|
|
195
|
+
rule applies to a cover-only request.
|
|
196
|
+
|
|
197
|
+
### Finishing the task: post a completion line and STOP (#311)
|
|
198
|
+
|
|
199
|
+
You are DONE once every requested cut has been handled — each one either has its
|
|
200
|
+
verified clean-image file saved, OR a clear blocker reported and the
|
|
201
|
+
prompt-and-import fallback handed off — and the cover is saved or its fallback
|
|
202
|
+
reported. Verifying that each file exists at the right path, format, and size is
|
|
203
|
+
ALL the checking required.
|
|
204
|
+
|
|
205
|
+
Do NOT start an open-ended "let me visually re-inspect the images" loop: you
|
|
206
|
+
cannot meaningfully re-judge generated art from a terminal, and looping there is
|
|
207
|
+
exactly what leaves the OWS session stuck in a long-running \`Working\` state after
|
|
208
|
+
the files already exist.
|
|
209
|
+
|
|
210
|
+
Post ONE final completion line, then STOP (return to the idle prompt — do not keep
|
|
211
|
+
working or wait for more input):
|
|
212
|
+
|
|
213
|
+
CARTOON ASSETS COMPLETE — N/N clean cut images saved, cover <saved|skipped>. Ready for lettering in OWS.
|
|
214
|
+
|
|
215
|
+
If some assets are blocked, use instead:
|
|
216
|
+
|
|
217
|
+
CARTOON ASSETS PARTIAL — X/N clean cut images saved, Y blocked: <reasons>. Ready for lettering on the saved cuts in OWS.
|
|
218
|
+
|
|
219
|
+
**Hand off generated PNGs for one-click import (#403).** Any cut whose image is a
|
|
220
|
+
PNG left in \`~/.codex/generated_images/…\` (the normal output of built-in
|
|
221
|
+
generation) must be named for the writer so they can import it — never silently
|
|
222
|
+
leave it in the cache, where a writer cannot find it. For each such file, list its
|
|
223
|
+
real source path and the cut/cover it belongs to, e.g.:
|
|
224
|
+
|
|
225
|
+
IMPORT NEEDED — cut 3: ~/.codex/generated_images/<file>.png → import into cut 3 with the OWS per-cut "Import from Codex" button (it converts the PNG automatically).
|
|
226
|
+
IMPORT NEEDED — cover: ~/.codex/generated_images/<file>.png → import via the genesis panel's "Import generated image" control.
|
|
227
|
+
|
|
228
|
+
A generated PNG handed off this way is a complete, expected result — list every one
|
|
229
|
+
so none is stranded, and do NOT re-attempt generation just to avoid the import.
|
|
230
|
+
|
|
231
|
+
### Post-run checklist — files live in the STORY FOLDER, not the cache (#362)
|
|
232
|
+
|
|
233
|
+
Before you post the completion line, confirm in the story folder itself (not in
|
|
234
|
+
\`~/.codex/generated_images/\`):
|
|
235
|
+
|
|
236
|
+
- \`structure.md\`, \`genesis.md\`, and every \`plot-NN.cuts.json\` exist.
|
|
237
|
+
- Each cut you reported as saved has a real \`assets/plot-NN/cut-XX-clean.webp\`
|
|
238
|
+
(WebP/JPEG, < 1MB) at that path — verify with \`find\`/\`file\`, not from memory.
|
|
239
|
+
- The cover, if generated, is at \`assets/cover.webp\`.
|
|
240
|
+
- Any image you could NOT finalize is named in an \`IMPORT NEEDED\` line above.
|
|
241
|
+
|
|
242
|
+
If a file is only in the generation cache, it does not count as saved — either
|
|
243
|
+
finalize it into the asset path or report it as \`IMPORT NEEDED\`.
|
|
244
|
+
|
|
245
|
+
After that line, the lettering/export/publish steps are the writer's job in OWS —
|
|
246
|
+
hand off and stop.`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return `### You cannot create image files yourself — hand the prompt to the writer
|
|
250
|
+
|
|
251
|
+
You (Claude) do **not** generate image files in this terminal. You produce the
|
|
252
|
+
exact clean-image PROMPT for each cut; the writer (or a configured image tool, if
|
|
253
|
+
any) generates the actual image externally and imports it back into OWS.
|
|
254
|
+
|
|
255
|
+
If no image-generation tool is available in the terminal, for each cut:
|
|
256
|
+
1. PREPARE THE EXACT CLEAN-IMAGE PROMPT (shot type + description + characters +
|
|
257
|
+
the no-text constraint). This is the same prompt the OWS UI exposes per cut.
|
|
258
|
+
2. Tell the writer to generate it externally (any provider/tool they prefer) and
|
|
259
|
+
then upload/import the resulting WebP/JPEG into OWS using the per-cut
|
|
260
|
+
"Copy prompt" + "Upload clean image" controls.
|
|
261
|
+
3. **Do NOT claim that \`assets/plot-NN/cut-XX-clean.webp\` was created unless the
|
|
262
|
+
file actually exists.** Never report an image as generated when you only
|
|
263
|
+
produced a prompt.
|
|
264
|
+
4. After a clean image file exists, update/verify the cut's \`cleanImagePath\`
|
|
265
|
+
through the OWS import flow or the cuts API — do not invent the path.
|
|
266
|
+
|
|
267
|
+
If a configured image tool exists, it may generate the clean image directly;
|
|
268
|
+
otherwise the manual prompt + import path above is the safe baseline.
|
|
269
|
+
|
|
270
|
+
Saved clean images live at: \`assets/plot-NN/cut-XX-clean.webp\` (OWS records the
|
|
271
|
+
path; you do not hand-write it unless the file is really there).
|
|
272
|
+
|
|
273
|
+
**Only exception:** Include text in images when it is part of the physical scene
|
|
274
|
+
(a sign on a building, text on a screen, a letter being read) AND the writer
|
|
275
|
+
has explicitly requested it.`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Provider-branched step 2 of the Episode Workflow checklist. */
|
|
279
|
+
function episodeWorkflowImageStep(provider: AgentProvider): string {
|
|
280
|
+
if (provider === "codex") {
|
|
281
|
+
return `2. **Generate** — Write the planning files first. Create the real clean-image file for each cut.
|
|
282
|
+
If it is WebP/JPEG under 1MB, finalize it into \`assets/plot-NN/cut-XX-clean.webp\`;
|
|
283
|
+
if it is a PNG (the usual built-in output), leave it in the cache and hand it off
|
|
284
|
+
with an \`IMPORT NEEDED\` line for the writer's per-cut "Import from Codex" button —
|
|
285
|
+
both are acceptable finishes. Checkpoint per file and make one bounded attempt; if
|
|
286
|
+
image generation is unavailable, report the blocker and fall back to preparing the
|
|
287
|
+
prompt for the writer to import — never sit in an indefinite \`Working\` state.`;
|
|
288
|
+
}
|
|
289
|
+
return `2. **Prompt & import** — Prepare the clean-image prompt for each cut; the writer
|
|
290
|
+
generates it externally (or a configured image tool, if any) and uploads/
|
|
291
|
+
imports the clean image via OWS. You do not create the image file yourself.`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function cartoonInstructions(provider: AgentProvider): string {
|
|
295
|
+
return `# Writing Instructions — Cartoon
|
|
296
|
+
|
|
297
|
+
> Auto-generated by PlotLink OWS. Do not edit manually.
|
|
298
|
+
> For API endpoints and publish details, see ~/.plotlink-ows/CLAUDE.md.
|
|
299
|
+
|
|
300
|
+
## Story File Structure
|
|
301
|
+
|
|
302
|
+
\`\`\`
|
|
303
|
+
.story.json — { "contentType": "cartoon" }
|
|
304
|
+
structure.md — Story bible: style guide, character bible, episode format (outline, not publishable)
|
|
305
|
+
genesis.md — Reader-facing prologue / Episode 1 opening (prose, real # title)
|
|
306
|
+
genesis.cuts.json — Cut plan for Genesis-as-Episode-1 ({ "plotFile": "genesis", cuts: [...] })
|
|
307
|
+
plot-NN.cuts.json — Cut plan for episode NN
|
|
308
|
+
plot-NN.md — Episode publish markdown (image sequence, generated from final uploaded images)
|
|
309
|
+
assets/
|
|
310
|
+
plot-NN/ — (and assets/genesis/ for Genesis cuts)
|
|
311
|
+
cut-01-clean.webp — Clean image (no text or bubbles)
|
|
312
|
+
cut-01-final.webp — Final lettered version
|
|
313
|
+
...
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
### Scaffold states (keep these explicit)
|
|
317
|
+
|
|
318
|
+
A new cartoon scaffold has four distinguishable states — don't let a placeholder
|
|
319
|
+
look publish-ready:
|
|
320
|
+
|
|
321
|
+
- **Story bible / outline** — \`structure.md\`. Never publishable; it's the plan.
|
|
322
|
+
- **Episode 1 planned** — \`genesis.md\` (prose opening) + \`genesis.cuts.json\`
|
|
323
|
+
with real cuts. Use \`"plotFile": "genesis"\` — OWS supports Genesis as
|
|
324
|
+
Episode 1, not only \`plot-NN\`.
|
|
325
|
+
- **Future episode placeholder** — a \`plot-NN.cuts.json\` with \`"cuts": []\` (and
|
|
326
|
+
a stub \`plot-NN.md\`) is a **planned / not-started** episode. Leave \`cuts\`
|
|
327
|
+
empty until you actually plan that episode; OWS shows it as "not started", not
|
|
328
|
+
"ready to publish". Do NOT hand-write publish markdown for it.
|
|
329
|
+
- **Publish markdown** — \`plot-NN.md\` is generated by OWS from the final
|
|
330
|
+
uploaded cut images (the "Prepare episode for publish" step), never authored by
|
|
331
|
+
hand.
|
|
332
|
+
|
|
333
|
+
## structure.md Format (Cartoon)
|
|
334
|
+
|
|
335
|
+
### Required Sections
|
|
336
|
+
|
|
337
|
+
1. **Visual Style Guide**
|
|
338
|
+
- Art style (manga, Franco-Belgian, webcomic, American comic, etc.)
|
|
339
|
+
- Color palette (full color, limited palette, monochrome + spot color)
|
|
340
|
+
- Line weight and inking style
|
|
341
|
+
- Panel/cut layout preferences (grid, dynamic, full-bleed)
|
|
342
|
+
- **Style Lock (REQUIRED — prevents photoreal drift).** Write one short,
|
|
343
|
+
reusable block that locks the rendering style and you repeat on EVERY image
|
|
344
|
+
generation. It MUST carry both positive descriptors and hard negatives,
|
|
345
|
+
because image generation drifts into polished digital painting / photoreal
|
|
346
|
+
concept art unless the prompt fights it. Pattern (adapt the specifics to this
|
|
347
|
+
story's reference, e.g. a semi-realistic Korean webtoon):
|
|
348
|
+
- **Positive:** illustrated comic/webtoon panel art; clean black contour/ink
|
|
349
|
+
lines; flat or cel shading; simplified-but-realistic (semi-realistic)
|
|
350
|
+
anatomy and faces; backgrounds drawn as illustrated panels, not photos.
|
|
351
|
+
- **Hard negatives:** NOT photorealistic, NOT a photograph, NOT a glossy or
|
|
352
|
+
painterly digital painting, NOT concept art, NOT a 3D/CGI render, NOT
|
|
353
|
+
airbrushed, no photoreal textures.
|
|
354
|
+
"Semi-realistic webtoon" on its own is too weak — keep the explicit negatives.
|
|
355
|
+
OWS already prepends a baseline style lock to every per-cut "Copy Codex task" /
|
|
356
|
+
"Copy prompt"; your Style Lock here adds the story-specific reference on top and
|
|
357
|
+
must be honored on the cover too.
|
|
358
|
+
|
|
359
|
+
2. **Character Bible**
|
|
360
|
+
For each character, describe their VISUAL identity:
|
|
361
|
+
- Physical features: hair color/style, eye color, build, height
|
|
362
|
+
- Signature outfit and accessories
|
|
363
|
+
- Distinguishing visual features (scars, glasses, tattoos)
|
|
364
|
+
- Expression notes (resting expression, characteristic gestures)
|
|
365
|
+
|
|
366
|
+
3. **Episode Format**
|
|
367
|
+
- Target cuts per episode (typical: 4–12)
|
|
368
|
+
- Pacing rhythm: how to alternate wide/medium/close-up shots
|
|
369
|
+
- Aspect ratio preference for cuts
|
|
370
|
+
|
|
371
|
+
4. **Bubble and Lettering Conventions**
|
|
372
|
+
- Speech bubble style (round, angular, cloud-like)
|
|
373
|
+
- Thought bubble style
|
|
374
|
+
- Narration/caption box style
|
|
375
|
+
- Sound effect (SFX) conventions
|
|
376
|
+
- Font or lettering style preferences
|
|
377
|
+
|
|
378
|
+
5. **Cut Planning Rules**
|
|
379
|
+
- Shot progression guidelines (e.g., establish → act → react)
|
|
380
|
+
- When to use wide vs. close-up shots
|
|
381
|
+
- Transition conventions between scenes
|
|
382
|
+
|
|
383
|
+
6. **Genesis Opening Plan (Reader Onboarding)**
|
|
384
|
+
- How Genesis opens the story for readers: the lead's situation and their
|
|
385
|
+
emotional/comedic problem, the tone, and what's at stake.
|
|
386
|
+
- The bridge from Genesis into episode 1 — what plot-01 picks up, so the
|
|
387
|
+
prologue and the first episode connect without restarting or repeating.
|
|
388
|
+
- Keep it text-only for MVP; note the intended title.
|
|
389
|
+
|
|
390
|
+
## genesis.md Format — Reader-facing Prologue / Story Opening
|
|
391
|
+
|
|
392
|
+
On PlotLink the story page **opens at Genesis**, so for a cartoon write \`genesis.md\`
|
|
393
|
+
as the actual **prologue / story opening readers see first** — NOT a back-cover
|
|
394
|
+
synopsis, and NOT a cold jump into scene 1. It is text-only for this MVP (no
|
|
395
|
+
image cuts) and **must start with a real \`# Title\` heading** (the published
|
|
396
|
+
chapter title).
|
|
397
|
+
|
|
398
|
+
Write it as polished reader-facing prose (~600–1000 characters) across **3–6
|
|
399
|
+
short paragraphs** that build — do NOT compress the opening into a single block
|
|
400
|
+
or a one-line premise. Let the prologue breathe across these beats:
|
|
401
|
+
|
|
402
|
+
- **Opens on the main character's situation** and their emotional or comedic
|
|
403
|
+
problem — put us in a moment, not a pitch.
|
|
404
|
+
- **What the lead wants** (their desire/goal) and **what's at stake** — give the
|
|
405
|
+
reader someone to root for before the first episode.
|
|
406
|
+
- **Establishes tone and premise** — the comedic/romantic hook that makes this
|
|
407
|
+
story this story.
|
|
408
|
+
- **Builds toward the first visual episode** and ends on a beat that hands off
|
|
409
|
+
into Episode 01 — a clear bridge, so plot-01 feels like the story continuing,
|
|
410
|
+
not starting over.
|
|
411
|
+
- **Avoids lore dumps** and worldbuilding exposition — reveal through the
|
|
412
|
+
character's immediate situation.
|
|
413
|
+
- For comedic romance / webtoon pacing: lead with the funny/awkward hook and the
|
|
414
|
+
relationship tension; keep it warm and propulsive.
|
|
415
|
+
|
|
416
|
+
A single dense paragraph or a bare logline is NOT enough — readers meet Genesis
|
|
417
|
+
first, so it must onboard them with real buildup, not drop them cold into a scene.
|
|
418
|
+
|
|
419
|
+
Genesis vs plot-01: **Genesis opens and onboards the reader** (prose prologue);
|
|
420
|
+
**plot-01 is the first cut-based visual episode** that must **open on a titled
|
|
421
|
+
beat continuing directly from where Genesis leaves off** — not a cold jump and
|
|
422
|
+
not a restart. Do not start plot-01 without the setup Genesis provides, and do
|
|
423
|
+
not duplicate the prologue inside plot-01.
|
|
424
|
+
|
|
425
|
+
## Cut Planning — plot-NN.cuts.json
|
|
426
|
+
|
|
427
|
+
Before generating images for an episode, create the cut plan first. The cuts.json
|
|
428
|
+
is the single source of truth for the episode's visual sequence.
|
|
429
|
+
|
|
430
|
+
**CRITICAL: Use this EXACT schema. Do NOT invent alternate or nested planning
|
|
431
|
+
structures.** OWS reads this file with a strict validator — any other shape is
|
|
432
|
+
rejected.
|
|
433
|
+
|
|
434
|
+
Valid \`plot-01.cuts.json\`:
|
|
435
|
+
|
|
436
|
+
\`\`\`json
|
|
437
|
+
{
|
|
438
|
+
"version": 1,
|
|
439
|
+
"plotFile": "plot-01",
|
|
440
|
+
"title": "Episode 1 — First Rain",
|
|
441
|
+
"cuts": [
|
|
442
|
+
{
|
|
443
|
+
"id": 1,
|
|
444
|
+
"shotType": "wide",
|
|
445
|
+
"description": "Establishing shot of the rain-soaked city at dusk",
|
|
446
|
+
"characters": ["Mira"],
|
|
447
|
+
"dialogue": [
|
|
448
|
+
{ "speaker": "Mira", "text": "It always rains here." }
|
|
449
|
+
],
|
|
450
|
+
"narration": "The city never slept, and neither did she.",
|
|
451
|
+
"sfx": "RAIN",
|
|
452
|
+
"cleanImagePath": null,
|
|
453
|
+
"finalImagePath": null,
|
|
454
|
+
"exportedAt": null,
|
|
455
|
+
"uploadedCid": null,
|
|
456
|
+
"uploadedUrl": null,
|
|
457
|
+
"overlays": []
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}
|
|
461
|
+
\`\`\`
|
|
462
|
+
|
|
463
|
+
**Always set a top-level \`title\`** — a real, **reader-facing, specific** episode
|
|
464
|
+
title (e.g. "Episode 1 — First Rain", or just "First Rain"). The published
|
|
465
|
+
cartoon markdown is image-only with no heading, so this \`title\` becomes the
|
|
466
|
+
**public chapter title readers see on PlotLink**. It must NOT be a default or
|
|
467
|
+
placeholder label: never the raw filename \`plot-01\`, and never a bare generic
|
|
468
|
+
"Episode NN"/"Chapter NN". OWS **blocks** publish on those (#347/#358/#365/#368)
|
|
469
|
+
and, after publish, **verifies the indexed public title** is reader-facing
|
|
470
|
+
(#379). The same rule applies to the story itself — **Genesis must carry a real
|
|
471
|
+
\`# Title\`, never the default \`Genesis\` label**. EVERY episode needs a public
|
|
472
|
+
title specific to what happens in it, not a numbered placeholder.
|
|
473
|
+
|
|
474
|
+
### Text / interstitial panels (#352)
|
|
475
|
+
|
|
476
|
+
A cut may be a **text panel** instead of an image cut — a narration card, title
|
|
477
|
+
card, time/scene transition ("Three weeks later"), or a beat of pure prose for
|
|
478
|
+
pacing/buildup. Set \`"kind": "text"\` on the cut (image cuts omit \`kind\` or use
|
|
479
|
+
\`"image"\`). A text panel needs **no clean image**: it renders text on a styled
|
|
480
|
+
background and still exports + uploads a final image like any cut.
|
|
481
|
+
|
|
482
|
+
\`\`\`json
|
|
483
|
+
{ "id": 4, "kind": "text", "shotType": "wide", "description": "Time skip",
|
|
484
|
+
"background": "#101820", "aspectRatio": "4:5",
|
|
485
|
+
"characters": [], "dialogue": [], "narration": "Three weeks later.", "sfx": "",
|
|
486
|
+
"cleanImagePath": null, "finalImagePath": null, "exportedAt": null,
|
|
487
|
+
"uploadedCid": null, "uploadedUrl": null, "overlays": [] }
|
|
488
|
+
\`\`\`
|
|
489
|
+
|
|
490
|
+
Use text panels **between image cuts** to control rhythm — an opening title, a
|
|
491
|
+
scene/time transition, an interior-monologue beat, or buildup before a reveal.
|
|
492
|
+
Optional \`background\` (CSS color) and \`aspectRatio\` ("W:H") style the card. In
|
|
493
|
+
OWS the writer can also add one with the **"Add text panel"** button, then open
|
|
494
|
+
the lettering editor to place the text. Don't overuse them; they punctuate the
|
|
495
|
+
visual story, they don't replace it.
|
|
496
|
+
|
|
497
|
+
### Required field naming (do NOT use the wrong forms)
|
|
498
|
+
|
|
499
|
+
| Use this | NOT this |
|
|
500
|
+
|----------|----------|
|
|
501
|
+
| \`version: 1\` (top level) | \`$schema\`, \`story\`, \`workflow\`, \`promptDefaults\` |
|
|
502
|
+
| numeric \`id\` (1, 2, 3) | string ids like \`"c01"\` |
|
|
503
|
+
| \`shotType\` | \`shot\` |
|
|
504
|
+
| \`description\` | nested \`image.prompt\` |
|
|
505
|
+
| \`dialogue[].text\` | \`dialogue[].line\` |
|
|
506
|
+
| \`narration\` (string) | nested \`text.narration\` |
|
|
507
|
+
| \`sfx\` (single string) | \`sfx\` as an array |
|
|
508
|
+
| \`cleanImagePath\` / \`finalImagePath\` (top-level on the cut) | nested \`image.clean\` / \`image.final\` |
|
|
509
|
+
|
|
510
|
+
- \`shotType\` must be one of: \`wide\`, \`medium\`, \`close-up\`, \`extreme-close-up\`.
|
|
511
|
+
- All image/export/upload path fields start as \`null\`; OWS fills them in.
|
|
512
|
+
- \`overlays\` starts as an empty array \`[]\`; the lettering editor populates it.
|
|
513
|
+
**Leave \`overlays\` empty (\`[]\`)** — the writer places bubbles/captions in the
|
|
514
|
+
OWS lettering editor. If you ever do emit overlays, each MUST use the real OWS
|
|
515
|
+
overlay schema below — NEVER a semantic \`position\` string like \`"upper-left"\`,
|
|
516
|
+
which has no geometry, does not render, and would export a blank, unlettered
|
|
517
|
+
image.
|
|
518
|
+
- **Overlay schema (only if you emit overlays):** each overlay is an object with
|
|
519
|
+
\`id\` (string), \`type\` (\`"speech"\` | \`"narration"\` | \`"sfx"\`), numeric \`x\`, \`y\`,
|
|
520
|
+
\`width\`, \`height\` (fractions of the image, 0–1, where \`x\`/\`y\` are the top-left
|
|
521
|
+
corner), \`text\` (string), optional \`speaker\` (speech only), and optional
|
|
522
|
+
\`tailAnchor\` (\`{ "x": number, "y": number }\`, speech only). Example:
|
|
523
|
+
\`{ "id": "ov-1", "type": "speech", "x": 0.08, "y": 0.06, "width": 0.4, "height": 0.16, "text": "...", "speaker": "Hana", "tailAnchor": { "x": 0.5, "y": 1.2 } }\`.
|
|
524
|
+
There is NO \`position\` field.
|
|
525
|
+
- **Every publishable cut must become a final uploaded image.** Even
|
|
526
|
+
narration-only or background-only cuts must get a clean image, be lettered/
|
|
527
|
+
exported, and uploaded before the episode can publish. \`cleanImagePath\` may
|
|
528
|
+
be \`null\` during early planning, but a cut with no uploaded image will block
|
|
529
|
+
publish readiness.
|
|
530
|
+
|
|
531
|
+
## Asset Tooling — use OWS flows, do NOT shell out to image tools
|
|
532
|
+
|
|
533
|
+
OWS owns the cartoon asset pipeline. Local image tools are NOT part of the
|
|
534
|
+
contract and may be absent — do **NOT** call ImageMagick (\`magick\`/\`convert\`/
|
|
535
|
+
\`identify\`), \`sharp\`, Playwright / headless browsers, or any ad-hoc shell tool to
|
|
536
|
+
resize, convert, composite, letter, or measure images. If you find yourself
|
|
537
|
+
reaching for a CLI image tool, STOP — there is a supported OWS action for it, and
|
|
538
|
+
guessing at unavailable tooling is exactly what stalls a cartoon episode.
|
|
539
|
+
|
|
540
|
+
**Deterministic handoff — who does what:**
|
|
541
|
+
|
|
542
|
+
| Step | Who | How (no shell image tools) |
|
|
543
|
+
|------|-----|----------------------------|
|
|
544
|
+
| Generate clean cut images | You (Codex) | Generate the image. If it is WebP/JPEG < 1MB, finalize it into \`assets/plot-NN/cut-XX-clean.webp\` with a plain \`cp\`/\`mv\`. If it is a PNG (the usual built-in output), do NOT convert or rename it — hand it off with an \`IMPORT NEEDED\` line for the writer's "Import from Codex" button, which converts it automatically. Either is a valid finish; do NOT post-process with magick/sharp. |
|
|
545
|
+
| Generate the cover | You (Codex) | Save \`assets/cover.webp\` (~600x900, WebP, < 1MB). OWS auto-detects it for genesis publish — no manual selection needed. |
|
|
546
|
+
| Discover / record clean images | OWS | Run the "Sync clean images" action (or let OWS auto-detect); OWS records \`cleanImagePath\`. Never hand-write paths or stat files yourself. |
|
|
547
|
+
| Letter & export final images | The writer, in the OWS lettering editor | Speech bubbles, captions, and SFX are placed in the OWS editor and exported to \`assets/plot-NN/cut-XX-final.webp\`. You do NOT composite or letter text — not with magick, not with sharp, not at all. |
|
|
548
|
+
| Upload finals + build markdown | OWS | The writer runs "Upload & Prepare for Publish"; OWS uploads each final image and emits the publish markdown. You never hand-write \`plot-NN.md\`. |
|
|
549
|
+
|
|
550
|
+
**No agent-side image tools are required** — OWS provides image generation (your
|
|
551
|
+
session), clean-image sync, the lettering/export editor, and upload/markdown
|
|
552
|
+
generation. If a capability you genuinely need is missing (e.g. image generation
|
|
553
|
+
itself is unavailable in this session), do NOT improvise with shell tools: fall
|
|
554
|
+
back to the prompt-and-import handoff below and tell the writer, so the missing
|
|
555
|
+
capability surfaces immediately instead of after a dead-end path.
|
|
556
|
+
|
|
557
|
+
## CRITICAL: Clean-Image-First Workflow
|
|
558
|
+
|
|
559
|
+
**Do NOT bake dialogue, speech bubbles, sound effects, or any text into generated images.**
|
|
560
|
+
|
|
561
|
+
Clean images must contain:
|
|
562
|
+
- No speech bubbles
|
|
563
|
+
- No text overlays
|
|
564
|
+
- No sound effect text
|
|
565
|
+
- No narration captions
|
|
566
|
+
- No lettering of any kind
|
|
567
|
+
|
|
568
|
+
**Lock the style on every generation — do not drift to photoreal.** Reuse the
|
|
569
|
+
**Style Lock** from structure.md's Visual Style Guide in EVERY image prompt (cuts
|
|
570
|
+
and cover) so the look stays consistent and illustrated. The requested style is an
|
|
571
|
+
illustrated comic/webtoon panel — clean black contour lines, flat/cel shading,
|
|
572
|
+
simplified-but-realistic anatomy — and explicitly **NOT** photorealistic, **NOT** a
|
|
573
|
+
painterly digital painting / concept art, and **NOT** a 3D render. If a generated
|
|
574
|
+
image reads as a photo or polished concept art, it is off-style: regenerate it as
|
|
575
|
+
illustrated panel art rather than accepting the drift. (The per-cut "Copy Codex
|
|
576
|
+
task" / "Copy prompt" OWS exposes already carries this baseline style lock.)
|
|
577
|
+
|
|
578
|
+
${cleanImageWorkflowSection(provider)}
|
|
579
|
+
|
|
580
|
+
## Character Consistency
|
|
581
|
+
|
|
582
|
+
- Reference the character bible from structure.md for EVERY image generation
|
|
583
|
+
- Maintain consistent visual traits across all cuts and all episodes
|
|
584
|
+
- Same hair, same eye color, same outfit unless the story dictates a change
|
|
585
|
+
- Note any intentional appearance changes in the cuts.json
|
|
586
|
+
|
|
587
|
+
## Lettering Handoff
|
|
588
|
+
|
|
589
|
+
After clean images are generated and approved by the writer:
|
|
590
|
+
|
|
591
|
+
1. The writer uses the OWS lettering editor to add speech bubbles and text
|
|
592
|
+
2. Lettered versions are saved as: \`assets/plot-NN/cut-XX-final.webp\` (the OWS
|
|
593
|
+
editor's export writes this file — you do not create or composite it)
|
|
594
|
+
3. Do NOT attempt to add bubbles or text to images — only the writer controls
|
|
595
|
+
lettering, via the OWS editor. Never composite text with \`magick\`, \`sharp\`,
|
|
596
|
+
Playwright, or any other tool; lettering/export is not an agent shell task.
|
|
597
|
+
4. The publish markdown references final lettered images, not clean images
|
|
598
|
+
|
|
599
|
+
## Publish Markdown — plot-NN.md
|
|
600
|
+
|
|
601
|
+
**Do NOT hand-write plot-NN.md with image links.** OWS generates the publish
|
|
602
|
+
markdown from cuts.json after final images are uploaded. Hand-authored markdown
|
|
603
|
+
with local asset paths (\`assets/...\`) or placeholder URLs ("final image
|
|
604
|
+
pending") will be rejected by publish readiness checks.
|
|
605
|
+
|
|
606
|
+
Correct flow:
|
|
607
|
+
|
|
608
|
+
1. Upload final (lettered) images via OWS — this records the IPFS URL per cut in
|
|
609
|
+
cuts.json (\`uploadedUrl\`).
|
|
610
|
+
2. Use OWS "Prepare episode for publish" / "Upload & Prepare for Publish" to produce
|
|
611
|
+
plot-NN.md. OWS emits marker-delimited blocks:
|
|
612
|
+
\`\`\`
|
|
613
|
+
<!-- ows:cartoon-cut cut-001 start -->
|
|
614
|
+

|
|
615
|
+
<!-- ows:cartoon-cut cut-001 end -->
|
|
616
|
+
\`\`\`
|
|
617
|
+
3. Cuts that are not yet uploaded produce a safe \`<!-- ... awaiting upload -->\`
|
|
618
|
+
comment, never a fake image URL. Publish is blocked until all cuts upload.
|
|
619
|
+
4. Each image is WebP or JPEG, under 1MB. Total markdown under 10K characters.
|
|
620
|
+
5. Keep human-readable planning notes in cuts.json or structure.md — never as
|
|
621
|
+
fake publish image links.
|
|
622
|
+
|
|
623
|
+
## Publishing Rules
|
|
624
|
+
|
|
625
|
+
- Content is **immutable after publish** — verify all images in Preview first
|
|
626
|
+
- Genesis publishes via \`createStoryline\` (one-time, text-only synopsis)
|
|
627
|
+
- Episodes publish via \`chainPlot\` (per episode, image-sequence markdown)
|
|
628
|
+
- Once published, images cannot be replaced or edited
|
|
629
|
+
|
|
630
|
+
## Security — never print secrets into the terminal
|
|
631
|
+
|
|
632
|
+
- Do NOT print, \`echo\`, \`cat\`, or log auth material in this terminal: no
|
|
633
|
+
\`Authorization: Bearer\` headers or session tokens, no \`OWS_PASSPHRASE\`, and no
|
|
634
|
+
login passphrase or any login command that contains it.
|
|
635
|
+
- The app stores the passphrase in \`~/.plotlink-ows/.env\` and authenticates for
|
|
636
|
+
you — you never need to read or echo it. Use the documented API; keep secrets
|
|
637
|
+
out of command output.
|
|
638
|
+
|
|
639
|
+
## Episode Workflow
|
|
640
|
+
|
|
641
|
+
1. **Plan** — Create plot-NN.cuts.json with shot-by-shot breakdown
|
|
642
|
+
${episodeWorkflowImageStep(provider)}
|
|
643
|
+
3. **Review** — Writer reviews clean images, requests adjustments
|
|
644
|
+
4. **Letter** — Writer adds speech bubbles and text via lettering editor
|
|
645
|
+
5. **Upload** — Upload final lettered images to get IPFS URLs (recorded in cuts.json)
|
|
646
|
+
6. **Generate** — Use OWS to generate plot-NN.md from cuts.json (do not hand-write it)
|
|
647
|
+
7. **Preview** — Verify all images render correctly
|
|
648
|
+
8. **Publish** — Chain the episode (immutable after this step)
|
|
649
|
+
|
|
650
|
+
## Final response format — end EVERY completed task with this (#419)
|
|
651
|
+
|
|
652
|
+
A validation summary alone is not enough: a non-technical writer needs to know
|
|
653
|
+
exactly what to do next and what to paste back to you. So after ANY major task
|
|
654
|
+
(story setup, episode planning, clean-image generation, lettering handoff,
|
|
655
|
+
export, upload, publish), end your reply with this exact five-part section, in
|
|
656
|
+
plain language (no jargon, no internal file paths in the pasteable prompt):
|
|
657
|
+
|
|
658
|
+
\`\`\`
|
|
659
|
+
Done
|
|
660
|
+
- <3-5 short bullets of what changed>
|
|
661
|
+
|
|
662
|
+
Current stage
|
|
663
|
+
- <one line, e.g. "Episode 1 planned; clean images not generated yet">
|
|
664
|
+
|
|
665
|
+
Next recommended action
|
|
666
|
+
- <one clear action in plain language>
|
|
667
|
+
|
|
668
|
+
Prompt you can paste next
|
|
669
|
+
- <a single copy-paste prompt the writer can hand back to you>
|
|
670
|
+
|
|
671
|
+
Do not do yet
|
|
672
|
+
- <short safeguards when relevant; omit the bullet if nothing applies>
|
|
673
|
+
\`\`\`
|
|
674
|
+
|
|
675
|
+
Pick the "Prompt you can paste next" to match the stage you just finished:
|
|
676
|
+
|
|
677
|
+
- **After story setup** → \`Plan the cuts for Episode 1 / Genesis. Don't generate images, letter, upload, or publish yet.\`
|
|
678
|
+
- **After episode planning** → \`Generate clean images for Episode 1 / Genesis from genesis.cuts.json. Do not letter, upload, or publish yet.\`
|
|
679
|
+
- **After clean images** → \`The clean images for Episode 1 are ready — review them, then I'll letter them in the OWS editor.\` (lettering/export/upload happen in the OWS UI, not via the agent)
|
|
680
|
+
- **After lettering/export/upload** → \`Episode 1's final images are uploaded — prepare the publish markdown and show me the Preview before publishing.\`
|
|
681
|
+
- **After publish** → \`Verify Episode 1 is live on plotlink.xyz, then let's plan Episode 2.\`
|
|
682
|
+
|
|
683
|
+
Keep it concise — this section replaces a long technical status dump, it does not
|
|
684
|
+
add to one. Write the whole section in the writer's language. This format is
|
|
685
|
+
required for cartoon stories regardless of which assistant (Claude or Codex) you
|
|
686
|
+
are; it never applies to fiction stories.
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export function generateStoryInstructions(
|
|
691
|
+
contentType: "fiction" | "cartoon",
|
|
692
|
+
// Cartoon instructions branch by provider so a Codex cartoon session is told to
|
|
693
|
+
// create the real clean-image file, while Claude/legacy sessions get the manual
|
|
694
|
+
// prompt-and-import handoff. Fiction ignores provider (always Claude). Absent ⇒
|
|
695
|
+
// "claude" (matches the fiction-safe absent⇒Claude default; see #268).
|
|
696
|
+
provider: AgentProvider = "claude",
|
|
697
|
+
): string {
|
|
698
|
+
if (contentType === "cartoon") return cartoonInstructions(provider);
|
|
699
|
+
return fictionInstructions();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const MARKER_PREFIX = "<!-- plotlink-ows:story-instructions:";
|
|
703
|
+
|
|
704
|
+
// Cartoon markers encode the provider so a story repaired Claude⇒Codex (or vice
|
|
705
|
+
// versa) regenerates its CLAUDE.md on the next spawn instead of keeping stale,
|
|
706
|
+
// provider-mismatched wording. Fiction has no provider variant — its marker is
|
|
707
|
+
// unchanged for rollback safety.
|
|
708
|
+
function marker(contentType: "fiction" | "cartoon", provider: AgentProvider): string {
|
|
709
|
+
const suffix = contentType === "cartoon" ? `cartoon:${provider}` : "fiction";
|
|
710
|
+
return `${MARKER_PREFIX}${suffix} -->`;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function writeStoryInstructions(
|
|
714
|
+
storyDir: string,
|
|
715
|
+
contentType: "fiction" | "cartoon",
|
|
716
|
+
provider: AgentProvider = "claude",
|
|
717
|
+
): void {
|
|
718
|
+
const claudeMdPath = path.join(storyDir, "CLAUDE.md");
|
|
719
|
+
const expectedMarker = marker(contentType, provider);
|
|
720
|
+
|
|
721
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
722
|
+
try {
|
|
723
|
+
const firstLine = fs.readFileSync(claudeMdPath, "utf-8").split("\n")[0];
|
|
724
|
+
if (firstLine === expectedMarker) return;
|
|
725
|
+
if (!firstLine.startsWith(MARKER_PREFIX)) return;
|
|
726
|
+
} catch { /* regenerate on error */ }
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const content = expectedMarker + "\n" + generateStoryInstructions(contentType, provider);
|
|
730
|
+
fs.writeFileSync(claudeMdPath, content, "utf-8");
|
|
731
|
+
}
|