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,80 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { StoryProgress, EpisodeProgress } from "@app-lib/story-progress";
|
|
3
|
+
|
|
4
|
+
interface EpisodesPageProps {
|
|
5
|
+
storyName: string;
|
|
6
|
+
authFetch: (url: string, opts?: RequestInit) => Promise<Response>;
|
|
7
|
+
onOpenFile: (storyName: string, file: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* "Episodes" workflow page for cartoon stories (#439, spec §2 nav target).
|
|
12
|
+
*
|
|
13
|
+
* A reader-ordered list of the story's episodes (Genesis = Episode 1, plot-NN =
|
|
14
|
+
* Episode N+1) with status, so a normal creator manages episodes without the
|
|
15
|
+
* file tree. Clicking one opens it in the preview. The dedicated rich episode
|
|
16
|
+
* manager is a later ticket (§11); this is the workflow nav's episodes target.
|
|
17
|
+
*/
|
|
18
|
+
export function EpisodesPage({ storyName, authFetch, onOpenFile }: EpisodesPageProps) {
|
|
19
|
+
const [episodes, setEpisodes] = useState<EpisodeProgress[] | null>(null);
|
|
20
|
+
const [loading, setLoading] = useState(true);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
const load = async () => {
|
|
25
|
+
setLoading(true);
|
|
26
|
+
try {
|
|
27
|
+
const res = await authFetch(`/api/stories/${storyName}/progress`);
|
|
28
|
+
const data: StoryProgress | null = res.ok ? await res.json() : null;
|
|
29
|
+
if (!cancelled) {
|
|
30
|
+
setEpisodes(Array.isArray(data?.episodes) ? data!.episodes : null);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
if (!cancelled) { setEpisodes(null); setLoading(false); }
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
load();
|
|
38
|
+
return () => { cancelled = true; };
|
|
39
|
+
}, [storyName, authFetch]);
|
|
40
|
+
|
|
41
|
+
if (loading) {
|
|
42
|
+
return <div className="h-full flex items-center justify-center text-muted text-sm" data-testid="episodes-loading">Loading episodes…</div>;
|
|
43
|
+
}
|
|
44
|
+
if (!episodes) {
|
|
45
|
+
return <div className="h-full flex items-center justify-center text-muted text-sm">Could not load episodes.</div>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="h-full overflow-y-auto px-4 py-4" data-testid="episodes-page">
|
|
50
|
+
<h2 className="text-base font-serif text-foreground">Episodes</h2>
|
|
51
|
+
<p className="mt-0.5 text-[11px] text-muted">Genesis is Episode 1; each plot file is the next episode.</p>
|
|
52
|
+
|
|
53
|
+
{episodes.length === 0 ? (
|
|
54
|
+
<p className="mt-4 text-xs text-muted italic" data-testid="episodes-empty">No episodes yet — write the Genesis to start Episode 1.</p>
|
|
55
|
+
) : (
|
|
56
|
+
<ol className="mt-3 flex flex-col gap-1">
|
|
57
|
+
{episodes.map((ep) => (
|
|
58
|
+
<li key={ep.file}>
|
|
59
|
+
<button
|
|
60
|
+
onClick={() => onOpenFile(storyName, ep.file)}
|
|
61
|
+
data-testid={`episodes-row-${ep.file}`}
|
|
62
|
+
data-state={ep.state}
|
|
63
|
+
className="w-full text-left flex items-start gap-2 rounded px-2 py-1.5 hover:bg-surface"
|
|
64
|
+
>
|
|
65
|
+
<span className="min-w-0 flex-1">
|
|
66
|
+
<span className="flex items-center gap-1.5">
|
|
67
|
+
<span className="text-xs font-medium text-foreground">{ep.label}</span>
|
|
68
|
+
{ep.title && <span className="text-[11px] text-muted truncate">· {ep.title}</span>}
|
|
69
|
+
<span className="ml-auto text-[10px] text-muted">{ep.file}</span>
|
|
70
|
+
</span>
|
|
71
|
+
<span className="block text-[11px] text-muted">{ep.summary}</span>
|
|
72
|
+
</span>
|
|
73
|
+
</button>
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ol>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { groupCartoonIssues, type CartoonChecklist } from "@app-lib/cartoon-readiness";
|
|
2
|
+
|
|
3
|
+
type StepStatus = "done" | "current" | "todo";
|
|
4
|
+
|
|
5
|
+
const STATUS_MARK: Record<StepStatus, string> = { done: "✓", current: "▸", todo: "○" };
|
|
6
|
+
|
|
7
|
+
interface FinishEpisodePanelProps {
|
|
8
|
+
/** Writer-language production checklist for this episode (null ⇒ not a cartoon plot). */
|
|
9
|
+
checklist: CartoonChecklist | null;
|
|
10
|
+
/** Flat readiness/upload issues; grouped by actionable step for display. */
|
|
11
|
+
issues: string[];
|
|
12
|
+
/** Run the guided finish flow (upload finals → prepare episode markdown, in order). */
|
|
13
|
+
onFinish: () => void;
|
|
14
|
+
/** True while the finish flow is running. */
|
|
15
|
+
finishing: boolean;
|
|
16
|
+
/** Live progress line shown on the button while finishing (e.g. "Uploading cut 2 (2/7)…"). */
|
|
17
|
+
progressText?: string;
|
|
18
|
+
/** Whether there is anything left to finish (≥1 exported final not yet published). */
|
|
19
|
+
canFinish: boolean;
|
|
20
|
+
/** The publish markdown is built and passes readiness — "Episode sequence prepared". */
|
|
21
|
+
markdownReady?: boolean;
|
|
22
|
+
/** The episode is published on-chain. */
|
|
23
|
+
published?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface DisplayStep {
|
|
27
|
+
key: string;
|
|
28
|
+
label: string;
|
|
29
|
+
status: StepStatus;
|
|
30
|
+
detail: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Guided "Finish episode" flow for a cartoon plot (#414).
|
|
35
|
+
*
|
|
36
|
+
* The end-to-end pilot showed the production tail (export → upload → prepare
|
|
37
|
+
* markdown → publish) was technically complete but fragmented: a writer had to know
|
|
38
|
+
* which low-level button to click and read a flat wall of "Cut N: …" errors. This
|
|
39
|
+
* panel makes the tail one guided surface in writer language: it shows the six
|
|
40
|
+
* production steps with live status, offers ONE primary "Finish episode" action
|
|
41
|
+
* that runs the remaining automatable steps in order (resumable — already-uploaded
|
|
42
|
+
* cuts are skipped by the caller), and groups any blockers under the actionable
|
|
43
|
+
* step heading instead of a long red list. The lower-level controls stay available
|
|
44
|
+
* elsewhere in the workspace for manual recovery.
|
|
45
|
+
*
|
|
46
|
+
* Renders nothing when there is no checklist (e.g. a fiction plot or an unparsed
|
|
47
|
+
* cut plan), so it never appears outside the cartoon flow.
|
|
48
|
+
*/
|
|
49
|
+
export function FinishEpisodePanel({
|
|
50
|
+
checklist,
|
|
51
|
+
issues,
|
|
52
|
+
onFinish,
|
|
53
|
+
finishing,
|
|
54
|
+
progressText,
|
|
55
|
+
canFinish,
|
|
56
|
+
markdownReady = false,
|
|
57
|
+
published = false,
|
|
58
|
+
}: FinishEpisodePanelProps) {
|
|
59
|
+
if (!checklist || checklist.steps.length === 0) return null;
|
|
60
|
+
|
|
61
|
+
const groups = groupCartoonIssues(issues);
|
|
62
|
+
|
|
63
|
+
// The base checklist (plan → upload) models per-cut art/lettering/export/upload
|
|
64
|
+
// progress; it has no notion of the publish markdown being assembled. #414 needs
|
|
65
|
+
// the post-upload tail modelled explicitly, so replace its single "publish" step
|
|
66
|
+
// with two real states: "Episode sequence prepared" (markdown built + ready) and
|
|
67
|
+
// "Ready to publish" (which becomes "Published" once it's on-chain).
|
|
68
|
+
const uploadDone = checklist.steps.find((s) => s.key === "upload")?.status === "done";
|
|
69
|
+
const ready = uploadDone && markdownReady && !published; // ready to publish, not yet published
|
|
70
|
+
|
|
71
|
+
const assembleStatus: StepStatus = published || markdownReady ? "done" : uploadDone ? "current" : "todo";
|
|
72
|
+
const readyStatus: StepStatus = published ? "done" : ready ? "current" : "todo";
|
|
73
|
+
|
|
74
|
+
const steps: DisplayStep[] = [
|
|
75
|
+
...checklist.steps.filter((s) => s.key !== "publish"),
|
|
76
|
+
{ key: "assemble", label: "Episode sequence prepared", status: assembleStatus, detail: null },
|
|
77
|
+
{ key: "ready", label: published ? "Published to PlotLink" : "Ready to publish", status: readyStatus, detail: null },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const buttonLabel = finishing
|
|
81
|
+
? progressText || "Finishing…"
|
|
82
|
+
: published
|
|
83
|
+
? "Published ✓"
|
|
84
|
+
: ready
|
|
85
|
+
? "Episode ready to publish"
|
|
86
|
+
: "Finish episode";
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
className="px-3 py-2 border-b border-border bg-surface/50 space-y-2 flex-shrink-0"
|
|
91
|
+
data-testid="finish-episode-panel"
|
|
92
|
+
>
|
|
93
|
+
<div className="flex items-center justify-between gap-2">
|
|
94
|
+
<span className="text-[11px] font-medium text-foreground">Finish episode</span>
|
|
95
|
+
{checklist.nextStep && (
|
|
96
|
+
<span className="text-[10px] text-muted truncate" data-testid="finish-next-step">
|
|
97
|
+
Next: {checklist.nextStep}
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Writer-language step status — the exact webtoon production sequence. */}
|
|
103
|
+
<ol className="flex flex-wrap gap-1.5">
|
|
104
|
+
{steps.map((s) => (
|
|
105
|
+
<li
|
|
106
|
+
key={s.key}
|
|
107
|
+
data-testid={`finish-step-${s.key}`}
|
|
108
|
+
data-status={s.status}
|
|
109
|
+
className={`flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] ${
|
|
110
|
+
s.status === "current"
|
|
111
|
+
? "border-accent/40 bg-accent/10 text-accent"
|
|
112
|
+
: s.status === "done"
|
|
113
|
+
? "border-border bg-background/70 text-foreground"
|
|
114
|
+
: "border-border/70 bg-background/40 text-muted"
|
|
115
|
+
}`}
|
|
116
|
+
>
|
|
117
|
+
<span aria-hidden>{STATUS_MARK[s.status]}</span>
|
|
118
|
+
<span>{s.label}</span>
|
|
119
|
+
{s.detail && <span className="text-muted">· {s.detail}</span>}
|
|
120
|
+
</li>
|
|
121
|
+
))}
|
|
122
|
+
</ol>
|
|
123
|
+
|
|
124
|
+
<button
|
|
125
|
+
onClick={onFinish}
|
|
126
|
+
disabled={finishing || !canFinish}
|
|
127
|
+
data-testid="finish-episode-btn"
|
|
128
|
+
title="Upload the exported final panels, then prepare the episode for publishing — picks up where it left off"
|
|
129
|
+
className="px-3 py-1 text-xs border border-accent/40 text-accent rounded hover:bg-accent/5 disabled:opacity-50"
|
|
130
|
+
>
|
|
131
|
+
{buttonLabel}
|
|
132
|
+
</button>
|
|
133
|
+
|
|
134
|
+
{/* Blockers grouped by the step that fixes them, not a flat red list. */}
|
|
135
|
+
{groups.length > 0 && (
|
|
136
|
+
<div className="space-y-1.5" data-testid="finish-issues">
|
|
137
|
+
{groups.map((g) => (
|
|
138
|
+
<div key={g.key} data-testid={`finish-issue-group-${g.key}`} className="text-[10px]">
|
|
139
|
+
<p className="font-medium text-amber-700">{g.title}</p>
|
|
140
|
+
<ul className="ml-3 list-disc text-muted">
|
|
141
|
+
{g.lines.map((line, i) => (
|
|
142
|
+
<li key={i}>{line}</li>
|
|
143
|
+
))}
|
|
144
|
+
</ul>
|
|
145
|
+
</div>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -154,7 +154,7 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
|
|
|
154
154
|
<div className="text-center space-y-2">
|
|
155
155
|
<h1 className="text-2xl font-serif text-foreground">Write. Publish. Earn.</h1>
|
|
156
156
|
<p className="text-muted text-sm">
|
|
157
|
-
Claude
|
|
157
|
+
Claude or Codex helps create your story. You publish it on-chain.
|
|
158
158
|
</p>
|
|
159
159
|
</div>
|
|
160
160
|
|
|
@@ -173,12 +173,15 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
|
|
|
173
173
|
<div className="rounded border border-border p-4 space-y-2 text-xs text-muted">
|
|
174
174
|
<p className="font-medium text-foreground text-sm">How it works</p>
|
|
175
175
|
<ol className="space-y-1.5 list-decimal list-inside">
|
|
176
|
-
<li>Open the <strong>Stories</strong> tab —
|
|
177
|
-
<li>Tell
|
|
178
|
-
<li>Review the live preview as
|
|
176
|
+
<li>Open the <strong>Stories</strong> tab — your writing agent launches in the terminal</li>
|
|
177
|
+
<li>Tell the agent your story idea — it brainstorms, outlines, and writes</li>
|
|
178
|
+
<li>Review the live preview as the agent creates files</li>
|
|
179
179
|
<li>Click <strong>Publish</strong> to put your story on-chain</li>
|
|
180
180
|
<li>Earn 5% royalties on every trade at <a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent underline">plotlink.xyz</a></li>
|
|
181
181
|
</ol>
|
|
182
|
+
<p className="text-[11px] text-muted">
|
|
183
|
+
Fiction defaults to Claude; cartoon mode uses Codex for clean-image generation.
|
|
184
|
+
</p>
|
|
182
185
|
</div>
|
|
183
186
|
|
|
184
187
|
<div className="text-center">
|