mioku 0.9.2 → 0.9.4

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,18 +404,19 @@ 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
- */
407
+ function hasPublicKeyword(keywords) {
408
+ return Array.isArray(keywords) && keywords.includes("mioku");
409
+ }
431
410
  async function registerPluginArtifacts(ctx) {
432
- const enabledPlugins = new Set(Array.isArray(ctx.botConfig?.plugins) ? ctx.botConfig.plugins : []);
411
+ const enabledPlugins = new Set(mioki.botConfig.plugins ?? []);
433
412
  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;
413
+ const helpService = ctx.services.help;
414
+ const aiService = ctx.services.ai;
436
415
  if (helpService) {
437
416
  let helpCount = 0;
438
417
  for (const metadata of pluginMetadata) {
439
418
  if (!metadata.config.help) continue;
419
+ if (!hasPublicKeyword(metadata.packageJson?.keywords)) continue;
440
420
  helpService.registerHelp(metadata.name, metadata.config.help);
441
421
  helpCount += 1;
442
422
  }
@@ -459,7 +439,7 @@ async function registerPluginArtifacts(ctx) {
459
439
  skillCount += 1;
460
440
  }
461
441
  } catch (error) {
462
- logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error?.message || error}`);
442
+ logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error}`);
463
443
  }
464
444
  }
465
445
  logger.info(`[plugin-artifacts] Registered ${skillCount} skill(s)`);
@@ -469,136 +449,97 @@ async function registerPluginArtifacts(ctx) {
469
449
  //#region src/core/service-config.ts
470
450
  const SERVICE_CONFIG_ROOT = "service";
471
451
  function resolveConfigDir(serviceName) {
472
- return node_path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
452
+ return path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
473
453
  }
474
454
  function resolveConfigPath(serviceName, configName) {
475
- return node_path.join(resolveConfigDir(serviceName), `${configName}.json`);
455
+ return path.join(resolveConfigDir(serviceName), `${configName}.json`);
476
456
  }
477
457
  function ensureDir(serviceName) {
478
458
  const dir = resolveConfigDir(serviceName);
479
- if (!node_fs.existsSync(dir)) node_fs.mkdirSync(dir, { recursive: true });
459
+ if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
480
460
  }
481
- function registerServiceConfig(serviceName, configName, defaults) {
461
+ async function registerServiceConfig(serviceName, configName, defaults) {
482
462
  ensureDir(serviceName);
483
463
  const configPath = resolveConfigPath(serviceName, configName);
484
- if (!node_fs.existsSync(configPath)) node_fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), "utf-8");
464
+ if (!(0, fs.existsSync)(configPath)) await fs_promises.writeFile(configPath, JSON.stringify(defaults, null, 2), "utf-8");
485
465
  }
486
- function getServiceConfig(serviceName, configName) {
466
+ async function getServiceConfig(serviceName, configName) {
487
467
  const configPath = resolveConfigPath(serviceName, configName);
488
468
  try {
489
- if (node_fs.existsSync(configPath)) return JSON.parse(node_fs.readFileSync(configPath, "utf-8"));
490
- } catch {}
491
- return {};
469
+ return JSON.parse(await fs_promises.readFile(configPath, "utf-8"));
470
+ } catch (error) {
471
+ if (!(0, fs.existsSync)(configPath)) return {};
472
+ logger.warn(`[service-config] 读取 ${serviceName}/${configName} 失败: ${error}`);
473
+ return {};
474
+ }
492
475
  }
493
- function updateServiceConfig(serviceName, configName, value) {
476
+ async function updateServiceConfig(serviceName, configName, value) {
494
477
  ensureDir(serviceName);
495
- node_fs.writeFileSync(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
478
+ await fs_promises.writeFile(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
496
479
  }
497
- function getServiceConfigs(serviceName) {
480
+ async function getServiceConfigs(serviceName) {
498
481
  const dir = resolveConfigDir(serviceName);
499
- if (!node_fs.existsSync(dir)) return {};
482
+ if (!(0, fs.existsSync)(dir)) return {};
500
483
  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 {}
484
+ const files = (await fs_promises.readdir(dir)).filter((name) => name.endsWith(".json"));
485
+ for (const file of files) try {
486
+ result[path.basename(file, ".json")] = JSON.parse(await fs_promises.readFile(path.join(dir, file), "utf-8"));
487
+ } catch (error) {
488
+ logger.warn(`[service-config] 读取 ${serviceName}/${file} 失败: ${error}`);
507
489
  }
508
490
  return result;
509
491
  }
510
- function deleteServiceConfig(serviceName, configName) {
492
+ async function deleteServiceConfig(serviceName, configName) {
511
493
  const configPath = resolveConfigPath(serviceName, configName);
512
- if (!node_fs.existsSync(configPath)) return false;
513
- node_fs.unlinkSync(configPath);
494
+ if (!(0, fs.existsSync)(configPath)) return false;
495
+ await fs_promises.unlink(configPath);
514
496
  return true;
515
497
  }
516
498
 
517
499
  //#endregion
518
- //#region src/service-types.ts
500
+ //#region src/types.ts
519
501
  const TOOL_RESULT_FOLLOWUP_KEY = "__miokuFollowup";
520
502
 
521
503
  //#endregion
522
504
  //#region src/core/data-paths.ts
523
- /**
524
- * Get the data directory for a specific plugin
525
- * Returns: {cwd}/data/{pluginName}
526
- */
527
505
  function getPluginDataDir(pluginName) {
528
- const dataDir = path.join(process.cwd(), "data", pluginName);
529
- return dataDir;
506
+ return path.join(process.cwd(), "data", pluginName);
530
507
  }
531
- /**
532
- * Get the data directory for a service
533
- * Returns: {cwd}/data/{serviceName}
534
- */
535
508
  function getServiceDataDir(serviceName) {
536
- const dataDir = path.join(process.cwd(), "data", serviceName);
537
- return dataDir;
509
+ return path.join(process.cwd(), "data", serviceName);
538
510
  }
539
- /**
540
- * Get the main data directory
541
- * Returns: {cwd}/data
542
- */
543
511
  function getDataDir() {
544
512
  return path.join(process.cwd(), "data");
545
513
  }
546
- /**
547
- * Get the config directory for a plugin
548
- * Returns: {cwd}/config/{pluginName}
549
- */
550
514
  function getPluginConfigDir(pluginName) {
551
- const configDir = path.join(process.cwd(), "config", pluginName);
552
- return configDir;
515
+ return path.join(process.cwd(), "config", pluginName);
553
516
  }
554
- /**
555
- * Get the config directory for a service
556
- * Returns: {cwd}/config/service/{serviceName}
557
- */
558
517
  function getServiceConfigDir(serviceName) {
559
- const configDir = path.join(process.cwd(), "config", "service", serviceName);
560
- return configDir;
518
+ return path.join(process.cwd(), "config", "service", serviceName);
561
519
  }
562
- /**
563
- * Get the main config directory
564
- * Returns: {cwd}/config
565
- */
566
520
  function getConfigDir() {
567
521
  return path.join(process.cwd(), "config");
568
522
  }
569
- /**
570
- * Ensure a directory exists, creating it if necessary
571
- */
572
523
  function ensureDataDir(pluginName) {
573
524
  const dir = getPluginDataDir(pluginName);
574
- const { existsSync: existsSync$2, mkdirSync: mkdirSync$3 } = require("fs");
575
- if (!existsSync$2(dir)) mkdirSync$3(dir, { recursive: true });
525
+ if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
576
526
  return dir;
577
527
  }
578
528
 
579
529
  //#endregion
580
530
  //#region src/core/plugin-runtime-state.ts
581
- const runtimeState = {};
582
- /**
583
- * Get the runtime state for a plugin
584
- */
531
+ const store = getOrCreate("plugin-runtime-state", () => ({}));
585
532
  function getPluginRuntimeState(pluginName) {
586
- if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
587
- return runtimeState[pluginName];
533
+ if (!store[pluginName]) store[pluginName] = {};
534
+ return store[pluginName];
588
535
  }
589
- /**
590
- * Set/update the runtime state for a plugin
591
- */
592
536
  function setPluginRuntimeState(pluginName, state) {
593
- if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
594
- Object.assign(runtimeState[pluginName], state);
595
- return runtimeState[pluginName];
537
+ if (!store[pluginName]) store[pluginName] = {};
538
+ Object.assign(store[pluginName], state);
539
+ return store[pluginName];
596
540
  }
597
- /**
598
- * Reset the runtime state for a plugin
599
- */
600
541
  function resetPluginRuntimeState(pluginName) {
601
- delete runtimeState[pluginName];
542
+ delete store[pluginName];
602
543
  }
603
544
 
604
545
  //#endregion
@@ -606,48 +547,31 @@ function resetPluginRuntimeState(pluginName) {
606
547
  function definePlugin(plugin) {
607
548
  return plugin;
608
549
  }
609
- /**
610
- * Start Mioku with plugin and service discovery
611
- */
612
550
  async function start(options = {}) {
613
551
  const { cwd = process.cwd() } = options;
614
552
  if (cwd) process.chdir(cwd);
615
- const { start: startMioki, logger: logger$1, botConfig } = await import("mioki");
553
+ const { start: startMioki, logger: logger$1, botConfig: botConfig$1 } = await import("mioki");
616
554
  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
555
  logger$1.info("こんにちは..");
624
556
  logger$1.info("---------------------------------------");
625
557
  logger$1.info("---------- Mioku 正在启动 ------------");
626
558
  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);
559
+ await bootstrapMioku({
560
+ cwd,
561
+ botConfig: botConfig$1,
562
+ startMioki
563
+ });
564
+ }
565
+ function readVersion() {
566
+ try {
567
+ const here = path.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
568
+ const pkgPath = path.join(here, "..", "package.json");
569
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
570
+ } catch {
571
+ return "0.0.0";
647
572
  }
648
- await startMioki({ cwd });
649
573
  }
650
- const version = "1.0.0";
574
+ const version = readVersion();
651
575
 
652
576
  //#endregion
653
577
  exports.TOOL_RESULT_FOLLOWUP_KEY = TOOL_RESULT_FOLLOWUP_KEY;