@cloud411716/fancy-webnovel 0.1.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.
@@ -0,0 +1,4 @@
1
+ - insert:
2
+ - id: fancy-bootstrap
3
+ name: "@cloud411716/fancy-webnovel"
4
+ from: "./plugins/fancy-bootstrap/index.js"
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@cloud411716/fancy-webnovel",
3
+ "private": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "version": "0.1.0",
8
+ "type": "module",
9
+ "main": "plugins/fancy-bootstrap/index.js",
10
+ "exports": {
11
+ "./fancy-bootstrap": "./plugins/fancy-bootstrap/index.js",
12
+ "./fancy-scan": "./plugins/fancy-scan/index.js"
13
+ },
14
+ "files": ["plugins/", "cordis.patch.yml"],
15
+ "peerDependencies": {
16
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2"
17
+ },
18
+ "peerDependenciesMeta": {
19
+ "@deepseek-ai/dsh-llm": {
20
+ "optional": true
21
+ }
22
+ },
23
+ "dsh": {
24
+ "bundle": {
25
+ "patch": "./cordis.patch.yml"
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: fancy-bootstrap
3
+ name: "@cloud411716/fancy-webnovel-bootstrap"
@@ -0,0 +1,103 @@
1
+ /**
2
+ * fancy-bootstrap DSH plugin
3
+ */
4
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
5
+ import { spawn } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ export const name = 'fancy-bootstrap';
13
+ export const inject = ['commands', 'userQuestions'] as const;
14
+
15
+ function runInit(projectRoot: string): Promise<{ ok: boolean; output: string; error?: string }> {
16
+ return new Promise((resolve) => {
17
+ const scriptPath = join(__dirname, 'scripts', 'init.ts');
18
+ const proc = spawn('npx', ['tsx', scriptPath, projectRoot], { timeout: 60000 });
19
+ let stdout = '';
20
+ let stderr = '';
21
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); });
22
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
23
+ proc.on('close', (code) => {
24
+ try {
25
+ const parsed = JSON.parse(stdout);
26
+ resolve({ ok: parsed.ok === true, output: stdout, error: stderr || undefined });
27
+ } catch {
28
+ resolve({ ok: false, output: stdout, error: stderr || `exit code ${code}` });
29
+ }
30
+ });
31
+ proc.on('error', (err) => { resolve({ ok: false, output: '', error: err.message }); });
32
+ });
33
+ }
34
+
35
+ function notify(session: any, text: string) {
36
+ queueMicrotask(() => {
37
+ session.append(
38
+ "user/message",
39
+ createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }),
40
+ { surfaceOp: "append" }
41
+ );
42
+ });
43
+ }
44
+
45
+ export function apply(ctx: any) {
46
+ ctx.effect(function* () {
47
+ yield ctx.commands.register({
48
+ name: 'fancy-bootstrap',
49
+ description: '初始化网文项目 (Usage: /fancy-bootstrap [项目根])',
50
+ handler: async (invocation: any) => {
51
+ const session = invocation.agent.session;
52
+ const rawInput = invocation.rawInput.trim();
53
+
54
+ // 1. 获取项目根路径
55
+ let projectRoot = rawInput;
56
+ if (!projectRoot) {
57
+ const result = await ctx.userQuestions.ask({
58
+ questions: [{ id: 'path', question: '请输入项目根路径' }],
59
+ });
60
+ projectRoot = result.answers[0]?.custom?.trim();
61
+ if (!projectRoot) {
62
+ return { kind: 'success', text: '' };
63
+ }
64
+ }
65
+
66
+ // 2. 二次确认(隐藏自定义输入框)
67
+ const confirm = await ctx.userQuestions.ask({
68
+ questions: [{
69
+ id: 'confirm',
70
+ question: `确认将以下路径作为项目根?`,
71
+ detail: projectRoot,
72
+ hideCustomInput: true,
73
+ options: [
74
+ { label: '确认创建', description: '开始在此目录初始化项目' },
75
+ { label: '取消', description: '取消本次操作' },
76
+ ],
77
+ }],
78
+ });
79
+ const chosen = confirm.answers[0]?.selected?.[0] || confirm.answers[0]?.custom;
80
+ if (chosen === '取消') {
81
+ return { kind: 'success', text: '' };
82
+ }
83
+
84
+ // 3. 调用 init.ts(脚本内自行验证)
85
+ const { ok, output, error } = await runInit(projectRoot);
86
+
87
+ if (!ok) {
88
+ let detail = '';
89
+ try {
90
+ const parsed = JSON.parse(output);
91
+ if (parsed.error?.message) detail = parsed.error.message;
92
+ } catch { detail = error || output || '未知错误'; }
93
+ notify(session, `❌ 初始化失败:${detail}`);
94
+ return { kind: 'success', text: '' };
95
+ }
96
+
97
+ // 4. 成功
98
+ notify(session, `✅ 项目初始化完成:${projectRoot}\n\n下一步:/fancy-scan 扫榜分析`);
99
+ return { kind: 'success', text: '' };
100
+ }
101
+ });
102
+ }, 'fancy-bootstrap:register');
103
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@cloud411716/fancy-webnovel-bootstrap",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "index.ts",
6
+ "files": ["index.ts", "scripts/", "cordis.patch.yml"],
7
+ "peerDependencies": {
8
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2"
9
+ },
10
+ "peerDependenciesMeta": {
11
+ "@deepseek-ai/dsh-llm": {
12
+ "optional": true
13
+ }
14
+ },
15
+ "dsh": {
16
+ "bundle": {
17
+ "patch": "./cordis.patch.yml"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * fancy-bootstrap init.ts — 项目初始化
3
+ */
4
+
5
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
6
+ import { expandHome, resolvePath, joinPath } from './path-utils.ts';
7
+
8
+ const DIRS = [
9
+ '设定/角色', '设定/势力', '设定/物品', '设定/地点',
10
+ '大纲', '正文', '故事档案', '对标', '拆文库',
11
+ '审查报告', '图片', '发布',
12
+ '故事档案/备份', '故事档案/.快照', '故事档案/派生',
13
+ '调试', 'RAG索引',
14
+ ];
15
+
16
+ function mkdirp(dir: string): void {
17
+ mkdirSync(dir, { recursive: true });
18
+ }
19
+
20
+ function writeJson(filePath: string, data: unknown): void {
21
+ writeFileSync(filePath, JSON.stringify(data), 'utf-8');
22
+ }
23
+
24
+ function nowIso(): string {
25
+ return new Date().toISOString().replace('.000Z', 'Z');
26
+ }
27
+
28
+ function main() {
29
+ const projectRootRaw = process.argv[2] ?? '';
30
+
31
+ // 路径合法性检查
32
+ if (projectRootRaw.includes('..') || /[<>:"|?*]/.test(projectRootRaw) || /[\x00-\x1f]/.test(projectRootRaw)) {
33
+ console.log(JSON.stringify({ ok: false, error: { code: 1, code_name: 'EC001', message: '路径不合法' } }));
34
+ process.exit(1);
35
+ }
36
+
37
+ const projectRoot = resolvePath(expandHome(projectRootRaw));
38
+ const deployed = joinPath(projectRoot, '.fancy-deployed');
39
+
40
+ if (existsSync(deployed)) {
41
+ console.log(JSON.stringify({ ok: false, error: { code: 2, code_name: 'EC002', message: '项目已存在,不能重复 init' } }));
42
+ process.exit(2);
43
+ }
44
+ if (!existsSync(projectRoot)) {
45
+ console.log(JSON.stringify({ ok: false, error: { code: 18, code_name: 'EC018', message: `目录不存在: ${projectRoot}` } }));
46
+ process.exit(18);
47
+ }
48
+
49
+ const now = nowIso();
50
+
51
+ try {
52
+ // 创建目录
53
+ for (const d of DIRS) mkdirp(joinPath(projectRoot, d));
54
+
55
+ // .fancy-deployed
56
+ writeJson(deployed, {
57
+ schema_version: '2.0.0', fancy_skills_version: '2.0.0', deployed_at: now,
58
+ project_root_abs: projectRoot, project_root_rel: '.', current_book: '', books: [], active_book_index: 0,
59
+ });
60
+
61
+ // truth files
62
+ const archive = joinPath(projectRoot, '故事档案');
63
+ writeJson(joinPath(archive, '_story_state.json'), {
64
+ schema_version: '2.0.0', last_modified: now,
65
+ project: { name: '', created_at: now, last_modified: now, genre: '', target_words: 0, target_chapters: 0, platform: '', platform_other: '' },
66
+ phase: 'uninitialized', phase_history: [{ phase: 'uninitialized', at: now }],
67
+ 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 },
68
+ author_intent: { style: 'xiaobai', pacing: 'medium', pov: 'third', target_audience: 'general', preferences: [], hard_constraints: [], anti_tropes: [], core_summary: '', core_conflict: '', reader_promise: '' },
69
+ active_book: { project_root: '.', set_at: now },
70
+ });
71
+ writeJson(joinPath(archive, '_timeline.json'), {
72
+ schema_version: '2.0.0', last_modified: now,
73
+ world_clock: { start_date: now, current_date: now, calendar_unit: 'day' },
74
+ events: [], character_status_at_chapter: {}, dead_characters: [], fact_log: [], discoveries: [],
75
+ });
76
+ writeJson(joinPath(archive, '_character_state.json'), { schema_version: '2.0.0', last_modified: now, characters: {}, info_boundary_log: [] });
77
+ writeJson(joinPath(archive, '_foreshadows.json'), { schema_version: '2.0.0', last_modified: now, foreshadows: [], type_distribution: {}, open_count: 0 });
78
+ writeJson(joinPath(archive, '_chapter_index.json'), { schema_version: '2.0.0', last_modified: now, chapters: [] });
79
+
80
+ // author intent 模板
81
+ const intentMd = [
82
+ '# 作者长期意图(Author Intent)',
83
+ '',
84
+ '> 由 fancy-bootstrap 自动生成。',
85
+ '> **注意**:书名/题材/平台/字数等信息在 fancy-topic 阶段填入。',
86
+ '',
87
+ '## 项目信息',
88
+ '> ⚠️ 以下由 fancy-topic 填入:书名 / 题材 / 平台 / 目标字数 / 目标章数',
89
+ '',
90
+ '## 故事核心',
91
+ '> ⚠️ 由 fancy-topic 填入:一句话 / 核心冲突 / 读者承诺',
92
+ '',
93
+ '## 风格基调',
94
+ '- 文风:xiaobai(待 fancy-bible 细化)',
95
+ '- 节奏:medium(待 fancy-bible 细化)',
96
+ '- POV:third(待 fancy-bible 细化)',
97
+ '',
98
+ `## 用户已声明的偏好`,
99
+ `- ${now}:项目目录创建(fancy-bootstrap)`,
100
+ ].join('\n');
101
+ writeFileSync(joinPath(archive, '_author_intent.md'), intentMd, 'utf-8');
102
+
103
+ } catch (e: unknown) {
104
+ console.log(JSON.stringify({ ok: false, error: { code: 17, code_name: 'EC017', message: `初始化失败: ${e instanceof Error ? e.message : String(e)}` } }));
105
+ process.exit(17);
106
+ }
107
+
108
+ console.log(JSON.stringify({
109
+ ok: true, operation: 'init', project_root: projectRoot, phase: 'uninitialized',
110
+ directories_created: DIRS.length,
111
+ files_created: ['.fancy-deployed', '故事档案/_story_state.json', '故事档案/_timeline.json', '故事档案/_character_state.json', '故事档案/_foreshadows.json', '故事档案/_chapter_index.json', '故事档案/_author_intent.md'],
112
+ note: '项目信息由 fancy-topic 填入',
113
+ }));
114
+ }
115
+
116
+ main();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 路径工具
3
+ */
4
+ export function expandHome(pathStr: string): string {
5
+ if (pathStr.startsWith('~/') || pathStr === '~') {
6
+ return pathStr === '~' ? (process.env.HOME ?? '/root') : pathStr.replace('~', process.env.HOME ?? '/root');
7
+ }
8
+ return pathStr;
9
+ }
10
+
11
+ export function resolvePath(pathStr: string): string {
12
+ return expandHome(pathStr).replace(/\/+$/, '');
13
+ }
14
+
15
+ export function joinPath(...parts: string[]): string {
16
+ return parts.join('/').replace(/\/+/g, '/');
17
+ }