@cloud411716/fancy-webnovel 0.1.36 → 0.1.38
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/index.js
CHANGED
|
@@ -1,241 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* 所有消息模板 → scripts/templates.js
|
|
6
|
-
* 所有基础设施 → scripts/infra.js
|
|
7
|
-
*/
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { dirname, join } from 'path';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
-
import { notify, llmFollowup, spawnScript } from './infra.js';
|
|
12
|
-
import { scan, bootstrap, PLATFORM_TABLE, platformLabel } from './templates.js';
|
|
13
|
-
|
|
14
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const SCRIPTS_DIR = join(__dirname, 'plugins', 'fancy-bootstrap', 'scripts');
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// 路径工具
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
function isValidPath(p) {
|
|
22
|
-
return !p.includes('..') && !/[<>:"'|?*[\x00-\x1f]/.test(p) && !/\s/.test(p);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// fancy-bootstrap
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
async function applyBootstrap(ctx) {
|
|
30
|
-
ctx.commands.register({
|
|
31
|
-
name: 'fancy-bootstrap',
|
|
32
|
-
description: '📁 初始化项目根 ( usage: /fancy-bootstrap [项目根] )',
|
|
33
|
-
handler: async (invocation) => {
|
|
34
|
-
const session = invocation.agent.session;
|
|
35
|
-
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
36
|
-
|
|
37
|
-
let projectRoot = rawInput;
|
|
38
|
-
|
|
39
|
-
// 无参数 → 弹窗
|
|
40
|
-
if (!projectRoot) {
|
|
41
|
-
const result = await ctx.userQuestions.ask({
|
|
42
|
-
questions: [bootstrap.askPath()],
|
|
43
|
-
});
|
|
44
|
-
projectRoot = result.answers[0]?.custom?.trim() || '';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!projectRoot) {
|
|
48
|
-
notify(session, bootstrap.notify({ type: 'no_path' }));
|
|
49
|
-
return { kind: 'success', text: '' };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (projectRoot === '.') {
|
|
53
|
-
projectRoot = process.cwd();
|
|
54
|
-
notify(session, bootstrap.notify({ type: 'using_cwd', projectRoot }));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!isValidPath(projectRoot)) {
|
|
58
|
-
notify(session, bootstrap.notify({ type: 'invalid_path' }));
|
|
59
|
-
return { kind: 'success', text: '' };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
63
|
-
notify(session, bootstrap.notify({ type: 'already_initialized' }));
|
|
64
|
-
return { kind: 'success', text: '' };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const confirm = await ctx.userQuestions.ask({
|
|
68
|
-
questions: [{
|
|
69
|
-
id: 'confirm',
|
|
70
|
-
question: bootstrap.notify({ type: 'confirm', projectRoot }),
|
|
71
|
-
detail: projectRoot,
|
|
72
|
-
hideCustomInput: true,
|
|
73
|
-
options: bootstrap.confirmOptions(),
|
|
74
|
-
}],
|
|
75
|
-
});
|
|
76
|
-
const ans = confirm.answers[0];
|
|
77
|
-
const chosen = ans?.selected?.[0] ?? ans?.custom;
|
|
78
|
-
if (chosen !== '确认创建') {
|
|
79
|
-
notify(session, bootstrap.notify({ type: 'cancelled' }));
|
|
80
|
-
return { kind: 'success', text: '' };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
notify(session, bootstrap.notify({ type: 'initializing', projectRoot }));
|
|
84
|
-
const result = await spawnScript('node', [join(SCRIPTS_DIR, 'init.js'), projectRoot], { timeout: 30000 });
|
|
85
|
-
|
|
86
|
-
if (result.ok) {
|
|
87
|
-
notify(session, bootstrap.notify({ type: 'success' }));
|
|
88
|
-
} else {
|
|
89
|
-
const err = (typeof result.error === 'object' && result.error !== null)
|
|
90
|
-
? (result.error.message || JSON.stringify(result.error))
|
|
91
|
-
: String(result.error || '未知错误');
|
|
92
|
-
notify(session, bootstrap.notify({ type: 'error', err }));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { kind: 'success', text: '' };
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// fancy-scan
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
async function applyScan(ctx) {
|
|
105
|
-
ctx.commands.register({
|
|
106
|
-
name: 'fancy-scan',
|
|
107
|
-
description: '📊 扫榜分析 ( usage: /fancy-scan [平台] [篇幅] )',
|
|
108
|
-
handler: async (invocation) => {
|
|
109
|
-
const session = invocation.agent.session;
|
|
110
|
-
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
111
|
-
|
|
112
|
-
// 获取项目根
|
|
113
|
-
let projectRoot = rawInput ? rawInput.split(/\s+/)[0] : '';
|
|
114
|
-
if (!projectRoot) projectRoot = process.cwd();
|
|
115
|
-
|
|
116
|
-
if (!existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
117
|
-
notify(session, scan.notify({ type: 'not_initialized' }));
|
|
118
|
-
return { kind: 'success', text: '' };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// 解析参数
|
|
122
|
-
let platform = '', length = '';
|
|
123
|
-
if (rawInput) {
|
|
124
|
-
const parts = rawInput.split(/\s+/);
|
|
125
|
-
platform = parts[0] || '';
|
|
126
|
-
length = parts[1] || '';
|
|
127
|
-
const numMatch = platform.match(/^(\d+)$/);
|
|
128
|
-
if (numMatch) {
|
|
129
|
-
const row = PLATFORM_TABLE.find(r => r[0] === numMatch[1]);
|
|
130
|
-
if (row) {
|
|
131
|
-
platform = row[1];
|
|
132
|
-
if (platform === 'zhihu' || platform === 'qimao') {
|
|
133
|
-
length = parts[1] || 'long';
|
|
134
|
-
} else {
|
|
135
|
-
length = row[2] === '长篇' ? 'long' : (row[2] === '短篇' ? 'short' : 'long');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 参数不全 → 弹窗选择
|
|
142
|
-
if (!platform || !length || !PLATFORM_TABLE.find(r => r[1] === platform)) {
|
|
143
|
-
notify(session, scan.platformList());
|
|
144
|
-
|
|
145
|
-
const choice = await ctx.userQuestions.ask({
|
|
146
|
-
questions: [scan.askPlatform()],
|
|
147
|
-
});
|
|
148
|
-
const answer = choice.answers[0]?.custom?.trim();
|
|
149
|
-
if (!answer) {
|
|
150
|
-
notify(session, scan.notify({ type: 'cancelled_with_hint' }));
|
|
151
|
-
return { kind: 'success', text: '' };
|
|
152
|
-
}
|
|
153
|
-
const numMatch = answer.match(/^(\d+)$/);
|
|
154
|
-
const row = numMatch ? PLATFORM_TABLE.find(r => r[0] === numMatch[1]) : null;
|
|
155
|
-
if (!row) {
|
|
156
|
-
notify(session, scan.notify({ type: 'invalid_choice' }));
|
|
157
|
-
return { kind: 'success', text: '' };
|
|
158
|
-
}
|
|
159
|
-
platform = row[1];
|
|
160
|
-
length = (platform === 'zhihu' || platform === 'qimao')
|
|
161
|
-
? null
|
|
162
|
-
: (row[2] === '长篇' ? 'long' : 'short');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 知乎/七猫二次选择篇幅
|
|
166
|
-
if (platform === 'zhihu' || platform === 'qimao') {
|
|
167
|
-
if (!length || !['long', 'short'].includes(length)) {
|
|
168
|
-
const lenResult = await ctx.userQuestions.ask({
|
|
169
|
-
questions: [scan.askLength(platform)],
|
|
170
|
-
});
|
|
171
|
-
const lenAnswer = lenResult.answers[0]?.selected?.[0] ?? lenResult.answers[0]?.custom;
|
|
172
|
-
if (!lenAnswer || lenAnswer === '取消') {
|
|
173
|
-
notify(session, scan.notify({ type: 'cancelled' }));
|
|
174
|
-
return { kind: 'success', text: '' };
|
|
175
|
-
}
|
|
176
|
-
length = (lenAnswer === '长篇') ? 'long' : 'short';
|
|
177
|
-
}
|
|
178
|
-
} else if (!length) {
|
|
179
|
-
length = 'long';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
notify(session, scan.notify({ type: 'collecting', platform, length }));
|
|
183
|
-
|
|
184
|
-
const scriptResult = await spawnScript(
|
|
185
|
-
'node',
|
|
186
|
-
[join(SCRIPTS_DIR, '..', 'fancy-scan', 'scripts', 'run-scan.js'),
|
|
187
|
-
'--project-root', projectRoot, '--platform', platform, '--length', length],
|
|
188
|
-
{ timeout: 120000 }
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
if (!scriptResult.ok) {
|
|
192
|
-
const err = (typeof scriptResult.error === 'object' && scriptResult.error !== null)
|
|
193
|
-
? (scriptResult.error.message || JSON.stringify(scriptResult.error))
|
|
194
|
-
: String(scriptResult.error || '未知错误');
|
|
195
|
-
notify(session, scan.notify({ type: 'failed', err }));
|
|
196
|
-
return { kind: 'success', text: '' };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 解析 receipt
|
|
200
|
-
let receipt;
|
|
201
|
-
try { receipt = JSON.parse(scriptResult.output); } catch (_) {
|
|
202
|
-
notify(session, scan.notify({ type: 'parse_error' }));
|
|
203
|
-
return { kind: 'success', text: '' };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const files = receipt.scan_files || [];
|
|
207
|
-
const topicFile = receipt.topic_decision || '';
|
|
208
|
-
|
|
209
|
-
// 通知用户 + 触发 LLM 分析
|
|
210
|
-
notify(session, scan.notify({ type: 'handover', platform, length, files, topicFile, projectRoot }));
|
|
211
|
-
|
|
212
|
-
const prompt = scan.llmAnalysisPrompt({
|
|
213
|
-
platform, length,
|
|
214
|
-
date: receipt.date || '',
|
|
215
|
-
files,
|
|
216
|
-
topicFile,
|
|
217
|
-
projectRoot,
|
|
218
|
-
});
|
|
219
|
-
llmFollowup(invocation, prompt);
|
|
220
|
-
|
|
221
|
-
return { kind: 'success', text: '' };
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// 导出
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
1
|
+
// @cloud411716/fancy-webnovel unified entry point
|
|
2
|
+
import { apply as applyBootstrap } from './plugins/fancy-bootstrap/index.js';
|
|
3
|
+
import { apply as applyScan } from './plugins/fancy-scan/index.js';
|
|
229
4
|
|
|
230
5
|
export const inject = ['commands', 'userQuestions'];
|
|
231
6
|
|
|
232
7
|
export async function apply(ctx) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
await applyScan(ctx);
|
|
236
|
-
} catch (e) {
|
|
237
|
-
const { writeFileSync } = await import('fs');
|
|
238
|
-
writeFileSync('/tmp/dsh-apply-error.txt', String(e) + '\n' + e.stack, 'utf-8');
|
|
239
|
-
throw e;
|
|
240
|
-
}
|
|
8
|
+
await applyBootstrap(ctx);
|
|
9
|
+
await applyScan(ctx);
|
|
241
10
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
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 } 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 result = await spawnScript('node', [join(SCRIPTS_DIR, 'init.js'), projectRoot], { timeout: 30000 });
|
|
83
|
+
|
|
84
|
+
if (result.ok) {
|
|
85
|
+
notify(session, bootstrap.notify({ type: 'success' }));
|
|
86
|
+
} else {
|
|
87
|
+
const err = (typeof result.error === 'object' && result.error !== null)
|
|
88
|
+
? (result.error.message || JSON.stringify(result.error))
|
|
89
|
+
: String(result.error || '未知错误');
|
|
90
|
+
notify(session, bootstrap.notify({ type: 'error', err }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { kind: 'success', text: '' };
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bootstrap 模板 — 仅 fancy-bootstrap 使用
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const bootstrap = {
|
|
6
|
+
askPath(question = '📂 请输入项目根路径 ( 或者输入 . 代表当前路径 )') {
|
|
7
|
+
return { question };
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
notify({ type, projectRoot }) {
|
|
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 (err) => '❌ 初始化失败:' + err;
|
|
30
|
+
default:
|
|
31
|
+
return String(type);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
confirmOptions() {
|
|
36
|
+
return [
|
|
37
|
+
{ label: '确认创建', description: '开始在此目录初始化项目' },
|
|
38
|
+
{ label: '取消', description: '取消本次操作' },
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fancy-scan plugin entry
|
|
3
|
+
*/
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { notify, llmFollowup, spawnScript } from '../../infra.js';
|
|
8
|
+
import { scan, PLATFORM_TABLE, platformLabel } from './templates.js';
|
|
9
|
+
|
|
10
|
+
export const inject = ['commands', 'userQuestions'];
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const SCAN_SCRIPTS_DIR = join(__dirname, 'scripts');
|
|
14
|
+
|
|
15
|
+
export async function apply(ctx) {
|
|
16
|
+
ctx.commands.register({
|
|
17
|
+
name: 'fancy-scan',
|
|
18
|
+
description: '📊 扫榜分析 ( usage: /fancy-scan [平台] [篇幅] )',
|
|
19
|
+
handler: async (invocation) => {
|
|
20
|
+
const session = invocation.agent.session;
|
|
21
|
+
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
22
|
+
|
|
23
|
+
// 获取项目根
|
|
24
|
+
let projectRoot = rawInput ? rawInput.split(/\s+/)[0] : '';
|
|
25
|
+
if (!projectRoot) projectRoot = process.cwd();
|
|
26
|
+
|
|
27
|
+
if (!existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
28
|
+
notify(session, scan.notify({ type: 'not_initialized' }));
|
|
29
|
+
return { kind: 'success', text: '' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 解析参数
|
|
33
|
+
let platform = '', length = '';
|
|
34
|
+
if (rawInput) {
|
|
35
|
+
const parts = rawInput.split(/\s+/);
|
|
36
|
+
platform = parts[0] || '';
|
|
37
|
+
length = parts[1] || '';
|
|
38
|
+
const numMatch = platform.match(/^(\d+)$/);
|
|
39
|
+
if (numMatch) {
|
|
40
|
+
const row = PLATFORM_TABLE.find(r => r[0] === numMatch[1]);
|
|
41
|
+
if (row) {
|
|
42
|
+
platform = row[1];
|
|
43
|
+
if (platform === 'zhihu' || platform === 'qimao') {
|
|
44
|
+
length = parts[1] || 'long';
|
|
45
|
+
} else {
|
|
46
|
+
length = row[2] === '长篇' ? 'long' : (row[2] === '短篇' ? 'short' : 'long');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 参数不全 → 弹窗选择
|
|
53
|
+
if (!platform || !length || !PLATFORM_TABLE.find(r => r[1] === platform)) {
|
|
54
|
+
notify(session, scan.platformList());
|
|
55
|
+
|
|
56
|
+
const choice = await ctx.userQuestions.ask({
|
|
57
|
+
questions: [scan.askPlatform()],
|
|
58
|
+
});
|
|
59
|
+
const answer = choice.answers[0]?.custom?.trim();
|
|
60
|
+
if (!answer) {
|
|
61
|
+
notify(session, scan.notify({ type: 'cancelled_with_hint' }));
|
|
62
|
+
return { kind: 'success', text: '' };
|
|
63
|
+
}
|
|
64
|
+
const numMatch = answer.match(/^(\d+)$/);
|
|
65
|
+
const row = numMatch ? PLATFORM_TABLE.find(r => r[0] === numMatch[1]) : null;
|
|
66
|
+
if (!row) {
|
|
67
|
+
notify(session, scan.notify({ type: 'invalid_choice' }));
|
|
68
|
+
return { kind: 'success', text: '' };
|
|
69
|
+
}
|
|
70
|
+
platform = row[1];
|
|
71
|
+
length = (platform === 'zhihu' || platform === 'qimao')
|
|
72
|
+
? null
|
|
73
|
+
: (row[2] === '长篇' ? 'long' : 'short');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 知乎/七猫二次选择篇幅
|
|
77
|
+
if (platform === 'zhihu' || platform === 'qimao') {
|
|
78
|
+
if (!length || !['long', 'short'].includes(length)) {
|
|
79
|
+
const lenResult = await ctx.userQuestions.ask({
|
|
80
|
+
questions: [scan.askLength(platform)],
|
|
81
|
+
});
|
|
82
|
+
const lenAnswer = lenResult.answers[0]?.selected?.[0] ?? lenResult.answers[0]?.custom;
|
|
83
|
+
if (!lenAnswer || lenAnswer === '取消') {
|
|
84
|
+
notify(session, scan.notify({ type: 'cancelled' }));
|
|
85
|
+
return { kind: 'success', text: '' };
|
|
86
|
+
}
|
|
87
|
+
length = (lenAnswer === '长篇') ? 'long' : 'short';
|
|
88
|
+
}
|
|
89
|
+
} else if (!length) {
|
|
90
|
+
length = 'long';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
notify(session, scan.notify({ type: 'collecting', platform, length }));
|
|
94
|
+
|
|
95
|
+
const scriptResult = await spawnScript(
|
|
96
|
+
'node',
|
|
97
|
+
[join(SCAN_SCRIPTS_DIR, 'run-scan.js'),
|
|
98
|
+
'--project-root', projectRoot, '--platform', platform, '--length', length],
|
|
99
|
+
{ timeout: 120000 }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!scriptResult.ok) {
|
|
103
|
+
const err = (typeof scriptResult.error === 'object' && scriptResult.error !== null)
|
|
104
|
+
? (scriptResult.error.message || JSON.stringify(scriptResult.error))
|
|
105
|
+
: String(scriptResult.error || '未知错误');
|
|
106
|
+
notify(session, scan.notify({ type: 'failed', err }));
|
|
107
|
+
return { kind: 'success', text: '' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 解析 receipt
|
|
111
|
+
let receipt;
|
|
112
|
+
try { receipt = JSON.parse(scriptResult.output); } catch (_) {
|
|
113
|
+
notify(session, scan.notify({ type: 'parse_error' }));
|
|
114
|
+
return { kind: 'success', text: '' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const files = receipt.scan_files || [];
|
|
118
|
+
const topicFile = receipt.topic_decision || '';
|
|
119
|
+
|
|
120
|
+
// 通知用户 + 触发 LLM 分析
|
|
121
|
+
notify(session, scan.notify({ type: 'handover', platform, length, files, topicFile, projectRoot }));
|
|
122
|
+
|
|
123
|
+
const prompt = scan.llmAnalysisPrompt({
|
|
124
|
+
platform, length,
|
|
125
|
+
date: receipt.date || '',
|
|
126
|
+
files,
|
|
127
|
+
topicFile,
|
|
128
|
+
projectRoot,
|
|
129
|
+
});
|
|
130
|
+
llmFollowup(invocation, prompt);
|
|
131
|
+
|
|
132
|
+
return { kind: 'success', text: '' };
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 包括:
|
|
5
|
-
* bootstrap 提示消息
|
|
6
|
-
* scan 提示消息
|
|
7
|
-
* LLM 分析 prompt 模板
|
|
8
|
-
*
|
|
9
|
-
* 原则:所有展示给用户的文字、发送给 LLM 的 prompt
|
|
10
|
-
* 均从此文件导出,插件主代码只做逻辑和拼装。
|
|
2
|
+
* scan 模板 — 仅 fancy-scan 使用
|
|
11
3
|
*/
|
|
12
4
|
|
|
13
5
|
// ---------------------------------------------------------------------------
|
|
@@ -29,49 +21,7 @@ export function platformLabel(platform) {
|
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
// ---------------------------------------------------------------------------
|
|
32
|
-
//
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
export const bootstrap = {
|
|
36
|
-
askPath(question = '📂 请输入项目根路径 ( 或者输入 . 代表当前路径 )') {
|
|
37
|
-
return { question };
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
notify({ type, projectRoot }) {
|
|
41
|
-
switch (type) {
|
|
42
|
-
case 'no_path':
|
|
43
|
-
return '⚠️ 未提供路径,已取消';
|
|
44
|
-
case 'using_cwd':
|
|
45
|
-
return `📂 使用当前目录:${projectRoot}`;
|
|
46
|
-
case 'invalid_path':
|
|
47
|
-
return '❌ 路径不合法(不能含空格、.. 或特殊字符)';
|
|
48
|
-
case 'already_initialized':
|
|
49
|
-
return '⚠️ 该目录已初始化(.fancy-deployed 已存在)';
|
|
50
|
-
case 'confirm':
|
|
51
|
-
return `❓ 确认将以下路径作为项目根?\n${projectRoot}`;
|
|
52
|
-
case 'cancelled':
|
|
53
|
-
return '🚫 已取消';
|
|
54
|
-
case 'initializing':
|
|
55
|
-
return `⏳ 正在初始化 ${projectRoot}...`;
|
|
56
|
-
case 'success':
|
|
57
|
-
return '✅ 项目初始化完成!\n下一步:运行 /fancy-scan 扫榜';
|
|
58
|
-
case 'error':
|
|
59
|
-
return (err) => '❌ 初始化失败:' + err;
|
|
60
|
-
default:
|
|
61
|
-
return String(type);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
confirmOptions() {
|
|
66
|
-
return [
|
|
67
|
-
{ label: '确认创建', description: '开始在此目录初始化项目' },
|
|
68
|
-
{ label: '取消', description: '取消本次操作' },
|
|
69
|
-
];
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// fancy-scan 模板
|
|
24
|
+
// 模板
|
|
75
25
|
// ---------------------------------------------------------------------------
|
|
76
26
|
|
|
77
27
|
export const scan = {
|
|
@@ -91,7 +41,7 @@ export const scan = {
|
|
|
91
41
|
|
|
92
42
|
askLength(platform) {
|
|
93
43
|
return {
|
|
94
|
-
question: `${platformLabel(platform)}
|
|
44
|
+
question: `${platformLabel(platform)} 同时支持长篇和短篇,请选择:`,
|
|
95
45
|
hideCustomInput: true,
|
|
96
46
|
options: [
|
|
97
47
|
{ label: '长篇', description: '' },
|
|
@@ -143,16 +93,6 @@ export const scan = {
|
|
|
143
93
|
}
|
|
144
94
|
},
|
|
145
95
|
|
|
146
|
-
/**
|
|
147
|
-
* 发送给 LLM 的分析 prompt 模板
|
|
148
|
-
* @param {object} params
|
|
149
|
-
* @param params.platform
|
|
150
|
-
* @param params.length
|
|
151
|
-
* @param params.date
|
|
152
|
-
* @param params.files string[] — 完整绝对路径列表
|
|
153
|
-
* @param params.topicFile string — topic_decision 文件完整路径
|
|
154
|
-
* @param params.projectRoot string
|
|
155
|
-
*/
|
|
156
96
|
llmAnalysisPrompt({ platform, length, date, files, topicFile, projectRoot }) {
|
|
157
97
|
const pLabel = platformLabel(platform);
|
|
158
98
|
const lenStr = length === 'long' ? '长篇' : '短篇';
|