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