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