init.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { Command } from "commander";
  2. import { access, readFile, writeFile, mkdir } from "node:fs/promises";
  3. import { join, basename } from "node:path";
  4. import { log, logError, GLOBAL_ENV_PATH } from "../utils.js";
  5. async function hasGlobalConfig(): Promise<boolean> {
  6. try {
  7. const content = await readFile(GLOBAL_ENV_PATH, "utf-8");
  8. return content.includes("INKOS_LLM_API_KEY=") && !content.includes("your-api-key-here");
  9. } catch {
  10. return false;
  11. }
  12. }
  13. export const initCommand = new Command("init")
  14. .description("Initialize an InkOS project (current directory by default)")
  15. .argument("[name]", "Project name (creates subdirectory). Omit to init current directory.")
  16. .option("--lang <language>", "Default writing language: zh (Chinese) or en (English)", "zh")
  17. .action(async (name: string | undefined, opts: { lang?: string }) => {
  18. const projectDir = name ? join(process.cwd(), name) : process.cwd();
  19. const projectName = name ?? basename(projectDir);
  20. try {
  21. await mkdir(projectDir, { recursive: true });
  22. // Check if inkos.json already exists
  23. const configPath = join(projectDir, "inkos.json");
  24. try {
  25. await access(configPath);
  26. throw new Error(`inkos.json already exists in ${projectDir}. Use a different directory or delete the existing project.`);
  27. } catch (e) {
  28. if (e instanceof Error && e.message.includes("already exists")) throw e;
  29. // File doesn't exist, good
  30. }
  31. await mkdir(join(projectDir, "books"), { recursive: true });
  32. await mkdir(join(projectDir, "radar"), { recursive: true });
  33. const config = {
  34. name: projectName,
  35. version: "0.1.0",
  36. language: opts.lang ?? "zh",
  37. llm: {
  38. provider: process.env.INKOS_LLM_PROVIDER ?? "openai",
  39. baseUrl: process.env.INKOS_LLM_BASE_URL ?? "https://api.openai.com/v1",
  40. model: process.env.INKOS_LLM_MODEL ?? "gpt-4o",
  41. },
  42. notify: [],
  43. daemon: {
  44. schedule: {
  45. radarCron: "0 */6 * * *",
  46. writeCron: "*/15 * * * *",
  47. },
  48. maxConcurrentBooks: 3,
  49. },
  50. };
  51. await writeFile(
  52. join(projectDir, "inkos.json"),
  53. JSON.stringify(config, null, 2),
  54. "utf-8",
  55. );
  56. const global = await hasGlobalConfig();
  57. if (global) {
  58. await writeFile(
  59. join(projectDir, ".env"),
  60. [
  61. "# Project-level LLM overrides (optional)",
  62. "# Global config at ~/.inkos/.env will be used by default.",
  63. "# Uncomment below to override for this project only:",
  64. "# INKOS_LLM_PROVIDER=openai",
  65. "# INKOS_LLM_BASE_URL=https://api.openai.com/v1",
  66. "# INKOS_LLM_API_KEY=your-api-key-here",
  67. "# INKOS_LLM_MODEL=gpt-4o",
  68. ].join("\n"),
  69. "utf-8",
  70. );
  71. } else {
  72. await writeFile(
  73. join(projectDir, ".env"),
  74. [
  75. "# LLM Configuration",
  76. "# Tip: Run 'inkos config set-global' to set once for all projects.",
  77. "# Provider: openai (OpenAI / compatible proxy), anthropic (Anthropic native)",
  78. "INKOS_LLM_PROVIDER=openai",
  79. "INKOS_LLM_BASE_URL=https://api.openai.com/v1",
  80. "INKOS_LLM_API_KEY=your-api-key-here",
  81. "INKOS_LLM_MODEL=gpt-4o",
  82. "",
  83. "# Optional parameters (defaults shown):",
  84. "# INKOS_LLM_TEMPERATURE=0.7",
  85. "# INKOS_LLM_MAX_TOKENS=8192",
  86. "# INKOS_LLM_THINKING_BUDGET=0 # Anthropic extended thinking budget",
  87. "# INKOS_LLM_API_FORMAT=chat # chat (default) or responses (OpenAI Responses API)",
  88. "",
  89. "# Anthropic example:",
  90. "# INKOS_LLM_PROVIDER=anthropic",
  91. "# INKOS_LLM_BASE_URL=https://api.anthropic.com",
  92. "# INKOS_LLM_MODEL=claude-sonnet-4-5-20250514",
  93. ].join("\n"),
  94. "utf-8",
  95. );
  96. }
  97. await writeFile(
  98. join(projectDir, ".gitignore"),
  99. [".env", "node_modules/", ".DS_Store"].join("\n"),
  100. "utf-8",
  101. );
  102. log(`Project initialized at ${projectDir}`);
  103. log("");
  104. if (global) {
  105. log("Global LLM config detected. Ready to go!");
  106. log("");
  107. log("Next steps:");
  108. if (name) log(` cd ${name}`);
  109. log(" inkos book create --title '我的小说' --genre xuanhuan --platform tomato");
  110. } else {
  111. log("Next steps:");
  112. if (name) log(` cd ${name}`);
  113. log(" # Option 1: Set global config (recommended, one-time):");
  114. log(" inkos config set-global --provider openai --base-url https://api.openai.com/v1 --api-key sk-xxx --model gpt-4o");
  115. log(" # Option 2: Edit .env for this project only");
  116. log("");
  117. log(" inkos book create --title '我的小说' --genre xuanhuan --platform tomato");
  118. }
  119. log(" inkos write next <book-id>");
  120. } catch (e) {
  121. logError(`Failed to initialize project: ${e}`);
  122. process.exit(1);
  123. }
  124. });