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