@cloud411716/fancy-webnovel 2.2.0 → 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.
- package/client.js +201 -15
- package/cordis.patch.yml +5 -0
- package/docs/douyin-plugin.md +118 -0
- package/docs/material-library/README.md +404 -0
- package/docs/material-library/import.js +437 -0
- package/docs/material-library/prompt.md +370 -0
- package/docs/material-library/queries.surql +94 -0
- package/docs/material-library/sample.jsonl +14 -0
- package/docs/material-library/schema.surql +46 -0
- package/docs/mcp-profile.example.yml +24 -0
- package/docs/story-graph-design.md +602 -0
- package/docs/story-graph-validation.md +81 -0
- package/docs/workspace-mcp.md +284 -0
- package/index.js +11 -7
- package/lib/atomic-write.js +109 -0
- package/lib/chapter-order.js +100 -0
- package/lib/confirmation.js +80 -0
- package/lib/db-init.js +123 -0
- package/lib/deployed-state.js +11 -11
- package/lib/direction-labels.js +265 -0
- package/lib/error-codes.js +31 -31
- package/lib/infra.js +29 -4
- package/lib/material-db.js +149 -0
- package/lib/path-guard.js +150 -0
- package/lib/projects.js +400 -0
- package/lib/scraper-utils.js +140 -0
- package/lib/surreal-server.js +338 -0
- package/lib/topic-decision.js +860 -0
- package/lib/topic-fields.js +251 -0
- package/lib/topic-schema.js +225 -0
- package/lib/workspace.js +501 -0
- package/package.json +29 -4
- package/plugins/fancy-analyze/analyze-definitions.md +519 -0
- package/plugins/fancy-analyze/index.js +1128 -77
- package/plugins/fancy-analyze/ingest-coverage.js +107 -0
- package/plugins/fancy-analyze/schema-analyze.md +355 -107
- package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
- package/plugins/fancy-analyze/summary-progress.js +71 -7
- package/plugins/fancy-analyze/templates.js +437 -139
- package/plugins/fancy-bootstrap/index.js +276 -131
- package/plugins/fancy-bootstrap/scripts/init.js +59 -25
- package/plugins/fancy-bootstrap/templates.js +35 -18
- package/plugins/fancy-db/index.js +172 -0
- package/plugins/fancy-douyin/deploy.js +134 -0
- package/plugins/fancy-douyin/index.js +551 -0
- package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
- package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
- package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
- package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
- package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
- package/plugins/fancy-douyin/state.js +197 -0
- package/plugins/fancy-douyin/templates.js +127 -0
- package/plugins/fancy-export/exporter.js +805 -0
- package/plugins/fancy-export/index.js +63 -0
- package/plugins/fancy-import/import-driver.js +29 -6
- package/plugins/fancy-import/index.js +13 -5
- package/plugins/fancy-import/prompts/01-import-prompt.md +39 -14
- package/plugins/fancy-import/templates.js +17 -8
- package/plugins/fancy-material/exporter.js +153 -0
- package/plugins/fancy-material/genres.js +256 -0
- package/plugins/fancy-material/index.js +835 -0
- package/plugins/fancy-material/inventory.js +143 -0
- package/plugins/fancy-material/kinds.js +313 -0
- package/plugins/fancy-material/queue.js +55 -0
- package/plugins/fancy-material/scripts/query.js +254 -0
- package/plugins/fancy-material/templates.js +258 -0
- package/plugins/fancy-material/tool-deploy.js +138 -0
- package/plugins/fancy-material/validate.js +149 -0
- package/plugins/fancy-project/index.js +133 -0
- package/plugins/fancy-scan/existing-block.js +100 -0
- package/plugins/fancy-scan/index.js +410 -21
- package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
- package/plugins/fancy-scan/scripts/append-block.js +725 -0
- package/plugins/fancy-scan/scripts/report-validate.js +824 -0
- package/plugins/fancy-scan/scripts/run-scan.js +169 -22
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +145 -16
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +179 -49
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +38 -20
- package/plugins/fancy-scan/templates/blocks.js +155 -0
- package/plugins/fancy-scan/templates/platforms.js +372 -0
- package/plugins/fancy-scan/templates/report-schema.js +90 -0
- package/plugins/fancy-scan/templates.js +545 -146
- package/plugins/fancy-scan/tool-deploy.js +335 -0
- package/plugins/fancy-scan/wait-turn.js +46 -0
- package/plugins/fancy-story-builder/db/README.md +106 -0
- package/plugins/fancy-story-builder/db/client.js +228 -0
- package/plugins/fancy-story-builder/db/commands.js +145 -0
- package/plugins/fancy-story-builder/db/ids.js +39 -0
- package/plugins/fancy-story-builder/db/import.js +798 -0
- package/plugins/fancy-story-builder/db/project.js +299 -0
- package/plugins/fancy-story-builder/db/query.js +361 -0
- package/plugins/fancy-story-builder/db/schema.js +417 -0
- package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
- package/plugins/fancy-story-builder/flesh-output.js +19 -6
- package/plugins/fancy-story-builder/index.js +4105 -396
- package/plugins/fancy-story-builder/patch-driver.js +390 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +29 -19
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +72 -10
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +40 -5
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +37 -0
- package/plugins/fancy-story-builder/purpose-template.md +42 -66
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -56
- package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
- package/plugins/fancy-story-builder/references/glossary.md +15 -6
- package/plugins/fancy-story-builder/references/index.md +97 -6
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +25 -7
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +5 -1
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +6 -2
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +15 -5
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +2 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +19 -1
- package/plugins/fancy-story-builder/schema-setting.json +1100 -0
- package/plugins/fancy-story-builder/schema-setting.md +201 -293
- package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +4 -1
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +135 -12
- package/plugins/fancy-story-builder/scripts/graph-utils.js +146 -47
- package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +100 -4
- package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +5 -1
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +158 -17
- package/plugins/fancy-story-builder/scripts/stage-batches.js +36 -2
- package/plugins/fancy-story-builder/scripts/stage-contract.js +12 -0
- package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +1 -1
- package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
- package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
- package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
- package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
- package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
- package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
- package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
- package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
- package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
- package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
- package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
- package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
- package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
- package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
- package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
- package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
- package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
- package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
- package/plugins/fancy-story-builder/storydb/README.md +271 -0
- package/plugins/fancy-story-builder/storydb/cli.js +458 -0
- package/plugins/fancy-story-builder/storydb/client.js +351 -0
- package/plugins/fancy-story-builder/storydb/compile.js +452 -0
- package/plugins/fancy-story-builder/storydb/context.js +288 -0
- package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
- package/plugins/fancy-story-builder/storydb/driver.js +106 -0
- package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
- package/plugins/fancy-story-builder/storydb/export.js +175 -0
- package/plugins/fancy-story-builder/storydb/ids.js +31 -0
- package/plugins/fancy-story-builder/storydb/impact.js +170 -0
- package/plugins/fancy-story-builder/storydb/import.js +559 -0
- package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
- package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
- package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
- package/plugins/fancy-story-builder/storydb/stage.js +426 -0
- package/plugins/fancy-story-builder/storydb/store.js +891 -0
- package/plugins/fancy-story-builder/storydb/tools.js +592 -0
- package/plugins/fancy-story-builder/storydb/validation.js +183 -0
- package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
- package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
- package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
- 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 +267 -44
- 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 +121 -19
- 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 +129 -21
- 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
- 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 +76 -17
- 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 +56 -8
- 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 +63 -13
- 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 +34 -6
- 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 +81 -18
- 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 +202 -42
- 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 +160 -27
- 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 +217 -41
- 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 +215 -41
- package/plugins/fancy-story-builder/templates.js +272 -74
- package/plugins/fancy-story-builder/wiki-references.js +429 -93
- package/plugins/fancy-story-builder/writing-driver.js +336 -9
- package/plugins/fancy-topic/index.js +414 -61
- package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
- package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
- package/plugins/fancy-topic/scripts/select-topic.js +106 -63
- package/plugins/fancy-topic/templates.js +225 -122
- package/plugins/fancy-topic/tool-deploy.js +200 -0
- package/lib/safe-rm.js +0 -94
|
@@ -1,168 +1,313 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fancy-bootstrap
|
|
2
|
+
* fancy-bootstrap — first-time workspace initialization.
|
|
3
|
+
*
|
|
4
|
+
* A workspace is a directory the user chooses once. It owns the shared
|
|
5
|
+
* scan/analyze artifacts, the SurrealDB server data, and every project
|
|
6
|
+
* directory under <workspace>/projects/<uuid>.
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
|
|
9
|
+
import { existsSync, realpathSync, statSync } from 'fs';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'path';
|
|
12
|
+
import { askUserQuestion, notify } from '#infra';
|
|
13
|
+
import { isSensitiveProjectPath, isValidProjectPath } from '#path-guard';
|
|
14
|
+
import { readDeployedState, deployedStateProblemText, DEPLOYED_STATUS } from '#deployed-state';
|
|
15
|
+
import { initWorkspaceDatabase } from '#db-init';
|
|
16
|
+
import { findSurrealBinary, installSurreal, serverStatus } from '#surreal-server';
|
|
17
|
+
import {
|
|
18
|
+
findWorkspace,
|
|
19
|
+
isWorkspace,
|
|
20
|
+
formatLocalTime,
|
|
21
|
+
listGlobalWorkspaces,
|
|
22
|
+
readWorkspace,
|
|
23
|
+
resolvePath,
|
|
24
|
+
resolveWorkspace,
|
|
25
|
+
workspacePaths,
|
|
26
|
+
} from '#workspace';
|
|
9
27
|
|
|
10
28
|
export const inject = ['commands', 'userQuestions'];
|
|
11
29
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
function helpText() {
|
|
31
|
+
return `📁 工作区初始化 用法
|
|
32
|
+
|
|
33
|
+
/fancy-bootstrap 交互式指定工作区目录进行初始化
|
|
34
|
+
/fancy-bootstrap <目录> 在指定目录初始化工作区
|
|
35
|
+
/fancy-bootstrap status 查看当前工作区
|
|
36
|
+
/fancy-bootstrap list 列出本机记录的所有工作区
|
|
37
|
+
/fancy-bootstrap help 显示本帮助
|
|
38
|
+
|
|
39
|
+
工作区结构:
|
|
40
|
+
<工作区>/projects/<UUID>/ 每个小说项目一个目录
|
|
41
|
+
<工作区>/扫榜结果/ 多个项目共享的扫榜产物
|
|
42
|
+
<工作区>/参考资料/ 待拆解的对标小说(fancy-analyze 读取)
|
|
43
|
+
<工作区>/对标/ 多个项目共享的对标分析
|
|
44
|
+
<工作区>/.fancy/ SurrealDB 数据、密钥、工作区配置
|
|
45
|
+
|
|
46
|
+
初始化会创建 SurrealDB 数据目录、项目注册表、数据库校验函数和素材库 schema。
|
|
47
|
+
启动时按 FANCY_SURREAL_BIN → <工作区>/.fancy/bin/surreal → PATH 的顺序查找 surreal;
|
|
48
|
+
都没有时才尝试下载官方二进制到 <工作区>/.fancy/bin/。`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizePath(raw, session) {
|
|
52
|
+
let value = String(raw || '').trim();
|
|
53
|
+
if (!value) return null;
|
|
54
|
+
if (value === '~' || value.startsWith('~/')) value = join(homedir(), value.slice(1));
|
|
55
|
+
if (!isAbsolute(value)) {
|
|
56
|
+
const base = session?.header?.cwd ? resolve(session.header.cwd) : null;
|
|
57
|
+
if (!base) return null;
|
|
58
|
+
value = resolve(base, value);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve symlinks for an existing path, or for a not-yet-created path use the
|
|
65
|
+
* deepest existing ancestor and append the missing segments. This lets the
|
|
66
|
+
* sensitive-path check see through `/tmp/link -> /etc` even when the workspace
|
|
67
|
+
* directory itself does not exist yet.
|
|
68
|
+
*/
|
|
69
|
+
function realTargetPath(target) {
|
|
70
|
+
let current = resolve(target);
|
|
71
|
+
const missing = [];
|
|
72
|
+
for (;;) {
|
|
73
|
+
try {
|
|
74
|
+
if (existsSync(current)) {
|
|
75
|
+
const real = realpathSync(current);
|
|
76
|
+
return missing.length ? join(real, ...missing) : real;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
return resolve(target);
|
|
80
|
+
}
|
|
81
|
+
const parent = dirname(current);
|
|
82
|
+
if (parent === current) return resolve(target);
|
|
83
|
+
missing.unshift(basename(current));
|
|
84
|
+
current = parent;
|
|
36
85
|
}
|
|
37
|
-
return true;
|
|
38
86
|
}
|
|
39
87
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
88
|
+
function formatWorkspace(root) {
|
|
89
|
+
const workspace = readWorkspace(root);
|
|
90
|
+
const paths = workspacePaths(root);
|
|
91
|
+
return [
|
|
92
|
+
'📁 工作区:' + root,
|
|
93
|
+
'工作区 ID:' + workspace.workspace_id,
|
|
94
|
+
'创建时间:' + formatLocalTime(workspace.created_at),
|
|
95
|
+
'更新时间:' + formatLocalTime(workspace.updated_at),
|
|
96
|
+
'当前项目:' + (workspace.active_project || '未选择(可用 /fancy-project use <名字|UUID> 指定)'),
|
|
97
|
+
'项目目录:' + paths.projects,
|
|
98
|
+
'扫榜结果:' + paths.scan,
|
|
99
|
+
'对标分析:' + paths.analyze,
|
|
100
|
+
'参考资料:' + paths.referenceSources,
|
|
101
|
+
'SurrealDB:' + (workspace.surreal?.url || '—'),
|
|
102
|
+
'素材库:' + (workspace.surreal?.material_database || 'material_library'),
|
|
103
|
+
'数据库目录:' + paths.surrealData,
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
43
106
|
|
|
44
107
|
export async function apply(ctx) {
|
|
45
108
|
ctx.commands.register({
|
|
46
109
|
name: 'fancy-bootstrap',
|
|
47
|
-
description: '📁
|
|
48
|
-
input: {
|
|
49
|
-
hint: '[项目根]',
|
|
50
|
-
},
|
|
110
|
+
description: '📁 初始化 fancy-webnovel 工作区 ( usage: /fancy-bootstrap [目录] | status | list | help )',
|
|
111
|
+
input: { hint: '[目录] | status | list' },
|
|
51
112
|
handler: async (invocation) => {
|
|
52
113
|
const session = invocation.agent.session;
|
|
53
|
-
const rawInput = invocation.rawInput
|
|
54
|
-
const { signal } = invocation;
|
|
114
|
+
const rawInput = String(invocation.rawInput || '').trim();
|
|
55
115
|
|
|
56
|
-
|
|
116
|
+
if (rawInput.toLowerCase() === 'help') return notify(session, 'success', helpText());
|
|
57
117
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
118
|
+
if (rawInput.toLowerCase() === 'status') {
|
|
119
|
+
try {
|
|
120
|
+
const root = resolveWorkspace({ cwd: session?.header?.cwd, required: true });
|
|
121
|
+
return notify(session, 'success', formatWorkspace(root) + '\n\n' + JSON.stringify(await serverStatus(root), null, 2));
|
|
122
|
+
} catch {
|
|
123
|
+
return notify(session, 'error', '❌ 当前目录不是 fancy-webnovel 工作区,无法查看状态。请运行 /fancy-bootstrap 初始化工作区,或者切换到其他工作区。');
|
|
63
124
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (rawInput.toLowerCase() === 'list') {
|
|
128
|
+
let entries = listGlobalWorkspaces();
|
|
129
|
+
const current = findWorkspace(session?.header?.cwd);
|
|
130
|
+
if (!entries.length && !current) {
|
|
131
|
+
return notify(session, 'success', '🌐 本机未发现 fancy-webnovel 工作区。\n运行 /fancy-bootstrap <目录> 创建第一个工作区。');
|
|
71
132
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
133
|
+
let currentEntry = current ? entries.find((entry) => entry.workspace === current) : null;
|
|
134
|
+
if (current && !currentEntry) {
|
|
135
|
+
// A lightweight/legacy root may be usable without appearing in the
|
|
136
|
+
// registry yet. Keep it visible and put it first.
|
|
137
|
+
currentEntry = {
|
|
138
|
+
workspace: current,
|
|
139
|
+
workspace_id: '',
|
|
140
|
+
name: basename(current),
|
|
141
|
+
exists: isWorkspace(current),
|
|
142
|
+
last_used: '',
|
|
143
|
+
};
|
|
144
|
+
entries = [currentEntry, ...entries];
|
|
76
145
|
}
|
|
77
|
-
|
|
146
|
+
const ordered = currentEntry
|
|
147
|
+
? [currentEntry, ...entries.filter((entry) => entry !== currentEntry)]
|
|
148
|
+
: entries;
|
|
149
|
+
|
|
150
|
+
const details = await Promise.all(ordered.map(async (entry) => {
|
|
151
|
+
let surreal = null;
|
|
152
|
+
try { surreal = readWorkspace(entry.workspace).surreal || null; } catch { /* missing/lightweight */ }
|
|
153
|
+
let status = null;
|
|
154
|
+
if (entry.exists && surreal) {
|
|
155
|
+
try { status = await serverStatus(entry.workspace); } catch { /* ignore probe errors */ }
|
|
156
|
+
}
|
|
157
|
+
return { entry, surreal, status };
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
const lines = ['🌐 本机工作区(' + ordered.length + ')', ''];
|
|
161
|
+
for (const { entry, surreal, status } of details) {
|
|
162
|
+
lines.push((entry.workspace === current ? '* ' : '- ') + entry.name);
|
|
163
|
+
lines.push(' 路径:' + entry.workspace);
|
|
164
|
+
lines.push(' ID:' + (entry.workspace_id || '—') + (entry.exists ? '' : ' ⚠️ 目录不存在/已删除'));
|
|
165
|
+
if (surreal) {
|
|
166
|
+
lines.push(' SurrealDB:' + (surreal.url || '—'));
|
|
167
|
+
if (surreal.namespace || surreal.registry_database) {
|
|
168
|
+
lines.push(' 命名空间:' + (surreal.namespace || '—') + ' 注册表:' + (surreal.registry_database || '—'));
|
|
169
|
+
}
|
|
170
|
+
if (surreal.data_dir) lines.push(' 数据目录:' + surreal.data_dir);
|
|
171
|
+
if (status) {
|
|
172
|
+
if (status.running) {
|
|
173
|
+
const version = status.health?.version ? ' · ' + status.health.version : '';
|
|
174
|
+
const credentials = status.credentials_ok === false ? '(凭据不匹配)' : '';
|
|
175
|
+
lines.push(' 服务:运行中' + version + credentials);
|
|
176
|
+
} else {
|
|
177
|
+
lines.push(' 服务:未运行');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
lines.push(' SurrealDB:—');
|
|
182
|
+
}
|
|
183
|
+
if (entry.last_used) lines.push(' 最近使用:' + formatLocalTime(entry.last_used));
|
|
184
|
+
lines.push('');
|
|
185
|
+
}
|
|
186
|
+
lines.push('* 表示当前 cwd 所在工作区。');
|
|
187
|
+
return notify(session, 'success', lines.join('\n'));
|
|
78
188
|
}
|
|
79
189
|
|
|
80
|
-
|
|
81
|
-
|
|
190
|
+
// Only auto-report a workspace when the caller is actually inside one,
|
|
191
|
+
// or when FANCY_HOME explicitly points at one. The global pointer is a
|
|
192
|
+
// convenience for other commands, not a reason to hijack bootstrap from
|
|
193
|
+
// an unrelated directory.
|
|
194
|
+
const cwd = session?.header?.cwd;
|
|
195
|
+
let insideWorkspace = null;
|
|
196
|
+
if (process.env.FANCY_HOME) {
|
|
197
|
+
const envRoot = resolvePath(process.env.FANCY_HOME, cwd || process.cwd());
|
|
198
|
+
if (isWorkspace(envRoot)) insideWorkspace = envRoot;
|
|
199
|
+
}
|
|
200
|
+
if (!insideWorkspace && cwd) insideWorkspace = findWorkspace(cwd);
|
|
201
|
+
if (insideWorkspace && !rawInput) {
|
|
202
|
+
return notify(session, 'success', 'ℹ️ 当前目录已位于工作区:\n\n' + formatWorkspace(insideWorkspace));
|
|
82
203
|
}
|
|
83
204
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
205
|
+
let target = rawInput ? normalizePath(rawInput, session) : null;
|
|
206
|
+
if (!rawInput) {
|
|
207
|
+
if (invocation.signal?.aborted) return notify(session, 'error', '🚫 操作已取消。');
|
|
208
|
+
|
|
209
|
+
const asked = await askUserQuestion(ctx, invocation, [{
|
|
210
|
+
id: 'workspace_path',
|
|
211
|
+
header: '选择工作区',
|
|
212
|
+
question: '选择或输入工作区目录',
|
|
213
|
+
options: (() => {
|
|
214
|
+
const cwd = session?.header?.cwd ? resolve(session.header.cwd) : null;
|
|
215
|
+
const items = [];
|
|
216
|
+
if (cwd) items.push({ label: cwd, description: '当前目录' });
|
|
217
|
+
for (const item of [
|
|
218
|
+
{ label: join(homedir(), 'fancy-webnovel'), description: '用户主目录' },
|
|
219
|
+
{ label: join(homedir(), 'Documents', 'fancy-webnovel'), description: '文档目录' },
|
|
220
|
+
]) {
|
|
221
|
+
if (!items.some((entry) => entry.label === item.label)) items.push(item);
|
|
222
|
+
}
|
|
223
|
+
return items;
|
|
224
|
+
})(),
|
|
225
|
+
}]);
|
|
226
|
+
if (!asked.ok) {
|
|
227
|
+
if (asked.failure?.type === 'cancelled') return notify(session, 'error', '🚫 操作已取消。');
|
|
228
|
+
return notify(session, 'error', asked.failure?.message || '❌ 提问失败');
|
|
93
229
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
notify(session, 'success', bootstrap.notify({ type: 'using_cwd', projectRoot }));
|
|
230
|
+
const answer = asked.answer?.answers?.[0];
|
|
231
|
+
target = normalizePath(answer?.custom || answer?.selected?.[0] || '', session);
|
|
97
232
|
}
|
|
98
233
|
|
|
99
|
-
if (!
|
|
100
|
-
|
|
234
|
+
if (!target) return notify(session, 'error', '⚠️ 未提供工作区路径,操作已取消。');
|
|
235
|
+
const inputTarget = target;
|
|
236
|
+
const targetReal = realTargetPath(target);
|
|
237
|
+
if (isSensitiveProjectPath(targetReal) || !isValidProjectPath(targetReal)) {
|
|
238
|
+
return notify(session, 'error', '❌ 路径不合法或属于受保护目录:' + inputTarget
|
|
239
|
+
+ (targetReal !== inputTarget ? '\n实际路径:' + targetReal : ''));
|
|
101
240
|
}
|
|
241
|
+
target = targetReal;
|
|
242
|
+
try {
|
|
243
|
+
if (!statSync(target).isDirectory()) {
|
|
244
|
+
return notify(session, 'error', '❌ 工作区路径不是目录:' + target);
|
|
245
|
+
}
|
|
246
|
+
} catch { /* path does not exist yet; init will create it */ }
|
|
102
247
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
return notify(session, 'error',
|
|
248
|
+
// A directory that already has .fancy-deployed is already initialized
|
|
249
|
+
// (full workspace or lightweight scan/topic root). A corrupt marker is
|
|
250
|
+
// reported explicitly instead of being overwritten or mistaken for a new
|
|
251
|
+
// workspace.
|
|
252
|
+
const deployed = readDeployedState(target);
|
|
253
|
+
if (deployed.status === DEPLOYED_STATUS.OK) {
|
|
254
|
+
return notify(session, 'error', '⚠️ 该目录已完成初始化,无需重复执行(.fancy-deployed 已存在):' + target);
|
|
110
255
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return notify(session, 'error',
|
|
115
|
-
deployedStateProblemText(existingState, { projectRoot })
|
|
116
|
-
|| bootstrap.notify({ type: 'already_initialized' }));
|
|
256
|
+
if (deployed.status !== DEPLOYED_STATUS.MISSING) {
|
|
257
|
+
return notify(session, 'error', deployedStateProblemText(deployed, { projectRoot: target })
|
|
258
|
+
|| '❌ 状态文件 .fancy-deployed 无法读取:' + target);
|
|
117
259
|
}
|
|
118
260
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
question: bootstrap.notify({ type: 'confirm', projectRoot }),
|
|
129
|
-
options: bootstrap.confirmOptions(),
|
|
261
|
+
if (invocation.signal?.aborted) return notify(session, 'error', '🚫 操作已取消。');
|
|
262
|
+
const confirmed = await askUserQuestion(ctx, invocation, [{
|
|
263
|
+
id: 'confirm_workspace',
|
|
264
|
+
header: '确认工作区',
|
|
265
|
+
question: '确认使用 ' + target + ' 作为 fancy-webnovel 工作区?',
|
|
266
|
+
options: [
|
|
267
|
+
{ label: '确认创建', description: '创建/初始化工作区、数据库和校验函数' },
|
|
268
|
+
{ label: '取消', description: '不做任何修改' },
|
|
269
|
+
],
|
|
130
270
|
}]);
|
|
131
|
-
if (!
|
|
132
|
-
return notify(session, 'error',
|
|
133
|
-
|
|
134
|
-
err: asked.failure.message
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
const confirm = asked.answer;
|
|
138
|
-
// Handle case where user skipped or X'd the question
|
|
139
|
-
if (!confirm?.answers?.[0]) {
|
|
140
|
-
return notify(session, 'error', bootstrap.notify({ type: 'cancelled' }));
|
|
141
|
-
}
|
|
142
|
-
const ans = confirm.answers[0];
|
|
143
|
-
const chosen = ans?.selected?.[0] ?? ans?.custom;
|
|
144
|
-
if (chosen !== '确认创建') {
|
|
145
|
-
// 区分"点了取消"与"自己输入了别的东西":
|
|
146
|
-
// 后者此前会被静默当成取消,用户不知道自己输入的内容被丢掉了。
|
|
147
|
-
if (ans?.custom && !ans?.selected?.length) {
|
|
148
|
-
return notify(session, 'error', bootstrap.notify({
|
|
149
|
-
type: 'confirm_invalid',
|
|
150
|
-
custom: ans.custom
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
return notify(session, 'error', bootstrap.notify({ type: 'cancelled' }));
|
|
271
|
+
if (!confirmed.ok) {
|
|
272
|
+
if (confirmed.failure?.type === 'cancelled') return notify(session, 'error', '🚫 操作已取消。');
|
|
273
|
+
return notify(session, 'error', confirmed.failure?.message || '❌ 提问失败');
|
|
154
274
|
}
|
|
275
|
+
const choice = confirmed.answer?.answers?.[0]?.selected?.[0];
|
|
276
|
+
if (choice !== '确认创建') return notify(session, 'error', '🚫 操作已取消。');
|
|
155
277
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
278
|
+
try {
|
|
279
|
+
// First init may need the standalone binary. Install it into the
|
|
280
|
+
// workspace so it survives PATH changes and is easy to remove.
|
|
281
|
+
const notes = [];
|
|
282
|
+
if (!findSurrealBinary(target)) {
|
|
283
|
+
notes.push('⏬ 未找到 surreal,已自动下载到工作区 .fancy/bin/。');
|
|
284
|
+
await installSurreal(target);
|
|
285
|
+
}
|
|
286
|
+
const result = await initWorkspaceDatabase(target);
|
|
287
|
+
return notify(session, 'success', [
|
|
288
|
+
'✅ 工作区初始化完成',
|
|
289
|
+
...(notes.length ? ['', ...notes] : []),
|
|
290
|
+
'',
|
|
291
|
+
formatWorkspace(result.workspace),
|
|
292
|
+
'',
|
|
293
|
+
'数据库:schema ' + result.schema_version + ' · validation ' + result.validation_version,
|
|
294
|
+
'素材库:' + (result.material?.database || 'material_library') + '(schema ' + (result.material?.schema_version || 6) + ')',
|
|
295
|
+
'服务:' + (result.server.running ? '运行中' : '未运行'),
|
|
296
|
+
...(result.started && result.started.endpoint_changed
|
|
297
|
+
? ['⚠️ 原端口被另一个 SurrealDB 占用,已改用:' + result.started.endpoint]
|
|
298
|
+
: []),
|
|
299
|
+
'',
|
|
300
|
+
'下一步:',
|
|
301
|
+
'1. /fancy-scan 扫榜',
|
|
302
|
+
'2. /fancy-topic 选题',
|
|
303
|
+
'3. /fancy-topic setup 创建项目(会分配 UUID、项目库和独立数据库用户)',
|
|
304
|
+
'4. /fancy-material 生成素材库(可选,写作时查名字/金手指/冲突/场景)',
|
|
305
|
+
'5. /fancy-douyin add <主页> 采集抖音图文素材(可选)',
|
|
306
|
+
'6. /fancy-build scale 开始写故事',
|
|
307
|
+
].join('\n'));
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return notify(session, 'error', '❌ 初始化失败:' + (err && err.message ? err.message : String(err)));
|
|
165
310
|
}
|
|
166
|
-
}
|
|
311
|
+
},
|
|
167
312
|
});
|
|
168
313
|
}
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
* 启动时检查 topic_decision_<date>.md 文件是否存在;不存在则报错让用户
|
|
21
21
|
* 先跑 fancy-scan 并等待 LLM 完成分析写入该文件。
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
23
|
+
import { existsSync, lstatSync, mkdirSync, realpathSync, statSync, unlinkSync } from 'fs';
|
|
24
24
|
import { expandHome, resolvePath, joinPath } from './path-utils.js';
|
|
25
25
|
import { EC, errorResponse } from '#error-codes';
|
|
26
|
+
import { atomicWriteFile } from '#atomic-write';
|
|
27
|
+
import { isValidProjectPath, isSensitiveProjectPath, projectPathProblem } from '#path-guard';
|
|
26
28
|
|
|
27
29
|
function nowIso() {
|
|
28
30
|
return new Date().toISOString().replace('.000Z', 'Z');
|
|
@@ -56,17 +58,15 @@ function fail(ec, extra = {}) {
|
|
|
56
58
|
function main() {
|
|
57
59
|
const projectRootRaw = process.argv[2] ?? '';
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// 路径校验与 plugins/fancy-bootstrap/index.js 用**同一套规则**
|
|
62
|
+
// (lib/path-guard.js)。此前两边不一致:index.js 拒空白、init.js 不拒;
|
|
63
|
+
// 两边都放行 shell 元字符($ ` ; & |)—— 而路径会被原样拼进
|
|
64
|
+
// 提示词里的 shell 命令,双引号挡不住命令替换。
|
|
65
|
+
if (isSensitiveProjectPath(projectRootRaw)) {
|
|
66
|
+
fail(EC.EC019_SENSITIVE_PATH);
|
|
61
67
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const SENSITIVE_PREFIXES = ['/etc', '/proc', '/sys', '/dev', '/root/.ssh', '/root/.gnupg', '/var/log', '/var/lib', '/boot'];
|
|
65
|
-
const normalized = projectRootRaw.replace(/\\/g, '/');
|
|
66
|
-
for (const prefix of SENSITIVE_PREFIXES) {
|
|
67
|
-
if (normalized === prefix || normalized.startsWith(prefix + '/')) {
|
|
68
|
-
fail(EC.EC019_SENSITIVE_PATH);
|
|
69
|
-
}
|
|
68
|
+
if (!isValidProjectPath(projectRootRaw)) {
|
|
69
|
+
fail(EC.EC001_INVALID_PATH, { message: projectPathProblem(projectRootRaw) || '路径不合法' });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const projectRoot = resolvePath(expandHome(projectRootRaw));
|
|
@@ -81,21 +81,38 @@ function main() {
|
|
|
81
81
|
const projectRootReal = realPathOf(projectRoot);
|
|
82
82
|
const deployed = joinPath(projectRootReal, '.fancy-deployed');
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
// 标记文件必须是普通文件;符号链接会把“写项目状态”变成“写链接指向的文件”,
|
|
85
|
+
// 而项目根是用户可写的 —— 一个坏链接就能把内容写到项目外。
|
|
86
|
+
let deployedStat = null;
|
|
87
|
+
try { deployedStat = lstatSync(deployed); } catch { /* 不存在 */ }
|
|
88
|
+
if (deployedStat) {
|
|
89
|
+
if (deployedStat.isSymbolicLink()) {
|
|
90
|
+
fail(EC.EC017_INIT_FAILED, {
|
|
91
|
+
message: '.fancy-deployed 是符号链接,拒绝写入;请删除该链接后重新初始化'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (!deployedStat.isFile()) {
|
|
95
|
+
fail(EC.EC017_INIT_FAILED, {
|
|
96
|
+
message: '.fancy-deployed 已存在但不是普通文件,请先处理它再重新初始化'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
85
99
|
fail(EC.EC002_ALREADY_INITIALIZED);
|
|
86
100
|
}
|
|
87
101
|
if (!existsSync(projectRootReal)) {
|
|
88
|
-
fail(EC.EC018_DIR_NOT_FOUND, {
|
|
102
|
+
fail(EC.EC018_DIR_NOT_FOUND, {
|
|
103
|
+
message: `项目根目录不存在: ${projectRoot}`,
|
|
104
|
+
reason: 'missing',
|
|
105
|
+
});
|
|
89
106
|
}
|
|
90
107
|
if (!isDirectory(projectRootReal)) {
|
|
91
|
-
fail(EC.EC018_DIR_NOT_FOUND, {
|
|
108
|
+
fail(EC.EC018_DIR_NOT_FOUND, {
|
|
109
|
+
message: `项目根路径不是目录: ${projectRoot}`,
|
|
110
|
+
reason: 'not_directory',
|
|
111
|
+
});
|
|
92
112
|
}
|
|
93
113
|
// 真实路径再查一遍敏感前缀(符号链接已经解开)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (realNormalized === prefix || realNormalized.startsWith(prefix + '/')) {
|
|
97
|
-
fail(EC.EC019_SENSITIVE_PATH);
|
|
98
|
-
}
|
|
114
|
+
if (isSensitiveProjectPath(projectRootReal)) {
|
|
115
|
+
fail(EC.EC019_SENSITIVE_PATH);
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
const now = nowIso();
|
|
@@ -107,12 +124,29 @@ function main() {
|
|
|
107
124
|
// 而项目其实是个半初始化状态(这次失败、那次也失败)。
|
|
108
125
|
// 现在标记文件是"最后一步",失败就不会留下误导性的标记。
|
|
109
126
|
//
|
|
110
|
-
// 预创建工具目录,方便 fancy-analyze
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
// 预创建工具目录,方便 fancy-analyze 等插件直接使用。
|
|
128
|
+
// 这两个目录后续会被其它插件写入(原文/拆解结果),不能是符号链接 ——
|
|
129
|
+
// 否则一次初始化就能把它们指到项目外。
|
|
130
|
+
for (const name of ['参考资料', '对标']) {
|
|
131
|
+
const p = joinPath(projectRootReal, name);
|
|
132
|
+
let st = null;
|
|
133
|
+
try { st = lstatSync(p); } catch { /* 不存在 */ }
|
|
134
|
+
if (st) {
|
|
135
|
+
if (st.isSymbolicLink()) {
|
|
136
|
+
throw new Error(`${name}/ 是符号链接,拒绝初始化;请换成真实目录`);
|
|
137
|
+
}
|
|
138
|
+
if (!st.isDirectory()) {
|
|
139
|
+
throw new Error(`${name}/ 已存在但不是目录,拒绝初始化`);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
mkdirSync(p, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
113
145
|
|
|
114
|
-
// 写 .fancy-deployed
|
|
115
|
-
|
|
146
|
+
// 写 .fancy-deployed 标记文件。
|
|
147
|
+
// 原子写 + 0600:里面有 llm_wiki_token,不能让同机其它用户读到;
|
|
148
|
+
// 也不能在写到一半被杀时留下半份 JSON(下次启动会报“状态文件损坏”)。
|
|
149
|
+
atomicWriteFile(deployed, JSON.stringify({
|
|
116
150
|
deployed_at: now,
|
|
117
151
|
project_root_abs: projectRootReal,
|
|
118
152
|
phase: 'initialized',
|
|
@@ -121,7 +155,7 @@ function main() {
|
|
|
121
155
|
reference_id: '',
|
|
122
156
|
novel_id: '',
|
|
123
157
|
provider: 'custom',
|
|
124
|
-
}, null, 2),
|
|
158
|
+
}, null, 2), { mode: 0o600 });
|
|
125
159
|
} catch (e) {
|
|
126
160
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
127
161
|
// 失败时把标记文件删掉,别留半初始化状态
|