Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions app/lib/generate-claude-md.ts
Original file line number Diff line number Diff line change
@@ -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)
...
\`\`\`

## 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

## Supported 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})`);
}
4 changes: 4 additions & 0 deletions app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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`, {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plotlink-ows",
"version": "1.0.28",
"version": "1.0.29",
"bin": {
"plotlink-ows": "./bin/plotlink-ows.js"
},
Expand Down
Loading