@cloud411716/fancy-webnovel 1.0.46 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/client.js +369 -0
  2. package/cordis.patch.yml +4 -13
  3. package/docs/douyin-plugin.md +118 -0
  4. package/docs/material-library/README.md +404 -0
  5. package/docs/material-library/import.js +437 -0
  6. package/docs/material-library/prompt.md +370 -0
  7. package/docs/material-library/queries.surql +94 -0
  8. package/docs/material-library/sample.jsonl +14 -0
  9. package/docs/material-library/schema.surql +46 -0
  10. package/docs/mcp-profile.example.yml +24 -0
  11. package/docs/story-graph-design.md +602 -0
  12. package/docs/story-graph-validation.md +81 -0
  13. package/docs/workspace-mcp.md +284 -0
  14. package/index.js +26 -38
  15. package/lib/atomic-write.js +109 -0
  16. package/lib/chapter-order.js +100 -0
  17. package/lib/chinese-num.js +111 -0
  18. package/lib/confirmation.js +80 -0
  19. package/lib/db-init.js +123 -0
  20. package/lib/deployed-state.js +141 -0
  21. package/lib/direction-labels.js +265 -0
  22. package/lib/error-codes.js +104 -0
  23. package/lib/infra.js +594 -0
  24. package/lib/llm-wiki.js +109 -0
  25. package/lib/material-db.js +149 -0
  26. package/lib/path-guard.js +150 -0
  27. package/lib/projects.js +400 -0
  28. package/lib/scraper-utils.js +305 -0
  29. package/lib/surreal-server.js +338 -0
  30. package/lib/topic-decision.js +860 -0
  31. package/lib/topic-fields.js +251 -0
  32. package/lib/topic-schema.js +225 -0
  33. package/lib/workspace.js +501 -0
  34. package/package.json +62 -5
  35. package/plugins/fancy-analyze/analyze-definitions.md +519 -0
  36. package/plugins/fancy-analyze/command-input.js +65 -0
  37. package/plugins/fancy-analyze/index.js +2024 -0
  38. package/plugins/fancy-analyze/ingest-coverage.js +107 -0
  39. package/plugins/fancy-analyze/schema-analyze.md +627 -0
  40. package/plugins/fancy-analyze/schema-write.md +178 -0
  41. package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
  42. package/plugins/fancy-analyze/scripts/split_source.js +292 -0
  43. package/plugins/fancy-analyze/summary-progress.js +495 -0
  44. package/plugins/fancy-analyze/templates.js +1179 -0
  45. package/plugins/fancy-bootstrap/index.js +313 -0
  46. package/plugins/fancy-bootstrap/scripts/init.js +177 -0
  47. package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
  48. package/plugins/fancy-bootstrap/templates.js +71 -0
  49. package/plugins/fancy-db/index.js +172 -0
  50. package/plugins/fancy-douyin/deploy.js +134 -0
  51. package/plugins/fancy-douyin/index.js +551 -0
  52. package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
  53. package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
  54. package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
  55. package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
  56. package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
  57. package/plugins/fancy-douyin/state.js +197 -0
  58. package/plugins/fancy-douyin/templates.js +127 -0
  59. package/plugins/fancy-export/exporter.js +805 -0
  60. package/plugins/fancy-export/index.js +63 -0
  61. package/plugins/fancy-import/import-core.js +1118 -0
  62. package/plugins/fancy-import/import-driver.js +604 -0
  63. package/plugins/fancy-import/index.js +738 -0
  64. package/plugins/fancy-import/prompts/01-import-prompt.md +131 -0
  65. package/plugins/fancy-import/templates.js +58 -0
  66. package/plugins/fancy-material/exporter.js +153 -0
  67. package/plugins/fancy-material/genres.js +256 -0
  68. package/plugins/fancy-material/index.js +835 -0
  69. package/plugins/fancy-material/inventory.js +143 -0
  70. package/plugins/fancy-material/kinds.js +313 -0
  71. package/plugins/fancy-material/queue.js +55 -0
  72. package/plugins/fancy-material/scripts/query.js +254 -0
  73. package/plugins/fancy-material/templates.js +258 -0
  74. package/plugins/fancy-material/tool-deploy.js +138 -0
  75. package/plugins/fancy-material/validate.js +149 -0
  76. package/plugins/fancy-project/index.js +133 -0
  77. package/plugins/fancy-scan/existing-block.js +100 -0
  78. package/plugins/fancy-scan/index.js +708 -0
  79. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
  80. package/plugins/fancy-scan/scripts/append-block.js +725 -0
  81. package/plugins/fancy-scan/scripts/report-validate.js +824 -0
  82. package/plugins/fancy-scan/scripts/run-scan.js +667 -0
  83. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +923 -0
  84. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +469 -0
  85. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +629 -0
  86. package/plugins/fancy-scan/templates/blocks.js +155 -0
  87. package/plugins/fancy-scan/templates/platforms.js +372 -0
  88. package/plugins/fancy-scan/templates/report-schema.js +90 -0
  89. package/plugins/fancy-scan/templates.js +694 -0
  90. package/plugins/fancy-scan/tool-deploy.js +335 -0
  91. package/plugins/fancy-scan/wait-turn.js +46 -0
  92. package/plugins/fancy-story-builder/db/README.md +106 -0
  93. package/plugins/fancy-story-builder/db/client.js +228 -0
  94. package/plugins/fancy-story-builder/db/commands.js +145 -0
  95. package/plugins/fancy-story-builder/db/ids.js +39 -0
  96. package/plugins/fancy-story-builder/db/import.js +798 -0
  97. package/plugins/fancy-story-builder/db/project.js +299 -0
  98. package/plugins/fancy-story-builder/db/query.js +361 -0
  99. package/plugins/fancy-story-builder/db/schema.js +417 -0
  100. package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
  101. package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
  102. package/plugins/fancy-story-builder/deslop-driver.js +821 -0
  103. package/plugins/fancy-story-builder/flesh-output.js +168 -0
  104. package/plugins/fancy-story-builder/index.js +6861 -0
  105. package/plugins/fancy-story-builder/patch-driver.js +390 -0
  106. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +563 -0
  107. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +602 -0
  108. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +539 -0
  109. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +225 -0
  110. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
  111. package/plugins/fancy-story-builder/purpose-template.md +83 -0
  112. package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
  113. package/plugins/fancy-story-builder/references/formulas-skeleton.json +402 -0
  114. package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
  115. package/plugins/fancy-story-builder/references/glossary.md +594 -0
  116. package/plugins/fancy-story-builder/references/index.md +581 -0
  117. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
  118. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
  119. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
  120. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
  121. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
  122. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
  123. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
  124. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
  125. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
  126. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
  127. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
  128. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
  129. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
  130. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
  131. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
  132. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
  133. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
  134. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
  135. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +181 -0
  136. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
  137. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +243 -0
  138. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
  139. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +185 -0
  140. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
  141. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +187 -0
  142. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
  143. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +209 -0
  144. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
  145. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
  146. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
  147. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
  148. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +160 -0
  149. package/plugins/fancy-story-builder/rhythm-output.js +140 -0
  150. package/plugins/fancy-story-builder/schema-setting.json +1100 -0
  151. package/plugins/fancy-story-builder/schema-setting.md +232 -0
  152. package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
  153. package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
  154. package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
  155. package/plugins/fancy-story-builder/scripts/content-quality.js +416 -0
  156. package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
  157. package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
  158. package/plugins/fancy-story-builder/scripts/flesh-validate.js +1312 -0
  159. package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
  160. package/plugins/fancy-story-builder/scripts/graph-utils.js +1484 -0
  161. package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
  162. package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
  163. package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
  164. package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
  165. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1312 -0
  166. package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
  167. package/plugins/fancy-story-builder/scripts/semantic-lint.js +381 -0
  168. package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
  169. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1973 -0
  170. package/plugins/fancy-story-builder/scripts/stage-batches.js +1405 -0
  171. package/plugins/fancy-story-builder/scripts/stage-contract.js +429 -0
  172. package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
  173. package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
  174. package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
  175. package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
  176. package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
  177. package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
  178. package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
  179. package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
  180. package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
  181. package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
  182. package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
  183. package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
  184. package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
  185. package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
  186. package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
  187. package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
  188. package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
  189. package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
  190. package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
  191. package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
  192. package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
  193. package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
  194. package/plugins/fancy-story-builder/skeleton-output.js +199 -0
  195. package/plugins/fancy-story-builder/stage-driver.js +1049 -0
  196. package/plugins/fancy-story-builder/storydb/README.md +271 -0
  197. package/plugins/fancy-story-builder/storydb/cli.js +458 -0
  198. package/plugins/fancy-story-builder/storydb/client.js +351 -0
  199. package/plugins/fancy-story-builder/storydb/compile.js +452 -0
  200. package/plugins/fancy-story-builder/storydb/context.js +288 -0
  201. package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
  202. package/plugins/fancy-story-builder/storydb/driver.js +106 -0
  203. package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
  204. package/plugins/fancy-story-builder/storydb/export.js +175 -0
  205. package/plugins/fancy-story-builder/storydb/ids.js +31 -0
  206. package/plugins/fancy-story-builder/storydb/impact.js +170 -0
  207. package/plugins/fancy-story-builder/storydb/import.js +559 -0
  208. package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
  209. package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
  210. package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
  211. package/plugins/fancy-story-builder/storydb/stage.js +426 -0
  212. package/plugins/fancy-story-builder/storydb/store.js +891 -0
  213. package/plugins/fancy-story-builder/storydb/tools.js +592 -0
  214. package/plugins/fancy-story-builder/storydb/validation.js +183 -0
  215. package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
  216. package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
  217. package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
  218. package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
  219. package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
  220. package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
  221. package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
  222. package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
  223. package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
  224. package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
  225. package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
  226. package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
  227. package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
  228. package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
  229. package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
  230. package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
  231. package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
  232. package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
  233. package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
  234. package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
  235. package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
  236. package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
  237. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +427 -0
  238. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
  239. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
  240. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
  241. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +168 -0
  242. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +184 -0
  243. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
  244. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +131 -0
  245. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +85 -0
  246. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +103 -0
  247. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +68 -0
  248. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +142 -0
  249. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +292 -0
  250. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +266 -0
  251. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +336 -0
  252. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
  253. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +308 -0
  254. package/plugins/fancy-story-builder/templates.js +1446 -0
  255. package/plugins/fancy-story-builder/wiki-references.js +540 -0
  256. package/plugins/fancy-story-builder/writing-driver.js +1388 -0
  257. package/plugins/fancy-topic/index.js +1057 -0
  258. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
  259. package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
  260. package/plugins/fancy-topic/scripts/select-topic.js +317 -0
  261. package/plugins/fancy-topic/templates.js +476 -0
  262. package/plugins/fancy-topic/tool-deploy.js +200 -0
  263. package/commands/bootstrap/index.js +0 -164
  264. package/commands/bootstrap/init-project.js +0 -121
  265. package/commands/scan/index.js +0 -283
  266. package/commands/scan/platforms/fanqie.js +0 -146
  267. package/commands/scan/platforms/jinjiang.js +0 -143
  268. package/commands/scan/platforms/qidian.js +0 -102
  269. package/commands/scan/platforms/qimao.js +0 -127
  270. package/commands/scan/scraper.js +0 -195
  271. package/infra.js +0 -160
@@ -0,0 +1,592 @@
1
+ /**
2
+ * Shared tool catalog for the story graph.
3
+ *
4
+ * Both transports use this file:
5
+ * - `mcp.js` exposes the catalog over MCP stdio for DSH/Hermes/other clients
6
+ * - `dsh-tools.js` registers the same operations as native DSH tools
7
+ *
8
+ * All tools take an optional `project` (novel name or UUID). When omitted the
9
+ * resolver uses the cwd, the workspace active project, or the only project.
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import {
15
+ openStoryGraph,
16
+ ensureRootStory,
17
+ flattenError,
18
+ } from './client.js';
19
+ import {
20
+ apply,
21
+ previewApply,
22
+ getEntity,
23
+ searchEntities,
24
+ validate,
25
+ nextWork,
26
+ decisionsFor,
27
+ readQuery,
28
+ } from './store.js';
29
+ import { schemaCard, allSchemaCards } from './compile.js';
30
+ import { chapterContext, entityContext } from './context.js';
31
+ import { impactRelation, impactForeshadow } from './impact.js';
32
+ import { exportStoryGraph } from '../../fancy-export/exporter.js';
33
+ import { stageCheck } from './stage.js';
34
+ import { parseChapterSelection } from './stage-command.js';
35
+ import {
36
+ resolveWorkspace,
37
+ workspaceSummary,
38
+ workspacePaths,
39
+ readWorkspace,
40
+ } from '../../../lib/workspace.js';
41
+ import {
42
+ createProject,
43
+ deleteProject,
44
+ getProject,
45
+ listProjects,
46
+ renameProject,
47
+ resolveProject,
48
+ setProjectActive,
49
+ updateProjectTopic,
50
+ } from '../../../lib/projects.js';
51
+ import { initDatabases, initProjectDatabase } from '../../../lib/db-init.js';
52
+ import { installSurreal, serverStatus, startServer, stopServer } from '../../../lib/surreal-server.js';
53
+ import { createConfirmation, verifyConfirmation } from '../../../lib/confirmation.js';
54
+
55
+ const sessions = new Map();
56
+ const initialized = new Set();
57
+
58
+ function jsonSchema(properties = {}, required = []) {
59
+ return { type: 'object', properties, required, additionalProperties: false };
60
+ }
61
+
62
+ const PROJECT = { type: 'string', description: '项目小说名或 UUID;省略时用当前项目' };
63
+ const WORKSPACE = { type: 'string', description: '工作区路径;省略时从 cwd/FANCY_HOME/全局指针解析' };
64
+
65
+ export const TOOLS = [
66
+ {
67
+ name: 'workspace_status',
68
+ description: '查看工作区、SurrealDB 服务、项目列表和共享目录。',
69
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
70
+ },
71
+ {
72
+ name: 'project_list',
73
+ description: '列出工作区里的所有项目(小说名 + UUID + 数据库)。',
74
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
75
+ },
76
+ {
77
+ name: 'project_get',
78
+ description: '按小说名或 UUID 查项目信息。',
79
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT }, ['project']),
80
+ },
81
+ {
82
+ name: 'project_find',
83
+ description: '按关键词模糊查找项目。',
84
+ inputSchema: jsonSchema({ workspace: WORKSPACE, query: { type: 'string' } }, ['query']),
85
+ },
86
+ {
87
+ name: 'project_create',
88
+ description: '创建项目:分配 UUID、项目目录、独立数据库和数据库用户。title 是小说名。',
89
+ inputSchema: jsonSchema({
90
+ workspace: WORKSPACE,
91
+ title: { type: 'string' },
92
+ topic: { type: 'object' },
93
+ }, ['title']),
94
+ },
95
+ {
96
+ name: 'project_update_topic',
97
+ description: '更新项目的选题元数据(不改变 UUID 和目录)。',
98
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, topic: { type: 'object' } }, ['project', 'topic']),
99
+ },
100
+ {
101
+ name: 'project_rename',
102
+ description: '只改数据库里的书名;UUID、目录、数据库都不变。',
103
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, title: { type: 'string' } }, ['project', 'title']),
104
+ },
105
+ {
106
+ name: 'project_use',
107
+ description: '把项目设为工作区当前项目。',
108
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT }, ['project']),
109
+ },
110
+ {
111
+ name: 'project_delete',
112
+ description: '删除项目数据库。必须传 confirm=true;purge=true 时同时删除项目目录。',
113
+ inputSchema: jsonSchema({
114
+ workspace: WORKSPACE,
115
+ project: PROJECT,
116
+ confirm: { type: 'boolean' },
117
+ purge: { type: 'boolean' },
118
+ }, ['project', 'confirm']),
119
+ },
120
+ {
121
+ name: 'db_status',
122
+ description: '查看 SurrealDB 服务状态。',
123
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
124
+ },
125
+ {
126
+ name: 'db_start',
127
+ description: '启动工作区 SurrealDB 服务。',
128
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
129
+ },
130
+ {
131
+ name: 'db_stop',
132
+ description: '停止工作区 SurrealDB 服务。',
133
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
134
+ },
135
+ {
136
+ name: 'db_install',
137
+ description: '下载 surreal 二进制到工作区 .fancy/bin。',
138
+ inputSchema: jsonSchema({ workspace: WORKSPACE, version: { type: 'string' } }),
139
+ },
140
+ {
141
+ name: 'db_init',
142
+ description: '初始化工作区/项目数据库:创建 schema、校验函数、权限和元数据。',
143
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, all: { type: 'boolean' } }),
144
+ },
145
+ {
146
+ name: 'scan_list',
147
+ description: '列出工作区共享的扫榜结果文件。',
148
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
149
+ },
150
+ {
151
+ name: 'analyze_list',
152
+ description: '列出工作区共享的对标分析目录。',
153
+ inputSchema: jsonSchema({ workspace: WORKSPACE }),
154
+ },
155
+ {
156
+ name: 'story_status',
157
+ description: '查看当前项目的总进度、章节数、各阶段数据量。',
158
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT }),
159
+ },
160
+ {
161
+ name: 'story_schema',
162
+ description: '查看故事图表结构;不传 table 时返回全部 schema 卡片。',
163
+ inputSchema: jsonSchema({ table: { type: 'string' } }),
164
+ },
165
+ {
166
+ name: 'story_get',
167
+ description: '按类型和 ID 取一条记录。',
168
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, kind: { type: 'string' }, id: { type: 'string' } }, ['kind', 'id']),
169
+ },
170
+ {
171
+ name: 'story_search',
172
+ description: '按字段条件查记录。where 是对象,例如 {"type":"主角"}。',
173
+ inputSchema: jsonSchema({
174
+ workspace: WORKSPACE,
175
+ project: PROJECT,
176
+ kind: { type: 'string' },
177
+ where: { type: 'object' },
178
+ limit: { type: 'integer' },
179
+ }, ['kind']),
180
+ },
181
+ {
182
+ name: 'story_context',
183
+ description: '取章节或实体的最小上下文包;章节模式可带前三章 handoff 和全文检索。',
184
+ inputSchema: jsonSchema({
185
+ workspace: WORKSPACE,
186
+ project: PROJECT,
187
+ chapter: { type: 'integer' },
188
+ prev: { type: 'integer' },
189
+ tail: { type: 'integer' },
190
+ search: { type: 'string' },
191
+ entity: { type: 'string' },
192
+ }),
193
+ },
194
+ {
195
+ name: 'story_query',
196
+ description: '只读 SurrealQL 查询(SELECT / INFO / RETURN / SHOW / DESCRIBE / EXPLAIN)。',
197
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, sql: { type: 'string' } }, ['sql']),
198
+ },
199
+ {
200
+ name: 'story_impact',
201
+ description: '影响分析:relation(改人物关系)或 foreshadow(改伏笔动作)。只读,不写入。',
202
+ inputSchema: jsonSchema({
203
+ workspace: WORKSPACE,
204
+ project: PROJECT,
205
+ kind: { type: 'string', enum: ['relation', 'foreshadow'] },
206
+ a: { type: 'string' },
207
+ b: { type: 'string' },
208
+ to: { type: 'string' },
209
+ from: { type: 'integer' },
210
+ action: { type: 'string' },
211
+ chapter: { type: 'integer' },
212
+ }, ['kind', 'a']),
213
+ },
214
+ {
215
+ name: 'story_validate',
216
+ description: '运行数据库内置校验函数,返回 errors / pending / warnings。',
217
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, scope: { type: 'string' } }),
218
+ },
219
+ {
220
+ name: 'story_stage_check',
221
+ description: '检查阶段完成度:scale / macro / cast / rhythm / outline / writing。',
222
+ inputSchema: jsonSchema({
223
+ workspace: WORKSPACE,
224
+ project: PROJECT,
225
+ stage: { type: 'string' },
226
+ chapters: { type: 'string', description: '章节范围,例如 CH001、1-10、all' },
227
+ }, ['stage']),
228
+ },
229
+ {
230
+ name: 'story_next',
231
+ description: '返回当前待办队列。',
232
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, limit: { type: 'integer' } }),
233
+ },
234
+ {
235
+ name: 'story_why',
236
+ description: '查看某条记录当前生效的设计决策。',
237
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT, subject: { type: 'string' } }, ['subject']),
238
+ },
239
+ {
240
+ name: 'story_export',
241
+ description: '把当前项目导出成中文 Markdown 到项目目录/导出。',
242
+ inputSchema: jsonSchema({ workspace: WORKSPACE, project: PROJECT }),
243
+ },
244
+ {
245
+ name: 'story_preview',
246
+ description: '预览一次写入:校验 ops/decisions 并返回 confirmation_token。写入前必须先调用它,把预览给用户确认。',
247
+ inputSchema: jsonSchema({
248
+ workspace: WORKSPACE,
249
+ project: PROJECT,
250
+ workflow: { type: 'string' },
251
+ summary: { type: 'string' },
252
+ ops: { type: 'array', items: { type: 'object' } },
253
+ decisions: { type: 'array', items: { type: 'object' } },
254
+ perChapter: { type: 'boolean' },
255
+ chapters: { type: 'array', items: { type: 'object' } },
256
+ replaceChapter: { type: 'boolean' },
257
+ chapter: { type: 'string' },
258
+ supersedeSubjects: { type: 'array', items: { type: 'string' } },
259
+ parent: { type: 'string' },
260
+ }),
261
+ },
262
+ {
263
+ name: 'story_apply',
264
+ description: '执行一次写入。必须带 story_preview 返回的 confirmation_token;没有 token 会返回 CONFIRMATION_REQUIRED 和预览。',
265
+ inputSchema: jsonSchema({
266
+ workspace: WORKSPACE,
267
+ project: PROJECT,
268
+ confirmation_token: { type: 'string' },
269
+ workflow: { type: 'string' },
270
+ summary: { type: 'string' },
271
+ ops: { type: 'array', items: { type: 'object' } },
272
+ decisions: { type: 'array', items: { type: 'object' } },
273
+ perChapter: { type: 'boolean' },
274
+ chapters: { type: 'array', items: { type: 'object' } },
275
+ replaceChapter: { type: 'boolean' },
276
+ chapter: { type: 'string' },
277
+ supersedeSubjects: { type: 'array', items: { type: 'string' } },
278
+ parent: { type: 'string' },
279
+ }),
280
+ },
281
+ ];
282
+
283
+ function errorResult(err) {
284
+ return {
285
+ ok: false,
286
+ error: {
287
+ code: err && err.code ? err.code : 'TOOL_FAILED',
288
+ message: flattenError(err),
289
+ detail: err && err.detail ? err.detail : undefined,
290
+ },
291
+ };
292
+ }
293
+
294
+ function workspaceOf(args) {
295
+ return resolveWorkspace({ cwd: args.cwd || process.cwd(), explicit: args.workspace, required: true, allowGlobal: true });
296
+ }
297
+
298
+ async function projectOf(args, workspace) {
299
+ if (args.project) {
300
+ const project = await getProject(workspace, args.project);
301
+ if (!project) throw new Error('未找到项目:' + args.project);
302
+ return project;
303
+ }
304
+ return resolveProject({ workspace, cwd: args.cwd || process.cwd() });
305
+ }
306
+
307
+ async function withStory(args, fn) {
308
+ const workspace = workspaceOf(args);
309
+ const project = await projectOf(args, workspace);
310
+ const key = project.project_id;
311
+ let sdb = sessions.get(key);
312
+ if (!sdb || !sdb.isOpen()) {
313
+ sdb = await openStoryGraph(project.dir, {
314
+ project,
315
+ skipSchema: initialized.has(key),
316
+ });
317
+ sessions.set(key, sdb);
318
+ initialized.add(key);
319
+ }
320
+ await ensureRootStory(sdb, {});
321
+ try {
322
+ return await fn({ sdb, project, workspace });
323
+ } catch (err) {
324
+ throw err;
325
+ }
326
+ }
327
+
328
+ function listFiles(dir, limit = 200) {
329
+ if (!fs.existsSync(dir)) return [];
330
+ const out = [];
331
+ const walk = (current) => {
332
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
333
+ if (out.length >= limit) return;
334
+ const full = path.join(current, entry.name);
335
+ if (entry.isDirectory()) walk(full);
336
+ else {
337
+ const stat = fs.statSync(full);
338
+ out.push({
339
+ name: path.relative(dir, full),
340
+ path: full,
341
+ size: stat.size,
342
+ modified: stat.mtime.toISOString(),
343
+ });
344
+ }
345
+ }
346
+ };
347
+ walk(dir);
348
+ return out;
349
+ }
350
+
351
+ function writePayload(input) {
352
+ const keys = ['workflow', 'summary', 'ops', 'decisions', 'perChapter', 'chapters', 'replaceChapter', 'chapter', 'supersedeSubjects', 'parent'];
353
+ const out = {};
354
+ for (const key of keys) if (input[key] !== undefined) out[key] = input[key];
355
+ return out;
356
+ }
357
+
358
+ async function preview(args) {
359
+ return withStory(args, async ({ sdb, project, workspace }) => {
360
+ const payload = writePayload(args);
361
+ const report = await previewApply(sdb, payload);
362
+ const confirmation = createConfirmation(workspace, project, payload);
363
+ return {
364
+ ok: report.ok,
365
+ requires_user_confirmation: true,
366
+ project: { id: project.project_id, name: project.name },
367
+ preview: report,
368
+ ...confirmation,
369
+ instructions: '把 preview 展示给用户;用户明确确认后,再调用 story_apply 并带上 confirmation_token。',
370
+ };
371
+ });
372
+ }
373
+
374
+ export async function callTool(name, args = {}) {
375
+ try {
376
+ switch (name) {
377
+ case 'workspace_status': {
378
+ const workspace = workspaceOf(args);
379
+ return {
380
+ ok: true,
381
+ workspace: workspaceSummary(workspace),
382
+ server: await serverStatus(workspace),
383
+ projects: await listProjects(workspace),
384
+ };
385
+ }
386
+ case 'project_list': {
387
+ const workspace = workspaceOf(args);
388
+ return { ok: true, projects: await listProjects(workspace) };
389
+ }
390
+ case 'project_get': {
391
+ const workspace = workspaceOf(args);
392
+ const project = await getProject(workspace, args.project);
393
+ return project ? { ok: true, project } : { ok: false, error: { code: 'PROJECT_NOT_FOUND', message: '未找到项目:' + args.project } };
394
+ }
395
+ case 'project_find': {
396
+ const workspace = workspaceOf(args);
397
+ const projects = await listProjects(workspace);
398
+ const q = String(args.query || '').toLowerCase();
399
+ return { ok: true, projects: projects.filter((p) => p.name.toLowerCase().includes(q) || p.project_id.startsWith(q)) };
400
+ }
401
+ case 'project_create': {
402
+ const workspace = workspaceOf(args);
403
+ const project = await createProject(workspace, { title: args.title, topic: args.topic || {} });
404
+ const init = await initProjectDatabase(workspace, project.project_id);
405
+ return { ok: true, project, init };
406
+ }
407
+ case 'project_update_topic': {
408
+ const workspace = workspaceOf(args);
409
+ const project = await updateProjectTopic(workspace, args.project, args.topic);
410
+ return { ok: true, project };
411
+ }
412
+ case 'project_rename': {
413
+ const workspace = workspaceOf(args);
414
+ const project = await renameProject(workspace, args.project, args.title);
415
+ return { ok: true, project };
416
+ }
417
+ case 'project_use': {
418
+ const workspace = workspaceOf(args);
419
+ const project = await setProjectActive(workspace, args.project);
420
+ return { ok: true, project };
421
+ }
422
+ case 'project_delete': {
423
+ const workspace = workspaceOf(args);
424
+ const target = await getProject(workspace, args.project);
425
+ if (target) {
426
+ const cached = sessions.get(target.project_id);
427
+ if (cached) {
428
+ try { await cached.close(); } catch { /* ignore */ }
429
+ sessions.delete(target.project_id);
430
+ }
431
+ initialized.delete(target.project_id);
432
+ }
433
+ const result = await deleteProject(workspace, args.project, { confirm: args.confirm === true, purge: args.purge === true });
434
+ return { ok: true, ...result };
435
+ }
436
+ case 'db_status': {
437
+ const workspace = workspaceOf(args);
438
+ return { ok: true, status: await serverStatus(workspace) };
439
+ }
440
+ case 'db_start': {
441
+ const workspace = workspaceOf(args);
442
+ return { ok: true, result: await startServer(workspace) };
443
+ }
444
+ case 'db_stop': {
445
+ const workspace = workspaceOf(args);
446
+ return { ok: true, result: await stopServer(workspace) };
447
+ }
448
+ case 'db_install': {
449
+ const workspace = workspaceOf(args);
450
+ return { ok: true, result: await installSurreal(workspace, { version: args.version }) };
451
+ }
452
+ case 'db_init': {
453
+ const workspace = workspaceOf(args);
454
+ const result = await initDatabases(workspace, {
455
+ project: args.project,
456
+ all: args.all !== false,
457
+ });
458
+ return { ok: true, result };
459
+ }
460
+ case 'scan_list': {
461
+ const workspace = workspaceOf(args);
462
+ return { ok: true, dir: workspacePaths(workspace).scan, files: listFiles(workspacePaths(workspace).scan) };
463
+ }
464
+ case 'analyze_list': {
465
+ const workspace = workspaceOf(args);
466
+ return { ok: true, dir: workspacePaths(workspace).analyze, files: listFiles(workspacePaths(workspace).analyze) };
467
+ }
468
+ case 'story_status':
469
+ return withStory(args, async ({ sdb, project }) => {
470
+ const story = await sdb.one('SELECT * FROM story:main;');
471
+ const counts = {};
472
+ for (const table of ['volume', 'act', 'character', 'faction', 'location', 'item', 'conflict', 'foreshadow', 'payoff', 'climax', 'information', 'chapter', 'outline', 'scene', 'chapter_text', 'handoff']) {
473
+ const row = await sdb.one(`SELECT count() AS n FROM ${table} WHERE story = story:main GROUP ALL;`);
474
+ counts[table] = Number(row && row.n) || 0;
475
+ }
476
+ return { ok: true, project: { id: project.project_id, name: project.name }, story, counts };
477
+ });
478
+ case 'story_schema':
479
+ return {
480
+ ok: true,
481
+ ...(args.table ? { table: args.table, card: schemaCard(args.table) } : { cards: allSchemaCards() }),
482
+ };
483
+ case 'story_get':
484
+ return withStory(args, async ({ sdb }) => ({ ok: true, entity: await getEntity(sdb, args.kind, args.id) }));
485
+ case 'story_search':
486
+ return withStory(args, async ({ sdb }) => ({
487
+ ok: true,
488
+ rows: await searchEntities(sdb, args.kind, { where: args.where || {}, limit: args.limit }),
489
+ }));
490
+ case 'story_context':
491
+ return withStory(args, async ({ sdb }) => {
492
+ if (args.entity) {
493
+ const [kind, id] = String(args.entity).split(':');
494
+ return { ok: true, context: await entityContext(sdb, kind, id) };
495
+ }
496
+ return {
497
+ ok: true,
498
+ context: await chapterContext(sdb, Number(args.chapter), {
499
+ prev: Number(args.prev) || 0,
500
+ tailChars: Number(args.tail) || 600,
501
+ search: args.search || '',
502
+ }),
503
+ };
504
+ });
505
+ case 'story_query':
506
+ return withStory(args, async ({ sdb }) => ({ ok: true, rows: await readQuery(sdb, args.sql) }));
507
+ case 'story_impact':
508
+ return withStory(args, async ({ sdb }) => {
509
+ if (args.kind === 'relation') {
510
+ return {
511
+ ok: true,
512
+ impact: await impactRelation(sdb, args.a, args.b, {
513
+ newKind: args.to || null,
514
+ fromChapter: Number(args.from) || 1,
515
+ }),
516
+ };
517
+ }
518
+ if (args.kind === 'foreshadow') {
519
+ return {
520
+ ok: true,
521
+ impact: await impactForeshadow(sdb, args.a, {
522
+ action: args.action || null,
523
+ chapter: args.chapter || null,
524
+ }),
525
+ };
526
+ }
527
+ throw new Error('未知影响分析类型:' + args.kind);
528
+ });
529
+ case 'story_validate':
530
+ return withStory(args, async ({ sdb }) => ({ ok: true, report: await validate(sdb, { scope: args.scope || null }) }));
531
+ case 'story_stage_check':
532
+ return withStory(args, async ({ sdb }) => {
533
+ const chapters = await sdb.rows('SELECT chapter_id, display_no FROM chapter WHERE story = story:main ORDER BY display_no;');
534
+ const selection = parseChapterSelection(args.chapters || 'all', chapters, args.stage);
535
+ if (!selection.ok) return { ok: false, error: { code: 'BAD_CHAPTER_RANGE', message: selection.error } };
536
+ const result = await stageCheck(sdb, args.stage, {
537
+ chapterIds: selection.all ? null : selection.ids,
538
+ });
539
+ return { ok: true, result: { ...result, selection: selection.label } };
540
+ });
541
+ case 'story_next':
542
+ return withStory(args, async ({ sdb }) => ({ ok: true, rows: await nextWork(sdb, Number(args.limit) || 20) }));
543
+ case 'story_why':
544
+ return withStory(args, async ({ sdb }) => ({ ok: true, decisions: await decisionsFor(sdb, args.subject) }));
545
+ case 'story_export':
546
+ return withStory(args, async ({ sdb, project }) => ({ ok: true, result: await exportStoryGraph(sdb, project.dir) }));
547
+ case 'story_preview':
548
+ return preview(args);
549
+ case 'story_apply': {
550
+ const workspace = workspaceOf(args);
551
+ const project = await projectOf(args, workspace);
552
+ const payload = writePayload(args);
553
+ if (!args.confirmation_token) {
554
+ return withStory(args, async ({ sdb }) => {
555
+ const report = await previewApply(sdb, payload);
556
+ return {
557
+ ok: false,
558
+ code: 'CONFIRMATION_REQUIRED',
559
+ message: '写入尚未确认。先展示预览,用户同意后再调用 story_apply 并带 confirmation_token。',
560
+ preview: report,
561
+ ...createConfirmation(workspace, project, payload),
562
+ };
563
+ });
564
+ }
565
+ const verified = verifyConfirmation(workspace, project, args.confirmation_token, payload);
566
+ if (!verified.ok) {
567
+ return {
568
+ ok: false,
569
+ code: 'CONFIRMATION_INVALID',
570
+ message: '确认令牌无效或已过期(' + verified.reason + ')。请重新调用 story_preview。',
571
+ };
572
+ }
573
+ return withStory(args, async ({ sdb }) => ({
574
+ ok: true,
575
+ result: await apply(sdb, payload),
576
+ }));
577
+ }
578
+ default:
579
+ return { ok: false, error: { code: 'UNKNOWN_TOOL', message: '未知工具:' + name } };
580
+ }
581
+ } catch (err) {
582
+ return errorResult(err);
583
+ }
584
+ }
585
+
586
+ export async function closeAll() {
587
+ for (const sdb of sessions.values()) {
588
+ try { await sdb.close(); } catch { /* ignore */ }
589
+ }
590
+ sessions.clear();
591
+ initialized.clear();
592
+ }