Browse Source

feat: add import CLI command and enhance style command

- New 'import canon' command for spinoff canon import
- Style command: add statistical profile display
- Register import command in CLI index
majx_mac 8 months ago
parent
commit
fb8cabe640

+ 49 - 0
packages/cli/src/commands/import.ts

@@ -0,0 +1,49 @@
+import { Command } from "commander";
+import { PipelineRunner } from "@actalk/inkos-core";
+import { loadConfig, createClient, findProjectRoot, resolveBookId, log, logError } from "../utils.js";
+
+export const importCommand = new Command("import")
+  .description("Import external data into a book");
+
+importCommand
+  .command("canon")
+  .description("Import parent book's canon for spinoff writing")
+  .argument("[target-book-id]", "Target book ID (auto-detected if only one book)")
+  .requiredOption("--from <parent-book-id>", "Parent book ID to import canon from")
+  .option("--json", "Output JSON")
+  .action(async (targetBookIdArg: string | undefined, opts) => {
+    try {
+      const root = findProjectRoot();
+      const targetBookId = await resolveBookId(targetBookIdArg, root);
+      const config = await loadConfig();
+      const client = createClient(config);
+
+      const pipeline = new PipelineRunner({
+        client,
+        model: config.llm.model,
+        projectRoot: root,
+      });
+
+      if (!opts.json) log(`Importing canon from "${opts.from}" into "${targetBookId}"...`);
+
+      await pipeline.importCanon(targetBookId, opts.from);
+
+      if (opts.json) {
+        log(JSON.stringify({
+          targetBookId,
+          parentBookId: opts.from,
+          output: "story/parent_canon.md",
+        }, null, 2));
+      } else {
+        log(`Canon imported: story/parent_canon.md`);
+        log(`Writer and auditor will auto-detect this file for spinoff mode.`);
+      }
+    } catch (e) {
+      if (opts.json) {
+        log(JSON.stringify({ error: String(e) }));
+      } else {
+        logError(`Canon import failed: ${e}`);
+      }
+      process.exit(1);
+    }
+  });

+ 36 - 6
packages/cli/src/commands/style.ts

@@ -1,6 +1,6 @@
 import { Command } from "commander";
-import { StateManager, analyzeStyle } from "@actalk/inkos-core";
-import { findProjectRoot, resolveBookId, log, logError } from "../utils.js";
+import { StateManager, analyzeStyle, PipelineRunner } from "@actalk/inkos-core";
+import { loadConfig, createClient, findProjectRoot, resolveBookId, log, logError } from "../utils.js";
 import { readFile, writeFile, mkdir } from "node:fs/promises";
 import { join, resolve } from "node:path";
 
@@ -43,10 +43,12 @@ styleCommand
 
 styleCommand
   .command("import")
-  .description("Import a style profile into a book")
+  .description("Import style profile + generate style guide (LLM) into a book")
   .argument("<file>", "Text file to analyze and import")
   .argument("[book-id]", "Book ID (auto-detected if only one book)")
   .option("--name <name>", "Source name for the profile")
+  .option("--stats-only", "Only save statistical profile, skip LLM style guide generation")
+  .option("--json", "Output JSON")
   .action(async (file: string, bookIdArg: string | undefined, opts) => {
     try {
       const root = findProjectRoot();
@@ -65,10 +67,38 @@ styleCommand
         "utf-8",
       );
 
-      log(`Style profile imported to "${bookId}" from "${file}"`);
-      log(`  Avg sentence: ${profile.avgSentenceLength} chars, TTR: ${profile.vocabularyDiversity}`);
+      if (!opts.json) log(`Statistical profile saved (TTR: ${profile.vocabularyDiversity})`);
+
+      // LLM-powered style guide generation
+      if (!opts.statsOnly) {
+        if (!opts.json) log("Generating qualitative style guide via LLM...");
+        const config = await loadConfig();
+        const client = createClient(config);
+        const pipeline = new PipelineRunner({
+          client,
+          model: config.llm.model,
+          projectRoot: root,
+        });
+        await pipeline.generateStyleGuide(bookId, text);
+        if (!opts.json) log("Style guide (style_guide.md) generated.");
+      }
+
+      if (opts.json) {
+        log(JSON.stringify({
+          bookId,
+          file,
+          statsProfile: `story/style_profile.json`,
+          styleGuide: opts.statsOnly ? null : `story/style_guide.md`,
+        }, null, 2));
+      } else {
+        log(`Style imported to "${bookId}" from "${file}"`);
+      }
     } catch (e) {
-      logError(`Import failed: ${e}`);
+      if (opts.json) {
+        log(JSON.stringify({ error: String(e) }));
+      } else {
+        logError(`Import failed: ${e}`);
+      }
       process.exit(1);
     }
   });

+ 2 - 0
packages/cli/src/index.ts

@@ -20,6 +20,7 @@ import { updateCommand } from "./commands/update.js";
 import { detectCommand } from "./commands/detect.js";
 import { styleCommand } from "./commands/style.js";
 import { analyticsCommand } from "./commands/analytics.js";
+import { importCommand } from "./commands/import.js";
 
 const program = new Command();
 
@@ -48,5 +49,6 @@ program.addCommand(updateCommand);
 program.addCommand(detectCommand);
 program.addCommand(styleCommand);
 program.addCommand(analyticsCommand);
+program.addCommand(importCommand);
 
 program.parse();