// Extracted from newOS4 index.html — SystemModule
class SystemModule {
constructor() {
this.commands = {};
this.terminal = null;
}
getHelpInfo() {
return {
'help': 'help [command] — Show all commands or usage for a specific command',
'echo': 'echo — Print text to the terminal',
'mem': 'mem — Show memory usage (heap + localStorage + VFS)',
'status': 'status — Display system status (network, files, modules, Ollama)',
'version': 'version — Show version, build date, and cache key',
'uptime': 'uptime — Show time since page load + last save time',
'errors': 'errors [n] — Show last n ERROR log entries (default: 20)',
'doctor': 'doctor — Run all health checks, rank issues, show top 3 warnings',
'bug-report': 'bug-report — Generate a markdown bug report to /tmp/ for debugging',
'bug': 'bug — File a structured bug report (stored server-side, visible in BUG-REPORT.html)',
'preview': 'preview — Render an HTML file from the VFS in a preview pane',
'reset': 'reset [mem|vfs|config|all] — Global reset command (default: mem)',
'factory-reset': 'factory-reset — Alias for reset all',
'reset-vfs': 'reset-vfs — Alias for reset vfs',
'logs': 'logs [n] [LEVEL] — Show last n log entries (optionally filtered by level: INFO/WARN/ERROR)',
'logrotate': 'logrotate — Rotate current.log to a dated archive (auto-cleans old)',
'health-check': 'health-check — Audit VFS size, modules, logs, trash, skills, tool failures',
'memory-map-export': 'memory-map-export — Export memory index to /memory/reference.md (overwrites file)',
'font-size': 'font-size <8-32> — Set terminal font size (persists across sessions)',
'setfont': 'setfont <8-32> — Alias for font-size',
'smart-prompt': 'smart-prompt [on|off] — Toggle context-aware prompt (shows name, skills, sessions, memory, edits)',
'expand': 'expand — Toggle full-screen terminal output',
'sysmsg': 'sysmsg [on|off] — Toggle system/warning/error/info message visibility',
'checkpoint': 'checkpoint [name] — Create a VFS snapshot (name defaults to ISO timestamp)',
'checkpoint-list': 'checkpoint-list — List all VFS checkpoints',
'rollback': 'rollback — Restore VFS from a checkpoint',
'checkpoint-del': 'checkpoint-del — Delete a VFS checkpoint',
};
}
registerCommands() {
this.commands['help'] = (args) => this.handleHelp(args);
this.commands['echo'] = (args) => this.terminal.print(args.join(' '));
this.commands['mem'] = () => {
const heap = performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(2) : 'N/A';
const heapLimit = performance.memory ? (performance.memory.jsHeapSizeLimit / 1048576).toFixed(0) : 'N/A';
// localStorage size
let lsBytes = 0;
try {
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k) lsBytes += (k.length + (localStorage.getItem(k) || '').length) * 2;
}
} catch (e) {}
const lsKB = (lsBytes / 1024).toFixed(1);
// VFS size (in-memory)
let vfsBytes = 0;
try { vfsBytes = JSON.stringify(this.terminal.fs.root).length; } catch (e) {}
const vfsKB = (vfsBytes / 1024).toFixed(1);
// sessionStorage size
let ssBytes = 0;
try {
for (let i = 0; i < sessionStorage.length; i++) {
const k = sessionStorage.key(i);
if (k) ssBytes += (k.length + (sessionStorage.getItem(k) || '').length) * 2;
}
} catch (e) {}
const ssKB = (ssBytes / 1024).toFixed(1);
// Total
const total = (parseFloat(heap === 'N/A' ? '0' : heap) + parseFloat(lsKB) + parseFloat(ssKB) + parseFloat(vfsKB)).toFixed(1);
this.terminal.print('💾 Memory Usage', 'header');
this.terminal.print(` JS heap: ${heap} MB${heap !== 'N/A' ? ' / ' + heapLimit + ' MB limit' : ''}`, parseFloat(heap) > 100 ? 'warning' : '');
this.terminal.print(` localStorage: ${lsKB} KB (${localStorage.length} keys)`, parseFloat(lsKB) > 1024 ? 'warning' : '');
this.terminal.print(` sessionStorage: ${ssKB} KB (${sessionStorage.length} keys)`, '');
this.terminal.print(` VFS in-memory: ${vfsKB} KB`, parseFloat(vfsKB) > 256 ? 'warning' : '');
this.terminal.print(` Total: ${total} MB`, '');
document.getElementById('header-status').innerText = `Mem: ${heap}MB`;
};
// Refresh memory display on boot
setTimeout(() => {
if (performance.memory) {
const mem = (performance.memory.usedJSHeapSize / 1048576).toFixed(1);
document.getElementById('header-status').innerText = `Mem: ${mem}MB`;
}
}, 500);
this.commands['status'] = () => {
this.terminal.print(`System: WebX11 ${window.NEWOS4_VERSION || 'v4.0'}`);
this.terminal.print(`Network: ${navigator.onLine ? 'Online' : 'Offline'}`);
this.terminal.print(`Files: ${this.terminal.fs.ls().length} items`);
this.terminal.print(`Modules: ${Object.keys(this.terminal.moduleManager.modules).length} loaded`);
this.terminal.print(`Ollama: ${this.terminal.ollamaManager.connected ? 'Connected' : 'Disconnected'}`);
};
// v4.5.43: version — show version, build date, cache key
this.commands['version'] = () => {
const v = window.NEWOS4_VERSION || 'unknown';
const cacheKey = localStorage.getItem('webx11_cache_ver') || 'unknown';
// Try to get build date from script tag or module list
const buildDate = (() => {
try {
const scripts = document.querySelectorAll('script[src*="v="]');
for (const s of scripts) {
const m = s.src.match(/v=([0-9-]+-v[0-9.]+[a-z]?)/);
if (m) return m[1].split('-').slice(0, 3).join('-');
}
} catch (e) {}
return 'unknown';
})();
const ua = navigator.userAgent.split(' ').slice(-1)[0] || 'unknown';
this.terminal.print('ℹ️ WebX11 newOS4', 'header');
this.terminal.print(` Version: ${v}`);
this.terminal.print(` Build: ${buildDate}`);
this.terminal.print(` Cache key: ${cacheKey}`);
this.terminal.print(` Browser: ${ua}`);
this.terminal.print(` Online: ${navigator.onLine ? 'yes' : 'no'}`);
};
// v4.5.43: uptime — time since page load + last save time
this.commands['uptime'] = () => {
const loadTime = performance.timeOrigin || Date.now() - (window.__pageLoadMs || 0);
const ms = Date.now() - loadTime;
const s = Math.floor(ms / 1000);
const days = Math.floor(s / 86400);
const hours = Math.floor((s % 86400) / 3600);
const mins = Math.floor((s % 3600) / 60);
const secs = s % 60;
let uptime;
if (days > 0) uptime = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) uptime = `${hours}h ${mins}m ${secs}s`;
else if (mins > 0) uptime = `${mins}m ${secs}s`;
else uptime = `${secs}s`;
this.terminal.print('⏱️ Uptime', 'header');
this.terminal.print(` Since load: ${uptime} (${s}s)`);
// Check last save time from localStorage modification
try {
const lastSave = localStorage.getItem('webx11_last_save');
if (lastSave) {
const ago = Math.floor((Date.now() - parseInt(lastSave)) / 1000);
this.terminal.print(` Last save: ${ago}s ago`, ago > 300 ? 'warning' : '');
} else {
this.terminal.print(` Last save: unknown`, 'info');
}
} catch (e) {}
};
// v4.5.43: errors [n] — alias for logs [n] ERROR
this.commands['errors'] = (args) => {
const n = parseInt(args[0]) || 20;
// v4.6.59: read the canonical counter (DebugSupervisor DB + failed tool
// calls) — the same numbers the dashboard and world model publish. The VFS
// log is reported separately because it only receives explicit
// terminal.log('ERROR') calls, so it cannot be the source of truth.
const e = this.terminal.countErrors();
if (e.total === 0) {
this.terminal.print(`✅ 0 errors — debug DB ${e.debug} · failed tool calls ${e.latency} · log ${e.log}`, 'success');
} else {
this.terminal.print(`--- ${e.total} ${e.total === 1 ? 'error' : 'errors'} — debug DB ${e.debug} · failed tool calls ${e.latency} ---`, 'error');
const ds = this.terminal.moduleManager.modules['DebugSupervisorModule'];
const entries = Object.entries((ds && ds._errorDB && ds._errorDB.errors) || {})
.map(([hash, v]) => Object.assign({ hash: hash }, v))
.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0))
.slice(0, n);
entries.forEach(x => this.terminal.print(
(x.hash || '').slice(0, 8) + ' ×' + (x.count || 1) + ' ' +
((x.severity || '') + ' ' + (x.message || '')).trim(), 'error'));
}
this.terminal.print(` scope: this browser session · log scan ${e.log} [ERROR] line(s) in /logs/current.log`, 'info');
};
// v4.5.43: doctor — run all health checks, rank issues, show top 3
this.commands['doctor'] = () => {
const issues = [];
const fs = this.terminal.fs;
// Check 1: Network
if (!navigator.onLine) issues.push({ severity: 'high', msg: 'Network: offline' });
// Check 2: Ollama
if (!this.terminal.ollamaManager.connected) {
issues.push({ severity: 'medium', msg: 'Ollama: not connected (AI features limited)' });
}
// Check 3: VFS size
try {
const vfsBytes = JSON.stringify(fs.root).length;
if (vfsBytes > 524288) issues.push({ severity: 'high', msg: `VFS: ${(vfsBytes/1024).toFixed(0)}KB > 512KB limit` });
else if (vfsBytes > 262144) issues.push({ severity: 'medium', msg: `VFS: ${(vfsBytes/1024).toFixed(0)}KB > 256KB (consider reset-vfs)` });
} catch (e) {}
// Check 4: Heap
if (performance.memory) {
const heapMB = performance.memory.usedJSHeapSize / 1048576;
if (heapMB > 200) issues.push({ severity: 'high', msg: `Heap: ${heapMB.toFixed(0)}MB > 200MB` });
else if (heapMB > 100) issues.push({ severity: 'medium', msg: `Heap: ${heapMB.toFixed(0)}MB > 100MB` });
}
// Check 5: Logs
try {
const logContent = fs.readFile('/logs/current.log') || '';
const logLines = logContent.split('\n').filter(l => l).length;
if (logLines > 500) issues.push({ severity: 'low', msg: `Logs: ${logLines} lines (consider logrotate)` });
} catch (e) {}
// Check 6: Errors in last 100 log lines
try {
const logContent = fs.readFile('/logs/current.log') || '';
const recent = logContent.split('\n').filter(l => l).slice(-100);
const errorCount = recent.filter(l => l.includes('[ERROR]')).length;
if (errorCount > 5) issues.push({ severity: 'high', msg: `Errors: ${errorCount} errors in last 100 log lines` });
else if (errorCount > 0) issues.push({ severity: 'medium', msg: `Errors: ${errorCount} errors in last 100 log lines` });
} catch (e) {}
// Check 7: localStorage quota
let lsBytes = 0;
try {
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k) lsBytes += (k.length + (localStorage.getItem(k) || '').length) * 2;
}
} catch (e) {}
if (lsBytes > 4 * 1024 * 1024) issues.push({ severity: 'high', msg: `localStorage: ${(lsBytes/1024/1024).toFixed(1)}MB approaching 5MB quota` });
// Check 8: Disabled modules
let disabled = [];
try { disabled = JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]'); } catch (e) {}
if (disabled.length > 0) {
issues.push({ severity: 'low', msg: `Disabled modules: ${disabled.length} (${disabled.join(', ')})` });
}
this.terminal.print('🩺 Doctor Report', 'header');
if (issues.length === 0) {
this.terminal.print(' ✅ All systems healthy', 'success');
return;
}
// Sort by severity: high > medium > low
const sevOrder = { high: 0, medium: 1, low: 2 };
issues.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity]);
this.terminal.print(` Found ${issues.length} issue(s):`, 'warning');
for (const i of issues.slice(0, 5)) {
const icon = i.severity === 'high' ? '🔴' : i.severity === 'medium' ? '🟡' : '🟢';
this.terminal.print(` ${icon} [${i.severity.toUpperCase()}] ${i.msg}`, i.severity === 'high' ? 'error' : 'warning');
}
if (issues.length > 5) this.terminal.print(` ... and ${issues.length - 5} more`, 'info');
this.terminal.print('', '');
this.terminal.print(' Recommended actions:', 'info');
if (issues.some(i => i.msg.startsWith('Network'))) this.terminal.print(' → Check internet connection');
if (issues.some(i => i.msg.startsWith('Ollama'))) this.terminal.print(' → Check Ollama is running (ollama serve)');
if (issues.some(i => i.msg.startsWith('VFS'))) this.terminal.print(' → Run reset-vfs --confirm to clear or checkpoint/rollback');
if (issues.some(i => i.msg.startsWith('Heap'))) this.terminal.print(' → Refresh the page to clear memory');
if (issues.some(i => i.msg.startsWith('Logs'))) this.terminal.print(' → Run logrotate');
if (issues.some(i => i.msg.startsWith('Errors'))) this.terminal.print(' → Run errors to see details, or bug-report to save state');
if (issues.some(i => i.msg.startsWith('localStorage'))) this.terminal.print(' → Run factory-reset --confirm (last resort)');
};
// v4.5.43: bug-report — generate markdown report to /tmp/
this.commands['bug-report'] = () => {
const fs = this.terminal.fs;
const lines = [];
const ts = new Date().toISOString();
lines.push(`# WebX11 newOS4 Bug Report`);
lines.push(`Generated: ${ts}`);
lines.push('');
lines.push('## System');
lines.push(`- Version: ${window.NEWOS4_VERSION || 'unknown'}`);
lines.push(`- Cache key: ${localStorage.getItem('webx11_cache_ver') || 'unknown'}`);
lines.push(`- Online: ${navigator.onLine ? 'yes' : 'no'}`);
lines.push(`- Browser: ${navigator.userAgent}`);
lines.push(`- Screen: ${screen.width}x${screen.height}`);
lines.push(`- Heap: ${performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB' : 'N/A'}`);
lines.push(`- VFS size: ${(JSON.stringify(fs.root).length / 1024).toFixed(1)}KB`);
lines.push(`- Modules loaded: ${Object.keys(this.terminal.moduleManager.modules).length}`);
lines.push(`- Ollama: ${this.terminal.ollamaManager.connected ? 'connected' : 'disconnected'}`);
lines.push('');
// Uptime
const loadTime = performance.timeOrigin || Date.now();
const upSec = Math.floor((Date.now() - loadTime) / 1000);
lines.push(`- Uptime: ${upSec}s`);
lines.push('');
lines.push('## Recent Errors (last 20)');
try {
const logContent = fs.readFile('/logs/current.log') || '';
const errs = logContent.split('\n').filter(l => l && l.includes('[ERROR]')).slice(-20);
if (errs.length === 0) lines.push('_No errors found_');
else errs.forEach(e => lines.push('- ' + e));
} catch (e) { lines.push('_Could not read logs_'); }
lines.push('');
lines.push('## Recent Commands (last 10)');
try {
const hist = JSON.parse(localStorage.getItem('webx11_command_history') || '[]');
const recent = hist.slice(-10);
if (recent.length === 0) lines.push('_No history_');
else recent.forEach(c => lines.push('- ' + c));
} catch (e) { lines.push('_No history available_'); }
lines.push('');
lines.push('## Disabled Modules');
try {
const disabled = JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]');
if (disabled.length === 0) lines.push('_None_');
else disabled.forEach(d => lines.push('- ' + d));
} catch (e) { lines.push('_Unknown_'); }
lines.push('');
lines.push('## File List (top-level VFS)');
try {
const items = fs.ls();
items.slice(0, 20).forEach(i => lines.push('- ' + i));
if (items.length > 20) lines.push(`- ... and ${items.length - 20} more`);
} catch (e) { lines.push('_Could not list_'); }
lines.push('');
lines.push('---');
lines.push('Send this file when reporting a bug.');
const content = lines.join('\n');
const filename = `/tmp/bug-report-${Date.now()}.md`;
try {
fs.writeFile(filename, content);
this.terminal.print(`✅ Bug report saved to ${filename}`, 'success');
this.terminal.print(` (${(content.length / 1024).toFixed(1)}KB)`, 'info');
} catch (e) {
this.terminal.print(`❌ Could not save bug report: ${e.message}`, 'error');
}
};
// v4.6.37: bug — file a structured report to the backend (Part A)
this.commands['bug'] = async (args) => {
const title = (args || []).join(' ').trim() || 'Untitled bug report';
const diag = {
version: window.NEWOS4_VERSION || 'unknown',
url: location.href,
ua: navigator.userAgent,
online: navigator.onLine,
modules: Object.keys(this.terminal.moduleManager.modules).length,
errors: [],
};
try {
// Prefer the LIVE error DB: localStorage writes are debounced, so a report
// filed right after an error would otherwise carry an empty list.
let list = [];
try {
const ds = Object.values(this.terminal.moduleManager.modules)
.find(m => m && m._errorDB && m._errorDB.errors);
if (ds) list = Object.values(ds._errorDB.errors);
} catch (_e) {}
if (!list.length) {
const raw = JSON.parse(localStorage.getItem('webx11_debug_db') || '[]');
list = Array.isArray(raw) ? raw : Object.values((raw && raw.errors) || {});
}
diag.errors = list.slice(-20).map(e => ({
message: String(e.message || e.msg || '').slice(0, 300),
source: String(e.source || e.url || '').slice(0, 200),
count: Number(e.count) || 1,
// The stack is the only thing that makes a report actionable; it was
// dropped here while ingest kept it, which is why the 2026-10-20
// `toLowerCase` report could not be localised for three days.
stack: e.stack ? String(e.stack).split('\n').slice(0, 10).join('\n').slice(0, 1200) : null,
}));
} catch (_e) {}
try {
diag.recent_commands = JSON.parse(localStorage.getItem('webx11_command_history') || '[]')
.slice(-10)
.map(c => String(c).replace(/(password|token|secret|api[_-]?key)\S*/ig, '$1:[redacted]'));
} catch (_e) {}
this.terminal.print('📮 Filing bug report…', 'info');
let res = null, err = null;
try {
const r = await fetch('/spaces/newos4/api/debug/report.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, diagnostics: diag }),
});
res = await r.json();
if (!r.ok) err = res.error || ('HTTP ' + r.status);
} catch (e) { err = e.message; }
if (err) { this.terminal.print('❌ Report failed: ' + err, 'error'); return; }
this.terminal.print('✅ Report filed: ' + res.id + (res.duplicate ? ' (already known)' : ''), 'success');
this.terminal.print(' View: /spaces/newos4/downloads/bugs/BUG-REPORT.html', 'info');
};
this.commands['preview'] = (args) => this.terminal.openPreview(args);
// v4.5.64: reset — unified reset command superseding factory-reset, reset-vfs, reset-mem, config-reset
this.commands['reset'] = (args) => this._globalReset(args);
this.commands['factory-reset'] = (args) => this._globalReset(['all', ...args]);
this.commands['reset-vfs'] = (args) => this._globalReset(['vfs', ...args]);
this.commands['factory-reset-old'] = (args) => {
if (!args[0] || args[0] !== '--confirm') {
this.terminal.print('⚠️ FACTORY RESET will clear all data:', 'error');
this.terminal.print(' • Filesystem (all files + directories)', 'warning');
this.terminal.print(' • AI memory, patterns, user profile', 'warning');
this.terminal.print(' • Ollama endpoint, model selection', 'warning');
this.terminal.print(' • Terminal history, font size', 'warning');
this.terminal.print(' • All config, modules, packages', 'warning');
this.terminal.print(' • Mindmaps, notes, bookmarks', 'warning');
this.terminal.print(' • Themes, context profiles, servers', 'warning');
this.terminal.print(' • Chat settings, my data', 'warning');
this.terminal.print('Type: factory-reset --confirm to proceed', 'error');
return;
}
// v4.5.47: explicitly wipe VFS storage and session backups before clearing localStorage
try {
this.terminal.fs.deleteFile('_sessions/_auto.json');
this.terminal.fs.deleteFile('_sessions/_auto-context.json');
this.terminal.fs.reset();
} catch (e) { console.warn('factory-reset FS cleanup:', e); }
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && key.startsWith('webx11_')) localStorage.removeItem(key);
}
this.terminal.outputContainer.innerHTML = '';
this.terminal.print('✅ Factory reset complete. Reloading...', 'success');
setTimeout(() => location.reload(), 1000);
};
this.commands['reset-vfs'] = (args) => {
if (!args[0] || args[0] !== '--confirm') {
this.terminal.print('⚠️ RESET VFS will clear all files and directories:', 'warning');
this.terminal.print(' • Virtual filesystem (all files + directories)', 'warning');
this.terminal.print(' • VFS will be reset to empty root', 'warning');
this.terminal.print(' • All settings, modules, AI profile preserved', 'info');
this.terminal.print('Type: reset-vfs --confirm to proceed', 'error');
return;
}
this.terminal.fs.root = { type: 'dir', content: {} };
this.terminal.fs.currentPath = [];
// v4.5.47: delete auto-session backups so they don't resurrect
try {
this.terminal.fs.deleteFile('_sessions/_auto.json');
this.terminal.fs.deleteFile('_sessions/_auto-context.json');
} catch (e) {}
localStorage.setItem('webx11_last_reset', Date.now().toString());
this.terminal.fs.saveToStorage();
this.terminal.print('✅ VFS reset complete. Reloading...', 'success');
setTimeout(() => location.reload(), 1000);
};
this.commands['logs'] = (args) => {
const n = parseInt(args[0]) || 20;
const level = args[1] ? args[1].toUpperCase() : null;
const content = this.terminal.fs.readFile('/logs/current.log');
if (!content) { this.terminal.print('No logs yet', 'info'); return; }
let lines = content.split('\n').filter(l => l);
if (level) lines = lines.filter(l => l.includes('[' + level + ']'));
const tail = lines.slice(-n);
if (tail.length === 0) { this.terminal.print('No matching log entries', 'info'); return; }
this.terminal.print('--- Logs (last ' + tail.length + ') ---', 'info');
tail.forEach(l => this.terminal.print(l, l.includes('[ERROR]') ? 'error' : 'info'));
};
// Auto-rotate logs on startup (silently)
this._autoRotateLogs();
this.commands['logrotate'] = () => {
const rotated = this._autoRotateLogs(true);
if (rotated) {
this.terminal.print('Log rotated: ' + rotated, 'success');
} else {
this.terminal.print('Log rotation not needed (<50KB, <500 lines)', 'info');
}
};
this.commands['health-check'] = () => this._healthCheck();
this.commands['memory-map-export'] = () => this._memoryMapExport();
this.commands['font-size'] = (args) => {
if (!args.length || !args[0]) {
const cur = localStorage.getItem('webx11_font_size');
this.terminal.print(`Current font-size: ${cur || '16'}px`, 'info');
this.terminal.print('Usage: font-size <8-32>', 'info');
return;
}
const val = parseInt(args[0], 10);
if (isNaN(val) || val < 8 || val > 32) {
this.terminal.print('font-size must be between 8 and 32', 'error');
return;
}
localStorage.setItem('webx11_font_size', val.toString());
// Apply directly to body — bypasses CSS inheritance issues
document.body.style.fontSize = val + 'px';
document.getElementById('terminal-container').style.fontSize = val + 'px';
document.getElementById('command-input').style.fontSize = val + 'px';
this.terminal.print(`Font size set to ${val}px (saved) 👆`, 'success');
};
this.commands['setfont'] = this.commands['font-size'];
// Checkpoint commands (delegated to virtual-fs.js)
this.commands['checkpoint'] = (args) => {
try {
const name = args[0] || '';
const created = this.terminal.fs.checkpoint(name);
this.terminal.print('✅ Checkpoint created: ' + (created || name), 'success');
} catch (e) {
this.terminal.print('Checkpoint failed: ' + e.message, 'error');
}
};
this.commands['checkpoint-list'] = () => {
try {
const list = this.terminal.fs.listCheckpoints();
if (!list || list.length === 0) {
this.terminal.print('No checkpoints found', 'info');
return;
}
this.terminal.print('📋 Checkpoints:', 'text-muted');
list.forEach(c => this.terminal.print(' • ' + c));
} catch (e) {
this.terminal.print('checkpoint-list failed: ' + e.message, 'error');
}
};
this.commands['checkpoint-del'] = (args) => {
if (!args[0]) return this.terminal.print('Usage: checkpoint-del ', 'warning');
try {
if (this.terminal.fs.deleteCheckpoint(args[0])) {
this.terminal.print('Deleted checkpoint: ' + args[0], 'success');
} else {
this.terminal.print('Checkpoint not found: ' + args[0], 'error');
}
} catch (e) {
this.terminal.print('checkpoint-del failed: ' + e.message, 'error');
}
};
this.commands['rollback'] = (args) => {
if (!args[0]) return this.terminal.print('Usage: rollback ', 'warning');
try {
if (this.terminal.fs.rollback(args[0])) {
this.terminal.print('↻ Rolled back to checkpoint: ' + args[0], 'success');
} else {
this.terminal.print('Rollback failed: checkpoint not found or invalid', 'error');
}
} catch (e) {
this.terminal.print('Rollback failed: ' + e.message, 'error');
}
};
}
// v4.5.64: Unified reset — subcommands: mem, vfs, config, all
_globalReset(args) {
const scope = (args[0] || 'mem').toLowerCase();
const confirmed = args.includes('--confirm') || args.includes('-y');
const scopes = {
mem: {
title: 'AI MEMORY',
items: ['AI conversation context', 'AI patterns & guardrails', 'AI user profile', 'AI system prompt', 'VaultMind token', 'AI internal logs (tool calls, heap stats)'],
keys: ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token'],
desc: 'Clears AI state only. VFS, settings, modules, and config are untouched.'
},
vfs: {
title: 'VIRTUAL FILESYSTEM',
items: ['All files and directories', 'Session backups', 'VFS metadata', 'Neural VFS index'],
desc: 'Clears the VFS to empty root. Settings, modules, AI profile, and config are preserved.'
},
config: {
title: 'CONFIGURATION',
items: ['All webx11_config_* keys', 'Ollama endpoint & model', 'Security settings', 'Terminal config', 'Font size', 'Theme'],
keys: null, // prefix-based wipe
desc: 'Resets all configuration to defaults. VFS and AI memory are untouched.'
},
all: {
title: 'EVERYTHING',
items: ['VFS (all files)', 'AI memory & profile', 'All config & settings', 'Module registry & disabled list', 'Terminal history', 'Themes', 'Chat settings', 'Cron jobs', 'Floating elements', 'Aliases', 'Tutorial state', 'Server list', 'Orchestrator config', 'Secret vault keys', 'Neural VFS config'],
desc: 'Complete wipe. The system will reload as if first visit.'
}
};
const s = scopes[scope];
if (!s) {
this.terminal.print(`Usage: reset [mem|vfs|config|all] [--confirm]`, 'warning');
this.terminal.print(` reset mem — Clear AI memory (default)`, 'info');
this.terminal.print(` reset vfs — Clear virtual filesystem`, 'info');
this.terminal.print(` reset config — Reset all config to defaults`, 'info');
this.terminal.print(` reset all — Wipe everything (nuclear)`, 'info');
this.terminal.print(``, 'info');
this.terminal.print(`Aliases: factory-reset = reset all, reset-vfs = reset vfs`, 'info');
return;
}
if (!confirmed) {
this.terminal.print(`⚠️ RESET ${s.title} will clear:`, 'error');
s.items.forEach(item => this.terminal.print(` • ${item}`, 'warning'));
this.terminal.print(``, 'info');
this.terminal.print(s.desc, 'info');
this.terminal.print(``, 'info');
this.terminal.print(`Type: reset ${scope} --confirm`, 'error');
return;
}
// Execute reset
if (scope === 'mem') {
this._resetMem();
} else if (scope === 'vfs') {
this._resetVfs();
} else if (scope === 'config') {
this._resetConfig();
} else if (scope === 'all') {
this._resetAll();
}
localStorage.setItem('webx11_last_reset', Date.now().toString());
this.terminal.print(`✅ ${s.title} reset complete. Reloading...`, 'success');
setTimeout(() => location.reload(), 1000);
}
_resetMem() {
const keys = ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token'];
keys.forEach(k => localStorage.removeItem(k));
// Also clear AI logs in VFS
try { this.terminal.fs.deleteFile('/logs/ai-tool-log.json'); } catch (e) {}
}
_resetVfs() {
this.terminal.fs.root = { type: 'dir', content: {} };
this.terminal.fs.currentPath = [];
try {
this.terminal.fs.deleteFile('_sessions/_auto.json');
this.terminal.fs.deleteFile('_sessions/_auto-context.json');
} catch (e) {}
this.terminal.fs.saveToStorage();
}
_resetConfig() {
// Wipe all webx11_config_* keys
const toRemove = [];
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && (key.startsWith('webx11_config_') || key === 'webx11_ollama_endpoint' || key === 'webx11_ollama_model' || key.startsWith('webx11_security_') || key === 'webx11_terminal_config' || key === 'webx11_font_size' || key === 'webx11_active_theme' || key === 'webx11_themes' || key === 'webx11_model_params' || key.startsWith('webx11_validator_') || key === 'webx11_optimization_enabled' || key === 'webx11_stream_enabled' || key === 'webx11_smart_prompt' || key === 'webx11_config_ai_sound' || key === 'webx11_welcome_dismissed')) {
toRemove.push(key);
}
}
toRemove.forEach(k => localStorage.removeItem(k));
}
_resetAll() {
// Delete session backups so they don't resurrect
try {
this.terminal.fs.deleteFile('_sessions/_auto.json');
this.terminal.fs.deleteFile('_sessions/_auto-context.json');
this.terminal.fs.reset();
} catch (e) { console.warn('reset all FS cleanup:', e); }
// Wipe ALL webx11_ prefixed keys
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && key.startsWith('webx11_')) localStorage.removeItem(key);
}
// Also wipe non-prefixed keys used by modules
const extraKeys = ['vaultmind_token', 'cslite_messages', 'webx11_chat_settings', 'webx11_chat_logs', 'webx11_chat_api'];
extraKeys.forEach(k => localStorage.removeItem(k));
this.terminal.outputContainer.innerHTML = '';
}
_autoRotateLogs(force = false) {
try {
const content = this.terminal.fs.readFile('/logs/current.log') || '';
const lines = content.split('\n').filter(l => l);
const size = content.length;
if (!force && size < 51200 && lines.length < 500) return null;
const now = new Date();
const stamp = now.getFullYear() + '-' +
String(now.getMonth()+1).padStart(2,'0') + '-' +
String(now.getDate()).padStart(2,'0') + '_' +
String(now.getHours()).padStart(2,'0') + '00';
this.terminal.fs.writeFile('/logs/' + stamp + '.log', content);
this.terminal.fs.writeFile('/logs/current.log', '');
// Clean oldest rotated logs (>10)
const logsDir = this.terminal.fs.readDir('/logs');
if (logsDir && logsDir.content) {
const rotated = Object.keys(logsDir.content)
.filter(k => k.endsWith('.log') && k !== 'current.log')
.sort();
while (rotated.length > 10) {
const oldest = rotated.shift();
this.terminal.fs.deleteFile('/logs/' + oldest);
}
}
return stamp + '.log';
} catch (e) { return null; }
}
_healthCheck() {
const fs = this.terminal.fs;
let warnings = 0;
this.terminal.print('╔══════════════════════════════════════╗', 'info');
this.terminal.print('║ 🔍 SYSTEM HEALTH CHECK ║', 'info');
this.terminal.print('╚══════════════════════════════════════╝', 'info');
// VFS size
const dump = JSON.stringify(fs.root).length;
const vfsOk = dump < 524288;
this.terminal.print(`VFS size: ${(dump/1024).toFixed(1)}KB ${vfsOk ? '✅' : '⚠️ > 512KB'}`, vfsOk ? 'info' : 'warning');
if (!vfsOk) warnings++;
// Modules
const modTotal = Object.keys(this.terminal.moduleManager.modules || {}).length;
this.terminal.print(`Modules loaded: ${modTotal} ✅`, 'info');
// Logs
const logContent = fs.readFile('/logs/current.log') || '';
const logLines = logContent.split('\n').filter(l => l).length;
this.terminal.print(`Current log: ${logLines} lines`, logLines > 500 ? 'warning' : 'info');
if (logLines > 500) warnings++;
// Trash
const trashList = fs.listTrash ? fs.listTrash() : [];
this.terminal.print(`Trash items: ${trashList.length}`, 'info');
// Skills
const skillIdx = fs.fsReadJSON ? (fs.fsReadJSON('/skills/index.json') || {}) : {};
const skillCount = Object.keys(skillIdx).length;
this.terminal.print(`Skills: ${skillCount}`, skillCount > 0 ? 'info' : 'warning');
if (skillCount === 0) warnings++;
// Summary
this.terminal.print('─'.repeat(40), 'text-muted');
if (warnings === 0) {
this.terminal.print('All checks passed ✅', 'success');
} else {
this.terminal.print(`${warnings} warning(s) found`, 'warning');
}
}
_memoryMapExport() {
const fs = this.terminal.fs;
const idx = fs.fsReadJSON ? (fs.fsReadJSON('/memory/index.json') || {}) : null;
if (!idx) {
this.terminal.print('No memory index found at /memory/index.json', 'error');
return;
}
// v4.5.43: Actually export to /memory/reference.md (was: print to terminal)
const lines = [];
lines.push('---');
lines.push('name: adaptive-memory-map');
lines.push('description: Auto-generated from memory/index.json');
lines.push('tags: [memory, context, tracking, adaptation]');
lines.push('category: system');
lines.push('---');
lines.push('');
for (const [k, v] of Object.entries(idx)) {
lines.push(`## ${k}`);
lines.push(`- Tasks: ${v.tasks || 0}`);
lines.push(`- Updated: ${v.updated || 'unknown'}`);
lines.push(`- Summary: ${(v.summary || '').substring(0, 200)}...`);
lines.push('');
}
const content = lines.join('\n');
try {
fs.writeFile('/memory/reference.md', content);
this.terminal.print(`✅ Memory map exported to /memory/reference.md (${(content.length / 1024).toFixed(1)}KB)`, 'success');
} catch (e) {
this.terminal.print(`❌ Could not save: ${e.message}`, 'error');
}
}
handleHelp(args) {
const helpMap = this.terminal.moduleManager.getAllHelp();
// Single command lookup
if (args && args.length > 0) {
const cmd = args[0].toLowerCase();
const allCommands = this.terminal.moduleManager.getAllCommands();
if (allCommands[cmd]) {
const helpEntry = helpMap[cmd];
if (helpEntry) {
this.terminal.print(helpEntry, 'info');
} else {
this.terminal.print(`No help available for ${cmd}`, 'warning');
}
} else {
this.terminal.print(`No help available for ${cmd}`, 'warning');
}
return;
}
// Dynamically group commands by owning module
const cmdToModule = this.terminal.moduleManager.getAllCommands();
const moduleGroups = {};
for (const [cmd, modName] of Object.entries(cmdToModule)) {
if (!moduleGroups[modName]) moduleGroups[modName] = [];
moduleGroups[modName].push(cmd);
}
const modLabels = {
FileSystemModule: '📁 Filesystem',
WebX11AIAssistantModule: '🤖 AI',
WebXAIAliasModule: '🌐 WebX',
SearXNGModule: '🔍 Search & Web',
WebFetcherModule: '🔍 Search & Web',
AliasModule: '🔧 Tools',
BusyBoxModule: '🔧 Tools',
CalcModule: '🔧 Tools',
CronModule: '🔧 Tools',
CryptoGzipModule: '🔧 Tools',
DiffModule: '🔧 Tools',
EditorModule: '🔧 Tools',
FileManagerModule: '🔧 Tools',
FinderModule: '🔧 Tools',
MindmapModule: '🔧 Tools',
ThemeModule: '🔧 Tools',
TutorialModule: '🔧 Tools',
VoiceModule: '🔧 Tools',
WpmModule: '🔧 Tools',
SystemModule: '⚙️ System',
ShellModule: '⚙️ System',
ServerMonitorModule: '📡 Network',
ModuleManagerModule: '🧩 Modules',
OllamaModule: '🦙 Ollama',
NeuralVFSModule: '🧠 Neural VFS',
SkillPackCoreModule: '📚 Skills',
ConfigManagerModule: '⚙️ Config',
SandboxModule: '🏖️ Sandbox',
WebDAVModule: '🧰 Extras',
ChatStudioModule: '🧰 Extras',
SimpleGUIModule: '🖥️ GUI',
SecretVault: '🔒 Vault',
OrchestratorModule: '🧠 Orchestrator',
PredictorModule: '🔮 Prediction',
MemoryGraphModule: '📊 Memory',
WorldModelModule: '🌍 World',
LeaseModule: '🔐 Leases',
AgentRegistryModule: '🤖 Agents',
};
// Merge modules with the same label
const grouped = {};
const usedLabels = new Set();
for (const [modName, cmds] of Object.entries(moduleGroups)) {
const label = modLabels[modName] || `🧩 ${modName.replace(/Module$/, '')}`;
if (!grouped[label]) grouped[label] = [];
grouped[label].push(...cmds);
usedLabels.add(label);
}
let result = '╔══════════════════════════════════════╗\n║ 🔷 WebX11 ' + (window.NEWOS4_VERSION || 'v4.0') + ' — All Commands ║\n╚══════════════════════════════════════╝\n\n';
const total = Object.keys(helpMap).length;
result += `📊 ${total} commands loaded\n\n`;
for (const [label, cmds] of Object.entries(grouped)) {
result += label + '\n';
cmds.forEach(c => {
const desc = (helpMap[c] || c).split(' — ')[1] || '';
const short = desc.substring(0, 55);
result += ` ${c.padEnd(16)}${short}\n`;
});
result += '\n';
}
result += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n help → detailed usage\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
this.terminal.print(result.trim(), 'info');
}
}