// 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');
}
};
}
}