From cb3a36426351b5c2f798d038354b213201bfd49d Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 14 May 2026 16:37:41 +0900 Subject: [PATCH 1/2] [#183] Auto-generate CLAUDE.md in ~/.plotlink-ows/ on startup On each app startup, writes/updates ~/.plotlink-ows/CLAUDE.md with full API reference (auth, endpoints, workflows, genres, languages). Skips the write if the file already matches the current version. Claude sessions spawned in story directories auto-discover this file. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/lib/generate-claude-md.ts | 138 ++++++++++++++++++++++++++++++++++ app/server.ts | 4 + package.json | 2 +- 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 app/lib/generate-claude-md.ts diff --git a/app/lib/generate-claude-md.ts b/app/lib/generate-claude-md.ts new file mode 100644 index 0000000..0f17e73 --- /dev/null +++ b/app/lib/generate-claude-md.ts @@ -0,0 +1,138 @@ +import fs from "fs"; +import path from "path"; +import { CONFIG_DIR } from "./paths"; + +const CLAUDE_MD_PATH = path.join(CONFIG_DIR, "CLAUDE.md"); + +/** Read the installed package version from package.json */ +function getVersion(): string { + const pkgPath = path.join(__dirname, "..", "..", "package.json"); + try { + return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version; + } catch { + return "unknown"; + } +} + +function generateContent(version: string, port: number): string { + return `# PlotLink OWS — Local Writer API (v${version}) + +> Auto-generated by PlotLink OWS on startup. Do not edit manually. + +Local writer app running at \`http://localhost:${port}\`. +All endpoints except auth use \`Authorization: Bearer {token}\` headers. + +## Authentication + +The OWS passphrase is stored in \`~/.plotlink-ows/.env\` as \`OWS_PASSPHRASE\`. +For login, the passphrase is hashed with HMAC-SHA256 and compared against the stored hash. + +| Endpoint | Method | Auth | Purpose | +|----------|--------|------|---------| +| \`/api/auth/status\` | GET | No | Check if passphrase is configured | +| \`/api/auth/setup\` | POST | No | First-run passphrase setup (≥4 chars) → returns \`{ token }\` | +| \`/api/auth/login\` | POST | No | Login with passphrase → returns \`{ token }\` (24h TTL) | +| \`/api/auth/verify\` | GET | Bearer | Check token validity | +| \`/api/auth/reset-passphrase\` | POST | Bearer | Update passphrase | + +## Publishing + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| \`/api/publish/preflight\` | GET | Check wallet balance, Filebase config | +| \`/api/publish/file\` | POST | Publish story on-chain (SSE stream of progress events) | +| \`/api/publish/retry-index\` | POST | Retry indexing for a published file | +| \`/api/publish/upload-cover\` | POST | Upload cover image — FormData \`file\` field, **WebP or JPEG only**, max 500KB → returns \`{ cid }\` | +| \`/api/publish/update-storyline\` | POST | Update storyline metadata (coverCid, genre, language, isNsfw) | + +**Publish flow:** Upload to IPFS → estimate gas → sign with OWS wallet → broadcast → confirm → index on plotlink.xyz (8s delay + 10 retries × 30s). Genesis files call \`createStoryline\`, plot files (\`plot-*.md\`) call \`chainPlot\`. Content limit: 10K chars. + +**Cover update workflow:** +1. \`POST /api/publish/upload-cover\` with image file → get \`cid\` +2. \`POST /api/publish/update-storyline\` with \`{ storylineId, coverCid: cid }\` → updates on plotlink.xyz + +**Metadata update workflow:** +1. \`POST /api/publish/update-storyline\` with \`{ storylineId, genre?, language?, isNsfw? }\` + +Both upload-cover and update-storyline sign messages with the OWS wallet. + +## Stories + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| \`/api/stories\` | GET | List all stories | +| \`/api/stories/archived\` | GET | List archived stories | +| \`/api/stories/archive\` | POST | Archive a story \`{ name }\` | +| \`/api/stories/restore\` | POST | Restore archived story \`{ name }\` | +| \`/api/stories/:name\` | GET | Story detail with file contents | +| \`/api/stories/:name/:file\` | GET | Single file content and publish status | +| \`/api/stories/:name/:file\` | PUT | Update file content \`{ content }\` | +| \`/api/stories/:name/:file/publish-status\` | POST | Record publish result (txHash, storylineId, etc.) | +| \`/api/stories/:name/:file/mark-not-indexed\` | POST | Mark file as not indexed \`{ indexError? }\` | + +## Terminal + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| \`/api/terminal/spawn\` | POST | Spawn Claude CLI session for a story \`{ storyName?, resume? }\` | +| \`/api/terminal/session/:storyName\` | GET | Get stored session ID for a story | +| \`/api/terminal/status\` | GET | List all active terminal sessions | +| \`/api/terminal/rename\` | POST | Rename session \`{ oldName, newName }\` | +| \`/api/terminal/stop\` | POST | Kill default PTY (legacy) | +| \`/api/terminal/:storyName\` | DELETE | Kill a story's PTY | +| \`/api/terminal/:storyName/discard\` | DELETE | Kill PTY and clean metadata | +| \`/ws/terminal\` | WebSocket | Live PTY relay \`?token={token}&story={name}&resume={bool}\` | + +## Other Endpoints + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| \`/api/wallet\` | GET | Wallet info and balances (ETH, USDC, PLOT) | +| \`/api/wallet/create\` | POST | Create OWS wallet if not exists | +| \`/api/dashboard\` | GET | Writer dashboard stats (stories, costs, royalties) | +| \`/api/settings/register-agent\` | POST | Register wallet on ERC-8004 | +| \`/api/settings/generate-binding\` | POST | Generate wallet binding proof | +| \`/api/settings/link-status\` | GET | Check ERC-8004 registration status | +| \`/api/health\` | GET | Health check | + +## Story File Structure + +Stories live in \`~/.plotlink-ows/stories/{story-name}/\`: + +\`\`\` +stories/{story-name}/ + structure.md # Outline, characters, arc + genesis.md # Synopsis hook (~1000 chars) + plot-01.md # Chapter 1 (max 10K chars) + ... +\`\`\` + +## Valid Genres (21) + +Romance, Fantasy, Science Fiction, Mystery, Thriller, Horror, Adventure, Historical Fiction, Contemporary Lit, Humor, Poetry, Non-Fiction, Fanfiction, Short Story, Paranormal, Werewolf, LGBTQ+, New Adult, Teen Fiction, Diverse Lit, Others + +## Valid Languages (11) + +English, Chinese, Korean, Japanese, Spanish, French, Hindi, Arabic, Portuguese, Russian, Others +`; +} + +/** + * Write/update ~/.plotlink-ows/CLAUDE.md on startup. + * Skips the write if the file already contains the current version stamp. + */ +export function generateClaudeMd(): void { + const version = getVersion(); + const port = Number(process.env.APP_PORT) || 7777; + + // Check if the file already matches the current version + if (fs.existsSync(CLAUDE_MD_PATH)) { + try { + const firstLine = fs.readFileSync(CLAUDE_MD_PATH, "utf-8").split("\n")[0]; + if (firstLine.includes(`(v${version})`)) return; + } catch { /* regenerate on read error */ } + } + + fs.writeFileSync(CLAUDE_MD_PATH, generateContent(version, port), "utf-8"); + console.log(` Updated ~/.plotlink-ows/CLAUDE.md (v${version})`); +} diff --git a/app/server.ts b/app/server.ts index 836d24f..c8b72f8 100644 --- a/app/server.ts +++ b/app/server.ts @@ -23,6 +23,7 @@ import { terminalRoutes, attachTerminalWs } from "./routes/terminal"; import { storiesRoutes } from "./routes/stories"; import { settingsRoutes } from "./routes/settings"; import { initDb } from "./db"; +import { generateClaudeMd } from "./lib/generate-claude-md"; import { execSync } from "child_process"; import fs from "fs"; @@ -117,6 +118,9 @@ async function start() { // Auto-migrate from old package-relative paths migrateOldData(); + // Generate/update ~/.plotlink-ows/CLAUDE.md for agent discovery + generateClaudeMd(); + // Run Prisma db push to ensure schema is up to date const schemaPath = path.join(__dirname, "prisma", "schema.prisma"); execSync(`npx prisma db push --schema ${schemaPath} --skip-generate`, { diff --git a/package.json b/package.json index 2ef7239..3d8de2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink-ows", - "version": "1.0.28", + "version": "1.0.29", "bin": { "plotlink-ows": "./bin/plotlink-ows.js" }, From e20a4a37e6d1884e27623d87ddc45ef179368e28 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 14 May 2026 16:41:00 +0900 Subject: [PATCH 2/2] [#183] Label genres/languages as 'supported' instead of 'valid' Co-Authored-By: Claude Opus 4.6 (1M context) --- app/lib/generate-claude-md.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/generate-claude-md.ts b/app/lib/generate-claude-md.ts index 0f17e73..9ba5e49 100644 --- a/app/lib/generate-claude-md.ts +++ b/app/lib/generate-claude-md.ts @@ -107,11 +107,11 @@ stories/{story-name}/ ... \`\`\` -## Valid Genres (21) +## Supported Genres (21) Romance, Fantasy, Science Fiction, Mystery, Thriller, Horror, Adventure, Historical Fiction, Contemporary Lit, Humor, Poetry, Non-Fiction, Fanfiction, Short Story, Paranormal, Werewolf, LGBTQ+, New Adult, Teen Fiction, Diverse Lit, Others -## Valid Languages (11) +## Supported Languages (11) English, Chinese, Korean, Japanese, Spanish, French, Hindi, Arabic, Portuguese, Russian, Others `;