@cloud411716/fancy-webnovel 0.3.16 → 1.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/commands/bootstrap/index.js +138 -0
- package/commands/bootstrap/init-project.js +121 -0
- package/commands/scan/index.js +226 -0
- package/commands/scan/platforms/fanqie.js +148 -0
- package/commands/scan/platforms/jinjiang.js +143 -0
- package/commands/scan/platforms/qidian.js +106 -0
- package/commands/scan/platforms/qimao.js +127 -0
- package/commands/scan/scraper.js +195 -0
- package/cordis.patch.yml +14 -0
- package/events.js +53 -0
- package/index.js +40 -6
- package/infra.js +83 -102
- package/package.json +3 -11
- package/plugins/fancy-bootstrap/index.js +0 -100
- package/plugins/fancy-bootstrap/scripts/init.js +0 -111
- package/plugins/fancy-bootstrap/scripts/path-utils.js +0 -22
- package/plugins/fancy-bootstrap/templates.js +0 -41
- package/plugins/fancy-scan/index.js +0 -195
- package/plugins/fancy-scan/scripts/run-scan.js +0 -295
- package/plugins/fancy-scan/scripts/scan-output-format.md +0 -188
- package/plugins/fancy-scan/scripts/scraper-registry.md +0 -104
- package/plugins/fancy-scan/scripts/scrapers/cdp-utils.cjs +0 -75
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.cjs +0 -441
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.cjs +0 -406
- package/plugins/fancy-scan/scripts/scrapers/package-lock.json +0 -27
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.cjs +0 -461
- package/plugins/fancy-scan/templates.js +0 -158
package/infra.js
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* infra.js — DSH
|
|
2
|
+
* infra.js — DSH Plugin Core Infrastructure
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* notify(session, text)
|
|
6
|
-
* llmFollowup(invocation,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* spawnScript(cmd, args, opts) — spawn 子进程,解析 stdout JSON
|
|
4
|
+
* Provides:
|
|
5
|
+
* notify(session, text) — append a user-facing message to the session log
|
|
6
|
+
* llmFollowup(invocation,prompt)— hand a prompt to the LLM as a user message
|
|
7
|
+
* scanProgress(ctx, phase, data)— emit structured scan progress events
|
|
8
|
+
* sleep(ctx, ms) — DSH-native promise delay via ctx.timeout()
|
|
10
9
|
*
|
|
11
|
-
*
|
|
10
|
+
* All fancy-* plugins import from this file.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
// --------------------------------------------------------------------------
|
|
15
|
-
// notify —
|
|
14
|
+
// notify — append a text message as a session event (no LLM round-trip)
|
|
16
15
|
// --------------------------------------------------------------------------
|
|
17
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} session - DSH Session object
|
|
19
|
+
* @param {string} text
|
|
20
|
+
*/
|
|
18
21
|
export function notify(session, text) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
25
|
-
});
|
|
22
|
+
session.append(
|
|
23
|
+
'user/message',
|
|
24
|
+
{ content: [{ type: 'text', text: String(text) }], source: { kind: 'user' } },
|
|
25
|
+
{ surfaceOp: 'append' }
|
|
26
|
+
);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
// --------------------------------------------------------------------------
|
|
29
|
-
// llmFollowup
|
|
30
|
+
// llmFollowup — hand a prompt to the LLM as a user message
|
|
30
31
|
// --------------------------------------------------------------------------
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {object} invocation - CommandInvocation from ctx.commands handler
|
|
35
|
+
* @param {string|object} content
|
|
36
|
+
*/
|
|
32
37
|
export function llmFollowup(invocation, content) {
|
|
33
38
|
const prompt = typeof content === 'string' ? content : JSON.stringify(content);
|
|
34
39
|
invocation.agent.followup({
|
|
@@ -38,103 +43,79 @@ export function llmFollowup(invocation, content) {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
// --------------------------------------------------------------------------
|
|
41
|
-
//
|
|
42
|
-
// 调用 startActivity() 返回 stop 函数,进入新阶段时先 stop 再 start
|
|
46
|
+
// sleep — DSH-native promise delay using ctx.timeout()
|
|
43
47
|
// --------------------------------------------------------------------------
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Return a promise that resolves after `ms` milliseconds.
|
|
51
|
+
* Uses ctx.timeout() (Cordis timer service) instead of native setTimeout.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} ctx - Cordis plugin context
|
|
54
|
+
* @param {number} ms - milliseconds to wait
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
export function sleep(ctx, ms) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
ctx.timeout(resolve, ms);
|
|
60
|
+
});
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
// --------------------------------------------------------------------------
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// 调用时传入 session + onCancel 回调(清理函数,如 kill 子进程、停止计时等)
|
|
54
|
-
// 返回 stop 函数(调用以移除监听器,流程正常结束时调用)
|
|
55
|
-
// 注意:不在弹窗场景下使用(弹窗自身有取消逻辑)
|
|
64
|
+
// scanProgress — emit structured scan progress via DSH events
|
|
56
65
|
// --------------------------------------------------------------------------
|
|
57
|
-
const _cancelHandlers = new Map(); // session → { stopFn, onCancel }
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Emit a scan progress event. These flow into the session log and can be
|
|
69
|
+
* observed by the running agent for real-time progress feedback.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} ctx
|
|
72
|
+
* @param {'start'|'book'|'rank-done'|'done'} phase
|
|
73
|
+
* @param {object} data
|
|
74
|
+
*/
|
|
75
|
+
export function scanProgress(ctx, phase, data) {
|
|
76
|
+
switch (phase) {
|
|
77
|
+
case 'start':
|
|
78
|
+
ctx.events.emit('fancy/scan:start', data.platform, data.channel, data.type, data.label);
|
|
79
|
+
break
|
|
80
|
+
case 'book':
|
|
81
|
+
ctx.events.emit('fancy/scan:book', data.platform, data.channel, data.type,
|
|
82
|
+
data.index, data.total, data.title, data.rank);
|
|
83
|
+
break
|
|
84
|
+
case 'rank-done':
|
|
85
|
+
ctx.events.emit('fancy/scan:rank-done', data.platform, data.channel, data.type,
|
|
86
|
+
data.ok, data.written, data.failed, data.errorMsg);
|
|
87
|
+
break
|
|
88
|
+
case 'done':
|
|
89
|
+
ctx.events.emit('fancy/scan:done', data.platform, data.totalBooks, data.totalFiles, data.ok);
|
|
90
|
+
break
|
|
65
91
|
}
|
|
66
|
-
|
|
67
|
-
let cancelled = false;
|
|
68
|
-
|
|
69
|
-
const handler = () => {
|
|
70
|
-
if (cancelled) return;
|
|
71
|
-
cancelled = true;
|
|
72
|
-
for (const fn of stopFns) { try { fn(); } catch (_) {} }
|
|
73
|
-
cleanup();
|
|
74
|
-
notify(session, '🚫 操作已取消。');
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const cleanup = () => {
|
|
78
|
-
process.removeListener('SIGINT', handler);
|
|
79
|
-
_cancelHandlers.delete(session);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const stopFns = [onCancel].filter(Boolean);
|
|
83
|
-
process.on('SIGINT', handler);
|
|
84
|
-
|
|
85
|
-
const stopFn = () => {
|
|
86
|
-
if (!cancelled) {
|
|
87
|
-
for (const fn of stopFns) { try { fn(); } catch (_) {} }
|
|
88
|
-
cleanup();
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
_cancelHandlers.set(session, { stopFn });
|
|
93
|
-
return stopFn;
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
// --------------------------------------------------------------------------
|
|
97
|
-
//
|
|
98
|
-
// opts.stop() 会在进程终止时自动调用(用于清理 turn/start)
|
|
95
|
+
// writeFile / mkdir — Node.js fs, used for project dirs outside workspace
|
|
99
96
|
// --------------------------------------------------------------------------
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Write text to a file, creating directories as needed.
|
|
100
|
+
* Uses Node.js fs directly (not ctx.fs) since project roots may be
|
|
101
|
+
* outside the DSH sandbox workspace.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} filepath
|
|
104
|
+
* @param {string} content
|
|
105
|
+
* @param {string} [encoding='utf-8']
|
|
106
|
+
*/
|
|
107
|
+
export async function writeFile(filepath, content, encoding = 'utf-8') {
|
|
108
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
109
|
+
const { dirname } = await import('path');
|
|
110
|
+
mkdirSync(dirname(filepath), { recursive: true });
|
|
111
|
+
writeFileSync(filepath, content, encoding);
|
|
112
|
+
}
|
|
116
113
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const parsed = JSON.parse(stdout);
|
|
125
|
-
settle({
|
|
126
|
-
ok: parsed.ok === true,
|
|
127
|
-
output: stdout,
|
|
128
|
-
error: parsed.error
|
|
129
|
-
? (typeof parsed.error === 'object'
|
|
130
|
-
? (parsed.error.message || JSON.stringify(parsed.error))
|
|
131
|
-
: String(parsed.error))
|
|
132
|
-
: stderr || undefined,
|
|
133
|
-
});
|
|
134
|
-
} catch (_) {
|
|
135
|
-
settle({ ok: false, output: stdout, error: stderr || ('exit ' + code) });
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
proc.on('error', (err) => { clearTimeout(timer); cleanup(); settle({ ok: false, output: '', error: err.message }); });
|
|
139
|
-
});
|
|
114
|
+
/**
|
|
115
|
+
* Create a directory, creating parents as needed.
|
|
116
|
+
* @param {string} dirpath
|
|
117
|
+
*/
|
|
118
|
+
export async function mkdirp(dirpath) {
|
|
119
|
+
const { mkdirSync } = await import('fs');
|
|
120
|
+
mkdirSync(dirpath, { recursive: true });
|
|
140
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloud411716/fancy-webnovel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -9,21 +9,13 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
11
|
"infra.js",
|
|
12
|
-
"
|
|
12
|
+
"events.js",
|
|
13
13
|
"cordis.patch.yml",
|
|
14
|
-
"
|
|
14
|
+
"commands/"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"playwright-core": "1.48.0"
|
|
18
18
|
},
|
|
19
|
-
"peerDependencies": {
|
|
20
|
-
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2"
|
|
21
|
-
},
|
|
22
|
-
"peerDependenciesMeta": {
|
|
23
|
-
"@deepseek-ai/dsh-llm": {
|
|
24
|
-
"optional": true
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
19
|
"dsh": {
|
|
28
20
|
"bundle": {
|
|
29
21
|
"patch": "./cordis.patch.yml"
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fancy-bootstrap plugin entry
|
|
3
|
-
*/
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { dirname, join } from 'path';
|
|
6
|
-
import { existsSync } from 'fs';
|
|
7
|
-
import { notify, spawnScript, startActivity } from '../../infra.js';
|
|
8
|
-
import { bootstrap } from './templates.js';
|
|
9
|
-
|
|
10
|
-
export const inject = ['commands', 'userQuestions'];
|
|
11
|
-
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const SCRIPTS_DIR = join(__dirname, 'scripts');
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// 路径工具
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
function isValidPath(p) {
|
|
20
|
-
return !p.includes('..') && !/[<>:"'|?*[\x00-\x1f]/.test(p) && !/\s/.test(p);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// 注册
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
export async function apply(ctx) {
|
|
28
|
-
ctx.commands.register({
|
|
29
|
-
name: 'fancy-bootstrap',
|
|
30
|
-
description: '📁 初始化项目根 ( usage: /fancy-bootstrap [项目根] )',
|
|
31
|
-
handler: async (invocation) => {
|
|
32
|
-
const session = invocation.agent.session;
|
|
33
|
-
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
34
|
-
|
|
35
|
-
let projectRoot = rawInput;
|
|
36
|
-
|
|
37
|
-
// 无参数 → 弹窗
|
|
38
|
-
if (!projectRoot) {
|
|
39
|
-
const result = await ctx.userQuestions.ask({
|
|
40
|
-
questions: [bootstrap.askPath()],
|
|
41
|
-
});
|
|
42
|
-
projectRoot = result.answers[0]?.custom?.trim() || '';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!projectRoot) {
|
|
46
|
-
notify(session, bootstrap.notify({ type: 'no_path' }));
|
|
47
|
-
return { kind: 'success', text: '' };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (projectRoot === '.') {
|
|
51
|
-
projectRoot = process.cwd();
|
|
52
|
-
notify(session, bootstrap.notify({ type: 'using_cwd', projectRoot }));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!isValidPath(projectRoot)) {
|
|
56
|
-
notify(session, bootstrap.notify({ type: 'invalid_path' }));
|
|
57
|
-
return { kind: 'success', text: '' };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
61
|
-
notify(session, bootstrap.notify({ type: 'already_initialized' }));
|
|
62
|
-
return { kind: 'success', text: '' };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const confirm = await ctx.userQuestions.ask({
|
|
66
|
-
questions: [{
|
|
67
|
-
id: 'confirm',
|
|
68
|
-
question: bootstrap.notify({ type: 'confirm', projectRoot }),
|
|
69
|
-
detail: projectRoot,
|
|
70
|
-
hideCustomInput: true,
|
|
71
|
-
options: bootstrap.confirmOptions(),
|
|
72
|
-
}],
|
|
73
|
-
});
|
|
74
|
-
const ans = confirm.answers[0];
|
|
75
|
-
const chosen = ans?.selected?.[0] ?? ans?.custom;
|
|
76
|
-
if (chosen !== '确认创建') {
|
|
77
|
-
notify(session, bootstrap.notify({ type: 'cancelled' }));
|
|
78
|
-
return { kind: 'success', text: '' };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
notify(session, bootstrap.notify({ type: 'initializing', projectRoot }));
|
|
82
|
-
const stopActivity = startActivity(session);
|
|
83
|
-
const result = await spawnScript('node', [join(SCRIPTS_DIR, 'init.js'), projectRoot], {
|
|
84
|
-
timeout: 30000,
|
|
85
|
-
stop: stopActivity,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (result.ok) {
|
|
89
|
-
notify(session, bootstrap.notify({ type: 'success' }));
|
|
90
|
-
} else {
|
|
91
|
-
const err = (typeof result.error === 'object' && result.error !== null)
|
|
92
|
-
? (result.error.message || JSON.stringify(result.error))
|
|
93
|
-
: String(result.error || '未知错误');
|
|
94
|
-
notify(session, bootstrap.notify({ type: 'error', err }));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return { kind: 'success', text: '' };
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fancy-bootstrap init — 项目初始化
|
|
3
|
-
*/
|
|
4
|
-
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
5
|
-
import { expandHome, resolvePath, joinPath } from './path-utils.js';
|
|
6
|
-
|
|
7
|
-
const DIRS = [
|
|
8
|
-
'设定/角色', '设定/势力', '设定/物品', '设定/地点',
|
|
9
|
-
'大纲', '正文', '故事档案', '对标', '拆文库',
|
|
10
|
-
'审查报告', '图片', '发布',
|
|
11
|
-
'故事档案/备份', '故事档案/.快照', '故事档案/派生',
|
|
12
|
-
'调试', 'RAG索引',
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
function mkdirp(dir) {
|
|
16
|
-
mkdirSync(dir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function writeJson(filePath, data) {
|
|
20
|
-
writeFileSync(filePath, JSON.stringify(data), 'utf-8');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function nowIso() {
|
|
24
|
-
return new Date().toISOString().replace('.000Z', 'Z');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function main() {
|
|
28
|
-
const projectRootRaw = process.argv[2] ?? '';
|
|
29
|
-
|
|
30
|
-
if (projectRootRaw.includes('..') || /[<>:\"'|?*]/.test(projectRootRaw) || /[\x00-\x1f]/.test(projectRootRaw)) {
|
|
31
|
-
console.log(JSON.stringify({ ok: false, error: { code: 1, code_name: 'EC001', message: '路径不合法' } }));
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const projectRoot = resolvePath(expandHome(projectRootRaw));
|
|
36
|
-
const deployed = joinPath(projectRoot, '.fancy-deployed');
|
|
37
|
-
|
|
38
|
-
if (existsSync(deployed)) {
|
|
39
|
-
console.log(JSON.stringify({ ok: false, error: { code: 2, code_name: 'EC002', message: '项目已存在,不能重复 init' } }));
|
|
40
|
-
process.exit(2);
|
|
41
|
-
}
|
|
42
|
-
if (!existsSync(projectRoot)) {
|
|
43
|
-
console.log(JSON.stringify({ ok: false, error: { code: 18, code_name: 'EC018', message: `目录不存在: ${projectRoot}` } }));
|
|
44
|
-
process.exit(18);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const now = nowIso();
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
for (const d of DIRS) mkdirp(joinPath(projectRoot, d));
|
|
51
|
-
|
|
52
|
-
writeJson(deployed, {
|
|
53
|
-
schema_version: '2.0.0', fancy_skills_version: '2.0.0', deployed_at: now,
|
|
54
|
-
project_root_abs: projectRoot, project_root_rel: '.', current_book: '', books: [], active_book_index: 0,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const archive = joinPath(projectRoot, '故事档案');
|
|
58
|
-
writeJson(joinPath(archive, '_story_state.json'), {
|
|
59
|
-
schema_version: '2.0.0', last_modified: now,
|
|
60
|
-
project: { name: '', created_at: now, last_modified: now, genre: '', target_words: 0, target_chapters: 0, platform: '', platform_other: '' },
|
|
61
|
-
phase: 'uninitialized', phase_history: [{ phase: 'uninitialized', at: now }],
|
|
62
|
-
state_machine: { current_volume: 1, last_chapter_committed: 0, next_chapter_to_write: 1, chapters_total_words: 0, committed_chapters: [], pause_reason: null, paused_at: null },
|
|
63
|
-
author_intent: { style: 'xiaobai', pacing: 'medium', pov: 'third', target_audience: 'general', preferences: [], hard_constraints: [], anti_tropes: [], core_summary: '', core_conflict: '', reader_promise: '' },
|
|
64
|
-
active_book: { project_root: '.', set_at: now },
|
|
65
|
-
});
|
|
66
|
-
writeJson(joinPath(archive, '_timeline.json'), {
|
|
67
|
-
schema_version: '2.0.0', last_modified: now,
|
|
68
|
-
world_clock: { start_date: now, current_date: now, calendar_unit: 'day' },
|
|
69
|
-
events: [], character_status_at_chapter: {}, dead_characters: [], fact_log: [], discoveries: [],
|
|
70
|
-
});
|
|
71
|
-
writeJson(joinPath(archive, '_character_state.json'), { schema_version: '2.0.0', last_modified: now, characters: {}, info_boundary_log: [] });
|
|
72
|
-
writeJson(joinPath(archive, '_foreshadows.json'), { schema_version: '2.0.0', last_modified: now, foreshadows: [], type_distribution: {}, open_count: 0 });
|
|
73
|
-
writeJson(joinPath(archive, '_chapter_index.json'), { schema_version: '2.0.0', last_modified: now, chapters: [] });
|
|
74
|
-
|
|
75
|
-
const intentMd = [
|
|
76
|
-
'# 作者长期意图(Author Intent)',
|
|
77
|
-
'',
|
|
78
|
-
'> 由 fancy-bootstrap 自动生成。',
|
|
79
|
-
'> **注意**:书名/题材/平台/字数等信息在 fancy-topic 阶段填入。',
|
|
80
|
-
'',
|
|
81
|
-
'## 项目信息',
|
|
82
|
-
'> ⚠️ 以下由 fancy-topic 填入:书名 / 题材 / 平台 / 目标字数 / 目标章数',
|
|
83
|
-
'',
|
|
84
|
-
'## 故事核心',
|
|
85
|
-
'> ⚠️ 由 fancy-topic 填入:一句话 / 核心冲突 / 读者承诺',
|
|
86
|
-
'',
|
|
87
|
-
'## 风格基调',
|
|
88
|
-
'- 文风:xiaobai(待 fancy-bible 细化)',
|
|
89
|
-
'- 节奏:medium(待 fancy-bible 细化)',
|
|
90
|
-
'- POV:third(待 fancy-bible 细化)',
|
|
91
|
-
'',
|
|
92
|
-
`## 用户已声明的偏好`,
|
|
93
|
-
`- ${now}:项目目录创建(fancy-bootstrap)`,
|
|
94
|
-
].join('\n');
|
|
95
|
-
writeFileSync(joinPath(archive, '_author_intent.md'), intentMd, 'utf-8');
|
|
96
|
-
|
|
97
|
-
} catch (e) {
|
|
98
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
99
|
-
console.log(JSON.stringify({ ok: false, error: { code: 17, code_name: 'EC017', message: `初始化失败: ${errMsg}` } }));
|
|
100
|
-
process.exit(17);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
console.log(JSON.stringify({
|
|
104
|
-
ok: true, operation: 'init', project_root: projectRoot, phase: 'uninitialized',
|
|
105
|
-
directories_created: DIRS.length,
|
|
106
|
-
files_created: ['.fancy-deployed', '故事档案/_story_state.json', '故事档案/_timeline.json', '故事档案/_character_state.json', '故事档案/_foreshadows.json', '故事档案/_chapter_index.json', '故事档案/_author_intent.md'],
|
|
107
|
-
note: '项目信息由 fancy-topic 填入',
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
main();
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* path-utils.js — init.js 专用路径工具
|
|
3
|
-
* restore from git
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync } from 'fs';
|
|
6
|
-
import { homedir } from 'os';
|
|
7
|
-
import { resolve, join } from 'path';
|
|
8
|
-
|
|
9
|
-
export function expandHome(p) {
|
|
10
|
-
if (p === '~' || p.startsWith('~/')) {
|
|
11
|
-
return join(homedir(), p.slice(1));
|
|
12
|
-
}
|
|
13
|
-
return p;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function resolvePath(p) {
|
|
17
|
-
return resolve(p);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function joinPath(base, ...parts) {
|
|
21
|
-
return join(base, ...parts);
|
|
22
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bootstrap 模板 — 仅 fancy-bootstrap 使用
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const bootstrap = {
|
|
6
|
-
askPath(question = '📂 请输入项目根路径 ( 或者输入 . 代表当前路径 )') {
|
|
7
|
-
return { question };
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
notify({ type, projectRoot, err }) {
|
|
11
|
-
switch (type) {
|
|
12
|
-
case 'no_path':
|
|
13
|
-
return '⚠️ 未提供路径,已取消';
|
|
14
|
-
case 'using_cwd':
|
|
15
|
-
return `📂 使用当前目录:${projectRoot}`;
|
|
16
|
-
case 'invalid_path':
|
|
17
|
-
return '❌ 路径不合法(不能含空格、.. 或特殊字符)';
|
|
18
|
-
case 'already_initialized':
|
|
19
|
-
return '⚠️ 该目录已初始化(.fancy-deployed 已存在)';
|
|
20
|
-
case 'confirm':
|
|
21
|
-
return `❓ 确认将以下路径作为项目根?\n${projectRoot}`;
|
|
22
|
-
case 'cancelled':
|
|
23
|
-
return '🚫 已取消';
|
|
24
|
-
case 'initializing':
|
|
25
|
-
return `⏳ 正在初始化 ${projectRoot}...`;
|
|
26
|
-
case 'success':
|
|
27
|
-
return '✅ 项目初始化完成!\n下一步:运行 /fancy-scan 扫榜';
|
|
28
|
-
case 'error':
|
|
29
|
-
return '❌ 初始化失败:' + (typeof err === 'function' ? err() : err);
|
|
30
|
-
default:
|
|
31
|
-
return String(type);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
confirmOptions() {
|
|
36
|
-
return [
|
|
37
|
-
{ label: '确认创建' },
|
|
38
|
-
{ label: '取消' },
|
|
39
|
-
];
|
|
40
|
-
},
|
|
41
|
-
};
|