// Extracted from newOS4 index.html — ModuleManagerModule class ModuleManagerModule { constructor() { this.commands = {}; this.terminal = null; } getHelpInfo() { return { 'modules': 'modules — Open the module manager GUI', 'listmods': 'listmods — List all loaded modules with status', 'modules-list': 'modules-list [core|ext] — List all available modules from the module index (46 total)', 'loadmod': 'loadmod — Load a module from a URL', 'removemod': 'removemod — Remove a loaded module by name', 'startup-modules': 'startup-modules — Open the startup modules selector', 'load-local': 'load-local — Load a module from VFS or URL', 'loadlocal': 'loadlocal — Alias for load-local', }; } registerCommands() { this.commands['modules'] = () => { this.terminal.openModuleManager(); }; this.commands['loadmod'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: loadmod ", 'warning'); await this.terminal.moduleManager.loadModuleFromURL(args[0]); this.terminal.renderModuleList(); }; this.commands['removemod'] = (args) => { if(!args[0]) return this.terminal.print("Usage: removemod ", 'warning'); if(this.terminal.moduleManager.unregisterModule(args[0])) { this.terminal.print(`Module '${args[0]}' removed`, 'success'); } else { this.terminal.print(`Module not found: ${args[0]}`, 'error'); } }; this.commands['listmods'] = () => { const mods = this.terminal.moduleManager.getModuleList(); if(mods.length === 0) return this.terminal.print("No modules loaded"); mods.forEach(m => { const status = m.loaded ? '✓' : '✗'; const badge = m.url ? '' : ' [Built-in]'; this.terminal.print(`${status} ${m.name}${badge}`); }); }; this.commands['modules-list'] = async (args) => { try { const filter = (args[0] || '').toLowerCase(); const resp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || '')); if (!resp.ok) return this.terminal.print('Failed to load module index', 'error'); const idx = await resp.json(); let mods = Object.values(idx.modules); if (filter === 'core' || filter === 'ext') mods = mods.filter(m => m.type === filter); if (filter === 'infra') mods = mods.filter(m => m.layer === 'infrastructure'); const loaded = this.terminal.moduleManager.modules || {}; const loadedCount = Object.keys(loaded).length; const byLayer = {}; mods.forEach(m => { const l = m.layer || m.type || 'other'; if (!byLayer[l]) byLayer[l] = []; byLayer[l].push(m); }); const layerSummary = Object.entries(byLayer).map(([l, ms]) => `${ms.length} ${l}`).join(', '); this.terminal.print(`📦 Module Index (${mods.length} modules: ${layerSummary} — ${loadedCount} loaded)`, 'header'); for (const [layer, layerMods] of Object.entries(byLayer)) { this.terminal.print(` ── ${layer.toUpperCase()} (${layerMods.length}) ──`, 'info'); for (const m of layerMods) { const isLoaded = loaded[m.class] ? '✓' : ' '; const layerTag = m.layer === 'infrastructure' ? '[infra]' : m.type === 'core' ? '[core]' : '[ext]'; const cmdHint = m.commands.length > 0 ? ` ${m.commands.slice(0, 4).join(', ')}${m.commands.length > 4 ? '...' : ''}` : ''; const desc = m.description ? ` — ${m.description.substring(0, 60)}` : ''; this.terminal.print(` ${isLoaded} ${layerTag} ${m.name}${cmdHint}${desc}`, ''); } } } catch (e) { this.terminal.print(`modules-list error: ${e.message}`, 'error'); } }; } }