@waterwx/dsh-novel-forge 1.3.3 → 1.4.1

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.
@@ -26,6 +26,11 @@ export declare function renameBook(id: string, bookName: string): boolean;
26
26
  * @returns 是否发生了播种。
27
27
  */
28
28
  export declare function seedBookshelfFromOutputDir(outputDir: string): boolean;
29
+ /** 导入已有项目目录到书架:校验 novel-project.json,已存在则直接激活。 */
30
+ export declare function importDir(outputDir: string): {
31
+ book: BookEntry;
32
+ existed: boolean;
33
+ };
29
34
  /** 激活一本书。 */
30
35
  export declare function activateBook(id: string): BookEntry | undefined;
31
36
  /** 移除一本书。 */
@@ -16,6 +16,8 @@ export declare class NovelApi {
16
16
  ok: boolean;
17
17
  bookName: string;
18
18
  }>;
19
+ /** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流)。 */
20
+ outlineReverse(onFrame: (frame: JobFrame) => void): Promise<void>;
19
21
  /** 开书想法 → AI 大纲:生成 count 个方案(换批时传 exclude 避开已暂留方向)。 */
20
22
  outlineSuggest(idea: string, count?: number, exclude?: string[]): Promise<import('../protocol.ts').OutlineSuggestResponse>;
21
23
  /** 拆书分析:对已写章节做结构/人物/文风/卖点体检。 */
@@ -116,6 +118,14 @@ export declare class NovelApi {
116
118
  bookActivate(id: string): Promise<import('../protocol.ts').BookshelfSnapshot>;
117
119
  /** 移除书架条目。 */
118
120
  bookRemove(id: string): Promise<import('../protocol.ts').BookshelfSnapshot>;
121
+ /** 导入已有项目目录(Mode A):校验 novel-project.json,登记/激活书架。 */
122
+ bookImportDir(outputDir: string): Promise<import('../protocol.ts').BookImportDirResponse>;
123
+ /** 导入 txt/md 全本(Mode B):服务器本地文件路径模式。 */
124
+ bookImportText(filePath: string, outputDir?: string): Promise<import('../protocol.ts').BookImportTextResponse>;
125
+ /** 拆章预览(浏览器上传全文,不落盘):返回识别到的章节与跳过清单。 */
126
+ bookImportTextPreview(text: string, fileName?: string): Promise<import('../protocol.ts').BookImportTextPreviewResponse>;
127
+ /** 导入 txt/md 全本(Mode B):浏览器上传全文内容模式。 */
128
+ bookImportTextContent(text: string, fileName: string, outputDir?: string): Promise<import('../protocol.ts').BookImportTextResponse>;
119
129
  /** 生产单:启动批量生产(区间或新增 N 章;计划不足自动补)。 */
120
130
  runStart(req: import('../protocol.ts').RunStartRequest): Promise<import('../protocol.ts').RunState>;
121
131
  /** 生产单控制:pause / resume / stop。 */
@@ -143,12 +143,14 @@ export declare const zh: {
143
143
  readonly 'settings.model': "模型";
144
144
  readonly 'settings.modelCustom': "自定义…";
145
145
  readonly 'settings.modelCustomPlaceholder': "输入任意模型 id";
146
- readonly 'settings.reasoningEffort': "思考强度 (reasoningEffort)";
146
+ readonly 'settings.reasoningEffort': "写作类任务思考强度 (reasoningEffort)";
147
+ readonly 'settings.reasoningHint': "章节生成/润色/重写/审稿等写作任务使用;关掉更省更快。";
148
+ readonly 'settings.analysisReasoning': "分析类任务思考强度(提炼/拆书/反推大纲)";
149
+ readonly 'settings.analysisReasoningHint': "角色/场景/道藏提炼、拆书、反推大纲等一次性分析任务默认 low,保证质量,不受上面写作档位影响。";
147
150
  readonly 'settings.reasoning.off': "off · 关闭思考(推荐日常写文)";
148
151
  readonly 'settings.reasoning.low': "low · 低思考";
149
152
  readonly 'settings.reasoning.high': "high · 高思考";
150
153
  readonly 'settings.reasoning.max': "max · 最高思考(质检/推演)";
151
- readonly 'settings.reasoningHint': "off 最快最省;high/max 更慢更贵但推理更强";
152
154
  readonly 'settings.theme': "主题";
153
155
  readonly 'settings.themeLiquid': "iOS 液态玻璃(绿)";
154
156
  readonly 'settings.themeClassic': "经典毛玻璃(蓝)";
@@ -0,0 +1,7 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ export declare function ImportModal({ api, onClose, onImported, }: {
3
+ api: NovelApi;
4
+ onClose: () => void;
5
+ /** 导入成功并已激活该书后回调(刷新书架)。 */
6
+ onImported: () => void | Promise<void>;
7
+ }): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
2
  import type { BookshelfSnapshot } from '../../protocol.ts';
3
3
  /** 书架首页。 */
4
- export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBook, }: {
4
+ export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, }: {
5
5
  api: NovelApi;
6
6
  shelf: BookshelfSnapshot;
7
7
  /** 点击书卡:激活该书并进入工作台。 */
@@ -10,4 +10,6 @@ export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBoo
10
10
  onReadBook: (id: string) => void;
11
11
  /** 点击+:进入开书向导页。 */
12
12
  onAddBook: () => void;
13
+ /** 点击「导入」:打开导入弹窗(已有项目目录 / txt/md 全本)。 */
14
+ onImportBook: () => void;
13
15
  }): import("react").JSX.Element;
@@ -71,6 +71,33 @@ export declare function refreshPlotlineProgress(ctx: Context, config: NovelConfi
71
71
  export declare function extractRoles(ctx: Context, config: NovelConfig, project: ProjectState): Promise<RoleRecord[]>;
72
72
  /** ✨ AI 从全书提炼场景库:正文/编年录 → 高频重要场景的结构化视觉锚点。 */
73
73
  export declare function extractScenes(ctx: Context, config: NovelConfig, project: ProjectState): Promise<SceneCard[]>;
74
+ /** 从 txt/md 全本文本拆章(纯逻辑,不落盘):识别章节头、剥离重复标题、去重、排序并统一重新编号。 */
75
+ export declare function splitBookText(raw: string): Array<{
76
+ no: number;
77
+ title: string;
78
+ body: string;
79
+ }>;
80
+ /** 拆章预览(不落盘):章节编号/标题/字数 + 跳过清单。 */
81
+ export declare function previewBookText(raw: string): {
82
+ chapters: Array<{
83
+ no: number;
84
+ title: string;
85
+ chars: number;
86
+ }>;
87
+ skipped: string[];
88
+ };
89
+ /** 从全本文本导入(浏览器上传 / 服务器文件共用):建项目、写章节文件、保存。 */
90
+ export declare function importBookTextFromText(raw: string, outputDir: string, bookName: string): {
91
+ bookName: string;
92
+ chapters: number;
93
+ skipped: string[];
94
+ };
95
+ /** 从 txt/md 全本文件导入:编码自适应读取后拆章建项目,status=written(待审稿)。 */
96
+ export declare function importBookText(filePath: string, outputDir: string): {
97
+ bookName: string;
98
+ chapters: number;
99
+ skipped: string[];
100
+ };
74
101
  /** 动漫形象描述词(中文描述 + 英文 booru 标签 + 关键外貌标签)。 */
75
102
  export interface RoleVisualPrompt {
76
103
  zh: string;
@@ -155,6 +182,11 @@ export declare function generateChapterStream(ctx: Context, config: NovelConfig,
155
182
  }, void, unknown>;
156
183
  /** Generate a chapter summary (narrative memory). */
157
184
  export declare function summarizeChapter(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): Promise<string>;
185
+ /**
186
+ * 反向推大纲:从已写章节正文反推出全书总纲(分卷 + 章节要点 + 主线/人物弧线/伏笔清单)。
187
+ * 两阶段:分批提取章节事件摘要 → 汇总生成大纲。不修改章节/设定,只返回大纲文本。
188
+ */
189
+ export declare function reverseOutlineFromChapters(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, onProgress?: (done: number, total: number, phase: string) => void): Promise<string>;
158
190
  /**
159
191
  * 摘要 + 事实抽取合并为一次 LLM 调用(省一次调用与一次正文输入,
160
192
  * 批量生成时整体开销约省 25%)。
@@ -35,6 +35,8 @@ export interface Config {
35
35
  model?: string;
36
36
  /** LLM reasoning effort (off/low/high/max). */
37
37
  reasoningEffort?: 'off' | 'low' | 'high' | 'max';
38
+ /** 分析类任务(提炼/拆书/反推大纲等)的推理档位;默认 low。 */
39
+ analysisReasoning?: 'off' | 'low' | 'high' | 'max';
38
40
  /** Target characters per chapter. */
39
41
  chapterChars?: number;
40
42
  /** Max output tokens per chapter call. */
@@ -35,6 +35,12 @@ export declare const NOVEL_API: {
35
35
  /** 清空助手对话记录。 */
36
36
  readonly assistantClear: "/api/dsh-novel-forge/assistant/clear";
37
37
  readonly bookshelf: "/api/dsh-novel-forge/bookshelf";
38
+ /** 导入已有项目目录(含 novel-project.json)到书架。 */
39
+ readonly bookshelfImportDir: "/api/dsh-novel-forge/bookshelf/import-dir";
40
+ /** 导入 txt/md 全本:拆章建项目并登记书架。 */
41
+ readonly bookshelfImportText: "/api/dsh-novel-forge/bookshelf/import-text";
42
+ /** 导入 txt/md 全本:拆章预览(不落盘)。 */
43
+ readonly bookshelfImportTextPreview: "/api/dsh-novel-forge/bookshelf/import-text/preview";
38
44
  /** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
39
45
  readonly reset: "/api/dsh-novel-forge/reset";
40
46
  /** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
@@ -69,6 +75,8 @@ export declare const NOVEL_API: {
69
75
  readonly sensitiveCheck: "/api/dsh-novel-forge/sensitive-check";
70
76
  /** 开书想法 → AI 补全大纲:输入一句话想法,生成 2-3 个可选大纲方案。 */
71
77
  readonly outlineSuggest: "/api/dsh-novel-forge/outline/suggest";
78
+ /** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流)。 */
79
+ readonly outlineReverse: "/api/dsh-novel-forge/outline/reverse";
72
80
  /** 拆书分析:对已写章节做结构/人物/文风/卖点四维体检(两阶段:源笔记→分节分析)。 */
73
81
  readonly breakdown: "/api/dsh-novel-forge/breakdown";
74
82
  /** 漫剧分镜生成:章节 → 角色锚点 + 分镜表(可适配豆包/Seedance/SD)。 */
@@ -128,6 +136,60 @@ export interface BookActivateRequest {
128
136
  export interface BookRemoveRequest {
129
137
  id: string;
130
138
  }
139
+ /** POST /bookshelf/import-dir 请求:导入已有项目目录。 */
140
+ export interface BookImportDirRequest {
141
+ /** 项目目录(须含 novel-project.json)。 */
142
+ outputDir: string;
143
+ }
144
+ /** POST /bookshelf/import-dir 响应。 */
145
+ export interface BookImportDirResponse {
146
+ book: BookEntry;
147
+ /** true = 目录已在书架中(本次为重新激活)。 */
148
+ existed: boolean;
149
+ }
150
+ /** POST /bookshelf/import-text 请求:导入 txt/md 全本。两种模式二选一:
151
+ * - filePath:服务器本地文件路径;
152
+ * - text + fileName:浏览器上传的全文内容(fileName 用于推断书名)。
153
+ */
154
+ export interface BookImportTextRequest {
155
+ /** 模式一:源文件绝对路径(txt 或 md)。 */
156
+ filePath?: string;
157
+ /** 模式二:浏览器上传的全文内容。 */
158
+ text?: string;
159
+ /** 模式二:原文件名(txt/md),用于推断书名与显示。 */
160
+ fileName?: string;
161
+ /** 输出目录;缺省为 ~/.dsh/novels/书名。 */
162
+ outputDir?: string;
163
+ }
164
+ /** POST /bookshelf/import-text 响应。 */
165
+ export interface BookImportTextResponse {
166
+ bookName: string;
167
+ /** 成功拆出的章节数。 */
168
+ chapters: number;
169
+ /** 因内容过短被跳过的章节标题列表。 */
170
+ skipped: string[];
171
+ /** 登记后的书架条目。 */
172
+ book: BookEntry;
173
+ }
174
+ /** POST /bookshelf/import-text/preview 请求:上传全文做拆章预览(不落盘)。 */
175
+ export interface BookImportTextPreviewRequest {
176
+ text: string;
177
+ /** 原文件名(txt/md),用于推断书名。 */
178
+ fileName?: string;
179
+ }
180
+ /** POST /bookshelf/import-text/preview 响应。 */
181
+ export interface BookImportTextPreviewResponse {
182
+ /** 预计书名(fileName 去扩展名)。 */
183
+ bookName: string;
184
+ /** 识别出的章节(已按正文长度过滤过短章节)。 */
185
+ chapters: Array<{
186
+ no: number;
187
+ title: string;
188
+ chars: number;
189
+ }>;
190
+ /** 因内容过短被跳过的章节标题列表。 */
191
+ skipped: string[];
192
+ }
131
193
  /** Chapter lifecycle states (the writing pipeline's state machine). */
132
194
  export type ChapterStatus = 'pending' | 'generating' | 'written' | 'reviewing' | 'approved' | 'rejected' | 'error';
133
195
  /** One chapter in the plan. */
@@ -764,6 +826,8 @@ export interface NovelConfig {
764
826
  model: string;
765
827
  /** LLM reasoning effort: off = no thinking; low/high/max = thinking intensity. */
766
828
  reasoningEffort: 'off' | 'low' | 'high' | 'max';
829
+ /** 分析类任务(提炼/拆书/反推大纲等)的推理档位;默认 low,不受上面写作档位影响。 */
830
+ analysisReasoning: 'off' | 'low' | 'high' | 'max';
767
831
  /** Target characters per chapter. */
768
832
  chapterChars: number;
769
833
  /** Max output tokens per chapter call. */
@@ -929,6 +993,15 @@ export type JobFrame = {
929
993
  no: number;
930
994
  chars: number;
931
995
  draft: string;
996
+ } | {
997
+ type: 'outline-progress';
998
+ done: number;
999
+ total: number;
1000
+ phase: string;
1001
+ } | {
1002
+ type: 'outline-done';
1003
+ outline: string;
1004
+ chars: number;
932
1005
  } | {
933
1006
  type: 'error';
934
1007
  no: number;
@@ -1000,6 +1073,7 @@ export interface ConfigPatch {
1000
1073
  provider?: string;
1001
1074
  model?: string;
1002
1075
  reasoningEffort?: 'off' | 'low' | 'high' | 'max';
1076
+ analysisReasoning?: 'off' | 'low' | 'high' | 'max';
1003
1077
  chapterChars?: number;
1004
1078
  maxTokens?: number;
1005
1079
  reviewPassScore?: number;
package/package.json CHANGED
@@ -1,97 +1,97 @@
1
- {
2
- "name": "@waterwx/dsh-novel-forge",
3
- "description": "AI 编译小说工作台 for the dsh web GUI: import a novel outline (docx/text), plan chapters with LLM, generate chapter by chapter (3000-4000 chars each), and save every chapter as Markdown into your chosen output folder.",
4
- "version": "1.3.3",
5
- "type": "module",
6
- "engines": {
7
- "node": "^22.19.0 || >=24.0.0"
8
- },
9
- "main": "lib/index.js",
10
- "types": "lib/types/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./lib/types/index.d.ts",
14
- "default": "./lib/index.js"
15
- },
16
- "./client": {
17
- "types": "./lib/types/client/index.d.ts",
18
- "default": "./lib/client.js"
19
- },
20
- "./src/*": "./src/*",
21
- "./package.json": "./package.json"
22
- },
23
- "dsh": {
24
- "bundle": {
25
- "patch": "./cordis.patch.yml"
26
- },
27
- "client": {
28
- "inject": [
29
- "@deepseek-ai/dsh-client-runtime",
30
- "@deepseek-ai/dsh-client-connection",
31
- "@deepseek-ai/dsh-client-ui-settings"
32
- ],
33
- "platform": "web"
34
- }
35
- },
36
- "dependencies": {
37
- "fflate": "^0.8.2",
38
- "schemastery": "^3.18.0"
39
- },
40
- "peerDependencies": {
41
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
42
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
43
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
44
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
45
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
46
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
47
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
48
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
49
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
50
- "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
51
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
52
- "react": "^18.2.0",
53
- "react-dom": "^18.2.0"
54
- },
55
- "devDependencies": {
56
- "@deepseek-ai/cordis": "^4.0.1",
57
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
58
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
59
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
60
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
61
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
62
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
63
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
64
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
65
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
66
- "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
67
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
68
- "@types/node": "^22.20.0",
69
- "@types/react": "~18.3.1",
70
- "@types/react-dom": "^18.3.5",
71
- "lightningcss": "1.32.0",
72
- "react": "^18.3.1",
73
- "react-dom": "^18.3.1",
74
- "tsdown": "0.22.2",
75
- "typescript": "~5.7.2",
76
- "vitest": "^3.0.0"
77
- },
78
- "files": [
79
- "lib/**/*.js",
80
- "lib/**/*.js.map",
81
- "lib/**/*.d.ts",
82
- "lib/**/*.d.ts.map",
83
- "src",
84
- "scripts/patch-schemastery.mjs",
85
- "cordis.patch.yml",
86
- "README.md"
87
- ],
88
- "license": "Apache-2.0",
89
- "scripts": {
90
- "postinstall": "node scripts/patch-schemastery.mjs",
91
- "build": "tsc -p tsconfig.build.json && tsdown",
92
- "bundle": "tsdown",
93
- "watch": "tsdown --watch",
94
- "typecheck": "tsc --noEmit",
95
- "test": "vitest run"
96
- }
97
- }
1
+ {
2
+ "name": "@waterwx/dsh-novel-forge",
3
+ "description": "AI 编译小说工作台 for the dsh web GUI: import a novel outline (docx/text), plan chapters with LLM, generate chapter by chapter (3000-4000 chars each), and save every chapter as Markdown into your chosen output folder.",
4
+ "version": "1.4.1",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/types/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./client": {
17
+ "types": "./lib/types/client/index.d.ts",
18
+ "default": "./lib/client.js"
19
+ },
20
+ "./src/*": "./src/*",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dsh": {
24
+ "bundle": {
25
+ "patch": "./cordis.patch.yml"
26
+ },
27
+ "client": {
28
+ "inject": [
29
+ "@deepseek-ai/dsh-client-runtime",
30
+ "@deepseek-ai/dsh-client-connection",
31
+ "@deepseek-ai/dsh-client-ui-settings"
32
+ ],
33
+ "platform": "web"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "fflate": "^0.8.2",
38
+ "schemastery": "^3.18.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
42
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
43
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
44
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
45
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
46
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
47
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
48
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
49
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
50
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
51
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
52
+ "react": "^18.2.0",
53
+ "react-dom": "^18.2.0"
54
+ },
55
+ "devDependencies": {
56
+ "@deepseek-ai/cordis": "^4.0.1",
57
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
58
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
59
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
60
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
61
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
62
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
63
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
64
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
65
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
66
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
67
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
68
+ "@types/node": "^22.20.0",
69
+ "@types/react": "~18.3.1",
70
+ "@types/react-dom": "^18.3.5",
71
+ "lightningcss": "1.32.0",
72
+ "react": "^18.3.1",
73
+ "react-dom": "^18.3.1",
74
+ "tsdown": "0.22.2",
75
+ "typescript": "~5.7.2",
76
+ "vitest": "^3.0.0"
77
+ },
78
+ "files": [
79
+ "lib/**/*.js",
80
+ "lib/**/*.js.map",
81
+ "lib/**/*.d.ts",
82
+ "lib/**/*.d.ts.map",
83
+ "src",
84
+ "scripts/patch-schemastery.mjs",
85
+ "cordis.patch.yml",
86
+ "README.md"
87
+ ],
88
+ "license": "Apache-2.0",
89
+ "scripts": {
90
+ "postinstall": "node scripts/patch-schemastery.mjs",
91
+ "build": "tsc -p tsconfig.build.json && tsdown",
92
+ "bundle": "tsdown",
93
+ "watch": "tsdown --watch",
94
+ "typecheck": "tsc --noEmit",
95
+ "test": "vitest run"
96
+ }
97
+ }
package/src/bookshelf.ts CHANGED
@@ -114,6 +114,22 @@ export function seedBookshelfFromOutputDir(outputDir: string): boolean {
114
114
  return true
115
115
  }
116
116
 
117
+ /** 导入已有项目目录到书架:校验 novel-project.json,已存在则直接激活。 */
118
+ export function importDir(outputDir: string): { book: BookEntry; existed: boolean } {
119
+ const project = loadProject(outputDir)
120
+ if (project === undefined) throw new Error(`目录中未找到有效的 novel-project.json:${outputDir}`)
121
+ const store = loadBookshelf()
122
+ const existed = store.books.find(b => b.outputDir === outputDir)
123
+ if (existed !== undefined) {
124
+ store.activeBookId = existed.id
125
+ saveBookshelf(store)
126
+ return { book: existed, existed: true }
127
+ }
128
+ const bookName = project.bookName !== '' ? project.bookName : (outputDir.split(/[\\/]/).pop() ?? '未命名小说')
129
+ const book = createBook(bookName, outputDir)
130
+ return { book, existed: false }
131
+ }
132
+
117
133
  /** 激活一本书。 */
118
134
  export function activateBook(id: string): BookEntry | undefined {
119
135
  const store = loadBookshelf()