@cloud411716/fancy-webnovel 1.0.46 → 2.2.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 +183 -0
- package/cordis.patch.yml +0 -14
- package/index.js +21 -37
- package/lib/chinese-num.js +111 -0
- package/lib/deployed-state.js +141 -0
- package/lib/error-codes.js +104 -0
- package/lib/infra.js +569 -0
- package/lib/llm-wiki.js +109 -0
- package/lib/safe-rm.js +94 -0
- package/lib/scraper-utils.js +165 -0
- package/package.json +37 -5
- package/plugins/fancy-analyze/command-input.js +65 -0
- package/plugins/fancy-analyze/index.js +973 -0
- package/plugins/fancy-analyze/schema-analyze.md +379 -0
- package/plugins/fancy-analyze/schema-write.md +178 -0
- package/plugins/fancy-analyze/scripts/split_source.js +292 -0
- package/plugins/fancy-analyze/summary-progress.js +431 -0
- package/plugins/fancy-analyze/templates.js +881 -0
- package/plugins/fancy-bootstrap/index.js +168 -0
- package/plugins/fancy-bootstrap/scripts/init.js +143 -0
- package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
- package/plugins/fancy-bootstrap/templates.js +54 -0
- package/plugins/fancy-import/import-core.js +1118 -0
- package/plugins/fancy-import/import-driver.js +581 -0
- package/plugins/fancy-import/index.js +730 -0
- package/plugins/fancy-import/prompts/01-import-prompt.md +106 -0
- package/plugins/fancy-import/templates.js +49 -0
- package/plugins/fancy-scan/index.js +319 -0
- package/plugins/fancy-scan/scripts/run-scan.js +520 -0
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +794 -0
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +339 -0
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +611 -0
- package/plugins/fancy-scan/templates.js +295 -0
- package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
- package/plugins/fancy-story-builder/deslop-driver.js +821 -0
- package/plugins/fancy-story-builder/flesh-output.js +155 -0
- package/plugins/fancy-story-builder/index.js +3152 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +553 -0
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +540 -0
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +504 -0
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +188 -0
- package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
- package/plugins/fancy-story-builder/purpose-template.md +107 -0
- package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -0
- package/plugins/fancy-story-builder/references/glossary.md +585 -0
- package/plugins/fancy-story-builder/references/index.md +490 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +163 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +239 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +181 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +177 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +208 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +142 -0
- package/plugins/fancy-story-builder/rhythm-output.js +140 -0
- package/plugins/fancy-story-builder/schema-setting.md +324 -0
- package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
- package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +413 -0
- package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
- package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +1189 -0
- package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
- package/plugins/fancy-story-builder/scripts/graph-utils.js +1385 -0
- package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
- package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
- package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1216 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +377 -0
- package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1832 -0
- package/plugins/fancy-story-builder/scripts/stage-batches.js +1371 -0
- package/plugins/fancy-story-builder/scripts/stage-contract.js +417 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
- package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
- package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
- package/plugins/fancy-story-builder/skeleton-output.js +199 -0
- package/plugins/fancy-story-builder/stage-driver.js +1049 -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 +204 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
- 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 +66 -0
- 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 +76 -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 +72 -0
- 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 +37 -0
- 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 +53 -0
- 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 +40 -0
- 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 +79 -0
- 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 +132 -0
- 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 +133 -0
- 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 +160 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
- 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 +134 -0
- package/plugins/fancy-story-builder/templates.js +1248 -0
- package/plugins/fancy-story-builder/wiki-references.js +204 -0
- package/plugins/fancy-story-builder/writing-driver.js +1061 -0
- package/plugins/fancy-topic/index.js +704 -0
- package/plugins/fancy-topic/scripts/select-topic.js +274 -0
- package/plugins/fancy-topic/templates.js +373 -0
- package/commands/bootstrap/index.js +0 -164
- package/commands/bootstrap/init-project.js +0 -121
- package/commands/scan/index.js +0 -283
- package/commands/scan/platforms/fanqie.js +0 -146
- package/commands/scan/platforms/jinjiang.js +0 -143
- package/commands/scan/platforms/qidian.js +0 -102
- package/commands/scan/platforms/qimao.js +0 -127
- package/commands/scan/scraper.js +0 -195
- package/infra.js +0 -160
package/commands/scan/scraper.js
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scraper.js — 扫榜编排层
|
|
3
|
-
*
|
|
4
|
-
* 职责:
|
|
5
|
-
* 1. 对每个平台/榜单调用对应的 platform scraper
|
|
6
|
-
* 2. 重试逻辑(每个榜单最多 3 次,间隔 5 秒)
|
|
7
|
-
* 3. 通过 DSH 事件实时汇报进度
|
|
8
|
-
* 4. 生成 Markdown 文件并写入项目目录
|
|
9
|
-
* 5. 所有操作纳入 ctx.effect() 生命周期,Ctrl+C 可终止
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { mkdirSync } from 'fs';
|
|
13
|
-
import { join } from 'path';
|
|
14
|
-
import { sleep, scanProgress, writeFile } from '../../infra.js';
|
|
15
|
-
import * as qidian from './platforms/qidian.js';
|
|
16
|
-
import * as fanqie from './platforms/fanqie.js';
|
|
17
|
-
import * as jinjiang from './platforms/jinjiang.js';
|
|
18
|
-
import * as qimao from './platforms/qimao.js';
|
|
19
|
-
|
|
20
|
-
const SCRAPER_TIMEOUT = 30 * 60 * 1000; // 30 分钟 per rank
|
|
21
|
-
const MAX_RETRIES = 3;
|
|
22
|
-
const RETRY_DELAY = 5000;
|
|
23
|
-
|
|
24
|
-
// Platform registry
|
|
25
|
-
const PLATFORMS = { qidian, fanqie, jinjiang, qimao };
|
|
26
|
-
|
|
27
|
-
function todayStr() {
|
|
28
|
-
const d = new Date();
|
|
29
|
-
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Run scans for a list of { platform, channel, type, label } entries.
|
|
34
|
-
* Uses ctx.effect() for lifecycle management and ctx.events for progress.
|
|
35
|
-
*
|
|
36
|
-
* @param {object} ctx - Cordis plugin context
|
|
37
|
-
* @param {string} projectRoot
|
|
38
|
-
* @param {Array} ranks - array of { platform, channel, type, label }
|
|
39
|
-
* @param {AbortSignal} signal
|
|
40
|
-
* @returns {Promise<{ totalBooks: number, totalFiles: number, failedRanks: Array, lastReceipt: object|null }>}
|
|
41
|
-
*/
|
|
42
|
-
export async function runScans(ctx, projectRoot, ranks, signal) {
|
|
43
|
-
const scanDir = join(projectRoot, '扫榜数据');
|
|
44
|
-
mkdirSync(scanDir, { recursive: true });
|
|
45
|
-
|
|
46
|
-
const today = todayStr();
|
|
47
|
-
let totalBooks = 0;
|
|
48
|
-
let totalFiles = 0;
|
|
49
|
-
const failedRanks = [];
|
|
50
|
-
let lastReceipt = null;
|
|
51
|
-
|
|
52
|
-
for (const rank of ranks) {
|
|
53
|
-
if (signal.aborted) {
|
|
54
|
-
scanProgress(ctx, 'rank-done', {
|
|
55
|
-
platform: rank.platform, channel: rank.channel, type: rank.type,
|
|
56
|
-
ok: false, written: 0, failed: 0, errorMsg: 'aborted by user',
|
|
57
|
-
});
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const platform = PLATFORMS[rank.platform];
|
|
62
|
-
if (!platform) {
|
|
63
|
-
failedRanks.push({ rank, err: `unknown platform: ${rank.platform}` });
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
scanProgress(ctx, 'start', {
|
|
68
|
-
platform: rank.platform,
|
|
69
|
-
channel: rank.channel,
|
|
70
|
-
type: rank.type,
|
|
71
|
-
label: rank.label,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
let result;
|
|
75
|
-
let attempt = 0;
|
|
76
|
-
|
|
77
|
-
while (attempt < MAX_RETRIES) {
|
|
78
|
-
if (signal.aborted) break;
|
|
79
|
-
attempt++;
|
|
80
|
-
try {
|
|
81
|
-
result = await withTimeout(
|
|
82
|
-
platform.scrapeRank(rank.id, ctx, signal),
|
|
83
|
-
ctx,
|
|
84
|
-
SCRAPER_TIMEOUT,
|
|
85
|
-
);
|
|
86
|
-
if (result.ok) break;
|
|
87
|
-
} catch (err) {
|
|
88
|
-
result = { ok: false, books: [], error: err.message };
|
|
89
|
-
}
|
|
90
|
-
if (attempt < MAX_RETRIES && !signal.aborted) {
|
|
91
|
-
await sleep(ctx, RETRY_DELAY);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (signal.aborted) {
|
|
96
|
-
failedRanks.push({ rank, err: 'aborted' });
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (!result.ok || !result.books.length) {
|
|
101
|
-
failedRanks.push({ rank, err: result.error ?? 'no books' });
|
|
102
|
-
scanProgress(ctx, 'rank-done', {
|
|
103
|
-
platform: rank.platform, channel: rank.channel, type: rank.type,
|
|
104
|
-
ok: false, written: 0, failed: result.books?.length ?? 0,
|
|
105
|
-
errorMsg: result.error,
|
|
106
|
-
});
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Write markdown file
|
|
111
|
-
let filepath;
|
|
112
|
-
try {
|
|
113
|
-
const content = renderMarkdown(platform.label, rank.label, result.books);
|
|
114
|
-
const filename = `${platform.label}${rank.label}_${today}.md`;
|
|
115
|
-
filepath = join(scanDir, filename);
|
|
116
|
-
await writeFile(filepath, content);
|
|
117
|
-
totalFiles++;
|
|
118
|
-
totalBooks += result.books.length;
|
|
119
|
-
lastReceipt = {
|
|
120
|
-
platform: rank.platform,
|
|
121
|
-
date: today,
|
|
122
|
-
scan_files: [filepath],
|
|
123
|
-
topic_decision: join(scanDir, `topic_decision_${today}.md`),
|
|
124
|
-
};
|
|
125
|
-
} catch (err) {
|
|
126
|
-
failedRanks.push({ rank, err: `write failed: ${err.message}` });
|
|
127
|
-
scanProgress(ctx, 'rank-done', {
|
|
128
|
-
platform: rank.platform, channel: rank.channel, type: rank.type,
|
|
129
|
-
ok: false, written: 0, failed: result.books.length,
|
|
130
|
-
errorMsg: `write failed: ${err.message}`,
|
|
131
|
-
});
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
scanProgress(ctx, 'rank-done', {
|
|
136
|
-
platform: rank.platform, channel: rank.channel, type: rank.type,
|
|
137
|
-
ok: true, written: result.books.length, failed: 0,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
scanProgress(ctx, 'done', {
|
|
142
|
-
platform: ranks[0]?.platform ?? '',
|
|
143
|
-
totalBooks,
|
|
144
|
-
totalFiles,
|
|
145
|
-
ok: failedRanks.length < ranks.length,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return { totalBooks, totalFiles, failedRanks, lastReceipt };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Wrap a promise with a ctx.timeout deadline.
|
|
153
|
-
* ctx.timeout(fn, ms) returns a synchronous dispose function that is safe
|
|
154
|
-
* to call multiple times (idempotent), so this is guaranteed leak-free.
|
|
155
|
-
*/
|
|
156
|
-
async function withTimeout(promise, ctx, ms) {
|
|
157
|
-
let dispose;
|
|
158
|
-
const timeout = new Promise((_, reject) => {
|
|
159
|
-
dispose = ctx.timeout(() => reject(new Error(`timeout after ${ms}ms`)), ms);
|
|
160
|
-
});
|
|
161
|
-
try {
|
|
162
|
-
return await Promise.race([promise, timeout]);
|
|
163
|
-
} finally {
|
|
164
|
-
try { dispose?.(); } catch (_) {}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Render books as Markdown.
|
|
170
|
-
*/
|
|
171
|
-
function renderMarkdown(platformLabel, rankLabel, books) {
|
|
172
|
-
const lines = [
|
|
173
|
-
`# ${platformLabel} · ${rankLabel}`,
|
|
174
|
-
'',
|
|
175
|
-
`> 自动采集于 ${new Date().toLocaleString('zh-CN')}`,
|
|
176
|
-
'',
|
|
177
|
-
'| 排名 | 书名 | 作者 | 类别 | 字数 | 简介 |',
|
|
178
|
-
'|------|------|------|------|------|------|',
|
|
179
|
-
];
|
|
180
|
-
|
|
181
|
-
for (const b of books) {
|
|
182
|
-
const title = escapeTable(b.title || '');
|
|
183
|
-
const author = escapeTable(b.author || '');
|
|
184
|
-
const category = escapeTable(b.category || '-');
|
|
185
|
-
const words = b.words ? `${(b.words / 10000).toFixed(1)}万` : '-';
|
|
186
|
-
const intro = escapeTable((b.intro || '').slice(0, 80));
|
|
187
|
-
lines.push(`| ${b.rank ?? ''} | ${title} | ${author} | ${category} | ${words} | ${intro} |`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return lines.join('\n') + '\n';
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function escapeTable(s) {
|
|
194
|
-
return String(s).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
195
|
-
}
|
package/infra.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* infra.js — DSH Plugin Core Infrastructure
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* notify(session, kind, text) — return success/error result for TUI toast + Web left-panel
|
|
6
|
-
* llmFollowup(invocation,prompt) — hand a prompt to the LLM as a user message
|
|
7
|
-
* StatusLine(session) — in-place updatable status bar via surfaceOp replace
|
|
8
|
-
* scanProgress(ctx, phase, data) — emit structured scan progress events
|
|
9
|
-
* sleep(ctx, ms) — DSH-native promise delay via ctx.timeout()
|
|
10
|
-
*
|
|
11
|
-
* All fancy-* plugins import from this file.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// --------------------------------------------------------------------------
|
|
15
|
-
// notify — return error result propagated through dsh-commands settleThrown()
|
|
16
|
-
// --------------------------------------------------------------------------
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
/** Strip ANSI escape sequences from a string (defensive, prevents TTY leak). */
|
|
20
|
-
function stripAnsi(str) {
|
|
21
|
-
return String(str).replace(/\x1b[\s\S]*?([a-zA-Z]|$)|\x1b[()][AB012]|\x1b\][^\x07]*(?:\x07|$)/g, '$1');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param {object} session - DSH Session object (unused, kept for API compatibility)
|
|
26
|
-
* @param {'error'|'success'} kind
|
|
27
|
-
* @param {string} text
|
|
28
|
-
* @returns {{ kind: 'error'|'success', text: string }} Result propagated through
|
|
29
|
-
* dsh-commands settleThrown() → command/done → channel.notify() (TUI toast)
|
|
30
|
-
* and command/done (Web left-panel with real commandId prefix)
|
|
31
|
-
*/
|
|
32
|
-
export function notify(session, kind, text) {
|
|
33
|
-
return { kind, text: stripAnsi(text) };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// --------------------------------------------------------------------------
|
|
37
|
-
// StatusLine — in-place updatable status bar using session surfaceOp replace
|
|
38
|
-
// --------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Appends a single session event and updates it in-place on subsequent calls
|
|
42
|
-
* using session's surfaceOp replace mechanism. The event always stays at the
|
|
43
|
-
* same log position; only its content changes — visually a terminal status bar.
|
|
44
|
-
*
|
|
45
|
-
* Usage:
|
|
46
|
-
* const status = new StatusLine(session);
|
|
47
|
-
* status.update('Working…');
|
|
48
|
-
* status.update('Done ✓');
|
|
49
|
-
* status.clear();
|
|
50
|
-
*/
|
|
51
|
-
export class StatusLine {
|
|
52
|
-
#session;
|
|
53
|
-
#seq;
|
|
54
|
-
|
|
55
|
-
constructor(session) { this.#session = session; }
|
|
56
|
-
|
|
57
|
-
update(text) {
|
|
58
|
-
const event = this.#seq
|
|
59
|
-
? this.#session.append(
|
|
60
|
-
'fancy/status',
|
|
61
|
-
{ content: [{ type: 'text', text }], source: { kind: 'user' } },
|
|
62
|
-
{ surfaceOp: { op: 'replace', start: this.#seq, end: this.#seq } }
|
|
63
|
-
)
|
|
64
|
-
: this.#session.append(
|
|
65
|
-
'fancy/status',
|
|
66
|
-
{ content: [{ type: 'text', text }], source: { kind: 'user' } },
|
|
67
|
-
{ surfaceOp: 'append' }
|
|
68
|
-
);
|
|
69
|
-
this.#seq ??= event.seq;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
clear() { this.update(''); }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// --------------------------------------------------------------------------
|
|
76
|
-
// llmFollowup — hand a prompt to the LLM as a user message
|
|
77
|
-
// --------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {object} invocation - CommandInvocation from ctx.commands handler
|
|
81
|
-
* @param {string|object} content
|
|
82
|
-
*/
|
|
83
|
-
export function llmFollowup(invocation, content) {
|
|
84
|
-
const prompt = typeof content === 'string' ? content : JSON.stringify(content);
|
|
85
|
-
invocation.agent.followup({
|
|
86
|
-
content: [{ type: 'text', text: prompt }],
|
|
87
|
-
source: { kind: 'user' },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// --------------------------------------------------------------------------
|
|
92
|
-
// sleep — DSH-native promise delay using ctx.timeout()
|
|
93
|
-
// --------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Return a promise that resolves after `ms` milliseconds.
|
|
97
|
-
* Uses ctx.timeout() (Cordis timer service) instead of native setTimeout.
|
|
98
|
-
*
|
|
99
|
-
* @param {object} ctx - Cordis plugin context
|
|
100
|
-
* @param {number} ms - milliseconds to wait
|
|
101
|
-
* @returns {Promise<void>}
|
|
102
|
-
*/
|
|
103
|
-
export function sleep(ctx, ms) {
|
|
104
|
-
return new Promise((resolve) => {
|
|
105
|
-
ctx.timeout(resolve, ms);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// --------------------------------------------------------------------------
|
|
110
|
-
// scanProgress — emit structured scan progress via DSH events
|
|
111
|
-
// --------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Emit a scan progress event. These flow into the session log and can be
|
|
115
|
-
* observed by the running agent for real-time progress feedback.
|
|
116
|
-
*
|
|
117
|
-
* @param {object} ctx
|
|
118
|
-
* @param {'start'|'book'|'rank-done'|'done'} phase
|
|
119
|
-
* @param {object} data
|
|
120
|
-
*/
|
|
121
|
-
export function scanProgress(ctx, phase, data) {
|
|
122
|
-
switch (phase) {
|
|
123
|
-
case 'start':
|
|
124
|
-
ctx.events.emit('fancy/scan:start', data.platform, data.channel, data.type, data.label);
|
|
125
|
-
break
|
|
126
|
-
case 'book':
|
|
127
|
-
ctx.events.emit('fancy/scan:book', data.platform, data.channel, data.type,
|
|
128
|
-
data.index, data.total, data.title, data.rank);
|
|
129
|
-
break
|
|
130
|
-
case 'rank-done':
|
|
131
|
-
ctx.events.emit('fancy/scan:rank-done', data.platform, data.channel, data.type,
|
|
132
|
-
data.ok, data.written, data.failed, data.errorMsg);
|
|
133
|
-
break
|
|
134
|
-
case 'done':
|
|
135
|
-
ctx.events.emit('fancy/scan:done', data.platform, data.totalBooks, data.totalFiles, data.ok);
|
|
136
|
-
break
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// --------------------------------------------------------------------------
|
|
141
|
-
// writeFile — Node.js fs, used for project dirs outside workspace
|
|
142
|
-
// --------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Write text to a file, creating directories as needed.
|
|
146
|
-
* Uses Node.js fs directly (not ctx.fs) since project roots may be
|
|
147
|
-
* outside the DSH sandbox workspace.
|
|
148
|
-
*
|
|
149
|
-
* @param {string} filepath
|
|
150
|
-
* @param {string} content
|
|
151
|
-
* @param {string} [encoding='utf-8']
|
|
152
|
-
*/
|
|
153
|
-
export async function writeFile(filepath, content, encoding = 'utf-8') {
|
|
154
|
-
const { writeFileSync, mkdirSync } = await import('fs');
|
|
155
|
-
const { dirname } = await import('path');
|
|
156
|
-
mkdirSync(dirname(filepath), { recursive: true });
|
|
157
|
-
writeFileSync(filepath, content, encoding);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|