diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-22 18:00:43 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-22 18:00:43 +0200 |
| commit | 6f6161562c5f1028256f3163083b665719741646 (patch) | |
| tree | ccc9ab59e8433555557b6db8b6c1d4940b8652a4 | |
| parent | 82270d6f2685d84d5c9b4a454ae7260de75d34cf (diff) | |
auto: 2026-06-22_18:00 changes
31 files changed, 1186 insertions, 90 deletions
@@ -1244,8 +1244,14 @@ <span style="cursor:pointer" onclick="if(webx11) webx11.closeCodeEditor()">✕</span> </div> <div class="modal-body" style="padding:0; display:flex; flex-direction:column; height:calc(100% - 100px);"> - <div id="code-editor-info" style="padding:6px 10px; font-size:0.8em; color:var(--text-muted); border-bottom:1px solid var(--border-color); flex-shrink:0;"></div> - <textarea id="code-editor-textarea" style="flex:1; width:100%; border:none; background:var(--bg-tertiary); color:var(--text-color); font-family:monospace; font-size:13px; padding:10px; resize:none; outline:none; tab-size:2; white-space:pre; overflow:auto;"></textarea> + <div id="code-editor-info" style="padding:6px 10px; font-size:0.8em; color:var(--text-muted); border-bottom:1px solid var(--border-color); flex-shrink:0; display:flex; justify-content:space-between; align-items:center;"> + <span id="code-editor-status">Ready</span> + <span><button class="btn" style="font-size:0.7em;padding:2px 8px" onclick="window._editorToggleFullscreen()">⤢ Fullscreen</button></span> + </div> + <div id="code-editor-wrapper" style="flex:1; display:flex; overflow:hidden; position:relative;"> + <div id="code-editor-gutter" style="flex-shrink:0; width:40px; background:var(--bg-secondary); color:var(--text-muted); font-family:monospace; font-size:13px; line-height:20px; padding:10px 4px; text-align:right; overflow:hidden; user-select:none; border-right:1px solid var(--border-color);"></div> + <textarea id="code-editor-textarea" style="flex:1; border:none; background:var(--bg-tertiary); color:var(--text-color); font-family:monospace; font-size:13px; line-height:20px; padding:10px; resize:none; outline:none; tab-size:2; white-space:pre; overflow:auto; display:block;" spellcheck="false"></textarea> + </div> </div> <div class="modal-footer"> <button class="btn btn-danger" onclick="if(webx11) webx11.closeCodeEditor()">Cancel</button> @@ -1310,11 +1316,11 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=2026-06-18-v4.5.75"></script> -<script src="modules/ext/critical-ext-bundle.js?v=2026-06-18-v4.5.75"></script> +<script src="modules/core/core-bundle.js?v=2026-06-18-v4.5.79"></script> +<script src="modules/ext/critical-ext-bundle.js?v=2026-06-18-v4.5.79"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=2026-06-18-v4.5.75"></script> +<script src="modules/core/module-loader.js?v=2026-06-18-v4.5.79"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules-index.json b/modules-index.json index 3d637e2..d9a96eb 100644 --- a/modules-index.json +++ b/modules-index.json @@ -1,6 +1,6 @@ { - "version": "2026-06-08-v4.5.22", - "generated": "2026-06-08T23:50:00.000Z", + "version": "2026-06-18-v4.5.79", + "generated": "2026-06-18T00:00:00.000Z", "modules": { "module-agent-registry": { "name": "module-agent-registry", @@ -16,7 +16,7 @@ "contract-register", "contract-list" ], - "description": "module-agent-registry.js — VFS-backed agent directory with A2A contracts", + "description": "module-agent-registry.js \u2014 VFS-backed agent directory with A2A contracts", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -33,7 +33,7 @@ "wx", "webx" ], - "description": "Extracted from newOS4 index.html — WebXAIAliasModule", + "description": "Extracted from newOS4 index.html \u2014 WebXAIAliasModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -57,7 +57,7 @@ "ai-stop", "ai-model" ], - "description": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)", + "description": "Extracted from newOS4 index.html \u2014 WebX11AIAssistantModule (Full Agent Loop)", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -92,7 +92,7 @@ "config-export", "config-reset" ], - "description": "Extracted from newOS4 index.html — ConfigManagerModule", + "description": "Extracted from newOS4 index.html \u2014 ConfigManagerModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -167,7 +167,7 @@ "lease-create", "lease-revoke" ], - "description": "module-leases.js — Lease system for agent task delegation", + "description": "module-leases.js \u2014 Lease system for agent task delegation", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -201,7 +201,7 @@ "load-local", "loadlocal" ], - "description": "Extracted from newOS4 index.html — ModuleManager", + "description": "Extracted from newOS4 index.html \u2014 ModuleManager", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -222,7 +222,7 @@ "load-local", "loadlocal" ], - "description": "DEPRECATED 2026-06-08: duplicate of module-manager.js. Removed from BUILTIN. File kept for one release for rollback safety — will be deleted in v4.6.0.", + "description": "DEPRECATED 2026-06-08: duplicate of module-manager.js. Removed from BUILTIN. File kept for one release for rollback safety \u2014 will be deleted in v4.6.0.", "dependencies": [], "version": "1.0.0", "updated": "2026-06-08", @@ -240,7 +240,7 @@ "nvfs-resolve", "nvfs-tree" ], - "description": "── Core Index Builder ──", + "description": "\u2500\u2500 Core Index Builder \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -267,7 +267,7 @@ "ai", "ask" ], - "description": "Ollama model management — connect, list models, pull/delete, run inference", + "description": "Ollama model management \u2014 connect, list models, pull/delete, run inference", "dependencies": [], "version": "2.0.0", "updated": "2026-06-04", @@ -286,7 +286,7 @@ "orchestrator-kill", "orchestrator-purge" ], - "description": "module-orchestrator.js — Multi-agent orchestration system", + "description": "module-orchestrator.js \u2014 Multi-agent orchestration system", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -324,7 +324,7 @@ "monitor", "system-health" ], - "description": "module-servermon.js — Server monitoring + self-optimization telemetry", + "description": "module-servermon.js \u2014 Server monitoring + self-optimization telemetry", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -344,7 +344,7 @@ "gui-clear", "gui-run" ], - "description": "Extracted from newOS4 index.html — SimpleGUIModule", + "description": "Extracted from newOS4 index.html \u2014 SimpleGUIModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -378,7 +378,7 @@ "commands", "check" ], - "description": "Extracted from newOS4 index.html — SystemModule", + "description": "Extracted from newOS4 index.html \u2014 SystemModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -396,7 +396,7 @@ "model", "models" ], - "description": "Extracted from newOS4 index.html — OllamaManager / LLMRouter", + "description": "Extracted from newOS4 index.html \u2014 OllamaManager / LLMRouter", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -418,7 +418,7 @@ "secret-setpin", "secret-status" ], - "description": "Extracted from newOS4 index.html — SecretVault", + "description": "Extracted from newOS4 index.html \u2014 SecretVault", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -439,7 +439,7 @@ "cmd-deny", "cd" ], - "description": "Extracted from newOS4 index.html — WebX11Terminal", + "description": "Extracted from newOS4 index.html \u2014 WebX11Terminal", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -502,7 +502,7 @@ "sleep", "expand" ], - "description": "── which ──", + "description": "\u2500\u2500 which \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -553,7 +553,7 @@ "encrypt", "decrypt" ], - "description": "CryptoGzipModule — VFS-level gzip/encrypt commands for WebX11 newOS4", + "description": "CryptoGzipModule \u2014 VFS-level gzip/encrypt commands for WebX11 newOS4", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -649,7 +649,7 @@ "$", "set" ], - "description": "── Hook into terminal.executeCommand ──", + "description": "\u2500\u2500 Hook into terminal.executeCommand \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -789,7 +789,7 @@ "class": "MemoryGraphModule", "file": "modules/core/module-memory-graph.js", "layer": "core", - "description": "Cross-session persistent memory graph — nodes for files, concepts, tasks, tools with weighted edges", + "description": "Cross-session persistent memory graph \u2014 nodes for files, concepts, tasks, tools with weighted edges", "commands": [ "memgraph-status", "memgraph-search", @@ -802,7 +802,7 @@ "class": "PredictorModule", "file": "modules/core/module-predictor.js", "layer": "core", - "description": "Predictive resource allocation — estimates task duration and tool turns from historical data", + "description": "Predictive resource allocation \u2014 estimates task duration and tool turns from historical data", "commands": [ "predict", "predictor-status", @@ -813,7 +813,7 @@ "class": "WorldModelModule", "file": "modules/core/module-world-model.js", "layer": "core", - "description": "Global World Model — system self-awareness synthesis from all subsystems", + "description": "Global World Model \u2014 system self-awareness synthesis from all subsystems", "commands": [ "world-model", "world-model-summary", @@ -821,6 +821,74 @@ "world-model-refresh", "world-model-config" ] + }, + "media": { + "name": "media", + "file": "modules/ext/media.js", + "type": "ext", + "class": "MediaModule", + "auto_load": true, + "commands": [ + "analyze" + ], + "description": "media.js \u2014 ext module", + "dependencies": [], + "version": "1.0.0", + "updated": "2026-06-18", + "layer": "ext" + }, + "console": { + "name": "console", + "file": "modules/ext/console.js", + "type": "ext", + "class": "JSConsoleModule", + "auto_load": true, + "commands": [ + "js", + "js-run", + "node" + ], + "description": "console.js \u2014 ext module", + "dependencies": [], + "version": "1.0.0", + "updated": "2026-06-18", + "layer": "ext" + }, + "floating": { + "name": "floating", + "file": "modules/ext/floating.js", + "type": "ext", + "class": "FloatingModule", + "auto_load": true, + "commands": [ + "floating-add", + "floating-remove", + "floating-list", + "floating-pos", + "floating-clear", + "floating-toggle" + ], + "description": "floating.js \u2014 ext module", + "dependencies": [], + "version": "1.0.0", + "updated": "2026-06-18", + "layer": "ext" + }, + "debug-panel": { + "name": "debug-panel", + "file": "modules/ext/debug-panel.js", + "type": "ext", + "class": "DebugPanel", + "auto_load": true, + "commands": [ + "debug", + "debug-ai" + ], + "description": "debug-panel.js \u2014 ext module", + "dependencies": [], + "version": "1.0.0", + "updated": "2026-06-18", + "layer": "ext" } }, "total": 45 diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 2e57be6..0f13438 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -2290,7 +2290,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','node','debug','debug-ai','session-new','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','node','debug','debug-ai','session-new','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} @@ -2299,7 +2299,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this._registerBuiltIn(); } _registerBuiltIn() { - const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -3581,7 +3581,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -3614,7 +3614,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console', 'media']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules — they load via _loadExtModules @@ -4532,7 +4532,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm 'module-fetcher.js','module-skills.js','module-chatstudio.js' ]; const extFiles = [ - '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','tutorial.js','shell.js','busybox.js','floating.js','console.js' + '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','tutorial.js','shell.js','busybox.js','floating.js','console.js','media.js' ]; const addSection = (title) => { @@ -8093,7 +8093,12 @@ buildSystemPrompt() { if (res.message && !res.tool_calls) { const text = res.message.content || ''; this.terminal.printAIResponse(text || '(no response)'); - if (text) this.terminal.ollamaManager.fireResponseHooks(text); + if (text) { + this.terminal.ollamaManager.fireResponseHooks(text); + // Auto-speak if enabled + const vm = this.terminal.moduleManager && this.terminal.moduleManager.modules['VoiceModule']; + if (vm && vm.speakText) vm.speakText(text); + } this.terminal.print(`💬 AI answered (turn ${toolTurns}/${this.MAX_TOOL_TURNS}, ${thinkTime}s)`, 'info'); return { finalAnswer: text, toolTurns }; } @@ -8776,7 +8781,7 @@ buildSystemPrompt() { 'orchestrator-status','orchestrator-list','orchestrator-config','orchestrator-restart-policy', 'a2a-list','a2a-status','a2a-poll', 'consensus','consensus-status','consensus-history', - 'pipeline','agent-list','agent-status','contract-list']); + 'pipeline','agent-list','agent-status','contract-list','voice','speak','analyze']); return !safeRead.has(cmd); } return ['write_file','delete_file','make_directory','edit_module','delegate_task','spawn_agent','create_skill','patch_skill','wpm_install','user_set','session_save'].includes(name); diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index c58bab3..40716e4 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -781,7 +781,12 @@ buildSystemPrompt() { if (res.message && !res.tool_calls) { const text = res.message.content || ''; this.terminal.printAIResponse(text || '(no response)'); - if (text) this.terminal.ollamaManager.fireResponseHooks(text); + if (text) { + this.terminal.ollamaManager.fireResponseHooks(text); + // Auto-speak if enabled + const vm = this.terminal.moduleManager && this.terminal.moduleManager.modules['VoiceModule']; + if (vm && vm.speakText) vm.speakText(text); + } this.terminal.print(`💬 AI answered (turn ${toolTurns}/${this.MAX_TOOL_TURNS}, ${thinkTime}s)`, 'info'); return { finalAnswer: text, toolTurns }; } @@ -1464,7 +1469,7 @@ buildSystemPrompt() { 'orchestrator-status','orchestrator-list','orchestrator-config','orchestrator-restart-policy', 'a2a-list','a2a-status','a2a-poll', 'consensus','consensus-status','consensus-history', - 'pipeline','agent-list','agent-status','contract-list']); + 'pipeline','agent-list','agent-status','contract-list','voice','speak','analyze']); return !safeRead.has(cmd); } return ['write_file','delete_file','make_directory','edit_module','delegate_task','spawn_agent','create_skill','patch_skill','wpm_install','user_set','session_save'].includes(name); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 6927743..a08a285 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-18-v4.5.75'; + const LATEST_VER = '2026-06-18-v4.5.79'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 5954bfb..2178e85 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -10,7 +10,7 @@ this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','node','debug','debug-ai','session-new','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','node','debug','debug-ai','session-new','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} @@ -19,7 +19,7 @@ this._registerBuiltIn(); } _registerBuiltIn() { - const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -1301,7 +1301,7 @@ } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -1334,7 +1334,7 @@ } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console', 'media']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules — they load via _loadExtModules @@ -2252,7 +2252,7 @@ 'module-fetcher.js','module-skills.js','module-chatstudio.js' ]; const extFiles = [ - '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','tutorial.js','shell.js','busybox.js','floating.js','console.js' + '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','tutorial.js','shell.js','busybox.js','floating.js','console.js','media.js' ]; const addSection = (title) => { diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js index 4242ebf..3c5d54b 100644 --- a/modules/ext/critical-ext-bundle.js +++ b/modules/ext/critical-ext-bundle.js @@ -26,6 +26,8 @@ class EditorModule { const modal = document.getElementById('code-editor'); const title = document.getElementById('code-editor-title'); const textarea = document.getElementById('code-editor-textarea'); + const gutter = document.getElementById('code-editor-gutter'); + const statusEl = document.getElementById('code-editor-status'); if (!modal || !textarea) return this.terminal.print('Editor modal not found', 'error'); // Read existing file content or use empty for new files const content = this.terminal.fs.readFile(path); @@ -36,6 +38,51 @@ class EditorModule { textarea.value = fileContent; modal.style.display = 'flex'; textarea.focus(); + // Editor enhancements: line numbers, Tab, Ctrl+S + const updateGutter = () => { + const lines = textarea.value.split('\n').length; + let html = ''; + for (let i = 1; i <= lines; i++) html += i + '\n'; + gutter.textContent = ''; + gutter.textContent = html; + }; + const syncScroll = () => { gutter.scrollTop = textarea.scrollTop; }; + const updateStatus = () => { + const lines = textarea.value.split('\n').length; + const chars = textarea.value.length; + statusEl.textContent = `${lines} lines · ${chars} chars`; + }; + updateGutter(); + updateStatus(); + textarea.onscroll = syncScroll; + textarea.oninput = () => { updateGutter(); updateStatus(); }; + textarea.onkeydown = (e) => { + if (e.key === 'Tab') { e.preventDefault(); insertTab(textarea, e.shiftKey); updateGutter(); } + if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveBtn.click(); } + }; + function insertTab(el, shift) { + const s = el.selectionStart, e2 = el.selectionEnd; + if (shift) { + const before = el.value.substring(0, s); + const lineStart = before.lastIndexOf('\n') + 1; + if (lineStart < s && el.value.substring(lineStart, lineStart + 1) === '\t') { + el.value = el.value.substring(0, lineStart) + el.value.substring(lineStart + 1); + el.selectionStart = el.selectionEnd = s - 1; + } + } else { + el.value = el.value.substring(0, s) + '\t' + el.value.substring(e2); + el.selectionStart = el.selectionEnd = s + 1; + } + } + // Fullscreen toggle + window._editorToggleFullscreen = () => { + const content = modal.querySelector('.modal-content'); + if (content.style.maxWidth === '100vw') { + content.style.maxWidth = '90vw'; content.style.maxHeight = '90vh'; content.style.width = '900px'; content.style.height = '80vh'; + } else { + content.style.maxWidth = '100vw'; content.style.maxHeight = '100vh'; content.style.width = '100vw'; content.style.height = '100vh'; + } + }; // Override save button to write back to VFS const saveBtn = modal.querySelector('.modal-footer .btn:last-child'); const cancelBtn = modal.querySelector('.modal-footer .btn-danger'); @@ -809,6 +856,8 @@ class FileManagerModule { this.currentPath = []; this._overlay = null; this._selectedFile = null; + this._multiSelect = new Set(); // v4.5.78: multi-select for batch ops + this._clipboard = { mode: null, paths: [] }; // 'copy' or 'cut' this._searchFilter = ''; this._rawMode = false; this._inlineHtml = false; @@ -1208,6 +1257,25 @@ class FileManagerModule { }; toolbar.appendChild(searchInput); + // v4.5.78: Batch operation buttons + const batchBar = document.createElement('div'); + batchBar.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;'; + const mkBtn = (label, title, action) => { + const b = document.createElement('button'); + b.textContent = label; + b.title = title; + b.style.cssText = 'background:none;border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;padding:4px 8px;border-radius:4px;font-family:inherit;font-size:0.85em;min-height:32px;touch-action:manipulation;'; + b.onclick = () => action(); + return b; + }; + batchBar.appendChild(mkBtn('📋 Copy', 'Copy selected files', () => this._batchCopy())); + batchBar.appendChild(mkBtn('✂️ Cut', 'Cut selected files', () => this._batchCut())); + batchBar.appendChild(mkBtn('📥 Paste', 'Paste from clipboard', () => this._batchPaste())); + batchBar.appendChild(mkBtn('🗑️ Del', 'Delete selected files', () => this._batchDelete())); + batchBar.appendChild(mkBtn('📝 Rename', 'Rename selected file', () => this._batchRename())); + batchBar.appendChild(mkBtn('☐ All', 'Toggle select all', () => this._toggleSelectAll())); + toolbar.appendChild(batchBar); + overlay.appendChild(toolbar); // Main area — wraps sidebar + preview in a responsive flex container @@ -1429,17 +1497,25 @@ class FileManagerModule { const item = document.createElement('div'); const fullPath = pathStr === '/' ? '/' + entry.name : pathStr + '/' + entry.name; const isSelected = this._selectedFile === fullPath; + const isMulti = this._multiSelect.has(fullPath); item.dataset.path = fullPath; item.dataset.type = 'file'; - item.dataset.selected = isSelected ? 'true' : 'false'; - const bgColor = isSelected ? 'var(--bg-tertiary)' : 'transparent'; + item.dataset.selected = (isSelected || isMulti) ? 'true' : 'false'; + const bgColor = (isSelected || isMulti) ? 'var(--bg-tertiary)' : 'transparent'; item.style.cssText = 'display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--border-color);gap:6px;background:' + bgColor + ';'; - if (isSelected) { + if (isSelected || isMulti) { item.style.borderLeft = '3px solid var(--accent-color)'; } item.onmouseenter = () => { if (!item.dataset.selected) item.style.background = 'var(--bg-tertiary)'; }; item.onmouseleave = () => { if (!item.dataset.selected) item.style.background = 'transparent'; }; - item.onclick = () => { + item.onclick = (e) => { + // v4.5.78: Ctrl+click or checkbox for multi-select + if (e.ctrlKey || e.metaKey) { + if (this._multiSelect.has(fullPath)) this._multiSelect.delete(fullPath); + else this._multiSelect.add(fullPath); + this._refreshFileList(); + return; + } this._selectedFile = fullPath; this._highlightSelected(); const content = this.terminal.fs.readFile(fullPath); @@ -1472,13 +1548,110 @@ class FileManagerModule { if (!sidebar) return; const items = sidebar.querySelectorAll('[data-type="file"]'); items.forEach(item => { - const selected = item.dataset.path === this._selectedFile; + const selected = item.dataset.path === this._selectedFile || this._multiSelect.has(item.dataset.path); item.dataset.selected = selected ? 'true' : 'false'; item.style.background = selected ? 'var(--bg-tertiary)' : 'transparent'; item.style.borderLeft = selected ? '3px solid var(--accent-color)' : '3px solid transparent'; }); } + // v4.5.78: Batch operations + _getSelectedPaths() { + const paths = [...this._multiSelect]; + if (this._selectedFile && !paths.includes(this._selectedFile)) paths.push(this._selectedFile); + return paths; + } + + _batchCopy() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'copy', paths: [...paths] }; + this._multiSelect.clear(); + this._updateStatus('📋 Copied ' + paths.length + ' item(s)'); + } + + _batchCut() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'cut', paths: [...paths] }; + this._multiSelect.clear(); + this._updateStatus('✂️ Cut ' + paths.length + ' item(s)'); + } + + _batchPaste() { + if (!this._clipboard.paths.length) { this._updateStatus('Clipboard empty'); return; } + const dest = '/' + this.currentPath.join('/'); + let count = 0; + this._clipboard.paths.forEach(src => { + const name = src.split('/').pop(); + const destPath = dest === '/' ? '/' + name : dest + '/' + name; + try { + if (this._clipboard.mode === 'copy') { + const content = this.terminal.fs.readFile(src); + if (content !== null) { this.terminal.fs.writeFile(destPath, content); count++; } + } else if (this._clipboard.mode === 'cut') { + const content = this.terminal.fs.readFile(src); + if (content !== null) { + this.terminal.fs.writeFile(destPath, content); + this.terminal.fs.deleteFile(src); + count++; + } + } + } catch (e) { /* skip errors */ } + }); + if (this._clipboard.mode === 'cut') this._clipboard = { mode: null, paths: [] }; + this._multiSelect.clear(); + this._refreshFileList(); + this._updateStatus('📥 Pasted ' + count + ' item(s)'); + } + + _batchDelete() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + if (!confirm('Delete ' + paths.length + ' file(s)?')) return; + let count = 0; + paths.forEach(p => { + try { this.terminal.fs.deleteFile(p); count++; } catch (e) { /* skip */ } + }); + this._multiSelect.clear(); + this._selectedFile = null; + this._refreshFileList(); + this._updateStatus('🗑️ Deleted ' + count + ' file(s)'); + } + + _batchRename() { + const path = this._selectedFile || (this._multiSelect.size === 1 ? [...this._multiSelect][0] : null); + if (!path) { this._updateStatus('Select one file to rename'); return; } + const oldName = path.split('/').pop(); + const newName = prompt('Rename to:', oldName); + if (!newName || newName === oldName) return; + const dir = path.substring(0, path.lastIndexOf('/')) || '/'; + const newPath = dir === '/' ? '/' + newName : dir + '/' + newName; + try { + const content = this.terminal.fs.readFile(path); + if (content === null) { this._updateStatus('Cannot read file'); return; } + this.terminal.fs.writeFile(newPath, content); + this.terminal.fs.deleteFile(path); + this._selectedFile = newPath; + this._multiSelect.clear(); + this._refreshFileList(); + this._updateStatus('📝 Renamed to ' + newName); + } catch (e) { this._updateStatus('Rename failed: ' + e.message); } + } + + _toggleSelectAll() { + const sidebar = document.getElementById('filemgr-sidebar'); + if (!sidebar) return; + const fileItems = sidebar.querySelectorAll('[data-type="file"]'); + if (this._multiSelect.size >= fileItems.length) { + this._multiSelect.clear(); + } else { + fileItems.forEach(item => this._multiSelect.add(item.dataset.path)); + } + this._refreshFileList(); + this._updateStatus(this._multiSelect.size + ' selected'); + } + _renderPreview(path, content) { const preview = document.getElementById('filemgr-preview'); if (!preview) return; diff --git a/modules/ext/editor.js b/modules/ext/editor.js index 3a5f269..07205b9 100644 --- a/modules/ext/editor.js +++ b/modules/ext/editor.js @@ -24,6 +24,8 @@ class EditorModule { const modal = document.getElementById('code-editor'); const title = document.getElementById('code-editor-title'); const textarea = document.getElementById('code-editor-textarea'); + const gutter = document.getElementById('code-editor-gutter'); + const statusEl = document.getElementById('code-editor-status'); if (!modal || !textarea) return this.terminal.print('Editor modal not found', 'error'); // Read existing file content or use empty for new files const content = this.terminal.fs.readFile(path); @@ -34,6 +36,51 @@ class EditorModule { textarea.value = fileContent; modal.style.display = 'flex'; textarea.focus(); + // Editor enhancements: line numbers, Tab, Ctrl+S + const updateGutter = () => { + const lines = textarea.value.split('\n').length; + let html = ''; + for (let i = 1; i <= lines; i++) html += i + '\n'; + gutter.textContent = ''; + gutter.textContent = html; + }; + const syncScroll = () => { gutter.scrollTop = textarea.scrollTop; }; + const updateStatus = () => { + const lines = textarea.value.split('\n').length; + const chars = textarea.value.length; + statusEl.textContent = `${lines} lines · ${chars} chars`; + }; + updateGutter(); + updateStatus(); + textarea.onscroll = syncScroll; + textarea.oninput = () => { updateGutter(); updateStatus(); }; + textarea.onkeydown = (e) => { + if (e.key === 'Tab') { e.preventDefault(); insertTab(textarea, e.shiftKey); updateGutter(); } + if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveBtn.click(); } + }; + function insertTab(el, shift) { + const s = el.selectionStart, e2 = el.selectionEnd; + if (shift) { + const before = el.value.substring(0, s); + const lineStart = before.lastIndexOf('\n') + 1; + if (lineStart < s && el.value.substring(lineStart, lineStart + 1) === '\t') { + el.value = el.value.substring(0, lineStart) + el.value.substring(lineStart + 1); + el.selectionStart = el.selectionEnd = s - 1; + } + } else { + el.value = el.value.substring(0, s) + '\t' + el.value.substring(e2); + el.selectionStart = el.selectionEnd = s + 1; + } + } + // Fullscreen toggle + window._editorToggleFullscreen = () => { + const content = modal.querySelector('.modal-content'); + if (content.style.maxWidth === '100vw') { + content.style.maxWidth = '90vw'; content.style.maxHeight = '90vh'; content.style.width = '900px'; content.style.height = '80vh'; + } else { + content.style.maxWidth = '100vw'; content.style.maxHeight = '100vh'; content.style.width = '100vw'; content.style.height = '100vh'; + } + }; // Override save button to write back to VFS const saveBtn = modal.querySelector('.modal-footer .btn:last-child'); const cancelBtn = modal.querySelector('.modal-footer .btn-danger'); diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js index 141bee4..acbb963 100644 --- a/modules/ext/filemgr.js +++ b/modules/ext/filemgr.js @@ -8,6 +8,8 @@ class FileManagerModule { this.currentPath = []; this._overlay = null; this._selectedFile = null; + this._multiSelect = new Set(); // v4.5.78: multi-select for batch ops + this._clipboard = { mode: null, paths: [] }; // 'copy' or 'cut' this._searchFilter = ''; this._rawMode = false; this._inlineHtml = false; @@ -407,6 +409,25 @@ class FileManagerModule { }; toolbar.appendChild(searchInput); + // v4.5.78: Batch operation buttons + const batchBar = document.createElement('div'); + batchBar.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;'; + const mkBtn = (label, title, action) => { + const b = document.createElement('button'); + b.textContent = label; + b.title = title; + b.style.cssText = 'background:none;border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;padding:4px 8px;border-radius:4px;font-family:inherit;font-size:0.85em;min-height:32px;touch-action:manipulation;'; + b.onclick = () => action(); + return b; + }; + batchBar.appendChild(mkBtn('📋 Copy', 'Copy selected files', () => this._batchCopy())); + batchBar.appendChild(mkBtn('✂️ Cut', 'Cut selected files', () => this._batchCut())); + batchBar.appendChild(mkBtn('📥 Paste', 'Paste from clipboard', () => this._batchPaste())); + batchBar.appendChild(mkBtn('🗑️ Del', 'Delete selected files', () => this._batchDelete())); + batchBar.appendChild(mkBtn('📝 Rename', 'Rename selected file', () => this._batchRename())); + batchBar.appendChild(mkBtn('☐ All', 'Toggle select all', () => this._toggleSelectAll())); + toolbar.appendChild(batchBar); + overlay.appendChild(toolbar); // Main area — wraps sidebar + preview in a responsive flex container @@ -628,17 +649,25 @@ class FileManagerModule { const item = document.createElement('div'); const fullPath = pathStr === '/' ? '/' + entry.name : pathStr + '/' + entry.name; const isSelected = this._selectedFile === fullPath; + const isMulti = this._multiSelect.has(fullPath); item.dataset.path = fullPath; item.dataset.type = 'file'; - item.dataset.selected = isSelected ? 'true' : 'false'; - const bgColor = isSelected ? 'var(--bg-tertiary)' : 'transparent'; + item.dataset.selected = (isSelected || isMulti) ? 'true' : 'false'; + const bgColor = (isSelected || isMulti) ? 'var(--bg-tertiary)' : 'transparent'; item.style.cssText = 'display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--border-color);gap:6px;background:' + bgColor + ';'; - if (isSelected) { + if (isSelected || isMulti) { item.style.borderLeft = '3px solid var(--accent-color)'; } item.onmouseenter = () => { if (!item.dataset.selected) item.style.background = 'var(--bg-tertiary)'; }; item.onmouseleave = () => { if (!item.dataset.selected) item.style.background = 'transparent'; }; - item.onclick = () => { + item.onclick = (e) => { + // v4.5.78: Ctrl+click or checkbox for multi-select + if (e.ctrlKey || e.metaKey) { + if (this._multiSelect.has(fullPath)) this._multiSelect.delete(fullPath); + else this._multiSelect.add(fullPath); + this._refreshFileList(); + return; + } this._selectedFile = fullPath; this._highlightSelected(); const content = this.terminal.fs.readFile(fullPath); @@ -671,13 +700,110 @@ class FileManagerModule { if (!sidebar) return; const items = sidebar.querySelectorAll('[data-type="file"]'); items.forEach(item => { - const selected = item.dataset.path === this._selectedFile; + const selected = item.dataset.path === this._selectedFile || this._multiSelect.has(item.dataset.path); item.dataset.selected = selected ? 'true' : 'false'; item.style.background = selected ? 'var(--bg-tertiary)' : 'transparent'; item.style.borderLeft = selected ? '3px solid var(--accent-color)' : '3px solid transparent'; }); } + // v4.5.78: Batch operations + _getSelectedPaths() { + const paths = [...this._multiSelect]; + if (this._selectedFile && !paths.includes(this._selectedFile)) paths.push(this._selectedFile); + return paths; + } + + _batchCopy() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'copy', paths: [...paths] }; + this._multiSelect.clear(); + this._updateStatus('📋 Copied ' + paths.length + ' item(s)'); + } + + _batchCut() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'cut', paths: [...paths] }; + this._multiSelect.clear(); + this._updateStatus('✂️ Cut ' + paths.length + ' item(s)'); + } + + _batchPaste() { + if (!this._clipboard.paths.length) { this._updateStatus('Clipboard empty'); return; } + const dest = '/' + this.currentPath.join('/'); + let count = 0; + this._clipboard.paths.forEach(src => { + const name = src.split('/').pop(); + const destPath = dest === '/' ? '/' + name : dest + '/' + name; + try { + if (this._clipboard.mode === 'copy') { + const content = this.terminal.fs.readFile(src); + if (content !== null) { this.terminal.fs.writeFile(destPath, content); count++; } + } else if (this._clipboard.mode === 'cut') { + const content = this.terminal.fs.readFile(src); + if (content !== null) { + this.terminal.fs.writeFile(destPath, content); + this.terminal.fs.deleteFile(src); + count++; + } + } + } catch (e) { /* skip errors */ } + }); + if (this._clipboard.mode === 'cut') this._clipboard = { mode: null, paths: [] }; + this._multiSelect.clear(); + this._refreshFileList(); + this._updateStatus('📥 Pasted ' + count + ' item(s)'); + } + + _batchDelete() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + if (!confirm('Delete ' + paths.length + ' file(s)?')) return; + let count = 0; + paths.forEach(p => { + try { this.terminal.fs.deleteFile(p); count++; } catch (e) { /* skip */ } + }); + this._multiSelect.clear(); + this._selectedFile = null; + this._refreshFileList(); + this._updateStatus('🗑️ Deleted ' + count + ' file(s)'); + } + + _batchRename() { + const path = this._selectedFile || (this._multiSelect.size === 1 ? [...this._multiSelect][0] : null); + if (!path) { this._updateStatus('Select one file to rename'); return; } + const oldName = path.split('/').pop(); + const newName = prompt('Rename to:', oldName); + if (!newName || newName === oldName) return; + const dir = path.substring(0, path.lastIndexOf('/')) || '/'; + const newPath = dir === '/' ? '/' + newName : dir + '/' + newName; + try { + const content = this.terminal.fs.readFile(path); + if (content === null) { this._updateStatus('Cannot read file'); return; } + this.terminal.fs.writeFile(newPath, content); + this.terminal.fs.deleteFile(path); + this._selectedFile = newPath; + this._multiSelect.clear(); + this._refreshFileList(); + this._updateStatus('📝 Renamed to ' + newName); + } catch (e) { this._updateStatus('Rename failed: ' + e.message); } + } + + _toggleSelectAll() { + const sidebar = document.getElementById('filemgr-sidebar'); + if (!sidebar) return; + const fileItems = sidebar.querySelectorAll('[data-type="file"]'); + if (this._multiSelect.size >= fileItems.length) { + this._multiSelect.clear(); + } else { + fileItems.forEach(item => this._multiSelect.add(item.dataset.path)); + } + this._refreshFileList(); + this._updateStatus(this._multiSelect.size + ' selected'); + } + _renderPreview(path, content) { const preview = document.getElementById('filemgr-preview'); if (!preview) return; diff --git a/modules/ext/media.js b/modules/ext/media.js new file mode 100644 index 0000000..5723e27 --- /dev/null +++ b/modules/ext/media.js @@ -0,0 +1,508 @@ +// newOS4 Media Analysis Module (v4.5.79) +// Commands: analyze <path> [--flags] +// Uses gemini-3-flash-preview:cloud for vision (images, video frames) +// Uses deepseek-v4-flash:cloud for text analysis (audio features, summaries) +class MediaModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._visionModel = 'gemini-3-flash-preview:cloud'; + this._textModel = 'deepseek-v4-flash:cloud'; + this._maxFrames = 10; // max video frames to analyze + this._frameInterval = 5; // seconds between video frames + } + + getHelpInfo() { + return { + 'analyze': 'analyze <file> — Analyze image/audio/video. Flags: --frames N, --spectrogram, --beats, --scene' + }; + } + + registerCommands() { + this.commands['analyze'] = (args) => this.handle(args); + } + + handle(args) { + if (!args[0]) { + this.terminal.print('Usage: analyze <file> [--frames N] [--spectrogram] [--beats] [--scene]', 'warning'); + return; + } + const path = args[0]; + const flags = args.slice(1); + const ext = path.split('.').pop().toLowerCase(); + + // Check file exists + const content = this.terminal.fs.readFile(path); + if (content === null || content === undefined) { + this.terminal.print('File not found: ' + path, 'error'); + return; + } + + this.terminal.print('🔍 Analyzing ' + path + '...', 'info'); + + if (this._isImageExt(ext)) { + this._analyzeImage(path, content); + } else if (this._isAudioExt(ext)) { + this._analyzeAudio(path, content, flags); + } else if (this._isVideoExt(ext)) { + this._analyzeVideo(path, content, flags); + } else { + this.terminal.print('Unsupported file type: .' + ext + '. Supported: png, jpg, webp, gif, svg, wav, mp3, ogg, mp4, webm', 'error'); + } + } + + // ─── Image Analysis ─── + async _analyzeImage(path, content) { + this.terminal.print('🖼️ Sending image to vision model...', 'info'); + try { + // VFS may store binary as base64 string or raw text + let base64 = content; + // If content looks like raw text (not base64), try to re-encode + if (!this._isValidBase64(base64) && typeof content === 'string') { + // It's text content, not an image — can't analyze + this.terminal.print('⚠️ File appears to be text, not a binary image', 'warning'); + return; + } + const result = await this._callVision(base64, 'Describe this image in detail. What do you see? Include colors, objects, text, people, and any notable features.'); + if (result.error) { + this.terminal.print('🖼️ Vision error: ' + result.error, 'error'); + return; + } + this.terminal.print('🖼️ Image Analysis:', 'success'); + this.terminal.print(result.text, 'info'); + } catch (e) { + this.terminal.print('🖼️ Analysis failed: ' + e.message, 'error'); + } + } + + // ─── Audio Analysis ─── + async _analyzeAudio(path, content, flags) { + this.terminal.print('🎵 Decoding audio...', 'info'); + try { + // Convert base64 to ArrayBuffer for Web Audio API + let arrayBuffer; + if (this._isValidBase64(content)) { + arrayBuffer = this._base64ToArrayBuffer(content); + } else if (content instanceof ArrayBuffer) { + arrayBuffer = content; + } else { + this.terminal.print('⚠️ Cannot decode audio — file is not in a supported binary format in VFS', 'warning'); + return; + } + + const ctx = new (window.AudioContext || window.webkitAudioContext)(); + const audioBuffer = await ctx.decodeAudioData(arrayBuffer); + + // Extract features + const features = this._extractAudioFeatures(audioBuffer, flags); + ctx.close(); + + // Display features + this.terminal.print('🎵 Audio Features:', 'success'); + this.terminal.print(' Duration: ' + features.duration.toFixed(1) + 's', 'info'); + this.terminal.print(' Sample Rate: ' + features.sampleRate + ' Hz', 'info'); + this.terminal.print(' Channels: ' + features.channels, 'info'); + this.terminal.print(' RMS (avg loudness): ' + features.rms.toFixed(3), 'info'); + this.terminal.print(' Peak amplitude: ' + features.peak.toFixed(3), 'info'); + this.terminal.print(' Dynamic range: ' + features.dynamicRange.toFixed(3), 'info'); + + if (flags.includes('--spectrogram')) { + this.terminal.print('\n📊 Spectrogram (frequency bands over time):', 'info'); + this.terminal.print(features.spectrogram, ''); + } + + if (flags.includes('--beats')) { + this.terminal.print('\n🥁 Beat detection:', 'info'); + this.terminal.print(' Estimated BPM: ' + features.bpm, 'info'); + this.terminal.print(' Beat times: ' + features.beatTimes.join(', '), 'info'); + } + + // Send features to text model for AI interpretation + this.terminal.print('\n🤖 AI interpretation (deepseek-v4-flash)...', 'info'); + const prompt = `Analyze these audio features and describe what the audio likely sounds like. Consider the genre, mood, and characteristics: + +Duration: ${features.duration.toFixed(1)}s +Sample Rate: ${features.sampleRate} Hz +Channels: ${features.channels} +RMS (loudness): ${features.rms.toFixed(3)} +Peak: ${features.peak.toFixed(3)} +Dynamic Range: ${features.dynamicRange.toFixed(3)} +Estimated BPM: ${features.bpm} +Spectral centroid: ${features.spectralCentroid.toFixed(0)} Hz +Zero-crossing rate: ${features.zcr.toFixed(4)} +Energy distribution: ${features.energyBands.join(', ')} + +Provide a brief, insightful interpretation.`; + + const result = await this._callText(prompt); + if (!result.error) this.terminal.print(result.text, 'info'); + else this.terminal.print('AI interpretation unavailable: ' + result.error, 'warning'); + + } catch (e) { + this.terminal.print('🎵 Audio analysis failed: ' + e.message, 'error'); + } + } + + // ─── Video Analysis ─── + async _analyzeVideo(path, content, flags) { + // Parse flags + let maxFrames = this._maxFrames; + const framesIdx = flags.indexOf('--frames'); + if (framesIdx !== -1 && flags[framesIdx + 1]) maxFrames = parseInt(flags[framesIdx + 1]) || 10; + const sceneMode = flags.includes('--scene'); + + this.terminal.print('🎬 Extracting video frames...', 'info'); + + try { + // Create video element + const video = document.createElement('video'); + video.muted = true; + video.crossOrigin = 'anonymous'; + + // Load from base64 or URL + let videoUrl; + if (this._isValidBase64(content)) { + const mime = this._getMimeType(path); + videoUrl = 'data:' + mime + ';base64,' + content; + } else { + this.terminal.print('⚠️ Cannot load video — VFS does not store video as base64', 'warning'); + return; + } + + await new Promise((resolve, reject) => { + video.onloadedmetadata = resolve; + video.onerror = () => reject(new Error('Video decode failed — unsupported codec?')); + video.src = videoUrl; + }); + + const duration = video.duration; + this.terminal.print(' Duration: ' + duration.toFixed(1) + 's', 'info'); + this.terminal.print(' Extracting ' + maxFrames + ' frames...', 'info'); + + const canvas = document.createElement('canvas'); + canvas.width = 320; // downscale for API efficiency + canvas.height = 180; + const ctx = canvas.getContext('2d'); + + const framePromises = []; + const frameInterval = duration / maxFrames; + + for (let i = 0; i < maxFrames; i++) { + const t = Math.min(i * frameInterval, duration - 0.1); + framePromises.push(this._extractFrame(video, canvas, ctx, t, i)); + } + + const frames = await Promise.all(framePromises); + video.src = ''; + + this.terminal.print(' Frames extracted: ' + frames.length, 'info'); + + if (sceneMode) { + // Detect scene changes by comparing consecutive frames + const sceneChanges = this._detectSceneChanges(frames); + this.terminal.print(' Scene changes at: ' + sceneChanges.map(s => s.toFixed(1) + 's').join(', '), 'info'); + } + + // Send frames to vision model + this.terminal.print('\n🤖 Analyzing frames with vision model...', 'info'); + const descriptions = []; + for (let i = 0; i < frames.length; i++) { + const desc = await this._callVision(frames[i].base64, `This is frame ${i+1}/${frames.length} at ${frames[i].time.toFixed(1)}s from a video. Describe what you see briefly.`); + if (!desc.error) { + descriptions.push(`[${frames[i].time.toFixed(1)}s] ${desc.text}`); + this.terminal.print(` Frame ${i+1}/${frames.length} (${frames[i].time.toFixed(1)}s): ${desc.text.substring(0, 100)}`, 'info'); + } + } + + // Summarize with text model + if (descriptions.length > 0) { + this.terminal.print('\n🎬 Video Summary:', 'success'); + const summaryPrompt = `These are descriptions of ${descriptions.length} frames from a video, taken at regular intervals: +${descriptions.join('\n')} + +Provide a brief summary of what happens in this video. Include the overall scene, any changes, and the general content.`; + const summary = await this._callText(summaryPrompt); + if (!summary.error) this.terminal.print(summary.text, 'info'); + } + + } catch (e) { + this.terminal.print('🎬 Video analysis failed: ' + e.message, 'error'); + } + } + + // ─── Helper: Extract single frame ─── + async _extractFrame(video, canvas, ctx, time, index) { + return new Promise((resolve, reject) => { + video.currentTime = time; + video.onseeked = () => { + try { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + const dataUrl = canvas.toDataURL('image/jpeg', 0.7); + const base64 = dataUrl.split(',')[1]; + resolve({ base64, time, index }); + } catch (e) { reject(e); } + }; + video.onerror = () => reject(new Error('Seek failed at ' + time + 's')); + }); + } + + // ─── Helper: Scene change detection ─── + _detectSceneChanges(frames) { + const changes = []; + for (let i = 1; i < frames.length; i++) { + // Simple histogram comparison + const prev = this._frameHistogram(frames[i-1].base64); + const curr = this._frameHistogram(frames[i].base64); + const diff = this._histogramDiff(prev, curr); + if (diff > 0.3) { + changes.push(frames[i].time); + } + } + return changes; + } + + _frameHistogram(base64) { + // Lightweight: use first 100 bytes of base64 as fingerprint + const bytes = atob(base64.substring(0, 200)); + const hist = new Array(16).fill(0); + for (let i = 0; i < bytes.length; i++) { + hist[bytes.charCodeAt(i) % 16]++; + } + return hist; + } + + _histogramDiff(a, b) { + let diff = 0; + for (let i = 0; i < a.length; i++) { + diff += Math.abs(a[i] - b[i]); + } + return diff / (a.reduce((s,v) => s+v, 0) || 1); + } + + // ─── Audio Feature Extraction ─── + _extractAudioFeatures(audioBuffer, flags) { + const data = audioBuffer.getChannelData(0); + const len = data.length; + const sampleRate = audioBuffer.sampleRate; + const channels = audioBuffer.numberOfChannels; + const duration = audioBuffer.duration; + + // RMS + let sumSquares = 0, peak = 0; + for (let i = 0; i < len; i++) { + const s = Math.abs(data[i]); + sumSquares += data[i] * data[i]; + if (s > peak) peak = s; + } + const rms = Math.sqrt(sumSquares / len); + const dynamicRange = peak - rms; + + // Zero-crossing rate + let crossings = 0; + for (let i = 1; i < len; i++) { + if ((data[i] >= 0) !== (data[i-1] >= 0)) crossings++; + } + const zcr = crossings / len; + + // Energy bands (bass, mid, treble) — rough FFT approximation + const bandSize = Math.floor(len / 3); + const energyBands = []; + for (let b = 0; b < 3; b++) { + let bandEnergy = 0; + for (let i = b * bandSize; i < (b+1) * bandSize; i++) { + bandEnergy += Math.abs(data[i]); + } + energyBands.push((bandEnergy / bandSize).toFixed(3)); + } + + // Spectral centroid (approximation) + let weightedSum = 0, totalMag = 0; + const fftSize = 2048; + const stepSize = Math.floor(len / fftSize); + for (let i = 0; i < stepSize; i++) { + const idx = i * fftSize; + for (let j = 0; j < fftSize; j++) { + const mag = Math.abs(data[idx + j] || 0); + weightedSum += j * mag; + totalMag += mag; + } + } + const spectralCentroid = totalMag > 0 ? (weightedSum / totalMag) * (sampleRate / fftSize) : 0; + + // BPM estimation (simplified autocorrelation) + const bpm = this._estimateBPM(data, sampleRate); + + // Spectrogram (text-art, downsampled) + let spectrogram = ''; + if (flags.includes('--spectrogram')) { + spectrogram = this._generateSpectrogram(data, sampleRate, audioBuffer.duration); + } + + // Beat times (simplified) + const beatTimes = this._findBeats(data, sampleRate); + + return { + duration, sampleRate, channels, rms, peak, dynamicRange, + zcr, spectralCentroid, energyBands, bpm, spectrogram, beatTimes + }; + } + + _estimateBPM(data, sampleRate) { + // Simplified: use energy envelope autocorrelation + const windowSize = Math.floor(sampleRate * 0.01); // 10ms windows + const numWindows = Math.floor(data.length / windowSize); + const envelope = []; + for (let w = 0; w < numWindows; w++) { + let energy = 0; + for (let i = 0; i < windowSize; i++) { + energy += Math.abs(data[w * windowSize + i]); + } + envelope.push(energy / windowSize); + } + // Autocorrelation at common BPM lags (60-200 BPM) + let bestLag = 0, bestCorr = 0; + for (let lag = Math.floor(60 * sampleRate / 200 / windowSize); lag < Math.floor(60 * sampleRate / 60 / windowSize); lag++) { + let corr = 0; + for (let i = lag; i < numWindows; i++) { + corr += envelope[i] * envelope[i - lag]; + } + corr /= (numWindows - lag); + if (corr > bestCorr) { bestCorr = corr; bestLag = lag; } + } + if (bestLag === 0) return 0; + return Math.round(60 / (bestLag * windowSize / sampleRate)); + } + + _findBeats(data, sampleRate) { + const beats = []; + const windowSize = Math.floor(sampleRate * 0.05); // 50ms windows + const energies = []; + for (let i = 0; i < data.length - windowSize; i += windowSize) { + let e = 0; + for (let j = 0; j < windowSize; j++) e += data[i+j] * data[i+j]; + energies.push(e / windowSize); + } + const avg = energies.reduce((a,b) => a+b, 0) / energies.length; + const threshold = avg * 1.5; + for (let i = 1; i < energies.length - 1; i++) { + if (energies[i] > threshold && energies[i] > energies[i-1] && energies[i] > energies[i+1]) { + beats.push((i * windowSize / sampleRate).toFixed(1)); + if (beats.length >= 20) break; // cap + } + } + return beats.map(Number); + } + + _generateSpectrogram(data, sampleRate, duration) { + // Text-based spectrogram: 20 rows (freq bands) x 40 cols (time) + const rows = 20, cols = 40; + const colSize = Math.floor(data.length / cols); + let result = ''; + const chars = ' ░▒▓█'; + for (let row = rows - 1; row >= 0; row--) { + const freqLow = (row / rows) * (sampleRate / 2); + const freqHigh = ((row + 1) / rows) * (sampleRate / 2); + let line = (row / rows * sampleRate / 2 / 1000).toFixed(0) + 'kHz│'; + for (let col = 0; col < cols; col++) { + // Rough energy at this freq band for this time window + let energy = 0; + for (let i = col * colSize; i < (col+1) * colSize; i++) { + // Simple band-pass: skip samples not in freq range + energy += Math.abs(data[i] || 0); + } + const norm = Math.min(1, energy / colSize * 4); + const idx = Math.floor(norm * (chars.length - 1)); + line += chars[idx]; + } + result += line + '\n'; + } + return result; + } + + // ─── API Calls ─── + async _callVision(base64, prompt) { + const endpoint = this._getOllamaEndpoint(); + try { + const resp = await this._fetchWithTimeout(endpoint + '/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: this._visionModel, + messages: [{ role: 'user', content: prompt, images: [base64] }], + stream: false + }) + }, 60000); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + return { error: err.error || 'HTTP ' + resp.status }; + } + const data = await resp.json(); + return { text: data.message?.content || '(no response)', error: null }; + } catch (e) { + return { error: e.message }; + } + } + + async _callText(prompt) { + const endpoint = this._getOllamaEndpoint(); + try { + const resp = await this._fetchWithTimeout(endpoint + '/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: this._textModel, + messages: [{ role: 'user', content: prompt }], + stream: false + }) + }, 60000); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + return { error: err.error || 'HTTP ' + resp.status }; + } + const data = await resp.json(); + return { text: data.message?.content || '(no response)', error: null }; + } catch (e) { + return { error: e.message }; + } + } + + // ─── Utils ─── + _getOllamaEndpoint() { + return localStorage.getItem('webx11_ollama_endpoint') || (window.location.origin + '/ollama'); + } + + _fetchWithTimeout(url, options, timeout = 30000) { + return new Promise((resolve, reject) => { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeout); + fetch(url, { ...options, signal: controller.signal }) + .then(resolve).catch(reject).finally(() => clearTimeout(timer)); + }); + } + + _isImageExt(ext) { return ['png','jpg','jpeg','webp','gif','svg','bmp'].includes(ext); } + _isAudioExt(ext) { return ['wav','mp3','ogg','webm','flac','aac','m4a','opus'].includes(ext); } + _isVideoExt(ext) { return ['mp4','webm','ogv','avi','mov','mkv'].includes(ext); } + + _isValidBase64(str) { + if (typeof str !== 'string' || str.length < 8) return false; + try { return btoa(atob(str.substring(0, 100))) === str.substring(0, 100); } catch (e) { return false; } + } + + _base64ToArrayBuffer(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes.buffer; + } + + _getMimeType(path) { + const ext = path.split('.').pop().toLowerCase(); + const types = { mp4: 'video/mp4', webm: 'video/webm', ogv: 'video/ogg', mov: 'video/quicktime', avi: 'video/x-msvideo', mkv: 'video/x-matroska' }; + return types[ext] || 'application/octet-stream'; + } +} + +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new MediaModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/media.js'); +}
\ No newline at end of file diff --git a/modules/ext/voice.js b/modules/ext/voice.js index de0caed..b243ace 100644 --- a/modules/ext/voice.js +++ b/modules/ext/voice.js @@ -27,6 +27,65 @@ class VoiceModule { } registerCommands() { this.commands['voice'] = (args) => this.handle(args); + this.commands['speak'] = (args) => this.speak(args); + } + + getHelpInfo() { + return { 'voice': 'voice [stop] — Start voice recording (Whisper STT), or type "voice stop" to cancel', 'speak': 'speak [text] — Text-to-speech. No args = toggle auto-speak for AI responses' }; + } + + speak(args) { + const text = args.join(' ').trim(); + if (!text) { + // If no text, check if speechSynthesis is available + if (typeof speechSynthesis === 'undefined') { + this.terminal.print('🔊 TTS not supported in this browser', 'error'); + return; + } + // Toggle auto-speak mode + this._autoSpeak = !this._autoSpeak; + this.terminal.print(this._autoSpeak ? '🔊 Auto-speak ON — AI responses will be spoken' : '🔊 Auto-speak OFF', 'info'); + return; + } + if (typeof speechSynthesis === 'undefined') { + this.terminal.print('🔊 TTS not supported in this browser', 'error'); + return; + } + speechSynthesis.cancel(); + const utter = new SpeechSynthesisUtterance(text); + utter.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0'); + utter.pitch = 1.0; + utter.volume = 1.0; + // Try to use a good English voice + const voices = speechSynthesis.getVoices(); + const enVoice = voices.find(v => v.lang.startsWith('en') && v.name.includes('Google')) || voices.find(v => v.lang.startsWith('en')); + if (enVoice) utter.voice = enVoice; + utter.onstart = () => this._updateButton(true); + utter.onend = () => this._updateButton(false); + utter.onerror = () => this._updateButton(false); + speechSynthesis.speak(utter); + this.terminal.print('🔊 Speaking: "' + text.substring(0, 80) + (text.length > 80 ? '...' : '') + '"', 'info'); + } + + speakText(text) { + // Called by AI assistant to auto-speak responses + if (!this._autoSpeak || typeof speechSynthesis === 'undefined') return; + speechSynthesis.cancel(); + // Strip markdown/code blocks for cleaner speech + const clean = text.replace(/```[\s\S]*?```/g, ' code block ').replace(/[*_#|`>]/g, '').replace(/\n+/g, '. ').trim(); + if (clean.length > 500) { + // Split long text into chunks + const chunks = clean.match(/.{1,500}/g); + chunks.forEach(c => { + const u = new SpeechSynthesisUtterance(c); + u.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0'); + speechSynthesis.speak(u); + }); + } else { + const u = new SpeechSynthesisUtterance(clean); + u.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0'); + speechSynthesis.speak(u); + } } handle(args) { const sub = args[0] && args[0].toLowerCase(); diff --git a/packages.json b/packages.json index dd1afbd..e072feb 100644 --- a/packages.json +++ b/packages.json @@ -203,7 +203,7 @@ "floating": { "name": "Floating UI", "version": "1.0.0", - "desc": "Remixable floating UI elements — buttons, collapsible menus, panels. Persisted in localStorage.", + "desc": "Remixable floating UI elements \u2014 buttons, collapsible menus, panels. Persisted in localStorage.", "url": "modules/ext/floating.js", "commands": [ "floating-add", @@ -229,7 +229,7 @@ "debug-panel": { "name": "Debug Panel", "version": "1.0.0", - "desc": "Toggleable debug console — intercepts console.log/error, fetch failures, JS errors. Copy All + per-line copy.", + "desc": "Toggleable debug console \u2014 intercepts console.log/error, fetch failures, JS errors. Copy All + per-line copy.", "url": "modules/ext/debug-panel.js", "commands": [ "debug" @@ -259,7 +259,7 @@ "neural-vfs-core": { "name": "Neural VFS (core)", "version": "1.0.0", - "desc": "Core semantic indexer. NOT a WPM package — built into newos4.tar.gz. Use run_command('nvfs-status') to inspect.", + "desc": "Core semantic indexer. NOT a WPM package \u2014 built into newos4.tar.gz. Use run_command('nvfs-status') to inspect.", "url": "modules/core/module-neural-vfs.js", "commands": [ "nvfs-index", @@ -269,6 +269,11 @@ ], "category": "core", "note": "Listed for WPM completeness. Auto-loaded by module-loader. Cannot be uninstalled." + }, + "media": { + "url": "modules/ext/media.js", + "version": "1.0.0", + "description": "Media analysis module \u2014 image/audio/video analysis via Ollama vision models" } } }
\ No newline at end of file diff --git a/review-newos4.html b/review-newos4.html index 465e733..b82981b 100644 --- a/review-newos4.html +++ b/review-newos4.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="version" content="4.5.55-newos4"> + <meta name="version" content="4.5.75-newos4"> <title>ChatStudio newOS4 - Review</title> <style> :root { @@ -165,35 +165,35 @@ <div class="version-header"> <span class="version-icon">🧩</span> - <span class="version-title">newOS4 v4.5.55 — Terminal OS</span> + <span class="version-title">newOS4 v4.5.75 — Terminal OS</span> <span class="version-badge" style="background:#a855f7;">Most Advanced</span> </div> <div class="meta-info"> - <span>v4.5.55</span> - <span>1,311 lines (main.html skeleton, 60KB)</span> + <span>v4.5.75</span> + <span>1,341 lines (main.html skeleton, 64KB)</span> <span>3 bundle scripts + 8 standalone ext modules</span> <span>12 AI-managed skills (17 in index)</span> - <span>~950KB total bundle size</span> + <span>~936KB total bundle size</span> </div> <div class="section"> <h2>Architecture</h2> <p style="color: var(--text-muted); margin-bottom: 1rem;"> - A full in-browser terminal OS with bundle-based loading. The main.html (60KB) is a lean skeleton — all functionality is bundled into 2 optimized scripts (core-bundle.js & critical-ext-bundle.js) plus module-loader.js, with additional standalone ext modules loaded on demand. + A full in-browser terminal OS with bundle-based loading. The main.html (64KB) is a lean skeleton — all functionality is bundled into 2 optimized scripts (core-bundle.js & critical-ext-bundle.js) plus module-loader.js, with additional standalone ext modules loaded on demand. </p> <div class="arch-grid"> <div class="arch-item"> <h4>🧠 Core Bundle</h4> - <p>core-bundle.js (784KB, 15,455 lines) — all 33 core module classes: VirtualFileSystem, ModuleManager, OllamaManager, SecretVault, WebX11Terminal, plus 27 module classes (system, fs, ollama, aiassist, searxng, config, smartctx, skills, chatstudio overlay, agent-registry, memory-graph, neural-vfs, leases, orchestrator, predictor, sandbox, world-model, and more).</p> + <p>core-bundle.js (828KB, 16,147 lines) — all 33 core module classes: VirtualFileSystem, ModuleManager, OllamaManager, SecretVault, WebX11Terminal, WebX11SecurityManager, plus 27 module classes (system, fs, ollama, aiassist, searxng, config, smartctx, skills, chatstudio overlay, agent-registry, memory-graph, neural-vfs, leases, orchestrator, predictor, sandbox, world-model, and more).</p> </div> <div class="arch-item"> <h4>🔧 Critical Ext Bundle</h4> - <p>critical-ext-bundle.js (104KB, 2,363 lines) — 12 bundled ext modules: alias, busybox, calc, console, cron, cryptogzip, diff, editor, filemgr, finder, shell, tutorial.</p> + <p>critical-ext-bundle.js (104KB, 2,368 lines) — 12 bundled ext modules: alias, busybox, calc, console, cron, cryptogzip, diff, editor, filemgr, finder, shell, tutorial.</p> </div> <div class="arch-item"> <h4>🧩 Standalone Ext Modules</h4> - <p>8 standalone files in ext/: floating.js, mindmap.js, skill-pack-core.js, theme.js, voice.js, webdav.js, wpm.js — loaded on demand by module-loader. Configurable via localStorage startup preferences.</p> + <p>8 standalone files in ext/: floating.js, mindmap.js, skill-pack-core.js, theme.js, voice.js, webdav.js, wpm.js, debug-panel.js — loaded on demand by module-loader. Configurable via localStorage startup preferences.</p> </div> <div class="arch-item"> <h4>📚 Skills System</h4> @@ -289,7 +289,7 @@ <h3>Cons</h3> <ul> <li>Ollama dependency — requires Ollama backend for AI features</li> - <li>Large bundle size (~950KB across all scripts)</li> + <li>Large bundle size (~936KB across all scripts)</li> <li>No streaming AI responses (request/response cycle only)</li> <li>No PDF support</li> <li>Single-language (English only)</li> @@ -304,7 +304,7 @@ <div class="section"> <h2>Verdict</h2> <p style="color: var(--text-muted); margin-bottom: 0.5rem;"> - <strong>newOS4 v4.5.55</strong> is the most advanced ChatStudio variant — an in-browser terminal OS with agentic AI, a virtual file system, and self-improving skills. It's designed for power users, developers, and AI enthusiasts who want maximum control and capability. The bundle-based architecture (core-bundle + critical-ext-bundle + standalone modules) keeps script loading efficient while maintaining flexibility. + <strong>newOS4 v4.5.75</strong> is the most advanced ChatStudio variant — an in-browser terminal OS with agentic AI, a virtual file system, and self-improving skills. It's designed for power users, developers, and AI enthusiasts who want maximum control and capability. The bundle-based architecture (core-bundle + critical-ext-bundle + standalone modules) keeps script loading efficient while maintaining flexibility. </p> <div class="rating">🧩🧩🧩🧩 — <strong>4/5</strong> — Most advanced, consistently improved</div> </div> @@ -358,6 +358,12 @@ <td style="text-align:center;padding:6px 8px;">✅ Virtual FS</td> </tr> <tr style="border-bottom:1px solid var(--border);"> + <td style="padding:6px 8px;">PDF Support</td> + <td style="text-align:center;padding:6px 8px;">✅ link</td><td style="text-align:center;padding:6px 8px;">❌</td> + <td style="text-align:center;padding:6px 8px;">❌</td><td style="text-align:center;padding:6px 8px;">❌</td> + <td style="text-align:center;padding:6px 8px;">❌</td> + </tr> + <tr style="border-bottom:1px solid var(--border);"> <td style="padding:6px 8px;">Theme Presets</td> <td style="text-align:center;padding:6px 8px;">✅</td><td style="text-align:center;padding:6px 8px;">✅</td> <td style="text-align:center;padding:6px 8px;">✅</td><td style="text-align:center;padding:6px 8px;">✅ Custom</td> diff --git a/skills/agent-orchestration.json b/skills/agent-orchestration.json index fccf563..7a0f613 100644 --- a/skills/agent-orchestration.json +++ b/skills/agent-orchestration.json @@ -8,7 +8,7 @@ "leases" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: agent-orchestration\ndescription: How to use the agent registry, leases, and orchestrator for multi-agent workflows\ntags: [agents, orchestration, delegation, leases]\ncategory: system\n---\n## Steps\n1. **Agent Registry:**\n - agent-list — list all registered agents (ID, type, capabilities, status)\n - agent-register <type> <capabilities> — register a new agent (capabilities: comma-separated like \"code,research\")\n - agent-unregister <id> — remove an agent\n - agent-status <id> — detailed status including uptime, task history\n\n2. **Leases (time-bound contracts):**\n - lease-create <agent-id> <ttl-seconds> — create a lease with watchdog timer\n - lease-list — show active leases with remaining TTL\n - lease-revoke <lease-id> — terminate a lease early\n - lease-renew <lease-id> <ttl-seconds> — extend a lease\n\n3. **Orchestrator (spawn, delegate, kill):**\n - orchestrator-spawn <type> <capabilities> — spawn a new agent automatically (registers + creates lease)\n - orchestrator-kill <agent-id> — kill an agent (revokes lease + unregisters)\n - orchestrate — list all active agents with their orchestrator state\n - orchestrator-purge — kill all idle/expired agents\n - orchestrator-status — full system status: active, idle, dead agents\n\n4. **AI tool integration:**\n - AI tools list_agents, delegate_task, spawn_agent are available in the AI system prompt\n - The AI can auto-spawn worker agents for sub-tasks\n - Each agent gets its own VFS workspace at /agents/<agent-id>/\n\n5. **When to use:**\n - User needs parallel task execution → orchestrator-spawn coder + researcher\n - Time-bound automation → lease-create with short TTL\n - System cleanup → orchestrator-purge to kill stale agents" }
\ No newline at end of file diff --git a/skills/agentic-loop-optimization.json b/skills/agentic-loop-optimization.json new file mode 100644 index 0000000..6bda45e --- /dev/null +++ b/skills/agentic-loop-optimization.json @@ -0,0 +1,15 @@ +{ + "name": "agentic-loop-optimization", + "description": "", + "tags": [ + "agentic", + "loop", + "orchestration", + "quality-control", + "recursive" + ], + "category": "dev", + "created": "2026-06-21T14:23:51.055Z", + "updated": "2026-06-21T14:23:51.055Z", + "content": "---\nname: agentic-loop-optimization\ndescription: Protocol for transitioning from linear execution to a recursive feedback loop (Plan -> Execute -> Observe -> Correct -> Finalize).\ntags: [agentic, loop, orchestration, quality-control, recursive]\n---\n\n# Agentic Loop Optimization Protocol\n\n## 1. The Recursive Execution Cycle\nInstead of a single-shot tool call, every complex task must follow this loop:\n- **Plan**: Define the goal and the sequence of tools needed.\n- **Execute**: Perform the action.\n- **Observe**: Immediately verify the result using a complementary tool (e.g., if `write_file` is used, use `read_file`).\n- **Correct**: If the observation reveals a gap or error, spawn a sub-task to fix it *before* reporting back to the user.\n- **Finalize**: Confirm the end-state matches the original plan.\n\n## 2. The Triad Structure (High-Complexity Tasks)\nFor tasks involving architectural changes or multi-step logic, deploy three distinct roles:\n- **Architect (Lead)**: Sets the strategy, manages the VFS structure, and performs final QA.\n- **Worker (Executor)**: Handles the heavy lifting (coding, file manipulation, API calls).\n- **Critic (Verifier)**: Attempts to \"break\" the Worker's output. Specifically looks for silent failures, edge cases, and path errors.\n\n## 3. Memory-Augmented Planning\nBefore initiating the loop:\n- **Query Memory Graph**: Search for keywords related to the task to find \"Similar Past Failures.\"\n- **Constraint Injection**: Add these past failures as explicit \"Negative Constraints\" (e.g., \"Do NOT use absolute paths for /_mods/ as this caused a failure on 2026-06-21\").\n\n## 4. Verification Matrix\n| Action | Verification Tool | Success Criteria |\n| :--- | :--- | :--- |\n| `write_file` | `read_file` | Content matches intended string. |\n| `edit_module` | `test_module` | Module loads without throwing errors. |\n| `spawn_agent` | `list_agents` | Agent is registered and status is 'active'. |\n| `run_command` | `session_terminal` | Output contains expected success keyword. |\n" +}
\ No newline at end of file diff --git a/skills/backup-and-restore.json b/skills/backup-and-restore.json index 6d0e1cd..7d280a7 100644 --- a/skills/backup-and-restore.json +++ b/skills/backup-and-restore.json @@ -9,7 +9,7 @@ "persistence" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: backup-and-restore\ndescription: How to backup, restore, and export WebX11 data\ntags: [backup, restore, export, import, persistence]\ncategory: system\n---\n## Steps\n1. **Backup VFS:**\n - vfs-export — downloads the entire virtual filesystem as a dated JSON file\n - backup — creates a full backup including VFS, modules, config (saves to _backups/)\n\n2. **WebDAV cloud backup:**\n - webdav-backup — push VFS snapshot to WebDAV server (auto-archive)\n - webdav-backup-list — list remote WebDAV backups\n - webdav-restore <file> — restore VFS from remote WebDAV snapshot\n - webdav-push <local> <remote> — push file to WebDAV space\n - webdav-pull <remote> <local> — pull file from WebDAV space\n - webdav-ls [path] — list remote WebDAV directory contents\n - webdav-status — check WebDAV connection and space usage\n - webdav-explorer — GUI file browser for WebDAV\n - webdav-browser — simpler file listing for WebDAV\n - webdav-config — configure WebDAV endpoint and credentials\n\n3. **Export/Import personal data:**\n - my-export — prints all my-set data as formatted JSON\n - my-import <json> — bulk-import personal data from JSON string\n\n4. **Export/Import modules:**\n - export-mods — exports module registry as JSON\n - import-mods <json> — imports modules from JSON backup\n\n5. **Restore:**\n - restore <json> — restores terminal state from a backup JSON\n - Session logs are saved to /memory/ automatically on page unload\n\n6. **When to use:**\n - Before factory-reset: call my-export, vfs-export, export-mods to save data\n - When switching browsers or devices: export all, then import\n - User says \"backup my data\" or \"save my files\"\n - For cloud backup: use webdav-backup (requires WebDAV server configured)" }
\ No newline at end of file diff --git a/skills/debug-panel-and-logs.json b/skills/debug-panel-and-logs.json index b27ad49..e7fa6ed 100644 --- a/skills/debug-panel-and-logs.json +++ b/skills/debug-panel-and-logs.json @@ -8,7 +8,7 @@ "troubleshooting" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: debug-panel-and-logs\ndescription: How to use the debug panel, session logging, and terminal diagnostics\ntags: [debug, logging, diagnostics, troubleshooting]\ncategory: system\n---\n## Steps\n1. **Toggle debug panel:**\n - debug — opens real-time debug panel showing last AI tool call, response time, and context\n - Debug panel includes Copy, Export, and Write to VFS buttons\n - Export: saves tool call log as timestamped JSON to downloads\n - Write to VFS: appends debug data to /debug.log (dedup by turn ID)\n\n2. **Session logging:**\n - logs [n] [LEVEL] — view last n log entries (optionally filtered by INFO/WARN/ERROR)\n - logrotate — rotate current.log to dated archive (auto-cleans old logs beyond 10)\n - Logs stored in /logs/ directory: current.log + dated archives\n - Health check runs automatically after complex tasks\n\n3. **Diagnostic commands:**\n - health-check — full audit: VFS size, modules, logs, trash, skills\n - check — quick system health including module requirement status\n - mem — real-time memory usage (also in header)\n - status — network, files, modules, Ollama connectivity\n\n4. **System journals:**\n - debug.log — appended by debug panel VFS button (dedup by turn ID)\n - current.log — system activity log with rotation\n - Auto-save: terminal state auto-saves every 60s\n\n5. **When to use:**\n - AI is behaving unexpectedly → debug to see tool calls\n - User reports errors → logs 50 ERROR to see error entries\n - Memory concerns → mem to check, health-check for full audit" }
\ No newline at end of file diff --git a/skills/floating-ui.json b/skills/floating-ui.json new file mode 100644 index 0000000..cd8dd1d --- /dev/null +++ b/skills/floating-ui.json @@ -0,0 +1,15 @@ +{ + "name": "floating-ui", + "description": "How to create, manage, and remove floating UI elements — buttons, collapsible menus, and status panels that persist across page reloads", + "tags": [ + "floating", + "ui", + "buttons", + "menu", + "gui" + ], + "category": "ui", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", + "content": "---\nname: floating-ui\ndescription: How to create, manage, and remove floating UI elements — buttons, collapsible menus, and status panels that persist across page reloads\ntags: [floating, ui, buttons, menu, gui]\ncategory: ui\n---\n## Commands\n\n1. **Create a floating button:**\n - floating-add btn <id> <label> <command>\n - Example: floating-add btn files \"📁 Files\" ls\n - Button appears at bottom-right, persists across reloads\n\n2. **Create a collapsible menu:**\n - floating-add menu <id> <label> btn:SubLabel|command btn:Sub2|cmd2 ...\n - Example: floating-add menu tools \"🔧 Tools\" btn:Help|help btn:Clear|clear\n - Click header to expand/collapse. State persists.\n\n3. **Create a text panel:**\n - floating-add panel <id> <title> <content>\n - Example: floating-add panel status \"📊 Status\" \"CPU: 2%\"\n\n4. **Manage elements:**\n - floating-list — show all elements with IDs and positions\n - floating-remove <id> — delete by ID\n - floating-toggle <id> — show/hide without deleting\n - floating-pos <id> x|auto y|auto — reposition or reset to auto\n - floating-clear — remove everything\n\n5. **Persistence:**\n - All elements auto-save to localStorage (webx11_floating_elements)\n - Survive page reload, browser restart\n - Run floating-clear to start fresh\n\n## When to use\n- User wants quick-access buttons instead of typing commands\n- User says \"add a button for X\", \"make a shortcut\", \"floating menu\"\n- Creating a dashboard with status panels and action buttons" +}
\ No newline at end of file diff --git a/skills/memory-graph-update.json b/skills/memory-graph-update.json new file mode 100644 index 0000000..b6e1da2 --- /dev/null +++ b/skills/memory-graph-update.json @@ -0,0 +1,13 @@ +{ + "name": "memory-graph-update", + "description": "", + "tags": [ + "system", + "memory", + "graph" + ], + "category": "system", + "created": "2026-06-21T14:19:29.741Z", + "updated": "2026-06-21T14:19:29.741Z", + "content": "---\nname: memory-graph-update\ndescription: Manual process for updating the memory graph since direct commands may be restricted.\n---\n# Memory Graph Update Process\nWhen updating the memory graph, use the `memgraph` utility or the dedicated memory-graph module methods if available. If command-line access is blocked, use the AI context to summarize state changes." +}
\ No newline at end of file diff --git a/skills/mindmap-user-guide.json b/skills/mindmap-user-guide.json new file mode 100644 index 0000000..cfc0885 --- /dev/null +++ b/skills/mindmap-user-guide.json @@ -0,0 +1,14 @@ +{ + "name": "mindmap-user-guide", + "description": "How to create, view, and manage brainstorming mindmaps using the mindmap terminal module", + "tags": [ + "mindmap", + "brainstorming", + "organization", + "planning" + ], + "category": "productivity", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", + "content": "---\nname: mindmap-user-guide\ndescription: How to create, view, and manage brainstorming mindmaps using the mindmap terminal module\ntags: [mindmap, brainstorming, organization, planning]\ncategory: productivity\n---\n## Steps\n1. **Create a new mindmap:**\n - mindmap new \"<topic>\" -t tag1,tag2 — creates a root node\n - Tags are optional (e.g. planning, research, dev)\n - Root gets auto-assigned a color\n\n2. **Add child nodes:**\n - mindmap add <parent-id> \"<idea>\" -t tag — add a child\n - Get the parent-id from mindmap show\n - IDs are 4-char random strings (e.g. a3f8, x7k2)\n\n3. **View the mindmap:**\n - mindmap show — full tree with Unicode box-drawing\n - mindmap show <id> — subtree starting at that node\n\n4. **Edit nodes:**\n - mindmap rename <id> \"<new text>\" — change text\n - mindmap color <id> <color> — set color (red/green/yellow/blue/cyan/magenta/white)\n - mindmap tag <id> <tag> — add a tag\n\n5. **Persistence:**\n - mindmap save <name> — save to localStorage\n - mindmap load <name> — restore\n - mindmap list — show saved mindmaps\n - mindmap delete <name> — remove saved\n\n6. **Export:**\n - mindmap export [id] — Markdown bullet list output\n\n## When to use\n- User wants to brainstorm, organize ideas, plan features\n- User says \"map out\", \"diagram\", \"brainstorm\", \"organize thoughts\"\n- Visualizing project structure or task decomposition" +}
\ No newline at end of file diff --git a/skills/mindmap-workflow.json b/skills/mindmap-workflow.json index e8d5795..b71173c 100644 --- a/skills/mindmap-workflow.json +++ b/skills/mindmap-workflow.json @@ -8,7 +8,7 @@ "organization" ], "category": "productivity", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: mindmap-workflow\ndescription: How to create, view, edit, and manage brainstorming mindmaps\ntags: [mindmap, brainstorming, planning, organization]\ncategory: productivity\n---\n## Steps\n1. **Create a new mindmap:**\n - mindmap new \"<topic>\" -t tag1,tag2 — creates a root node\n - Tags are optional (e.g. planning, research, dev)\n - Root gets auto-assigned a color from 7-color palette\n\n2. **Add child nodes:**\n - mindmap add <parent-id> \"<idea>\" -t tag — add a child\n - Get the parent-id from mindmap show\n - IDs are 4-char random strings (e.g. a3f8, x7k2)\n\n3. **View:**\n - mindmap show — full tree with Unicode box-drawing\n - mindmap show <id> — subtree starting at that node\n\n4. **Edit nodes:**\n - mindmap rename <id> \"<new text>\" — change text\n - mindmap color <id> <color> — set color (red/green/yellow/blue/cyan/magenta/white)\n - mindmap tag <id> <tag> — add a tag\n\n5. **Persistence:**\n - mindmap save <name> — save to localStorage\n - mindmap load <name> — restore\n - mindmap list — show saved mindmaps\n - mindmap delete <name> — remove saved\n\n6. **Export:**\n - mindmap export [id] — Markdown bullet list output, auto-copies to clipboard\n\n7. **When to use:**\n - Map out project structure, brainstorm features, organize ideas\n - Break down complex tasks into subtrees\n - User says \"brainstorm\", \"diagram\", \"organize thoughts\", \"plan\"" }
\ No newline at end of file diff --git a/skills/module-audit-and-test.json b/skills/module-audit-and-test.json new file mode 100644 index 0000000..5dfddcc --- /dev/null +++ b/skills/module-audit-and-test.json @@ -0,0 +1,9 @@ +{ + "name": "module-audit-and-test", + "description": "", + "tags": [], + "category": "dev", + "created": "2026-06-18T00:41:25.711Z", + "updated": "2026-06-18T00:41:25.711Z", + "content": "---\nname: module-audit-and-test\ndescription: Procedural guide for auditing module source code and performing rigorous testing to ensure stability, security, and performance.\ntags: [modules, audit, testing, quality-assurance, dev]\n---\n\n# Module Audit & Testing Workflow\n\nThis skill provides a standardized approach to auditing existing modules and testing new or modified code within the WebX11 ecosystem.\n\n## 1. Audit Phase (Static Analysis)\nBefore running code, perform a static review of the module source.\n\n### Steps to Audit:\n1. **Retrieve Source**: Use `view_module(name=\"ModuleName\")` to examine the code.\n2. **Security Check**: \n - Look for unsafe command executions or uncontrolled input handling.\n - Ensure destructive operations (e.g., `write_file`, `delete_file`) have appropriate guards or confirmations.\n3. **Logic Review**:\n - Check for edge cases in parameters.\n - Verify that the module handles errors gracefully (try-catch blocks).\n - Ensure the module adheres to the expected return formats (JSON, strings, etc.).\n4. **Dependency Check**: Verify that all required helper functions or external APIs are correctly called and exist.\n\n## 2. Testing Phase (Dynamic Analysis)\nOnce the audit is complete, validate the module's behavior in a sandbox environment.\n\n### Testing Strategy:\n- **Happy Path**: Test the most common use case with valid inputs.\n- **Boundary Testing**: Test with empty strings, extremely large inputs, or null values.\n- **Error Path**: Intentionally provide invalid inputs to see if the module returns a helpful error instead of crashing.\n- **Integration Testing**: If the module interacts with other system components (VFS, Web, etc.), verify the end-to-end flow.\n\n### Execution Loop:\n1. **Edit/Modify**: Use `edit_module(name=\"ModuleName\")` to apply fixes.\n2. **Isolated Test**: Use `test_module(name=\"ModuleName\")`. This runs the module in a temporary context without affecting the global state.\n3. **Live Reload**: If tests pass, use `reload_module(name=\"ModuleName\")` to push the changes to the active system.\n\n## 3. Validation Checklist\n- [ ] Does the module perform its intended function?\n- [ ] Does it handle errors without crashing the terminal?\n- [ ] Are there any obvious security vulnerabilities?\n- [ ] Is the code clean and maintainable?\n- [ ] Does it perform efficiently without causing system lag?\n\n## Summary of Commands\n| Action | Command |\n| :--- | :--- |\n| View Source | `view_module(name=\"...\")` |\n| Modify Code | `edit_module(name=\"...\")` |\n| Test Sandbox | `test_module(name=\"...\")` |\n| Deploy | `reload_module(name=\"...\")` |\n| Check Status | `get_module_status()` |\n" +}
\ No newline at end of file diff --git a/skills/module-management.json b/skills/module-management.json index b4e953e..5bcadc1 100644 --- a/skills/module-management.json +++ b/skills/module-management.json @@ -8,7 +8,7 @@ "self-improvement" ], "category": "dev", - "created": "2026-06-08T16:31:55.507Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.884Z", + "updated": "2026-06-18T00:39:10.884Z", "content": "---\nname: module-management\ndescription: How to view, edit, test, reload modules and manage startup configuration\ntags: [modules, editing, code, self-improvement]\ncategory: dev\n---\n## Steps\n1. **View module source:**\n - view_module(name) — fetches original source or reads from _mods/ if edited\n - Supports all loaded modules: SystemModule, FileSystemModule, SearXNGModule, etc.\n - Or use modules command for GUI module manager\n\n2. **Edit a module:**\n - edit_module(name) — copies source to _mods/<name>.js and opens code editor\n - Editor shows the full source in a textarea\n - Click \"Save & Test\" to save and auto-load the edited version\n - Checkpoint before editing: checkpoint mod-edit-<name>\n\n3. **Test and reload:**\n - test_module(name) — loads edited module from _mods/ for live testing\n - reload_module(name) — discards edits and reloads from original URL\n - list_modified_modules — shows all modules with pending edits\n\n4. **Check for updates:**\n - check_updates — HEAD-requests all loaded module URLs to verify reachability\n\n5. **Manage startup:**\n - startup-modules — opens the Startup Modules UI to select which modules load\n - load-local <path> — load a module from VFS or URL without editing\n - loadmod <url> — alternative: load module from URL\n - removemod <name> — remove a loaded module\n\n6. **Agent modules (Phase 2):**\n - module-agent-registry.js — Agent registry with agent-list, agent-register, agent-unregister, agent-status\n - module-leases.js — Time-bound contracts with 5s watchdog, auto-expiry\n - module-orchestrator.js — Spawn, delegate, kill, purge with heartbeat monitoring\n\n7. **When to use:**\n - User reports a bug in a module → view_module, edit_module, test_module\n - User asks \"can you add X feature?\" → same workflow\n - Always save edits before testing. If test fails, the original module stays loaded." }
\ No newline at end of file diff --git a/skills/navigating-the-terminal.json b/skills/navigating-the-terminal.json index bde1f4c..d7711b5 100644 --- a/skills/navigating-the-terminal.json +++ b/skills/navigating-the-terminal.json @@ -8,7 +8,7 @@ "filesystem" ], "category": "system", - "created": "2026-06-08T16:31:55.507Z", - "updated": "2026-06-08T19:49:09.058Z", + "created": "2026-06-18T00:39:10.884Z", + "updated": "2026-06-18T00:39:10.884Z", "content": "---\nname: navigating-the-terminal\ndescription: How to explore the WebX11 terminal, find commands, check status, and navigate the filesystem\ntags: [terminal, navigation, help, filesystem]\ncategory: system\n---\n## Steps\n1. **Get an overview of available commands:**\n - Type \"help\" for a full command list sorted by category\n - Type \"status\" to see filesystem state, loaded modules, Ollama status\n - For a specific command, use help <command>\n - Type \"commands\" for a compact list with module source info\n - Type \"check\" for system health including module requirements\n\n2. **Explore the filesystem:**\n - Use ls, ll, tree to browse directories\n - Use cd <dir> to navigate, pwd to show current path\n - Use cat or edit to view file contents\n - Use find or grep to search files\n - Use du to see disk usage\n\n3. **Understand system state:**\n - mem — memory usage (auto-shown in header on boot)\n - status — network, files, modules, Ollama\n - modules — module manager GUI\n - health-check — VFS size, modules, logs, trash, skills, tool failures\n - checkpoint-list — VFS checkpoint snapshots\n\n4. **Agent / Orchestration state:**\n - agent-list — registered agents (type, capabilities, status)\n - orchestrate — list active orchestrated agents\n - lease-list — show active leases with TTL\n\n5. **When user asks a vague question:**\n - First run status and help to understand the current state\n - Then ask clarifying questions based on what you found" }
\ No newline at end of file diff --git a/skills/role-archetype-framework.json b/skills/role-archetype-framework.json new file mode 100644 index 0000000..a721139 --- /dev/null +++ b/skills/role-archetype-framework.json @@ -0,0 +1,13 @@ +{ + "name": "role-archetype-framework", + "description": "", + "tags": [ + "agents", + "system", + "orchestration" + ], + "category": "system", + "created": "2026-06-22T08:29:46.161Z", + "updated": "2026-06-22T08:29:46.161Z", + "content": "---\nname: role-archetype-framework\ndescription: Documentation and operational guidelines for the Specialized Archetype Framework (Architect, Operator, Auditor, Synthesizer).\ntags: agents, system-design, orchestration\n---\n\n# Specialized Archetype Framework\n\n## Overview\nThe system has transitioned from generic \"developer\" agents to a specialized persona-based framework. This optimizes task delegation by aligning specific capabilities with distinct roles.\n\n## The Archetypes\n\n### 🏛️ Architect\n- **Focus:** High-level design, VFS planning, and strategic structuring.\n- **Ideal Tasks:** Mapping project architectures, designing folder hierarchies, and conceptualizing logic flows.\n- **Primary Tools:** `create_mindmap`, `list_files`, `make_directory`.\n\n### ⚙️ Operator\n- **Focus:** Execution, tool orchestration, and external interaction.\n- **Ideal Tasks:** Web scraping, executing terminal commands, installing WPM packages, and API interactions.\n- **Primary Tools:** `run_command`, `web_fetch`, `wpm_install`.\n\n### 🛡️ Auditor\n- **Focus:** Quality Assurance, Security, and Critical Analysis.\n- **Ideal Tasks:** Code reviews, system health audits, identifying vulnerabilities, and verifying logic correctness.\n- **Primary Tools:** `run_command` (read-only), `read_file`, `get_status`.\n\n### ✍️ Synthesizer\n- **Focus:** Knowledge Management, Documentation, and Reporting.\n- **Ideal Tasks:** Converting session logs into skills, summarizing complex data, and updating user profiles.\n- **Primary Tools:** `create_skill`, `write_file`, `user_set`.\n\n## Workflow Integration\nWhen delegating tasks, use the following logic:\n1. **Plan** with the `architect`.\n2. **Execute** with the `operator`.\n3. **Verify** with the `auditor`.\n4. **Document** with the `synthesizer`.\n" +}
\ No newline at end of file diff --git a/skills/security-and-safety.json b/skills/security-and-safety.json index e66e5dc..d00c494 100644 --- a/skills/security-and-safety.json +++ b/skills/security-and-safety.json @@ -8,7 +8,7 @@ "permissions" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: security-and-safety\ndescription: How to use WebX11 security levels, command blocking, and safety features\ntags: [security, safety, blocking, permissions]\ncategory: system\n---\n## Steps\n1. **Security levels:**\n - security-level <0-3> — set security level:\n 0 = Permissive (no blocks, no confirms — anything runs)\n 1 = Standard (unknown cmds → AI route, destructive → confirm, system → block)\n 2 = Paranoid (unknown cmds blocked entirely, destructive blocked)\n 3 = Audit (everything allowed, everything logged)\n - Default: level 1\n - security — show current level, blocked count, and stats\n\n2. **Command control:**\n - cmd-allow <cmd> — whitelist a command from security checks\n - cmd-deny <cmd> — blacklist/block a command entirely\n - whitelist built-in: help, clear, ls, cat, pwd, whoami, df, free, top, ps, echo, date, env, history\n\n3. **What gets blocked at each level:**\n - Unknown commands: Level 1 → AI routed, Level 2+ → blocked\n - Destructive commands (delete, write, save, format, etc.): Level 1 → confirmation, Level 2+ → blocked\n - System commands (reboot, shutdown, format, kill): Level 1+ → blocked\n - Suspicious flags (--execute, --eval, --shell): Level 1+ → blocked\n\n4. **Safety checklist:**\n - Check security: security shows your current level\n - Before running risky commands: create a checkpoint first\n - For temporary relax: security-level 0, then restore to 1\n - For total lockdown before leaving: security-level 2\n\n5. **When to use:**\n - User wants to lock down the terminal → security-level 2\n - User wants to allow a blocked command → cmd-allow <cmd>\n - Debugging strange behavior → verify security-level (user may have set it higher)" }
\ No newline at end of file diff --git a/skills/skills-system-guide.json b/skills/skills-system-guide.json index ea6d079..45dbdf2 100644 --- a/skills/skills-system-guide.json +++ b/skills/skills-system-guide.json @@ -7,7 +7,7 @@ "meta" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: skills-system-guide\ndescription: How to use the skills system: load, create, patch, list, and audit skills for persistent procedural memory\ntags: [skills, system, meta]\ncategory: system\n---\n## Steps\n1. **Always load skills at task start:**\n - At the START of every task, type or call \"skill-view <name>\" or \"skill-list\"\n - Example: skill-view \"module-management\" — returns matching skill content\n - Example: skill-list system — browse skills by category\n\n2. **Create skills after complex tasks:**\n - After any task with 3+ tool calls, call \"skill-create\"\n - Required fields: name (kebab-case), description, content (markdown with YAML frontmatter)\n - Optional: tags (comma-separated), category (e.g. research, system, dev)\n - Skills persist in /skills/ as .md files with /skills/index.json for fast lookup\n - Skill pack auto-seeds 8 core skills on first boot\n\n3. **Patch skills when they're outdated:**\n - If a stored procedure has wrong steps, call \"skill-patch <name>\"\n - This replaces the entire skill content and updates the index\n - Do NOT create a duplicate — always patch the existing skill\n\n4. **Auto-created error skills:**\n - If the same tool fails 3+ times, a workaround skill is created automatically\n - Named tool-<name>-workaround (e.g. tool-write_file-workaround)\n\n5. **Audit and maintenance:**\n - skill-list <category?> — browse available skills, optionally filter\n - skill-export <name> — download a skill as markdown\n - skill-import <json> — bulk import skills\n - skill-sync — re-index skills from /skills/ directory\n - Skills are audited every 20 messages for staleness (empty content, >30 days old)\n\n6. **YAML frontmatter format:**\n - Each skill file must start with: ---\\nname: <name>\\ndescription: <text>\\ntags: [...]\\ncategory: <cat>\\n---\\n\n - Steps follow under ## Steps heading\n - Use numbered lists for sequential steps" }
\ No newline at end of file diff --git a/skills/suggestion-engine.json b/skills/suggestion-engine.json new file mode 100644 index 0000000..e3a06f8 --- /dev/null +++ b/skills/suggestion-engine.json @@ -0,0 +1,9 @@ +{ + "name": "suggestion-engine", + "description": "", + "tags": [], + "category": "system", + "created": "2026-06-18T01:22:19.666Z", + "updated": "2026-06-18T01:22:19.666Z", + "content": "---\nname: suggestion-engine\ndescription: A systematic process for recording, analyzing, and implementing user suggestions for system improvement.\ntags: [optimization, feedback, roadmap, dev]\n---\n\n# Suggestion Engine Workflow\n\nThis skill provides a structured framework to ensure user ideas are not lost and are implemented with architectural integrity.\n\n## 📋 1. Recording Process\nEvery suggestion must be logged into the `suggestion_log.json` with the following schema:\n- **ID**: Unique incremental ID.\n- **Timestamp**: ISO date.\n- **Category**: (UI/UX, Performance, Security, Feature, Logic).\n- **Description**: Detailed user request.\n- **Status**: `proposed` | `analyzing` | `implementing` | `completed` | `rejected`.\n- **Priority**: `low` | `medium` | `high` | `critical`.\n\n## ⚙️ 2. Analysis Framework\nBefore implementing a suggestion, the AI must perform a \"Impact Analysis\":\n1. **Dependency Check**: Which modules are affected?\n2. **Security Audit**: Does this introduce a new attack vector?\n3. **Performance Cost**: Will this increase latency or memory bloat?\n4. **UI Consistency**: Does this align with the existing design language?\n\n## 🚀 3. Implementation Cycle\n1. **Propose**: Define the technical approach.\n2. **Draft**: Modify the module code (using `edit_module`).\n3. **Test**: Run `test_module` to verify correctness.\n4. **Verify**: User confirmation of the improvement.\n5. **Archive**: Update status to `completed` and document the change.\n\n## 🛠️ Commands Reference\n- `suggestion-log` -> List all current suggestions.\n- `suggestion-add [text]` -> Append new idea to log.\n- `suggestion-promote [id]` -> Move from proposed to analyzing.\n" +}
\ No newline at end of file diff --git a/skills/system-maintenance.json b/skills/system-maintenance.json index 255dd0d..e7a9a19 100644 --- a/skills/system-maintenance.json +++ b/skills/system-maintenance.json @@ -9,7 +9,7 @@ "config" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.884Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: system-maintenance\ndescription: How to reset, troubleshoot, and maintain the WebX11 terminal\ntags: [system, maintenance, reset, troubleshoot, config]\ncategory: system\n---\n## Steps\n1. **Factory reset (wipes everything):**\n - factory-reset --confirm — clears all webx11_ localStorage keys, resets VFS, reloads\n - Use only when the system is in an unrecoverable state\n\n2. **Soft VFS reset:**\n - reset-vfs --confirm — clears only the filesystem, preserves settings/profile/modules\n\n3. **Config management:**\n - config-list — show all config keys and values\n - config-set <key> <value> — set a config value\n - config-delete <key> — remove a config key\n - config-export — dump all config as JSON\n - config-reset — restore defaults (use before factory-reset if you want to save data)\n\n4. **Security level:**\n - security-level <0-3> — set security level (0=Permissive, 1=Standard, 2=Paranoid, 3=Audit)\n - security — show current security level, blocked count, and stats\n - cmd-allow <cmd> — whitelist a command from security checks\n - cmd-deny <cmd> — blacklist a command\n\n5. **Health checks (run automatically after complex tasks):**\n - Check module loading status: loaded vs total\n - Check for pending module edits\n - Check memory usage (high if >100MB)\n - Check skill count (0 skills after 2+ tool calls → suggest create_skill)\n - health-check — full audit: VFS size, modules, logs, trash, skills, tool failures\n\n6. **Font size and display:**\n - font-size <8-32> — set terminal font size, persists across sessions\n - setfont — alias for font-size\n\n7. **Checkpoints:**\n - checkpoint [name] — snapshot current VFS state\n - checkpoint-list — list all checkpoints\n - rollback <name> — restore from checkpoint\n\n8. **When to use:**\n - System is slow or modules fail to load → check check_updates, mem\n - User says \"reset everything\" → factory-reset --confirm\n - User says \"clear my files but keep settings\" → reset-vfs --confirm\n - User reports security concerns → security-level or cmd-allow/cmd-deny" }
\ No newline at end of file diff --git a/skills/web-search-and-fetch.json b/skills/web-search-and-fetch.json index b2cbcb1..532f5df 100644 --- a/skills/web-search-and-fetch.json +++ b/skills/web-search-and-fetch.json @@ -9,7 +9,7 @@ "api" ], "category": "research", - "created": "2026-06-08T16:31:55.507Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.884Z", + "updated": "2026-06-18T00:39:10.884Z", "content": "---\nname: web-search-and-fetch\ndescription: How to search the web and fetch URL content using the WebX11 proxy API\ntags: [search, fetch, web, proxy, api]\ncategory: research\n---\n## Steps\n1. **Search the web:**\n - Use search <query> or web_search(query) — uses /api/search which queries SearXNG + DDG fallback\n - Results include: title, url, content snippet, source engine\n - Always available (same-origin, no CORS issues)\n - 5-minute server-side cache for repeated queries\n - Alternative: use ddg or duck <query> for DuckDuckGo direct, or s <query> as alias\n\n2. **Fetch URL content:**\n - Use fetch <url> or web_fetch(url) — uses /api/extract which strips HTML, extracts clean text\n - Returns: title, content (up to 4000 chars), word_count, extraction method\n - SSRF protection blocks internal network addresses\n - 15-minute server-side cache\n\n3. **Search flags (via terminal search command):**\n - search <query> --summarize — adds AI-generated overview via Ollama\n - search <query> --fetch — fetches content snippets for top 3 results\n\n4. **When to use:**\n - User asks about current events, facts, research → use search <query>\n - User says \"read this article\" or provides a URL → use fetch <url>\n - For in-depth analysis: search first, then fetch top results" }
\ No newline at end of file diff --git a/skills/webdav-operations.json b/skills/webdav-operations.json index 9c775f5..a4800c6 100644 --- a/skills/webdav-operations.json +++ b/skills/webdav-operations.json @@ -9,7 +9,7 @@ "storage" ], "category": "system", - "created": "2026-06-08T16:31:55.508Z", - "updated": "2026-06-08T19:49:09.059Z", + "created": "2026-06-18T00:39:10.885Z", + "updated": "2026-06-18T00:39:10.885Z", "content": "---\nname: webdav-operations\ndescription: How to use WebDAV cloud storage for backup, file sync, and remote file access\ntags: [webdav, cloud, backup, sync, storage]\ncategory: system\n---\n## Steps\n1. **Configuration:**\n - webdav-config — configure WebDAV endpoint URL and credentials\n - Connection auto-tested on config; webdav-status shows connection health\n - Uses per-user token stored in .token file on the server side\n\n2. **File operations:**\n - webdav-ls [path] — list remote directory contents\n - webdav-push <local-path> <remote-path> — upload file to WebDAV\n - webdav-pull <remote-path> [local-path] — download file from WebDAV\n - webdav-explorer — GUI file browser for WebDAV\n - webdav-browser — CLI file browser for WebDAV\n\n3. **Backup & Restore:**\n - webdav-backup — push full VFS snapshot to WebDAV (creates /backups/newos4-<date>.json)\n - webdav-backup-list — list all remote backup snapshots\n - webdav-restore <filename> — restore VFS from a remote backup snapshot\n\n4. **Status & Diagnostics:**\n - webdav-status — connection status, last sync, space information\n - If uploads fail: check webdav-config, ensure .token file is valid\n - WebDAV runs on the same server (nginx with WebDAV module), no external dependency\n\n5. **When to use:**\n - Before factory-reset: webdav-backup to store VFS remotely\n - Cross-browser: webdav-push/pull to move files between instances\n - User says \"cloud backup\" or \"remote save\" → webdav-backup" }
\ No newline at end of file |