mioku 0.9.2 → 0.9.3

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/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const node_fs = require_chunk.__toESM(require("node:fs"));
3
- const node_path = require_chunk.__toESM(require("node:path"));
4
- const fs_promises = require_chunk.__toESM(require("fs/promises"));
5
- const path = require_chunk.__toESM(require("path"));
6
2
  const fs = require_chunk.__toESM(require("fs"));
3
+ const path = require_chunk.__toESM(require("path"));
4
+ const url = require_chunk.__toESM(require("url"));
5
+ const fs_promises = require_chunk.__toESM(require("fs/promises"));
6
+ const mioki = require_chunk.__toESM(require("mioki"));
7
7
 
8
8
  //#region src/core/logger.ts
9
9
  const fallbackLogger = {
@@ -26,9 +26,8 @@ const logger = {
26
26
  };
27
27
 
28
28
  //#endregion
29
- //#region src/core/plugin-linker.ts
30
- const DEFAULT_RUNTIME_PLUGINS_DIR = ".mioku/plugins";
31
- async function pathExists$3(filePath) {
29
+ //#region src/core/module-scanner.ts
30
+ async function pathExists(filePath) {
32
31
  try {
33
32
  await fs_promises.access(filePath);
34
33
  return true;
@@ -36,6 +35,66 @@ async function pathExists$3(filePath) {
36
35
  return false;
37
36
  }
38
37
  }
38
+ async function resolveRealpath(p) {
39
+ try {
40
+ return await fs_promises.realpath(p);
41
+ } catch {
42
+ return p;
43
+ }
44
+ }
45
+ function toImportPath(filePath) {
46
+ if (process.platform === "win32") return "file:///" + filePath.replace(/\\/g, "/");
47
+ return filePath;
48
+ }
49
+ async function scanLocalDir(dir) {
50
+ const results = [];
51
+ if (!await pathExists(dir)) return results;
52
+ let entries;
53
+ try {
54
+ entries = await fs_promises.readdir(dir, { withFileTypes: true });
55
+ } catch {
56
+ return results;
57
+ }
58
+ for (const entry of entries) {
59
+ const entryPath = path.join(dir, entry.name);
60
+ try {
61
+ const stat = await fs_promises.stat(entryPath);
62
+ if (!stat.isDirectory()) continue;
63
+ } catch {
64
+ continue;
65
+ }
66
+ results.push({
67
+ name: entry.name,
68
+ path: await resolveRealpath(entryPath)
69
+ });
70
+ }
71
+ return results;
72
+ }
73
+ async function scanNodeModules(prefix) {
74
+ const results = [];
75
+ const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
76
+ if (!await pathExists(nodeModulesPath)) return results;
77
+ let entries;
78
+ try {
79
+ entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
80
+ } catch {
81
+ return results;
82
+ }
83
+ for (const entry of entries) {
84
+ if (!entry.name.startsWith(prefix)) continue;
85
+ const name = entry.name.slice(prefix.length);
86
+ const fullPath = path.join(nodeModulesPath, entry.name);
87
+ results.push({
88
+ name,
89
+ path: await resolveRealpath(fullPath)
90
+ });
91
+ }
92
+ return results;
93
+ }
94
+
95
+ //#endregion
96
+ //#region src/core/plugin-linker.ts
97
+ const DEFAULT_RUNTIME_PLUGINS_DIR = ".mioku/plugins";
39
98
  async function removeIfBrokenSymlink(entryPath) {
40
99
  let stat;
41
100
  try {
@@ -54,8 +113,7 @@ async function removeIfBrokenSymlink(entryPath) {
54
113
  }
55
114
  }
56
115
  function relativeSymlinkTarget(linkPath, targetPath) {
57
- const relativePath = path.relative(path.dirname(linkPath), targetPath);
58
- return relativePath || ".";
116
+ return path.relative(path.dirname(linkPath), targetPath) || ".";
59
117
  }
60
118
  async function ensurePluginLink(runtimePluginsDir, metadata) {
61
119
  const linkPath = path.join(runtimePluginsDir, metadata.name);
@@ -80,7 +138,7 @@ async function ensurePluginLink(runtimePluginsDir, metadata) {
80
138
  logger.warn(`[plugin-linker] ${linkPath} exists and is not a symlink, skip linking ${metadata.name}`);
81
139
  return false;
82
140
  }
83
- if (!await pathExists$3(targetPath)) {
141
+ if (!await pathExists(targetPath)) {
84
142
  logger.warn(`[plugin-linker] Plugin target missing, skip linking ${metadata.name}: ${targetPath}`);
85
143
  return false;
86
144
  }
@@ -90,12 +148,11 @@ async function ensurePluginLink(runtimePluginsDir, metadata) {
90
148
  }
91
149
  async function prepareRuntimePluginLinks(plugins, runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR)) {
92
150
  await fs_promises.mkdir(runtimePluginsDir, { recursive: true });
93
- const discoveredNames = new Set(plugins.map((plugin) => plugin.name));
151
+ const discoveredNames = new Set(plugins.map((p) => p.name));
94
152
  const entries = await fs_promises.readdir(runtimePluginsDir, { withFileTypes: true });
95
153
  for (const entry of entries) {
96
154
  const entryPath = path.join(runtimePluginsDir, entry.name);
97
- const symlinkState = await removeIfBrokenSymlink(entryPath);
98
- if (symlinkState !== "ok") continue;
155
+ if (await removeIfBrokenSymlink(entryPath) !== "ok") continue;
99
156
  if (entry.isSymbolicLink() && !discoveredNames.has(entry.name)) {
100
157
  await fs_promises.rm(entryPath, { force: true });
101
158
  logger.info(`[plugin-linker] Removed stale plugin link: ${entry.name}`);
@@ -107,279 +164,166 @@ async function prepareRuntimePluginLinks(plugins, runtimePluginsDir = path.resol
107
164
  }
108
165
 
109
166
  //#endregion
110
- //#region src/core/plugin-manager.ts
111
- const PLUGIN_MANAGER_SYMBOL = Symbol.for("mioku.plugin-manager");
112
- async function pathExists$2(filePath) {
113
- try {
114
- await fs_promises.access(filePath);
115
- return true;
116
- } catch {
117
- return false;
118
- }
167
+ //#region src/core/registry.ts
168
+ const STORE_SYMBOL = Symbol.for("mioku.globalStore");
169
+ function store$1() {
170
+ const g = globalThis;
171
+ if (!g[STORE_SYMBOL]) g[STORE_SYMBOL] = {};
172
+ return g[STORE_SYMBOL];
173
+ }
174
+ function getOrCreate(key, factory) {
175
+ const s = store$1();
176
+ if (s[key] === void 0) s[key] = factory();
177
+ return s[key];
119
178
  }
120
- /**
121
- * 插件管理器
122
- *
123
- * Discover and manage plugins from both local directories and node_modules.
124
- */
179
+
180
+ //#endregion
181
+ //#region src/core/plugin-manager.ts
182
+ const PLUGIN_PREFIX = "mioku-plugin-";
125
183
  var PluginManager = class PluginManager {
126
- pluginMetadata = new Map();
184
+ plugins = new Map();
127
185
  static getInstance() {
128
- const g = global;
129
- if (!g[PLUGIN_MANAGER_SYMBOL]) g[PLUGIN_MANAGER_SYMBOL] = new PluginManager();
130
- return g[PLUGIN_MANAGER_SYMBOL];
186
+ return getOrCreate("plugin-manager", () => new PluginManager());
131
187
  }
132
188
  async discoverPlugins(miokuConfig = {}) {
133
- const configuredPluginsDir = miokuConfig.plugins_dir;
134
- const pluginsDir = configuredPluginsDir && configuredPluginsDir !== DEFAULT_RUNTIME_PLUGINS_DIR ? path.resolve(process.cwd(), configuredPluginsDir) : path.resolve(process.cwd(), "plugins");
135
- this.pluginMetadata.clear();
136
- if (!await pathExists$2(pluginsDir)) (0, fs.mkdirSync)(pluginsDir, { recursive: true });
137
- const discovered = [];
138
- if (await pathExists$2(pluginsDir)) {
139
- const localPlugins = await this.discoverFromDir(pluginsDir);
140
- discovered.push(...localPlugins);
141
- }
142
- const nodeModulesPlugins = await this.discoverFromNodeModules();
143
- discovered.push(...nodeModulesPlugins);
144
- logger.info(`O.o 发现了 ${this.pluginMetadata.size} 个插件`);
145
- return Array.from(this.pluginMetadata.values());
146
- }
147
- async discoverFromDir(pluginsDir) {
148
- const discovered = [];
149
- try {
150
- const entries = await fs_promises.readdir(pluginsDir, { withFileTypes: true });
151
- for (const entry of entries) {
152
- const pluginPath = path.join(pluginsDir, entry.name);
153
- const metadataPath = await this.resolveDirectoryPath(pluginPath);
154
- if (!metadataPath) continue;
155
- const metadata = await this.loadPluginMetadata(entry.name, pluginPath);
156
- if (metadata) {
157
- discovered.push(metadata);
158
- this.pluginMetadata.set(metadata.name, metadata);
159
- }
160
- }
161
- } catch (error) {
162
- logger.error(`扫描插件目录失败: ${error}`);
189
+ const configuredDir = miokuConfig.plugins_dir;
190
+ const pluginsDir = configuredDir && configuredDir !== DEFAULT_RUNTIME_PLUGINS_DIR ? path.resolve(process.cwd(), configuredDir) : path.resolve(process.cwd(), "plugins");
191
+ this.plugins.clear();
192
+ if (!await pathExists(pluginsDir)) (0, fs.mkdirSync)(pluginsDir, { recursive: true });
193
+ const local = await scanLocalDir(pluginsDir);
194
+ for (const { name, path: p } of local) {
195
+ const metadata = await this.loadPluginMetadata(name, p);
196
+ if (metadata) this.plugins.set(metadata.name, metadata);
163
197
  }
164
- return discovered;
165
- }
166
- async discoverFromNodeModules() {
167
- const discovered = [];
168
- const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
169
- if (!await pathExists$2(nodeModulesPath)) return discovered;
170
- try {
171
- const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
172
- for (const entry of entries) {
173
- if (!entry.name.startsWith("mioku-plugin-")) continue;
174
- const pluginName = entry.name.replace(/^mioku-plugin-/, "");
175
- const pluginPath = path.join(nodeModulesPath, entry.name);
176
- const metadata = await this.loadPluginMetadata(pluginName, pluginPath);
177
- if (metadata) {
178
- discovered.push(metadata);
179
- this.pluginMetadata.set(metadata.name, metadata);
180
- }
181
- }
182
- } catch (error) {
183
- logger.debug(`扫描 node_modules 插件失败: ${error}`);
184
- }
185
- return discovered;
186
- }
187
- async resolveDirectoryPath(entryPath) {
188
- try {
189
- const stat = await fs_promises.stat(entryPath);
190
- return stat.isDirectory() ? entryPath : null;
191
- } catch {
192
- return null;
198
+ const external = await scanNodeModules(PLUGIN_PREFIX);
199
+ for (const { name, path: p } of external) {
200
+ const metadata = await this.loadPluginMetadata(name, p);
201
+ if (metadata) this.plugins.set(metadata.name, metadata);
193
202
  }
203
+ logger.info(`O.o 发现了 ${this.plugins.size} 个插件`);
204
+ return [...this.plugins.values()];
194
205
  }
195
206
  async loadPluginMetadata(name, pluginPath) {
196
- let resolvedPath = pluginPath;
197
- try {
198
- resolvedPath = await fs_promises.realpath(pluginPath);
199
- } catch {}
200
- const packageJsonPath = path.join(resolvedPath, "package.json");
207
+ const resolvedPath = await resolveRealpath(pluginPath);
201
208
  let packageJson = null;
202
209
  try {
203
- const content = await fs_promises.readFile(packageJsonPath, "utf-8");
204
- packageJson = JSON.parse(content);
205
- } catch {}
206
- const metadata = {
210
+ packageJson = JSON.parse(await fs_promises.readFile(path.join(resolvedPath, "package.json"), "utf-8"));
211
+ } catch (error) {
212
+ logger.warn(`[plugin-manager] 读取 ${name} package.json 失败: ${error}`);
213
+ }
214
+ const config = packageJson?.mioku ?? {};
215
+ return {
207
216
  name,
208
- version: packageJson?.version || "0.0.0",
217
+ version: packageJson?.version ?? "0.0.0",
209
218
  description: packageJson?.description,
210
219
  path: resolvedPath,
211
- packageJson,
212
- config: packageJson?.mioku || {}
220
+ packageJson: packageJson ?? {},
221
+ config
213
222
  };
214
- return metadata;
215
223
  }
216
224
  collectRequiredServices() {
217
225
  const services = new Set();
218
- for (const metadata of this.pluginMetadata.values()) if (metadata.config.services) metadata.config.services.forEach((s) => services.add(s));
226
+ for (const metadata of this.plugins.values()) for (const service of metadata.config.services ?? []) services.add(service);
219
227
  return services;
220
228
  }
221
229
  getPluginMetadata(name) {
222
- return this.pluginMetadata.get(name);
230
+ return this.plugins.get(name);
223
231
  }
224
232
  getAllMetadata() {
225
- return Array.from(this.pluginMetadata.values());
233
+ return [...this.plugins.values()];
226
234
  }
227
235
  reset() {
228
- this.pluginMetadata.clear();
236
+ this.plugins.clear();
229
237
  }
230
238
  };
231
239
  var plugin_manager_default = PluginManager.getInstance();
232
240
 
233
241
  //#endregion
234
242
  //#region src/core/service-manager.ts
235
- const SERVICE_MANAGER_SYMBOL = Symbol.for("mioku.service-manager");
236
- async function pathExists$1(filePath) {
237
- try {
238
- await fs_promises.access(filePath);
239
- return true;
240
- } catch {
241
- return false;
242
- }
243
- }
244
- /**
245
- * 服务管理器
246
- */
243
+ const SERVICE_PREFIX = "mioku-service-";
247
244
  var ServiceManager = class ServiceManager {
248
245
  services = new Map();
249
246
  serviceMetadata = new Map();
250
- servicesDir = "services";
251
247
  static getInstance() {
252
- const g = global;
253
- if (!g[SERVICE_MANAGER_SYMBOL]) g[SERVICE_MANAGER_SYMBOL] = new ServiceManager();
254
- return g[SERVICE_MANAGER_SYMBOL];
248
+ return getOrCreate("service-manager", () => new ServiceManager());
255
249
  }
256
250
  async discoverServices(miokuConfig = {}) {
257
- if (miokuConfig.services_dir) this.servicesDir = path.resolve(process.cwd(), miokuConfig.services_dir);
258
- else this.servicesDir = path.resolve(process.cwd(), "services");
259
- const discovered = [];
251
+ const servicesDir = miokuConfig.services_dir ? path.resolve(process.cwd(), miokuConfig.services_dir) : path.resolve(process.cwd(), "services");
260
252
  this.serviceMetadata.clear();
261
- if ((0, fs.existsSync)(this.servicesDir)) {
262
- const localServices = await this.discoverFromDir(this.servicesDir);
263
- discovered.push(...localServices);
264
- } else (0, fs.mkdirSync)(this.servicesDir, { recursive: true });
265
- await this.loadBuiltinServices();
266
- logger.info(`o.O 发现了 ${this.serviceMetadata.size} 个服务`);
267
- return Array.from(this.serviceMetadata.values());
268
- }
269
- async discoverFromDir(servicesDir) {
270
- const discovered = [];
271
- try {
272
- const entries = await fs_promises.readdir(servicesDir, { withFileTypes: true });
273
- for (const entry of entries) {
274
- if (!entry.isDirectory()) continue;
275
- const servicePath = path.join(servicesDir, entry.name);
276
- const metadata = await this.loadServiceMetadata(entry.name, servicePath);
277
- if (metadata) {
278
- discovered.push(metadata);
279
- this.serviceMetadata.set(entry.name, metadata);
280
- }
253
+ if ((0, fs.existsSync)(servicesDir)) {
254
+ const local = await scanLocalDir(servicesDir);
255
+ for (const { name, path: p } of local) {
256
+ const metadata = await this.loadServiceMetadata(name, p);
257
+ if (metadata) this.serviceMetadata.set(name, metadata);
281
258
  }
282
- } catch (error) {
283
- logger.error(`扫描服务目录失败: ${error}`);
259
+ } else (0, fs.mkdirSync)(servicesDir, { recursive: true });
260
+ const external = await scanNodeModules(SERVICE_PREFIX);
261
+ for (const { name, path: p } of external) {
262
+ const metadata = await this.loadServiceMetadata(name, p);
263
+ if (metadata) this.serviceMetadata.set(name, metadata);
284
264
  }
285
- return discovered;
265
+ logger.info(`o.O 发现了 ${this.serviceMetadata.size} 个服务`);
266
+ return [...this.serviceMetadata.values()];
286
267
  }
287
268
  async loadServiceMetadata(name, servicePath) {
288
- let resolvedPath = servicePath;
289
- try {
290
- resolvedPath = await fs_promises.realpath(servicePath);
291
- } catch {}
292
- const packageJsonPath = path.join(resolvedPath, "package.json");
293
- if (!await pathExists$1(packageJsonPath)) return null;
269
+ const packageJsonPath = path.join(servicePath, "package.json");
270
+ if (!await pathExists(packageJsonPath)) return null;
294
271
  try {
295
272
  const packageJson = JSON.parse(await fs_promises.readFile(packageJsonPath, "utf-8"));
296
- const metadata = {
273
+ return {
297
274
  name,
298
- version: packageJson.version || "0.0.0",
275
+ version: packageJson.version ?? "0.0.0",
299
276
  description: packageJson.description,
300
- path: resolvedPath,
277
+ path: servicePath,
301
278
  packageJson
302
279
  };
303
- return metadata;
304
280
  } catch (error) {
305
- logger.error(`解析服务 ${name} 失败: ${error.message}`);
281
+ logger.warn(`[service-manager] 解析服务 ${name} 失败: ${error}`);
306
282
  return null;
307
283
  }
308
284
  }
309
- /**
310
- * Load built-in services from the package
311
- */
312
- async loadBuiltinServices() {
313
- await this.discoverFromNodeModules();
314
- }
315
- async discoverFromNodeModules() {
316
- const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
317
- if (!await pathExists$1(nodeModulesPath)) return;
318
- try {
319
- const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
320
- for (const entry of entries) {
321
- if (!entry.name.startsWith("mioku-service-")) continue;
322
- const serviceName = entry.name.replace(/^mioku-service-/, "");
323
- const servicePath = path.join(nodeModulesPath, entry.name);
324
- const metadata = await this.loadServiceMetadata(serviceName, servicePath);
325
- if (metadata) this.serviceMetadata.set(serviceName, metadata);
326
- }
327
- } catch (error) {
328
- logger.debug(`扫描 node_modules 服务失败: ${error}`);
329
- }
330
- }
331
- async checkMissingServices(requiredServices) {
285
+ async checkMissingServices(required) {
332
286
  const missing = [];
333
- for (const serviceName of requiredServices) if (!this.serviceMetadata.has(serviceName)) missing.push(serviceName);
287
+ for (const name of required) if (!this.serviceMetadata.has(name)) missing.push(name);
334
288
  return missing;
335
289
  }
336
290
  async loadAllServices(ctx) {
337
- const allMetadata = Array.from(this.serviceMetadata.values());
338
- logger.info(`O.o 准备加载 ${allMetadata.length} 个服务...`);
339
- for (const metadata of allMetadata) await this.loadService(metadata, ctx);
291
+ const all = [...this.serviceMetadata.values()];
292
+ logger.info(`O.o 准备加载 ${all.length} 个服务...`);
293
+ for (const metadata of all) await this.loadService(metadata, ctx);
340
294
  }
341
295
  async loadService(metadata, ctx) {
342
296
  try {
343
- const indexPath = path.join(metadata.path, "index.ts");
344
- const indexJsPath = path.join(metadata.path, "index.js");
345
- const indexExists = await pathExists$1(indexPath);
346
- const indexJsExists = await pathExists$1(indexJsPath);
347
- const entryPoint = indexExists ? indexPath : indexJsPath;
348
- if (!entryPoint || !indexExists && !indexJsExists) {
349
- logger.error(`服务 ${metadata.name} 入口丢失`);
297
+ const tsEntry = path.join(metadata.path, "index.ts");
298
+ const jsEntry = path.join(metadata.path, "index.js");
299
+ const entry = await pathExists(tsEntry) ? tsEntry : await pathExists(jsEntry) ? jsEntry : null;
300
+ if (!entry) {
301
+ logger.error(`[service-manager] 服务 ${metadata.name} 入口丢失`);
350
302
  return false;
351
303
  }
352
- let importPath = entryPoint;
353
- if (process.platform === "win32") importPath = "file:///" + entryPoint.replace(/\\/g, "/");
354
- const serviceModule = await import(importPath);
355
- const service = serviceModule.default || serviceModule;
356
- if (!service || typeof service.init !== "function") return false;
357
- await service.init();
358
- if (service.api) {
359
- if (!ctx.services) ctx.services = {};
360
- ctx.services[metadata.name] = service.api;
304
+ const serviceModule = await import(toImportPath(entry));
305
+ const service = serviceModule.default ?? serviceModule;
306
+ if (!service || typeof service.init !== "function") {
307
+ logger.warn(`[service-manager] 服务 ${metadata.name} 无效:缺少 init()`);
308
+ return false;
361
309
  }
310
+ await service.init();
311
+ if (service.api) ctx.services[metadata.name] = service.api;
362
312
  this.services.set(metadata.name, service);
363
313
  return true;
364
314
  } catch (error) {
365
- logger.error(`加载服务 ${metadata.name} 失败: ${error.message}`);
315
+ logger.error(`[service-manager] 加载服务 ${metadata.name} 失败: ${error}`);
366
316
  return false;
367
317
  }
368
318
  }
369
- /**
370
- * Register a builtin service directly
371
- */
372
319
  registerBuiltinService(name, service) {
373
320
  this.services.set(name, service);
374
321
  }
375
- /**
376
- * Get a loaded service
377
- */
378
322
  getService(name) {
379
323
  return this.services.get(name);
380
324
  }
381
325
  async disposeAll() {
382
- for (const [name, service] of this.services) if (service.dispose) await service.dispose();
326
+ for (const [, service] of this.services) await service.dispose?.();
383
327
  this.services.clear();
384
328
  }
385
329
  reset() {
@@ -390,21 +334,56 @@ var ServiceManager = class ServiceManager {
390
334
  var service_manager_default = ServiceManager.getInstance();
391
335
 
392
336
  //#endregion
393
- //#region src/core/plugin-artifact-registry.ts
394
- async function pathExists(filePath) {
337
+ //#region src/core/bootstrap.ts
338
+ async function readMiokuConfig() {
339
+ const packageJsonPath = path.join(process.cwd(), "package.json");
395
340
  try {
396
- await fs_promises.access(filePath);
397
- return true;
341
+ const pkg = JSON.parse(await fs_promises.readFile(packageJsonPath, "utf-8"));
342
+ return pkg.mioki ?? {};
398
343
  } catch {
399
- return false;
344
+ return {};
400
345
  }
401
346
  }
402
- function toImportPath(filePath) {
403
- if (process.platform === "win32") return "file:///" + filePath.replace(/\\/g, "/");
404
- return filePath;
347
+ function ensureRuntimeDirectories() {
348
+ for (const dir of [
349
+ "data",
350
+ "config",
351
+ "temp"
352
+ ]) if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
353
+ }
354
+ async function discoverAndLinkPlugins(miokuConfig) {
355
+ logger.info("O.o Miku 正在翻找插件..");
356
+ const discovered = await plugin_manager_default.discoverPlugins(miokuConfig);
357
+ logger.info(`O.o 共发现 ${discovered.length} 个插件: ${discovered.map((p) => p.name).join(", ")}`);
358
+ const runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR);
359
+ return prepareRuntimePluginLinks(discovered, runtimePluginsDir);
405
360
  }
361
+ async function discoverAndValidateServices(miokuConfig) {
362
+ logger.info("o.O Miku 正在翻找服务..");
363
+ await service_manager_default.discoverServices(miokuConfig);
364
+ const requiredServices = plugin_manager_default.collectRequiredServices();
365
+ const missing = await service_manager_default.checkMissingServices(requiredServices);
366
+ if (missing.length > 0) logger.warn(`发现缺失服务: ${missing.join(", ")}`);
367
+ }
368
+ function applyPluginAllowlist(miokuConfig, botConfig$1, linkedPluginNames) {
369
+ botConfig$1.plugins_dir = DEFAULT_RUNTIME_PLUGINS_DIR;
370
+ if (miokuConfig.plugins !== void 0) return;
371
+ for (const name of linkedPluginNames) if (!botConfig$1.plugins.includes(name)) botConfig$1.plugins.push(name);
372
+ }
373
+ async function bootstrapMioku(deps) {
374
+ const { cwd, botConfig: botConfig$1, startMioki } = deps;
375
+ const miokuConfig = await readMiokuConfig();
376
+ ensureRuntimeDirectories();
377
+ const linkedPluginNames = await discoverAndLinkPlugins(miokuConfig);
378
+ await discoverAndValidateServices(miokuConfig);
379
+ applyPluginAllowlist(miokuConfig, botConfig$1, linkedPluginNames);
380
+ await startMioki({ cwd });
381
+ }
382
+
383
+ //#endregion
384
+ //#region src/core/plugin-artifact-registry.ts
406
385
  function isAISkill(value) {
407
- return value && typeof value === "object" && typeof value.name === "string" && Array.isArray(value.tools);
386
+ return !!value && typeof value === "object" && typeof value.name === "string" && Array.isArray(value.tools);
408
387
  }
409
388
  function extractSkills(moduleExports) {
410
389
  const candidates = [
@@ -425,14 +404,11 @@ async function resolveSkillsEntry(pluginPath) {
425
404
  if (await pathExists(jsPath)) return jsPath;
426
405
  return null;
427
406
  }
428
- /**
429
- * Auto-register plugin help manifests and AI skills
430
- */
431
407
  async function registerPluginArtifacts(ctx) {
432
- const enabledPlugins = new Set(Array.isArray(ctx.botConfig?.plugins) ? ctx.botConfig.plugins : []);
408
+ const enabledPlugins = new Set(mioki.botConfig.plugins ?? []);
433
409
  const pluginMetadata = plugin_manager_default.getAllMetadata().filter((metadata) => enabledPlugins.size > 0 ? enabledPlugins.has(metadata.name) : true);
434
- const helpService = ctx.services?.help;
435
- const aiService = ctx.services?.ai;
410
+ const helpService = ctx.services.help;
411
+ const aiService = ctx.services.ai;
436
412
  if (helpService) {
437
413
  let helpCount = 0;
438
414
  for (const metadata of pluginMetadata) {
@@ -459,7 +435,7 @@ async function registerPluginArtifacts(ctx) {
459
435
  skillCount += 1;
460
436
  }
461
437
  } catch (error) {
462
- logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error?.message || error}`);
438
+ logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error}`);
463
439
  }
464
440
  }
465
441
  logger.info(`[plugin-artifacts] Registered ${skillCount} skill(s)`);
@@ -469,136 +445,97 @@ async function registerPluginArtifacts(ctx) {
469
445
  //#region src/core/service-config.ts
470
446
  const SERVICE_CONFIG_ROOT = "service";
471
447
  function resolveConfigDir(serviceName) {
472
- return node_path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
448
+ return path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
473
449
  }
474
450
  function resolveConfigPath(serviceName, configName) {
475
- return node_path.join(resolveConfigDir(serviceName), `${configName}.json`);
451
+ return path.join(resolveConfigDir(serviceName), `${configName}.json`);
476
452
  }
477
453
  function ensureDir(serviceName) {
478
454
  const dir = resolveConfigDir(serviceName);
479
- if (!node_fs.existsSync(dir)) node_fs.mkdirSync(dir, { recursive: true });
455
+ if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
480
456
  }
481
- function registerServiceConfig(serviceName, configName, defaults) {
457
+ async function registerServiceConfig(serviceName, configName, defaults) {
482
458
  ensureDir(serviceName);
483
459
  const configPath = resolveConfigPath(serviceName, configName);
484
- if (!node_fs.existsSync(configPath)) node_fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), "utf-8");
460
+ if (!(0, fs.existsSync)(configPath)) await fs_promises.writeFile(configPath, JSON.stringify(defaults, null, 2), "utf-8");
485
461
  }
486
- function getServiceConfig(serviceName, configName) {
462
+ async function getServiceConfig(serviceName, configName) {
487
463
  const configPath = resolveConfigPath(serviceName, configName);
488
464
  try {
489
- if (node_fs.existsSync(configPath)) return JSON.parse(node_fs.readFileSync(configPath, "utf-8"));
490
- } catch {}
491
- return {};
465
+ return JSON.parse(await fs_promises.readFile(configPath, "utf-8"));
466
+ } catch (error) {
467
+ if (!(0, fs.existsSync)(configPath)) return {};
468
+ logger.warn(`[service-config] 读取 ${serviceName}/${configName} 失败: ${error}`);
469
+ return {};
470
+ }
492
471
  }
493
- function updateServiceConfig(serviceName, configName, value) {
472
+ async function updateServiceConfig(serviceName, configName, value) {
494
473
  ensureDir(serviceName);
495
- node_fs.writeFileSync(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
474
+ await fs_promises.writeFile(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
496
475
  }
497
- function getServiceConfigs(serviceName) {
476
+ async function getServiceConfigs(serviceName) {
498
477
  const dir = resolveConfigDir(serviceName);
499
- if (!node_fs.existsSync(dir)) return {};
478
+ if (!(0, fs.existsSync)(dir)) return {};
500
479
  const result = {};
501
- const files = node_fs.readdirSync(dir).filter((name) => name.endsWith(".json"));
502
- for (const file of files) {
503
- const filePath = node_path.join(dir, file);
504
- try {
505
- result[node_path.basename(file, ".json")] = JSON.parse(node_fs.readFileSync(filePath, "utf-8"));
506
- } catch {}
480
+ const files = (await fs_promises.readdir(dir)).filter((name) => name.endsWith(".json"));
481
+ for (const file of files) try {
482
+ result[path.basename(file, ".json")] = JSON.parse(await fs_promises.readFile(path.join(dir, file), "utf-8"));
483
+ } catch (error) {
484
+ logger.warn(`[service-config] 读取 ${serviceName}/${file} 失败: ${error}`);
507
485
  }
508
486
  return result;
509
487
  }
510
- function deleteServiceConfig(serviceName, configName) {
488
+ async function deleteServiceConfig(serviceName, configName) {
511
489
  const configPath = resolveConfigPath(serviceName, configName);
512
- if (!node_fs.existsSync(configPath)) return false;
513
- node_fs.unlinkSync(configPath);
490
+ if (!(0, fs.existsSync)(configPath)) return false;
491
+ await fs_promises.unlink(configPath);
514
492
  return true;
515
493
  }
516
494
 
517
495
  //#endregion
518
- //#region src/service-types.ts
496
+ //#region src/types.ts
519
497
  const TOOL_RESULT_FOLLOWUP_KEY = "__miokuFollowup";
520
498
 
521
499
  //#endregion
522
500
  //#region src/core/data-paths.ts
523
- /**
524
- * Get the data directory for a specific plugin
525
- * Returns: {cwd}/data/{pluginName}
526
- */
527
501
  function getPluginDataDir(pluginName) {
528
- const dataDir = path.join(process.cwd(), "data", pluginName);
529
- return dataDir;
502
+ return path.join(process.cwd(), "data", pluginName);
530
503
  }
531
- /**
532
- * Get the data directory for a service
533
- * Returns: {cwd}/data/{serviceName}
534
- */
535
504
  function getServiceDataDir(serviceName) {
536
- const dataDir = path.join(process.cwd(), "data", serviceName);
537
- return dataDir;
505
+ return path.join(process.cwd(), "data", serviceName);
538
506
  }
539
- /**
540
- * Get the main data directory
541
- * Returns: {cwd}/data
542
- */
543
507
  function getDataDir() {
544
508
  return path.join(process.cwd(), "data");
545
509
  }
546
- /**
547
- * Get the config directory for a plugin
548
- * Returns: {cwd}/config/{pluginName}
549
- */
550
510
  function getPluginConfigDir(pluginName) {
551
- const configDir = path.join(process.cwd(), "config", pluginName);
552
- return configDir;
511
+ return path.join(process.cwd(), "config", pluginName);
553
512
  }
554
- /**
555
- * Get the config directory for a service
556
- * Returns: {cwd}/config/service/{serviceName}
557
- */
558
513
  function getServiceConfigDir(serviceName) {
559
- const configDir = path.join(process.cwd(), "config", "service", serviceName);
560
- return configDir;
514
+ return path.join(process.cwd(), "config", "service", serviceName);
561
515
  }
562
- /**
563
- * Get the main config directory
564
- * Returns: {cwd}/config
565
- */
566
516
  function getConfigDir() {
567
517
  return path.join(process.cwd(), "config");
568
518
  }
569
- /**
570
- * Ensure a directory exists, creating it if necessary
571
- */
572
519
  function ensureDataDir(pluginName) {
573
520
  const dir = getPluginDataDir(pluginName);
574
- const { existsSync: existsSync$2, mkdirSync: mkdirSync$3 } = require("fs");
575
- if (!existsSync$2(dir)) mkdirSync$3(dir, { recursive: true });
521
+ if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
576
522
  return dir;
577
523
  }
578
524
 
579
525
  //#endregion
580
526
  //#region src/core/plugin-runtime-state.ts
581
- const runtimeState = {};
582
- /**
583
- * Get the runtime state for a plugin
584
- */
527
+ const store = getOrCreate("plugin-runtime-state", () => ({}));
585
528
  function getPluginRuntimeState(pluginName) {
586
- if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
587
- return runtimeState[pluginName];
529
+ if (!store[pluginName]) store[pluginName] = {};
530
+ return store[pluginName];
588
531
  }
589
- /**
590
- * Set/update the runtime state for a plugin
591
- */
592
532
  function setPluginRuntimeState(pluginName, state) {
593
- if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
594
- Object.assign(runtimeState[pluginName], state);
595
- return runtimeState[pluginName];
533
+ if (!store[pluginName]) store[pluginName] = {};
534
+ Object.assign(store[pluginName], state);
535
+ return store[pluginName];
596
536
  }
597
- /**
598
- * Reset the runtime state for a plugin
599
- */
600
537
  function resetPluginRuntimeState(pluginName) {
601
- delete runtimeState[pluginName];
538
+ delete store[pluginName];
602
539
  }
603
540
 
604
541
  //#endregion
@@ -606,48 +543,31 @@ function resetPluginRuntimeState(pluginName) {
606
543
  function definePlugin(plugin) {
607
544
  return plugin;
608
545
  }
609
- /**
610
- * Start Mioku with plugin and service discovery
611
- */
612
546
  async function start(options = {}) {
613
547
  const { cwd = process.cwd() } = options;
614
548
  if (cwd) process.chdir(cwd);
615
- const { start: startMioki, logger: logger$1, botConfig } = await import("mioki");
549
+ const { start: startMioki, logger: logger$1, botConfig: botConfig$1 } = await import("mioki");
616
550
  setMiokuLogger(logger$1);
617
- const packageJsonPath = path.join(process.cwd(), "package.json");
618
- let miokuConfig = {};
619
- if (fs.existsSync(packageJsonPath)) {
620
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
621
- miokuConfig = pkg.mioki || {};
622
- }
623
551
  logger$1.info("こんにちは..");
624
552
  logger$1.info("---------------------------------------");
625
553
  logger$1.info("---------- Mioku 正在启动 ------------");
626
554
  logger$1.info("---------------------------------------");
627
- const requiredDirs = [
628
- "data",
629
- "config",
630
- "temp"
631
- ];
632
- for (const dir of requiredDirs) if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
633
- logger$1.info("O.o Miku 正在翻找插件..");
634
- const discoveredPlugins = await plugin_manager_default.discoverPlugins(miokuConfig);
635
- logger$1.info(`O.o 共发现 ${discoveredPlugins.length} 个插件: ${discoveredPlugins.map((p) => p.name).join(", ")}`);
636
- const runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR);
637
- const linkedPluginNames = await prepareRuntimePluginLinks(discoveredPlugins, runtimePluginsDir);
638
- botConfig.plugins_dir = DEFAULT_RUNTIME_PLUGINS_DIR;
639
- logger$1.info("o.O Miku 正在翻找服务..");
640
- await service_manager_default.discoverServices(miokuConfig);
641
- const requiredServices = plugin_manager_default.collectRequiredServices();
642
- const missingServices = await service_manager_default.checkMissingServices(requiredServices);
643
- if (missingServices.length > 0) logger$1.warn(`发现缺失服务: ${missingServices.join(", ")}`);
644
- const userSpecifiedPlugins = miokuConfig.plugins !== void 0;
645
- if (!userSpecifiedPlugins) {
646
- for (const name of linkedPluginNames) if (!botConfig.plugins.includes(name)) botConfig.plugins.push(name);
555
+ await bootstrapMioku({
556
+ cwd,
557
+ botConfig: botConfig$1,
558
+ startMioki
559
+ });
560
+ }
561
+ function readVersion() {
562
+ try {
563
+ const here = path.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
564
+ const pkgPath = path.join(here, "..", "package.json");
565
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
566
+ } catch {
567
+ return "0.0.0";
647
568
  }
648
- await startMioki({ cwd });
649
569
  }
650
- const version = "1.0.0";
570
+ const version = readVersion();
651
571
 
652
572
  //#endregion
653
573
  exports.TOOL_RESULT_FOLLOWUP_KEY = TOOL_RESULT_FOLLOWUP_KEY;