webnovel-writer.js 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. #!/usr/bin/env node
  2. import path from 'node:path'
  3. import { fileURLToPath } from 'node:url'
  4. import { CacheManager } from '../src/cache/index.js'
  5. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  6. const argv = process.argv.slice(2)
  7. const command = argv[0]
  8. if (!command || command === '--help') {
  9. console.log('用法:webnovel-writer <命令> [选项]')
  10. console.log('')
  11. console.log('精准读取接口(41 个,分布于 21 个命令;逐条清单见任务 prd.md AC2):')
  12. console.log(' read-chapter <章号> [--front-matter|--tail=N|--head=N|--摘要]')
  13. console.log(' read-chapters [--range=a-b --摘要|--recent=N --tail=M]')
  14. console.log(' list-chapters --章定位=推进 [--卷=N]')
  15. console.log(' read-thread <ID> [--履历|--收尾计划|--描述]')
  16. console.log(' list-threads [--悬了太久|--type=<t> [--status=<s>]|--strength=<强>]')
  17. console.log(' read-timeline [--current-and-prev|--current-volume|--卷=N|--在场=名]')
  18. console.log(' read-character <正名> [--front-matter|--section=<标题>]')
  19. console.log(' resolve-alias <别名>')
  20. console.log(' list-characters [--status=<状态>]')
  21. console.log(' read-worldview --section=<标题>')
  22. console.log(' read-secret <ID> [--基本信息|--内容]')
  23. console.log(' list-secrets [--reader-knows=false]')
  24. console.log(' read-outline [--总纲 [--section=<标题>|--结局]|--卷=N [--section=<标题>]]')
  25. console.log(' list-volumes')
  26. console.log(' grep-story <关键词> [--regex=<pattern>]')
  27. console.log(' report-overdue-threads | report-secret-accumulation | report-thread-activity --卷=N')
  28. console.log(' report-weak-hook-streak | report-book-stats | report-style-drift')
  29. process.exit(0)
  30. }
  31. // 解析选项与位置参数:--key=value → {key:value},--flag → {flag:true}
  32. function parseArgs(rest) {
  33. const options = {}
  34. const positionalArgs = []
  35. for (const arg of rest) {
  36. if (arg.startsWith('--')) {
  37. const m = arg.match(/^--([^=]+)(?:=(.*))?$/)
  38. if (m) options[m[1]] = m[2] !== undefined ? m[2] : true
  39. } else {
  40. positionalArgs.push(arg)
  41. }
  42. }
  43. return { options, positionalArgs }
  44. }
  45. let cache
  46. try {
  47. const commandPath = path.join(__dirname, '../src/commands', `${command}.js`)
  48. const commandUrl = new URL(`file:///${commandPath.replace(/\\/g, '/')}`).href
  49. const mod = await import(commandUrl)
  50. const { options, positionalArgs } = parseArgs(argv.slice(1))
  51. const repoPath = process.cwd() // M3 状态机后续处理工作目录定位
  52. cache = new CacheManager(path.join(repoPath, '.cache', 'index.db'))
  53. await cache.ensureReady(repoPath)
  54. const result = await mod.run(positionalArgs, options, { repoPath, cache })
  55. if (result.ok) {
  56. if (result.output) console.log(result.output)
  57. process.exitCode = 0
  58. } else {
  59. console.error(result.error)
  60. process.exitCode = 1
  61. }
  62. } catch (err) {
  63. if (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'ENOENT') {
  64. console.error(`未知命令「${command}」。运行 webnovel-writer --help 查看可用命令。`)
  65. process.exitCode = 1
  66. } else {
  67. // 永不带栈崩溃(错误规范 §1)
  68. console.error(`执行命令「${command}」时出错:${err.message}`)
  69. process.exitCode = 1
  70. }
  71. } finally {
  72. if (cache) await cache.close() // 显式关闭,避免 Windows 文件锁
  73. }