"use strict"; const fs = require("fs"); const path = require("path"); function safeReadJson(filePath, logFn) { try { const txt = fs.readFileSync(filePath, "utf8"); if (!txt || txt.trim().length === 0) return null; return JSON.parse(txt); } catch (e) { if (logFn) logFn("safeReadJson failed", filePath, e && e.message ? e.message : e); return null; } } function isDirectory(p) { try { return fs.statSync(p).isDirectory(); } catch (e) { return false; } } function scanSkillEntry(skillDir, folderName, opts) { opts = opts || {}; const menuJsonPath = path.join(skillDir, "menuEntry.json"); const menuJson = safeReadJson(menuJsonPath, opts.log); if (!menuJson) return null; if (menuJson.hidden === true) return null; const entry = { id: folderName, type: menuJson.type || "skill", title: menuJson.title || menuJson.label || folderName, icon: menuJson.icon || menuJson.iconSrc || (opts.defaultIcon || "resources/icons/settings.png"), color: menuJson.color || menuJson.colors || (opts.defaultColor || "default"), description: menuJson.description || "", path: skillDir, order: typeof menuJson.order === "number" ? menuJson.order : (typeof opts.defaultOrder === "number" ? opts.defaultOrder : 100), skillId: menuJson.skillId || folderName, submenuTitle: menuJson.submenuTitle || menuJson.title || folderName }; if (entry.type === "submenu") { entry.children = scanSubmenuChildren(skillDir, opts); } // Legacy support if (menuJson.isSubmenu === true && entry.type !== "submenu") { entry.type = "submenu"; entry.children = scanSubmenuChildren(skillDir, opts); } return entry; } function scanSubmenuChildren(submenuDir, opts) { const children = []; let items; try { items = fs.readdirSync(submenuDir); } catch (e) { return children; } items.forEach(function (name) { if (!name || name.charAt(0) === ".") return; if (name === "@be" || name === "node_modules") return; const childPath = path.join(submenuDir, name); if (!isDirectory(childPath)) return; const entry = scanSkillEntry(childPath, name, opts); if (entry && entry.type === "skill") { children.push(entry); } }); children.sort(function (a, b) { if (a.order !== b.order) return a.order - b.order; return String(a.title || "").localeCompare(String(b.title || "")); }); return children; } function scanAllMenuEntries(skillsRoot, opts) { opts = opts || {}; const entries = []; if (!skillsRoot || !fs.existsSync(skillsRoot)) { if (opts.log) opts.log("skills root missing", skillsRoot); return entries; } let children; try { children = fs.readdirSync(skillsRoot); } catch (e) { if (opts.log) opts.log("failed to read skills root", skillsRoot, e && e.message ? e.message : e); return entries; } children.forEach(function (name) { if (!name || name.charAt(0) === ".") return; if (name === "@be" || name === "node_modules") return; const skillDir = path.join(skillsRoot, name); if (!isDirectory(skillDir)) return; const entry = scanSkillEntry(skillDir, name, opts); if (entry) entries.push(entry); }); entries.sort(function (a, b) { if (a.order !== b.order) return a.order - b.order; return String(a.title || "").localeCompare(String(b.title || "")); }); return entries; } function collectDynamicSkillIds(entries) { const ids = new Set(); (entries || []).forEach(function (e) { if (!e) return; if (e.type === "skill") { if (e.skillId) ids.add(e.skillId); if (e.id) ids.add(e.id); } if (e.type === "submenu" && e.children && e.children.length) { e.children.forEach(function (c) { if (!c) return; if (c.skillId) ids.add(c.skillId); if (c.id) ids.add(c.id); }); } }); return ids; } module.exports = { scanAllMenuEntries: scanAllMenuEntries, scanSubmenuChildren: scanSubmenuChildren, collectDynamicSkillIds: collectDynamicSkillIds };