diff options
| -rw-r--r-- | help.html | 2 | ||||
| -rw-r--r-- | main.html | 8 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 4 | ||||
| -rw-r--r-- | modules/core/terminal.js | 26 | ||||
| -rw-r--r-- | modules/ext/alias.js | 2 | ||||
| -rw-r--r-- | modules/ext/busybox.js | 3 | ||||
| -rw-r--r-- | modules/ext/calc.js | 2 | ||||
| -rw-r--r-- | modules/ext/cron.js | 2 | ||||
| -rw-r--r-- | modules/ext/cryptogzip.js | 18 | ||||
| -rw-r--r-- | modules/ext/diff.js | 2 | ||||
| -rw-r--r-- | modules/ext/editor.js | 2 | ||||
| -rw-r--r-- | modules/ext/filemgr.js | 169 | ||||
| -rw-r--r-- | modules/ext/finder.js | 2 | ||||
| -rw-r--r-- | modules/ext/mindmap.js | 20 | ||||
| -rw-r--r-- | modules/ext/shell.js | 3 | ||||
| -rw-r--r-- | modules/ext/skill-pack-core.js | 3 | ||||
| -rw-r--r-- | modules/ext/theme.js | 2 | ||||
| -rw-r--r-- | modules/ext/voice.js | 2 | ||||
| -rw-r--r-- | modules/ext/wpm.js | 4 | ||||
| -rw-r--r-- | review-newos4.html | 333 | ||||
| -rw-r--r-- | skills/index.json | 166 | ||||
| -rw-r--r-- | skills/test.md | 1 |
22 files changed, 614 insertions, 162 deletions
@@ -1001,7 +1001,7 @@ listmods</pre> <footer> <div class="links"> - <a href="./index.html">Launch WebX11</a> + <a href="./main.html">Launch WebX11</a> <a href="https://ollama.com" target="_blank">Ollama</a> </div> <p>WebX11 v4.2 · 2026</p> @@ -625,7 +625,9 @@ <button class="btn" onclick="webx11.connectOllama()" style="width: 100%;">Connect</button> </div> <h4 style="color: var(--info-color); margin-bottom: 10px;">Available Models</h4> - <div id="ollama-models"></div> + <input type="text" id="ollama-search" class="module-url-input" placeholder="Search models..." oninput="webx11.renderOllamaModels()" style="margin-bottom: 8px;"> + <div id="ollama-model-count" style="font-size: 0.8em; color: var(--text-muted); margin-bottom: 6px;"></div> + <div id="ollama-models" style="max-height: 300px; overflow-y: auto;"></div> <div style="margin-top: 15px;"> <label style="display: block; margin-bottom: 5px; color: var(--text-muted);">Current Model:</label> <select id="ollama-current-model" class="module-url-input"> @@ -662,7 +664,7 @@ <div class="modal-body" style="display:flex; flex-direction:column; gap:10px;"> <div style="flex-shrink:0;"> <h4 style="color: var(--info-color); margin-bottom: 8px;">Installed Skills</h4> - <div id="skills-list"></div> + <div id="skills-list" style="max-height:200px;overflow-y:auto;"></div> </div> <div id="skill-preview" style="display:none; flex-shrink:0; background:var(--bg-tertiary); border:1px solid var(--border-color); border-radius:4px; padding:10px; max-height:250px; overflow-y:auto; white-space:pre-wrap; font-family:var(--font-primary); font-size:0.85em; color:var(--text-color);"> <div style="display:flex; justify-content:space-between; margin-bottom:6px;"> @@ -818,7 +820,7 @@ </script> <!-- Async dependency-ordered module loader (replaces 26 synchronous script tags) --> - <script src="modules/core/module-loader.js?v=34"></script> + <script src="modules/core/module-loader.js?v=35"></script> </body> </html> diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 2510fde..3a32f38 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -99,9 +99,9 @@ window.__moduleLoaderReload = reloadWithSelection; - const CACHE = '?v=' + (localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'); + const CACHE = '?v=' + (localStorage.getItem('webx11_cache_ver') || '2026-05-28'); if (!localStorage.getItem('webx11_cache_ver')) { - localStorage.setItem('webx11_cache_ver', '2026-05-27-14'); + localStorage.setItem('webx11_cache_ver', '2026-05-28'); } async function init() { diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 309e477..1bfc4cd 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -1160,7 +1160,7 @@ // Remove leading slash for VFS paths const vfsPath = path.replace(/^\/*/, ''); let content = this.fs.readFile(vfsPath); - if (content === null) content = this.fs.fsRead('/' + vfsPath); + if (content === null) content = this.fs.readFile('/' + vfsPath); if (content === null) { this.print(`File not found: ${path}`, 'error'); return; @@ -1285,11 +1285,11 @@ } viewSkillUI(name) { const fs = this.fs; - const content = fs.fsRead(`/skills/${name}.md`); + const content = fs.readFile(`/skills/${name}.md`); if (!content) { this.print(`Skill '${name}' not found`, 'error'); return; } document.getElementById('skill-preview-title').textContent = name; document.getElementById('skill-preview-content').textContent = content; - document.getElementById('skill-preview').style.display = 'block'; + document.getElementById('skill-preview').style.display = 'flex'; } closeSkillPreview() { document.getElementById('skill-preview').style.display = 'none'; @@ -1298,7 +1298,7 @@ if (!confirm(`Delete skill "${name}"?`)) return; const fs = this.fs; fs.mkdirRecursive('/skills'); - fs.fsWrite(`/skills/${name}.md`, ''); + fs.writeFile(`/skills/${name}.md`, ''); const index = fs.fsReadJSON('/skills/index.json') || {}; delete index[name]; fs.fsWriteJSON('/skills/index.json', index); @@ -1313,7 +1313,7 @@ if (!name) { alert('Skill name is required'); return; } const fs = this.fs; fs.mkdirRecursive('/skills'); - if (fs.fsRead(`/skills/${name}.md`)) { + if (fs.readFile(`/skills/${name}.md`)) { alert(`Skill '${name}' already exists. Delete it first or use skill-patch command.`); return; } @@ -1321,7 +1321,7 @@ if (!content.startsWith('---')) { content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n${content}`; } - fs.fsWrite(`/skills/${name}.md`, content); + fs.writeFile(`/skills/${name}.md`, content); const index = fs.fsReadJSON('/skills/index.json') || {}; index[name] = { name, description, category: 'general', tags }; fs.fsWriteJSON('/skills/index.json', index); @@ -1346,14 +1346,26 @@ renderOllamaModels() { const container = document.getElementById('ollama-models'); const select = document.getElementById('ollama-current-model'); + const searchInput = document.getElementById('ollama-search'); + const countDiv = document.getElementById('ollama-model-count'); + const query = (searchInput ? searchInput.value : '').toLowerCase().trim(); container.innerHTML = ''; select.innerHTML = '<option value="">Select a model</option>'; const models = this.ollamaManager.getModels(); if (models.length === 0) { container.innerHTML = '<div class="text-center" style="padding:20px; color:#666">Connect to see models</div>'; + if (countDiv) countDiv.textContent = ''; return; } - models.forEach(model => { + const filtered = query ? models.filter(m => m.name.toLowerCase().includes(query)) : models; + if (countDiv) countDiv.textContent = filtered.length === models.length + ? models.length + ' model' + (models.length !== 1 ? 's' : '') + : filtered.length + ' / ' + models.length + ' model' + (models.length !== 1 ? 's' : ''); + if (filtered.length === 0) { + container.innerHTML = '<div class="text-center" style="padding:20px; color:#888">No models match "' + query + '"</div>'; + return; + } + filtered.forEach(model => { const div = document.createElement('div'); div.className = 'model-item'; div.innerHTML = `<span>${model.name}</span><span style="font-size:0.8em; color:#666">${model.size ? (model.size / 1e9).toFixed(1) + 'GB' : ''}</span>`; diff --git a/modules/ext/alias.js b/modules/ext/alias.js index 4ffab72..80663ac 100644 --- a/modules/ext/alias.js +++ b/modules/ext/alias.js @@ -104,5 +104,5 @@ class AliasModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new AliasModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/ext/alias.js'); + webx11.moduleManager.registerModule(new AliasModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/alias.js'); } diff --git a/modules/ext/busybox.js b/modules/ext/busybox.js index f143087..709324f 100644 --- a/modules/ext/busybox.js +++ b/modules/ext/busybox.js @@ -405,3 +405,6 @@ class BusyBoxModule { this.terminal.print(expanded, 'info'); } } +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new BusyBoxModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/busybox.js'); +} diff --git a/modules/ext/calc.js b/modules/ext/calc.js index e28079e..9b602f6 100644 --- a/modules/ext/calc.js +++ b/modules/ext/calc.js @@ -73,5 +73,5 @@ class CalcModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new CalcModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/calc.js'); + webx11.moduleManager.registerModule(new CalcModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/calc.js'); } diff --git a/modules/ext/cron.js b/modules/ext/cron.js index 66cd18e..5ed5ff6 100644 --- a/modules/ext/cron.js +++ b/modules/ext/cron.js @@ -106,5 +106,5 @@ class CronModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new CronModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/cron.js'); + webx11.moduleManager.registerModule(new CronModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/cron.js'); } diff --git a/modules/ext/cryptogzip.js b/modules/ext/cryptogzip.js index 6f873ff..b67e067 100644 --- a/modules/ext/cryptogzip.js +++ b/modules/ext/cryptogzip.js @@ -1,9 +1,18 @@ class CryptoGzipModule { // CryptoGzipModule — VFS-level gzip/encrypt commands for WebX11 newOS4 - // Commands: gzip <path>, gunzip <path.gz>, encrypt <path>, decrypt <path.enc> - constructor(terminal) { - this.terminal = terminal; + // Commands: gzip <path>, gunzip <path.gz>, encrypt <path> <passphrase>, decrypt <path.enc> <passphrase> + constructor() { this.commands = {}; + this.terminal = null; + } + + getHelpInfo() { + return { + 'gzip': 'gzip <path> — Compress a VFS file to .gz', + 'gunzip': 'gunzip <path.gz> — Decompress a .gz file', + 'encrypt': 'encrypt <path> <passphrase> — AES-256-GCM encrypt a VFS file', + 'decrypt': 'decrypt <path.enc> <passphrase> — Decrypt a .enc file' + }; } _vfsExists(path) { @@ -279,3 +288,6 @@ class CryptoGzipModule { }; } } +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new CryptoGzipModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/cryptogzip.js'); +} diff --git a/modules/ext/diff.js b/modules/ext/diff.js index 8b79b6b..64469ac 100644 --- a/modules/ext/diff.js +++ b/modules/ext/diff.js @@ -283,5 +283,5 @@ class DiffModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new DiffModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/diff.js'); + webx11.moduleManager.registerModule(new DiffModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/diff.js'); } diff --git a/modules/ext/editor.js b/modules/ext/editor.js index 571a535..c597dd5 100644 --- a/modules/ext/editor.js +++ b/modules/ext/editor.js @@ -66,5 +66,5 @@ class EditorModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new EditorModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/editor.js'); + webx11.moduleManager.registerModule(new EditorModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/editor.js'); } diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js index 5532cee..4074f06 100644 --- a/modules/ext/filemgr.js +++ b/modules/ext/filemgr.js @@ -7,6 +7,7 @@ class FileManagerModule { this._selectedFile = null; this._searchFilter = ''; this._rawMode = false; + this._inlineHtml = false; } requirements() { @@ -15,8 +16,8 @@ class FileManagerModule { getHelpInfo() { return { - 'files': 'Open the file manager overlay with VFS directory listing and preview', - 'preview': 'Preview a specific file (usage: preview <path>)', + 'files': 'files — Open the file manager overlay with VFS directory listing, preview, encrypt, zip', + 'preview': 'preview <path> — Preview a specific file', }; } @@ -52,15 +53,16 @@ class FileManagerModule { _detectType(filePath) { const ext = filePath.split('.').pop().toLowerCase(); if (!ext || ext === filePath) return 'other'; - const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - const videoExts = ['mp4', 'webm']; - const audioExts = ['mp3', 'wav', 'ogg']; - const codeExts = ['js', 'py', 'html', 'css', 'yaml', 'yml', 'toml']; + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico']; + const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv']; + const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a']; + const codeExts = ['js', 'py', 'css', 'yaml', 'yml', 'toml']; if (ext === 'md') return 'md'; if (imageExts.includes(ext)) return 'image'; if (videoExts.includes(ext)) return 'video'; if (audioExts.includes(ext)) return 'audio'; if (codeExts.includes(ext)) return 'code'; + if (ext === 'html') return 'html'; if (ext === 'json') return 'json'; if (ext === 'csv') return 'csv'; if (ext === 'pdf') return 'pdf'; @@ -113,7 +115,10 @@ class FileManagerModule { const fileName = path.split('/').pop() || path; // Handle binary detection for media types - const isLikelyBinary = typeof content === 'string' && content.length > 0 && !content.startsWith('\n') && !content.startsWith('\r') && !content.startsWith(' ') && !content.startsWith('<') && !content.startsWith('{') && !content.startsWith('[') && content.charCodeAt(0) < 32 && content.charCodeAt(0) !== 9 && content.charCodeAt(0) !== 10 && content.charCodeAt(0) !== 13; + if (typeof content !== 'string') { + return '<div style="padding:20px;text-align:center;color:var(--text-muted)">Cannot preview: non-string content</div>'; + } + const isLikelyBinary = content.length > 0 && !content.startsWith('\n') && !content.startsWith('\r') && !content.startsWith(' ') && !content.startsWith('<') && !content.startsWith('{') && !content.startsWith('[') && content.charCodeAt(0) < 32 && content.charCodeAt(0) !== 9 && content.charCodeAt(0) !== 10 && content.charCodeAt(0) !== 13; const isTextContent = typeof content === 'string' && (content.startsWith('{') || content.startsWith('[') || content.startsWith('<') || content.startsWith('#') || /^[\w\s\n\r\t.,;:!?()\-=+*@#$%^&"'\/\\|`~]/.test(content.substring(0, 100))); @@ -121,6 +126,17 @@ class FileManagerModule { return '<div class="markdown-preview" style="padding:12px;font-family:var(--font-primary);line-height:1.5">' + this._renderMarkdown(content) + '</div>'; } + if (type === 'html') { + // Render HTML in an inline sandbox frame + const safeHtml = content + .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') + .replace(/on\w+\s*=\s*"[^"]*"/gi, '') + .replace(/on\w+\s*=\s*'[^']*'/gi, ''); + return '<div style="padding:0;width:100%;height:60vh;border:none;overflow:hidden">' + + '<iframe srcdoc="' + this._escapeHtml(safeHtml.replace(/"/g, '"').replace(/'/g, ''')) + '" ' + + 'style="width:100%;height:100%;border:none;background:#fff" sandbox="allow-same-origin"></iframe></div>'; + } + if (type === 'text') { const truncated = content.substring(0, 2000); const extra = content.length > 2000 ? '<div style="color:var(--text-muted);margin-top:8px">(truncated to 2000 chars)</div>' : ''; @@ -158,11 +174,14 @@ class FileManagerModule { } if (type === 'image') { - if (isLikelyBinary && !isTextContent) { - return '<div style="padding:20px;text-align:center;color:var(--text-muted)">📷 Image — <span style="color:var(--info-color);cursor:pointer;text-decoration:underline" onclick="' + this._escapeHtml("var a=document.createElement('a');a.href='data:image/" + ext + ";base64," + btoa(content) + "';a.download='" + fileName + "';a.click()") + '">download to view</span></div>'; + // VFS stores images as base64 strings — detect and render inline + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {jpg:'image/jpeg',jpeg:'image/jpeg',png:'image/png',gif:'image/gif',webp:'image/webp',bmp:'image/bmp',ico:'image/x-icon'}; + const mime = mimeTypes[ext] || 'image/png'; + return '<div style="padding:12px;text-align:center;max-width:100%"><img src="data:' + mime + ';base64,' + content + '" style="max-width:100%;max-height:70vh;border-radius:4px;border:1px solid var(--border-color)" alt="' + this._escapeHtml(fileName) + '"></div>'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '<div style="padding:12px;text-align:center"><img src="data:image/' + ext + ';base64,' + base64 + '" style="max-width:80%;max-height:60vh;border-radius:4px;border:1px solid var(--border-color)" alt="' + this._escapeHtml(fileName) + '"></div>'; + return '<div style="padding:20px;text-align:center;color:var(--text-muted)">📷 Image — cannot render, file may be stored as binary</div>'; } if (type === 'svg') { @@ -187,28 +206,31 @@ class FileManagerModule { } if (type === 'video') { - if (isLikelyBinary && !isTextContent) { - return '<div style="padding:20px;text-align:center;color:var(--text-muted)">📷 Video — <span style="color:var(--info-color);cursor:pointer;text-decoration:underline" onclick="var a=document.createElement(\'a\');a.href=\'data:video/' + ext + ';base64,' + btoa(content) + '\';a.download=\'' + fileName + '\';a.click()">download to view</span></div>'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp4:'video/mp4',webm:'video/webm',avi:'video/x-msvideo',mov:'video/quicktime',mkv:'video/x-matroska'}; + const mime = mimeTypes[ext] || 'video/mp4'; + return '<div style="padding:12px;text-align:center"><video controls style="max-width:90%;max-height:65vh;border-radius:4px"><source src="data:' + mime + ';base64,' + content + '" type="' + mime + '"></video></div>'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '<div style="padding:12px;text-align:center"><video controls style="max-width:80%;max-height:60vh;border-radius:4px"><source src="data:video/' + ext + ';base64,' + base64 + '" type="video/' + ext + '"></video></div>'; + return '<div style="padding:20px;text-align:center;color:var(--text-muted)">🎬 Video — cannot render</div>'; } if (type === 'audio') { - if (isLikelyBinary && !isTextContent) { - return '<div style="padding:20px;text-align:center;color:var(--text-muted)">🎵 Audio — <span style="color:var(--info-color);cursor:pointer;text-decoration:underline" onclick="var a=document.createElement(\'a\');a.href=\'data:audio/' + ext + ';base64,' + btoa(content) + '\';a.download=\'' + fileName + '\';a.click()">download to play</span></div>'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp3:'audio/mpeg',wav:'audio/wav',ogg:'audio/ogg',flac:'audio/flac',aac:'audio/aac',m4a:'audio/mp4'}; + const mime = mimeTypes[ext] || 'audio/mpeg'; + return '<div style="padding:12px;text-align:center"><audio controls style="max-width:90%"><source src="data:' + mime + ';base64,' + content + '" type="' + mime + '"></audio></div>'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '<div style="padding:12px;text-align:center"><audio controls style="max-width:80%"><source src="data:audio/' + ext + ';base64,' + base64 + '" type="audio/' + ext + '"></audio></div>'; + return '<div style="padding:20px;text-align:center;color:var(--text-muted)">🎵 Audio — cannot render</div>'; } if (type === 'pdf') { - try { - const base64 = btoa(unescape(encodeURIComponent(content))); - return '<div style="padding:12px;text-align:center;height:70vh"><embed src="data:application/pdf;base64,' + base64 + '" type="application/pdf" style="width:100%;height:100%;border:1px solid var(--border-color);border-radius:4px"></div>'; - } catch (e) { - return '<div style="padding:20px;text-align:center;color:var(--text-muted)">📄 PDF — <span style="color:var(--info-color);cursor:pointer;text-decoration:underline" onclick="var a=document.createElement(\'a\');a.href=\'data:application/pdf;base64,' + btoa(content) + '\';a.download=\'' + fileName + '\';a.click()">download to view</span></div>'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + return '<div style="padding:12px;text-align:center;height:70vh"><embed src="data:application/pdf;base64,' + content + '" type="application/pdf" style="width:100%;height:100%;border:1px solid var(--border-color);border-radius:4px"></div>'; } + return '<div style="padding:20px;text-align:center;color:var(--text-muted)">📄 PDF — cannot render</div>'; } if (type === 'csv') { @@ -285,7 +307,7 @@ class FileManagerModule { const overlay = document.createElement('div'); overlay.id = 'filemgr-overlay'; - overlay.style.cssText = 'position:fixed;inset:0;z-index:1800;display:flex;flex-direction:column;background:var(--bg-color);color:var(--text-color);font-family:var(--font-primary);font-size:14px;'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:1800;display:flex;flex-direction:column;background:var(--bg-color);color:var(--text-color);font-family:var(--font-primary);font-size:14px;height:100vh;'; // Inject responsive styles for the overlay const styleId = 'filemgr-styles'; @@ -318,10 +340,11 @@ class FileManagerModule { border-right: none !important; border-bottom: 1px solid var(--border-color); } - #filemgr-overlay #filemgr-preview { - width: 100% !important; - min-height: 40vh; - } + #filemgr-overlay #filemgr-preview { + width: 100% !important; + flex: 1 !important; + min-height: 0; + } #filemgr-action-bar { flex-wrap: wrap; gap: 6px; @@ -384,12 +407,12 @@ class FileManagerModule { const mainArea = document.createElement('div'); mainArea.id = 'filemgr-main-container'; mainArea.className = 'filemgr-main'; - mainArea.style.cssText = 'overflow:hidden;'; + mainArea.style.cssText = 'flex:1;overflow:hidden;display:flex;min-height:0;'; // Sidebar const sidebar = document.createElement('div'); sidebar.id = 'filemgr-sidebar'; - sidebar.style.cssText = 'width:30%;min-width:200px;overflow-y:auto;background:var(--bg-secondary);border-right:1px solid var(--border-color);'; + sidebar.style.cssText = 'width:25%;min-width:150px;overflow-y:auto;background:var(--bg-secondary);border-right:1px solid var(--border-color);'; mainArea.appendChild(sidebar); @@ -413,6 +436,18 @@ class FileManagerModule { statusSpan.textContent = 'No file selected'; actionBar.appendChild(statusSpan); + const encryptBtn = document.createElement('button'); + encryptBtn.textContent = '🔐 Encrypt'; + encryptBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--warning-color);color:var(--warning-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + encryptBtn.onclick = () => this._actionEncrypt(); + actionBar.appendChild(encryptBtn); + + const compressBtn = document.createElement('button'); + compressBtn.textContent = '📦 Zip'; + compressBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--info-color);color:var(--info-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + compressBtn.onclick = () => this._actionCompress(); + actionBar.appendChild(compressBtn); + const deleteBtn = document.createElement('button'); deleteBtn.textContent = '🗑 Delete'; deleteBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--error-color);color:var(--error-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; @@ -431,6 +466,12 @@ class FileManagerModule { editBtn.onclick = () => this._actionEdit(); actionBar.appendChild(editBtn); + const newFileBtn = document.createElement('button'); + newFileBtn.textContent = '📄 New'; + newFileBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--success-color);color:var(--success-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + newFileBtn.onclick = () => this._actionNewFile(); + actionBar.appendChild(newFileBtn); + const previewToggleBtn = document.createElement('button'); previewToggleBtn.id = 'filemgr-toggle-raw'; previewToggleBtn.textContent = '📄 Raw'; @@ -727,9 +768,14 @@ class FileManagerModule { this._updateStatus('No file selected to edit'); return; } - if (typeof webx11 !== 'undefined' && webx11 && webx11.executeCommand) { - webx11.executeCommand('edit ' + this._selectedFile); - this._updateStatus('✏ Opened in editor: ' + this._selectedFile); + const path = this._selectedFile; + // Close file manager first so code editor modal (z-index:1000) is visible above + this._closeOverlay(); + if (typeof webx11 !== 'undefined' && webx11 && webx11.dispatchCommand) { + setTimeout(() => { + webx11.dispatchCommand('edit', [path]); + }, 100); + this._updateStatus('✏ Opened in editor: ' + path); } else { this._updateStatus('❌ Editor not available'); } @@ -748,8 +794,61 @@ class FileManagerModule { this._renderPreview(this._selectedFile, content); } } + + _actionEncrypt() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.enc')) { + this._updateStatus('⚠ Already encrypted (.enc)'); + return; + } + const passphrase = prompt('Enter passphrase for AES-256-GCM encryption:'); + if (!passphrase || passphrase.length < 4) { + this._updateStatus('❌ Passphrase too short (min 4 chars)'); + return; + } + // Delegate to cryptogzip module's encrypt command + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('encrypt ' + this._selectedFile + ' ' + passphrase); + this._updateStatus('🔐 Encrypted: ' + this._selectedFile + '.enc'); + this._refreshFileList(); + } + } + + _actionCompress() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.gz')) { + this._updateStatus('⚠ Already compressed (.gz)'); + return; + } + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('gzip ' + this._selectedFile); + this._updateStatus('📦 Compressed: ' + this._selectedFile + '.gz'); + this._refreshFileList(); + } + } + + _actionNewFile() { + const name = prompt('New file name (e.g. myfile.txt):'); + if (!name) return; + const pathStr = '/' + this.currentPath.join('/'); + const cleanPath = pathStr === '/' ? '/' + name : pathStr + '/' + name; + const content = prompt('Enter file content (optional):') || ''; + const ok = this.terminal.fs.writeFile(cleanPath, content); + if (ok) { + this._updateStatus('✅ Created: ' + cleanPath + ' (' + content.length + ' chars)'); + this._refreshFileList(); + } else { + this._updateStatus('❌ Failed to create file'); + } + } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new FileManagerModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/ext/filemgr.js'); + webx11.moduleManager.registerModule(new FileManagerModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/filemgr.js'); } diff --git a/modules/ext/finder.js b/modules/ext/finder.js index 1ca8cf7..8230668 100644 --- a/modules/ext/finder.js +++ b/modules/ext/finder.js @@ -43,5 +43,5 @@ class FinderModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new FinderModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/finder.js'); + webx11.moduleManager.registerModule(new FinderModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/finder.js'); } diff --git a/modules/ext/mindmap.js b/modules/ext/mindmap.js index 30d6889..9f62cba 100644 --- a/modules/ext/mindmap.js +++ b/modules/ext/mindmap.js @@ -166,7 +166,9 @@ class MindmapModule { if (!args[0]) return this.terminal.print('Usage: mindmap save <name>', 'warning'); if (!this.root) return this.terminal.print('No mindmap to save.', 'warning'); try { - localStorage.setItem('webx11_mindmap_' + args[0], JSON.stringify(this.root)); + const fs = this.terminal.fs; + fs.mkdirRecursive('/mindmaps'); + fs.writeFile('/mindmaps/' + args[0] + '.json', JSON.stringify(this.root, null, 2)); this.terminal.print('Mindmap saved as: ' + args[0], 'success'); } catch (e) { this.terminal.print('Failed to save: ' + e.message, 'error'); @@ -175,7 +177,8 @@ class MindmapModule { _load(args) { if (!args[0]) return this.terminal.print('Usage: mindmap load <name>', 'warning'); try { - const data = localStorage.getItem('webx11_mindmap_' + args[0]); + const fs = this.terminal.fs; + const data = fs.readFile('/mindmaps/' + args[0] + '.json'); if (!data) return this.terminal.print('No saved mindmap: ' + args[0], 'error'); this.root = JSON.parse(data); this.terminal.print('Loaded mindmap: ' + args[0] + ' (' + this._countNodes(this.root) + ' nodes)', 'success'); @@ -184,18 +187,17 @@ class MindmapModule { } } _list() { - const keys = []; - for (let i = 0; i < localStorage.length; i++) { - const k = localStorage.key(i); - if (k && k.startsWith('webx11_mindmap_')) keys.push(k.replace('webx11_mindmap_', '')); - } + const fs = this.terminal.fs; + const files = fs.readDir('/mindmaps') || []; + const keys = files.filter(f => f.name.endsWith('.json')).map(f => f.name.replace('.json', '')); if (!keys.length) return this.terminal.print('No saved mindmaps.', 'info'); this.terminal.print('=== Saved Mindmaps ===', 'info'); keys.forEach(k => this.terminal.print(' ' + k)); } _delete(args) { if (!args[0]) return this.terminal.print('Usage: mindmap delete <name>', 'warning'); - localStorage.removeItem('webx11_mindmap_' + args[0]); + const fs = this.terminal.fs; + fs.deleteFile('/mindmaps/' + args[0] + '.json'); this.terminal.print('Deleted mindmap: ' + args[0], 'success'); } _rename(args) { @@ -232,5 +234,5 @@ class MindmapModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new MindmapModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/mindmap.js'); + webx11.moduleManager.registerModule(new MindmapModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/mindmap.js'); } diff --git a/modules/ext/shell.js b/modules/ext/shell.js index d5e6a43..6beb0eb 100644 --- a/modules/ext/shell.js +++ b/modules/ext/shell.js @@ -515,3 +515,6 @@ class ShellModule { this.terminal.print(`Script ${path} completed (${lineNum} lines)`, 'success'); } } +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new ShellModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/shell.js'); +} diff --git a/modules/ext/skill-pack-core.js b/modules/ext/skill-pack-core.js index 51a2e55..cc8ae88 100644 --- a/modules/ext/skill-pack-core.js +++ b/modules/ext/skill-pack-core.js @@ -283,3 +283,6 @@ class SkillPackCoreModule { this._seeded = true; } } +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new SkillPackCoreModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/skill-pack-core.js'); +} diff --git a/modules/ext/theme.js b/modules/ext/theme.js index 9a1dab5..1d99b2c 100644 --- a/modules/ext/theme.js +++ b/modules/ext/theme.js @@ -168,5 +168,5 @@ class ThemeModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new ThemeModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/theme.js'); + webx11.moduleManager.registerModule(new ThemeModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/theme.js'); } diff --git a/modules/ext/voice.js b/modules/ext/voice.js index 0fd96cd..06c5480 100644 --- a/modules/ext/voice.js +++ b/modules/ext/voice.js @@ -98,5 +98,5 @@ class VoiceModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new VoiceModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/voice.js'); + webx11.moduleManager.registerModule(new VoiceModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/voice.js'); } diff --git a/modules/ext/wpm.js b/modules/ext/wpm.js index 9c8d820..4dc9c29 100644 --- a/modules/ext/wpm.js +++ b/modules/ext/wpm.js @@ -2,7 +2,7 @@ class WpmModule { constructor() { this.commands = {}; this.terminal = null; - this.indexUrl = 'https://webx11.duckdns.org/spaces/chatstudio/newos4/packages.json'; + this.indexUrl = 'https://webx11.duckdns.org/spaces/newos4/packages.json'; this.index = null; this.installed = this.loadInstalled(); } @@ -145,5 +145,5 @@ class WpmModule { } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new WpmModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/wpm.js'); + webx11.moduleManager.registerModule(new WpmModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/wpm.js'); } diff --git a/review-newos4.html b/review-newos4.html new file mode 100644 index 0000000..1108c81 --- /dev/null +++ b/review-newos4.html @@ -0,0 +1,333 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Review: Module OS v4.2 — WebX11 Terminal</title> + <link rel="stylesheet" href="/spaces/assets/css/theme.css"> + <style> + :root { --cs-glass: rgba(17,17,17,0.75); --cs-glass-border: rgba(0,255,136,0.1); } + html, body { max-width: 100vw; overflow-x: hidden; } + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background: var(--bg-primary); color: var(--text-primary); + min-height: 100vh; line-height: 1.6; + } + a { color: var(--accent); text-decoration: none; } + a:hover { color: var(--accent-dim); } + .container { max-width: 800px; margin: 0 auto; padding: 0 24px; } + header { + background: rgba(0,0,0,0.85); backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + padding: 14px 0; position: sticky; top: 0; z-index: 10000; + } + .header-content { + display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; gap: 12px; + } + .logo { font-size: 1.2em; font-weight: 700; } + .logo-icon { font-size: 1.3em; } + + h1 { font-size: 2.2em; margin: 40px 0 10px; background: linear-gradient(135deg, var(--accent), #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } + .meta { color: var(--text-secondary); margin-bottom: 30px; } + .summary-box { + background: var(--bg-secondary); border: 1px solid var(--border); + border-radius: 12px; padding: 20px; margin: 30px 0; + display: flex; gap: 20px; flex-wrap: wrap; + } + .summary-box .stat { + flex: 1; min-width: 100px; text-align: center; + } + .summary-box .value { font-size: 1.8em; font-weight: 700; color: var(--accent); } + .summary-box .label { font-size: 0.8em; color: var(--text-secondary); } + .rating { font-size: 1.5em; } + + section { margin: 40px 0; } + section h2 { font-size: 1.4em; margin-bottom: 20px; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 8px; } + h3 { margin: 20px 0 10px; font-size: 1.1em; } + + ul { padding-left: 20px; } + li { margin-bottom: 8px; color: var(--text-secondary); } + li strong { color: var(--text-primary); } + + .tag { + display: inline-block; background: rgba(0,255,136,0.08); + border: 1px solid rgba(0,255,136,0.2); color: var(--accent); + padding: 2px 10px; border-radius: 12px; font-size: 0.8em; + margin: 2px; + } + .pro { border-left: 3px solid #22c55e; padding-left: 16px; margin: 8px 0; border-radius: 2px; } + .con { border-left: 3px solid #ef4444; padding-left: 16px; margin: 8px 0; } + + @media (max-width: 600px) { + .container { padding: 0 16px; } + h1 { font-size: 1.6em; } + } + </style> +</head> +<body> +<header> + <div class="header-content"> + <span class="logo"><span class="logo-icon">🧩</span> Module OS v4.2 Review</span> + <div style="display:flex;gap:8px;flex-wrap:wrap;"> + <a href="/spaces/newos4/help.html">📖 Help →</a> + <a href="/spaces/newos4/main.html">Launch OS →</a> + </div> + </div> +</header> + +<div class="container"> + +<h1>Module OS v4.2</h1> +<p class="meta">WebX11 Terminal · Self-improving Modular OS · Reviewed 2026-05-28 · ✅ Live QA Passed</p> + +<div class="summary-box"> + <div class="stat"><div class="value">41</div><div class="label">Modules</div></div> + <div class="stat"><div class="value">130+</div><div class="label">Commands</div></div> + <div class="stat"><div class="value">56KB</div><div class="label">Help Docs</div></div> + <div class="stat"><div class="value">36KB</div><div class="label">App Size</div></div> + <div class="stat"><div class="value">✅</div><div class="label">QA Status</div></div> +</div> + +<div class="callout" style="background:rgba(0,255,136,0.08); border:1px solid var(--accent); border-radius:8px; padding:16px; margin:20px 0;"> + <strong>✅ Live Test Results (2026-05-28)</strong><br> + <span style="color:var(--text-secondary);font-size:0.9em;"> + Boot/Init: ✅ 41 modules loaded, splash hidden, 0 JS errors<br> + Core Terminal: ✅ help, ls, put, cat, clear, mem all working<br> + File Persistence: ✅ VFS survives page reload, <code>_dirty</code> flag skips idle saves<br> + UI Modals: ✅ Skills Manager, File Browser, Config, WebDAV Browser, File Manager<br> + Autocomplete: ✅ Tab cycle, Enter/click accept, Escape hide<br> + All 16 ext modules: calc, voice, cron, theme, wpm, mindmap, webdav, skill-pack-core, editor, diff, alias, finder, filemgr, cryptogzip, busybox, shell — load without errors<br> + AI Streaming: ✅ NDJSON stream parsing, real-time token display<br> + WebDAV Browser: ✅ Modal file explorer, clickable dirs, preview, upload/download, breadcrumbs<br> + Session Nesting: ✅ Fixed — deep clone + strip <code>_sessions</code>, no recursive growth<br> + Terminal History: ✅ Persists across page reload<br> + SecretVault: ✅ AES-GCM encrypted credential storage via Web Crypto API<br> + Context Compression: ✅ Auto-compresses AI history when >2000 tokens<br> + Trash System: ✅ rm moves to trash, 4 dedicated trash commands<br> + VFS Checkpoints: ✅ snapshot/rollback via checkpoint command<br> + Requirements Check: ✅ all modules report prerequisites via <code>check</code> command + </span> +</div> + +<section> + <h2>Architecture</h2> + <p>newOS4 v4.2 uses a <strong>modular class-based architecture</strong> with async dependency loading:</p> + <ul> + <li><strong>module-loader.js</strong> — 4-phase async loader: VFS → ModuleManager → OllamaManager/Terminal → Ext modules. Cache-busting via localStorage version key + per-load timestamps.</li> + <li><strong>VirtualFileSystem</strong> — localStorage-backed in-browser filesystem with VFS checkpoints (snapshot/rollback), trash system (rm → trash, ls-trash, empty-trash), and dirty-flag auto-save.</li> + <li><strong>ModuleManager</strong> — Central registry with load/unload/reload, ASI-safe comment-stripped class parsing, <code>loadModuleFromCode()</code> for runtime module loading.</li> + <li><strong>OllamaManager → LLMRouter</strong> — Multi-provider router with auto-fallback chain. Supports Ollama (native) + any OpenAI-compatible API (OpenRouter). SSE streaming parsing. Provider priority-based failover.</li> + <li><strong>SecretVault</strong> — AES-256-GCM encrypted credential manager via Web Crypto API. PBKDF2 key derivation (100K iterations). PIN or open mode.</li> + <li><strong>Terminal</strong> — Full terminal with autocomplete, command history, pipe support, clickable output, dirty-flag auto-save, output trimming (200-line cap with memory-pressure auto-reduction).</li> + </ul> + + <h3>Module System</h3> + <p><strong>25 core modules</strong> (foundation + built-in) + <strong>16 ext modules</strong> = 41 total. All ES6 classes registered via <code>webx11.moduleManager.registerModule()</code>. Ext modules are togglable via the startup gear on splash. Modules report requirements via <code>requirements()</code> method — checked by the <code>check</code> command.</p> + <p><strong>Dynamic help system:</strong> Every module implements <code>getHelpInfo()</code> returning a command → description map. <code>ModuleManager.getAllHelp()</code> collects all entries at runtime. No more hardcoded 195-line help map. New modules appear in <code>help</code> immediately.</p> +</section> + +<section> + <h2>Key Features</h2> + + <h3>🧠 AI Assistant (WebX11AIAssistantModule)</h3> + <ul> + <li>Agentic loop with up to <strong>8 tool-call turns</strong> (was 5)</li> + <li><strong>27 tool definitions</strong>: all standard tools + <code>session_terminal</code> for reading last 50 lines of terminal history</li> + <li><strong>Tool failure tracking</strong> — after 3 failures on the same tool, warns user to create a workaround skill</li> + <li><strong>Per-tool timing logs</strong> — every tool call logged with duration in terminal console</li> + <li><strong>Streaming responses</strong> — NDJSON stream parsing shows tokens in real-time</li> + <li><strong>Context compression</strong> — auto-compresses when conversation exceeds 2000 tokens (summarizes old messages, keeps last 3 exchanges)</li> + <li><strong>Manual compression</strong> via <code>ai-compress</code> command</li> + <li>Auto-loads relevant skills and recent session memory at start</li> + <li>Inline destructive action confirmation with 120-second countdown</li> + <li>Status bar "🤖 AI Thinking..." spinner while processing</li> + <li>Post-task reflection suggesting skill creation</li> + <li>LLMRouter-backed — auto-falls to next provider on failure</li> + </ul> + + <h3>🔐 SecretVault (Core Module)</h3> + <ul> + <li>AES-256-GCM encrypted credential storage via Web Crypto API</li> + <li>PBKDF2 key derivation: 100,000 iterations, SHA-256, 256-bit key</li> + <li>Two modes: PIN mode (password-derived) or open mode (random hex key)</li> + <li>7 CLI commands: <code>secret-set</code>, <code>secret-get</code>, <code>secret-list</code>, <code>secret-del</code>, <code>secret-lock</code>, <code>secret-unlock</code>, <code>secret-setpin</code></li> + <li>Auto-redaction: <code>redactSecret()</code> scans output for stored secrets and replaces with <code>[REDACTED]</code></li> + <li>Integration: <code>ollama-manager</code> stores API keys via SecretVault; modules check <code>window.webx11_secretVault</code></li> + </ul> + + <h3>🔄 LLMRouter (OllamaManager)</h3> + <ul> + <li>Multi-provider router: supports Ollama (native <code>/api/chat</code> + <code>/api/generate</code>) and any OpenAI-compatible API (OpenRouter, etc.)</li> + <li>Provider priority chain — falls to next on failure, tracks <code>fallbackCount</code> per provider</li> + <li>SSE streaming for OpenAI-compatible endpoints: parses <code>data: {"choices":[{"delta":{"content":"..."}}]}</code> format</li> + <li>Auto-probes provider on <code>connect()</code> via <code>/api/tags</code> (Ollama) or <code>/v1/models</code> (OpenAI)</li> + <li>API keys stored via SecretVault when available</li> + </ul> + + <h3>📚 Skills System (SkillsModule)</h3> + <ul> + <li>8 CLI commands: <code>skill-list</code>, <code>skill-view</code>, <code>skill-create</code>, <code>skill-patch</code>, <code>skill-delete</code>, <code>skill-export</code>, <code>skill-import</code>, <code>skill-sync</code></li> + <li><strong>Skill index caching</strong> — in-memory index with 2-second debounced flush to VFS, eliminates repeated <code>fsReadJSON</code> calls</li> + <li>Skills Manager UI modal (📚 button in quick-actions)</li> + <li>Stored as Markdown with YAML frontmatter in VFS <code>/skills/</code></li> + <li>Auto-loads relevant skills at the start of every AI task</li> + <li>Auto-creates error workaround skills after 3+ repeated errors</li> + <li>Auto-audits skills for staleness every 20 messages</li> + </ul> + + <h3>🗑️ Trash System</h3> + <ul> + <li><code>rm</code> moves files to trash instead of permanent deletion (safer)</li> + <li>5 dedicated commands: <code>trash</code>, <code>trash-dir</code>, <code>ls-trash</code>, <code>empty-trash</code>, <code>rm -rf</code> for real deletion</li> + <li>Trash persists in VFS across page reloads</li> + </ul> + + <h3>📸 VFS Checkpoints</h3> + <ul> + <li>Snapshots: <code>checkpoint [name]</code> — saves full VFS state to <code>/_snapshots/</code></li> + <li>Rollback: <code>rollback <name></code> — restores VFS from snapshot</li> + <li>Management: <code>checkpoint-list</code>, <code>checkpoint-del</code></li> + <li>Auto-prunes oldest checkpoints when exceeding 10</li> + </ul> + + <h3>📁 File Manager (Ext Module)</h3> + <ul> + <li>Fullscreen overlay with 30% sidebar + 70% preview pane</li> + <li>Commands: <code>files</code>, <code>preview <path></code></li> + <li>Preview types: Markdown (regex render), images (data URI), video/audio, code (line-numbered table), JSON (highlighted), CSV, PDF (embed)</li> + <li>Live search filter (Ctrl+F), action bar (delete, download, edit via editor.js)</li> + <li>Breadcrumb navigation, Raw/Rendered toggle</li> + <li>50KB file truncation warning, empty dir indicator, binary fallback</li> + </ul> + + <h3>🧰 Tool Registry — <code>requirements()</code></h3> + <ul> + <li>Every module implements <code>requirements()</code> returning <code>{ met, missing }</code></li> + <li><code>check</code> command runs all requirements and reports overall health</li> + <li><code>commands</code> command shows module source + requirements per module</li> + </ul> + + <h3>🎤 Voice (VoiceModule)</h3> + <ul> + <li>🎤 button in input area next to ⏎ Send</li> + <li>Green pulse animation while listening, red on error</li> + <li>SpeechRecognition API (Chrome, Edge, Safari 14.1+), graceful fallback on Firefox</li> + <li>Visibility state persists in localStorage</li> + </ul> + + <h3>💾 Session Persistence</h3> + <ul> + <li>Auto-saves every 3 minutes — but only when <code>_dirty</code> flag is set (idle = 0 writes)</li> + <li>Nesting bug fixed: deep clones VFS, strips <code>_sessions</code> before serialization</li> + <li>Terminal command history persists across page reloads</li> + <li><strong>Output trimming</strong>: 200-line cap with memory-pressure auto-trim to 100/50 when heap >80MB/120MB</li> + </ul> + + <h3>Other Key Modules</h3> + <ul> + <li><strong>SearXNG + DuckDuckGo</strong> — Web search with fallback</li> + <li><strong>Web Fetcher</strong> — Fetch and extract clean content from any URL</li> + <li><strong>WebDAV Sync</strong> — 8 commands + browser modal for cloud sync (config, push, pull, backup, restore, del)</li> + <li><strong>Shell Module</strong> — Full shell: pipe <code>|</code>, redirects <code>> >> <</code>, variables <code>$VAR</code>, chaining <code>&& || ;</code>, VFS script execution</li> + <li><strong>BusyBox</strong> — 16 utility commands: which, env, date, cal, yes, seq, base64, printf, cut, uniq, hostname, whoami, basename, dirname, sleep, expand</li> + <li><strong>CryptoGzip</strong> — AES-256-GCM encrypt/decrypt and gzip/gunzip for VFS files</li> + <li><strong>Session System</strong> — save/load/list/delete/export/import via AI tools</li> + <li><strong>My Data</strong> — Persistent user profile with 7 CLI commands</li> + <li><strong>Ollama Model Manager</strong> — models, model-info, model-pull, model-delete, model-run</li> + <li><strong>Cron Scheduler</strong> — schedule commands at intervals (min 5s), localStorage persistence</li> + <li><strong>Alias Module</strong> — define/manage command aliases (ll, h, cls defaults) with localStorage persistence</li> + <li><strong>Text Editor</strong> — inline VFS file editing via modal (edit, save, cancel)</li> + <li><strong>Theme Switcher</strong> — dark, light, amber, cyber + custom</li> + <li><strong>Command Aliases</strong> — ll, h, cls defaults with localStorage persistence</li> + </ul> + + <h3>All Modules (41 total)</h3> + <p><strong>Foundation (5):</strong> virtual-fs.js, module-manager.js, secret-vault.js, ollama-manager.js, terminal.js</p> + <p><strong>Core (20):</strong> module-system.js, module-fs.js, module-modmgr.js, module-ollama.js, module-my.js, module-searxng.js, module-servermon.js, module-ollamamodels.js, module-config.js, module-simplegui.js, module-io.js, module-dynamicprompts.js, module-aiassist.js, module-aiasm.js, module-smartctx.js, module-duckduckgo.js, module-fetcher.js, module-skills.js, module-chatstudio.js, module-fs.js</p> + <p><strong>Ext (16):</strong> calc.js, voice.js, cron.js, theme.js, wpm.js, mindmap.js, webdav.js, skill-pack-core.js, editor.js, diff.js, alias.js, finder.js, filemgr.js, cryptogzip.js, busybox.js, shell.js</p> +</section> + +<section> + <h2>Recent Improvements</h2> + + <h3>🔐 SecretVault (2026-05-25)</h3> + <p>AES-256-GCM encrypted credential manager using Web Crypto API. PBKDF2 key derivation (100K iterations) with salt. PIN mode or open mode. 7 CLI commands. Auto-redacts stored secrets from output. Integrated with LLMRouter for API key storage.</p> + + <h3>🔄 LLMRouter (2026-05-25)</h3> + <p>Refactored from single-endpoint OllamaManager into a multi-provider router with priority-based fallback. Supports Ollama and any OpenAI-compatible API (OpenRouter, etc.). SSE streaming parsing. Provider auto-fallback on failure with per-provider stats tracking.</p> + + <h3>📦 Context Compression (2026-05-25)</h3> + <p>When AI conversation exceeds 2000 tokens, <code>_compressContext()</code> keeps last 3 exchanges, summarizes the rest into one system message via AI summarization or fallback truncation. Also available as <code>ai-compress</code> command. Max tool turns increased from 5 to 8.</p> + + <h3>📸 VFS Checkpoints + Trash (2026-05-25)</h3> + <p>Snapshot/rollback system (<code>checkpoint</code>, <code>rollback</code>, <code>checkpoint-list</code>, <code>checkpoint-del</code>) plus trash system (<code>rm</code> → trash, <code>trash-dir</code>, <code>ls-trash</code>, <code>empty-trash</code>). Auto-prunes to max 10 checkpoints.</p> + + <h3>🧰 Tool Registry + File Manager (2026-05-25)</h3> + <p>Tool Registry: every module implements <code>requirements()</code>. File Manager: 704-line fullscreen overlay with sidebar, preview pane, search, edit, download, delete commands.</p> + + <h3>🧰 Dynamic Help System & Module Loading Fixes (2026-05-25)</h3> + <p>Replaced <code>_buildHelpMap()</code> (195 lines) with <code>ModuleManager.getAllHelp()</code>. Fixed double-loading of ext modules (skip list + relative URL guard in <code>registerModule()</code>). Fixed ASI comment-stripping bug in class parsing. Added <code>loadModuleFromCode()</code> for runtime module loading. Fixed <code>check_updates</code> returning empty.</p> + + <h3>🧠 AI Session Terminal Tool + Error Tracking (2026-05-25)</h3> + <p>Added <code>session_terminal</code> tool: reads last 50 lines of terminal auto-save for context when user references past terminal work. Added <code>_trackToolFailure()</code>: after 3 failures on the same tool, warns user and suggests creating a workaround skill. Per-tool timing logs with duration.</p> + + <h3>📁 WebDAV Browser UI + File Manager (2026-05-25)</h3> + <p>Full modal file explorer for the dedicated WebDAV space, with clickable directory navigation, breadcrumbs, file preview (HTML in iframe, text inline), upload button, download on double-click, and refresh. New File Manager ext module adds <code>files</code> and <code>preview</code> commands with fullscreen overlay.</p> + + <h3>📦 4 New Ext Modules (2026-05-25)</h3> + <ul> + <li><strong>BusyBox</strong> — 16 utility commands: which, env, date, cal, yes, seq, base64, printf, cut, uniq, hostname, whoami, basename, dirname, sleep, expand</li> + <li><strong>Shell</strong> — Full shell: pipes, redirects, variables, chaining, .sh script execution</li> + <li><strong>CryptoGzip</strong> — AES-256-GCM encrypt/decrypt and gzip/gunzip for VFS files</li> + <li><strong>File Manager</strong> — Fullscreen file browser with 10+ preview renderers</li> + </ul> + + <h3>💾 Terminal Performance</h3> + <ul> + <li>Output trimming: 200-line cap with memory-pressure auto-reduction (100/50 lines when >80MB/120MB heap)</li> + <li>Auto-save interval: 60s → 180s, dirty-flag skip</li> + <li>History lazy-loads via <code>requestAnimationFrame</code></li> + <li>localStorage quota warning when approaching limit</li> + <li><code>mem</code> command: shows JS heap size, terminal lines, history buffer count</li> + </ul> +</section> + +<section> + <h2>Pros & Cons</h2> + + <div class="pro"><strong>✅ Self-improving</strong> — AI can create, patch, and manage its own skills</div> + <div class="pro"><strong>✅ Rich tool ecosystem</strong> — 41 modules, 130+ commands, search, fetch, webdav, cron, theming, calc, voice, encrypt, shell</div> + <div class="pro"><strong>✅ AI streaming + auto-compression</strong> — NDJSON stream parsing + context auto-compress at 2000 tokens</div> + <div class="pro"><strong>✅ SecretVault</strong> — AES-GCM encrypted credential storage, auto-redaction</div> + <div class="pro"><strong>✅ LLMRouter</strong> — multi-provider with auto-fallback, supports Ollama + any OpenAI-compatible API</div> + <div class="pro"><strong>✅ Dynamic help system</strong> — no hardcoded help map, new modules appear automatically</div> + <div class="pro"><strong>✅ VFS Checkpoints + Trash</strong> — snapshot/rollback and safe deletion with trash commands</div> + <div class="pro"><strong>✅ Tool Registry</strong> — every module reports requirements via <code>requirements()</code></div> + <div class="pro"><strong>✅ Clean terminal UX</strong> — autocomplete, command history, pipe support, clickable output, mobile responsive</div> + <div class="pro"><strong>✅ Efficient auto-save</strong> — dirty-flag check skips saves when idle, nesting bug fixed</div> + <div class="pro"><strong>✅ WebDAV Browser UI</strong> — modal file explorer with breadcrumb nav, preview, upload, download</div> + <div class="pro"><strong>✅ Self-contained</strong> — no backend needed except Ollama/OpenRouter for AI features</div> + <div class="pro"><strong>✅ Standalone at <code>/spaces/newos4/</code></strong> — removed duplicate chatstudio/newos4 instance, single source of truth</div> + + <div class="con"><strong>❌ Ollama/OpenAI dependency</strong> — AI features require a running provider</div> + <div class="con"><strong>❌ 8-turn limit</strong> — agent loop max is 8 tool calls, complex tasks may still hit ceiling</div> + <div class="con"><strong>❌ No module isolation</strong> — all modules share same JS scope</div> + <div class="con"><strong>❌ No TTS</strong> — voice input only, no text-to-speech for AI responses</div> + <div class="con"><strong>❌ No nginx token validation</strong> — WebDAV token is decorative; auth via API port 8896</div> +</section> + +<section> + <h2>Verdict</h2> + <p>newOS4 v4.2 is the most <strong>feature-rich</strong> version of the WebX11 Terminal. The refactored modular architecture (41 modules with dynamic help, SecretVault, LLMRouter, context compression, VFS checkpoints, trash system, tool registry, and file manager) makes it uniquely capable — while keeping the main.html at just 36KB.</p> + <p>The dedicated instance at <code>/spaces/newos4/</code> with nginx autoindex, WebDAV token authentication, and a proper API backend gives it real cloud-backed persistence. Session nesting is fixed, auto-save only fires on actual changes, the <code>mem</code> command gives live heap visibility, and output trimming prevents browser crashes under memory pressure.</p> + <p><strong>Best for:</strong> Power users, developers, and anyone who wants a browser-based terminal with AI that can learn, grow, and securely store credentials.</p> + <p><strong>Not for:</strong> Users who just want quick AI chat, or users without Ollama/OpenRouter access.</p> + <p>Rating: <strong>🧩🧩🧩🧩🧩</strong> (5/5 — most advanced WebX11 Terminal variant)</p> + <p style="margin-top:20px;">📖 <a href="/spaces/newos4/help.html" style="color:var(--accent);">Full documentation available here →</a></p> +</section> + +</div> +</body> +</html> diff --git a/skills/index.json b/skills/index.json index ad002f5..373efdf 100644 --- a/skills/index.json +++ b/skills/index.json @@ -1,149 +1,133 @@ { - "navigating-the-terminal": { - "name": "navigating-the-terminal", - "description": "How to explore the WebX11 terminal, find commands, check status, and navigate the filesystem", - "tags": [ - "terminal", - "navigation", - "help", - "filesystem" - ], - "category": "system", - "created": "2026-05-26T18:25:28.851Z", - "updated": "2026-05-26T18:25:28.851Z" - }, - "memory-and-profile": { - "name": "memory-and-profile", - "description": "How to use the user profile system, session memory, and pattern tracking for persistent context", - "tags": [ - "memory", - "profile", - "session", - "persistence" - ], - "category": "system", - "created": "2026-05-26T18:25:28.851Z", - "updated": "2026-05-26T18:25:28.851Z" - }, "web-search-and-fetch": { "name": "web-search-and-fetch", "description": "How to search the web and fetch URL content using the WebX11 proxy API", + "category": "research", "tags": [ "search", "fetch", "web", "proxy", "api" - ], - "category": "research", - "created": "2026-05-26T18:25:28.852Z", - "updated": "2026-05-26T18:25:28.852Z" + ] + }, + "webdav-sync": { + "name": "webdav-sync", + "description": "Sync, backup, and restore VFS via WebDAV in WebX11. CRITICAL: Only use verified commands.", + "category": "system", + "tags": [ + "webdav", + "sync", + "backup", + "cloud", + "vfs" + ] + }, + "test": { + "name": "test", + "description": "No description", + "category": "general", + "tags": [] }, "module-management": { "name": "module-management", "description": "How to view, edit, test, reload modules and manage startup configuration", + "category": "dev", "tags": [ "modules", "editing", "code", "self-improvement" - ], - "category": "dev", - "created": "2026-05-26T18:25:28.853Z", - "updated": "2026-05-26T18:25:28.853Z" - }, - "system-maintenance": { - "name": "system-maintenance", - "description": "How to reset, troubleshoot, and maintain the WebX11 terminal", - "tags": [ - "system", - "maintenance", - "reset", - "troubleshoot", - "config" - ], - "category": "system", - "created": "2026-05-26T18:25:28.854Z", - "updated": "2026-05-26T18:25:28.854Z" + ] }, "backup-and-restore": { "name": "backup-and-restore", "description": "How to backup, restore, and export WebX11 data", + "category": "system", "tags": [ "backup", "restore", "export", "import", "persistence" - ], + ] + }, + "navigating-the-terminal": { + "name": "navigating-the-terminal", + "description": "How to explore the WebX11 terminal, find commands, check status, and navigate the filesystem", + "category": "system", + "tags": [ + "terminal", + "navigation", + "help", + "filesystem" + ] + }, + "system-maintenance": { + "name": "system-maintenance", + "description": "How to reset, troubleshoot, and maintain the WebX11 terminal", "category": "system", - "created": "2026-05-26T18:25:28.855Z", - "updated": "2026-05-26T18:25:28.855Z" + "tags": [ + "system", + "maintenance", + "reset", + "troubleshoot", + "config" + ] }, "mindmap-workflow": { "name": "mindmap-workflow", "description": "How to create, view, edit, and manage brainstorming mindmaps", + "category": "productivity", "tags": [ "mindmap", "brainstorming", "planning", "organization" - ], - "category": "productivity", - "created": "2026-05-26T18:25:28.856Z", - "updated": "2026-05-26T18:25:28.856Z" + ] }, - "skills-system-guide": { - "name": "skills-system-guide", - "description": "How to use the skills system: load, create, patch, list, and audit skills for persistent procedural memory", - "tags": [ - "skills", - "system", - "meta" - ], - "category": "system", - "created": "2026-05-26T18:25:28.857Z", - "updated": "2026-05-26T18:25:28.857Z" + "webx11-dev-cycle": { + "name": "webx11-dev-cycle", + "description": "Professional workflow for analyzing, prototyping, and implementing new features in WebX11.", + "category": "general", + "tags": [] }, "mindmap-user-guide": { "name": "mindmap-user-guide", "description": "How to create, view, and manage brainstorming mindmaps using the mindmap terminal module", + "category": "productivity", "tags": [ "mindmap", "brainstorming", "organization", "planning" - ], - "category": "productivity", - "created": "2026-05-26T18:28:53.203Z", - "updated": "2026-05-26T18:28:53.203Z" + ] }, - "webdav-sync": { - "name": "webdav-sync", - "description": "Sync, backup, and restore VFS via WebDAV in WebX11. CRITICAL: Only use verified commands.", - "tags": [ - "webdav", - "sync", - "backup", - "cloud", - "vfs" - ], + "memory-and-profile": { + "name": "memory-and-profile", + "description": "How to use the user profile system, session memory, and pattern tracking for persistent context", "category": "system", - "created": "2026-05-26T18:32:07.434Z", - "updated": "2026-05-26T20:20:17.764Z" + "tags": [ + "memory", + "profile", + "session", + "persistence" + ] }, "memory-map": { "name": "memory-map", "description": "Maintain and query the persistent memory map at /memory/map.json for Agando/DarkMoto Systems.", - "tags": [ - "memory", - "persistence", - "context", - "map", - "agando" - ], "category": "system", - "created": "2026-05-26T20:07:04.617Z", - "updated": "2026-05-26T20:07:04.617Z" + "tags": [] + }, + "skills-system-guide": { + "name": "skills-system-guide", + "description": "How to use the skills system: load, create, patch, list, and audit skills for persistent procedural memory", + "category": "system", + "tags": [ + "skills", + "system", + "meta" + ] } }
\ No newline at end of file diff --git a/skills/test.md b/skills/test.md deleted file mode 100644 index cefd788..0000000 --- a/skills/test.md +++ /dev/null @@ -1 +0,0 @@ -# Hello from WebDAV
\ No newline at end of file |