class WpmModule { constructor() { this.commands = {}; this.commandsMeta = { 'wpm': { argType: 'enum', enumFn: '_listSubcommands' }, }; this.terminal = null; this.indexUrl = 'https://webx11.duckdns.org/spaces/newos4/packages.json'; this.index = null; this.installed = this.loadInstalled(); } getHelpInfo() { return { 'wpm': 'wpm [...] — Web Package Manager for installing and managing modules' }; } loadInstalled() { try { const data = localStorage.getItem('webx11_wpm_installed'); return data ? JSON.parse(data) : {}; } catch (e) { return {}; } } saveInstalled() { localStorage.setItem('webx11_wpm_installed', JSON.stringify(this.installed)); } registerCommands() { this.commands['wpm'] = (args) => this.handle(args); } handle(args) { if (!args[0]) return this.terminal.print('Usage: wpm install , wpm list, wpm search , wpm update, wpm remove , wpm info ', 'warning'); const sub = args[0].toLowerCase(); if (sub === 'install') this.install(args[1]); else if (sub === 'list') this.list(); else if (sub === 'search') this.search(args.slice(1).join(' ')); else if (sub === 'update') this.update(); else if (sub === 'remove') this.remove(args[1]); else if (sub === 'info') this.info(args[1]); else this.terminal.print(`Unknown: ${sub}`, 'error'); } async loadIndex() { if (this.index) return this.index; try { this.terminal.print('Fetching package index...', 'info'); const res = await fetch(this.indexUrl); if (!res.ok) throw new Error('HTTP ' + res.status); this.index = await res.json(); return this.index; } catch (e) { this.terminal.print(`Failed to load index: ${e.message}`, 'error'); return null; } } async install(name) { if (!name) return this.terminal.print('Usage: wpm install ', 'warning'); const idx = await this.loadIndex(); if (!idx) return; const pkg = idx.packages[name]; if (!pkg) return this.terminal.print(`Package "${name}" not found. Try: wpm search `, 'error'); if (this.installed[name]) return this.terminal.print(`Package "${name}" already installed. Use wpm update to reinstall.`, 'warning'); this.terminal.print(`Installing ${pkg.name} v${pkg.version}...`, 'info'); this.terminal.print(` ${pkg.desc}`, 'info'); // P0: SRI — fetch source, compute SHA-256, verify integrity before loading try { const res = await fetch(pkg.url); if (!res.ok) throw new Error('HTTP ' + res.status); const source = await res.text(); // Compute SHA-256 via Web Crypto API const encoder = new TextEncoder(); const data = encoder.encode(source); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashHex = Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')).join(''); // Verify if package declares a hash if (pkg.hash) { if (hashHex !== pkg.hash) { this.terminal.print(`❌ INTEGRITY CHECK FAILED: hash mismatch!`, 'error'); this.terminal.print(` Expected: ${pkg.hash}`, 'error'); this.terminal.print(` Got: ${hashHex}`, 'error'); this.terminal.print(` Package may have been tampered with. Aborting.`, 'error'); return; } this.terminal.print(`✅ Integrity verified (SHA-256: ${hashHex.substring(0, 16)}...)`, 'success'); } else { this.terminal.print(`⚠ No hash declared. Computed SHA-256: ${hashHex.substring(0, 24)}...`, 'warning'); } // Load the verified source const success = await this.terminal.moduleManager.loadModuleFromURL(pkg.url); if (success) { this.installed[name] = { version: pkg.version, installed: new Date().toISOString(), hash: hashHex }; this.saveInstalled(); this.terminal.print(`"${name}" installed successfully!`, 'success'); } } catch (e) { this.terminal.print(`Install failed: ${e.message}`, 'error'); } } async list() { const idx = await this.loadIndex(); if (!idx) return; const names = Object.keys(this.installed); this.terminal.print('=== Installed Packages ===', 'info'); if (names.length === 0) { this.terminal.print(' None. Use: wpm install ', 'info'); } else { names.forEach(name => { const inst = this.installed[name]; this.terminal.print(` ${name} v${inst.version} (${inst.installed.split('T')[0]})`); }); } this.terminal.print('\n=== Available Packages ===', 'info'); Object.keys(idx.packages).sort().forEach(name => { const pkg = idx.packages[name]; const installed = this.installed[name] ? ' ✅' : ''; this.terminal.print(` ${name} - ${pkg.desc}${installed}`); }); } async search(keyword) { if (!keyword) return this.terminal.print('Usage: wpm search ', 'warning'); const idx = await this.loadIndex(); if (!idx) return; const kw = keyword.toLowerCase(); const results = []; Object.entries(idx.packages).forEach(([name, pkg]) => { if (name.includes(kw) || pkg.desc.toLowerCase().includes(kw) || pkg.category.toLowerCase().includes(kw)) { results.push({ name, ...pkg }); } }); if (results.length === 0) return this.terminal.print(`No packages matching "${keyword}"`, 'info'); this.terminal.print(`=== Search: "${keyword}" (${results.length} results) ===`, 'info'); results.forEach(r => { const installed = this.installed[r.name] ? ' [installed]' : ''; this.terminal.print(` ${r.name} [${r.category}] - ${r.desc}${installed}`); }); this.terminal.print('Use: wpm install to install', 'info'); } async update() { this.index = null; await this.loadIndex(); if (!this.index) return; this.terminal.print('Index refreshed.', 'success'); const outdated = []; Object.keys(this.installed).forEach(name => { const pkg = this.index.packages[name]; if (pkg && pkg.version !== this.installed[name].version) { outdated.push(name); } }); if (outdated.length > 0) { this.terminal.print(`Outdated packages: ${outdated.join(', ')}. Reinstall with "wpm install "`, 'warning'); } } async remove(name) { if (!name) return this.terminal.print('Usage: wpm remove ', 'warning'); if (!this.installed[name]) return this.terminal.print(`Package "${name}" is not installed via wpm`, 'warning'); const idx = await this.loadIndex(); const className = idx && idx.packages[name] ? idx.packages[name].name.replace(/\s+/g, '') + 'Module' : null; if (className && this.terminal.moduleManager.unregisterModule(className)) { this.terminal.print(`Module ${className} unregistered`, 'info'); } delete this.installed[name]; this.saveInstalled(); this.terminal.print(`Package "${name}" removed. If module persists, use: removemod `, 'success'); } async info(name) { if (!name) return this.terminal.print('Usage: wpm info ', 'warning'); const idx = await this.loadIndex(); if (!idx) return; const pkg = idx.packages[name]; if (!pkg) return this.terminal.print(`Package "${name}" not found`, 'error'); this.terminal.print(`=== ${pkg.name} v${pkg.version} ===`, 'info'); this.terminal.print(`Description: ${pkg.desc}`); this.terminal.print(`Category: ${pkg.category}`); this.terminal.print(`Commands: ${pkg.commands.join(', ')}`); this.terminal.print(`URL: ${pkg.url}`); const installed = this.installed[name]; if (installed) { this.terminal.print(`Installed: ${installed.installed}`, 'success'); if (installed.hash) this.terminal.print(`Hash: ${installed.hash.substring(0, 24)}...`, 'info'); } else this.terminal.print('Not installed. Use: wpm install ' + name, 'info'); } // ── Enum helpers for autocomplete ── _listSubcommands() { return ['install', 'list', 'search', 'update', 'remove', 'info']; } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new WpmModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/wpm.js'); }