import { Command } from "commander"; import * as dotenv from "dotenv"; import { Configuration, OpenAIApi } from "openai"; import { Plot } from "@xeserv/plottoriffic"; import { generateName } from "@kotofurumiya/th-namegen"; import * as fs from "node:fs/promises"; import { existsSync as fileExists } from "fs"; import { readPackage } from "read-pkg"; import { execa } from "execa"; import * as book from "./book.js"; dotenv.config(); const packageInfo = await readPackage(); const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); const openai = new OpenAIApi(configuration); const program = new Command(); program .name(packageInfo.name) .description(packageInfo.description as string) .version(packageInfo.version); program .command("init [dir]") .description("create a new random folder for a book") .action(async (dir = `var/${generateName()}`) => { await fs.mkdir(dir, { recursive: true }); await fs.mkdir(`${dir}/src`, { recursive: true }); console.log(`created folder ${dir}`); }); program .command("genPlotto ") .description("generate a new book plotto description") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (fileExists(`${dir}/plotto.json`)) { console.error(`plotto data already exists in ${dir}`); process.exit(1); } const plot = await book.createPlot(dir); console.log(`created plot, subject: ${plot.subject}`); }); program .command("showPlotto ") .description("show the plotto description for a book directory") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } const plot: Plot = JSON.parse(await fs.readFile(`${dir}/plotto.json`, "utf8")); console.log(JSON.stringify(plot, undefined, " ")); }); program .command("genSummary ") .description("generate a new summary based on a plotto description") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (fileExists(`${dir}/summary.json`)) { console.error(`plot summary already exists in ${dir}`); process.exit(1); } const plot: Plot = JSON.parse(await fs.readFile(`${dir}/plotto.json`, "utf8")); const summary = await book.createAndParseSummary(dir, openai, plot); console.log(`generated book summary`); }); program .command("showSummary ") .description("dump high-level details about a plot summary") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (!fileExists(`${dir}/summary.json`)) { console.error(`plot summary does not exist in ${dir}, run genSummary?`); process.exit(1); } const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8")); console.log(`title: ${summary.title}\n${summary.plotSummary}\n\ncharacters:`); summary.characters.forEach((ch) => console.log(`- ${ch.name}: ${ch.role}`)); console.log("\nchapters:"); summary.chapterList.forEach(({ title, summary }) => console.log(`- ${title} - ${summary}`)); }); program .command("genChapterScenes ") .description("generate the list of scenes for all of the chapters in the chapter") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (!fileExists(`${dir}/summary.json`)) { console.error(`plot summary does not exist in ${dir}, run genSummary?`); process.exit(1); } if (fileExists(`${dir}/chapterScenes.json`)) { console.error(`plot summary already exists in ${dir}`); process.exit(1); } const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8")); const chapters: book.Chapter[] = []; for (const ch of summary.chapterList) { chapters.push(await book.createChapterScenes(dir, openai, summary, ch)); } console.log(chapters); await fs.writeFile(`${dir}/chapterScenes.json`, JSON.stringify(chapters)); }); program .command("writeScenes ") .description("write individual scenes to the disk in full detail (expensive command)") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (!fileExists(`${dir}/summary.json`)) { console.error(`plot summary does not exist in ${dir}, run genSummary?`); process.exit(1); } if (!fileExists(`${dir}/chapterScenes.json`)) { console.error(`plot summary does not exist in ${dir}, run genChapterScenes?`); process.exit(1); } if (fileExists(`${dir}/fnames.json`)) { console.error(`file name list already exists in ${dir}`); process.exit(1); } await fs.mkdir(`${dir}/src`, { recursive: true }); const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8")); const chapters: book.Chapter[] = JSON.parse( await fs.readFile(`${dir}/chapterScenes.json`, "utf8") ); const fnames: string[] = []; for (let [chNum, ch] of chapters.entries()) { chNum = chNum + 1; const fname = `ch-${chNum}-sc-00.md`; fnames.push(fname); await fs.writeFile(`${dir}/src/${fname}`, `# ${ch.title}\n\n`); for (let [sceneNum, scene] of ch.sceneDescriptions.entries()) { sceneNum = sceneNum + 1; fnames.push(await book.writeChapterScene(dir, openai, summary, ch, chNum, sceneNum, scene)); } } console.log(fnames); await fs.writeFile(`${dir}/fnames.json`, JSON.stringify(fnames, undefined, " ")); }); program .command("buildEbook ") .description("generate the .epub file for the novel") .action(async (dir) => { if (!fileExists(dir)) { console.error(`${dir} does not exist, run init?`); process.exit(1); } if (!fileExists(`${dir}/summary.json`)) { console.error(`plot summary does not exist in ${dir}, run genSummary?`); process.exit(1); } if (!fileExists(`${dir}/fnames.json`)) { console.error(`file name list doesn't exist in ${dir}, run writeScenes?`); process.exit(1); } const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8")); const fnames: string[] = JSON.parse(await fs.readFile(`${dir}/fnames.json`, "utf8")); await fs.writeFile( `${dir}/src/title.txt`, `--- title: "${summary.title}" author: Midori Yasomi rights: All rights reserved language: en-US --- ` ); await fs.writeFile(`${dir}/src/aboutAuthor.txt`, "---\n\n" + book.authorBio); let files = [`${dir}/src/title.txt`]; files = files.concat(fnames.map((fname) => `${dir}/src/${fname}`)); files = files.concat([`${dir}/src/aboutAuthor.txt`]); let args = ["-o", `${dir}/ebook.epub`, "--to", "epub"]; args = args.concat(files); await execa("pandoc", args); args = ["-o", `${dir}/ebook.html`, "--to", "html"]; args = args.concat(files); await execa("pandoc", args); args = ["--lua-filter", "./wordcount.lua"]; args = args.concat(files); const { stdout } = await execa("pandoc", args); console.log(stdout); }); program .command("writeOneScene ") .description("write a single scene of the novel, useful for debugging the generation process") .action(async (dir) => { await fs.mkdir(`${dir}/src`, { recursive: true }); const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8")); const chapters: book.Chapter[] = JSON.parse( await fs.readFile(`${dir}/chapterScenes.json`, "utf8") ); const chNum = 1; const sceneNum = 2; const ch = chapters[chNum - 1]; const scene = ch.sceneDescriptions[sceneNum - 1]; await book.writeChapterScene(dir, openai, summary, ch, chNum, sceneNum, scene); }); program.parse();