book.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { Command } from "commander";
  2. import { PipelineRunner, StateManager, type BookConfig } from "@inkos/core";
  3. import { loadConfig, createClient, findProjectRoot, log, logError } from "../utils.js";
  4. export const bookCommand = new Command("book")
  5. .description("Manage books");
  6. bookCommand
  7. .command("create")
  8. .description("Create a new book with AI-generated foundation")
  9. .requiredOption("--title <title>", "Book title")
  10. .option("--genre <genre>", "Genre", "xuanhuan")
  11. .option("--platform <platform>", "Target platform", "tomato")
  12. .option("--target-chapters <n>", "Target chapter count", "200")
  13. .option("--chapter-words <n>", "Words per chapter", "3000")
  14. .action(async (opts) => {
  15. try {
  16. const config = await loadConfig();
  17. const client = createClient(config);
  18. const root = findProjectRoot();
  19. const bookId = opts.title
  20. .toLowerCase()
  21. .replace(/[^a-z0-9\u4e00-\u9fff]/g, "-")
  22. .replace(/-+/g, "-")
  23. .slice(0, 30);
  24. const now = new Date().toISOString();
  25. const book: BookConfig = {
  26. id: bookId,
  27. title: opts.title,
  28. platform: opts.platform,
  29. genre: opts.genre,
  30. status: "outlining",
  31. targetChapters: parseInt(opts.targetChapters, 10),
  32. chapterWordCount: parseInt(opts.chapterWords, 10),
  33. createdAt: now,
  34. updatedAt: now,
  35. };
  36. log(`Creating book "${book.title}" (${book.genre} / ${book.platform})...`);
  37. const pipeline = new PipelineRunner({
  38. client,
  39. model: config.llm.model,
  40. projectRoot: root,
  41. });
  42. await pipeline.initBook(book);
  43. log(`Book created: ${bookId}`);
  44. log(` Location: books/${bookId}/`);
  45. log(` Story bible, outline, style guide generated.`);
  46. log("");
  47. log(`Next: inkos write next ${bookId}`);
  48. } catch (e) {
  49. logError(`Failed to create book: ${e}`);
  50. process.exit(1);
  51. }
  52. });
  53. bookCommand
  54. .command("list")
  55. .description("List all books")
  56. .action(async () => {
  57. try {
  58. const root = findProjectRoot();
  59. const state = new StateManager(root);
  60. const bookIds = await state.listBooks();
  61. if (bookIds.length === 0) {
  62. log("No books found. Create one with: inkos book create --title '...'");
  63. return;
  64. }
  65. for (const id of bookIds) {
  66. const book = await state.loadBookConfig(id);
  67. const nextChapter = await state.getNextChapterNumber(id);
  68. log(` ${id} | ${book.title} | ${book.genre}/${book.platform} | ${book.status} | chapters: ${nextChapter - 1}`);
  69. }
  70. } catch (e) {
  71. logError(`Failed to list books: ${e}`);
  72. process.exit(1);
  73. }
  74. });