xinyu-pro 0.22.4 → 0.22.6
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/app/api/chat/route.ts +6 -8
- package/app/api/generate-svg/route.ts +5 -7
- package/app/api/generate-theme/route.ts +5 -7
- package/app/api/plugins/scan/route.ts +6 -21
- package/app/api/suggest-fields/route.ts +5 -7
- package/app/editor/page.tsx +6 -3
- package/app/extensions/edit/[id]/page.tsx +2412 -2342
- package/app/extensions/page.tsx +1576 -1572
- package/app/page.tsx +2 -2
- package/app/settings/page.tsx +1 -1
- package/bin/cli.js +226 -211
- package/components/ui/ThemeSwitcher.tsx +39 -37
- package/lib/db.ts +6 -9
- package/lib/get-ai-config.ts +33 -0
- package/lib/plugin-files.ts +186 -186
- package/package.json +2 -3
- package/version.json +7 -6
- package/.env.example +0 -17
- package/plugins/xinyu.auto-start-and-choices.xye +0 -0
- package/plugins/xinyu.bag-system.xye +0 -0
- package/plugins/xinyu.cache-optimizer.xye +0 -0
- package/plugins/xinyu.character-studio.xye +0 -0
- package/plugins/xinyu.dice-arbiter.xye +0 -0
- package/plugins/xinyu.game-auto-start-choices.xye +0 -0
- package/plugins/xinyu.markdown-render.xye +0 -0
- package/plugins/xinyu.slot-ui-beautify.xye +0 -0
- package/plugins/xinyu.world-info.xye +0 -0
package/lib/db.ts
CHANGED
|
@@ -6,9 +6,7 @@ let SQL: SqlJsStatic | null = null;
|
|
|
6
6
|
let db: SqlJsDatabase | null = null;
|
|
7
7
|
let initPromise: Promise<void> | null = null;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return process.env.DB_PATH || join(process.cwd(), 'data', 'xinyu.db');
|
|
11
|
-
}
|
|
9
|
+
const DB_PATH = join(process.cwd(), 'data', 'xinyu.db');
|
|
12
10
|
|
|
13
11
|
async function ensureDb(): Promise<SqlJsDatabase> {
|
|
14
12
|
if (db) return db;
|
|
@@ -17,12 +15,11 @@ async function ensureDb(): Promise<SqlJsDatabase> {
|
|
|
17
15
|
if (!SQL) SQL = await initSqlJs({
|
|
18
16
|
locateFile: (file: string) => join(process.cwd(), 'public', file),
|
|
19
17
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const buffer = readFileSync(dbPath);
|
|
18
|
+
if (existsSync(DB_PATH)) {
|
|
19
|
+
const buffer = readFileSync(DB_PATH);
|
|
23
20
|
db = new SQL.Database(buffer);
|
|
24
21
|
} else {
|
|
25
|
-
const dir = join(
|
|
22
|
+
const dir = join(DB_PATH, '..');
|
|
26
23
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
27
24
|
db = new SQL.Database();
|
|
28
25
|
}
|
|
@@ -36,7 +33,7 @@ async function ensureDb(): Promise<SqlJsDatabase> {
|
|
|
36
33
|
function persist(): void {
|
|
37
34
|
if (db) {
|
|
38
35
|
const data = db.export();
|
|
39
|
-
writeFileSync(
|
|
36
|
+
writeFileSync(DB_PATH, Buffer.from(data));
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -44,7 +41,7 @@ export function getRawDb(): SqlJsDatabase | null {
|
|
|
44
41
|
return db;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
export async function execute(sql: string, params?:
|
|
44
|
+
export async function execute(sql: string, params?: any[]): Promise<[unknown, unknown]> {
|
|
48
45
|
const database = await ensureDb();
|
|
49
46
|
const trimmed = sql.trim().toUpperCase();
|
|
50
47
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execute } from '@/lib/db';
|
|
2
|
+
import { ensureDb } from '@/lib/db-init';
|
|
3
|
+
|
|
4
|
+
export interface AiConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiBase: string;
|
|
7
|
+
model: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getAiConfig(): Promise<AiConfig> {
|
|
11
|
+
const defaults = {
|
|
12
|
+
apiKey: '',
|
|
13
|
+
apiBase: 'https://api.openai.com/v1',
|
|
14
|
+
model: 'gpt-4o',
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
await ensureDb();
|
|
18
|
+
const [rows] = await execute('SELECT value FROM app_settings WHERE key = ?', ['app_settings']);
|
|
19
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
20
|
+
const row = rows[0] as Record<string, unknown>;
|
|
21
|
+
const raw = row.value;
|
|
22
|
+
const settings = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
23
|
+
return {
|
|
24
|
+
apiKey: settings.aiApiKey || defaults.apiKey,
|
|
25
|
+
apiBase: settings.aiApiBase || defaults.apiBase,
|
|
26
|
+
model: settings.aiModel || defaults.model,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error('Failed to read AI config from database, using defaults:', e);
|
|
31
|
+
}
|
|
32
|
+
return defaults;
|
|
33
|
+
}
|
package/lib/plugin-files.ts
CHANGED
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
// lib/plugin-files.ts - 插件文件读写工具(代码 + 资源)
|
|
2
|
-
|
|
3
|
-
import { promises as fs } from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
/** 插件文件根目录 */
|
|
7
|
-
const PLUGIN_ROOT = path.join(process.cwd(), 'data', 'plugins');
|
|
8
|
-
|
|
9
|
-
/** 校验插件 ID 合法性(防止路径遍历攻击) */
|
|
10
|
-
function isValidPluginId(id: string): boolean {
|
|
11
|
-
return /^[a-zA-Z0-9._-]+$/.test(id) && id.length <= 200 && !id.includes('..');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** 校验文件路径安全性 */
|
|
15
|
-
function isSafePath(filePath: string): boolean {
|
|
16
|
-
const normalized = path.normalize(filePath);
|
|
17
|
-
return !normalized.includes('..') && !path.isAbsolute(normalized) && normalized !== '';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 获取插件目录的绝对路径
|
|
22
|
-
*/
|
|
23
|
-
export function getPluginDir(pluginId: string): string {
|
|
24
|
-
if (!isValidPluginId(pluginId)) {
|
|
25
|
-
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
26
|
-
}
|
|
27
|
-
return path.join(PLUGIN_ROOT, pluginId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 获取插件代码文件的绝对路径
|
|
32
|
-
* @param pluginId 插件 ID
|
|
33
|
-
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
34
|
-
*/
|
|
35
|
-
export function getPluginCodePath(pluginId: string, entryFile?: string): string {
|
|
36
|
-
return path.join(getPluginDir(pluginId), entryFile || 'plugin.js');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 获取插件代码的相对路径(存入数据库)
|
|
41
|
-
* @param pluginId 插件 ID
|
|
42
|
-
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
43
|
-
*/
|
|
44
|
-
export function getPluginCodeRelativePath(pluginId: string, entryFile?: string): string {
|
|
45
|
-
return `data/plugins/${pluginId}/${entryFile || 'plugin.js'}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 读取插件代码
|
|
50
|
-
* @param pluginId 插件 ID
|
|
51
|
-
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
52
|
-
*/
|
|
53
|
-
export async function readPluginCode(pluginId: string, entryFile?: string): Promise<string> {
|
|
54
|
-
const filePath = getPluginCodePath(pluginId, entryFile);
|
|
55
|
-
try {
|
|
56
|
-
return await fs.readFile(filePath, 'utf-8');
|
|
57
|
-
} catch {
|
|
58
|
-
throw new Error(`插件代码文件不存在: ${pluginId}/${entryFile || 'plugin.js'}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 写入插件代码
|
|
64
|
-
* @param pluginId 插件 ID
|
|
65
|
-
* @param code 代码内容
|
|
66
|
-
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
67
|
-
*/
|
|
68
|
-
export async function writePluginCode(pluginId: string, code: string, entryFile?: string): Promise<string> {
|
|
69
|
-
const pluginDir = getPluginDir(pluginId);
|
|
70
|
-
await fs.mkdir(pluginDir, { recursive: true });
|
|
71
|
-
const fileName = entryFile || 'plugin.js';
|
|
72
|
-
const filePath = path.join(pluginDir, fileName);
|
|
73
|
-
await fs.writeFile(filePath, code, 'utf-8');
|
|
74
|
-
return getPluginCodeRelativePath(pluginId, fileName);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 删除插件整个目录(代码 + 资源)
|
|
79
|
-
*/
|
|
80
|
-
export async function deletePluginCode(pluginId: string): Promise<void> {
|
|
81
|
-
const pluginDir = getPluginDir(pluginId);
|
|
82
|
-
try {
|
|
83
|
-
await fs.rm(pluginDir, { recursive: true, force: true });
|
|
84
|
-
} catch {
|
|
85
|
-
// 目录不存在时忽略
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 读取插件资源文件(返回 Buffer)
|
|
91
|
-
*/
|
|
92
|
-
export async function readPluginResource(pluginId: string, resourcePath: string): Promise<Buffer> {
|
|
93
|
-
if (!isValidPluginId(pluginId)) {
|
|
94
|
-
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
95
|
-
}
|
|
96
|
-
if (!isSafePath(resourcePath)) {
|
|
97
|
-
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
101
|
-
|
|
102
|
-
// 安全检查:确保路径在插件目录内
|
|
103
|
-
const pluginDir = path.resolve(getPluginDir(pluginId));
|
|
104
|
-
const resolvedPath = path.resolve(absolutePath);
|
|
105
|
-
if (!resolvedPath.startsWith(pluginDir + path.sep)) {
|
|
106
|
-
throw new Error(`资源路径越界: ${resourcePath}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return await fs.readFile(absolutePath);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 读取插件资源文件(返回文本)
|
|
114
|
-
*/
|
|
115
|
-
export async function readPluginResourceText(pluginId: string, resourcePath: string): Promise<string> {
|
|
116
|
-
const buffer = await readPluginResource(pluginId, resourcePath);
|
|
117
|
-
return buffer.toString('utf-8');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 写入插件资源文件
|
|
122
|
-
*/
|
|
123
|
-
export async function writePluginResource(pluginId: string, resourcePath: string, data: Buffer | string): Promise<void> {
|
|
124
|
-
if (!isValidPluginId(pluginId)) {
|
|
125
|
-
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
126
|
-
}
|
|
127
|
-
if (!isSafePath(resourcePath)) {
|
|
128
|
-
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
132
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
133
|
-
await fs.writeFile(absolutePath, data);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 删除插件资源文件
|
|
138
|
-
*/
|
|
139
|
-
export async function deletePluginResource(pluginId: string, resourcePath: string): Promise<void> {
|
|
140
|
-
if (!isValidPluginId(pluginId)) {
|
|
141
|
-
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
142
|
-
}
|
|
143
|
-
if (!isSafePath(resourcePath)) {
|
|
144
|
-
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
148
|
-
try {
|
|
149
|
-
await fs.unlink(absolutePath);
|
|
150
|
-
} catch {
|
|
151
|
-
// 文件不存在时忽略
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 列出插件目录下的所有文件
|
|
157
|
-
*/
|
|
158
|
-
export async function listPluginFiles(pluginId: string): Promise<string[]> {
|
|
159
|
-
const pluginDir = getPluginDir(pluginId);
|
|
160
|
-
const files: string[] = [];
|
|
161
|
-
try {
|
|
162
|
-
await collectFiles(pluginDir, pluginDir, files);
|
|
163
|
-
} catch {
|
|
164
|
-
// 目录不存在时返回空数组
|
|
165
|
-
}
|
|
166
|
-
return files;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** 递归收集文件和目录 */
|
|
170
|
-
async function collectFiles(baseDir: string, currentDir: string, result: string[]): Promise<void> {
|
|
171
|
-
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
172
|
-
let hasChild = false;
|
|
173
|
-
for (const entry of entries) {
|
|
174
|
-
hasChild = true;
|
|
175
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
await collectFiles(baseDir, fullPath, result);
|
|
178
|
-
} else {
|
|
179
|
-
result.push(path.relative(baseDir, fullPath));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// 空目录也收集(以 / 结尾标识)
|
|
183
|
-
if (!hasChild && currentDir !== baseDir) {
|
|
184
|
-
result.push(path.relative(baseDir, currentDir) + '/');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
1
|
+
// lib/plugin-files.ts - 插件文件读写工具(代码 + 资源)
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
/** 插件文件根目录 */
|
|
7
|
+
const PLUGIN_ROOT = path.join(process.cwd(), 'data', 'plugins');
|
|
8
|
+
|
|
9
|
+
/** 校验插件 ID 合法性(防止路径遍历攻击) */
|
|
10
|
+
function isValidPluginId(id: string): boolean {
|
|
11
|
+
return /^[a-zA-Z0-9._-]+$/.test(id) && id.length <= 200 && !id.includes('..');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 校验文件路径安全性 */
|
|
15
|
+
function isSafePath(filePath: string): boolean {
|
|
16
|
+
const normalized = path.normalize(filePath);
|
|
17
|
+
return !normalized.includes('..') && !path.isAbsolute(normalized) && normalized !== '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 获取插件目录的绝对路径
|
|
22
|
+
*/
|
|
23
|
+
export function getPluginDir(pluginId: string): string {
|
|
24
|
+
if (!isValidPluginId(pluginId)) {
|
|
25
|
+
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
26
|
+
}
|
|
27
|
+
return path.join(PLUGIN_ROOT, pluginId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取插件代码文件的绝对路径
|
|
32
|
+
* @param pluginId 插件 ID
|
|
33
|
+
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
34
|
+
*/
|
|
35
|
+
export function getPluginCodePath(pluginId: string, entryFile?: string): string {
|
|
36
|
+
return path.join(getPluginDir(pluginId), entryFile || 'plugin.js');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取插件代码的相对路径(存入数据库)
|
|
41
|
+
* @param pluginId 插件 ID
|
|
42
|
+
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
43
|
+
*/
|
|
44
|
+
export function getPluginCodeRelativePath(pluginId: string, entryFile?: string): string {
|
|
45
|
+
return `data/plugins/${pluginId}/${entryFile || 'plugin.js'}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 读取插件代码
|
|
50
|
+
* @param pluginId 插件 ID
|
|
51
|
+
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
52
|
+
*/
|
|
53
|
+
export async function readPluginCode(pluginId: string, entryFile?: string): Promise<string> {
|
|
54
|
+
const filePath = getPluginCodePath(pluginId, entryFile);
|
|
55
|
+
try {
|
|
56
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error(`插件代码文件不存在: ${pluginId}/${entryFile || 'plugin.js'}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 写入插件代码
|
|
64
|
+
* @param pluginId 插件 ID
|
|
65
|
+
* @param code 代码内容
|
|
66
|
+
* @param entryFile 入口文件名(如 index.js),默认 plugin.js
|
|
67
|
+
*/
|
|
68
|
+
export async function writePluginCode(pluginId: string, code: string, entryFile?: string): Promise<string> {
|
|
69
|
+
const pluginDir = getPluginDir(pluginId);
|
|
70
|
+
await fs.mkdir(pluginDir, { recursive: true });
|
|
71
|
+
const fileName = entryFile || 'plugin.js';
|
|
72
|
+
const filePath = path.join(pluginDir, fileName);
|
|
73
|
+
await fs.writeFile(filePath, code, 'utf-8');
|
|
74
|
+
return getPluginCodeRelativePath(pluginId, fileName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 删除插件整个目录(代码 + 资源)
|
|
79
|
+
*/
|
|
80
|
+
export async function deletePluginCode(pluginId: string): Promise<void> {
|
|
81
|
+
const pluginDir = getPluginDir(pluginId);
|
|
82
|
+
try {
|
|
83
|
+
await fs.rm(pluginDir, { recursive: true, force: true });
|
|
84
|
+
} catch {
|
|
85
|
+
// 目录不存在时忽略
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 读取插件资源文件(返回 Buffer)
|
|
91
|
+
*/
|
|
92
|
+
export async function readPluginResource(pluginId: string, resourcePath: string): Promise<Buffer> {
|
|
93
|
+
if (!isValidPluginId(pluginId)) {
|
|
94
|
+
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
95
|
+
}
|
|
96
|
+
if (!isSafePath(resourcePath)) {
|
|
97
|
+
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
101
|
+
|
|
102
|
+
// 安全检查:确保路径在插件目录内
|
|
103
|
+
const pluginDir = path.resolve(getPluginDir(pluginId));
|
|
104
|
+
const resolvedPath = path.resolve(absolutePath);
|
|
105
|
+
if (!resolvedPath.startsWith(pluginDir + path.sep)) {
|
|
106
|
+
throw new Error(`资源路径越界: ${resourcePath}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return await fs.readFile(absolutePath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 读取插件资源文件(返回文本)
|
|
114
|
+
*/
|
|
115
|
+
export async function readPluginResourceText(pluginId: string, resourcePath: string): Promise<string> {
|
|
116
|
+
const buffer = await readPluginResource(pluginId, resourcePath);
|
|
117
|
+
return buffer.toString('utf-8');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 写入插件资源文件
|
|
122
|
+
*/
|
|
123
|
+
export async function writePluginResource(pluginId: string, resourcePath: string, data: Buffer | string): Promise<void> {
|
|
124
|
+
if (!isValidPluginId(pluginId)) {
|
|
125
|
+
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
126
|
+
}
|
|
127
|
+
if (!isSafePath(resourcePath)) {
|
|
128
|
+
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
132
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
133
|
+
await fs.writeFile(absolutePath, data);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 删除插件资源文件
|
|
138
|
+
*/
|
|
139
|
+
export async function deletePluginResource(pluginId: string, resourcePath: string): Promise<void> {
|
|
140
|
+
if (!isValidPluginId(pluginId)) {
|
|
141
|
+
throw new Error(`非法插件 ID: ${pluginId}`);
|
|
142
|
+
}
|
|
143
|
+
if (!isSafePath(resourcePath)) {
|
|
144
|
+
throw new Error(`非法资源路径: ${resourcePath}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const absolutePath = path.join(PLUGIN_ROOT, pluginId, resourcePath);
|
|
148
|
+
try {
|
|
149
|
+
await fs.unlink(absolutePath);
|
|
150
|
+
} catch {
|
|
151
|
+
// 文件不存在时忽略
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 列出插件目录下的所有文件
|
|
157
|
+
*/
|
|
158
|
+
export async function listPluginFiles(pluginId: string): Promise<string[]> {
|
|
159
|
+
const pluginDir = getPluginDir(pluginId);
|
|
160
|
+
const files: string[] = [];
|
|
161
|
+
try {
|
|
162
|
+
await collectFiles(pluginDir, pluginDir, files);
|
|
163
|
+
} catch {
|
|
164
|
+
// 目录不存在时返回空数组
|
|
165
|
+
}
|
|
166
|
+
return files;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 递归收集文件和目录 */
|
|
170
|
+
async function collectFiles(baseDir: string, currentDir: string, result: string[]): Promise<void> {
|
|
171
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
172
|
+
let hasChild = false;
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
hasChild = true;
|
|
175
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
await collectFiles(baseDir, fullPath, result);
|
|
178
|
+
} else {
|
|
179
|
+
result.push(path.relative(baseDir, fullPath));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 空目录也收集(以 / 结尾标识)
|
|
183
|
+
if (!hasChild && currentDir !== baseDir) {
|
|
184
|
+
result.push(path.relative(baseDir, currentDir) + '/');
|
|
185
|
+
}
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xinyu-pro",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "星语 Pro - AI 驱动的互动叙事平台",
|
|
6
6
|
"author": "RestRegular",
|
|
@@ -13,14 +13,12 @@
|
|
|
13
13
|
"app/",
|
|
14
14
|
"components/",
|
|
15
15
|
"lib/",
|
|
16
|
-
"plugins/",
|
|
17
16
|
"public/",
|
|
18
17
|
"styles/",
|
|
19
18
|
"next.config.mjs",
|
|
20
19
|
"tailwind.config.ts",
|
|
21
20
|
"tsconfig.json",
|
|
22
21
|
"postcss.config.mjs",
|
|
23
|
-
".env.example",
|
|
24
22
|
"version.json"
|
|
25
23
|
],
|
|
26
24
|
"engines": {
|
|
@@ -50,6 +48,7 @@
|
|
|
50
48
|
},
|
|
51
49
|
"devDependencies": {
|
|
52
50
|
"@types/adm-zip": "^0.5.8",
|
|
51
|
+
"@types/sql.js": "^1.4.11",
|
|
53
52
|
"eslint": "^8",
|
|
54
53
|
"eslint-config-next": "14.2.35"
|
|
55
54
|
}
|
package/version.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.22.
|
|
3
|
-
"name": "xinyu-pro",
|
|
4
|
-
"displayName": "星语 Pro",
|
|
5
|
-
"description": "AI 驱动的互动叙事平台"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"version": "0.22.6",
|
|
3
|
+
"name": "xinyu-pro",
|
|
4
|
+
"displayName": "星语 Pro",
|
|
5
|
+
"description": "AI 驱动的互动叙事平台",
|
|
6
|
+
"updatedAt": "2026-05-31T13:38:02.566Z"
|
|
7
|
+
}
|
package/.env.example
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# AI 大模型 API 密钥
|
|
2
|
-
AI_API_KEY=sk-your-api-key-here
|
|
3
|
-
|
|
4
|
-
# AI 模型名称(可选,默认 gpt-4o)
|
|
5
|
-
AI_MODEL=gpt-4o
|
|
6
|
-
|
|
7
|
-
# AI API 地址(可选,默认 OpenAI,可替换为兼容接口)
|
|
8
|
-
AI_API_BASE=https://api.openai.com/v1
|
|
9
|
-
|
|
10
|
-
# GitHub Personal Access Token(需具备 repo 权限)
|
|
11
|
-
GITHUB_TOKEN=ghp_your-token-here
|
|
12
|
-
|
|
13
|
-
# GitHub 用户名
|
|
14
|
-
GITHUB_USERNAME=your-username
|
|
15
|
-
|
|
16
|
-
# 数据库文件路径(可选,默认 data/xinyu.db)
|
|
17
|
-
DB_PATH=data/xinyu.db
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|