aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-modmgr.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-11 18:00:19 +0200
committerAgando <roduccomm@gmail.com>2026-06-11 18:00:19 +0200
commit76e26bb6f16e35bffbf76896957bb9753241833c (patch)
tree25bd27029c2cad1d46208bb3ae026e8272e0a6bf /modules/core/module-modmgr.js
parentc3d5fe64c268d7ca85d1e303b98201391f28c9e9 (diff)
auto: 2026-06-11_18:00 changes
Diffstat (limited to 'modules/core/module-modmgr.js')
-rw-r--r--modules/core/module-modmgr.js321
1 files changed, 317 insertions, 4 deletions
diff --git a/modules/core/module-modmgr.js b/modules/core/module-modmgr.js
index 32bb2cc..ec8c384 100644
--- a/modules/core/module-modmgr.js
+++ b/modules/core/module-modmgr.js
@@ -1,18 +1,65 @@
// Extracted from newOS4 index.html — ModuleManagerModule
+// v4.5.43: Enhanced with module-info, module-list, module-stats, module-disable/enable,
+// module-reload, module-reset. Categories: CORE/EXT/FOUNDATION.
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)',
+ 'modules-list': 'modules-list [core|ext] — List all available modules from the module index',
+ 'module-list': 'module-list [flags] — List loaded modules grouped by category. Flags: --flat, --core, --ext, --foundation, --lazy, --eager, --loaded, --unloaded, --search=<pat>, --failed, --disabled',
+ 'module-info': 'module-info <name> — Show module details: path, status, size, type, source, commands list',
+ 'module-stats': 'module-stats — Show aggregate module statistics (loaded/unloaded/lazy/failed/disabled + total size)',
+ 'module-load': 'module-load <name> — Mark a lazy module as loaded',
+ 'module-unload': 'module-unload <name> — Mark a module as unloaded',
+ 'module-reload': 'module-reload <name> — Unload then reload a module (shows diff)',
+ 'module-reset': 'module-reset <name> — Reset a module to a clean state (clears its state, keeps commands)',
+ 'module-disable': 'module-disable <name> — Disable a module from auto-loading on refresh (persists)',
+ 'module-enable': 'module-enable <name> — Re-enable a previously disabled module',
'loadmod': 'loadmod <url> — Load a module from a URL',
'removemod': 'removemod <name> — Remove a loaded module by name',
- 'startup-modules': 'startup-modules — Open the startup modules selector',
- 'load-local': 'load-local <path> — Load a module from VFS or URL',
- 'loadlocal': 'loadlocal <path> — Alias for load-local',
};
}
+ // Helper: read disabled list from localStorage
+ _getDisabled() {
+ try { return JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]'); }
+ catch { return []; }
+ }
+ _setDisabled(list) {
+ localStorage.setItem('webx11_module_disabled', JSON.stringify(list));
+ }
+ // Helper: get category for a module (CORE / EXTENSION / FOUNDATION / UNKNOWN)
+ _getCategory(mod) {
+ if (!mod) return 'UNKNOWN';
+ if (mod.path && mod.path.includes('/modules/ext/')) return 'EXTENSION';
+ if (mod.path && mod.path.includes('/modules/core/')) return 'CORE';
+ if (mod.type === 'foundation' || (mod.path && mod.path.includes('FOUNDATION'))) return 'FOUNDATION';
+ return 'CORE'; // default
+ }
+ // Helper: collect all modules (from registry) into an array of {name, mod}
+ _collectAll() {
+ const mm = this.terminal.moduleManager;
+ const result = [];
+ const allMods = mm.modules || new Map();
+ // moduleManager.modules is a Map<name, moduleInstance>
+ if (allMods instanceof Map) {
+ for (const [name, mod] of allMods) {
+ result.push({ name, mod });
+ }
+ } else {
+ for (const name of Object.keys(allMods)) {
+ result.push({ name, mod: allMods[name] });
+ }
+ }
+ return result;
+ }
+ // Helper: get a single module by name
+ _getMod(name) {
+ const mm = this.terminal.moduleManager;
+ if (mm.modules instanceof Map) return mm.modules.get(name);
+ return mm.modules[name];
+ }
registerCommands() {
this.commands['modules'] = () => { this.terminal.openModuleManager(); };
this.commands['loadmod'] = async (args) => {
@@ -70,5 +117,271 @@
this.terminal.print(`modules-list error: ${e.message}`, 'error');
}
};
+
+ // ── v4.5.43 enhanced module commands ─────────────────────
+
+ // module-list [flags] — grouped by category with filters
+ this.commands['module-list'] = (args) => {
+ const flags = args.filter(a => a.startsWith('--'));
+ const allMods = this._collectAll();
+ const disabled = this._getDisabled();
+
+ // Apply filters
+ let filtered = allMods;
+ if (flags.includes('--core')) {
+ filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'CORE');
+ }
+ if (flags.includes('--ext')) {
+ filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'EXTENSION');
+ }
+ if (flags.includes('--foundation')) {
+ filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'FOUNDATION');
+ }
+ if (flags.includes('--lazy')) {
+ filtered = filtered.filter(({ mod }) => mod.lazy);
+ }
+ if (flags.includes('--eager')) {
+ filtered = filtered.filter(({ mod }) => !mod.lazy);
+ }
+ if (flags.includes('--loaded')) {
+ filtered = filtered.filter(({ mod }) => mod.loaded !== false);
+ }
+ if (flags.includes('--unloaded')) {
+ filtered = filtered.filter(({ mod }) => mod.loaded === false);
+ }
+ if (flags.includes('--failed')) {
+ filtered = filtered.filter(({ mod }) => mod.status === 'failed' || mod.error);
+ }
+ if (flags.includes('--disabled')) {
+ filtered = filtered.filter(({ name }) => disabled.includes(name));
+ }
+ const searchFlag = flags.find(f => f.startsWith('--search='));
+ if (searchFlag) {
+ const pattern = searchFlag.split('=')[1].toLowerCase();
+ filtered = filtered.filter(({ name, mod }) =>
+ name.toLowerCase().includes(pattern) ||
+ (mod.description || '').toLowerCase().includes(pattern)
+ );
+ }
+
+ if (filtered.length === 0) {
+ this.terminal.print('No modules match filters', 'info');
+ return;
+ }
+
+ const flat = flags.includes('--flat');
+
+ if (flat) {
+ // Old format: wall of text
+ this.terminal.print(`📦 Modules (${filtered.length} of ${allMods.length})`, 'header');
+ for (const { name, mod } of filtered) {
+ const size = mod.size ? `${(mod.size / 1024).toFixed(1)}KB` : '?KB';
+ const lazy = mod.lazy ? ' [L]' : '';
+ const loaded = mod.loaded !== false ? '✓' : '✗';
+ const isDisabled = disabled.includes(name) ? ' [DISABLED]' : '';
+ this.terminal.print(` ${loaded} ${name.padEnd(28)} ${size.padStart(8)}${lazy}${isDisabled}`);
+ }
+ } else {
+ // Grouped by category
+ const groups = { CORE: [], EXTENSION: [], FOUNDATION: [], UNKNOWN: [] };
+ for (const { name, mod } of filtered) {
+ const cat = this._getCategory(mod);
+ groups[cat].push({ name, mod });
+ }
+ for (const cat of ['CORE', 'EXTENSION', 'FOUNDATION', 'UNKNOWN']) {
+ if (groups[cat].length === 0) continue;
+ this.terminal.print(`── ${cat} (${groups[cat].length}) ──`, 'info');
+ for (const { name, mod } of groups[cat]) {
+ const size = mod.size ? `${(mod.size / 1024).toFixed(1)}KB` : '?KB';
+ const lazy = mod.lazy ? ' [L]' : '';
+ const loaded = mod.loaded !== false ? '✓' : '✗';
+ const isDisabled = disabled.includes(name) ? ' [DISABLED]' : '';
+ const errMark = (mod.status === 'failed' || mod.error) ? ' ⚠️' : '';
+ this.terminal.print(` ${loaded} ${name.padEnd(28)} ${size.padStart(8)}${lazy}${isDisabled}${errMark}`);
+ }
+ }
+ }
+
+ // Aggregate summary
+ const totalSize = filtered.reduce((sum, { mod }) => sum + (mod.size || 0), 0);
+ const lazyCount = filtered.filter(({ mod }) => mod.lazy).length;
+ const failedCount = filtered.filter(({ mod }) => mod.status === 'failed' || mod.error).length;
+ const disabledCount = filtered.filter(({ name }) => disabled.includes(name)).length;
+ this.terminal.print('─'.repeat(50), 'text-muted');
+ this.terminal.print(
+ `Total: ${filtered.length} modules, ${(totalSize / 1024).toFixed(1)}KB, ` +
+ `${lazyCount} lazy, ${failedCount} failed, ${disabledCount} disabled`,
+ failedCount > 0 ? 'warning' : 'info'
+ );
+ };
+
+ // module-info <name> — detailed view including commands
+ this.commands['module-info'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-info <name>', 'warning');
+ const mod = this._getMod(args[0]);
+ if (!mod) return this.terminal.print(`Module not found: ${args[0]}`, 'error');
+
+ const disabled = this._getDisabled();
+ const isDisabled = disabled.includes(args[0]);
+ const cat = this._getCategory(mod);
+ const sizeKB = mod.size ? (mod.size / 1024).toFixed(1) : '?';
+ const commandCount = mod.commands ? Object.keys(mod.commands).length : 0;
+ const status = mod.status || (mod.loaded !== false ? 'loaded' : 'unloaded');
+
+ this.terminal.print(`📋 Module: ${args[0]}`, 'header');
+ this.terminal.print(` Status: ${status}${isDisabled ? ' [DISABLED]' : ''}`);
+ this.terminal.print(` Category: ${cat}`);
+ this.terminal.print(` Type: ${mod.type || 'unknown'}`);
+ this.terminal.print(` Path: ${mod.path || 'unknown'}`);
+ this.terminal.print(` Size: ${sizeKB} KB`);
+ this.terminal.print(` Lazy: ${mod.lazy ? 'yes' : 'no'}`);
+ this.terminal.print(` Commands: ${commandCount}`);
+
+ if (mod.description) {
+ this.terminal.print(` Description: ${mod.description}`);
+ }
+
+ // List the commands
+ if (mod.commands && commandCount > 0) {
+ this.terminal.print(` ── Commands (${commandCount}) ──`, 'info');
+ const cmds = Object.keys(mod.commands).sort();
+ // Print in 2 columns
+ const half = Math.ceil(cmds.length / 2);
+ for (let i = 0; i < half; i++) {
+ const left = cmds[i] || '';
+ const right = cmds[i + half] || '';
+ this.terminal.print(` ${left.padEnd(24)} ${right}`);
+ }
+ }
+
+ if (mod.error) {
+ this.terminal.print(` ⚠️ Error: ${mod.error}`, 'error');
+ }
+ };
+
+ // module-stats — aggregate view
+ this.commands['module-stats'] = () => {
+ const all = this._collectAll();
+ const disabled = this._getDisabled();
+ const loaded = all.filter(({ mod }) => mod.loaded !== false).length;
+ const unloaded = all.filter(({ mod }) => mod.loaded === false).length;
+ const lazy = all.filter(({ mod }) => mod.lazy).length;
+ const eager = all.length - lazy;
+ const failed = all.filter(({ mod }) => mod.status === 'failed' || mod.error).length;
+ const disabledCount = all.filter(({ name }) => disabled.includes(name)).length;
+ const totalSize = all.reduce((sum, { mod }) => sum + (mod.size || 0), 0);
+ const totalCmds = all.reduce((sum, { mod }) => sum + (mod.commands ? Object.keys(mod.commands).length : 0), 0);
+
+ this.terminal.print('📊 Module Stats', 'header');
+ this.terminal.print(` Total: ${all.length} modules`);
+ this.terminal.print(` Loaded: ${loaded}`);
+ this.terminal.print(` Unloaded: ${unloaded}`);
+ this.terminal.print(` Lazy: ${lazy}`);
+ this.terminal.print(` Eager: ${eager}`);
+ this.terminal.print(` Failed: ${failed}`, failed > 0 ? 'warning' : '');
+ this.terminal.print(` Disabled: ${disabledCount}`, disabledCount > 0 ? 'warning' : '');
+ this.terminal.print(` Total size: ${(totalSize / 1024).toFixed(1)} KB`);
+ this.terminal.print(` Total cmds: ${totalCmds}`);
+ };
+
+ // module-disable <name> — persists in localStorage
+ this.commands['module-disable'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-disable <name>', 'warning');
+ const name = args[0];
+ if (!this._getMod(name)) return this.terminal.print(`Module not found: ${name}`, 'error');
+ const list = this._getDisabled();
+ if (list.includes(name)) {
+ return this.terminal.print(`Module '${name}' is already disabled`, 'info');
+ }
+ list.push(name);
+ this._setDisabled(list);
+ this.terminal.print(`✅ Module '${name}' disabled (will not auto-load on next refresh)`, 'success');
+ this.terminal.print(` To re-enable: module-enable ${name}`, 'info');
+ };
+
+ // module-enable <name> — removes from disabled list
+ this.commands['module-enable'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-enable <name>', 'warning');
+ const name = args[0];
+ const list = this._getDisabled();
+ if (!list.includes(name)) {
+ return this.terminal.print(`Module '${name}' is not disabled`, 'info');
+ }
+ const newList = list.filter(n => n !== name);
+ this._setDisabled(newList);
+ this.terminal.print(`✅ Module '${name}' re-enabled (will auto-load on next refresh)`, 'success');
+ };
+
+ // module-load <name> — mark a lazy module as loaded
+ this.commands['module-load'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-load <name>', 'warning');
+ const name = args[0];
+ const mod = this._getMod(name);
+ if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error');
+ if (mod.loaded !== false) {
+ return this.terminal.print(`Module '${name}' is already loaded`, 'info');
+ }
+ mod.loaded = true;
+ this.terminal.print(`✅ Module '${name}' marked as loaded`, 'success');
+ };
+
+ // module-unload <name> — mark as unloaded
+ this.commands['module-unload'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-unload <name>', 'warning');
+ const name = args[0];
+ const mod = this._getMod(name);
+ if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error');
+ if (mod.loaded === false) {
+ return this.terminal.print(`Module '${name}' is already unloaded`, 'info');
+ }
+ mod.loaded = false;
+ this.terminal.print(`✅ Module '${name}' marked as unloaded (commands will not be available)`, 'success');
+ };
+
+ // module-reload <name> — unload + reload with diff
+ this.commands['module-reload'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-reload <name>', 'warning');
+ const name = args[0];
+ const mod = this._getMod(name);
+ if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error');
+ const wasLoaded = mod.loaded !== false;
+ const wasLazy = mod.lazy;
+ this.terminal.print(`🔄 Reloading '${name}' (was: ${wasLoaded ? 'loaded' : 'unloaded'}, lazy=${wasLazy})`, 'info');
+ mod.loaded = false;
+ setTimeout(() => {
+ mod.loaded = true;
+ this.terminal.print(`✅ Module '${name}' reloaded`, 'success');
+ const cmdCount = mod.commands ? Object.keys(mod.commands).length : 0;
+ this.terminal.print(` Now: loaded=true, lazy=${mod.lazy}, ${cmdCount} commands`, 'info');
+ }, 100);
+ };
+
+ // module-reset <name> — clear module state and reload
+ this.commands['module-reset'] = (args) => {
+ if (!args[0]) return this.terminal.print('Usage: module-reset <name>', 'warning');
+ const name = args[0];
+ const mod = this._getMod(name);
+ if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error');
+ this.terminal.print(`🔄 Resetting module '${name}' state...`, 'info');
+ // Unload
+ mod.loaded = false;
+ // Clear any state-related localStorage keys for this module
+ let clearedKeys = 0;
+ try {
+ for (let i = localStorage.length - 1; i >= 0; i--) {
+ const key = localStorage.key(i);
+ // Match module-specific keys (e.g., "webx11_<module>_..." or "webx11_<module>")
+ if (key && (key === `webx11_${name}` || key.startsWith(`webx11_${name}_`) || key.startsWith(`${name}_`))) {
+ localStorage.removeItem(key);
+ clearedKeys++;
+ }
+ }
+ } catch (e) {}
+ // Reload after a tick
+ setTimeout(() => {
+ mod.loaded = true;
+ this.terminal.print(`✅ Module '${name}' reset (cleared ${clearedKeys} localStorage keys)`, 'success');
+ }, 100);
+ };
}
}